@scotthuang/agent-knock-knock 0.11.2 → 0.11.4
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 +19 -0
- package/README.md +15 -5
- package/dist/src/cli.js +1144 -58
- package/dist/src/cli.js.map +1 -1
- package/dist/src/codex-terminal-agent-adapter.d.ts +5 -1
- package/dist/src/codex-terminal-agent-adapter.js +357 -0
- package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin.js +118 -2
- package/dist/src/openclaw-plugin.js.map +1 -1
- package/dist/src/terminal-agent-adapter.d.ts +95 -0
- package/dist/src/terminal-agent-adapter.js +8 -0
- package/dist/src/terminal-agent-adapter.js.map +1 -1
- package/dist/src/terminal-agent-bridge.d.ts +79 -1
- package/dist/src/terminal-agent-bridge.js +332 -7
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/dist/src/terminal-control-provider.d.ts +8 -0
- package/dist/src/terminal-control-provider.js +44 -2
- package/dist/src/terminal-control-provider.js.map +1 -1
- package/docs/quickstart-tmux.md +2 -0
- package/openclaw.plugin.json +9 -1
- package/package.json +8 -2
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +14 -4
|
@@ -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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"}
|
|
@@ -1,6 +1,29 @@
|
|
|
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
|
+
/** A terminal send failed at a boundary that proves input never started. */
|
|
5
|
+
export declare class TerminalInputNotStartedError extends Error {
|
|
6
|
+
readonly code = "AKK_TERMINAL_INPUT_NOT_STARTED";
|
|
7
|
+
constructor(message: string, options?: {
|
|
8
|
+
cause?: unknown;
|
|
9
|
+
});
|
|
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
|
+
}
|
|
4
27
|
export interface TerminalBridgeStatus {
|
|
5
28
|
provider: "tmux";
|
|
6
29
|
target: string;
|
|
@@ -98,6 +121,47 @@ export interface TerminalSendResult {
|
|
|
98
121
|
terminalControl: TerminalControlRef;
|
|
99
122
|
multiline: boolean;
|
|
100
123
|
}
|
|
124
|
+
export type TerminalNativeInspectionMaterializationKind = "exact_slash_composer" | "exact_slash_popup";
|
|
125
|
+
export interface TerminalNativeInspectionMaterializationEvidence {
|
|
126
|
+
kind: TerminalNativeInspectionMaterializationKind;
|
|
127
|
+
digest: string;
|
|
128
|
+
stableForMs: number;
|
|
129
|
+
stableCaptures: number;
|
|
130
|
+
}
|
|
131
|
+
export interface TerminalNativeInspectionBeforeEnterContext {
|
|
132
|
+
agent: "codex";
|
|
133
|
+
terminalControl: TerminalControlRef;
|
|
134
|
+
plan: TerminalNativeInspectionPlan;
|
|
135
|
+
preEnterScreenDigest: string;
|
|
136
|
+
materialization: TerminalNativeInspectionMaterializationEvidence;
|
|
137
|
+
}
|
|
138
|
+
export interface TerminalNativeInspectionOptions {
|
|
139
|
+
runtime?: TerminalRuntimeIdentity;
|
|
140
|
+
/**
|
|
141
|
+
* Gives the CLI one final in-lock authorization point for its Store binding
|
|
142
|
+
* and action-token fences. The bridge recaptures the exact composer and
|
|
143
|
+
* revalidates terminal identity again after this hook before pressing Enter.
|
|
144
|
+
*/
|
|
145
|
+
beforeEnter?: (context: TerminalNativeInspectionBeforeEnterContext) => void | Promise<void>;
|
|
146
|
+
}
|
|
147
|
+
export interface TerminalNativeInspectionResult {
|
|
148
|
+
stage: "enter_dispatched";
|
|
149
|
+
agent: "codex";
|
|
150
|
+
terminalControl: TerminalControlRef;
|
|
151
|
+
command: "/status";
|
|
152
|
+
behaviorProfile: string;
|
|
153
|
+
preEnterScreenDigest: string;
|
|
154
|
+
preEnterEvidenceInventory: readonly TerminalNativeInspectionEvidenceInventoryEntry[];
|
|
155
|
+
materialization: TerminalNativeInspectionMaterializationEvidence;
|
|
156
|
+
enterCount: 1;
|
|
157
|
+
}
|
|
158
|
+
export interface TerminalNativeInspectionObservationResult {
|
|
159
|
+
terminalControl: TerminalControlRef;
|
|
160
|
+
status: TerminalBridgeStatus;
|
|
161
|
+
/** Same raw-screen fingerprint format used by adapter stale checks. */
|
|
162
|
+
screenDigest: string;
|
|
163
|
+
observation: TerminalNativeInspectionObservation;
|
|
164
|
+
}
|
|
101
165
|
export interface TerminalApprovalAuthorizationContext {
|
|
102
166
|
agent: ExecutorKind;
|
|
103
167
|
terminalControl: TerminalControlRef;
|
|
@@ -145,6 +209,20 @@ export declare class TerminalAgentBridge {
|
|
|
145
209
|
runtime?: TerminalRuntimeIdentity;
|
|
146
210
|
}): Promise<TerminalBridgeStatus>;
|
|
147
211
|
send(agent: ExecutorKind, terminalControl: TerminalControlRef, text: string, options?: TerminalSendOptions): Promise<TerminalSendResult>;
|
|
212
|
+
/**
|
|
213
|
+
* Submit one closed, adapter-owned native inspection command.
|
|
214
|
+
*
|
|
215
|
+
* This intentionally does not use `send()`: native slash commands need a
|
|
216
|
+
* stricter composer proof, and failures after text injection must leave the
|
|
217
|
+
* draft untouched instead of issuing the legacy best-effort C-u cleanup.
|
|
218
|
+
*/
|
|
219
|
+
submitNativeInspection(agent: ExecutorKind, terminalControl: TerminalControlRef, plan: TerminalNativeInspectionPlan, options?: TerminalNativeInspectionOptions): Promise<TerminalNativeInspectionResult>;
|
|
220
|
+
observeNativeInspection(agent: ExecutorKind, terminalControl: TerminalControlRef, request: TerminalNativeInspectionObservationRequest, options?: {
|
|
221
|
+
runtime?: TerminalRuntimeIdentity;
|
|
222
|
+
scrollbackLines?: number;
|
|
223
|
+
}): Promise<TerminalNativeInspectionObservationResult>;
|
|
224
|
+
private settleCodexNativeInspectionComposer;
|
|
225
|
+
private revalidateCodexNativeInspectionComposer;
|
|
148
226
|
private settleCodexMultilineComposer;
|
|
149
227
|
/**
|
|
150
228
|
* Prove that the exact managed draft is still present after Enter dispatch.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { formatTerminalConversationId, parseTerminalConversationId, terminalControlCapabilitiesForAdapter } from "./terminal-agent-adapter.js";
|
|
3
|
-
import { enrichActiveProcessesWithTerminalControl, terminalRefFromPane } from "./terminal-control-provider.js";
|
|
3
|
+
import { enrichActiveProcessesWithTerminalControl, TerminalControlInputNotSentError, terminalRefFromPane } from "./terminal-control-provider.js";
|
|
4
4
|
// Codex 0.146.x keeps Enter in paste/newline mode for 120ms after burst input.
|
|
5
5
|
// Cross that boundary rather than landing on it, and also require observable
|
|
6
6
|
// composer stability instead of treating this delay alone as acceptance.
|
|
@@ -11,7 +11,40 @@ 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
|
+
};
|
|
14
21
|
const CODEX_LARGE_PASTE_CHAR_THRESHOLD = 1_000;
|
|
22
|
+
/** A terminal send failed at a boundary that proves input never started. */
|
|
23
|
+
export class TerminalInputNotStartedError extends Error {
|
|
24
|
+
code = "AKK_TERMINAL_INPUT_NOT_STARTED";
|
|
25
|
+
constructor(message, options = {}) {
|
|
26
|
+
super(message, options);
|
|
27
|
+
this.name = "TerminalInputNotStartedError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A fail-closed native-inspection submission result.
|
|
32
|
+
*
|
|
33
|
+
* Only `not_started` proves that a caller may safely retry. Once text has
|
|
34
|
+
* reached the composer, or Enter has been attempted, automated retries could
|
|
35
|
+
* duplicate input or execute a different command after terminal drift.
|
|
36
|
+
*/
|
|
37
|
+
export class NativeInspectionSubmissionError extends Error {
|
|
38
|
+
stage;
|
|
39
|
+
code = "AKK_NATIVE_INSPECTION_SUBMISSION_FAILED";
|
|
40
|
+
doNotRetry;
|
|
41
|
+
constructor(stage, message, options = {}) {
|
|
42
|
+
super(message, options);
|
|
43
|
+
this.stage = stage;
|
|
44
|
+
this.name = "NativeInspectionSubmissionError";
|
|
45
|
+
this.doNotRetry = stage !== "not_started";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
15
48
|
export class TerminalAgentBridge {
|
|
16
49
|
registry;
|
|
17
50
|
terminalProvider;
|
|
@@ -139,17 +172,33 @@ export class TerminalAgentBridge {
|
|
|
139
172
|
async send(agent, terminalControl, text, options = {}) {
|
|
140
173
|
const adapter = this.registry.require(agent);
|
|
141
174
|
if (!terminalControl.capabilities.includes("send_keys")) {
|
|
142
|
-
throw new
|
|
175
|
+
throw new TerminalInputNotStartedError(`${adapter.displayName} terminal input is not supported`);
|
|
143
176
|
}
|
|
144
177
|
const normalized = text.trimEnd();
|
|
145
178
|
if (!normalized) {
|
|
146
|
-
throw new
|
|
179
|
+
throw new TerminalInputNotStartedError("terminal message is empty");
|
|
147
180
|
}
|
|
148
181
|
const multiline = /[\r\n]/u.test(normalized);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
182
|
+
let verifiedForText;
|
|
183
|
+
try {
|
|
184
|
+
verifiedForText = await this.verifyTerminalIdentity(adapter.agent, terminalControl, options.runtime);
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
throw new TerminalInputNotStartedError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
await this.terminalProvider.sendText(verifiedForText.target, normalized, {
|
|
191
|
+
socketPath: verifiedForText.socketPath
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
if (error instanceof TerminalControlInputNotSentError) {
|
|
196
|
+
throw new TerminalInputNotStartedError(error.message, {
|
|
197
|
+
cause: error
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
153
202
|
await options.onTransportStage?.({
|
|
154
203
|
stage: "text_injected",
|
|
155
204
|
agent: adapter.agent,
|
|
@@ -195,6 +244,184 @@ export class TerminalAgentBridge {
|
|
|
195
244
|
multiline
|
|
196
245
|
};
|
|
197
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Submit one closed, adapter-owned native inspection command.
|
|
249
|
+
*
|
|
250
|
+
* This intentionally does not use `send()`: native slash commands need a
|
|
251
|
+
* stricter composer proof, and failures after text injection must leave the
|
|
252
|
+
* draft untouched instead of issuing the legacy best-effort C-u cleanup.
|
|
253
|
+
*/
|
|
254
|
+
async submitNativeInspection(agent, terminalControl, plan, options = {}) {
|
|
255
|
+
const adapter = this.registry.require(agent);
|
|
256
|
+
try {
|
|
257
|
+
assertCodexStatusInspectionPlan(adapter, terminalControl, plan);
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
throw nativeInspectionSubmissionError("not_started", error);
|
|
261
|
+
}
|
|
262
|
+
let verifiedForText;
|
|
263
|
+
try {
|
|
264
|
+
verifiedForText = await this.verifyTerminalIdentity(adapter.agent, terminalControl, options.runtime);
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
throw nativeInspectionSubmissionError("not_started", error);
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
await this.terminalProvider.sendText(verifiedForText.target, plan.command, { socketPath: verifiedForText.socketPath });
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
if (error instanceof TerminalControlInputNotSentError) {
|
|
274
|
+
throw nativeInspectionSubmissionError("not_started", error);
|
|
275
|
+
}
|
|
276
|
+
// The transport cannot prove whether an untyped failure happened before
|
|
277
|
+
// or after tmux accepted the literal input. Fail closed as injected.
|
|
278
|
+
throw nativeInspectionSubmissionError("text_injected", error);
|
|
279
|
+
}
|
|
280
|
+
let settled;
|
|
281
|
+
try {
|
|
282
|
+
settled = await this.settleCodexNativeInspectionComposer(adapter, terminalControl, plan, options.runtime);
|
|
283
|
+
await options.beforeEnter?.({
|
|
284
|
+
agent: "codex",
|
|
285
|
+
terminalControl: settled.terminalControl,
|
|
286
|
+
plan,
|
|
287
|
+
preEnterScreenDigest: settled.screenDigest,
|
|
288
|
+
materialization: settled.materialization
|
|
289
|
+
});
|
|
290
|
+
settled = await this.revalidateCodexNativeInspectionComposer(adapter, settled.terminalControl, plan, settled.materialization, options.runtime);
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
throw nativeInspectionSubmissionError("text_injected", error);
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
// Exactly one Enter attempt. Any error is submission-uncertain and must
|
|
297
|
+
// never trigger a fallback executable or a blind second Enter.
|
|
298
|
+
await this.terminalProvider.sendKeys(settled.terminalControl.target, ["C-m"], { socketPath: settled.terminalControl.socketPath });
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
throw nativeInspectionSubmissionError("enter_uncertain", error);
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
stage: "enter_dispatched",
|
|
305
|
+
agent: "codex",
|
|
306
|
+
terminalControl: settled.terminalControl,
|
|
307
|
+
command: "/status",
|
|
308
|
+
behaviorProfile: plan.behaviorProfile,
|
|
309
|
+
preEnterScreenDigest: settled.screenDigest,
|
|
310
|
+
preEnterEvidenceInventory: settled.evidenceInventory,
|
|
311
|
+
materialization: settled.materialization,
|
|
312
|
+
enterCount: 1
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
async observeNativeInspection(agent, terminalControl, request, options = {}) {
|
|
316
|
+
const adapter = this.registry.require(agent);
|
|
317
|
+
if (!adapter.observeNativeInspection) {
|
|
318
|
+
throw new Error(`${adapter.displayName} native inspection observation is not supported`);
|
|
319
|
+
}
|
|
320
|
+
const captured = await this.captureInspection(adapter, terminalControl, {
|
|
321
|
+
runtime: options.runtime,
|
|
322
|
+
scrollbackLines: options.scrollbackLines
|
|
323
|
+
});
|
|
324
|
+
const screenDigest = nativeInspectionScreenFingerprint(captured.screen);
|
|
325
|
+
const observation = adapter.observeNativeInspection({
|
|
326
|
+
operation: request.operation,
|
|
327
|
+
previousScreenFingerprint: request.previousScreenFingerprint,
|
|
328
|
+
preEnterEvidenceInventory: request.preEnterEvidenceInventory,
|
|
329
|
+
expectedNativeThreadId: request.expectedNativeThreadId,
|
|
330
|
+
expectedAgentVersion: request.expectedAgentVersion,
|
|
331
|
+
screen: captured.screen
|
|
332
|
+
});
|
|
333
|
+
return {
|
|
334
|
+
terminalControl: captured.terminalControl,
|
|
335
|
+
status: statusFromInspection(adapter, captured.terminalControl, captured.inspection, { screen: captured.screen, runtime: options.runtime }),
|
|
336
|
+
screenDigest,
|
|
337
|
+
observation
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
async settleCodexNativeInspectionComposer(adapter, terminalControl, plan, runtime) {
|
|
341
|
+
const startedAt = performance.now();
|
|
342
|
+
let stableDigest;
|
|
343
|
+
let stableKind;
|
|
344
|
+
let stableSince;
|
|
345
|
+
let stableCaptures = 0;
|
|
346
|
+
while (performance.now() - startedAt <= CODEX_MULTILINE_SETTLE_TIMEOUT_MS) {
|
|
347
|
+
const captured = await this.captureInspection(adapter, terminalControl, {
|
|
348
|
+
runtime,
|
|
349
|
+
scrollbackLines: CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES
|
|
350
|
+
});
|
|
351
|
+
assertNativeInspectionComposerSafe(captured.inspection);
|
|
352
|
+
const materialized = exactCodexNativeInspectionComposerCapture(captured.screen, plan);
|
|
353
|
+
const now = performance.now();
|
|
354
|
+
if (materialized) {
|
|
355
|
+
if (materialized.digest === stableDigest &&
|
|
356
|
+
materialized.kind === stableKind) {
|
|
357
|
+
stableCaptures += 1;
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
stableDigest = materialized.digest;
|
|
361
|
+
stableKind = materialized.kind;
|
|
362
|
+
stableSince = now;
|
|
363
|
+
stableCaptures = 1;
|
|
364
|
+
}
|
|
365
|
+
const stableForMs = stableSince === undefined ? 0 : now - stableSince;
|
|
366
|
+
if (stableCaptures >= CODEX_MULTILINE_STABLE_CAPTURES &&
|
|
367
|
+
stableForMs >= plan.composer.minimumStableMs) {
|
|
368
|
+
const evidence = {
|
|
369
|
+
kind: materialized.kind,
|
|
370
|
+
digest: materialized.digest,
|
|
371
|
+
stableForMs,
|
|
372
|
+
stableCaptures
|
|
373
|
+
};
|
|
374
|
+
return this.revalidateCodexNativeInspectionComposer(adapter, captured.terminalControl, plan, evidence, runtime);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
stableDigest = undefined;
|
|
379
|
+
stableKind = undefined;
|
|
380
|
+
stableSince = undefined;
|
|
381
|
+
stableCaptures = 0;
|
|
382
|
+
}
|
|
383
|
+
const elapsed = performance.now() - startedAt;
|
|
384
|
+
const remaining = CODEX_MULTILINE_SETTLE_TIMEOUT_MS - elapsed;
|
|
385
|
+
if (remaining <= 0) {
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
await terminalSettleDelay(Math.min(CODEX_MULTILINE_SETTLE_POLL_MS, remaining));
|
|
389
|
+
}
|
|
390
|
+
throw new Error("Codex /status composer did not become exact, idle, and stable before the bounded submit deadline");
|
|
391
|
+
}
|
|
392
|
+
async revalidateCodexNativeInspectionComposer(adapter, terminalControl, plan, expected, runtime) {
|
|
393
|
+
const captured = await this.captureInspection(adapter, terminalControl, {
|
|
394
|
+
runtime,
|
|
395
|
+
scrollbackLines: CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES
|
|
396
|
+
});
|
|
397
|
+
assertNativeInspectionComposerSafe(captured.inspection);
|
|
398
|
+
const materialized = exactCodexNativeInspectionComposerCapture(captured.screen, plan);
|
|
399
|
+
if (!materialized ||
|
|
400
|
+
materialized.digest !== expected.digest ||
|
|
401
|
+
materialized.kind !== expected.kind) {
|
|
402
|
+
throw new Error("Codex /status composer changed after its stable pre-submit capture");
|
|
403
|
+
}
|
|
404
|
+
const baseline = adapter.observeNativeInspection?.({
|
|
405
|
+
operation: plan.operation,
|
|
406
|
+
screen: captured.screen
|
|
407
|
+
});
|
|
408
|
+
if (!baseline ||
|
|
409
|
+
baseline.status === "ambiguous" ||
|
|
410
|
+
!Array.isArray(baseline.evidenceInventory)) {
|
|
411
|
+
throw new Error(baseline?.reason ??
|
|
412
|
+
"Codex /status pre-Enter evidence inventory was not proven");
|
|
413
|
+
}
|
|
414
|
+
const verifiedImmediatelyBeforeEnter = await this.verifyTerminalIdentity(adapter.agent, captured.terminalControl, runtime);
|
|
415
|
+
if (!sameTerminalControlIdentity(captured.terminalControl, verifiedImmediatelyBeforeEnter)) {
|
|
416
|
+
throw new Error("terminal control identity changed after the final Codex /status composer capture");
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
terminalControl: verifiedImmediatelyBeforeEnter,
|
|
420
|
+
screenDigest: nativeInspectionScreenFingerprint(captured.screen),
|
|
421
|
+
evidenceInventory: baseline.evidenceInventory,
|
|
422
|
+
materialization: expected
|
|
423
|
+
};
|
|
424
|
+
}
|
|
198
425
|
async settleCodexMultilineComposer(adapter, terminalControl, expectedText, runtime) {
|
|
199
426
|
const startedAt = performance.now();
|
|
200
427
|
let stableDigest;
|
|
@@ -715,6 +942,104 @@ export class TerminalAgentBridge {
|
|
|
715
942
|
return result?.terminalControl ?? terminalControl;
|
|
716
943
|
}
|
|
717
944
|
}
|
|
945
|
+
function nativeInspectionSubmissionError(stage, error) {
|
|
946
|
+
if (error instanceof NativeInspectionSubmissionError) {
|
|
947
|
+
const stageRank = {
|
|
948
|
+
not_started: 0,
|
|
949
|
+
text_injected: 1,
|
|
950
|
+
enter_uncertain: 2
|
|
951
|
+
};
|
|
952
|
+
if (stageRank[error.stage] >= stageRank[stage]) {
|
|
953
|
+
return error;
|
|
954
|
+
}
|
|
955
|
+
return new NativeInspectionSubmissionError(stage, error.message, {
|
|
956
|
+
cause: error
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
return new NativeInspectionSubmissionError(stage, error instanceof Error ? error.message : String(error), { cause: error });
|
|
960
|
+
}
|
|
961
|
+
function assertCodexStatusInspectionPlan(adapter, terminalControl, plan) {
|
|
962
|
+
if (adapter.agent !== "codex") {
|
|
963
|
+
throw new Error("native /status inspection is currently supported only for Codex");
|
|
964
|
+
}
|
|
965
|
+
if (!terminalControl.capabilities.includes("send_keys")) {
|
|
966
|
+
throw new Error("Codex terminal input is not supported");
|
|
967
|
+
}
|
|
968
|
+
if (!terminalControl.capabilities.includes("screen_status")) {
|
|
969
|
+
throw new Error("Codex terminal screen inspection is not supported");
|
|
970
|
+
}
|
|
971
|
+
if (plan.operation.kind !== "status" ||
|
|
972
|
+
plan.command !== "/status" ||
|
|
973
|
+
plan.effect !== "read_only" ||
|
|
974
|
+
plan.requiresIdle !== true ||
|
|
975
|
+
plan.composer.kind !== "exact" ||
|
|
976
|
+
!Number.isFinite(plan.composer.minimumStableMs) ||
|
|
977
|
+
plan.composer.minimumStableMs < CODEX_PASTE_ENTER_SETTLE_MS ||
|
|
978
|
+
plan.expectedResult.kind !== "native_status" ||
|
|
979
|
+
![
|
|
980
|
+
"codex-tui-0.146.0",
|
|
981
|
+
"codex-tui-0.146.1"
|
|
982
|
+
].includes(plan.behaviorProfile)) {
|
|
983
|
+
throw new Error("refusing a non-closed or unverified native inspection plan");
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
function assertNativeInspectionComposerSafe(inspection) {
|
|
987
|
+
if (inspection.approval.blocked ||
|
|
988
|
+
inspection.activity.state === "awaiting_approval" ||
|
|
989
|
+
inspection.activity.state === "working") {
|
|
990
|
+
throw new Error("Codex became busy or blocked while its /status composer was settling");
|
|
991
|
+
}
|
|
992
|
+
// Codex's generic activity parser deliberately reports a non-empty slash
|
|
993
|
+
// composer as unknown. At this stage the caller has already proved an idle,
|
|
994
|
+
// empty styled composer under the terminal lock; the exact-current composer
|
|
995
|
+
// capture below is the stronger continuation proof after AKK injected only
|
|
996
|
+
// the adapter-owned /status command.
|
|
997
|
+
}
|
|
998
|
+
function exactCodexNativeInspectionComposerCapture(screen, plan) {
|
|
999
|
+
const lines = screen.replace(/\r\n?/gu, "\n").split("\n");
|
|
1000
|
+
let currentComposerIndex = -1;
|
|
1001
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
1002
|
+
if (CODEX_COMPOSER_MARKER.test(lines[index])) {
|
|
1003
|
+
currentComposerIndex = index;
|
|
1004
|
+
break;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
if (currentComposerIndex < 0) {
|
|
1008
|
+
return undefined;
|
|
1009
|
+
}
|
|
1010
|
+
const composerText = lines[currentComposerIndex]
|
|
1011
|
+
.replace(/^[›»]\s?/u, "")
|
|
1012
|
+
.trimEnd();
|
|
1013
|
+
if (composerText !== plan.command) {
|
|
1014
|
+
return undefined;
|
|
1015
|
+
}
|
|
1016
|
+
const footerIndex = lines.findIndex((line, candidateIndex) => candidateIndex > currentComposerIndex &&
|
|
1017
|
+
CODEX_COMPOSER_FOOTER.test(line.trim()));
|
|
1018
|
+
const region = lines.slice(currentComposerIndex, footerIndex < 0 ? lines.length : footerIndex);
|
|
1019
|
+
while (region.length > 1 && region.at(-1)?.trim() === "") {
|
|
1020
|
+
region.pop();
|
|
1021
|
+
}
|
|
1022
|
+
const popupRows = region.slice(1).filter((line) => line.trim().length > 0);
|
|
1023
|
+
let kind;
|
|
1024
|
+
if (popupRows.length === 0) {
|
|
1025
|
+
kind = "exact_slash_composer";
|
|
1026
|
+
}
|
|
1027
|
+
else if (popupRows.length === 1 &&
|
|
1028
|
+
popupRows[0].trimEnd() ===
|
|
1029
|
+
CODEX_NATIVE_STATUS_POPUP_BY_PROFILE[plan.behaviorProfile]) {
|
|
1030
|
+
kind = "exact_slash_popup";
|
|
1031
|
+
}
|
|
1032
|
+
else {
|
|
1033
|
+
return undefined;
|
|
1034
|
+
}
|
|
1035
|
+
return {
|
|
1036
|
+
kind,
|
|
1037
|
+
digest: createHash("sha256").update(region.join("\n")).digest("hex")
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
function nativeInspectionScreenFingerprint(screen) {
|
|
1041
|
+
return `sha256:${createHash("sha256").update(screen).digest("hex")}`;
|
|
1042
|
+
}
|
|
718
1043
|
function exactCodexComposerCapture(screen, expectedText) {
|
|
719
1044
|
const lines = screen.replace(/\r\n?/gu, "\n").split("\n");
|
|
720
1045
|
const expectedComparable = composerComparableText(expectedText);
|