@scotthuang/agent-knock-knock 0.12.29 → 0.12.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/src/claude-terminal-agent-adapter.js +20 -1
- package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
- package/dist/src/cli-core.js +50 -35
- package/dist/src/cli-core.js.map +1 -1
- package/dist/src/codex-terminal-agent-adapter.d.ts +2 -1
- package/dist/src/codex-terminal-agent-adapter.js +40 -4
- package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
- package/dist/src/host-adapter.d.ts +1 -1
- package/dist/src/host-adapter.js +1 -1
- package/dist/src/host-bridge-tools.js +2 -2
- package/dist/src/openclaw-plugin-command-adapter.js +155 -24
- package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-helpers.d.ts +2 -0
- package/dist/src/openclaw-plugin-helpers.js +28 -7
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin-schemas.d.ts +104 -0
- package/dist/src/openclaw-plugin-schemas.js +108 -0
- package/dist/src/openclaw-plugin-schemas.js.map +1 -1
- package/dist/src/openclaw-private-authority-offers.d.ts +2 -0
- package/dist/src/openclaw-private-authority-offers.js +12 -0
- package/dist/src/openclaw-private-authority-offers.js.map +1 -1
- package/dist/src/terminal-agent-adapter.d.ts +34 -0
- package/dist/src/terminal-agent-adapter.js +54 -0
- package/dist/src/terminal-agent-adapter.js.map +1 -1
- package/dist/src/terminal-agent-bridge.d.ts +75 -1
- package/dist/src/terminal-agent-bridge.js +759 -149
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/dist/src/terminal-command-cli-adapter.d.ts +2 -1
- package/dist/src/terminal-command-cli-adapter.js +46 -8
- package/dist/src/terminal-command-cli-adapter.js.map +1 -1
- package/dist/src/terminal-dispatch-execution.js +15 -0
- package/dist/src/terminal-dispatch-execution.js.map +1 -1
- package/dist/src/terminal-identity-authority-cli-adapter.d.ts +1 -0
- package/dist/src/terminal-identity-authority-cli-adapter.js +10 -1
- package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -1
- package/dist/src/terminal-interaction-authority.d.ts +33 -0
- package/dist/src/terminal-interaction-authority.js +3 -0
- package/dist/src/terminal-interaction-authority.js.map +1 -0
- package/dist/src/terminal-interaction-cli-adapter.d.ts +66 -0
- package/dist/src/terminal-interaction-cli-adapter.js +354 -0
- package/dist/src/terminal-interaction-cli-adapter.js.map +1 -0
- package/dist/src/terminal-interaction-protocol.d.ts +111 -0
- package/dist/src/terminal-interaction-protocol.js +432 -0
- package/dist/src/terminal-interaction-protocol.js.map +1 -0
- package/dist/src/terminal-list-renderer.js +31 -2
- package/dist/src/terminal-list-renderer.js.map +1 -1
- package/dist/src/terminal-monitor-cli-adapter.js +12 -3
- package/dist/src/terminal-monitor-cli-adapter.js.map +1 -1
- package/dist/src/terminal-monitor-decision-policy.d.ts +1 -1
- package/dist/src/terminal-monitor-state-cli-adapter.js +15 -1
- package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -1
- package/dist/src/terminal-mutation-cli-runtime.d.ts +44 -0
- package/dist/src/terminal-mutation-cli-runtime.js +41 -0
- package/dist/src/terminal-mutation-cli-runtime.js.map +1 -0
- package/dist/src/terminal-questionnaire-adapter.d.ts +84 -0
- package/dist/src/terminal-questionnaire-adapter.js +677 -0
- package/dist/src/terminal-questionnaire-adapter.js.map +1 -0
- package/docs/host-bridge-profiles.md +2 -2
- package/docs/quickstart-herdr.md +1 -1
- package/docs/quickstart-tmux.md +3 -1
- package/openclaw.plugin.json +4 -0
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +11 -8
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { formatTerminalConversationId, parseTerminalConversationId, terminalControlCapabilitiesForAdapter } from "./terminal-agent-adapter.js";
|
|
2
|
+
import { formatTerminalConversationId, parseTerminalConversationId, terminalControlCapabilitiesForAdapter, terminalApprovalActionForDecision, terminalApprovalChoices } from "./terminal-agent-adapter.js";
|
|
3
3
|
import { enrichActiveProcessesWithTerminalControl, TerminalControlInputNotSentError } from "./terminal-control-provider.js";
|
|
4
4
|
import { hasCanonicalTerminalEndpoint, sameTerminalControlIncarnation, terminalEndpointFromControlRef, terminalEndpointIdentityKey } from "./terminal-control-ref.js";
|
|
5
5
|
import { CLAUDE_INJECTED_PASTE_FRAME_PROFILE, claudeInjectedPasteProofEligible, revalidateClaudeComposerProof, settleClaudeInjectedPasteComposer } from "./claude-injected-paste-proof.js";
|
|
6
|
+
import { TERMINAL_INTERACTION_SCHEMA, TERMINAL_INTERACTION_VERSION, validateTerminalInteractionProjection, validateTerminalInteractionResponse } from "./terminal-interaction-protocol.js";
|
|
7
|
+
import { inspectNativeQuestionnaire } from "./terminal-questionnaire-adapter.js";
|
|
6
8
|
// Verified Codex profiles through 0.153.0 keep Enter in paste/newline mode for
|
|
7
9
|
// 120ms after burst input. Cross that boundary rather than landing on it, and
|
|
8
10
|
// also require observable composer stability instead of treating this delay
|
|
@@ -140,6 +142,9 @@ const CLAUDE_NATIVE_STATUS_SETTLE_BY_PROFILE = {
|
|
|
140
142
|
}
|
|
141
143
|
};
|
|
142
144
|
const CODEX_LARGE_PASTE_CHAR_THRESHOLD = 1_000;
|
|
145
|
+
const TERMINAL_INTERACTION_TTL_MS = 10 * 60 * 1_000;
|
|
146
|
+
const TERMINAL_INTERACTION_IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
147
|
+
const TERMINAL_INTERACTION_FINGERPRINT_PATTERN = /^[0-9a-f]{64}$/u;
|
|
143
148
|
/** A terminal send failed at a boundary that proves input never started. */
|
|
144
149
|
export class TerminalInputNotStartedError extends Error {
|
|
145
150
|
code = "AKK_TERMINAL_INPUT_NOT_STARTED";
|
|
@@ -214,6 +219,20 @@ export class NativeInspectionDismissalError extends Error {
|
|
|
214
219
|
this.name = "NativeInspectionDismissalError";
|
|
215
220
|
}
|
|
216
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* A response reservation or terminal input may already have happened. The
|
|
224
|
+
* same interaction must never be dispatched automatically after this error.
|
|
225
|
+
*/
|
|
226
|
+
export class TerminalInteractionDispatchReservedError extends Error {
|
|
227
|
+
stage;
|
|
228
|
+
code = "AKK_TERMINAL_INTERACTION_DISPATCH_RESERVED";
|
|
229
|
+
doNotRetry = true;
|
|
230
|
+
constructor(stage, message, options = {}) {
|
|
231
|
+
super(message, options);
|
|
232
|
+
this.stage = stage;
|
|
233
|
+
this.name = "TerminalInteractionDispatchReservedError";
|
|
234
|
+
}
|
|
235
|
+
}
|
|
217
236
|
function terminalSendComposerRequirements(agent, multiline, options) {
|
|
218
237
|
const exactEmptyRetryAuthority = options.requireExactEmptyComposerAfterBeforeText;
|
|
219
238
|
const requireExactEmptyComposerBeforeText = options.requireExactEmptyComposerBeforeText === true;
|
|
@@ -272,11 +291,152 @@ function throwPostTextHookFailureAfterUserEnter(enabled, error) {
|
|
|
272
291
|
return;
|
|
273
292
|
throw new TerminalEnterDispatchReservedError("user-explicit Codex Enter was dispatched but post-text bookkeeping failed; do not retry", { cause: error });
|
|
274
293
|
}
|
|
294
|
+
function terminalApprovalPreflight(input) {
|
|
295
|
+
const { adapter, inspection, decision } = input;
|
|
296
|
+
if (!inspection.approval.approvable) {
|
|
297
|
+
return { ready: false, result: {
|
|
298
|
+
approved: false,
|
|
299
|
+
blocked: inspection.approval.blocked,
|
|
300
|
+
reason: inspection.approval.reason,
|
|
301
|
+
promptKind: inspection.approval.promptKind,
|
|
302
|
+
command: inspection.approval.command,
|
|
303
|
+
screenExcerpt: inspection.screenExcerpt
|
|
304
|
+
} };
|
|
305
|
+
}
|
|
306
|
+
if (decision === "approve_once" &&
|
|
307
|
+
(inspection.approval.action.mode ?? "keys") === "keys" &&
|
|
308
|
+
inspection.approval.action.keys.length === 0) {
|
|
309
|
+
return { ready: false, result: {
|
|
310
|
+
approved: false,
|
|
311
|
+
blocked: true,
|
|
312
|
+
decision,
|
|
313
|
+
reason: `${adapter.displayName} approval action has no keys`,
|
|
314
|
+
label: inspection.approval.action.label,
|
|
315
|
+
promptKind: inspection.approval.promptKind,
|
|
316
|
+
command: inspection.approval.command,
|
|
317
|
+
screenExcerpt: inspection.screenExcerpt
|
|
318
|
+
} };
|
|
319
|
+
}
|
|
320
|
+
const action = terminalApprovalActionForDecision(inspection.approval, decision);
|
|
321
|
+
if (!action) {
|
|
322
|
+
return { ready: false, result: {
|
|
323
|
+
approved: false,
|
|
324
|
+
blocked: true,
|
|
325
|
+
decision,
|
|
326
|
+
reason: `${adapter.displayName} does not expose the ${decision} decision for this exact approval prompt`,
|
|
327
|
+
promptKind: inspection.approval.promptKind,
|
|
328
|
+
command: inspection.approval.command,
|
|
329
|
+
screenExcerpt: inspection.screenExcerpt
|
|
330
|
+
} };
|
|
331
|
+
}
|
|
332
|
+
const decisionMode = action.mode ?? "keys";
|
|
333
|
+
const canonicalFingerprint = terminalApprovalFingerprint(adapter.agent, input.activeTerminalControl, inspection, { screen: input.screen, runtime: input.runtime, decision });
|
|
334
|
+
if (input.requiredDecisionMode &&
|
|
335
|
+
decisionMode !== input.requiredDecisionMode) {
|
|
336
|
+
return { ready: false, result: {
|
|
337
|
+
approved: false,
|
|
338
|
+
blocked: true,
|
|
339
|
+
reason: `${adapter.displayName} approval mode ${decisionMode} is not eligible for this decision`,
|
|
340
|
+
decision,
|
|
341
|
+
label: action.label,
|
|
342
|
+
promptKind: inspection.approval.promptKind,
|
|
343
|
+
command: inspection.approval.command,
|
|
344
|
+
fingerprint: canonicalFingerprint,
|
|
345
|
+
screenExcerpt: inspection.screenExcerpt,
|
|
346
|
+
decisionMode,
|
|
347
|
+
requestId: action.requestId
|
|
348
|
+
} };
|
|
349
|
+
}
|
|
350
|
+
if (decisionMode === "keys" && action.keys.length === 0) {
|
|
351
|
+
return { ready: false, result: {
|
|
352
|
+
approved: false,
|
|
353
|
+
blocked: true,
|
|
354
|
+
reason: `${adapter.displayName} approval action has no keys`,
|
|
355
|
+
decision,
|
|
356
|
+
label: action.label,
|
|
357
|
+
promptKind: inspection.approval.promptKind,
|
|
358
|
+
command: inspection.approval.command,
|
|
359
|
+
screenExcerpt: inspection.screenExcerpt
|
|
360
|
+
} };
|
|
361
|
+
}
|
|
362
|
+
if (!isTerminalApprovalPromptEvidence(inspection.approval.promptEvidence)) {
|
|
363
|
+
return { ready: false, result: {
|
|
364
|
+
approved: false,
|
|
365
|
+
blocked: true,
|
|
366
|
+
reason: `${adapter.displayName} approval prompt has no adapter-verified prompt evidence`,
|
|
367
|
+
decision,
|
|
368
|
+
label: action.label,
|
|
369
|
+
promptKind: inspection.approval.promptKind,
|
|
370
|
+
command: inspection.approval.command,
|
|
371
|
+
screenExcerpt: inspection.screenExcerpt,
|
|
372
|
+
decisionMode,
|
|
373
|
+
requestId: action.requestId
|
|
374
|
+
} };
|
|
375
|
+
}
|
|
376
|
+
const legacyFingerprint = hasCanonicalTerminalEndpoint(input.terminalControl)
|
|
377
|
+
? undefined
|
|
378
|
+
: terminalApprovalFingerprint(adapter.agent, input.terminalControl, inspection, { screen: input.screen, runtime: input.runtime, decision });
|
|
379
|
+
const useLegacyFingerprint = Boolean(input.expectedFingerprint &&
|
|
380
|
+
input.expectedFingerprint === legacyFingerprint);
|
|
381
|
+
const fingerprintControl = useLegacyFingerprint
|
|
382
|
+
? input.terminalControl
|
|
383
|
+
: input.activeTerminalControl;
|
|
384
|
+
const fingerprint = useLegacyFingerprint
|
|
385
|
+
? legacyFingerprint
|
|
386
|
+
: canonicalFingerprint;
|
|
387
|
+
if (adapter.agent === "claude" &&
|
|
388
|
+
!input.expectedFingerprint) {
|
|
389
|
+
return { ready: false, result: {
|
|
390
|
+
approved: false,
|
|
391
|
+
blocked: true,
|
|
392
|
+
reason: "screen approval requires the latest expected fingerprint",
|
|
393
|
+
decision,
|
|
394
|
+
key: action.keys.length === 1 ? action.keys[0] : undefined,
|
|
395
|
+
keys: action.keys,
|
|
396
|
+
label: action.label,
|
|
397
|
+
promptKind: inspection.approval.promptKind,
|
|
398
|
+
command: inspection.approval.command,
|
|
399
|
+
cwd: inspection.approval.cwd,
|
|
400
|
+
toolName: inspection.approval.toolName,
|
|
401
|
+
requestDetail: inspection.approval.requestDetail,
|
|
402
|
+
fingerprint,
|
|
403
|
+
screenExcerpt: inspection.screenExcerpt,
|
|
404
|
+
decisionMode,
|
|
405
|
+
requestId: action.requestId
|
|
406
|
+
} };
|
|
407
|
+
}
|
|
408
|
+
if (input.expectedFingerprint &&
|
|
409
|
+
input.expectedFingerprint !== fingerprint) {
|
|
410
|
+
return { ready: false, result: {
|
|
411
|
+
approved: false,
|
|
412
|
+
blocked: true,
|
|
413
|
+
reason: "approval fingerprint changed before execution",
|
|
414
|
+
decision,
|
|
415
|
+
key: action.keys.length === 1 ? action.keys[0] : undefined,
|
|
416
|
+
keys: action.keys,
|
|
417
|
+
label: action.label,
|
|
418
|
+
promptKind: inspection.approval.promptKind,
|
|
419
|
+
command: inspection.approval.command,
|
|
420
|
+
fingerprint,
|
|
421
|
+
screenExcerpt: inspection.screenExcerpt,
|
|
422
|
+
decisionMode,
|
|
423
|
+
requestId: action.requestId
|
|
424
|
+
} };
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
ready: true,
|
|
428
|
+
action,
|
|
429
|
+
decisionMode,
|
|
430
|
+
fingerprintControl,
|
|
431
|
+
fingerprint
|
|
432
|
+
};
|
|
433
|
+
}
|
|
275
434
|
export class TerminalAgentBridge {
|
|
276
435
|
registry;
|
|
277
436
|
terminalProvider;
|
|
278
437
|
verifyIdentity;
|
|
279
438
|
nowMs;
|
|
439
|
+
now;
|
|
280
440
|
sleep;
|
|
281
441
|
diagnosticLog;
|
|
282
442
|
constructor(options) {
|
|
@@ -284,6 +444,7 @@ export class TerminalAgentBridge {
|
|
|
284
444
|
this.terminalProvider = options.terminalProvider;
|
|
285
445
|
this.verifyIdentity = options.verifyIdentity;
|
|
286
446
|
this.nowMs = options.nowMs ?? (() => performance.now());
|
|
447
|
+
this.now = options.now ?? (() => new Date());
|
|
287
448
|
this.sleep = options.sleep ?? terminalSettleDelay;
|
|
288
449
|
this.diagnosticLog = options.diagnosticLog;
|
|
289
450
|
}
|
|
@@ -405,7 +566,8 @@ export class TerminalAgentBridge {
|
|
|
405
566
|
const captured = await this.captureInspection(adapter, terminalControl, options);
|
|
406
567
|
return statusFromInspection(adapter, captured.terminalControl, captured.inspection, {
|
|
407
568
|
screen: captured.screen,
|
|
408
|
-
runtime: options.runtime
|
|
569
|
+
runtime: options.runtime,
|
|
570
|
+
now: this.now()
|
|
409
571
|
});
|
|
410
572
|
}
|
|
411
573
|
catch (error) {
|
|
@@ -1791,6 +1953,7 @@ export class TerminalAgentBridge {
|
|
|
1791
1953
|
}
|
|
1792
1954
|
async approve(agent, terminalControl, options = {}) {
|
|
1793
1955
|
const adapter = this.registry.require(agent);
|
|
1956
|
+
const decision = options.decision ?? "approve_once";
|
|
1794
1957
|
if (!adapter.capabilities.terminalApproval ||
|
|
1795
1958
|
!terminalControl.capabilities.includes("terminal_approval")) {
|
|
1796
1959
|
return {
|
|
@@ -1812,124 +1975,27 @@ export class TerminalAgentBridge {
|
|
|
1812
1975
|
const captured = await this.captureInspection(adapter, terminalControl, options);
|
|
1813
1976
|
const { inspection } = captured;
|
|
1814
1977
|
const activeTerminalControl = captured.terminalControl;
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
promptKind: inspection.approval.promptKind,
|
|
1821
|
-
command: inspection.approval.command,
|
|
1822
|
-
screenExcerpt: inspection.screenExcerpt
|
|
1823
|
-
};
|
|
1824
|
-
}
|
|
1825
|
-
const decisionMode = inspection.approval.action.mode ?? "keys";
|
|
1826
|
-
if (options.requiredDecisionMode && decisionMode !== options.requiredDecisionMode) {
|
|
1827
|
-
return {
|
|
1828
|
-
approved: false,
|
|
1829
|
-
blocked: true,
|
|
1830
|
-
reason: `${adapter.displayName} approval mode ${decisionMode} is not eligible for this decision`,
|
|
1831
|
-
label: inspection.approval.action.label,
|
|
1832
|
-
promptKind: inspection.approval.promptKind,
|
|
1833
|
-
command: inspection.approval.command,
|
|
1834
|
-
fingerprint: terminalApprovalFingerprint(adapter.agent, activeTerminalControl, inspection, {
|
|
1835
|
-
screen: captured.screen,
|
|
1836
|
-
runtime: options.runtime
|
|
1837
|
-
}),
|
|
1838
|
-
screenExcerpt: inspection.screenExcerpt,
|
|
1839
|
-
decisionMode,
|
|
1840
|
-
requestId: inspection.approval.action.requestId
|
|
1841
|
-
};
|
|
1842
|
-
}
|
|
1843
|
-
if (decisionMode === "keys" && inspection.approval.action.keys.length === 0) {
|
|
1844
|
-
return {
|
|
1845
|
-
approved: false,
|
|
1846
|
-
blocked: true,
|
|
1847
|
-
reason: `${adapter.displayName} approval action has no keys`,
|
|
1848
|
-
label: inspection.approval.action.label,
|
|
1849
|
-
promptKind: inspection.approval.promptKind,
|
|
1850
|
-
command: inspection.approval.command,
|
|
1851
|
-
screenExcerpt: inspection.screenExcerpt
|
|
1852
|
-
};
|
|
1853
|
-
}
|
|
1854
|
-
if (decisionMode === "keys" &&
|
|
1855
|
-
!isTerminalApprovalPromptEvidence(inspection.approval.promptEvidence)) {
|
|
1856
|
-
return {
|
|
1857
|
-
approved: false,
|
|
1858
|
-
blocked: true,
|
|
1859
|
-
reason: `${adapter.displayName} approval prompt has no adapter-verified prompt evidence`,
|
|
1860
|
-
label: inspection.approval.action.label,
|
|
1861
|
-
promptKind: inspection.approval.promptKind,
|
|
1862
|
-
command: inspection.approval.command,
|
|
1863
|
-
screenExcerpt: inspection.screenExcerpt,
|
|
1864
|
-
decisionMode,
|
|
1865
|
-
requestId: inspection.approval.action.requestId
|
|
1866
|
-
};
|
|
1867
|
-
}
|
|
1868
|
-
const canonicalFingerprint = terminalApprovalFingerprint(adapter.agent, activeTerminalControl, inspection, {
|
|
1978
|
+
const preflight = terminalApprovalPreflight({
|
|
1979
|
+
adapter,
|
|
1980
|
+
terminalControl,
|
|
1981
|
+
activeTerminalControl,
|
|
1982
|
+
inspection,
|
|
1869
1983
|
screen: captured.screen,
|
|
1984
|
+
decision,
|
|
1985
|
+
expectedFingerprint: options.expectedFingerprint,
|
|
1986
|
+
requiredDecisionMode: options.requiredDecisionMode,
|
|
1870
1987
|
runtime: options.runtime
|
|
1871
1988
|
});
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
screen: captured.screen,
|
|
1876
|
-
runtime: options.runtime
|
|
1877
|
-
});
|
|
1878
|
-
const useLegacyFingerprint = Boolean(options.expectedFingerprint &&
|
|
1879
|
-
options.expectedFingerprint === legacyFingerprint);
|
|
1880
|
-
const fingerprintControl = useLegacyFingerprint
|
|
1881
|
-
? terminalControl
|
|
1882
|
-
: activeTerminalControl;
|
|
1883
|
-
const fingerprint = useLegacyFingerprint
|
|
1884
|
-
? legacyFingerprint
|
|
1885
|
-
: canonicalFingerprint;
|
|
1886
|
-
if (adapter.agent === "claude" &&
|
|
1887
|
-
decisionMode === "keys" &&
|
|
1888
|
-
!options.expectedFingerprint) {
|
|
1889
|
-
return {
|
|
1890
|
-
approved: false,
|
|
1891
|
-
blocked: true,
|
|
1892
|
-
reason: "screen approval requires the latest expected fingerprint",
|
|
1893
|
-
key: inspection.approval.action.keys.length === 1
|
|
1894
|
-
? inspection.approval.action.keys[0]
|
|
1895
|
-
: undefined,
|
|
1896
|
-
keys: inspection.approval.action.keys,
|
|
1897
|
-
label: inspection.approval.action.label,
|
|
1898
|
-
promptKind: inspection.approval.promptKind,
|
|
1899
|
-
command: inspection.approval.command,
|
|
1900
|
-
cwd: inspection.approval.cwd,
|
|
1901
|
-
toolName: inspection.approval.toolName,
|
|
1902
|
-
requestDetail: inspection.approval.requestDetail,
|
|
1903
|
-
fingerprint,
|
|
1904
|
-
screenExcerpt: inspection.screenExcerpt,
|
|
1905
|
-
decisionMode,
|
|
1906
|
-
requestId: inspection.approval.action.requestId
|
|
1907
|
-
};
|
|
1908
|
-
}
|
|
1909
|
-
if (options.expectedFingerprint && options.expectedFingerprint !== fingerprint) {
|
|
1910
|
-
return {
|
|
1911
|
-
approved: false,
|
|
1912
|
-
blocked: true,
|
|
1913
|
-
reason: "approval fingerprint changed before execution",
|
|
1914
|
-
key: inspection.approval.action.keys.length === 1
|
|
1915
|
-
? inspection.approval.action.keys[0]
|
|
1916
|
-
: undefined,
|
|
1917
|
-
keys: inspection.approval.action.keys,
|
|
1918
|
-
label: inspection.approval.action.label,
|
|
1919
|
-
promptKind: inspection.approval.promptKind,
|
|
1920
|
-
command: inspection.approval.command,
|
|
1921
|
-
fingerprint,
|
|
1922
|
-
screenExcerpt: inspection.screenExcerpt,
|
|
1923
|
-
decisionMode,
|
|
1924
|
-
requestId: inspection.approval.action.requestId
|
|
1925
|
-
};
|
|
1926
|
-
}
|
|
1989
|
+
if (!preflight.ready)
|
|
1990
|
+
return preflight.result;
|
|
1991
|
+
const { action, decisionMode, fingerprintControl, fingerprint } = preflight;
|
|
1927
1992
|
if (options.authorize) {
|
|
1928
1993
|
const authorization = await options.authorize({
|
|
1929
1994
|
agent: adapter.agent,
|
|
1930
1995
|
terminalControl: activeTerminalControl,
|
|
1931
1996
|
inspection,
|
|
1932
1997
|
fingerprint,
|
|
1998
|
+
decision,
|
|
1933
1999
|
runtime: options.runtime
|
|
1934
2000
|
});
|
|
1935
2001
|
if (!authorization.approved) {
|
|
@@ -1937,11 +2003,12 @@ export class TerminalAgentBridge {
|
|
|
1937
2003
|
approved: false,
|
|
1938
2004
|
blocked: true,
|
|
1939
2005
|
reason: authorization.reason ?? "approval was not authorized",
|
|
1940
|
-
|
|
1941
|
-
|
|
2006
|
+
decision,
|
|
2007
|
+
key: action.keys.length === 1
|
|
2008
|
+
? action.keys[0]
|
|
1942
2009
|
: undefined,
|
|
1943
|
-
keys:
|
|
1944
|
-
label:
|
|
2010
|
+
keys: action.keys,
|
|
2011
|
+
label: action.label,
|
|
1945
2012
|
promptKind: inspection.approval.promptKind,
|
|
1946
2013
|
command: inspection.approval.command,
|
|
1947
2014
|
toolName: inspection.approval.toolName,
|
|
@@ -1949,7 +2016,7 @@ export class TerminalAgentBridge {
|
|
|
1949
2016
|
fingerprint,
|
|
1950
2017
|
screenExcerpt: inspection.screenExcerpt,
|
|
1951
2018
|
decisionMode,
|
|
1952
|
-
requestId:
|
|
2019
|
+
requestId: action.requestId
|
|
1953
2020
|
};
|
|
1954
2021
|
}
|
|
1955
2022
|
}
|
|
@@ -1968,21 +2035,34 @@ export class TerminalAgentBridge {
|
|
|
1968
2035
|
screenExcerpt: recapturedInspection.screenExcerpt
|
|
1969
2036
|
};
|
|
1970
2037
|
}
|
|
1971
|
-
const
|
|
2038
|
+
const recapturedAction = terminalApprovalActionForDecision(recapturedInspection.approval, decision);
|
|
2039
|
+
if (!recapturedAction) {
|
|
2040
|
+
return {
|
|
2041
|
+
approved: false,
|
|
2042
|
+
blocked: true,
|
|
2043
|
+
decision,
|
|
2044
|
+
reason: `the ${decision} decision is no longer available after authorization`,
|
|
2045
|
+
promptKind: recapturedInspection.approval.promptKind,
|
|
2046
|
+
screenExcerpt: recapturedInspection.screenExcerpt
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
const recapturedDecisionMode = recapturedAction.mode ?? "keys";
|
|
1972
2050
|
const recapturedFingerprint = terminalApprovalFingerprint(adapter.agent, fingerprintControl, recapturedInspection, {
|
|
1973
2051
|
screen: recaptured.screen,
|
|
1974
|
-
runtime: options.runtime
|
|
2052
|
+
runtime: options.runtime,
|
|
2053
|
+
decision
|
|
1975
2054
|
});
|
|
1976
2055
|
if (recapturedDecisionMode !== decisionMode) {
|
|
1977
2056
|
return {
|
|
1978
2057
|
approved: false,
|
|
1979
2058
|
blocked: true,
|
|
1980
2059
|
reason: "approval decision mode changed after authorization",
|
|
1981
|
-
|
|
1982
|
-
|
|
2060
|
+
decision,
|
|
2061
|
+
key: recapturedAction.keys.length === 1
|
|
2062
|
+
? recapturedAction.keys[0]
|
|
1983
2063
|
: undefined,
|
|
1984
|
-
keys:
|
|
1985
|
-
label:
|
|
2064
|
+
keys: recapturedAction.keys,
|
|
2065
|
+
label: recapturedAction.label,
|
|
1986
2066
|
promptKind: recapturedInspection.approval.promptKind,
|
|
1987
2067
|
command: recapturedInspection.approval.command,
|
|
1988
2068
|
cwd: recapturedInspection.approval.cwd,
|
|
@@ -1991,7 +2071,7 @@ export class TerminalAgentBridge {
|
|
|
1991
2071
|
fingerprint: recapturedFingerprint,
|
|
1992
2072
|
screenExcerpt: recapturedInspection.screenExcerpt,
|
|
1993
2073
|
decisionMode: recapturedDecisionMode,
|
|
1994
|
-
requestId:
|
|
2074
|
+
requestId: recapturedAction.requestId
|
|
1995
2075
|
};
|
|
1996
2076
|
}
|
|
1997
2077
|
if (recapturedFingerprint !== fingerprint) {
|
|
@@ -1999,11 +2079,12 @@ export class TerminalAgentBridge {
|
|
|
1999
2079
|
approved: false,
|
|
2000
2080
|
blocked: true,
|
|
2001
2081
|
reason: "approval fingerprint changed after authorization",
|
|
2002
|
-
|
|
2003
|
-
|
|
2082
|
+
decision,
|
|
2083
|
+
key: recapturedAction.keys.length === 1
|
|
2084
|
+
? recapturedAction.keys[0]
|
|
2004
2085
|
: undefined,
|
|
2005
|
-
keys:
|
|
2006
|
-
label:
|
|
2086
|
+
keys: recapturedAction.keys,
|
|
2087
|
+
label: recapturedAction.label,
|
|
2007
2088
|
promptKind: recapturedInspection.approval.promptKind,
|
|
2008
2089
|
command: recapturedInspection.approval.command,
|
|
2009
2090
|
cwd: recapturedInspection.approval.cwd,
|
|
@@ -2012,7 +2093,7 @@ export class TerminalAgentBridge {
|
|
|
2012
2093
|
fingerprint: recapturedFingerprint,
|
|
2013
2094
|
screenExcerpt: recapturedInspection.screenExcerpt,
|
|
2014
2095
|
decisionMode: recapturedDecisionMode,
|
|
2015
|
-
requestId:
|
|
2096
|
+
requestId: recapturedAction.requestId
|
|
2016
2097
|
};
|
|
2017
2098
|
}
|
|
2018
2099
|
const verifiedForApproval = await this.verifyTerminalIdentity(adapter.agent, recaptured.terminalControl, options.runtime);
|
|
@@ -2030,11 +2111,13 @@ export class TerminalAgentBridge {
|
|
|
2030
2111
|
terminalControl: verifiedForApproval,
|
|
2031
2112
|
inspection: recapturedInspection,
|
|
2032
2113
|
fingerprint: recapturedFingerprint,
|
|
2033
|
-
|
|
2114
|
+
decision,
|
|
2115
|
+
keys: recapturedAction.keys,
|
|
2034
2116
|
runtime: options.runtime
|
|
2035
2117
|
});
|
|
2036
2118
|
let dispatchTerminalControl = verifiedForApproval;
|
|
2037
2119
|
let dispatchInspection = recapturedInspection;
|
|
2120
|
+
let dispatchAction = recapturedAction;
|
|
2038
2121
|
if (options.beforeKeyDispatch) {
|
|
2039
2122
|
const afterReservation = await this.captureInspection(adapter, verifiedForApproval, options);
|
|
2040
2123
|
const afterReservationInspection = afterReservation.inspection;
|
|
@@ -2051,22 +2134,35 @@ export class TerminalAgentBridge {
|
|
|
2051
2134
|
screenExcerpt: afterReservationInspection.screenExcerpt
|
|
2052
2135
|
};
|
|
2053
2136
|
}
|
|
2054
|
-
const
|
|
2137
|
+
const afterReservationAction = terminalApprovalActionForDecision(afterReservationInspection.approval, decision);
|
|
2138
|
+
if (!afterReservationAction) {
|
|
2139
|
+
return {
|
|
2140
|
+
approved: false,
|
|
2141
|
+
blocked: true,
|
|
2142
|
+
decision,
|
|
2143
|
+
reason: `the ${decision} decision is no longer available after dispatch reservation`,
|
|
2144
|
+
promptKind: afterReservationInspection.approval.promptKind,
|
|
2145
|
+
screenExcerpt: afterReservationInspection.screenExcerpt
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
const afterReservationMode = afterReservationAction.mode ?? "keys";
|
|
2055
2149
|
const afterReservationFingerprint = terminalApprovalFingerprint(adapter.agent, fingerprintControl, afterReservationInspection, {
|
|
2056
2150
|
screen: afterReservation.screen,
|
|
2057
|
-
runtime: options.runtime
|
|
2151
|
+
runtime: options.runtime,
|
|
2152
|
+
decision
|
|
2058
2153
|
});
|
|
2059
2154
|
if (afterReservationMode !== "keys" ||
|
|
2060
2155
|
afterReservationFingerprint !== recapturedFingerprint) {
|
|
2061
2156
|
return {
|
|
2062
2157
|
approved: false,
|
|
2063
2158
|
blocked: true,
|
|
2159
|
+
decision,
|
|
2064
2160
|
reason: "approval fingerprint changed after dispatch reservation",
|
|
2065
|
-
key:
|
|
2066
|
-
?
|
|
2161
|
+
key: afterReservationAction.keys.length === 1
|
|
2162
|
+
? afterReservationAction.keys[0]
|
|
2067
2163
|
: undefined,
|
|
2068
|
-
keys:
|
|
2069
|
-
label:
|
|
2164
|
+
keys: afterReservationAction.keys,
|
|
2165
|
+
label: afterReservationAction.label,
|
|
2070
2166
|
promptKind: afterReservationInspection.approval.promptKind,
|
|
2071
2167
|
command: afterReservationInspection.approval.command,
|
|
2072
2168
|
cwd: afterReservationInspection.approval.cwd,
|
|
@@ -2075,11 +2171,12 @@ export class TerminalAgentBridge {
|
|
|
2075
2171
|
fingerprint: afterReservationFingerprint,
|
|
2076
2172
|
screenExcerpt: afterReservationInspection.screenExcerpt,
|
|
2077
2173
|
decisionMode: afterReservationMode,
|
|
2078
|
-
requestId:
|
|
2174
|
+
requestId: afterReservationAction.requestId
|
|
2079
2175
|
};
|
|
2080
2176
|
}
|
|
2081
2177
|
dispatchTerminalControl = afterReservation.terminalControl;
|
|
2082
2178
|
dispatchInspection = afterReservationInspection;
|
|
2179
|
+
dispatchAction = afterReservationAction;
|
|
2083
2180
|
}
|
|
2084
2181
|
if (!dispatchInspection.approval.approvable) {
|
|
2085
2182
|
return {
|
|
@@ -2094,22 +2191,108 @@ export class TerminalAgentBridge {
|
|
|
2094
2191
|
if (!sameTerminalControlIdentity(dispatchTerminalControl, verifiedImmediatelyBeforeSend)) {
|
|
2095
2192
|
throw new Error("terminal control identity changed after the final approval capture");
|
|
2096
2193
|
}
|
|
2097
|
-
await this.terminalProvider.sendKeys(this.terminalProvider.endpoint(verifiedImmediatelyBeforeSend),
|
|
2194
|
+
await this.terminalProvider.sendKeys(this.terminalProvider.endpoint(verifiedImmediatelyBeforeSend), dispatchAction.keys);
|
|
2098
2195
|
return {
|
|
2099
|
-
approved:
|
|
2196
|
+
approved: decision === "approve_once",
|
|
2197
|
+
decisionDispatched: true,
|
|
2198
|
+
decision,
|
|
2100
2199
|
blocked: false,
|
|
2101
|
-
key:
|
|
2102
|
-
?
|
|
2200
|
+
key: dispatchAction.keys.length === 1
|
|
2201
|
+
? dispatchAction.keys[0]
|
|
2103
2202
|
: undefined,
|
|
2104
|
-
keys:
|
|
2105
|
-
label:
|
|
2203
|
+
keys: dispatchAction.keys,
|
|
2204
|
+
label: dispatchAction.label,
|
|
2106
2205
|
promptKind: dispatchApproval.promptKind,
|
|
2107
2206
|
command: dispatchApproval.command,
|
|
2108
2207
|
cwd: dispatchApproval.cwd,
|
|
2109
2208
|
fingerprint: recapturedFingerprint,
|
|
2110
2209
|
screenExcerpt: dispatchInspection.screenExcerpt,
|
|
2111
2210
|
decisionMode: recapturedDecisionMode,
|
|
2112
|
-
requestId:
|
|
2211
|
+
requestId: dispatchAction.requestId
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
async respondInteraction(agent, terminalControl, response, options) {
|
|
2215
|
+
const runtime = {
|
|
2216
|
+
...options.runtime,
|
|
2217
|
+
agentVersion: options.agentVersion
|
|
2218
|
+
};
|
|
2219
|
+
const optionReason = terminalInteractionOptionPreflightReason(options, runtime);
|
|
2220
|
+
if (optionReason) {
|
|
2221
|
+
return blockedTerminalInteractionResponse(response, optionReason);
|
|
2222
|
+
}
|
|
2223
|
+
const first = await this.captureTerminalInteractionOffer(agent, terminalControl, runtime, options.scrollbackLines).catch((error) => error);
|
|
2224
|
+
if (first instanceof Error) {
|
|
2225
|
+
return blockedTerminalInteractionResponse(response, first.message);
|
|
2226
|
+
}
|
|
2227
|
+
const firstReason = terminalInteractionOfferPreflightReason(first.offer, options);
|
|
2228
|
+
if (firstReason || !first.offer) {
|
|
2229
|
+
return blockedTerminalInteractionResponse(response, firstReason ?? "native questionnaire has no safe interaction offer", first.offer);
|
|
2230
|
+
}
|
|
2231
|
+
const validatedResponse = validateTerminalInteractionResponse(response, first.offer.projection, { now: this.now() });
|
|
2232
|
+
const planReason = terminalInteractionPlanPreflightReason(validatedResponse, first.offer.actionPlan);
|
|
2233
|
+
if (planReason) {
|
|
2234
|
+
return blockedTerminalInteractionResponse(validatedResponse, planReason, first.offer);
|
|
2235
|
+
}
|
|
2236
|
+
const capabilityReason = terminalInteractionCapabilityReason(this.terminalProvider, first.terminalControl, first.offer.actionPlan);
|
|
2237
|
+
if (capabilityReason) {
|
|
2238
|
+
return blockedTerminalInteractionResponse(validatedResponse, capabilityReason, first.offer);
|
|
2239
|
+
}
|
|
2240
|
+
const authorization = await authorizeTerminalInteraction(options.authorize, interactionHookContext(agent, first.terminalControl, first.offer, validatedResponse, runtime));
|
|
2241
|
+
if (!authorization.approved) {
|
|
2242
|
+
return blockedTerminalInteractionResponse(validatedResponse, authorization.reason ?? "terminal interaction response was not authorized", first.offer);
|
|
2243
|
+
}
|
|
2244
|
+
const afterAuthorization = await this.captureTerminalInteractionOffer(agent, first.terminalControl, runtime, options.scrollbackLines).catch((error) => error);
|
|
2245
|
+
if (afterAuthorization instanceof Error) {
|
|
2246
|
+
return blockedTerminalInteractionResponse(validatedResponse, afterAuthorization.message, first.offer);
|
|
2247
|
+
}
|
|
2248
|
+
if (!sameTerminalInteractionOffer(first, afterAuthorization)) {
|
|
2249
|
+
return blockedTerminalInteractionResponse(validatedResponse, "native questionnaire changed after authorization", first.offer);
|
|
2250
|
+
}
|
|
2251
|
+
await reserveTerminalInteractionDispatch(options.beforeDispatch, interactionHookContext(agent, afterAuthorization.terminalControl, afterAuthorization.offer, validatedResponse, runtime));
|
|
2252
|
+
const finalCapture = await this.captureTerminalInteractionOffer(agent, afterAuthorization.terminalControl, runtime, options.scrollbackLines).catch((error) => {
|
|
2253
|
+
if (options.beforeDispatch) {
|
|
2254
|
+
throw terminalInteractionReservedError("reservation_uncertain", error, "cannot recapture native questionnaire after dispatch reservation");
|
|
2255
|
+
}
|
|
2256
|
+
return error;
|
|
2257
|
+
});
|
|
2258
|
+
if (finalCapture instanceof Error) {
|
|
2259
|
+
return blockedTerminalInteractionResponse(validatedResponse, finalCapture.message, first.offer);
|
|
2260
|
+
}
|
|
2261
|
+
if (!sameTerminalInteractionOffer(afterAuthorization, finalCapture)) {
|
|
2262
|
+
if (options.beforeDispatch) {
|
|
2263
|
+
throw new TerminalInteractionDispatchReservedError("reservation_uncertain", "native questionnaire changed after dispatch reservation");
|
|
2264
|
+
}
|
|
2265
|
+
return blockedTerminalInteractionResponse(validatedResponse, "native questionnaire changed before dispatch", first.offer);
|
|
2266
|
+
}
|
|
2267
|
+
const verified = await this.verifyTerminalIdentity(agent, finalCapture.terminalControl, runtime).catch((error) => {
|
|
2268
|
+
throw terminalInteractionReservedError(options.beforeDispatch ? "reservation_uncertain" : "key_uncertain", error, "terminal identity could not be verified immediately before interaction dispatch");
|
|
2269
|
+
});
|
|
2270
|
+
if (!sameTerminalControlIdentity(finalCapture.terminalControl, verified)) {
|
|
2271
|
+
throw new TerminalInteractionDispatchReservedError(options.beforeDispatch ? "reservation_uncertain" : "key_uncertain", "terminal identity changed after the final questionnaire capture");
|
|
2272
|
+
}
|
|
2273
|
+
const answer = validatedResponse.answers[0];
|
|
2274
|
+
const outcome = await dispatchTerminalInteractionAnswer({
|
|
2275
|
+
agent,
|
|
2276
|
+
provider: this.terminalProvider,
|
|
2277
|
+
terminalControl: verified,
|
|
2278
|
+
actionPlan: finalCapture.offer.actionPlan,
|
|
2279
|
+
answer,
|
|
2280
|
+
sleep: this.sleep,
|
|
2281
|
+
reverifyTerminalIdentity: async (currentControl) => {
|
|
2282
|
+
const reverified = await this.verifyTerminalIdentity(agent, currentControl, runtime);
|
|
2283
|
+
if (!sameTerminalControlIdentity(currentControl, reverified)) {
|
|
2284
|
+
throw new Error("terminal identity changed between interaction text and Enter");
|
|
2285
|
+
}
|
|
2286
|
+
return reverified;
|
|
2287
|
+
}
|
|
2288
|
+
});
|
|
2289
|
+
return {
|
|
2290
|
+
responded: true,
|
|
2291
|
+
blocked: false,
|
|
2292
|
+
interactionId: validatedResponse.interaction_id,
|
|
2293
|
+
questionId: answer.question_id,
|
|
2294
|
+
responseKind: answer.response_kind,
|
|
2295
|
+
outcome
|
|
2113
2296
|
};
|
|
2114
2297
|
}
|
|
2115
2298
|
async monitorPoll(options) {
|
|
@@ -2126,7 +2309,8 @@ export class TerminalAgentBridge {
|
|
|
2126
2309
|
inspection = captured.inspection;
|
|
2127
2310
|
status = statusFromInspection(adapter, captured.terminalControl, inspection, {
|
|
2128
2311
|
screen: captured.screen,
|
|
2129
|
-
runtime: options.screenOptions?.runtime
|
|
2312
|
+
runtime: options.screenOptions?.runtime,
|
|
2313
|
+
now: this.now()
|
|
2130
2314
|
});
|
|
2131
2315
|
}
|
|
2132
2316
|
catch (error) {
|
|
@@ -2181,6 +2365,25 @@ export class TerminalAgentBridge {
|
|
|
2181
2365
|
})
|
|
2182
2366
|
};
|
|
2183
2367
|
}
|
|
2368
|
+
async captureTerminalInteractionOffer(agent, terminalControl, runtime, scrollbackLines) {
|
|
2369
|
+
const adapter = this.registry.require(agent);
|
|
2370
|
+
const captured = await this.captureInspection(adapter, terminalControl, {
|
|
2371
|
+
scrollbackLines,
|
|
2372
|
+
runtime
|
|
2373
|
+
});
|
|
2374
|
+
return {
|
|
2375
|
+
terminalControl: captured.terminalControl,
|
|
2376
|
+
inspection: captured.inspection,
|
|
2377
|
+
offer: terminalInteractionBridgeOffer({
|
|
2378
|
+
agent,
|
|
2379
|
+
terminalControl: captured.terminalControl,
|
|
2380
|
+
inspection: captured.inspection,
|
|
2381
|
+
screen: captured.screen,
|
|
2382
|
+
runtime,
|
|
2383
|
+
now: this.now()
|
|
2384
|
+
})
|
|
2385
|
+
};
|
|
2386
|
+
}
|
|
2184
2387
|
async verifyTerminalIdentity(agent, terminalControl, runtime) {
|
|
2185
2388
|
let verifiedControl = terminalControl;
|
|
2186
2389
|
if (this.verifyIdentity) {
|
|
@@ -2914,7 +3117,13 @@ export function terminalApprovalFingerprint(agent, terminalControl, inspection,
|
|
|
2914
3117
|
if (!inspection.approval.approvable) {
|
|
2915
3118
|
return undefined;
|
|
2916
3119
|
}
|
|
2917
|
-
const
|
|
3120
|
+
const decision = options.decision ?? "approve_once";
|
|
3121
|
+
const choices = terminalApprovalChoices(inspection.approval);
|
|
3122
|
+
const action = terminalApprovalActionForDecision(inspection.approval, decision);
|
|
3123
|
+
if (!action) {
|
|
3124
|
+
return undefined;
|
|
3125
|
+
}
|
|
3126
|
+
const decisionMode = action.mode ?? "keys";
|
|
2918
3127
|
const promptEvidence = inspection.approval.promptEvidence;
|
|
2919
3128
|
if (decisionMode === "keys" &&
|
|
2920
3129
|
!isTerminalApprovalPromptEvidence(promptEvidence)) {
|
|
@@ -2968,8 +3177,16 @@ export function terminalApprovalFingerprint(agent, terminalControl, inspection,
|
|
|
2968
3177
|
message_id: options.runtime?.messageId,
|
|
2969
3178
|
terminal_target: options.runtime?.terminalTarget
|
|
2970
3179
|
},
|
|
2971
|
-
|
|
2972
|
-
|
|
3180
|
+
decision,
|
|
3181
|
+
keys: action.keys,
|
|
3182
|
+
label: action.label,
|
|
3183
|
+
available_choices: choices.map((choice) => ({
|
|
3184
|
+
decision: choice.decision,
|
|
3185
|
+
keys: choice.keys,
|
|
3186
|
+
label: choice.label,
|
|
3187
|
+
mode: choice.mode ?? "keys",
|
|
3188
|
+
request_id: choice.requestId
|
|
3189
|
+
})),
|
|
2973
3190
|
prompt_kind: inspection.approval.promptKind,
|
|
2974
3191
|
command: inspection.approval.command,
|
|
2975
3192
|
cwd: inspection.approval.cwd,
|
|
@@ -2992,7 +3209,7 @@ export function terminalApprovalFingerprint(agent, terminalControl, inspection,
|
|
|
2992
3209
|
}
|
|
2993
3210
|
: undefined,
|
|
2994
3211
|
decision_mode: decisionMode,
|
|
2995
|
-
request_id:
|
|
3212
|
+
request_id: action.requestId
|
|
2996
3213
|
}))
|
|
2997
3214
|
.digest("hex");
|
|
2998
3215
|
}
|
|
@@ -3006,12 +3223,396 @@ function isTerminalApprovalPromptEvidence(value) {
|
|
|
3006
3223
|
typeof evidence.sha256 === "string" &&
|
|
3007
3224
|
/^[0-9a-f]{64}$/u.test(evidence.sha256);
|
|
3008
3225
|
}
|
|
3226
|
+
function terminalInteractionTurnId(runtime) {
|
|
3227
|
+
// Questionnaire mutation is managed-Turn-only. A raw terminal's
|
|
3228
|
+
// conversationId is a discovery label, not durable response authority.
|
|
3229
|
+
const candidate = runtime?.turnId;
|
|
3230
|
+
return typeof candidate === "string" &&
|
|
3231
|
+
TERMINAL_INTERACTION_IDENTIFIER_PATTERN.test(candidate)
|
|
3232
|
+
? candidate
|
|
3233
|
+
: undefined;
|
|
3234
|
+
}
|
|
3235
|
+
function terminalInteractionExpiry(now) {
|
|
3236
|
+
const nowMs = now.getTime();
|
|
3237
|
+
if (!Number.isFinite(nowMs)) {
|
|
3238
|
+
return undefined;
|
|
3239
|
+
}
|
|
3240
|
+
const windowStart = Math.floor(nowMs / TERMINAL_INTERACTION_TTL_MS) *
|
|
3241
|
+
TERMINAL_INTERACTION_TTL_MS;
|
|
3242
|
+
return new Date(windowStart + TERMINAL_INTERACTION_TTL_MS).toISOString();
|
|
3243
|
+
}
|
|
3244
|
+
function terminalInteractionQuestion(question) {
|
|
3245
|
+
const common = {
|
|
3246
|
+
question_id: question.question_id,
|
|
3247
|
+
prompt: question.prompt,
|
|
3248
|
+
required: question.required
|
|
3249
|
+
};
|
|
3250
|
+
if (question.response_kind === "single_select" ||
|
|
3251
|
+
question.response_kind === "multi_select") {
|
|
3252
|
+
return {
|
|
3253
|
+
...common,
|
|
3254
|
+
response_kind: question.response_kind,
|
|
3255
|
+
options: question.options ?? []
|
|
3256
|
+
};
|
|
3257
|
+
}
|
|
3258
|
+
return {
|
|
3259
|
+
...common,
|
|
3260
|
+
response_kind: question.response_kind
|
|
3261
|
+
};
|
|
3262
|
+
}
|
|
3263
|
+
function terminalInteractionCanonicalIdentity(terminalControl, runtime) {
|
|
3264
|
+
if (!hasCanonicalTerminalEndpoint(terminalControl)) {
|
|
3265
|
+
return undefined;
|
|
3266
|
+
}
|
|
3267
|
+
const terminal = terminalEndpointFromControlRef(terminalControl);
|
|
3268
|
+
if (!Number.isInteger(terminal.processAnchorPid) ||
|
|
3269
|
+
Number(terminal.processAnchorPid) <= 0) {
|
|
3270
|
+
return undefined;
|
|
3271
|
+
}
|
|
3272
|
+
return {
|
|
3273
|
+
terminal_identity: terminalEndpointIdentityKey(terminal),
|
|
3274
|
+
terminal_process_anchor_pid: terminal.processAnchorPid,
|
|
3275
|
+
agent_pid: runtime.pid,
|
|
3276
|
+
native_session_id: runtime.nativeSessionId,
|
|
3277
|
+
native_process_uuid: runtime.nativeProcessUuid,
|
|
3278
|
+
native_process_birth: runtime.nativeProcessBirth,
|
|
3279
|
+
native_process_started_at: runtime.nativeProcessStartedAt,
|
|
3280
|
+
native_rollout: runtime.nativeRollout
|
|
3281
|
+
};
|
|
3282
|
+
}
|
|
3283
|
+
function terminalInteractionBridgeOffer(input) {
|
|
3284
|
+
const turnId = terminalInteractionTurnId(input.runtime);
|
|
3285
|
+
const agentVersion = input.runtime?.agentVersion;
|
|
3286
|
+
const expiry = terminalInteractionExpiry(input.now);
|
|
3287
|
+
const canonicalIdentity = input.runtime
|
|
3288
|
+
? terminalInteractionCanonicalIdentity(input.terminalControl, input.runtime)
|
|
3289
|
+
: undefined;
|
|
3290
|
+
if (!turnId ||
|
|
3291
|
+
typeof agentVersion !== "string" ||
|
|
3292
|
+
agentVersion.length === 0 ||
|
|
3293
|
+
!expiry ||
|
|
3294
|
+
!canonicalIdentity ||
|
|
3295
|
+
input.inspection.approval.blocked) {
|
|
3296
|
+
return undefined;
|
|
3297
|
+
}
|
|
3298
|
+
const nativeInspection = inspectNativeQuestionnaire({
|
|
3299
|
+
agent: input.agent,
|
|
3300
|
+
version: agentVersion,
|
|
3301
|
+
screen: input.screen
|
|
3302
|
+
});
|
|
3303
|
+
if (nativeInspection.status === "none") {
|
|
3304
|
+
return undefined;
|
|
3305
|
+
}
|
|
3306
|
+
const authorityMaterial = JSON.stringify({
|
|
3307
|
+
version: 1,
|
|
3308
|
+
agent: input.agent,
|
|
3309
|
+
turn_id: turnId,
|
|
3310
|
+
terminal: canonicalIdentity,
|
|
3311
|
+
profile: nativeInspection.profile,
|
|
3312
|
+
prompt_sha256: nativeInspection.prompt_evidence.sha256
|
|
3313
|
+
});
|
|
3314
|
+
const promptFingerprint = createHash("sha256")
|
|
3315
|
+
.update(`terminal-interaction-authority\0${authorityMaterial}`, "utf8")
|
|
3316
|
+
.digest("hex");
|
|
3317
|
+
const interactionId = `ti_${createHash("sha256")
|
|
3318
|
+
.update(`terminal-interaction-public\0${authorityMaterial}`, "utf8")
|
|
3319
|
+
.digest("hex")
|
|
3320
|
+
.slice(0, 40)}`;
|
|
3321
|
+
const question = terminalInteractionQuestion(nativeInspection.question);
|
|
3322
|
+
const executable = nativeInspection.status === "actionable" &&
|
|
3323
|
+
nativeInspection.action_plan.kind !== "manual_only";
|
|
3324
|
+
const responseUncertain = input.runtime?.interactionDispatchState === "reserved" ||
|
|
3325
|
+
input.runtime?.interactionDispatchState === "uncertain";
|
|
3326
|
+
const projection = validateTerminalInteractionProjection({
|
|
3327
|
+
schema: TERMINAL_INTERACTION_SCHEMA,
|
|
3328
|
+
version: TERMINAL_INTERACTION_VERSION,
|
|
3329
|
+
interaction_id: interactionId,
|
|
3330
|
+
turn_id: turnId,
|
|
3331
|
+
agent: input.agent,
|
|
3332
|
+
kind: "questionnaire",
|
|
3333
|
+
state: responseUncertain
|
|
3334
|
+
? "response_uncertain"
|
|
3335
|
+
: executable
|
|
3336
|
+
? "pending"
|
|
3337
|
+
: "manual_required",
|
|
3338
|
+
step: {
|
|
3339
|
+
index: nativeInspection.current_step,
|
|
3340
|
+
total: nativeInspection.total_steps
|
|
3341
|
+
},
|
|
3342
|
+
questions: [question],
|
|
3343
|
+
expires_at: expiry,
|
|
3344
|
+
capabilities: {
|
|
3345
|
+
respond: executable && !responseUncertain,
|
|
3346
|
+
batch_response: false,
|
|
3347
|
+
free_text: question.response_kind === "free_text",
|
|
3348
|
+
multi_select: question.response_kind === "multi_select"
|
|
3349
|
+
}
|
|
3350
|
+
});
|
|
3351
|
+
return {
|
|
3352
|
+
projection,
|
|
3353
|
+
promptFingerprint,
|
|
3354
|
+
actionPlan: nativeInspection.action_plan,
|
|
3355
|
+
nativeInspection
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
3358
|
+
function terminalInteractionOptionPreflightReason(options, runtime) {
|
|
3359
|
+
if (typeof options.agentVersion !== "string" ||
|
|
3360
|
+
!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(options.agentVersion)) {
|
|
3361
|
+
return "an exact semantic agent version is required";
|
|
3362
|
+
}
|
|
3363
|
+
if (options.runtime?.agentVersion !== undefined &&
|
|
3364
|
+
options.runtime.agentVersion !== options.agentVersion) {
|
|
3365
|
+
return "runtime agent version does not match the requested interaction profile";
|
|
3366
|
+
}
|
|
3367
|
+
if (!terminalInteractionTurnId(runtime)) {
|
|
3368
|
+
return "a protocol-safe exact Turn id is required";
|
|
3369
|
+
}
|
|
3370
|
+
if (!TERMINAL_INTERACTION_FINGERPRINT_PATTERN.test(options.expectedFingerprint)) {
|
|
3371
|
+
return "a valid interaction prompt fingerprint is required";
|
|
3372
|
+
}
|
|
3373
|
+
if (!Number.isFinite(Date.parse(options.expectedExpiresAt))) {
|
|
3374
|
+
return "a valid interaction expiry is required";
|
|
3375
|
+
}
|
|
3376
|
+
return undefined;
|
|
3377
|
+
}
|
|
3378
|
+
function terminalInteractionOfferPreflightReason(offer, options) {
|
|
3379
|
+
if (!offer) {
|
|
3380
|
+
return "native questionnaire has no identity-fenced interaction offer";
|
|
3381
|
+
}
|
|
3382
|
+
if (offer.projection.state !== "pending" ||
|
|
3383
|
+
!offer.projection.capabilities.respond ||
|
|
3384
|
+
offer.actionPlan.kind === "manual_only") {
|
|
3385
|
+
return `native questionnaire requires manual response: ${offer.nativeInspection.status === "manual_required"
|
|
3386
|
+
? offer.nativeInspection.reason
|
|
3387
|
+
: "unsupported action plan"}`;
|
|
3388
|
+
}
|
|
3389
|
+
if (offer.promptFingerprint !== options.expectedFingerprint) {
|
|
3390
|
+
return "native questionnaire fingerprint changed before execution";
|
|
3391
|
+
}
|
|
3392
|
+
if (offer.projection.expires_at !== options.expectedExpiresAt) {
|
|
3393
|
+
return "native questionnaire expiry changed before execution";
|
|
3394
|
+
}
|
|
3395
|
+
return undefined;
|
|
3396
|
+
}
|
|
3397
|
+
function terminalInteractionCapabilityReason(provider, terminalControl, plan) {
|
|
3398
|
+
const requiresText = plan.kind === "free_text";
|
|
3399
|
+
const missing = [
|
|
3400
|
+
!terminalControl.capabilities.includes("screen_status")
|
|
3401
|
+
? "terminal:screen_status"
|
|
3402
|
+
: undefined,
|
|
3403
|
+
!terminalControl.capabilities.includes("send_keys")
|
|
3404
|
+
? "terminal:send_keys"
|
|
3405
|
+
: undefined,
|
|
3406
|
+
!provider.providerCapabilities.includes("stable_resource_resolution")
|
|
3407
|
+
? "provider:stable_resource_resolution"
|
|
3408
|
+
: undefined,
|
|
3409
|
+
!provider.providerCapabilities.includes("screen_capture")
|
|
3410
|
+
? "provider:screen_capture"
|
|
3411
|
+
: undefined,
|
|
3412
|
+
!provider.providerCapabilities.includes("key_delivery")
|
|
3413
|
+
? "provider:key_delivery"
|
|
3414
|
+
: undefined,
|
|
3415
|
+
requiresText && !provider.providerCapabilities.includes("text_delivery")
|
|
3416
|
+
? "provider:text_delivery"
|
|
3417
|
+
: undefined
|
|
3418
|
+
].filter((value) => value !== undefined);
|
|
3419
|
+
return missing.length > 0
|
|
3420
|
+
? `terminal interaction capability preflight failed: ${missing.join(", ")}`
|
|
3421
|
+
: undefined;
|
|
3422
|
+
}
|
|
3423
|
+
function terminalInteractionPlanPreflightReason(response, plan) {
|
|
3424
|
+
const answer = response.answers[0];
|
|
3425
|
+
if (plan.kind !== "free_text" || answer?.response_kind !== "free_text") {
|
|
3426
|
+
return undefined;
|
|
3427
|
+
}
|
|
3428
|
+
const textStage = plan.stages[0];
|
|
3429
|
+
if (textStage?.kind !== "answer_text" ||
|
|
3430
|
+
!Number.isSafeInteger(textStage.max_characters) ||
|
|
3431
|
+
textStage.max_characters < 1) {
|
|
3432
|
+
return "native free-text interaction has no verified input limit";
|
|
3433
|
+
}
|
|
3434
|
+
return answer.text.length <= textStage.max_characters
|
|
3435
|
+
? undefined
|
|
3436
|
+
: `free-text answer exceeds the verified native limit of ${textStage.max_characters} characters`;
|
|
3437
|
+
}
|
|
3438
|
+
function blockedTerminalInteractionResponse(response, reason, offer) {
|
|
3439
|
+
const answer = Array.isArray(response.answers) ? response.answers[0] : undefined;
|
|
3440
|
+
return {
|
|
3441
|
+
responded: false,
|
|
3442
|
+
blocked: true,
|
|
3443
|
+
reason,
|
|
3444
|
+
interactionId: response.interaction_id,
|
|
3445
|
+
questionId: answer?.question_id ?? offer?.projection.questions[0]?.question_id,
|
|
3446
|
+
responseKind: answer?.response_kind
|
|
3447
|
+
};
|
|
3448
|
+
}
|
|
3449
|
+
function interactionHookContext(agent, terminalControl, offer, response, runtime) {
|
|
3450
|
+
return {
|
|
3451
|
+
agent,
|
|
3452
|
+
terminalControl,
|
|
3453
|
+
fingerprint: offer.promptFingerprint,
|
|
3454
|
+
projection: offer.projection,
|
|
3455
|
+
response,
|
|
3456
|
+
runtime
|
|
3457
|
+
};
|
|
3458
|
+
}
|
|
3459
|
+
async function authorizeTerminalInteraction(authorize, context) {
|
|
3460
|
+
return authorize ? authorize(context) : { approved: true };
|
|
3461
|
+
}
|
|
3462
|
+
async function reserveTerminalInteractionDispatch(beforeDispatch, context) {
|
|
3463
|
+
if (!beforeDispatch) {
|
|
3464
|
+
return;
|
|
3465
|
+
}
|
|
3466
|
+
try {
|
|
3467
|
+
await beforeDispatch(context);
|
|
3468
|
+
}
|
|
3469
|
+
catch (error) {
|
|
3470
|
+
throw terminalInteractionReservedError("reservation_uncertain", error, "terminal interaction dispatch reservation failed");
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
function sameTerminalInteractionOffer(left, right) {
|
|
3474
|
+
return Boolean(left.offer &&
|
|
3475
|
+
right.offer &&
|
|
3476
|
+
sameTerminalControlIdentity(left.terminalControl, right.terminalControl) &&
|
|
3477
|
+
left.offer.promptFingerprint === right.offer.promptFingerprint &&
|
|
3478
|
+
JSON.stringify(left.offer.projection) === JSON.stringify(right.offer.projection) &&
|
|
3479
|
+
JSON.stringify(left.offer.actionPlan) === JSON.stringify(right.offer.actionPlan));
|
|
3480
|
+
}
|
|
3481
|
+
function terminalInteractionReservedError(stage, error, message) {
|
|
3482
|
+
if (error instanceof TerminalInteractionDispatchReservedError) {
|
|
3483
|
+
return error;
|
|
3484
|
+
}
|
|
3485
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
3486
|
+
return new TerminalInteractionDispatchReservedError(stage, `${message}: ${detail}`, { cause: error });
|
|
3487
|
+
}
|
|
3488
|
+
function terminalInteractionChoiceAction(plan, answer) {
|
|
3489
|
+
const choice = plan.choices.find((candidate) => candidate.option_id === answer.selected_option_ids[0]);
|
|
3490
|
+
const stage = choice?.stages[0];
|
|
3491
|
+
if (!choice ||
|
|
3492
|
+
choice.stages.length !== 1 ||
|
|
3493
|
+
stage?.kind !== "key" ||
|
|
3494
|
+
!/^[1-5]$/u.test(stage.key)) {
|
|
3495
|
+
throw new Error("single-select interaction has no closed adapter action");
|
|
3496
|
+
}
|
|
3497
|
+
return {
|
|
3498
|
+
key: stage.key,
|
|
3499
|
+
outcome: choice.outcome === "open_custom_text"
|
|
3500
|
+
? "custom_text_opened"
|
|
3501
|
+
: "submitted_or_advanced"
|
|
3502
|
+
};
|
|
3503
|
+
}
|
|
3504
|
+
function terminalInteractionConfirmKey(plan, answer) {
|
|
3505
|
+
const stages = answer.confirm ? plan.confirm_stages : plan.cancel_stages;
|
|
3506
|
+
const stage = stages[0];
|
|
3507
|
+
if (stages.length !== 1 ||
|
|
3508
|
+
stage?.kind !== "key" ||
|
|
3509
|
+
!["C-m", "Escape", "1", "2"].includes(stage.key) ||
|
|
3510
|
+
JSON.stringify(plan.confirm_stages) === JSON.stringify(plan.cancel_stages)) {
|
|
3511
|
+
throw new Error("confirmation interaction has no closed adapter action");
|
|
3512
|
+
}
|
|
3513
|
+
return stage.key;
|
|
3514
|
+
}
|
|
3515
|
+
async function sendTerminalInteractionKeys(provider, terminalControl, keys) {
|
|
3516
|
+
try {
|
|
3517
|
+
await provider.sendKeys(provider.endpoint(terminalControl), keys);
|
|
3518
|
+
}
|
|
3519
|
+
catch (error) {
|
|
3520
|
+
throw terminalInteractionReservedError("key_uncertain", error, "terminal interaction key dispatch is uncertain");
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
async function dispatchTerminalInteractionFreeText(input) {
|
|
3524
|
+
const [textStage, enterStage] = input.plan.stages;
|
|
3525
|
+
if (input.plan.stages.length !== 2 ||
|
|
3526
|
+
textStage?.kind !== "answer_text" ||
|
|
3527
|
+
textStage.single_line !== true ||
|
|
3528
|
+
textStage.max_characters < input.answer.text.length ||
|
|
3529
|
+
enterStage?.kind !== "key" ||
|
|
3530
|
+
enterStage.key !== "C-m") {
|
|
3531
|
+
throw new TerminalInteractionDispatchReservedError("reservation_uncertain", "free-text interaction has no closed adapter action");
|
|
3532
|
+
}
|
|
3533
|
+
try {
|
|
3534
|
+
await input.provider.sendText(input.provider.endpoint(input.terminalControl), input.answer.text);
|
|
3535
|
+
}
|
|
3536
|
+
catch (error) {
|
|
3537
|
+
throw terminalInteractionReservedError("text_uncertain", error, "terminal interaction text dispatch is uncertain");
|
|
3538
|
+
}
|
|
3539
|
+
if (input.agent === "codex") {
|
|
3540
|
+
try {
|
|
3541
|
+
await input.sleep(CODEX_PASTE_ENTER_SETTLE_MS);
|
|
3542
|
+
}
|
|
3543
|
+
catch (error) {
|
|
3544
|
+
throw terminalInteractionReservedError("text_uncertain", error, "terminal interaction paste-settle outcome is uncertain");
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
let enterControl;
|
|
3548
|
+
try {
|
|
3549
|
+
enterControl = await input.reverifyTerminalIdentity(input.terminalControl);
|
|
3550
|
+
if (!sameTerminalControlIdentity(input.terminalControl, enterControl)) {
|
|
3551
|
+
throw new Error("terminal identity changed between interaction text and Enter");
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
catch (error) {
|
|
3555
|
+
throw terminalInteractionReservedError("text_uncertain", error, "terminal identity after interaction text dispatch is uncertain");
|
|
3556
|
+
}
|
|
3557
|
+
await sendTerminalInteractionKeys(input.provider, enterControl, [enterStage.key]);
|
|
3558
|
+
return "submitted_or_advanced";
|
|
3559
|
+
}
|
|
3560
|
+
async function dispatchTerminalInteractionAnswer(input) {
|
|
3561
|
+
if (input.actionPlan.kind === "single_select" &&
|
|
3562
|
+
input.answer.response_kind === "single_select") {
|
|
3563
|
+
const action = terminalInteractionChoiceAction(input.actionPlan, input.answer);
|
|
3564
|
+
await sendTerminalInteractionKeys(input.provider, input.terminalControl, [action.key]);
|
|
3565
|
+
return action.outcome;
|
|
3566
|
+
}
|
|
3567
|
+
if (input.actionPlan.kind === "confirm" &&
|
|
3568
|
+
input.answer.response_kind === "confirm") {
|
|
3569
|
+
const key = terminalInteractionConfirmKey(input.actionPlan, input.answer);
|
|
3570
|
+
await sendTerminalInteractionKeys(input.provider, input.terminalControl, [key]);
|
|
3571
|
+
return input.answer.confirm ? "confirmed" : "cancelled";
|
|
3572
|
+
}
|
|
3573
|
+
if (input.actionPlan.kind === "free_text" &&
|
|
3574
|
+
input.answer.response_kind === "free_text") {
|
|
3575
|
+
return dispatchTerminalInteractionFreeText({
|
|
3576
|
+
agent: input.agent,
|
|
3577
|
+
provider: input.provider,
|
|
3578
|
+
terminalControl: input.terminalControl,
|
|
3579
|
+
plan: input.actionPlan,
|
|
3580
|
+
answer: input.answer,
|
|
3581
|
+
reverifyTerminalIdentity: input.reverifyTerminalIdentity,
|
|
3582
|
+
sleep: input.sleep
|
|
3583
|
+
});
|
|
3584
|
+
}
|
|
3585
|
+
throw new TerminalInteractionDispatchReservedError("reservation_uncertain", "terminal interaction answer does not have a closed adapter action");
|
|
3586
|
+
}
|
|
3009
3587
|
function statusFromInspection(adapter, terminalControl, inspection, options = {}) {
|
|
3010
3588
|
const approval = dispatchableApprovalInspection(adapter, inspection.approval);
|
|
3011
3589
|
const fingerprint = terminalApprovalFingerprint(adapter.agent, terminalControl, {
|
|
3012
3590
|
...inspection,
|
|
3013
3591
|
approval
|
|
3014
3592
|
}, options);
|
|
3593
|
+
const choices = approval.approvable
|
|
3594
|
+
? terminalApprovalChoices(approval).flatMap((choice) => {
|
|
3595
|
+
if (!choice.decision) {
|
|
3596
|
+
return [];
|
|
3597
|
+
}
|
|
3598
|
+
const choiceFingerprint = terminalApprovalFingerprint(adapter.agent, terminalControl, { ...inspection, approval }, { ...options, decision: choice.decision });
|
|
3599
|
+
return choiceFingerprint ? [{
|
|
3600
|
+
decision: choice.decision,
|
|
3601
|
+
label: choice.label,
|
|
3602
|
+
fingerprint: choiceFingerprint
|
|
3603
|
+
}] : [];
|
|
3604
|
+
})
|
|
3605
|
+
: [];
|
|
3606
|
+
const interaction = options.screen === undefined
|
|
3607
|
+
? undefined
|
|
3608
|
+
: terminalInteractionBridgeOffer({
|
|
3609
|
+
agent: adapter.agent,
|
|
3610
|
+
terminalControl,
|
|
3611
|
+
inspection: { ...inspection, approval },
|
|
3612
|
+
screen: options.screen,
|
|
3613
|
+
runtime: options.runtime,
|
|
3614
|
+
now: options.now ?? new Date()
|
|
3615
|
+
});
|
|
3015
3616
|
return {
|
|
3016
3617
|
provider: terminalControl.kind,
|
|
3017
3618
|
target: terminalControl.target,
|
|
@@ -3036,6 +3637,7 @@ function statusFromInspection(adapter, terminalControl, inspection, options = {}
|
|
|
3036
3637
|
request_detail: approval.requestDetail,
|
|
3037
3638
|
reason: approval.approvable ? undefined : approval.reason,
|
|
3038
3639
|
fingerprint,
|
|
3640
|
+
choices: choices.length > 0 ? choices : undefined,
|
|
3039
3641
|
decision_mode: approval.approvable ? approval.action.mode ?? "keys" : undefined,
|
|
3040
3642
|
request_id: approval.approvable ? approval.action.requestId : undefined,
|
|
3041
3643
|
policy_evidence: approval.approvable && approval.policyEvidence
|
|
@@ -3054,7 +3656,11 @@ function statusFromInspection(adapter, terminalControl, inspection, options = {}
|
|
|
3054
3656
|
? undefined
|
|
3055
3657
|
: createHash("sha256").update(options.screen).digest("hex"),
|
|
3056
3658
|
approval: approvalOutput(approval)
|
|
3057
|
-
}
|
|
3659
|
+
},
|
|
3660
|
+
...(interaction === undefined ? {} : {
|
|
3661
|
+
interaction_state: interaction.projection,
|
|
3662
|
+
interaction_prompt_fingerprint: interaction.promptFingerprint
|
|
3663
|
+
})
|
|
3058
3664
|
};
|
|
3059
3665
|
}
|
|
3060
3666
|
function dispatchableApprovalInspection(adapter, approval) {
|
|
@@ -3117,6 +3723,10 @@ function approvalOutput(approval) {
|
|
|
3117
3723
|
cwd: approval.cwd,
|
|
3118
3724
|
toolName: approval.toolName,
|
|
3119
3725
|
requestDetail: approval.requestDetail,
|
|
3726
|
+
choices: terminalApprovalChoices(approval).map((choice) => ({
|
|
3727
|
+
decision: choice.decision,
|
|
3728
|
+
label: choice.label
|
|
3729
|
+
})),
|
|
3120
3730
|
policyEvidence: approval.policyEvidence
|
|
3121
3731
|
? {
|
|
3122
3732
|
source: approval.policyEvidence.source,
|