@scotthuang/agent-knock-knock 0.13.16 → 0.13.18
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 +15 -0
- package/README.md +4 -4
- package/dist/src/claude-terminal-agent-adapter.js +1 -1
- package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
- package/dist/src/codex-app-server-interaction-client.js +68 -26
- package/dist/src/codex-app-server-interaction-client.js.map +1 -1
- package/dist/src/codex-app-server-read-client.js +52 -20
- package/dist/src/codex-app-server-read-client.js.map +1 -1
- package/dist/src/codex-astra-composer-proof.js +1 -1
- package/dist/src/codex-astra-composer-proof.js.map +1 -1
- package/dist/src/codex-async-question-adapter.js +14 -4
- package/dist/src/codex-async-question-adapter.js.map +1 -1
- package/dist/src/codex-lifecycle-compatibility.d.ts +1 -1
- package/dist/src/codex-lifecycle-compatibility.js +9 -3
- package/dist/src/codex-lifecycle-compatibility.js.map +1 -1
- package/dist/src/codex-open-rollout-inventory.d.ts +51 -0
- package/dist/src/codex-open-rollout-inventory.js +365 -0
- package/dist/src/codex-open-rollout-inventory.js.map +1 -0
- package/dist/src/codex-paginated-thread-binding.js +1 -1
- package/dist/src/codex-paginated-thread-binding.js.map +1 -1
- package/dist/src/codex-permission-surface.d.ts +1 -1
- package/dist/src/codex-permission-surface.js +1 -1
- package/dist/src/codex-permission-surface.js.map +1 -1
- package/dist/src/codex-rollout-bound-completion.d.ts +44 -0
- package/dist/src/codex-rollout-bound-completion.js +357 -0
- package/dist/src/codex-rollout-bound-completion.js.map +1 -0
- package/dist/src/codex-rollout-file.d.ts +15 -0
- package/dist/src/codex-rollout-file.js +98 -0
- package/dist/src/codex-rollout-file.js.map +1 -0
- package/dist/src/codex-rollout-human-task.d.ts +61 -0
- package/dist/src/codex-rollout-human-task.js +555 -0
- package/dist/src/codex-rollout-human-task.js.map +1 -0
- package/dist/src/codex-rollout-interaction-evidence.d.ts +48 -0
- package/dist/src/codex-rollout-interaction-evidence.js +521 -0
- package/dist/src/codex-rollout-interaction-evidence.js.map +1 -0
- package/dist/src/codex-rollout-inventory.d.ts +5 -0
- package/dist/src/codex-rollout-inventory.js +81 -0
- package/dist/src/codex-rollout-inventory.js.map +1 -0
- package/dist/src/codex-rollout-records.d.ts +49 -0
- package/dist/src/codex-rollout-records.js +298 -0
- package/dist/src/codex-rollout-records.js.map +1 -0
- package/dist/src/codex-sqlite-thread-query.d.ts +85 -0
- package/dist/src/codex-sqlite-thread-query.js +440 -0
- package/dist/src/codex-sqlite-thread-query.js.map +1 -0
- package/dist/src/codex-status-history-navigation.d.ts +1 -1
- package/dist/src/codex-status-history-navigation.js +1 -1
- package/dist/src/codex-status-history-navigation.js.map +1 -1
- package/dist/src/codex-store-adapter.d.ts +3 -50
- package/dist/src/codex-store-adapter.js +5 -796
- package/dist/src/codex-store-adapter.js.map +1 -1
- package/dist/src/codex-terminal-agent-adapter.js +4 -2
- package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
- package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +4 -134
- package/dist/src/native-thread-lifecycle-cli-adapter.js +19 -633
- package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
- package/dist/src/native-thread-lifecycle-cli-contract.d.ts +138 -0
- package/dist/src/native-thread-lifecycle-cli-contract.js +2 -0
- package/dist/src/native-thread-lifecycle-cli-contract.js.map +1 -0
- package/dist/src/semantic-permission-control.js +33 -6
- package/dist/src/semantic-permission-control.js.map +1 -1
- package/dist/src/semantic-tool-descriptions.d.ts +26 -0
- package/dist/src/semantic-tool-descriptions.js +259 -0
- package/dist/src/semantic-tool-descriptions.js.map +1 -0
- package/dist/src/semantic-tool-runtime.js +45 -37
- package/dist/src/semantic-tool-runtime.js.map +1 -1
- package/dist/src/semantic-tool-schemas.js +134 -33
- package/dist/src/semantic-tool-schemas.js.map +1 -1
- package/dist/src/terminal-action-contracts.d.ts +4 -0
- package/dist/src/terminal-action-contracts.js +695 -0
- package/dist/src/terminal-action-contracts.js.map +1 -0
- package/dist/src/terminal-composer-classifier.d.ts +1 -1
- package/dist/src/terminal-composer-classifier.js +2 -2
- package/dist/src/terminal-composer-classifier.js.map +1 -1
- package/dist/src/terminal-interaction-response-bridge.js +1 -1
- package/dist/src/terminal-interaction-response-bridge.js.map +1 -1
- package/dist/src/terminal-list-renderer.d.ts +1 -2
- package/dist/src/terminal-list-renderer.js +8 -424
- package/dist/src/terminal-list-renderer.js.map +1 -1
- package/dist/src/terminal-model-control-action-policy.d.ts +1 -1
- package/dist/src/terminal-model-control-availability.d.ts +1 -1
- package/dist/src/terminal-model-control-availability.js +1 -1
- package/dist/src/terminal-model-control-availability.js.map +1 -1
- package/dist/src/terminal-model-control-cli-adapter.d.ts +34 -0
- package/dist/src/terminal-model-control-cli-adapter.js +669 -0
- package/dist/src/terminal-model-control-cli-adapter.js.map +1 -0
- package/dist/src/terminal-model-control-contract.d.ts +198 -0
- package/dist/src/terminal-model-control-contract.js +15 -0
- package/dist/src/terminal-model-control-contract.js.map +1 -0
- package/dist/src/terminal-model-control-input.d.ts +29 -0
- package/dist/src/terminal-model-control-input.js +86 -0
- package/dist/src/terminal-model-control-input.js.map +1 -0
- package/dist/src/terminal-model-control-profile.d.ts +5 -1
- package/dist/src/terminal-model-control-profile.js +21 -7
- package/dist/src/terminal-model-control-profile.js.map +1 -1
- package/dist/src/terminal-model-control-subject.d.ts +52 -0
- package/dist/src/terminal-model-control-subject.js +73 -0
- package/dist/src/terminal-model-control-subject.js.map +1 -1
- package/dist/src/terminal-model-control-surface.d.ts +82 -0
- package/dist/src/terminal-model-control-surface.js +688 -0
- package/dist/src/terminal-model-control-surface.js.map +1 -0
- package/dist/src/terminal-model-control.d.ts +6 -270
- package/dist/src/terminal-model-control.js +11 -988
- package/dist/src/terminal-model-control.js.map +1 -1
- package/dist/src/terminal-model-selection.d.ts +33 -0
- package/dist/src/terminal-model-selection.js +143 -0
- package/dist/src/terminal-model-selection.js.map +1 -0
- package/dist/src/terminal-monitor-application-service.js +38 -59
- package/dist/src/terminal-monitor-application-service.js.map +1 -1
- package/dist/src/terminal-monitor-attention-store.d.ts +65 -0
- package/dist/src/terminal-monitor-attention-store.js +518 -0
- package/dist/src/terminal-monitor-attention-store.js.map +1 -0
- package/dist/src/terminal-monitor-collateral-recovery.d.ts +12 -0
- package/dist/src/terminal-monitor-collateral-recovery.js +415 -0
- package/dist/src/terminal-monitor-collateral-recovery.js.map +1 -0
- package/dist/src/terminal-monitor-interaction-projection.d.ts +19 -0
- package/dist/src/terminal-monitor-interaction-projection.js +48 -0
- package/dist/src/terminal-monitor-interaction-projection.js.map +1 -0
- package/dist/src/terminal-monitor-startup-recovery.d.ts +15 -0
- package/dist/src/terminal-monitor-startup-recovery.js +767 -0
- package/dist/src/terminal-monitor-startup-recovery.js.map +1 -0
- package/dist/src/terminal-monitor-state-cli-adapter.d.ts +2 -104
- package/dist/src/terminal-monitor-state-cli-adapter.js +28 -1640
- package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -1
- package/dist/src/terminal-monitor-state-contract.d.ts +108 -0
- package/dist/src/terminal-monitor-state-contract.js +8 -0
- package/dist/src/terminal-monitor-state-contract.js.map +1 -0
- package/dist/src/terminal-native-inspection-bridge.d.ts +4 -153
- package/dist/src/terminal-native-inspection-bridge.js +7 -646
- package/dist/src/terminal-native-inspection-bridge.js.map +1 -1
- package/dist/src/terminal-native-inspection-contract.d.ts +128 -0
- package/dist/src/terminal-native-inspection-contract.js +38 -0
- package/dist/src/terminal-native-inspection-contract.js.map +1 -0
- package/dist/src/terminal-native-inspection-profile.d.ts +15 -0
- package/dist/src/terminal-native-inspection-profile.js +189 -0
- package/dist/src/terminal-native-inspection-profile.js.map +1 -0
- package/dist/src/terminal-native-inspection-proof.d.ts +62 -0
- package/dist/src/terminal-native-inspection-proof.js +362 -0
- package/dist/src/terminal-native-inspection-proof.js.map +1 -0
- package/dist/src/terminal-permission-control-bridge.js +1 -1
- package/dist/src/terminal-permission-control-bridge.js.map +1 -1
- package/dist/src/terminal-permission-control.js +5 -1
- package/dist/src/terminal-permission-control.js.map +1 -1
- package/dist/src/terminal-questionnaire-adapter.js +5 -2
- package/dist/src/terminal-questionnaire-adapter.js.map +1 -1
- package/dist/src/terminal-runtime-cli-adapter.d.ts +2 -1
- package/dist/src/terminal-runtime-cli-adapter.js +1 -1
- package/dist/src/terminal-runtime-cli-adapter.js.map +1 -1
- package/dist/src/terminal-submission-acceptance.d.ts +5 -148
- package/dist/src/terminal-submission-acceptance.js +21 -1845
- package/dist/src/terminal-submission-acceptance.js.map +1 -1
- package/dist/src/terminal-watch-anchor-capture.d.ts +9 -0
- package/dist/src/terminal-watch-anchor-capture.js +139 -0
- package/dist/src/terminal-watch-anchor-capture.js.map +1 -0
- package/dist/src/terminal-watch-cli-adapter.d.ts +3 -117
- package/dist/src/terminal-watch-cli-adapter.js +10 -1992
- package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
- package/dist/src/terminal-watch-cli-contract.d.ts +117 -0
- package/dist/src/terminal-watch-cli-contract.js +2 -0
- package/dist/src/terminal-watch-cli-contract.js.map +1 -0
- package/dist/src/terminal-watch-fallback-observation.d.ts +15 -0
- package/dist/src/terminal-watch-fallback-observation.js +249 -0
- package/dist/src/terminal-watch-fallback-observation.js.map +1 -0
- package/dist/src/terminal-watch-interaction-observation.d.ts +26 -0
- package/dist/src/terminal-watch-interaction-observation.js +262 -0
- package/dist/src/terminal-watch-interaction-observation.js.map +1 -0
- package/dist/src/terminal-watch-observation-adapter.d.ts +4 -0
- package/dist/src/terminal-watch-observation-adapter.js +307 -0
- package/dist/src/terminal-watch-observation-adapter.js.map +1 -0
- package/dist/src/terminal-watch-observation-evidence.d.ts +25 -0
- package/dist/src/terminal-watch-observation-evidence.js +184 -0
- package/dist/src/terminal-watch-observation-evidence.js.map +1 -0
- package/dist/src/terminal-watch-response-authority.d.ts +6 -0
- package/dist/src/terminal-watch-response-authority.js +333 -0
- package/dist/src/terminal-watch-response-authority.js.map +1 -0
- package/dist/src/terminal-watch-response-cli-adapter.d.ts +3 -0
- package/dist/src/terminal-watch-response-cli-adapter.js +230 -0
- package/dist/src/terminal-watch-response-cli-adapter.js.map +1 -0
- package/dist/src/terminal-watch-terminal-identity.d.ts +39 -0
- package/dist/src/terminal-watch-terminal-identity.js +380 -0
- package/dist/src/terminal-watch-terminal-identity.js.map +1 -0
- package/docs/codex-0.160.0-compatibility.md +126 -0
- package/docs/codex-permissions.md +12 -1
- package/package.json +3 -2
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +3 -3
|
@@ -0,0 +1,767 @@
|
|
|
1
|
+
/** Reconcile startup receipts without sending terminal input; preserve terminal/Store/state lock order. */
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { validateCodexRolloutAcceptanceAnchor } from "./terminal-submission-acceptance.js";
|
|
5
|
+
import { deferredForegroundActiveEnterDispatchedAt, deferredForegroundActiveMessageId, deferredForegroundActivePreparedAt, deferredForegroundActiveTextInjectedAt, isDeferredForegroundSubmissionRetryPending, loadDeferredForegroundTransfer, saveDeferredForegroundTransfer } from "./deferred-foreground-transfer.js";
|
|
6
|
+
import { isFinalDeferredForegroundTransferStatus } from "./deferred-foreground-transfer-policy.js";
|
|
7
|
+
import { executorForConversation, effectiveTurnStatus, isExplicitUserAbandonedManagementTurn, isTerminalDispatchOwnerReleasedStatus, sessionIdForConversation, turnIdForConversation } from "./protocol.js";
|
|
8
|
+
import { appendEvent, loadState, pathsForConversationDir, saveState, withStoreWriterLeaseAsync } from "./store.js";
|
|
9
|
+
import { cliPid } from "./cli-runtime-context.js";
|
|
10
|
+
import { terminalControlFromTakeover } from "./terminal-runtime-cli-adapter.js";
|
|
11
|
+
import { terminalControlsShareIncarnation } from "./terminal-authority-policy.js";
|
|
12
|
+
import { terminalControlEvidenceMatches } from "./terminal-control-ref.js";
|
|
13
|
+
import { applyTerminalBridgeSubmission, terminalAcceptanceEvidenceForConversation, terminalBridgeRequestFingerprint, terminalBridgeSubmission } from "./terminal-dispatch-receipt.js";
|
|
14
|
+
import { terminalMonitorDeadlineAt } from "./terminal-monitor-decision-policy.js";
|
|
15
|
+
import { sameCanonicalStatePath } from "./terminal-dispatch-ledger-codec.js";
|
|
16
|
+
import { terminalSubmissionPayload } from "./terminal-dispatch-execution.js";
|
|
17
|
+
import { loadTerminalSubmissionRetry, decideTerminalSubmissionRetryStartup, projectTerminalSubmissionRetryPending, saveTerminalSubmissionRetry, terminalSubmissionRetryLedgerFields } from "./terminal-submission-retry-service.js";
|
|
18
|
+
import { callbackExpectedForConversation, callbackRouteFingerprintForConversation } from "./callback-route-authority.js";
|
|
19
|
+
import { canonicalJson } from "./canonical-json.js";
|
|
20
|
+
import { isRecord, nonBlankString } from "./value-guards.js";
|
|
21
|
+
import { takeoverFor } from "./terminal-monitor-state-contract.js";
|
|
22
|
+
export class TerminalMonitorStartupRecovery {
|
|
23
|
+
#dependencies;
|
|
24
|
+
#stateFileLock;
|
|
25
|
+
constructor(dependencies, stateFileLock) {
|
|
26
|
+
this.#dependencies = dependencies;
|
|
27
|
+
this.#stateFileLock = stateFileLock;
|
|
28
|
+
}
|
|
29
|
+
async recoverSubmissionRetry(storeDir, initialConversation, paths) {
|
|
30
|
+
const initialAttempt = loadTerminalSubmissionRetry(paths.statePath);
|
|
31
|
+
if (!initialAttempt) {
|
|
32
|
+
return initialConversation;
|
|
33
|
+
}
|
|
34
|
+
const initialTakeover = takeoverFor(initialConversation);
|
|
35
|
+
const initialControl = terminalControlFromTakeover(initialTakeover);
|
|
36
|
+
if (executorForConversation(initialConversation).kind !== "codex" ||
|
|
37
|
+
!initialControl) {
|
|
38
|
+
throw new Error("submission retry startup recovery lost its exact Codex terminal authority");
|
|
39
|
+
}
|
|
40
|
+
const canonical = pathsForConversationDir(path.dirname(paths.statePath));
|
|
41
|
+
if (path.resolve(canonical.storeDir) !== path.resolve(storeDir) ||
|
|
42
|
+
path.resolve(canonical.statePath) !== path.resolve(paths.statePath) ||
|
|
43
|
+
path.resolve(canonical.logPath) !== path.resolve(paths.logPath)) {
|
|
44
|
+
throw new Error("submission retry startup recovery is outside its canonical Turn Store");
|
|
45
|
+
}
|
|
46
|
+
const releaseTerminal = this.#dependencies.dispatch.repository.acquire(storeDir, initialControl, { timeoutMs: 30000 });
|
|
47
|
+
try {
|
|
48
|
+
return await withStoreWriterLeaseAsync(storeDir, async () => {
|
|
49
|
+
const releaseState = this.#stateFileLock.acquire(`${paths.statePath}.lock`);
|
|
50
|
+
try {
|
|
51
|
+
let conversation = loadState(paths.statePath);
|
|
52
|
+
const takeover = takeoverFor(conversation);
|
|
53
|
+
const control = terminalControlFromTakeover(takeover);
|
|
54
|
+
const attempt = loadTerminalSubmissionRetry(paths.statePath);
|
|
55
|
+
if (!attempt) {
|
|
56
|
+
return conversation;
|
|
57
|
+
}
|
|
58
|
+
if (executorForConversation(conversation).kind !== "codex" ||
|
|
59
|
+
!control ||
|
|
60
|
+
!terminalControlsShareIncarnation(control, initialControl)) {
|
|
61
|
+
throw new Error("submission retry startup recovery terminal authority changed");
|
|
62
|
+
}
|
|
63
|
+
const callbackDelivery = isRecord(conversation.callback_delivery)
|
|
64
|
+
? conversation.callback_delivery
|
|
65
|
+
: undefined;
|
|
66
|
+
if (callbackDelivery?.status === "delivered" ||
|
|
67
|
+
isTerminalDispatchOwnerReleasedStatus(effectiveTurnStatus(conversation))) {
|
|
68
|
+
return conversation;
|
|
69
|
+
}
|
|
70
|
+
const submission = terminalBridgeSubmission(conversation);
|
|
71
|
+
const ledger = this.#dependencies.dispatch.repository.load(control);
|
|
72
|
+
if (!submission || !ledger) {
|
|
73
|
+
throw new Error("submission retry startup recovery lacks its Turn or ledger receipt");
|
|
74
|
+
}
|
|
75
|
+
if (!["agent_accepted"].includes(String(submission.status)) &&
|
|
76
|
+
!["agent_accepted"].includes(String(ledger.status)) &&
|
|
77
|
+
!["enter_dispatched", "agent_accepted"].includes(attempt.state)) {
|
|
78
|
+
return conversation;
|
|
79
|
+
}
|
|
80
|
+
const authority = assertTerminalSubmissionRetryStartupAuthority({
|
|
81
|
+
storeDir,
|
|
82
|
+
paths,
|
|
83
|
+
conversation,
|
|
84
|
+
takeover,
|
|
85
|
+
submission,
|
|
86
|
+
ledger,
|
|
87
|
+
attempt,
|
|
88
|
+
control,
|
|
89
|
+
ledgerMatchesControl: this.#dependencies.dispatch.repository
|
|
90
|
+
.matchesControl(ledger, control)
|
|
91
|
+
});
|
|
92
|
+
const startup = decideTerminalSubmissionRetryStartup({
|
|
93
|
+
attempt,
|
|
94
|
+
submission,
|
|
95
|
+
ledger
|
|
96
|
+
});
|
|
97
|
+
if (startup.action === "refuse") {
|
|
98
|
+
throw new Error(startup.reason);
|
|
99
|
+
}
|
|
100
|
+
if (startup.action === "finalize_accepted") {
|
|
101
|
+
return finalizeTerminalSubmissionRetryStartupAccepted({
|
|
102
|
+
storeDir,
|
|
103
|
+
paths,
|
|
104
|
+
conversation,
|
|
105
|
+
submission,
|
|
106
|
+
ledger,
|
|
107
|
+
attempt,
|
|
108
|
+
control,
|
|
109
|
+
authority,
|
|
110
|
+
saveLedger: (next) => this.#dependencies.dispatch.repository
|
|
111
|
+
.save(control, next)
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (startup.action === "repair_terminal_ledger") {
|
|
115
|
+
reconcileTerminalSubmissionRetryStartupTerminalLedger({
|
|
116
|
+
paths,
|
|
117
|
+
conversation,
|
|
118
|
+
ledger,
|
|
119
|
+
attempt,
|
|
120
|
+
startup,
|
|
121
|
+
saveLedger: (next) => this.#dependencies.dispatch.repository
|
|
122
|
+
.save(control, next)
|
|
123
|
+
});
|
|
124
|
+
return conversation;
|
|
125
|
+
}
|
|
126
|
+
if (startup.action === "repair_terminal_state") {
|
|
127
|
+
return reconcileTerminalSubmissionRetryStartupTerminalState({
|
|
128
|
+
storeDir,
|
|
129
|
+
paths,
|
|
130
|
+
conversation,
|
|
131
|
+
submission,
|
|
132
|
+
attempt,
|
|
133
|
+
control,
|
|
134
|
+
authority,
|
|
135
|
+
startup
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (startup.action === "no_change")
|
|
139
|
+
return conversation;
|
|
140
|
+
const projection = authority.projection;
|
|
141
|
+
if (!projection) {
|
|
142
|
+
return conversation;
|
|
143
|
+
}
|
|
144
|
+
const receiptAlreadyPending = submission.status === "enter_dispatched";
|
|
145
|
+
if (receiptAlreadyPending
|
|
146
|
+
? conversation.status !== "waiting_for_agent"
|
|
147
|
+
: !["stalled", "waiting_for_agent"].includes(String(conversation.status))) {
|
|
148
|
+
return conversation;
|
|
149
|
+
}
|
|
150
|
+
let repairedTransfer = false;
|
|
151
|
+
if (authority.deferredTransferId) {
|
|
152
|
+
repairedTransfer = mirrorDeferredSubmissionRetryEnter({
|
|
153
|
+
storeDir,
|
|
154
|
+
transferId: authority.deferredTransferId,
|
|
155
|
+
attempt,
|
|
156
|
+
messageId: projection.messageId,
|
|
157
|
+
preparedAt: projection.preparedAt,
|
|
158
|
+
textInjectedAt: projection.textInjectedAt,
|
|
159
|
+
requestHash: authority.requestHash,
|
|
160
|
+
sessionId: sessionIdForConversation(conversation),
|
|
161
|
+
turnId: turnIdForConversation(conversation),
|
|
162
|
+
statePath: paths.statePath
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
const stateNeedsRepair = !receiptAlreadyPending;
|
|
166
|
+
if (receiptAlreadyPending && !terminalSubmissionRetryStateIsPending({
|
|
167
|
+
conversation,
|
|
168
|
+
submission,
|
|
169
|
+
projection
|
|
170
|
+
})) {
|
|
171
|
+
return conversation;
|
|
172
|
+
}
|
|
173
|
+
if (stateNeedsRepair) {
|
|
174
|
+
const epochConversation = terminalSubmissionRetryMonitorEpoch(conversation, projection.enterDispatchedAt);
|
|
175
|
+
conversation = applyTerminalBridgeSubmission({
|
|
176
|
+
conversation: epochConversation,
|
|
177
|
+
messageId: projection.messageId,
|
|
178
|
+
messageType: authority.messageType,
|
|
179
|
+
requestText: authority.requestText,
|
|
180
|
+
status: "enter_dispatched",
|
|
181
|
+
preparedAt: projection.preparedAt,
|
|
182
|
+
textInjectedAt: projection.textInjectedAt,
|
|
183
|
+
enterDispatchedAt: projection.enterDispatchedAt,
|
|
184
|
+
lastProvenStage: "enter_dispatched"
|
|
185
|
+
}, {
|
|
186
|
+
dispatcherPid: cliPid(),
|
|
187
|
+
storeDir,
|
|
188
|
+
terminalControl: control
|
|
189
|
+
});
|
|
190
|
+
saveState(paths.statePath, conversation);
|
|
191
|
+
}
|
|
192
|
+
const ledgerNeedsRepair = ledger.status !== "enter_dispatched" ||
|
|
193
|
+
nonBlankString(ledger.text_injected_at) !==
|
|
194
|
+
projection.textInjectedAt ||
|
|
195
|
+
nonBlankString(ledger.enter_dispatched_at) !==
|
|
196
|
+
projection.enterDispatchedAt ||
|
|
197
|
+
ledger.submission_retry_state !== "enter_dispatched" ||
|
|
198
|
+
ledger.submission_retry_revision !== attempt.revision;
|
|
199
|
+
if (ledgerNeedsRepair) {
|
|
200
|
+
this.#dependencies.dispatch.repository.save(control, {
|
|
201
|
+
...ledger,
|
|
202
|
+
...terminalSubmissionRetryLedgerFields(attempt),
|
|
203
|
+
status: "enter_dispatched",
|
|
204
|
+
text_injected_at: projection.textInjectedAt,
|
|
205
|
+
enter_dispatched_at: projection.enterDispatchedAt,
|
|
206
|
+
enter_not_attempted_at: undefined,
|
|
207
|
+
enter_not_attempted_reason: undefined,
|
|
208
|
+
uncertain_at: undefined,
|
|
209
|
+
safe_to_retry: undefined,
|
|
210
|
+
acceptance_evidence: undefined,
|
|
211
|
+
agent_accepted_at: undefined,
|
|
212
|
+
not_accepted_at: undefined,
|
|
213
|
+
dispatcher_pid: null
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
if (repairedTransfer || stateNeedsRepair || ledgerNeedsRepair) {
|
|
217
|
+
appendEvent(paths.logPath, {
|
|
218
|
+
ts: projection.enterDispatchedAt,
|
|
219
|
+
conversation_id: conversation.conversation_id,
|
|
220
|
+
event: "terminal_submission_retry_startup_reconciled",
|
|
221
|
+
message_id: projection.messageId,
|
|
222
|
+
terminal_input_sent: false
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return loadState(paths.statePath);
|
|
226
|
+
}
|
|
227
|
+
finally {
|
|
228
|
+
releaseState();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
releaseTerminal();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async recoverDeferred(options, storeDir, initialConversation, paths) {
|
|
237
|
+
if (isExplicitUserAbandonedManagementTurn(initialConversation)) {
|
|
238
|
+
return initialConversation;
|
|
239
|
+
}
|
|
240
|
+
const takeover = takeoverFor(initialConversation);
|
|
241
|
+
const transferId = nonBlankString(takeover?.deferred_foreground_transfer_id);
|
|
242
|
+
if (!transferId) {
|
|
243
|
+
return initialConversation;
|
|
244
|
+
}
|
|
245
|
+
const initialControl = terminalControlFromTakeover(takeover);
|
|
246
|
+
const initialPid = Number(takeover?.terminal_agent_pid);
|
|
247
|
+
if (!initialControl ||
|
|
248
|
+
!Number.isSafeInteger(initialPid) ||
|
|
249
|
+
initialPid <= 1) {
|
|
250
|
+
throw new Error(`deferred foreground Turn ${initialConversation.conversation_id} ` +
|
|
251
|
+
"lost its exact terminal process authority");
|
|
252
|
+
}
|
|
253
|
+
const releaseTerminal = this.#dependencies.dispatch.repository.acquire(storeDir, initialControl, { timeoutMs: 30000 });
|
|
254
|
+
try {
|
|
255
|
+
return await withStoreWriterLeaseAsync(storeDir, async () => {
|
|
256
|
+
const releaseState = this.#stateFileLock.acquire(`${paths.statePath}.lock`);
|
|
257
|
+
let conversation;
|
|
258
|
+
try {
|
|
259
|
+
conversation = loadState(paths.statePath);
|
|
260
|
+
if (isExplicitUserAbandonedManagementTurn(conversation)) {
|
|
261
|
+
return conversation;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
releaseState();
|
|
266
|
+
}
|
|
267
|
+
const currentTakeover = takeoverFor(conversation);
|
|
268
|
+
const currentTransferId = nonBlankString(currentTakeover.deferred_foreground_transfer_id);
|
|
269
|
+
const currentControl = terminalControlFromTakeover(currentTakeover);
|
|
270
|
+
const currentPid = Number(currentTakeover.terminal_agent_pid);
|
|
271
|
+
if (currentTransferId !== transferId ||
|
|
272
|
+
!currentControl ||
|
|
273
|
+
!terminalControlsShareIncarnation(currentControl, initialControl) ||
|
|
274
|
+
!Number.isSafeInteger(currentPid) ||
|
|
275
|
+
currentPid !== initialPid) {
|
|
276
|
+
throw new Error(`deferred foreground Turn ${conversation.conversation_id} ` +
|
|
277
|
+
"changed its exact terminal process authority during recovery");
|
|
278
|
+
}
|
|
279
|
+
const transfer = loadDeferredForegroundTransfer(storeDir, transferId);
|
|
280
|
+
if (isFinalDeferredForegroundTransferStatus(transfer.status)) {
|
|
281
|
+
return conversation;
|
|
282
|
+
}
|
|
283
|
+
const terminal = await this.#dependencies.authority
|
|
284
|
+
.createBridge(options)
|
|
285
|
+
.resolveStoredTerminal("codex", currentPid, currentControl, { pid: currentPid });
|
|
286
|
+
await this.#dependencies.authority.handoff
|
|
287
|
+
.recoverDeferredCodexForegroundTransferBeforeMutation({
|
|
288
|
+
options,
|
|
289
|
+
terminal: terminal
|
|
290
|
+
});
|
|
291
|
+
const releaseFinalState = this.#stateFileLock.acquire(`${paths.statePath}.lock`);
|
|
292
|
+
try {
|
|
293
|
+
conversation = loadState(paths.statePath);
|
|
294
|
+
this.#assertExactPendingDeferred(conversation, storeDir, transferId);
|
|
295
|
+
return conversation;
|
|
296
|
+
}
|
|
297
|
+
finally {
|
|
298
|
+
releaseFinalState();
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
finally {
|
|
303
|
+
releaseTerminal();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
#assertExactPendingDeferred(conversation, storeDir, transferId) {
|
|
307
|
+
const transfer = loadDeferredForegroundTransfer(storeDir, transferId);
|
|
308
|
+
if (isFinalDeferredForegroundTransferStatus(transfer.status)) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const takeover = takeoverFor(conversation);
|
|
312
|
+
const submission = terminalBridgeSubmission(conversation);
|
|
313
|
+
const anchor = validateCodexRolloutAcceptanceAnchor(takeover?.codex_rollout_acceptance_anchor);
|
|
314
|
+
const activeMessageId = deferredForegroundActiveMessageId(transfer);
|
|
315
|
+
const exactRetryPending = isDeferredForegroundSubmissionRetryPending(transfer) &&
|
|
316
|
+
activeMessageId === transfer.submission_retry_message_id &&
|
|
317
|
+
nonBlankString(submission?.prepared_at) ===
|
|
318
|
+
deferredForegroundActivePreparedAt(transfer) &&
|
|
319
|
+
nonBlankString(submission?.text_injected_at) ===
|
|
320
|
+
deferredForegroundActiveTextInjectedAt(transfer) &&
|
|
321
|
+
nonBlankString(submission?.enter_dispatched_at) ===
|
|
322
|
+
deferredForegroundActiveEnterDispatchedAt(transfer);
|
|
323
|
+
if (anchor?.version !== 3 ||
|
|
324
|
+
conversation.status !== "waiting_for_agent" ||
|
|
325
|
+
submission?.status !== "enter_dispatched" ||
|
|
326
|
+
nonBlankString(submission.message_id) !==
|
|
327
|
+
nonBlankString(takeover?.terminal_bridge_message_id) ||
|
|
328
|
+
nonBlankString(submission.message_id) !== activeMessageId ||
|
|
329
|
+
!(transfer.status === "dispatch_started" || exactRetryPending)) {
|
|
330
|
+
throw new Error(`deferred foreground Turn ${conversation.conversation_id} ` +
|
|
331
|
+
"is not an exact pending candidate-set acceptance");
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function terminalSubmissionRetryStartupAuthorityContext(input) {
|
|
336
|
+
const sessionId = sessionIdForConversation(input.conversation);
|
|
337
|
+
const turnId = turnIdForConversation(input.conversation);
|
|
338
|
+
const messageId = nonBlankString(input.submission.message_id);
|
|
339
|
+
const requestText = terminalSubmissionPayload(nonBlankString(input.takeover.terminal_bridge_request_text) ?? "");
|
|
340
|
+
const requestHash = terminalBridgeRequestFingerprint(requestText);
|
|
341
|
+
const messageType = nonBlankString(input.submission.message_type);
|
|
342
|
+
const messageBodyHash = createHash("sha256")
|
|
343
|
+
.update(requestText)
|
|
344
|
+
.digest("hex");
|
|
345
|
+
const callbackFingerprint = callbackRouteFingerprintForConversation(input.conversation) ?? null;
|
|
346
|
+
const deferredReferences = [
|
|
347
|
+
nonBlankString(input.takeover.deferred_foreground_transfer_id),
|
|
348
|
+
nonBlankString(input.submission.deferred_foreground_transfer_id),
|
|
349
|
+
nonBlankString(input.ledger.deferred_foreground_transfer_id),
|
|
350
|
+
input.attempt.deferred_foreground_transfer_id ?? undefined
|
|
351
|
+
];
|
|
352
|
+
const presentDeferredReferences = deferredReferences.filter((value) => value !== undefined);
|
|
353
|
+
const deferredTransferId = presentDeferredReferences[0];
|
|
354
|
+
const submissionTerminalEvidence = input.submission.terminal_endpoint !== undefined
|
|
355
|
+
? input.submission.terminal_endpoint
|
|
356
|
+
: input.submission.terminal_control;
|
|
357
|
+
const statePath = nonBlankString(input.conversation.state_path);
|
|
358
|
+
const logPath = nonBlankString(input.conversation.event_log_path);
|
|
359
|
+
const bindingId = nonBlankString(input.conversation.terminal_binding_id);
|
|
360
|
+
const bindingGeneration = Number(input.conversation.terminal_binding_generation);
|
|
361
|
+
return {
|
|
362
|
+
sessionId,
|
|
363
|
+
turnId,
|
|
364
|
+
messageId,
|
|
365
|
+
requestText,
|
|
366
|
+
requestHash,
|
|
367
|
+
messageType,
|
|
368
|
+
messageBodyHash,
|
|
369
|
+
callbackFingerprint,
|
|
370
|
+
deferredReferences,
|
|
371
|
+
presentDeferredReferences,
|
|
372
|
+
deferredTransferId,
|
|
373
|
+
submissionTerminalEvidence,
|
|
374
|
+
statePath,
|
|
375
|
+
logPath,
|
|
376
|
+
bindingId,
|
|
377
|
+
bindingGeneration
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
function terminalSubmissionRetryStartupIdentityMatches(input, context) {
|
|
381
|
+
return !(!context.requestText || !context.requestHash || !context.messageId ||
|
|
382
|
+
(context.messageType !== "task" && context.messageType !== "answer") ||
|
|
383
|
+
nonBlankString(input.takeover.terminal_bridge_message_id) !==
|
|
384
|
+
context.messageId ||
|
|
385
|
+
input.attempt.session_id !== context.sessionId ||
|
|
386
|
+
input.attempt.turn_id !== context.turnId ||
|
|
387
|
+
input.attempt.original_message_id !== context.messageId ||
|
|
388
|
+
input.attempt.active_message_id !== context.messageId ||
|
|
389
|
+
input.attempt.request_hash !== context.requestHash ||
|
|
390
|
+
input.attempt.terminal_target !== input.control.target ||
|
|
391
|
+
input.attempt.callback_route_fingerprint !== context.callbackFingerprint);
|
|
392
|
+
}
|
|
393
|
+
function terminalSubmissionRetryStartupSubmissionPathMatches(input) {
|
|
394
|
+
return path.resolve(nonBlankString(input.submission.store_dir) ?? "") ===
|
|
395
|
+
path.resolve(input.storeDir);
|
|
396
|
+
}
|
|
397
|
+
function terminalSubmissionRetryStartupSubmissionMatches(input, context) {
|
|
398
|
+
return !(nonBlankString(input.submission.session_id) !== context.sessionId ||
|
|
399
|
+
nonBlankString(input.submission.turn_id) !== context.turnId ||
|
|
400
|
+
nonBlankString(input.submission.request_hash) !== context.requestHash ||
|
|
401
|
+
nonBlankString(input.submission.message_body_hash) !==
|
|
402
|
+
context.messageBodyHash ||
|
|
403
|
+
nonBlankString(input.submission.executor_kind) !== "codex" ||
|
|
404
|
+
!terminalSubmissionRetryStartupSubmissionPathMatches(input) ||
|
|
405
|
+
(input.submission.callback_route_fingerprint ?? null) !==
|
|
406
|
+
context.callbackFingerprint ||
|
|
407
|
+
!terminalControlEvidenceMatches(context.submissionTerminalEvidence, input.control));
|
|
408
|
+
}
|
|
409
|
+
function terminalSubmissionRetryStartupLedgerPathMatches(input, context) {
|
|
410
|
+
return !(path.resolve(nonBlankString(input.ledger.store_dir) ?? "") !==
|
|
411
|
+
path.resolve(input.storeDir) ||
|
|
412
|
+
!sameCanonicalStatePath(input.ledger.state_path, input.paths.statePath) ||
|
|
413
|
+
!sameCanonicalStatePath(context.statePath, input.paths.statePath) ||
|
|
414
|
+
!sameCanonicalStatePath(input.ledger.event_log_path, input.paths.logPath) ||
|
|
415
|
+
!sameCanonicalStatePath(context.logPath, input.paths.logPath));
|
|
416
|
+
}
|
|
417
|
+
function terminalSubmissionRetryStartupLedgerMatches(input, context) {
|
|
418
|
+
return !(nonBlankString(input.ledger.generation_id) !== context.messageId ||
|
|
419
|
+
nonBlankString(input.ledger.message_id) !== context.messageId ||
|
|
420
|
+
nonBlankString(input.ledger.conversation_id) !==
|
|
421
|
+
input.conversation.conversation_id ||
|
|
422
|
+
nonBlankString(input.ledger.session_id) !== context.sessionId ||
|
|
423
|
+
nonBlankString(input.ledger.turn_id) !== context.turnId ||
|
|
424
|
+
nonBlankString(input.ledger.message_type) !== context.messageType ||
|
|
425
|
+
nonBlankString(input.ledger.message_body_hash) !==
|
|
426
|
+
context.messageBodyHash ||
|
|
427
|
+
nonBlankString(input.ledger.executor_kind) !== "codex" ||
|
|
428
|
+
nonBlankString(input.ledger.request_hash) !== context.requestHash ||
|
|
429
|
+
!terminalSubmissionRetryStartupLedgerPathMatches(input, context) ||
|
|
430
|
+
(input.ledger.callback_route_fingerprint ?? null) !==
|
|
431
|
+
context.callbackFingerprint ||
|
|
432
|
+
input.ledger.callback_expected !==
|
|
433
|
+
callbackExpectedForConversation(input.conversation) ||
|
|
434
|
+
!input.ledgerMatchesControl ||
|
|
435
|
+
(context.bindingId !== undefined &&
|
|
436
|
+
nonBlankString(input.ledger.binding_id) !== context.bindingId) ||
|
|
437
|
+
(Number.isSafeInteger(context.bindingGeneration) &&
|
|
438
|
+
Number(input.ledger.binding_generation) !== context.bindingGeneration));
|
|
439
|
+
}
|
|
440
|
+
function terminalSubmissionRetryStartupDeferredMatches(input, context) {
|
|
441
|
+
return !((context.deferredTransferId !== undefined &&
|
|
442
|
+
(context.presentDeferredReferences.length !==
|
|
443
|
+
context.deferredReferences.length ||
|
|
444
|
+
new Set(context.presentDeferredReferences).size !== 1)) ||
|
|
445
|
+
(context.deferredTransferId === undefined &&
|
|
446
|
+
input.attempt.deferred_foreground_transfer_id !== null));
|
|
447
|
+
}
|
|
448
|
+
function terminalSubmissionRetryStartupProjectionFields(input) {
|
|
449
|
+
if (input.attempt.state === "agent_accepted" ||
|
|
450
|
+
["agent_accepted", "not_accepted"].includes(String(input.submission.status)) ||
|
|
451
|
+
["agent_accepted", "not_accepted"].includes(String(input.ledger.status))) {
|
|
452
|
+
return {};
|
|
453
|
+
}
|
|
454
|
+
return {
|
|
455
|
+
projection: projectTerminalSubmissionRetryPending({
|
|
456
|
+
attempt: input.attempt,
|
|
457
|
+
submission: input.submission,
|
|
458
|
+
ledger: input.ledger
|
|
459
|
+
})
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function assertTerminalSubmissionRetryStartupAuthority(input) {
|
|
463
|
+
const context = terminalSubmissionRetryStartupAuthorityContext(input);
|
|
464
|
+
if (!terminalSubmissionRetryStartupIdentityMatches(input, context) ||
|
|
465
|
+
!terminalSubmissionRetryStartupSubmissionMatches(input, context) ||
|
|
466
|
+
!terminalSubmissionRetryStartupLedgerMatches(input, context) ||
|
|
467
|
+
!terminalSubmissionRetryStartupDeferredMatches(input, context)) {
|
|
468
|
+
throw new Error("submission retry startup recovery authority disagrees across Turn and ledger");
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
requestText: context.requestText,
|
|
472
|
+
requestHash: context.requestHash,
|
|
473
|
+
messageType: context.messageType,
|
|
474
|
+
deferredTransferId: context.deferredTransferId,
|
|
475
|
+
...terminalSubmissionRetryStartupProjectionFields(input)
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
function finalizeTerminalSubmissionRetryStartupAccepted(input) {
|
|
479
|
+
const stateAccepted = input.submission.status === "agent_accepted";
|
|
480
|
+
const ledgerAccepted = input.ledger.status === "agent_accepted";
|
|
481
|
+
if (!stateAccepted && !ledgerAccepted) {
|
|
482
|
+
throw new Error("accepted submission retry sidecar lacks native acceptance evidence");
|
|
483
|
+
}
|
|
484
|
+
const stateEvidence = stateAccepted
|
|
485
|
+
? terminalAcceptanceEvidenceForConversation(input.conversation, input.authority.requestText, input.submission.acceptance_evidence)
|
|
486
|
+
: undefined;
|
|
487
|
+
const ledgerEvidence = ledgerAccepted
|
|
488
|
+
? terminalAcceptanceEvidenceForConversation(input.conversation, input.authority.requestText, input.ledger.acceptance_evidence)
|
|
489
|
+
: undefined;
|
|
490
|
+
if (stateEvidence && ledgerEvidence &&
|
|
491
|
+
canonicalJson(stateEvidence) !== canonicalJson(ledgerEvidence)) {
|
|
492
|
+
throw new Error("accepted submission retry evidence conflicts across Turn and ledger");
|
|
493
|
+
}
|
|
494
|
+
const evidence = stateEvidence ?? ledgerEvidence;
|
|
495
|
+
const stateAcceptedAt = stateAccepted
|
|
496
|
+
? nonBlankString(input.submission.agent_accepted_at)
|
|
497
|
+
: undefined;
|
|
498
|
+
const ledgerAcceptedAt = ledgerAccepted
|
|
499
|
+
? nonBlankString(input.ledger.agent_accepted_at)
|
|
500
|
+
: undefined;
|
|
501
|
+
if (!evidence || !stateAcceptedAt && !ledgerAcceptedAt ||
|
|
502
|
+
stateAcceptedAt && ledgerAcceptedAt && stateAcceptedAt !== ledgerAcceptedAt) {
|
|
503
|
+
throw new Error("accepted submission retry timestamp conflicts across Turn and ledger");
|
|
504
|
+
}
|
|
505
|
+
const acceptedAt = (stateAcceptedAt ?? ledgerAcceptedAt);
|
|
506
|
+
if (input.authority.deferredTransferId) {
|
|
507
|
+
const transfer = loadDeferredForegroundTransfer(input.storeDir, input.authority.deferredTransferId);
|
|
508
|
+
if (transfer.status !== "resolved") {
|
|
509
|
+
if (isFinalDeferredForegroundTransferStatus(transfer.status)) {
|
|
510
|
+
throw new Error("accepted submission retry conflicts with an aborted deferred transfer");
|
|
511
|
+
}
|
|
512
|
+
return input.conversation;
|
|
513
|
+
}
|
|
514
|
+
if (transfer.agent_accepted_at !== acceptedAt ||
|
|
515
|
+
transfer.message_id !== input.attempt.active_message_id) {
|
|
516
|
+
throw new Error("accepted submission retry conflicts with its resolved deferred transfer");
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (input.attempt.state === "agent_accepted" &&
|
|
520
|
+
input.attempt.agent_accepted_at !== acceptedAt) {
|
|
521
|
+
throw new Error("accepted submission retry sidecar timestamp conflicts with native evidence");
|
|
522
|
+
}
|
|
523
|
+
let conversation = input.conversation;
|
|
524
|
+
const stateNeedsRepair = !stateAccepted;
|
|
525
|
+
if (stateNeedsRepair) {
|
|
526
|
+
const acceptedOwner = {
|
|
527
|
+
...conversation,
|
|
528
|
+
status: "waiting_for_agent",
|
|
529
|
+
updated_at: acceptedAt
|
|
530
|
+
};
|
|
531
|
+
delete acceptedOwner.stalled_at;
|
|
532
|
+
delete acceptedOwner.stalled_reason;
|
|
533
|
+
delete acceptedOwner.failed_at;
|
|
534
|
+
delete acceptedOwner.failure_reason;
|
|
535
|
+
delete acceptedOwner.idle_since;
|
|
536
|
+
conversation = applyTerminalBridgeSubmission({
|
|
537
|
+
conversation: acceptedOwner,
|
|
538
|
+
messageId: input.attempt.active_message_id,
|
|
539
|
+
messageType: input.authority.messageType,
|
|
540
|
+
requestText: input.authority.requestText,
|
|
541
|
+
status: "agent_accepted",
|
|
542
|
+
preparedAt: nonBlankString(input.submission.prepared_at),
|
|
543
|
+
textInjectedAt: nonBlankString(input.submission.text_injected_at) ??
|
|
544
|
+
nonBlankString(input.ledger.text_injected_at),
|
|
545
|
+
enterDispatchedAt: nonBlankString(input.submission.enter_dispatched_at) ??
|
|
546
|
+
nonBlankString(input.ledger.enter_dispatched_at),
|
|
547
|
+
agentAcceptedAt: acceptedAt,
|
|
548
|
+
acceptanceEvidence: evidence,
|
|
549
|
+
lastProvenStage: "agent_accepted"
|
|
550
|
+
}, {
|
|
551
|
+
dispatcherPid: cliPid(),
|
|
552
|
+
storeDir: input.storeDir,
|
|
553
|
+
terminalControl: input.control
|
|
554
|
+
});
|
|
555
|
+
saveState(input.paths.statePath, conversation);
|
|
556
|
+
}
|
|
557
|
+
let attempt = input.attempt;
|
|
558
|
+
const attemptNeedsRepair = attempt.state !== "agent_accepted";
|
|
559
|
+
if (attemptNeedsRepair) {
|
|
560
|
+
attempt = saveTerminalSubmissionRetry(input.paths.statePath, {
|
|
561
|
+
...attempt,
|
|
562
|
+
state: "agent_accepted",
|
|
563
|
+
agent_accepted_at: acceptedAt,
|
|
564
|
+
updated_at: acceptedAt
|
|
565
|
+
}, attempt.revision);
|
|
566
|
+
}
|
|
567
|
+
const ledgerNeedsRepair = !ledgerAccepted ||
|
|
568
|
+
input.ledger.submission_retry_state !== "agent_accepted" ||
|
|
569
|
+
input.ledger.submission_retry_revision !== attempt.revision;
|
|
570
|
+
if (ledgerNeedsRepair) {
|
|
571
|
+
input.saveLedger({
|
|
572
|
+
...input.ledger,
|
|
573
|
+
...terminalSubmissionRetryLedgerFields(attempt),
|
|
574
|
+
status: "agent_accepted",
|
|
575
|
+
agent_accepted_at: acceptedAt,
|
|
576
|
+
acceptance_evidence: evidence,
|
|
577
|
+
not_accepted_at: undefined,
|
|
578
|
+
uncertain_at: undefined,
|
|
579
|
+
safe_to_retry: undefined,
|
|
580
|
+
dispatcher_pid: null
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
if (stateNeedsRepair || attemptNeedsRepair || ledgerNeedsRepair) {
|
|
584
|
+
appendEvent(input.paths.logPath, {
|
|
585
|
+
ts: acceptedAt,
|
|
586
|
+
conversation_id: conversation.conversation_id,
|
|
587
|
+
event: "terminal_submission_retry_startup_accepted_reconciled",
|
|
588
|
+
message_id: input.attempt.active_message_id,
|
|
589
|
+
terminal_input_sent: false
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
return loadState(input.paths.statePath);
|
|
593
|
+
}
|
|
594
|
+
function reconcileTerminalSubmissionRetryStartupTerminalLedger(input) {
|
|
595
|
+
const notAccepted = input.startup.outcome === "not_accepted";
|
|
596
|
+
input.saveLedger({
|
|
597
|
+
...input.ledger,
|
|
598
|
+
...terminalSubmissionRetryLedgerFields(input.attempt),
|
|
599
|
+
status: input.startup.outcome,
|
|
600
|
+
...(notAccepted
|
|
601
|
+
? {
|
|
602
|
+
not_accepted_at: input.startup.at,
|
|
603
|
+
uncertain_at: undefined,
|
|
604
|
+
error: undefined,
|
|
605
|
+
safe_to_retry: undefined
|
|
606
|
+
}
|
|
607
|
+
: {
|
|
608
|
+
not_accepted_at: undefined,
|
|
609
|
+
uncertain_at: input.startup.at,
|
|
610
|
+
error: input.startup.reason,
|
|
611
|
+
safe_to_retry: false
|
|
612
|
+
}),
|
|
613
|
+
acceptance_evidence: undefined,
|
|
614
|
+
agent_accepted_at: undefined,
|
|
615
|
+
dispatcher_pid: null
|
|
616
|
+
});
|
|
617
|
+
appendEvent(input.paths.logPath, {
|
|
618
|
+
ts: input.startup.at,
|
|
619
|
+
conversation_id: input.conversation.conversation_id,
|
|
620
|
+
event: `terminal_submission_retry_startup_${input.startup.outcome}_reconciled`,
|
|
621
|
+
message_id: input.attempt.active_message_id,
|
|
622
|
+
terminal_input_sent: false,
|
|
623
|
+
do_not_retry: true
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
function reconcileTerminalSubmissionRetryStartupTerminalState(input) {
|
|
627
|
+
const stalled = {
|
|
628
|
+
...input.conversation,
|
|
629
|
+
status: "stalled",
|
|
630
|
+
stalled_at: input.startup.at,
|
|
631
|
+
stalled_reason: input.startup.reason ??
|
|
632
|
+
"submission retry native acceptance became uncertain",
|
|
633
|
+
updated_at: input.startup.at
|
|
634
|
+
};
|
|
635
|
+
const conversation = applyTerminalBridgeSubmission({
|
|
636
|
+
conversation: stalled,
|
|
637
|
+
messageId: input.attempt.active_message_id,
|
|
638
|
+
messageType: input.authority.messageType,
|
|
639
|
+
requestText: input.authority.requestText,
|
|
640
|
+
status: "uncertain",
|
|
641
|
+
preparedAt: nonBlankString(input.submission.prepared_at),
|
|
642
|
+
textInjectedAt: nonBlankString(input.submission.text_injected_at),
|
|
643
|
+
enterDispatchedAt: nonBlankString(input.submission.enter_dispatched_at),
|
|
644
|
+
uncertainAt: input.startup.at,
|
|
645
|
+
error: input.startup.reason,
|
|
646
|
+
safeToRetry: false,
|
|
647
|
+
lastProvenStage: "enter_dispatched"
|
|
648
|
+
}, {
|
|
649
|
+
dispatcherPid: cliPid(),
|
|
650
|
+
storeDir: input.storeDir,
|
|
651
|
+
terminalControl: input.control
|
|
652
|
+
});
|
|
653
|
+
saveState(input.paths.statePath, conversation);
|
|
654
|
+
appendEvent(input.paths.logPath, {
|
|
655
|
+
ts: input.startup.at,
|
|
656
|
+
conversation_id: conversation.conversation_id,
|
|
657
|
+
event: "terminal_submission_retry_startup_uncertain_reconciled",
|
|
658
|
+
message_id: input.attempt.active_message_id,
|
|
659
|
+
terminal_input_sent: false,
|
|
660
|
+
do_not_retry: true
|
|
661
|
+
});
|
|
662
|
+
return conversation;
|
|
663
|
+
}
|
|
664
|
+
function mirrorDeferredSubmissionRetryEnter(input) {
|
|
665
|
+
const transfer = loadDeferredForegroundTransfer(input.storeDir, input.transferId);
|
|
666
|
+
assertDeferredSubmissionRetryMirrorAuthority(transfer, input);
|
|
667
|
+
if (isFinalDeferredForegroundTransferStatus(transfer.status)) {
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
if (transfer.submission_retry_enter_dispatched_at) {
|
|
671
|
+
if (!isDeferredForegroundSubmissionRetryPending(transfer)) {
|
|
672
|
+
throw new Error("deferred submission retry startup authority is not exactly pending");
|
|
673
|
+
}
|
|
674
|
+
return false;
|
|
675
|
+
}
|
|
676
|
+
if (transfer.status !== "uncertain" ||
|
|
677
|
+
transfer.input_stage !== "text_injected" ||
|
|
678
|
+
transfer.enter_dispatched_at !== undefined) {
|
|
679
|
+
throw new Error("deferred submission retry startup recovery found an unsafe stage lag");
|
|
680
|
+
}
|
|
681
|
+
const repaired = saveDeferredForegroundTransfer(input.storeDir, {
|
|
682
|
+
...transfer,
|
|
683
|
+
input_stage: "enter_dispatched",
|
|
684
|
+
enter_dispatched_at: input.attempt.enter_dispatched_at,
|
|
685
|
+
submission_retry_enter_dispatched_at: input.attempt.enter_dispatched_at
|
|
686
|
+
}, {
|
|
687
|
+
expectedRevision: Number(transfer.revision)
|
|
688
|
+
});
|
|
689
|
+
if (!isDeferredForegroundSubmissionRetryPending(repaired)) {
|
|
690
|
+
throw new Error("deferred submission retry startup recovery did not produce exact pending authority");
|
|
691
|
+
}
|
|
692
|
+
return true;
|
|
693
|
+
}
|
|
694
|
+
function assertDeferredSubmissionRetryMirrorAuthority(transfer, input) {
|
|
695
|
+
const exactMode = input.attempt.mode === "exact_draft_enter";
|
|
696
|
+
if (transfer.transfer_id !== input.transferId ||
|
|
697
|
+
transfer.turn_id !== input.turnId ||
|
|
698
|
+
transfer.target_session_id !== input.sessionId ||
|
|
699
|
+
!sameCanonicalStatePath(transfer.state_path, input.statePath) ||
|
|
700
|
+
transfer.request_hash !== input.requestHash ||
|
|
701
|
+
transfer.message_id !== input.messageId ||
|
|
702
|
+
transfer.prepared_at !== input.preparedAt ||
|
|
703
|
+
transfer.submission_retry_attempt_id !== input.attempt.attempt_id ||
|
|
704
|
+
transfer.submission_retry_mode !== input.attempt.mode ||
|
|
705
|
+
transfer.submission_retry_message_id !== input.messageId ||
|
|
706
|
+
transfer.submission_retry_prepared_at !== input.preparedAt ||
|
|
707
|
+
transfer.submission_retry_enter_reserved_at !==
|
|
708
|
+
input.attempt.enter_reserved_at ||
|
|
709
|
+
transfer.submission_retry_enter_dispatched_at !== undefined &&
|
|
710
|
+
transfer.submission_retry_enter_dispatched_at !==
|
|
711
|
+
input.attempt.enter_dispatched_at ||
|
|
712
|
+
transfer.submission_retry_enter_dispatched_at !== undefined &&
|
|
713
|
+
(transfer.input_stage !== "enter_dispatched" ||
|
|
714
|
+
transfer.enter_dispatched_at !== input.attempt.enter_dispatched_at) ||
|
|
715
|
+
(exactMode
|
|
716
|
+
? transfer.submission_retry_text_reserved_at !== undefined ||
|
|
717
|
+
transfer.submission_retry_text_injected_at !== undefined ||
|
|
718
|
+
transfer.text_injected_at !== input.textInjectedAt
|
|
719
|
+
: transfer.submission_retry_text_reserved_at !==
|
|
720
|
+
input.attempt.replacement_text_reserved_at ||
|
|
721
|
+
transfer.submission_retry_text_injected_at !==
|
|
722
|
+
input.attempt.replacement_text_injected_at)) {
|
|
723
|
+
throw new Error("deferred submission retry startup authority disagrees with its attempt");
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
function terminalSubmissionRetryMonitorEpoch(conversation, at) {
|
|
727
|
+
const takeover = takeoverFor(conversation);
|
|
728
|
+
const inactivityMinutes = Number(takeover.terminal_bridge_inactivity_timeout_minutes);
|
|
729
|
+
const hardMinutes = Number(takeover.terminal_bridge_hard_timeout_minutes);
|
|
730
|
+
const inactivityDeadlineAt = terminalMonitorDeadlineAt(at, inactivityMinutes);
|
|
731
|
+
const hardDeadlineAt = terminalMonitorDeadlineAt(at, hardMinutes);
|
|
732
|
+
if (!inactivityDeadlineAt || !hardDeadlineAt) {
|
|
733
|
+
throw new Error("submission retry startup recovery lacks its configured monitor timeouts");
|
|
734
|
+
}
|
|
735
|
+
const next = {
|
|
736
|
+
...conversation,
|
|
737
|
+
status: "waiting_for_agent",
|
|
738
|
+
native_session_takeover: {
|
|
739
|
+
...takeover,
|
|
740
|
+
terminal_bridge_started_at: at,
|
|
741
|
+
terminal_bridge_monitor_started_at: at,
|
|
742
|
+
terminal_bridge_last_activity_at: at,
|
|
743
|
+
terminal_bridge_last_activity_reason: "submission retry Enter dispatched",
|
|
744
|
+
terminal_bridge_inactivity_deadline_at: inactivityDeadlineAt,
|
|
745
|
+
terminal_bridge_hard_deadline_at: hardDeadlineAt
|
|
746
|
+
},
|
|
747
|
+
updated_at: at
|
|
748
|
+
};
|
|
749
|
+
delete next.stalled_at;
|
|
750
|
+
delete next.stalled_reason;
|
|
751
|
+
delete next.failed_at;
|
|
752
|
+
delete next.failure_reason;
|
|
753
|
+
delete next.idle_since;
|
|
754
|
+
return next;
|
|
755
|
+
}
|
|
756
|
+
function terminalSubmissionRetryStateIsPending(input) {
|
|
757
|
+
return input.conversation.status === "waiting_for_agent" &&
|
|
758
|
+
input.submission.status === "enter_dispatched" &&
|
|
759
|
+
nonBlankString(input.submission.message_id) === input.projection.messageId &&
|
|
760
|
+
nonBlankString(input.submission.prepared_at) ===
|
|
761
|
+
input.projection.preparedAt &&
|
|
762
|
+
nonBlankString(input.submission.text_injected_at) ===
|
|
763
|
+
input.projection.textInjectedAt &&
|
|
764
|
+
nonBlankString(input.submission.enter_dispatched_at) ===
|
|
765
|
+
input.projection.enterDispatchedAt;
|
|
766
|
+
}
|
|
767
|
+
//# sourceMappingURL=terminal-monitor-startup-recovery.js.map
|