@scotthuang/agent-knock-knock 0.2.40 → 0.2.41
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 +10 -0
- package/README.md +32 -0
- package/dist/src/approval-policy.d.ts +46 -0
- package/dist/src/approval-policy.js +321 -0
- package/dist/src/approval-policy.js.map +1 -0
- package/dist/src/cli.js +141 -7
- package/dist/src/cli.js.map +1 -1
- package/dist/src/openclaw-plugin.js +34 -0
- package/dist/src/openclaw-plugin.js.map +1 -1
- package/openclaw.plugin.json +52 -0
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -633,18 +633,21 @@ function detectCodexApprovalPrompt(screen) {
|
|
|
633
633
|
if (key !== "y") {
|
|
634
634
|
return {
|
|
635
635
|
approvable: false,
|
|
636
|
-
reason: `primary approval shortcut is ${key}, not y
|
|
636
|
+
reason: `primary approval shortcut is ${key}, not y`,
|
|
637
|
+
...approvalCandidateFromPrompt(prompt.marker, prompt.region)
|
|
637
638
|
};
|
|
638
639
|
}
|
|
639
640
|
return {
|
|
640
641
|
approvable: true,
|
|
641
642
|
key,
|
|
642
|
-
label: match[1].trim()
|
|
643
|
+
label: match[1].trim(),
|
|
644
|
+
...approvalCandidateFromPrompt(prompt.marker, prompt.region)
|
|
643
645
|
};
|
|
644
646
|
}
|
|
645
647
|
return {
|
|
646
648
|
approvable: false,
|
|
647
|
-
reason: "no primary approve option with a shortcut was detected"
|
|
649
|
+
reason: "no primary approve option with a shortcut was detected",
|
|
650
|
+
...approvalCandidateFromPrompt(prompt.marker, prompt.region)
|
|
648
651
|
};
|
|
649
652
|
}
|
|
650
653
|
function isCodexApprovalPromptVisible(screen) {
|
|
@@ -659,9 +662,12 @@ function codexApprovalPromptRegion(screen) {
|
|
|
659
662
|
];
|
|
660
663
|
const lines = screen.split(/\r?\n/);
|
|
661
664
|
let markerIndex = -1;
|
|
665
|
+
let matchedMarker = "";
|
|
662
666
|
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
663
|
-
|
|
667
|
+
const marker = approvalMarkers.find((candidate) => lines[index].includes(candidate));
|
|
668
|
+
if (marker) {
|
|
664
669
|
markerIndex = index;
|
|
670
|
+
matchedMarker = marker;
|
|
665
671
|
break;
|
|
666
672
|
}
|
|
667
673
|
}
|
|
@@ -681,9 +687,40 @@ function codexApprovalPromptRegion(screen) {
|
|
|
681
687
|
}
|
|
682
688
|
return {
|
|
683
689
|
visible: true,
|
|
684
|
-
region: regionLines.join("\n")
|
|
690
|
+
region: regionLines.join("\n"),
|
|
691
|
+
marker: matchedMarker
|
|
685
692
|
};
|
|
686
693
|
}
|
|
694
|
+
function approvalCandidateFromPrompt(marker, region) {
|
|
695
|
+
const promptKind = marker === "Would you like to run the following command?"
|
|
696
|
+
? "run_command"
|
|
697
|
+
: marker === "Would you like to make the following edits?"
|
|
698
|
+
? "file_edit"
|
|
699
|
+
: marker === "Would you like to grant these permissions?"
|
|
700
|
+
? "grant_permissions"
|
|
701
|
+
: "unknown";
|
|
702
|
+
return {
|
|
703
|
+
promptKind,
|
|
704
|
+
command: promptKind === "run_command" ? commandFromApprovalRegion(region) : undefined
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
function commandFromApprovalRegion(region) {
|
|
708
|
+
const lines = region.split(/\r?\n/);
|
|
709
|
+
const commandStart = lines.findIndex((line) => /^\s*\$\s+/u.test(line));
|
|
710
|
+
if (commandStart < 0) {
|
|
711
|
+
return undefined;
|
|
712
|
+
}
|
|
713
|
+
const parts = [];
|
|
714
|
+
for (let index = commandStart; index < lines.length; index += 1) {
|
|
715
|
+
const line = lines[index];
|
|
716
|
+
if (index > commandStart && (!line.trim() || /^[\s›]*\d+\.\s+/u.test(line) || /Press enter to confirm/u.test(line))) {
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
parts.push(index === commandStart ? line.replace(/^\s*\$\s+/u, "").trim() : line.trim());
|
|
720
|
+
}
|
|
721
|
+
const command = parts.filter(Boolean).join(" ").trim();
|
|
722
|
+
return command ? redactString(command) : undefined;
|
|
723
|
+
}
|
|
687
724
|
function isPostApprovalActivityLine(line) {
|
|
688
725
|
const trimmed = line.trim();
|
|
689
726
|
if (!trimmed) {
|
|
@@ -1636,6 +1673,8 @@ async function listStateForTerminal(terminalControl, options) {
|
|
|
1636
1673
|
approvable: approval.approvable,
|
|
1637
1674
|
key: approval.approvable ? approval.key : undefined,
|
|
1638
1675
|
label: approval.approvable ? approval.label : undefined,
|
|
1676
|
+
prompt_kind: approval.promptKind,
|
|
1677
|
+
command: approval.command,
|
|
1639
1678
|
reason: approval.approvable ? undefined : approval.reason,
|
|
1640
1679
|
screen_excerpt: blocked ? screenExcerpt(screen, 1000) : undefined
|
|
1641
1680
|
},
|
|
@@ -1852,6 +1891,8 @@ async function terminalStatusForControl(terminalControl, options) {
|
|
|
1852
1891
|
approvable: approval.approvable,
|
|
1853
1892
|
key: approval.approvable ? approval.key : undefined,
|
|
1854
1893
|
label: approval.approvable ? approval.label : undefined,
|
|
1894
|
+
prompt_kind: approval.promptKind,
|
|
1895
|
+
command: approval.command,
|
|
1855
1896
|
reason: approval.approvable ? undefined : approval.reason
|
|
1856
1897
|
},
|
|
1857
1898
|
screen: {
|
|
@@ -1886,11 +1927,29 @@ function terminalBridgeApprovalFingerprint({ terminalControl, terminalStatus })
|
|
|
1886
1927
|
target: terminalControl.target,
|
|
1887
1928
|
key: approval.key,
|
|
1888
1929
|
label: approval.label,
|
|
1930
|
+
prompt_kind: approval.prompt_kind,
|
|
1931
|
+
command: approval.command,
|
|
1889
1932
|
excerpt: screen.excerpt
|
|
1890
1933
|
}))
|
|
1891
1934
|
.digest("hex")
|
|
1892
1935
|
.slice(0, 16);
|
|
1893
1936
|
}
|
|
1937
|
+
function terminalBridgeApprovalFingerprintForScreen({ terminalControl, screen, approval }) {
|
|
1938
|
+
return terminalBridgeApprovalFingerprint({
|
|
1939
|
+
terminalControl,
|
|
1940
|
+
terminalStatus: {
|
|
1941
|
+
approval_state: {
|
|
1942
|
+
key: approval.key,
|
|
1943
|
+
label: approval.label,
|
|
1944
|
+
prompt_kind: approval.promptKind,
|
|
1945
|
+
command: approval.command
|
|
1946
|
+
},
|
|
1947
|
+
screen: {
|
|
1948
|
+
excerpt: screenExcerpt(screen)
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
});
|
|
1952
|
+
}
|
|
1894
1953
|
function terminalBridgeApprovalInstructions({ conversation, terminalControl, terminalStatus }) {
|
|
1895
1954
|
const approval = isRecord(terminalStatus?.approval_state) ? terminalStatus.approval_state : {};
|
|
1896
1955
|
const screen = isRecord(terminalStatus?.screen) ? terminalStatus.screen : {};
|
|
@@ -2392,6 +2451,38 @@ async function runApprove(options) {
|
|
|
2392
2451
|
});
|
|
2393
2452
|
return;
|
|
2394
2453
|
}
|
|
2454
|
+
const expectedFingerprint = stringValue(options.expectedApprovalFingerprint);
|
|
2455
|
+
const actualFingerprint = terminalBridgeApprovalFingerprintForScreen({ terminalControl, screen, approval });
|
|
2456
|
+
const autoApproved = options.autoApproved === true;
|
|
2457
|
+
const policyRuleId = stringValue(options.policyRuleId);
|
|
2458
|
+
const policyFingerprint = stringValue(options.policyFingerprint);
|
|
2459
|
+
if (expectedFingerprint && actualFingerprint !== expectedFingerprint) {
|
|
2460
|
+
if (autoApproved) {
|
|
2461
|
+
appendEvent(logPath, {
|
|
2462
|
+
ts: new Date().toISOString(),
|
|
2463
|
+
conversation_id: conversation.conversation_id,
|
|
2464
|
+
event: "terminal_auto_approval_decision",
|
|
2465
|
+
action: "rejected",
|
|
2466
|
+
reason: "approval fingerprint changed before execution",
|
|
2467
|
+
terminal_control: terminalControl,
|
|
2468
|
+
expected_fingerprint: expectedFingerprint,
|
|
2469
|
+
actual_fingerprint: actualFingerprint,
|
|
2470
|
+
policy_rule_id: policyRuleId,
|
|
2471
|
+
policy_fingerprint: policyFingerprint
|
|
2472
|
+
});
|
|
2473
|
+
}
|
|
2474
|
+
printJson({
|
|
2475
|
+
conversation,
|
|
2476
|
+
approved: false,
|
|
2477
|
+
blocked: true,
|
|
2478
|
+
reason: "approval fingerprint changed before execution",
|
|
2479
|
+
terminal_control: terminalControl,
|
|
2480
|
+
expected_approval_fingerprint: expectedFingerprint,
|
|
2481
|
+
actual_approval_fingerprint: actualFingerprint,
|
|
2482
|
+
screen_excerpt: screenExcerpt(screen)
|
|
2483
|
+
});
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2395
2486
|
await provider.sendKeys(terminalControl.target, [approval.key], {
|
|
2396
2487
|
socketPath: terminalControl.socketPath
|
|
2397
2488
|
});
|
|
@@ -2401,13 +2492,33 @@ async function runApprove(options) {
|
|
|
2401
2492
|
event: "terminal_approval_send",
|
|
2402
2493
|
terminal_control: terminalControl,
|
|
2403
2494
|
key: approval.key,
|
|
2404
|
-
label: approval.label
|
|
2495
|
+
label: approval.label,
|
|
2496
|
+
approval_fingerprint: actualFingerprint,
|
|
2497
|
+
auto_approved: autoApproved,
|
|
2498
|
+
policy_rule_id: policyRuleId,
|
|
2499
|
+
policy_fingerprint: policyFingerprint
|
|
2405
2500
|
});
|
|
2501
|
+
if (autoApproved) {
|
|
2502
|
+
appendEvent(logPath, {
|
|
2503
|
+
ts: new Date().toISOString(),
|
|
2504
|
+
conversation_id: conversation.conversation_id,
|
|
2505
|
+
event: "terminal_auto_approval_decision",
|
|
2506
|
+
action: "approved",
|
|
2507
|
+
terminal_control: terminalControl,
|
|
2508
|
+
approval_fingerprint: actualFingerprint,
|
|
2509
|
+
policy_rule_id: policyRuleId,
|
|
2510
|
+
policy_fingerprint: policyFingerprint
|
|
2511
|
+
});
|
|
2512
|
+
}
|
|
2406
2513
|
runtimeLog("info", "terminal_approval_send", {
|
|
2407
2514
|
conversation_id: conversation.conversation_id,
|
|
2408
2515
|
terminal_target: terminalControl.target,
|
|
2409
2516
|
key: approval.key,
|
|
2410
|
-
label: approval.label
|
|
2517
|
+
label: approval.label,
|
|
2518
|
+
approval_fingerprint: actualFingerprint,
|
|
2519
|
+
auto_approved: autoApproved,
|
|
2520
|
+
policy_rule_id: policyRuleId,
|
|
2521
|
+
policy_fingerprint: policyFingerprint
|
|
2411
2522
|
});
|
|
2412
2523
|
const nativeTakeoverForUpdate = isRecord(conversation.native_session_takeover)
|
|
2413
2524
|
? { ...conversation.native_session_takeover }
|
|
@@ -2454,6 +2565,9 @@ async function runApprove(options) {
|
|
|
2454
2565
|
terminal_control: terminalControl,
|
|
2455
2566
|
key: approval.key,
|
|
2456
2567
|
label: approval.label,
|
|
2568
|
+
approval_fingerprint: actualFingerprint,
|
|
2569
|
+
auto_approved: autoApproved,
|
|
2570
|
+
policy_rule_id: policyRuleId,
|
|
2457
2571
|
monitor_pid: bridgeMonitor?.pid ?? null
|
|
2458
2572
|
});
|
|
2459
2573
|
}
|
|
@@ -3687,6 +3801,12 @@ async function runTerminalBridgeMonitor(options) {
|
|
|
3687
3801
|
terminal_control: terminalControl,
|
|
3688
3802
|
terminal_status: terminalStatus,
|
|
3689
3803
|
approval_fingerprint: fingerprint,
|
|
3804
|
+
approval_candidate: terminalBridgeApprovalCandidate({
|
|
3805
|
+
executor,
|
|
3806
|
+
terminalControl,
|
|
3807
|
+
terminalStatus,
|
|
3808
|
+
fingerprint
|
|
3809
|
+
}),
|
|
3690
3810
|
approve_command: `AKK approve ${notification.conversation.conversation_id}`,
|
|
3691
3811
|
deny_command: `AKK cancel ${notification.conversation.conversation_id}`,
|
|
3692
3812
|
approve_tool: "agent_knock_knock_approve",
|
|
@@ -3814,6 +3934,20 @@ async function runTerminalBridgeMonitor(options) {
|
|
|
3814
3934
|
sleepSync(pollIntervalMs);
|
|
3815
3935
|
}
|
|
3816
3936
|
}
|
|
3937
|
+
function terminalBridgeApprovalCandidate({ executor, terminalControl, terminalStatus, fingerprint }) {
|
|
3938
|
+
const approval = isRecord(terminalStatus?.approval_state) ? terminalStatus.approval_state : {};
|
|
3939
|
+
if (approval.approvable !== true) {
|
|
3940
|
+
return undefined;
|
|
3941
|
+
}
|
|
3942
|
+
return {
|
|
3943
|
+
agent: executor.kind,
|
|
3944
|
+
kind: stringValue(approval.prompt_kind) ?? "unknown",
|
|
3945
|
+
command: stringValue(approval.command),
|
|
3946
|
+
cwd: terminalControl.currentPath,
|
|
3947
|
+
fingerprint,
|
|
3948
|
+
terminal_target: terminalControl.target
|
|
3949
|
+
};
|
|
3950
|
+
}
|
|
3817
3951
|
async function terminalBridgeCodexContext({ conversation, nativeTakeover, options }) {
|
|
3818
3952
|
const provider = createAgentSessionProvider("codex", options);
|
|
3819
3953
|
const nativeSessionId = stringValue(nativeTakeover?.["native_session_id"]);
|