@scotthuang/agent-knock-knock 0.11.4 → 0.11.5
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 +11 -0
- package/README.md +4 -4
- package/dist/src/claude-terminal-agent-adapter.d.ts +13 -1
- package/dist/src/claude-terminal-agent-adapter.js +424 -0
- package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
- package/dist/src/cli.js +271 -25
- package/dist/src/cli.js.map +1 -1
- package/dist/src/codex-terminal-agent-adapter.js +4 -1
- 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 +18 -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 +233 -29
- 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
|
@@ -24,6 +24,14 @@ export declare class NativeInspectionSubmissionError extends Error {
|
|
|
24
24
|
cause?: unknown;
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
|
+
/** A verified modal could not be dismissed across one exact key attempt. */
|
|
28
|
+
export declare class NativeInspectionDismissalError extends Error {
|
|
29
|
+
readonly code = "AKK_NATIVE_INSPECTION_DISMISSAL_FAILED";
|
|
30
|
+
readonly doNotRetry = true;
|
|
31
|
+
constructor(message: string, options?: {
|
|
32
|
+
cause?: unknown;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
27
35
|
export interface TerminalBridgeStatus {
|
|
28
36
|
provider: "tmux";
|
|
29
37
|
target: string;
|
|
@@ -129,7 +137,7 @@ export interface TerminalNativeInspectionMaterializationEvidence {
|
|
|
129
137
|
stableCaptures: number;
|
|
130
138
|
}
|
|
131
139
|
export interface TerminalNativeInspectionBeforeEnterContext {
|
|
132
|
-
agent:
|
|
140
|
+
agent: ExecutorKind;
|
|
133
141
|
terminalControl: TerminalControlRef;
|
|
134
142
|
plan: TerminalNativeInspectionPlan;
|
|
135
143
|
preEnterScreenDigest: string;
|
|
@@ -146,15 +154,33 @@ export interface TerminalNativeInspectionOptions {
|
|
|
146
154
|
}
|
|
147
155
|
export interface TerminalNativeInspectionResult {
|
|
148
156
|
stage: "enter_dispatched";
|
|
149
|
-
agent:
|
|
157
|
+
agent: ExecutorKind;
|
|
150
158
|
terminalControl: TerminalControlRef;
|
|
151
|
-
command:
|
|
159
|
+
command: string;
|
|
152
160
|
behaviorProfile: string;
|
|
153
161
|
preEnterScreenDigest: string;
|
|
154
162
|
preEnterEvidenceInventory: readonly TerminalNativeInspectionEvidenceInventoryEntry[];
|
|
155
163
|
materialization: TerminalNativeInspectionMaterializationEvidence;
|
|
156
164
|
enterCount: 1;
|
|
157
165
|
}
|
|
166
|
+
export interface TerminalNativeInspectionBeforeDismissContext {
|
|
167
|
+
agent: ExecutorKind;
|
|
168
|
+
terminalControl: TerminalControlRef;
|
|
169
|
+
plan: TerminalNativeInspectionPlan;
|
|
170
|
+
evidenceFingerprint: string;
|
|
171
|
+
}
|
|
172
|
+
export interface TerminalNativeInspectionDismissalOptions {
|
|
173
|
+
runtime?: TerminalRuntimeIdentity;
|
|
174
|
+
scrollbackLines?: number;
|
|
175
|
+
beforeDismiss?: (context: TerminalNativeInspectionBeforeDismissContext) => void | Promise<void>;
|
|
176
|
+
}
|
|
177
|
+
export interface TerminalNativeInspectionDismissalResult {
|
|
178
|
+
stage: "dismiss_dispatched";
|
|
179
|
+
agent: ExecutorKind;
|
|
180
|
+
terminalControl: TerminalControlRef;
|
|
181
|
+
keys: readonly string[];
|
|
182
|
+
dismissCount: 1;
|
|
183
|
+
}
|
|
158
184
|
export interface TerminalNativeInspectionObservationResult {
|
|
159
185
|
terminalControl: TerminalControlRef;
|
|
160
186
|
status: TerminalBridgeStatus;
|
|
@@ -221,8 +247,14 @@ export declare class TerminalAgentBridge {
|
|
|
221
247
|
runtime?: TerminalRuntimeIdentity;
|
|
222
248
|
scrollbackLines?: number;
|
|
223
249
|
}): Promise<TerminalNativeInspectionObservationResult>;
|
|
224
|
-
|
|
225
|
-
|
|
250
|
+
/**
|
|
251
|
+
* Dismiss one exact adapter-owned modal result after re-observing the same
|
|
252
|
+
* evidence under the terminal identity fence. There is exactly one key
|
|
253
|
+
* attempt and no automated retry across an uncertain dismissal boundary.
|
|
254
|
+
*/
|
|
255
|
+
dismissNativeInspection(agent: ExecutorKind, terminalControl: TerminalControlRef, plan: TerminalNativeInspectionPlan, request: TerminalNativeInspectionObservationRequest, expectedEvidenceFingerprint: string, options?: TerminalNativeInspectionDismissalOptions): Promise<TerminalNativeInspectionDismissalResult>;
|
|
256
|
+
private settleNativeInspectionComposer;
|
|
257
|
+
private revalidateNativeInspectionComposer;
|
|
226
258
|
private settleCodexMultilineComposer;
|
|
227
259
|
/**
|
|
228
260
|
* Prove that the exact managed draft is still present after Enter dispatch.
|
|
@@ -281,6 +313,12 @@ export declare class TerminalAgentBridge {
|
|
|
281
313
|
private captureInspection;
|
|
282
314
|
private verifyTerminalIdentity;
|
|
283
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* Prove the exact idle input frame from the verified Claude Code 2.1.218
|
|
318
|
+
* profile. A loose or historical `❯` prompt is not enough to authorize even
|
|
319
|
+
* the initial literal `/status` injection.
|
|
320
|
+
*/
|
|
321
|
+
export declare function isExactClaudeNativeInspectionIdleComposer(screen: string): boolean;
|
|
284
322
|
export declare function terminalApprovalFingerprint(agent: ExecutorKind, terminalControl: TerminalControlRef, inspection: TerminalScreenInspection, options?: {
|
|
285
323
|
screen?: string;
|
|
286
324
|
runtime?: TerminalRuntimeIdentity;
|
|
@@ -18,6 +18,14 @@ const CODEX_NATIVE_STATUS_POPUP_BY_PROFILE = {
|
|
|
18
18
|
"codex-tui-0.146.0": " /status show current session configuration and token usage",
|
|
19
19
|
"codex-tui-0.146.1": " /status show current session configuration and token usage"
|
|
20
20
|
};
|
|
21
|
+
const CLAUDE_NATIVE_STATUS_POPUP_BY_PROFILE = {
|
|
22
|
+
"claude-code-2.1.218-native-status": [
|
|
23
|
+
"/status Show Claude Code status including version, model, account, API connectivity, and tool statuses",
|
|
24
|
+
"/statusline Set up Claude Code's status line UI",
|
|
25
|
+
"/ide Manage IDE integrations and show status",
|
|
26
|
+
"/usage Show session cost, plan usage, and activity stats"
|
|
27
|
+
]
|
|
28
|
+
};
|
|
21
29
|
const CODEX_LARGE_PASTE_CHAR_THRESHOLD = 1_000;
|
|
22
30
|
/** A terminal send failed at a boundary that proves input never started. */
|
|
23
31
|
export class TerminalInputNotStartedError extends Error {
|
|
@@ -45,6 +53,15 @@ export class NativeInspectionSubmissionError extends Error {
|
|
|
45
53
|
this.doNotRetry = stage !== "not_started";
|
|
46
54
|
}
|
|
47
55
|
}
|
|
56
|
+
/** A verified modal could not be dismissed across one exact key attempt. */
|
|
57
|
+
export class NativeInspectionDismissalError extends Error {
|
|
58
|
+
code = "AKK_NATIVE_INSPECTION_DISMISSAL_FAILED";
|
|
59
|
+
doNotRetry = true;
|
|
60
|
+
constructor(message, options = {}) {
|
|
61
|
+
super(message, options);
|
|
62
|
+
this.name = "NativeInspectionDismissalError";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
48
65
|
export class TerminalAgentBridge {
|
|
49
66
|
registry;
|
|
50
67
|
terminalProvider;
|
|
@@ -254,7 +271,7 @@ export class TerminalAgentBridge {
|
|
|
254
271
|
async submitNativeInspection(agent, terminalControl, plan, options = {}) {
|
|
255
272
|
const adapter = this.registry.require(agent);
|
|
256
273
|
try {
|
|
257
|
-
|
|
274
|
+
assertClosedStatusInspectionPlan(adapter, terminalControl, plan);
|
|
258
275
|
}
|
|
259
276
|
catch (error) {
|
|
260
277
|
throw nativeInspectionSubmissionError("not_started", error);
|
|
@@ -279,15 +296,15 @@ export class TerminalAgentBridge {
|
|
|
279
296
|
}
|
|
280
297
|
let settled;
|
|
281
298
|
try {
|
|
282
|
-
settled = await this.
|
|
299
|
+
settled = await this.settleNativeInspectionComposer(adapter, terminalControl, plan, options.runtime);
|
|
283
300
|
await options.beforeEnter?.({
|
|
284
|
-
agent:
|
|
301
|
+
agent: adapter.agent,
|
|
285
302
|
terminalControl: settled.terminalControl,
|
|
286
303
|
plan,
|
|
287
304
|
preEnterScreenDigest: settled.screenDigest,
|
|
288
305
|
materialization: settled.materialization
|
|
289
306
|
});
|
|
290
|
-
settled = await this.
|
|
307
|
+
settled = await this.revalidateNativeInspectionComposer(adapter, settled.terminalControl, plan, settled.materialization, options.runtime);
|
|
291
308
|
}
|
|
292
309
|
catch (error) {
|
|
293
310
|
throw nativeInspectionSubmissionError("text_injected", error);
|
|
@@ -302,9 +319,9 @@ export class TerminalAgentBridge {
|
|
|
302
319
|
}
|
|
303
320
|
return {
|
|
304
321
|
stage: "enter_dispatched",
|
|
305
|
-
agent:
|
|
322
|
+
agent: adapter.agent,
|
|
306
323
|
terminalControl: settled.terminalControl,
|
|
307
|
-
command:
|
|
324
|
+
command: plan.command,
|
|
308
325
|
behaviorProfile: plan.behaviorProfile,
|
|
309
326
|
preEnterScreenDigest: settled.screenDigest,
|
|
310
327
|
preEnterEvidenceInventory: settled.evidenceInventory,
|
|
@@ -322,14 +339,24 @@ export class TerminalAgentBridge {
|
|
|
322
339
|
scrollbackLines: options.scrollbackLines
|
|
323
340
|
});
|
|
324
341
|
const screenDigest = nativeInspectionScreenFingerprint(captured.screen);
|
|
325
|
-
const
|
|
342
|
+
const adapterObservation = adapter.observeNativeInspection({
|
|
326
343
|
operation: request.operation,
|
|
327
344
|
previousScreenFingerprint: request.previousScreenFingerprint,
|
|
328
345
|
preEnterEvidenceInventory: request.preEnterEvidenceInventory,
|
|
329
346
|
expectedNativeThreadId: request.expectedNativeThreadId,
|
|
330
347
|
expectedAgentVersion: request.expectedAgentVersion,
|
|
348
|
+
expectedCwd: request.expectedCwd,
|
|
331
349
|
screen: captured.screen
|
|
332
350
|
});
|
|
351
|
+
const observation = adapter.agent === "claude" &&
|
|
352
|
+
options.runtime?.requireExactClaudeAgentRow === true &&
|
|
353
|
+
adapterObservation.status === "observed" &&
|
|
354
|
+
adapterObservation.evidence === "claude_status_panel"
|
|
355
|
+
? {
|
|
356
|
+
...adapterObservation,
|
|
357
|
+
evidence: "claude_status_panel+claude_agents_exact_pid"
|
|
358
|
+
}
|
|
359
|
+
: adapterObservation;
|
|
333
360
|
return {
|
|
334
361
|
terminalControl: captured.terminalControl,
|
|
335
362
|
status: statusFromInspection(adapter, captured.terminalControl, captured.inspection, { screen: captured.screen, runtime: options.runtime }),
|
|
@@ -337,7 +364,78 @@ export class TerminalAgentBridge {
|
|
|
337
364
|
observation
|
|
338
365
|
};
|
|
339
366
|
}
|
|
340
|
-
|
|
367
|
+
/**
|
|
368
|
+
* Dismiss one exact adapter-owned modal result after re-observing the same
|
|
369
|
+
* evidence under the terminal identity fence. There is exactly one key
|
|
370
|
+
* attempt and no automated retry across an uncertain dismissal boundary.
|
|
371
|
+
*/
|
|
372
|
+
async dismissNativeInspection(agent, terminalControl, plan, request, expectedEvidenceFingerprint, options = {}) {
|
|
373
|
+
const adapter = this.registry.require(agent);
|
|
374
|
+
try {
|
|
375
|
+
assertClosedStatusInspectionPlan(adapter, terminalControl, plan);
|
|
376
|
+
assertClosedNativeInspectionDismissal(plan);
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
throw new NativeInspectionDismissalError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
380
|
+
}
|
|
381
|
+
const observeExactPanel = async (control) => {
|
|
382
|
+
const captured = await this.captureInspection(adapter, control, {
|
|
383
|
+
runtime: options.runtime,
|
|
384
|
+
scrollbackLines: options.scrollbackLines
|
|
385
|
+
});
|
|
386
|
+
const observation = adapter.observeNativeInspection?.({
|
|
387
|
+
operation: request.operation,
|
|
388
|
+
previousScreenFingerprint: request.previousScreenFingerprint,
|
|
389
|
+
preEnterEvidenceInventory: request.preEnterEvidenceInventory,
|
|
390
|
+
expectedNativeThreadId: request.expectedNativeThreadId,
|
|
391
|
+
expectedAgentVersion: request.expectedAgentVersion,
|
|
392
|
+
expectedCwd: request.expectedCwd,
|
|
393
|
+
screen: captured.screen
|
|
394
|
+
});
|
|
395
|
+
if (!observation ||
|
|
396
|
+
observation.status !== "observed" ||
|
|
397
|
+
observation.result?.kind !== "native_status" ||
|
|
398
|
+
observation.evidenceFingerprint !== expectedEvidenceFingerprint) {
|
|
399
|
+
throw new Error(observation?.reason ??
|
|
400
|
+
"the exact fresh native status panel changed before dismissal");
|
|
401
|
+
}
|
|
402
|
+
const verified = await this.verifyTerminalIdentity(adapter.agent, captured.terminalControl, options.runtime);
|
|
403
|
+
if (!sameTerminalControlIdentity(captured.terminalControl, verified)) {
|
|
404
|
+
throw new Error("terminal control identity changed before native status dismissal");
|
|
405
|
+
}
|
|
406
|
+
return verified;
|
|
407
|
+
};
|
|
408
|
+
try {
|
|
409
|
+
let verified = await observeExactPanel(terminalControl);
|
|
410
|
+
await options.beforeDismiss?.({
|
|
411
|
+
agent: adapter.agent,
|
|
412
|
+
terminalControl: verified,
|
|
413
|
+
plan,
|
|
414
|
+
evidenceFingerprint: expectedEvidenceFingerprint
|
|
415
|
+
});
|
|
416
|
+
verified = await observeExactPanel(verified);
|
|
417
|
+
try {
|
|
418
|
+
await this.terminalProvider.sendKeys(verified.target, plan.expectedResult.dismissal.keys, { socketPath: verified.socketPath });
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
throw new NativeInspectionDismissalError("native status modal dismissal outcome is uncertain; do not retry automatically", { cause: error });
|
|
422
|
+
}
|
|
423
|
+
return {
|
|
424
|
+
stage: "dismiss_dispatched",
|
|
425
|
+
agent: adapter.agent,
|
|
426
|
+
terminalControl: verified,
|
|
427
|
+
keys: plan.expectedResult.dismissal.keys,
|
|
428
|
+
dismissCount: 1
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
if (error instanceof NativeInspectionDismissalError) {
|
|
433
|
+
throw error;
|
|
434
|
+
}
|
|
435
|
+
throw new NativeInspectionDismissalError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
async settleNativeInspectionComposer(adapter, terminalControl, plan, runtime) {
|
|
341
439
|
const startedAt = performance.now();
|
|
342
440
|
let stableDigest;
|
|
343
441
|
let stableKind;
|
|
@@ -349,7 +447,7 @@ export class TerminalAgentBridge {
|
|
|
349
447
|
scrollbackLines: CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES
|
|
350
448
|
});
|
|
351
449
|
assertNativeInspectionComposerSafe(captured.inspection);
|
|
352
|
-
const materialized =
|
|
450
|
+
const materialized = exactNativeInspectionComposerCapture(adapter.agent, captured.screen, plan);
|
|
353
451
|
const now = performance.now();
|
|
354
452
|
if (materialized) {
|
|
355
453
|
if (materialized.digest === stableDigest &&
|
|
@@ -371,7 +469,7 @@ export class TerminalAgentBridge {
|
|
|
371
469
|
stableForMs,
|
|
372
470
|
stableCaptures
|
|
373
471
|
};
|
|
374
|
-
return this.
|
|
472
|
+
return this.revalidateNativeInspectionComposer(adapter, captured.terminalControl, plan, evidence, runtime);
|
|
375
473
|
}
|
|
376
474
|
}
|
|
377
475
|
else {
|
|
@@ -387,19 +485,19 @@ export class TerminalAgentBridge {
|
|
|
387
485
|
}
|
|
388
486
|
await terminalSettleDelay(Math.min(CODEX_MULTILINE_SETTLE_POLL_MS, remaining));
|
|
389
487
|
}
|
|
390
|
-
throw new Error(
|
|
488
|
+
throw new Error(`${adapter.displayName} /status composer did not become exact, idle, and stable before the bounded submit deadline`);
|
|
391
489
|
}
|
|
392
|
-
async
|
|
490
|
+
async revalidateNativeInspectionComposer(adapter, terminalControl, plan, expected, runtime) {
|
|
393
491
|
const captured = await this.captureInspection(adapter, terminalControl, {
|
|
394
492
|
runtime,
|
|
395
493
|
scrollbackLines: CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES
|
|
396
494
|
});
|
|
397
495
|
assertNativeInspectionComposerSafe(captured.inspection);
|
|
398
|
-
const materialized =
|
|
496
|
+
const materialized = exactNativeInspectionComposerCapture(adapter.agent, captured.screen, plan);
|
|
399
497
|
if (!materialized ||
|
|
400
498
|
materialized.digest !== expected.digest ||
|
|
401
499
|
materialized.kind !== expected.kind) {
|
|
402
|
-
throw new Error(
|
|
500
|
+
throw new Error(`${adapter.displayName} /status composer changed after its stable pre-submit capture`);
|
|
403
501
|
}
|
|
404
502
|
const baseline = adapter.observeNativeInspection?.({
|
|
405
503
|
operation: plan.operation,
|
|
@@ -409,11 +507,11 @@ export class TerminalAgentBridge {
|
|
|
409
507
|
baseline.status === "ambiguous" ||
|
|
410
508
|
!Array.isArray(baseline.evidenceInventory)) {
|
|
411
509
|
throw new Error(baseline?.reason ??
|
|
412
|
-
|
|
510
|
+
`${adapter.displayName} /status pre-Enter evidence inventory was not proven`);
|
|
413
511
|
}
|
|
414
512
|
const verifiedImmediatelyBeforeEnter = await this.verifyTerminalIdentity(adapter.agent, captured.terminalControl, runtime);
|
|
415
513
|
if (!sameTerminalControlIdentity(captured.terminalControl, verifiedImmediatelyBeforeEnter)) {
|
|
416
|
-
throw new Error(
|
|
514
|
+
throw new Error(`terminal control identity changed after the final ${adapter.displayName} /status composer capture`);
|
|
417
515
|
}
|
|
418
516
|
return {
|
|
419
517
|
terminalControl: verifiedImmediatelyBeforeEnter,
|
|
@@ -958,36 +1056,55 @@ function nativeInspectionSubmissionError(stage, error) {
|
|
|
958
1056
|
}
|
|
959
1057
|
return new NativeInspectionSubmissionError(stage, error instanceof Error ? error.message : String(error), { cause: error });
|
|
960
1058
|
}
|
|
961
|
-
function
|
|
962
|
-
if (adapter.agent !== "codex") {
|
|
963
|
-
throw new Error("native /status inspection is currently supported only for Codex");
|
|
964
|
-
}
|
|
1059
|
+
function assertClosedStatusInspectionPlan(adapter, terminalControl, plan) {
|
|
965
1060
|
if (!terminalControl.capabilities.includes("send_keys")) {
|
|
966
|
-
throw new Error(
|
|
1061
|
+
throw new Error(`${adapter.displayName} terminal input is not supported`);
|
|
967
1062
|
}
|
|
968
1063
|
if (!terminalControl.capabilities.includes("screen_status")) {
|
|
969
|
-
throw new Error(
|
|
1064
|
+
throw new Error(`${adapter.displayName} terminal screen inspection is not supported`);
|
|
970
1065
|
}
|
|
1066
|
+
const codexProfile = adapter.agent === "codex" && [
|
|
1067
|
+
"codex-tui-0.146.0",
|
|
1068
|
+
"codex-tui-0.146.1"
|
|
1069
|
+
].includes(plan.behaviorProfile);
|
|
1070
|
+
const claudeProfile = adapter.agent === "claude" &&
|
|
1071
|
+
plan.behaviorProfile === "claude-code-2.1.218-native-status";
|
|
1072
|
+
const exactPresentation = codexProfile
|
|
1073
|
+
? plan.expectedResult.presentation === "inline" &&
|
|
1074
|
+
plan.expectedResult.dismissal === undefined &&
|
|
1075
|
+
plan.composer.minimumStableMs >= CODEX_PASTE_ENTER_SETTLE_MS
|
|
1076
|
+
: claudeProfile
|
|
1077
|
+
? plan.expectedResult.presentation === "modal" &&
|
|
1078
|
+
plan.composer.minimumStableMs >= 80 &&
|
|
1079
|
+
plan.expectedResult.dismissal?.expected === "idle_empty_composer" &&
|
|
1080
|
+
JSON.stringify(plan.expectedResult.dismissal.keys) ===
|
|
1081
|
+
JSON.stringify(["Escape"])
|
|
1082
|
+
: false;
|
|
971
1083
|
if (plan.operation.kind !== "status" ||
|
|
972
1084
|
plan.command !== "/status" ||
|
|
973
1085
|
plan.effect !== "read_only" ||
|
|
974
1086
|
plan.requiresIdle !== true ||
|
|
975
1087
|
plan.composer.kind !== "exact" ||
|
|
976
1088
|
!Number.isFinite(plan.composer.minimumStableMs) ||
|
|
977
|
-
plan.composer.minimumStableMs <
|
|
1089
|
+
plan.composer.minimumStableMs < 0 ||
|
|
978
1090
|
plan.expectedResult.kind !== "native_status" ||
|
|
979
|
-
!
|
|
980
|
-
"codex-tui-0.146.0",
|
|
981
|
-
"codex-tui-0.146.1"
|
|
982
|
-
].includes(plan.behaviorProfile)) {
|
|
1091
|
+
!exactPresentation) {
|
|
983
1092
|
throw new Error("refusing a non-closed or unverified native inspection plan");
|
|
984
1093
|
}
|
|
985
1094
|
}
|
|
986
|
-
function
|
|
1095
|
+
function assertClosedNativeInspectionDismissal(plan) {
|
|
1096
|
+
if (plan.expectedResult.presentation !== "modal" ||
|
|
1097
|
+
plan.expectedResult.dismissal?.expected !== "idle_empty_composer" ||
|
|
1098
|
+
JSON.stringify(plan.expectedResult.dismissal.keys) !==
|
|
1099
|
+
JSON.stringify(["Escape"])) {
|
|
1100
|
+
throw new Error("native inspection has no closed verified modal dismissal plan");
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
function assertNativeInspectionComposerSafe(inspection, displayName = "terminal agent") {
|
|
987
1104
|
if (inspection.approval.blocked ||
|
|
988
1105
|
inspection.activity.state === "awaiting_approval" ||
|
|
989
1106
|
inspection.activity.state === "working") {
|
|
990
|
-
throw new Error(
|
|
1107
|
+
throw new Error(`${displayName} became busy or blocked while its /status composer was settling`);
|
|
991
1108
|
}
|
|
992
1109
|
// Codex's generic activity parser deliberately reports a non-empty slash
|
|
993
1110
|
// composer as unknown. At this stage the caller has already proved an idle,
|
|
@@ -995,6 +1112,11 @@ function assertNativeInspectionComposerSafe(inspection) {
|
|
|
995
1112
|
// capture below is the stronger continuation proof after AKK injected only
|
|
996
1113
|
// the adapter-owned /status command.
|
|
997
1114
|
}
|
|
1115
|
+
function exactNativeInspectionComposerCapture(agent, screen, plan) {
|
|
1116
|
+
return agent === "codex"
|
|
1117
|
+
? exactCodexNativeInspectionComposerCapture(screen, plan)
|
|
1118
|
+
: exactClaudeNativeInspectionComposerCapture(screen, plan);
|
|
1119
|
+
}
|
|
998
1120
|
function exactCodexNativeInspectionComposerCapture(screen, plan) {
|
|
999
1121
|
const lines = screen.replace(/\r\n?/gu, "\n").split("\n");
|
|
1000
1122
|
let currentComposerIndex = -1;
|
|
@@ -1037,6 +1159,88 @@ function exactCodexNativeInspectionComposerCapture(screen, plan) {
|
|
|
1037
1159
|
digest: createHash("sha256").update(region.join("\n")).digest("hex")
|
|
1038
1160
|
};
|
|
1039
1161
|
}
|
|
1162
|
+
function exactClaudeNativeInspectionComposerCapture(screen, plan) {
|
|
1163
|
+
const frame = exactClaudeNativeInspectionComposerFrame(screen);
|
|
1164
|
+
if (!frame) {
|
|
1165
|
+
return undefined;
|
|
1166
|
+
}
|
|
1167
|
+
const { lines, openIndex, closeIndex, composerRows, trailing } = frame;
|
|
1168
|
+
if (composerRows.length !== 1 ||
|
|
1169
|
+
!/^\s*❯(?:\s|$)/u.test(composerRows[0]) ||
|
|
1170
|
+
composerRows[0].replace(/^\s*❯\s?/u, "").trimEnd() !== plan.command) {
|
|
1171
|
+
return undefined;
|
|
1172
|
+
}
|
|
1173
|
+
let kind;
|
|
1174
|
+
if (trailing.length === 0 || claudeNativeInspectionTrailingIsFooter(trailing)) {
|
|
1175
|
+
kind = "exact_slash_composer";
|
|
1176
|
+
}
|
|
1177
|
+
else {
|
|
1178
|
+
const suggestions = [];
|
|
1179
|
+
for (const line of trailing) {
|
|
1180
|
+
const trimmed = line.trim();
|
|
1181
|
+
if (trimmed.startsWith("/")) {
|
|
1182
|
+
suggestions.push(trimmed.replace(/\s+/gu, " "));
|
|
1183
|
+
}
|
|
1184
|
+
else if (suggestions.length > 0) {
|
|
1185
|
+
suggestions[suggestions.length - 1] +=
|
|
1186
|
+
` ${trimmed.replace(/\s+/gu, " ")}`;
|
|
1187
|
+
}
|
|
1188
|
+
else {
|
|
1189
|
+
return undefined;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
if (JSON.stringify(suggestions) !== JSON.stringify(CLAUDE_NATIVE_STATUS_POPUP_BY_PROFILE[plan.behaviorProfile])) {
|
|
1193
|
+
return undefined;
|
|
1194
|
+
}
|
|
1195
|
+
kind = "exact_slash_popup";
|
|
1196
|
+
}
|
|
1197
|
+
return {
|
|
1198
|
+
kind,
|
|
1199
|
+
digest: createHash("sha256")
|
|
1200
|
+
.update(lines.slice(openIndex, closeIndex + 1).concat(trailing).join("\n"))
|
|
1201
|
+
.digest("hex")
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Prove the exact idle input frame from the verified Claude Code 2.1.218
|
|
1206
|
+
* profile. A loose or historical `❯` prompt is not enough to authorize even
|
|
1207
|
+
* the initial literal `/status` injection.
|
|
1208
|
+
*/
|
|
1209
|
+
export function isExactClaudeNativeInspectionIdleComposer(screen) {
|
|
1210
|
+
const frame = exactClaudeNativeInspectionComposerFrame(screen);
|
|
1211
|
+
if (!frame) {
|
|
1212
|
+
return false;
|
|
1213
|
+
}
|
|
1214
|
+
return (frame.composerRows.length === 1 &&
|
|
1215
|
+
/^\s*❯\s*$/u.test(frame.composerRows[0]) &&
|
|
1216
|
+
(frame.trailing.length === 0 ||
|
|
1217
|
+
claudeNativeInspectionTrailingIsFooter(frame.trailing)));
|
|
1218
|
+
}
|
|
1219
|
+
function exactClaudeNativeInspectionComposerFrame(screen) {
|
|
1220
|
+
const lines = screen.replace(/\r\n?/gu, "\n").replace(/\u00a0/gu, " ")
|
|
1221
|
+
.split("\n");
|
|
1222
|
+
const dividerIndexes = lines
|
|
1223
|
+
.map((line, index) => /^\s*[─━]{8,}\s*$/u.test(line) ? index : -1)
|
|
1224
|
+
.filter((index) => index >= 0);
|
|
1225
|
+
if (dividerIndexes.length < 2) {
|
|
1226
|
+
return undefined;
|
|
1227
|
+
}
|
|
1228
|
+
const closeIndex = dividerIndexes.at(-1);
|
|
1229
|
+
const openIndex = dividerIndexes.at(-2);
|
|
1230
|
+
return {
|
|
1231
|
+
lines,
|
|
1232
|
+
openIndex,
|
|
1233
|
+
closeIndex,
|
|
1234
|
+
composerRows: lines.slice(openIndex + 1, closeIndex)
|
|
1235
|
+
.filter((line) => line.trim().length > 0),
|
|
1236
|
+
trailing: lines.slice(closeIndex + 1)
|
|
1237
|
+
.filter((line) => line.trim().length > 0)
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
function claudeNativeInspectionTrailingIsFooter(lines) {
|
|
1241
|
+
return lines.length <= 2 && lines.every((line) => /^\s*(?:[⏵⏴]{1,2}|\?)\s*.*(?:shift\+tab|accept edits|bypass permissions|for shortcuts|← for agents)/iu
|
|
1242
|
+
.test(line));
|
|
1243
|
+
}
|
|
1040
1244
|
function nativeInspectionScreenFingerprint(screen) {
|
|
1041
1245
|
return `sha256:${createHash("sha256").update(screen).digest("hex")}`;
|
|
1042
1246
|
}
|