@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.
Files changed (143) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +3 -0
  3. package/dist/src/agent-session-provider.d.ts +5 -0
  4. package/dist/src/agent-session-provider.js +10 -1
  5. package/dist/src/agent-session-provider.js.map +1 -1
  6. package/dist/src/callback-outbox-service.d.ts +2 -0
  7. package/dist/src/callback-outbox-service.js +1 -1
  8. package/dist/src/callback-outbox-service.js.map +1 -1
  9. package/dist/src/callback-transport.d.ts +7 -0
  10. package/dist/src/callback-transport.js +1 -1
  11. package/dist/src/callback-transport.js.map +1 -1
  12. package/dist/src/cli-core.js +35 -32
  13. package/dist/src/cli-core.js.map +1 -1
  14. package/dist/src/codex-store-adapter.d.ts +1 -1
  15. package/dist/src/codex-store-adapter.js +35 -8
  16. package/dist/src/codex-store-adapter.js.map +1 -1
  17. package/dist/src/deferred-foreground-authority-cli-adapter.d.ts +20 -0
  18. package/dist/src/deferred-foreground-authority-cli-adapter.js +216 -8
  19. package/dist/src/deferred-foreground-authority-cli-adapter.js.map +1 -1
  20. package/dist/src/host-adapter.d.ts +1 -1
  21. package/dist/src/host-adapter.js +1 -1
  22. package/dist/src/host-bridge-tools.js +2 -2
  23. package/dist/src/managed-turn-recovery-service.d.ts +8 -1
  24. package/dist/src/managed-turn-recovery-service.js +55 -13
  25. package/dist/src/managed-turn-recovery-service.js.map +1 -1
  26. package/dist/src/mutation-transaction.d.ts +1 -0
  27. package/dist/src/mutation-transaction.js +19 -15
  28. package/dist/src/mutation-transaction.js.map +1 -1
  29. package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +39 -3
  30. package/dist/src/native-thread-lifecycle-cli-adapter.js +307 -7
  31. package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
  32. package/dist/src/openclaw-plugin-callback-adapter.js +17 -7
  33. package/dist/src/openclaw-plugin-callback-adapter.js.map +1 -1
  34. package/dist/src/openclaw-plugin-command-adapter.js +297 -83
  35. package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
  36. package/dist/src/openclaw-plugin-schemas.d.ts +57 -0
  37. package/dist/src/openclaw-plugin-schemas.js +59 -1
  38. package/dist/src/openclaw-plugin-schemas.js.map +1 -1
  39. package/dist/src/openclaw-private-authority-offers.d.ts +17 -0
  40. package/dist/src/openclaw-private-authority-offers.js +75 -3
  41. package/dist/src/openclaw-private-authority-offers.js.map +1 -1
  42. package/dist/src/store.d.ts +1 -1
  43. package/dist/src/store.js +6 -4
  44. package/dist/src/store.js.map +1 -1
  45. package/dist/src/terminal-acceptance-application-service.js +2 -2
  46. package/dist/src/terminal-acceptance-application-service.js.map +1 -1
  47. package/dist/src/terminal-acceptance-cli-adapter.d.ts +11 -1
  48. package/dist/src/terminal-acceptance-cli-adapter.js +290 -16
  49. package/dist/src/terminal-acceptance-cli-adapter.js.map +1 -1
  50. package/dist/src/terminal-action-projection.d.ts +1 -1
  51. package/dist/src/terminal-action-projection.js +6 -0
  52. package/dist/src/terminal-action-projection.js.map +1 -1
  53. package/dist/src/terminal-agent-adapter.d.ts +9 -0
  54. package/dist/src/terminal-agent-adapter.js.map +1 -1
  55. package/dist/src/terminal-agent-bridge.d.ts +52 -8
  56. package/dist/src/terminal-agent-bridge.js +111 -94
  57. package/dist/src/terminal-agent-bridge.js.map +1 -1
  58. package/dist/src/terminal-authority-policy.js +3 -0
  59. package/dist/src/terminal-authority-policy.js.map +1 -1
  60. package/dist/src/terminal-binding-authority.d.ts +2 -0
  61. package/dist/src/terminal-binding-authority.js +8 -0
  62. package/dist/src/terminal-binding-authority.js.map +1 -1
  63. package/dist/src/terminal-command-cli-adapter.d.ts +17 -1
  64. package/dist/src/terminal-command-cli-adapter.js +784 -286
  65. package/dist/src/terminal-command-cli-adapter.js.map +1 -1
  66. package/dist/src/terminal-delegate-cli-adapter.js +21 -7
  67. package/dist/src/terminal-delegate-cli-adapter.js.map +1 -1
  68. package/dist/src/terminal-dispatch-application.d.ts +3 -1
  69. package/dist/src/terminal-dispatch-application.js +13 -5
  70. package/dist/src/terminal-dispatch-application.js.map +1 -1
  71. package/dist/src/terminal-dispatch-composition.d.ts +43 -0
  72. package/dist/src/terminal-dispatch-execution.d.ts +2 -2
  73. package/dist/src/terminal-dispatch-execution.js +53 -3
  74. package/dist/src/terminal-dispatch-execution.js.map +1 -1
  75. package/dist/src/terminal-dispatch-policy.d.ts +20 -0
  76. package/dist/src/terminal-dispatch-policy.js +33 -1
  77. package/dist/src/terminal-dispatch-policy.js.map +1 -1
  78. package/dist/src/terminal-dispatch-presenter.d.ts +45 -0
  79. package/dist/src/terminal-dispatch-presenter.js +220 -18
  80. package/dist/src/terminal-dispatch-presenter.js.map +1 -1
  81. package/dist/src/terminal-dispatch-receipt.d.ts +5 -1
  82. package/dist/src/terminal-dispatch-receipt.js +26 -3
  83. package/dist/src/terminal-dispatch-receipt.js.map +1 -1
  84. package/dist/src/terminal-identity-authority-cli-adapter.js +103 -4
  85. package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -1
  86. package/dist/src/terminal-interaction-authority.d.ts +48 -1
  87. package/dist/src/terminal-interaction-authority.js +30 -0
  88. package/dist/src/terminal-interaction-authority.js.map +1 -1
  89. package/dist/src/terminal-interaction-cli-adapter.js +9 -0
  90. package/dist/src/terminal-interaction-cli-adapter.js.map +1 -1
  91. package/dist/src/terminal-interaction-core.d.ts +104 -0
  92. package/dist/src/terminal-interaction-core.js +407 -0
  93. package/dist/src/terminal-interaction-core.js.map +1 -0
  94. package/dist/src/terminal-interaction-protocol.d.ts +89 -0
  95. package/dist/src/terminal-interaction-protocol.js +199 -0
  96. package/dist/src/terminal-interaction-protocol.js.map +1 -1
  97. package/dist/src/terminal-list-cli-adapter.d.ts +5 -0
  98. package/dist/src/terminal-list-cli-adapter.js +190 -40
  99. package/dist/src/terminal-list-cli-adapter.js.map +1 -1
  100. package/dist/src/terminal-list-renderer.js +60 -7
  101. package/dist/src/terminal-list-renderer.js.map +1 -1
  102. package/dist/src/terminal-maintenance-cli-adapter.js +14 -3
  103. package/dist/src/terminal-maintenance-cli-adapter.js.map +1 -1
  104. package/dist/src/terminal-monitor-application-service.d.ts +1 -0
  105. package/dist/src/terminal-monitor-application-service.js +72 -10
  106. package/dist/src/terminal-monitor-application-service.js.map +1 -1
  107. package/dist/src/terminal-monitor-cli-adapter.d.ts +3 -0
  108. package/dist/src/terminal-monitor-cli-adapter.js +24 -8
  109. package/dist/src/terminal-monitor-cli-adapter.js.map +1 -1
  110. package/dist/src/terminal-monitor-state-cli-adapter.js +15 -2
  111. package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -1
  112. package/dist/src/terminal-mutation-cli-runtime.d.ts +11 -1
  113. package/dist/src/terminal-mutation-cli-runtime.js +9 -4
  114. package/dist/src/terminal-mutation-cli-runtime.js.map +1 -1
  115. package/dist/src/terminal-status-cli-adapter.d.ts +1 -7
  116. package/dist/src/terminal-status-cli-adapter.js +29 -3
  117. package/dist/src/terminal-status-cli-adapter.js.map +1 -1
  118. package/dist/src/terminal-status-facts.d.ts +17 -1
  119. package/dist/src/terminal-status-facts.js +59 -0
  120. package/dist/src/terminal-status-facts.js.map +1 -1
  121. package/dist/src/terminal-submission-acceptance.d.ts +37 -2
  122. package/dist/src/terminal-submission-acceptance.js +633 -30
  123. package/dist/src/terminal-submission-acceptance.js.map +1 -1
  124. package/dist/src/terminal-submission-facts.d.ts +5 -0
  125. package/dist/src/terminal-submission-facts.js.map +1 -1
  126. package/dist/src/terminal-watch-callback-cli-adapter.d.ts +2 -1
  127. package/dist/src/terminal-watch-callback-cli-adapter.js +2 -1
  128. package/dist/src/terminal-watch-callback-cli-adapter.js.map +1 -1
  129. package/dist/src/terminal-watch-cli-adapter.d.ts +6 -2
  130. package/dist/src/terminal-watch-cli-adapter.js +1051 -15
  131. package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
  132. package/dist/src/terminal-watch-service.d.ts +15 -1
  133. package/dist/src/terminal-watch-service.js +169 -13
  134. package/dist/src/terminal-watch-service.js.map +1 -1
  135. package/dist/src/terminal-watch-store.d.ts +72 -3
  136. package/dist/src/terminal-watch-store.js +407 -27
  137. package/dist/src/terminal-watch-store.js.map +1 -1
  138. package/docs/host-bridge-profiles.md +2 -2
  139. package/docs/quickstart-herdr.md +5 -3
  140. package/docs/quickstart-tmux.md +7 -5
  141. package/openclaw.plugin.json +8 -0
  142. package/package.json +1 -1
  143. 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 { captureCodexCandidateSetRolloutAcceptanceAnchor, captureCodexRolloutAcceptanceAnchor, detectCodexBoundRolloutCompletion, detectCodexCandidateSetRolloutAcceptance, detectCodexRolloutAcceptance, captureCodexHumanStartedActiveTaskAnchor, observeCodexHumanStartedActiveTask } from "./terminal-submission-acceptance.js";
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: options.callbackRouteControllerScope === "route_bound_v1"
31
- ? routeBoundWatchCallbackRoute(explicitRoute, watch)
32
- : explicitRoute
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
- function runWatchStatus(options) {
347
- const watch = serviceFor(options).get(requiredWatchId(options.watch));
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) {