@scotthuang/agent-knock-knock 0.2.4 → 0.2.6
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 +17 -0
- package/README.md +14 -2
- package/dist/src/cli.js +228 -10
- package/dist/src/cli.js.map +1 -1
- 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 +2 -0
- package/dist/src/terminal-control-provider.js +61 -32
- 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,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.6 - 2026-06-25
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Added tmux executable path fallback for OpenClaw Gateway environments whose service `PATH` cannot resolve `tmux`, allowing `AKK list` and terminal-control actions to discover and operate tmux panes via common absolute install paths.
|
|
8
|
+
|
|
9
|
+
## 0.2.5 - 2026-06-25
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added grouped `AKK list` output for `delegated`, `native`, and `terminal_controlled` sessions while preserving the legacy `tasks` field.
|
|
14
|
+
- Added terminal-controlled approval state to `AKK list`, allowing tmux-backed Codex sessions to show when an approval prompt is visible and approvable.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Updated OpenClaw routing guidance so active/native Codex session questions use `AKK list` instead of the separate native discover tool.
|
|
19
|
+
|
|
3
20
|
## 0.2.4 - 2026-06-24
|
|
4
21
|
|
|
5
22
|
### 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);
|
|
@@ -650,13 +650,7 @@ function terminalControlFromTakeover(nativeTakeover) {
|
|
|
650
650
|
};
|
|
651
651
|
}
|
|
652
652
|
function detectCodexApprovalPrompt(screen) {
|
|
653
|
-
|
|
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>
|