@scotthuang/agent-knock-knock 0.11.4 → 0.11.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +6 -6
- package/dist/src/claude-lifecycle-compatibility.d.ts +16 -0
- package/dist/src/claude-lifecycle-compatibility.js +77 -0
- package/dist/src/claude-lifecycle-compatibility.js.map +1 -0
- package/dist/src/claude-local-transcript-provider.js +37 -17
- package/dist/src/claude-local-transcript-provider.js.map +1 -1
- package/dist/src/claude-terminal-agent-adapter.d.ts +13 -1
- package/dist/src/claude-terminal-agent-adapter.js +439 -8
- package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
- package/dist/src/cli.js +293 -25
- package/dist/src/cli.js.map +1 -1
- package/dist/src/codex-lifecycle-compatibility.js +2 -1
- package/dist/src/codex-lifecycle-compatibility.js.map +1 -1
- package/dist/src/codex-terminal-agent-adapter.d.ts +1 -0
- package/dist/src/codex-terminal-agent-adapter.js +7 -2
- package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin.js +4 -4
- package/dist/src/openclaw-plugin.js.map +1 -1
- package/dist/src/terminal-agent-adapter.d.ts +20 -0
- package/dist/src/terminal-agent-adapter.js.map +1 -1
- package/dist/src/terminal-agent-bridge.d.ts +43 -5
- package/dist/src/terminal-agent-bridge.js +296 -42
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/docs/quickstart-tmux.md +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ActiveTerminalProcess, TerminalAgentAdapter, TerminalApprovalInspection, TerminalDurableCompletionRequest, TerminalProcessSnapshot, TerminalScreenInspection, TerminalScreenInspectionOptions, TerminalThreadLifecycleCapabilities, TerminalThreadLifecycleObservation, TerminalThreadLifecycleObservationRequest, TerminalThreadLifecycleOperation, TerminalThreadLifecyclePlan } from "./terminal-agent-adapter.js";
|
|
1
|
+
import type { ActiveTerminalProcess, TerminalAgentAdapter, TerminalApprovalInspection, TerminalDurableCompletionRequest, TerminalNativeInspectionCapabilities, TerminalNativeInspectionObservation, TerminalNativeInspectionObservationRequest, TerminalNativeInspectionOperation, TerminalNativeInspectionPlan, TerminalProcessSnapshot, TerminalScreenInspection, TerminalScreenInspectionOptions, TerminalThreadLifecycleCapabilities, TerminalThreadLifecycleObservation, TerminalThreadLifecycleObservationRequest, TerminalThreadLifecycleOperation, TerminalThreadLifecyclePlan } from "./terminal-agent-adapter.js";
|
|
2
2
|
export type ClaudeProcessKind = "claude_cli";
|
|
3
3
|
/** A row returned by `claude agents --json --all`. Unknown fields are intentionally ignored. */
|
|
4
4
|
export interface ClaudeAgentRow {
|
|
@@ -8,6 +8,7 @@ export interface ClaudeAgentRow {
|
|
|
8
8
|
sessionId?: string;
|
|
9
9
|
startedAt?: number;
|
|
10
10
|
status?: string;
|
|
11
|
+
waitingFor?: string;
|
|
11
12
|
}
|
|
12
13
|
export interface CreateClaudeTerminalAgentAdapterOptions {
|
|
13
14
|
/**
|
|
@@ -39,7 +40,18 @@ export interface ClaudePendingApprovalEvidence {
|
|
|
39
40
|
evidenceFingerprint: string;
|
|
40
41
|
observedEndOffsetBytes: number;
|
|
41
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* On each verified Claude TUI profile, the exact `/status` composer and its
|
|
45
|
+
* closed completion list remained byte-stable across an 80 ms capture interval
|
|
46
|
+
* and one subsequent Enter opened the Status panel. This is a Claude-local
|
|
47
|
+
* single-line boundary; it deliberately does not reuse Codex's 121 ms burst
|
|
48
|
+
* paste boundary.
|
|
49
|
+
*/
|
|
50
|
+
export declare const CLAUDE_NATIVE_INSPECTION_COMPOSER_STABLE_MS = 80;
|
|
42
51
|
export declare function createClaudeTerminalAgentAdapter(options?: CreateClaudeTerminalAgentAdapterOptions): TerminalAgentAdapter<ClaudeProcessKind>;
|
|
52
|
+
export declare function probeClaudeNativeInspection(agentVersion: string | undefined): TerminalNativeInspectionCapabilities;
|
|
53
|
+
export declare function planClaudeNativeInspection(operation: TerminalNativeInspectionOperation, capabilities: TerminalNativeInspectionCapabilities): TerminalNativeInspectionPlan;
|
|
54
|
+
export declare function observeClaudeNativeInspection(request: TerminalNativeInspectionObservationRequest): TerminalNativeInspectionObservation;
|
|
43
55
|
export declare function probeClaudeThreadLifecycle(agentVersion: string | undefined): TerminalThreadLifecycleCapabilities;
|
|
44
56
|
export declare function planClaudeThreadLifecycle(operation: TerminalThreadLifecycleOperation, capabilities: TerminalThreadLifecycleCapabilities): TerminalThreadLifecyclePlan;
|
|
45
57
|
export declare function observeClaudeThreadLifecycle(request: TerminalThreadLifecycleObservationRequest, legacyOperation?: undefined, fallbackAgentRows?: readonly ClaudeAgentRow[]): TerminalThreadLifecycleObservation;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { claudeLifecycleBehaviorProfile, claudeLifecycleCompatibilityProfile, claudeNativeInspectionBehaviorProfile, profiledClaudeNativeStatusPanelFields } from "./claude-lifecycle-compatibility.js";
|
|
3
4
|
import { redactString } from "./runtime-log.js";
|
|
4
5
|
import { isExactNativeThreadId } from "./managed-session.js";
|
|
5
6
|
const CLAUDE_SUBCOMMANDS = new Set([
|
|
@@ -73,8 +74,31 @@ const CLAUDE_EXCERPT_LINES = 80;
|
|
|
73
74
|
const CLAUDE_PERMISSION_DETAIL_LENGTH = 600;
|
|
74
75
|
const CLAUDE_AUTO_APPROVAL_COMMAND_LENGTH = 2000;
|
|
75
76
|
const CLAUDE_NATIVE_VERSION_PATTERN = /^\d+\.\d+\.\d+$/u;
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
/**
|
|
78
|
+
* On each verified Claude TUI profile, the exact `/status` composer and its
|
|
79
|
+
* closed completion list remained byte-stable across an 80 ms capture interval
|
|
80
|
+
* and one subsequent Enter opened the Status panel. This is a Claude-local
|
|
81
|
+
* single-line boundary; it deliberately does not reuse Codex's 121 ms burst
|
|
82
|
+
* paste boundary.
|
|
83
|
+
*/
|
|
84
|
+
export const CLAUDE_NATIVE_INSPECTION_COMPOSER_STABLE_MS = 80;
|
|
85
|
+
const CLAUDE_STATUS_PANEL_HEADER = /^\s*Settings\s+Status\s+Config\s+Usage\s+Stats\s*$/u;
|
|
86
|
+
const CLAUDE_STATUS_PANEL_FOOTER = /^\s*Esc to cancel\s*$/u;
|
|
87
|
+
const CLAUDE_STATUS_PANEL_DIVIDER = /^\s*[─━]{8,}\s*$/u;
|
|
88
|
+
const CLAUDE_STATUS_PANEL_FIELD = /^\s{0,4}([^:]{1,64}):\s+(.{1,1024})\s*$/u;
|
|
89
|
+
const CLAUDE_STATUS_PANEL_MAX_SCAN_LINES = 512;
|
|
90
|
+
const CLAUDE_STATUS_PANEL_MAX_LINES = 48;
|
|
91
|
+
const CLAUDE_STATUS_PANEL_MAX_REGION_LENGTH = 8_192;
|
|
92
|
+
const CLAUDE_STATUS_PANEL_MAX_FIELDS = 24;
|
|
93
|
+
const CLAUDE_STATUS_PANEL_MAX_FIELD_VALUE_LENGTH = 512;
|
|
94
|
+
const CLAUDE_STATUS_PANEL_MAX_EXCERPT_LENGTH = 4_000;
|
|
95
|
+
const CLAUDE_STATUS_PANEL_MAX_EVIDENCE_ENTRIES = 24;
|
|
96
|
+
const PROFILED_CLAUDE_STATUS_PANEL_FIELDS = new Set(profiledClaudeNativeStatusPanelFields());
|
|
97
|
+
const CLAUDE_STATUS_PANEL_SENSITIVE_FIELDS = new Set([
|
|
98
|
+
"Session name",
|
|
99
|
+
"Auth token"
|
|
100
|
+
]);
|
|
101
|
+
const NATIVE_INSPECTION_FINGERPRINT = /^sha256:[0-9a-f]{64}$/u;
|
|
78
102
|
const ANSI_ESCAPE_PATTERN = /\x1B(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/gu;
|
|
79
103
|
export function createClaudeTerminalAgentAdapter(options = {}) {
|
|
80
104
|
const agentRows = options.agentRows ?? [];
|
|
@@ -106,6 +130,9 @@ export function createClaudeTerminalAgentAdapter(options = {}) {
|
|
|
106
130
|
probeThreadLifecycle: probeClaudeThreadLifecycle,
|
|
107
131
|
planThreadLifecycle: planClaudeThreadLifecycle,
|
|
108
132
|
observeThreadLifecycle: lifecycleObserver,
|
|
133
|
+
probeNativeInspection: probeClaudeNativeInspection,
|
|
134
|
+
planNativeInspection: planClaudeNativeInspection,
|
|
135
|
+
observeNativeInspection: observeClaudeNativeInspection,
|
|
109
136
|
...(durableCompletion
|
|
110
137
|
? {
|
|
111
138
|
async detectDurableCompletion(request) {
|
|
@@ -115,6 +142,192 @@ export function createClaudeTerminalAgentAdapter(options = {}) {
|
|
|
115
142
|
: {})
|
|
116
143
|
};
|
|
117
144
|
}
|
|
145
|
+
export function probeClaudeNativeInspection(agentVersion) {
|
|
146
|
+
if (!agentVersion) {
|
|
147
|
+
return {
|
|
148
|
+
status: "unknown",
|
|
149
|
+
statusInspection: false,
|
|
150
|
+
reason: "the running Claude Code version could not be verified"
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const behaviorProfile = claudeNativeInspectionBehaviorProfile(agentVersion);
|
|
154
|
+
const supported = behaviorProfile !== undefined;
|
|
155
|
+
return {
|
|
156
|
+
status: supported ? "supported" : "unsupported",
|
|
157
|
+
agentVersion,
|
|
158
|
+
behaviorProfile,
|
|
159
|
+
statusInspection: supported,
|
|
160
|
+
reason: supported
|
|
161
|
+
? "Claude Code /status native inspection is supported by the verified version"
|
|
162
|
+
: "this exact Claude Code version has no AKK native inspection behavior profile"
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
export function planClaudeNativeInspection(operation, capabilities) {
|
|
166
|
+
const profile = claudeLifecycleCompatibilityProfile(capabilities.agentVersion);
|
|
167
|
+
if (operation.kind !== "status" ||
|
|
168
|
+
capabilities.status !== "supported" ||
|
|
169
|
+
capabilities.statusInspection !== true ||
|
|
170
|
+
!profile ||
|
|
171
|
+
capabilities.behaviorProfile !== profile.nativeInspectionBehaviorProfile) {
|
|
172
|
+
throw new Error(capabilities.reason);
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
operation,
|
|
176
|
+
behaviorProfile: profile.nativeInspectionBehaviorProfile,
|
|
177
|
+
command: "/status",
|
|
178
|
+
effect: "read_only",
|
|
179
|
+
requiresIdle: true,
|
|
180
|
+
composer: {
|
|
181
|
+
kind: "exact",
|
|
182
|
+
minimumStableMs: profile.nativeInspectionComposerStableMs,
|
|
183
|
+
maximumSettleMs: profile.nativeInspectionComposerSettleTimeoutMs
|
|
184
|
+
},
|
|
185
|
+
expectedResult: {
|
|
186
|
+
kind: "native_status",
|
|
187
|
+
presentation: "modal",
|
|
188
|
+
dismissal: {
|
|
189
|
+
keys: ["Escape"],
|
|
190
|
+
expected: "idle_empty_composer"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
export function observeClaudeNativeInspection(request) {
|
|
196
|
+
const rawScreen = request.screen ?? "";
|
|
197
|
+
const rawScreenFingerprint = fingerprintClaudeNativeInspection(rawScreen);
|
|
198
|
+
const screen = normalizeClaudeNativeInspectionScreen(rawScreen);
|
|
199
|
+
const screenFingerprint = fingerprintClaudeNativeInspection(screen);
|
|
200
|
+
const inventory = claudeStatusPanelEvidenceInventory(screen);
|
|
201
|
+
if (inventory.status === "ambiguous") {
|
|
202
|
+
return {
|
|
203
|
+
status: "ambiguous",
|
|
204
|
+
screenFingerprint,
|
|
205
|
+
evidenceInventory: inventory.entries,
|
|
206
|
+
reason: inventory.reason
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const baselineError = validateClaudeStatusEvidenceInventory(request.preEnterEvidenceInventory);
|
|
210
|
+
if (baselineError) {
|
|
211
|
+
return {
|
|
212
|
+
status: "ambiguous",
|
|
213
|
+
screenFingerprint,
|
|
214
|
+
evidenceInventory: inventory.entries,
|
|
215
|
+
reason: baselineError
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
if (request.previousScreenFingerprint !== undefined &&
|
|
219
|
+
(request.previousScreenFingerprint === rawScreenFingerprint ||
|
|
220
|
+
request.previousScreenFingerprint === screenFingerprint)) {
|
|
221
|
+
return {
|
|
222
|
+
status: "stale",
|
|
223
|
+
screenFingerprint,
|
|
224
|
+
evidenceInventory: inventory.entries,
|
|
225
|
+
reason: "the Claude screen did not change after the native inspection command"
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const parsed = parseCurrentClaudeStatusPanel(screen);
|
|
229
|
+
if (parsed.status !== "observed") {
|
|
230
|
+
return {
|
|
231
|
+
status: parsed.status,
|
|
232
|
+
screenFingerprint,
|
|
233
|
+
evidenceInventory: inventory.entries,
|
|
234
|
+
reason: parsed.reason
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const evidenceFingerprint = fingerprintClaudeNativeInspection(parsed.region);
|
|
238
|
+
if (request.preEnterEvidenceInventory !== undefined) {
|
|
239
|
+
const priorOccurrences = request.preEnterEvidenceInventory.find((entry) => entry.evidenceFingerprint === evidenceFingerprint)?.occurrenceCount ?? 0;
|
|
240
|
+
const currentOccurrences = inventory.entries.find((entry) => entry.evidenceFingerprint === evidenceFingerprint)?.occurrenceCount ?? 0;
|
|
241
|
+
if (currentOccurrences <= priorOccurrences) {
|
|
242
|
+
return {
|
|
243
|
+
status: "stale",
|
|
244
|
+
nativeThreadId: parsed.nativeThreadId,
|
|
245
|
+
observedAgentVersion: parsed.agentVersion,
|
|
246
|
+
evidence: "claude_status_panel",
|
|
247
|
+
evidenceFingerprint,
|
|
248
|
+
screenFingerprint,
|
|
249
|
+
evidenceInventory: inventory.entries,
|
|
250
|
+
reason: "the Claude Status panel did not add fresh exact evidence after Enter"
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (!claudeNativeInspectionBehaviorProfile(parsed.agentVersion)) {
|
|
255
|
+
return {
|
|
256
|
+
status: "mismatch",
|
|
257
|
+
nativeThreadId: parsed.nativeThreadId,
|
|
258
|
+
observedAgentVersion: parsed.agentVersion,
|
|
259
|
+
evidenceFingerprint,
|
|
260
|
+
screenFingerprint,
|
|
261
|
+
evidenceInventory: inventory.entries,
|
|
262
|
+
reason: `Claude /status reported unsupported version ${parsed.agentVersion}`
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
if (request.expectedAgentVersion !== undefined &&
|
|
266
|
+
parsed.agentVersion !== request.expectedAgentVersion) {
|
|
267
|
+
return {
|
|
268
|
+
status: "mismatch",
|
|
269
|
+
nativeThreadId: parsed.nativeThreadId,
|
|
270
|
+
observedAgentVersion: parsed.agentVersion,
|
|
271
|
+
evidenceFingerprint,
|
|
272
|
+
screenFingerprint,
|
|
273
|
+
evidenceInventory: inventory.entries,
|
|
274
|
+
reason: `Claude /status reported version ${parsed.agentVersion}, not the ` +
|
|
275
|
+
`verified running version ${request.expectedAgentVersion}`
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
if (!isExactNativeThreadId(request.expectedNativeThreadId)) {
|
|
279
|
+
return {
|
|
280
|
+
status: "mismatch",
|
|
281
|
+
nativeThreadId: parsed.nativeThreadId,
|
|
282
|
+
observedAgentVersion: parsed.agentVersion,
|
|
283
|
+
evidenceFingerprint,
|
|
284
|
+
screenFingerprint,
|
|
285
|
+
evidenceInventory: inventory.entries,
|
|
286
|
+
reason: "Claude native status inspection requires an exact claude agents Session identity"
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
if (parsed.nativeThreadId !== request.expectedNativeThreadId.toLowerCase()) {
|
|
290
|
+
return {
|
|
291
|
+
status: "mismatch",
|
|
292
|
+
nativeThreadId: parsed.nativeThreadId,
|
|
293
|
+
observedAgentVersion: parsed.agentVersion,
|
|
294
|
+
evidenceFingerprint,
|
|
295
|
+
screenFingerprint,
|
|
296
|
+
evidenceInventory: inventory.entries,
|
|
297
|
+
reason: `Claude /status reported ${parsed.nativeThreadId}, not the exact ` +
|
|
298
|
+
`claude agents Session ${request.expectedNativeThreadId.toLowerCase()}`
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
if (request.expectedCwd !== undefined &&
|
|
302
|
+
normalizeLifecyclePath(parsed.cwd) !==
|
|
303
|
+
normalizeLifecyclePath(request.expectedCwd)) {
|
|
304
|
+
return {
|
|
305
|
+
status: "mismatch",
|
|
306
|
+
nativeThreadId: parsed.nativeThreadId,
|
|
307
|
+
observedAgentVersion: parsed.agentVersion,
|
|
308
|
+
evidenceFingerprint,
|
|
309
|
+
screenFingerprint,
|
|
310
|
+
evidenceInventory: inventory.entries,
|
|
311
|
+
reason: `Claude /status cwd ${parsed.cwd} does not match the verified pane cwd`
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
return {
|
|
315
|
+
status: "observed",
|
|
316
|
+
nativeThreadId: parsed.nativeThreadId,
|
|
317
|
+
observedAgentVersion: parsed.agentVersion,
|
|
318
|
+
evidence: "claude_status_panel",
|
|
319
|
+
evidenceFingerprint,
|
|
320
|
+
screenFingerprint,
|
|
321
|
+
evidenceInventory: inventory.entries,
|
|
322
|
+
result: {
|
|
323
|
+
kind: "native_status",
|
|
324
|
+
nativeThreadId: parsed.nativeThreadId,
|
|
325
|
+
agentVersion: parsed.agentVersion,
|
|
326
|
+
fields: parsed.fields,
|
|
327
|
+
excerpt: claudeNativeStatusExcerpt(parsed.fields)
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
}
|
|
118
331
|
export function probeClaudeThreadLifecycle(agentVersion) {
|
|
119
332
|
if (!agentVersion) {
|
|
120
333
|
return {
|
|
@@ -124,11 +337,12 @@ export function probeClaudeThreadLifecycle(agentVersion) {
|
|
|
124
337
|
reason: "the running Claude Code version could not be verified"
|
|
125
338
|
};
|
|
126
339
|
}
|
|
127
|
-
const
|
|
340
|
+
const behaviorProfile = claudeLifecycleBehaviorProfile(agentVersion);
|
|
341
|
+
const supported = behaviorProfile !== undefined;
|
|
128
342
|
return {
|
|
129
343
|
status: supported ? "supported" : "unsupported",
|
|
130
344
|
agentVersion,
|
|
131
|
-
behaviorProfile
|
|
345
|
+
behaviorProfile,
|
|
132
346
|
newThread: supported,
|
|
133
347
|
resumeExact: supported,
|
|
134
348
|
candidateDiscovery: supported,
|
|
@@ -138,9 +352,10 @@ export function probeClaudeThreadLifecycle(agentVersion) {
|
|
|
138
352
|
};
|
|
139
353
|
}
|
|
140
354
|
export function planClaudeThreadLifecycle(operation, capabilities) {
|
|
355
|
+
const behaviorProfile = claudeLifecycleBehaviorProfile(capabilities.agentVersion);
|
|
141
356
|
if (capabilities.status !== "supported" ||
|
|
142
|
-
|
|
143
|
-
capabilities.behaviorProfile !==
|
|
357
|
+
!behaviorProfile ||
|
|
358
|
+
capabilities.behaviorProfile !== behaviorProfile) {
|
|
144
359
|
throw new Error(capabilities.reason);
|
|
145
360
|
}
|
|
146
361
|
if (operation.kind === "new_thread") {
|
|
@@ -149,7 +364,7 @@ export function planClaudeThreadLifecycle(operation, capabilities) {
|
|
|
149
364
|
}
|
|
150
365
|
return {
|
|
151
366
|
operation,
|
|
152
|
-
behaviorProfile
|
|
367
|
+
behaviorProfile,
|
|
153
368
|
steps: [{
|
|
154
369
|
kind: "transition",
|
|
155
370
|
command: "/clear",
|
|
@@ -168,7 +383,7 @@ export function planClaudeThreadLifecycle(operation, capabilities) {
|
|
|
168
383
|
}
|
|
169
384
|
return {
|
|
170
385
|
operation,
|
|
171
|
-
behaviorProfile
|
|
386
|
+
behaviorProfile,
|
|
172
387
|
steps: [{
|
|
173
388
|
kind: "transition",
|
|
174
389
|
command: `/resume ${operation.nativeThreadId}`,
|
|
@@ -280,6 +495,222 @@ export function observeClaudeThreadLifecycle(requestOrScreen, legacyOperation, f
|
|
|
280
495
|
reason: `Claude resumed ${nativeThreadId}, not the requested native thread ${expected}`
|
|
281
496
|
};
|
|
282
497
|
}
|
|
498
|
+
function parseCurrentClaudeStatusPanel(screen) {
|
|
499
|
+
const lines = screen.split("\n").slice(-CLAUDE_STATUS_PANEL_MAX_SCAN_LINES);
|
|
500
|
+
const headers = lines
|
|
501
|
+
.map((line, index) => CLAUDE_STATUS_PANEL_HEADER.test(line) ? index : -1)
|
|
502
|
+
.filter((index) => index >= 0);
|
|
503
|
+
if (headers.length === 0) {
|
|
504
|
+
return {
|
|
505
|
+
status: "missing",
|
|
506
|
+
reason: "no current Claude Settings Status panel is visible"
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
const headerIndex = headers.at(-1);
|
|
510
|
+
if (headerIndex === 0 ||
|
|
511
|
+
!CLAUDE_STATUS_PANEL_DIVIDER.test(lines[headerIndex - 1])) {
|
|
512
|
+
return {
|
|
513
|
+
status: "ambiguous",
|
|
514
|
+
reason: "the Claude Status header is not anchored to its exact panel divider"
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
const footerIndexes = lines
|
|
518
|
+
.map((line, index) => index > headerIndex &&
|
|
519
|
+
index - headerIndex <= CLAUDE_STATUS_PANEL_MAX_LINES &&
|
|
520
|
+
CLAUDE_STATUS_PANEL_FOOTER.test(line)
|
|
521
|
+
? index
|
|
522
|
+
: -1)
|
|
523
|
+
.filter((index) => index >= 0);
|
|
524
|
+
if (footerIndexes.length !== 1) {
|
|
525
|
+
return {
|
|
526
|
+
status: "ambiguous",
|
|
527
|
+
reason: "the Claude Status panel is incomplete or has ambiguous dismissal markers"
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
const footerIndex = footerIndexes[0];
|
|
531
|
+
if (lines.slice(footerIndex + 1).some((line) => line.trim().length > 0)) {
|
|
532
|
+
return {
|
|
533
|
+
status: "missing",
|
|
534
|
+
reason: "the newest Claude Status panel is historical rather than current"
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
const regionLines = lines.slice(headerIndex, footerIndex + 1);
|
|
538
|
+
const region = regionLines.join("\n");
|
|
539
|
+
if (regionLines.length > CLAUDE_STATUS_PANEL_MAX_LINES ||
|
|
540
|
+
region.length > CLAUDE_STATUS_PANEL_MAX_REGION_LENGTH) {
|
|
541
|
+
return {
|
|
542
|
+
status: "ambiguous",
|
|
543
|
+
reason: "the Claude Status panel exceeds the bounded inspection region"
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
const rawFields = new Map();
|
|
547
|
+
for (const line of regionLines.slice(1, -1)) {
|
|
548
|
+
if (line.trim().length === 0) {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
const match = CLAUDE_STATUS_PANEL_FIELD.exec(line);
|
|
552
|
+
if (!match) {
|
|
553
|
+
return {
|
|
554
|
+
status: "ambiguous",
|
|
555
|
+
reason: "the Claude Status panel contains an unprofiled non-field row"
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
const name = match[1].trim();
|
|
559
|
+
const value = match[2].trim();
|
|
560
|
+
if (!PROFILED_CLAUDE_STATUS_PANEL_FIELDS.has(name)) {
|
|
561
|
+
return {
|
|
562
|
+
status: "ambiguous",
|
|
563
|
+
reason: "the Claude Status panel contains an unprofiled field"
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
if (rawFields.has(name)) {
|
|
567
|
+
return {
|
|
568
|
+
status: "ambiguous",
|
|
569
|
+
reason: `the Claude Status panel repeats field ${name}`
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
rawFields.set(name, value);
|
|
573
|
+
}
|
|
574
|
+
if (rawFields.size === 0 || rawFields.size > CLAUDE_STATUS_PANEL_MAX_FIELDS) {
|
|
575
|
+
return {
|
|
576
|
+
status: "ambiguous",
|
|
577
|
+
reason: "the Claude Status panel has an invalid number of fields"
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
const agentVersion = rawFields.get("Version");
|
|
581
|
+
const nativeThreadId = rawFields.get("Session ID")?.toLowerCase();
|
|
582
|
+
const cwd = rawFields.get("cwd");
|
|
583
|
+
const model = rawFields.get("Model");
|
|
584
|
+
if (!agentVersion ||
|
|
585
|
+
!isExactNativeThreadId(nativeThreadId) ||
|
|
586
|
+
!cwd ||
|
|
587
|
+
!path.isAbsolute(cwd) ||
|
|
588
|
+
!model) {
|
|
589
|
+
return {
|
|
590
|
+
status: "ambiguous",
|
|
591
|
+
reason: "the Claude Status panel lacks exact Version, Session ID, cwd, or Model fields"
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
const exactProfileFields = new Set(claudeLifecycleCompatibilityProfile(agentVersion)
|
|
595
|
+
?.nativeStatusPanelFields ?? []);
|
|
596
|
+
const exactRequiredValues = claudeLifecycleCompatibilityProfile(agentVersion)
|
|
597
|
+
?.nativeStatusPanelRequiredValues ?? {};
|
|
598
|
+
if (exactProfileFields.size > 0 &&
|
|
599
|
+
[...rawFields.keys()].some((name) => !exactProfileFields.has(name))) {
|
|
600
|
+
return {
|
|
601
|
+
status: "ambiguous",
|
|
602
|
+
reason: "the Claude Status panel fields do not match its exact version profile"
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
if (Object.entries(exactRequiredValues).some(([name, value]) => rawFields.get(name) !== value)) {
|
|
606
|
+
return {
|
|
607
|
+
status: "ambiguous",
|
|
608
|
+
reason: "the Claude Status panel lacks an exact field value required by its version profile"
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
const fields = [...rawFields.entries()].map(([name, rawValue]) => ({
|
|
612
|
+
name,
|
|
613
|
+
value: redactClaudeNativeStatusField(name, rawValue)
|
|
614
|
+
.slice(0, CLAUDE_STATUS_PANEL_MAX_FIELD_VALUE_LENGTH)
|
|
615
|
+
}));
|
|
616
|
+
return {
|
|
617
|
+
status: "observed",
|
|
618
|
+
region,
|
|
619
|
+
agentVersion,
|
|
620
|
+
nativeThreadId,
|
|
621
|
+
cwd,
|
|
622
|
+
fields
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function claudeStatusPanelEvidenceInventory(screen) {
|
|
626
|
+
const lines = screen.split("\n").slice(-CLAUDE_STATUS_PANEL_MAX_SCAN_LINES);
|
|
627
|
+
const counts = new Map();
|
|
628
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
629
|
+
if (!CLAUDE_STATUS_PANEL_HEADER.test(lines[index])) {
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
if (index === 0 ||
|
|
633
|
+
!CLAUDE_STATUS_PANEL_DIVIDER.test(lines[index - 1])) {
|
|
634
|
+
return {
|
|
635
|
+
status: "ambiguous",
|
|
636
|
+
entries: [],
|
|
637
|
+
reason: "a Claude Status marker is not anchored to its exact panel divider"
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
const footerIndex = lines.findIndex((line, candidateIndex) => candidateIndex > index &&
|
|
641
|
+
candidateIndex - index <= CLAUDE_STATUS_PANEL_MAX_LINES &&
|
|
642
|
+
CLAUDE_STATUS_PANEL_FOOTER.test(line));
|
|
643
|
+
if (footerIndex < 0) {
|
|
644
|
+
return {
|
|
645
|
+
status: "ambiguous",
|
|
646
|
+
entries: [],
|
|
647
|
+
reason: "a Claude Status panel marker has no bounded exact footer"
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
const region = lines.slice(index, footerIndex + 1).join("\n");
|
|
651
|
+
if (region.length > CLAUDE_STATUS_PANEL_MAX_REGION_LENGTH) {
|
|
652
|
+
return {
|
|
653
|
+
status: "ambiguous",
|
|
654
|
+
entries: [],
|
|
655
|
+
reason: "a Claude Status panel exceeds the bounded evidence region"
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
const fingerprint = fingerprintClaudeNativeInspection(region);
|
|
659
|
+
counts.set(fingerprint, (counts.get(fingerprint) ?? 0) + 1);
|
|
660
|
+
index = footerIndex;
|
|
661
|
+
}
|
|
662
|
+
const entries = [...counts.entries()].map(([evidenceFingerprint, occurrenceCount]) => ({ evidenceFingerprint, occurrenceCount }));
|
|
663
|
+
if (entries.length > CLAUDE_STATUS_PANEL_MAX_EVIDENCE_ENTRIES) {
|
|
664
|
+
return {
|
|
665
|
+
status: "ambiguous",
|
|
666
|
+
entries,
|
|
667
|
+
reason: "the Claude Status evidence inventory exceeds its bounded entry count"
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
return { status: "observed", entries };
|
|
671
|
+
}
|
|
672
|
+
function validateClaudeStatusEvidenceInventory(inventory) {
|
|
673
|
+
if (inventory === undefined) {
|
|
674
|
+
return undefined;
|
|
675
|
+
}
|
|
676
|
+
if (inventory.length > CLAUDE_STATUS_PANEL_MAX_EVIDENCE_ENTRIES) {
|
|
677
|
+
return "the pre-Enter Claude Status evidence inventory is over-bounded";
|
|
678
|
+
}
|
|
679
|
+
const seen = new Set();
|
|
680
|
+
for (const entry of inventory) {
|
|
681
|
+
if (!NATIVE_INSPECTION_FINGERPRINT.test(entry.evidenceFingerprint) ||
|
|
682
|
+
!Number.isSafeInteger(entry.occurrenceCount) ||
|
|
683
|
+
entry.occurrenceCount < 1 ||
|
|
684
|
+
seen.has(entry.evidenceFingerprint)) {
|
|
685
|
+
return "the pre-Enter Claude Status evidence inventory is malformed";
|
|
686
|
+
}
|
|
687
|
+
seen.add(entry.evidenceFingerprint);
|
|
688
|
+
}
|
|
689
|
+
return undefined;
|
|
690
|
+
}
|
|
691
|
+
function redactClaudeNativeStatusField(name, value) {
|
|
692
|
+
if (CLAUDE_STATUS_PANEL_SENSITIVE_FIELDS.has(name) ||
|
|
693
|
+
/(?:auth|account|email|api\s*key|token|credential|login|organization|user)/iu
|
|
694
|
+
.test(name)) {
|
|
695
|
+
return "[REDACTED]";
|
|
696
|
+
}
|
|
697
|
+
return redactString(value).replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/giu, "[REDACTED EMAIL]");
|
|
698
|
+
}
|
|
699
|
+
function claudeNativeStatusExcerpt(fields) {
|
|
700
|
+
return [
|
|
701
|
+
"Claude Code native status",
|
|
702
|
+
...fields.map((field) => `${field.name}: ${field.value}`)
|
|
703
|
+
].join("\n").slice(0, CLAUDE_STATUS_PANEL_MAX_EXCERPT_LENGTH);
|
|
704
|
+
}
|
|
705
|
+
function normalizeClaudeNativeInspectionScreen(screen) {
|
|
706
|
+
return screen
|
|
707
|
+
.replace(ANSI_ESCAPE_PATTERN, "")
|
|
708
|
+
.replace(/\r\n?/gu, "\n")
|
|
709
|
+
.replace(/\u00a0/gu, " ");
|
|
710
|
+
}
|
|
711
|
+
function fingerprintClaudeNativeInspection(value) {
|
|
712
|
+
return `sha256:${createHash("sha256").update(value).digest("hex")}`;
|
|
713
|
+
}
|
|
283
714
|
function normalizeLifecyclePath(value) {
|
|
284
715
|
const candidate = nonEmptyString(value);
|
|
285
716
|
return candidate === undefined ? undefined : path.resolve(candidate);
|