@scotthuang/agent-knock-knock 0.12.2 → 0.12.3
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/dist/src/cli-core.js
CHANGED
|
@@ -26,7 +26,7 @@ import { parseTerminalConversationId } from "./terminal-agent-adapter.js";
|
|
|
26
26
|
import { associateTerminalEndpointEvidence, hasCanonicalTerminalEndpoint, sameTerminalControlEvidenceIncarnation, sameTerminalControlIncarnation, terminalControlEvidence, terminalControlEvidenceMatches, terminalEndpointFromControlRef, terminalEndpointIdentityFromEvidence, terminalEndpointIdentityKey, terminalLegacyRuntimeRoute, sameTerminalEndpointIdentity } from "./terminal-control-ref.js";
|
|
27
27
|
import { createProductionTerminalAgentRegistry } from "./terminal-agent-registry.js";
|
|
28
28
|
import { parseProcessElapsedSeconds, StaticTerminalProcessSource, SystemTerminalProcessSource } from "./terminal-process-source.js";
|
|
29
|
-
import { isExactClaudeNativeInspectionIdleComposer, NativeInspectionSubmissionError, TerminalAgentBridge, TerminalInputNotStartedError } from "./terminal-agent-bridge.js";
|
|
29
|
+
import { isExactClaudeIdleComposer, isExactClaudeNativeInspectionIdleComposer, NativeInspectionSubmissionError, TerminalAgentBridge, TerminalInputNotStartedError } from "./terminal-agent-bridge.js";
|
|
30
30
|
import { evaluateApprovalPolicy } from "./approval-policy.js";
|
|
31
31
|
import { evaluateDoctorCapabilities, runDoctorCapabilityProbes } from "./doctor-capabilities.js";
|
|
32
32
|
import { runOpenClawChainDiagnostics } from "./openclaw-doctor.js";
|
|
@@ -2916,24 +2916,24 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
|
|
|
2916
2916
|
});
|
|
2917
2917
|
}
|
|
2918
2918
|
}
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2919
|
+
const nativeIdentityObservation = await observeCurrentNativeAgentSessionIdentity({
|
|
2920
|
+
options,
|
|
2921
|
+
agent: session.agent,
|
|
2922
|
+
pid: session.pid,
|
|
2923
|
+
cwd: session.cwd ?? terminalControl.currentPath,
|
|
2924
|
+
preferredSessionId: codexIdentityContext?.preferredSessionId,
|
|
2925
|
+
allowedCompanionIdentity: codexIdentityContext?.companions.primary,
|
|
2926
|
+
allowedAdditionalIdentities: codexIdentityContext?.companions.additional
|
|
2927
|
+
});
|
|
2928
|
+
const nativeAgentIdentity = nativeIdentityObservation.status === "resolved"
|
|
2929
|
+
? nativeIdentityObservation.identity
|
|
2930
|
+
: undefined;
|
|
2931
|
+
if (nativeIdentityObservation.status === "unavailable") {
|
|
2932
2932
|
runtimeLog("warn", "terminal_native_session_identity_unavailable", {
|
|
2933
2933
|
agent: session.agent,
|
|
2934
2934
|
terminal_target: terminalControl.target,
|
|
2935
2935
|
pid: session.pid,
|
|
2936
|
-
error:
|
|
2936
|
+
error: nativeIdentityObservation.reason
|
|
2937
2937
|
});
|
|
2938
2938
|
}
|
|
2939
2939
|
let nativeProcessUuid = nativeAgentIdentity?.processUuid;
|
|
@@ -3002,24 +3002,24 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
|
|
|
3002
3002
|
const nativeInspectionHasBlockingTurn = listConversations(storeDirFromOptions(options)).some((turn) => isDiscoverableTmuxConversation(turn) &&
|
|
3003
3003
|
terminalControlsShareIncarnation(terminalControlForManagedConversation(turn), terminalControl) &&
|
|
3004
3004
|
SESSION_SEND_BLOCKING_STATUSES.has(turn.status));
|
|
3005
|
-
let
|
|
3005
|
+
let automatedInputComposerReady = nativeInspectionComposerEmpty(session.agent, terminalState.screen_excerpt);
|
|
3006
3006
|
if (session.agent === "codex" &&
|
|
3007
|
-
terminalState.activity_state === "idle"
|
|
3007
|
+
(terminalState.activity_state === "idle" ||
|
|
3008
|
+
(terminalState.activity_state === "unknown" &&
|
|
3009
|
+
nativeIdentityObservation.status === "verified_absent")) &&
|
|
3008
3010
|
terminalState.approval_state.blocked !== true &&
|
|
3009
|
-
nativeInspectionCapability.status === "supported" &&
|
|
3010
|
-
nativeInspectionCapability.statusInspection === true &&
|
|
3011
3011
|
terminalControl.capabilities.includes("send_keys") &&
|
|
3012
3012
|
terminalControl.capabilities.includes("screen_status")) {
|
|
3013
3013
|
try {
|
|
3014
3014
|
const provider = createTerminalControlProvider(options);
|
|
3015
3015
|
const resolvedTerminal = await provider.resolve(provider.endpoint(terminalControl));
|
|
3016
3016
|
const styledScreen = await provider.capture(resolvedTerminal, { scrollbackLines: 40, preserveEscapes: true });
|
|
3017
|
-
|
|
3017
|
+
automatedInputComposerReady = codexStyledComposerEmpty(styledScreen);
|
|
3018
3018
|
}
|
|
3019
3019
|
catch {
|
|
3020
3020
|
// Advertising an input action is optional. The action itself repeats the
|
|
3021
3021
|
// same styled composer proof under the terminal lock before any input.
|
|
3022
|
-
|
|
3022
|
+
automatedInputComposerReady = false;
|
|
3023
3023
|
}
|
|
3024
3024
|
}
|
|
3025
3025
|
const entry = {
|
|
@@ -3040,6 +3040,14 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
|
|
|
3040
3040
|
native_agent_process_birth: nativeProcessBirth,
|
|
3041
3041
|
native_agent_rollout: nativeAgentIdentity?.rollout,
|
|
3042
3042
|
native_agent_identity_evidence: nativeProcessEvidence,
|
|
3043
|
+
native_agent_identity_observation: {
|
|
3044
|
+
status: nativeIdentityObservation.status,
|
|
3045
|
+
...(nativeIdentityObservation.status === "unavailable"
|
|
3046
|
+
? { reason: nativeIdentityObservation.reason }
|
|
3047
|
+
: nativeIdentityObservation.status === "verified_absent"
|
|
3048
|
+
? { evidence: nativeIdentityObservation.evidence }
|
|
3049
|
+
: {})
|
|
3050
|
+
},
|
|
3043
3051
|
agent_version: agentVersion,
|
|
3044
3052
|
native_thread_lifecycle: lifecycleCapability,
|
|
3045
3053
|
native_inspection: nativeInspectionCapability,
|
|
@@ -3050,6 +3058,10 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
|
|
|
3050
3058
|
approval_state: terminalState.approval_state,
|
|
3051
3059
|
activity_state: terminalState.activity_state,
|
|
3052
3060
|
activity_reason: terminalState.activity_reason,
|
|
3061
|
+
// Internal action-projection evidence. terminalFirstListProjection strips
|
|
3062
|
+
// this field after gating every automated-input action that can follow a
|
|
3063
|
+
// human native-thread switch.
|
|
3064
|
+
_automated_input_composer_ready: automatedInputComposerReady,
|
|
3053
3065
|
...(orphanedDispatch
|
|
3054
3066
|
? {
|
|
3055
3067
|
orphaned_terminal_dispatch: {
|
|
@@ -3080,7 +3092,8 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
|
|
|
3080
3092
|
codexLifecycleIncarnationAvailable,
|
|
3081
3093
|
native_inspect: nativeInspectionCapability.status === "supported" &&
|
|
3082
3094
|
nativeInspectionCapability.statusInspection === true &&
|
|
3083
|
-
|
|
3095
|
+
terminalState.activity_state === "idle" &&
|
|
3096
|
+
automatedInputComposerReady &&
|
|
3084
3097
|
terminalControl.capabilities.includes("send_keys") &&
|
|
3085
3098
|
terminalControl.capabilities.includes("screen_status") &&
|
|
3086
3099
|
(session.agent === "codex"
|
|
@@ -3106,6 +3119,7 @@ function terminalFirstListProjection({ storeDir, terminals, managedSessions, ses
|
|
|
3106
3119
|
return control ? [control] : [];
|
|
3107
3120
|
});
|
|
3108
3121
|
const projectedTerminals = terminals.map((terminal) => {
|
|
3122
|
+
const { _automated_input_composer_ready: automatedInputComposerReady, ...publicTerminal } = terminal;
|
|
3109
3123
|
const terminalControl = isRecord(terminal.terminal_control)
|
|
3110
3124
|
? terminal.terminal_control
|
|
3111
3125
|
: undefined;
|
|
@@ -3269,6 +3283,71 @@ function terminalFirstListProjection({ storeDir, terminals, managedSessions, ses
|
|
|
3269
3283
|
})
|
|
3270
3284
|
: undefined;
|
|
3271
3285
|
const blockingHandoffTurns = conflictingSessionTurns.filter((turn) => SESSION_SEND_BLOCKING_STATUSES.has(turn.status));
|
|
3286
|
+
const nativeIdentityObservation = isRecord(terminal.native_agent_identity_observation)
|
|
3287
|
+
? terminal.native_agent_identity_observation
|
|
3288
|
+
: undefined;
|
|
3289
|
+
const verifiedEmptyProcessUuid = stringValue(terminal.native_agent_process_uuid);
|
|
3290
|
+
const verifiedEmptyProcessBirth = stringValue(terminal.native_agent_process_birth);
|
|
3291
|
+
const verifiedEmptyWorkspace = stringValue(terminal.workspace ?? terminal.cwd);
|
|
3292
|
+
const verifiedEmptySourceNativeThreadId = stringValue(soleBindingConflict?.session.binding?.native_thread_id)?.toLowerCase();
|
|
3293
|
+
const verifiedEmptySourceActiveElsewhere = Boolean(verifiedEmptySourceNativeThreadId &&
|
|
3294
|
+
terminals.some((candidate) => candidate !== terminal &&
|
|
3295
|
+
candidate.agent === "codex" &&
|
|
3296
|
+
stringValue(candidate.native_agent_session_id)?.toLowerCase() ===
|
|
3297
|
+
verifiedEmptySourceNativeThreadId));
|
|
3298
|
+
const verifiedEmptyRawSendAction = isRecord(rawActions.send)
|
|
3299
|
+
? rawActions.send
|
|
3300
|
+
: {
|
|
3301
|
+
tool: "agent_knock_knock_send",
|
|
3302
|
+
arguments: { selector: stringValue(terminal.id) },
|
|
3303
|
+
missing_required: ["request"]
|
|
3304
|
+
};
|
|
3305
|
+
const verifiedEmptyCodexHandoffEligible = Boolean(mutationsAllowed &&
|
|
3306
|
+
terminal.agent === "codex" &&
|
|
3307
|
+
terminalControl &&
|
|
3308
|
+
discoveredOwnership.state === "none" &&
|
|
3309
|
+
unresolvedSessionClaims.length === 0 &&
|
|
3310
|
+
matchingSessions.length === 0 &&
|
|
3311
|
+
soleBindingConflict?.kind === "unverifiable" &&
|
|
3312
|
+
nativeIdentityObservation?.status === "verified_absent" &&
|
|
3313
|
+
verifiedEmptyProcessUuid &&
|
|
3314
|
+
verifiedEmptyProcessBirth &&
|
|
3315
|
+
verifiedEmptyWorkspace &&
|
|
3316
|
+
exactBoundCodexSourceForVerifiedEmptyHandoff({
|
|
3317
|
+
session: soleBindingConflict.session,
|
|
3318
|
+
terminalId: String(terminal.id),
|
|
3319
|
+
terminalControl,
|
|
3320
|
+
pid: Number(terminal.pid),
|
|
3321
|
+
workspace: verifiedEmptyWorkspace,
|
|
3322
|
+
processUuid: verifiedEmptyProcessUuid,
|
|
3323
|
+
processBirth: verifiedEmptyProcessBirth
|
|
3324
|
+
}) &&
|
|
3325
|
+
["idle", "unknown"].includes(String(terminal.activity_state)) &&
|
|
3326
|
+
automatedInputComposerReady === true &&
|
|
3327
|
+
!(isRecord(terminal.approval_state) &&
|
|
3328
|
+
terminal.approval_state.blocked === true) &&
|
|
3329
|
+
blockingHandoffTurns.length === 0 &&
|
|
3330
|
+
!managedSessionHasUnresolvedNativeTransition(storeDir, soleBindingConflict.session) &&
|
|
3331
|
+
!verifiedEmptySourceActiveElsewhere &&
|
|
3332
|
+
terminal.orphaned_terminal_dispatch === undefined &&
|
|
3333
|
+
terminalControl?.capabilities.includes("send_keys") &&
|
|
3334
|
+
terminalControl.capabilities.includes("screen_status"));
|
|
3335
|
+
const verifiedEmptyCodexSnapshotToken = verifiedEmptyCodexHandoffEligible &&
|
|
3336
|
+
terminalControl &&
|
|
3337
|
+
soleBindingConflict &&
|
|
3338
|
+
verifiedEmptyProcessUuid &&
|
|
3339
|
+
verifiedEmptyProcessBirth &&
|
|
3340
|
+
verifiedEmptyWorkspace
|
|
3341
|
+
? verifiedEmptyCodexHandoffToken({
|
|
3342
|
+
terminalId: String(terminal.id),
|
|
3343
|
+
terminalControl,
|
|
3344
|
+
pid: Number(terminal.pid),
|
|
3345
|
+
workspace: verifiedEmptyWorkspace,
|
|
3346
|
+
processUuid: verifiedEmptyProcessUuid,
|
|
3347
|
+
processBirth: verifiedEmptyProcessBirth,
|
|
3348
|
+
sourceSession: soleBindingConflict.session
|
|
3349
|
+
})
|
|
3350
|
+
: undefined;
|
|
3272
3351
|
const reconcileBindingAction = mutationsAllowed &&
|
|
3273
3352
|
discoveredOwnership.state === "none" &&
|
|
3274
3353
|
unresolvedSessionClaims.length === 0 &&
|
|
@@ -3300,6 +3379,7 @@ function terminalFirstListProjection({ storeDir, terminals, managedSessions, ses
|
|
|
3300
3379
|
unresolvedSessionClaims.length === 0 &&
|
|
3301
3380
|
soleBindingConflict?.kind === "live_external_thread_change" &&
|
|
3302
3381
|
terminal.activity_state === "idle" &&
|
|
3382
|
+
automatedInputComposerReady === true &&
|
|
3303
3383
|
!(isRecord(terminal.approval_state) &&
|
|
3304
3384
|
terminal.approval_state.blocked === true) &&
|
|
3305
3385
|
!conflictingSessionTurns.some((turn) => SESSION_SEND_BLOCKING_STATUSES.has(turn.status)) &&
|
|
@@ -3455,6 +3535,19 @@ function terminalFirstListProjection({ storeDir, terminals, managedSessions, ses
|
|
|
3455
3535
|
}
|
|
3456
3536
|
}
|
|
3457
3537
|
: {}),
|
|
3538
|
+
...(verifiedEmptyCodexSnapshotToken
|
|
3539
|
+
? {
|
|
3540
|
+
send: {
|
|
3541
|
+
...verifiedEmptyRawSendAction,
|
|
3542
|
+
arguments: {
|
|
3543
|
+
...(isRecord(verifiedEmptyRawSendAction.arguments)
|
|
3544
|
+
? verifiedEmptyRawSendAction.arguments
|
|
3545
|
+
: {}),
|
|
3546
|
+
expected_terminal_token: verifiedEmptyCodexSnapshotToken
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
: {}),
|
|
3458
3551
|
...(reconcileBindingAction
|
|
3459
3552
|
? { reconcile_binding: reconcileBindingAction }
|
|
3460
3553
|
: {})
|
|
@@ -3482,8 +3575,19 @@ function terminalFirstListProjection({ storeDir, terminals, managedSessions, ses
|
|
|
3482
3575
|
lifecycle_binding_token: managedSessionBindingToken(authoritativeSession)
|
|
3483
3576
|
}
|
|
3484
3577
|
: {};
|
|
3578
|
+
const publicManagementConflict = ownership.state === "conflict" && verifiedEmptyCodexSnapshotToken
|
|
3579
|
+
? {
|
|
3580
|
+
...ownership.conflict,
|
|
3581
|
+
kind: "verified_empty_native_session",
|
|
3582
|
+
reason: "the previously bound Codex rollout is conclusively closed " +
|
|
3583
|
+
"while the same terminal process is at an exact empty prompt",
|
|
3584
|
+
recovery: "use only the exact snapshot-bound send action listed for this terminal"
|
|
3585
|
+
}
|
|
3586
|
+
: ownership.state === "conflict"
|
|
3587
|
+
? ownership.conflict
|
|
3588
|
+
: undefined;
|
|
3485
3589
|
return {
|
|
3486
|
-
...
|
|
3590
|
+
...publicTerminal,
|
|
3487
3591
|
...authoritativeIdentity,
|
|
3488
3592
|
management_state: ownership.state === "conflict"
|
|
3489
3593
|
? "conflict"
|
|
@@ -3491,15 +3595,23 @@ function terminalFirstListProjection({ storeDir, terminals, managedSessions, ses
|
|
|
3491
3595
|
? "managed"
|
|
3492
3596
|
: "unmanaged",
|
|
3493
3597
|
...(ownership.state === "conflict"
|
|
3494
|
-
? { management_conflict:
|
|
3598
|
+
? { management_conflict: publicManagementConflict }
|
|
3495
3599
|
: {}),
|
|
3496
3600
|
...(externalHandoffDetected
|
|
3497
3601
|
? {
|
|
3498
3602
|
handoff_state: externalHandoffAdoptable
|
|
3499
3603
|
? "external_handoff_adoptable"
|
|
3500
|
-
: "external_handoff_blocked"
|
|
3604
|
+
: "external_handoff_blocked",
|
|
3605
|
+
...(automatedInputComposerReady !== true
|
|
3606
|
+
? {
|
|
3607
|
+
handoff_blocked_reason: "the current terminal composer is not an exact empty idle frame"
|
|
3608
|
+
}
|
|
3609
|
+
: {})
|
|
3501
3610
|
}
|
|
3502
3611
|
: {}),
|
|
3612
|
+
...(verifiedEmptyCodexSnapshotToken
|
|
3613
|
+
? { handoff_state: "verified_empty_native_session_adoptable" }
|
|
3614
|
+
: {}),
|
|
3503
3615
|
...(handoffDecision ? { handoff_decision: handoffDecision } : {}),
|
|
3504
3616
|
managed: management,
|
|
3505
3617
|
available_actions: availableActions
|
|
@@ -4199,13 +4311,13 @@ function managedListApprovalState(conversation) {
|
|
|
4199
4311
|
}
|
|
4200
4312
|
function listActionContracts() {
|
|
4201
4313
|
return {
|
|
4202
|
-
version:
|
|
4314
|
+
version: 9,
|
|
4203
4315
|
instructions: [
|
|
4204
4316
|
"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. That nested action requires explicit user confirmation; after it succeeds, refresh list before any follow-current send.",
|
|
4205
4317
|
"An existing managed session's ordinary send targets session_id and creates a new turn. A turn id is never an ordinary send target.",
|
|
4206
4318
|
"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.",
|
|
4207
4319
|
"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.",
|
|
4208
|
-
"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. Other binding conflicts remain fail-closed and may expose only exact low-level reconcile_binding recovery.",
|
|
4320
|
+
"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. Other binding conflicts remain fail-closed and may expose only exact low-level reconcile_binding recovery.",
|
|
4209
4321
|
"List resumable threads before resume; use only a complete native_thread_id and the action returned for that candidate.",
|
|
4210
4322
|
"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.",
|
|
4211
4323
|
"Use respond only for an in-flight turn that is explicitly waiting for OpenClaw.",
|
|
@@ -4239,6 +4351,17 @@ function listActionContracts() {
|
|
|
4239
4351
|
meaning: "currently_safe_actions",
|
|
4240
4352
|
authoritative_for_tool_calls: true
|
|
4241
4353
|
},
|
|
4354
|
+
native_agent_identity_observation: {
|
|
4355
|
+
meaning: "the latest bounded native identity probe; verified_absent is distinct from an unavailable resolver",
|
|
4356
|
+
authoritative_for_tool_calls: false
|
|
4357
|
+
},
|
|
4358
|
+
management_conflict: {
|
|
4359
|
+
verified_empty_native_session: "a previously bound Codex rollout is conclusively closed; only the exact snapshot-bound send in available_actions may detach it and create an isolated virgin Session"
|
|
4360
|
+
},
|
|
4361
|
+
handoff_state: {
|
|
4362
|
+
meaning: "why a conflict-scoped follow-current send is available or blocked",
|
|
4363
|
+
verified_empty_native_session_adoptable: "the exact current terminal snapshot permits the listed conflict send; availability is revalidated before terminal input"
|
|
4364
|
+
},
|
|
4242
4365
|
handoff_decision: {
|
|
4243
4366
|
meaning: "an explicit human choice required before superseding an active source Turn",
|
|
4244
4367
|
authoritative_action_path: "terminals[].handoff_decision.choices.take_over_current.action",
|
|
@@ -4262,7 +4385,7 @@ function listActionContracts() {
|
|
|
4262
4385
|
"agentHardTimeoutMinutes"
|
|
4263
4386
|
],
|
|
4264
4387
|
unsupported: ["timeoutSeconds"],
|
|
4265
|
-
ordinary_use: "Create a new managed turn in the exact Session. A live terminal selector can attach an unmanaged pane
|
|
4388
|
+
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, or use the exact listed conflict send to detach a verified-empty Codex source before an isolated virgin attach; an explicit session_id never follows the pane."
|
|
4266
4389
|
},
|
|
4267
4390
|
new_thread: {
|
|
4268
4391
|
tool: "agent_knock_knock_new_thread",
|
|
@@ -5652,6 +5775,218 @@ async function reattachManagedSessionForNativeIdentity({ options, terminal, iden
|
|
|
5652
5775
|
}, { expectedRevision: managedSessionRevision(existing) });
|
|
5653
5776
|
}
|
|
5654
5777
|
const HUMAN_OBSERVED_HANDOFF_FINGERPRINT = nativeThreadCommandFingerprint("adopt_external_thread:human_observed:no_terminal_input:v1");
|
|
5778
|
+
function exactBoundCodexSourceForVerifiedEmptyHandoff({ session, terminalId, terminalControl, pid, workspace, processUuid, processBirth }) {
|
|
5779
|
+
const binding = session.binding;
|
|
5780
|
+
return Boolean(session.agent === "codex" &&
|
|
5781
|
+
session.status === "bound" &&
|
|
5782
|
+
binding &&
|
|
5783
|
+
isExactNativeThreadId(binding.native_thread_id) &&
|
|
5784
|
+
isCompleteNativeRollout(binding.native_process.rollout) &&
|
|
5785
|
+
binding.native_process.process_uuid &&
|
|
5786
|
+
binding.native_process.process_birth &&
|
|
5787
|
+
binding.native_process.pid === pid &&
|
|
5788
|
+
terminalControlAliasMatches(binding.terminal_id, binding.terminal_control, terminalId, terminalControl) &&
|
|
5789
|
+
workspace &&
|
|
5790
|
+
path.resolve(session.workspace) === path.resolve(workspace) &&
|
|
5791
|
+
processIncarnationRelationship({
|
|
5792
|
+
binding,
|
|
5793
|
+
livePid: pid,
|
|
5794
|
+
liveProcessUuid: processUuid,
|
|
5795
|
+
liveProcessBirth: processBirth
|
|
5796
|
+
}) === "same");
|
|
5797
|
+
}
|
|
5798
|
+
function verifiedEmptyCodexHandoffToken({ terminalId, terminalControl, pid, workspace, processUuid, processBirth, sourceSession }) {
|
|
5799
|
+
const terminalToken = unmanagedTerminalBindingToken({
|
|
5800
|
+
terminalId,
|
|
5801
|
+
terminalControl,
|
|
5802
|
+
agent: "codex",
|
|
5803
|
+
pid,
|
|
5804
|
+
workspace,
|
|
5805
|
+
processUuid,
|
|
5806
|
+
processBirth
|
|
5807
|
+
});
|
|
5808
|
+
return createHash("sha256")
|
|
5809
|
+
.update(JSON.stringify({
|
|
5810
|
+
version: 1,
|
|
5811
|
+
kind: "verified_empty_codex_handoff",
|
|
5812
|
+
terminal_token: terminalToken,
|
|
5813
|
+
source_session_id: sourceSession.session_id,
|
|
5814
|
+
source_revision: managedSessionRevision(sourceSession),
|
|
5815
|
+
source_binding_token: managedSessionBindingToken(sourceSession),
|
|
5816
|
+
observation: "verified_absent"
|
|
5817
|
+
}))
|
|
5818
|
+
.digest("hex");
|
|
5819
|
+
}
|
|
5820
|
+
async function assertVerifiedEmptyCodexHandoffBoundary({ options, terminal, sourceSession, expectedSourceStatus, requireNoDispatch, requireEmptyComposer = true }) {
|
|
5821
|
+
const currentSource = loadManagedSession(storeDirFromOptions(options), sourceSession.session_id);
|
|
5822
|
+
if (currentSource.status !== expectedSourceStatus ||
|
|
5823
|
+
currentSource.revision !== sourceSession.revision ||
|
|
5824
|
+
managedSessionBindingToken(currentSource) !==
|
|
5825
|
+
managedSessionBindingToken(sourceSession)) {
|
|
5826
|
+
throw new Error("the verified-empty source Session changed; refresh AKK list");
|
|
5827
|
+
}
|
|
5828
|
+
const liveIncarnation = codexProcessIncarnationForPid(terminal.pid);
|
|
5829
|
+
if (!exactBoundCodexSourceForVerifiedEmptyHandoff({
|
|
5830
|
+
session: {
|
|
5831
|
+
...currentSource,
|
|
5832
|
+
// The exact source binding remains authoritative after the monotonic
|
|
5833
|
+
// detach; only the Session status changes.
|
|
5834
|
+
status: "bound"
|
|
5835
|
+
},
|
|
5836
|
+
terminalId: terminal.conversationId,
|
|
5837
|
+
terminalControl: terminal.terminalControl,
|
|
5838
|
+
pid: terminal.pid,
|
|
5839
|
+
workspace: terminal.terminalControl.currentPath,
|
|
5840
|
+
processUuid: liveIncarnation.processUuid,
|
|
5841
|
+
processBirth: liveIncarnation.processBirth
|
|
5842
|
+
})) {
|
|
5843
|
+
throw new Error("the verified-empty source binding no longer matches the terminal");
|
|
5844
|
+
}
|
|
5845
|
+
const observation = await observeCurrentNativeAgentSessionIdentity({
|
|
5846
|
+
options,
|
|
5847
|
+
agent: "codex",
|
|
5848
|
+
pid: terminal.pid,
|
|
5849
|
+
cwd: terminal.terminalControl.currentPath
|
|
5850
|
+
});
|
|
5851
|
+
if (observation.status === "unavailable") {
|
|
5852
|
+
throw new Error(`Codex native identity observation is unavailable: ${observation.reason}`);
|
|
5853
|
+
}
|
|
5854
|
+
if (observation.status !== "verified_absent") {
|
|
5855
|
+
throw new Error(`Codex materialized native thread ${observation.identity.sessionId}; ` +
|
|
5856
|
+
"refresh AKK list before sending");
|
|
5857
|
+
}
|
|
5858
|
+
const status = await createTerminalAgentBridge(options).status("codex", terminal.terminalControl, {
|
|
5859
|
+
runtime: terminalRuntimeForLiveIdentity({
|
|
5860
|
+
terminal,
|
|
5861
|
+
expectedEmptyNativeSession: true
|
|
5862
|
+
})
|
|
5863
|
+
});
|
|
5864
|
+
if (status.reachable !== true ||
|
|
5865
|
+
status.approval_state.blocked === true ||
|
|
5866
|
+
!["idle", "unknown"].includes(status.activity_state)) {
|
|
5867
|
+
throw new Error(`terminal ${terminal.terminalControl.target} is not at a verified ` +
|
|
5868
|
+
`empty Codex prompt (${status.activity_state}: ${status.activity_reason})`);
|
|
5869
|
+
}
|
|
5870
|
+
if (requireNoDispatch) {
|
|
5871
|
+
assertTerminalLifecycleReady({
|
|
5872
|
+
options,
|
|
5873
|
+
terminal,
|
|
5874
|
+
// A fully styled empty/placeholder composer below is stronger prompt
|
|
5875
|
+
// evidence than the plain-screen parser's conservative `unknown`.
|
|
5876
|
+
terminalStatus: { ...status, activity_state: "idle" }
|
|
5877
|
+
});
|
|
5878
|
+
}
|
|
5879
|
+
if (managedSessionHasUnresolvedNativeTransition(storeDirFromOptions(options), currentSource)) {
|
|
5880
|
+
throw new Error(`managed Session ${currentSource.session_id} has an unresolved native-thread transition`);
|
|
5881
|
+
}
|
|
5882
|
+
if (requireEmptyComposer) {
|
|
5883
|
+
await assertCodexComposerReadyForAutomatedInput({
|
|
5884
|
+
options,
|
|
5885
|
+
terminalControl: terminal.terminalControl
|
|
5886
|
+
});
|
|
5887
|
+
}
|
|
5888
|
+
}
|
|
5889
|
+
async function maybeDetachVerifiedEmptyCodexSource({ options, terminal, sourceSession, observation }) {
|
|
5890
|
+
if (terminal.agent !== "codex" ||
|
|
5891
|
+
!sourceSession?.binding ||
|
|
5892
|
+
observation.status !== "verified_absent") {
|
|
5893
|
+
return undefined;
|
|
5894
|
+
}
|
|
5895
|
+
const processUuid = sourceSession.binding.native_process.process_uuid;
|
|
5896
|
+
const processBirth = sourceSession.binding.native_process.process_birth;
|
|
5897
|
+
const workspace = terminal.terminalControl.currentPath;
|
|
5898
|
+
const liveIncarnation = codexProcessIncarnationForPid(terminal.pid);
|
|
5899
|
+
if (!processUuid ||
|
|
5900
|
+
!processBirth ||
|
|
5901
|
+
!workspace ||
|
|
5902
|
+
!exactBoundCodexSourceForVerifiedEmptyHandoff({
|
|
5903
|
+
session: sourceSession,
|
|
5904
|
+
terminalId: terminal.conversationId,
|
|
5905
|
+
terminalControl: terminal.terminalControl,
|
|
5906
|
+
pid: terminal.pid,
|
|
5907
|
+
workspace,
|
|
5908
|
+
processUuid: liveIncarnation.processUuid,
|
|
5909
|
+
processBirth: liveIncarnation.processBirth
|
|
5910
|
+
})) {
|
|
5911
|
+
return undefined;
|
|
5912
|
+
}
|
|
5913
|
+
const expectedToken = verifiedEmptyCodexHandoffToken({
|
|
5914
|
+
terminalId: terminal.conversationId,
|
|
5915
|
+
terminalControl: terminal.terminalControl,
|
|
5916
|
+
pid: terminal.pid,
|
|
5917
|
+
workspace,
|
|
5918
|
+
processUuid,
|
|
5919
|
+
processBirth,
|
|
5920
|
+
sourceSession
|
|
5921
|
+
});
|
|
5922
|
+
if (stringValue(options.expectedTerminalToken) !== expectedToken) {
|
|
5923
|
+
throw new Error("verified-empty Codex handoff requires the fresh exact terminal token " +
|
|
5924
|
+
"advertised by AKK list");
|
|
5925
|
+
}
|
|
5926
|
+
await assertVerifiedEmptyCodexHandoffBoundary({
|
|
5927
|
+
options,
|
|
5928
|
+
terminal,
|
|
5929
|
+
sourceSession,
|
|
5930
|
+
expectedSourceStatus: "bound",
|
|
5931
|
+
requireNoDispatch: true
|
|
5932
|
+
});
|
|
5933
|
+
await assertNativeThreadHasExclusiveOwnership({
|
|
5934
|
+
options,
|
|
5935
|
+
agent: "codex",
|
|
5936
|
+
currentPid: terminal.pid,
|
|
5937
|
+
nativeThreadId: sourceSession.binding.native_thread_id,
|
|
5938
|
+
storeDir: storeDirFromOptions(options),
|
|
5939
|
+
terminalControl: terminal.terminalControl,
|
|
5940
|
+
excludedManagedSessionId: sourceSession.session_id
|
|
5941
|
+
});
|
|
5942
|
+
const detachedAt = cliNow().toISOString();
|
|
5943
|
+
const detached = saveManagedSession(storeDirFromOptions(options), {
|
|
5944
|
+
...sourceSession,
|
|
5945
|
+
status: "detached",
|
|
5946
|
+
detached_at: detachedAt,
|
|
5947
|
+
updated_at: detachedAt
|
|
5948
|
+
}, {
|
|
5949
|
+
expectedRevision: managedSessionRevision(sourceSession)
|
|
5950
|
+
});
|
|
5951
|
+
runtimeLog("info", "verified_empty_codex_source_detached", {
|
|
5952
|
+
terminal_id: terminal.conversationId,
|
|
5953
|
+
source_session_id: sourceSession.session_id,
|
|
5954
|
+
native_thread_id: sourceSession.binding.native_thread_id,
|
|
5955
|
+
process_uuid: processUuid,
|
|
5956
|
+
process_birth: processBirth,
|
|
5957
|
+
terminal_input_sent: false
|
|
5958
|
+
});
|
|
5959
|
+
return {
|
|
5960
|
+
detached,
|
|
5961
|
+
boundary: {
|
|
5962
|
+
terminal,
|
|
5963
|
+
detachedSourceSessionId: detached.session_id,
|
|
5964
|
+
detachedSourceRevision: managedSessionRevision(detached),
|
|
5965
|
+
detachedSourceBindingToken: managedSessionBindingToken(detached),
|
|
5966
|
+
processUuid,
|
|
5967
|
+
processBirth
|
|
5968
|
+
}
|
|
5969
|
+
};
|
|
5970
|
+
}
|
|
5971
|
+
async function assertVerifiedEmptyCodexTransportBoundary({ options, boundary, requireEmptyComposer }) {
|
|
5972
|
+
const source = loadManagedSession(storeDirFromOptions(options), boundary.detachedSourceSessionId);
|
|
5973
|
+
if (source.status !== "detached" ||
|
|
5974
|
+
managedSessionRevision(source) !== boundary.detachedSourceRevision ||
|
|
5975
|
+
managedSessionBindingToken(source) !==
|
|
5976
|
+
boundary.detachedSourceBindingToken ||
|
|
5977
|
+
source.binding?.native_process.process_uuid !== boundary.processUuid ||
|
|
5978
|
+
source.binding.native_process.process_birth !== boundary.processBirth) {
|
|
5979
|
+
throw new Error("verified-empty Codex source authority changed before text injection");
|
|
5980
|
+
}
|
|
5981
|
+
await assertVerifiedEmptyCodexHandoffBoundary({
|
|
5982
|
+
options,
|
|
5983
|
+
terminal: boundary.terminal,
|
|
5984
|
+
sourceSession: source,
|
|
5985
|
+
expectedSourceStatus: "detached",
|
|
5986
|
+
requireNoDispatch: false,
|
|
5987
|
+
requireEmptyComposer
|
|
5988
|
+
});
|
|
5989
|
+
}
|
|
5655
5990
|
function observedHandoffAuthorityToken({ terminal, identity, sourceSession, target }) {
|
|
5656
5991
|
const exact = exactLifecycleProcessIdentity(terminal, identity);
|
|
5657
5992
|
const terminalToken = unmanagedTerminalBindingToken({
|
|
@@ -9229,14 +9564,14 @@ async function runSend(options) {
|
|
|
9229
9564
|
})) {
|
|
9230
9565
|
return;
|
|
9231
9566
|
}
|
|
9232
|
-
|
|
9233
|
-
|
|
9567
|
+
let claimedSession = soleBoundManagedSessionClaimForTerminal(rawStoreDir, terminalConversation);
|
|
9568
|
+
let knownCodexCompanions = claimedSession
|
|
9234
9569
|
? codexAllowedCompanionSetForManagedSession({
|
|
9235
9570
|
storeDir: rawStoreDir,
|
|
9236
9571
|
session: claimedSession
|
|
9237
9572
|
})
|
|
9238
9573
|
: { additional: [] };
|
|
9239
|
-
|
|
9574
|
+
const nativeIdentityObservation = await observeCurrentNativeAgentSessionIdentity({
|
|
9240
9575
|
options,
|
|
9241
9576
|
agent: terminalConversation.agent,
|
|
9242
9577
|
pid: terminalConversation.pid,
|
|
@@ -9247,6 +9582,26 @@ async function runSend(options) {
|
|
|
9247
9582
|
allowedCompanionIdentity: knownCodexCompanions.primary,
|
|
9248
9583
|
allowedAdditionalIdentities: knownCodexCompanions.additional
|
|
9249
9584
|
});
|
|
9585
|
+
if (nativeIdentityObservation.status === "unavailable") {
|
|
9586
|
+
throw new Error(`native ${terminalConversation.agent} identity observation is ` +
|
|
9587
|
+
`unavailable: ${nativeIdentityObservation.reason}`);
|
|
9588
|
+
}
|
|
9589
|
+
let currentNativeIdentity = nativeIdentityObservation.status === "resolved"
|
|
9590
|
+
? nativeIdentityObservation.identity
|
|
9591
|
+
: undefined;
|
|
9592
|
+
const verifiedEmptyHandoff = await maybeDetachVerifiedEmptyCodexSource({
|
|
9593
|
+
options,
|
|
9594
|
+
terminal: terminalConversation,
|
|
9595
|
+
sourceSession: claimedSession,
|
|
9596
|
+
observation: nativeIdentityObservation
|
|
9597
|
+
});
|
|
9598
|
+
if (verifiedEmptyHandoff) {
|
|
9599
|
+
// The old rollout is conclusively closed. Never carry it forward as
|
|
9600
|
+
// a pre-materialization companion for the new virgin Session.
|
|
9601
|
+
claimedSession = undefined;
|
|
9602
|
+
knownCodexCompanions = { additional: [] };
|
|
9603
|
+
currentNativeIdentity = undefined;
|
|
9604
|
+
}
|
|
9250
9605
|
const physicalNativeIdentityBeforeHandoff = currentNativeIdentity;
|
|
9251
9606
|
const handoff = await maybeAdoptObservedExternalThread({
|
|
9252
9607
|
options,
|
|
@@ -9415,7 +9770,8 @@ async function runSend(options) {
|
|
|
9415
9770
|
terminal: terminalConversation,
|
|
9416
9771
|
transition: handoff.transition
|
|
9417
9772
|
}
|
|
9418
|
-
: undefined
|
|
9773
|
+
: undefined,
|
|
9774
|
+
verifiedEmptyCodexHandoff: verifiedEmptyHandoff?.boundary
|
|
9419
9775
|
});
|
|
9420
9776
|
}
|
|
9421
9777
|
finally {
|
|
@@ -10374,7 +10730,7 @@ async function runTerminalConversationApprove({ options, conversationId, agent,
|
|
|
10374
10730
|
releaseTerminalLock();
|
|
10375
10731
|
}
|
|
10376
10732
|
}
|
|
10377
|
-
async function runTerminalControlSend({ options, conversation, nextConversation, statePath, logPath, executor, message, terminalControl, terminalSendLockHeld = false, terminalStateLockHeld = false, storeWriterLeaseHeld = false, recordMessageAfterSend = false, recordRawAttachmentAfterSend = false, onTerminalPreflightVerified = undefined, allowedPreMaterializationIdentity = undefined, allowedAdditionalIdentities = [], observedHandoff = undefined, continuingTurnResponse = false }) {
|
|
10733
|
+
async function runTerminalControlSend({ options, conversation, nextConversation, statePath, logPath, executor, message, terminalControl, terminalSendLockHeld = false, terminalStateLockHeld = false, storeWriterLeaseHeld = false, recordMessageAfterSend = false, recordRawAttachmentAfterSend = false, onTerminalPreflightVerified = undefined, allowedPreMaterializationIdentity = undefined, allowedAdditionalIdentities = [], observedHandoff = undefined, verifiedEmptyCodexHandoff = undefined, continuingTurnResponse = false }) {
|
|
10378
10734
|
const bridge = terminalBridgeEnabled(conversation);
|
|
10379
10735
|
if (!terminalSendLockHeld) {
|
|
10380
10736
|
const releaseTerminalLock = acquireTerminalBridgeSendLock(storeDirFromOptions(options), terminalControl, { timeoutMs: 30000 });
|
|
@@ -10397,6 +10753,7 @@ async function runTerminalControlSend({ options, conversation, nextConversation,
|
|
|
10397
10753
|
allowedPreMaterializationIdentity,
|
|
10398
10754
|
allowedAdditionalIdentities,
|
|
10399
10755
|
observedHandoff,
|
|
10756
|
+
verifiedEmptyCodexHandoff,
|
|
10400
10757
|
continuingTurnResponse
|
|
10401
10758
|
});
|
|
10402
10759
|
}
|
|
@@ -10437,6 +10794,7 @@ async function runTerminalControlSend({ options, conversation, nextConversation,
|
|
|
10437
10794
|
allowedPreMaterializationIdentity,
|
|
10438
10795
|
allowedAdditionalIdentities,
|
|
10439
10796
|
observedHandoff,
|
|
10797
|
+
verifiedEmptyCodexHandoff,
|
|
10440
10798
|
continuingTurnResponse
|
|
10441
10799
|
});
|
|
10442
10800
|
}
|
|
@@ -10464,6 +10822,7 @@ async function runTerminalControlSend({ options, conversation, nextConversation,
|
|
|
10464
10822
|
allowedPreMaterializationIdentity,
|
|
10465
10823
|
allowedAdditionalIdentities,
|
|
10466
10824
|
observedHandoff,
|
|
10825
|
+
verifiedEmptyCodexHandoff,
|
|
10467
10826
|
continuingTurnResponse
|
|
10468
10827
|
}));
|
|
10469
10828
|
}
|
|
@@ -10645,7 +11004,18 @@ async function runTerminalControlSend({ options, conversation, nextConversation,
|
|
|
10645
11004
|
scrollbackLines: Number(options.scrollbackLines ?? 120),
|
|
10646
11005
|
runtime: preSendRuntime
|
|
10647
11006
|
});
|
|
10648
|
-
|
|
11007
|
+
if (verifiedEmptyCodexHandoff) {
|
|
11008
|
+
if (executor.kind !== "codex" ||
|
|
11009
|
+
status.reachable !== true ||
|
|
11010
|
+
status.approval_state.blocked === true ||
|
|
11011
|
+
!["idle", "unknown"].includes(status.activity_state)) {
|
|
11012
|
+
throw new Error(`Codex verified-empty handoff is not at a safe prompt ` +
|
|
11013
|
+
`(${status.activity_state}: ${status.activity_reason})`);
|
|
11014
|
+
}
|
|
11015
|
+
}
|
|
11016
|
+
else {
|
|
11017
|
+
assertSafeTerminalSend(executor.kind, status);
|
|
11018
|
+
}
|
|
10649
11019
|
if (executor.kind === "codex" && needsPostSendNativeBinding) {
|
|
10650
11020
|
if (pendingManagedNativeBinding || allowedPreMaterializationIdentity) {
|
|
10651
11021
|
const expectedForegroundId = stringValue(sendTakeover?.terminal_agent_expected_session_id);
|
|
@@ -11015,7 +11385,8 @@ async function runTerminalControlSend({ options, conversation, nextConversation,
|
|
|
11015
11385
|
try {
|
|
11016
11386
|
await terminalBridge.send(executor.kind, terminalControl, terminalPayload, {
|
|
11017
11387
|
runtime: preSendRuntime,
|
|
11018
|
-
requireExactComposerBeforeEnter: observedHandoff !== undefined
|
|
11388
|
+
requireExactComposerBeforeEnter: observedHandoff !== undefined ||
|
|
11389
|
+
verifiedEmptyCodexHandoff !== undefined,
|
|
11019
11390
|
beforeText: observedHandoff
|
|
11020
11391
|
? async () => {
|
|
11021
11392
|
await assertObservedHandoffTransportBoundary({
|
|
@@ -11025,7 +11396,15 @@ async function runTerminalControlSend({ options, conversation, nextConversation,
|
|
|
11025
11396
|
requireEmptyComposer: true
|
|
11026
11397
|
});
|
|
11027
11398
|
}
|
|
11028
|
-
:
|
|
11399
|
+
: verifiedEmptyCodexHandoff
|
|
11400
|
+
? async () => {
|
|
11401
|
+
await assertVerifiedEmptyCodexTransportBoundary({
|
|
11402
|
+
options,
|
|
11403
|
+
boundary: verifiedEmptyCodexHandoff,
|
|
11404
|
+
requireEmptyComposer: true
|
|
11405
|
+
});
|
|
11406
|
+
}
|
|
11407
|
+
: undefined,
|
|
11029
11408
|
beforeEnter: observedHandoff
|
|
11030
11409
|
? async () => {
|
|
11031
11410
|
await assertObservedHandoffTransportBoundary({
|
|
@@ -11035,7 +11414,15 @@ async function runTerminalControlSend({ options, conversation, nextConversation,
|
|
|
11035
11414
|
requireEmptyComposer: false
|
|
11036
11415
|
});
|
|
11037
11416
|
}
|
|
11038
|
-
:
|
|
11417
|
+
: verifiedEmptyCodexHandoff
|
|
11418
|
+
? async () => {
|
|
11419
|
+
await assertVerifiedEmptyCodexTransportBoundary({
|
|
11420
|
+
options,
|
|
11421
|
+
boundary: verifiedEmptyCodexHandoff,
|
|
11422
|
+
requireEmptyComposer: false
|
|
11423
|
+
});
|
|
11424
|
+
}
|
|
11425
|
+
: undefined,
|
|
11039
11426
|
async onTransportStage(event) {
|
|
11040
11427
|
const stageAt = cliNow().toISOString();
|
|
11041
11428
|
if (event.stage === "text_injected") {
|
|
@@ -11084,6 +11471,13 @@ async function runTerminalControlSend({ options, conversation, nextConversation,
|
|
|
11084
11471
|
requireEmptyComposer: false
|
|
11085
11472
|
});
|
|
11086
11473
|
}
|
|
11474
|
+
if (event.stage === "text_injected" && verifiedEmptyCodexHandoff) {
|
|
11475
|
+
await assertVerifiedEmptyCodexTransportBoundary({
|
|
11476
|
+
options,
|
|
11477
|
+
boundary: verifiedEmptyCodexHandoff,
|
|
11478
|
+
requireEmptyComposer: false
|
|
11479
|
+
});
|
|
11480
|
+
}
|
|
11087
11481
|
try {
|
|
11088
11482
|
appendEvent(logPath, {
|
|
11089
11483
|
ts: stageAt,
|
|
@@ -12523,6 +12917,29 @@ async function resolveCurrentNativeAgentSessionIdentity({ options, agent, pid, c
|
|
|
12523
12917
|
evidence: "claude_agents_exact_pid"
|
|
12524
12918
|
};
|
|
12525
12919
|
}
|
|
12920
|
+
/**
|
|
12921
|
+
* Preserve the security-significant difference between a successful native
|
|
12922
|
+
* identity probe that found no current Session and a probe that could not be
|
|
12923
|
+
* completed. `undefined` from the Codex provider is authoritative only after
|
|
12924
|
+
* both the process-incarnation and open-rollout inspections succeeded.
|
|
12925
|
+
*/
|
|
12926
|
+
async function observeCurrentNativeAgentSessionIdentity(request) {
|
|
12927
|
+
try {
|
|
12928
|
+
const identity = await resolveCurrentNativeAgentSessionIdentity(request);
|
|
12929
|
+
return identity
|
|
12930
|
+
? { status: "resolved", identity }
|
|
12931
|
+
: {
|
|
12932
|
+
status: "verified_absent",
|
|
12933
|
+
evidence: "native_identity_resolver_verified_absent"
|
|
12934
|
+
};
|
|
12935
|
+
}
|
|
12936
|
+
catch (error) {
|
|
12937
|
+
return {
|
|
12938
|
+
status: "unavailable",
|
|
12939
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
12940
|
+
};
|
|
12941
|
+
}
|
|
12942
|
+
}
|
|
12526
12943
|
function nativeAgentIdentityMatchesTurn(conversation, currentIdentity) {
|
|
12527
12944
|
const takeover = isRecord(conversation.native_session_takeover)
|
|
12528
12945
|
? conversation.native_session_takeover
|
|
@@ -18232,14 +18649,7 @@ function claudeComposerVisible(screen) {
|
|
|
18232
18649
|
.some((line) => /^\s*❯(?:\s|$)/u.test(line));
|
|
18233
18650
|
}
|
|
18234
18651
|
function claudeComposerEmpty(screen) {
|
|
18235
|
-
|
|
18236
|
-
while (lines.length > 0 && lines.at(-1)?.trim() === "") {
|
|
18237
|
-
lines.pop();
|
|
18238
|
-
}
|
|
18239
|
-
const composers = lines
|
|
18240
|
-
.slice(-8)
|
|
18241
|
-
.filter((line) => /^\s*❯(?:\s|$)/u.test(line));
|
|
18242
|
-
return composers.length === 1 && /^\s*❯\s*$/u.test(composers[0]);
|
|
18652
|
+
return isExactClaudeIdleComposer(String(screen ?? ""));
|
|
18243
18653
|
}
|
|
18244
18654
|
function assertManualLifecycleProcessIncarnation(transition, live) {
|
|
18245
18655
|
if (live.processUuid !== transition.before_process_uuid ||
|