refactor: remove dead code and consolidate repeated paths (#100650)

* refactor(search): reuse provider setup finalizer

* refactor(android): remove unused helpers

* refactor(apps): remove unused Swift declarations

* refactor(diagnostics): reuse private reply delivery

* refactor(memory): reuse raw short-term store writer

* refactor(logbook): reuse batch row mapping

* refactor(scripts): centralize oxlint format policy

* refactor(memory): share qmd cache context hashing

* refactor(doctor): share auth issue classification

* chore(i18n): refresh native source inventory
This commit is contained in:
Vincent Koc 2026-07-06 02:24:37 -07:00 committed by GitHub
parent b4b1984fe7
commit c9abc75884
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 227 additions and 414 deletions

View file

@ -1843,7 +1843,7 @@
},
{
"kind": "ui-named-argument",
"line": 1133,
"line": 1122,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Setup Gateway",
"surface": "android",
@ -1851,7 +1851,7 @@
},
{
"kind": "ui-named-argument",
"line": 1139,
"line": 1128,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Start your Gateway.",
"surface": "android",
@ -1859,7 +1859,7 @@
},
{
"kind": "ui-named-argument",
"line": 1145,
"line": 1134,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Generate a QR code.",
"surface": "android",
@ -1867,7 +1867,7 @@
},
{
"kind": "ui-named-argument",
"line": 1166,
"line": 1155,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Choose from gallery",
"surface": "android",
@ -1875,7 +1875,7 @@
},
{
"kind": "ui-named-argument",
"line": 1220,
"line": 1209,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "QR code not accepted",
"surface": "android",
@ -1883,7 +1883,7 @@
},
{
"kind": "ui-named-argument",
"line": 1235,
"line": 1224,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Choose another image",
"surface": "android",
@ -1891,7 +1891,7 @@
},
{
"kind": "ui-named-argument",
"line": 1288,
"line": 1277,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Scan QR code",
"surface": "android",
@ -1899,7 +1899,7 @@
},
{
"kind": "ui-named-argument",
"line": 1290,
"line": 1279,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Open the camera and frame the code from openclaw qr.",
"surface": "android",
@ -1907,7 +1907,7 @@
},
{
"kind": "ui-named-argument",
"line": 1324,
"line": 1313,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Align the QR code inside the square.",
"surface": "android",
@ -1915,7 +1915,7 @@
},
{
"kind": "ui-named-argument",
"line": 1342,
"line": 1331,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Camera access is needed to scan the setup QR.",
"surface": "android",
@ -1923,7 +1923,7 @@
},
{
"kind": "ui-named-argument",
"line": 1347,
"line": 1336,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Allow camera",
"surface": "android",
@ -1931,7 +1931,7 @@
},
{
"kind": "ui-named-argument",
"line": 1368,
"line": 1357,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Close scanner",
"surface": "android",
@ -1939,7 +1939,7 @@
},
{
"kind": "ui-named-argument",
"line": 1530,
"line": 1519,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Enter setup code",
"surface": "android",
@ -1947,7 +1947,7 @@
},
{
"kind": "ui-named-argument",
"line": 1531,
"line": 1520,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Setup code",
"surface": "android",
@ -1955,7 +1955,7 @@
},
{
"kind": "ui-named-argument",
"line": 1535,
"line": 1524,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Paste setup code",
"surface": "android",
@ -1963,7 +1963,7 @@
},
{
"kind": "ui-named-argument",
"line": 1539,
"line": 1528,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Setup code was not accepted",
"surface": "android",
@ -1971,7 +1971,7 @@
},
{
"kind": "ui-named-argument",
"line": 1543,
"line": 1532,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Use setup code",
"surface": "android",
@ -1979,7 +1979,7 @@
},
{
"kind": "ui-named-argument",
"line": 1574,
"line": 1563,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Manual setup",
"surface": "android",
@ -1987,7 +1987,7 @@
},
{
"kind": "ui-named-argument",
"line": 1577,
"line": 1566,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Gateway URL",
"surface": "android",
@ -1995,7 +1995,7 @@
},
{
"kind": "ui-named-argument",
"line": 1579,
"line": 1568,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Host",
"surface": "android",
@ -2003,7 +2003,7 @@
},
{
"kind": "ui-named-argument",
"line": 1580,
"line": 1569,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Port",
"surface": "android",
@ -2011,7 +2011,7 @@
},
{
"kind": "ui-named-argument",
"line": 1583,
"line": 1572,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Use the Gateway computer's LAN address or secure remote hostname.",
"surface": "android",
@ -2019,7 +2019,7 @@
},
{
"kind": "ui-named-argument",
"line": 1590,
"line": 1579,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Token",
"surface": "android",
@ -2027,7 +2027,7 @@
},
{
"kind": "ui-named-argument",
"line": 1591,
"line": 1580,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Paste token",
"surface": "android",
@ -2035,7 +2035,7 @@
},
{
"kind": "ui-named-argument",
"line": 1593,
"line": 1582,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Paste a shared Gateway token or operator-issued token.",
"surface": "android",
@ -2043,7 +2043,7 @@
},
{
"kind": "ui-named-argument",
"line": 1600,
"line": 1589,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Password",
"surface": "android",
@ -2051,7 +2051,7 @@
},
{
"kind": "ui-named-argument",
"line": 1601,
"line": 1590,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Password optional",
"surface": "android",
@ -2059,7 +2059,7 @@
},
{
"kind": "ui-named-argument",
"line": 1605,
"line": 1594,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Connection type",
"surface": "android",
@ -2067,7 +2067,7 @@
},
{
"kind": "ui-named-argument",
"line": 1607,
"line": 1596,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Local network",
"surface": "android",
@ -2075,7 +2075,7 @@
},
{
"kind": "ui-named-argument",
"line": 1608,
"line": 1597,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Secure remote",
"surface": "android",
@ -2083,7 +2083,7 @@
},
{
"kind": "ui-named-argument",
"line": 1611,
"line": 1600,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Local works for LAN or emulator hosts. Secure remote is for wss:// or Tailscale Serve/Funnel.",
"surface": "android",
@ -2091,7 +2091,7 @@
},
{
"kind": "ui-named-argument",
"line": 1619,
"line": 1608,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Could not test connection",
"surface": "android",
@ -2099,7 +2099,7 @@
},
{
"kind": "ui-named-argument",
"line": 1624,
"line": 1613,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Test connection",
"surface": "android",
@ -2107,7 +2107,7 @@
},
{
"kind": "ui-call",
"line": 1802,
"line": 1791,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "View details",
"surface": "android",
@ -2115,7 +2115,7 @@
},
{
"kind": "ui-call",
"line": 1835,
"line": 1824,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Connection details",
"surface": "android",
@ -2123,7 +2123,7 @@
},
{
"kind": "ui-call",
"line": 1847,
"line": 1836,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Copy",
"surface": "android",
@ -2131,7 +2131,7 @@
},
{
"kind": "ui-call",
"line": 1852,
"line": 1841,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Close",
"surface": "android",
@ -2139,7 +2139,7 @@
},
{
"kind": "ui-toast",
"line": 1864,
"line": 1853,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Details copied",
"surface": "android",
@ -2147,7 +2147,7 @@
},
{
"kind": "ui-named-argument",
"line": 1949,
"line": 1938,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Approve node access",
"surface": "android",
@ -2155,7 +2155,7 @@
},
{
"kind": "ui-named-argument",
"line": 1956,
"line": 1945,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Gateway pairing is complete. Approve this phone as a node so OpenClaw can use the device capabilities you enable.",
"surface": "android",
@ -2163,7 +2163,7 @@
},
{
"kind": "ui-named-argument",
"line": 1964,
"line": 1953,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "On the Gateway computer, run:",
"surface": "android",
@ -2171,7 +2171,7 @@
},
{
"kind": "ui-named-argument",
"line": 1973,
"line": 1962,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Use the requestId from the pending command in the approve command.",
"surface": "android",
@ -2179,7 +2179,7 @@
},
{
"kind": "ui-named-argument",
"line": 1984,
"line": 1973,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "I have approved",
"surface": "android",
@ -2187,7 +2187,7 @@
},
{
"kind": "ui-named-argument",
"line": 1997,
"line": 1986,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Still waiting for approval",
"surface": "android",
@ -2195,7 +2195,7 @@
},
{
"kind": "ui-named-argument",
"line": 2000,
"line": 1989,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Run the approve command on the Gateway computer, then check again.",
"surface": "android",
@ -2203,7 +2203,7 @@
},
{
"kind": "ui-named-argument",
"line": 2007,
"line": 1996,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "OK",
"surface": "android",
@ -2211,7 +2211,7 @@
},
{
"kind": "ui-named-argument",
"line": 2157,
"line": 2146,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Copy approval command",
"surface": "android",
@ -2219,7 +2219,7 @@
},
{
"kind": "ui-named-argument",
"line": 2183,
"line": 2172,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Only enable access you are comfortable letting OpenClaw use while this phone is connected. You can change these later in Android Settings.",
"surface": "android",
@ -2227,7 +2227,7 @@
},
{
"kind": "ui-named-argument",
"line": 2195,
"line": 2184,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Continue",
"surface": "android",
@ -2235,7 +2235,7 @@
},
{
"kind": "ui-named-argument",
"line": 2222,
"line": 2211,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Back",
"surface": "android",
@ -2243,7 +2243,7 @@
},
{
"kind": "ui-named-argument",
"line": 2269,
"line": 2258,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Permissions",
"surface": "android",
@ -2251,7 +2251,7 @@
},
{
"kind": "conditional-branch",
"line": 2594,
"line": 2583,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "The code may have expired or been generated for another Gateway.",
"surface": "android",
@ -2259,7 +2259,7 @@
},
{
"kind": "conditional-branch",
"line": 2598,
"line": 2587,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Gateway password is required. Enter it again or edit this connection.",
"surface": "android",
@ -2267,7 +2267,7 @@
},
{
"kind": "conditional-branch",
"line": 2599,
"line": 2588,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Gateway password is invalid. Re-enter it or reset this gateway connection.",
"surface": "android",
@ -2275,7 +2275,7 @@
},
{
"kind": "conditional-branch",
"line": 2600,
"line": 2589,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Gateway token is required. Enter it again or edit this connection.",
"surface": "android",
@ -2283,7 +2283,7 @@
},
{
"kind": "conditional-branch",
"line": 2602,
"line": 2591,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Gateway requires this device identity. Re-authenticate or reset this gateway connection.",
"surface": "android",
@ -2291,7 +2291,7 @@
},
{
"kind": "conditional-branch",
"line": 2606,
"line": 2595,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Saved authentication is invalid. Re-authenticate or reset this gateway connection.",
"surface": "android",
@ -2299,7 +2299,7 @@
},
{
"kind": "conditional-branch",
"line": 2607,
"line": 2596,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Gateway authentication is not configured. Edit this connection and try again.",
"surface": "android",
@ -2307,7 +2307,7 @@
},
{
"kind": "conditional-branch",
"line": 2608,
"line": 2597,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Gateway authentication needs review. Check gateway settings, then retry.",
"surface": "android",
@ -2315,7 +2315,7 @@
},
{
"kind": "conditional-branch",
"line": 2625,
"line": 2614,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "$summary $it",
"surface": "android",
@ -2323,7 +2323,7 @@
},
{
"kind": "ui-toast",
"line": 2768,
"line": 2757,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Approval command copied",
"surface": "android",
@ -2331,7 +2331,7 @@
},
{
"kind": "conditional-branch",
"line": 2830,
"line": 2819,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Granted",
"surface": "android",
@ -2339,7 +2339,7 @@
},
{
"kind": "conditional-branch",
"line": 2830,
"line": 2819,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
"source": "Not granted",
"surface": "android",

View file

@ -3502,11 +3502,6 @@ class NodeRuntime private constructor(
private fun skillMissingCount(missing: JsonObject?): Int = listOf("bins", "env", "config", "os").sumOf { key -> (missing?.get(key) as? JsonArray)?.size ?: 0 }
private fun parseGatewayNodes(nodes: JsonArray?): List<GatewayNodeSummary> =
nodes
?.mapNotNull(::parseGatewayNodeSummary)
.orEmpty()
private fun parsePendingDevices(devices: JsonArray?): List<GatewayPendingDeviceSummary> =
devices
?.mapNotNull { item ->

View file

@ -1096,17 +1096,6 @@ private fun GatewayPrerequisiteRow(
}
}
@Composable
private fun GatewayCommand(
label: String,
command: String,
) {
Column(verticalArrangement = Arrangement.spacedBy(3.dp)) {
Text(text = label, style = ClawTheme.type.caption, color = ClawTheme.colors.textMuted)
Text(text = command, style = ClawTheme.type.mono.copy(fontSize = 16.sp, lineHeight = 22.sp), color = ClawTheme.colors.text)
}
}
@Composable
private fun SetupCodeInstructionsScreen(
scannerActive: Boolean,

View file

@ -176,22 +176,6 @@ private struct AboutLinkRow: View {
}
}
private struct AboutMetaRow: View {
let label: String
let value: String
var body: some View {
HStack {
Text(self.label)
.foregroundStyle(.secondary)
Spacer()
Text(self.value)
.font(.caption.monospaced())
.foregroundStyle(.primary)
}
}
}
#if DEBUG
struct AboutSettings_Previews: PreviewProvider {
private static let updater = DisabledUpdaterController()

View file

@ -236,20 +236,6 @@ private struct SelectedConnectAuth {
let suppressedDeviceTokenRetry: Bool
}
private enum GatewayConnectErrorCodes {
static let authTokenMismatch = GatewayConnectAuthDetailCode.authTokenMismatch.rawValue
static let authDeviceTokenMismatch = GatewayConnectAuthDetailCode.authDeviceTokenMismatch.rawValue
static let authTokenMissing = GatewayConnectAuthDetailCode.authTokenMissing.rawValue
static let authTokenNotConfigured = GatewayConnectAuthDetailCode.authTokenNotConfigured.rawValue
static let authPasswordMissing = GatewayConnectAuthDetailCode.authPasswordMissing.rawValue
static let authPasswordMismatch = GatewayConnectAuthDetailCode.authPasswordMismatch.rawValue
static let authPasswordNotConfigured = GatewayConnectAuthDetailCode.authPasswordNotConfigured.rawValue
static let authRateLimited = GatewayConnectAuthDetailCode.authRateLimited.rawValue
static let pairingRequired = GatewayConnectAuthDetailCode.pairingRequired.rawValue
static let controlUiDeviceIdentityRequired = GatewayConnectAuthDetailCode.controlUiDeviceIdentityRequired.rawValue
static let deviceIdentityRequired = GatewayConnectAuthDetailCode.deviceIdentityRequired.rawValue
}
public actor GatewayChannelActor {
nonisolated static func resolveRequestTimeoutMs(_ timeoutMs: Double?, defaultMs: Double) -> Double? {
timeoutMs == 0 ? nil : (timeoutMs ?? defaultMs)

View file

@ -29,17 +29,3 @@ public actor TranscriptsStore {
public func latest() -> [String] { entries }
}
extension String {
private func appendLine(to url: URL) throws {
let data = (self + "\n").data(using: .utf8) ?? Data()
if FileManager.default.fileExists(atPath: url.path) {
let handle = try FileHandle(forWritingTo: url)
try handle.seekToEnd()
try handle.write(contentsOf: data)
try handle.close()
} else {
try data.write(to: url)
}
}
}

View file

@ -96,6 +96,17 @@ type FrameRow = {
idle: number;
};
type BatchRow = {
id: number;
day: string;
start_ms: number;
end_ms: number;
status: LogbookBatchStatus;
error: string | null;
frame_count: number;
model: string | null;
};
type CardRow = {
id: number;
day: string;
@ -125,6 +136,19 @@ function toFrame(row: FrameRow): LogbookFrame {
};
}
function toBatch(row: BatchRow): LogbookBatch {
return {
id: row.id,
day: row.day,
startMs: row.start_ms,
endMs: row.end_ms,
status: row.status,
error: row.error ?? undefined,
frameCount: row.frame_count,
model: row.model ?? undefined,
};
}
function parseDistractions(raw: string): LogbookDistraction[] {
try {
const parsed = JSON.parse(raw) as unknown;
@ -312,31 +336,8 @@ export class LogbookStore {
`SELECT id, day, start_ms, end_ms, status, error, frame_count, model
FROM batches ORDER BY id DESC LIMIT 1`,
)
.get() as
| {
id: number;
day: string;
start_ms: number;
end_ms: number;
status: LogbookBatchStatus;
error: string | null;
frame_count: number;
model: string | null;
}
| undefined;
if (!row) {
return null;
}
return {
id: row.id,
day: row.day,
startMs: row.start_ms,
endMs: row.end_ms,
status: row.status,
error: row.error ?? undefined,
frameCount: row.frame_count,
model: row.model ?? undefined,
};
.get() as BatchRow | undefined;
return row ? toBatch(row) : null;
}
/** Requeues batches stuck in `running` after a crash so frames are not orphaned. */
@ -362,31 +363,8 @@ export class LogbookStore {
`SELECT id, day, start_ms, end_ms, status, error, frame_count, model
FROM batches WHERE status = 'pending' ORDER BY start_ms ASC LIMIT 1`,
)
.get() as
| {
id: number;
day: string;
start_ms: number;
end_ms: number;
status: LogbookBatchStatus;
error: string | null;
frame_count: number;
model: string | null;
}
| undefined;
if (!row) {
return null;
}
return {
id: row.id,
day: row.day,
startMs: row.start_ms,
endMs: row.end_ms,
status: row.status,
error: row.error ?? undefined,
frameCount: row.frame_count,
model: row.model ?? undefined,
};
.get() as BatchRow | undefined;
return row ? toBatch(row) : null;
}
batchFrames(batchId: number): LogbookFrame[] {

View file

@ -113,10 +113,10 @@ function buildCollectionConfigHash(collections: readonly QmdRuntimeManagedCollec
return hashText(normalized);
}
function buildCollectionValidationCacheContextInput(
params: QmdRuntimeCollectionValidationCacheContext,
): string {
return JSON.stringify({
function buildRuntimeCacheContextRecord(
params: QmdRuntimeCacheContextBase & { sources: readonly string[] },
) {
return {
agentId: normalizeText(params.agentId),
commandHash: hashText(normalizeText(params.qmdCommand)),
environmentHash: normalizeText(params.qmdEnvironmentHash ?? ""),
@ -124,6 +124,14 @@ function buildCollectionValidationCacheContextInput(
qmdVersion: normalizeText(params.qmdVersion ?? ""),
searchMode: params.searchMode,
sourceSet: sortedUnique(params.sources),
};
}
function buildCollectionValidationCacheContextInput(
params: QmdRuntimeCollectionValidationCacheContext,
): string {
return JSON.stringify({
...buildRuntimeCacheContextRecord(params),
collectionConfigHash: buildCollectionConfigHash(params.collections),
});
}
@ -131,39 +139,19 @@ function buildCollectionValidationCacheContextInput(
function buildMultiCollectionProbeCacheContextInput(
params: QmdRuntimeMultiCollectionProbeCacheContext,
): string {
return JSON.stringify({
agentId: normalizeText(params.agentId),
commandHash: hashText(normalizeText(params.qmdCommand)),
environmentHash: normalizeText(params.qmdEnvironmentHash ?? ""),
indexPathHash: normalizePathIdentity(params.qmdIndexPath),
qmdVersion: normalizeText(params.qmdVersion ?? ""),
searchMode: params.searchMode,
sourceSet: sortedUnique(params.sources),
});
}
function buildCollectionValidationCacheHash(
params: QmdRuntimeCollectionValidationCacheContext,
): string {
return hashText(buildCollectionValidationCacheContextInput(params));
}
function buildMultiCollectionProbeCacheHash(
params: QmdRuntimeMultiCollectionProbeCacheContext,
): string {
return hashText(buildMultiCollectionProbeCacheContextInput(params));
return JSON.stringify(buildRuntimeCacheContextRecord(params));
}
export function buildQmdCollectionValidationCacheContextHash(
params: QmdRuntimeCollectionValidationCacheContext,
): string {
return buildCollectionValidationCacheHash(params);
return hashText(buildCollectionValidationCacheContextInput(params));
}
export function buildQmdMultiCollectionProbeCacheContextHash(
params: QmdRuntimeMultiCollectionProbeCacheContext,
): string {
return buildMultiCollectionProbeCacheHash(params);
return hashText(buildMultiCollectionProbeCacheContextInput(params));
}
function collectionValidationStore(): PluginStateKeyedStore<QmdRuntimeCacheCollectionValidationEntry> {
@ -183,14 +171,14 @@ function multiCollectionProbeStore(): PluginStateKeyedStore<QmdRuntimeCacheMulti
function collectionValidationEntryKey(params: QmdRuntimeCollectionValidationCacheContext): string {
return memoryCoreWorkspaceEntryKey(
params.workspaceDir,
`qmd-runtime-cache.collection-validation:${buildCollectionValidationCacheHash(params)}`,
`qmd-runtime-cache.collection-validation:${buildQmdCollectionValidationCacheContextHash(params)}`,
);
}
function multiCollectionProbeEntryKey(params: QmdRuntimeMultiCollectionProbeCacheContext): string {
return memoryCoreWorkspaceEntryKey(
params.workspaceDir,
`qmd-runtime-cache.multi-collection-probe:${buildMultiCollectionProbeCacheHash(params)}`,
`qmd-runtime-cache.multi-collection-probe:${buildQmdMultiCollectionProbeCacheContextHash(params)}`,
);
}
@ -317,7 +305,7 @@ export async function readQmdCollectionValidationCache(
try {
const store = collectionValidationStore();
const key = collectionValidationEntryKey(params);
const expectedKeyHash = buildCollectionValidationCacheHash(params);
const expectedKeyHash = buildQmdCollectionValidationCacheContextHash(params);
const raw = await store.lookup(key);
if (!raw) {
return { state: "miss" };
@ -335,7 +323,7 @@ export async function writeQmdCollectionValidationCache(
): Promise<boolean> {
try {
const key = collectionValidationEntryKey(params);
const keyHash = buildCollectionValidationCacheHash(params);
const keyHash = buildQmdCollectionValidationCacheContextHash(params);
const collectionConfigHash = buildCollectionConfigHash(params.collections);
const createdAtMs = Math.max(0, Math.floor(nowMs));
const ttlMs = QMD_RUNTIME_CACHE_COLLECTION_VALIDATION_TTL_MS;
@ -379,7 +367,7 @@ export async function readQmdMultiCollectionProbeCache(
try {
const store = multiCollectionProbeStore();
const key = multiCollectionProbeEntryKey(params);
const expectedKeyHash = buildMultiCollectionProbeCacheHash(params);
const expectedKeyHash = buildQmdMultiCollectionProbeCacheContextHash(params);
const raw = await store.lookup(key);
if (!raw) {
return { state: "miss" };
@ -398,7 +386,7 @@ export async function writeQmdMultiCollectionProbeCache(
): Promise<boolean> {
try {
const key = multiCollectionProbeEntryKey(params);
const keyHash = buildMultiCollectionProbeCacheHash(params);
const keyHash = buildQmdMultiCollectionProbeCacheContextHash(params);
const createdAtMs = Math.max(0, Math.floor(nowMs));
const ttlMs = QMD_RUNTIME_CACHE_MULTI_COLLECTION_PROBE_TTL_MS;
const store = multiCollectionProbeStore();

View file

@ -2849,55 +2849,43 @@ export async function removeGroundedShortTermCandidates(params: {
return { removed, storePath };
}
async function writeRawShortTermStoreForTest(
workspaceDir: string,
raw: unknown,
namespace: string,
metaKey: "recall" | "phase",
): Promise<void> {
const record = asRecord(raw);
const entries = asRecord(record?.entries);
await Promise.all([
writeMemoryCoreWorkspaceEntries({
namespace,
workspaceDir,
entries: entries ? Object.entries(entries).map(([key, value]) => ({ key, value })) : [],
}),
writeMemoryCoreWorkspaceEntry({
namespace: SHORT_TERM_META_NAMESPACE,
workspaceDir,
key: metaKey,
value: {
updatedAt:
typeof record?.updatedAt === "string" && record.updatedAt.trim()
? record.updatedAt
: new Date().toISOString(),
},
}),
]);
}
export const testing = {
parseLockOwnerPid,
isProcessLikelyAlive,
readRecallStore: readStore,
readPhaseSignalStore,
writeRawRecallStore: async (workspaceDir: string, raw: unknown) => {
const record = asRecord(raw);
const entries = asRecord(record?.entries);
await Promise.all([
writeMemoryCoreWorkspaceEntries({
namespace: SHORT_TERM_RECALL_NAMESPACE,
workspaceDir,
entries: entries ? Object.entries(entries).map(([key, value]) => ({ key, value })) : [],
}),
writeMemoryCoreWorkspaceEntry({
namespace: SHORT_TERM_META_NAMESPACE,
workspaceDir,
key: "recall",
value: {
updatedAt:
typeof record?.updatedAt === "string" && record.updatedAt.trim()
? record.updatedAt
: new Date().toISOString(),
},
}),
]);
},
writeRawPhaseSignalStore: async (workspaceDir: string, raw: unknown) => {
const record = asRecord(raw);
const entries = asRecord(record?.entries);
await Promise.all([
writeMemoryCoreWorkspaceEntries({
namespace: SHORT_TERM_PHASE_SIGNAL_NAMESPACE,
workspaceDir,
entries: entries ? Object.entries(entries).map(([key, value]) => ({ key, value })) : [],
}),
writeMemoryCoreWorkspaceEntry({
namespace: SHORT_TERM_META_NAMESPACE,
workspaceDir,
key: "phase",
value: {
updatedAt:
typeof record?.updatedAt === "string" && record.updatedAt.trim()
? record.updatedAt
: new Date().toISOString(),
},
}),
]);
},
writeRawRecallStore: (workspaceDir: string, raw: unknown) =>
writeRawShortTermStoreForTest(workspaceDir, raw, SHORT_TERM_RECALL_NAMESPACE, "recall"),
writeRawPhaseSignalStore: (workspaceDir: string, raw: unknown) =>
writeRawShortTermStoreForTest(workspaceDir, raw, SHORT_TERM_PHASE_SIGNAL_NAMESPACE, "phase"),
writeShortTermLock: async (workspaceDir: string, entry: ShortTermLockEntry) => {
await openMemoryCoreStateStore<ShortTermLockEntry>({
namespace: SHORT_TERM_LOCK_NAMESPACE,

View file

@ -42,6 +42,17 @@ function hasFlag(args, name) {
return args.some((arg) => arg === name || arg.startsWith(`${name}=`));
}
function hasOxlintFormatArg(args) {
return args.some(
(arg) =>
arg === "--format" ||
arg.startsWith("--format=") ||
arg === "-f" ||
arg.startsWith("-f=") ||
(arg.startsWith("-f") && arg.length > 2),
);
}
/** Apply local tsgo defaults for declaration skipping, caching, throttling, and profiling. */
export function applyLocalTsgoPolicy(args, env, hostResources) {
const nextEnv = { ...env };
@ -96,6 +107,9 @@ export function applyLocalOxlintPolicy(args, env, hostResources) {
) {
insertBeforeSeparator(nextArgs, "--report-unused-disable-directives-severity", "error");
}
if (nextEnv.GITHUB_ACTIONS === "true" && !hasOxlintFormatArg(nextArgs)) {
insertBeforeSeparator(nextArgs, "--format", "stylish");
}
if (shouldThrottleLocalHeavyChecks(nextEnv, hostResources)) {
if (!hasFlag(nextArgs, "--threads")) {

View file

@ -9,23 +9,6 @@ import {
} from "./local-heavy-check-runtime.mjs";
import { createManagedCommandInvocation } from "./managed-child-process.mjs";
function hasOxlintFormatArg(args) {
return args.some(
(arg) =>
arg === "--format" ||
arg.startsWith("--format=") ||
arg === "-f" ||
arg.startsWith("-f=") ||
(arg.startsWith("-f") && arg.length > 2),
);
}
function addOxlintFormatArg(args, value) {
const separatorIndex = args.indexOf("--");
const insertIndex = separatorIndex === -1 ? args.length : separatorIndex;
args.splice(insertIndex, 0, "--format", value);
}
/**
* Runs focused extension oxlint with a temp config and local heavy-check lock.
*/
@ -61,9 +44,6 @@ export function runExtensionOxlint(params) {
const baseArgs = ["-c", tempConfigPath, ...process.argv.slice(2), ...extensionFiles];
const { args: finalArgs, env } = applyLocalOxlintPolicy(baseArgs, process.env);
if (env.GITHUB_ACTIONS === "true" && !hasOxlintFormatArg(finalArgs)) {
addOxlintFormatArg(finalArgs, "stylish");
}
const oxlint = createManagedCommandInvocation({
args: finalArgs,
bin: oxlintPath,

View file

@ -40,23 +40,6 @@ const OXLINT_VALUE_FLAGS = new Set([
"--warn",
]);
function hasOxlintFormatArg(args) {
return args.some(
(arg) =>
arg === "--format" ||
arg.startsWith("--format=") ||
arg === "-f" ||
arg.startsWith("-f=") ||
(arg.startsWith("-f") && arg.length > 2),
);
}
function addOxlintFormatArg(args, value) {
const separatorIndex = args.indexOf("--");
const insertIndex = separatorIndex === -1 ? args.length : separatorIndex;
args.splice(insertIndex, 0, "--format", value);
}
/**
* Returns whether oxlint args need package-boundary declaration artifacts first.
*/
@ -229,9 +212,6 @@ export async function main(argv = process.argv.slice(2), runtimeEnv = process.en
);
const sparseTargets = filterSparseMissingOxlintTargets(policyArgs);
const finalArgs = sparseTargets.args;
if (env.GITHUB_ACTIONS === "true" && !hasOxlintFormatArg(finalArgs)) {
addOxlintFormatArg(finalArgs, "stylish");
}
if (sparseTargets.skippedTargets.length > 0) {
console.error(
`[oxlint] sparse checkout is missing tracked target(s); skipping ${sparseTargets.skippedTargets.join(", ")}`,

View file

@ -116,14 +116,7 @@ async function handleDiagnosticsCommandWithDeps(
}
if (params.isGroup) {
const targets = await deps.resolvePrivateDiagnosticsTargets(params);
if (targets.length === 0) {
return {
shouldContinue: false,
reply: { text: DIAGNOSTICS_PRIVATE_ROUTE_UNAVAILABLE },
};
}
const privateTarget = targets[0];
const privateTarget = (await deps.resolvePrivateDiagnosticsTargets(params))[0];
if (!privateTarget) {
return {
shouldContinue: false,
@ -140,17 +133,7 @@ async function handleDiagnosticsCommandWithDeps(
reply: { text: DIAGNOSTICS_PRIVATE_ROUTE_ACK },
};
}
const delivered = await deps.deliverPrivateDiagnosticsReply({
commandParams: params,
targets: [privateTarget],
reply: privateReply,
});
return {
shouldContinue: false,
reply: {
text: delivered ? DIAGNOSTICS_PRIVATE_ROUTE_ACK : DIAGNOSTICS_PRIVATE_ROUTE_UNAVAILABLE,
},
};
return await deliverGroupDiagnosticsReplyPrivately(deps, params, privateReply, privateTarget);
}
const reply = await buildDiagnosticsReply(deps, params, args);
@ -183,16 +166,10 @@ async function deliverGroupDiagnosticsReplyPrivately(
deps: DiagnosticsCommandDeps,
params: HandleCommandsParams,
reply: ReplyPayload,
privateTarget?: PrivateCommandRouteTarget,
) {
const targets = await deps.resolvePrivateDiagnosticsTargets(params);
if (targets.length === 0) {
return {
shouldContinue: false,
reply: { text: DIAGNOSTICS_PRIVATE_ROUTE_UNAVAILABLE },
};
}
const privateTarget = targets[0];
if (!privateTarget) {
const target = privateTarget ?? (await deps.resolvePrivateDiagnosticsTargets(params))[0];
if (!target) {
return {
shouldContinue: false,
reply: { text: DIAGNOSTICS_PRIVATE_ROUTE_UNAVAILABLE },
@ -200,7 +177,7 @@ async function deliverGroupDiagnosticsReplyPrivately(
}
const delivered = await deps.deliverPrivateDiagnosticsReply({
commandParams: params,
targets: [privateTarget],
targets: [target],
reply,
});
return {

View file

@ -11,6 +11,7 @@ import {
buildAuthHealthSummary,
DEFAULT_OAUTH_WARN_MS,
formatRemainingShort,
type AuthHealthSummary,
} from "../agents/auth-health.js";
import {
type AuthCredentialReasonCode,
@ -337,6 +338,16 @@ function authProfileCooldownToHealthFinding(params: {
};
}
function isAuthProfileHealthIssue(profile: AuthHealthSummary["profiles"][number]): boolean {
if (profile.type === "api_key") {
return profile.status === "missing";
}
return (
(profile.type === "oauth" || profile.type === "token") &&
(profile.status === "expired" || profile.status === "expiring" || profile.status === "missing")
);
}
async function collectAuthProfileHealthFindingsForTarget(params: {
cfg: OpenClawConfig;
allowKeychainPrompt: boolean;
@ -382,17 +393,7 @@ async function collectAuthProfileHealthFindingsForTarget(params: {
warnAfterMs: DEFAULT_OAUTH_WARN_MS,
allowKeychainPrompt: params.allowKeychainPrompt,
});
const issues = summary.profiles.filter((profile) => {
if (profile.type === "api_key") {
return profile.status === "missing";
}
return (
(profile.type === "oauth" || profile.type === "token") &&
(profile.status === "expired" ||
profile.status === "expiring" ||
profile.status === "missing")
);
});
const issues = summary.profiles.filter(isAuthProfileHealthIssue);
for (const issue of issues) {
const authIssue: AuthIssue = {
profileId: issue.profileId,
@ -495,18 +496,7 @@ async function noteAuthProfileHealthForTarget(params: {
allowKeychainPrompt: params.allowKeychainPrompt,
});
const findIssues = () =>
summary.profiles.filter((profile) => {
if (profile.type === "api_key") {
return profile.status === "missing";
}
return (
(profile.type === "oauth" || profile.type === "token") &&
(profile.status === "expired" ||
profile.status === "expiring" ||
profile.status === "missing")
);
});
const findIssues = () => summary.profiles.filter(isAuthProfileHealthIssue);
let issues = findIssues();
if (issues.length === 0) {

View file

@ -559,6 +559,15 @@ export async function runSearchSetupFlow(
if (!entry) {
return config;
}
const finalizeSelection = (nextConfig: OpenClawConfig) =>
finalizeSearchProviderSetup({
originalConfig: config,
nextConfig,
entry,
runtime,
prompter,
opts,
});
const credentialLabel = resolveSearchProviderCredentialLabel(entry);
const existingKey = resolveExistingKey(config, choice);
const keyConfigured = hasExistingKey(config, choice);
@ -582,14 +591,7 @@ export async function runSearchSetupFlow(
const result = existingKey
? applySearchKey(config, choice, existingKey)
: applySearchProviderSelection(config, choice);
return await finalizeSearchProviderSetup({
originalConfig: config,
nextConfig: result,
entry,
runtime,
prompter,
opts,
});
return await finalizeSelection(result);
}
if (!needsCredential) {
@ -601,14 +603,7 @@ export async function runSearchSetupFlow(
].join("\n"),
"Web search",
);
return await finalizeSearchProviderSetup({
originalConfig: config,
nextConfig: applySearchProviderSelection(config, choice),
entry,
runtime,
prompter,
opts,
});
return await finalizeSelection(applySearchProviderSelection(config, choice));
}
if (entry.credentialNote) {
@ -625,14 +620,7 @@ export async function runSearchSetupFlow(
].join("\n"),
"Web search",
);
return await finalizeSearchProviderSetup({
originalConfig: config,
nextConfig: applySearchProviderSelection(config, choice),
entry,
runtime,
prompter,
opts,
});
return await finalizeSelection(applySearchProviderSelection(config, choice));
}
if (providerAuthProfileAvailable && authProviderId) {
@ -645,27 +633,13 @@ export async function runSearchSetupFlow(
].join("\n"),
"Web search",
);
return await finalizeSearchProviderSetup({
originalConfig: config,
nextConfig: applySearchProviderSelection(config, choice),
entry,
runtime,
prompter,
opts,
});
return await finalizeSelection(applySearchProviderSelection(config, choice));
}
const useSecretRefMode = opts?.secretInputMode === "ref"; // pragma: allowlist secret
if (useSecretRefMode) {
if (keyConfigured) {
return await finalizeSearchProviderSetup({
originalConfig: config,
nextConfig: applySearchProviderSelection(config, choice),
entry,
runtime,
prompter,
opts,
});
return await finalizeSelection(applySearchProviderSelection(config, choice));
}
const ref = buildSearchEnvRef(config, choice);
await prompter.note(
@ -677,14 +651,7 @@ export async function runSearchSetupFlow(
].join("\n"),
"Web search",
);
return await finalizeSearchProviderSetup({
originalConfig: config,
nextConfig: applySearchKey(config, choice, ref),
entry,
runtime,
prompter,
opts,
});
return await finalizeSelection(applySearchKey(config, choice, ref));
}
const keyInput = await prompter.text({
@ -700,36 +667,15 @@ export async function runSearchSetupFlow(
const key = normalizeOptionalString(keyInput) ?? "";
if (key) {
const secretInput = resolveSearchSecretInput(config, choice, key, opts?.secretInputMode);
return await finalizeSearchProviderSetup({
originalConfig: config,
nextConfig: applySearchKey(config, choice, secretInput),
entry,
runtime,
prompter,
opts,
});
return await finalizeSelection(applySearchKey(config, choice, secretInput));
}
if (existingKey) {
return await finalizeSearchProviderSetup({
originalConfig: config,
nextConfig: applySearchKey(config, choice, existingKey),
entry,
runtime,
prompter,
opts,
});
return await finalizeSelection(applySearchKey(config, choice, existingKey));
}
if (keyConfigured || envAvailable) {
return await finalizeSearchProviderSetup({
originalConfig: config,
nextConfig: applySearchProviderSelection(config, choice),
entry,
runtime,
prompter,
opts,
});
return await finalizeSelection(applySearchProviderSelection(config, choice));
}
await prompter.note(

View file

@ -33,6 +33,9 @@ function makeEnv(overrides: Record<string, string | undefined> = {}) {
if (!Object.hasOwn(overrides, "OPENCLAW_LOCAL_CHECK_MODE")) {
delete env.OPENCLAW_LOCAL_CHECK_MODE;
}
if (!Object.hasOwn(overrides, "GITHUB_ACTIONS")) {
delete env.GITHUB_ACTIONS;
}
return env;
}
@ -296,6 +299,35 @@ describe("local-heavy-check-runtime", () => {
expect(env.GOMEMLIMIT).toBeUndefined();
});
it("uses stylish oxlint output in GitHub Actions before the command separator", () => {
const { args } = applyLocalOxlintPolicy(
["--", "src/example.ts"],
makeEnv({
GITHUB_ACTIONS: "true",
OPENCLAW_LOCAL_CHECK_MODE: "full",
}),
ROOMY_HOST,
);
expect(args.slice(-4)).toEqual(["--format", "stylish", "--", "src/example.ts"]);
});
it.each(["--format", "--format=json", "-f", "-f=json", "-fjson"])(
"preserves an explicit oxlint format argument: %s",
(formatArg) => {
const { args } = applyLocalOxlintPolicy(
[formatArg],
makeEnv({
GITHUB_ACTIONS: "true",
OPENCLAW_LOCAL_CHECK_MODE: "full",
}),
ROOMY_HOST,
);
expect(args).not.toContain("stylish");
},
);
it("skips the heavy-check lock for explicit oxlint file targets", () => {
const cwd = createTempDir("openclaw-oxlint-lock-skip-");
const target = path.join(cwd, "sample.ts");