@scotthuang/agent-knock-knock 0.2.21 → 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 CHANGED
@@ -1,5 +1,13 @@
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
+
3
11
  ## 0.2.21 - 2026-07-03
4
12
 
5
13
  ### 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>]