@scotthuang/agent-knock-knock 0.12.8 → 0.12.10
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 +18 -0
- package/README.md +4 -2
- package/dist/src/claude-terminal-agent-adapter.js +20 -5
- package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
- package/dist/src/cli-core.js +324 -302
- package/dist/src/cli-core.js.map +1 -1
- package/dist/src/codex-terminal-agent-adapter.d.ts +2 -1
- package/dist/src/codex-terminal-agent-adapter.js +134 -38
- package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
- package/dist/src/terminal-agent-adapter.d.ts +21 -0
- package/dist/src/terminal-agent-adapter.js +20 -0
- package/dist/src/terminal-agent-adapter.js.map +1 -1
- package/dist/src/terminal-agent-bridge.js +75 -9
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +4 -2
package/dist/src/cli-core.js
CHANGED
|
@@ -5853,7 +5853,7 @@ function managedListApprovalState(conversation) {
|
|
|
5853
5853
|
}
|
|
5854
5854
|
function listActionContracts() {
|
|
5855
5855
|
return {
|
|
5856
|
-
version:
|
|
5856
|
+
version: 16,
|
|
5857
5857
|
instructions: [
|
|
5858
5858
|
"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.",
|
|
5859
5859
|
"The session_exact scope uses session_id only when it is prefilled by the listed send action. A rollout-backed managed Codex pane instead uses the terminal_follow_current scope with its exact selector plus expected_terminal_token because even one materialized rollout does not prove the current TUI foreground thread. A turn id is never an ordinary send target.",
|
|
@@ -5864,6 +5864,7 @@ function listActionContracts() {
|
|
|
5864
5864
|
"List resumable threads before resume; use only a complete native_thread_id and the action returned for that candidate.",
|
|
5865
5865
|
"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.",
|
|
5866
5866
|
"Use respond only for an in-flight turn that is explicitly waiting for OpenClaw.",
|
|
5867
|
+
"Approval fingerprint v2 binds stable terminal, process, and action fields plus an unredacted exact prompt-region digest. Whole-screen digests and scrollback excerpts are diagnostic only and never authorize approval. A keys-mode prompt without exact prompt-region evidence is not approvable and exposes no approve action.",
|
|
5867
5868
|
"Managed controls target turn_id. A raw terminal may be controlled only through its own list-prefilled conversation_id action. When a Codex managed prompt has no usable AKK Turn owner, list may expose one manual terminal-scoped approve carrying expected_terminal_token; copy the complete action after explicit human confirmation. It does not mutate the Turn or Session binding, has no durable dispatch receipt, and is never eligible for automatic approval. If its result is interrupted, refresh status and inspect the live prompt instead of retrying blindly. Never construct, guess, or reuse either selector or token.",
|
|
5868
5869
|
"Start with the action's prefilled arguments, supply every missing_required field, and consult the top-level action's optional fields only when needed.",
|
|
5869
5870
|
"Authoritative full IDs are prefilled; short_ref is for display and human input.",
|
|
@@ -6023,6 +6024,9 @@ function listActionContracts() {
|
|
|
6023
6024
|
required: ["turn_id", "expected_approval_fingerprint"],
|
|
6024
6025
|
optional: ["expected_terminal_token"],
|
|
6025
6026
|
terminal_scoped_scope: "Only the latest list-prefilled Codex conversation_id/token action may approve a managed current prompt without a usable Turn owner. It remains human-confirmed, revalidates exact terminal/process/Session/dispatch/prompt state before keys, never mutates managed state, has no durable dispatch receipt, cannot be auto-approved, and must not be blindly retried after an interrupted result.",
|
|
6027
|
+
fingerprint_contract: "v2_prompt_region",
|
|
6028
|
+
fingerprint_authority: "Stable terminal, process, and action fields plus the unredacted exact prompt-region digest; whole-screen digests and scrollback excerpts are diagnostic only.",
|
|
6029
|
+
missing_prompt_region_evidence: "not_approvable",
|
|
6026
6030
|
requires_explicit_user_confirmation: true,
|
|
6027
6031
|
requires_fresh_status: true
|
|
6028
6032
|
},
|
|
@@ -12427,6 +12431,10 @@ function terminalBridgeApprovalFingerprint({ terminalControl, terminalStatus })
|
|
|
12427
12431
|
if (adapterFingerprint) {
|
|
12428
12432
|
return adapterFingerprint;
|
|
12429
12433
|
}
|
|
12434
|
+
if (approval.approvable === true &&
|
|
12435
|
+
(stringValue(approval.decision_mode) ?? "keys") === "keys") {
|
|
12436
|
+
return undefined;
|
|
12437
|
+
}
|
|
12430
12438
|
const endpoint = terminalEndpointFromControlRef(terminalControl);
|
|
12431
12439
|
const processAnchorPid = Number(endpoint.processAnchorPid);
|
|
12432
12440
|
if (!Number.isSafeInteger(processAnchorPid) || processAnchorPid <= 1) {
|
|
@@ -13808,331 +13816,337 @@ async function runApprove(options) {
|
|
|
13808
13816
|
release();
|
|
13809
13817
|
}
|
|
13810
13818
|
};
|
|
13811
|
-
releaseStateLock = acquireFileLock(`${statePath}.lock`);
|
|
13812
13819
|
const writerStoreDir = pathsForConversationDir(path.dirname(statePath)).storeDir;
|
|
13813
|
-
|
|
13814
|
-
|
|
13815
|
-
|
|
13816
|
-
|
|
13817
|
-
|
|
13818
|
-
|
|
13819
|
-
|
|
13820
|
-
|
|
13821
|
-
|
|
13822
|
-
|
|
13823
|
-
|
|
13824
|
-
|
|
13825
|
-
|
|
13826
|
-
|
|
13827
|
-
|
|
13828
|
-
|
|
13829
|
-
|
|
13830
|
-
|
|
13831
|
-
|
|
13832
|
-
|
|
13833
|
-
|
|
13834
|
-
|
|
13835
|
-
|
|
13836
|
-
|
|
13837
|
-
|
|
13838
|
-
|
|
13839
|
-
|
|
13840
|
-
|
|
13841
|
-
|
|
13842
|
-
|
|
13843
|
-
|
|
13844
|
-
|
|
13845
|
-
|
|
13846
|
-
|
|
13847
|
-
|
|
13848
|
-
|
|
13820
|
+
const runApprovalWithStateLock = async () => {
|
|
13821
|
+
let approval;
|
|
13822
|
+
let lockedConversation = conversation;
|
|
13823
|
+
const currentConversation = loadState(statePath);
|
|
13824
|
+
const currentTakeover = isRecord(currentConversation.native_session_takeover)
|
|
13825
|
+
? currentConversation.native_session_takeover
|
|
13826
|
+
: undefined;
|
|
13827
|
+
const currentControl = terminalControlFromTakeover(currentTakeover);
|
|
13828
|
+
const currentApproval = isRecord(currentTakeover?.terminal_bridge_approval)
|
|
13829
|
+
? currentTakeover.terminal_bridge_approval
|
|
13830
|
+
: undefined;
|
|
13831
|
+
if (currentConversation.status !== conversation.status ||
|
|
13832
|
+
currentTakeover?.terminal_bridge_message_id !== nativeTakeover?.terminal_bridge_message_id ||
|
|
13833
|
+
!currentControl ||
|
|
13834
|
+
!terminalControlsShareIncarnation(currentControl, terminalControl) ||
|
|
13835
|
+
(claudeScreenApproval &&
|
|
13836
|
+
currentApproval?.fingerprint !== monitoredApproval?.fingerprint)) {
|
|
13837
|
+
throw new Error("approval state changed while waiting for terminal control; refresh status and retry");
|
|
13838
|
+
}
|
|
13839
|
+
assertManagedTerminalDispatchOwner({
|
|
13840
|
+
storeDir: writerStoreDir,
|
|
13841
|
+
conversation: currentConversation,
|
|
13842
|
+
terminalControl: currentControl,
|
|
13843
|
+
action: "approve"
|
|
13844
|
+
});
|
|
13845
|
+
lockedConversation = currentConversation;
|
|
13846
|
+
const currentRuntimeIdentity = terminalRuntimeIdentityForConversation(currentConversation, currentControl);
|
|
13847
|
+
approval = await createTerminalAgentBridge(options).approve(executor.kind, currentControl, {
|
|
13848
|
+
expectedFingerprint,
|
|
13849
|
+
scrollbackLines: Number(options.scrollbackLines ?? 120),
|
|
13850
|
+
runtime: currentRuntimeIdentity,
|
|
13851
|
+
managedRequest: terminalDurableRequestForConversation(currentConversation, currentControl),
|
|
13852
|
+
requiredDecisionMode: autoApproved && executor.kind === "claude" ? "keys" : undefined,
|
|
13853
|
+
authorize: autoApproved
|
|
13854
|
+
? ({ agent, terminalControl: currentTerminalControl, inspection, fingerprint }) => {
|
|
13855
|
+
if (!autoApprovalPolicy) {
|
|
13856
|
+
return {
|
|
13857
|
+
approved: false,
|
|
13858
|
+
reason: "automatic approval requires an executor-side policy"
|
|
13859
|
+
};
|
|
13860
|
+
}
|
|
13861
|
+
const candidate = policyCandidateForInspection({
|
|
13862
|
+
agent,
|
|
13863
|
+
currentTerminalControl,
|
|
13864
|
+
inspection,
|
|
13865
|
+
fingerprint
|
|
13866
|
+
});
|
|
13867
|
+
executorPolicyDecision = evaluateApprovalPolicy({
|
|
13868
|
+
policy: autoApprovalPolicy,
|
|
13869
|
+
candidate
|
|
13870
|
+
});
|
|
13871
|
+
if (executorPolicyDecision.action !== "approve") {
|
|
13872
|
+
return {
|
|
13873
|
+
approved: false,
|
|
13874
|
+
reason: `executor-side auto-approval policy rejected the current request: ${executorPolicyDecision.reason}`
|
|
13875
|
+
};
|
|
13876
|
+
}
|
|
13877
|
+
if (policyRuleId && executorPolicyDecision.ruleId !== policyRuleId) {
|
|
13878
|
+
return {
|
|
13879
|
+
approved: false,
|
|
13880
|
+
reason: "executor-side auto-approval rule changed before execution"
|
|
13881
|
+
};
|
|
13882
|
+
}
|
|
13883
|
+
if (policyFingerprint &&
|
|
13884
|
+
executorPolicyDecision.policyFingerprint !== policyFingerprint) {
|
|
13885
|
+
return {
|
|
13886
|
+
approved: false,
|
|
13887
|
+
reason: "executor-side auto-approval policy changed before execution"
|
|
13888
|
+
};
|
|
13889
|
+
}
|
|
13890
|
+
return { approved: true };
|
|
13891
|
+
}
|
|
13892
|
+
: undefined,
|
|
13893
|
+
beforeKeyDispatch: claudeScreenApproval
|
|
13894
|
+
? ({ fingerprint, terminalControl: dispatchControl, inspection, keys }) => {
|
|
13895
|
+
if (autoApproved) {
|
|
13849
13896
|
if (!autoApprovalPolicy) {
|
|
13850
|
-
|
|
13851
|
-
approved: false,
|
|
13852
|
-
reason: "automatic approval requires an executor-side policy"
|
|
13853
|
-
};
|
|
13897
|
+
throw new Error("automatic approval requires an executor-side policy before dispatch");
|
|
13854
13898
|
}
|
|
13855
|
-
const
|
|
13856
|
-
agent,
|
|
13857
|
-
currentTerminalControl,
|
|
13858
|
-
inspection,
|
|
13859
|
-
fingerprint
|
|
13860
|
-
});
|
|
13861
|
-
executorPolicyDecision = evaluateApprovalPolicy({
|
|
13899
|
+
const freshPolicyDecision = evaluateApprovalPolicy({
|
|
13862
13900
|
policy: autoApprovalPolicy,
|
|
13863
|
-
candidate
|
|
13901
|
+
candidate: policyCandidateForInspection({
|
|
13902
|
+
agent: executor.kind,
|
|
13903
|
+
currentTerminalControl: dispatchControl,
|
|
13904
|
+
inspection,
|
|
13905
|
+
fingerprint
|
|
13906
|
+
})
|
|
13864
13907
|
});
|
|
13865
|
-
if (
|
|
13866
|
-
|
|
13867
|
-
approved: false,
|
|
13868
|
-
reason: `executor-side auto-approval policy rejected the current request: ${executorPolicyDecision.reason}`
|
|
13869
|
-
};
|
|
13908
|
+
if (freshPolicyDecision.action !== "approve") {
|
|
13909
|
+
throw new Error(`executor-side auto-approval policy rejected the recaptured request: ${freshPolicyDecision.reason}`);
|
|
13870
13910
|
}
|
|
13871
|
-
if (
|
|
13872
|
-
|
|
13873
|
-
|
|
13874
|
-
|
|
13875
|
-
|
|
13911
|
+
if (executorPolicyDecision?.ruleId &&
|
|
13912
|
+
freshPolicyDecision.ruleId !== executorPolicyDecision.ruleId) {
|
|
13913
|
+
throw new Error("executor-side auto-approval rule changed after recapture");
|
|
13914
|
+
}
|
|
13915
|
+
if (policyRuleId && freshPolicyDecision.ruleId !== policyRuleId) {
|
|
13916
|
+
throw new Error("executor-side auto-approval rule changed before dispatch");
|
|
13876
13917
|
}
|
|
13877
13918
|
if (policyFingerprint &&
|
|
13878
|
-
|
|
13879
|
-
|
|
13880
|
-
approved: false,
|
|
13881
|
-
reason: "executor-side auto-approval policy changed before execution"
|
|
13882
|
-
};
|
|
13919
|
+
freshPolicyDecision.policyFingerprint !== policyFingerprint) {
|
|
13920
|
+
throw new Error("executor-side auto-approval policy changed before dispatch");
|
|
13883
13921
|
}
|
|
13884
|
-
|
|
13922
|
+
executorPolicyDecision = freshPolicyDecision;
|
|
13885
13923
|
}
|
|
13886
|
-
|
|
13887
|
-
|
|
13888
|
-
? ({ fingerprint, terminalControl: dispatchControl, inspection, keys }) => {
|
|
13889
|
-
if (autoApproved) {
|
|
13890
|
-
if (!autoApprovalPolicy) {
|
|
13891
|
-
throw new Error("automatic approval requires an executor-side policy before dispatch");
|
|
13892
|
-
}
|
|
13893
|
-
const freshPolicyDecision = evaluateApprovalPolicy({
|
|
13894
|
-
policy: autoApprovalPolicy,
|
|
13895
|
-
candidate: policyCandidateForInspection({
|
|
13896
|
-
agent: executor.kind,
|
|
13897
|
-
currentTerminalControl: dispatchControl,
|
|
13898
|
-
inspection,
|
|
13899
|
-
fingerprint
|
|
13900
|
-
})
|
|
13901
|
-
});
|
|
13902
|
-
if (freshPolicyDecision.action !== "approve") {
|
|
13903
|
-
throw new Error(`executor-side auto-approval policy rejected the recaptured request: ${freshPolicyDecision.reason}`);
|
|
13904
|
-
}
|
|
13905
|
-
if (executorPolicyDecision?.ruleId &&
|
|
13906
|
-
freshPolicyDecision.ruleId !== executorPolicyDecision.ruleId) {
|
|
13907
|
-
throw new Error("executor-side auto-approval rule changed after recapture");
|
|
13908
|
-
}
|
|
13909
|
-
if (policyRuleId && freshPolicyDecision.ruleId !== policyRuleId) {
|
|
13910
|
-
throw new Error("executor-side auto-approval rule changed before dispatch");
|
|
13911
|
-
}
|
|
13912
|
-
if (policyFingerprint &&
|
|
13913
|
-
freshPolicyDecision.policyFingerprint !== policyFingerprint) {
|
|
13914
|
-
throw new Error("executor-side auto-approval policy changed before dispatch");
|
|
13915
|
-
}
|
|
13916
|
-
executorPolicyDecision = freshPolicyDecision;
|
|
13917
|
-
}
|
|
13918
|
-
if (approvalDispatchReserved) {
|
|
13919
|
-
throw new Error("Claude approval dispatch was already reserved");
|
|
13920
|
-
}
|
|
13921
|
-
approvalDispatchReserved = true;
|
|
13922
|
-
if (!releaseStateLock) {
|
|
13923
|
-
throw new Error("approval state lock was released before terminal dispatch");
|
|
13924
|
-
}
|
|
13925
|
-
const latestConversation = loadState(statePath);
|
|
13926
|
-
const latestTakeover = isRecord(latestConversation.native_session_takeover)
|
|
13927
|
-
? latestConversation.native_session_takeover
|
|
13928
|
-
: undefined;
|
|
13929
|
-
const latestControl = terminalControlFromTakeover(latestTakeover);
|
|
13930
|
-
const latestApproval = isRecord(latestTakeover?.terminal_bridge_approval)
|
|
13931
|
-
? latestTakeover.terminal_bridge_approval
|
|
13932
|
-
: undefined;
|
|
13933
|
-
const latestNotifiedAt = validTimestampMs(latestApproval?.notified_at);
|
|
13934
|
-
const latestApprovalState = isRecord(latestApproval?.approval_state)
|
|
13935
|
-
? latestApproval.approval_state
|
|
13936
|
-
: undefined;
|
|
13937
|
-
const latestPolicyEvidence = isRecord(latestApprovalState?.policy_evidence)
|
|
13938
|
-
? latestApprovalState.policy_evidence
|
|
13939
|
-
: undefined;
|
|
13940
|
-
const recapturedPolicyEvidence = inspection.approval.approvable
|
|
13941
|
-
? inspection.approval.policyEvidence
|
|
13942
|
-
: undefined;
|
|
13943
|
-
const latestDispatch = isRecord(latestTakeover?.terminal_bridge_approval_dispatch)
|
|
13944
|
-
? latestTakeover.terminal_bridge_approval_dispatch
|
|
13945
|
-
: undefined;
|
|
13946
|
-
if (!latestTakeover ||
|
|
13947
|
-
latestConversation.status !== "waiting_for_openclaw" ||
|
|
13948
|
-
latestTakeover.terminal_bridge_message_id !==
|
|
13949
|
-
nativeTakeover?.terminal_bridge_message_id ||
|
|
13950
|
-
latestApproval?.fingerprint !== fingerprint ||
|
|
13951
|
-
latestNotifiedAt === undefined ||
|
|
13952
|
-
cliNowMs() - latestNotifiedAt > CLAUDE_SCREEN_APPROVAL_TTL_MS ||
|
|
13953
|
-
expectedFingerprint !== fingerprint ||
|
|
13954
|
-
!terminalControlsShareIncarnation(latestControl, dispatchControl) ||
|
|
13955
|
-
(autoApproved &&
|
|
13956
|
-
(latestPolicyEvidence?.source !== "claude_transcript" ||
|
|
13957
|
-
latestPolicyEvidence.evidence_fingerprint !==
|
|
13958
|
-
recapturedPolicyEvidence?.evidenceFingerprint))) {
|
|
13959
|
-
throw new Error("approval state changed before terminal dispatch; refresh status and retry");
|
|
13960
|
-
}
|
|
13961
|
-
if (latestDispatch?.state === "reserved" &&
|
|
13962
|
-
latestDispatch.terminal_bridge_message_id ===
|
|
13963
|
-
latestTakeover.terminal_bridge_message_id) {
|
|
13964
|
-
throw new Error("a previous Claude approval dispatch has an uncertain outcome; inspect and resolve the terminal manually");
|
|
13965
|
-
}
|
|
13966
|
-
const reservedAt = cliNow().toISOString();
|
|
13967
|
-
const reservedConversation = {
|
|
13968
|
-
...latestConversation,
|
|
13969
|
-
native_session_takeover: {
|
|
13970
|
-
...latestTakeover,
|
|
13971
|
-
terminal_bridge_approval_dispatch: {
|
|
13972
|
-
state: "reserved",
|
|
13973
|
-
attempt_id: randomUUID(),
|
|
13974
|
-
fingerprint,
|
|
13975
|
-
keys,
|
|
13976
|
-
terminal_target: dispatchControl.target,
|
|
13977
|
-
terminal_bridge_message_id: latestTakeover.terminal_bridge_message_id,
|
|
13978
|
-
reserved_at: reservedAt
|
|
13979
|
-
}
|
|
13980
|
-
},
|
|
13981
|
-
updated_at: reservedAt
|
|
13982
|
-
};
|
|
13983
|
-
saveState(statePath, reservedConversation);
|
|
13984
|
-
lockedConversation = reservedConversation;
|
|
13924
|
+
if (approvalDispatchReserved) {
|
|
13925
|
+
throw new Error("Claude approval dispatch was already reserved");
|
|
13985
13926
|
}
|
|
13986
|
-
|
|
13987
|
-
|
|
13988
|
-
|
|
13989
|
-
|
|
13990
|
-
|
|
13991
|
-
|
|
13992
|
-
|
|
13993
|
-
|
|
13994
|
-
|
|
13995
|
-
|
|
13996
|
-
|
|
13997
|
-
|
|
13998
|
-
|
|
13999
|
-
|
|
14000
|
-
|
|
14001
|
-
|
|
14002
|
-
|
|
14003
|
-
|
|
14004
|
-
|
|
14005
|
-
|
|
14006
|
-
|
|
13927
|
+
approvalDispatchReserved = true;
|
|
13928
|
+
if (!releaseStateLock) {
|
|
13929
|
+
throw new Error("approval state lock was released before terminal dispatch");
|
|
13930
|
+
}
|
|
13931
|
+
const latestConversation = loadState(statePath);
|
|
13932
|
+
const latestTakeover = isRecord(latestConversation.native_session_takeover)
|
|
13933
|
+
? latestConversation.native_session_takeover
|
|
13934
|
+
: undefined;
|
|
13935
|
+
const latestControl = terminalControlFromTakeover(latestTakeover);
|
|
13936
|
+
const latestApproval = isRecord(latestTakeover?.terminal_bridge_approval)
|
|
13937
|
+
? latestTakeover.terminal_bridge_approval
|
|
13938
|
+
: undefined;
|
|
13939
|
+
const latestNotifiedAt = validTimestampMs(latestApproval?.notified_at);
|
|
13940
|
+
const latestApprovalState = isRecord(latestApproval?.approval_state)
|
|
13941
|
+
? latestApproval.approval_state
|
|
13942
|
+
: undefined;
|
|
13943
|
+
const latestPolicyEvidence = isRecord(latestApprovalState?.policy_evidence)
|
|
13944
|
+
? latestApprovalState.policy_evidence
|
|
13945
|
+
: undefined;
|
|
13946
|
+
const recapturedPolicyEvidence = inspection.approval.approvable
|
|
13947
|
+
? inspection.approval.policyEvidence
|
|
13948
|
+
: undefined;
|
|
13949
|
+
const latestDispatch = isRecord(latestTakeover?.terminal_bridge_approval_dispatch)
|
|
13950
|
+
? latestTakeover.terminal_bridge_approval_dispatch
|
|
13951
|
+
: undefined;
|
|
13952
|
+
if (!latestTakeover ||
|
|
13953
|
+
latestConversation.status !== "waiting_for_openclaw" ||
|
|
13954
|
+
latestTakeover.terminal_bridge_message_id !==
|
|
13955
|
+
nativeTakeover?.terminal_bridge_message_id ||
|
|
13956
|
+
latestApproval?.fingerprint !== fingerprint ||
|
|
13957
|
+
latestNotifiedAt === undefined ||
|
|
13958
|
+
cliNowMs() - latestNotifiedAt > CLAUDE_SCREEN_APPROVAL_TTL_MS ||
|
|
13959
|
+
expectedFingerprint !== fingerprint ||
|
|
13960
|
+
!terminalControlsShareIncarnation(latestControl, dispatchControl) ||
|
|
13961
|
+
(autoApproved &&
|
|
13962
|
+
(latestPolicyEvidence?.source !== "claude_transcript" ||
|
|
13963
|
+
latestPolicyEvidence.evidence_fingerprint !==
|
|
13964
|
+
recapturedPolicyEvidence?.evidenceFingerprint))) {
|
|
13965
|
+
throw new Error("approval state changed before terminal dispatch; refresh status and retry");
|
|
13966
|
+
}
|
|
13967
|
+
if (latestDispatch?.state === "reserved" &&
|
|
13968
|
+
latestDispatch.terminal_bridge_message_id ===
|
|
13969
|
+
latestTakeover.terminal_bridge_message_id) {
|
|
13970
|
+
throw new Error("a previous Claude approval dispatch has an uncertain outcome; inspect and resolve the terminal manually");
|
|
13971
|
+
}
|
|
13972
|
+
const reservedAt = cliNow().toISOString();
|
|
13973
|
+
const reservedConversation = {
|
|
13974
|
+
...latestConversation,
|
|
13975
|
+
native_session_takeover: {
|
|
13976
|
+
...latestTakeover,
|
|
13977
|
+
terminal_bridge_approval_dispatch: {
|
|
13978
|
+
state: "reserved",
|
|
13979
|
+
attempt_id: randomUUID(),
|
|
13980
|
+
fingerprint,
|
|
13981
|
+
keys,
|
|
13982
|
+
terminal_target: dispatchControl.target,
|
|
13983
|
+
terminal_bridge_message_id: latestTakeover.terminal_bridge_message_id,
|
|
13984
|
+
reserved_at: reservedAt
|
|
13985
|
+
}
|
|
13986
|
+
},
|
|
13987
|
+
updated_at: reservedAt
|
|
13988
|
+
};
|
|
13989
|
+
saveState(statePath, reservedConversation);
|
|
13990
|
+
lockedConversation = reservedConversation;
|
|
14007
13991
|
}
|
|
14008
|
-
|
|
14009
|
-
|
|
14010
|
-
|
|
14011
|
-
|
|
14012
|
-
|
|
14013
|
-
|
|
14014
|
-
|
|
14015
|
-
actual_approval_fingerprint: actualFingerprint,
|
|
14016
|
-
screen_excerpt: approval.screenExcerpt
|
|
14017
|
-
});
|
|
14018
|
-
return;
|
|
14019
|
-
}
|
|
14020
|
-
appendEvent(logPath, {
|
|
14021
|
-
ts: cliNow().toISOString(),
|
|
14022
|
-
conversation_id: conversation.conversation_id,
|
|
14023
|
-
event: "terminal_approval_send",
|
|
14024
|
-
terminal_control: terminalControl,
|
|
14025
|
-
key: approval.key,
|
|
14026
|
-
keys: approval.keys,
|
|
14027
|
-
label: approval.label,
|
|
14028
|
-
decision_mode: approval.decisionMode,
|
|
14029
|
-
request_id: approval.requestId,
|
|
14030
|
-
approval_fingerprint: actualFingerprint,
|
|
14031
|
-
auto_approved: autoApproved,
|
|
14032
|
-
policy_rule_id: effectivePolicyRuleId,
|
|
14033
|
-
policy_fingerprint: effectivePolicyFingerprint
|
|
14034
|
-
});
|
|
13992
|
+
: undefined
|
|
13993
|
+
});
|
|
13994
|
+
const actualFingerprint = approval.fingerprint;
|
|
13995
|
+
const effectivePolicyRuleId = executorPolicyDecision?.ruleId ?? policyRuleId;
|
|
13996
|
+
const effectivePolicyFingerprint = executorPolicyDecision?.policyFingerprint ?? policyFingerprint;
|
|
13997
|
+
if (!approval.approved) {
|
|
13998
|
+
releaseApprovalStateLock();
|
|
14035
13999
|
if (autoApproved) {
|
|
14036
14000
|
appendEvent(logPath, {
|
|
14037
14001
|
ts: cliNow().toISOString(),
|
|
14038
14002
|
conversation_id: conversation.conversation_id,
|
|
14039
14003
|
event: "terminal_auto_approval_decision",
|
|
14040
|
-
action: "
|
|
14004
|
+
action: "rejected",
|
|
14005
|
+
reason: approval.reason,
|
|
14041
14006
|
terminal_control: terminalControl,
|
|
14042
|
-
|
|
14007
|
+
expected_fingerprint: expectedFingerprint,
|
|
14008
|
+
actual_fingerprint: actualFingerprint,
|
|
14043
14009
|
policy_rule_id: effectivePolicyRuleId,
|
|
14044
14010
|
policy_fingerprint: effectivePolicyFingerprint
|
|
14045
14011
|
});
|
|
14046
14012
|
}
|
|
14047
|
-
runtimeLog("info", "terminal_approval_send", {
|
|
14048
|
-
conversation_id: conversation.conversation_id,
|
|
14049
|
-
terminal_target: terminalControl.target,
|
|
14050
|
-
key: approval.key,
|
|
14051
|
-
keys: approval.keys,
|
|
14052
|
-
label: approval.label,
|
|
14053
|
-
decision_mode: approval.decisionMode,
|
|
14054
|
-
request_id: approval.requestId,
|
|
14055
|
-
approval_fingerprint: actualFingerprint,
|
|
14056
|
-
auto_approved: autoApproved,
|
|
14057
|
-
policy_rule_id: effectivePolicyRuleId,
|
|
14058
|
-
policy_fingerprint: effectivePolicyFingerprint
|
|
14059
|
-
});
|
|
14060
|
-
const nativeTakeoverForUpdate = isRecord(lockedConversation.native_session_takeover)
|
|
14061
|
-
? { ...lockedConversation.native_session_takeover }
|
|
14062
|
-
: {};
|
|
14063
|
-
const resolvedApproval = isRecord(nativeTakeoverForUpdate.terminal_bridge_approval)
|
|
14064
|
-
? nativeTakeoverForUpdate.terminal_bridge_approval
|
|
14065
|
-
: undefined;
|
|
14066
|
-
const resolvedApprovalScreenDigest = stringValue(resolvedApproval?.screen_digest);
|
|
14067
|
-
const resolvedApprovalState = isRecord(resolvedApproval?.approval_state)
|
|
14068
|
-
? resolvedApproval.approval_state
|
|
14069
|
-
: undefined;
|
|
14070
|
-
const resolvedTranscriptIdentity = claudeTranscriptApprovalIdentity(resolvedApprovalState);
|
|
14071
|
-
const approvalResolvedAt = cliNow().toISOString();
|
|
14072
|
-
const agentTimeoutMinutes = Number(options.agentTimeoutMinutes ??
|
|
14073
|
-
nativeTakeoverForUpdate.terminal_bridge_inactivity_timeout_minutes ??
|
|
14074
|
-
DEFAULT_AGENT_TIMEOUT_MINUTES);
|
|
14075
|
-
const agentHardTimeoutMinutes = positiveMinutes(options.agentHardTimeoutMinutes ??
|
|
14076
|
-
nativeTakeoverForUpdate.terminal_bridge_hard_timeout_minutes ??
|
|
14077
|
-
DEFAULT_AGENT_HARD_TIMEOUT_MINUTES, "--agent-hard-timeout-minutes");
|
|
14078
|
-
const nextNativeTakeover = {
|
|
14079
|
-
...nativeTakeoverForUpdate,
|
|
14080
|
-
terminal_bridge_approval: undefined,
|
|
14081
|
-
terminal_bridge_approval_dispatch: undefined,
|
|
14082
|
-
terminal_bridge_approval_resolved_at: approvalResolvedAt,
|
|
14083
|
-
terminal_bridge_last_approval_fingerprint: actualFingerprint,
|
|
14084
|
-
terminal_bridge_last_approval_screen_digest: resolvedApprovalScreenDigest,
|
|
14085
|
-
terminal_bridge_last_approval_request_id: resolvedTranscriptIdentity?.requestId,
|
|
14086
|
-
terminal_bridge_last_approval_evidence_fingerprint: resolvedTranscriptIdentity?.evidenceFingerprint,
|
|
14087
|
-
terminal_bridge_last_approval_prompt_cleared_at: undefined,
|
|
14088
|
-
terminal_bridge_last_approval_at: approvalResolvedAt,
|
|
14089
|
-
terminal_bridge_last_approval_message_id: nativeTakeoverForUpdate.terminal_bridge_message_id,
|
|
14090
|
-
terminal_bridge_monitor_lock_version: TERMINAL_BRIDGE_MONITOR_LOCK_VERSION,
|
|
14091
|
-
terminal_bridge_monitor_started_at: approvalResolvedAt,
|
|
14092
|
-
terminal_bridge_last_activity_at: approvalResolvedAt,
|
|
14093
|
-
terminal_bridge_last_activity_reason: "approval resolved",
|
|
14094
|
-
terminal_bridge_inactivity_timeout_minutes: agentTimeoutMinutes,
|
|
14095
|
-
terminal_bridge_hard_timeout_minutes: agentHardTimeoutMinutes,
|
|
14096
|
-
terminal_bridge_inactivity_deadline_at: deadlineAt(approvalResolvedAt, agentTimeoutMinutes),
|
|
14097
|
-
terminal_bridge_hard_deadline_at: deadlineAt(stringValue(nativeTakeoverForUpdate.terminal_bridge_started_at) ?? approvalResolvedAt, agentHardTimeoutMinutes)
|
|
14098
|
-
};
|
|
14099
|
-
delete nextNativeTakeover.terminal_bridge_approval;
|
|
14100
|
-
delete nextNativeTakeover.terminal_bridge_approval_dispatch;
|
|
14101
|
-
delete nextNativeTakeover.terminal_bridge_last_approval_prompt_cleared_at;
|
|
14102
|
-
const nextConversation = {
|
|
14103
|
-
...lockedConversation,
|
|
14104
|
-
status: terminalBridgeEnabled(lockedConversation)
|
|
14105
|
-
? "waiting_for_agent"
|
|
14106
|
-
: lockedConversation.status,
|
|
14107
|
-
native_session_takeover: nextNativeTakeover,
|
|
14108
|
-
updated_at: approvalResolvedAt
|
|
14109
|
-
};
|
|
14110
|
-
saveState(statePath, nextConversation);
|
|
14111
|
-
releaseApprovalStateLock();
|
|
14112
|
-
releaseApprovalTerminalLock();
|
|
14113
|
-
const bridgeMonitor = ensureTerminalBridgeMonitorAfterApproval({
|
|
14114
|
-
conversation: nextConversation,
|
|
14115
|
-
statePath,
|
|
14116
|
-
logPath,
|
|
14117
|
-
terminalControl,
|
|
14118
|
-
options
|
|
14119
|
-
});
|
|
14120
14013
|
printJson({
|
|
14121
|
-
conversation
|
|
14122
|
-
approved:
|
|
14014
|
+
conversation,
|
|
14015
|
+
approved: false,
|
|
14016
|
+
blocked: approval.blocked,
|
|
14017
|
+
reason: approval.reason,
|
|
14018
|
+
terminal_control: terminalControl,
|
|
14019
|
+
expected_approval_fingerprint: expectedFingerprint,
|
|
14020
|
+
actual_approval_fingerprint: actualFingerprint,
|
|
14021
|
+
screen_excerpt: approval.screenExcerpt
|
|
14022
|
+
});
|
|
14023
|
+
return;
|
|
14024
|
+
}
|
|
14025
|
+
appendEvent(logPath, {
|
|
14026
|
+
ts: cliNow().toISOString(),
|
|
14027
|
+
conversation_id: conversation.conversation_id,
|
|
14028
|
+
event: "terminal_approval_send",
|
|
14029
|
+
terminal_control: terminalControl,
|
|
14030
|
+
key: approval.key,
|
|
14031
|
+
keys: approval.keys,
|
|
14032
|
+
label: approval.label,
|
|
14033
|
+
decision_mode: approval.decisionMode,
|
|
14034
|
+
request_id: approval.requestId,
|
|
14035
|
+
approval_fingerprint: actualFingerprint,
|
|
14036
|
+
auto_approved: autoApproved,
|
|
14037
|
+
policy_rule_id: effectivePolicyRuleId,
|
|
14038
|
+
policy_fingerprint: effectivePolicyFingerprint
|
|
14039
|
+
});
|
|
14040
|
+
if (autoApproved) {
|
|
14041
|
+
appendEvent(logPath, {
|
|
14042
|
+
ts: cliNow().toISOString(),
|
|
14043
|
+
conversation_id: conversation.conversation_id,
|
|
14044
|
+
event: "terminal_auto_approval_decision",
|
|
14045
|
+
action: "approved",
|
|
14123
14046
|
terminal_control: terminalControl,
|
|
14124
|
-
key: approval.key,
|
|
14125
|
-
keys: approval.keys,
|
|
14126
|
-
label: approval.label,
|
|
14127
|
-
decision_mode: approval.decisionMode,
|
|
14128
|
-
request_id: approval.requestId,
|
|
14129
14047
|
approval_fingerprint: actualFingerprint,
|
|
14130
|
-
auto_approved: autoApproved,
|
|
14131
14048
|
policy_rule_id: effectivePolicyRuleId,
|
|
14132
|
-
policy_fingerprint: effectivePolicyFingerprint
|
|
14133
|
-
monitor_pid: bridgeMonitor.monitorPid ?? null,
|
|
14134
|
-
monitor_handoff_pid: bridgeMonitor.handoffWatchdog?.pid ?? null
|
|
14049
|
+
policy_fingerprint: effectivePolicyFingerprint
|
|
14135
14050
|
});
|
|
14051
|
+
}
|
|
14052
|
+
runtimeLog("info", "terminal_approval_send", {
|
|
14053
|
+
conversation_id: conversation.conversation_id,
|
|
14054
|
+
terminal_target: terminalControl.target,
|
|
14055
|
+
key: approval.key,
|
|
14056
|
+
keys: approval.keys,
|
|
14057
|
+
label: approval.label,
|
|
14058
|
+
decision_mode: approval.decisionMode,
|
|
14059
|
+
request_id: approval.requestId,
|
|
14060
|
+
approval_fingerprint: actualFingerprint,
|
|
14061
|
+
auto_approved: autoApproved,
|
|
14062
|
+
policy_rule_id: effectivePolicyRuleId,
|
|
14063
|
+
policy_fingerprint: effectivePolicyFingerprint
|
|
14064
|
+
});
|
|
14065
|
+
const nativeTakeoverForUpdate = isRecord(lockedConversation.native_session_takeover)
|
|
14066
|
+
? { ...lockedConversation.native_session_takeover }
|
|
14067
|
+
: {};
|
|
14068
|
+
const resolvedApproval = isRecord(nativeTakeoverForUpdate.terminal_bridge_approval)
|
|
14069
|
+
? nativeTakeoverForUpdate.terminal_bridge_approval
|
|
14070
|
+
: undefined;
|
|
14071
|
+
const resolvedApprovalScreenDigest = stringValue(resolvedApproval?.screen_digest);
|
|
14072
|
+
const resolvedApprovalState = isRecord(resolvedApproval?.approval_state)
|
|
14073
|
+
? resolvedApproval.approval_state
|
|
14074
|
+
: undefined;
|
|
14075
|
+
const resolvedTranscriptIdentity = claudeTranscriptApprovalIdentity(resolvedApprovalState);
|
|
14076
|
+
const approvalResolvedAt = cliNow().toISOString();
|
|
14077
|
+
const agentTimeoutMinutes = Number(options.agentTimeoutMinutes ??
|
|
14078
|
+
nativeTakeoverForUpdate.terminal_bridge_inactivity_timeout_minutes ??
|
|
14079
|
+
DEFAULT_AGENT_TIMEOUT_MINUTES);
|
|
14080
|
+
const agentHardTimeoutMinutes = positiveMinutes(options.agentHardTimeoutMinutes ??
|
|
14081
|
+
nativeTakeoverForUpdate.terminal_bridge_hard_timeout_minutes ??
|
|
14082
|
+
DEFAULT_AGENT_HARD_TIMEOUT_MINUTES, "--agent-hard-timeout-minutes");
|
|
14083
|
+
const nextNativeTakeover = {
|
|
14084
|
+
...nativeTakeoverForUpdate,
|
|
14085
|
+
terminal_bridge_approval: undefined,
|
|
14086
|
+
terminal_bridge_approval_dispatch: undefined,
|
|
14087
|
+
terminal_bridge_approval_resolved_at: approvalResolvedAt,
|
|
14088
|
+
terminal_bridge_last_approval_fingerprint: actualFingerprint,
|
|
14089
|
+
terminal_bridge_last_approval_screen_digest: resolvedApprovalScreenDigest,
|
|
14090
|
+
terminal_bridge_last_approval_request_id: resolvedTranscriptIdentity?.requestId,
|
|
14091
|
+
terminal_bridge_last_approval_evidence_fingerprint: resolvedTranscriptIdentity?.evidenceFingerprint,
|
|
14092
|
+
terminal_bridge_last_approval_prompt_cleared_at: undefined,
|
|
14093
|
+
terminal_bridge_last_approval_at: approvalResolvedAt,
|
|
14094
|
+
terminal_bridge_last_approval_message_id: nativeTakeoverForUpdate.terminal_bridge_message_id,
|
|
14095
|
+
terminal_bridge_monitor_lock_version: TERMINAL_BRIDGE_MONITOR_LOCK_VERSION,
|
|
14096
|
+
terminal_bridge_monitor_started_at: approvalResolvedAt,
|
|
14097
|
+
terminal_bridge_last_activity_at: approvalResolvedAt,
|
|
14098
|
+
terminal_bridge_last_activity_reason: "approval resolved",
|
|
14099
|
+
terminal_bridge_inactivity_timeout_minutes: agentTimeoutMinutes,
|
|
14100
|
+
terminal_bridge_hard_timeout_minutes: agentHardTimeoutMinutes,
|
|
14101
|
+
terminal_bridge_inactivity_deadline_at: deadlineAt(approvalResolvedAt, agentTimeoutMinutes),
|
|
14102
|
+
terminal_bridge_hard_deadline_at: deadlineAt(stringValue(nativeTakeoverForUpdate.terminal_bridge_started_at) ?? approvalResolvedAt, agentHardTimeoutMinutes)
|
|
14103
|
+
};
|
|
14104
|
+
delete nextNativeTakeover.terminal_bridge_approval;
|
|
14105
|
+
delete nextNativeTakeover.terminal_bridge_approval_dispatch;
|
|
14106
|
+
delete nextNativeTakeover.terminal_bridge_last_approval_prompt_cleared_at;
|
|
14107
|
+
const nextConversation = {
|
|
14108
|
+
...lockedConversation,
|
|
14109
|
+
status: terminalBridgeEnabled(lockedConversation)
|
|
14110
|
+
? "waiting_for_agent"
|
|
14111
|
+
: lockedConversation.status,
|
|
14112
|
+
native_session_takeover: nextNativeTakeover,
|
|
14113
|
+
updated_at: approvalResolvedAt
|
|
14114
|
+
};
|
|
14115
|
+
saveState(statePath, nextConversation);
|
|
14116
|
+
releaseApprovalStateLock();
|
|
14117
|
+
const bridgeMonitor = ensureTerminalBridgeMonitorAfterApproval({
|
|
14118
|
+
conversation: nextConversation,
|
|
14119
|
+
statePath,
|
|
14120
|
+
logPath,
|
|
14121
|
+
terminalControl,
|
|
14122
|
+
options
|
|
14123
|
+
});
|
|
14124
|
+
printJson({
|
|
14125
|
+
conversation: nextConversation,
|
|
14126
|
+
approved: true,
|
|
14127
|
+
terminal_control: terminalControl,
|
|
14128
|
+
key: approval.key,
|
|
14129
|
+
keys: approval.keys,
|
|
14130
|
+
label: approval.label,
|
|
14131
|
+
decision_mode: approval.decisionMode,
|
|
14132
|
+
request_id: approval.requestId,
|
|
14133
|
+
approval_fingerprint: actualFingerprint,
|
|
14134
|
+
auto_approved: autoApproved,
|
|
14135
|
+
policy_rule_id: effectivePolicyRuleId,
|
|
14136
|
+
policy_fingerprint: effectivePolicyFingerprint,
|
|
14137
|
+
monitor_pid: bridgeMonitor.monitorPid ?? null,
|
|
14138
|
+
monitor_handoff_pid: bridgeMonitor.handoffWatchdog?.pid ?? null
|
|
14139
|
+
});
|
|
14140
|
+
};
|
|
14141
|
+
try {
|
|
14142
|
+
return await withStoreWriterLeaseAsync(writerStoreDir, async () => {
|
|
14143
|
+
releaseStateLock = acquireFileLock(`${statePath}.lock`);
|
|
14144
|
+
try {
|
|
14145
|
+
return await runApprovalWithStateLock();
|
|
14146
|
+
}
|
|
14147
|
+
finally {
|
|
14148
|
+
releaseApprovalStateLock();
|
|
14149
|
+
}
|
|
14136
14150
|
});
|
|
14137
14151
|
}
|
|
14138
14152
|
finally {
|
|
@@ -20341,12 +20355,20 @@ async function runTerminalBridgeMonitorWithLock(options, lifecycle, expectedTerm
|
|
|
20341
20355
|
});
|
|
20342
20356
|
}
|
|
20343
20357
|
}
|
|
20358
|
+
const approvalPromptEvidenceUnavailable = isRecord(approval) &&
|
|
20359
|
+
approval.blocked === true &&
|
|
20360
|
+
approval.approvable === true &&
|
|
20361
|
+
(stringValue(approval.decision_mode) ?? "keys") === "keys" &&
|
|
20362
|
+
!stringValue(approval.fingerprint);
|
|
20344
20363
|
if (!suppressApprovalNotification &&
|
|
20345
20364
|
isRecord(approval) &&
|
|
20346
20365
|
approval.blocked === true &&
|
|
20347
|
-
approval.approvable !== true
|
|
20348
|
-
|
|
20349
|
-
|
|
20366
|
+
(approval.approvable !== true ||
|
|
20367
|
+
approvalPromptEvidenceUnavailable)) {
|
|
20368
|
+
const approvalReason = approvalPromptEvidenceUnavailable
|
|
20369
|
+
? `${executor.display_name} approval prompt lacks exact prompt-region evidence`
|
|
20370
|
+
: stringValue(approval.reason) ??
|
|
20371
|
+
"Claude Code permission state cannot be safely resolved through AKK";
|
|
20350
20372
|
appendEvent(logPath, {
|
|
20351
20373
|
ts: cliNow().toISOString(),
|
|
20352
20374
|
conversation_id: conversation.conversation_id,
|