@scotthuang/agent-knock-knock 0.2.21 → 0.2.23
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 +15 -0
- package/README.md +2 -0
- package/dist/src/cli.js +230 -0
- 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/openclaw.plugin.json +5 -1
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.23 - 2026-07-04
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Declared `agent_knock_knock_describe` in the OpenClaw plugin contract and metadata so OpenClaw exposes the new describe tool.
|
|
8
|
+
- Updated the installed AKK skill routing rules to use `agent_knock_knock_describe` for session-content summary requests instead of falling back to direct terminal inspection.
|
|
9
|
+
|
|
10
|
+
## 0.2.22 - 2026-07-04
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Added `AKK describe` / `AKK summary` to summarize what an AKK-managed, native Codex, or terminal-controlled Codex session is about.
|
|
15
|
+
- Added the `agent_knock_knock_describe` OpenClaw tool and `/akk describe <conversation-id>` command.
|
|
16
|
+
- 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.
|
|
17
|
+
|
|
3
18
|
## 0.2.21 - 2026-07-03
|
|
4
19
|
|
|
5
20
|
### Added
|
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
|
}
|
|
@@ -1601,6 +1604,21 @@ function parseTerminalConversationId(conversationId) {
|
|
|
1601
1604
|
pid
|
|
1602
1605
|
};
|
|
1603
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
|
+
}
|
|
1604
1622
|
async function runStatus(options) {
|
|
1605
1623
|
cleanupIdleConversations(storeDirFromOptions(options), options);
|
|
1606
1624
|
const terminalConversation = await resolveTerminalConversationFromOptions(options);
|
|
@@ -1649,6 +1667,55 @@ async function runStatus(options) {
|
|
|
1649
1667
|
trace: Boolean(options.trace)
|
|
1650
1668
|
});
|
|
1651
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
|
+
}
|
|
1652
1719
|
async function terminalStatusForControl(terminalControl, options) {
|
|
1653
1720
|
try {
|
|
1654
1721
|
const screen = await createTerminalControlProvider(options).capture(terminalControl.target, {
|
|
@@ -3641,6 +3708,168 @@ function summarizeEvent(event) {
|
|
|
3641
3708
|
body: typeof event.body === "string" ? event.body.slice(0, 500) : undefined
|
|
3642
3709
|
};
|
|
3643
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
|
+
}
|
|
3644
3873
|
function buildConversationTrace({ conversation, events, logPath }) {
|
|
3645
3874
|
const outputPath = traceOutputPath({ conversation, events, logPath });
|
|
3646
3875
|
const output = outputPath && fs.existsSync(outputPath)
|
|
@@ -4648,6 +4877,7 @@ function usage() {
|
|
|
4648
4877
|
agent-knock-knock delegate --request <text> [--agent ${agentList}] [--store-dir <dir>] [--all-proxy <url>] [--agent-timeout-minutes <minutes>] [--token <gateway-token>] [--send|--background]
|
|
4649
4878
|
agent-knock-knock list [--store-dir <dir>] [--agent ${agentList}] [--status <status>] [--all] [--managed-only] [--no-approval-scan] [--terminal-debug]
|
|
4650
4879
|
agent-knock-knock status --conversation <id> [--store-dir <dir>] [--trace]
|
|
4880
|
+
agent-knock-knock describe --conversation <id> [--store-dir <dir>]
|
|
4651
4881
|
agent-knock-knock send --conversation <id> --message <text> [--type answer|task|control] [--all-proxy <url>] [--agent-timeout-minutes <minutes>]
|
|
4652
4882
|
agent-knock-knock approve --conversation <id>
|
|
4653
4883
|
agent-knock-knock cancel --conversation <id> [--all-proxy <url>]
|