@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 +18 -0
- package/README.md +14 -2
- package/dist/src/cli.js +242 -15
- package/dist/src/cli.js.map +1 -1
- package/dist/src/codex-session-provider.d.ts +1 -0
- package/dist/src/codex-session-provider.js +8 -0
- package/dist/src/codex-session-provider.js.map +1 -1
- package/dist/src/openclaw-plugin.js +15 -1
- package/dist/src/openclaw-plugin.js.map +1 -1
- package/dist/src/terminal-control-provider.d.ts +18 -5
- package/dist/src/terminal-control-provider.js +54 -16
- package/dist/src/terminal-control-provider.js.map +1 -1
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +8 -4
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
|
-
|
|
159
|
+
List current AKK-managed and local agent work:
|
|
160
160
|
|
|
161
161
|
```text
|
|
162
|
-
AKK
|
|
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
|
-
|
|
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
|
-
|
|
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>
|