@scotthuang/agent-knock-knock 0.12.6 → 0.12.7
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 +17 -0
- package/README.md +4 -4
- package/dist/src/cli-core.js +735 -58
- package/dist/src/cli-core.js.map +1 -1
- package/dist/src/deferred-foreground-transfer.d.ts +3 -0
- package/dist/src/deferred-foreground-transfer.js +29 -0
- package/dist/src/deferred-foreground-transfer.js.map +1 -1
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +3 -3
package/dist/src/cli-core.js
CHANGED
|
@@ -6,6 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
import process from "node:process";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { createCodexTerminalAgentAdapter, detectCodexDurableCompletion } from "./codex-terminal-agent-adapter.js";
|
|
9
|
+
import { codexLifecycleBehaviorProfile } from "./codex-lifecycle-compatibility.js";
|
|
9
10
|
import { createClaudeTerminalAgentAdapter } from "./claude-terminal-agent-adapter.js";
|
|
10
11
|
import { captureClaudeTranscriptAnchor, defaultClaudeHome, detectClaudeTranscriptAcceptance, detectClaudeTranscriptCompletion, detectClaudeTranscriptPendingApproval, listClaudeThreadLifecycleCandidates, revalidateClaudeThreadLifecycleCandidate } from "./claude-local-transcript-provider.js";
|
|
11
12
|
import { captureCodexCandidateSetRolloutAcceptanceAnchor, captureCodexRolloutAcceptanceAnchor, detectCodexBoundRolloutCompletion, detectCodexCandidateSetRolloutAcceptance, detectCodexRolloutAcceptance, terminalSubmissionReplayReceipt, validateCodexRolloutAcceptanceAnchor, validateTerminalSubmissionAcceptanceEvidence } from "./terminal-submission-acceptance.js";
|
|
@@ -2600,8 +2601,8 @@ function assertNoUnresolvedTerminalBridgeSubmission(storeDir, terminalControl, c
|
|
|
2600
2601
|
for (const candidate of listConversations(storeDir)) {
|
|
2601
2602
|
const submission = terminalBridgeSubmission(candidate);
|
|
2602
2603
|
if (candidate.conversation_id === currentConversationId ||
|
|
2603
|
-
!isActiveStatus(candidate.status) ||
|
|
2604
2604
|
!submission ||
|
|
2605
|
+
TERMINAL_DISPATCH_RELEASE_STATUSES.has(effectiveTurnStatus(candidate)) ||
|
|
2605
2606
|
![
|
|
2606
2607
|
"prepared",
|
|
2607
2608
|
"text_injected",
|
|
@@ -3329,6 +3330,12 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
|
|
|
3329
3330
|
statusInspection: false,
|
|
3330
3331
|
reason: "native inspection is unavailable"
|
|
3331
3332
|
};
|
|
3333
|
+
const codexLatentClearResumeObservationValue = session.agent === "codex"
|
|
3334
|
+
? codexLatentClearResumeObservation({
|
|
3335
|
+
screen: terminalState.screen_excerpt,
|
|
3336
|
+
agentVersion
|
|
3337
|
+
})
|
|
3338
|
+
: undefined;
|
|
3332
3339
|
const lifecycleBindingToken = unmanagedTerminalBindingToken({
|
|
3333
3340
|
terminalId: bridge.terminalConversationId(session),
|
|
3334
3341
|
terminalControl,
|
|
@@ -3347,7 +3354,8 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
|
|
|
3347
3354
|
if (session.agent === "codex" &&
|
|
3348
3355
|
(terminalState.activity_state === "idle" ||
|
|
3349
3356
|
(terminalState.activity_state === "unknown" &&
|
|
3350
|
-
nativeIdentityObservation.status === "verified_absent"
|
|
3357
|
+
(nativeIdentityObservation.status === "verified_absent" ||
|
|
3358
|
+
codexOpenRootRolloutInventory !== undefined))) &&
|
|
3351
3359
|
terminalState.approval_state.blocked !== true &&
|
|
3352
3360
|
terminalControl.capabilities.includes("send_keys") &&
|
|
3353
3361
|
terminalControl.capabilities.includes("screen_status")) {
|
|
@@ -3403,6 +3411,14 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
|
|
|
3403
3411
|
// this field after gating every automated-input action that can follow a
|
|
3404
3412
|
// human native-thread switch.
|
|
3405
3413
|
_automated_input_composer_ready: automatedInputComposerReady,
|
|
3414
|
+
...(codexLatentClearResumeObservationValue
|
|
3415
|
+
? {
|
|
3416
|
+
_codex_latent_clear_resume: {
|
|
3417
|
+
source_native_thread_id: codexLatentClearResumeObservationValue.sourceNativeThreadId,
|
|
3418
|
+
fingerprint: codexLatentClearResumeObservationValue.fingerprint
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
: {}),
|
|
3406
3422
|
...(codexOpenRootRolloutInventory
|
|
3407
3423
|
? {
|
|
3408
3424
|
_codex_open_root_rollout_inventory: codexOpenRootRolloutInventory
|
|
@@ -3482,7 +3498,7 @@ function terminalFirstListProjection({ storeDir, terminals, managedSessions, ses
|
|
|
3482
3498
|
return control ? [control] : [];
|
|
3483
3499
|
});
|
|
3484
3500
|
const projectedTerminals = terminals.map((terminal) => {
|
|
3485
|
-
const { _automated_input_composer_ready: automatedInputComposerReady, _codex_open_root_rollout_inventory: codexOpenRootRolloutInventoryValue, ...publicTerminal } = terminal;
|
|
3501
|
+
const { _automated_input_composer_ready: automatedInputComposerReady, _codex_open_root_rollout_inventory: codexOpenRootRolloutInventoryValue, _codex_latent_clear_resume: codexLatentClearResumeValue, ...publicTerminal } = terminal;
|
|
3486
3502
|
const codexOpenRootRolloutInventory = isRecord(codexOpenRootRolloutInventoryValue)
|
|
3487
3503
|
? codexOpenRootRolloutInventoryValue
|
|
3488
3504
|
: undefined;
|
|
@@ -3732,16 +3748,29 @@ function terminalFirstListProjection({ storeDir, terminals, managedSessions, ses
|
|
|
3732
3748
|
codexOpenRootRolloutInventory.roots.length > 0
|
|
3733
3749
|
? codexOpenRootRolloutInventory
|
|
3734
3750
|
: undefined;
|
|
3735
|
-
const
|
|
3751
|
+
const abandonedConflictSource = !authoritativeSession &&
|
|
3752
|
+
soleBindingConflict?.kind === "unverifiable" &&
|
|
3753
|
+
matchingSessions.length === 0 &&
|
|
3754
|
+
conflictingBoundSessionClaims.length === 1 &&
|
|
3755
|
+
unresolvedSessionClaims.length === 0 &&
|
|
3756
|
+
terminal.agent === "codex"
|
|
3757
|
+
? soleBindingConflict.session
|
|
3758
|
+
: undefined;
|
|
3759
|
+
const deferredCodexSource = (authoritativeSession ??
|
|
3760
|
+
abandonedConflictSource) &&
|
|
3736
3761
|
terminal.agent === "codex" &&
|
|
3737
3762
|
(nativeIdentityObservation?.status === "verified_absent" ||
|
|
3738
3763
|
deferredCodexCandidateInventory !== undefined)
|
|
3739
|
-
? authoritativeSession
|
|
3764
|
+
? authoritativeSession ?? abandonedConflictSource
|
|
3740
3765
|
: undefined;
|
|
3741
3766
|
const deferredCodexProcessUuid = stringValue(terminal.native_agent_process_uuid);
|
|
3742
3767
|
const deferredCodexProcessBirth = stringValue(terminal.native_agent_process_birth);
|
|
3743
3768
|
const deferredCodexWorkspace = stringValue(terminal.workspace ?? terminal.cwd);
|
|
3744
3769
|
const deferredCodexSourceNativeThreadId = stringValue(deferredCodexSource?.binding?.native_thread_id)?.toLowerCase();
|
|
3770
|
+
const deferredCodexLatentClearResumeFingerprint = isRecord(codexLatentClearResumeValue) &&
|
|
3771
|
+
stringValue(codexLatentClearResumeValue.source_native_thread_id)?.toLowerCase() === deferredCodexSourceNativeThreadId
|
|
3772
|
+
? stringValue(codexLatentClearResumeValue.fingerprint)
|
|
3773
|
+
: undefined;
|
|
3745
3774
|
const deferredCodexSourceActiveElsewhere = Boolean(deferredCodexSourceNativeThreadId &&
|
|
3746
3775
|
terminals.some((candidate) => candidate !== terminal &&
|
|
3747
3776
|
candidate.agent === "codex" &&
|
|
@@ -3750,9 +3779,30 @@ function terminalFirstListProjection({ storeDir, terminals, managedSessions, ses
|
|
|
3750
3779
|
const deferredCodexDispatchSnapshot = terminalControl
|
|
3751
3780
|
? tryDeferredCodexForegroundDispatchSnapshot(terminalControl)
|
|
3752
3781
|
: undefined;
|
|
3753
|
-
const deferredCodexCandidateSourceTurnHistory = deferredCodexCandidateInventory &&
|
|
3782
|
+
const deferredCodexCandidateSourceTurnHistory = deferredCodexCandidateInventory &&
|
|
3783
|
+
deferredCodexSource?.binding?.native_process.rollout
|
|
3754
3784
|
? deferredCandidateSourceTurnHistory(storeDir, deferredCodexSource)
|
|
3755
3785
|
: undefined;
|
|
3786
|
+
const deferredCodexSourceRolloutPresent = Boolean(deferredCodexCandidateInventory &&
|
|
3787
|
+
deferredCodexSource?.binding?.native_process.rollout &&
|
|
3788
|
+
deferredCodexCandidateInventory.roots.some((root) => root.sessionId.toLowerCase() ===
|
|
3789
|
+
deferredCodexSource.binding?.native_thread_id?.toLowerCase() &&
|
|
3790
|
+
exactNativeRolloutMatches(root.rollout, deferredCodexSource.binding.native_process.rollout)));
|
|
3791
|
+
const deferredCodexSourceRolloutAuthority = abandonedConflictSource && !deferredCodexSourceRolloutPresent
|
|
3792
|
+
? "explicitly_abandoned_predecessor"
|
|
3793
|
+
: "present";
|
|
3794
|
+
const deferredCodexSourceAbandonmentFingerprint = deferredCodexSourceRolloutAuthority ===
|
|
3795
|
+
"explicitly_abandoned_predecessor" &&
|
|
3796
|
+
deferredCodexSource &&
|
|
3797
|
+
deferredCodexCandidateSourceTurnHistory &&
|
|
3798
|
+
deferredCodexDispatchSnapshot
|
|
3799
|
+
? explicitlyAbandonedCandidateSourceFingerprint({
|
|
3800
|
+
storeDir,
|
|
3801
|
+
session: deferredCodexSource,
|
|
3802
|
+
sourceTurnHistory: deferredCodexCandidateSourceTurnHistory,
|
|
3803
|
+
dispatchSnapshot: deferredCodexDispatchSnapshot
|
|
3804
|
+
})
|
|
3805
|
+
: undefined;
|
|
3756
3806
|
const deferredCodexStatusCardSourceEligible = Boolean(deferredCodexSource &&
|
|
3757
3807
|
terminalControl &&
|
|
3758
3808
|
deferredCodexProcessUuid &&
|
|
@@ -3785,13 +3835,24 @@ function terminalFirstListProjection({ storeDir, terminals, managedSessions, ses
|
|
|
3785
3835
|
workspace: deferredCodexWorkspace,
|
|
3786
3836
|
processUuid: deferredCodexProcessUuid,
|
|
3787
3837
|
processBirth: deferredCodexProcessBirth,
|
|
3788
|
-
inventory: deferredCodexCandidateInventory
|
|
3838
|
+
inventory: deferredCodexCandidateInventory,
|
|
3839
|
+
sourceRolloutAuthority: deferredCodexSourceRolloutAuthority
|
|
3840
|
+
}) &&
|
|
3841
|
+
(deferredCodexSourceRolloutAuthority === "present" ||
|
|
3842
|
+
Boolean(deferredCodexSourceAbandonmentFingerprint)) &&
|
|
3843
|
+
codexCandidateInventoryHasNoOtherManagedClaim({
|
|
3844
|
+
storeDir,
|
|
3845
|
+
inventory: deferredCodexCandidateInventory,
|
|
3846
|
+
sourceSessionId: deferredCodexSource.session_id,
|
|
3847
|
+
includeDetached: deferredCodexSourceRolloutAuthority ===
|
|
3848
|
+
"explicitly_abandoned_predecessor"
|
|
3789
3849
|
}) &&
|
|
3790
3850
|
candidateSourceTransitionHistoryIsTerminal(storeDir, deferredCodexSource));
|
|
3791
3851
|
const deferredCodexCandidateRouteNeeded = Boolean(deferredCodexCandidateInventory &&
|
|
3792
3852
|
(deferredCodexCandidateInventory.status === "unbound" ||
|
|
3793
3853
|
nativeIdentityObservation?.status === "unavailable" ||
|
|
3794
|
-
deferredCodexSource?.binding?.native_process.rollout === undefined
|
|
3854
|
+
deferredCodexSource?.binding?.native_process.rollout === undefined ||
|
|
3855
|
+
deferredCodexLatentClearResumeFingerprint !== undefined));
|
|
3795
3856
|
const deferredCodexForegroundEligible = Boolean(mutationsAllowed &&
|
|
3796
3857
|
!terminalHasNonterminalDeferredTransfer &&
|
|
3797
3858
|
deferredCodexSource &&
|
|
@@ -3799,8 +3860,12 @@ function terminalFirstListProjection({ storeDir, terminals, managedSessions, ses
|
|
|
3799
3860
|
hasCanonicalTerminalEndpoint(terminalControl) &&
|
|
3800
3861
|
discoveredOwnership.state === "none" &&
|
|
3801
3862
|
unresolvedSessionClaims.length === 0 &&
|
|
3802
|
-
matchingSessions.length === 1 &&
|
|
3803
|
-
|
|
3863
|
+
((matchingSessions.length === 1 &&
|
|
3864
|
+
conflictingBoundSessionClaims.length === 0) ||
|
|
3865
|
+
(deferredCodexSourceRolloutAuthority ===
|
|
3866
|
+
"explicitly_abandoned_predecessor" &&
|
|
3867
|
+
matchingSessions.length === 0 &&
|
|
3868
|
+
conflictingBoundSessionClaims.length === 1)) &&
|
|
3804
3869
|
deferredCodexProcessUuid &&
|
|
3805
3870
|
deferredCodexProcessBirth &&
|
|
3806
3871
|
deferredCodexWorkspace &&
|
|
@@ -3816,8 +3881,7 @@ function terminalFirstListProjection({ storeDir, terminals, managedSessions, ses
|
|
|
3816
3881
|
terminal.approval_state.blocked === true) &&
|
|
3817
3882
|
!deferredCodexSourceActiveElsewhere &&
|
|
3818
3883
|
terminalControl.capabilities.includes("send_keys") &&
|
|
3819
|
-
terminalControl.capabilities.includes("screen_status")
|
|
3820
|
-
isRecord(rawActions.send));
|
|
3884
|
+
terminalControl.capabilities.includes("screen_status"));
|
|
3821
3885
|
const deferredCodexForegroundToken = deferredCodexForegroundEligible &&
|
|
3822
3886
|
deferredCodexSource &&
|
|
3823
3887
|
terminalControl &&
|
|
@@ -3834,6 +3898,10 @@ function terminalFirstListProjection({ storeDir, terminals, managedSessions, ses
|
|
|
3834
3898
|
processBirth: deferredCodexProcessBirth,
|
|
3835
3899
|
sourceSession: deferredCodexSource,
|
|
3836
3900
|
dispatchSnapshot: deferredCodexDispatchSnapshot,
|
|
3901
|
+
sourceTurnHistory: deferredCodexCandidateSourceTurnHistory,
|
|
3902
|
+
candidateContextFingerprint: deferredCodexLatentClearResumeFingerprint,
|
|
3903
|
+
sourceRolloutAuthority: deferredCodexSourceRolloutAuthority,
|
|
3904
|
+
sourceAbandonmentFingerprint: deferredCodexSourceAbandonmentFingerprint,
|
|
3837
3905
|
...(deferredCodexCandidateInventory
|
|
3838
3906
|
? { candidateInventory: deferredCodexCandidateInventory }
|
|
3839
3907
|
: {})
|
|
@@ -4102,6 +4170,21 @@ function terminalFirstListProjection({ storeDir, terminals, managedSessions, ses
|
|
|
4102
4170
|
: ownership.state === "conflict"
|
|
4103
4171
|
? {
|
|
4104
4172
|
...safeTerminalActionsDuringConflict(sessionAwareRawActions),
|
|
4173
|
+
...(deferredCodexForegroundToken &&
|
|
4174
|
+
deferredCodexSourceRolloutAuthority ===
|
|
4175
|
+
"explicitly_abandoned_predecessor"
|
|
4176
|
+
? {
|
|
4177
|
+
send: {
|
|
4178
|
+
...verifiedEmptyRawSendAction,
|
|
4179
|
+
arguments: {
|
|
4180
|
+
...(isRecord(verifiedEmptyRawSendAction.arguments)
|
|
4181
|
+
? verifiedEmptyRawSendAction.arguments
|
|
4182
|
+
: {}),
|
|
4183
|
+
expected_terminal_token: deferredCodexForegroundToken
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4187
|
+
: {}),
|
|
4105
4188
|
...(externalHandoffAdoptable
|
|
4106
4189
|
? {
|
|
4107
4190
|
send: {
|
|
@@ -4132,15 +4215,14 @@ function terminalFirstListProjection({ storeDir, terminals, managedSessions, ses
|
|
|
4132
4215
|
? { reconcile_binding: reconcileBindingAction }
|
|
4133
4216
|
: {})
|
|
4134
4217
|
}
|
|
4135
|
-
: deferredCodexForegroundToken
|
|
4136
|
-
isRecord(rawActions.send)
|
|
4218
|
+
: deferredCodexForegroundToken
|
|
4137
4219
|
? {
|
|
4138
4220
|
...nonOwnerRawActions,
|
|
4139
4221
|
send: {
|
|
4140
|
-
...
|
|
4222
|
+
...verifiedEmptyRawSendAction,
|
|
4141
4223
|
arguments: {
|
|
4142
|
-
...(isRecord(
|
|
4143
|
-
?
|
|
4224
|
+
...(isRecord(verifiedEmptyRawSendAction.arguments)
|
|
4225
|
+
? verifiedEmptyRawSendAction.arguments
|
|
4144
4226
|
: {}),
|
|
4145
4227
|
expected_terminal_token: deferredCodexForegroundToken
|
|
4146
4228
|
}
|
|
@@ -4183,9 +4265,19 @@ function terminalFirstListProjection({ storeDir, terminals, managedSessions, ses
|
|
|
4183
4265
|
"while the same terminal process is at an exact empty prompt",
|
|
4184
4266
|
recovery: "use only the exact snapshot-bound send action listed for this terminal"
|
|
4185
4267
|
}
|
|
4186
|
-
: ownership.state === "conflict"
|
|
4187
|
-
|
|
4188
|
-
|
|
4268
|
+
: ownership.state === "conflict" &&
|
|
4269
|
+
deferredCodexForegroundToken &&
|
|
4270
|
+
deferredCodexSourceRolloutAuthority ===
|
|
4271
|
+
"explicitly_abandoned_predecessor"
|
|
4272
|
+
? {
|
|
4273
|
+
...ownership.conflict,
|
|
4274
|
+
kind: "explicitly_abandoned_predecessor_adoptable",
|
|
4275
|
+
reason: "the explicitly abandoned Codex predecessor rollout is no longer open and the current exact rollout inventory is unclaimed",
|
|
4276
|
+
recovery: "use only the exact snapshot-bound follow-current send action listed for this terminal"
|
|
4277
|
+
}
|
|
4278
|
+
: ownership.state === "conflict"
|
|
4279
|
+
? ownership.conflict
|
|
4280
|
+
: undefined;
|
|
4189
4281
|
return {
|
|
4190
4282
|
...publicTerminal,
|
|
4191
4283
|
...authoritativeIdentity,
|
|
@@ -5523,13 +5615,13 @@ function managedListApprovalState(conversation) {
|
|
|
5523
5615
|
}
|
|
5524
5616
|
function listActionContracts() {
|
|
5525
5617
|
return {
|
|
5526
|
-
version:
|
|
5618
|
+
version: 14,
|
|
5527
5619
|
instructions: [
|
|
5528
5620
|
"Treat terminals[] as the primary resource and use only actions present in available_actions, except the snapshot-bound terminals[].handoff_decision.choices.take_over_current.action and an exact terminals[].blocking_turns[].recovery_action. Either nested action requires explicit user confirmation; after it succeeds, refresh list before any follow-current send.",
|
|
5529
5621
|
"An existing managed session's ordinary send targets session_id and creates a new turn. A turn id is never an ordinary send target.",
|
|
5530
5622
|
"Read-only native-thread listing targets an exact terminal_id. Native-thread new/resume mutations also use the listed expected_binding_token and never create a Turn.",
|
|
5531
5623
|
"Native inspection is a separate terminal action: use only its closed inspection enum and current exact terminal_id/token; AKK status does not execute a native slash command.",
|
|
5532
|
-
"A verified, idle human native-thread switch may expose a terminal-scoped send with expected_terminal_token; that action atomically adopts the live context before creating its Turn. A conclusively ended Codex rollout may expose the same snapshot-bound send only after AKK proves zero current rollout and an exact empty composer; it detaches the ended Session and creates an isolated virgin Session. A status-card-only zero-rollout source,
|
|
5624
|
+
"A verified, idle human native-thread switch may expose a terminal-scoped send with expected_terminal_token; that action atomically adopts the live context before creating its Turn. A conclusively ended Codex rollout may expose the same snapshot-bound send only after AKK proves zero current rollout and an exact empty composer; it detaches the ended Session and creates an isolated virgin Session. A status-card-only zero-rollout source, a quiescent rollout-backed source whose complete pinned open-rollout inventory cannot identify one foreground candidate, or a supported Codex /clear foreground hint observed before its new rollout materializes may also expose this exact action. AKK freezes any released predecessor Turn history, submits the ordinary task once, and binds a separate provisional Session only after one post-anchor rollout uniquely accepts that exact request. The accepted UUID may equal or differ from the predecessor without merging their Session lineages, and narrow panes do not require /status. Until that promotion commits, strict session_id send, respond, approve, cancel, native lifecycle, and native_inspect remain unavailable, and the provisional binding has no callback authority. If dispatch, acceptance, or post-submit binding is uncertain, do not retry automatically. An explicitly closed uncertain Turn may authorize only a future candidate send when its exact resolved close ledger, append-only uncertain receipt, frozen Turn history, and complete current rollout inventory prove that the old bound rollout is absent and every candidate is unclaimed; close never forges the lost callback, and uncertain submissions cannot be renewed. Other binding conflicts remain fail-closed and may expose only exact low-level reconcile_binding recovery.",
|
|
5533
5625
|
"List resumable threads before resume; use only a complete native_thread_id and the action returned for that candidate.",
|
|
5534
5626
|
"Use a terminal selector only when explicitly named by the user or prefilled by that terminal row's send action. A handoff action also carries expected_terminal_token; never infer, guess, or reuse either value.",
|
|
5535
5627
|
"Use respond only for an in-flight turn that is explicitly waiting for OpenClaw.",
|
|
@@ -5604,7 +5696,7 @@ function listActionContracts() {
|
|
|
5604
5696
|
],
|
|
5605
5697
|
unsupported: ["timeoutSeconds"],
|
|
5606
5698
|
status_card_only_deferred_scope: "A zero-Turn Codex status-card binding has no rollout; only its listed selector/token send creates an isolated provisional Session and binds it after exact request acceptance. Until promotion commits, strict managed controls, native lifecycle, native_inspect, and callback authority remain unavailable; an uncertain dispatch, acceptance, or post-submit binding must not be retried automatically.",
|
|
5607
|
-
candidate_rollout_deferred_scope: "A quiescent rollout-backed Codex source may use a listed selector/token send
|
|
5699
|
+
candidate_rollout_deferred_scope: "A quiescent rollout-backed Codex source may use a listed selector/token send when AKK pins the complete exact candidate inventory but cannot prove one foreground UUID, or when a supported /clear foreground hint signals that the sole old rollout is no longer sufficient authority. Released predecessor Turn history stays read-only while a separate provisional Session sends once and waits for one unique post-anchor request acceptance. Same-UUID and different-UUID results keep separate Session lineages; zero, multiple, drifted, or uncertain acceptance is never retried blindly. Explicit close can abandon an uncertain receipt for future-send liveness only while the exact resolved close ledger, append-only receipt, frozen history, absent old rollout, and unclaimed candidate set remain authoritative; it never synthesizes callback delivery.",
|
|
5608
5700
|
ordinary_use: "Create a new managed turn in the exact Session. A live terminal selector can attach an unmanaged pane, adopt one verified human-selected native context, detach a verified-empty Codex source, or replace an eligible status-card/candidate-rollout source after the submitted request proves its unique exact rollout; an explicit session_id never follows the pane."
|
|
5609
5701
|
},
|
|
5610
5702
|
new_thread: {
|
|
@@ -5852,7 +5944,9 @@ function availableListActions(entry, { conversation } = {}) {
|
|
|
5852
5944
|
requires_user_intent: true
|
|
5853
5945
|
};
|
|
5854
5946
|
}
|
|
5855
|
-
if (terminalBridgeReady &&
|
|
5947
|
+
if (terminalBridgeReady &&
|
|
5948
|
+
entry.status === "stalled" &&
|
|
5949
|
+
terminalBridgeSubmission(conversation)?.status !== "uncertain") {
|
|
5856
5950
|
actions.renew = {
|
|
5857
5951
|
tool: "agent_knock_knock_renew",
|
|
5858
5952
|
arguments: targetArguments
|
|
@@ -6514,6 +6608,14 @@ function codexAllowedCompanionSetForManagedSession({ storeDir, session }) {
|
|
|
6514
6608
|
});
|
|
6515
6609
|
return { primary: selectedPrimary, additional };
|
|
6516
6610
|
}
|
|
6611
|
+
function codexCompanionsPresentInOpenRootInventory(companions, inventory) {
|
|
6612
|
+
const present = [companions.primary, ...companions.additional].filter((candidate) => Boolean(candidate &&
|
|
6613
|
+
inventory.roots.some((root) => nativeIdentityMatchesCodexPreMaterialization(root, candidate))));
|
|
6614
|
+
return {
|
|
6615
|
+
primary: present[0],
|
|
6616
|
+
additional: present.slice(1)
|
|
6617
|
+
};
|
|
6618
|
+
}
|
|
6517
6619
|
function codexManagedIdentityResolutionContext({ storeDir, terminal }) {
|
|
6518
6620
|
const claimedSession = soleBoundManagedSessionClaimForTerminal(storeDir, terminal);
|
|
6519
6621
|
const companions = claimedSession
|
|
@@ -7035,8 +7137,11 @@ function exactBoundCodexStatusCardSourceForDeferredSend({ session, terminalId, t
|
|
|
7035
7137
|
liveProcessBirth: processBirth
|
|
7036
7138
|
}) === "same");
|
|
7037
7139
|
}
|
|
7038
|
-
function exactCodexCandidateInventoryForDeferredSend({ inventory, sourceSession, pid, workspace, processUuid, processBirth }) {
|
|
7140
|
+
function exactCodexCandidateInventoryForDeferredSend({ inventory, sourceSession, pid, workspace, processUuid, processBirth, sourceRolloutAuthority = "present" }) {
|
|
7039
7141
|
const sourceNativeThreadId = sourceSession.binding?.native_thread_id;
|
|
7142
|
+
const sourceRollout = sourceSession.binding?.native_process.rollout;
|
|
7143
|
+
const sourceRootPresent = Boolean(sourceNativeThreadId && inventory.roots.some((root) => root.sessionId.toLowerCase() === sourceNativeThreadId.toLowerCase()));
|
|
7144
|
+
const sourceRolloutPresent = Boolean(sourceRollout && inventory.roots.some((root) => exactNativeRolloutMatches(root.rollout, sourceRollout)));
|
|
7040
7145
|
return Boolean(inventory.roots.length > 0 &&
|
|
7041
7146
|
inventory.pid === pid &&
|
|
7042
7147
|
inventory.processUuid === processUuid &&
|
|
@@ -7044,11 +7149,14 @@ function exactCodexCandidateInventoryForDeferredSend({ inventory, sourceSession,
|
|
|
7044
7149
|
inventory.cwd &&
|
|
7045
7150
|
path.resolve(inventory.cwd) === path.resolve(workspace) &&
|
|
7046
7151
|
/^[0-9a-f]{64}$/u.test(inventory.inventoryFingerprint) &&
|
|
7047
|
-
(
|
|
7048
|
-
(sourceNativeThreadId &&
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7152
|
+
(sourceRolloutAuthority === "explicitly_abandoned_predecessor"
|
|
7153
|
+
? isExactNativeThreadId(sourceNativeThreadId) &&
|
|
7154
|
+
isCompleteNativeRollout(sourceRollout) &&
|
|
7155
|
+
!sourceRootPresent &&
|
|
7156
|
+
!sourceRolloutPresent
|
|
7157
|
+
: sourceRollout === undefined || sourceRootPresent));
|
|
7158
|
+
}
|
|
7159
|
+
function exactBoundCodexCandidateSourceForDeferredSend({ session, terminalId, terminalControl, pid, workspace, processUuid, processBirth, inventory, sourceRolloutAuthority = "present" }) {
|
|
7052
7160
|
const binding = session.binding;
|
|
7053
7161
|
const sourceNativeThreadId = binding?.native_thread_id;
|
|
7054
7162
|
const sourceRollout = binding?.native_process.rollout;
|
|
@@ -7058,8 +7166,11 @@ function exactBoundCodexCandidateSourceForDeferredSend({ session, terminalId, te
|
|
|
7058
7166
|
binding &&
|
|
7059
7167
|
isExactNativeThreadId(sourceNativeThreadId) &&
|
|
7060
7168
|
isCompleteNativeRollout(sourceRollout) &&
|
|
7061
|
-
|
|
7062
|
-
|
|
7169
|
+
(sourceRolloutAuthority === "explicitly_abandoned_predecessor"
|
|
7170
|
+
? inventoryRoot === undefined &&
|
|
7171
|
+
!inventory.roots.some((root) => exactNativeRolloutMatches(root.rollout, sourceRollout))
|
|
7172
|
+
: inventoryRoot !== undefined &&
|
|
7173
|
+
exactNativeRolloutMatches(inventoryRoot.rollout, sourceRollout)) &&
|
|
7063
7174
|
terminalControlAliasMatches(binding.terminal_id, binding.terminal_control, terminalId, terminalControl) &&
|
|
7064
7175
|
path.resolve(session.workspace) === path.resolve(workspace) &&
|
|
7065
7176
|
processIncarnationRelationship({
|
|
@@ -7074,22 +7185,68 @@ function exactBoundCodexCandidateSourceForDeferredSend({ session, terminalId, te
|
|
|
7074
7185
|
pid,
|
|
7075
7186
|
workspace,
|
|
7076
7187
|
processUuid,
|
|
7077
|
-
processBirth
|
|
7188
|
+
processBirth,
|
|
7189
|
+
sourceRolloutAuthority
|
|
7078
7190
|
}));
|
|
7079
7191
|
}
|
|
7192
|
+
function codexCandidateInventoryHasNoOtherManagedClaim({ storeDir, inventory, sourceSessionId, includeDetached = true }) {
|
|
7193
|
+
const candidateIds = new Set(inventory.roots.map((root) => root.sessionId.toLowerCase()));
|
|
7194
|
+
return !listManagedSessions(storeDir).some((session) => session.session_id !== sourceSessionId &&
|
|
7195
|
+
(includeDetached || session.status !== "detached") &&
|
|
7196
|
+
isExactNativeThreadId(session.binding?.native_thread_id) &&
|
|
7197
|
+
candidateIds.has(session.binding.native_thread_id.toLowerCase()));
|
|
7198
|
+
}
|
|
7080
7199
|
function deferredCandidateSourceTurnHistory(storeDir, session) {
|
|
7200
|
+
const binding = session.binding;
|
|
7201
|
+
if (!binding?.native_thread_id) {
|
|
7202
|
+
return undefined;
|
|
7203
|
+
}
|
|
7204
|
+
const turns = managedTurnsForSession(storeDir, session.session_id);
|
|
7205
|
+
if (turns.some((turn) => {
|
|
7206
|
+
const callbackDelivered = isRecord(turn.callback_delivery) &&
|
|
7207
|
+
turn.callback_delivery.status === "delivered";
|
|
7208
|
+
const explicitlyAbandonedUncertain = exactExplicitlyAbandonedUncertainCandidateTurn({
|
|
7209
|
+
storeDir,
|
|
7210
|
+
session,
|
|
7211
|
+
turn
|
|
7212
|
+
});
|
|
7213
|
+
return (!TERMINAL_DISPATCH_RELEASE_STATUSES.has(turn.status) ||
|
|
7214
|
+
managedTurnNeedsAttention(turn) ||
|
|
7215
|
+
(isRecord(turn.callback_delivery) &&
|
|
7216
|
+
!callbackDelivered) ||
|
|
7217
|
+
(Boolean(turn.gateway_method) &&
|
|
7218
|
+
!callbackDelivered &&
|
|
7219
|
+
!explicitlyAbandonedUncertain) ||
|
|
7220
|
+
stringValue(turn.terminal_binding_id) !== binding.binding_id ||
|
|
7221
|
+
Number(turn.terminal_binding_generation) !== binding.generation ||
|
|
7222
|
+
(stringValue(turn.native_thread_id) ??
|
|
7223
|
+
stringValue(isRecord(turn.native_session_takeover)
|
|
7224
|
+
? turn.native_session_takeover.terminal_agent_session_id
|
|
7225
|
+
: undefined)) !== binding.native_thread_id);
|
|
7226
|
+
})) {
|
|
7227
|
+
return undefined;
|
|
7228
|
+
}
|
|
7229
|
+
return turns
|
|
7230
|
+
.map((turn) => ({
|
|
7231
|
+
turn_id: turnIdForConversation(turn),
|
|
7232
|
+
status: turn.status,
|
|
7233
|
+
updated_at: required(stringValue(turn.updated_at), `managed Turn ${turnIdForConversation(turn)} updated_at is unavailable`),
|
|
7234
|
+
binding_id: binding.binding_id,
|
|
7235
|
+
binding_generation: binding.generation,
|
|
7236
|
+
native_thread_id: binding.native_thread_id,
|
|
7237
|
+
turn_fingerprint: createHash("sha256")
|
|
7238
|
+
.update(JSON.stringify(turn))
|
|
7239
|
+
.digest("hex")
|
|
7240
|
+
}))
|
|
7241
|
+
.sort((left, right) => left.turn_id.localeCompare(right.turn_id));
|
|
7242
|
+
}
|
|
7243
|
+
function frozenCandidateSourceTurnHistory(storeDir, session) {
|
|
7081
7244
|
const binding = session.binding;
|
|
7082
7245
|
if (!binding?.native_thread_id) {
|
|
7083
7246
|
return undefined;
|
|
7084
7247
|
}
|
|
7085
7248
|
const turns = managedTurnsForSession(storeDir, session.session_id);
|
|
7086
7249
|
if (turns.some((turn) => !TERMINAL_DISPATCH_RELEASE_STATUSES.has(turn.status) ||
|
|
7087
|
-
managedTurnNeedsAttention(turn) ||
|
|
7088
|
-
(isRecord(turn.callback_delivery) &&
|
|
7089
|
-
turn.callback_delivery.status !== "delivered") ||
|
|
7090
|
-
(Boolean(turn.gateway_method) &&
|
|
7091
|
-
(!isRecord(turn.callback_delivery) ||
|
|
7092
|
-
turn.callback_delivery.status !== "delivered")) ||
|
|
7093
7250
|
stringValue(turn.terminal_binding_id) !== binding.binding_id ||
|
|
7094
7251
|
Number(turn.terminal_binding_generation) !== binding.generation ||
|
|
7095
7252
|
(stringValue(turn.native_thread_id) ??
|
|
@@ -7098,8 +7255,7 @@ function deferredCandidateSourceTurnHistory(storeDir, session) {
|
|
|
7098
7255
|
: undefined)) !== binding.native_thread_id)) {
|
|
7099
7256
|
return undefined;
|
|
7100
7257
|
}
|
|
7101
|
-
return turns
|
|
7102
|
-
.map((turn) => ({
|
|
7258
|
+
return turns.map((turn) => ({
|
|
7103
7259
|
turn_id: turnIdForConversation(turn),
|
|
7104
7260
|
status: turn.status,
|
|
7105
7261
|
updated_at: required(stringValue(turn.updated_at), `managed Turn ${turnIdForConversation(turn)} updated_at is unavailable`),
|
|
@@ -7109,8 +7265,220 @@ function deferredCandidateSourceTurnHistory(storeDir, session) {
|
|
|
7109
7265
|
turn_fingerprint: createHash("sha256")
|
|
7110
7266
|
.update(JSON.stringify(turn))
|
|
7111
7267
|
.digest("hex")
|
|
7112
|
-
}))
|
|
7113
|
-
|
|
7268
|
+
})).sort((left, right) => left.turn_id.localeCompare(right.turn_id));
|
|
7269
|
+
}
|
|
7270
|
+
function exactExplicitlyAbandonedUncertainCandidateTurn({ storeDir, session, turn }) {
|
|
7271
|
+
return explicitlyAbandonedUncertainCandidateTurnFingerprint({
|
|
7272
|
+
storeDir,
|
|
7273
|
+
session,
|
|
7274
|
+
turn
|
|
7275
|
+
}) !== undefined;
|
|
7276
|
+
}
|
|
7277
|
+
function explicitlyAbandonedUncertainCandidateTurnProof({ storeDir, session, turn, ledgerOverride, requireResolvedTopLevel = true }) {
|
|
7278
|
+
if (turn.status !== "closed" ||
|
|
7279
|
+
isRecord(turn.callback_delivery) ||
|
|
7280
|
+
isRecord(turn.terminal_bridge_completion_claim) ||
|
|
7281
|
+
!stringValue(turn.closed_at) ||
|
|
7282
|
+
!stringValue(turn.close_reason)) {
|
|
7283
|
+
return undefined;
|
|
7284
|
+
}
|
|
7285
|
+
const takeover = isRecord(turn.native_session_takeover)
|
|
7286
|
+
? turn.native_session_takeover
|
|
7287
|
+
: undefined;
|
|
7288
|
+
const submission = terminalBridgeSubmission(turn);
|
|
7289
|
+
const terminalControl = terminalControlFromTakeover(takeover);
|
|
7290
|
+
const messageId = stringValue(submission?.message_id);
|
|
7291
|
+
if (!takeover ||
|
|
7292
|
+
isRecord(takeover.terminal_bridge_completion_claim) ||
|
|
7293
|
+
!terminalControl ||
|
|
7294
|
+
submission?.status !== "uncertain" ||
|
|
7295
|
+
submission.safe_to_retry === true ||
|
|
7296
|
+
!messageId ||
|
|
7297
|
+
stringValue(takeover.terminal_bridge_message_id) !== messageId ||
|
|
7298
|
+
stringValue(submission.session_id) !== session.session_id ||
|
|
7299
|
+
stringValue(submission.turn_id) !== turnIdForConversation(turn)) {
|
|
7300
|
+
return undefined;
|
|
7301
|
+
}
|
|
7302
|
+
const canonical = pathsForConversation(turn.conversation_id, storeDir);
|
|
7303
|
+
if (path.resolve(stringValue(turn.state_path) ?? "") !==
|
|
7304
|
+
path.resolve(canonical.statePath) ||
|
|
7305
|
+
path.resolve(stringValue(turn.event_log_path) ?? "") !==
|
|
7306
|
+
path.resolve(canonical.logPath) ||
|
|
7307
|
+
path.resolve(managedSessionStoreDirForConversation(turn) ?? "") !==
|
|
7308
|
+
path.resolve(storeDir)) {
|
|
7309
|
+
return undefined;
|
|
7310
|
+
}
|
|
7311
|
+
let ledger;
|
|
7312
|
+
try {
|
|
7313
|
+
ledger = ledgerOverride ?? loadTerminalBridgeDispatchLedger(terminalControl);
|
|
7314
|
+
}
|
|
7315
|
+
catch {
|
|
7316
|
+
return undefined;
|
|
7317
|
+
}
|
|
7318
|
+
if (!ledger ||
|
|
7319
|
+
!terminalDispatchRecordMatchesControl(ledger, terminalControl) ||
|
|
7320
|
+
(requireResolvedTopLevel &&
|
|
7321
|
+
(ledger.status !== "resolved" ||
|
|
7322
|
+
ledger.reason !== "conversation explicitly closed by request" ||
|
|
7323
|
+
!validTimestampMs(ledger.resolved_at) ||
|
|
7324
|
+
stringValue(ledger.conversation_id) !== turn.conversation_id ||
|
|
7325
|
+
stringValue(ledger.session_id) !== session.session_id ||
|
|
7326
|
+
stringValue(ledger.turn_id) !== turnIdForConversation(turn) ||
|
|
7327
|
+
stringValue(ledger.message_id) !== messageId ||
|
|
7328
|
+
stringValue(ledger.request_hash) !==
|
|
7329
|
+
stringValue(submission.request_hash) ||
|
|
7330
|
+
stringValue(ledger.binding_id) !==
|
|
7331
|
+
stringValue(turn.terminal_binding_id) ||
|
|
7332
|
+
Number(ledger.binding_generation) !==
|
|
7333
|
+
Number(turn.terminal_binding_generation) ||
|
|
7334
|
+
path.resolve(stringValue(ledger.state_path) ?? "") !==
|
|
7335
|
+
path.resolve(canonical.statePath) ||
|
|
7336
|
+
path.resolve(stringValue(ledger.event_log_path) ?? "") !==
|
|
7337
|
+
path.resolve(canonical.logPath) ||
|
|
7338
|
+
path.resolve(stringValue(ledger.store_dir) ?? "") !==
|
|
7339
|
+
path.resolve(storeDir)))) {
|
|
7340
|
+
return undefined;
|
|
7341
|
+
}
|
|
7342
|
+
let receipt;
|
|
7343
|
+
try {
|
|
7344
|
+
const matchingReceipts = terminalLedgerReceiptHistory(ledger).filter((candidate) => stringValue(candidate.message_id) === messageId);
|
|
7345
|
+
receipt = matchingReceipts.length === 1
|
|
7346
|
+
? matchingReceipts[0]
|
|
7347
|
+
: undefined;
|
|
7348
|
+
}
|
|
7349
|
+
catch {
|
|
7350
|
+
return undefined;
|
|
7351
|
+
}
|
|
7352
|
+
if (!receipt ||
|
|
7353
|
+
receipt.status !== "uncertain" ||
|
|
7354
|
+
receipt.safe_to_retry === true ||
|
|
7355
|
+
!validTimestampMs(receipt.uncertain_at) ||
|
|
7356
|
+
!terminalDispatchRecordMatchesControl(receipt, terminalControl) ||
|
|
7357
|
+
stringValue(receipt.conversation_id) !== turn.conversation_id ||
|
|
7358
|
+
stringValue(receipt.session_id) !== session.session_id ||
|
|
7359
|
+
stringValue(receipt.turn_id) !== turnIdForConversation(turn) ||
|
|
7360
|
+
stringValue(receipt.request_hash) !== stringValue(submission.request_hash) ||
|
|
7361
|
+
stringValue(receipt.binding_id) !== stringValue(turn.terminal_binding_id) ||
|
|
7362
|
+
Number(receipt.binding_generation) !==
|
|
7363
|
+
Number(turn.terminal_binding_generation) ||
|
|
7364
|
+
path.resolve(stringValue(receipt.state_path) ?? "") !==
|
|
7365
|
+
path.resolve(canonical.statePath) ||
|
|
7366
|
+
path.resolve(stringValue(receipt.event_log_path) ?? "") !==
|
|
7367
|
+
path.resolve(canonical.logPath) ||
|
|
7368
|
+
path.resolve(stringValue(receipt.store_dir) ?? "") !== path.resolve(storeDir)) {
|
|
7369
|
+
return undefined;
|
|
7370
|
+
}
|
|
7371
|
+
let closeEvent;
|
|
7372
|
+
try {
|
|
7373
|
+
closeEvent = readExistingEvents(canonical.logPath).find((event) => event.event === "conversation_closed" &&
|
|
7374
|
+
event.conversation_id === turn.conversation_id &&
|
|
7375
|
+
event.status === "closed" &&
|
|
7376
|
+
event.ts === turn.closed_at &&
|
|
7377
|
+
event.reason === turn.close_reason);
|
|
7378
|
+
}
|
|
7379
|
+
catch {
|
|
7380
|
+
return undefined;
|
|
7381
|
+
}
|
|
7382
|
+
if (!closeEvent) {
|
|
7383
|
+
return undefined;
|
|
7384
|
+
}
|
|
7385
|
+
// Hash the immutable close event and append-only uncertain receipt, not the
|
|
7386
|
+
// mutable top-level terminal ledger that the next dispatch will replace.
|
|
7387
|
+
return {
|
|
7388
|
+
turnId: turnIdForConversation(turn),
|
|
7389
|
+
messageId,
|
|
7390
|
+
turnFingerprint: createHash("sha256")
|
|
7391
|
+
.update(JSON.stringify(turn))
|
|
7392
|
+
.digest("hex"),
|
|
7393
|
+
closeEvent,
|
|
7394
|
+
uncertainReceipt: receipt
|
|
7395
|
+
};
|
|
7396
|
+
}
|
|
7397
|
+
function explicitlyAbandonedUncertainCandidateTurnFingerprint(args) {
|
|
7398
|
+
const proof = explicitlyAbandonedUncertainCandidateTurnProof(args);
|
|
7399
|
+
return proof
|
|
7400
|
+
? terminalActionFingerprint({
|
|
7401
|
+
kind: "explicitly_abandoned_uncertain_codex_turn",
|
|
7402
|
+
source_session_id: args.session.session_id,
|
|
7403
|
+
source_revision: managedSessionRevision(args.session),
|
|
7404
|
+
source_binding_token: managedSessionBindingToken(args.session),
|
|
7405
|
+
turn_id: proof.turnId,
|
|
7406
|
+
turn_fingerprint: proof.turnFingerprint,
|
|
7407
|
+
close_event: proof.closeEvent,
|
|
7408
|
+
uncertain_receipt: proof.uncertainReceipt
|
|
7409
|
+
})
|
|
7410
|
+
: undefined;
|
|
7411
|
+
}
|
|
7412
|
+
function explicitlyAbandonedCandidateSourceFingerprint({ storeDir, session, sourceTurnHistory, dispatchSnapshot, sourceRevision = managedSessionRevision(session), sourceBindingToken = managedSessionBindingToken(session), ledgerOverride, requireResolvedTopLevel = true }) {
|
|
7413
|
+
if (dispatchSnapshot.status !== "resolved") {
|
|
7414
|
+
return undefined;
|
|
7415
|
+
}
|
|
7416
|
+
const abandonedTurnProofs = managedTurnsForSession(storeDir, session.session_id).flatMap((turn) => {
|
|
7417
|
+
const proof = explicitlyAbandonedUncertainCandidateTurnProof({
|
|
7418
|
+
storeDir,
|
|
7419
|
+
session,
|
|
7420
|
+
turn,
|
|
7421
|
+
ledgerOverride,
|
|
7422
|
+
requireResolvedTopLevel
|
|
7423
|
+
});
|
|
7424
|
+
return proof
|
|
7425
|
+
? [{
|
|
7426
|
+
turn_id: proof.turnId,
|
|
7427
|
+
message_id: proof.messageId,
|
|
7428
|
+
turn_fingerprint: proof.turnFingerprint,
|
|
7429
|
+
close_event: proof.closeEvent,
|
|
7430
|
+
uncertain_receipt: proof.uncertainReceipt
|
|
7431
|
+
}]
|
|
7432
|
+
: [];
|
|
7433
|
+
}).sort((left, right) => left.turn_id.localeCompare(right.turn_id));
|
|
7434
|
+
if (abandonedTurnProofs.length === 0) {
|
|
7435
|
+
return undefined;
|
|
7436
|
+
}
|
|
7437
|
+
return terminalActionFingerprint({
|
|
7438
|
+
kind: "explicitly_abandoned_codex_predecessor",
|
|
7439
|
+
source_session_id: session.session_id,
|
|
7440
|
+
source_revision: sourceRevision,
|
|
7441
|
+
source_binding_token: sourceBindingToken,
|
|
7442
|
+
source_turn_history: sourceTurnHistory,
|
|
7443
|
+
previous_dispatch_snapshot: dispatchSnapshot,
|
|
7444
|
+
abandoned_turns: abandonedTurnProofs
|
|
7445
|
+
});
|
|
7446
|
+
}
|
|
7447
|
+
function assertFrozenExplicitlyAbandonedPredecessorAuthority({ storeDir, transfer, terminalControl }) {
|
|
7448
|
+
if (transfer.source_rollout_authority !==
|
|
7449
|
+
"explicitly_abandoned_predecessor") {
|
|
7450
|
+
return;
|
|
7451
|
+
}
|
|
7452
|
+
const source = loadManagedSession(storeDir, transfer.source_session_id);
|
|
7453
|
+
const sourceAsBound = {
|
|
7454
|
+
...source,
|
|
7455
|
+
status: "bound",
|
|
7456
|
+
binding: transfer.source_before_binding,
|
|
7457
|
+
last_transition_id: transfer.source_previous_last_transition_id
|
|
7458
|
+
};
|
|
7459
|
+
const history = frozenCandidateSourceTurnHistory(storeDir, sourceAsBound);
|
|
7460
|
+
const ledger = loadTerminalBridgeDispatchLedger(terminalControl);
|
|
7461
|
+
const fingerprint = history && ledger
|
|
7462
|
+
? explicitlyAbandonedCandidateSourceFingerprint({
|
|
7463
|
+
storeDir,
|
|
7464
|
+
session: sourceAsBound,
|
|
7465
|
+
sourceTurnHistory: history,
|
|
7466
|
+
dispatchSnapshot: {
|
|
7467
|
+
status: transfer.previous_dispatch_status,
|
|
7468
|
+
fingerprint: transfer.previous_dispatch_fingerprint
|
|
7469
|
+
},
|
|
7470
|
+
sourceRevision: transfer.source_expected_revision,
|
|
7471
|
+
sourceBindingToken: transfer.source_binding_token,
|
|
7472
|
+
ledgerOverride: ledger,
|
|
7473
|
+
requireResolvedTopLevel: false
|
|
7474
|
+
})
|
|
7475
|
+
: undefined;
|
|
7476
|
+
if (!history ||
|
|
7477
|
+
JSON.stringify(history) !== JSON.stringify(transfer.source_turn_history) ||
|
|
7478
|
+
fingerprint !== transfer.source_abandonment_fingerprint) {
|
|
7479
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} lost its exact ` +
|
|
7480
|
+
"explicitly abandoned predecessor authority");
|
|
7481
|
+
}
|
|
7114
7482
|
}
|
|
7115
7483
|
function candidateSourceTransitionHistoryIsTerminal(storeDir, session) {
|
|
7116
7484
|
if (managedSessionHasUnresolvedNativeTransition(storeDir, session)) {
|
|
@@ -7129,7 +7497,7 @@ function candidateSourceTransitionHistoryIsTerminal(storeDir, session) {
|
|
|
7129
7497
|
return false;
|
|
7130
7498
|
}
|
|
7131
7499
|
}
|
|
7132
|
-
function deferredCodexForegroundBindingToken({ terminalId, terminalControl, pid, workspace, processUuid, processBirth, sourceSession, dispatchSnapshot, candidateInventory }) {
|
|
7500
|
+
function deferredCodexForegroundBindingToken({ terminalId, terminalControl, pid, workspace, processUuid, processBirth, sourceSession, dispatchSnapshot, candidateInventory, sourceTurnHistory, candidateContextFingerprint, sourceRolloutAuthority = "present", sourceAbandonmentFingerprint }) {
|
|
7133
7501
|
const terminalToken = unmanagedTerminalBindingToken({
|
|
7134
7502
|
terminalId,
|
|
7135
7503
|
terminalControl,
|
|
@@ -7147,18 +7515,40 @@ function deferredCodexForegroundBindingToken({ terminalId, terminalControl, pid,
|
|
|
7147
7515
|
candidate_native_thread_ids: candidateInventory.roots.map((root) => root.sessionId)
|
|
7148
7516
|
}
|
|
7149
7517
|
: undefined;
|
|
7518
|
+
const sourceTurnHistoryFingerprint = sourceTurnHistory
|
|
7519
|
+
? createHash("sha256")
|
|
7520
|
+
.update(JSON.stringify(sourceTurnHistory))
|
|
7521
|
+
.digest("hex")
|
|
7522
|
+
: undefined;
|
|
7150
7523
|
return createHash("sha256")
|
|
7151
7524
|
.update(JSON.stringify({
|
|
7152
|
-
version: candidateAuthority ?
|
|
7525
|
+
version: candidateAuthority ? 5 : 2,
|
|
7153
7526
|
kind: "deferred_codex_foreground_binding",
|
|
7154
7527
|
terminal_token: terminalToken,
|
|
7528
|
+
composer_state: "styled_empty",
|
|
7155
7529
|
source_session_id: sourceSession.session_id,
|
|
7156
7530
|
source_revision: managedSessionRevision(sourceSession),
|
|
7157
7531
|
source_binding_token: managedSessionBindingToken(sourceSession),
|
|
7158
7532
|
terminal_dispatch_snapshot: dispatchSnapshot,
|
|
7159
|
-
observation:
|
|
7160
|
-
? "
|
|
7161
|
-
:
|
|
7533
|
+
observation: candidateContextFingerprint
|
|
7534
|
+
? "latent_codex_thread_reset"
|
|
7535
|
+
: candidateAuthority
|
|
7536
|
+
? "exact_open_root_inventory"
|
|
7537
|
+
: "verified_absent",
|
|
7538
|
+
...(sourceTurnHistoryFingerprint
|
|
7539
|
+
? { source_turn_history_fingerprint: sourceTurnHistoryFingerprint }
|
|
7540
|
+
: {}),
|
|
7541
|
+
...(candidateContextFingerprint
|
|
7542
|
+
? { candidate_context_fingerprint: candidateContextFingerprint }
|
|
7543
|
+
: {}),
|
|
7544
|
+
...(candidateAuthority
|
|
7545
|
+
? { source_rollout_authority: sourceRolloutAuthority }
|
|
7546
|
+
: {}),
|
|
7547
|
+
...(sourceAbandonmentFingerprint
|
|
7548
|
+
? {
|
|
7549
|
+
source_abandonment_fingerprint: sourceAbandonmentFingerprint
|
|
7550
|
+
}
|
|
7551
|
+
: {}),
|
|
7162
7552
|
...(candidateAuthority ?? {})
|
|
7163
7553
|
}))
|
|
7164
7554
|
.digest("hex");
|
|
@@ -7484,7 +7874,8 @@ async function assertDeferredCodexForegroundBindingBoundary({ options, boundary,
|
|
|
7484
7874
|
pid: boundary.terminal.pid,
|
|
7485
7875
|
workspace: required(boundary.terminal.terminalControl.currentPath, "deferred Codex terminal workspace is unavailable"),
|
|
7486
7876
|
processUuid: boundary.processUuid,
|
|
7487
|
-
processBirth: boundary.processBirth
|
|
7877
|
+
processBirth: boundary.processBirth,
|
|
7878
|
+
sourceRolloutAuthority: boundary.sourceRolloutAuthority
|
|
7488
7879
|
})) {
|
|
7489
7880
|
throw new Error("the exact Codex open-root inventory changed; refresh AKK list before sending");
|
|
7490
7881
|
}
|
|
@@ -7526,7 +7917,27 @@ async function assertDeferredCodexForegroundBindingBoundary({ options, boundary,
|
|
|
7526
7917
|
}
|
|
7527
7918
|
}
|
|
7528
7919
|
else {
|
|
7529
|
-
const history =
|
|
7920
|
+
const history = expectedSourceStatus === "bound"
|
|
7921
|
+
? deferredCandidateSourceTurnHistory(storeDir, sourceAsBound)
|
|
7922
|
+
: frozenCandidateSourceTurnHistory(storeDir, sourceAsBound);
|
|
7923
|
+
const currentAbandonmentFingerprint = boundary.sourceRolloutAuthority ===
|
|
7924
|
+
"explicitly_abandoned_predecessor" &&
|
|
7925
|
+
history
|
|
7926
|
+
? explicitlyAbandonedCandidateSourceFingerprint({
|
|
7927
|
+
storeDir,
|
|
7928
|
+
session: sourceAsBound,
|
|
7929
|
+
sourceTurnHistory: history,
|
|
7930
|
+
dispatchSnapshot: boundary.previousDispatchSnapshot,
|
|
7931
|
+
sourceRevision: boundary.sourceBoundRevision,
|
|
7932
|
+
sourceBindingToken: boundary.sourceBoundBindingToken,
|
|
7933
|
+
...(expectedSourceStatus === "transitioning"
|
|
7934
|
+
? {
|
|
7935
|
+
ledgerOverride: loadTerminalBridgeDispatchLedger(boundary.terminal.terminalControl),
|
|
7936
|
+
requireResolvedTopLevel: false
|
|
7937
|
+
}
|
|
7938
|
+
: {})
|
|
7939
|
+
})
|
|
7940
|
+
: undefined;
|
|
7530
7941
|
if (!boundary.candidateAcceptanceAnchor ||
|
|
7531
7942
|
!history ||
|
|
7532
7943
|
JSON.stringify(history) !== JSON.stringify(boundary.sourceTurnHistory) ||
|
|
@@ -7538,8 +7949,15 @@ async function assertDeferredCodexForegroundBindingBoundary({ options, boundary,
|
|
|
7538
7949
|
workspace: required(boundary.terminal.terminalControl.currentPath, "deferred Codex terminal workspace is unavailable"),
|
|
7539
7950
|
processUuid: boundary.processUuid,
|
|
7540
7951
|
processBirth: boundary.processBirth,
|
|
7541
|
-
inventory: required(candidateInventoryForBoundary, "deferred Codex candidate inventory is unavailable")
|
|
7952
|
+
inventory: required(candidateInventoryForBoundary, "deferred Codex candidate inventory is unavailable"),
|
|
7953
|
+
sourceRolloutAuthority: boundary.sourceRolloutAuthority
|
|
7542
7954
|
}) ||
|
|
7955
|
+
(boundary.sourceRolloutAuthority ===
|
|
7956
|
+
"explicitly_abandoned_predecessor" &&
|
|
7957
|
+
(!boundary.sourceAbandonmentFingerprint ||
|
|
7958
|
+
currentAbandonmentFingerprint !==
|
|
7959
|
+
boundary.sourceAbandonmentFingerprint ||
|
|
7960
|
+
boundary.previousDispatchSnapshot.status !== "resolved")) ||
|
|
7543
7961
|
!candidateSourceTransitionHistoryIsTerminal(storeDir, sourceAsBound)) {
|
|
7544
7962
|
throw new Error("the deferred Codex candidate source history or rollout authority changed");
|
|
7545
7963
|
}
|
|
@@ -7560,6 +7978,16 @@ async function assertDeferredCodexForegroundBindingBoundary({ options, boundary,
|
|
|
7560
7978
|
physicalOnly: boundary.candidateAcceptanceAnchor !== undefined
|
|
7561
7979
|
})
|
|
7562
7980
|
});
|
|
7981
|
+
if (requireEmptyComposer && boundary.candidateContextFingerprint) {
|
|
7982
|
+
const currentContextFingerprint = codexLatentClearResumeFingerprint({
|
|
7983
|
+
screen: status.screen.excerpt,
|
|
7984
|
+
sourceNativeThreadId: source.binding?.native_thread_id,
|
|
7985
|
+
agentVersion: agentVersionForRunningProcess("codex", boundary.terminal.pid, options)
|
|
7986
|
+
});
|
|
7987
|
+
if (currentContextFingerprint !== boundary.candidateContextFingerprint) {
|
|
7988
|
+
throw new Error("the Codex /clear foreground hint changed; refresh AKK list before sending");
|
|
7989
|
+
}
|
|
7990
|
+
}
|
|
7563
7991
|
if (status.reachable !== true ||
|
|
7564
7992
|
status.approval_state.blocked === true ||
|
|
7565
7993
|
!["idle", "unknown"].includes(status.activity_state)) {
|
|
@@ -7596,6 +8024,35 @@ async function prepareDeferredCodexForegroundBinding({ options, terminal, source
|
|
|
7596
8024
|
"candidate_rollout_quiescent"
|
|
7597
8025
|
? deferredCandidateSourceTurnHistory(storeDir, sourceSession)
|
|
7598
8026
|
: undefined;
|
|
8027
|
+
const dispatchSnapshot = deferredCodexForegroundDispatchSnapshot(terminal.terminalControl);
|
|
8028
|
+
const sourceRolloutPresent = Boolean(candidateInventory &&
|
|
8029
|
+
sourceSession.binding.native_process.rollout &&
|
|
8030
|
+
candidateInventory.roots.some((root) => root.sessionId.toLowerCase() ===
|
|
8031
|
+
sourceSession.binding?.native_thread_id?.toLowerCase() &&
|
|
8032
|
+
exactNativeRolloutMatches(root.rollout, sourceSession.binding.native_process.rollout)));
|
|
8033
|
+
const sourceRolloutAuthority = sourceKind === "candidate_rollout_quiescent" &&
|
|
8034
|
+
candidateInventory &&
|
|
8035
|
+
sourceSession.binding.native_process.rollout &&
|
|
8036
|
+
!sourceRolloutPresent
|
|
8037
|
+
? "explicitly_abandoned_predecessor"
|
|
8038
|
+
: "present";
|
|
8039
|
+
const sourceAbandonmentFingerprint = sourceRolloutAuthority === "explicitly_abandoned_predecessor" &&
|
|
8040
|
+
candidateSourceTurnHistory
|
|
8041
|
+
? explicitlyAbandonedCandidateSourceFingerprint({
|
|
8042
|
+
storeDir,
|
|
8043
|
+
session: sourceSession,
|
|
8044
|
+
sourceTurnHistory: candidateSourceTurnHistory,
|
|
8045
|
+
dispatchSnapshot
|
|
8046
|
+
})
|
|
8047
|
+
: undefined;
|
|
8048
|
+
const candidateContextFingerprint = candidateMode &&
|
|
8049
|
+
sourceKind === "candidate_rollout_quiescent"
|
|
8050
|
+
? await observeCodexLatentClearResumeFingerprint({
|
|
8051
|
+
options,
|
|
8052
|
+
terminal,
|
|
8053
|
+
sourceNativeThreadId: sourceSession.binding.native_thread_id
|
|
8054
|
+
})
|
|
8055
|
+
: undefined;
|
|
7599
8056
|
const exactSource = !workspace
|
|
7600
8057
|
? false
|
|
7601
8058
|
: sourceKind === "candidate_rollout_quiescent"
|
|
@@ -7610,7 +8067,8 @@ async function prepareDeferredCodexForegroundBinding({ options, terminal, source
|
|
|
7610
8067
|
workspace,
|
|
7611
8068
|
processUuid: liveIncarnation.processUuid,
|
|
7612
8069
|
processBirth: liveIncarnation.processBirth,
|
|
7613
|
-
inventory: candidateInventory
|
|
8070
|
+
inventory: candidateInventory,
|
|
8071
|
+
sourceRolloutAuthority
|
|
7614
8072
|
})
|
|
7615
8073
|
: exactBoundCodexStatusCardSourceForDeferredSend({
|
|
7616
8074
|
session: sourceSession,
|
|
@@ -7631,7 +8089,10 @@ async function prepareDeferredCodexForegroundBinding({ options, terminal, source
|
|
|
7631
8089
|
!exactSource) {
|
|
7632
8090
|
return undefined;
|
|
7633
8091
|
}
|
|
7634
|
-
|
|
8092
|
+
if (sourceRolloutAuthority === "explicitly_abandoned_predecessor" &&
|
|
8093
|
+
!sourceAbandonmentFingerprint) {
|
|
8094
|
+
return undefined;
|
|
8095
|
+
}
|
|
7635
8096
|
const token = deferredCodexForegroundBindingToken({
|
|
7636
8097
|
terminalId: terminal.conversationId,
|
|
7637
8098
|
terminalControl: terminal.terminalControl,
|
|
@@ -7641,6 +8102,10 @@ async function prepareDeferredCodexForegroundBinding({ options, terminal, source
|
|
|
7641
8102
|
processBirth,
|
|
7642
8103
|
sourceSession,
|
|
7643
8104
|
dispatchSnapshot,
|
|
8105
|
+
sourceTurnHistory: candidateSourceTurnHistory,
|
|
8106
|
+
candidateContextFingerprint,
|
|
8107
|
+
sourceRolloutAuthority,
|
|
8108
|
+
sourceAbandonmentFingerprint,
|
|
7644
8109
|
...(candidateMode ? { candidateInventory } : {})
|
|
7645
8110
|
});
|
|
7646
8111
|
if (expectedToken !== token) {
|
|
@@ -7666,6 +8131,13 @@ async function prepareDeferredCodexForegroundBinding({ options, terminal, source
|
|
|
7666
8131
|
processBirth,
|
|
7667
8132
|
previousDispatchSnapshot: dispatchSnapshot,
|
|
7668
8133
|
sourceKind,
|
|
8134
|
+
sourceRolloutAuthority,
|
|
8135
|
+
...(sourceAbandonmentFingerprint
|
|
8136
|
+
? { sourceAbandonmentFingerprint }
|
|
8137
|
+
: {}),
|
|
8138
|
+
...(candidateContextFingerprint
|
|
8139
|
+
? { candidateContextFingerprint }
|
|
8140
|
+
: {}),
|
|
7669
8141
|
...(sourceSession.last_transition_id
|
|
7670
8142
|
? { sourcePreviousLastTransitionId: sourceSession.last_transition_id }
|
|
7671
8143
|
: {}),
|
|
@@ -7690,6 +8162,51 @@ async function prepareDeferredCodexForegroundBinding({ options, terminal, source
|
|
|
7690
8162
|
terminalControl: terminal.terminalControl,
|
|
7691
8163
|
excludedManagedSessionId: sourceSession.session_id
|
|
7692
8164
|
});
|
|
8165
|
+
if (sourceRolloutAuthority === "explicitly_abandoned_predecessor" &&
|
|
8166
|
+
!codexCandidateInventoryHasNoOtherManagedClaim({
|
|
8167
|
+
storeDir,
|
|
8168
|
+
inventory: required(candidateInventory, "Codex candidate inventory is unavailable"),
|
|
8169
|
+
sourceSessionId: sourceSession.session_id
|
|
8170
|
+
})) {
|
|
8171
|
+
throw new Error("the post-/clear Codex rollout candidate is already claimed by another Session");
|
|
8172
|
+
}
|
|
8173
|
+
if (candidateInventory &&
|
|
8174
|
+
sourceRolloutAuthority === "explicitly_abandoned_predecessor") {
|
|
8175
|
+
for (const root of candidateInventory.roots) {
|
|
8176
|
+
await assertNativeThreadHasExclusiveOwnership({
|
|
8177
|
+
options,
|
|
8178
|
+
agent: "codex",
|
|
8179
|
+
currentPid: terminal.pid,
|
|
8180
|
+
nativeThreadId: root.sessionId,
|
|
8181
|
+
storeDir,
|
|
8182
|
+
terminalControl: terminal.terminalControl,
|
|
8183
|
+
excludedManagedSessionId: root.sessionId.toLowerCase() ===
|
|
8184
|
+
sourceSession.binding.native_thread_id?.toLowerCase()
|
|
8185
|
+
? sourceSession.session_id
|
|
8186
|
+
: undefined
|
|
8187
|
+
});
|
|
8188
|
+
}
|
|
8189
|
+
}
|
|
8190
|
+
// The source semantic proof is about to be replaced by the transfer's own
|
|
8191
|
+
// top-level dispatch generation. Recheck it immediately before publishing
|
|
8192
|
+
// the prepared transfer so every later phase can rely on the immutable
|
|
8193
|
+
// fingerprint plus the Store protocol-5 source-Turn fence.
|
|
8194
|
+
if (sourceRolloutAuthority === "explicitly_abandoned_predecessor") {
|
|
8195
|
+
const freshHistory = deferredCandidateSourceTurnHistory(storeDir, sourceSession);
|
|
8196
|
+
const freshAbandonmentFingerprint = freshHistory
|
|
8197
|
+
? explicitlyAbandonedCandidateSourceFingerprint({
|
|
8198
|
+
storeDir,
|
|
8199
|
+
session: sourceSession,
|
|
8200
|
+
sourceTurnHistory: freshHistory,
|
|
8201
|
+
dispatchSnapshot
|
|
8202
|
+
})
|
|
8203
|
+
: undefined;
|
|
8204
|
+
if (JSON.stringify(freshHistory) !==
|
|
8205
|
+
JSON.stringify(candidateSourceTurnHistory) ||
|
|
8206
|
+
freshAbandonmentFingerprint !== sourceAbandonmentFingerprint) {
|
|
8207
|
+
throw new Error("the explicitly abandoned Codex predecessor authority changed; refresh AKK list");
|
|
8208
|
+
}
|
|
8209
|
+
}
|
|
7693
8210
|
const existingTransfer = listDeferredForegroundTransfers(storeDir).find((candidate) => !["resolved", "abort_resolved"].includes(candidate.status) &&
|
|
7694
8211
|
(candidate.source_session_id === sourceSession.session_id ||
|
|
7695
8212
|
(candidate.terminal_id === terminal.conversationId &&
|
|
@@ -7716,6 +8233,12 @@ async function prepareDeferredCodexForegroundBinding({ options, terminal, source
|
|
|
7716
8233
|
source_previous_last_transition_id: sourceSession.last_transition_id,
|
|
7717
8234
|
source_before_binding: sourceSession.binding,
|
|
7718
8235
|
source_kind: sourceKind,
|
|
8236
|
+
...(sourceRolloutAuthority === "explicitly_abandoned_predecessor"
|
|
8237
|
+
? { source_rollout_authority: sourceRolloutAuthority }
|
|
8238
|
+
: {}),
|
|
8239
|
+
...(sourceAbandonmentFingerprint
|
|
8240
|
+
? { source_abandonment_fingerprint: sourceAbandonmentFingerprint }
|
|
8241
|
+
: {}),
|
|
7719
8242
|
...(candidateSourceTurnHistory
|
|
7720
8243
|
? { source_turn_history: candidateSourceTurnHistory }
|
|
7721
8244
|
: {}),
|
|
@@ -7749,6 +8272,10 @@ function assertDeferredForegroundTransferMatchesBoundary({ transfer, boundary })
|
|
|
7749
8272
|
(transfer.version === 1
|
|
7750
8273
|
? "status_card_only"
|
|
7751
8274
|
: transfer.source_kind) !== boundary.sourceKind ||
|
|
8275
|
+
(transfer.source_rollout_authority ?? "present") !==
|
|
8276
|
+
boundary.sourceRolloutAuthority ||
|
|
8277
|
+
transfer.source_abandonment_fingerprint !==
|
|
8278
|
+
boundary.sourceAbandonmentFingerprint ||
|
|
7752
8279
|
JSON.stringify(transfer.source_turn_history) !==
|
|
7753
8280
|
JSON.stringify(boundary.sourceTurnHistory) ||
|
|
7754
8281
|
transfer.source_previous_last_transition_id !==
|
|
@@ -8135,6 +8662,11 @@ async function commitDeferredCodexForegroundBinding({ options, boundary, identit
|
|
|
8135
8662
|
const storeDir = storeDirFromOptions(options);
|
|
8136
8663
|
let transfer = loadDeferredForegroundTransfer(storeDir, boundary.transferId);
|
|
8137
8664
|
assertDeferredForegroundTransferMatchesBoundary({ transfer, boundary });
|
|
8665
|
+
assertFrozenExplicitlyAbandonedPredecessorAuthority({
|
|
8666
|
+
storeDir,
|
|
8667
|
+
transfer,
|
|
8668
|
+
terminalControl: boundary.terminal.terminalControl
|
|
8669
|
+
});
|
|
8138
8670
|
if (!["dispatch_started", "uncertain"].includes(transfer.status) ||
|
|
8139
8671
|
transfer.input_stage === "none" ||
|
|
8140
8672
|
identity.processUuid !== transfer.process_uuid ||
|
|
@@ -8184,7 +8716,11 @@ async function commitDeferredCodexForegroundBinding({ options, boundary, identit
|
|
|
8184
8716
|
? transfer.target_session_id
|
|
8185
8717
|
: sameNativeThread
|
|
8186
8718
|
? transfer.source_session_id
|
|
8187
|
-
: transfer.target_session_id
|
|
8719
|
+
: transfer.target_session_id,
|
|
8720
|
+
allowedManagedSessionIds: transfer.source_rollout_authority ===
|
|
8721
|
+
"explicitly_abandoned_predecessor"
|
|
8722
|
+
? [transfer.source_session_id]
|
|
8723
|
+
: []
|
|
8188
8724
|
});
|
|
8189
8725
|
const retirement = sameNativeThread
|
|
8190
8726
|
? "binding_scrubbed_same_native_thread"
|
|
@@ -8360,7 +8896,11 @@ async function resolveCommittedDeferredCodexForegroundTransfer({ options, bounda
|
|
|
8360
8896
|
nativeThreadId: transfer.target_native_thread_id,
|
|
8361
8897
|
storeDir,
|
|
8362
8898
|
terminalControl: boundary.terminal.terminalControl,
|
|
8363
|
-
excludedManagedSessionId: target.session_id
|
|
8899
|
+
excludedManagedSessionId: target.session_id,
|
|
8900
|
+
allowedManagedSessionIds: transfer.source_rollout_authority ===
|
|
8901
|
+
"explicitly_abandoned_predecessor"
|
|
8902
|
+
? [transfer.source_session_id]
|
|
8903
|
+
: []
|
|
8364
8904
|
});
|
|
8365
8905
|
transfer = saveDeferredForegroundTransfer(storeDir, {
|
|
8366
8906
|
...transfer,
|
|
@@ -8563,7 +9103,13 @@ async function maybeAdoptObservedExternalThread({ options, terminal, sourceSessi
|
|
|
8563
9103
|
options,
|
|
8564
9104
|
terminal,
|
|
8565
9105
|
sourceSession,
|
|
8566
|
-
resolvedIdentity
|
|
9106
|
+
resolvedIdentity,
|
|
9107
|
+
// Snapshot-bound terminal actions apply their own stricter boundary
|
|
9108
|
+
// before any input. In particular, a post-/clear Codex composer can be
|
|
9109
|
+
// safely empty while the passive activity classifier is still unknown;
|
|
9110
|
+
// do not force the unrelated external-handoff idle check before the
|
|
9111
|
+
// deferred candidate boundary gets a chance to revalidate it.
|
|
9112
|
+
requireSafeTerminal: !stringValue(options.expectedTerminalToken)
|
|
8567
9113
|
});
|
|
8568
9114
|
const identity = observed.identity;
|
|
8569
9115
|
const conflictKind = managedBindingConflictKindForResolvedTerminal({
|
|
@@ -9207,7 +9753,7 @@ function verifiedWorkspaceRelationship(targetWorkspace, candidateWorkspace) {
|
|
|
9207
9753
|
return "unknown";
|
|
9208
9754
|
}
|
|
9209
9755
|
}
|
|
9210
|
-
async function assertNativeThreadHasExclusiveOwnership({ options, agent, currentPid, nativeThreadId, storeDir, terminalControl, excludedManagedSessionId }) {
|
|
9756
|
+
async function assertNativeThreadHasExclusiveOwnership({ options, agent, currentPid, nativeThreadId, storeDir, terminalControl, excludedManagedSessionId, allowedManagedSessionIds = [] }) {
|
|
9211
9757
|
const normalizedNativeThreadId = nativeThreadId.toLowerCase();
|
|
9212
9758
|
if (!isExactNativeThreadId(normalizedNativeThreadId)) {
|
|
9213
9759
|
throw new Error("native thread ownership requires an exact thread UUID");
|
|
@@ -9232,6 +9778,7 @@ async function assertNativeThreadHasExclusiveOwnership({ options, agent, current
|
|
|
9232
9778
|
throw new Error(`native thread ownership is unverifiable: ${activeOwnerScan.uncertaintyReasons.join("; ")}`);
|
|
9233
9779
|
}
|
|
9234
9780
|
const conflictingSessions = listManagedSessions(storeDir).filter((session) => session.session_id !== excludedManagedSessionId &&
|
|
9781
|
+
!allowedManagedSessionIds.includes(session.session_id) &&
|
|
9235
9782
|
session.agent === agent &&
|
|
9236
9783
|
session.binding?.native_thread_id?.toLowerCase() === normalizedNativeThreadId);
|
|
9237
9784
|
if (conflictingSessions.length > 0) {
|
|
@@ -12398,12 +12945,27 @@ async function runSend(options) {
|
|
|
12398
12945
|
}
|
|
12399
12946
|
assertTerminalIncarnationCanStartTurn(storeDir, resolvedTerminal.terminalControl);
|
|
12400
12947
|
let currentSession = tryLoadManagedSession(storeDir, sessionId);
|
|
12401
|
-
|
|
12948
|
+
let knownCodexCompanions = currentSession
|
|
12402
12949
|
? codexAllowedCompanionSetForManagedSession({
|
|
12403
12950
|
storeDir,
|
|
12404
12951
|
session: currentSession
|
|
12405
12952
|
})
|
|
12406
12953
|
: { additional: [] };
|
|
12954
|
+
if (currentSession?.agent === "codex" &&
|
|
12955
|
+
knownCodexCompanions.primary) {
|
|
12956
|
+
try {
|
|
12957
|
+
const inventory = await inspectCodexOpenRootRolloutInventory({
|
|
12958
|
+
options,
|
|
12959
|
+
pid: resolvedTerminal.pid,
|
|
12960
|
+
cwd: resolvedTerminal.terminalControl.currentPath
|
|
12961
|
+
});
|
|
12962
|
+
knownCodexCompanions = codexCompanionsPresentInOpenRootInventory(knownCodexCompanions, inventory);
|
|
12963
|
+
}
|
|
12964
|
+
catch {
|
|
12965
|
+
// Inventory proof is an optimization only. Preserve the existing
|
|
12966
|
+
// closed /status fence when exact open-root membership is unavailable.
|
|
12967
|
+
}
|
|
12968
|
+
}
|
|
12407
12969
|
const lockedNativeIdentity = await resolveCurrentNativeAgentSessionIdentity({
|
|
12408
12970
|
options,
|
|
12409
12971
|
agent: resolvedTerminal.agent,
|
|
@@ -12433,6 +12995,11 @@ async function runSend(options) {
|
|
|
12433
12995
|
if (!bindingMatchesLiveTerminal(currentSession, resolvedTerminal, lockedNativeIdentity, storeDir)) {
|
|
12434
12996
|
throw new Error("managed session identity changed while waiting to send; refresh list and retry");
|
|
12435
12997
|
}
|
|
12998
|
+
await assertStrictCodexSessionHasNoLatentClear({
|
|
12999
|
+
options,
|
|
13000
|
+
terminal: resolvedTerminal,
|
|
13001
|
+
session: currentSession
|
|
13002
|
+
});
|
|
12436
13003
|
const logicalLockedNativeIdentity = logicalIdentityForManagedSession({
|
|
12437
13004
|
storeDir,
|
|
12438
13005
|
session: currentSession,
|
|
@@ -16596,6 +17163,11 @@ async function runRenew(options) {
|
|
|
16596
17163
|
if (conversation.status !== "stalled") {
|
|
16597
17164
|
throw new Error(`cannot renew ${conversation.conversation_id}; conversation is ${conversation.status}, not stalled`);
|
|
16598
17165
|
}
|
|
17166
|
+
if (terminalBridgeSubmission(conversation)?.status === "uncertain") {
|
|
17167
|
+
throw new Error(`cannot renew ${conversation.conversation_id}; its terminal submission ` +
|
|
17168
|
+
"is uncertain and cannot be attributed by monitoring. Inspect the pane " +
|
|
17169
|
+
"and explicitly close the Turn to abandon the unresolved result.");
|
|
17170
|
+
}
|
|
16599
17171
|
const nativeTakeover = isRecord(conversation.native_session_takeover)
|
|
16600
17172
|
? conversation.native_session_takeover
|
|
16601
17173
|
: undefined;
|
|
@@ -16622,6 +17194,9 @@ async function runRenew(options) {
|
|
|
16622
17194
|
if (current.status !== "stalled") {
|
|
16623
17195
|
throw new Error(`cannot renew ${current.conversation_id}; conversation is ${current.status}, not stalled`);
|
|
16624
17196
|
}
|
|
17197
|
+
if (terminalBridgeSubmission(current)?.status === "uncertain") {
|
|
17198
|
+
throw new Error(`cannot renew ${current.conversation_id}; its terminal submission is uncertain`);
|
|
17199
|
+
}
|
|
16625
17200
|
const currentTakeover = isRecord(current.native_session_takeover)
|
|
16626
17201
|
? current.native_session_takeover
|
|
16627
17202
|
: undefined;
|
|
@@ -20997,6 +21572,12 @@ function deferredCodexBoundaryFromTransfer({ terminal, transfer }) {
|
|
|
20997
21572
|
sourceKind: transfer.version === 1
|
|
20998
21573
|
? "status_card_only"
|
|
20999
21574
|
: required(transfer.source_kind, "deferred foreground source kind is unavailable"),
|
|
21575
|
+
sourceRolloutAuthority: transfer.source_rollout_authority ?? "present",
|
|
21576
|
+
...(transfer.source_abandonment_fingerprint
|
|
21577
|
+
? {
|
|
21578
|
+
sourceAbandonmentFingerprint: transfer.source_abandonment_fingerprint
|
|
21579
|
+
}
|
|
21580
|
+
: {}),
|
|
21000
21581
|
...(transfer.source_turn_history
|
|
21001
21582
|
? { sourceTurnHistory: transfer.source_turn_history }
|
|
21002
21583
|
: {}),
|
|
@@ -21565,6 +22146,11 @@ async function recoverAcceptedDeferredForegroundDispatch({ options, storeDir, te
|
|
|
21565
22146
|
statePath: authority.statePath,
|
|
21566
22147
|
expectedMessageBodyHash: stringValue(authority.submission.message_body_hash)
|
|
21567
22148
|
});
|
|
22149
|
+
assertFrozenExplicitlyAbandonedPredecessorAuthority({
|
|
22150
|
+
storeDir,
|
|
22151
|
+
transfer,
|
|
22152
|
+
terminalControl: terminal.terminalControl
|
|
22153
|
+
});
|
|
21568
22154
|
if (/u.test(line.trimEnd()));
|
|
23111
23697
|
}
|
|
23698
|
+
function codexLatentClearResumeObservation({ screen, agentVersion }) {
|
|
23699
|
+
const behaviorProfile = codexLifecycleBehaviorProfile(agentVersion);
|
|
23700
|
+
if (!behaviorProfile) {
|
|
23701
|
+
return undefined;
|
|
23702
|
+
}
|
|
23703
|
+
const withoutEscapes = (line) => line.replace(/\x1B(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/gu, "");
|
|
23704
|
+
const resumePrefix = /^\s*To continue this session, run codex resume\s+(.+?)\s*$/iu;
|
|
23705
|
+
const exactUuid = /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/iu;
|
|
23706
|
+
const lines = String(screen ?? "")
|
|
23707
|
+
.split(/\r?\n/u)
|
|
23708
|
+
.slice(-24)
|
|
23709
|
+
.map(withoutEscapes);
|
|
23710
|
+
const resumeIds = [];
|
|
23711
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
23712
|
+
const prefixMatch = resumePrefix.exec(lines[index]);
|
|
23713
|
+
if (!prefixMatch) {
|
|
23714
|
+
continue;
|
|
23715
|
+
}
|
|
23716
|
+
// Codex wraps the UUID after a hyphen in narrow panes. Only join the
|
|
23717
|
+
// immediately following line, strip layout whitespace, and still require
|
|
23718
|
+
// the resulting value to be one exact UUID. This keeps a prose lookalike
|
|
23719
|
+
// or an unrelated scrollback line from becoming routing authority.
|
|
23720
|
+
const firstFragment = prefixMatch[1].replace(/\s+/gu, "");
|
|
23721
|
+
const fragments = [
|
|
23722
|
+
firstFragment,
|
|
23723
|
+
...(firstFragment.endsWith("-")
|
|
23724
|
+
? [`${prefixMatch[1]}${lines[index + 1] ?? ""}`.replace(/\s+/gu, "")]
|
|
23725
|
+
: [])
|
|
23726
|
+
];
|
|
23727
|
+
const matched = fragments.flatMap((candidate) => {
|
|
23728
|
+
const uuidMatch = exactUuid.exec(candidate);
|
|
23729
|
+
return uuidMatch ? [uuidMatch[1].toLowerCase()] : [];
|
|
23730
|
+
})[0];
|
|
23731
|
+
if (matched) {
|
|
23732
|
+
resumeIds.push(matched);
|
|
23733
|
+
}
|
|
23734
|
+
}
|
|
23735
|
+
const sourceNativeThreadId = resumeIds.at(-1);
|
|
23736
|
+
if (!sourceNativeThreadId) {
|
|
23737
|
+
return undefined;
|
|
23738
|
+
}
|
|
23739
|
+
return {
|
|
23740
|
+
sourceNativeThreadId,
|
|
23741
|
+
fingerprint: terminalActionFingerprint({
|
|
23742
|
+
kind: "codex_latent_clear_resume_hint",
|
|
23743
|
+
behavior_profile: behaviorProfile,
|
|
23744
|
+
source_native_thread_id: sourceNativeThreadId
|
|
23745
|
+
})
|
|
23746
|
+
};
|
|
23747
|
+
}
|
|
23748
|
+
function codexLatentClearResumeFingerprint({ screen, sourceNativeThreadId, agentVersion }) {
|
|
23749
|
+
if (!isExactNativeThreadId(sourceNativeThreadId)) {
|
|
23750
|
+
return undefined;
|
|
23751
|
+
}
|
|
23752
|
+
const observation = codexLatentClearResumeObservation({
|
|
23753
|
+
screen,
|
|
23754
|
+
agentVersion
|
|
23755
|
+
});
|
|
23756
|
+
return observation?.sourceNativeThreadId === sourceNativeThreadId.toLowerCase()
|
|
23757
|
+
? observation.fingerprint
|
|
23758
|
+
: undefined;
|
|
23759
|
+
}
|
|
23760
|
+
async function observeCodexLatentClearResumeFingerprint({ options, terminal, sourceNativeThreadId }) {
|
|
23761
|
+
if (terminal.agent !== "codex" || !isExactNativeThreadId(sourceNativeThreadId)) {
|
|
23762
|
+
return undefined;
|
|
23763
|
+
}
|
|
23764
|
+
const agentVersion = agentVersionForRunningProcess("codex", terminal.pid, options);
|
|
23765
|
+
if (!codexLifecycleBehaviorProfile(agentVersion)) {
|
|
23766
|
+
return undefined;
|
|
23767
|
+
}
|
|
23768
|
+
const status = await createTerminalAgentBridge(options).status("codex", terminal.terminalControl, { runtime: terminalRuntimeForLiveIdentity({ terminal, physicalOnly: true }) });
|
|
23769
|
+
return codexLatentClearResumeFingerprint({
|
|
23770
|
+
screen: status.screen.excerpt,
|
|
23771
|
+
sourceNativeThreadId,
|
|
23772
|
+
agentVersion
|
|
23773
|
+
});
|
|
23774
|
+
}
|
|
23775
|
+
async function assertStrictCodexSessionHasNoLatentClear({ options, terminal, session }) {
|
|
23776
|
+
if (session.agent !== "codex") {
|
|
23777
|
+
return;
|
|
23778
|
+
}
|
|
23779
|
+
const fingerprint = await observeCodexLatentClearResumeFingerprint({
|
|
23780
|
+
options,
|
|
23781
|
+
terminal,
|
|
23782
|
+
sourceNativeThreadId: session.binding?.native_thread_id
|
|
23783
|
+
});
|
|
23784
|
+
if (fingerprint) {
|
|
23785
|
+
throw new Error("Codex /clear changed the foreground logical thread; refresh AKK list " +
|
|
23786
|
+
"and use its snapshot-bound follow-current send. No task input was sent.");
|
|
23787
|
+
}
|
|
23788
|
+
}
|
|
23112
23789
|
function nativeInspectionComposerEmpty(agent, screen) {
|
|
23113
23790
|
return agent === "codex"
|
|
23114
23791
|
? codexComposerEmpty(screen)
|