@scotthuang/agent-knock-knock 0.2.4 → 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,16 @@
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
+
3
14
  ## 0.2.4 - 2026-06-24
4
15
 
5
16
  ### 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);
@@ -650,13 +650,7 @@ function terminalControlFromTakeover(nativeTakeover) {
650
650
  };
651
651
  }
652
652
  function detectCodexApprovalPrompt(screen) {
653
- const approvalMarkers = [
654
- "Would you like to run the following command?",
655
- "Would you like to make the following edits?",
656
- "Would you like to grant these permissions?",
657
- "needs your approval."
658
- ];
659
- if (!approvalMarkers.some((marker) => screen.includes(marker))) {
653
+ if (!isCodexApprovalPromptVisible(screen)) {
660
654
  return {
661
655
  approvable: false,
662
656
  reason: "no Codex approval prompt was detected in the terminal screen"
@@ -685,6 +679,15 @@ function detectCodexApprovalPrompt(screen) {
685
679
  reason: "no primary approve option with a shortcut was detected"
686
680
  };
687
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
+ }
688
691
  function screenExcerpt(screen, maxLength = 4000) {
689
692
  const lines = screen.trimEnd().split(/\r?\n/);
690
693
  return lines.slice(Math.max(0, lines.length - 80)).join("\n").slice(-maxLength);
@@ -1267,7 +1270,7 @@ function environmentForExecutor(executor, options = {}) {
1267
1270
  all_proxy: proxy
1268
1271
  };
1269
1272
  }
1270
- function runList(options) {
1273
+ async function runList(options) {
1271
1274
  const storeDir = expandHome(options.storeDir ?? options.logDir ?? defaultStoreDir(process.cwd()));
1272
1275
  const cleanup = cleanupIdleConversations(storeDir, options);
1273
1276
  const includeAll = Boolean(options.all);
@@ -1278,20 +1281,235 @@ function runList(options) {
1278
1281
  .filter((conversation) => includeAll || isActiveStatus(conversation.status))
1279
1282
  .filter((conversation) => !agentFilter || conversation.agent === agentFilter)
1280
1283
  .filter((conversation) => !statusFilter || conversation.status === statusFilter);
1284
+ const delegated = conversations.map(delegatedListEntry);
1285
+ const nativeScan = await buildNativeListGroups({ options, agentFilter, statusFilter });
1281
1286
  printJson({
1282
1287
  store_dir: storeDir,
1283
1288
  cleanup,
1289
+ delegated,
1290
+ native: nativeScan.native,
1291
+ terminal_controlled: nativeScan.terminalControlled,
1292
+ native_scan: nativeScan.summary,
1284
1293
  tasks: conversations
1285
1294
  });
1286
1295
  runtimeLog("info", "tasks_listed", {
1287
1296
  store_dir: storeDir,
1288
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,
1289
1301
  include_all: includeAll,
1290
1302
  agent_filter: agentFilter,
1291
1303
  status_filter: statusFilter,
1292
1304
  cleanup
1293
1305
  });
1294
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
+ }
1295
1513
  async function runStatus(options) {
1296
1514
  cleanupIdleConversations(storeDirFromOptions(options), options);
1297
1515
  const { conversation, statePath, logPath } = loadConversationFromOptions(options);
@@ -3820,7 +4038,7 @@ function usage() {
3820
4038
  agent-knock-knock record --state <file> --message-json <json>
3821
4039
  agent-knock-knock bootstrap-prompt --callback-command <command> [--agent ${agentList}]
3822
4040
  agent-knock-knock delegate --request <text> [--agent ${agentList}] [--store-dir <dir>] [--all-proxy <url>] [--agent-timeout-minutes <minutes>] [--token <gateway-token>] [--send|--background]
3823
- 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]
3824
4042
  agent-knock-knock status --conversation <id> [--store-dir <dir>] [--trace]
3825
4043
  agent-knock-knock send --conversation <id> --message <text> [--type answer|task|control] [--all-proxy <url>] [--agent-timeout-minutes <minutes>]
3826
4044
  agent-knock-knock approve --conversation <id>