@scotthuang/agent-knock-knock 0.12.12 → 0.12.13

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 (76) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +39 -26
  3. package/dist/src/approval-policy.d.ts +11 -2
  4. package/dist/src/approval-policy.js +33 -7
  5. package/dist/src/approval-policy.js.map +1 -1
  6. package/dist/src/claude-local-transcript-provider.d.ts +81 -0
  7. package/dist/src/claude-local-transcript-provider.js +854 -18
  8. package/dist/src/claude-local-transcript-provider.js.map +1 -1
  9. package/dist/src/cli-core.js +85 -0
  10. package/dist/src/cli-core.js.map +1 -1
  11. package/dist/src/deferred-foreground-authority-cli-adapter.js +90 -19
  12. package/dist/src/deferred-foreground-authority-cli-adapter.js.map +1 -1
  13. package/dist/src/deferred-foreground-transfer.js +3 -4
  14. package/dist/src/deferred-foreground-transfer.js.map +1 -1
  15. package/dist/src/openclaw-callback-transport.d.ts +5 -0
  16. package/dist/src/openclaw-callback-transport.js +1 -1
  17. package/dist/src/openclaw-callback-transport.js.map +1 -1
  18. package/dist/src/openclaw-plugin-callback-adapter.js +96 -14
  19. package/dist/src/openclaw-plugin-callback-adapter.js.map +1 -1
  20. package/dist/src/openclaw-plugin-command-adapter.js +548 -107
  21. package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
  22. package/dist/src/openclaw-plugin-helpers.d.ts +27 -4
  23. package/dist/src/openclaw-plugin-helpers.js +309 -44
  24. package/dist/src/openclaw-plugin-helpers.js.map +1 -1
  25. package/dist/src/openclaw-plugin-schemas.d.ts +44 -68
  26. package/dist/src/openclaw-plugin-schemas.js +63 -91
  27. package/dist/src/openclaw-plugin-schemas.js.map +1 -1
  28. package/dist/src/openclaw-plugin-supervisor.js +48 -10
  29. package/dist/src/openclaw-plugin-supervisor.js.map +1 -1
  30. package/dist/src/openclaw-private-authority-offers.d.ts +22 -0
  31. package/dist/src/openclaw-private-authority-offers.js +177 -0
  32. package/dist/src/openclaw-private-authority-offers.js.map +1 -0
  33. package/dist/src/store.d.ts +1 -0
  34. package/dist/src/store.js +3 -1
  35. package/dist/src/store.js.map +1 -1
  36. package/dist/src/terminal-action-projection.d.ts +19 -1
  37. package/dist/src/terminal-action-projection.js +23 -0
  38. package/dist/src/terminal-action-projection.js.map +1 -1
  39. package/dist/src/terminal-command-cli-adapter.d.ts +5 -0
  40. package/dist/src/terminal-command-cli-adapter.js +249 -89
  41. package/dist/src/terminal-command-cli-adapter.js.map +1 -1
  42. package/dist/src/terminal-dispatch-repository-cli-adapter.js +305 -79
  43. package/dist/src/terminal-dispatch-repository-cli-adapter.js.map +1 -1
  44. package/dist/src/terminal-list-cli-adapter.d.ts +31 -7
  45. package/dist/src/terminal-list-cli-adapter.js +285 -67
  46. package/dist/src/terminal-list-cli-adapter.js.map +1 -1
  47. package/dist/src/terminal-list-renderer.d.ts +2 -0
  48. package/dist/src/terminal-list-renderer.js +116 -41
  49. package/dist/src/terminal-list-renderer.js.map +1 -1
  50. package/dist/src/terminal-monitor-cli-adapter.js +1 -1
  51. package/dist/src/terminal-monitor-cli-adapter.js.map +1 -1
  52. package/dist/src/terminal-monitor-state-cli-adapter.js +6 -15
  53. package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -1
  54. package/dist/src/terminal-status-cli-adapter.d.ts +10 -0
  55. package/dist/src/terminal-status-cli-adapter.js +89 -7
  56. package/dist/src/terminal-status-cli-adapter.js.map +1 -1
  57. package/dist/src/terminal-submission-acceptance.d.ts +60 -1
  58. package/dist/src/terminal-submission-acceptance.js +560 -0
  59. package/dist/src/terminal-submission-acceptance.js.map +1 -1
  60. package/dist/src/terminal-watch-callback-cli-adapter.d.ts +20 -0
  61. package/dist/src/terminal-watch-callback-cli-adapter.js +55 -0
  62. package/dist/src/terminal-watch-callback-cli-adapter.js.map +1 -0
  63. package/dist/src/terminal-watch-cli-adapter.d.ts +64 -0
  64. package/dist/src/terminal-watch-cli-adapter.js +508 -0
  65. package/dist/src/terminal-watch-cli-adapter.js.map +1 -0
  66. package/dist/src/terminal-watch-service.d.ts +97 -0
  67. package/dist/src/terminal-watch-service.js +714 -0
  68. package/dist/src/terminal-watch-service.js.map +1 -0
  69. package/dist/src/terminal-watch-store.d.ts +149 -0
  70. package/dist/src/terminal-watch-store.js +861 -0
  71. package/dist/src/terminal-watch-store.js.map +1 -0
  72. package/docs/quickstart-herdr.md +8 -0
  73. package/docs/quickstart-tmux.md +33 -6
  74. package/openclaw.plugin.json +11 -3
  75. package/package.json +1 -1
  76. package/templates/openclaw-skills/agent-knock-knock/SKILL.md +57 -39
@@ -1630,6 +1630,11 @@ async function runApprove(options) {
1630
1630
  }
1631
1631
  const loaded = loadConversationFromOptions(options);
1632
1632
  const { statePath, logPath } = loaded;
1633
+ assertAutoApprovalCallbackRoute({
1634
+ options,
1635
+ conversation: loaded.conversation,
1636
+ statePath
1637
+ });
1633
1638
  const conversation = await migrateLegacyTerminalAgentIdentity({
1634
1639
  ...loaded,
1635
1640
  options
@@ -1641,7 +1646,10 @@ async function runApprove(options) {
1641
1646
  if (!terminalControl) {
1642
1647
  throw new Error(`conversation ${conversation.conversation_id} is not controlled through a terminal`);
1643
1648
  }
1644
- if (!["waiting_for_agent", "waiting_for_openclaw"].includes(conversation.status)) {
1649
+ const autoApproved = options.autoApproved === true;
1650
+ const callbackAuthority = autoApprovalCallbackAuthorityFromOptions(options);
1651
+ if (!["waiting_for_agent", "waiting_for_openclaw"].includes(conversation.status) &&
1652
+ !(autoApproved && callbackAuthority)) {
1645
1653
  throw new Error(`cannot approve ${conversation.conversation_id}; conversation is ${conversation.status}`);
1646
1654
  }
1647
1655
  const executor = executorForConversation(conversation);
@@ -1651,7 +1659,6 @@ async function runApprove(options) {
1651
1659
  const suppliedExpectedFingerprint = stringValue(options.expectedApprovalFingerprint);
1652
1660
  const expectedFingerprint = suppliedExpectedFingerprint ??
1653
1661
  stringValue(monitoredApproval?.fingerprint);
1654
- const autoApproved = options.autoApproved === true;
1655
1662
  const claudeScreenApproval = executor.kind === "claude";
1656
1663
  if (claudeScreenApproval) {
1657
1664
  const monitoredState = isRecord(monitoredApproval?.approval_state)
@@ -1665,6 +1672,7 @@ async function runApprove(options) {
1665
1672
  const currentMessageId = stringValue(nativeTakeover?.terminal_bridge_message_id);
1666
1673
  const approvalResolvedAt = validTimestampMs(nativeTakeover?.terminal_bridge_approval_resolved_at);
1667
1674
  if (autoApproved &&
1675
+ callbackAuthority === undefined &&
1668
1676
  monitoredApproval === undefined &&
1669
1677
  pendingDispatch === undefined &&
1670
1678
  conversation.status === "waiting_for_agent" &&
@@ -1693,63 +1701,68 @@ async function runApprove(options) {
1693
1701
  });
1694
1702
  return;
1695
1703
  }
1696
- const notifiedAt = validTimestampMs(monitoredApproval?.notified_at);
1697
- if (conversation.status !== "waiting_for_openclaw" ||
1698
- monitoredState?.decision_mode !== "keys" ||
1699
- !stringValue(monitoredApproval?.fingerprint)) {
1700
- printJson({
1701
- conversation,
1702
- approved: false,
1703
- blocked: true,
1704
- reason: "Claude screen approval requires a current managed-turn approval notification",
1705
- terminal_control: terminalControl
1706
- });
1707
- return;
1708
- }
1709
- if (notifiedAt === undefined ||
1710
- cliNowMs() - notifiedAt > CLAUDE_SCREEN_APPROVAL_TTL_MS) {
1711
- printJson({
1712
- conversation,
1713
- approved: false,
1714
- blocked: true,
1715
- reason: "Claude screen approval expired; inspect and resolve the terminal manually",
1716
- terminal_control: terminalControl
1717
- });
1718
- return;
1719
- }
1720
- if (!suppliedExpectedFingerprint ||
1721
- expectedFingerprint !== monitoredApproval?.fingerprint) {
1722
- printJson({
1723
- conversation,
1724
- approved: false,
1725
- blocked: true,
1726
- reason: "Claude screen approval requires the latest notified fingerprint",
1727
- terminal_control: terminalControl
1728
- });
1729
- return;
1730
- }
1731
- if (pendingDispatch?.state === "reserved" &&
1732
- pendingDispatch.terminal_bridge_message_id ===
1733
- nativeTakeover?.terminal_bridge_message_id) {
1734
- printJson({
1735
- conversation,
1736
- approved: false,
1737
- blocked: true,
1738
- reason: "a previous Claude approval dispatch has an uncertain outcome; inspect and resolve the terminal manually",
1739
- terminal_control: terminalControl
1740
- });
1741
- return;
1742
- }
1743
- if (expectedFingerprint ===
1744
- stringValue(nativeTakeover?.terminal_bridge_last_approval_fingerprint)) {
1745
- printJson({
1746
- conversation,
1747
- approved: false,
1748
- blocked: true,
1749
- reason: "Claude screen approval fingerprint was already consumed",
1750
- terminal_control: terminalControl
1751
- });
1752
- return;
1704
+ // A provenance-bound callback whose approval is already absent must reach
1705
+ // the terminal+state lock. Only the persisted callback message and the
1706
+ // locked consumed-approval receipt may classify it as an idempotent replay.
1707
+ if (callbackAuthority === undefined || monitoredApproval !== undefined) {
1708
+ const notifiedAt = validTimestampMs(monitoredApproval?.notified_at);
1709
+ if (conversation.status !== "waiting_for_openclaw" ||
1710
+ monitoredState?.decision_mode !== "keys" ||
1711
+ !stringValue(monitoredApproval?.fingerprint)) {
1712
+ printJson({
1713
+ conversation,
1714
+ approved: false,
1715
+ blocked: true,
1716
+ reason: "Claude screen approval requires a current managed-turn approval notification",
1717
+ terminal_control: terminalControl
1718
+ });
1719
+ return;
1720
+ }
1721
+ if (notifiedAt === undefined ||
1722
+ cliNowMs() - notifiedAt > CLAUDE_SCREEN_APPROVAL_TTL_MS) {
1723
+ printJson({
1724
+ conversation,
1725
+ approved: false,
1726
+ blocked: true,
1727
+ reason: "Claude screen approval expired; inspect and resolve the terminal manually",
1728
+ terminal_control: terminalControl
1729
+ });
1730
+ return;
1731
+ }
1732
+ if (!suppliedExpectedFingerprint ||
1733
+ expectedFingerprint !== monitoredApproval?.fingerprint) {
1734
+ printJson({
1735
+ conversation,
1736
+ approved: false,
1737
+ blocked: true,
1738
+ reason: "Claude screen approval requires the latest notified fingerprint",
1739
+ terminal_control: terminalControl
1740
+ });
1741
+ return;
1742
+ }
1743
+ if (pendingDispatch?.state === "reserved" &&
1744
+ pendingDispatch.terminal_bridge_message_id ===
1745
+ nativeTakeover?.terminal_bridge_message_id) {
1746
+ printJson({
1747
+ conversation,
1748
+ approved: false,
1749
+ blocked: true,
1750
+ reason: "a previous Claude approval dispatch has an uncertain outcome; inspect and resolve the terminal manually",
1751
+ terminal_control: terminalControl
1752
+ });
1753
+ return;
1754
+ }
1755
+ if (expectedFingerprint ===
1756
+ stringValue(nativeTakeover?.terminal_bridge_last_approval_fingerprint)) {
1757
+ printJson({
1758
+ conversation,
1759
+ approved: false,
1760
+ blocked: true,
1761
+ reason: "Claude screen approval fingerprint was already consumed",
1762
+ terminal_control: terminalControl
1763
+ });
1764
+ return;
1765
+ }
1753
1766
  }
1754
1767
  }
1755
1768
  return runManagedApprovalDispatch({
@@ -1759,6 +1772,28 @@ async function runApprove(options) {
1759
1772
  autoApproved, claudeScreenApproval
1760
1773
  });
1761
1774
  }
1775
+ function approvalPolicyCandidateForInspection({ agent, currentTerminalControl, inspection, fingerprint }) {
1776
+ const evidence = inspection.approval.approvable
1777
+ ? inspection.approval.policyEvidence
1778
+ : undefined;
1779
+ return {
1780
+ agent,
1781
+ kind: evidence?.kind ?? inspection.approval.promptKind ?? "unknown",
1782
+ decisionMode: inspection.approval.approvable
1783
+ ? inspection.approval.action.mode ?? "keys"
1784
+ : undefined,
1785
+ command: evidence?.command ?? inspection.approval.command,
1786
+ cwd: evidence?.cwd ?? inspection.approval.cwd ?? currentTerminalControl.currentPath,
1787
+ fingerprint: fingerprint ?? "",
1788
+ terminalTarget: currentTerminalControl.target,
1789
+ ...(evidence?.source === "claude_transcript"
1790
+ ? {
1791
+ evidenceSource: "claude_transcript",
1792
+ evidenceFingerprint: evidence.evidenceFingerprint
1793
+ }
1794
+ : {})
1795
+ };
1796
+ }
1762
1797
  async function runManagedApprovalDispatch({ options, conversation, statePath, logPath, nativeTakeover, terminalControl, executor, monitoredApproval, expectedFingerprint, autoApproved, claudeScreenApproval }) {
1763
1798
  const policyRuleId = stringValue(options.policyRuleId);
1764
1799
  const policyFingerprint = stringValue(options.policyFingerprint);
@@ -1766,28 +1801,6 @@ async function runManagedApprovalDispatch({ options, conversation, statePath, lo
1766
1801
  ? parseJsonOption(options.autoApprovalPolicyJson, "--auto-approval-policy-json")
1767
1802
  : undefined;
1768
1803
  let executorPolicyDecision;
1769
- const policyCandidateForInspection = ({ agent, currentTerminalControl, inspection, fingerprint }) => {
1770
- const evidence = inspection.approval.approvable
1771
- ? inspection.approval.policyEvidence
1772
- : undefined;
1773
- return {
1774
- agent,
1775
- kind: evidence?.kind ?? inspection.approval.promptKind ?? "unknown",
1776
- decisionMode: inspection.approval.approvable
1777
- ? inspection.approval.action.mode ?? "keys"
1778
- : undefined,
1779
- command: evidence?.command ?? inspection.approval.command,
1780
- cwd: evidence?.cwd ?? inspection.approval.cwd ?? currentTerminalControl.currentPath,
1781
- fingerprint: fingerprint ?? "",
1782
- terminalTarget: currentTerminalControl.target,
1783
- ...(evidence?.source === "claude_transcript"
1784
- ? {
1785
- evidenceSource: "claude_transcript",
1786
- evidenceFingerprint: evidence.evidenceFingerprint
1787
- }
1788
- : {})
1789
- };
1790
- };
1791
1804
  const releaseTerminalLock = acquireTerminalBridgeSendLock(storeDirFromOptions(options), terminalControl, { timeoutMs: 30000 });
1792
1805
  let terminalLockReleased = false;
1793
1806
  const releaseApprovalTerminalLock = () => {
@@ -1817,21 +1830,43 @@ async function runManagedApprovalDispatch({ options, conversation, statePath, lo
1817
1830
  const currentApproval = isRecord(currentTakeover?.terminal_bridge_approval)
1818
1831
  ? currentTakeover.terminal_bridge_approval
1819
1832
  : undefined;
1820
- if (currentConversation.status !== conversation.status ||
1821
- currentTakeover?.terminal_bridge_message_id !== nativeTakeover?.terminal_bridge_message_id ||
1822
- !currentControl ||
1833
+ const callbackAuthorityState = assertAutoApprovalCallbackAuthority({
1834
+ options,
1835
+ conversation: currentConversation,
1836
+ statePath,
1837
+ takeover: currentTakeover,
1838
+ approval: currentApproval,
1839
+ expectedFingerprint
1840
+ });
1841
+ if (!currentControl ||
1823
1842
  !terminalControlsShareIncarnation(currentControl, terminalControl) ||
1824
- (claudeScreenApproval &&
1825
- currentApproval?.fingerprint !== monitoredApproval?.fingerprint)) {
1843
+ (callbackAuthorityState !== "already_approved" &&
1844
+ (currentConversation.status !== conversation.status ||
1845
+ currentTakeover?.terminal_bridge_message_id !==
1846
+ nativeTakeover?.terminal_bridge_message_id ||
1847
+ (claudeScreenApproval &&
1848
+ currentApproval?.fingerprint !== monitoredApproval?.fingerprint)))) {
1826
1849
  throw new Error("approval state changed while waiting for terminal control; refresh status and retry");
1827
1850
  }
1851
+ lockedConversation = currentConversation;
1852
+ if (callbackAuthorityState === "already_approved") {
1853
+ releaseApprovalStateLock();
1854
+ printJson({
1855
+ conversation: lockedConversation,
1856
+ approved: false,
1857
+ already_approved: true,
1858
+ blocked: false,
1859
+ reason: "automatic approval callback was already handled",
1860
+ terminal_control: currentControl
1861
+ });
1862
+ return;
1863
+ }
1828
1864
  assertManagedTerminalDispatchOwner({
1829
1865
  storeDir: writerStoreDir,
1830
1866
  conversation: currentConversation,
1831
1867
  terminalControl: currentControl,
1832
1868
  action: "approve"
1833
1869
  });
1834
- lockedConversation = currentConversation;
1835
1870
  const currentRuntimeIdentity = terminalRuntimeIdentityForConversation(currentConversation, currentControl);
1836
1871
  approval = await createTerminalAgentBridge(options).approve(executor.kind, currentControl, {
1837
1872
  expectedFingerprint,
@@ -1847,7 +1882,7 @@ async function runManagedApprovalDispatch({ options, conversation, statePath, lo
1847
1882
  reason: "automatic approval requires an executor-side policy"
1848
1883
  };
1849
1884
  }
1850
- const candidate = policyCandidateForInspection({
1885
+ const candidate = approvalPolicyCandidateForInspection({
1851
1886
  agent,
1852
1887
  currentTerminalControl,
1853
1888
  inspection,
@@ -1887,7 +1922,7 @@ async function runManagedApprovalDispatch({ options, conversation, statePath, lo
1887
1922
  }
1888
1923
  const freshPolicyDecision = evaluateApprovalPolicy({
1889
1924
  policy: autoApprovalPolicy,
1890
- candidate: policyCandidateForInspection({
1925
+ candidate: approvalPolicyCandidateForInspection({
1891
1926
  agent: executor.kind,
1892
1927
  currentTerminalControl: dispatchControl,
1893
1928
  inspection,
@@ -2147,6 +2182,131 @@ async function runManagedApprovalDispatch({ options, conversation, statePath, lo
2147
2182
  }
2148
2183
  }
2149
2184
  }
2185
+ function assertAutoApprovalCallbackAuthority(input) {
2186
+ if (input.options.autoApproved !== true) {
2187
+ return "not_callback";
2188
+ }
2189
+ const expected = autoApprovalCallbackAuthorityFromOptions(input.options);
2190
+ if (!expected) {
2191
+ return "not_callback";
2192
+ }
2193
+ assertAutoApprovalCallbackRoute({
2194
+ options: input.options,
2195
+ conversation: input.conversation,
2196
+ statePath: input.statePath
2197
+ });
2198
+ const delivery = isRecord(input.conversation.callback_delivery)
2199
+ ? input.conversation.callback_delivery
2200
+ : undefined;
2201
+ const callbackMessage = isRecord(delivery?.message)
2202
+ ? delivery.message
2203
+ : undefined;
2204
+ const callbackMetadata = isRecord(callbackMessage?.metadata)
2205
+ ? callbackMessage.metadata
2206
+ : undefined;
2207
+ const callbackCandidate = isRecord(callbackMetadata?.approval_candidate)
2208
+ ? callbackMetadata.approval_candidate
2209
+ : undefined;
2210
+ const callbackTerminalStatus = isRecord(callbackMetadata?.terminal_status)
2211
+ ? callbackMetadata.terminal_status
2212
+ : undefined;
2213
+ const callbackApprovalState = isRecord(callbackTerminalStatus?.approval_state)
2214
+ ? callbackTerminalStatus.approval_state
2215
+ : undefined;
2216
+ const persistedApprovalState = isRecord(input.approval?.approval_state)
2217
+ ? input.approval.approval_state
2218
+ : undefined;
2219
+ const callbackFingerprints = [
2220
+ stringValue(callbackMetadata?.approval_fingerprint),
2221
+ stringValue(callbackCandidate?.fingerprint),
2222
+ stringValue(callbackApprovalState?.fingerprint)
2223
+ ];
2224
+ const commonAuthorityMismatch = delivery?.kind !== "approval_notification" ||
2225
+ stringValue(callbackMessage?.id) !== expected.messageId ||
2226
+ stringValue(callbackMessage?.conversation_id) !== expected.conversationId ||
2227
+ stringValue(callbackMessage?.session_id) !== expected.sessionId ||
2228
+ stringValue(callbackMessage?.turn_id) !== expected.turnId ||
2229
+ !input.expectedFingerprint ||
2230
+ !/^[a-f0-9]{64}$/u.test(input.expectedFingerprint) ||
2231
+ callbackFingerprints.some((value) => value !== input.expectedFingerprint) ||
2232
+ input.takeover?.terminal_bridge !== true;
2233
+ if (commonAuthorityMismatch) {
2234
+ throw new Error("automatic approval callback no longer matches the locked Turn state; refresh status and retry");
2235
+ }
2236
+ if (input.approval) {
2237
+ const currentFingerprints = [
2238
+ stringValue(input.approval.fingerprint),
2239
+ stringValue(persistedApprovalState?.fingerprint)
2240
+ ];
2241
+ if (input.conversation.status !== "waiting_for_openclaw" ||
2242
+ currentFingerprints.some((value) => value !== input.expectedFingerprint) ||
2243
+ stringValue(input.approval.callback_message_id) !== expected.messageId) {
2244
+ throw new Error("automatic approval callback no longer matches the locked Turn state; refresh status and retry");
2245
+ }
2246
+ return "current";
2247
+ }
2248
+ if (input.takeover?.terminal_bridge_approval !== undefined ||
2249
+ input.takeover?.terminal_bridge_approval_dispatch !== undefined ||
2250
+ !isPostApprovalCallbackReplayStatus(input.conversation.status) ||
2251
+ stringValue(input.takeover?.terminal_bridge_message_id) !==
2252
+ expected.messageId ||
2253
+ stringValue(input.takeover?.terminal_bridge_last_approval_message_id) !==
2254
+ expected.messageId ||
2255
+ stringValue(input.takeover?.terminal_bridge_last_approval_fingerprint) !==
2256
+ input.expectedFingerprint ||
2257
+ validTimestampMs(input.takeover?.terminal_bridge_approval_resolved_at) === undefined) {
2258
+ throw new Error("automatic approval callback no longer matches the locked Turn receipt; refresh status and retry");
2259
+ }
2260
+ return "already_approved";
2261
+ }
2262
+ function isPostApprovalCallbackReplayStatus(status) {
2263
+ return [
2264
+ "waiting_for_agent",
2265
+ "running",
2266
+ "idle",
2267
+ "stalled",
2268
+ "callback_pending",
2269
+ "callback_failed",
2270
+ "failed",
2271
+ "closed",
2272
+ "cancelled",
2273
+ "cancelling"
2274
+ ].includes(status);
2275
+ }
2276
+ function autoApprovalCallbackAuthorityFromOptions(options) {
2277
+ const candidate = {
2278
+ conversationId: stringValue(options.expectedCallbackConversationId),
2279
+ sessionId: stringValue(options.expectedCallbackSessionId),
2280
+ turnId: stringValue(options.expectedCallbackTurnId),
2281
+ messageId: stringValue(options.expectedCallbackMessageId),
2282
+ openclawSession: stringValue(options.expectedCallbackOpenclawSession)
2283
+ };
2284
+ const values = Object.values(candidate);
2285
+ if (values.every((value) => value === undefined)) {
2286
+ return undefined;
2287
+ }
2288
+ if (values.some((value) => value === undefined)) {
2289
+ throw new Error("automatic approval callback identity is incomplete; no approval key was sent");
2290
+ }
2291
+ return candidate;
2292
+ }
2293
+ function assertAutoApprovalCallbackRoute(input) {
2294
+ if (input.options.autoApproved !== true) {
2295
+ return;
2296
+ }
2297
+ const expected = autoApprovalCallbackAuthorityFromOptions(input.options);
2298
+ if (!expected) {
2299
+ return;
2300
+ }
2301
+ const storedOpenClawSession = stringValue(input.conversation.gateway_session ?? input.conversation.openclaw_session);
2302
+ if (input.conversation.conversation_id !== expected.conversationId ||
2303
+ sessionIdForConversation(input.conversation) !== expected.sessionId ||
2304
+ turnIdForConversation(input.conversation) !== expected.turnId ||
2305
+ !sameCanonicalStatePath(input.conversation.state_path, input.statePath) ||
2306
+ storedOpenClawSession !== expected.openclawSession) {
2307
+ throw new Error("automatic approval callback does not match the selected Turn state; no state was changed");
2308
+ }
2309
+ }
2150
2310
  async function runTerminalConversationApprove({ options, terminal }) {
2151
2311
  const { conversationId, agent, terminalControl, pid } = terminal;
2152
2312
  const storeDir = storeDirFromOptions(options);