@scotthuang/agent-knock-knock 0.2.3 → 0.2.5

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 CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.5 - 2026-06-25
4
+
5
+ ### Added
6
+
7
+ - Added grouped `AKK list` output for `delegated`, `native`, and `terminal_controlled` sessions while preserving the legacy `tasks` field.
8
+ - Added terminal-controlled approval state to `AKK list`, allowing tmux-backed Codex sessions to show when an approval prompt is visible and approvable.
9
+
10
+ ### Changed
11
+
12
+ - Updated OpenClaw routing guidance so active/native Codex session questions use `AKK list` instead of the separate native discover tool.
13
+
14
+ ## 0.2.4 - 2026-06-24
15
+
16
+ ### Fixed
17
+
18
+ - Added tmux socket fallback discovery for OpenClaw Gateway environments whose service `TMPDIR` differs from the user's interactive terminal, allowing active discovery to mark tmux-controlled Codex panes reliably.
19
+ - Preserved the discovered tmux socket path through terminal-control takeover, status, send, and approve operations so follow-up terminal actions use the same tmux server.
20
+
3
21
  ## 0.2.3 - 2026-06-24
4
22
 
5
23
  ### Fixed
package/README.md CHANGED
@@ -156,11 +156,23 @@ Optional default-agent config:
156
156
 
157
157
  Experimental: AKK can discover and take over Codex CLI sessions that were started outside AKK. This is useful when you started Codex in a terminal, left the machine, and want OpenClaw to continue managing that work from chat.
158
158
 
159
- Discovery prompts:
159
+ List current AKK-managed and local agent work:
160
160
 
161
161
  ```text
162
- AKK Codex sessions
162
+ AKK list
163
163
  AKK Codex active
164
+ ```
165
+
166
+ `AKK list` returns separate groups for:
167
+
168
+ - `delegated`: AKK-managed background tasks.
169
+ - `native`: local native sessions that AKK can discover but cannot directly control.
170
+ - `terminal_controlled`: local sessions running in a controllable terminal provider such as tmux. These entries include terminal metadata, command capabilities, and concise approval state when a visible approval prompt is detected.
171
+
172
+ Lower-level discovery prompts remain available for historical session and capability inspection:
173
+
174
+ ```text
175
+ AKK Codex sessions
164
176
  AKK Codex capabilities
165
177
  ```
166
178
 
package/dist/src/cli.js CHANGED
@@ -87,7 +87,7 @@ async function runCommand(commandName, options) {
87
87
  runDelegate(options);
88
88
  }
89
89
  else if (commandName === "list") {
90
- runList(options);
90
+ await runList(options);
91
91
  }
92
92
  else if (commandName === "status") {
93
93
  await runStatus(options);
@@ -645,17 +645,12 @@ function terminalControlFromTakeover(nativeTakeover) {
645
645
  panePid,
646
646
  currentCommand: stringValue(terminalControl.currentCommand),
647
647
  currentPath: stringValue(terminalControl.currentPath),
648
+ socketPath: stringValue(terminalControl.socketPath),
648
649
  capabilities: ["capture_screen", "send_keys", "terminal_approval"]
649
650
  };
650
651
  }
651
652
  function detectCodexApprovalPrompt(screen) {
652
- const approvalMarkers = [
653
- "Would you like to run the following command?",
654
- "Would you like to make the following edits?",
655
- "Would you like to grant these permissions?",
656
- "needs your approval."
657
- ];
658
- if (!approvalMarkers.some((marker) => screen.includes(marker))) {
653
+ if (!isCodexApprovalPromptVisible(screen)) {
659
654
  return {
660
655
  approvable: false,
661
656
  reason: "no Codex approval prompt was detected in the terminal screen"
@@ -684,6 +679,15 @@ function detectCodexApprovalPrompt(screen) {
684
679
  reason: "no primary approve option with a shortcut was detected"
685
680
  };
686
681
  }
682
+ function isCodexApprovalPromptVisible(screen) {
683
+ const approvalMarkers = [
684
+ "Would you like to run the following command?",
685
+ "Would you like to make the following edits?",
686
+ "Would you like to grant these permissions?",
687
+ "needs your approval."
688
+ ];
689
+ return approvalMarkers.some((marker) => screen.includes(marker));
690
+ }
687
691
  function screenExcerpt(screen, maxLength = 4000) {
688
692
  const lines = screen.trimEnd().split(/\r?\n/);
689
693
  return lines.slice(Math.max(0, lines.length - 80)).join("\n").slice(-maxLength);
@@ -1266,7 +1270,7 @@ function environmentForExecutor(executor, options = {}) {
1266
1270
  all_proxy: proxy
1267
1271
  };
1268
1272
  }
1269
- function runList(options) {
1273
+ async function runList(options) {
1270
1274
  const storeDir = expandHome(options.storeDir ?? options.logDir ?? defaultStoreDir(process.cwd()));
1271
1275
  const cleanup = cleanupIdleConversations(storeDir, options);
1272
1276
  const includeAll = Boolean(options.all);
@@ -1277,20 +1281,235 @@ function runList(options) {
1277
1281
  .filter((conversation) => includeAll || isActiveStatus(conversation.status))
1278
1282
  .filter((conversation) => !agentFilter || conversation.agent === agentFilter)
1279
1283
  .filter((conversation) => !statusFilter || conversation.status === statusFilter);
1284
+ const delegated = conversations.map(delegatedListEntry);
1285
+ const nativeScan = await buildNativeListGroups({ options, agentFilter, statusFilter });
1280
1286
  printJson({
1281
1287
  store_dir: storeDir,
1282
1288
  cleanup,
1289
+ delegated,
1290
+ native: nativeScan.native,
1291
+ terminal_controlled: nativeScan.terminalControlled,
1292
+ native_scan: nativeScan.summary,
1283
1293
  tasks: conversations
1284
1294
  });
1285
1295
  runtimeLog("info", "tasks_listed", {
1286
1296
  store_dir: storeDir,
1287
1297
  returned_count: conversations.length,
1298
+ native_count: nativeScan.native.length,
1299
+ terminal_controlled_count: nativeScan.terminalControlled.length,
1300
+ native_scan_error: nativeScan.summary.error,
1288
1301
  include_all: includeAll,
1289
1302
  agent_filter: agentFilter,
1290
1303
  status_filter: statusFilter,
1291
1304
  cleanup
1292
1305
  });
1293
1306
  }
1307
+ async function buildNativeListGroups({ options, agentFilter, statusFilter }) {
1308
+ const empty = {
1309
+ native: [],
1310
+ terminalControlled: [],
1311
+ summary: {
1312
+ enabled: false,
1313
+ agents: [],
1314
+ error: undefined
1315
+ }
1316
+ };
1317
+ if (options.managedOnly) {
1318
+ return empty;
1319
+ }
1320
+ if (statusFilter && statusFilter !== "active") {
1321
+ return {
1322
+ ...empty,
1323
+ summary: {
1324
+ enabled: false,
1325
+ agents: [],
1326
+ skipped: `native active discovery skipped for status filter ${statusFilter}`
1327
+ }
1328
+ };
1329
+ }
1330
+ if (agentFilter && agentFilter !== "codex") {
1331
+ return {
1332
+ ...empty,
1333
+ summary: {
1334
+ enabled: true,
1335
+ agents: [],
1336
+ skipped: `native active discovery is not implemented for ${agentFilter}`
1337
+ }
1338
+ };
1339
+ }
1340
+ try {
1341
+ const provider = createAgentSessionProvider("codex", options);
1342
+ const activeSessions = await listActiveSessionsWithTerminalControl(provider, options);
1343
+ const rootSessions = rootActiveProcesses(activeSessions);
1344
+ const terminalControlled = [];
1345
+ const native = [];
1346
+ for (const session of rootSessions) {
1347
+ if (session.terminalControl) {
1348
+ terminalControlled.push(await terminalControlledListEntry(session, activeSessions, options));
1349
+ }
1350
+ else {
1351
+ native.push(nativeListEntry(session, activeSessions));
1352
+ }
1353
+ }
1354
+ return {
1355
+ native,
1356
+ terminalControlled,
1357
+ summary: {
1358
+ enabled: true,
1359
+ agents: ["codex"],
1360
+ active_count: activeSessions.length,
1361
+ native_count: native.length,
1362
+ terminal_controlled_count: terminalControlled.length,
1363
+ approval_scan: options.noApprovalScan ? "disabled" : "enabled"
1364
+ }
1365
+ };
1366
+ }
1367
+ catch (error) {
1368
+ return {
1369
+ native: [],
1370
+ terminalControlled: [],
1371
+ summary: {
1372
+ enabled: true,
1373
+ agents: ["codex"],
1374
+ error: error instanceof Error ? error.message : String(error)
1375
+ }
1376
+ };
1377
+ }
1378
+ }
1379
+ function delegatedListEntry(task) {
1380
+ return {
1381
+ ...task,
1382
+ id: task.conversation_id,
1383
+ source: "akk_delegate",
1384
+ commands: {
1385
+ send: canSendDelegated(task.status),
1386
+ cancel: isWaitingForAgent(task.status),
1387
+ close: task.status !== "closed",
1388
+ status: true,
1389
+ approve: false,
1390
+ capture_screen: false
1391
+ }
1392
+ };
1393
+ }
1394
+ function nativeListEntry(session, activeSessions) {
1395
+ return {
1396
+ id: `native:codex:${session.pid}`,
1397
+ source: "native_active",
1398
+ agent: "codex",
1399
+ status: "active",
1400
+ pid: session.pid,
1401
+ child_pids: childPidsForRoot(session, activeSessions),
1402
+ command: session.command,
1403
+ cwd: session.cwd,
1404
+ workspace: session.cwd,
1405
+ elapsed: session.elapsed,
1406
+ session_id: session.sessionId,
1407
+ confidence: session.confidence,
1408
+ reason: session.reason,
1409
+ commands: {
1410
+ safe_resume: Boolean(session.sessionId),
1411
+ terminate_then_resume: true,
1412
+ fork: Boolean(session.sessionId),
1413
+ terminal_control_attach: false,
1414
+ send: false,
1415
+ cancel: false,
1416
+ approve: false,
1417
+ close: false,
1418
+ status: false
1419
+ }
1420
+ };
1421
+ }
1422
+ async function terminalControlledListEntry(session, activeSessions, options) {
1423
+ const terminalControl = session.terminalControl;
1424
+ if (!terminalControl) {
1425
+ throw new Error(`process ${session.pid} is not terminal-controlled`);
1426
+ }
1427
+ const approvalState = await approvalStateForTerminal(terminalControl, options);
1428
+ return {
1429
+ id: `terminal:${terminalControl.kind}:${terminalControl.target}:${session.pid}`,
1430
+ source: "terminal_control",
1431
+ agent: "codex",
1432
+ status: "active",
1433
+ pid: session.pid,
1434
+ child_pids: childPidsForRoot(session, activeSessions),
1435
+ command: session.command,
1436
+ cwd: session.cwd,
1437
+ workspace: session.cwd,
1438
+ elapsed: session.elapsed,
1439
+ session_id: session.sessionId,
1440
+ confidence: session.confidence,
1441
+ reason: session.reason,
1442
+ terminal_control: terminalControl,
1443
+ approval_state: approvalState,
1444
+ commands: {
1445
+ send: true,
1446
+ capture_screen: true,
1447
+ approve: approvalState.approvable === true,
1448
+ status: true,
1449
+ cancel: false,
1450
+ close: false
1451
+ }
1452
+ };
1453
+ }
1454
+ async function approvalStateForTerminal(terminalControl, options) {
1455
+ if (options.noApprovalScan) {
1456
+ return {
1457
+ scanned: false,
1458
+ blocked: false,
1459
+ approvable: false,
1460
+ reason: "approval scan disabled"
1461
+ };
1462
+ }
1463
+ try {
1464
+ const screen = await createTerminalControlProvider(options).capture(terminalControl.target, {
1465
+ scrollbackLines: Number(options.scrollbackLines ?? 120),
1466
+ socketPath: terminalControl.socketPath
1467
+ });
1468
+ const approval = detectCodexApprovalPrompt(screen);
1469
+ const blocked = isCodexApprovalPromptVisible(screen);
1470
+ return {
1471
+ scanned: true,
1472
+ blocked,
1473
+ approvable: approval.approvable,
1474
+ key: approval.approvable ? approval.key : undefined,
1475
+ label: approval.approvable ? approval.label : undefined,
1476
+ reason: approval.approvable ? undefined : approval.reason,
1477
+ screen_excerpt: blocked ? screenExcerpt(screen, 1000) : undefined
1478
+ };
1479
+ }
1480
+ catch (error) {
1481
+ return {
1482
+ scanned: false,
1483
+ blocked: false,
1484
+ approvable: false,
1485
+ error: error instanceof Error ? error.message : String(error)
1486
+ };
1487
+ }
1488
+ }
1489
+ function rootActiveProcesses(processes) {
1490
+ const pids = new Set(processes.map((process) => process.pid));
1491
+ const roots = processes.filter((process) => !process.ppid || !pids.has(process.ppid));
1492
+ const seenTerminalTargets = new Set();
1493
+ return roots.filter((process) => {
1494
+ const terminalTarget = process.terminalControl?.target;
1495
+ if (!terminalTarget) {
1496
+ return true;
1497
+ }
1498
+ if (seenTerminalTargets.has(terminalTarget)) {
1499
+ return false;
1500
+ }
1501
+ seenTerminalTargets.add(terminalTarget);
1502
+ return true;
1503
+ });
1504
+ }
1505
+ function childPidsForRoot(root, processes) {
1506
+ return processes
1507
+ .filter((process) => process.ppid === root.pid)
1508
+ .map((process) => process.pid);
1509
+ }
1510
+ function canSendDelegated(status) {
1511
+ return !["failed", "closed", "cancelled"].includes(status);
1512
+ }
1294
1513
  async function runStatus(options) {
1295
1514
  cleanupIdleConversations(storeDirFromOptions(options), options);
1296
1515
  const { conversation, statePath, logPath } = loadConversationFromOptions(options);
@@ -1311,7 +1530,8 @@ async function runStatus(options) {
1311
1530
  result.terminal_control = terminalControl;
1312
1531
  try {
1313
1532
  const screen = await createTerminalControlProvider(options).capture(terminalControl.target, {
1314
- scrollbackLines: Number(options.scrollbackLines ?? 120)
1533
+ scrollbackLines: Number(options.scrollbackLines ?? 120),
1534
+ socketPath: terminalControl.socketPath
1315
1535
  });
1316
1536
  result.terminal_screen = {
1317
1537
  excerpt: screenExcerpt(screen),
@@ -1694,7 +1914,8 @@ async function runApprove(options) {
1694
1914
  }
1695
1915
  const provider = createTerminalControlProvider(options);
1696
1916
  const screen = await provider.capture(terminalControl.target, {
1697
- scrollbackLines: Number(options.scrollbackLines ?? 120)
1917
+ scrollbackLines: Number(options.scrollbackLines ?? 120),
1918
+ socketPath: terminalControl.socketPath
1698
1919
  });
1699
1920
  const approval = detectCodexApprovalPrompt(screen);
1700
1921
  if (!approval.approvable) {
@@ -1708,7 +1929,9 @@ async function runApprove(options) {
1708
1929
  });
1709
1930
  return;
1710
1931
  }
1711
- await provider.sendKeys(terminalControl.target, [approval.key]);
1932
+ await provider.sendKeys(terminalControl.target, [approval.key], {
1933
+ socketPath: terminalControl.socketPath
1934
+ });
1712
1935
  appendEvent(logPath, {
1713
1936
  ts: new Date().toISOString(),
1714
1937
  conversation_id: conversation.conversation_id,
@@ -1737,8 +1960,12 @@ async function runApprove(options) {
1737
1960
  }
1738
1961
  async function runTerminalControlSend({ options, conversation, nextConversation, statePath, logPath, executor, message, terminalControl, needsNativeTakeoverBootstrap }) {
1739
1962
  const provider = createTerminalControlProvider(options);
1740
- await provider.sendText(terminalControl.target, String(message.body ?? ""));
1741
- await provider.sendKeys(terminalControl.target, ["Enter"]);
1963
+ await provider.sendText(terminalControl.target, String(message.body ?? ""), {
1964
+ socketPath: terminalControl.socketPath
1965
+ });
1966
+ await provider.sendKeys(terminalControl.target, ["Enter"], {
1967
+ socketPath: terminalControl.socketPath
1968
+ });
1742
1969
  appendEvent(logPath, {
1743
1970
  ts: new Date().toISOString(),
1744
1971
  conversation_id: conversation.conversation_id,
@@ -3811,7 +4038,7 @@ function usage() {
3811
4038
  agent-knock-knock record --state <file> --message-json <json>
3812
4039
  agent-knock-knock bootstrap-prompt --callback-command <command> [--agent ${agentList}]
3813
4040
  agent-knock-knock delegate --request <text> [--agent ${agentList}] [--store-dir <dir>] [--all-proxy <url>] [--agent-timeout-minutes <minutes>] [--token <gateway-token>] [--send|--background]
3814
- agent-knock-knock list [--store-dir <dir>] [--agent ${agentList}] [--status <status>] [--all]
4041
+ agent-knock-knock list [--store-dir <dir>] [--agent ${agentList}] [--status <status>] [--all] [--managed-only] [--no-approval-scan]
3815
4042
  agent-knock-knock status --conversation <id> [--store-dir <dir>] [--trace]
3816
4043
  agent-knock-knock send --conversation <id> --message <text> [--type answer|task|control] [--all-proxy <url>] [--agent-timeout-minutes <minutes>]
3817
4044
  agent-knock-knock approve --conversation <id>