@scotthuang/agent-knock-knock 0.13.2 → 0.13.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +27 -6
- package/dist/src/callback-outbox-policy.js +27 -6
- package/dist/src/callback-outbox-policy.js.map +1 -1
- package/dist/src/callback-outbox-service.d.ts +4 -30
- package/dist/src/callback-outbox-service.js +21 -154
- package/dist/src/callback-outbox-service.js.map +1 -1
- package/dist/src/callback-outbox-settlement.js +13 -8
- package/dist/src/callback-outbox-settlement.js.map +1 -1
- package/dist/src/callback-route-service.d.ts +46 -0
- package/dist/src/callback-route-service.js +155 -0
- package/dist/src/callback-route-service.js.map +1 -0
- package/dist/src/claude-terminal-agent-adapter.js +4 -0
- package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
- package/dist/src/cli-core.js +9 -1
- package/dist/src/cli-core.js.map +1 -1
- package/dist/src/codex-process-incarnation.d.ts +8 -0
- package/dist/src/codex-process-incarnation.js +36 -0
- package/dist/src/codex-process-incarnation.js.map +1 -0
- package/dist/src/codex-terminal-agent-adapter.js +4 -0
- package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
- package/dist/src/deferred-foreground-conflict-policy.d.ts +22 -0
- package/dist/src/deferred-foreground-conflict-policy.js +52 -0
- package/dist/src/deferred-foreground-conflict-policy.js.map +1 -0
- package/dist/src/durable-notification-kernel.d.ts +94 -0
- package/dist/src/durable-notification-kernel.js +68 -0
- package/dist/src/durable-notification-kernel.js.map +1 -0
- package/dist/src/herdr-socket-transport.d.ts +43 -0
- package/dist/src/herdr-socket-transport.js +170 -0
- package/dist/src/herdr-socket-transport.js.map +1 -0
- package/dist/src/herdr-terminal-control-provider.d.ts +2 -45
- package/dist/src/herdr-terminal-control-provider.js +8 -173
- package/dist/src/herdr-terminal-control-provider.js.map +1 -1
- package/dist/src/host-adapter-capabilities.d.ts +35 -0
- package/dist/src/host-adapter-capabilities.js +146 -0
- package/dist/src/host-adapter-capabilities.js.map +1 -0
- package/dist/src/host-adapter.d.ts +9 -3
- package/dist/src/host-adapter.js +33 -9
- package/dist/src/host-adapter.js.map +1 -1
- package/dist/src/host-bridge-tools.d.ts +3 -4
- package/dist/src/host-bridge-tools.js +49 -92
- package/dist/src/host-bridge-tools.js.map +1 -1
- package/dist/src/host-bridge.js +5 -5
- package/dist/src/host-bridge.js.map +1 -1
- package/dist/src/host-monitor-reconciliation.d.ts +6 -0
- package/dist/src/host-monitor-reconciliation.js +96 -0
- package/dist/src/host-monitor-reconciliation.js.map +1 -0
- package/dist/src/managed-session.js +2 -18
- package/dist/src/managed-session.js.map +1 -1
- package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +13 -0
- package/dist/src/native-thread-lifecycle-cli-adapter.js +635 -3
- package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
- package/dist/src/openclaw-list-projection.d.ts +2 -0
- package/dist/src/openclaw-list-projection.js +3 -0
- package/dist/src/openclaw-list-projection.js.map +1 -0
- package/dist/src/openclaw-model-facing-field-policy.d.ts +2 -0
- package/dist/src/openclaw-model-facing-field-policy.js +3 -0
- package/dist/src/openclaw-model-facing-field-policy.js.map +1 -0
- package/dist/src/openclaw-plugin-command-adapter.d.ts +6 -29
- package/dist/src/openclaw-plugin-command-adapter.js +7 -2370
- package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-helpers.d.ts +2 -121
- package/dist/src/openclaw-plugin-helpers.js +2 -1164
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin-schemas.d.ts +2 -510
- package/dist/src/openclaw-plugin-schemas.js +2 -519
- package/dist/src/openclaw-plugin-schemas.js.map +1 -1
- package/dist/src/openclaw-plugin-supervisor.d.ts +4 -5
- package/dist/src/openclaw-plugin-supervisor.js +5 -94
- package/dist/src/openclaw-plugin-supervisor.js.map +1 -1
- package/dist/src/openclaw-plugin-value-helpers.d.ts +2 -0
- package/dist/src/openclaw-plugin-value-helpers.js +3 -0
- package/dist/src/openclaw-plugin-value-helpers.js.map +1 -0
- package/dist/src/openclaw-private-authority-offers.d.ts +4 -39
- package/dist/src/openclaw-private-authority-offers.js +3 -258
- package/dist/src/openclaw-private-authority-offers.js.map +1 -1
- package/dist/src/semantic-private-authority-offers.d.ts +41 -0
- package/dist/src/semantic-private-authority-offers.js +261 -0
- package/dist/src/semantic-private-authority-offers.js.map +1 -0
- package/dist/src/semantic-tool-arguments.d.ts +9 -0
- package/dist/src/semantic-tool-arguments.js +64 -0
- package/dist/src/semantic-tool-arguments.js.map +1 -0
- package/dist/src/semantic-tool-catalog.d.ts +48 -0
- package/dist/src/semantic-tool-catalog.js +105 -0
- package/dist/src/semantic-tool-catalog.js.map +1 -0
- package/dist/src/semantic-tool-command-helpers.d.ts +145 -0
- package/dist/src/semantic-tool-command-helpers.js +1272 -0
- package/dist/src/semantic-tool-command-helpers.js.map +1 -0
- package/dist/src/semantic-tool-list-projection.d.ts +10 -0
- package/dist/src/semantic-tool-list-projection.js +497 -0
- package/dist/src/semantic-tool-list-projection.js.map +1 -0
- package/dist/src/semantic-tool-model-facing-field-policy.d.ts +3 -0
- package/dist/src/semantic-tool-model-facing-field-policy.js +66 -0
- package/dist/src/semantic-tool-model-facing-field-policy.js.map +1 -0
- package/dist/src/semantic-tool-presentation.d.ts +37 -0
- package/dist/src/semantic-tool-presentation.js +680 -0
- package/dist/src/semantic-tool-presentation.js.map +1 -0
- package/dist/src/semantic-tool-private-authority.d.ts +39 -0
- package/dist/src/semantic-tool-private-authority.js +602 -0
- package/dist/src/semantic-tool-private-authority.js.map +1 -0
- package/dist/src/semantic-tool-relay.d.ts +16 -0
- package/dist/src/semantic-tool-relay.js +175 -0
- package/dist/src/semantic-tool-relay.js.map +1 -0
- package/dist/src/semantic-tool-runtime.d.ts +10 -0
- package/dist/src/semantic-tool-runtime.js +1157 -0
- package/dist/src/semantic-tool-runtime.js.map +1 -0
- package/dist/src/semantic-tool-schemas.d.ts +563 -0
- package/dist/src/semantic-tool-schemas.js +573 -0
- package/dist/src/semantic-tool-schemas.js.map +1 -0
- package/dist/src/semantic-tool-value-helpers.d.ts +5 -0
- package/dist/src/semantic-tool-value-helpers.js +27 -0
- package/dist/src/semantic-tool-value-helpers.js.map +1 -0
- package/dist/src/store-event-presentation.d.ts +17 -0
- package/dist/src/store-event-presentation.js +33 -0
- package/dist/src/store-event-presentation.js.map +1 -0
- package/dist/src/store.d.ts +3 -16
- package/dist/src/store.js +2 -30
- package/dist/src/store.js.map +1 -1
- package/dist/src/terminal-action-projection.d.ts +3 -17
- package/dist/src/terminal-action-projection.js +13 -41
- package/dist/src/terminal-action-projection.js.map +1 -1
- package/dist/src/terminal-agent-adapter.d.ts +7 -0
- package/dist/src/terminal-agent-adapter.js +8 -0
- package/dist/src/terminal-agent-adapter.js.map +1 -1
- package/dist/src/terminal-agent-bridge.d.ts +35 -332
- package/dist/src/terminal-agent-bridge.js +293 -2798
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/dist/src/terminal-approval-cli-adapter.d.ts +17 -0
- package/dist/src/terminal-approval-cli-adapter.js +921 -0
- package/dist/src/terminal-approval-cli-adapter.js.map +1 -0
- package/dist/src/terminal-command-cli-adapter.d.ts +2 -429
- package/dist/src/terminal-command-cli-adapter.js +344 -6061
- package/dist/src/terminal-command-cli-adapter.js.map +1 -1
- package/dist/src/terminal-command-cli-ports.d.ts +443 -0
- package/dist/src/terminal-command-cli-ports.js +2 -0
- package/dist/src/terminal-command-cli-ports.js.map +1 -0
- package/dist/src/terminal-command-dispatch-preparation.d.ts +81 -0
- package/dist/src/terminal-command-dispatch-preparation.js +209 -0
- package/dist/src/terminal-command-dispatch-preparation.js.map +1 -0
- package/dist/src/terminal-command-dispatch-transport.d.ts +35 -0
- package/dist/src/terminal-command-dispatch-transport.js +859 -0
- package/dist/src/terminal-command-dispatch-transport.js.map +1 -0
- package/dist/src/terminal-command-foreground-proof.d.ts +32 -0
- package/dist/src/terminal-command-foreground-proof.js +72 -0
- package/dist/src/terminal-command-foreground-proof.js.map +1 -0
- package/dist/src/terminal-command-send-preflight.d.ts +41 -0
- package/dist/src/terminal-command-send-preflight.js +86 -0
- package/dist/src/terminal-command-send-preflight.js.map +1 -0
- package/dist/src/terminal-composer-classifier.d.ts +55 -0
- package/dist/src/terminal-composer-classifier.js +638 -0
- package/dist/src/terminal-composer-classifier.js.map +1 -0
- package/dist/src/terminal-control-ref.d.ts +23 -0
- package/dist/src/terminal-control-ref.js +32 -0
- package/dist/src/terminal-control-ref.js.map +1 -1
- package/dist/src/terminal-dispatch-recovery-authority.d.ts +56 -0
- package/dist/src/terminal-dispatch-recovery-authority.js +251 -0
- package/dist/src/terminal-dispatch-recovery-authority.js.map +1 -0
- package/dist/src/terminal-dispatch-recovery-cli-adapter.d.ts +1 -9
- package/dist/src/terminal-dispatch-recovery-cli-adapter.js +5 -240
- package/dist/src/terminal-dispatch-recovery-cli-adapter.js.map +1 -1
- package/dist/src/terminal-handoff-cli-adapter.d.ts +1 -11
- package/dist/src/terminal-handoff-cli-adapter.js +2 -39
- package/dist/src/terminal-handoff-cli-adapter.js.map +1 -1
- package/dist/src/terminal-human-explicit-send-cli-adapter.d.ts +25 -0
- package/dist/src/terminal-human-explicit-send-cli-adapter.js +903 -0
- package/dist/src/terminal-human-explicit-send-cli-adapter.js.map +1 -0
- package/dist/src/terminal-identity-authority-cli-adapter.d.ts +1 -6
- package/dist/src/terminal-identity-authority-cli-adapter.js +3 -33
- package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -1
- package/dist/src/terminal-interaction-cli-adapter.js +209 -210
- package/dist/src/terminal-interaction-cli-adapter.js.map +1 -1
- package/dist/src/terminal-interaction-core.d.ts +0 -2
- package/dist/src/terminal-interaction-core.js +4 -3
- package/dist/src/terminal-interaction-core.js.map +1 -1
- package/dist/src/terminal-interaction-protocol.d.ts +21 -0
- package/dist/src/terminal-interaction-protocol.js +74 -0
- package/dist/src/terminal-interaction-protocol.js.map +1 -1
- package/dist/src/terminal-interaction-response-bridge.d.ts +126 -0
- package/dist/src/terminal-interaction-response-bridge.js +579 -0
- package/dist/src/terminal-interaction-response-bridge.js.map +1 -0
- package/dist/src/terminal-interaction-response-transaction.d.ts +44 -0
- package/dist/src/terminal-interaction-response-transaction.js +56 -0
- package/dist/src/terminal-interaction-response-transaction.js.map +1 -0
- package/dist/src/terminal-list-action-policy.d.ts +43 -0
- package/dist/src/terminal-list-action-policy.js +164 -0
- package/dist/src/terminal-list-action-policy.js.map +1 -0
- package/dist/src/terminal-list-cli-adapter.d.ts +6 -36
- package/dist/src/terminal-list-cli-adapter.js +314 -1711
- package/dist/src/terminal-list-cli-adapter.js.map +1 -1
- package/dist/src/terminal-list-facts.d.ts +129 -0
- package/dist/src/terminal-list-facts.js +179 -0
- package/dist/src/terminal-list-facts.js.map +1 -0
- package/dist/src/terminal-list-inventory.d.ts +59 -0
- package/dist/src/terminal-list-inventory.js +194 -0
- package/dist/src/terminal-list-inventory.js.map +1 -0
- package/dist/src/terminal-list-ownership-service.d.ts +118 -0
- package/dist/src/terminal-list-ownership-service.js +1003 -0
- package/dist/src/terminal-list-ownership-service.js.map +1 -0
- package/dist/src/terminal-list-projection-service.d.ts +49 -0
- package/dist/src/terminal-list-projection-service.js +407 -0
- package/dist/src/terminal-list-projection-service.js.map +1 -0
- package/dist/src/terminal-list-renderer.d.ts +12 -11
- package/dist/src/terminal-list-renderer.js +237 -104
- package/dist/src/terminal-list-renderer.js.map +1 -1
- package/dist/src/terminal-managed-send-cli-adapter.d.ts +33 -0
- package/dist/src/terminal-managed-send-cli-adapter.js +994 -0
- package/dist/src/terminal-managed-send-cli-adapter.js.map +1 -0
- package/dist/src/terminal-managed-turn-list-action-policy.d.ts +47 -0
- package/dist/src/terminal-managed-turn-list-action-policy.js +64 -0
- package/dist/src/terminal-managed-turn-list-action-policy.js.map +1 -0
- package/dist/src/terminal-model-control-action-policy.d.ts +60 -0
- package/dist/src/terminal-model-control-action-policy.js +96 -0
- package/dist/src/terminal-model-control-action-policy.js.map +1 -0
- package/dist/src/terminal-model-control-availability.d.ts +68 -0
- package/dist/src/terminal-model-control-availability.js +157 -0
- package/dist/src/terminal-model-control-availability.js.map +1 -0
- package/dist/src/terminal-model-control-bridge.d.ts +48 -0
- package/dist/src/terminal-model-control-bridge.js +259 -0
- package/dist/src/terminal-model-control-bridge.js.map +1 -0
- package/dist/src/terminal-model-control-profile.d.ts +55 -0
- package/dist/src/terminal-model-control-profile.js +137 -0
- package/dist/src/terminal-model-control-profile.js.map +1 -0
- package/dist/src/terminal-model-control-subject.d.ts +34 -0
- package/dist/src/terminal-model-control-subject.js +58 -0
- package/dist/src/terminal-model-control-subject.js.map +1 -0
- package/dist/src/terminal-model-control-transaction.d.ts +19 -0
- package/dist/src/terminal-model-control-transaction.js +39 -0
- package/dist/src/terminal-model-control-transaction.js.map +1 -0
- package/dist/src/terminal-model-control.d.ts +331 -0
- package/dist/src/terminal-model-control.js +1686 -0
- package/dist/src/terminal-model-control.js.map +1 -0
- package/dist/src/terminal-monitor-application-service.js +50 -17
- package/dist/src/terminal-monitor-application-service.js.map +1 -1
- package/dist/src/terminal-native-inspection-bridge.d.ts +211 -0
- package/dist/src/terminal-native-inspection-bridge.js +1127 -0
- package/dist/src/terminal-native-inspection-bridge.js.map +1 -0
- package/dist/src/terminal-runtime-cli-adapter.d.ts +2 -0
- package/dist/src/terminal-runtime-cli-adapter.js +59 -17
- package/dist/src/terminal-runtime-cli-adapter.js.map +1 -1
- package/dist/src/terminal-submission-retry-application.d.ts +24 -0
- package/dist/src/terminal-submission-retry-application.js +402 -0
- package/dist/src/terminal-submission-retry-application.js.map +1 -0
- package/dist/src/terminal-submission-retry-ports.d.ts +30 -0
- package/dist/src/terminal-submission-retry-ports.js +2 -0
- package/dist/src/terminal-submission-retry-ports.js.map +1 -0
- package/dist/src/terminal-submission-retry-reconciliation.d.ts +236 -0
- package/dist/src/terminal-submission-retry-reconciliation.js +1130 -0
- package/dist/src/terminal-submission-retry-reconciliation.js.map +1 -0
- package/dist/src/terminal-submission-retry-transport.d.ts +58 -0
- package/dist/src/terminal-submission-retry-transport.js +675 -0
- package/dist/src/terminal-submission-retry-transport.js.map +1 -0
- package/dist/src/terminal-text-submission-bridge.d.ts +200 -0
- package/dist/src/terminal-text-submission-bridge.js +632 -0
- package/dist/src/terminal-text-submission-bridge.js.map +1 -0
- package/dist/src/terminal-user-explicit-send-policy.d.ts +29 -0
- package/dist/src/terminal-user-explicit-send-policy.js +46 -0
- package/dist/src/terminal-user-explicit-send-policy.js.map +1 -0
- package/dist/src/terminal-watch-cli-adapter.js +91 -143
- package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
- package/dist/src/terminal-watch-codec.d.ts +9 -0
- package/dist/src/terminal-watch-codec.js +1051 -0
- package/dist/src/terminal-watch-codec.js.map +1 -0
- package/dist/src/terminal-watch-interaction-response-store.d.ts +19 -0
- package/dist/src/terminal-watch-interaction-response-store.js +87 -0
- package/dist/src/terminal-watch-interaction-response-store.js.map +1 -0
- package/dist/src/terminal-watch-record.d.ts +319 -0
- package/dist/src/terminal-watch-record.js +742 -0
- package/dist/src/terminal-watch-record.js.map +1 -0
- package/dist/src/terminal-watch-service.js +48 -27
- package/dist/src/terminal-watch-service.js.map +1 -1
- package/dist/src/terminal-watch-store.d.ts +3 -279
- package/dist/src/terminal-watch-store.js +5 -1779
- package/dist/src/terminal-watch-store.js.map +1 -1
- package/docs/quickstart-herdr.md +1 -1
- package/docs/quickstart-tmux.md +27 -2
- package/openclaw.plugin.json +13 -1
- package/package.json +7 -2
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +54 -11
|
@@ -0,0 +1,1051 @@
|
|
|
1
|
+
import { createLegacyOpenClawCallbackRoute, createTerminalWatchOpenClawCallbackRoute, parseCallbackRoute } from "./callback-transport.js";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { canonicalJson } from "./canonical-json.js";
|
|
4
|
+
import { claudeTranscriptAnchorFingerprint, validateClaudeHumanStartedActiveTaskCheckpoint, validateClaudeHumanStartedActiveTaskAnchor } from "./claude-local-transcript-provider.js";
|
|
5
|
+
import { validateCodexHumanStartedActiveTaskAnchor } from "./terminal-submission-acceptance.js";
|
|
6
|
+
import { exactNativeThreadId, normalizedRolloutIdentity, validateCodexRolloutAcceptanceAnchor, validateTerminalSubmissionAcceptanceEvidence } from "./terminal-submission-facts.js";
|
|
7
|
+
import { terminalEndpointIdentityFromEvidence, terminalRouteKeyFromEvidence } from "./terminal-control-ref.js";
|
|
8
|
+
import { sameTerminalInteractionSubject, validateTerminalInteractionSubjectProjection } from "./terminal-interaction-protocol.js";
|
|
9
|
+
import { aggregateMatchesProjection, reduceTerminalInteractionAggregate, validateTerminalInteractionAggregate } from "./terminal-interaction-core.js";
|
|
10
|
+
import { ABSOLUTE_PATH, ARRAY_VALUE, IGNORE_VALUE, NON_NEGATIVE_INTEGER, NULLABLE_ENDPOINT_STRING, POSITIVE_INTEGER, TERMINAL_WATCH_INTERACTION_POLICIES, TERMINAL_WATCH_MANUAL_INTERACTION_MAX_OPTION_DESCRIPTION_CHARACTERS, TERMINAL_WATCH_MANUAL_INTERACTION_MAX_OPTION_LABEL_CHARACTERS, TERMINAL_WATCH_MANUAL_INTERACTION_MAX_OPTIONS, TERMINAL_WATCH_MANUAL_INTERACTION_MAX_PROMPT_CHARACTERS, TERMINAL_WATCH_MANUAL_INTERACTION_MAX_STEPS, TERMINAL_WATCH_MANUAL_INTERACTION_RESPONSE_KINDS, TERMINAL_WATCH_NOTIFICATION_KINDS, TERMINAL_WATCH_NOTIFICATION_STATUSES, TERMINAL_WATCH_SCHEMA, TERMINAL_WATCH_STATUSES, TERMINAL_WATCH_TERMINAL_STATUSES, TERMINAL_WATCH_VERSION, WARNING_LIST, assertAnchorFingerprint, assertCompletionText, assertNonEmptyString, assertRecordId, assertReasonCode, assertSha256, assertStrictRecord, assertTimestamp, exactUuid, initialTerminalWatchObservationCheckpoint, isTerminalActivityWatch, isTerminalWatchOutcomeNotification, isUserExplicitFallbackWatch, literalGuard, nullableGuard, oneOfGuard, optionalGuard, parseTerminalWatchNotificationCallbackSnapshot, terminalWatchCallbackEnvelope, terminalWatchNotificationId, terminalWatchNotificationIdempotencyKey, upgradePredecessorCallbackPresentations, validatedClaudeTranscriptAnchor } from "./terminal-watch-record.js";
|
|
11
|
+
import { isRecord } from "./value-guards.js";
|
|
12
|
+
const WATCH_FIELDS = {
|
|
13
|
+
schema: literalGuard(TERMINAL_WATCH_SCHEMA),
|
|
14
|
+
version: literalGuard(TERMINAL_WATCH_VERSION),
|
|
15
|
+
watch_id: assertRecordId,
|
|
16
|
+
revision: optionalGuard(POSITIVE_INTEGER),
|
|
17
|
+
agent: oneOfGuard(["codex", "claude"]),
|
|
18
|
+
terminal: assertTerminalIdentity,
|
|
19
|
+
anchor: IGNORE_VALUE,
|
|
20
|
+
observation_checkpoint: IGNORE_VALUE,
|
|
21
|
+
interaction_policy: oneOfGuard(TERMINAL_WATCH_INTERACTION_POLICIES),
|
|
22
|
+
current_interaction: optionalGuard(IGNORE_VALUE),
|
|
23
|
+
warnings: optionalGuard(WARNING_LIST),
|
|
24
|
+
callback_route: optionalGuard(IGNORE_VALUE),
|
|
25
|
+
openclaw_session: assertNonEmptyString,
|
|
26
|
+
openclaw_bin: assertNonEmptyString,
|
|
27
|
+
created_at: assertTimestamp,
|
|
28
|
+
deadline_at: assertTimestamp,
|
|
29
|
+
updated_at: assertTimestamp,
|
|
30
|
+
status: oneOfGuard(TERMINAL_WATCH_STATUSES),
|
|
31
|
+
last_activity_at: assertTimestamp,
|
|
32
|
+
settlement: IGNORE_VALUE,
|
|
33
|
+
notification_outbox: ARRAY_VALUE
|
|
34
|
+
};
|
|
35
|
+
const TERMINAL_IDENTITY_FIELDS = {
|
|
36
|
+
terminal_id: assertNonEmptyString,
|
|
37
|
+
terminal_endpoint: assertTerminalEndpoint,
|
|
38
|
+
workspace: ABSOLUTE_PATH,
|
|
39
|
+
binding_token: assertSha256
|
|
40
|
+
};
|
|
41
|
+
const TERMINAL_ENDPOINT_FIELDS = {
|
|
42
|
+
schema: literalGuard("agent-knock-knock/terminal-endpoint"),
|
|
43
|
+
version: literalGuard(1),
|
|
44
|
+
kind: IGNORE_VALUE,
|
|
45
|
+
endpoint_key: IGNORE_VALUE,
|
|
46
|
+
resource_key: IGNORE_VALUE,
|
|
47
|
+
route_key: IGNORE_VALUE,
|
|
48
|
+
process_anchor_pid: POSITIVE_INTEGER,
|
|
49
|
+
target: NULLABLE_ENDPOINT_STRING,
|
|
50
|
+
socket_path: NULLABLE_ENDPOINT_STRING,
|
|
51
|
+
pane_pid: optionalGuard(nullableGuard(POSITIVE_INTEGER)),
|
|
52
|
+
server_socket_path: NULLABLE_ENDPOINT_STRING,
|
|
53
|
+
pane_id: NULLABLE_ENDPOINT_STRING,
|
|
54
|
+
session_name: NULLABLE_ENDPOINT_STRING,
|
|
55
|
+
session_dir: NULLABLE_ENDPOINT_STRING,
|
|
56
|
+
workspace_id: NULLABLE_ENDPOINT_STRING,
|
|
57
|
+
tab_id: NULLABLE_ENDPOINT_STRING,
|
|
58
|
+
terminal_id: NULLABLE_ENDPOINT_STRING,
|
|
59
|
+
current_path: NULLABLE_ENDPOINT_STRING
|
|
60
|
+
};
|
|
61
|
+
const SETTLEMENT_FIELDS = {
|
|
62
|
+
kind: oneOfGuard(TERMINAL_WATCH_TERMINAL_STATUSES),
|
|
63
|
+
evidence_fingerprint: assertSha256,
|
|
64
|
+
observed_at: assertTimestamp,
|
|
65
|
+
reason_code: optionalGuard(assertReasonCode),
|
|
66
|
+
completion_text: optionalGuard(assertCompletionText),
|
|
67
|
+
completion_id: optionalGuard(assertNonEmptyString),
|
|
68
|
+
completion_timestamp: optionalGuard(assertTimestamp)
|
|
69
|
+
};
|
|
70
|
+
const NOTIFICATION_RECEIPT_FIELDS = {
|
|
71
|
+
last_attempt_at: optionalGuard(assertTimestamp),
|
|
72
|
+
attempt_id: optionalGuard(assertNonEmptyString),
|
|
73
|
+
attempt_lease_expires_at: optionalGuard(assertTimestamp),
|
|
74
|
+
failed_at: optionalGuard(assertTimestamp),
|
|
75
|
+
next_attempt_at: optionalGuard(assertTimestamp),
|
|
76
|
+
last_error_code: optionalGuard(assertReasonCode),
|
|
77
|
+
delivered_at: optionalGuard(assertTimestamp),
|
|
78
|
+
superseded_at: optionalGuard(assertTimestamp)
|
|
79
|
+
};
|
|
80
|
+
const NOTIFICATION_FIELDS = {
|
|
81
|
+
notification_id: assertNonEmptyString,
|
|
82
|
+
idempotency_key: assertNonEmptyString,
|
|
83
|
+
kind: oneOfGuard(TERMINAL_WATCH_NOTIFICATION_KINDS),
|
|
84
|
+
evidence_fingerprint: assertSha256,
|
|
85
|
+
reason_code: optionalGuard(assertReasonCode),
|
|
86
|
+
manual_interaction: optionalGuard(IGNORE_VALUE),
|
|
87
|
+
callback_route: IGNORE_VALUE,
|
|
88
|
+
callback_envelope: IGNORE_VALUE,
|
|
89
|
+
status: oneOfGuard(TERMINAL_WATCH_NOTIFICATION_STATUSES),
|
|
90
|
+
attempts: NON_NEGATIVE_INTEGER,
|
|
91
|
+
created_at: assertTimestamp,
|
|
92
|
+
...NOTIFICATION_RECEIPT_FIELDS
|
|
93
|
+
};
|
|
94
|
+
const NOTIFICATION_SHAPES = {
|
|
95
|
+
pending: [0, []],
|
|
96
|
+
delivering: [1, [
|
|
97
|
+
"last_attempt_at",
|
|
98
|
+
"attempt_id",
|
|
99
|
+
"attempt_lease_expires_at"
|
|
100
|
+
]],
|
|
101
|
+
failed: [1, [
|
|
102
|
+
"last_attempt_at",
|
|
103
|
+
"failed_at",
|
|
104
|
+
"next_attempt_at",
|
|
105
|
+
"last_error_code"
|
|
106
|
+
]],
|
|
107
|
+
delivered: [1, ["last_attempt_at", "delivered_at"]],
|
|
108
|
+
superseded: [0, ["superseded_at"]]
|
|
109
|
+
};
|
|
110
|
+
export function assertTerminalWatch(value, expectedWatchId, options = {}) {
|
|
111
|
+
assertTerminalWatchRecord(value, expectedWatchId, options);
|
|
112
|
+
}
|
|
113
|
+
function assertTerminalWatchRecord(value, expectedWatchId, options) {
|
|
114
|
+
assertStrictRecord(value, "terminal Watch", WATCH_FIELDS);
|
|
115
|
+
if (expectedWatchId !== undefined && value.watch_id !== expectedWatchId) {
|
|
116
|
+
throw new Error(`terminal Watch id ${String(value.watch_id)} does not match ${expectedWatchId}`);
|
|
117
|
+
}
|
|
118
|
+
if (value.revision === undefined && !options.allowMissingRevision) {
|
|
119
|
+
throw new Error("terminal Watch revision must be a positive safe integer");
|
|
120
|
+
}
|
|
121
|
+
const watch = value;
|
|
122
|
+
assertTerminalWatchAnchor(watch.anchor, watch.agent, watch.terminal);
|
|
123
|
+
assertTerminalWatchObservationCheckpoint(watch.observation_checkpoint, watch.anchor);
|
|
124
|
+
if (isTerminalActivityWatch(watch) &&
|
|
125
|
+
watch.interaction_policy !== "notify_only") {
|
|
126
|
+
throw new Error("a terminal activity Watch must remain interaction notify-only");
|
|
127
|
+
}
|
|
128
|
+
assertTerminalWatchCurrentInteraction(watch.current_interaction, watch);
|
|
129
|
+
const checkpoint = watch.observation_checkpoint;
|
|
130
|
+
if (isTerminalActivityWatch(watch) &&
|
|
131
|
+
"schema" in checkpoint &&
|
|
132
|
+
checkpoint.schema ===
|
|
133
|
+
"agent-knock-knock/terminal-activity-watch-checkpoint" &&
|
|
134
|
+
watch.status === "active" &&
|
|
135
|
+
checkpoint.consecutive_idle_observations > 1) {
|
|
136
|
+
throw new Error("an active terminal activity Watch cannot already carry stable-idle settlement evidence");
|
|
137
|
+
}
|
|
138
|
+
if (watch.callback_route !== undefined) {
|
|
139
|
+
const route = parseCallbackRoute(watch.callback_route);
|
|
140
|
+
if (route.controller_session_id !== watch.openclaw_session) {
|
|
141
|
+
throw new Error("terminal Watch callback route does not match its controller session");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const minimumCheckpointOffset = watch.anchor.schema ===
|
|
145
|
+
"agent-knock-knock/terminal-activity-watch-anchor"
|
|
146
|
+
? 0
|
|
147
|
+
: watch.anchor.schema ===
|
|
148
|
+
"agent-knock-knock/claude-human-started-active-task-anchor"
|
|
149
|
+
? watch.anchor.turn_start_offset_bytes
|
|
150
|
+
: watch.anchor.schema ===
|
|
151
|
+
"agent-knock-knock/codex-human-started-active-task-anchor"
|
|
152
|
+
? watch.anchor.observed_end_offset_bytes
|
|
153
|
+
: watch.anchor.schema ===
|
|
154
|
+
"agent-knock-knock/claude-user-explicit-fallback-watch-anchor"
|
|
155
|
+
? watch.anchor.transcript_anchor.offset_bytes
|
|
156
|
+
: watch.anchor.acceptance_anchor.offset_bytes;
|
|
157
|
+
if (watch.observation_checkpoint.safe_resume_offset_bytes <
|
|
158
|
+
minimumCheckpointOffset) {
|
|
159
|
+
throw new Error("terminal Watch observation checkpoint cannot predate its task anchor");
|
|
160
|
+
}
|
|
161
|
+
if (Date.parse(watch.deadline_at) <= Date.parse(watch.created_at) ||
|
|
162
|
+
Date.parse(watch.updated_at) < Date.parse(watch.created_at) ||
|
|
163
|
+
Date.parse(watch.last_activity_at) > Date.parse(watch.updated_at)) {
|
|
164
|
+
throw new Error("terminal Watch timestamps are not monotonic");
|
|
165
|
+
}
|
|
166
|
+
const processStartedAt = watch.anchor.schema ===
|
|
167
|
+
"agent-knock-knock/claude-human-started-active-task-anchor"
|
|
168
|
+
? watch.anchor.agent_started_at_ms
|
|
169
|
+
: watch.anchor.schema ===
|
|
170
|
+
"agent-knock-knock/claude-user-explicit-fallback-watch-anchor"
|
|
171
|
+
? watch.anchor.transcript_anchor.agent_started_at_ms
|
|
172
|
+
: undefined;
|
|
173
|
+
if (Date.parse(watch.anchor.captured_at) > Date.parse(watch.created_at) ||
|
|
174
|
+
(processStartedAt !== undefined &&
|
|
175
|
+
processStartedAt > Date.parse(watch.created_at))) {
|
|
176
|
+
throw new Error("terminal Watch cannot predate its exact observation anchor");
|
|
177
|
+
}
|
|
178
|
+
assertSettlement(watch.settlement, watch.status);
|
|
179
|
+
if (watch.settlement &&
|
|
180
|
+
(Date.parse(watch.settlement.observed_at) > Date.parse(watch.updated_at) ||
|
|
181
|
+
(watch.settlement.completion_timestamp !== undefined &&
|
|
182
|
+
Date.parse(watch.settlement.completion_timestamp) >
|
|
183
|
+
Date.parse(watch.updated_at)))) {
|
|
184
|
+
throw new Error("terminal Watch settlement cannot be newer than its state");
|
|
185
|
+
}
|
|
186
|
+
assertNotificationOutbox(watch, options.allowPredecessorCallbackPresentation === true);
|
|
187
|
+
}
|
|
188
|
+
export function decodeTerminalWatch(value, expectedWatchId) {
|
|
189
|
+
const sourceVersion = isRecord(value) ? value.version : undefined;
|
|
190
|
+
const normalized = normalizeLegacyTerminalWatch(value);
|
|
191
|
+
const predecessorPresentation = sourceVersion === 1 || sourceVersion === 2;
|
|
192
|
+
assertTerminalWatchRecord(normalized, expectedWatchId, {
|
|
193
|
+
allowPredecessorCallbackPresentation: predecessorPresentation
|
|
194
|
+
});
|
|
195
|
+
const presentationUpgraded = predecessorPresentation
|
|
196
|
+
? upgradePredecessorCallbackPresentations(normalized)
|
|
197
|
+
: normalized;
|
|
198
|
+
assertTerminalWatch(presentationUpgraded, expectedWatchId);
|
|
199
|
+
const repaired = sourceVersion === 2 ||
|
|
200
|
+
sourceVersion === TERMINAL_WATCH_VERSION
|
|
201
|
+
? repairMisroutedUserExplicitFallbackCallback(presentationUpgraded)
|
|
202
|
+
: presentationUpgraded;
|
|
203
|
+
assertTerminalWatch(repaired, expectedWatchId);
|
|
204
|
+
return repaired;
|
|
205
|
+
}
|
|
206
|
+
const MISROUTED_FALLBACK_CALLBACK_ERROR = "callback_permanent_openclaw_callback_profile_changed";
|
|
207
|
+
const REPAIRED_FALLBACK_CALLBACK_ERROR = "callback_route_repaired_after_openclaw_profile_mismatch";
|
|
208
|
+
const REPAIRED_FALLBACK_CALLBACK_WARNING = "legacy_openclaw_fallback_callback_route_repaired";
|
|
209
|
+
/**
|
|
210
|
+
* v0.12.19-v0.12.22 accidentally snapshotted the managed Send Gateway
|
|
211
|
+
* method for user-explicit fallback Watches. That profile is rejected before
|
|
212
|
+
* callback I/O, so this exact shape is safe to redirect and retry with the
|
|
213
|
+
* original idempotency key. Every ambiguous or side-effect-capable state is
|
|
214
|
+
* left untouched.
|
|
215
|
+
*/
|
|
216
|
+
function repairMisroutedUserExplicitFallbackCallback(watch) {
|
|
217
|
+
if (!isUserExplicitFallbackWatch(watch) || !watch.callback_route) {
|
|
218
|
+
return watch;
|
|
219
|
+
}
|
|
220
|
+
const misrouted = createLegacyOpenClawCallbackRoute({
|
|
221
|
+
controllerSessionId: watch.openclaw_session,
|
|
222
|
+
gatewayMethod: "agent-knock-knock.callback",
|
|
223
|
+
openclawBin: watch.openclaw_bin
|
|
224
|
+
});
|
|
225
|
+
if (canonicalJson(watch.callback_route) !== canonicalJson(misrouted)) {
|
|
226
|
+
return watch;
|
|
227
|
+
}
|
|
228
|
+
for (const notification of watch.notification_outbox) {
|
|
229
|
+
const hasSnapshot = notification.callback_route !== undefined;
|
|
230
|
+
if (notification.status === "delivered" ||
|
|
231
|
+
notification.status === "delivering" ||
|
|
232
|
+
(notification.status === "failed" &&
|
|
233
|
+
(!hasSnapshot ||
|
|
234
|
+
notification.last_error_code !== MISROUTED_FALLBACK_CALLBACK_ERROR)) ||
|
|
235
|
+
(notification.status === "pending" &&
|
|
236
|
+
hasSnapshot)) {
|
|
237
|
+
return watch;
|
|
238
|
+
}
|
|
239
|
+
if (notification.callback_route !== undefined &&
|
|
240
|
+
canonicalJson(notification.callback_route) !== canonicalJson(misrouted)) {
|
|
241
|
+
return watch;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const route = createTerminalWatchOpenClawCallbackRoute({
|
|
245
|
+
controllerSessionId: watch.openclaw_session,
|
|
246
|
+
openclawBin: watch.openclaw_bin
|
|
247
|
+
});
|
|
248
|
+
const repairedWatch = {
|
|
249
|
+
...watch,
|
|
250
|
+
warnings: [...new Set([
|
|
251
|
+
...(watch.warnings ?? []),
|
|
252
|
+
REPAIRED_FALLBACK_CALLBACK_WARNING
|
|
253
|
+
])],
|
|
254
|
+
callback_route: route,
|
|
255
|
+
notification_outbox: []
|
|
256
|
+
};
|
|
257
|
+
repairedWatch.notification_outbox = watch.notification_outbox.map((notification) => {
|
|
258
|
+
const repaired = notification.status === "failed"
|
|
259
|
+
? {
|
|
260
|
+
...notification,
|
|
261
|
+
last_error_code: REPAIRED_FALLBACK_CALLBACK_ERROR
|
|
262
|
+
}
|
|
263
|
+
: { ...notification };
|
|
264
|
+
if (notification.callback_route === undefined)
|
|
265
|
+
return repaired;
|
|
266
|
+
return {
|
|
267
|
+
...repaired,
|
|
268
|
+
callback_route: route,
|
|
269
|
+
callback_envelope: terminalWatchCallbackEnvelope(repairedWatch, repaired, route)
|
|
270
|
+
};
|
|
271
|
+
});
|
|
272
|
+
return repairedWatch;
|
|
273
|
+
}
|
|
274
|
+
function normalizeLegacyTerminalWatch(value) {
|
|
275
|
+
if (!isRecord(value) ||
|
|
276
|
+
value.schema !== TERMINAL_WATCH_SCHEMA ||
|
|
277
|
+
(value.version !== 1 &&
|
|
278
|
+
value.version !== 2 &&
|
|
279
|
+
value.version !== TERMINAL_WATCH_VERSION) ||
|
|
280
|
+
!isRecord(value.anchor)) {
|
|
281
|
+
return value;
|
|
282
|
+
}
|
|
283
|
+
// Response authority is never inferred while reading a predecessor record.
|
|
284
|
+
// Only a newly-created v3 Watch may opt in explicitly.
|
|
285
|
+
const versionNormalized = value.version === TERMINAL_WATCH_VERSION
|
|
286
|
+
? value
|
|
287
|
+
: legacyTerminalWatchV3(value);
|
|
288
|
+
if (value.observation_checkpoint !== undefined) {
|
|
289
|
+
return versionNormalized;
|
|
290
|
+
}
|
|
291
|
+
const anchor = value.anchor;
|
|
292
|
+
if (anchor.schema ===
|
|
293
|
+
"agent-knock-knock/claude-human-started-active-task-anchor") {
|
|
294
|
+
const validated = validateClaudeHumanStartedActiveTaskAnchor(anchor);
|
|
295
|
+
return {
|
|
296
|
+
...versionNormalized,
|
|
297
|
+
observation_checkpoint: initialTerminalWatchObservationCheckpoint(validated)
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
if (anchor.schema ===
|
|
301
|
+
"agent-knock-knock/codex-human-started-active-task-anchor") {
|
|
302
|
+
const validated = validateCodexHumanStartedActiveTaskAnchor(anchor);
|
|
303
|
+
return {
|
|
304
|
+
...versionNormalized,
|
|
305
|
+
observation_checkpoint: initialTerminalWatchObservationCheckpoint(validated)
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
return versionNormalized;
|
|
309
|
+
}
|
|
310
|
+
function legacyTerminalWatchV3(value) {
|
|
311
|
+
// Pre-v3 schemas did not define either field. Discard, rather than trust,
|
|
312
|
+
// hand-added response authority or interaction state while migrating.
|
|
313
|
+
const { interaction_policy: _legacyInteractionPolicy, current_interaction: _legacyCurrentInteraction, ...legacy } = value;
|
|
314
|
+
return {
|
|
315
|
+
...legacy,
|
|
316
|
+
version: TERMINAL_WATCH_VERSION,
|
|
317
|
+
interaction_policy: "notify_only"
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
export function assertTerminalWatchObservationCheckpoint(value, anchor) {
|
|
321
|
+
if (anchor.schema === "agent-knock-knock/terminal-activity-watch-anchor") {
|
|
322
|
+
assertStrictRecord(value, "terminal activity Watch checkpoint", {
|
|
323
|
+
schema: literalGuard("agent-knock-knock/terminal-activity-watch-checkpoint"),
|
|
324
|
+
version: literalGuard(1),
|
|
325
|
+
safe_resume_offset_bytes: literalGuard(0),
|
|
326
|
+
has_seen_activity: (candidate, label) => {
|
|
327
|
+
if (typeof candidate !== "boolean") {
|
|
328
|
+
throw new Error(`${label} must be boolean`);
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
consecutive_idle_observations: NON_NEGATIVE_INTEGER,
|
|
332
|
+
last_activity_state: oneOfGuard([
|
|
333
|
+
"awaiting_approval", "working", "idle", "unknown"
|
|
334
|
+
])
|
|
335
|
+
});
|
|
336
|
+
const checkpoint = value;
|
|
337
|
+
if (checkpoint.consecutive_idle_observations > 0 &&
|
|
338
|
+
(!checkpoint.has_seen_activity ||
|
|
339
|
+
checkpoint.last_activity_state !== "idle")) {
|
|
340
|
+
throw new Error("terminal activity Watch idle observations require prior activity and an idle state");
|
|
341
|
+
}
|
|
342
|
+
if (checkpoint.last_activity_state !== "idle" &&
|
|
343
|
+
checkpoint.consecutive_idle_observations !== 0) {
|
|
344
|
+
throw new Error("terminal activity Watch non-idle checkpoint cannot retain idle observations");
|
|
345
|
+
}
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (anchor.schema ===
|
|
349
|
+
"agent-knock-knock/claude-human-started-active-task-anchor") {
|
|
350
|
+
validateClaudeHumanStartedActiveTaskCheckpoint(value, anchor);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (anchor.schema ===
|
|
354
|
+
"agent-knock-knock/codex-user-explicit-fallback-watch-anchor") {
|
|
355
|
+
assertCodexUserExplicitFallbackWatchCheckpoint(value, anchor);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (anchor.schema ===
|
|
359
|
+
"agent-knock-knock/claude-user-explicit-fallback-watch-anchor") {
|
|
360
|
+
assertClaudeUserExplicitFallbackWatchCheckpoint(value, anchor);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
assertStrictRecord(value, "terminal Watch observation checkpoint", {
|
|
364
|
+
safe_resume_offset_bytes: NON_NEGATIVE_INTEGER
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
function assertCodexUserExplicitFallbackWatchCheckpoint(value, anchor) {
|
|
368
|
+
assertStrictRecord(value, "Codex fallback Watch checkpoint", {
|
|
369
|
+
schema: literalGuard("agent-knock-knock/codex-user-explicit-fallback-watch-checkpoint"),
|
|
370
|
+
version: literalGuard(1),
|
|
371
|
+
safe_resume_offset_bytes: NON_NEGATIVE_INTEGER,
|
|
372
|
+
acceptance_evidence: optionalGuard(IGNORE_VALUE),
|
|
373
|
+
accepted_identity: optionalGuard(IGNORE_VALUE)
|
|
374
|
+
});
|
|
375
|
+
const acceptanceValue = value.acceptance_evidence;
|
|
376
|
+
const identityValue = value.accepted_identity;
|
|
377
|
+
if ((acceptanceValue === undefined) !== (identityValue === undefined)) {
|
|
378
|
+
throw new Error("Codex fallback Watch checkpoint acceptance identity is incomplete");
|
|
379
|
+
}
|
|
380
|
+
if (acceptanceValue === undefined || identityValue === undefined) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
assertStrictRecord(identityValue, "Codex fallback Watch accepted identity", {
|
|
384
|
+
native_thread_id: assertNonEmptyString,
|
|
385
|
+
process_uuid: assertNonEmptyString,
|
|
386
|
+
process_birth: assertNonEmptyString,
|
|
387
|
+
rollout: IGNORE_VALUE
|
|
388
|
+
});
|
|
389
|
+
const nativeThreadId = exactNativeThreadId(identityValue.native_thread_id);
|
|
390
|
+
if (!isRecord(identityValue.rollout)) {
|
|
391
|
+
throw new Error("Codex fallback Watch accepted rollout is invalid");
|
|
392
|
+
}
|
|
393
|
+
const rollout = normalizedRolloutIdentity(identityValue.rollout);
|
|
394
|
+
if (identityValue.native_thread_id !== nativeThreadId ||
|
|
395
|
+
JSON.stringify(identityValue.rollout) !== JSON.stringify(rollout) ||
|
|
396
|
+
!path.isAbsolute(rollout.path) ||
|
|
397
|
+
identityValue.process_uuid !== anchor.acceptance_anchor.process_uuid ||
|
|
398
|
+
identityValue.process_birth !== anchor.acceptance_anchor.process_birth) {
|
|
399
|
+
throw new Error("Codex fallback Watch accepted identity does not match its anchor");
|
|
400
|
+
}
|
|
401
|
+
const acceptance = validateTerminalSubmissionAcceptanceEvidence(acceptanceValue, {
|
|
402
|
+
source: "codex_rollout",
|
|
403
|
+
nativeThreadId,
|
|
404
|
+
requestHash: anchor.request_hash
|
|
405
|
+
});
|
|
406
|
+
if (acceptance.anchorFingerprint !==
|
|
407
|
+
anchor.acceptance_anchor.anchor_fingerprint) {
|
|
408
|
+
throw new Error("Codex fallback Watch acceptance evidence does not match its anchor");
|
|
409
|
+
}
|
|
410
|
+
const observedEndOffset = acceptance.metadata?.observed_end_offset_bytes;
|
|
411
|
+
if (typeof observedEndOffset !== "number" ||
|
|
412
|
+
!Number.isSafeInteger(observedEndOffset) ||
|
|
413
|
+
observedEndOffset <= anchor.acceptance_anchor.offset_bytes ||
|
|
414
|
+
Number(value.safe_resume_offset_bytes) < observedEndOffset) {
|
|
415
|
+
throw new Error("Codex fallback Watch acceptance checkpoint offset is invalid");
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function assertClaudeUserExplicitFallbackWatchCheckpoint(value, anchor) {
|
|
419
|
+
assertStrictRecord(value, "Claude fallback Watch checkpoint", {
|
|
420
|
+
schema: literalGuard("agent-knock-knock/claude-user-explicit-fallback-watch-checkpoint"),
|
|
421
|
+
version: literalGuard(1),
|
|
422
|
+
safe_resume_offset_bytes: NON_NEGATIVE_INTEGER,
|
|
423
|
+
acceptance_evidence: optionalGuard(IGNORE_VALUE),
|
|
424
|
+
accepted_prompt_uuid: optionalGuard(IGNORE_VALUE)
|
|
425
|
+
});
|
|
426
|
+
const acceptanceValue = value.acceptance_evidence;
|
|
427
|
+
const promptUuidValue = value.accepted_prompt_uuid;
|
|
428
|
+
if ((acceptanceValue === undefined) !== (promptUuidValue === undefined)) {
|
|
429
|
+
throw new Error("Claude fallback Watch checkpoint acceptance identity is incomplete");
|
|
430
|
+
}
|
|
431
|
+
if (acceptanceValue === undefined || promptUuidValue === undefined) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const acceptedPromptUuid = exactUuid(promptUuidValue, "Claude fallback accepted prompt UUID");
|
|
435
|
+
const acceptance = validateTerminalSubmissionAcceptanceEvidence(acceptanceValue, {
|
|
436
|
+
source: "claude_transcript",
|
|
437
|
+
nativeThreadId: anchor.transcript_anchor.session_id,
|
|
438
|
+
requestHash: anchor.request_hash
|
|
439
|
+
});
|
|
440
|
+
const metadata = acceptance.metadata;
|
|
441
|
+
const observedEndOffset = metadata?.observed_end_offset_bytes;
|
|
442
|
+
if (acceptance.acceptanceId !== acceptedPromptUuid ||
|
|
443
|
+
metadata?.prompt_uuid !== acceptedPromptUuid ||
|
|
444
|
+
metadata?.claude_version !== anchor.claude_version ||
|
|
445
|
+
metadata?.anchor_offset_bytes !== anchor.transcript_anchor.offset_bytes ||
|
|
446
|
+
metadata?.agent_started_at_ms !==
|
|
447
|
+
anchor.transcript_anchor.agent_started_at_ms ||
|
|
448
|
+
acceptance.anchorFingerprint !==
|
|
449
|
+
claudeTranscriptAnchorFingerprint(anchor.transcript_anchor) ||
|
|
450
|
+
typeof observedEndOffset !== "number" ||
|
|
451
|
+
!Number.isSafeInteger(observedEndOffset) ||
|
|
452
|
+
observedEndOffset <= anchor.transcript_anchor.offset_bytes ||
|
|
453
|
+
Number(value.safe_resume_offset_bytes) < observedEndOffset) {
|
|
454
|
+
throw new Error("Claude fallback Watch acceptance checkpoint does not match its anchor");
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function assertTerminalIdentity(value) {
|
|
458
|
+
assertStrictRecord(value, "terminal Watch terminal identity", TERMINAL_IDENTITY_FIELDS);
|
|
459
|
+
}
|
|
460
|
+
function assertTerminalEndpoint(value) {
|
|
461
|
+
assertStrictRecord(value, "terminal Watch endpoint", TERMINAL_ENDPOINT_FIELDS);
|
|
462
|
+
if (!terminalEndpointIdentityFromEvidence(value) ||
|
|
463
|
+
!terminalRouteKeyFromEvidence(value)) {
|
|
464
|
+
throw new Error("terminal Watch endpoint evidence is not exact");
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
function assertTerminalWatchAnchor(value, agent, terminal) {
|
|
468
|
+
if (!isRecord(value)) {
|
|
469
|
+
throw new Error("terminal Watch anchor must be an object");
|
|
470
|
+
}
|
|
471
|
+
if (value.schema === "agent-knock-knock/terminal-activity-watch-anchor") {
|
|
472
|
+
assertStrictRecord(value, "terminal activity Watch anchor", {
|
|
473
|
+
schema: literalGuard("agent-knock-knock/terminal-activity-watch-anchor"),
|
|
474
|
+
version: literalGuard(1),
|
|
475
|
+
captured_at: assertTimestamp,
|
|
476
|
+
terminal_id: assertNonEmptyString,
|
|
477
|
+
pid: POSITIVE_INTEGER,
|
|
478
|
+
initial_activity_state: oneOfGuard([
|
|
479
|
+
"awaiting_approval", "working", "idle", "unknown"
|
|
480
|
+
]),
|
|
481
|
+
native_process_uuid: optionalGuard(assertNonEmptyString),
|
|
482
|
+
native_process_birth: optionalGuard(assertNonEmptyString),
|
|
483
|
+
agent_version: optionalGuard(assertNonEmptyString),
|
|
484
|
+
anchor_fingerprint: assertSha256
|
|
485
|
+
});
|
|
486
|
+
if (value.terminal_id !== terminal.terminal_id) {
|
|
487
|
+
throw new Error("terminal activity Watch id does not match its terminal identity");
|
|
488
|
+
}
|
|
489
|
+
assertAnchorFingerprint(value, "terminal activity Watch anchor");
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
if (value.schema ===
|
|
493
|
+
"agent-knock-knock/codex-human-started-active-task-anchor") {
|
|
494
|
+
if (agent !== "codex") {
|
|
495
|
+
throw new Error("Codex Watch anchor cannot belong to another agent");
|
|
496
|
+
}
|
|
497
|
+
validateCodexHumanStartedActiveTaskAnchor(value);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (value.schema ===
|
|
501
|
+
"agent-knock-knock/claude-human-started-active-task-anchor") {
|
|
502
|
+
if (agent !== "claude") {
|
|
503
|
+
throw new Error("Claude Watch anchor cannot belong to another agent");
|
|
504
|
+
}
|
|
505
|
+
const anchor = validateClaudeHumanStartedActiveTaskAnchor(value);
|
|
506
|
+
if (anchor.cwd !== terminal.workspace) {
|
|
507
|
+
throw new Error("Claude Watch workspace does not match its task anchor");
|
|
508
|
+
}
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (value.schema ===
|
|
512
|
+
"agent-knock-knock/codex-user-explicit-fallback-watch-anchor") {
|
|
513
|
+
if (agent !== "codex") {
|
|
514
|
+
throw new Error("Codex fallback Watch anchor cannot belong to another agent");
|
|
515
|
+
}
|
|
516
|
+
assertStrictRecord(value, "Codex fallback Watch anchor", {
|
|
517
|
+
schema: literalGuard("agent-knock-knock/codex-user-explicit-fallback-watch-anchor"),
|
|
518
|
+
version: literalGuard(1),
|
|
519
|
+
captured_at: assertTimestamp,
|
|
520
|
+
request_hash: assertSha256,
|
|
521
|
+
codex_version: assertNonEmptyString,
|
|
522
|
+
acceptance_anchor: IGNORE_VALUE,
|
|
523
|
+
anchor_fingerprint: assertSha256
|
|
524
|
+
});
|
|
525
|
+
const acceptance = validateCodexRolloutAcceptanceAnchor(value.acceptance_anchor);
|
|
526
|
+
if (acceptance.captured_at !== value.captured_at) {
|
|
527
|
+
throw new Error("Codex fallback Watch capture time does not match its acceptance anchor");
|
|
528
|
+
}
|
|
529
|
+
assertAnchorFingerprint(value, "Codex fallback Watch anchor");
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (value.schema ===
|
|
533
|
+
"agent-knock-knock/claude-user-explicit-fallback-watch-anchor") {
|
|
534
|
+
if (agent !== "claude") {
|
|
535
|
+
throw new Error("Claude fallback Watch anchor cannot belong to another agent");
|
|
536
|
+
}
|
|
537
|
+
assertStrictRecord(value, "Claude fallback Watch anchor", {
|
|
538
|
+
schema: literalGuard("agent-knock-knock/claude-user-explicit-fallback-watch-anchor"),
|
|
539
|
+
version: literalGuard(1),
|
|
540
|
+
captured_at: assertTimestamp,
|
|
541
|
+
request_hash: assertSha256,
|
|
542
|
+
claude_version: assertNonEmptyString,
|
|
543
|
+
transcript_anchor: IGNORE_VALUE,
|
|
544
|
+
anchor_fingerprint: assertSha256
|
|
545
|
+
});
|
|
546
|
+
const transcript = validatedClaudeTranscriptAnchor(value.transcript_anchor);
|
|
547
|
+
if (transcript.captured_at !== value.captured_at ||
|
|
548
|
+
transcript.cwd !== terminal.workspace) {
|
|
549
|
+
throw new Error("Claude fallback Watch transcript anchor does not match its Watch");
|
|
550
|
+
}
|
|
551
|
+
assertAnchorFingerprint(value, "Claude fallback Watch anchor");
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
throw new Error("terminal Watch anchor schema is unsupported");
|
|
555
|
+
}
|
|
556
|
+
function assertSettlement(value, status) {
|
|
557
|
+
if (status === "active") {
|
|
558
|
+
if (value !== undefined) {
|
|
559
|
+
throw new Error("an active terminal Watch cannot carry a settlement");
|
|
560
|
+
}
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
assertStrictRecord(value, "terminal Watch settlement", SETTLEMENT_FIELDS);
|
|
564
|
+
if (value.kind !== status) {
|
|
565
|
+
throw new Error("terminal Watch settlement kind must match its status");
|
|
566
|
+
}
|
|
567
|
+
if (!["completed", "failed"].includes(status) &&
|
|
568
|
+
(value.completion_text !== undefined ||
|
|
569
|
+
value.completion_id !== undefined ||
|
|
570
|
+
value.completion_timestamp !== undefined)) {
|
|
571
|
+
throw new Error("only completed or failed terminal Watches may carry completion output");
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function assertNotificationOutbox(watch, allowPredecessorCallbackPresentation = false) {
|
|
575
|
+
const seenIds = new Set();
|
|
576
|
+
const seenEvidence = new Set();
|
|
577
|
+
let previousCreatedAt = Date.parse(watch.created_at);
|
|
578
|
+
for (const notification of watch.notification_outbox) {
|
|
579
|
+
assertNotification(notification, watch, allowPredecessorCallbackPresentation);
|
|
580
|
+
if (seenIds.has(notification.notification_id)) {
|
|
581
|
+
throw new Error("terminal Watch notification ids must be unique");
|
|
582
|
+
}
|
|
583
|
+
const evidenceKey = `${notification.kind}:${notification.evidence_fingerprint}`;
|
|
584
|
+
if (seenEvidence.has(evidenceKey)) {
|
|
585
|
+
throw new Error("terminal Watch notification evidence must be unique");
|
|
586
|
+
}
|
|
587
|
+
const createdAt = Date.parse(notification.created_at);
|
|
588
|
+
if (createdAt < previousCreatedAt ||
|
|
589
|
+
createdAt > Date.parse(watch.updated_at)) {
|
|
590
|
+
throw new Error("terminal Watch notification timestamps are not monotonic");
|
|
591
|
+
}
|
|
592
|
+
previousCreatedAt = createdAt;
|
|
593
|
+
seenIds.add(notification.notification_id);
|
|
594
|
+
seenEvidence.add(evidenceKey);
|
|
595
|
+
}
|
|
596
|
+
if (watch.status !== "active") {
|
|
597
|
+
const settlement = watch.settlement;
|
|
598
|
+
const terminalNotifications = watch.notification_outbox.filter((notification) => isTerminalWatchOutcomeNotification(notification.kind));
|
|
599
|
+
if (terminalNotifications.length !== 1 ||
|
|
600
|
+
terminalNotifications[0].kind !== watch.status ||
|
|
601
|
+
terminalNotifications[0].evidence_fingerprint !==
|
|
602
|
+
settlement.evidence_fingerprint) {
|
|
603
|
+
throw new Error("terminal Watch settlement must have exactly one notification");
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
else if (watch.notification_outbox.some((notification) => isTerminalWatchOutcomeNotification(notification.kind))) {
|
|
607
|
+
throw new Error("an active terminal Watch cannot have an outcome notification");
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function assertNotification(value, watch, allowPredecessorCallbackPresentation) {
|
|
611
|
+
assertStrictRecord(value, "terminal Watch notification", NOTIFICATION_FIELDS);
|
|
612
|
+
const notification = value;
|
|
613
|
+
if (notification.kind === "interaction_manual_required") {
|
|
614
|
+
assertTerminalWatchManualInteractionSummary(notification.manual_interaction);
|
|
615
|
+
}
|
|
616
|
+
else if (notification.manual_interaction !== undefined) {
|
|
617
|
+
throw new Error("only a manual-interaction terminal Watch notification may carry an interaction summary");
|
|
618
|
+
}
|
|
619
|
+
const expectedId = terminalWatchNotificationId(watch.watch_id, notification.kind, notification.evidence_fingerprint);
|
|
620
|
+
if (notification.notification_id !== expectedId ||
|
|
621
|
+
notification.idempotency_key !==
|
|
622
|
+
terminalWatchNotificationIdempotencyKey(watch.watch_id, expectedId)) {
|
|
623
|
+
throw new Error("terminal Watch notification identity is not deterministic");
|
|
624
|
+
}
|
|
625
|
+
const [minimumAttempts, receiptFields] = NOTIFICATION_SHAPES[notification.status];
|
|
626
|
+
assertNotificationShape(value, minimumAttempts, receiptFields);
|
|
627
|
+
parseTerminalWatchNotificationCallbackSnapshot(watch, notification, allowPredecessorCallbackPresentation);
|
|
628
|
+
}
|
|
629
|
+
export function assertTerminalWatchManualInteractionSummary(value) {
|
|
630
|
+
assertStrictRecord(value, "terminal Watch manual interaction", {
|
|
631
|
+
kind: literalGuard("questionnaire"),
|
|
632
|
+
response_kind: oneOfGuard(TERMINAL_WATCH_MANUAL_INTERACTION_RESPONSE_KINDS),
|
|
633
|
+
required: (candidate, label) => {
|
|
634
|
+
if (typeof candidate !== "boolean") {
|
|
635
|
+
throw new Error(`${label} must be boolean`);
|
|
636
|
+
}
|
|
637
|
+
},
|
|
638
|
+
current_step: POSITIVE_INTEGER,
|
|
639
|
+
total_steps: POSITIVE_INTEGER,
|
|
640
|
+
parser_status: oneOfGuard(["actionable", "manual_required"]),
|
|
641
|
+
prompt: optionalGuard((candidate, label) => assertBoundedInteractionText(candidate, label, TERMINAL_WATCH_MANUAL_INTERACTION_MAX_PROMPT_CHARACTERS)),
|
|
642
|
+
options: optionalGuard(ARRAY_VALUE),
|
|
643
|
+
manual_reason: optionalGuard(assertReasonCode)
|
|
644
|
+
});
|
|
645
|
+
const summary = value;
|
|
646
|
+
if (summary.total_steps > TERMINAL_WATCH_MANUAL_INTERACTION_MAX_STEPS ||
|
|
647
|
+
summary.current_step > summary.total_steps) {
|
|
648
|
+
throw new Error("terminal Watch manual interaction step is invalid");
|
|
649
|
+
}
|
|
650
|
+
if (summary.parser_status === "manual_required") {
|
|
651
|
+
if (summary.manual_reason === undefined) {
|
|
652
|
+
throw new Error("a manual-only terminal Watch interaction requires a reason");
|
|
653
|
+
}
|
|
654
|
+
if (summary.prompt !== undefined || summary.options !== undefined) {
|
|
655
|
+
throw new Error("a manual-only terminal Watch interaction cannot expose prompt or options");
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
else if (summary.manual_reason !== undefined) {
|
|
659
|
+
throw new Error("an actionable terminal Watch interaction cannot carry a manual reason");
|
|
660
|
+
}
|
|
661
|
+
else if (summary.prompt === undefined) {
|
|
662
|
+
throw new Error("an actionable terminal Watch interaction requires a bounded prompt");
|
|
663
|
+
}
|
|
664
|
+
if (summary.options !== undefined) {
|
|
665
|
+
if (summary.options.length < 1 ||
|
|
666
|
+
summary.options.length > TERMINAL_WATCH_MANUAL_INTERACTION_MAX_OPTIONS ||
|
|
667
|
+
(summary.response_kind !== "single_select" &&
|
|
668
|
+
summary.response_kind !== "multi_select")) {
|
|
669
|
+
throw new Error("terminal Watch manual interaction options are invalid");
|
|
670
|
+
}
|
|
671
|
+
for (const option of summary.options) {
|
|
672
|
+
assertStrictRecord(option, "terminal Watch manual interaction option", {
|
|
673
|
+
label: (candidate, label) => assertBoundedInteractionText(candidate, label, TERMINAL_WATCH_MANUAL_INTERACTION_MAX_OPTION_LABEL_CHARACTERS),
|
|
674
|
+
description: optionalGuard((candidate, label) => assertBoundedInteractionText(candidate, label, TERMINAL_WATCH_MANUAL_INTERACTION_MAX_OPTION_DESCRIPTION_CHARACTERS))
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
export function assertTerminalWatchCurrentInteraction(value, watch) {
|
|
680
|
+
if (value === undefined)
|
|
681
|
+
return;
|
|
682
|
+
assertStrictRecord(value, "terminal Watch current interaction", {
|
|
683
|
+
projection: IGNORE_VALUE,
|
|
684
|
+
aggregate: IGNORE_VALUE
|
|
685
|
+
});
|
|
686
|
+
const projection = validateTerminalInteractionSubjectProjection(value.projection);
|
|
687
|
+
const aggregate = validateTerminalInteractionAggregate(value.aggregate);
|
|
688
|
+
if (projection.subject.kind !== "terminal_watch" ||
|
|
689
|
+
projection.subject.watch_id !== watch.watch_id ||
|
|
690
|
+
projection.subject.anchor_fingerprint !==
|
|
691
|
+
watch.anchor.anchor_fingerprint ||
|
|
692
|
+
aggregate.subject.kind !== "terminal_watch" ||
|
|
693
|
+
!sameTerminalInteractionSubject(projection.subject, aggregate.subject)) {
|
|
694
|
+
throw new Error("terminal Watch interaction subject does not match its exact Watch anchor");
|
|
695
|
+
}
|
|
696
|
+
if (projection.agent !== watch.agent) {
|
|
697
|
+
throw new Error("terminal Watch interaction agent does not match its Watch");
|
|
698
|
+
}
|
|
699
|
+
if (!aggregateMatchesProjection(aggregate, projection)) {
|
|
700
|
+
throw new Error("terminal Watch interaction aggregate does not match its public projection");
|
|
701
|
+
}
|
|
702
|
+
if (watch.interaction_policy === "notify_only" &&
|
|
703
|
+
(projection.response_authority !== "notify_only" ||
|
|
704
|
+
aggregate.response_authority !== "notify_only" ||
|
|
705
|
+
projection.capabilities.respond)) {
|
|
706
|
+
throw new Error("a notify-only terminal Watch cannot persist response authority");
|
|
707
|
+
}
|
|
708
|
+
for (const [timestamp, label] of [
|
|
709
|
+
[projection.expires_at, "projection expiry"],
|
|
710
|
+
[aggregate.created_at, "aggregate creation"],
|
|
711
|
+
[aggregate.expires_at, "aggregate expiry"],
|
|
712
|
+
[aggregate.reservation?.reserved_at, "reservation time"],
|
|
713
|
+
[aggregate.resolution?.resolved_at, "resolution time"]
|
|
714
|
+
]) {
|
|
715
|
+
if (timestamp !== undefined) {
|
|
716
|
+
assertTimestamp(timestamp, `terminal Watch interaction ${label}`);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
const createdAt = Date.parse(aggregate.created_at);
|
|
720
|
+
const watchCreatedAt = Date.parse(watch.created_at);
|
|
721
|
+
const watchUpdatedAt = Date.parse(watch.updated_at);
|
|
722
|
+
if (createdAt < watchCreatedAt ||
|
|
723
|
+
createdAt > watchUpdatedAt ||
|
|
724
|
+
Date.parse(aggregate.expires_at) <= createdAt ||
|
|
725
|
+
(aggregate.reservation !== undefined &&
|
|
726
|
+
(Date.parse(aggregate.reservation.reserved_at) < createdAt ||
|
|
727
|
+
Date.parse(aggregate.reservation.reserved_at) > watchUpdatedAt)) ||
|
|
728
|
+
(aggregate.resolution !== undefined &&
|
|
729
|
+
(Date.parse(aggregate.resolution.resolved_at) <
|
|
730
|
+
Date.parse(aggregate.reservation?.reserved_at ?? aggregate.created_at) ||
|
|
731
|
+
Date.parse(aggregate.resolution.resolved_at) > watchUpdatedAt))) {
|
|
732
|
+
throw new Error("terminal Watch interaction timestamps are not monotonic");
|
|
733
|
+
}
|
|
734
|
+
if (aggregate.resolution !== undefined) {
|
|
735
|
+
assertReasonCode(aggregate.resolution.reason_code, "terminal Watch interaction resolution reason");
|
|
736
|
+
}
|
|
737
|
+
if (aggregate.reservation !== undefined) {
|
|
738
|
+
assertNonEmptyString(aggregate.reservation.attempt_id, "terminal Watch interaction reservation attempt id");
|
|
739
|
+
}
|
|
740
|
+
if (watch.status !== "active" &&
|
|
741
|
+
(aggregate.state === "pending" || aggregate.state === "reserved")) {
|
|
742
|
+
throw new Error("a settled terminal Watch cannot retain an actionable interaction");
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
function assertBoundedInteractionText(value, label, maxCharacters) {
|
|
746
|
+
if (typeof value !== "string" ||
|
|
747
|
+
value.trim().length === 0 ||
|
|
748
|
+
value.length > maxCharacters ||
|
|
749
|
+
/[\u0000-\u001F\u007F-\u009F]/u.test(value)) {
|
|
750
|
+
throw new Error(`${label} exceeds its safe text bound`);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
function assertNotificationShape(value, minimumAttempts, required) {
|
|
754
|
+
if (Number(value.attempts) < minimumAttempts) {
|
|
755
|
+
throw new Error(`terminal Watch ${String(value.status)} notification has no attempt`);
|
|
756
|
+
}
|
|
757
|
+
for (const key of required) {
|
|
758
|
+
if (value[key] === undefined) {
|
|
759
|
+
throw new Error(`terminal Watch ${String(value.status)} notification requires ${key}`);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
const allowed = new Set(required);
|
|
763
|
+
for (const key of Object.keys(NOTIFICATION_RECEIPT_FIELDS)) {
|
|
764
|
+
if (value[key] !== undefined && !allowed.has(key)) {
|
|
765
|
+
throw new Error(`terminal Watch ${String(value.status)} notification cannot carry ${key}`);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
export function assertTerminalWatchAdvance(current, candidate) {
|
|
770
|
+
for (const [label, before, after] of [
|
|
771
|
+
["schema", current.schema, candidate.schema],
|
|
772
|
+
["version", current.version, candidate.version],
|
|
773
|
+
["watch_id", current.watch_id, candidate.watch_id],
|
|
774
|
+
["agent", current.agent, candidate.agent],
|
|
775
|
+
["terminal", current.terminal, candidate.terminal],
|
|
776
|
+
["anchor", current.anchor, candidate.anchor],
|
|
777
|
+
[
|
|
778
|
+
"interaction_policy",
|
|
779
|
+
current.interaction_policy,
|
|
780
|
+
candidate.interaction_policy
|
|
781
|
+
],
|
|
782
|
+
["warnings", current.warnings, candidate.warnings],
|
|
783
|
+
["callback_route", current.callback_route, candidate.callback_route],
|
|
784
|
+
["openclaw_session", current.openclaw_session, candidate.openclaw_session],
|
|
785
|
+
["openclaw_bin", current.openclaw_bin, candidate.openclaw_bin],
|
|
786
|
+
["created_at", current.created_at, candidate.created_at],
|
|
787
|
+
["deadline_at", current.deadline_at, candidate.deadline_at]
|
|
788
|
+
]) {
|
|
789
|
+
if (JSON.stringify(before) !== JSON.stringify(after)) {
|
|
790
|
+
throw new Error(`terminal Watch cannot change immutable ${label}`);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
if (Date.parse(candidate.updated_at) < Date.parse(current.updated_at) ||
|
|
794
|
+
Date.parse(candidate.last_activity_at) < Date.parse(current.last_activity_at)) {
|
|
795
|
+
throw new Error("terminal Watch update cannot move time backwards");
|
|
796
|
+
}
|
|
797
|
+
if (candidate.observation_checkpoint.safe_resume_offset_bytes <
|
|
798
|
+
current.observation_checkpoint.safe_resume_offset_bytes) {
|
|
799
|
+
throw new Error("terminal Watch observation checkpoint cannot move backwards");
|
|
800
|
+
}
|
|
801
|
+
assertFallbackCheckpointAdvance(current.observation_checkpoint, candidate.observation_checkpoint);
|
|
802
|
+
assertTerminalWatchInteractionAdvance(current.current_interaction, candidate.current_interaction);
|
|
803
|
+
if (current.status !== "active" &&
|
|
804
|
+
candidate.status !== current.status) {
|
|
805
|
+
throw new Error("a settled terminal Watch cannot change outcome");
|
|
806
|
+
}
|
|
807
|
+
if (current.settlement !== undefined &&
|
|
808
|
+
JSON.stringify(candidate.settlement) !== JSON.stringify(current.settlement)) {
|
|
809
|
+
throw new Error("terminal Watch settlement is immutable");
|
|
810
|
+
}
|
|
811
|
+
if (current.status !== "active") {
|
|
812
|
+
if (candidate.last_activity_at !== current.last_activity_at ||
|
|
813
|
+
JSON.stringify(candidate.observation_checkpoint) !==
|
|
814
|
+
JSON.stringify(current.observation_checkpoint) ||
|
|
815
|
+
canonicalJson(candidate.current_interaction) !==
|
|
816
|
+
canonicalJson(current.current_interaction) ||
|
|
817
|
+
candidate.notification_outbox.length !== current.notification_outbox.length) {
|
|
818
|
+
throw new Error("a settled terminal Watch may change only notification delivery receipts");
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
else if (candidate.notification_outbox.length >
|
|
822
|
+
current.notification_outbox.length + 1) {
|
|
823
|
+
throw new Error("terminal Watch may append at most one notification per update");
|
|
824
|
+
}
|
|
825
|
+
assertNotificationAdvance(current.notification_outbox, candidate.notification_outbox);
|
|
826
|
+
}
|
|
827
|
+
function assertTerminalWatchInteractionAdvance(current, candidate) {
|
|
828
|
+
if (current === undefined) {
|
|
829
|
+
if (candidate !== undefined && candidate.aggregate.state !== "pending") {
|
|
830
|
+
throw new Error("a terminal Watch interaction must be created pending");
|
|
831
|
+
}
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
if (candidate === undefined) {
|
|
835
|
+
throw new Error("terminal Watch current interaction cannot be removed");
|
|
836
|
+
}
|
|
837
|
+
const before = current.aggregate;
|
|
838
|
+
const after = candidate.aggregate;
|
|
839
|
+
if (!sameTerminalInteractionSubject(before.subject, after.subject)) {
|
|
840
|
+
throw new Error("terminal Watch interaction subject cannot change");
|
|
841
|
+
}
|
|
842
|
+
if (before.interaction_id !== after.interaction_id) {
|
|
843
|
+
if (!["consumed", "response_uncertain", "superseded"].includes(before.state) ||
|
|
844
|
+
after.state !== "pending" ||
|
|
845
|
+
before.surface_id === after.surface_id ||
|
|
846
|
+
Date.parse(after.created_at) < Date.parse(before.resolution?.resolved_at ?? before.created_at)) {
|
|
847
|
+
throw new Error("terminal Watch cannot replace an unresolved interaction");
|
|
848
|
+
}
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
const expected = expectedTerminalWatchInteractionAggregate(before, after);
|
|
852
|
+
if (canonicalJson(expected) !== canonicalJson(after)) {
|
|
853
|
+
throw new Error(`terminal Watch interaction cannot advance ${before.state} to ${after.state}`);
|
|
854
|
+
}
|
|
855
|
+
assertTerminalWatchInteractionProjectionAdvance(current.projection, candidate.projection, before, after);
|
|
856
|
+
}
|
|
857
|
+
function expectedTerminalWatchInteractionAggregate(current, candidate) {
|
|
858
|
+
if (current.state === candidate.state) {
|
|
859
|
+
if (current.state === "pending" &&
|
|
860
|
+
(current.expires_at !== candidate.expires_at ||
|
|
861
|
+
current.response_authority !== candidate.response_authority)) {
|
|
862
|
+
return reduceTerminalInteractionAggregate(current, {
|
|
863
|
+
type: "refresh",
|
|
864
|
+
expires_at: candidate.expires_at,
|
|
865
|
+
response_authority: candidate.response_authority
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
return current;
|
|
869
|
+
}
|
|
870
|
+
if (candidate.state === "reserved" && candidate.reservation) {
|
|
871
|
+
return reduceTerminalInteractionAggregate(current, {
|
|
872
|
+
type: "reserve",
|
|
873
|
+
attempt_id: candidate.reservation.attempt_id,
|
|
874
|
+
response_hash: candidate.reservation.response_hash,
|
|
875
|
+
at: candidate.reservation.reserved_at
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
if (current.state === "reserved" && candidate.state === "pending") {
|
|
879
|
+
return reduceTerminalInteractionAggregate(current, { type: "release" });
|
|
880
|
+
}
|
|
881
|
+
if ((candidate.state === "consumed" ||
|
|
882
|
+
candidate.state === "response_uncertain") &&
|
|
883
|
+
candidate.resolution) {
|
|
884
|
+
return reduceTerminalInteractionAggregate(current, {
|
|
885
|
+
type: candidate.state === "consumed"
|
|
886
|
+
? "consume"
|
|
887
|
+
: "response_uncertain",
|
|
888
|
+
at: candidate.resolution.resolved_at,
|
|
889
|
+
reason_code: candidate.resolution.reason_code
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
if (candidate.state === "superseded" && candidate.resolution) {
|
|
893
|
+
return reduceTerminalInteractionAggregate(current, {
|
|
894
|
+
type: "supersede",
|
|
895
|
+
at: candidate.resolution.resolved_at,
|
|
896
|
+
reason_code: candidate.resolution.reason_code
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
return current;
|
|
900
|
+
}
|
|
901
|
+
function assertTerminalWatchInteractionProjectionAdvance(current, candidate, before, after) {
|
|
902
|
+
if (canonicalJson(current) === canonicalJson(candidate))
|
|
903
|
+
return;
|
|
904
|
+
if (before.state === "pending" && after.state === "pending") {
|
|
905
|
+
const expected = {
|
|
906
|
+
...current,
|
|
907
|
+
state: candidate.state,
|
|
908
|
+
expires_at: after.expires_at,
|
|
909
|
+
response_authority: after.response_authority,
|
|
910
|
+
capabilities: candidate.capabilities
|
|
911
|
+
};
|
|
912
|
+
if (canonicalJson(expected) === canonicalJson(candidate))
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
if (after.state === "response_uncertain") {
|
|
916
|
+
const expected = {
|
|
917
|
+
...current,
|
|
918
|
+
state: "response_uncertain",
|
|
919
|
+
capabilities: { ...current.capabilities, respond: false }
|
|
920
|
+
};
|
|
921
|
+
if (canonicalJson(expected) === canonicalJson(candidate))
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
throw new Error("terminal Watch interaction public projection changed outside a safe state transition");
|
|
925
|
+
}
|
|
926
|
+
function assertFallbackCheckpointAdvance(current, candidate) {
|
|
927
|
+
if (!("schema" in current)) {
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
if (current.schema ===
|
|
931
|
+
"agent-knock-knock/terminal-activity-watch-checkpoint") {
|
|
932
|
+
if (!("schema" in candidate) ||
|
|
933
|
+
candidate.schema !== current.schema) {
|
|
934
|
+
throw new Error("terminal activity Watch checkpoint schema cannot change");
|
|
935
|
+
}
|
|
936
|
+
if (current.has_seen_activity && !candidate.has_seen_activity) {
|
|
937
|
+
throw new Error("terminal activity Watch cannot forget observed activity");
|
|
938
|
+
}
|
|
939
|
+
if (candidate.consecutive_idle_observations >
|
|
940
|
+
current.consecutive_idle_observations + 1) {
|
|
941
|
+
throw new Error("terminal activity Watch idle observations cannot skip supervision sweeps");
|
|
942
|
+
}
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
const fallbackSchema = current.schema ===
|
|
946
|
+
"agent-knock-knock/codex-user-explicit-fallback-watch-checkpoint" ||
|
|
947
|
+
current.schema ===
|
|
948
|
+
"agent-knock-knock/claude-user-explicit-fallback-watch-checkpoint";
|
|
949
|
+
if (!fallbackSchema)
|
|
950
|
+
return;
|
|
951
|
+
if (!("schema" in candidate) ||
|
|
952
|
+
candidate.schema !== current.schema) {
|
|
953
|
+
throw new Error("fallback Watch checkpoint schema cannot change");
|
|
954
|
+
}
|
|
955
|
+
if (current.acceptance_evidence === undefined) {
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
const currentIdentity = current.schema ===
|
|
959
|
+
"agent-knock-knock/codex-user-explicit-fallback-watch-checkpoint"
|
|
960
|
+
? current.accepted_identity
|
|
961
|
+
: current.accepted_prompt_uuid;
|
|
962
|
+
const candidateIdentity = candidate.schema ===
|
|
963
|
+
"agent-knock-knock/codex-user-explicit-fallback-watch-checkpoint"
|
|
964
|
+
? candidate.accepted_identity
|
|
965
|
+
: candidate.accepted_prompt_uuid;
|
|
966
|
+
if (canonicalJson(candidate.acceptance_evidence) !==
|
|
967
|
+
canonicalJson(current.acceptance_evidence) ||
|
|
968
|
+
canonicalJson(candidateIdentity) !== canonicalJson(currentIdentity)) {
|
|
969
|
+
throw new Error("fallback Watch accepted identity cannot change");
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
function assertNotificationAdvance(current, candidate) {
|
|
973
|
+
if (candidate.length < current.length) {
|
|
974
|
+
throw new Error("terminal Watch notification outbox is append-only");
|
|
975
|
+
}
|
|
976
|
+
for (let index = 0; index < current.length; index += 1) {
|
|
977
|
+
const before = current[index];
|
|
978
|
+
const after = candidate[index];
|
|
979
|
+
for (const key of [
|
|
980
|
+
"notification_id",
|
|
981
|
+
"idempotency_key",
|
|
982
|
+
"kind",
|
|
983
|
+
"evidence_fingerprint",
|
|
984
|
+
"reason_code",
|
|
985
|
+
"created_at"
|
|
986
|
+
]) {
|
|
987
|
+
if (before[key] !== after[key]) {
|
|
988
|
+
throw new Error(`terminal Watch notification cannot change immutable ${key}`);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
if (canonicalJson(before.manual_interaction) !==
|
|
992
|
+
canonicalJson(after.manual_interaction)) {
|
|
993
|
+
throw new Error("terminal Watch notification cannot change immutable manual_interaction");
|
|
994
|
+
}
|
|
995
|
+
for (const key of ["callback_route", "callback_envelope"]) {
|
|
996
|
+
const existed = Object.hasOwn(before, key);
|
|
997
|
+
const exists = Object.hasOwn(after, key);
|
|
998
|
+
if (!existed && exists) {
|
|
999
|
+
const reclaimed = before.status === "delivering" &&
|
|
1000
|
+
after.status === "delivering" &&
|
|
1001
|
+
after.attempt_id !== before.attempt_id;
|
|
1002
|
+
const claimed = (before.status === "pending" ||
|
|
1003
|
+
before.status === "failed") &&
|
|
1004
|
+
after.status === "delivering";
|
|
1005
|
+
if (!claimed && !reclaimed) {
|
|
1006
|
+
throw new Error("terminal Watch notification callback snapshot may be backfilled only while claiming delivery");
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
if (existed && (!exists || canonicalJson(before[key]) !== canonicalJson(after[key]))) {
|
|
1010
|
+
throw new Error(`terminal Watch notification cannot change immutable ${key}`);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
assertNotificationStatusAdvance(before, after);
|
|
1014
|
+
}
|
|
1015
|
+
for (const added of candidate.slice(current.length)) {
|
|
1016
|
+
if (added.status !== "pending" || added.attempts !== 0) {
|
|
1017
|
+
throw new Error("a terminal Watch notification must be appended pending");
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
function assertNotificationStatusAdvance(current, candidate) {
|
|
1022
|
+
const allowed = {
|
|
1023
|
+
pending: ["pending", "delivering", "superseded"],
|
|
1024
|
+
delivering: [
|
|
1025
|
+
"delivering", "failed", "delivered", "superseded"
|
|
1026
|
+
],
|
|
1027
|
+
failed: ["failed", "delivering", "superseded"],
|
|
1028
|
+
delivered: ["delivered"],
|
|
1029
|
+
superseded: ["superseded"]
|
|
1030
|
+
};
|
|
1031
|
+
if (!allowed[current.status].includes(candidate.status)) {
|
|
1032
|
+
throw new Error(`terminal Watch notification cannot advance ${current.status} to ${candidate.status}`);
|
|
1033
|
+
}
|
|
1034
|
+
const reclaimed = current.status === "delivering" &&
|
|
1035
|
+
candidate.status === "delivering" &&
|
|
1036
|
+
candidate.attempt_id !== current.attempt_id;
|
|
1037
|
+
const claimed = ["pending", "failed"].includes(current.status) &&
|
|
1038
|
+
candidate.status === "delivering";
|
|
1039
|
+
const expectedAttempts = claimed || reclaimed
|
|
1040
|
+
? current.attempts + 1
|
|
1041
|
+
: current.attempts;
|
|
1042
|
+
if (candidate.attempts !== expectedAttempts) {
|
|
1043
|
+
throw new Error("terminal Watch notification attempt count is not monotonic");
|
|
1044
|
+
}
|
|
1045
|
+
if (current.status === candidate.status &&
|
|
1046
|
+
!reclaimed &&
|
|
1047
|
+
JSON.stringify(current) !== JSON.stringify(candidate)) {
|
|
1048
|
+
throw new Error("terminal Watch notification receipt cannot change without a phase advance");
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
//# sourceMappingURL=terminal-watch-codec.js.map
|