@scotthuang/agent-knock-knock 0.2.20 → 0.2.22
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 +16 -0
- package/README.md +2 -0
- package/dist/src/cli.js +316 -19
- package/dist/src/cli.js.map +1 -1
- package/dist/src/openclaw-plugin.js +64 -0
- package/dist/src/openclaw-plugin.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.22 - 2026-07-04
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Added `AKK describe` / `AKK summary` to summarize what an AKK-managed, native Codex, or terminal-controlled Codex session is about.
|
|
8
|
+
- Added the `agent_knock_knock_describe` OpenClaw tool and `/akk describe <conversation-id>` command.
|
|
9
|
+
- Reused Codex rollout history for exact session matches, added cwd-based fallback matching, and returned screen-only summaries with explicit low-confidence limitations when no history can be found.
|
|
10
|
+
|
|
11
|
+
## 0.2.21 - 2026-07-03
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Added structured tmux Codex activity detection for terminal-controlled sessions, exposing `activity_state` and `activity_reason` in `AKK status` and `AKK list`.
|
|
16
|
+
- Classified terminal-controlled Codex panes as `awaiting_approval`, `working`, `idle`, or `unknown` using conservative current-screen heuristics.
|
|
17
|
+
- Added regression coverage for approval, stale approval scrollback, working, idle, unknown, and list activity-state output.
|
|
18
|
+
|
|
3
19
|
## 0.2.20 - 2026-07-02
|
|
4
20
|
|
|
5
21
|
### Fixed
|
package/README.md
CHANGED
|
@@ -118,6 +118,7 @@ AKK Claude: review the latest commit
|
|
|
118
118
|
AKK Cursor: fix the flaky UI test
|
|
119
119
|
akk list
|
|
120
120
|
akk status <conversation-id>
|
|
121
|
+
akk describe <conversation-id>
|
|
121
122
|
akk send <conversation-id>: continue with the smaller implementation
|
|
122
123
|
akk cancel <conversation-id>
|
|
123
124
|
akk recover <conversation-id>
|
|
@@ -173,6 +174,7 @@ AKK list
|
|
|
173
174
|
- `terminal_controlled`: local sessions running in a controllable terminal provider. The current provider is tmux. These entries include terminal metadata, command capabilities, and concise approval state when a visible approval prompt is detected.
|
|
174
175
|
- `terminal_controlled` entries can be addressed directly by their `id` from `AKK list` for `AKK send <id>`, `AKK status <id>`, `AKK cancel <id>`, and `AKK approve <id>`. They do not need an AKK state file before terminal control.
|
|
175
176
|
- `AKK status <terminal-controlled-id>` is the unified way to inspect current terminal output. AKK captures the terminal pane internally and returns `terminal_screen`; there is no separate public screen-capture command.
|
|
177
|
+
- `AKK describe <id>` summarizes what a listed session is about. AKK-managed sessions use saved conversation history; native and terminal-controlled Codex sessions use exact Codex rollout history when a session id is available, fall back to cwd-matched rollout history when needed, and otherwise report only visible terminal/process context with lower confidence.
|
|
176
178
|
|
|
177
179
|
Takeover prompts:
|
|
178
180
|
|
package/dist/src/cli.js
CHANGED
|
@@ -92,6 +92,9 @@ async function runCommand(commandName, options) {
|
|
|
92
92
|
else if (commandName === "status") {
|
|
93
93
|
await runStatus(options);
|
|
94
94
|
}
|
|
95
|
+
else if (commandName === "describe" || commandName === "summary") {
|
|
96
|
+
await runDescribe(options);
|
|
97
|
+
}
|
|
95
98
|
else if (commandName === "send") {
|
|
96
99
|
await runSend(options);
|
|
97
100
|
}
|
|
@@ -669,6 +672,53 @@ function isPostApprovalActivityLine(line) {
|
|
|
669
672
|
}
|
|
670
673
|
return /^─\s*Worked for\b/u.test(trimmed);
|
|
671
674
|
}
|
|
675
|
+
function detectCodexActivityState(screen, approval = detectCodexApprovalPrompt(screen)) {
|
|
676
|
+
if (approval.approvable || isCodexApprovalPromptVisible(screen)) {
|
|
677
|
+
return {
|
|
678
|
+
state: "awaiting_approval",
|
|
679
|
+
reason: "current Codex approval prompt is visible"
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
const tailLines = screen.trimEnd().split(/\r?\n/).slice(-30);
|
|
683
|
+
const workingLine = tailLines.find((line) => isCodexWorkingLine(line));
|
|
684
|
+
if (workingLine) {
|
|
685
|
+
return {
|
|
686
|
+
state: "working",
|
|
687
|
+
reason: `Codex working marker detected: ${workingLine.trim()}`
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
const lastLine = tailLines
|
|
691
|
+
.map((line) => line.trim())
|
|
692
|
+
.filter(Boolean)
|
|
693
|
+
.at(-1);
|
|
694
|
+
if (lastLine && isCodexIdlePromptLine(lastLine)) {
|
|
695
|
+
return {
|
|
696
|
+
state: "idle",
|
|
697
|
+
reason: `Codex input prompt detected: ${lastLine}`
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
return {
|
|
701
|
+
state: "unknown",
|
|
702
|
+
reason: "no current Codex working, idle, or approval marker was detected in the terminal screen"
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
function isCodexWorkingLine(line) {
|
|
706
|
+
const trimmed = line.trim();
|
|
707
|
+
if (!trimmed) {
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
if (/^•\s+Working\b/u.test(trimmed)) {
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
713
|
+
if (/\besc to interrupt\b/u.test(trimmed) && (/\bWorking\b/u.test(trimmed) || /\b\/stop to close\b/u.test(trimmed))) {
|
|
714
|
+
return true;
|
|
715
|
+
}
|
|
716
|
+
return /\bbackground terminal running\b/u.test(trimmed);
|
|
717
|
+
}
|
|
718
|
+
function isCodexIdlePromptLine(line) {
|
|
719
|
+
const trimmed = line.trim();
|
|
720
|
+
return /^›(?:\s|$)/u.test(trimmed) && !/^›\s*1\./u.test(trimmed);
|
|
721
|
+
}
|
|
672
722
|
function screenExcerpt(screen, maxLength = 4000) {
|
|
673
723
|
const lines = screen.trimEnd().split(/\r?\n/);
|
|
674
724
|
return lines.slice(Math.max(0, lines.length - 80)).join("\n").slice(-maxLength);
|
|
@@ -1415,7 +1465,7 @@ async function terminalControlledListEntry(session, activeSessions, options) {
|
|
|
1415
1465
|
if (!terminalControl) {
|
|
1416
1466
|
throw new Error(`process ${session.pid} is not terminal-controlled`);
|
|
1417
1467
|
}
|
|
1418
|
-
const
|
|
1468
|
+
const terminalState = await listStateForTerminal(terminalControl, options);
|
|
1419
1469
|
return {
|
|
1420
1470
|
id: `terminal:${terminalControl.kind}:${terminalControl.target}:${session.pid}`,
|
|
1421
1471
|
source: "terminal_control",
|
|
@@ -1431,23 +1481,29 @@ async function terminalControlledListEntry(session, activeSessions, options) {
|
|
|
1431
1481
|
confidence: session.confidence,
|
|
1432
1482
|
reason: session.reason,
|
|
1433
1483
|
terminal_control: terminalControl,
|
|
1434
|
-
approval_state:
|
|
1484
|
+
approval_state: terminalState.approval_state,
|
|
1485
|
+
activity_state: terminalState.activity_state,
|
|
1486
|
+
activity_reason: terminalState.activity_reason,
|
|
1435
1487
|
commands: {
|
|
1436
1488
|
send: true,
|
|
1437
|
-
approve:
|
|
1489
|
+
approve: terminalState.approval_state.approvable === true,
|
|
1438
1490
|
status: true,
|
|
1439
1491
|
cancel: true,
|
|
1440
1492
|
close: false
|
|
1441
1493
|
}
|
|
1442
1494
|
};
|
|
1443
1495
|
}
|
|
1444
|
-
async function
|
|
1496
|
+
async function listStateForTerminal(terminalControl, options) {
|
|
1445
1497
|
if (options.noApprovalScan) {
|
|
1446
1498
|
return {
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1499
|
+
approval_state: {
|
|
1500
|
+
scanned: false,
|
|
1501
|
+
blocked: false,
|
|
1502
|
+
approvable: false,
|
|
1503
|
+
reason: "approval scan disabled"
|
|
1504
|
+
},
|
|
1505
|
+
activity_state: "unknown",
|
|
1506
|
+
activity_reason: "terminal screen scan disabled"
|
|
1451
1507
|
};
|
|
1452
1508
|
}
|
|
1453
1509
|
try {
|
|
@@ -1457,22 +1513,31 @@ async function approvalStateForTerminal(terminalControl, options) {
|
|
|
1457
1513
|
});
|
|
1458
1514
|
const approval = detectCodexApprovalPrompt(screen);
|
|
1459
1515
|
const blocked = isCodexApprovalPromptVisible(screen);
|
|
1516
|
+
const activity = detectCodexActivityState(screen, approval);
|
|
1460
1517
|
return {
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1518
|
+
approval_state: {
|
|
1519
|
+
scanned: true,
|
|
1520
|
+
blocked,
|
|
1521
|
+
approvable: approval.approvable,
|
|
1522
|
+
key: approval.approvable ? approval.key : undefined,
|
|
1523
|
+
label: approval.approvable ? approval.label : undefined,
|
|
1524
|
+
reason: approval.approvable ? undefined : approval.reason,
|
|
1525
|
+
screen_excerpt: blocked ? screenExcerpt(screen, 1000) : undefined
|
|
1526
|
+
},
|
|
1527
|
+
activity_state: activity.state,
|
|
1528
|
+
activity_reason: activity.reason
|
|
1468
1529
|
};
|
|
1469
1530
|
}
|
|
1470
1531
|
catch (error) {
|
|
1471
1532
|
return {
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1533
|
+
approval_state: {
|
|
1534
|
+
scanned: false,
|
|
1535
|
+
blocked: false,
|
|
1536
|
+
approvable: false,
|
|
1537
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1538
|
+
},
|
|
1539
|
+
activity_state: "unknown",
|
|
1540
|
+
activity_reason: error instanceof Error ? error.message : String(error)
|
|
1476
1541
|
};
|
|
1477
1542
|
}
|
|
1478
1543
|
}
|
|
@@ -1539,6 +1604,21 @@ function parseTerminalConversationId(conversationId) {
|
|
|
1539
1604
|
pid
|
|
1540
1605
|
};
|
|
1541
1606
|
}
|
|
1607
|
+
function parseNativeConversationId(conversationId) {
|
|
1608
|
+
const prefix = "native:codex:";
|
|
1609
|
+
if (!conversationId?.startsWith(prefix)) {
|
|
1610
|
+
return undefined;
|
|
1611
|
+
}
|
|
1612
|
+
const pid = Number(conversationId.slice(prefix.length));
|
|
1613
|
+
if (!Number.isInteger(pid)) {
|
|
1614
|
+
throw new Error(`invalid native conversation id: ${conversationId}`);
|
|
1615
|
+
}
|
|
1616
|
+
return {
|
|
1617
|
+
conversationId,
|
|
1618
|
+
agent: "codex",
|
|
1619
|
+
pid
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1542
1622
|
async function runStatus(options) {
|
|
1543
1623
|
cleanupIdleConversations(storeDirFromOptions(options), options);
|
|
1544
1624
|
const terminalConversation = await resolveTerminalConversationFromOptions(options);
|
|
@@ -1587,6 +1667,55 @@ async function runStatus(options) {
|
|
|
1587
1667
|
trace: Boolean(options.trace)
|
|
1588
1668
|
});
|
|
1589
1669
|
}
|
|
1670
|
+
async function runDescribe(options) {
|
|
1671
|
+
cleanupIdleConversations(storeDirFromOptions(options), options);
|
|
1672
|
+
const conversationId = required(options.conversation ?? options.conversationId, "--conversation is required");
|
|
1673
|
+
const terminalConversation = await resolveTerminalConversationFromOptions(options);
|
|
1674
|
+
if (terminalConversation) {
|
|
1675
|
+
const terminalStatus = await terminalStatusForControl(terminalConversation.terminalControl, options);
|
|
1676
|
+
const process = await activeCodexProcessForPid(options, parseTerminalConversationId(conversationId)?.pid);
|
|
1677
|
+
printJson(await describeNativeCodexSession({
|
|
1678
|
+
id: conversationId,
|
|
1679
|
+
source: "terminal_control",
|
|
1680
|
+
process,
|
|
1681
|
+
options,
|
|
1682
|
+
terminalControl: terminalConversation.terminalControl,
|
|
1683
|
+
terminalStatus
|
|
1684
|
+
}));
|
|
1685
|
+
return;
|
|
1686
|
+
}
|
|
1687
|
+
const nativeConversation = parseNativeConversationId(conversationId);
|
|
1688
|
+
if (nativeConversation) {
|
|
1689
|
+
const process = await activeCodexProcessForPid(options, nativeConversation.pid);
|
|
1690
|
+
printJson(await describeNativeCodexSession({
|
|
1691
|
+
id: conversationId,
|
|
1692
|
+
source: "native_active",
|
|
1693
|
+
process,
|
|
1694
|
+
options
|
|
1695
|
+
}));
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
const { conversation, statePath, logPath } = loadConversationFromOptions(options);
|
|
1699
|
+
const events = readExistingEvents(logPath);
|
|
1700
|
+
const terminalControl = terminalControlFromTakeover(isRecord(conversation.native_session_takeover) ? conversation.native_session_takeover : undefined);
|
|
1701
|
+
const terminalStatus = terminalControl ? await terminalStatusForControl(terminalControl, options) : undefined;
|
|
1702
|
+
printJson({
|
|
1703
|
+
conversation_id: conversation.conversation_id,
|
|
1704
|
+
source: "akk_managed",
|
|
1705
|
+
confidence: "high",
|
|
1706
|
+
about: managedConversationAbout(conversation, events, terminalStatus),
|
|
1707
|
+
summary: summarizeConversation(conversation),
|
|
1708
|
+
evidence: {
|
|
1709
|
+
initial_request: conversation.user_request,
|
|
1710
|
+
recent_messages: recentMessageEvidence(events),
|
|
1711
|
+
trace: buildConversationTrace({ conversation, events, logPath }),
|
|
1712
|
+
terminal_screen: terminalStatus?.screen
|
|
1713
|
+
},
|
|
1714
|
+
limitations: terminalStatus?.reachable === false ? ["terminal status unavailable"] : [],
|
|
1715
|
+
state_path: statePath,
|
|
1716
|
+
event_log_path: logPath
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1590
1719
|
async function terminalStatusForControl(terminalControl, options) {
|
|
1591
1720
|
try {
|
|
1592
1721
|
const screen = await createTerminalControlProvider(options).capture(terminalControl.target, {
|
|
@@ -1595,10 +1724,13 @@ async function terminalStatusForControl(terminalControl, options) {
|
|
|
1595
1724
|
});
|
|
1596
1725
|
const approval = detectCodexApprovalPrompt(screen);
|
|
1597
1726
|
const blocked = isCodexApprovalPromptVisible(screen);
|
|
1727
|
+
const activity = detectCodexActivityState(screen, approval);
|
|
1598
1728
|
return {
|
|
1599
1729
|
provider: terminalControl.kind,
|
|
1600
1730
|
target: terminalControl.target,
|
|
1601
1731
|
reachable: true,
|
|
1732
|
+
activity_state: activity.state,
|
|
1733
|
+
activity_reason: activity.reason,
|
|
1602
1734
|
approval_state: {
|
|
1603
1735
|
scanned: true,
|
|
1604
1736
|
blocked,
|
|
@@ -1618,6 +1750,8 @@ async function terminalStatusForControl(terminalControl, options) {
|
|
|
1618
1750
|
provider: terminalControl.kind,
|
|
1619
1751
|
target: terminalControl.target,
|
|
1620
1752
|
reachable: false,
|
|
1753
|
+
activity_state: "unknown",
|
|
1754
|
+
activity_reason: error instanceof Error ? error.message : String(error),
|
|
1621
1755
|
approval_state: {
|
|
1622
1756
|
scanned: false,
|
|
1623
1757
|
blocked: false,
|
|
@@ -3574,6 +3708,168 @@ function summarizeEvent(event) {
|
|
|
3574
3708
|
body: typeof event.body === "string" ? event.body.slice(0, 500) : undefined
|
|
3575
3709
|
};
|
|
3576
3710
|
}
|
|
3711
|
+
async function activeCodexProcessForPid(options, pid) {
|
|
3712
|
+
if (!Number.isInteger(pid)) {
|
|
3713
|
+
return undefined;
|
|
3714
|
+
}
|
|
3715
|
+
const provider = createAgentSessionProvider("codex", options);
|
|
3716
|
+
const activeSessions = await listActiveSessionsWithTerminalControl(provider, options);
|
|
3717
|
+
return activeSessions.find((process) => process.pid === pid);
|
|
3718
|
+
}
|
|
3719
|
+
async function describeNativeCodexSession({ id, source, process, options, terminalControl, terminalStatus }) {
|
|
3720
|
+
const provider = createAgentSessionProvider("codex", options);
|
|
3721
|
+
const directSessionId = process?.sessionId;
|
|
3722
|
+
if (directSessionId) {
|
|
3723
|
+
const context = await provider.getForkContext({
|
|
3724
|
+
sessionId: directSessionId,
|
|
3725
|
+
maxMessages: Number(options.maxMessages ?? 16),
|
|
3726
|
+
maxCommands: Number(options.maxCommands ?? 10),
|
|
3727
|
+
maxTextLength: Number(options.maxTextLength ?? 1200)
|
|
3728
|
+
});
|
|
3729
|
+
if (context) {
|
|
3730
|
+
return nativeDescriptionFromContext({
|
|
3731
|
+
id,
|
|
3732
|
+
source,
|
|
3733
|
+
confidence: "high",
|
|
3734
|
+
match: "session_id",
|
|
3735
|
+
process,
|
|
3736
|
+
context,
|
|
3737
|
+
terminalControl,
|
|
3738
|
+
terminalStatus,
|
|
3739
|
+
limitations: []
|
|
3740
|
+
});
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
const cwd = process?.cwd ?? terminalControl?.currentPath;
|
|
3744
|
+
const sessions = (await provider.listHistoricalSessions())
|
|
3745
|
+
.filter((session) => session.cwd === cwd)
|
|
3746
|
+
.sort((left, right) => Number(right.updatedAtMs ?? 0) - Number(left.updatedAtMs ?? 0));
|
|
3747
|
+
if (sessions.length > 0) {
|
|
3748
|
+
const selected = sessions[0];
|
|
3749
|
+
const context = await provider.getForkContext({
|
|
3750
|
+
sessionId: selected.id,
|
|
3751
|
+
maxMessages: Number(options.maxMessages ?? 16),
|
|
3752
|
+
maxCommands: Number(options.maxCommands ?? 10),
|
|
3753
|
+
maxTextLength: Number(options.maxTextLength ?? 1200)
|
|
3754
|
+
});
|
|
3755
|
+
if (context) {
|
|
3756
|
+
return nativeDescriptionFromContext({
|
|
3757
|
+
id,
|
|
3758
|
+
source,
|
|
3759
|
+
confidence: sessions.length === 1 ? "medium" : "low",
|
|
3760
|
+
match: sessions.length === 1 ? "cwd" : "cwd_latest",
|
|
3761
|
+
process,
|
|
3762
|
+
context,
|
|
3763
|
+
terminalControl,
|
|
3764
|
+
terminalStatus,
|
|
3765
|
+
limitations: sessions.length === 1
|
|
3766
|
+
? ["Codex session inferred from matching cwd because the active process did not expose a session id."]
|
|
3767
|
+
: [`Codex session inferred from the most recent of ${sessions.length} sessions with the same cwd.`],
|
|
3768
|
+
candidates: sessions.slice(0, 5).map((session) => ({
|
|
3769
|
+
session_id: session.id,
|
|
3770
|
+
cwd: session.cwd,
|
|
3771
|
+
title: session.title ?? session.preview ?? session.firstUserMessage,
|
|
3772
|
+
updated_at_ms: session.updatedAtMs,
|
|
3773
|
+
capability: session.capability
|
|
3774
|
+
}))
|
|
3775
|
+
});
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3778
|
+
return {
|
|
3779
|
+
conversation_id: id,
|
|
3780
|
+
source,
|
|
3781
|
+
confidence: "screen_only",
|
|
3782
|
+
match: "terminal_screen",
|
|
3783
|
+
about: screenOnlyAbout({ process, terminalStatus }),
|
|
3784
|
+
evidence: {
|
|
3785
|
+
process,
|
|
3786
|
+
terminal_control: terminalControl,
|
|
3787
|
+
terminal_status: terminalStatus
|
|
3788
|
+
},
|
|
3789
|
+
limitations: [
|
|
3790
|
+
"No exact Codex session id was available.",
|
|
3791
|
+
cwd ? "No matching Codex rollout history was found for this cwd." : "No process cwd was available for Codex history matching.",
|
|
3792
|
+
"Summary is limited to active process metadata and the visible terminal screen."
|
|
3793
|
+
]
|
|
3794
|
+
};
|
|
3795
|
+
}
|
|
3796
|
+
function nativeDescriptionFromContext({ id, source, confidence, match, process, context, terminalControl, terminalStatus, limitations, candidates }) {
|
|
3797
|
+
return {
|
|
3798
|
+
conversation_id: id,
|
|
3799
|
+
source,
|
|
3800
|
+
confidence,
|
|
3801
|
+
match,
|
|
3802
|
+
about: rolloutAbout(context, terminalStatus),
|
|
3803
|
+
codex_session: context.source,
|
|
3804
|
+
evidence: {
|
|
3805
|
+
process,
|
|
3806
|
+
terminal_control: terminalControl,
|
|
3807
|
+
terminal_status: terminalStatus,
|
|
3808
|
+
initial_request: firstUserText(context),
|
|
3809
|
+
title: context.source.title,
|
|
3810
|
+
recent_messages: context.messages.slice(-8),
|
|
3811
|
+
recent_commands: context.commands.slice(-8),
|
|
3812
|
+
candidates
|
|
3813
|
+
},
|
|
3814
|
+
limitations
|
|
3815
|
+
};
|
|
3816
|
+
}
|
|
3817
|
+
function managedConversationAbout(conversation, events, terminalStatus) {
|
|
3818
|
+
const request = truncateText(String(conversation.user_request ?? "").trim(), 220);
|
|
3819
|
+
const recent = recentMessageEvidence(events).at(-1)?.body;
|
|
3820
|
+
const parts = [
|
|
3821
|
+
request ? `Initial request: ${request}` : undefined,
|
|
3822
|
+
recent ? `Latest visible message: ${truncateText(recent, 180)}` : undefined,
|
|
3823
|
+
terminalStatus?.activity_state ? `Current terminal state: ${terminalStatus.activity_state}.` : undefined
|
|
3824
|
+
].filter(Boolean);
|
|
3825
|
+
return parts.length > 0 ? parts.join(" ") : "No durable task content is available for this AKK-managed session.";
|
|
3826
|
+
}
|
|
3827
|
+
function rolloutAbout(context, terminalStatus) {
|
|
3828
|
+
const title = truncateText(String(context.source.title ?? "").trim(), 180);
|
|
3829
|
+
const firstUser = firstUserText(context);
|
|
3830
|
+
const latestAssistant = [...context.messages].reverse().find((message) => message.role === "assistant")?.text;
|
|
3831
|
+
const latestCommand = context.commands.at(-1)?.command;
|
|
3832
|
+
const parts = [
|
|
3833
|
+
firstUser ? `Initial request: ${truncateText(firstUser, 220)}` : title ? `Codex title: ${title}` : undefined,
|
|
3834
|
+
latestAssistant ? `Latest visible progress: ${truncateText(latestAssistant, 180)}` : undefined,
|
|
3835
|
+
latestCommand ? `Recent command: ${truncateText(latestCommand, 140)}` : undefined,
|
|
3836
|
+
terminalStatus?.activity_state ? `Current terminal state: ${terminalStatus.activity_state}.` : undefined
|
|
3837
|
+
].filter(Boolean);
|
|
3838
|
+
return parts.length > 0 ? parts.join(" ") : "Codex history was found, but it did not include enough visible message content to summarize the session.";
|
|
3839
|
+
}
|
|
3840
|
+
function screenOnlyAbout({ process, terminalStatus }) {
|
|
3841
|
+
const activity = terminalStatus?.activity_reason ?? terminalStatus?.activity_state;
|
|
3842
|
+
const excerpt = terminalStatus?.screen?.excerpt;
|
|
3843
|
+
const parts = [
|
|
3844
|
+
process?.cwd ? `This Codex process is running in ${process.cwd}.` : undefined,
|
|
3845
|
+
activity ? `Terminal activity: ${truncateText(String(activity), 180)}` : undefined,
|
|
3846
|
+
excerpt ? `Visible screen: ${truncateText(String(excerpt), 220)}` : undefined
|
|
3847
|
+
].filter(Boolean);
|
|
3848
|
+
return parts.length > 0 ? parts.join(" ") : "Only active process metadata is available; no Codex conversation history or terminal screen content could be read.";
|
|
3849
|
+
}
|
|
3850
|
+
function firstUserText(context) {
|
|
3851
|
+
return context.messages.find((message) => message.role === "user")?.text ?? context.source.title;
|
|
3852
|
+
}
|
|
3853
|
+
function recentMessageEvidence(events) {
|
|
3854
|
+
return events
|
|
3855
|
+
.filter((event) => event.event === "message" && typeof event.body === "string")
|
|
3856
|
+
.slice(-8)
|
|
3857
|
+
.map((event) => ({
|
|
3858
|
+
ts: event.ts,
|
|
3859
|
+
from: event.from,
|
|
3860
|
+
to: event.to,
|
|
3861
|
+
type: event.type,
|
|
3862
|
+
round: event.round,
|
|
3863
|
+
body: truncateText(event.body, 800)
|
|
3864
|
+
}));
|
|
3865
|
+
}
|
|
3866
|
+
function truncateText(value, maxLength) {
|
|
3867
|
+
const text = String(value ?? "").replace(/\s+/g, " ").trim();
|
|
3868
|
+
if (text.length <= maxLength) {
|
|
3869
|
+
return text;
|
|
3870
|
+
}
|
|
3871
|
+
return `${text.slice(0, Math.max(0, maxLength - 3))}...`;
|
|
3872
|
+
}
|
|
3577
3873
|
function buildConversationTrace({ conversation, events, logPath }) {
|
|
3578
3874
|
const outputPath = traceOutputPath({ conversation, events, logPath });
|
|
3579
3875
|
const output = outputPath && fs.existsSync(outputPath)
|
|
@@ -4581,6 +4877,7 @@ function usage() {
|
|
|
4581
4877
|
agent-knock-knock delegate --request <text> [--agent ${agentList}] [--store-dir <dir>] [--all-proxy <url>] [--agent-timeout-minutes <minutes>] [--token <gateway-token>] [--send|--background]
|
|
4582
4878
|
agent-knock-knock list [--store-dir <dir>] [--agent ${agentList}] [--status <status>] [--all] [--managed-only] [--no-approval-scan] [--terminal-debug]
|
|
4583
4879
|
agent-knock-knock status --conversation <id> [--store-dir <dir>] [--trace]
|
|
4880
|
+
agent-knock-knock describe --conversation <id> [--store-dir <dir>]
|
|
4584
4881
|
agent-knock-knock send --conversation <id> --message <text> [--type answer|task|control] [--all-proxy <url>] [--agent-timeout-minutes <minutes>]
|
|
4585
4882
|
agent-knock-knock approve --conversation <id>
|
|
4586
4883
|
agent-knock-knock cancel --conversation <id> [--all-proxy <url>]
|