@scotthuang/agent-knock-knock 0.12.37 → 0.13.0
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 +32 -0
- package/README.md +3 -0
- package/dist/src/agent-session-provider.d.ts +5 -0
- package/dist/src/agent-session-provider.js +10 -1
- package/dist/src/agent-session-provider.js.map +1 -1
- package/dist/src/callback-outbox-service.d.ts +2 -0
- package/dist/src/callback-outbox-service.js +1 -1
- package/dist/src/callback-outbox-service.js.map +1 -1
- package/dist/src/callback-transport.d.ts +7 -0
- package/dist/src/callback-transport.js +1 -1
- package/dist/src/callback-transport.js.map +1 -1
- package/dist/src/cli-core.js +35 -32
- package/dist/src/cli-core.js.map +1 -1
- package/dist/src/codex-store-adapter.d.ts +1 -1
- package/dist/src/codex-store-adapter.js +35 -8
- package/dist/src/codex-store-adapter.js.map +1 -1
- package/dist/src/deferred-foreground-authority-cli-adapter.d.ts +20 -0
- package/dist/src/deferred-foreground-authority-cli-adapter.js +216 -8
- package/dist/src/deferred-foreground-authority-cli-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/managed-turn-recovery-service.d.ts +8 -1
- package/dist/src/managed-turn-recovery-service.js +55 -13
- package/dist/src/managed-turn-recovery-service.js.map +1 -1
- package/dist/src/mutation-transaction.d.ts +1 -0
- package/dist/src/mutation-transaction.js +19 -15
- package/dist/src/mutation-transaction.js.map +1 -1
- package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +39 -3
- package/dist/src/native-thread-lifecycle-cli-adapter.js +307 -7
- package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-callback-adapter.js +17 -7
- package/dist/src/openclaw-plugin-callback-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-command-adapter.js +297 -83
- package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-schemas.d.ts +57 -0
- package/dist/src/openclaw-plugin-schemas.js +59 -1
- package/dist/src/openclaw-plugin-schemas.js.map +1 -1
- package/dist/src/openclaw-private-authority-offers.d.ts +17 -0
- package/dist/src/openclaw-private-authority-offers.js +75 -3
- package/dist/src/openclaw-private-authority-offers.js.map +1 -1
- package/dist/src/store.d.ts +1 -1
- package/dist/src/store.js +6 -4
- package/dist/src/store.js.map +1 -1
- package/dist/src/terminal-acceptance-application-service.js +2 -2
- package/dist/src/terminal-acceptance-application-service.js.map +1 -1
- package/dist/src/terminal-acceptance-cli-adapter.d.ts +11 -1
- package/dist/src/terminal-acceptance-cli-adapter.js +290 -16
- package/dist/src/terminal-acceptance-cli-adapter.js.map +1 -1
- package/dist/src/terminal-action-projection.d.ts +1 -1
- package/dist/src/terminal-action-projection.js +6 -0
- package/dist/src/terminal-action-projection.js.map +1 -1
- package/dist/src/terminal-agent-adapter.d.ts +9 -0
- package/dist/src/terminal-agent-adapter.js.map +1 -1
- package/dist/src/terminal-agent-bridge.d.ts +52 -8
- package/dist/src/terminal-agent-bridge.js +111 -94
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/dist/src/terminal-authority-policy.js +3 -0
- package/dist/src/terminal-authority-policy.js.map +1 -1
- package/dist/src/terminal-binding-authority.d.ts +2 -0
- package/dist/src/terminal-binding-authority.js +8 -0
- package/dist/src/terminal-binding-authority.js.map +1 -1
- package/dist/src/terminal-command-cli-adapter.d.ts +17 -1
- package/dist/src/terminal-command-cli-adapter.js +784 -286
- package/dist/src/terminal-command-cli-adapter.js.map +1 -1
- package/dist/src/terminal-delegate-cli-adapter.js +21 -7
- package/dist/src/terminal-delegate-cli-adapter.js.map +1 -1
- package/dist/src/terminal-dispatch-application.d.ts +3 -1
- package/dist/src/terminal-dispatch-application.js +13 -5
- package/dist/src/terminal-dispatch-application.js.map +1 -1
- package/dist/src/terminal-dispatch-composition.d.ts +43 -0
- package/dist/src/terminal-dispatch-execution.d.ts +2 -2
- package/dist/src/terminal-dispatch-execution.js +53 -3
- package/dist/src/terminal-dispatch-execution.js.map +1 -1
- package/dist/src/terminal-dispatch-policy.d.ts +20 -0
- package/dist/src/terminal-dispatch-policy.js +33 -1
- package/dist/src/terminal-dispatch-policy.js.map +1 -1
- package/dist/src/terminal-dispatch-presenter.d.ts +45 -0
- package/dist/src/terminal-dispatch-presenter.js +220 -18
- package/dist/src/terminal-dispatch-presenter.js.map +1 -1
- package/dist/src/terminal-dispatch-receipt.d.ts +5 -1
- package/dist/src/terminal-dispatch-receipt.js +26 -3
- package/dist/src/terminal-dispatch-receipt.js.map +1 -1
- package/dist/src/terminal-identity-authority-cli-adapter.js +103 -4
- package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -1
- package/dist/src/terminal-interaction-authority.d.ts +48 -1
- package/dist/src/terminal-interaction-authority.js +30 -0
- package/dist/src/terminal-interaction-authority.js.map +1 -1
- package/dist/src/terminal-interaction-cli-adapter.js +9 -0
- package/dist/src/terminal-interaction-cli-adapter.js.map +1 -1
- package/dist/src/terminal-interaction-core.d.ts +104 -0
- package/dist/src/terminal-interaction-core.js +407 -0
- package/dist/src/terminal-interaction-core.js.map +1 -0
- package/dist/src/terminal-interaction-protocol.d.ts +89 -0
- package/dist/src/terminal-interaction-protocol.js +199 -0
- package/dist/src/terminal-interaction-protocol.js.map +1 -1
- package/dist/src/terminal-list-cli-adapter.d.ts +5 -0
- package/dist/src/terminal-list-cli-adapter.js +190 -40
- package/dist/src/terminal-list-cli-adapter.js.map +1 -1
- package/dist/src/terminal-list-renderer.js +60 -7
- package/dist/src/terminal-list-renderer.js.map +1 -1
- package/dist/src/terminal-maintenance-cli-adapter.js +14 -3
- package/dist/src/terminal-maintenance-cli-adapter.js.map +1 -1
- package/dist/src/terminal-monitor-application-service.d.ts +1 -0
- package/dist/src/terminal-monitor-application-service.js +72 -10
- package/dist/src/terminal-monitor-application-service.js.map +1 -1
- package/dist/src/terminal-monitor-cli-adapter.d.ts +3 -0
- package/dist/src/terminal-monitor-cli-adapter.js +24 -8
- package/dist/src/terminal-monitor-cli-adapter.js.map +1 -1
- package/dist/src/terminal-monitor-state-cli-adapter.js +15 -2
- package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -1
- package/dist/src/terminal-mutation-cli-runtime.d.ts +11 -1
- package/dist/src/terminal-mutation-cli-runtime.js +9 -4
- package/dist/src/terminal-mutation-cli-runtime.js.map +1 -1
- package/dist/src/terminal-status-cli-adapter.d.ts +1 -7
- package/dist/src/terminal-status-cli-adapter.js +29 -3
- package/dist/src/terminal-status-cli-adapter.js.map +1 -1
- package/dist/src/terminal-status-facts.d.ts +17 -1
- package/dist/src/terminal-status-facts.js +59 -0
- package/dist/src/terminal-status-facts.js.map +1 -1
- package/dist/src/terminal-submission-acceptance.d.ts +37 -2
- package/dist/src/terminal-submission-acceptance.js +633 -30
- package/dist/src/terminal-submission-acceptance.js.map +1 -1
- package/dist/src/terminal-submission-facts.d.ts +5 -0
- package/dist/src/terminal-submission-facts.js.map +1 -1
- package/dist/src/terminal-watch-callback-cli-adapter.d.ts +2 -1
- package/dist/src/terminal-watch-callback-cli-adapter.js +2 -1
- package/dist/src/terminal-watch-callback-cli-adapter.js.map +1 -1
- package/dist/src/terminal-watch-cli-adapter.d.ts +6 -2
- package/dist/src/terminal-watch-cli-adapter.js +1051 -15
- package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
- package/dist/src/terminal-watch-service.d.ts +15 -1
- package/dist/src/terminal-watch-service.js +169 -13
- package/dist/src/terminal-watch-service.js.map +1 -1
- package/dist/src/terminal-watch-store.d.ts +72 -3
- package/dist/src/terminal-watch-store.js +407 -27
- package/dist/src/terminal-watch-store.js.map +1 -1
- package/docs/host-bridge-profiles.md +2 -2
- package/docs/quickstart-herdr.md +5 -3
- package/docs/quickstart-tmux.md +7 -5
- package/openclaw.plugin.json +8 -0
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +15 -11
|
@@ -5,11 +5,17 @@ import { createTerminalWatchOpenClawCallbackRoute, parseCallbackRoute } from "./
|
|
|
5
5
|
import { captureClaudeTranscriptAnchor, captureClaudeHumanStartedActiveTaskAnchor, observeClaudeUserExplicitFallbackTranscript, observeClaudeHumanStartedActiveTask } from "./claude-local-transcript-provider.js";
|
|
6
6
|
import { claudeRuntimeCompatibilityWarning } from "./claude-lifecycle-compatibility.js";
|
|
7
7
|
import { codexRuntimeCompatibilityProfile } from "./codex-lifecycle-compatibility.js";
|
|
8
|
-
import {
|
|
8
|
+
import { captureTerminalInteractionRuntimeOffer, TerminalInteractionDispatchReservedError, TerminalInteractionInputNotStartedError } from "./terminal-agent-bridge.js";
|
|
9
|
+
import { executorForConversation, turnIdForConversation } from "./protocol.js";
|
|
10
|
+
import { rolloutFileIdentityMatches } from "./terminal-binding-authority.js";
|
|
11
|
+
import { captureCodexCandidateSetRolloutAcceptanceAnchor, captureCodexRolloutAcceptanceAnchor, detectCodexBoundQuestionnaireAttribution, detectCodexBoundRolloutCompletion, detectCodexCandidateSetRolloutAcceptance, detectCodexRolloutAcceptance, captureCodexHumanStartedActiveTaskAnchor, observeCodexHumanStartedActiveTask } from "./terminal-submission-acceptance.js";
|
|
12
|
+
import { createTerminalInteractionAggregate, hashTerminalInteractionResponse, reduceTerminalInteractionAggregate } from "./terminal-interaction-core.js";
|
|
13
|
+
import { selectTerminalInteractionResponder } from "./terminal-interaction-authority.js";
|
|
14
|
+
import { validateAnyTerminalInteractionProjection, validateTerminalInteractionSubjectResponse } from "./terminal-interaction-protocol.js";
|
|
9
15
|
import { sameTerminalControlEvidenceIncarnation, terminalControlEvidence } from "./terminal-control-ref.js";
|
|
10
16
|
import { createTerminalWatchCallbackCliAdapter, resolveTerminalWatchOpenClawCallback, resolveTerminalWatchOpenClawCallbackContext } from "./terminal-watch-callback-cli-adapter.js";
|
|
11
17
|
import { createTerminalWatchService, terminalWatchObservationFence } from "./terminal-watch-service.js";
|
|
12
|
-
import { createClaudeUserExplicitFallbackWatchAnchor, createCodexUserExplicitFallbackWatchAnchor, createTerminalActivityWatchAnchor, createTerminalWatchStore, isTerminalActivityWatch, isUserExplicitFallbackWatch, terminalUserExplicitFallbackWatchId } from "./terminal-watch-store.js";
|
|
18
|
+
import { assertTerminalWatchManualInteractionSummary, createClaudeUserExplicitFallbackWatchAnchor, createCodexUserExplicitFallbackWatchAnchor, createTerminalActivityWatchAnchor, createTerminalWatchStore, isTerminalActivityWatch, isUserExplicitFallbackWatch, terminalWatchNotificationOnlyRoute, terminalWatchRevision, terminalUserExplicitFallbackWatchId } from "./terminal-watch-store.js";
|
|
13
19
|
import { isRecord, nonBlankString as stringValue } from "./value-guards.js";
|
|
14
20
|
const DEFAULT_TERMINAL_WATCH_HARD_TIMEOUT_MINUTES = 720;
|
|
15
21
|
export function createTerminalWatchCliAdapter(dependencies) {
|
|
@@ -27,9 +33,11 @@ export function createTerminalWatchCliAdapter(dependencies) {
|
|
|
27
33
|
observe: (watch) => observeTerminalWatch(watch, options, dependencies),
|
|
28
34
|
resolveCallback: explicitRoute
|
|
29
35
|
? (watch) => ({
|
|
30
|
-
route:
|
|
31
|
-
?
|
|
32
|
-
:
|
|
36
|
+
route: watch.interaction_policy === "notify_only"
|
|
37
|
+
? terminalWatchNotificationOnlyRoute(explicitRoute)
|
|
38
|
+
: options.callbackRouteControllerScope === "route_bound_v1"
|
|
39
|
+
? routeBoundWatchCallbackRoute(explicitRoute, watch)
|
|
40
|
+
: explicitRoute
|
|
33
41
|
})
|
|
34
42
|
: resolveTerminalWatchOpenClawCallback,
|
|
35
43
|
resolveCallbackContext: explicitRoute
|
|
@@ -49,6 +57,18 @@ export function createTerminalWatchCliAdapter(dependencies) {
|
|
|
49
57
|
error_code: "terminal_watch_callback_agent_invalid"
|
|
50
58
|
};
|
|
51
59
|
}
|
|
60
|
+
const manualInteraction = metadata.manual_interaction;
|
|
61
|
+
if (manualInteraction !== undefined) {
|
|
62
|
+
try {
|
|
63
|
+
assertTerminalWatchManualInteractionSummary(manualInteraction);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return {
|
|
67
|
+
disposition: "permanent_failure",
|
|
68
|
+
error_code: "terminal_watch_callback_interaction_invalid"
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
52
72
|
callback.deliver({
|
|
53
73
|
watchId: input.envelope.source.kind === "terminal_watch"
|
|
54
74
|
? input.envelope.source.watch_id
|
|
@@ -72,6 +92,7 @@ export function createTerminalWatchCliAdapter(dependencies) {
|
|
|
72
92
|
detail: typeof metadata.reason_code === "string"
|
|
73
93
|
? metadata.reason_code
|
|
74
94
|
: undefined,
|
|
95
|
+
manualInteraction,
|
|
75
96
|
completionText: typeof metadata.completion_text === "string"
|
|
76
97
|
? metadata.completion_text
|
|
77
98
|
: undefined
|
|
@@ -343,10 +364,8 @@ export function createTerminalWatchCliAdapter(dependencies) {
|
|
|
343
364
|
const watch = service.cancel(requiredWatchId(options.watch));
|
|
344
365
|
dependencies.printJson({ watch: publicTerminalWatch(watch) });
|
|
345
366
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
dependencies.printJson({ watch: publicTerminalWatch(watch) });
|
|
349
|
-
}
|
|
367
|
+
const runWatchStatus = createTerminalWatchStatusRunner(dependencies, serviceFor);
|
|
368
|
+
const runRespondInteraction = createTerminalWatchInteractionResponder(dependencies, serviceFor);
|
|
350
369
|
async function runReconcileWatches(options) {
|
|
351
370
|
dependencies.printJson(await serviceFor(options).reconcileAll());
|
|
352
371
|
}
|
|
@@ -376,11 +395,254 @@ export function createTerminalWatchCliAdapter(dependencies) {
|
|
|
376
395
|
runWatch,
|
|
377
396
|
runUnwatch,
|
|
378
397
|
runWatchStatus,
|
|
398
|
+
runRespondInteraction,
|
|
379
399
|
runReconcileWatches,
|
|
380
400
|
listPublicWatches,
|
|
381
401
|
scanPublicWatchesForExactObservation
|
|
382
402
|
});
|
|
383
403
|
}
|
|
404
|
+
function createTerminalWatchStatusRunner(dependencies, serviceFor) {
|
|
405
|
+
return async (options) => {
|
|
406
|
+
const service = serviceFor(options);
|
|
407
|
+
const watchId = requiredWatchId(options.watch);
|
|
408
|
+
const interactionAccess = watchStatusInteractionAccess(service.get(watchId), options);
|
|
409
|
+
const watch = await service.reconcile(watchId);
|
|
410
|
+
dependencies.printJson({
|
|
411
|
+
watch: publicTerminalWatch(watch, [], interactionAccess)
|
|
412
|
+
});
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
function createTerminalWatchInteractionResponder(dependencies, serviceFor) {
|
|
416
|
+
return async function runRespondInteraction(options) {
|
|
417
|
+
const watchId = requiredWatchId(options.watch);
|
|
418
|
+
const interactionId = requiredString(options.interaction, "--interaction is required");
|
|
419
|
+
const expectedFingerprint = requiredSha256(options.expectedInteractionFingerprint, "interaction prompt fingerprint");
|
|
420
|
+
const expectedExpiresAt = requiredTimestamp(options.expectedInteractionExpiresAt, "--expected-interaction-expires-at");
|
|
421
|
+
const responseValue = parseWatchInteractionResponse(options.responseJson);
|
|
422
|
+
const storeDir = dependencies.storeDirFromOptions(options);
|
|
423
|
+
const repository = createTerminalWatchStore(storeDir, {
|
|
424
|
+
acquire: dependencies.acquireFileLock
|
|
425
|
+
});
|
|
426
|
+
const service = serviceFor(options);
|
|
427
|
+
const displayed = await service.reconcile(watchId);
|
|
428
|
+
assertWatchControllerSession(displayed, options.openclawSession);
|
|
429
|
+
const displayedInteraction = requiredExecutableWatchInteraction(displayed, interactionId, expectedFingerprint);
|
|
430
|
+
const response = validateTerminalInteractionSubjectResponse(responseValue, displayedInteraction.projection, { now: dependencies.now(), allowExpiredForLiveRecapture: true });
|
|
431
|
+
if (response.subject.kind !== "terminal_watch" ||
|
|
432
|
+
response.subject.watch_id !== watchId) {
|
|
433
|
+
throw new Error("--response-json subject watch_id does not match --watch");
|
|
434
|
+
}
|
|
435
|
+
const exact = await exactTerminalForWatch(displayed.terminal.terminal_id, options, dependencies);
|
|
436
|
+
if (!terminalMatchesWatch(exact.rawTerminal, displayed)) {
|
|
437
|
+
throw new Error("terminal Watch identity changed before interaction response; refresh status");
|
|
438
|
+
}
|
|
439
|
+
const terminalControl = terminalControlForWatch(exact.rawTerminal);
|
|
440
|
+
const releaseTerminal = dependencies.acquireTerminalLock(storeDir, terminalControl);
|
|
441
|
+
try {
|
|
442
|
+
const withWriterLease = dependencies.withStoreWriterLeaseAsync ??
|
|
443
|
+
(async (_storeDir, operation) => operation());
|
|
444
|
+
await withWriterLease(storeDir, async () => {
|
|
445
|
+
let current = repository.load(watchId);
|
|
446
|
+
assertWatchControllerSession(current, options.openclawSession);
|
|
447
|
+
const executableInteraction = requiredExecutableWatchInteraction(current, interactionId, expectedFingerprint);
|
|
448
|
+
const decision = terminalWatchResponseDecision(current, exact.rawTerminal, executableInteraction.projection.surface_id, executableInteraction.projection.prompt_fingerprint, options, dependencies);
|
|
449
|
+
if (!decision.executable || decision.suppress) {
|
|
450
|
+
throw new Error("a higher-priority terminal interaction responder owns this live questionnaire; refresh status");
|
|
451
|
+
}
|
|
452
|
+
const version = terminalWatchCapturedAgentVersion(current) ??
|
|
453
|
+
requiredString(exact.rawTerminal.agent_version, "running coding-agent version");
|
|
454
|
+
const runtime = terminalInteractionRuntimeForWatch({
|
|
455
|
+
watch: current,
|
|
456
|
+
rawTerminal: exact.rawTerminal,
|
|
457
|
+
checkpoint: current.observation_checkpoint,
|
|
458
|
+
version,
|
|
459
|
+
responseAuthority: "executable"
|
|
460
|
+
});
|
|
461
|
+
const bridge = dependencies.createBridge?.(options);
|
|
462
|
+
if (!bridge) {
|
|
463
|
+
throw new Error("terminal interaction bridge is unavailable");
|
|
464
|
+
}
|
|
465
|
+
const attemptId = dependencies.randomUUID();
|
|
466
|
+
const responseHash = hashTerminalInteractionResponse(response);
|
|
467
|
+
let reserved = false;
|
|
468
|
+
let execution;
|
|
469
|
+
try {
|
|
470
|
+
execution = await bridge.respondInteraction(current.agent, terminalControl, response, {
|
|
471
|
+
agentVersion: version,
|
|
472
|
+
expectedFingerprint,
|
|
473
|
+
expectedExpiresAt,
|
|
474
|
+
scrollbackLines: Number(options.scrollbackLines ?? 120),
|
|
475
|
+
runtime,
|
|
476
|
+
authorize: ({ projection, fingerprint }) => {
|
|
477
|
+
const latest = repository.load(watchId);
|
|
478
|
+
const interaction = requiredExecutableWatchInteraction(latest, interactionId, expectedFingerprint);
|
|
479
|
+
const latestDecision = terminalWatchResponseDecision(latest, exact.rawTerminal, interaction.projection.surface_id, interaction.projection.prompt_fingerprint, options, dependencies);
|
|
480
|
+
return projection.interaction_id === interactionId &&
|
|
481
|
+
fingerprint === expectedFingerprint &&
|
|
482
|
+
interaction.projection.subject.kind === "terminal_watch" &&
|
|
483
|
+
projection.version === 2 &&
|
|
484
|
+
projection.subject.kind === "terminal_watch" &&
|
|
485
|
+
projection.subject.watch_id === watchId &&
|
|
486
|
+
latestDecision.executable && !latestDecision.suppress
|
|
487
|
+
? { approved: true }
|
|
488
|
+
: {
|
|
489
|
+
approved: false,
|
|
490
|
+
reason: "terminal Watch interaction authority changed before response"
|
|
491
|
+
};
|
|
492
|
+
},
|
|
493
|
+
beforeDispatch: ({ projection, fingerprint }) => {
|
|
494
|
+
if (reserved) {
|
|
495
|
+
throw new Error("terminal Watch interaction response was already reserved");
|
|
496
|
+
}
|
|
497
|
+
current = repository.withWriterLease((scope) => scope.withWatchLock(watchId, () => {
|
|
498
|
+
const latest = scope.load(watchId);
|
|
499
|
+
const interaction = requiredExecutableWatchInteraction(latest, interactionId, expectedFingerprint);
|
|
500
|
+
if (projection.interaction_id !== interactionId ||
|
|
501
|
+
fingerprint !== expectedFingerprint ||
|
|
502
|
+
interaction.aggregate.state !== "pending") {
|
|
503
|
+
throw new Error("terminal Watch interaction authority changed before dispatch");
|
|
504
|
+
}
|
|
505
|
+
const dispatchDecision = terminalWatchResponseDecision(latest, exact.rawTerminal, interaction.projection.surface_id, interaction.projection.prompt_fingerprint, options, dependencies);
|
|
506
|
+
if (!dispatchDecision.executable ||
|
|
507
|
+
dispatchDecision.suppress) {
|
|
508
|
+
throw new TerminalInteractionInputNotStartedError("terminal Watch interaction ownership changed before dispatch");
|
|
509
|
+
}
|
|
510
|
+
const reservedAt = dependencies.now().toISOString();
|
|
511
|
+
const aggregate = reduceTerminalInteractionAggregate(interaction.aggregate, {
|
|
512
|
+
type: "reserve",
|
|
513
|
+
attempt_id: attemptId,
|
|
514
|
+
response_hash: responseHash,
|
|
515
|
+
at: reservedAt
|
|
516
|
+
});
|
|
517
|
+
const saved = scope.save({
|
|
518
|
+
...latest,
|
|
519
|
+
current_interaction: {
|
|
520
|
+
projection: interaction.projection,
|
|
521
|
+
aggregate
|
|
522
|
+
},
|
|
523
|
+
updated_at: reservedAt
|
|
524
|
+
}, { expectedRevision: terminalWatchRevision(latest) });
|
|
525
|
+
reserved = true;
|
|
526
|
+
return saved;
|
|
527
|
+
}));
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
if (reserved) {
|
|
533
|
+
const state = error instanceof TerminalInteractionInputNotStartedError
|
|
534
|
+
? "release"
|
|
535
|
+
: "response_uncertain";
|
|
536
|
+
current = repository.withWriterLease((scope) => scope.withWatchLock(watchId, () => {
|
|
537
|
+
const latest = scope.load(watchId);
|
|
538
|
+
const interaction = latest.current_interaction;
|
|
539
|
+
if (!interaction ||
|
|
540
|
+
interaction.projection.interaction_id !== interactionId ||
|
|
541
|
+
interaction.aggregate.state !== "reserved" ||
|
|
542
|
+
interaction.aggregate.reservation?.attempt_id !== attemptId) {
|
|
543
|
+
throw new Error("terminal Watch interaction reservation changed while settling failure");
|
|
544
|
+
}
|
|
545
|
+
const at = dependencies.now().toISOString();
|
|
546
|
+
const aggregate = reduceTerminalInteractionAggregate(interaction.aggregate, state === "release"
|
|
547
|
+
? { type: "release" }
|
|
548
|
+
: {
|
|
549
|
+
type: "response_uncertain",
|
|
550
|
+
at,
|
|
551
|
+
reason_code: "terminal_dispatch_uncertain"
|
|
552
|
+
});
|
|
553
|
+
const projection = state === "release"
|
|
554
|
+
? interaction.projection
|
|
555
|
+
: {
|
|
556
|
+
...interaction.projection,
|
|
557
|
+
state: "response_uncertain",
|
|
558
|
+
capabilities: {
|
|
559
|
+
...interaction.projection.capabilities,
|
|
560
|
+
respond: false
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
return scope.save({
|
|
564
|
+
...latest,
|
|
565
|
+
current_interaction: { projection, aggregate },
|
|
566
|
+
updated_at: at
|
|
567
|
+
}, { expectedRevision: terminalWatchRevision(latest) });
|
|
568
|
+
}));
|
|
569
|
+
}
|
|
570
|
+
throw error;
|
|
571
|
+
}
|
|
572
|
+
if (!execution.responded) {
|
|
573
|
+
dependencies.printJson({
|
|
574
|
+
watch: publicTerminalWatch(current, [], true),
|
|
575
|
+
interaction_id: interactionId,
|
|
576
|
+
responded: false,
|
|
577
|
+
blocked: execution.blocked,
|
|
578
|
+
reason: execution.reason
|
|
579
|
+
});
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
if (!reserved) {
|
|
583
|
+
throw new TerminalInteractionDispatchReservedError("reservation_uncertain", "terminal Watch response was dispatched without a durable reservation");
|
|
584
|
+
}
|
|
585
|
+
current = repository.withWriterLease((scope) => scope.withWatchLock(watchId, () => {
|
|
586
|
+
const latest = scope.load(watchId);
|
|
587
|
+
const interaction = latest.current_interaction;
|
|
588
|
+
if (!interaction ||
|
|
589
|
+
interaction.projection.interaction_id !== interactionId ||
|
|
590
|
+
interaction.aggregate.state !== "reserved" ||
|
|
591
|
+
interaction.aggregate.reservation?.attempt_id !== attemptId) {
|
|
592
|
+
throw new Error("terminal Watch interaction reservation changed after dispatch");
|
|
593
|
+
}
|
|
594
|
+
const answeredAt = dependencies.now().toISOString();
|
|
595
|
+
const aggregate = reduceTerminalInteractionAggregate(interaction.aggregate, {
|
|
596
|
+
type: "consume",
|
|
597
|
+
at: answeredAt,
|
|
598
|
+
reason_code: "terminal_response_dispatched"
|
|
599
|
+
});
|
|
600
|
+
return scope.save({
|
|
601
|
+
...latest,
|
|
602
|
+
current_interaction: {
|
|
603
|
+
projection: interaction.projection,
|
|
604
|
+
aggregate
|
|
605
|
+
},
|
|
606
|
+
updated_at: answeredAt,
|
|
607
|
+
last_activity_at: answeredAt,
|
|
608
|
+
notification_outbox: latest.notification_outbox.map((notification) => notification.kind === "interaction_required" &&
|
|
609
|
+
notification.evidence_fingerprint ===
|
|
610
|
+
sha256({
|
|
611
|
+
schema: "agent-knock-knock/terminal-watch-interaction-event",
|
|
612
|
+
version: 1,
|
|
613
|
+
watch_id: watchId,
|
|
614
|
+
interaction_id: interactionId,
|
|
615
|
+
surface_id: interaction.projection.surface_id
|
|
616
|
+
}) &&
|
|
617
|
+
(notification.status === "pending" ||
|
|
618
|
+
notification.status === "failed")
|
|
619
|
+
? {
|
|
620
|
+
...notification,
|
|
621
|
+
status: "superseded",
|
|
622
|
+
superseded_at: answeredAt,
|
|
623
|
+
next_attempt_at: undefined,
|
|
624
|
+
failed_at: undefined,
|
|
625
|
+
last_error_code: undefined
|
|
626
|
+
}
|
|
627
|
+
: notification)
|
|
628
|
+
}, { expectedRevision: terminalWatchRevision(latest) });
|
|
629
|
+
}));
|
|
630
|
+
dependencies.printJson({
|
|
631
|
+
watch: publicTerminalWatch(current, [], true),
|
|
632
|
+
interaction_id: interactionId,
|
|
633
|
+
responded: true,
|
|
634
|
+
blocked: false,
|
|
635
|
+
question_id: execution.questionId,
|
|
636
|
+
response_kind: execution.responseKind,
|
|
637
|
+
outcome: execution.outcome
|
|
638
|
+
});
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
finally {
|
|
642
|
+
releaseTerminal();
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
}
|
|
384
646
|
function watchAnchorVersionWarnings(anchor, terminal) {
|
|
385
647
|
const runningVersion = stringValue(terminal.agent_version);
|
|
386
648
|
if (!runningVersion) {
|
|
@@ -413,7 +675,7 @@ function watchAnchorVersionWarnings(anchor, terminal) {
|
|
|
413
675
|
*/
|
|
414
676
|
function routeBoundWatchCallbackRoute(template, watch) {
|
|
415
677
|
return Object.freeze({
|
|
416
|
-
...template,
|
|
678
|
+
...parseCallbackRoute(template),
|
|
417
679
|
controller_session_id: watch.openclaw_session
|
|
418
680
|
});
|
|
419
681
|
}
|
|
@@ -438,7 +700,8 @@ function callbackRouteForUserExplicitFallback(options) {
|
|
|
438
700
|
return undefined;
|
|
439
701
|
return createTerminalWatchOpenClawCallbackRoute({
|
|
440
702
|
controllerSessionId,
|
|
441
|
-
openclawBin: options.openclawBin
|
|
703
|
+
openclawBin: options.openclawBin,
|
|
704
|
+
respond: true
|
|
442
705
|
});
|
|
443
706
|
}
|
|
444
707
|
function assertSameUserExplicitFallbackTerminal(expected, observed) {
|
|
@@ -618,9 +881,12 @@ async function observeTerminalWatch(watch, options, dependencies) {
|
|
|
618
881
|
return observeTerminalActivityWatch({
|
|
619
882
|
watch,
|
|
620
883
|
exactTerminal,
|
|
884
|
+
rawTerminal,
|
|
621
885
|
projectedTerminal,
|
|
622
886
|
terminalIdentityMatch: activityTerminalIdentity ?? "mismatch",
|
|
623
|
-
observedAt
|
|
887
|
+
observedAt,
|
|
888
|
+
options,
|
|
889
|
+
dependencies
|
|
624
890
|
});
|
|
625
891
|
}
|
|
626
892
|
const observation = watch.anchor.schema ===
|
|
@@ -713,6 +979,18 @@ async function observeTerminalWatch(watch, options, dependencies) {
|
|
|
713
979
|
reason_code: "terminal_waiting_for_approval"
|
|
714
980
|
};
|
|
715
981
|
}
|
|
982
|
+
const interaction = terminalWatchQuestionnaireObservation({
|
|
983
|
+
watch,
|
|
984
|
+
rawTerminal,
|
|
985
|
+
projectedTerminal,
|
|
986
|
+
terminalMatches: true,
|
|
987
|
+
observedAt,
|
|
988
|
+
observationCheckpoint,
|
|
989
|
+
options,
|
|
990
|
+
dependencies
|
|
991
|
+
});
|
|
992
|
+
if (interaction)
|
|
993
|
+
return interaction;
|
|
716
994
|
return {
|
|
717
995
|
...fence,
|
|
718
996
|
kind: "pending",
|
|
@@ -722,7 +1000,7 @@ async function observeTerminalWatch(watch, options, dependencies) {
|
|
|
722
1000
|
};
|
|
723
1001
|
}
|
|
724
1002
|
function observeTerminalActivityWatch(input) {
|
|
725
|
-
const { watch, exactTerminal, projectedTerminal, terminalIdentityMatch, observedAt } = input;
|
|
1003
|
+
const { watch, exactTerminal, projectedTerminal, rawTerminal, terminalIdentityMatch, observedAt } = input;
|
|
726
1004
|
const fence = terminalWatchObservationFence(watch);
|
|
727
1005
|
if (exactTerminal.state === "unavailable") {
|
|
728
1006
|
return {
|
|
@@ -754,6 +1032,20 @@ function observeTerminalActivityWatch(input) {
|
|
|
754
1032
|
reason_code: "terminal_activity_unavailable"
|
|
755
1033
|
};
|
|
756
1034
|
}
|
|
1035
|
+
if (rawTerminal) {
|
|
1036
|
+
const interaction = terminalWatchQuestionnaireObservation({
|
|
1037
|
+
watch,
|
|
1038
|
+
rawTerminal,
|
|
1039
|
+
projectedTerminal,
|
|
1040
|
+
terminalMatches: true,
|
|
1041
|
+
observedAt,
|
|
1042
|
+
observationCheckpoint: terminalActivityObservationCheckpoint(watch),
|
|
1043
|
+
options: input.options,
|
|
1044
|
+
dependencies: input.dependencies
|
|
1045
|
+
});
|
|
1046
|
+
if (interaction)
|
|
1047
|
+
return interaction;
|
|
1048
|
+
}
|
|
757
1049
|
const state = terminalActivityState(projectedTerminal);
|
|
758
1050
|
const current = terminalActivityObservationCheckpoint(watch);
|
|
759
1051
|
const active = state === "working" || state === "awaiting_approval";
|
|
@@ -815,7 +1107,7 @@ function observeTerminalActivityWatch(input) {
|
|
|
815
1107
|
};
|
|
816
1108
|
}
|
|
817
1109
|
async function observeUserExplicitFallbackTerminalWatch(input) {
|
|
818
|
-
const { watch, exactTerminal, rawTerminal, terminalMatches, observedAt, options, dependencies } = input;
|
|
1110
|
+
const { watch, exactTerminal, rawTerminal, projectedTerminal, terminalMatches, observedAt, options, dependencies } = input;
|
|
819
1111
|
const fence = terminalWatchObservationFence(watch);
|
|
820
1112
|
if (watch.anchor.schema ===
|
|
821
1113
|
"agent-knock-knock/codex-user-explicit-fallback-watch-anchor") {
|
|
@@ -943,6 +1235,19 @@ async function observeUserExplicitFallbackTerminalWatch(input) {
|
|
|
943
1235
|
}
|
|
944
1236
|
return invalidatedObservation(watch, observedAt, `native_completion_${completion.diagnostics.code}`);
|
|
945
1237
|
}
|
|
1238
|
+
const interaction = fallbackQuestionnaireObservation({
|
|
1239
|
+
watch,
|
|
1240
|
+
exactTerminal,
|
|
1241
|
+
rawTerminal,
|
|
1242
|
+
projectedTerminal,
|
|
1243
|
+
terminalMatches,
|
|
1244
|
+
observedAt,
|
|
1245
|
+
observationCheckpoint: codexFallbackAcceptedCheckpoint(watch, acceptance, currentIdentity),
|
|
1246
|
+
options,
|
|
1247
|
+
dependencies
|
|
1248
|
+
});
|
|
1249
|
+
if (interaction)
|
|
1250
|
+
return interaction;
|
|
946
1251
|
return fallbackPendingOrTerminalObservation({
|
|
947
1252
|
watch,
|
|
948
1253
|
exactTerminal,
|
|
@@ -996,6 +1301,19 @@ async function observeUserExplicitFallbackTerminalWatch(input) {
|
|
|
996
1301
|
if (observation.status === "completed") {
|
|
997
1302
|
return fallbackCompletionObservation(watch, observedAt, observation.completion, observation.observedEndOffsetBytes, acceptedCheckpoint);
|
|
998
1303
|
}
|
|
1304
|
+
const interaction = fallbackQuestionnaireObservation({
|
|
1305
|
+
watch,
|
|
1306
|
+
exactTerminal,
|
|
1307
|
+
rawTerminal,
|
|
1308
|
+
projectedTerminal,
|
|
1309
|
+
terminalMatches,
|
|
1310
|
+
observedAt,
|
|
1311
|
+
observationCheckpoint: acceptedCheckpoint,
|
|
1312
|
+
options,
|
|
1313
|
+
dependencies
|
|
1314
|
+
});
|
|
1315
|
+
if (interaction)
|
|
1316
|
+
return interaction;
|
|
999
1317
|
return fallbackPendingOrTerminalObservation({
|
|
1000
1318
|
watch,
|
|
1001
1319
|
exactTerminal,
|
|
@@ -1006,6 +1324,635 @@ async function observeUserExplicitFallbackTerminalWatch(input) {
|
|
|
1006
1324
|
observationCheckpoint: acceptedCheckpoint
|
|
1007
1325
|
});
|
|
1008
1326
|
}
|
|
1327
|
+
function fallbackQuestionnaireObservation(input) {
|
|
1328
|
+
if (input.exactTerminal.state !== "available" ||
|
|
1329
|
+
!input.rawTerminal ||
|
|
1330
|
+
!input.projectedTerminal ||
|
|
1331
|
+
!input.terminalMatches) {
|
|
1332
|
+
return undefined;
|
|
1333
|
+
}
|
|
1334
|
+
return terminalWatchQuestionnaireObservation({
|
|
1335
|
+
...input,
|
|
1336
|
+
rawTerminal: input.rawTerminal,
|
|
1337
|
+
projectedTerminal: input.projectedTerminal
|
|
1338
|
+
}, true);
|
|
1339
|
+
}
|
|
1340
|
+
function terminalWatchQuestionnaireObservation(input, requireFallbackAttribution = false) {
|
|
1341
|
+
if (!input.rawTerminal || !input.terminalMatches)
|
|
1342
|
+
return undefined;
|
|
1343
|
+
const rawTerminal = input.rawTerminal;
|
|
1344
|
+
const screen = terminalWatchScreenExcerpt(rawTerminal, input.projectedTerminal);
|
|
1345
|
+
const version = terminalWatchCapturedAgentVersion(input.watch) ??
|
|
1346
|
+
stringValue(rawTerminal.agent_version);
|
|
1347
|
+
if (!screen || !version)
|
|
1348
|
+
return undefined;
|
|
1349
|
+
const responseWatch = input.observationCheckpoint
|
|
1350
|
+
? {
|
|
1351
|
+
...input.watch,
|
|
1352
|
+
observation_checkpoint: input.observationCheckpoint
|
|
1353
|
+
}
|
|
1354
|
+
: input.watch;
|
|
1355
|
+
const captureOffer = (responseAuthority) => captureTerminalInteractionRuntimeOffer({
|
|
1356
|
+
agent: input.watch.agent,
|
|
1357
|
+
terminalControl: terminalControlForWatch(rawTerminal),
|
|
1358
|
+
screen,
|
|
1359
|
+
runtime: terminalInteractionRuntimeForWatch({
|
|
1360
|
+
watch: responseWatch,
|
|
1361
|
+
rawTerminal,
|
|
1362
|
+
checkpoint: responseWatch.observation_checkpoint,
|
|
1363
|
+
version,
|
|
1364
|
+
responseAuthority
|
|
1365
|
+
}),
|
|
1366
|
+
now: new Date(input.observedAt),
|
|
1367
|
+
approvalBlocked: Boolean(approvalFingerprint(input.projectedTerminal)),
|
|
1368
|
+
trustedTerminalEvidence: input.watch.terminal.terminal_endpoint
|
|
1369
|
+
});
|
|
1370
|
+
let offer = captureOffer("notify_only");
|
|
1371
|
+
if (!offer)
|
|
1372
|
+
return undefined;
|
|
1373
|
+
if (requireFallbackAttribution &&
|
|
1374
|
+
!fallbackQuestionnaireContextMatches(input.watch, rawTerminal, input.observationCheckpoint ?? input.watch.observation_checkpoint, offer.nativeInspection)) {
|
|
1375
|
+
return undefined;
|
|
1376
|
+
}
|
|
1377
|
+
const responseDecision = terminalWatchResponseDecision(responseWatch, rawTerminal, offer.surfaceId, offer.promptFingerprint, input.options, input.dependencies);
|
|
1378
|
+
if (responseDecision.executable) {
|
|
1379
|
+
const executableOffer = captureOffer("executable");
|
|
1380
|
+
if (!executableOffer ||
|
|
1381
|
+
executableOffer.surfaceId !== offer.surfaceId ||
|
|
1382
|
+
executableOffer.projection.interaction_id !==
|
|
1383
|
+
offer.projection.interaction_id) {
|
|
1384
|
+
throw new Error("terminal Watch interaction surface changed during responder arbitration");
|
|
1385
|
+
}
|
|
1386
|
+
offer = executableOffer;
|
|
1387
|
+
}
|
|
1388
|
+
const currentInteraction = {
|
|
1389
|
+
projection: offer.projection.version === 2 &&
|
|
1390
|
+
offer.projection.subject.kind === "terminal_watch"
|
|
1391
|
+
? offer.projection
|
|
1392
|
+
: (() => {
|
|
1393
|
+
throw new Error("Watch interaction projection lost its Watch subject");
|
|
1394
|
+
})(),
|
|
1395
|
+
aggregate: createTerminalInteractionAggregate(offer.projection, input.observedAt)
|
|
1396
|
+
};
|
|
1397
|
+
const manualInteraction = terminalWatchManualInteractionSummary(offer.nativeInspection);
|
|
1398
|
+
return {
|
|
1399
|
+
...terminalWatchObservationFence(input.watch),
|
|
1400
|
+
kind: "interaction",
|
|
1401
|
+
observed_at: input.observedAt,
|
|
1402
|
+
last_activity_at: input.observedAt,
|
|
1403
|
+
...(input.observationCheckpoint
|
|
1404
|
+
? { observation_checkpoint: input.observationCheckpoint }
|
|
1405
|
+
: {}),
|
|
1406
|
+
evidence_fingerprint: sha256({
|
|
1407
|
+
schema: "agent-knock-knock/terminal-watch-interaction-event",
|
|
1408
|
+
version: 1,
|
|
1409
|
+
watch_id: input.watch.watch_id,
|
|
1410
|
+
interaction_id: offer.projection.interaction_id,
|
|
1411
|
+
surface_id: offer.surfaceId
|
|
1412
|
+
}),
|
|
1413
|
+
reason_code: responseDecision.suppress
|
|
1414
|
+
? "terminal_questionnaire_managed_responder_precedence"
|
|
1415
|
+
: currentInteraction.projection.capabilities.respond
|
|
1416
|
+
? "terminal_questionnaire_response_requested"
|
|
1417
|
+
: "terminal_questionnaire_requires_manual_response",
|
|
1418
|
+
current_interaction: currentInteraction,
|
|
1419
|
+
...(responseDecision.suppress
|
|
1420
|
+
? { suppress_notification: true }
|
|
1421
|
+
: {}),
|
|
1422
|
+
...(!responseDecision.suppress &&
|
|
1423
|
+
!currentInteraction.projection.capabilities.respond
|
|
1424
|
+
? { manual_interaction: manualInteraction }
|
|
1425
|
+
: {})
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* Durable fallback completion remains bound to its accepted provider artifact
|
|
1430
|
+
* even after the pane moves elsewhere. A live questionnaire is different: it
|
|
1431
|
+
* may be attributed to a Watch only while that exact accepted native context
|
|
1432
|
+
* is still the one rendered in the pane.
|
|
1433
|
+
*/
|
|
1434
|
+
function fallbackQuestionnaireContextMatches(watch, terminal, checkpoint, inspection) {
|
|
1435
|
+
if (watch.anchor.schema !==
|
|
1436
|
+
"agent-knock-knock/codex-user-explicit-fallback-watch-anchor") {
|
|
1437
|
+
return true;
|
|
1438
|
+
}
|
|
1439
|
+
if (!("schema" in checkpoint) ||
|
|
1440
|
+
checkpoint.schema !==
|
|
1441
|
+
"agent-knock-knock/codex-user-explicit-fallback-watch-checkpoint" ||
|
|
1442
|
+
!checkpoint.accepted_identity ||
|
|
1443
|
+
!checkpoint.acceptance_evidence) {
|
|
1444
|
+
return false;
|
|
1445
|
+
}
|
|
1446
|
+
const accepted = checkpoint.accepted_identity;
|
|
1447
|
+
const exactLiveContext = stringValue(terminal.native_agent_session_id)?.toLowerCase() ===
|
|
1448
|
+
accepted.native_thread_id &&
|
|
1449
|
+
stringValue(terminal.native_agent_process_uuid) === accepted.process_uuid &&
|
|
1450
|
+
stringValue(terminal.native_agent_process_birth) === accepted.process_birth &&
|
|
1451
|
+
rolloutFileIdentityMatches(terminal.native_agent_rollout, accepted.rollout);
|
|
1452
|
+
if (exactLiveContext)
|
|
1453
|
+
return true;
|
|
1454
|
+
const inventory = terminal._codex_open_root_rollout_inventory;
|
|
1455
|
+
if (!isRecord(inventory))
|
|
1456
|
+
return false;
|
|
1457
|
+
const attribution = detectCodexBoundQuestionnaireAttribution({
|
|
1458
|
+
currentInventory: inventory,
|
|
1459
|
+
acceptedIdentity: {
|
|
1460
|
+
sessionId: accepted.native_thread_id,
|
|
1461
|
+
processUuid: accepted.process_uuid,
|
|
1462
|
+
processBirth: accepted.process_birth,
|
|
1463
|
+
rollout: accepted.rollout
|
|
1464
|
+
},
|
|
1465
|
+
acceptanceId: checkpoint.acceptance_evidence.acceptanceId,
|
|
1466
|
+
screen: {
|
|
1467
|
+
currentStep: inspection.current_step,
|
|
1468
|
+
totalSteps: inspection.total_steps,
|
|
1469
|
+
prompt: inspection.question.prompt,
|
|
1470
|
+
responseKind: inspection.question.response_kind,
|
|
1471
|
+
...(inspection.question.options
|
|
1472
|
+
? {
|
|
1473
|
+
options: inspection.question.options.map((option) => ({
|
|
1474
|
+
label: option.label,
|
|
1475
|
+
...(option.description
|
|
1476
|
+
? { description: option.description }
|
|
1477
|
+
: {})
|
|
1478
|
+
}))
|
|
1479
|
+
}
|
|
1480
|
+
: {}),
|
|
1481
|
+
exactShape: inspection.status === "actionable" &&
|
|
1482
|
+
inspection.question.response_kind !== "multi_select"
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
return attribution.status === "matched";
|
|
1486
|
+
}
|
|
1487
|
+
function terminalWatchResponseDecision(watch, rawTerminal, surfaceId, promptFingerprint, options, dependencies) {
|
|
1488
|
+
if (watch.status !== "active" ||
|
|
1489
|
+
watch.interaction_policy !== "respond_when_exact" ||
|
|
1490
|
+
isTerminalActivityWatch(watch) ||
|
|
1491
|
+
!terminalWatchCandidateMatchesLiveContext(watch, rawTerminal) ||
|
|
1492
|
+
(watch.callback_route !== undefined &&
|
|
1493
|
+
watch.callback_route.capabilities?.respond !== true)) {
|
|
1494
|
+
return { executable: false, suppress: false };
|
|
1495
|
+
}
|
|
1496
|
+
const terminalControl = terminalControlForWatch(rawTerminal);
|
|
1497
|
+
let blockers;
|
|
1498
|
+
try {
|
|
1499
|
+
blockers = dependencies.terminalIncarnationBlockingTurns(dependencies.storeDirFromOptions(options), terminalControl);
|
|
1500
|
+
}
|
|
1501
|
+
catch {
|
|
1502
|
+
return { executable: false, suppress: false };
|
|
1503
|
+
}
|
|
1504
|
+
let storedWatches;
|
|
1505
|
+
try {
|
|
1506
|
+
storedWatches = createTerminalWatchStore(dependencies.storeDirFromOptions(options), { acquire: dependencies.acquireFileLock }).list();
|
|
1507
|
+
}
|
|
1508
|
+
catch {
|
|
1509
|
+
return { executable: false, suppress: false };
|
|
1510
|
+
}
|
|
1511
|
+
const currentIndex = storedWatches.findIndex((candidate) => candidate.watch_id === watch.watch_id);
|
|
1512
|
+
if (currentIndex < 0) {
|
|
1513
|
+
storedWatches.push(watch);
|
|
1514
|
+
}
|
|
1515
|
+
else {
|
|
1516
|
+
storedWatches[currentIndex] = watch;
|
|
1517
|
+
}
|
|
1518
|
+
const candidates = storedWatches.flatMap((candidate) => {
|
|
1519
|
+
const surfaceCandidate = terminalWatchSurfaceCandidate(candidate, watch, rawTerminal, surfaceId, promptFingerprint);
|
|
1520
|
+
return surfaceCandidate ? [surfaceCandidate] : [];
|
|
1521
|
+
});
|
|
1522
|
+
const fences = [
|
|
1523
|
+
...blockers.flatMap((turn) => {
|
|
1524
|
+
const fence = managedTerminalInteractionFence(turn, surfaceId, promptFingerprint, watch.agent);
|
|
1525
|
+
return fence ? [fence] : [];
|
|
1526
|
+
}),
|
|
1527
|
+
...candidates
|
|
1528
|
+
.filter((candidate) => candidate.disposition === "fence")
|
|
1529
|
+
.map((candidate) => ({
|
|
1530
|
+
owner_session: candidate.watch.openclaw_session
|
|
1531
|
+
}))
|
|
1532
|
+
];
|
|
1533
|
+
if (fences.length > 0) {
|
|
1534
|
+
return {
|
|
1535
|
+
executable: false,
|
|
1536
|
+
suppress: fences.some((fence) => fence.owner_session === watch.openclaw_session)
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
const claims = [
|
|
1540
|
+
...blockers.flatMap((turn) => {
|
|
1541
|
+
const claim = managedTerminalInteractionResponderClaim(turn, surfaceId, promptFingerprint, watch.agent);
|
|
1542
|
+
return claim ? [claim] : [];
|
|
1543
|
+
}),
|
|
1544
|
+
...candidates
|
|
1545
|
+
.filter((candidate) => candidate.disposition === "responder")
|
|
1546
|
+
.map(({ watch: candidate }) => ({
|
|
1547
|
+
owner_id: candidate.watch_id,
|
|
1548
|
+
owner_session: candidate.openclaw_session,
|
|
1549
|
+
surface_id: surfaceId,
|
|
1550
|
+
responder_class: isUserExplicitFallbackWatch(candidate)
|
|
1551
|
+
? "exact_request_watch"
|
|
1552
|
+
: "exact_task_watch",
|
|
1553
|
+
response_authority: "executable",
|
|
1554
|
+
created_at: candidate.created_at
|
|
1555
|
+
}))
|
|
1556
|
+
];
|
|
1557
|
+
const winner = selectTerminalInteractionResponder(claims);
|
|
1558
|
+
const executable = winner?.owner_id === watch.watch_id;
|
|
1559
|
+
return {
|
|
1560
|
+
executable,
|
|
1561
|
+
suppress: Boolean(winner &&
|
|
1562
|
+
!executable &&
|
|
1563
|
+
winner.owner_session === watch.openclaw_session)
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
function terminalWatchSurfaceCandidate(candidate, current, rawTerminal, surfaceId, promptFingerprint) {
|
|
1567
|
+
if (candidate.status !== "active" ||
|
|
1568
|
+
candidate.interaction_policy !== "respond_when_exact" ||
|
|
1569
|
+
isTerminalActivityWatch(candidate) ||
|
|
1570
|
+
candidate.terminal.terminal_id !== current.terminal.terminal_id ||
|
|
1571
|
+
!terminalWatchCandidateMatchesLiveContext(candidate, rawTerminal) ||
|
|
1572
|
+
(candidate.callback_route !== undefined &&
|
|
1573
|
+
candidate.callback_route.capabilities?.respond !== true)) {
|
|
1574
|
+
return undefined;
|
|
1575
|
+
}
|
|
1576
|
+
const persisted = candidate.current_interaction;
|
|
1577
|
+
const samePersistedSurface = Boolean(persisted &&
|
|
1578
|
+
persisted.projection.surface_id === surfaceId &&
|
|
1579
|
+
persisted.projection.prompt_fingerprint === promptFingerprint);
|
|
1580
|
+
if (candidate.watch_id !== current.watch_id) {
|
|
1581
|
+
if (!samePersistedSurface ||
|
|
1582
|
+
!terminalWatchesShareExactTask(candidate, current) ||
|
|
1583
|
+
(persisted.aggregate.state === "pending" &&
|
|
1584
|
+
(persisted.projection.response_authority !== "executable" ||
|
|
1585
|
+
persisted.projection.capabilities.respond !== true ||
|
|
1586
|
+
persisted.aggregate.response_authority !== "executable"))) {
|
|
1587
|
+
return undefined;
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
return {
|
|
1591
|
+
watch: candidate,
|
|
1592
|
+
disposition: samePersistedSurface && persisted.aggregate.state !== "pending"
|
|
1593
|
+
? "fence"
|
|
1594
|
+
: "responder"
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
function terminalWatchesShareExactTask(left, right) {
|
|
1598
|
+
const leftKey = terminalWatchExactTaskKey(left);
|
|
1599
|
+
const rightKey = terminalWatchExactTaskKey(right);
|
|
1600
|
+
return leftKey !== undefined && leftKey === rightKey;
|
|
1601
|
+
}
|
|
1602
|
+
function terminalWatchExactTaskKey(watch) {
|
|
1603
|
+
const anchor = watch.anchor;
|
|
1604
|
+
if (anchor.schema ===
|
|
1605
|
+
"agent-knock-knock/codex-human-started-active-task-anchor") {
|
|
1606
|
+
return codexWatchTaskKey({
|
|
1607
|
+
taskId: anchor.turn_id,
|
|
1608
|
+
nativeSessionId: anchor.native_thread_id,
|
|
1609
|
+
processUuid: anchor.process_uuid,
|
|
1610
|
+
processBirth: anchor.process_birth,
|
|
1611
|
+
rollout: anchor.rollout,
|
|
1612
|
+
requestHash: anchor.request_hash
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
if (anchor.schema ===
|
|
1616
|
+
"agent-knock-knock/codex-user-explicit-fallback-watch-anchor") {
|
|
1617
|
+
const checkpoint = watch.observation_checkpoint;
|
|
1618
|
+
if (!("schema" in checkpoint) ||
|
|
1619
|
+
checkpoint.schema !==
|
|
1620
|
+
"agent-knock-knock/codex-user-explicit-fallback-watch-checkpoint" ||
|
|
1621
|
+
!checkpoint.accepted_identity ||
|
|
1622
|
+
!checkpoint.acceptance_evidence)
|
|
1623
|
+
return undefined;
|
|
1624
|
+
return codexWatchTaskKey({
|
|
1625
|
+
taskId: checkpoint.acceptance_evidence.acceptanceId,
|
|
1626
|
+
nativeSessionId: checkpoint.accepted_identity.native_thread_id,
|
|
1627
|
+
processUuid: checkpoint.accepted_identity.process_uuid,
|
|
1628
|
+
processBirth: checkpoint.accepted_identity.process_birth,
|
|
1629
|
+
rollout: checkpoint.accepted_identity.rollout,
|
|
1630
|
+
requestHash: anchor.request_hash
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
if (anchor.schema ===
|
|
1634
|
+
"agent-knock-knock/claude-human-started-active-task-anchor") {
|
|
1635
|
+
return claudeWatchTaskKey({
|
|
1636
|
+
taskId: anchor.prompt_uuid,
|
|
1637
|
+
nativeSessionId: anchor.session_id,
|
|
1638
|
+
pid: anchor.pid,
|
|
1639
|
+
agentStartedAtMs: anchor.agent_started_at_ms,
|
|
1640
|
+
transcriptFileId: anchor.transcript_file_id,
|
|
1641
|
+
cwd: anchor.cwd,
|
|
1642
|
+
requestHash: anchor.request_hash
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
if (anchor.schema ===
|
|
1646
|
+
"agent-knock-knock/claude-user-explicit-fallback-watch-anchor") {
|
|
1647
|
+
const checkpoint = watch.observation_checkpoint;
|
|
1648
|
+
if (!("schema" in checkpoint) ||
|
|
1649
|
+
checkpoint.schema !==
|
|
1650
|
+
"agent-knock-knock/claude-user-explicit-fallback-watch-checkpoint" ||
|
|
1651
|
+
!checkpoint.accepted_prompt_uuid ||
|
|
1652
|
+
!checkpoint.acceptance_evidence)
|
|
1653
|
+
return undefined;
|
|
1654
|
+
const transcriptFileId = stringValue(checkpoint.acceptance_evidence.metadata?.transcript_file_id);
|
|
1655
|
+
if (!transcriptFileId)
|
|
1656
|
+
return undefined;
|
|
1657
|
+
return claudeWatchTaskKey({
|
|
1658
|
+
taskId: checkpoint.accepted_prompt_uuid,
|
|
1659
|
+
nativeSessionId: anchor.transcript_anchor.session_id,
|
|
1660
|
+
pid: anchor.transcript_anchor.pid,
|
|
1661
|
+
agentStartedAtMs: anchor.transcript_anchor.agent_started_at_ms,
|
|
1662
|
+
transcriptFileId,
|
|
1663
|
+
cwd: anchor.transcript_anchor.cwd,
|
|
1664
|
+
requestHash: anchor.request_hash
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
return undefined;
|
|
1668
|
+
}
|
|
1669
|
+
function codexWatchTaskKey(input) {
|
|
1670
|
+
const rollout = isRecord(input.rollout) ? input.rollout : undefined;
|
|
1671
|
+
const device = stringValue(rollout?.device);
|
|
1672
|
+
const inode = stringValue(rollout?.inode);
|
|
1673
|
+
const rolloutPath = stringValue(rollout?.path);
|
|
1674
|
+
if (!device || !inode || !rolloutPath)
|
|
1675
|
+
return undefined;
|
|
1676
|
+
return sha256({
|
|
1677
|
+
agent: "codex",
|
|
1678
|
+
task_id: input.taskId,
|
|
1679
|
+
native_session_id: input.nativeSessionId.toLowerCase(),
|
|
1680
|
+
process_uuid: input.processUuid,
|
|
1681
|
+
process_birth: input.processBirth,
|
|
1682
|
+
rollout: { device, inode, path: rolloutPath },
|
|
1683
|
+
request_hash: input.requestHash
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1686
|
+
function claudeWatchTaskKey(input) {
|
|
1687
|
+
if (!Number.isSafeInteger(input.pid) || input.pid <= 0 ||
|
|
1688
|
+
!Number.isSafeInteger(input.agentStartedAtMs) ||
|
|
1689
|
+
input.agentStartedAtMs <= 0)
|
|
1690
|
+
return undefined;
|
|
1691
|
+
return sha256({
|
|
1692
|
+
agent: "claude",
|
|
1693
|
+
task_id: input.taskId,
|
|
1694
|
+
native_session_id: input.nativeSessionId,
|
|
1695
|
+
pid: input.pid,
|
|
1696
|
+
agent_started_at_ms: input.agentStartedAtMs,
|
|
1697
|
+
transcript_file_id: input.transcriptFileId,
|
|
1698
|
+
cwd: input.cwd,
|
|
1699
|
+
request_hash: input.requestHash
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
function managedTerminalInteractionFence(turn, surfaceId, promptFingerprint, agent) {
|
|
1703
|
+
const ownerSession = stringValue(turn.openclaw_session);
|
|
1704
|
+
const takeover = isRecord(turn.native_session_takeover)
|
|
1705
|
+
? turn.native_session_takeover
|
|
1706
|
+
: undefined;
|
|
1707
|
+
if (ownerSession &&
|
|
1708
|
+
executorForConversation(turn).kind === agent &&
|
|
1709
|
+
stringValue(takeover?.terminal_bridge_last_interaction_surface_id) ===
|
|
1710
|
+
surfaceId &&
|
|
1711
|
+
stringValue(takeover?.terminal_bridge_last_interaction_fingerprint) ===
|
|
1712
|
+
promptFingerprint &&
|
|
1713
|
+
stringValue(takeover?.terminal_bridge_last_interaction_id) !== undefined &&
|
|
1714
|
+
canonicalTimestamp(takeover?.terminal_bridge_last_interaction_at) !== undefined) {
|
|
1715
|
+
return { owner_session: ownerSession };
|
|
1716
|
+
}
|
|
1717
|
+
const evidence = managedTerminalInteractionEvidence(turn, surfaceId, promptFingerprint, agent);
|
|
1718
|
+
if (!evidence)
|
|
1719
|
+
return undefined;
|
|
1720
|
+
const dispatch = isRecord(evidence.takeover.terminal_bridge_interaction_dispatch)
|
|
1721
|
+
? evidence.takeover.terminal_bridge_interaction_dispatch
|
|
1722
|
+
: undefined;
|
|
1723
|
+
const dispatchState = stringValue(dispatch?.state);
|
|
1724
|
+
const dispatchMatches = (dispatchState === "reserved" || dispatchState === "uncertain") &&
|
|
1725
|
+
stringValue(dispatch?.interaction_id) ===
|
|
1726
|
+
evidence.projection.interaction_id &&
|
|
1727
|
+
stringValue(dispatch?.interaction_prompt_fingerprint) ===
|
|
1728
|
+
promptFingerprint;
|
|
1729
|
+
return evidence.projection.state === "response_uncertain" || dispatchMatches
|
|
1730
|
+
? { owner_session: evidence.ownerSession }
|
|
1731
|
+
: undefined;
|
|
1732
|
+
}
|
|
1733
|
+
function canonicalTimestamp(value) {
|
|
1734
|
+
const timestamp = stringValue(value);
|
|
1735
|
+
return timestamp &&
|
|
1736
|
+
Number.isFinite(Date.parse(timestamp)) &&
|
|
1737
|
+
new Date(timestamp).toISOString() === timestamp
|
|
1738
|
+
? timestamp
|
|
1739
|
+
: undefined;
|
|
1740
|
+
}
|
|
1741
|
+
function managedTerminalInteractionResponderClaim(turn, surfaceId, promptFingerprint, agent) {
|
|
1742
|
+
const evidence = managedTerminalInteractionEvidence(turn, surfaceId, promptFingerprint, agent);
|
|
1743
|
+
if (!evidence)
|
|
1744
|
+
return undefined;
|
|
1745
|
+
const createdAt = stringValue(evidence.notification.notified_at);
|
|
1746
|
+
if (!createdAt ||
|
|
1747
|
+
!Number.isFinite(Date.parse(createdAt)) ||
|
|
1748
|
+
new Date(createdAt).toISOString() !== createdAt)
|
|
1749
|
+
return undefined;
|
|
1750
|
+
if (evidence.projection.state !== "pending" ||
|
|
1751
|
+
evidence.projection.capabilities.respond !== true) {
|
|
1752
|
+
return undefined;
|
|
1753
|
+
}
|
|
1754
|
+
return {
|
|
1755
|
+
owner_id: evidence.ownerId,
|
|
1756
|
+
owner_session: evidence.ownerSession,
|
|
1757
|
+
surface_id: surfaceId,
|
|
1758
|
+
responder_class: "managed_turn",
|
|
1759
|
+
response_authority: "executable",
|
|
1760
|
+
created_at: createdAt
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
function managedTerminalInteractionEvidence(turn, surfaceId, promptFingerprint, agent) {
|
|
1764
|
+
const takeover = isRecord(turn.native_session_takeover)
|
|
1765
|
+
? turn.native_session_takeover
|
|
1766
|
+
: undefined;
|
|
1767
|
+
const notification = isRecord(takeover?.terminal_bridge_interaction_notification)
|
|
1768
|
+
? takeover.terminal_bridge_interaction_notification
|
|
1769
|
+
: undefined;
|
|
1770
|
+
if (!takeover ||
|
|
1771
|
+
!notification ||
|
|
1772
|
+
stringValue(notification.surface_id) !== surfaceId ||
|
|
1773
|
+
stringValue(notification.prompt_fingerprint) !== promptFingerprint)
|
|
1774
|
+
return undefined;
|
|
1775
|
+
let ownerId;
|
|
1776
|
+
try {
|
|
1777
|
+
ownerId = turnIdForConversation(turn);
|
|
1778
|
+
}
|
|
1779
|
+
catch {
|
|
1780
|
+
return undefined;
|
|
1781
|
+
}
|
|
1782
|
+
const ownerSession = stringValue(turn.openclaw_session);
|
|
1783
|
+
if (!ownerSession)
|
|
1784
|
+
return undefined;
|
|
1785
|
+
const projection = (() => {
|
|
1786
|
+
try {
|
|
1787
|
+
return validateAnyTerminalInteractionProjection(notification.interaction_state);
|
|
1788
|
+
}
|
|
1789
|
+
catch {
|
|
1790
|
+
return undefined;
|
|
1791
|
+
}
|
|
1792
|
+
})();
|
|
1793
|
+
if (!projection ||
|
|
1794
|
+
projection.interaction_id !== stringValue(notification.interaction_id) ||
|
|
1795
|
+
projection.agent !== agent ||
|
|
1796
|
+
projection.questions.length !== 1 ||
|
|
1797
|
+
projection.questions[0]?.question_id !==
|
|
1798
|
+
stringValue(notification.question_id) ||
|
|
1799
|
+
(projection.version === 1
|
|
1800
|
+
? projection.turn_id !== ownerId
|
|
1801
|
+
: projection.subject.kind !== "managed_turn" ||
|
|
1802
|
+
projection.subject.turn_id !== ownerId))
|
|
1803
|
+
return undefined;
|
|
1804
|
+
return { ownerId, ownerSession, takeover, notification, projection };
|
|
1805
|
+
}
|
|
1806
|
+
function terminalWatchCandidateMatchesLiveContext(watch, terminal) {
|
|
1807
|
+
if (!terminalMatchesWatch(terminal, watch))
|
|
1808
|
+
return false;
|
|
1809
|
+
const sessionId = stringValue(terminal.native_agent_session_id);
|
|
1810
|
+
if (watch.anchor.schema ===
|
|
1811
|
+
"agent-knock-knock/codex-human-started-active-task-anchor") {
|
|
1812
|
+
return sessionId?.toLowerCase() === watch.anchor.native_thread_id &&
|
|
1813
|
+
rolloutFileIdentityMatches(terminal.native_agent_rollout, watch.anchor.rollout);
|
|
1814
|
+
}
|
|
1815
|
+
if (watch.anchor.schema ===
|
|
1816
|
+
"agent-knock-knock/codex-user-explicit-fallback-watch-anchor") {
|
|
1817
|
+
const checkpoint = watch.observation_checkpoint;
|
|
1818
|
+
return "schema" in checkpoint &&
|
|
1819
|
+
checkpoint.schema ===
|
|
1820
|
+
"agent-knock-knock/codex-user-explicit-fallback-watch-checkpoint" &&
|
|
1821
|
+
checkpoint.accepted_identity !== undefined &&
|
|
1822
|
+
sessionId?.toLowerCase() === checkpoint.accepted_identity.native_thread_id &&
|
|
1823
|
+
rolloutFileIdentityMatches(terminal.native_agent_rollout, checkpoint.accepted_identity.rollout);
|
|
1824
|
+
}
|
|
1825
|
+
if (watch.anchor.schema ===
|
|
1826
|
+
"agent-knock-knock/claude-human-started-active-task-anchor") {
|
|
1827
|
+
return sessionId === watch.anchor.session_id;
|
|
1828
|
+
}
|
|
1829
|
+
if (watch.anchor.schema ===
|
|
1830
|
+
"agent-knock-knock/claude-user-explicit-fallback-watch-anchor") {
|
|
1831
|
+
return sessionId === watch.anchor.transcript_anchor.session_id;
|
|
1832
|
+
}
|
|
1833
|
+
return false;
|
|
1834
|
+
}
|
|
1835
|
+
function terminalInteractionRuntimeForWatch(input) {
|
|
1836
|
+
const { watch, rawTerminal, checkpoint } = input;
|
|
1837
|
+
let nativeSessionId = stringValue(rawTerminal.native_agent_session_id);
|
|
1838
|
+
let nativeProcessUuid = stringValue(rawTerminal.native_agent_process_uuid);
|
|
1839
|
+
let nativeProcessBirth = stringValue(rawTerminal.native_agent_process_birth);
|
|
1840
|
+
let nativeRollout = isRecord(rawTerminal.native_agent_rollout)
|
|
1841
|
+
? rawTerminal.native_agent_rollout
|
|
1842
|
+
: undefined;
|
|
1843
|
+
if (watch.anchor.schema ===
|
|
1844
|
+
"agent-knock-knock/codex-human-started-active-task-anchor") {
|
|
1845
|
+
nativeSessionId = watch.anchor.native_thread_id;
|
|
1846
|
+
nativeProcessUuid = watch.anchor.process_uuid;
|
|
1847
|
+
nativeProcessBirth = watch.anchor.process_birth;
|
|
1848
|
+
nativeRollout = watch.anchor.rollout;
|
|
1849
|
+
}
|
|
1850
|
+
else if (watch.anchor.schema ===
|
|
1851
|
+
"agent-knock-knock/codex-user-explicit-fallback-watch-anchor" &&
|
|
1852
|
+
"schema" in checkpoint &&
|
|
1853
|
+
checkpoint.schema ===
|
|
1854
|
+
"agent-knock-knock/codex-user-explicit-fallback-watch-checkpoint" &&
|
|
1855
|
+
checkpoint.accepted_identity) {
|
|
1856
|
+
nativeSessionId = checkpoint.accepted_identity.native_thread_id;
|
|
1857
|
+
nativeProcessUuid = checkpoint.accepted_identity.process_uuid;
|
|
1858
|
+
nativeProcessBirth = checkpoint.accepted_identity.process_birth;
|
|
1859
|
+
nativeRollout = checkpoint.accepted_identity.rollout;
|
|
1860
|
+
}
|
|
1861
|
+
else if (watch.anchor.schema ===
|
|
1862
|
+
"agent-knock-knock/claude-human-started-active-task-anchor") {
|
|
1863
|
+
nativeSessionId = watch.anchor.session_id;
|
|
1864
|
+
}
|
|
1865
|
+
else if (watch.anchor.schema ===
|
|
1866
|
+
"agent-knock-knock/claude-user-explicit-fallback-watch-anchor") {
|
|
1867
|
+
nativeSessionId = watch.anchor.transcript_anchor.session_id;
|
|
1868
|
+
}
|
|
1869
|
+
const inventory = isRecord(rawTerminal._codex_open_root_rollout_inventory)
|
|
1870
|
+
? rawTerminal._codex_open_root_rollout_inventory
|
|
1871
|
+
: undefined;
|
|
1872
|
+
const allowedAdditionalNativeIdentities = inventory && nativeRollout
|
|
1873
|
+
? inventory.roots.filter((root) => !rolloutFileIdentityMatches(root.rollout, nativeRollout))
|
|
1874
|
+
.map((root) => ({
|
|
1875
|
+
sessionId: root.sessionId,
|
|
1876
|
+
processUuid: root.processUuid,
|
|
1877
|
+
processBirth: root.processBirth,
|
|
1878
|
+
rollout: root.rollout
|
|
1879
|
+
}))
|
|
1880
|
+
: [];
|
|
1881
|
+
const startedAt = Number(rawTerminal.native_agent_process_started_at);
|
|
1882
|
+
return {
|
|
1883
|
+
pid: positiveInteger(rawTerminal.pid, "terminal agent PID"),
|
|
1884
|
+
agentVersion: input.version,
|
|
1885
|
+
interactionSubject: {
|
|
1886
|
+
kind: "terminal_watch",
|
|
1887
|
+
watch_id: watch.watch_id,
|
|
1888
|
+
anchor_fingerprint: watch.anchor.anchor_fingerprint
|
|
1889
|
+
},
|
|
1890
|
+
interactionResponseAuthority: input.responseAuthority,
|
|
1891
|
+
nativeSessionId,
|
|
1892
|
+
nativeProcessUuid,
|
|
1893
|
+
nativeProcessBirth,
|
|
1894
|
+
nativeRollout,
|
|
1895
|
+
requireNativeProcessUuid: watch.agent === "claude" &&
|
|
1896
|
+
!isTerminalActivityWatch(watch),
|
|
1897
|
+
requireNativeRolloutIdentity: watch.agent === "codex" &&
|
|
1898
|
+
!isTerminalActivityWatch(watch),
|
|
1899
|
+
allowedAdditionalNativeIdentities,
|
|
1900
|
+
...(Number.isSafeInteger(startedAt) && startedAt > 0
|
|
1901
|
+
? { nativeProcessStartedAt: startedAt }
|
|
1902
|
+
: {}),
|
|
1903
|
+
cwd: watch.terminal.workspace,
|
|
1904
|
+
conversationId: watch.terminal.terminal_id,
|
|
1905
|
+
terminalTarget: terminalControlForWatch(rawTerminal).target
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
function terminalWatchScreenExcerpt(rawTerminal, projectedTerminal) {
|
|
1909
|
+
const direct = stringValue(projectedTerminal.screen_excerpt) ??
|
|
1910
|
+
stringValue(rawTerminal.screen_excerpt);
|
|
1911
|
+
if (direct)
|
|
1912
|
+
return direct;
|
|
1913
|
+
const status = isRecord(rawTerminal._terminal_status_snapshot)
|
|
1914
|
+
? rawTerminal._terminal_status_snapshot
|
|
1915
|
+
: undefined;
|
|
1916
|
+
const screen = status && isRecord(status.screen) ? status.screen : undefined;
|
|
1917
|
+
return stringValue(screen?.excerpt);
|
|
1918
|
+
}
|
|
1919
|
+
function terminalWatchManualInteractionSummary(inspection) {
|
|
1920
|
+
const exposeQuestion = inspection.status === "actionable";
|
|
1921
|
+
return {
|
|
1922
|
+
kind: "questionnaire",
|
|
1923
|
+
response_kind: inspection.question.response_kind,
|
|
1924
|
+
required: inspection.question.required,
|
|
1925
|
+
current_step: inspection.current_step,
|
|
1926
|
+
total_steps: inspection.total_steps,
|
|
1927
|
+
parser_status: inspection.status,
|
|
1928
|
+
...(exposeQuestion
|
|
1929
|
+
? {
|
|
1930
|
+
prompt: boundedInteractionText(inspection.question.prompt, 1_000),
|
|
1931
|
+
...(inspection.question.options &&
|
|
1932
|
+
inspection.question.options.length > 0
|
|
1933
|
+
? {
|
|
1934
|
+
options: inspection.question.options.slice(0, 8).map((option) => ({
|
|
1935
|
+
label: boundedInteractionText(option.label, 300),
|
|
1936
|
+
...(option.description
|
|
1937
|
+
? {
|
|
1938
|
+
description: boundedInteractionText(option.description, 600)
|
|
1939
|
+
}
|
|
1940
|
+
: {})
|
|
1941
|
+
}))
|
|
1942
|
+
}
|
|
1943
|
+
: {})
|
|
1944
|
+
}
|
|
1945
|
+
: { manual_reason: inspection.reason })
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
function boundedInteractionText(value, maxCharacters) {
|
|
1949
|
+
const normalized = value
|
|
1950
|
+
.replace(/[\u0000-\u001F\u007F-\u009F]+/gu, " ")
|
|
1951
|
+
.replace(/\s+/gu, " ")
|
|
1952
|
+
.trim()
|
|
1953
|
+
.slice(0, maxCharacters);
|
|
1954
|
+
return normalized || "Native questionnaire";
|
|
1955
|
+
}
|
|
1009
1956
|
function fallbackPendingOrTerminalObservation(input) {
|
|
1010
1957
|
if (input.exactTerminal.state === "unavailable") {
|
|
1011
1958
|
return {
|
|
@@ -1282,7 +2229,7 @@ function codexIdentityForWatch(watch) {
|
|
|
1282
2229
|
rollout: watch.anchor.rollout
|
|
1283
2230
|
};
|
|
1284
2231
|
}
|
|
1285
|
-
function publicTerminalWatch(watch, additionalWarnings = []) {
|
|
2232
|
+
function publicTerminalWatch(watch, additionalWarnings = [], exposeInteraction = false) {
|
|
1286
2233
|
const pending = watch.notification_outbox.filter(({ status }) => status === "pending" || status === "delivering" || status === "failed").length;
|
|
1287
2234
|
const capturedAgentVersion = terminalWatchCapturedAgentVersion(watch);
|
|
1288
2235
|
const compatibilityWarning = capturedAgentVersion === undefined
|
|
@@ -1300,6 +2247,22 @@ function publicTerminalWatch(watch, additionalWarnings = []) {
|
|
|
1300
2247
|
const latestFailedCallback = [...watch.notification_outbox]
|
|
1301
2248
|
.reverse()
|
|
1302
2249
|
.find(({ status }) => status === "failed");
|
|
2250
|
+
const currentInteraction = exposeInteraction && watch.status === "active" &&
|
|
2251
|
+
watch.current_interaction &&
|
|
2252
|
+
["pending", "reserved", "response_uncertain"].includes(watch.current_interaction.aggregate.state)
|
|
2253
|
+
? watch.current_interaction
|
|
2254
|
+
: undefined;
|
|
2255
|
+
const interactionProjection = currentInteraction?.aggregate.state ===
|
|
2256
|
+
"reserved"
|
|
2257
|
+
? {
|
|
2258
|
+
...currentInteraction.projection,
|
|
2259
|
+
state: "response_uncertain",
|
|
2260
|
+
capabilities: {
|
|
2261
|
+
...currentInteraction.projection.capabilities,
|
|
2262
|
+
respond: false
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
: currentInteraction?.projection;
|
|
1303
2266
|
return {
|
|
1304
2267
|
watch_id: watch.watch_id,
|
|
1305
2268
|
source: userExplicitFallback
|
|
@@ -1309,6 +2272,12 @@ function publicTerminalWatch(watch, additionalWarnings = []) {
|
|
|
1309
2272
|
: "user_selected_terminal_watch",
|
|
1310
2273
|
watch_mode: terminalActivityFallback ? "terminal_activity" : "exact_task",
|
|
1311
2274
|
confidence: terminalActivityFallback ? "best_effort" : "exact",
|
|
2275
|
+
interaction_policy: watch.interaction_policy,
|
|
2276
|
+
capabilities: {
|
|
2277
|
+
interaction_notify: true,
|
|
2278
|
+
interaction_respond: Boolean(interactionProjection?.state === "pending" &&
|
|
2279
|
+
interactionProjection.capabilities.respond)
|
|
2280
|
+
},
|
|
1312
2281
|
agent: watch.agent,
|
|
1313
2282
|
terminal_id: watch.terminal.terminal_id,
|
|
1314
2283
|
native_thread_id: terminalWatchNativeThreadId(watch),
|
|
@@ -1334,6 +2303,12 @@ function publicTerminalWatch(watch, additionalWarnings = []) {
|
|
|
1334
2303
|
? { last_error_code: latestFailedCallback.last_error_code }
|
|
1335
2304
|
: {})
|
|
1336
2305
|
},
|
|
2306
|
+
...(interactionProjection
|
|
2307
|
+
? {
|
|
2308
|
+
interaction_state: interactionProjection,
|
|
2309
|
+
interaction_prompt_fingerprint: interactionProjection.prompt_fingerprint
|
|
2310
|
+
}
|
|
2311
|
+
: {}),
|
|
1337
2312
|
...(watch.settlement
|
|
1338
2313
|
? {
|
|
1339
2314
|
settlement: {
|
|
@@ -1359,10 +2334,29 @@ function publicTerminalWatch(watch, additionalWarnings = []) {
|
|
|
1359
2334
|
requires_user_intent: true
|
|
1360
2335
|
}
|
|
1361
2336
|
}
|
|
2337
|
+
: {}),
|
|
2338
|
+
...(interactionProjection?.state === "pending" &&
|
|
2339
|
+
interactionProjection.capabilities.respond
|
|
2340
|
+
? {
|
|
2341
|
+
respond_interaction: {
|
|
2342
|
+
tool: "agent_knock_knock_respond_interaction",
|
|
2343
|
+
arguments: { watch_id: watch.watch_id },
|
|
2344
|
+
requires_user_intent: true
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
1362
2347
|
: {})
|
|
1363
2348
|
}
|
|
1364
2349
|
};
|
|
1365
2350
|
}
|
|
2351
|
+
function watchStatusInteractionAccess(watch, options) {
|
|
2352
|
+
if (!Object.hasOwn(options, "openclawSession"))
|
|
2353
|
+
return false;
|
|
2354
|
+
const requested = stringValue(options.openclawSession);
|
|
2355
|
+
if (!requested || requested !== watch.openclaw_session) {
|
|
2356
|
+
throw new Error(`terminal Watch ${watch.watch_id} belongs to a different controller session; executable interaction details were not disclosed`);
|
|
2357
|
+
}
|
|
2358
|
+
return true;
|
|
2359
|
+
}
|
|
1366
2360
|
function terminalWatchCapturedAgentVersion(watch) {
|
|
1367
2361
|
switch (watch.anchor.schema) {
|
|
1368
2362
|
case "agent-knock-knock/codex-human-started-active-task-anchor":
|
|
@@ -1450,6 +2444,48 @@ function requiredSha256(value, label) {
|
|
|
1450
2444
|
}
|
|
1451
2445
|
return result;
|
|
1452
2446
|
}
|
|
2447
|
+
function requiredTimestamp(value, label) {
|
|
2448
|
+
const result = requiredString(value, label);
|
|
2449
|
+
if (!Number.isFinite(Date.parse(result))) {
|
|
2450
|
+
throw new Error(`${label} must be a valid timestamp`);
|
|
2451
|
+
}
|
|
2452
|
+
return result;
|
|
2453
|
+
}
|
|
2454
|
+
function parseWatchInteractionResponse(value) {
|
|
2455
|
+
const serialized = requiredString(value, "--response-json");
|
|
2456
|
+
try {
|
|
2457
|
+
const parsed = JSON.parse(serialized);
|
|
2458
|
+
if (!isRecord(parsed)) {
|
|
2459
|
+
throw new Error("response must be an object");
|
|
2460
|
+
}
|
|
2461
|
+
return parsed;
|
|
2462
|
+
}
|
|
2463
|
+
catch (error) {
|
|
2464
|
+
throw new Error(`--response-json is invalid: ${safeDiagnostic(error)}`);
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
function assertWatchControllerSession(watch, requestedValue) {
|
|
2468
|
+
const requested = stringValue(requestedValue);
|
|
2469
|
+
if (!requested || requested !== watch.openclaw_session) {
|
|
2470
|
+
throw new Error(`terminal Watch ${watch.watch_id} belongs to a different controller session; no terminal input was sent`);
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
function requiredExecutableWatchInteraction(watch, interactionId, expectedFingerprint) {
|
|
2474
|
+
if (watch.status !== "active") {
|
|
2475
|
+
throw new Error(`terminal Watch ${watch.watch_id} is ${watch.status}`);
|
|
2476
|
+
}
|
|
2477
|
+
const interaction = watch.current_interaction;
|
|
2478
|
+
if (watch.interaction_policy !== "respond_when_exact" ||
|
|
2479
|
+
!interaction ||
|
|
2480
|
+
interaction.aggregate.state !== "pending" ||
|
|
2481
|
+
interaction.projection.interaction_id !== interactionId ||
|
|
2482
|
+
interaction.projection.prompt_fingerprint !== expectedFingerprint ||
|
|
2483
|
+
interaction.projection.response_authority !== "executable" ||
|
|
2484
|
+
!interaction.projection.capabilities.respond) {
|
|
2485
|
+
throw new Error("terminal Watch has no matching executable interaction offer; refresh status");
|
|
2486
|
+
}
|
|
2487
|
+
return interaction;
|
|
2488
|
+
}
|
|
1453
2489
|
function positiveInteger(value, label) {
|
|
1454
2490
|
const result = Number(value);
|
|
1455
2491
|
if (!Number.isSafeInteger(result) || result <= 0) {
|