@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/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
- if (approvalMarkers.some((marker) => lines[index].includes(marker))) {
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"]);