@scotthuang/agent-knock-knock 0.11.3 → 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.
@@ -1,5 +1,5 @@
1
1
  import type { ExecutorKind } from "./executors.js";
2
- import { type ActiveTerminalProcess, type TerminalAgentAdapter, type TerminalAgentAdapterCapabilities, type TerminalAgentAdapterRegistry, type TerminalCompletionEvidence, type TerminalControlRef, type TerminalDurableCompletionRequest, type TerminalProcessSnapshot, type TerminalRuntimeIdentity, type TerminalScreenInspection } from "./terminal-agent-adapter.js";
2
+ import { type ActiveTerminalProcess, type TerminalAgentAdapter, type TerminalAgentAdapterCapabilities, type TerminalAgentAdapterRegistry, type TerminalCompletionEvidence, type TerminalControlRef, type TerminalDurableCompletionRequest, type TerminalNativeInspectionEvidenceInventoryEntry, type TerminalNativeInspectionObservation, type TerminalNativeInspectionObservationRequest, type TerminalNativeInspectionPlan, type TerminalProcessSnapshot, type TerminalRuntimeIdentity, type TerminalScreenInspection } from "./terminal-agent-adapter.js";
3
3
  import { type TerminalControlProvider } from "./terminal-control-provider.js";
4
4
  /** A terminal send failed at a boundary that proves input never started. */
5
5
  export declare class TerminalInputNotStartedError extends Error {
@@ -8,6 +8,30 @@ export declare class TerminalInputNotStartedError extends Error {
8
8
  cause?: unknown;
9
9
  });
10
10
  }
11
+ export type NativeInspectionSubmissionStage = "not_started" | "text_injected" | "enter_uncertain";
12
+ /**
13
+ * A fail-closed native-inspection submission result.
14
+ *
15
+ * Only `not_started` proves that a caller may safely retry. Once text has
16
+ * reached the composer, or Enter has been attempted, automated retries could
17
+ * duplicate input or execute a different command after terminal drift.
18
+ */
19
+ export declare class NativeInspectionSubmissionError extends Error {
20
+ readonly stage: NativeInspectionSubmissionStage;
21
+ readonly code = "AKK_NATIVE_INSPECTION_SUBMISSION_FAILED";
22
+ readonly doNotRetry: boolean;
23
+ constructor(stage: NativeInspectionSubmissionStage, message: string, options?: {
24
+ cause?: unknown;
25
+ });
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
+ }
11
35
  export interface TerminalBridgeStatus {
12
36
  provider: "tmux";
13
37
  target: string;
@@ -105,6 +129,65 @@ export interface TerminalSendResult {
105
129
  terminalControl: TerminalControlRef;
106
130
  multiline: boolean;
107
131
  }
132
+ export type TerminalNativeInspectionMaterializationKind = "exact_slash_composer" | "exact_slash_popup";
133
+ export interface TerminalNativeInspectionMaterializationEvidence {
134
+ kind: TerminalNativeInspectionMaterializationKind;
135
+ digest: string;
136
+ stableForMs: number;
137
+ stableCaptures: number;
138
+ }
139
+ export interface TerminalNativeInspectionBeforeEnterContext {
140
+ agent: ExecutorKind;
141
+ terminalControl: TerminalControlRef;
142
+ plan: TerminalNativeInspectionPlan;
143
+ preEnterScreenDigest: string;
144
+ materialization: TerminalNativeInspectionMaterializationEvidence;
145
+ }
146
+ export interface TerminalNativeInspectionOptions {
147
+ runtime?: TerminalRuntimeIdentity;
148
+ /**
149
+ * Gives the CLI one final in-lock authorization point for its Store binding
150
+ * and action-token fences. The bridge recaptures the exact composer and
151
+ * revalidates terminal identity again after this hook before pressing Enter.
152
+ */
153
+ beforeEnter?: (context: TerminalNativeInspectionBeforeEnterContext) => void | Promise<void>;
154
+ }
155
+ export interface TerminalNativeInspectionResult {
156
+ stage: "enter_dispatched";
157
+ agent: ExecutorKind;
158
+ terminalControl: TerminalControlRef;
159
+ command: string;
160
+ behaviorProfile: string;
161
+ preEnterScreenDigest: string;
162
+ preEnterEvidenceInventory: readonly TerminalNativeInspectionEvidenceInventoryEntry[];
163
+ materialization: TerminalNativeInspectionMaterializationEvidence;
164
+ enterCount: 1;
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
+ }
184
+ export interface TerminalNativeInspectionObservationResult {
185
+ terminalControl: TerminalControlRef;
186
+ status: TerminalBridgeStatus;
187
+ /** Same raw-screen fingerprint format used by adapter stale checks. */
188
+ screenDigest: string;
189
+ observation: TerminalNativeInspectionObservation;
190
+ }
108
191
  export interface TerminalApprovalAuthorizationContext {
109
192
  agent: ExecutorKind;
110
193
  terminalControl: TerminalControlRef;
@@ -152,6 +235,26 @@ export declare class TerminalAgentBridge {
152
235
  runtime?: TerminalRuntimeIdentity;
153
236
  }): Promise<TerminalBridgeStatus>;
154
237
  send(agent: ExecutorKind, terminalControl: TerminalControlRef, text: string, options?: TerminalSendOptions): Promise<TerminalSendResult>;
238
+ /**
239
+ * Submit one closed, adapter-owned native inspection command.
240
+ *
241
+ * This intentionally does not use `send()`: native slash commands need a
242
+ * stricter composer proof, and failures after text injection must leave the
243
+ * draft untouched instead of issuing the legacy best-effort C-u cleanup.
244
+ */
245
+ submitNativeInspection(agent: ExecutorKind, terminalControl: TerminalControlRef, plan: TerminalNativeInspectionPlan, options?: TerminalNativeInspectionOptions): Promise<TerminalNativeInspectionResult>;
246
+ observeNativeInspection(agent: ExecutorKind, terminalControl: TerminalControlRef, request: TerminalNativeInspectionObservationRequest, options?: {
247
+ runtime?: TerminalRuntimeIdentity;
248
+ scrollbackLines?: number;
249
+ }): Promise<TerminalNativeInspectionObservationResult>;
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;
155
258
  private settleCodexMultilineComposer;
156
259
  /**
157
260
  * Prove that the exact managed draft is still present after Enter dispatch.
@@ -210,6 +313,12 @@ export declare class TerminalAgentBridge {
210
313
  private captureInspection;
211
314
  private verifyTerminalIdentity;
212
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;
213
322
  export declare function terminalApprovalFingerprint(agent: ExecutorKind, terminalControl: TerminalControlRef, inspection: TerminalScreenInspection, options?: {
214
323
  screen?: string;
215
324
  runtime?: TerminalRuntimeIdentity;
@@ -11,6 +11,21 @@ const CODEX_MULTILINE_STABLE_CAPTURES = 2;
11
11
  const CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES = 240;
12
12
  const CODEX_COMPOSER_MARKER = /^[›»](?:\s|$)/u;
13
13
  const CODEX_COMPOSER_FOOTER = /^(?:gpt-[\w.-]+(?:\s|$)|[-\w.]+ default ·)/u;
14
+ // Codex 0.146.0 and 0.146.1 render the same verified slash-completion row.
15
+ // Keep it closed per behavior profile so changed text cannot silently become
16
+ // a newly authorized native command surface.
17
+ const CODEX_NATIVE_STATUS_POPUP_BY_PROFILE = {
18
+ "codex-tui-0.146.0": " /status show current session configuration and token usage",
19
+ "codex-tui-0.146.1": " /status show current session configuration and token usage"
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
+ };
14
29
  const CODEX_LARGE_PASTE_CHAR_THRESHOLD = 1_000;
15
30
  /** A terminal send failed at a boundary that proves input never started. */
16
31
  export class TerminalInputNotStartedError extends Error {
@@ -20,6 +35,33 @@ export class TerminalInputNotStartedError extends Error {
20
35
  this.name = "TerminalInputNotStartedError";
21
36
  }
22
37
  }
38
+ /**
39
+ * A fail-closed native-inspection submission result.
40
+ *
41
+ * Only `not_started` proves that a caller may safely retry. Once text has
42
+ * reached the composer, or Enter has been attempted, automated retries could
43
+ * duplicate input or execute a different command after terminal drift.
44
+ */
45
+ export class NativeInspectionSubmissionError extends Error {
46
+ stage;
47
+ code = "AKK_NATIVE_INSPECTION_SUBMISSION_FAILED";
48
+ doNotRetry;
49
+ constructor(stage, message, options = {}) {
50
+ super(message, options);
51
+ this.stage = stage;
52
+ this.name = "NativeInspectionSubmissionError";
53
+ this.doNotRetry = stage !== "not_started";
54
+ }
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
+ }
23
65
  export class TerminalAgentBridge {
24
66
  registry;
25
67
  terminalProvider;
@@ -219,6 +261,265 @@ export class TerminalAgentBridge {
219
261
  multiline
220
262
  };
221
263
  }
264
+ /**
265
+ * Submit one closed, adapter-owned native inspection command.
266
+ *
267
+ * This intentionally does not use `send()`: native slash commands need a
268
+ * stricter composer proof, and failures after text injection must leave the
269
+ * draft untouched instead of issuing the legacy best-effort C-u cleanup.
270
+ */
271
+ async submitNativeInspection(agent, terminalControl, plan, options = {}) {
272
+ const adapter = this.registry.require(agent);
273
+ try {
274
+ assertClosedStatusInspectionPlan(adapter, terminalControl, plan);
275
+ }
276
+ catch (error) {
277
+ throw nativeInspectionSubmissionError("not_started", error);
278
+ }
279
+ let verifiedForText;
280
+ try {
281
+ verifiedForText = await this.verifyTerminalIdentity(adapter.agent, terminalControl, options.runtime);
282
+ }
283
+ catch (error) {
284
+ throw nativeInspectionSubmissionError("not_started", error);
285
+ }
286
+ try {
287
+ await this.terminalProvider.sendText(verifiedForText.target, plan.command, { socketPath: verifiedForText.socketPath });
288
+ }
289
+ catch (error) {
290
+ if (error instanceof TerminalControlInputNotSentError) {
291
+ throw nativeInspectionSubmissionError("not_started", error);
292
+ }
293
+ // The transport cannot prove whether an untyped failure happened before
294
+ // or after tmux accepted the literal input. Fail closed as injected.
295
+ throw nativeInspectionSubmissionError("text_injected", error);
296
+ }
297
+ let settled;
298
+ try {
299
+ settled = await this.settleNativeInspectionComposer(adapter, terminalControl, plan, options.runtime);
300
+ await options.beforeEnter?.({
301
+ agent: adapter.agent,
302
+ terminalControl: settled.terminalControl,
303
+ plan,
304
+ preEnterScreenDigest: settled.screenDigest,
305
+ materialization: settled.materialization
306
+ });
307
+ settled = await this.revalidateNativeInspectionComposer(adapter, settled.terminalControl, plan, settled.materialization, options.runtime);
308
+ }
309
+ catch (error) {
310
+ throw nativeInspectionSubmissionError("text_injected", error);
311
+ }
312
+ try {
313
+ // Exactly one Enter attempt. Any error is submission-uncertain and must
314
+ // never trigger a fallback executable or a blind second Enter.
315
+ await this.terminalProvider.sendKeys(settled.terminalControl.target, ["C-m"], { socketPath: settled.terminalControl.socketPath });
316
+ }
317
+ catch (error) {
318
+ throw nativeInspectionSubmissionError("enter_uncertain", error);
319
+ }
320
+ return {
321
+ stage: "enter_dispatched",
322
+ agent: adapter.agent,
323
+ terminalControl: settled.terminalControl,
324
+ command: plan.command,
325
+ behaviorProfile: plan.behaviorProfile,
326
+ preEnterScreenDigest: settled.screenDigest,
327
+ preEnterEvidenceInventory: settled.evidenceInventory,
328
+ materialization: settled.materialization,
329
+ enterCount: 1
330
+ };
331
+ }
332
+ async observeNativeInspection(agent, terminalControl, request, options = {}) {
333
+ const adapter = this.registry.require(agent);
334
+ if (!adapter.observeNativeInspection) {
335
+ throw new Error(`${adapter.displayName} native inspection observation is not supported`);
336
+ }
337
+ const captured = await this.captureInspection(adapter, terminalControl, {
338
+ runtime: options.runtime,
339
+ scrollbackLines: options.scrollbackLines
340
+ });
341
+ const screenDigest = nativeInspectionScreenFingerprint(captured.screen);
342
+ const adapterObservation = adapter.observeNativeInspection({
343
+ operation: request.operation,
344
+ previousScreenFingerprint: request.previousScreenFingerprint,
345
+ preEnterEvidenceInventory: request.preEnterEvidenceInventory,
346
+ expectedNativeThreadId: request.expectedNativeThreadId,
347
+ expectedAgentVersion: request.expectedAgentVersion,
348
+ expectedCwd: request.expectedCwd,
349
+ screen: captured.screen
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;
360
+ return {
361
+ terminalControl: captured.terminalControl,
362
+ status: statusFromInspection(adapter, captured.terminalControl, captured.inspection, { screen: captured.screen, runtime: options.runtime }),
363
+ screenDigest,
364
+ observation
365
+ };
366
+ }
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) {
439
+ const startedAt = performance.now();
440
+ let stableDigest;
441
+ let stableKind;
442
+ let stableSince;
443
+ let stableCaptures = 0;
444
+ while (performance.now() - startedAt <= CODEX_MULTILINE_SETTLE_TIMEOUT_MS) {
445
+ const captured = await this.captureInspection(adapter, terminalControl, {
446
+ runtime,
447
+ scrollbackLines: CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES
448
+ });
449
+ assertNativeInspectionComposerSafe(captured.inspection);
450
+ const materialized = exactNativeInspectionComposerCapture(adapter.agent, captured.screen, plan);
451
+ const now = performance.now();
452
+ if (materialized) {
453
+ if (materialized.digest === stableDigest &&
454
+ materialized.kind === stableKind) {
455
+ stableCaptures += 1;
456
+ }
457
+ else {
458
+ stableDigest = materialized.digest;
459
+ stableKind = materialized.kind;
460
+ stableSince = now;
461
+ stableCaptures = 1;
462
+ }
463
+ const stableForMs = stableSince === undefined ? 0 : now - stableSince;
464
+ if (stableCaptures >= CODEX_MULTILINE_STABLE_CAPTURES &&
465
+ stableForMs >= plan.composer.minimumStableMs) {
466
+ const evidence = {
467
+ kind: materialized.kind,
468
+ digest: materialized.digest,
469
+ stableForMs,
470
+ stableCaptures
471
+ };
472
+ return this.revalidateNativeInspectionComposer(adapter, captured.terminalControl, plan, evidence, runtime);
473
+ }
474
+ }
475
+ else {
476
+ stableDigest = undefined;
477
+ stableKind = undefined;
478
+ stableSince = undefined;
479
+ stableCaptures = 0;
480
+ }
481
+ const elapsed = performance.now() - startedAt;
482
+ const remaining = CODEX_MULTILINE_SETTLE_TIMEOUT_MS - elapsed;
483
+ if (remaining <= 0) {
484
+ break;
485
+ }
486
+ await terminalSettleDelay(Math.min(CODEX_MULTILINE_SETTLE_POLL_MS, remaining));
487
+ }
488
+ throw new Error(`${adapter.displayName} /status composer did not become exact, idle, and stable before the bounded submit deadline`);
489
+ }
490
+ async revalidateNativeInspectionComposer(adapter, terminalControl, plan, expected, runtime) {
491
+ const captured = await this.captureInspection(adapter, terminalControl, {
492
+ runtime,
493
+ scrollbackLines: CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES
494
+ });
495
+ assertNativeInspectionComposerSafe(captured.inspection);
496
+ const materialized = exactNativeInspectionComposerCapture(adapter.agent, captured.screen, plan);
497
+ if (!materialized ||
498
+ materialized.digest !== expected.digest ||
499
+ materialized.kind !== expected.kind) {
500
+ throw new Error(`${adapter.displayName} /status composer changed after its stable pre-submit capture`);
501
+ }
502
+ const baseline = adapter.observeNativeInspection?.({
503
+ operation: plan.operation,
504
+ screen: captured.screen
505
+ });
506
+ if (!baseline ||
507
+ baseline.status === "ambiguous" ||
508
+ !Array.isArray(baseline.evidenceInventory)) {
509
+ throw new Error(baseline?.reason ??
510
+ `${adapter.displayName} /status pre-Enter evidence inventory was not proven`);
511
+ }
512
+ const verifiedImmediatelyBeforeEnter = await this.verifyTerminalIdentity(adapter.agent, captured.terminalControl, runtime);
513
+ if (!sameTerminalControlIdentity(captured.terminalControl, verifiedImmediatelyBeforeEnter)) {
514
+ throw new Error(`terminal control identity changed after the final ${adapter.displayName} /status composer capture`);
515
+ }
516
+ return {
517
+ terminalControl: verifiedImmediatelyBeforeEnter,
518
+ screenDigest: nativeInspectionScreenFingerprint(captured.screen),
519
+ evidenceInventory: baseline.evidenceInventory,
520
+ materialization: expected
521
+ };
522
+ }
222
523
  async settleCodexMultilineComposer(adapter, terminalControl, expectedText, runtime) {
223
524
  const startedAt = performance.now();
224
525
  let stableDigest;
@@ -739,6 +1040,210 @@ export class TerminalAgentBridge {
739
1040
  return result?.terminalControl ?? terminalControl;
740
1041
  }
741
1042
  }
1043
+ function nativeInspectionSubmissionError(stage, error) {
1044
+ if (error instanceof NativeInspectionSubmissionError) {
1045
+ const stageRank = {
1046
+ not_started: 0,
1047
+ text_injected: 1,
1048
+ enter_uncertain: 2
1049
+ };
1050
+ if (stageRank[error.stage] >= stageRank[stage]) {
1051
+ return error;
1052
+ }
1053
+ return new NativeInspectionSubmissionError(stage, error.message, {
1054
+ cause: error
1055
+ });
1056
+ }
1057
+ return new NativeInspectionSubmissionError(stage, error instanceof Error ? error.message : String(error), { cause: error });
1058
+ }
1059
+ function assertClosedStatusInspectionPlan(adapter, terminalControl, plan) {
1060
+ if (!terminalControl.capabilities.includes("send_keys")) {
1061
+ throw new Error(`${adapter.displayName} terminal input is not supported`);
1062
+ }
1063
+ if (!terminalControl.capabilities.includes("screen_status")) {
1064
+ throw new Error(`${adapter.displayName} terminal screen inspection is not supported`);
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;
1083
+ if (plan.operation.kind !== "status" ||
1084
+ plan.command !== "/status" ||
1085
+ plan.effect !== "read_only" ||
1086
+ plan.requiresIdle !== true ||
1087
+ plan.composer.kind !== "exact" ||
1088
+ !Number.isFinite(plan.composer.minimumStableMs) ||
1089
+ plan.composer.minimumStableMs < 0 ||
1090
+ plan.expectedResult.kind !== "native_status" ||
1091
+ !exactPresentation) {
1092
+ throw new Error("refusing a non-closed or unverified native inspection plan");
1093
+ }
1094
+ }
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") {
1104
+ if (inspection.approval.blocked ||
1105
+ inspection.activity.state === "awaiting_approval" ||
1106
+ inspection.activity.state === "working") {
1107
+ throw new Error(`${displayName} became busy or blocked while its /status composer was settling`);
1108
+ }
1109
+ // Codex's generic activity parser deliberately reports a non-empty slash
1110
+ // composer as unknown. At this stage the caller has already proved an idle,
1111
+ // empty styled composer under the terminal lock; the exact-current composer
1112
+ // capture below is the stronger continuation proof after AKK injected only
1113
+ // the adapter-owned /status command.
1114
+ }
1115
+ function exactNativeInspectionComposerCapture(agent, screen, plan) {
1116
+ return agent === "codex"
1117
+ ? exactCodexNativeInspectionComposerCapture(screen, plan)
1118
+ : exactClaudeNativeInspectionComposerCapture(screen, plan);
1119
+ }
1120
+ function exactCodexNativeInspectionComposerCapture(screen, plan) {
1121
+ const lines = screen.replace(/\r\n?/gu, "\n").split("\n");
1122
+ let currentComposerIndex = -1;
1123
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
1124
+ if (CODEX_COMPOSER_MARKER.test(lines[index])) {
1125
+ currentComposerIndex = index;
1126
+ break;
1127
+ }
1128
+ }
1129
+ if (currentComposerIndex < 0) {
1130
+ return undefined;
1131
+ }
1132
+ const composerText = lines[currentComposerIndex]
1133
+ .replace(/^[›»]\s?/u, "")
1134
+ .trimEnd();
1135
+ if (composerText !== plan.command) {
1136
+ return undefined;
1137
+ }
1138
+ const footerIndex = lines.findIndex((line, candidateIndex) => candidateIndex > currentComposerIndex &&
1139
+ CODEX_COMPOSER_FOOTER.test(line.trim()));
1140
+ const region = lines.slice(currentComposerIndex, footerIndex < 0 ? lines.length : footerIndex);
1141
+ while (region.length > 1 && region.at(-1)?.trim() === "") {
1142
+ region.pop();
1143
+ }
1144
+ const popupRows = region.slice(1).filter((line) => line.trim().length > 0);
1145
+ let kind;
1146
+ if (popupRows.length === 0) {
1147
+ kind = "exact_slash_composer";
1148
+ }
1149
+ else if (popupRows.length === 1 &&
1150
+ popupRows[0].trimEnd() ===
1151
+ CODEX_NATIVE_STATUS_POPUP_BY_PROFILE[plan.behaviorProfile]) {
1152
+ kind = "exact_slash_popup";
1153
+ }
1154
+ else {
1155
+ return undefined;
1156
+ }
1157
+ return {
1158
+ kind,
1159
+ digest: createHash("sha256").update(region.join("\n")).digest("hex")
1160
+ };
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
+ }
1244
+ function nativeInspectionScreenFingerprint(screen) {
1245
+ return `sha256:${createHash("sha256").update(screen).digest("hex")}`;
1246
+ }
742
1247
  function exactCodexComposerCapture(screen, expectedText) {
743
1248
  const lines = screen.replace(/\r\n?/gu, "\n").split("\n");
744
1249
  const expectedComparable = composerComparableText(expectedText);