@scotthuang/agent-knock-knock 0.12.37 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/README.md +3 -0
- package/dist/src/agent-session-provider.d.ts +5 -0
- package/dist/src/agent-session-provider.js +10 -1
- package/dist/src/agent-session-provider.js.map +1 -1
- package/dist/src/callback-outbox-service.d.ts +2 -0
- package/dist/src/callback-outbox-service.js +1 -1
- package/dist/src/callback-outbox-service.js.map +1 -1
- package/dist/src/callback-transport.d.ts +7 -0
- package/dist/src/callback-transport.js +1 -1
- package/dist/src/callback-transport.js.map +1 -1
- package/dist/src/cli-core.js +35 -32
- package/dist/src/cli-core.js.map +1 -1
- package/dist/src/codex-store-adapter.d.ts +1 -1
- package/dist/src/codex-store-adapter.js +35 -8
- package/dist/src/codex-store-adapter.js.map +1 -1
- package/dist/src/deferred-foreground-authority-cli-adapter.d.ts +20 -0
- package/dist/src/deferred-foreground-authority-cli-adapter.js +216 -8
- package/dist/src/deferred-foreground-authority-cli-adapter.js.map +1 -1
- package/dist/src/host-adapter.d.ts +1 -1
- package/dist/src/host-adapter.js +1 -1
- package/dist/src/host-bridge-tools.js +2 -2
- package/dist/src/managed-turn-recovery-service.d.ts +8 -1
- package/dist/src/managed-turn-recovery-service.js +55 -13
- package/dist/src/managed-turn-recovery-service.js.map +1 -1
- package/dist/src/mutation-transaction.d.ts +1 -0
- package/dist/src/mutation-transaction.js +19 -15
- package/dist/src/mutation-transaction.js.map +1 -1
- package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +39 -3
- package/dist/src/native-thread-lifecycle-cli-adapter.js +307 -7
- package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-callback-adapter.js +17 -7
- package/dist/src/openclaw-plugin-callback-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-command-adapter.js +297 -83
- package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-schemas.d.ts +57 -0
- package/dist/src/openclaw-plugin-schemas.js +59 -1
- package/dist/src/openclaw-plugin-schemas.js.map +1 -1
- package/dist/src/openclaw-private-authority-offers.d.ts +17 -0
- package/dist/src/openclaw-private-authority-offers.js +75 -3
- package/dist/src/openclaw-private-authority-offers.js.map +1 -1
- package/dist/src/store.d.ts +1 -1
- package/dist/src/store.js +6 -4
- package/dist/src/store.js.map +1 -1
- package/dist/src/terminal-acceptance-application-service.js +2 -2
- package/dist/src/terminal-acceptance-application-service.js.map +1 -1
- package/dist/src/terminal-acceptance-cli-adapter.d.ts +11 -1
- package/dist/src/terminal-acceptance-cli-adapter.js +290 -16
- package/dist/src/terminal-acceptance-cli-adapter.js.map +1 -1
- package/dist/src/terminal-action-projection.d.ts +1 -1
- package/dist/src/terminal-action-projection.js +6 -0
- package/dist/src/terminal-action-projection.js.map +1 -1
- package/dist/src/terminal-agent-adapter.d.ts +9 -0
- package/dist/src/terminal-agent-adapter.js.map +1 -1
- package/dist/src/terminal-agent-bridge.d.ts +52 -8
- package/dist/src/terminal-agent-bridge.js +111 -94
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/dist/src/terminal-authority-policy.js +3 -0
- package/dist/src/terminal-authority-policy.js.map +1 -1
- package/dist/src/terminal-binding-authority.d.ts +2 -0
- package/dist/src/terminal-binding-authority.js +8 -0
- package/dist/src/terminal-binding-authority.js.map +1 -1
- package/dist/src/terminal-command-cli-adapter.d.ts +17 -1
- package/dist/src/terminal-command-cli-adapter.js +784 -286
- package/dist/src/terminal-command-cli-adapter.js.map +1 -1
- package/dist/src/terminal-delegate-cli-adapter.js +21 -7
- package/dist/src/terminal-delegate-cli-adapter.js.map +1 -1
- package/dist/src/terminal-dispatch-application.d.ts +3 -1
- package/dist/src/terminal-dispatch-application.js +13 -5
- package/dist/src/terminal-dispatch-application.js.map +1 -1
- package/dist/src/terminal-dispatch-composition.d.ts +43 -0
- package/dist/src/terminal-dispatch-execution.d.ts +2 -2
- package/dist/src/terminal-dispatch-execution.js +53 -3
- package/dist/src/terminal-dispatch-execution.js.map +1 -1
- package/dist/src/terminal-dispatch-policy.d.ts +20 -0
- package/dist/src/terminal-dispatch-policy.js +33 -1
- package/dist/src/terminal-dispatch-policy.js.map +1 -1
- package/dist/src/terminal-dispatch-presenter.d.ts +45 -0
- package/dist/src/terminal-dispatch-presenter.js +220 -18
- package/dist/src/terminal-dispatch-presenter.js.map +1 -1
- package/dist/src/terminal-dispatch-receipt.d.ts +5 -1
- package/dist/src/terminal-dispatch-receipt.js +26 -3
- package/dist/src/terminal-dispatch-receipt.js.map +1 -1
- package/dist/src/terminal-identity-authority-cli-adapter.js +103 -4
- package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -1
- package/dist/src/terminal-interaction-authority.d.ts +48 -1
- package/dist/src/terminal-interaction-authority.js +30 -0
- package/dist/src/terminal-interaction-authority.js.map +1 -1
- package/dist/src/terminal-interaction-cli-adapter.js +9 -0
- package/dist/src/terminal-interaction-cli-adapter.js.map +1 -1
- package/dist/src/terminal-interaction-core.d.ts +104 -0
- package/dist/src/terminal-interaction-core.js +407 -0
- package/dist/src/terminal-interaction-core.js.map +1 -0
- package/dist/src/terminal-interaction-protocol.d.ts +89 -0
- package/dist/src/terminal-interaction-protocol.js +199 -0
- package/dist/src/terminal-interaction-protocol.js.map +1 -1
- package/dist/src/terminal-list-cli-adapter.d.ts +5 -0
- package/dist/src/terminal-list-cli-adapter.js +190 -40
- package/dist/src/terminal-list-cli-adapter.js.map +1 -1
- package/dist/src/terminal-list-renderer.js +60 -7
- package/dist/src/terminal-list-renderer.js.map +1 -1
- package/dist/src/terminal-maintenance-cli-adapter.js +14 -3
- package/dist/src/terminal-maintenance-cli-adapter.js.map +1 -1
- package/dist/src/terminal-monitor-application-service.d.ts +1 -0
- package/dist/src/terminal-monitor-application-service.js +72 -10
- package/dist/src/terminal-monitor-application-service.js.map +1 -1
- package/dist/src/terminal-monitor-cli-adapter.d.ts +3 -0
- package/dist/src/terminal-monitor-cli-adapter.js +24 -8
- package/dist/src/terminal-monitor-cli-adapter.js.map +1 -1
- package/dist/src/terminal-monitor-state-cli-adapter.js +15 -2
- package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -1
- package/dist/src/terminal-mutation-cli-runtime.d.ts +11 -1
- package/dist/src/terminal-mutation-cli-runtime.js +9 -4
- package/dist/src/terminal-mutation-cli-runtime.js.map +1 -1
- package/dist/src/terminal-status-cli-adapter.d.ts +1 -7
- package/dist/src/terminal-status-cli-adapter.js +29 -3
- package/dist/src/terminal-status-cli-adapter.js.map +1 -1
- package/dist/src/terminal-status-facts.d.ts +17 -1
- package/dist/src/terminal-status-facts.js +59 -0
- package/dist/src/terminal-status-facts.js.map +1 -1
- package/dist/src/terminal-submission-acceptance.d.ts +37 -2
- package/dist/src/terminal-submission-acceptance.js +633 -30
- package/dist/src/terminal-submission-acceptance.js.map +1 -1
- package/dist/src/terminal-submission-facts.d.ts +5 -0
- package/dist/src/terminal-submission-facts.js.map +1 -1
- package/dist/src/terminal-watch-callback-cli-adapter.d.ts +2 -1
- package/dist/src/terminal-watch-callback-cli-adapter.js +2 -1
- package/dist/src/terminal-watch-callback-cli-adapter.js.map +1 -1
- package/dist/src/terminal-watch-cli-adapter.d.ts +6 -2
- package/dist/src/terminal-watch-cli-adapter.js +1051 -15
- package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
- package/dist/src/terminal-watch-service.d.ts +15 -1
- package/dist/src/terminal-watch-service.js +169 -13
- package/dist/src/terminal-watch-service.js.map +1 -1
- package/dist/src/terminal-watch-store.d.ts +72 -3
- package/dist/src/terminal-watch-store.js +407 -27
- package/dist/src/terminal-watch-store.js.map +1 -1
- package/docs/host-bridge-profiles.md +2 -2
- package/docs/quickstart-herdr.md +5 -3
- package/docs/quickstart-tmux.md +7 -5
- package/openclaw.plugin.json +8 -0
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +15 -11
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
// Raw CLI infrastructure for ordinary terminal send/respond/approval commands.
|
|
2
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
+
import fs from "node:fs";
|
|
4
5
|
import os from "node:os";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import { callbackExpectedForConversation, callbackExpectedForConversationWithLegacyFallback, callbackRouteFingerprintForConversation } from "./callback-route-authority.js";
|
|
7
|
-
import { supersedeUnacceptedCallbackDeliveries } from "./callback-outbox-policy.js";
|
|
8
|
+
import { callbackDeliveryAttemptOutcome, supersedeUnacceptedCallbackDeliveries } from "./callback-outbox-policy.js";
|
|
8
9
|
import { captureClaudeTranscriptAnchor, defaultClaudeHome } from "./claude-local-transcript-provider.js";
|
|
9
|
-
import { validateCodexRolloutAcceptanceAnchor } from "./terminal-submission-acceptance.js";
|
|
10
|
+
import { captureCodexCandidateSetRolloutAcceptanceAnchor, validateCodexRolloutAcceptanceAnchor } from "./terminal-submission-acceptance.js";
|
|
11
|
+
import { fingerprint } from "./terminal-submission-facts.js";
|
|
10
12
|
import { isRecord, nonBlankString as stringValue } from "./value-guards.js";
|
|
11
|
-
import { deferredForegroundActiveMessageId, isDeferredForegroundSubmissionRetryPending } from "./deferred-foreground-transfer.js";
|
|
13
|
+
import { deferredForegroundActiveMessageId, isDeferredForegroundSubmissionRetryPending, listDeferredForegroundTransfers } from "./deferred-foreground-transfer.js";
|
|
14
|
+
import { isFinalDeferredForegroundTransferStatus } from "./deferred-foreground-transfer-policy.js";
|
|
15
|
+
import { humanExplicitCallbackDebtDisposition, humanExplicitCallbackDebtRetirementDisposition, humanExplicitCallbackDebtManagedTokenMatches } from "./deferred-foreground-authority-cli-adapter.js";
|
|
12
16
|
import { applyMessageToConversation, budgetAction, createMessage, effectiveTurnStatus, executorForConversation, isSessionSendBlockingStatus, isTerminalDispatchOwnerReleasedStatus, sessionIdForConversation, turnIdForConversation } from "./protocol.js";
|
|
13
17
|
import { readNdjsonLog } from "./transcript.js";
|
|
14
18
|
import { appendEvent, appendExplicitUserCloseEvent, defaultStoreDir, ensureDir, ensureStoreWritable, listConversations, loadState, messageEvent, pathsForConversation, pathsForConversationDir, saveExplicitUserCloseState, saveState, withStoreWriterLeaseAsync } from "./store.js";
|
|
15
19
|
import { createManagedSessionId, managedSessionBindingToken, managedSessionRevision, unmanagedTerminalBindingToken } from "./managed-session.js";
|
|
16
|
-
import { listManagedSessions, loadManagedSession, saveManagedSession, tryLoadManagedSession } from "./session-store.js";
|
|
20
|
+
import { listNativeThreadTransitions, listManagedSessions, loadManagedSession, saveManagedSession, tryLoadManagedSession } from "./session-store.js";
|
|
17
21
|
import { isTerminalApprovalDecision } from "./terminal-agent-adapter.js";
|
|
18
22
|
import { terminalControlEvidenceMatches } from "./terminal-control-ref.js";
|
|
19
23
|
import { isExactClaudeNativeInspectionIdleComposer, TerminalEnterDispatchNotAttemptedError, TerminalInputNotStartedError } from "./terminal-agent-bridge.js";
|
|
20
24
|
import { decideTerminalSubmissionRetry, loadTerminalSubmissionRetry, projectTerminalSubmissionRetryPending, saveTerminalSubmissionRetry, terminalSubmissionRetryLedgerFields, TERMINAL_SUBMISSION_RETRY_SCHEMA, TERMINAL_SUBMISSION_RETRY_VERSION } from "./terminal-submission-retry-service.js";
|
|
21
25
|
import { evaluateApprovalPolicy } from "./approval-policy.js";
|
|
22
|
-
import {
|
|
26
|
+
import { rolloutFileIdentityMatches } from "./terminal-binding-authority.js";
|
|
27
|
+
import { codexCompanionsPresentInOpenRootInventory, exactBoundCodexSendSource, isCompleteNativeRollout, nativeIdentityMatchesCodexPreMaterialization, terminalControlAliasMatches, terminalControlsShareIncarnation } from "./terminal-authority-policy.js";
|
|
23
28
|
import { decideTerminalSendAuthority } from "./terminal-action-projection.js";
|
|
24
29
|
import { terminalScopedCodexApprovalPromptSnapshot } from "./terminal-scoped-approval-authority.js";
|
|
25
30
|
import { withCanonicalMutationLocks } from "./mutation-transaction.js";
|
|
@@ -28,6 +33,7 @@ import { claudeTranscriptApprovalIdentity, terminalMonitorDeadlineAt as deadline
|
|
|
28
33
|
import * as monitorOwner from "./terminal-monitor-ownership-policy.js";
|
|
29
34
|
import { sameCanonicalStatePath, terminalDispatchLedgerLooksLifecycle, terminalDispatchReceiptHistory as terminalLedgerReceiptHistory } from "./terminal-dispatch-ledger-codec.js";
|
|
30
35
|
import * as dispatchApplication from "./terminal-dispatch-application.js";
|
|
36
|
+
import { decideUserExplicitTerminalInputSafety } from "./terminal-dispatch-policy.js";
|
|
31
37
|
import { bindDeferredForegroundApplicationScope, bindDeferredForegroundWriterScope } from "./deferred-foreground-capability.js";
|
|
32
38
|
import * as deferredRecoveryAdapter from "./deferred-foreground-recovery-cli-adapter.js";
|
|
33
39
|
import { cleanupDeferredForegroundUserClose } from "./deferred-foreground-user-close.js";
|
|
@@ -35,11 +41,16 @@ import { deferredForegroundBoundaryProjection } from "./deferred-foreground-prep
|
|
|
35
41
|
import { terminalSubmissionPayload } from "./terminal-dispatch-execution.js";
|
|
36
42
|
import { createTerminalUserSendIntentRepository, TerminalUserSendIntentBoundaryConflictError, TerminalUserSendIntentUncertainError } from "./terminal-user-send-intent.js";
|
|
37
43
|
import { terminalUserExplicitFallbackWatchId } from "./terminal-watch-store.js";
|
|
38
|
-
import {
|
|
44
|
+
import { CODEX_FOREGROUND_IDENTIFICATION_SCROLLBACK_LINES } from "./native-thread-lifecycle-cli-adapter.js";
|
|
45
|
+
import { presentTerminalCompleted, presentTerminalDispatchReplay, presentTerminalIdentityFailure, terminalSendEnterDispatched, terminalSendResultContract, presentTerminalUncertain, presentTerminalZeroInputAbort as renderTerminalZeroInputAbort } from "./terminal-dispatch-presenter.js";
|
|
39
46
|
import * as dispatchReceipt from "./terminal-dispatch-receipt.js";
|
|
40
47
|
import { expandHome, positiveMilliseconds, writeCliJson as printJson } from "./cli-command-runtime.js";
|
|
41
48
|
import { cliEnv, cliExit, cliNow, cliNowMs, cliPid, cliRuntimeLog as runtimeLog } from "./cli-runtime-context.js";
|
|
42
49
|
const terminalCommandContext = new AsyncLocalStorage();
|
|
50
|
+
// An atomic identify-and-send proof is deliberately an in-process,
|
|
51
|
+
// unforgeable side channel. It must never be accepted from CLI arguments or
|
|
52
|
+
// persisted as Session authority.
|
|
53
|
+
const foregroundIdentificationProofs = new WeakMap();
|
|
43
54
|
function terminalCommandRuntime() {
|
|
44
55
|
const runtime = terminalCommandContext.getStore();
|
|
45
56
|
if (!runtime) {
|
|
@@ -81,6 +92,7 @@ const deferredForegroundRecoveryAdapterPorts = rawPort("deferredForegroundRecove
|
|
|
81
92
|
const ensureTerminalBridgeMonitorAfterApproval = rawPort("ensureTerminalBridgeMonitorAfterApproval");
|
|
82
93
|
const exactSafeAbortedRecoveredSessionMatches = rawPort("exactSafeAbortedRecoveredSessionMatches");
|
|
83
94
|
const inspectCodexOpenRootRolloutInventory = rawPort("inspectCodexOpenRootRolloutInventory");
|
|
95
|
+
const identifyCodexForegroundWhileLocked = rawPort("identifyCodexForegroundWhileLocked");
|
|
84
96
|
const isDiscoverableTmuxConversation = rawPort("isDiscoverableTmuxConversation");
|
|
85
97
|
const loadClaudeAgentRows = rawPort("loadClaudeAgentRows");
|
|
86
98
|
const loadConversationFromOptions = rawPort("loadConversationFromOptions");
|
|
@@ -143,6 +155,11 @@ const DEFAULT_AGENT_TIMEOUT_MINUTES = 60;
|
|
|
143
155
|
const DEFAULT_AGENT_HARD_TIMEOUT_MINUTES = 720;
|
|
144
156
|
const DEFAULT_TERMINAL_ACCEPTANCE_TIMEOUT_MS = 5000;
|
|
145
157
|
const DEFAULT_TERMINAL_ACCEPTANCE_POLL_INTERVAL_MS = 50;
|
|
158
|
+
// Human-explicit Send still falls back to one physical terminal dispatch when
|
|
159
|
+
// managed preparation is genuinely unavailable. Give ordinary Store/monitor
|
|
160
|
+
// contention a short chance to clear first so a transient writer lease does
|
|
161
|
+
// not unnecessarily discard managed questionnaire response authority.
|
|
162
|
+
const USER_EXPLICIT_MANAGED_LOCK_GRACE_MS = 1_000;
|
|
146
163
|
const CLAUDE_SCREEN_APPROVAL_TTL_MS = 10 * 60 * 1000;
|
|
147
164
|
function replayReceiptBindingMismatch(receipt, expectation) {
|
|
148
165
|
return Boolean(!terminalDispatchRecordMatchesControl(receipt, expectation.terminalControl) ||
|
|
@@ -261,7 +278,7 @@ function replayLoggedMessageMismatch(loggedMessage, owner, expectedMessageType,
|
|
|
261
278
|
loggedMessage.session_id !== sessionIdForConversation(owner) ||
|
|
262
279
|
loggedMessage.turn_id !== turnIdForConversation(owner));
|
|
263
280
|
}
|
|
264
|
-
function replayExactActiveTerminalSubmission({ options, terminalControl, requestText, expectedStoreDir, expectedSessionId, expectedTurnId, expectedMessageType = "task", expectedStatePath }) {
|
|
281
|
+
function replayExactActiveTerminalSubmission({ options, terminalControl, requestText, expectedStoreDir, expectedSessionId, expectedTurnId, expectedMessageType = "task", expectedStatePath, userExplicitTerminalId }) {
|
|
265
282
|
const messageId = stringValue(options.messageId);
|
|
266
283
|
if (!messageId) {
|
|
267
284
|
return false;
|
|
@@ -338,14 +355,15 @@ function replayExactActiveTerminalSubmission({ options, terminalControl, request
|
|
|
338
355
|
expectedSessionId,
|
|
339
356
|
expectedTurnId,
|
|
340
357
|
expectedMessageType,
|
|
341
|
-
expectedStatePath
|
|
358
|
+
expectedStatePath,
|
|
359
|
+
userExplicitTerminalId
|
|
342
360
|
})) {
|
|
343
361
|
return true;
|
|
344
362
|
}
|
|
345
363
|
if (durableReceiptCannotDispatchAgain(ledgerReceipt, loadedLedger, loadedLedgerMessageId, messageId)) {
|
|
346
364
|
throw new Error(`terminal idempotency key ${messageId} already has durable ` +
|
|
347
365
|
`${String(ledgerReceipt.status)} proof and cannot be dispatched again; ` +
|
|
348
|
-
"no terminal input was sent");
|
|
366
|
+
"no additional terminal input was sent");
|
|
349
367
|
}
|
|
350
368
|
const incarnationLedger = resolveTerminalDispatchLedgerPaneIncarnation(terminalControl, loadedLedger);
|
|
351
369
|
const ledger = replayableReconciledLedger(reconcilePreparedTerminalDispatchLedger(terminalControl, incarnationLedger), expectation, messageId);
|
|
@@ -433,46 +451,31 @@ function replayExactActiveTerminalSubmission({ options, terminalControl, request
|
|
|
433
451
|
});
|
|
434
452
|
const acceptanceInvalid = replayReceipt.submission_outcome === "uncertain";
|
|
435
453
|
const callbackExpected = callbackExpectedForConversationWithLegacyFallback(owner, ledger.callback_expected);
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
454
|
+
presentTerminalDispatchReplay({
|
|
455
|
+
owner,
|
|
456
|
+
receipt: replayReceipt,
|
|
457
|
+
accepted: replayReceipt.submission_outcome === "agent_accepted",
|
|
458
|
+
acceptanceInvalid,
|
|
459
|
+
receiptConversationId: owner.conversation_id,
|
|
460
|
+
receiptMessageId: messageId,
|
|
461
|
+
callbackExpected,
|
|
462
|
+
...(userExplicitTerminalId
|
|
463
|
+
? {
|
|
464
|
+
userExplicit: {
|
|
465
|
+
terminalId: userExplicitTerminalId,
|
|
466
|
+
messageId
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
: {})
|
|
470
|
+
}, {
|
|
440
471
|
message: replayedMessage,
|
|
441
|
-
delivered: replayReceipt.delivered,
|
|
442
|
-
status: replayReceipt.status,
|
|
443
|
-
submission_outcome: replayReceipt.submission_outcome,
|
|
444
|
-
background: true,
|
|
445
|
-
callback_expected: !acceptanceInvalid && callbackExpected,
|
|
446
|
-
terminal_control: terminalControl,
|
|
447
472
|
executor,
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
? "AKK replayed the durable native acceptance receipt and sent no additional terminal input."
|
|
455
|
-
: acceptanceInvalid
|
|
456
|
-
? "AKK replayed an invalid native acceptance receipt as uncertain and sent no additional terminal input."
|
|
457
|
-
: "AKK replayed the original transport proof without upgrading it and sent no additional terminal input.",
|
|
458
|
-
openclaw_next_action: replayReceipt.delivered
|
|
459
|
-
? openClawYieldNextAction({
|
|
460
|
-
conversationId: owner.conversation_id,
|
|
461
|
-
sessionId: sessionIdForConversation(owner),
|
|
462
|
-
turnId: turnIdForConversation(owner),
|
|
463
|
-
source: "terminal_control",
|
|
464
|
-
callbackExpected
|
|
465
|
-
})
|
|
466
|
-
: {
|
|
467
|
-
action: "inspect",
|
|
468
|
-
conversation_id: owner.conversation_id,
|
|
469
|
-
session_id: sessionIdForConversation(owner),
|
|
470
|
-
turn_id: turnIdForConversation(owner),
|
|
471
|
-
do_not_retry: true,
|
|
472
|
-
reason: acceptanceInvalid
|
|
473
|
-
? "Stored native acceptance evidence is invalid."
|
|
474
|
-
: "Only terminal transport is proven."
|
|
475
|
-
}
|
|
473
|
+
terminalControl
|
|
474
|
+
}, {
|
|
475
|
+
write: printJson,
|
|
476
|
+
budget: budgetAction,
|
|
477
|
+
nextAction: openClawYieldNextAction,
|
|
478
|
+
summarize: textSummary
|
|
476
479
|
});
|
|
477
480
|
return true;
|
|
478
481
|
}
|
|
@@ -597,7 +600,7 @@ function validateStoredTerminalSubmissionMatch({ owner, receipt, options, termin
|
|
|
597
600
|
}
|
|
598
601
|
return loggedMessage;
|
|
599
602
|
}
|
|
600
|
-
function replayExactStoredTerminalSubmission({ options, terminalControl, requestText, requestHash, messageId, expectedStoreDir, expectedSessionId, expectedTurnId, expectedMessageType, expectedStatePath }) {
|
|
603
|
+
function replayExactStoredTerminalSubmission({ options, terminalControl, requestText, requestHash, messageId, expectedStoreDir, expectedSessionId, expectedTurnId, expectedMessageType, expectedStatePath, userExplicitTerminalId }) {
|
|
601
604
|
const allMatches = listConversations(expectedStoreDir).flatMap((owner) => terminalBridgeSubmissionReceipts(owner)
|
|
602
605
|
.filter((receipt) => stringValue(receipt.message_id) === messageId)
|
|
603
606
|
.map((receipt) => ({ owner, receipt })));
|
|
@@ -663,7 +666,7 @@ function replayExactStoredTerminalSubmission({ options, terminalControl, request
|
|
|
663
666
|
}
|
|
664
667
|
if (!["submitted", "enter_dispatched", "agent_accepted"].includes(String(receipt.status))) {
|
|
665
668
|
throw new Error(`terminal idempotency key ${messageId} has durable ${String(receipt.status)} ` +
|
|
666
|
-
"proof and must not be retried; no terminal input was sent");
|
|
669
|
+
"proof and must not be retried; no additional terminal input was sent");
|
|
667
670
|
}
|
|
668
671
|
const executor = executorForConversation(owner);
|
|
669
672
|
const nativeThreadId = stringValue(receipt.native_thread_id) ??
|
|
@@ -699,52 +702,45 @@ function replayExactStoredTerminalSubmission({ options, terminalControl, request
|
|
|
699
702
|
});
|
|
700
703
|
const acceptanceInvalid = replayReceipt.submission_outcome === "uncertain";
|
|
701
704
|
const callbackExpected = callbackExpectedForConversation(owner);
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
705
|
+
presentTerminalDispatchReplay({
|
|
706
|
+
owner,
|
|
707
|
+
receipt: replayReceipt,
|
|
708
|
+
accepted: replayReceipt.submission_outcome === "agent_accepted",
|
|
709
|
+
acceptanceInvalid,
|
|
710
|
+
receiptConversationId: owner.conversation_id,
|
|
711
|
+
receiptMessageId: messageId,
|
|
712
|
+
callbackExpected,
|
|
713
|
+
...(userExplicitTerminalId
|
|
714
|
+
? {
|
|
715
|
+
userExplicit: {
|
|
716
|
+
terminalId: userExplicitTerminalId,
|
|
717
|
+
messageId
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
: {})
|
|
721
|
+
}, {
|
|
706
722
|
message: replayedMessage,
|
|
707
|
-
delivered: replayReceipt.delivered,
|
|
708
|
-
status: replayReceipt.status,
|
|
709
|
-
submission_outcome: replayReceipt.submission_outcome,
|
|
710
|
-
background: true,
|
|
711
|
-
callback_expected: !acceptanceInvalid && callbackExpected,
|
|
712
|
-
terminal_control: terminalControl,
|
|
713
723
|
executor,
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
? "AKK replayed the stored durable native acceptance receipt and sent no additional terminal input."
|
|
721
|
-
: acceptanceInvalid
|
|
722
|
-
? "AKK replayed an invalid stored native acceptance receipt as uncertain and sent no additional terminal input."
|
|
723
|
-
: "AKK replayed the stored transport proof without upgrading it and sent no additional terminal input.",
|
|
724
|
-
openclaw_next_action: replayReceipt.delivered
|
|
725
|
-
? openClawYieldNextAction({
|
|
726
|
-
conversationId: owner.conversation_id,
|
|
727
|
-
sessionId: sessionIdForConversation(owner),
|
|
728
|
-
turnId: turnIdForConversation(owner),
|
|
729
|
-
source: "terminal_control",
|
|
730
|
-
callbackExpected
|
|
731
|
-
})
|
|
732
|
-
: {
|
|
733
|
-
action: "inspect",
|
|
734
|
-
conversation_id: owner.conversation_id,
|
|
735
|
-
session_id: sessionIdForConversation(owner),
|
|
736
|
-
turn_id: turnIdForConversation(owner),
|
|
737
|
-
do_not_retry: true,
|
|
738
|
-
reason: acceptanceInvalid
|
|
739
|
-
? "Stored native acceptance evidence is invalid."
|
|
740
|
-
: "Only terminal transport is proven."
|
|
741
|
-
}
|
|
724
|
+
terminalControl
|
|
725
|
+
}, {
|
|
726
|
+
write: printJson,
|
|
727
|
+
budget: budgetAction,
|
|
728
|
+
nextAction: openClawYieldNextAction,
|
|
729
|
+
summarize: textSummary
|
|
742
730
|
});
|
|
743
731
|
return true;
|
|
744
732
|
}
|
|
745
733
|
const terminalBridgeSubmission = dispatchReceipt.terminalBridgeSubmission;
|
|
746
734
|
const terminalBridgeSubmissionReceipts = dispatchReceipt.terminalBridgeSubmissionReceipts;
|
|
747
735
|
const unresolvedTerminalBridgeSubmission = dispatchReceipt.unresolvedTerminalBridgeSubmission;
|
|
736
|
+
function durableTerminalInputDispatched(conversation) {
|
|
737
|
+
try {
|
|
738
|
+
return dispatchReceipt.terminalInputDispatchedForConversation(conversation);
|
|
739
|
+
}
|
|
740
|
+
catch {
|
|
741
|
+
return dispatchReceipt.terminalSubmissionIndicatesInputDispatched(terminalBridgeSubmission(conversation));
|
|
742
|
+
}
|
|
743
|
+
}
|
|
748
744
|
function assertNoUnresolvedTerminalBridgeSubmission(storeDir, terminalControl, currentConversationId, requestText) {
|
|
749
745
|
const requestHash = terminalBridgeRequestFingerprint(requestText);
|
|
750
746
|
for (const candidate of listConversations(storeDir)) {
|
|
@@ -1406,12 +1402,24 @@ function terminalSubmissionRetryLedgerFieldsAtState(attempt, state, revision) {
|
|
|
1406
1402
|
return fields;
|
|
1407
1403
|
}
|
|
1408
1404
|
function printTerminalSubmissionRetryOutcome(input) {
|
|
1405
|
+
const submission = terminalBridgeSubmission(input.conversation);
|
|
1406
|
+
const enterDispatched = input.terminalInputSent ||
|
|
1407
|
+
["agent_accepted", "enter_dispatched", "not_accepted"].includes(input.outcome) ||
|
|
1408
|
+
terminalSendEnterDispatched(submission) ||
|
|
1409
|
+
["enter_dispatched", "agent_accepted"].includes(String(input.attempt?.state ?? ""));
|
|
1410
|
+
const terminalInputDispatched = enterDispatched ||
|
|
1411
|
+
durableTerminalInputDispatched(input.conversation);
|
|
1412
|
+
const accepted = input.outcome === "agent_accepted";
|
|
1413
|
+
const pending = input.outcome === "enter_dispatched";
|
|
1414
|
+
const callbackAvailable = !isTerminalDispatchOwnerReleasedStatus(input.conversation.status) &&
|
|
1415
|
+
(accepted || pending) &&
|
|
1416
|
+
callbackExpectedForConversation(input.conversation);
|
|
1409
1417
|
printJson({
|
|
1410
1418
|
session_id: sessionIdForConversation(input.conversation),
|
|
1411
1419
|
turn_id: turnIdForConversation(input.conversation),
|
|
1412
1420
|
conversation: input.conversation,
|
|
1413
|
-
delivered:
|
|
1414
|
-
delivery_receipt:
|
|
1421
|
+
delivered: enterDispatched,
|
|
1422
|
+
delivery_receipt: accepted
|
|
1415
1423
|
? "agent_accepted"
|
|
1416
1424
|
: input.outcome === "enter_dispatched" ||
|
|
1417
1425
|
input.outcome === "not_accepted"
|
|
@@ -1434,9 +1442,16 @@ function printTerminalSubmissionRetryOutcome(input) {
|
|
|
1434
1442
|
replayed: true,
|
|
1435
1443
|
terminal_control: input.terminalControl,
|
|
1436
1444
|
terminal_input_sent: input.terminalInputSent,
|
|
1437
|
-
callback_expected:
|
|
1438
|
-
|
|
1439
|
-
|
|
1445
|
+
callback_expected: callbackAvailable,
|
|
1446
|
+
...terminalSendResultContract({
|
|
1447
|
+
terminalInputDispatched,
|
|
1448
|
+
agentAcceptance: accepted ? "proven" : "unproven",
|
|
1449
|
+
managementMode: "managed",
|
|
1450
|
+
observationMode: callbackAvailable ? "managed_monitor" : "none",
|
|
1451
|
+
callbackAvailable,
|
|
1452
|
+
interactionNotificationAvailable: accepted,
|
|
1453
|
+
interactionResponseAvailable: accepted
|
|
1454
|
+
}),
|
|
1440
1455
|
...(input.attempt
|
|
1441
1456
|
? {
|
|
1442
1457
|
submission_retry_attempt_id: input.attempt.attempt_id,
|
|
@@ -2996,6 +3011,54 @@ async function runSend(options) {
|
|
|
2996
3011
|
}
|
|
2997
3012
|
return runManagedSessionSend(options, messageBody);
|
|
2998
3013
|
}
|
|
3014
|
+
function captureDetachedCodexCandidateSessionClaims(input) {
|
|
3015
|
+
const { inventory, terminal } = input;
|
|
3016
|
+
const candidates = new Map(inventory.roots.map((root) => [
|
|
3017
|
+
root.sessionId.toLowerCase(),
|
|
3018
|
+
root
|
|
3019
|
+
]));
|
|
3020
|
+
const claims = listManagedSessions(input.storeDir).flatMap((session) => {
|
|
3021
|
+
const binding = session.binding;
|
|
3022
|
+
const nativeThreadId = binding?.native_thread_id?.toLowerCase();
|
|
3023
|
+
const candidate = nativeThreadId ? candidates.get(nativeThreadId) : undefined;
|
|
3024
|
+
if (session.status !== "detached" || session.agent !== "codex" ||
|
|
3025
|
+
!binding || !nativeThreadId || !candidate ||
|
|
3026
|
+
!isCompleteNativeRollout(binding.native_process.rollout) ||
|
|
3027
|
+
path.resolve(session.workspace) !==
|
|
3028
|
+
path.resolve(terminal.terminalControl.currentPath ?? "") ||
|
|
3029
|
+
binding.native_process.pid !== inventory.pid ||
|
|
3030
|
+
binding.native_process.process_uuid !== inventory.processUuid ||
|
|
3031
|
+
binding.native_process.process_birth !== inventory.processBirth ||
|
|
3032
|
+
candidate.processUuid !== inventory.processUuid ||
|
|
3033
|
+
candidate.processBirth !== inventory.processBirth ||
|
|
3034
|
+
!terminalControlAliasMatches(binding.terminal_id, binding.terminal_control, terminal.conversationId, terminal.terminalControl) ||
|
|
3035
|
+
!rolloutFileIdentityMatches(binding.native_process.rollout, candidate.rollout)) {
|
|
3036
|
+
return [];
|
|
3037
|
+
}
|
|
3038
|
+
return [{
|
|
3039
|
+
session_id: session.session_id,
|
|
3040
|
+
session_revision: managedSessionRevision(session),
|
|
3041
|
+
session_binding_token: managedSessionBindingToken(session),
|
|
3042
|
+
binding_id: binding.binding_id,
|
|
3043
|
+
binding_generation: binding.generation,
|
|
3044
|
+
native_thread_id: nativeThreadId,
|
|
3045
|
+
process_uuid: inventory.processUuid,
|
|
3046
|
+
process_birth: inventory.processBirth,
|
|
3047
|
+
source_rollout: { ...binding.native_process.rollout },
|
|
3048
|
+
candidate_rollout: { ...candidate.rollout }
|
|
3049
|
+
}];
|
|
3050
|
+
}).sort((left, right) => left.native_thread_id.localeCompare(right.native_thread_id) ||
|
|
3051
|
+
left.session_id.localeCompare(right.session_id));
|
|
3052
|
+
if (claims.length === 0)
|
|
3053
|
+
return undefined;
|
|
3054
|
+
const base = {
|
|
3055
|
+
schema: "agent-knock-knock/codex-detached-candidate-session-claims",
|
|
3056
|
+
version: 1,
|
|
3057
|
+
anchor_fingerprint: input.anchor.anchor_fingerprint,
|
|
3058
|
+
claims
|
|
3059
|
+
};
|
|
3060
|
+
return { ...base, claims_fingerprint: fingerprint(base) };
|
|
3061
|
+
}
|
|
2999
3062
|
function rawTerminalInitialAuthority({ options, terminal, storeDir }) {
|
|
3000
3063
|
const claimedSession = soleBoundManagedSessionClaimForTerminal(storeDir, terminal);
|
|
3001
3064
|
const suppliedExpectedTerminalToken = stringValue(options.expectedTerminalToken);
|
|
@@ -3235,6 +3298,7 @@ function releaseTerminalManagementForExplicitSendUnderWriter(input) {
|
|
|
3235
3298
|
warnings.push(`Turn ${explicitSendTurnReference(current)} dispatch cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3236
3299
|
}
|
|
3237
3300
|
try {
|
|
3301
|
+
const terminalInputSent = durableTerminalInputDispatched(current);
|
|
3238
3302
|
appendExplicitUserCloseEvent(paths.logPath, {
|
|
3239
3303
|
ts: now,
|
|
3240
3304
|
conversation_id: current.conversation_id,
|
|
@@ -3242,7 +3306,8 @@ function releaseTerminalManagementForExplicitSendUnderWriter(input) {
|
|
|
3242
3306
|
status: "closed",
|
|
3243
3307
|
reason: closed.close_reason,
|
|
3244
3308
|
disposition: "user_abandoned_management",
|
|
3245
|
-
terminal_input_sent:
|
|
3309
|
+
terminal_input_sent: terminalInputSent,
|
|
3310
|
+
terminal_input_dispatched: terminalInputSent,
|
|
3246
3311
|
coding_agent_stopped: false,
|
|
3247
3312
|
tmux_pane_closed: false
|
|
3248
3313
|
});
|
|
@@ -3339,21 +3404,19 @@ function explicitTerminalSendIntentRuntimeDir() {
|
|
|
3339
3404
|
: path.join(path.dirname(defaultStoreDir()), "runtime-v2");
|
|
3340
3405
|
}
|
|
3341
3406
|
function assertSafeUserExplicitTerminalSend(status) {
|
|
3342
|
-
|
|
3343
|
-
throw new Error("the explicitly selected terminal is unreachable");
|
|
3344
|
-
}
|
|
3345
|
-
const approval = isRecord(status.approval_state)
|
|
3407
|
+
const approval = status && isRecord(status.approval_state)
|
|
3346
3408
|
? status.approval_state
|
|
3347
3409
|
: undefined;
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3410
|
+
const decision = decideUserExplicitTerminalInputSafety({
|
|
3411
|
+
reachable: status?.reachable === true,
|
|
3412
|
+
approvalScanned: approval?.scanned === true,
|
|
3413
|
+
approvalBlocked: approval?.blocked === true,
|
|
3414
|
+
approvalReason: stringValue(approval?.reason),
|
|
3415
|
+
awaitingApproval: status?.activity_state === "awaiting_approval",
|
|
3416
|
+
questionnaireActive: status?.interaction_state !== undefined
|
|
3417
|
+
});
|
|
3418
|
+
if (decision.action === "reject") {
|
|
3419
|
+
throw new Error(decision.reason);
|
|
3357
3420
|
}
|
|
3358
3421
|
}
|
|
3359
3422
|
function terminalUserSendIntentContext(options, messageBody, terminal) {
|
|
@@ -3432,13 +3495,19 @@ function acquireUserExplicitTerminalSendLock(options, terminal, warnings) {
|
|
|
3432
3495
|
function printReplayedUserExplicitSend(options, terminal, intent, deliveryMode) {
|
|
3433
3496
|
if (deliveryMode === "managed") {
|
|
3434
3497
|
printJson({
|
|
3435
|
-
delivered:
|
|
3498
|
+
delivered: true,
|
|
3436
3499
|
replayed: true,
|
|
3437
3500
|
status: "submission_pending_acceptance",
|
|
3438
3501
|
submission_outcome: "pending_acceptance",
|
|
3439
3502
|
delivery_receipt: "enter_dispatched",
|
|
3440
3503
|
do_not_retry: true,
|
|
3441
|
-
|
|
3504
|
+
...terminalSendResultContract({
|
|
3505
|
+
terminalInputDispatched: true,
|
|
3506
|
+
agentAcceptance: "unproven",
|
|
3507
|
+
managementMode: "managed",
|
|
3508
|
+
observationMode: "none",
|
|
3509
|
+
callbackAvailable: false
|
|
3510
|
+
}),
|
|
3442
3511
|
terminal_id: terminal.conversationId,
|
|
3443
3512
|
message_id: intent.messageId,
|
|
3444
3513
|
scope: "terminal_user_explicit"
|
|
@@ -3454,11 +3523,20 @@ function printReplayedUserExplicitSend(options, terminal, intent, deliveryMode)
|
|
|
3454
3523
|
options,
|
|
3455
3524
|
watchId
|
|
3456
3525
|
});
|
|
3526
|
+
const callbackAvailable = callbackReceipt?.callback_expected === true;
|
|
3457
3527
|
printJson({
|
|
3458
3528
|
delivered: true,
|
|
3459
3529
|
delivered_unmanaged: true,
|
|
3530
|
+
delivery_receipt: "enter_dispatched",
|
|
3460
3531
|
...(callbackReceipt ?? { callback_expected: false }),
|
|
3461
|
-
|
|
3532
|
+
...terminalSendResultContract({
|
|
3533
|
+
terminalInputDispatched: true,
|
|
3534
|
+
agentAcceptance: "unproven",
|
|
3535
|
+
managementMode: "unmanaged",
|
|
3536
|
+
observationMode: callbackAvailable ? "terminal_watch" : "none",
|
|
3537
|
+
callbackAvailable,
|
|
3538
|
+
interactionNotificationAvailable: callbackAvailable
|
|
3539
|
+
}),
|
|
3462
3540
|
replayed: true,
|
|
3463
3541
|
terminal_id: terminal.conversationId,
|
|
3464
3542
|
message_id: intent.messageId,
|
|
@@ -3748,9 +3826,11 @@ async function runUserExplicitTerminalFallback(options, terminal, managedFailure
|
|
|
3748
3826
|
composer_disposition: composerDisposition,
|
|
3749
3827
|
delivered_unmanaged: true
|
|
3750
3828
|
});
|
|
3829
|
+
const callbackAvailable = callbackReceipt?.callback_expected === true;
|
|
3751
3830
|
printJson({
|
|
3752
3831
|
delivered: true,
|
|
3753
3832
|
delivered_unmanaged: true,
|
|
3833
|
+
delivery_receipt: "enter_dispatched",
|
|
3754
3834
|
cleanup_warnings: cleanupWarnings,
|
|
3755
3835
|
intent_warnings: intentWarnings,
|
|
3756
3836
|
callback_warnings: callbackWarnings,
|
|
@@ -3758,7 +3838,14 @@ async function runUserExplicitTerminalFallback(options, terminal, managedFailure
|
|
|
3758
3838
|
terminal_id: fresh.conversationId,
|
|
3759
3839
|
message_id: messageId,
|
|
3760
3840
|
scope: "terminal_user_explicit",
|
|
3761
|
-
|
|
3841
|
+
...terminalSendResultContract({
|
|
3842
|
+
terminalInputDispatched: true,
|
|
3843
|
+
agentAcceptance: "unproven",
|
|
3844
|
+
managementMode: "unmanaged",
|
|
3845
|
+
observationMode: callbackAvailable ? "terminal_watch" : "none",
|
|
3846
|
+
callbackAvailable,
|
|
3847
|
+
interactionNotificationAvailable: callbackAvailable
|
|
3848
|
+
}),
|
|
3762
3849
|
composer_disposition: composerDisposition,
|
|
3763
3850
|
composer_cleared_before_send: true,
|
|
3764
3851
|
// Deprecated compatibility alias. The v22 policy always dispatches
|
|
@@ -3783,7 +3870,14 @@ async function runUserExplicitTerminalFallback(options, terminal, managedFailure
|
|
|
3783
3870
|
}
|
|
3784
3871
|
}
|
|
3785
3872
|
async function runRawTerminalSend(options, messageBody, terminalConversation) {
|
|
3873
|
+
const identifyForeground = options.identifyForeground === true;
|
|
3786
3874
|
const suppliedExpectedTerminalToken = stringValue(options.expectedTerminalToken);
|
|
3875
|
+
if (identifyForeground && terminalConversation.agent !== "codex") {
|
|
3876
|
+
throw new Error("atomic foreground identification currently supports only Codex terminals");
|
|
3877
|
+
}
|
|
3878
|
+
if (identifyForeground && !suppliedExpectedTerminalToken) {
|
|
3879
|
+
throw new Error("atomic foreground identification requires --expected-terminal-token");
|
|
3880
|
+
}
|
|
3787
3881
|
const userExplicitAuthorityRequested = Boolean(suppliedExpectedTerminalToken && options.managedOnly !== true);
|
|
3788
3882
|
const freshExplicitAuthority = userExplicitAuthorityRequested
|
|
3789
3883
|
? hasFreshExplicitTerminalSendToken(options, terminalConversation)
|
|
@@ -3821,6 +3915,14 @@ async function runRawTerminalSend(options, messageBody, terminalConversation) {
|
|
|
3821
3915
|
}
|
|
3822
3916
|
return;
|
|
3823
3917
|
}
|
|
3918
|
+
if (identifyForeground) {
|
|
3919
|
+
// The explicit atomic contract never falls back to unmanaged delivery:
|
|
3920
|
+
// doing so would discard the very identity/respond capability the caller
|
|
3921
|
+
// asked this operation to establish. The task payload is proven unsent,
|
|
3922
|
+
// so release its same-id reservation for an explicit later retry.
|
|
3923
|
+
cancelProvenZeroInputUserExplicitSendIntent(intentLease, terminalConversation, managedResult.failure);
|
|
3924
|
+
throw managedResult.failure;
|
|
3925
|
+
}
|
|
3824
3926
|
return runUserExplicitTerminalFallback(explicitOptions, terminalConversation, managedResult.failure, intentLease);
|
|
3825
3927
|
}
|
|
3826
3928
|
async function runManagedRawTerminalSend(options, messageBody, terminalConversation, deferZeroInputFailurePresentation = false) {
|
|
@@ -3843,6 +3945,343 @@ async function runManagedRawTerminalSend(options, messageBody, terminalConversat
|
|
|
3843
3945
|
`automatic unmanaged fallback: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
3844
3946
|
}
|
|
3845
3947
|
}
|
|
3948
|
+
const USER_EXPLICIT_CALLBACK_SUPERSEDE_REASON = "superseded_by_user_explicit_send";
|
|
3949
|
+
function sessionHasUnresolvedForegroundMutation(storeDir, sessionId) {
|
|
3950
|
+
try {
|
|
3951
|
+
if (listNativeThreadTransitions(storeDir).some((transition) => (transition.source_session_id === sessionId ||
|
|
3952
|
+
transition.target_session_id === sessionId) &&
|
|
3953
|
+
!["committed", "aborted"].includes(transition.status))) {
|
|
3954
|
+
return true;
|
|
3955
|
+
}
|
|
3956
|
+
return listDeferredForegroundTransfers(storeDir).some((transfer) => (transfer.source_session_id === sessionId ||
|
|
3957
|
+
transfer.target_session_id === sessionId) &&
|
|
3958
|
+
!isFinalDeferredForegroundTransferStatus(transfer.status));
|
|
3959
|
+
}
|
|
3960
|
+
catch {
|
|
3961
|
+
// Corrupt or unreadable lifecycle evidence must never be bypassed.
|
|
3962
|
+
return true;
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
async function supersedeExactHumanExplicitCallbackDebt(input) {
|
|
3966
|
+
const { options, terminal, session, candidateInventory, storeDir, scopes, resources } = input;
|
|
3967
|
+
const expectedManagedToken = stringValue(options.expectedTerminalToken);
|
|
3968
|
+
const binding = session?.binding;
|
|
3969
|
+
if (terminal.agent !== "codex" ||
|
|
3970
|
+
stringValue(options.expectedUserExplicitTerminalToken) === undefined ||
|
|
3971
|
+
!session ||
|
|
3972
|
+
session.status !== "bound" ||
|
|
3973
|
+
!binding ||
|
|
3974
|
+
!humanExplicitCallbackDebtManagedTokenMatches(expectedManagedToken, session) ||
|
|
3975
|
+
!candidateInventory ||
|
|
3976
|
+
!exactBoundCodexSendSource({
|
|
3977
|
+
kind: "candidate",
|
|
3978
|
+
sourceSession: session,
|
|
3979
|
+
context: {
|
|
3980
|
+
terminalId: terminal.conversationId,
|
|
3981
|
+
terminalControl: terminal.terminalControl,
|
|
3982
|
+
pid: terminal.pid,
|
|
3983
|
+
workspace: terminal.terminalControl.currentPath,
|
|
3984
|
+
liveProcessUuid: candidateInventory.processUuid,
|
|
3985
|
+
liveProcessBirth: candidateInventory.processBirth
|
|
3986
|
+
},
|
|
3987
|
+
inventory: candidateInventory,
|
|
3988
|
+
sourceRolloutAuthority: "present"
|
|
3989
|
+
}) ||
|
|
3990
|
+
sessionHasUnresolvedForegroundMutation(storeDir, session.session_id)) {
|
|
3991
|
+
return false;
|
|
3992
|
+
}
|
|
3993
|
+
const bindingTurns = managedTurnsForSession(storeDir, session.session_id)
|
|
3994
|
+
.filter((turn) => turn.terminal_binding_id === binding.binding_id &&
|
|
3995
|
+
turn.terminal_binding_generation === binding.generation);
|
|
3996
|
+
const dispositions = bindingTurns.map((turn) => ({
|
|
3997
|
+
turn,
|
|
3998
|
+
disposition: humanExplicitCallbackDebtRetirementDisposition(turn, session)
|
|
3999
|
+
}));
|
|
4000
|
+
const candidates = dispositions
|
|
4001
|
+
.filter(({ disposition }) => disposition === "supersedable")
|
|
4002
|
+
.map(({ turn }) => turn);
|
|
4003
|
+
if (candidates.length === 0 ||
|
|
4004
|
+
dispositions.some(({ disposition }) => disposition === undefined)) {
|
|
4005
|
+
return false;
|
|
4006
|
+
}
|
|
4007
|
+
let superseded = 0;
|
|
4008
|
+
for (const candidate of candidates) {
|
|
4009
|
+
const paths = pathsForConversation(candidate.conversation_id, storeDir);
|
|
4010
|
+
const expectedDelivery = JSON.stringify(candidate.callback_delivery);
|
|
4011
|
+
await withTerminalDispatchStateScope(scopes, resources, paths.statePath, paths.logPath, async () => {
|
|
4012
|
+
const current = loadState(paths.statePath);
|
|
4013
|
+
if (JSON.stringify(current.callback_delivery) !== expectedDelivery ||
|
|
4014
|
+
humanExplicitCallbackDebtDisposition(current, session) !==
|
|
4015
|
+
"supersedable") {
|
|
4016
|
+
return;
|
|
4017
|
+
}
|
|
4018
|
+
const at = cliNow().toISOString();
|
|
4019
|
+
const next = supersedeUnacceptedCallbackDeliveries(current, {
|
|
4020
|
+
at,
|
|
4021
|
+
reason: USER_EXPLICIT_CALLBACK_SUPERSEDE_REASON
|
|
4022
|
+
});
|
|
4023
|
+
if (next.callback_delivery === current.callback_delivery)
|
|
4024
|
+
return;
|
|
4025
|
+
saveState(paths.statePath, next);
|
|
4026
|
+
appendEvent(paths.logPath, {
|
|
4027
|
+
ts: at,
|
|
4028
|
+
conversation_id: current.conversation_id,
|
|
4029
|
+
event: "callback_delivery_superseded_by_user_explicit_send",
|
|
4030
|
+
status: current.status,
|
|
4031
|
+
reason: USER_EXPLICIT_CALLBACK_SUPERSEDE_REASON,
|
|
4032
|
+
prior_callback_status: isRecord(current.callback_delivery)
|
|
4033
|
+
? current.callback_delivery.status
|
|
4034
|
+
: undefined,
|
|
4035
|
+
prior_callback_attempt_disposition: callbackDeliveryAttemptOutcome(current.callback_delivery)
|
|
4036
|
+
?.disposition,
|
|
4037
|
+
// An uncertain transport may already have reached the controller.
|
|
4038
|
+
// This records that the human-priority Send retired only future
|
|
4039
|
+
// callback retries; it never asserts that delivery did not occur.
|
|
4040
|
+
human_override_of_uncertain_callback: callbackDeliveryAttemptOutcome(current.callback_delivery)
|
|
4041
|
+
?.disposition === "uncertain",
|
|
4042
|
+
terminal_input_sent: false,
|
|
4043
|
+
terminal_input_dispatched: false
|
|
4044
|
+
});
|
|
4045
|
+
superseded += 1;
|
|
4046
|
+
});
|
|
4047
|
+
}
|
|
4048
|
+
return superseded === candidates.length;
|
|
4049
|
+
}
|
|
4050
|
+
function rawTerminalObservedIdentityForPreparation(input) {
|
|
4051
|
+
if (input.atomicForegroundIdentification ||
|
|
4052
|
+
input.sourceLessCandidateInventory !== undefined ||
|
|
4053
|
+
input.observation.status !== "resolved") {
|
|
4054
|
+
return undefined;
|
|
4055
|
+
}
|
|
4056
|
+
return input.observation.identity;
|
|
4057
|
+
}
|
|
4058
|
+
async function maybePrepareVerifiedEmptyCodexHandoff(input) {
|
|
4059
|
+
if (input.implicitCandidateAuthority ||
|
|
4060
|
+
input.atomicForegroundIdentification) {
|
|
4061
|
+
return undefined;
|
|
4062
|
+
}
|
|
4063
|
+
return maybeDetachVerifiedEmptyCodexSource({
|
|
4064
|
+
options: input.options,
|
|
4065
|
+
terminal: input.terminal,
|
|
4066
|
+
sourceSession: input.sourceSession,
|
|
4067
|
+
observation: input.observation
|
|
4068
|
+
});
|
|
4069
|
+
}
|
|
4070
|
+
function assertAtomicForegroundCandidateInventory(input) {
|
|
4071
|
+
if (!input.enabled || input.claimedSession === undefined)
|
|
4072
|
+
return;
|
|
4073
|
+
if (input.inventory && input.inventory.roots.length > 0)
|
|
4074
|
+
return;
|
|
4075
|
+
throw new Error("atomic foreground identification requires a fresh nonempty " +
|
|
4076
|
+
"candidate-set acceptance boundary; no task input was sent");
|
|
4077
|
+
}
|
|
4078
|
+
function assertAtomicForegroundAcceptanceBoundary(input) {
|
|
4079
|
+
if (!input.enabled)
|
|
4080
|
+
return;
|
|
4081
|
+
if (input.deferred?.candidateAcceptanceAnchor || input.postSend)
|
|
4082
|
+
return;
|
|
4083
|
+
throw new Error("atomic foreground identification could not establish an exact " +
|
|
4084
|
+
"task-acceptance boundary; no task input was sent");
|
|
4085
|
+
}
|
|
4086
|
+
async function prepareRawTerminalDispatchAuthority(input) {
|
|
4087
|
+
const { options, messageBody, terminal, storeDir, scopes, resources } = input;
|
|
4088
|
+
const atomicForegroundIdentification = options.identifyForeground === true;
|
|
4089
|
+
const initialAuthority = rawTerminalInitialAuthority({
|
|
4090
|
+
options,
|
|
4091
|
+
terminal,
|
|
4092
|
+
storeDir
|
|
4093
|
+
});
|
|
4094
|
+
let { claimedSession, knownCodexCompanions } = initialAuthority;
|
|
4095
|
+
const { suppliedExpectedTerminalToken, implicitCodexCandidateAuthority } = initialAuthority;
|
|
4096
|
+
const userExplicitTerminalToken = stringValue(options.expectedUserExplicitTerminalToken);
|
|
4097
|
+
const nativeIdentityObservation = await observeCurrentNativeAgentSessionIdentity({
|
|
4098
|
+
options,
|
|
4099
|
+
agent: terminal.agent,
|
|
4100
|
+
pid: terminal.pid,
|
|
4101
|
+
cwd: terminal.terminalControl.currentPath,
|
|
4102
|
+
preferredSessionId: knownCodexCompanions.primary
|
|
4103
|
+
? claimedSession?.binding?.native_thread_id
|
|
4104
|
+
: undefined,
|
|
4105
|
+
allowedCompanionIdentity: knownCodexCompanions.primary,
|
|
4106
|
+
allowedAdditionalIdentities: knownCodexCompanions.additional
|
|
4107
|
+
});
|
|
4108
|
+
let deferredCodexCandidateInventory;
|
|
4109
|
+
if (terminal.agent === "codex" &&
|
|
4110
|
+
(userExplicitTerminalToken ||
|
|
4111
|
+
suppliedExpectedTerminalToken ||
|
|
4112
|
+
implicitCodexCandidateAuthority)) {
|
|
4113
|
+
try {
|
|
4114
|
+
const inventory = await inspectCodexOpenRootRolloutInventory({
|
|
4115
|
+
options,
|
|
4116
|
+
pid: terminal.pid,
|
|
4117
|
+
cwd: terminal.terminalControl.currentPath
|
|
4118
|
+
});
|
|
4119
|
+
// An empty set is meaningful only for a source-less human Send: it is
|
|
4120
|
+
// the frozen proof that the first accepting rollout must appear after
|
|
4121
|
+
// Enter. Preserve the older bound/deferred behavior otherwise.
|
|
4122
|
+
if (inventory.roots.length > 0 ||
|
|
4123
|
+
(userExplicitTerminalToken !== undefined && claimedSession === undefined)) {
|
|
4124
|
+
deferredCodexCandidateInventory = inventory;
|
|
4125
|
+
}
|
|
4126
|
+
}
|
|
4127
|
+
catch (error) {
|
|
4128
|
+
if ((userExplicitTerminalToken !== undefined &&
|
|
4129
|
+
claimedSession === undefined) ||
|
|
4130
|
+
implicitCodexCandidateAuthority ||
|
|
4131
|
+
nativeIdentityObservation.status === "unavailable") {
|
|
4132
|
+
throw new Error(`native Codex foreground attribution requires a fresh complete ` +
|
|
4133
|
+
`open-root inventory: ${error instanceof Error ? error.message : String(error)}`);
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
assertRawTerminalCandidateAuthority({
|
|
4138
|
+
terminal,
|
|
4139
|
+
nativeIdentityObservation,
|
|
4140
|
+
deferredCodexCandidateInventory,
|
|
4141
|
+
implicitCodexCandidateAuthority
|
|
4142
|
+
});
|
|
4143
|
+
const sourceLessCodexCandidateInventory = terminal.agent === "codex" &&
|
|
4144
|
+
userExplicitTerminalToken !== undefined &&
|
|
4145
|
+
claimedSession === undefined
|
|
4146
|
+
? deferredCodexCandidateInventory
|
|
4147
|
+
: undefined;
|
|
4148
|
+
let currentNativeIdentity = rawTerminalObservedIdentityForPreparation({
|
|
4149
|
+
atomicForegroundIdentification,
|
|
4150
|
+
sourceLessCandidateInventory: sourceLessCodexCandidateInventory,
|
|
4151
|
+
observation: nativeIdentityObservation
|
|
4152
|
+
});
|
|
4153
|
+
// A fresh nonempty inventory is the stronger physical authority for an
|
|
4154
|
+
// implicit candidate send. Do not let an earlier verified-absent
|
|
4155
|
+
// observation divert this path into the token-only empty handoff.
|
|
4156
|
+
const verifiedEmptyHandoff = await maybePrepareVerifiedEmptyCodexHandoff({
|
|
4157
|
+
options,
|
|
4158
|
+
terminal,
|
|
4159
|
+
sourceSession: claimedSession,
|
|
4160
|
+
observation: nativeIdentityObservation,
|
|
4161
|
+
implicitCandidateAuthority: implicitCodexCandidateAuthority,
|
|
4162
|
+
atomicForegroundIdentification
|
|
4163
|
+
});
|
|
4164
|
+
if (verifiedEmptyHandoff) {
|
|
4165
|
+
// The old rollout is conclusively closed. Never carry it forward as a
|
|
4166
|
+
// pre-materialization companion for the new virgin Session.
|
|
4167
|
+
claimedSession = undefined;
|
|
4168
|
+
knownCodexCompanions = { additional: [] };
|
|
4169
|
+
currentNativeIdentity = undefined;
|
|
4170
|
+
}
|
|
4171
|
+
const physicalNativeIdentityBeforeHandoff = currentNativeIdentity;
|
|
4172
|
+
// The status card emitted by identify_foreground is diagnostic evidence,
|
|
4173
|
+
// never handoff authority. Let the existing candidate-set/deferred path
|
|
4174
|
+
// bind only the rollout that later accepts the exact task request.
|
|
4175
|
+
let handoff = atomicForegroundIdentification
|
|
4176
|
+
? { identity: undefined, adopted: false }
|
|
4177
|
+
: await maybeAdoptObservedExternalThread({
|
|
4178
|
+
options,
|
|
4179
|
+
terminal,
|
|
4180
|
+
// A no-token raw send to an already managed rollout-backed Codex pane
|
|
4181
|
+
// is an internal follow-current delegation, not a sole-root
|
|
4182
|
+
// continuation.
|
|
4183
|
+
sourceSession: implicitCodexCandidateAuthority
|
|
4184
|
+
? undefined
|
|
4185
|
+
: claimedSession,
|
|
4186
|
+
resolvedIdentity: sourceLessCodexCandidateInventory
|
|
4187
|
+
? undefined
|
|
4188
|
+
: currentNativeIdentity,
|
|
4189
|
+
storeDir
|
|
4190
|
+
});
|
|
4191
|
+
currentNativeIdentity =
|
|
4192
|
+
handoff.adopted && terminal.agent === "codex" &&
|
|
4193
|
+
!handoff.session?.binding?.native_process.rollout
|
|
4194
|
+
? physicalNativeIdentityBeforeHandoff
|
|
4195
|
+
: handoff.identity;
|
|
4196
|
+
if (!handoff.adopted) {
|
|
4197
|
+
await supersedeExactHumanExplicitCallbackDebt({
|
|
4198
|
+
options,
|
|
4199
|
+
terminal,
|
|
4200
|
+
session: claimedSession,
|
|
4201
|
+
candidateInventory: deferredCodexCandidateInventory,
|
|
4202
|
+
storeDir,
|
|
4203
|
+
scopes,
|
|
4204
|
+
resources
|
|
4205
|
+
});
|
|
4206
|
+
}
|
|
4207
|
+
assertAtomicForegroundCandidateInventory({
|
|
4208
|
+
enabled: atomicForegroundIdentification,
|
|
4209
|
+
claimedSession,
|
|
4210
|
+
inventory: deferredCodexCandidateInventory
|
|
4211
|
+
});
|
|
4212
|
+
const deferredCodexForegroundBinding = !handoff.adopted
|
|
4213
|
+
? await prepareDeferredCodexForegroundBinding({
|
|
4214
|
+
options,
|
|
4215
|
+
scope: bindDeferredForegroundWriterScope(scopes, resources),
|
|
4216
|
+
terminal,
|
|
4217
|
+
sourceSession: claimedSession,
|
|
4218
|
+
observation: nativeIdentityObservation,
|
|
4219
|
+
candidateInventory: deferredCodexCandidateInventory,
|
|
4220
|
+
requestText: String(messageBody),
|
|
4221
|
+
allowImplicitFreshAuthority: implicitCodexCandidateAuthority
|
|
4222
|
+
})
|
|
4223
|
+
: undefined;
|
|
4224
|
+
const postSendCodexCandidateAnchor = terminal.agent === "codex" &&
|
|
4225
|
+
userExplicitTerminalToken !== undefined &&
|
|
4226
|
+
sourceLessCodexCandidateInventory !== undefined &&
|
|
4227
|
+
verifiedEmptyHandoff === undefined &&
|
|
4228
|
+
!handoff.adopted &&
|
|
4229
|
+
deferredCodexForegroundBinding === undefined &&
|
|
4230
|
+
claimedSession === undefined
|
|
4231
|
+
? captureCodexCandidateSetRolloutAcceptanceAnchor({
|
|
4232
|
+
inventory: sourceLessCodexCandidateInventory,
|
|
4233
|
+
now: cliNow()
|
|
4234
|
+
})
|
|
4235
|
+
: undefined;
|
|
4236
|
+
const postSendCodexDetachedSessionClaims = postSendCodexCandidateAnchor &&
|
|
4237
|
+
sourceLessCodexCandidateInventory
|
|
4238
|
+
? captureDetachedCodexCandidateSessionClaims({
|
|
4239
|
+
storeDir,
|
|
4240
|
+
terminal,
|
|
4241
|
+
inventory: sourceLessCodexCandidateInventory,
|
|
4242
|
+
anchor: postSendCodexCandidateAnchor
|
|
4243
|
+
})
|
|
4244
|
+
: undefined;
|
|
4245
|
+
assertAtomicForegroundAcceptanceBoundary({
|
|
4246
|
+
enabled: atomicForegroundIdentification,
|
|
4247
|
+
deferred: deferredCodexForegroundBinding,
|
|
4248
|
+
postSend: postSendCodexCandidateAnchor
|
|
4249
|
+
});
|
|
4250
|
+
const freshSendAuthority = decideTerminalSendAuthority({
|
|
4251
|
+
ownership: "conflict",
|
|
4252
|
+
verifiedEmpty: Boolean(verifiedEmptyHandoff),
|
|
4253
|
+
externalHandoff: handoff.adopted,
|
|
4254
|
+
deferred: Boolean(deferredCodexForegroundBinding)
|
|
4255
|
+
});
|
|
4256
|
+
if (freshSendAuthority.mode === "deferred" &&
|
|
4257
|
+
deferredCodexForegroundBinding) {
|
|
4258
|
+
claimedSession = undefined;
|
|
4259
|
+
knownCodexCompanions = { additional: [] };
|
|
4260
|
+
currentNativeIdentity = undefined;
|
|
4261
|
+
handoff = { identity: undefined, adopted: false };
|
|
4262
|
+
}
|
|
4263
|
+
else if ((userExplicitTerminalToken ||
|
|
4264
|
+
suppliedExpectedTerminalToken ||
|
|
4265
|
+
implicitCodexCandidateAuthority) &&
|
|
4266
|
+
!postSendCodexCandidateAnchor &&
|
|
4267
|
+
freshSendAuthority.mode === "conflict") {
|
|
4268
|
+
throw new Error("managed continuation authority is unavailable for the current " +
|
|
4269
|
+
"terminal context" +
|
|
4270
|
+
(userExplicitTerminalToken
|
|
4271
|
+
? "; the physical terminal authority remains valid"
|
|
4272
|
+
: "; refresh AKK list before retrying managed delivery"));
|
|
4273
|
+
}
|
|
4274
|
+
return {
|
|
4275
|
+
nativeIdentityObservation,
|
|
4276
|
+
knownCodexCompanions,
|
|
4277
|
+
currentNativeIdentity,
|
|
4278
|
+
verifiedEmptyHandoff,
|
|
4279
|
+
handoff,
|
|
4280
|
+
deferredCodexForegroundBinding,
|
|
4281
|
+
postSendCodexCandidateAnchor,
|
|
4282
|
+
postSendCodexDetachedSessionClaims
|
|
4283
|
+
};
|
|
4284
|
+
}
|
|
3846
4285
|
async function runManagedRawTerminalSendAttempt(options, messageBody, terminalConversation, deferZeroInputFailurePresentation, attempt) {
|
|
3847
4286
|
// A token copied from list is authority for exactly the advertised full
|
|
3848
4287
|
// terminal selector. Reject aliases and implicit/no-selector resolution
|
|
@@ -3856,7 +4295,33 @@ async function runManagedRawTerminalSendAttempt(options, messageBody, terminalCo
|
|
|
3856
4295
|
}
|
|
3857
4296
|
const rawStoreDir = storeDirFromOptions(options);
|
|
3858
4297
|
let controlSendResult;
|
|
3859
|
-
|
|
4298
|
+
const atomicForegroundIdentification = options.identifyForeground === true;
|
|
4299
|
+
foregroundIdentificationProofs.delete(options);
|
|
4300
|
+
const userExplicitLockOptions = deferZeroInputFailurePresentation
|
|
4301
|
+
? {
|
|
4302
|
+
terminalTimeoutMs: 0,
|
|
4303
|
+
storeWriterTimeoutMs: USER_EXPLICIT_MANAGED_LOCK_GRACE_MS
|
|
4304
|
+
}
|
|
4305
|
+
: undefined;
|
|
4306
|
+
const lockOptions = atomicForegroundIdentification
|
|
4307
|
+
? {
|
|
4308
|
+
...userExplicitLockOptions,
|
|
4309
|
+
afterTerminalAcquired: async () => {
|
|
4310
|
+
const expectedTerminalToken = required(stringValue(options.expectedUserExplicitTerminalToken), "atomic foreground identification requires fresh physical terminal authority");
|
|
4311
|
+
const proof = await identifyCodexForegroundWhileLocked({
|
|
4312
|
+
options,
|
|
4313
|
+
terminal: terminalConversation,
|
|
4314
|
+
expectedTerminalToken
|
|
4315
|
+
});
|
|
4316
|
+
if (proof.terminalId !== terminalConversation.conversationId ||
|
|
4317
|
+
proof.pid !== terminalConversation.pid) {
|
|
4318
|
+
throw new Error("foreground identification returned evidence for a different terminal");
|
|
4319
|
+
}
|
|
4320
|
+
foregroundIdentificationProofs.set(options, proof);
|
|
4321
|
+
}
|
|
4322
|
+
}
|
|
4323
|
+
: userExplicitLockOptions;
|
|
4324
|
+
await withCanonicalMutationLocks(terminalWriterMutationLocks(rawStoreDir, terminalConversation.terminalControl, lockOptions), async (scopes, resources) => {
|
|
3860
4325
|
await assertFreshUserExplicitTerminalSendTargetWhileLocked(options, terminalConversation);
|
|
3861
4326
|
await mutationDispatchLedger.beforeMutation(scopes, resources, options, terminalConversation);
|
|
3862
4327
|
// Upgrade legacy Stores before resolving their Session authority. The
|
|
@@ -3868,129 +4333,24 @@ async function runManagedRawTerminalSendAttempt(options, messageBody, terminalCo
|
|
|
3868
4333
|
terminalControl: terminalConversation.terminalControl,
|
|
3869
4334
|
requestText: String(messageBody),
|
|
3870
4335
|
expectedStoreDir: rawStoreDir,
|
|
3871
|
-
expectedMessageType: "task"
|
|
4336
|
+
expectedMessageType: "task",
|
|
4337
|
+
...(stringValue(options.expectedUserExplicitTerminalToken)
|
|
4338
|
+
? { userExplicitTerminalId: terminalConversation.conversationId }
|
|
4339
|
+
: {})
|
|
3872
4340
|
})) {
|
|
3873
4341
|
controlSendResult = { outcome: "replayed" };
|
|
3874
4342
|
attempt.result = controlSendResult;
|
|
3875
4343
|
return;
|
|
3876
4344
|
}
|
|
3877
4345
|
terminalListCliFacade.assertTerminalIncarnationCanStartTurn(rawStoreDir, terminalConversation.terminalControl);
|
|
3878
|
-
const
|
|
3879
|
-
options,
|
|
3880
|
-
terminal: terminalConversation,
|
|
3881
|
-
storeDir: rawStoreDir
|
|
3882
|
-
});
|
|
3883
|
-
let { claimedSession, knownCodexCompanions } = initialAuthority;
|
|
3884
|
-
const { suppliedExpectedTerminalToken, implicitCodexCandidateAuthority } = initialAuthority;
|
|
3885
|
-
const nativeIdentityObservation = await observeCurrentNativeAgentSessionIdentity({
|
|
3886
|
-
options,
|
|
3887
|
-
agent: terminalConversation.agent,
|
|
3888
|
-
pid: terminalConversation.pid,
|
|
3889
|
-
cwd: terminalConversation.terminalControl.currentPath,
|
|
3890
|
-
preferredSessionId: knownCodexCompanions.primary
|
|
3891
|
-
? claimedSession?.binding?.native_thread_id
|
|
3892
|
-
: undefined,
|
|
3893
|
-
allowedCompanionIdentity: knownCodexCompanions.primary,
|
|
3894
|
-
allowedAdditionalIdentities: knownCodexCompanions.additional
|
|
3895
|
-
});
|
|
3896
|
-
let deferredCodexCandidateInventory;
|
|
3897
|
-
if (terminalConversation.agent === "codex" &&
|
|
3898
|
-
(suppliedExpectedTerminalToken || implicitCodexCandidateAuthority)) {
|
|
3899
|
-
try {
|
|
3900
|
-
const inventory = await inspectCodexOpenRootRolloutInventory({
|
|
3901
|
-
options,
|
|
3902
|
-
pid: terminalConversation.pid,
|
|
3903
|
-
cwd: terminalConversation.terminalControl.currentPath
|
|
3904
|
-
});
|
|
3905
|
-
if (inventory.roots.length > 0) {
|
|
3906
|
-
deferredCodexCandidateInventory = inventory;
|
|
3907
|
-
}
|
|
3908
|
-
}
|
|
3909
|
-
catch (error) {
|
|
3910
|
-
if (implicitCodexCandidateAuthority ||
|
|
3911
|
-
nativeIdentityObservation.status === "unavailable") {
|
|
3912
|
-
throw new Error(`native Codex foreground attribution requires a fresh complete ` +
|
|
3913
|
-
`open-root inventory: ${error instanceof Error ? error.message : String(error)}`);
|
|
3914
|
-
}
|
|
3915
|
-
}
|
|
3916
|
-
}
|
|
3917
|
-
assertRawTerminalCandidateAuthority({
|
|
3918
|
-
terminal: terminalConversation,
|
|
3919
|
-
nativeIdentityObservation,
|
|
3920
|
-
deferredCodexCandidateInventory,
|
|
3921
|
-
implicitCodexCandidateAuthority
|
|
3922
|
-
});
|
|
3923
|
-
let currentNativeIdentity = nativeIdentityObservation.status === "resolved"
|
|
3924
|
-
? nativeIdentityObservation.identity
|
|
3925
|
-
: undefined;
|
|
3926
|
-
// A fresh nonempty inventory is the stronger physical authority for an
|
|
3927
|
-
// implicit candidate send. Do not let an earlier verified-absent
|
|
3928
|
-
// observation divert this path into the token-only empty handoff.
|
|
3929
|
-
const verifiedEmptyHandoff = implicitCodexCandidateAuthority
|
|
3930
|
-
? undefined
|
|
3931
|
-
: await maybeDetachVerifiedEmptyCodexSource({
|
|
3932
|
-
options,
|
|
3933
|
-
terminal: terminalConversation,
|
|
3934
|
-
sourceSession: claimedSession,
|
|
3935
|
-
observation: nativeIdentityObservation
|
|
3936
|
-
});
|
|
3937
|
-
if (verifiedEmptyHandoff) {
|
|
3938
|
-
// The old rollout is conclusively closed. Never carry it forward as
|
|
3939
|
-
// a pre-materialization companion for the new virgin Session.
|
|
3940
|
-
claimedSession = undefined;
|
|
3941
|
-
knownCodexCompanions = { additional: [] };
|
|
3942
|
-
currentNativeIdentity = undefined;
|
|
3943
|
-
}
|
|
3944
|
-
const physicalNativeIdentityBeforeHandoff = currentNativeIdentity;
|
|
3945
|
-
let handoff = await maybeAdoptObservedExternalThread({
|
|
4346
|
+
const { nativeIdentityObservation, knownCodexCompanions, currentNativeIdentity, verifiedEmptyHandoff, handoff, deferredCodexForegroundBinding, postSendCodexCandidateAnchor, postSendCodexDetachedSessionClaims } = await prepareRawTerminalDispatchAuthority({
|
|
3946
4347
|
options,
|
|
4348
|
+
messageBody,
|
|
3947
4349
|
terminal: terminalConversation,
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
// transfer below owns attribution and lineage.
|
|
3952
|
-
sourceSession: implicitCodexCandidateAuthority
|
|
3953
|
-
? undefined
|
|
3954
|
-
: claimedSession,
|
|
3955
|
-
resolvedIdentity: currentNativeIdentity,
|
|
3956
|
-
storeDir: rawStoreDir
|
|
3957
|
-
});
|
|
3958
|
-
currentNativeIdentity =
|
|
3959
|
-
handoff.adopted &&
|
|
3960
|
-
terminalConversation.agent === "codex" &&
|
|
3961
|
-
!handoff.session?.binding?.native_process.rollout
|
|
3962
|
-
? physicalNativeIdentityBeforeHandoff
|
|
3963
|
-
: handoff.identity;
|
|
3964
|
-
const deferredCodexForegroundBinding = !handoff.adopted
|
|
3965
|
-
? await prepareDeferredCodexForegroundBinding({
|
|
3966
|
-
options,
|
|
3967
|
-
scope: bindDeferredForegroundWriterScope(scopes, resources),
|
|
3968
|
-
terminal: terminalConversation,
|
|
3969
|
-
sourceSession: claimedSession,
|
|
3970
|
-
observation: nativeIdentityObservation,
|
|
3971
|
-
candidateInventory: deferredCodexCandidateInventory,
|
|
3972
|
-
requestText: String(messageBody),
|
|
3973
|
-
allowImplicitFreshAuthority: implicitCodexCandidateAuthority
|
|
3974
|
-
})
|
|
3975
|
-
: undefined;
|
|
3976
|
-
const freshSendAuthority = decideTerminalSendAuthority({
|
|
3977
|
-
ownership: "conflict",
|
|
3978
|
-
verifiedEmpty: Boolean(verifiedEmptyHandoff),
|
|
3979
|
-
externalHandoff: handoff.adopted,
|
|
3980
|
-
deferred: Boolean(deferredCodexForegroundBinding)
|
|
4350
|
+
storeDir: rawStoreDir,
|
|
4351
|
+
scopes,
|
|
4352
|
+
resources
|
|
3981
4353
|
});
|
|
3982
|
-
if (freshSendAuthority.mode === "deferred" &&
|
|
3983
|
-
deferredCodexForegroundBinding) {
|
|
3984
|
-
claimedSession = undefined;
|
|
3985
|
-
knownCodexCompanions = { additional: [] };
|
|
3986
|
-
currentNativeIdentity = undefined;
|
|
3987
|
-
handoff = { identity: undefined, adopted: false };
|
|
3988
|
-
}
|
|
3989
|
-
else if ((suppliedExpectedTerminalToken || implicitCodexCandidateAuthority) &&
|
|
3990
|
-
freshSendAuthority.mode === "conflict") {
|
|
3991
|
-
throw new Error("the expected terminal token no longer authorizes the current " +
|
|
3992
|
-
"terminal context; refresh AKK list");
|
|
3993
|
-
}
|
|
3994
4354
|
let managedSession = deferredCodexForegroundBinding
|
|
3995
4355
|
? undefined
|
|
3996
4356
|
: handoff.session ??
|
|
@@ -4068,7 +4428,7 @@ async function runManagedRawTerminalSendAttempt(options, messageBody, terminalCo
|
|
|
4068
4428
|
const materializedNativeIdentity = currentNativeIdentity?.sessionId === logicalNativeIdentity?.sessionId
|
|
4069
4429
|
? currentNativeIdentity
|
|
4070
4430
|
: undefined;
|
|
4071
|
-
if (!(handoff.adopted &&
|
|
4431
|
+
if (options.identifyForeground !== true && !(handoff.adopted &&
|
|
4072
4432
|
terminalConversation.agent === "codex" &&
|
|
4073
4433
|
!managedSession.binding?.native_process.rollout)) {
|
|
4074
4434
|
await verifyCodexPendingManagedSendStatus({
|
|
@@ -4197,10 +4557,14 @@ async function runManagedRawTerminalSendAttempt(options, messageBody, terminalCo
|
|
|
4197
4557
|
}
|
|
4198
4558
|
: undefined,
|
|
4199
4559
|
verifiedEmptyCodexHandoff: verifiedEmptyHandoff?.boundary,
|
|
4560
|
+
postSendCodexCandidateAnchor,
|
|
4561
|
+
postSendCodexDetachedSessionClaims,
|
|
4200
4562
|
deferredCodexForegroundBinding
|
|
4201
4563
|
});
|
|
4202
4564
|
attempt.result = controlSendResult;
|
|
4203
|
-
}, deferZeroInputFailurePresentation
|
|
4565
|
+
}, deferZeroInputFailurePresentation
|
|
4566
|
+
? { timeoutMs: USER_EXPLICIT_MANAGED_LOCK_GRACE_MS }
|
|
4567
|
+
: undefined);
|
|
4204
4568
|
});
|
|
4205
4569
|
if (!controlSendResult) {
|
|
4206
4570
|
throw new Error("managed terminal Send completed without an outcome");
|
|
@@ -5438,8 +5802,121 @@ async function runTerminalConversationApprove({ options, terminal }) {
|
|
|
5438
5802
|
releaseTerminalLock();
|
|
5439
5803
|
}
|
|
5440
5804
|
}
|
|
5805
|
+
function terminalSendCandidateAcceptanceAnchor(request) {
|
|
5806
|
+
return request.deferredCodexForegroundBinding?.candidateAcceptanceAnchor ??
|
|
5807
|
+
request.postSendCodexCandidateAnchor;
|
|
5808
|
+
}
|
|
5809
|
+
function assertTerminalNativeBindingBeforeSend(input) {
|
|
5810
|
+
if (!input.needsPostSendNativeBinding) {
|
|
5811
|
+
input.execution.assertTurnIdentity({
|
|
5812
|
+
conversation: input.conversation,
|
|
5813
|
+
currentIdentity: input.currentNativeIdentity,
|
|
5814
|
+
operation: "send to"
|
|
5815
|
+
});
|
|
5816
|
+
return;
|
|
5817
|
+
}
|
|
5818
|
+
if (input.currentNativeIdentity &&
|
|
5819
|
+
!nativeIdentityMatchesCodexPreMaterialization(input.currentNativeIdentity, input.allowedPreMaterializationIdentity)) {
|
|
5820
|
+
throw new Error("native agent session appeared while preparing an unmaterialized terminal binding; refresh list and retry");
|
|
5821
|
+
}
|
|
5822
|
+
}
|
|
5823
|
+
function terminalPreSendRuntime(input) {
|
|
5824
|
+
const { request, terminalControl, terminalAgentPid } = input;
|
|
5825
|
+
const { conversation, nextConversation, executor, message, allowedPreMaterializationIdentity, allowedAdditionalIdentities = [], deferredCodexForegroundBinding } = request;
|
|
5826
|
+
const candidateAnchor = terminalSendCandidateAcceptanceAnchor(request);
|
|
5827
|
+
return {
|
|
5828
|
+
...(candidateAnchor
|
|
5829
|
+
? terminalRuntimeForLiveIdentity({
|
|
5830
|
+
terminal: deferredCodexForegroundBinding?.terminal ?? {
|
|
5831
|
+
conversationId: conversation.conversation_id,
|
|
5832
|
+
agent: executor.kind,
|
|
5833
|
+
pid: terminalAgentPid,
|
|
5834
|
+
terminalControl
|
|
5835
|
+
},
|
|
5836
|
+
physicalOnly: true
|
|
5837
|
+
})
|
|
5838
|
+
: terminalRuntimeIdentityForConversation(nextConversation, terminalControl)),
|
|
5839
|
+
allowedPreMaterializationNativeIdentity: allowedPreMaterializationIdentity,
|
|
5840
|
+
allowedAdditionalNativeIdentities: allowedAdditionalIdentities,
|
|
5841
|
+
messageId: message.id
|
|
5842
|
+
};
|
|
5843
|
+
}
|
|
5844
|
+
function assertTerminalPreSendStatus(input) {
|
|
5845
|
+
const { request, status } = input;
|
|
5846
|
+
if (stringValue(request.options.expectedUserExplicitTerminalToken)) {
|
|
5847
|
+
// Managed promotion is only a sidecar for a human-priority Send. It may
|
|
5848
|
+
// relax lifecycle/activity attribution, never the shared physical gate
|
|
5849
|
+
// that proves this TUI is not an approval or questionnaire input mode.
|
|
5850
|
+
assertSafeUserExplicitTerminalSend(status);
|
|
5851
|
+
}
|
|
5852
|
+
const deferredCodexPrompt = Boolean(request.verifiedEmptyCodexHandoff ||
|
|
5853
|
+
request.deferredCodexForegroundBinding ||
|
|
5854
|
+
request.postSendCodexCandidateAnchor);
|
|
5855
|
+
if (!deferredCodexPrompt) {
|
|
5856
|
+
assertSafeTerminalSend(request.executor.kind, status);
|
|
5857
|
+
return;
|
|
5858
|
+
}
|
|
5859
|
+
if (request.executor.kind !== "codex" ||
|
|
5860
|
+
status.reachable !== true ||
|
|
5861
|
+
status.approval_state.blocked === true ||
|
|
5862
|
+
status.interaction_state !== undefined ||
|
|
5863
|
+
!["idle", "unknown"].includes(status.activity_state)) {
|
|
5864
|
+
throw new Error(`Codex deferred foreground send is not at a safe prompt ` +
|
|
5865
|
+
`(${status.activity_state}: ${status.activity_reason})`);
|
|
5866
|
+
}
|
|
5867
|
+
}
|
|
5868
|
+
function assertAtomicForegroundIdentificationProof(input) {
|
|
5869
|
+
if (input.options.identifyForeground !== true)
|
|
5870
|
+
return;
|
|
5871
|
+
if (input.executor.kind !== "codex") {
|
|
5872
|
+
throw new Error("atomic foreground identification currently supports only Codex terminals");
|
|
5873
|
+
}
|
|
5874
|
+
const proof = foregroundIdentificationProofs.get(input.options);
|
|
5875
|
+
if (!proof) {
|
|
5876
|
+
throw new Error("atomic foreground identification proof is unavailable; no task input was sent");
|
|
5877
|
+
}
|
|
5878
|
+
const expiresAtMs = Date.parse(proof.expiresAt);
|
|
5879
|
+
if (!Number.isFinite(expiresAtMs) || cliNowMs() >= expiresAtMs) {
|
|
5880
|
+
throw new Error("atomic foreground identification proof expired before task dispatch; no task input was sent");
|
|
5881
|
+
}
|
|
5882
|
+
const processIncarnation = processIncarnationForPid(input.terminalAgentPid);
|
|
5883
|
+
if (proof.pid !== input.terminalAgentPid ||
|
|
5884
|
+
proof.processUuid !== processIncarnation.processUuid ||
|
|
5885
|
+
proof.processBirth !== processIncarnation.processBirth) {
|
|
5886
|
+
throw new Error("Codex process incarnation changed after foreground identification; no task input was sent");
|
|
5887
|
+
}
|
|
5888
|
+
const cwd = stringValue(input.terminalControl.currentPath);
|
|
5889
|
+
let realCwd;
|
|
5890
|
+
try {
|
|
5891
|
+
realCwd = cwd ? fs.realpathSync(cwd) : undefined;
|
|
5892
|
+
}
|
|
5893
|
+
catch {
|
|
5894
|
+
realCwd = undefined;
|
|
5895
|
+
}
|
|
5896
|
+
if (!realCwd || realCwd !== proof.realCwd) {
|
|
5897
|
+
throw new Error("Codex terminal cwd changed after foreground identification; no task input was sent");
|
|
5898
|
+
}
|
|
5899
|
+
if (!stringValue(input.status.screen.digest) ||
|
|
5900
|
+
input.status.screen.digest !== proof.postProbeScreenDigest) {
|
|
5901
|
+
throw new Error("Codex screen generation changed after foreground identification; no task input was sent");
|
|
5902
|
+
}
|
|
5903
|
+
const observation = createRuntimeTerminalAgentRegistry(input.options)
|
|
5904
|
+
.require("codex")
|
|
5905
|
+
.observeNativeInspection?.({
|
|
5906
|
+
operation: { kind: "status" },
|
|
5907
|
+
screen: input.status.screen.excerpt ?? "",
|
|
5908
|
+
expectedNativeThreadId: proof.nativeThreadId,
|
|
5909
|
+
expectedAgentVersion: proof.agentVersion,
|
|
5910
|
+
expectedCwd: proof.realCwd
|
|
5911
|
+
});
|
|
5912
|
+
if (observation?.status !== "observed" ||
|
|
5913
|
+
observation.nativeThreadId !== proof.nativeThreadId ||
|
|
5914
|
+
observation.evidenceFingerprint !== proof.evidenceFingerprint) {
|
|
5915
|
+
throw new Error("Codex /status identity changed after foreground identification; no task input was sent");
|
|
5916
|
+
}
|
|
5917
|
+
}
|
|
5441
5918
|
async function prepareTerminalControlSend(request) {
|
|
5442
|
-
const { transaction, options, conversation,
|
|
5919
|
+
const { transaction, options, conversation, executor, message, recordRawAttachmentAfterSend = false, allowedPreMaterializationIdentity, allowedAdditionalIdentities = [], deferredCodexForegroundBinding, continuingTurnResponse = false } = request;
|
|
5443
5920
|
const bridge = terminalBridgeEnabled(conversation);
|
|
5444
5921
|
const route = bindTerminalDispatchRoute(transaction.scopes, transaction.resources);
|
|
5445
5922
|
const { terminalControl, storeDir: lockedStoreDir, statePath, logPath } = route;
|
|
@@ -5490,8 +5967,8 @@ async function prepareTerminalControlSend(request) {
|
|
|
5490
5967
|
: undefined;
|
|
5491
5968
|
const terminalAgentPid = Number(sendTakeover?.terminal_agent_pid);
|
|
5492
5969
|
const expectedManagedNativeThreadId = stringValue(sendTakeover?.terminal_agent_expected_session_id) ?? stringValue(sendTakeover?.terminal_agent_session_id);
|
|
5493
|
-
const
|
|
5494
|
-
|
|
5970
|
+
const candidateSetAnchor = terminalSendCandidateAcceptanceAnchor(request);
|
|
5971
|
+
const currentNativeIdentity = candidateSetAnchor
|
|
5495
5972
|
? undefined
|
|
5496
5973
|
: await execution.resolveCurrentNativeIdentity({
|
|
5497
5974
|
agent: executor.kind,
|
|
@@ -5508,30 +5985,18 @@ async function prepareTerminalControlSend(request) {
|
|
|
5508
5985
|
const pendingManagedNativeBinding = Boolean(!stringValue(sendTakeover?.terminal_agent_session_id) &&
|
|
5509
5986
|
stringValue(sendTakeover?.terminal_agent_expected_session_id));
|
|
5510
5987
|
const needsPostSendNativeBinding = virginRawAttach || pendingManagedNativeBinding;
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
}
|
|
5524
|
-
const preSendRuntime = {
|
|
5525
|
-
...(deferredCodexForegroundBinding?.candidateAcceptanceAnchor
|
|
5526
|
-
? terminalRuntimeForLiveIdentity({
|
|
5527
|
-
terminal: deferredCodexForegroundBinding.terminal,
|
|
5528
|
-
physicalOnly: true
|
|
5529
|
-
})
|
|
5530
|
-
: terminalRuntimeIdentityForConversation(nextConversation, terminalControl)),
|
|
5531
|
-
allowedPreMaterializationNativeIdentity: allowedPreMaterializationIdentity,
|
|
5532
|
-
allowedAdditionalNativeIdentities: allowedAdditionalIdentities,
|
|
5533
|
-
messageId: message.id
|
|
5534
|
-
};
|
|
5988
|
+
assertTerminalNativeBindingBeforeSend({
|
|
5989
|
+
execution,
|
|
5990
|
+
conversation,
|
|
5991
|
+
currentNativeIdentity,
|
|
5992
|
+
needsPostSendNativeBinding,
|
|
5993
|
+
allowedPreMaterializationIdentity
|
|
5994
|
+
});
|
|
5995
|
+
const preSendRuntime = terminalPreSendRuntime({
|
|
5996
|
+
request,
|
|
5997
|
+
terminalControl,
|
|
5998
|
+
terminalAgentPid
|
|
5999
|
+
});
|
|
5535
6000
|
let preSendScreenFingerprint;
|
|
5536
6001
|
let codexRolloutAcceptanceAnchor;
|
|
5537
6002
|
let claudeTranscriptAnchor;
|
|
@@ -5540,21 +6005,20 @@ async function prepareTerminalControlSend(request) {
|
|
|
5540
6005
|
: undefined;
|
|
5541
6006
|
try {
|
|
5542
6007
|
const status = await terminalBridge.status(executor.kind, terminalControl, {
|
|
5543
|
-
scrollbackLines:
|
|
6008
|
+
scrollbackLines: options.identifyForeground === true
|
|
6009
|
+
? foregroundIdentificationProofs.get(options)
|
|
6010
|
+
?.observationScrollbackLines ?? 240
|
|
6011
|
+
: Number(options.scrollbackLines ?? 120),
|
|
5544
6012
|
runtime: preSendRuntime
|
|
5545
6013
|
});
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
}
|
|
5555
|
-
else {
|
|
5556
|
-
assertSafeTerminalSend(executor.kind, status);
|
|
5557
|
-
}
|
|
6014
|
+
assertAtomicForegroundIdentificationProof({
|
|
6015
|
+
options,
|
|
6016
|
+
executor,
|
|
6017
|
+
terminalControl,
|
|
6018
|
+
terminalAgentPid,
|
|
6019
|
+
status
|
|
6020
|
+
});
|
|
6021
|
+
assertTerminalPreSendStatus({ request, status });
|
|
5558
6022
|
const userExplicitManagedCodexAttempt = Boolean(stringValue(options.expectedUserExplicitTerminalToken));
|
|
5559
6023
|
if (executor.kind === "claude" &&
|
|
5560
6024
|
userExplicitManagedCodexAttempt &&
|
|
@@ -5596,7 +6060,7 @@ async function prepareTerminalControlSend(request) {
|
|
|
5596
6060
|
boundProcessBirth: stringValue(sendTakeover?.terminal_agent_process_birth),
|
|
5597
6061
|
allowedPreMaterializationIdentity,
|
|
5598
6062
|
needsPostSendNativeBinding,
|
|
5599
|
-
candidateSetAnchor
|
|
6063
|
+
candidateSetAnchor
|
|
5600
6064
|
});
|
|
5601
6065
|
}
|
|
5602
6066
|
else {
|
|
@@ -5655,8 +6119,7 @@ async function resolveTerminalDispatchSubmissionOwner(prepared, request, applica
|
|
|
5655
6119
|
expectedSessionId: stringValue(sendTakeover?.terminal_agent_expected_session_id),
|
|
5656
6120
|
allowedPreMaterializationIdentity,
|
|
5657
6121
|
allowedAdditionalIdentities,
|
|
5658
|
-
...(
|
|
5659
|
-
acceptanceAnchor &&
|
|
6122
|
+
...(acceptanceAnchor &&
|
|
5660
6123
|
[2, 3].includes(acceptanceAnchor.version)
|
|
5661
6124
|
? {
|
|
5662
6125
|
requiredCodexAcceptance: {
|
|
@@ -5711,15 +6174,26 @@ async function resolveTerminalDispatchSubmissionOwner(prepared, request, applica
|
|
|
5711
6174
|
});
|
|
5712
6175
|
}
|
|
5713
6176
|
else {
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
}
|
|
6177
|
+
if (executor.kind === "codex") {
|
|
6178
|
+
await rawPort("prepareManagedSessionNativeIdentityClaim")({
|
|
6179
|
+
options,
|
|
6180
|
+
conversation: boundConversation,
|
|
6181
|
+
identity: boundIdentity,
|
|
6182
|
+
storeDir: identityRoute.storeDir,
|
|
6183
|
+
terminalControl
|
|
6184
|
+
});
|
|
6185
|
+
}
|
|
6186
|
+
else {
|
|
6187
|
+
await assertNativeThreadHasExclusiveOwnership({
|
|
6188
|
+
options,
|
|
6189
|
+
agent: executor.kind,
|
|
6190
|
+
currentPid: terminalAgentPid,
|
|
6191
|
+
nativeThreadId: boundIdentity.sessionId,
|
|
6192
|
+
storeDir: identityRoute.storeDir,
|
|
6193
|
+
terminalControl,
|
|
6194
|
+
excludedManagedSessionId: sessionIdForConversation(boundConversation)
|
|
6195
|
+
});
|
|
6196
|
+
}
|
|
5723
6197
|
persistManagedSessionNativeIdentity({
|
|
5724
6198
|
conversation: boundConversation,
|
|
5725
6199
|
terminalControl,
|
|
@@ -5727,7 +6201,8 @@ async function resolveTerminalDispatchSubmissionOwner(prepared, request, applica
|
|
|
5727
6201
|
storeDir: identityRoute.storeDir
|
|
5728
6202
|
});
|
|
5729
6203
|
}
|
|
5730
|
-
if (acceptanceAnchor
|
|
6204
|
+
if (acceptanceAnchor &&
|
|
6205
|
+
[2, 3].includes(acceptanceAnchor.version) &&
|
|
5731
6206
|
cliEnv().AKK_TEST_EXIT_AFTER_VIRGIN_SESSION_BINDING === "1") {
|
|
5732
6207
|
cliExit(86);
|
|
5733
6208
|
}
|
|
@@ -5787,8 +6262,7 @@ async function resolveTerminalDispatchSubmissionOwner(prepared, request, applica
|
|
|
5787
6262
|
if (boundIdentity && boundConversation) {
|
|
5788
6263
|
return ready(boundConversation);
|
|
5789
6264
|
}
|
|
5790
|
-
if (
|
|
5791
|
-
acceptanceAnchor?.version === 3 &&
|
|
6265
|
+
if (acceptanceAnchor?.version === 3 &&
|
|
5792
6266
|
bindingError === undefined) {
|
|
5793
6267
|
return ready(stagedConversation, true);
|
|
5794
6268
|
}
|
|
@@ -5889,7 +6363,7 @@ function terminalDispatchRollbackRepositories({ request, prepared, deferredForeg
|
|
|
5889
6363
|
return { rollbackBeforeInput, restoreDeferred };
|
|
5890
6364
|
}
|
|
5891
6365
|
function createTerminalDispatchRuntime(request, prepared, rollback) {
|
|
5892
|
-
const { transaction, conversation, nextConversation, executor, message, recordRawAttachmentAfterSend = false } = request;
|
|
6366
|
+
const { transaction, conversation, nextConversation, executor, message, recordRawAttachmentAfterSend = false, postSendCodexDetachedSessionClaims } = request;
|
|
5893
6367
|
const { bridge, terminalControl, lockedStoreDir, statePath, logPath, bridgeStartedAt, submissionPreparedAt, agentTimeoutMinutes, agentHardTimeoutMinutes, terminalPayload, terminalRequestHash, previousDispatchLedger, preSendScreenFingerprint, codexRolloutAcceptanceAnchor, claudeTranscriptAnchor, claudeHome } = prepared;
|
|
5894
6368
|
const terminalMessage = message;
|
|
5895
6369
|
const bridgeConversation = bridge
|
|
@@ -5903,6 +6377,7 @@ function createTerminalDispatchRuntime(request, prepared, rollback) {
|
|
|
5903
6377
|
monitorLockVersion: monitorOwner.LOCK_VERSION,
|
|
5904
6378
|
preSendScreenFingerprint,
|
|
5905
6379
|
codexRolloutAcceptanceAnchor,
|
|
6380
|
+
codexDetachedCandidateSessionClaims: postSendCodexDetachedSessionClaims,
|
|
5906
6381
|
claudeTranscriptAnchor,
|
|
5907
6382
|
claudeHome
|
|
5908
6383
|
})
|
|
@@ -6059,16 +6534,38 @@ function terminalDispatchTransportLifecycle({ request, prepared, application, de
|
|
|
6059
6534
|
: {}),
|
|
6060
6535
|
recordStage: (stage, at, afterDurable) => application.recordTransportStage(stage, at, afterDurable)
|
|
6061
6536
|
});
|
|
6537
|
+
const lifecycleBeforeText = lifecycle.beforeText;
|
|
6538
|
+
const guardedLifecycle = options.identifyForeground === true
|
|
6539
|
+
? {
|
|
6540
|
+
...lifecycle,
|
|
6541
|
+
beforeText: async () => {
|
|
6542
|
+
await lifecycleBeforeText?.();
|
|
6543
|
+
const proof = foregroundIdentificationProofs.get(options);
|
|
6544
|
+
const status = await prepared.terminalBridge.status(request.executor.kind, prepared.terminalControl, {
|
|
6545
|
+
runtime: prepared.preSendRuntime,
|
|
6546
|
+
scrollbackLines: proof?.observationScrollbackLines ??
|
|
6547
|
+
CODEX_FOREGROUND_IDENTIFICATION_SCROLLBACK_LINES
|
|
6548
|
+
});
|
|
6549
|
+
assertAtomicForegroundIdentificationProof({
|
|
6550
|
+
options,
|
|
6551
|
+
executor: request.executor,
|
|
6552
|
+
terminalControl: prepared.terminalControl,
|
|
6553
|
+
terminalAgentPid: prepared.terminalAgentPid,
|
|
6554
|
+
status
|
|
6555
|
+
});
|
|
6556
|
+
}
|
|
6557
|
+
}
|
|
6558
|
+
: lifecycle;
|
|
6062
6559
|
const userExplicitTerminalSend = Boolean(stringValue(options.expectedUserExplicitTerminalToken));
|
|
6063
6560
|
return userExplicitTerminalSend
|
|
6064
6561
|
? {
|
|
6065
|
-
...
|
|
6562
|
+
...guardedLifecycle,
|
|
6066
6563
|
requireExactEmptyComposerBeforeText: true,
|
|
6067
6564
|
...(request.executor.kind === "codex"
|
|
6068
6565
|
? { userExplicitEnterAfterTextWithoutComposerVeto: true }
|
|
6069
6566
|
: {})
|
|
6070
6567
|
}
|
|
6071
|
-
:
|
|
6568
|
+
: guardedLifecycle;
|
|
6072
6569
|
}
|
|
6073
6570
|
async function terminalDispatchAcceptance({ request, prepared, conversation }) {
|
|
6074
6571
|
const { options, executor } = request;
|
|
@@ -6222,7 +6719,8 @@ async function runTerminalDispatchTransport({ request, prepared, application, pr
|
|
|
6222
6719
|
if (!progress.enterDispatchedAt) {
|
|
6223
6720
|
throw new Error("terminal bridge returned without an enter_dispatched receipt");
|
|
6224
6721
|
}
|
|
6225
|
-
if (codexRolloutAcceptanceAnchor
|
|
6722
|
+
if (codexRolloutAcceptanceAnchor &&
|
|
6723
|
+
[2, 3].includes(codexRolloutAcceptanceAnchor.version) &&
|
|
6226
6724
|
cliEnv().AKK_TEST_EXIT_AFTER_VIRGIN_ENTER_DISPATCHED === "1") {
|
|
6227
6725
|
cliExit(86);
|
|
6228
6726
|
}
|