@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
|
@@ -117,6 +117,15 @@ export interface TerminalRuntimeIdentity {
|
|
|
117
117
|
* authorize terminal side effects by itself.
|
|
118
118
|
*/
|
|
119
119
|
requireNativeProcessUuid?: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Native inspection of Claude requires the same unique interactive
|
|
122
|
+
* `claude agents --json --all` row to remain idle at every bridge identity
|
|
123
|
+
* fence. The process-incarnation timestamp is part of that exact row.
|
|
124
|
+
*/
|
|
125
|
+
requireExactClaudeAgentRow?: boolean;
|
|
126
|
+
nativeProcessStartedAt?: number;
|
|
127
|
+
/** Exact Claude agents state permitted at this bridge phase. */
|
|
128
|
+
exactClaudeAgentState?: "idle" | "status_dialog";
|
|
120
129
|
/**
|
|
121
130
|
* Current Codex turns require the exact open root rollout descriptor and
|
|
122
131
|
* process-birth evidence. A session id by itself is not an incarnation
|
|
@@ -283,9 +292,18 @@ export interface TerminalNativeInspectionPlan {
|
|
|
283
292
|
composer: {
|
|
284
293
|
kind: "exact";
|
|
285
294
|
minimumStableMs: number;
|
|
295
|
+
/** Version-profiled upper bound for exact composer materialization. */
|
|
296
|
+
maximumSettleMs: number;
|
|
286
297
|
};
|
|
287
298
|
expectedResult: {
|
|
288
299
|
kind: "native_status";
|
|
300
|
+
/** Inline cards remain at the prompt; modal panels must be dismissed. */
|
|
301
|
+
presentation: "inline" | "modal";
|
|
302
|
+
/** Closed adapter-owned dismissal sequence for a verified modal result. */
|
|
303
|
+
dismissal?: {
|
|
304
|
+
keys: readonly string[];
|
|
305
|
+
expected: "idle_empty_composer";
|
|
306
|
+
};
|
|
289
307
|
};
|
|
290
308
|
}
|
|
291
309
|
export interface TerminalNativeInspectionField {
|
|
@@ -328,6 +346,8 @@ export interface TerminalNativeInspectionObservationRequest {
|
|
|
328
346
|
expectedNativeThreadId?: string;
|
|
329
347
|
/** Optional exact running version that the status surface must confirm. */
|
|
330
348
|
expectedAgentVersion?: string;
|
|
349
|
+
/** Optional exact working directory that the status surface must confirm. */
|
|
350
|
+
expectedCwd?: string;
|
|
331
351
|
}
|
|
332
352
|
export interface TerminalNativeInspectionObservation {
|
|
333
353
|
status: "observed" | "missing" | "ambiguous" | "stale" | "mismatch";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal-agent-adapter.js","sourceRoot":"","sources":["../../src/terminal-agent-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAqB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"terminal-agent-adapter.js","sourceRoot":"","sources":["../../src/terminal-agent-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -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 a verified Claude Code native-status
|
|
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;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { formatTerminalConversationId, parseTerminalConversationId, terminalControlCapabilitiesForAdapter } from "./terminal-agent-adapter.js";
|
|
3
3
|
import { enrichActiveProcessesWithTerminalControl, TerminalControlInputNotSentError, terminalRefFromPane } from "./terminal-control-provider.js";
|
|
4
|
-
// Codex 0.
|
|
5
|
-
// Cross that boundary rather than landing on it, and
|
|
6
|
-
// composer stability instead of treating this delay
|
|
4
|
+
// Verified Codex profiles through 0.147.0 keep Enter in paste/newline mode for
|
|
5
|
+
// 120ms after burst input. Cross that boundary rather than landing on it, and
|
|
6
|
+
// also require observable composer stability instead of treating this delay
|
|
7
|
+
// alone as acceptance.
|
|
7
8
|
const CODEX_PASTE_ENTER_SETTLE_MS = 121;
|
|
8
9
|
const CODEX_MULTILINE_SETTLE_POLL_MS = 30;
|
|
9
10
|
const CODEX_MULTILINE_SETTLE_TIMEOUT_MS = 2_000;
|
|
@@ -11,12 +12,34 @@ const CODEX_MULTILINE_STABLE_CAPTURES = 2;
|
|
|
11
12
|
const CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES = 240;
|
|
12
13
|
const CODEX_COMPOSER_MARKER = /^[›»](?:\s|$)/u;
|
|
13
14
|
const CODEX_COMPOSER_FOOTER = /^(?:gpt-[\w.-]+(?:\s|$)|[-\w.]+ default ·)/u;
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
15
|
+
// Keep every exact slash-completion shape closed per behavior profile so a
|
|
16
|
+
// version adding another matching command cannot silently become an authorized
|
|
17
|
+
// native command surface.
|
|
17
18
|
const CODEX_NATIVE_STATUS_POPUP_BY_PROFILE = {
|
|
18
|
-
"codex-tui-0.146.0":
|
|
19
|
-
|
|
19
|
+
"codex-tui-0.146.0": [
|
|
20
|
+
" /status show current session configuration and token usage"
|
|
21
|
+
],
|
|
22
|
+
"codex-tui-0.146.1": [
|
|
23
|
+
" /status show current session configuration and token usage"
|
|
24
|
+
],
|
|
25
|
+
"codex-tui-0.147.0": [
|
|
26
|
+
" /status show current session configuration and token usage",
|
|
27
|
+
" /statusline configure which items appear in the status line"
|
|
28
|
+
]
|
|
29
|
+
};
|
|
30
|
+
const CLAUDE_NATIVE_STATUS_POPUP_BY_PROFILE = {
|
|
31
|
+
"claude-code-2.1.218-native-status": [
|
|
32
|
+
"/status Show Claude Code status including version, model, account, API connectivity, and tool statuses",
|
|
33
|
+
"/statusline Set up Claude Code's status line UI",
|
|
34
|
+
"/ide Manage IDE integrations and show status",
|
|
35
|
+
"/usage Show session cost, plan usage, and activity stats"
|
|
36
|
+
],
|
|
37
|
+
"claude-code-2.1.226-native-status": [
|
|
38
|
+
"/status Show Claude Code status including version, model, account, API connectivity, and tool statuses",
|
|
39
|
+
"/statusline Set up Claude Code's status line UI",
|
|
40
|
+
"/ide Manage IDE integrations and show status",
|
|
41
|
+
"/usage Show session cost, plan usage, and activity stats"
|
|
42
|
+
]
|
|
20
43
|
};
|
|
21
44
|
const CODEX_LARGE_PASTE_CHAR_THRESHOLD = 1_000;
|
|
22
45
|
/** A terminal send failed at a boundary that proves input never started. */
|
|
@@ -45,6 +68,15 @@ export class NativeInspectionSubmissionError extends Error {
|
|
|
45
68
|
this.doNotRetry = stage !== "not_started";
|
|
46
69
|
}
|
|
47
70
|
}
|
|
71
|
+
/** A verified modal could not be dismissed across one exact key attempt. */
|
|
72
|
+
export class NativeInspectionDismissalError extends Error {
|
|
73
|
+
code = "AKK_NATIVE_INSPECTION_DISMISSAL_FAILED";
|
|
74
|
+
doNotRetry = true;
|
|
75
|
+
constructor(message, options = {}) {
|
|
76
|
+
super(message, options);
|
|
77
|
+
this.name = "NativeInspectionDismissalError";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
48
80
|
export class TerminalAgentBridge {
|
|
49
81
|
registry;
|
|
50
82
|
terminalProvider;
|
|
@@ -254,7 +286,7 @@ export class TerminalAgentBridge {
|
|
|
254
286
|
async submitNativeInspection(agent, terminalControl, plan, options = {}) {
|
|
255
287
|
const adapter = this.registry.require(agent);
|
|
256
288
|
try {
|
|
257
|
-
|
|
289
|
+
assertClosedStatusInspectionPlan(adapter, terminalControl, plan);
|
|
258
290
|
}
|
|
259
291
|
catch (error) {
|
|
260
292
|
throw nativeInspectionSubmissionError("not_started", error);
|
|
@@ -279,15 +311,15 @@ export class TerminalAgentBridge {
|
|
|
279
311
|
}
|
|
280
312
|
let settled;
|
|
281
313
|
try {
|
|
282
|
-
settled = await this.
|
|
314
|
+
settled = await this.settleNativeInspectionComposer(adapter, terminalControl, plan, options.runtime);
|
|
283
315
|
await options.beforeEnter?.({
|
|
284
|
-
agent:
|
|
316
|
+
agent: adapter.agent,
|
|
285
317
|
terminalControl: settled.terminalControl,
|
|
286
318
|
plan,
|
|
287
319
|
preEnterScreenDigest: settled.screenDigest,
|
|
288
320
|
materialization: settled.materialization
|
|
289
321
|
});
|
|
290
|
-
settled = await this.
|
|
322
|
+
settled = await this.revalidateNativeInspectionComposer(adapter, settled.terminalControl, plan, settled.materialization, options.runtime);
|
|
291
323
|
}
|
|
292
324
|
catch (error) {
|
|
293
325
|
throw nativeInspectionSubmissionError("text_injected", error);
|
|
@@ -302,9 +334,9 @@ export class TerminalAgentBridge {
|
|
|
302
334
|
}
|
|
303
335
|
return {
|
|
304
336
|
stage: "enter_dispatched",
|
|
305
|
-
agent:
|
|
337
|
+
agent: adapter.agent,
|
|
306
338
|
terminalControl: settled.terminalControl,
|
|
307
|
-
command:
|
|
339
|
+
command: plan.command,
|
|
308
340
|
behaviorProfile: plan.behaviorProfile,
|
|
309
341
|
preEnterScreenDigest: settled.screenDigest,
|
|
310
342
|
preEnterEvidenceInventory: settled.evidenceInventory,
|
|
@@ -322,14 +354,24 @@ export class TerminalAgentBridge {
|
|
|
322
354
|
scrollbackLines: options.scrollbackLines
|
|
323
355
|
});
|
|
324
356
|
const screenDigest = nativeInspectionScreenFingerprint(captured.screen);
|
|
325
|
-
const
|
|
357
|
+
const adapterObservation = adapter.observeNativeInspection({
|
|
326
358
|
operation: request.operation,
|
|
327
359
|
previousScreenFingerprint: request.previousScreenFingerprint,
|
|
328
360
|
preEnterEvidenceInventory: request.preEnterEvidenceInventory,
|
|
329
361
|
expectedNativeThreadId: request.expectedNativeThreadId,
|
|
330
362
|
expectedAgentVersion: request.expectedAgentVersion,
|
|
363
|
+
expectedCwd: request.expectedCwd,
|
|
331
364
|
screen: captured.screen
|
|
332
365
|
});
|
|
366
|
+
const observation = adapter.agent === "claude" &&
|
|
367
|
+
options.runtime?.requireExactClaudeAgentRow === true &&
|
|
368
|
+
adapterObservation.status === "observed" &&
|
|
369
|
+
adapterObservation.evidence === "claude_status_panel"
|
|
370
|
+
? {
|
|
371
|
+
...adapterObservation,
|
|
372
|
+
evidence: "claude_status_panel+claude_agents_exact_pid"
|
|
373
|
+
}
|
|
374
|
+
: adapterObservation;
|
|
333
375
|
return {
|
|
334
376
|
terminalControl: captured.terminalControl,
|
|
335
377
|
status: statusFromInspection(adapter, captured.terminalControl, captured.inspection, { screen: captured.screen, runtime: options.runtime }),
|
|
@@ -337,19 +379,91 @@ export class TerminalAgentBridge {
|
|
|
337
379
|
observation
|
|
338
380
|
};
|
|
339
381
|
}
|
|
340
|
-
|
|
382
|
+
/**
|
|
383
|
+
* Dismiss one exact adapter-owned modal result after re-observing the same
|
|
384
|
+
* evidence under the terminal identity fence. There is exactly one key
|
|
385
|
+
* attempt and no automated retry across an uncertain dismissal boundary.
|
|
386
|
+
*/
|
|
387
|
+
async dismissNativeInspection(agent, terminalControl, plan, request, expectedEvidenceFingerprint, options = {}) {
|
|
388
|
+
const adapter = this.registry.require(agent);
|
|
389
|
+
try {
|
|
390
|
+
assertClosedStatusInspectionPlan(adapter, terminalControl, plan);
|
|
391
|
+
assertClosedNativeInspectionDismissal(plan);
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
throw new NativeInspectionDismissalError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
395
|
+
}
|
|
396
|
+
const observeExactPanel = async (control) => {
|
|
397
|
+
const captured = await this.captureInspection(adapter, control, {
|
|
398
|
+
runtime: options.runtime,
|
|
399
|
+
scrollbackLines: options.scrollbackLines
|
|
400
|
+
});
|
|
401
|
+
const observation = adapter.observeNativeInspection?.({
|
|
402
|
+
operation: request.operation,
|
|
403
|
+
previousScreenFingerprint: request.previousScreenFingerprint,
|
|
404
|
+
preEnterEvidenceInventory: request.preEnterEvidenceInventory,
|
|
405
|
+
expectedNativeThreadId: request.expectedNativeThreadId,
|
|
406
|
+
expectedAgentVersion: request.expectedAgentVersion,
|
|
407
|
+
expectedCwd: request.expectedCwd,
|
|
408
|
+
screen: captured.screen
|
|
409
|
+
});
|
|
410
|
+
if (!observation ||
|
|
411
|
+
observation.status !== "observed" ||
|
|
412
|
+
observation.result?.kind !== "native_status" ||
|
|
413
|
+
observation.evidenceFingerprint !== expectedEvidenceFingerprint) {
|
|
414
|
+
throw new Error(observation?.reason ??
|
|
415
|
+
"the exact fresh native status panel changed before dismissal");
|
|
416
|
+
}
|
|
417
|
+
const verified = await this.verifyTerminalIdentity(adapter.agent, captured.terminalControl, options.runtime);
|
|
418
|
+
if (!sameTerminalControlIdentity(captured.terminalControl, verified)) {
|
|
419
|
+
throw new Error("terminal control identity changed before native status dismissal");
|
|
420
|
+
}
|
|
421
|
+
return verified;
|
|
422
|
+
};
|
|
423
|
+
try {
|
|
424
|
+
let verified = await observeExactPanel(terminalControl);
|
|
425
|
+
await options.beforeDismiss?.({
|
|
426
|
+
agent: adapter.agent,
|
|
427
|
+
terminalControl: verified,
|
|
428
|
+
plan,
|
|
429
|
+
evidenceFingerprint: expectedEvidenceFingerprint
|
|
430
|
+
});
|
|
431
|
+
verified = await observeExactPanel(verified);
|
|
432
|
+
try {
|
|
433
|
+
await this.terminalProvider.sendKeys(verified.target, plan.expectedResult.dismissal.keys, { socketPath: verified.socketPath });
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
throw new NativeInspectionDismissalError("native status modal dismissal outcome is uncertain; do not retry automatically", { cause: error });
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
stage: "dismiss_dispatched",
|
|
440
|
+
agent: adapter.agent,
|
|
441
|
+
terminalControl: verified,
|
|
442
|
+
keys: plan.expectedResult.dismissal.keys,
|
|
443
|
+
dismissCount: 1
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
if (error instanceof NativeInspectionDismissalError) {
|
|
448
|
+
throw error;
|
|
449
|
+
}
|
|
450
|
+
throw new NativeInspectionDismissalError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
async settleNativeInspectionComposer(adapter, terminalControl, plan, runtime) {
|
|
341
454
|
const startedAt = performance.now();
|
|
455
|
+
const settleTimeoutMs = plan.composer.maximumSettleMs;
|
|
342
456
|
let stableDigest;
|
|
343
457
|
let stableKind;
|
|
344
458
|
let stableSince;
|
|
345
459
|
let stableCaptures = 0;
|
|
346
|
-
while (performance.now() - startedAt <=
|
|
460
|
+
while (performance.now() - startedAt <= settleTimeoutMs) {
|
|
347
461
|
const captured = await this.captureInspection(adapter, terminalControl, {
|
|
348
462
|
runtime,
|
|
349
463
|
scrollbackLines: CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES
|
|
350
464
|
});
|
|
351
465
|
assertNativeInspectionComposerSafe(captured.inspection);
|
|
352
|
-
const materialized =
|
|
466
|
+
const materialized = exactNativeInspectionComposerCapture(adapter.agent, captured.screen, plan);
|
|
353
467
|
const now = performance.now();
|
|
354
468
|
if (materialized) {
|
|
355
469
|
if (materialized.digest === stableDigest &&
|
|
@@ -371,7 +485,7 @@ export class TerminalAgentBridge {
|
|
|
371
485
|
stableForMs,
|
|
372
486
|
stableCaptures
|
|
373
487
|
};
|
|
374
|
-
return this.
|
|
488
|
+
return this.revalidateNativeInspectionComposer(adapter, captured.terminalControl, plan, evidence, runtime);
|
|
375
489
|
}
|
|
376
490
|
}
|
|
377
491
|
else {
|
|
@@ -381,25 +495,25 @@ export class TerminalAgentBridge {
|
|
|
381
495
|
stableCaptures = 0;
|
|
382
496
|
}
|
|
383
497
|
const elapsed = performance.now() - startedAt;
|
|
384
|
-
const remaining =
|
|
498
|
+
const remaining = settleTimeoutMs - elapsed;
|
|
385
499
|
if (remaining <= 0) {
|
|
386
500
|
break;
|
|
387
501
|
}
|
|
388
502
|
await terminalSettleDelay(Math.min(CODEX_MULTILINE_SETTLE_POLL_MS, remaining));
|
|
389
503
|
}
|
|
390
|
-
throw new Error(
|
|
504
|
+
throw new Error(`${adapter.displayName} /status composer did not become exact, idle, and stable before the bounded submit deadline`);
|
|
391
505
|
}
|
|
392
|
-
async
|
|
506
|
+
async revalidateNativeInspectionComposer(adapter, terminalControl, plan, expected, runtime) {
|
|
393
507
|
const captured = await this.captureInspection(adapter, terminalControl, {
|
|
394
508
|
runtime,
|
|
395
509
|
scrollbackLines: CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES
|
|
396
510
|
});
|
|
397
511
|
assertNativeInspectionComposerSafe(captured.inspection);
|
|
398
|
-
const materialized =
|
|
512
|
+
const materialized = exactNativeInspectionComposerCapture(adapter.agent, captured.screen, plan);
|
|
399
513
|
if (!materialized ||
|
|
400
514
|
materialized.digest !== expected.digest ||
|
|
401
515
|
materialized.kind !== expected.kind) {
|
|
402
|
-
throw new Error(
|
|
516
|
+
throw new Error(`${adapter.displayName} /status composer changed after its stable pre-submit capture`);
|
|
403
517
|
}
|
|
404
518
|
const baseline = adapter.observeNativeInspection?.({
|
|
405
519
|
operation: plan.operation,
|
|
@@ -409,11 +523,11 @@ export class TerminalAgentBridge {
|
|
|
409
523
|
baseline.status === "ambiguous" ||
|
|
410
524
|
!Array.isArray(baseline.evidenceInventory)) {
|
|
411
525
|
throw new Error(baseline?.reason ??
|
|
412
|
-
|
|
526
|
+
`${adapter.displayName} /status pre-Enter evidence inventory was not proven`);
|
|
413
527
|
}
|
|
414
528
|
const verifiedImmediatelyBeforeEnter = await this.verifyTerminalIdentity(adapter.agent, captured.terminalControl, runtime);
|
|
415
529
|
if (!sameTerminalControlIdentity(captured.terminalControl, verifiedImmediatelyBeforeEnter)) {
|
|
416
|
-
throw new Error(
|
|
530
|
+
throw new Error(`terminal control identity changed after the final ${adapter.displayName} /status composer capture`);
|
|
417
531
|
}
|
|
418
532
|
return {
|
|
419
533
|
terminalControl: verifiedImmediatelyBeforeEnter,
|
|
@@ -958,36 +1072,64 @@ function nativeInspectionSubmissionError(stage, error) {
|
|
|
958
1072
|
}
|
|
959
1073
|
return new NativeInspectionSubmissionError(stage, error instanceof Error ? error.message : String(error), { cause: error });
|
|
960
1074
|
}
|
|
961
|
-
function
|
|
962
|
-
if (adapter.agent !== "codex") {
|
|
963
|
-
throw new Error("native /status inspection is currently supported only for Codex");
|
|
964
|
-
}
|
|
1075
|
+
function assertClosedStatusInspectionPlan(adapter, terminalControl, plan) {
|
|
965
1076
|
if (!terminalControl.capabilities.includes("send_keys")) {
|
|
966
|
-
throw new Error(
|
|
1077
|
+
throw new Error(`${adapter.displayName} terminal input is not supported`);
|
|
967
1078
|
}
|
|
968
1079
|
if (!terminalControl.capabilities.includes("screen_status")) {
|
|
969
|
-
throw new Error(
|
|
1080
|
+
throw new Error(`${adapter.displayName} terminal screen inspection is not supported`);
|
|
970
1081
|
}
|
|
1082
|
+
const codexProfile = adapter.agent === "codex" &&
|
|
1083
|
+
CODEX_NATIVE_STATUS_POPUP_BY_PROFILE[plan.behaviorProfile] !== undefined;
|
|
1084
|
+
const claudeProfile = adapter.agent === "claude" &&
|
|
1085
|
+
CLAUDE_NATIVE_STATUS_POPUP_BY_PROFILE[plan.behaviorProfile] !== undefined;
|
|
1086
|
+
const expectedSettle = codexProfile
|
|
1087
|
+
? { minimumStableMs: CODEX_PASTE_ENTER_SETTLE_MS, maximumSettleMs: 2_000 }
|
|
1088
|
+
: plan.behaviorProfile === "claude-code-2.1.218-native-status"
|
|
1089
|
+
? { minimumStableMs: 80, maximumSettleMs: 2_000 }
|
|
1090
|
+
: plan.behaviorProfile === "claude-code-2.1.226-native-status"
|
|
1091
|
+
? { minimumStableMs: 80, maximumSettleMs: 5_000 }
|
|
1092
|
+
: undefined;
|
|
1093
|
+
const exactPresentation = codexProfile
|
|
1094
|
+
? plan.expectedResult.presentation === "inline" &&
|
|
1095
|
+
plan.expectedResult.dismissal === undefined &&
|
|
1096
|
+
plan.composer.minimumStableMs === expectedSettle?.minimumStableMs &&
|
|
1097
|
+
plan.composer.maximumSettleMs === expectedSettle.maximumSettleMs
|
|
1098
|
+
: claudeProfile
|
|
1099
|
+
? plan.expectedResult.presentation === "modal" &&
|
|
1100
|
+
plan.composer.minimumStableMs === expectedSettle?.minimumStableMs &&
|
|
1101
|
+
plan.composer.maximumSettleMs === expectedSettle.maximumSettleMs &&
|
|
1102
|
+
plan.expectedResult.dismissal?.expected === "idle_empty_composer" &&
|
|
1103
|
+
JSON.stringify(plan.expectedResult.dismissal.keys) ===
|
|
1104
|
+
JSON.stringify(["Escape"])
|
|
1105
|
+
: false;
|
|
971
1106
|
if (plan.operation.kind !== "status" ||
|
|
972
1107
|
plan.command !== "/status" ||
|
|
973
1108
|
plan.effect !== "read_only" ||
|
|
974
1109
|
plan.requiresIdle !== true ||
|
|
975
1110
|
plan.composer.kind !== "exact" ||
|
|
976
1111
|
!Number.isFinite(plan.composer.minimumStableMs) ||
|
|
977
|
-
plan.composer.minimumStableMs <
|
|
1112
|
+
plan.composer.minimumStableMs < 0 ||
|
|
1113
|
+
!Number.isFinite(plan.composer.maximumSettleMs) ||
|
|
1114
|
+
plan.composer.maximumSettleMs < plan.composer.minimumStableMs ||
|
|
978
1115
|
plan.expectedResult.kind !== "native_status" ||
|
|
979
|
-
!
|
|
980
|
-
"codex-tui-0.146.0",
|
|
981
|
-
"codex-tui-0.146.1"
|
|
982
|
-
].includes(plan.behaviorProfile)) {
|
|
1116
|
+
!exactPresentation) {
|
|
983
1117
|
throw new Error("refusing a non-closed or unverified native inspection plan");
|
|
984
1118
|
}
|
|
985
1119
|
}
|
|
986
|
-
function
|
|
1120
|
+
function assertClosedNativeInspectionDismissal(plan) {
|
|
1121
|
+
if (plan.expectedResult.presentation !== "modal" ||
|
|
1122
|
+
plan.expectedResult.dismissal?.expected !== "idle_empty_composer" ||
|
|
1123
|
+
JSON.stringify(plan.expectedResult.dismissal.keys) !==
|
|
1124
|
+
JSON.stringify(["Escape"])) {
|
|
1125
|
+
throw new Error("native inspection has no closed verified modal dismissal plan");
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
function assertNativeInspectionComposerSafe(inspection, displayName = "terminal agent") {
|
|
987
1129
|
if (inspection.approval.blocked ||
|
|
988
1130
|
inspection.activity.state === "awaiting_approval" ||
|
|
989
1131
|
inspection.activity.state === "working") {
|
|
990
|
-
throw new Error(
|
|
1132
|
+
throw new Error(`${displayName} became busy or blocked while its /status composer was settling`);
|
|
991
1133
|
}
|
|
992
1134
|
// Codex's generic activity parser deliberately reports a non-empty slash
|
|
993
1135
|
// composer as unknown. At this stage the caller has already proved an idle,
|
|
@@ -995,6 +1137,11 @@ function assertNativeInspectionComposerSafe(inspection) {
|
|
|
995
1137
|
// capture below is the stronger continuation proof after AKK injected only
|
|
996
1138
|
// the adapter-owned /status command.
|
|
997
1139
|
}
|
|
1140
|
+
function exactNativeInspectionComposerCapture(agent, screen, plan) {
|
|
1141
|
+
return agent === "codex"
|
|
1142
|
+
? exactCodexNativeInspectionComposerCapture(screen, plan)
|
|
1143
|
+
: exactClaudeNativeInspectionComposerCapture(screen, plan);
|
|
1144
|
+
}
|
|
998
1145
|
function exactCodexNativeInspectionComposerCapture(screen, plan) {
|
|
999
1146
|
const lines = screen.replace(/\r\n?/gu, "\n").split("\n");
|
|
1000
1147
|
let currentComposerIndex = -1;
|
|
@@ -1024,9 +1171,8 @@ function exactCodexNativeInspectionComposerCapture(screen, plan) {
|
|
|
1024
1171
|
if (popupRows.length === 0) {
|
|
1025
1172
|
kind = "exact_slash_composer";
|
|
1026
1173
|
}
|
|
1027
|
-
else if (popupRows.
|
|
1028
|
-
|
|
1029
|
-
CODEX_NATIVE_STATUS_POPUP_BY_PROFILE[plan.behaviorProfile]) {
|
|
1174
|
+
else if (JSON.stringify(popupRows.map((line) => line.trimEnd())) ===
|
|
1175
|
+
JSON.stringify(CODEX_NATIVE_STATUS_POPUP_BY_PROFILE[plan.behaviorProfile])) {
|
|
1030
1176
|
kind = "exact_slash_popup";
|
|
1031
1177
|
}
|
|
1032
1178
|
else {
|
|
@@ -1037,6 +1183,114 @@ function exactCodexNativeInspectionComposerCapture(screen, plan) {
|
|
|
1037
1183
|
digest: createHash("sha256").update(region.join("\n")).digest("hex")
|
|
1038
1184
|
};
|
|
1039
1185
|
}
|
|
1186
|
+
function exactClaudeNativeInspectionComposerCapture(screen, plan) {
|
|
1187
|
+
const frame = exactClaudeNativeInspectionComposerFrame(screen);
|
|
1188
|
+
if (!frame) {
|
|
1189
|
+
return undefined;
|
|
1190
|
+
}
|
|
1191
|
+
const { lines, openIndex, closeIndex, composerRows, trailing } = frame;
|
|
1192
|
+
if (composerRows.length !== 1 ||
|
|
1193
|
+
!/^\s*❯(?:\s|$)/u.test(composerRows[0]) ||
|
|
1194
|
+
composerRows[0].replace(/^\s*❯\s?/u, "").trimEnd() !== plan.command) {
|
|
1195
|
+
return undefined;
|
|
1196
|
+
}
|
|
1197
|
+
let kind;
|
|
1198
|
+
if (trailing.length === 0 || claudeNativeInspectionTrailingIsFooter(trailing)) {
|
|
1199
|
+
kind = "exact_slash_composer";
|
|
1200
|
+
}
|
|
1201
|
+
else {
|
|
1202
|
+
const suggestions = [];
|
|
1203
|
+
for (const line of trailing) {
|
|
1204
|
+
const trimmed = line.trim();
|
|
1205
|
+
if (trimmed.startsWith("/")) {
|
|
1206
|
+
suggestions.push(trimmed.replace(/\s+/gu, " "));
|
|
1207
|
+
}
|
|
1208
|
+
else if (suggestions.length > 0) {
|
|
1209
|
+
suggestions[suggestions.length - 1] +=
|
|
1210
|
+
` ${trimmed.replace(/\s+/gu, " ")}`;
|
|
1211
|
+
}
|
|
1212
|
+
else {
|
|
1213
|
+
return undefined;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
if (!closedClaudeNativeStatusSuggestionsMatch(suggestions, CLAUDE_NATIVE_STATUS_POPUP_BY_PROFILE[plan.behaviorProfile])) {
|
|
1217
|
+
return undefined;
|
|
1218
|
+
}
|
|
1219
|
+
kind = "exact_slash_popup";
|
|
1220
|
+
}
|
|
1221
|
+
return {
|
|
1222
|
+
kind,
|
|
1223
|
+
digest: createHash("sha256")
|
|
1224
|
+
.update(lines.slice(openIndex, closeIndex + 1).concat(trailing).join("\n"))
|
|
1225
|
+
.digest("hex")
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Claude truncates a suggestion row with a Unicode ellipsis at narrow pane
|
|
1230
|
+
* widths. Keep authorization closed by accepting only an exact ordered row or
|
|
1231
|
+
* an explicit ellipsis whose preceding text is an exact, non-trivial prefix of
|
|
1232
|
+
* that same profiled row. A caller still cannot introduce, omit, or reorder a
|
|
1233
|
+
* slash command.
|
|
1234
|
+
*/
|
|
1235
|
+
function closedClaudeNativeStatusSuggestionsMatch(observed, expected) {
|
|
1236
|
+
if (!expected || observed.length !== expected.length) {
|
|
1237
|
+
return false;
|
|
1238
|
+
}
|
|
1239
|
+
return observed.every((row, index) => {
|
|
1240
|
+
const exact = expected[index];
|
|
1241
|
+
if (row === exact) {
|
|
1242
|
+
return true;
|
|
1243
|
+
}
|
|
1244
|
+
if (!row.endsWith("…")) {
|
|
1245
|
+
return false;
|
|
1246
|
+
}
|
|
1247
|
+
const prefix = row.slice(0, -1);
|
|
1248
|
+
const commandEnd = exact.indexOf(" ");
|
|
1249
|
+
return (commandEnd > 0 &&
|
|
1250
|
+
prefix.length >= commandEnd + 12 &&
|
|
1251
|
+
exact.startsWith(prefix));
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Prove the exact idle input frame from a verified Claude Code native-status
|
|
1256
|
+
* profile. A loose or historical `❯` prompt is not enough to authorize even
|
|
1257
|
+
* the initial literal `/status` injection.
|
|
1258
|
+
*/
|
|
1259
|
+
export function isExactClaudeNativeInspectionIdleComposer(screen) {
|
|
1260
|
+
const frame = exactClaudeNativeInspectionComposerFrame(screen);
|
|
1261
|
+
if (!frame) {
|
|
1262
|
+
return false;
|
|
1263
|
+
}
|
|
1264
|
+
return (frame.composerRows.length === 1 &&
|
|
1265
|
+
/^\s*❯\s*$/u.test(frame.composerRows[0]) &&
|
|
1266
|
+
(frame.trailing.length === 0 ||
|
|
1267
|
+
claudeNativeInspectionTrailingIsFooter(frame.trailing)));
|
|
1268
|
+
}
|
|
1269
|
+
function exactClaudeNativeInspectionComposerFrame(screen) {
|
|
1270
|
+
const lines = screen.replace(/\r\n?/gu, "\n").replace(/\u00a0/gu, " ")
|
|
1271
|
+
.split("\n");
|
|
1272
|
+
const dividerIndexes = lines
|
|
1273
|
+
.map((line, index) => /^\s*[─━]{8,}\s*$/u.test(line) ? index : -1)
|
|
1274
|
+
.filter((index) => index >= 0);
|
|
1275
|
+
if (dividerIndexes.length < 2) {
|
|
1276
|
+
return undefined;
|
|
1277
|
+
}
|
|
1278
|
+
const closeIndex = dividerIndexes.at(-1);
|
|
1279
|
+
const openIndex = dividerIndexes.at(-2);
|
|
1280
|
+
return {
|
|
1281
|
+
lines,
|
|
1282
|
+
openIndex,
|
|
1283
|
+
closeIndex,
|
|
1284
|
+
composerRows: lines.slice(openIndex + 1, closeIndex)
|
|
1285
|
+
.filter((line) => line.trim().length > 0),
|
|
1286
|
+
trailing: lines.slice(closeIndex + 1)
|
|
1287
|
+
.filter((line) => line.trim().length > 0)
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
function claudeNativeInspectionTrailingIsFooter(lines) {
|
|
1291
|
+
return lines.length <= 2 && lines.every((line) => /^\s*(?:[⏵⏴]{1,2}|\?)\s*.*(?:shift\+tab|accept edits|bypass permissions|for shortcuts|← for agents)/iu
|
|
1292
|
+
.test(line));
|
|
1293
|
+
}
|
|
1040
1294
|
function nativeInspectionScreenFingerprint(screen) {
|
|
1041
1295
|
return `sha256:${createHash("sha256").update(screen).digest("hex")}`;
|
|
1042
1296
|
}
|