@scotthuang/agent-knock-knock 0.6.0 → 0.6.2
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 +21 -0
- package/README.md +2 -0
- package/dist/src/cli.js +294 -11
- package/dist/src/cli.js.map +1 -1
- package/dist/src/codex-terminal-agent-adapter.js +40 -15
- package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin.js +3 -3
- package/dist/src/openclaw-plugin.js.map +1 -1
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +6 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.2 - 2026-07-31
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Publish `AKK list` action contract v2 with machine-readable field semantics that distinguish managed task lifecycle, terminal process liveness, parsed screen activity, deprecated compatibility flags, and the authoritative `available_actions` snapshot.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Recognize both current `»` and legacy `›` Codex composer markers across idle detection, approval parsing, stale-prompt rejection, completion boundaries, and terminal artifact cleanup while preserving fail-closed working and approval precedence.
|
|
12
|
+
|
|
13
|
+
## 0.6.1 - 2026-07-30
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Return per-session `available_actions` and a versioned action contract from `AKK list`, including exact tool names, authoritative target arguments, fresh-status approval prerequisites, and recovery message IDs when currently available.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Exclude unsupported and retired ACPX executor records from session discovery so historical Cursor or pre-tmux state cannot break valid Codex and Claude Code routing, while active legacy ownership still fences its pane against double dispatch.
|
|
22
|
+
- Keep idle cleanup logging safe when it closes an unsupported legacy executor record.
|
|
23
|
+
|
|
3
24
|
## 0.6.0 - 2026-07-30
|
|
4
25
|
|
|
5
26
|
### Changed
|
package/README.md
CHANGED
|
@@ -197,6 +197,8 @@ The core command surface is intentionally small:
|
|
|
197
197
|
|
|
198
198
|
Selectors fail closed: `only` works only with one actionable target, `latest` requires a unique newest target, and `codex` or `claude` must identify exactly one eligible pane. `AKK list` shows stable short references while JSON output retains the authoritative full IDs. Before every terminal operation, AKK revalidates the expected agent PID and tmux pane identity, then confirms that the process and pane working directories still match; every send also revalidates the idle prompt immediately before typing.
|
|
199
199
|
|
|
200
|
+
For natural-language tool use, `agent_knock_knock_list` returns an `available_actions` object on rows in `delegated[]` and `terminal_controlled[]`; `tasks[]` remains a compatibility summary. Use only an action shown there, start with its prefilled authoritative arguments, and supply every `missing_required` field. A delegated row's `status` is its task lifecycle, while a terminal-controlled row's `status` only says whether the coding-agent process is alive and its `activity_state` reports the parsed screen state. The legacy `commands` flags have mixed compatibility semantics, are deprecated, and must never drive tool calls; `available_actions` is the only authoritative current-action source. `send` uses `selector`; status, approval, cancellation, renewal, callback retry, and close use `conversation_id`. For an ordinary send, add only `request`—`timeoutSeconds` is not a supported argument, and monitoring limits should be omitted unless the user explicitly asks to change them.
|
|
201
|
+
|
|
200
202
|
Workspace is not a routing boundary. AKK can list, inspect, and control verified panes across projects; when more than one target matches, use a selector to choose one explicitly.
|
|
201
203
|
|
|
202
204
|
If no eligible pane exists, AKK stops with setup guidance. If a send is ambiguous, run `/akk list` and retry with the returned `@short-ref`.
|
package/dist/src/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import { captureClaudeTranscriptAnchor, defaultClaudeHome, detectClaudeTranscrip
|
|
|
11
11
|
import { CodexLocalSessionProvider } from "./codex-local-session-provider.js";
|
|
12
12
|
import { CodexStoreAdapter } from "./codex-store-adapter.js";
|
|
13
13
|
import { applyMessageToConversation, budgetAction, createConversation, createMessage, executorForConversation, extractStructuredMessage, parseMessageJson, resolveExecutor } from "./protocol.js";
|
|
14
|
-
import { EXECUTOR_KINDS, executorDefinitionForKind } from "./executors.js";
|
|
14
|
+
import { EXECUTOR_KINDS, executorDefinitionForKind, isExecutorKind } from "./executors.js";
|
|
15
15
|
import { redactString, writeRuntimeLog } from "./runtime-log.js";
|
|
16
16
|
import { formatTranscript, readNdjsonLog } from "./transcript.js";
|
|
17
17
|
import { appendEvent, defaultStoreDir, ensureDir, listConversations, logPathForStatePath, loadConversationById, loadState, messageEvent, pathsForConversation, pathsForConversationDir, saveState, statePathForConversationId } from "./store.js";
|
|
@@ -1331,16 +1331,23 @@ async function runList(options) {
|
|
|
1331
1331
|
const includeAll = Boolean(options.all);
|
|
1332
1332
|
const agentFilter = options.agent ? resolveExecutor({ kind: options.agent }).kind : undefined;
|
|
1333
1333
|
const statusFilter = options.status;
|
|
1334
|
-
const
|
|
1334
|
+
const allStoredConversations = listConversations(storeDir);
|
|
1335
|
+
const storedConversations = allStoredConversations
|
|
1336
|
+
.filter(isDiscoverableTmuxConversation)
|
|
1335
1337
|
.filter((conversation) => includeAll || isActiveStatus(conversation.status))
|
|
1336
1338
|
.filter((conversation) => matchesConfiguredWorkspace(options.workspace, conversation.workspace))
|
|
1337
1339
|
.filter((conversation) => !agentFilter || executorForConversation(conversation).kind === agentFilter)
|
|
1338
1340
|
.filter((conversation) => !statusFilter || conversation.status === statusFilter);
|
|
1339
1341
|
const conversations = storedConversations.map((conversation) => summarizeConversation(conversation));
|
|
1340
|
-
const delegated = storedConversations.map((conversation) => delegatedListEntry(summarizeConversation(conversation), {
|
|
1342
|
+
const delegated = storedConversations.map((conversation) => delegatedListEntry(summarizeConversation(conversation), {
|
|
1343
|
+
terminalBridge: terminalBridgeEnabled(conversation),
|
|
1344
|
+
approvalState: managedListApprovalState(conversation),
|
|
1345
|
+
conversation
|
|
1346
|
+
}));
|
|
1341
1347
|
const terminalScan = await buildTerminalListGroup({ options, agentFilter, statusFilter });
|
|
1342
|
-
const managedTerminalKeys = new Set(
|
|
1348
|
+
const managedTerminalKeys = new Set(allStoredConversations
|
|
1343
1349
|
.filter((conversation) => isActiveStatus(conversation.status))
|
|
1350
|
+
.filter((conversation) => matchesConfiguredWorkspace(options.workspace, conversation.workspace))
|
|
1344
1351
|
.map((conversation) => terminalControlSelectorKey(terminalControlFromTakeover(isRecord(conversation.native_session_takeover)
|
|
1345
1352
|
? conversation.native_session_takeover
|
|
1346
1353
|
: undefined)))
|
|
@@ -1355,6 +1362,7 @@ async function runList(options) {
|
|
|
1355
1362
|
printJson({
|
|
1356
1363
|
store_dir: storeDir,
|
|
1357
1364
|
cleanup,
|
|
1365
|
+
action_contracts: listActionContracts(),
|
|
1358
1366
|
delegated,
|
|
1359
1367
|
terminal_controlled: terminalControlled,
|
|
1360
1368
|
terminal_scan: {
|
|
@@ -1454,12 +1462,13 @@ async function terminalControlDiagnostics(provider) {
|
|
|
1454
1462
|
paneCount: (await provider.listPanes()).length
|
|
1455
1463
|
};
|
|
1456
1464
|
}
|
|
1457
|
-
function delegatedListEntry(task, { terminalBridge = false } = {}) {
|
|
1458
|
-
|
|
1465
|
+
function delegatedListEntry(task, { terminalBridge = false, approvalState, conversation } = {}) {
|
|
1466
|
+
const entry = {
|
|
1459
1467
|
...task,
|
|
1460
1468
|
id: task.conversation_id,
|
|
1461
1469
|
short_ref: sessionShortRef(task.conversation_id),
|
|
1462
1470
|
source: "akk_delegate",
|
|
1471
|
+
...(approvalState ? { approval_state: approvalState } : {}),
|
|
1463
1472
|
commands: {
|
|
1464
1473
|
send: canSendDelegated(task.status),
|
|
1465
1474
|
cancel: isWaitingForAgent(task.status),
|
|
@@ -1468,6 +1477,10 @@ function delegatedListEntry(task, { terminalBridge = false } = {}) {
|
|
|
1468
1477
|
approve: terminalBridge && isActiveStatus(task.status)
|
|
1469
1478
|
}
|
|
1470
1479
|
};
|
|
1480
|
+
return {
|
|
1481
|
+
...entry,
|
|
1482
|
+
available_actions: availableListActions(entry, { conversation })
|
|
1483
|
+
};
|
|
1471
1484
|
}
|
|
1472
1485
|
async function terminalControlledListEntry(session, activeSessions, options, bridge = createTerminalAgentBridge(options)) {
|
|
1473
1486
|
const terminalControl = session.terminalControl;
|
|
@@ -1481,7 +1494,7 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
|
|
|
1481
1494
|
terminalTarget: terminalControl.target
|
|
1482
1495
|
});
|
|
1483
1496
|
const orphanedDispatch = orphanedTerminalDispatchForRecovery(terminalControl);
|
|
1484
|
-
|
|
1497
|
+
const entry = {
|
|
1485
1498
|
id: bridge.terminalConversationId(session),
|
|
1486
1499
|
short_ref: sessionShortRef(bridge.terminalConversationId(session)),
|
|
1487
1500
|
source: "terminal_control",
|
|
@@ -1520,6 +1533,10 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
|
|
|
1520
1533
|
close: orphanedDispatch !== undefined
|
|
1521
1534
|
}
|
|
1522
1535
|
};
|
|
1536
|
+
return {
|
|
1537
|
+
...entry,
|
|
1538
|
+
available_actions: availableListActions(entry)
|
|
1539
|
+
};
|
|
1523
1540
|
}
|
|
1524
1541
|
async function listStateForTerminal(agent, terminalControl, options, bridge = createTerminalAgentBridge(options), runtime) {
|
|
1525
1542
|
if (options.noApprovalScan) {
|
|
@@ -1586,7 +1603,239 @@ function childPidsForRoot(root, processes) {
|
|
|
1586
1603
|
.map((process) => process.pid);
|
|
1587
1604
|
}
|
|
1588
1605
|
function canSendDelegated(status) {
|
|
1589
|
-
return !["failed", "closed", "cancelled"].includes(status);
|
|
1606
|
+
return !["done", "failed", "closed", "cancelled"].includes(status);
|
|
1607
|
+
}
|
|
1608
|
+
function managedListApprovalState(conversation) {
|
|
1609
|
+
if (!terminalBridgeEnabled(conversation) ||
|
|
1610
|
+
!["waiting_for_agent", "waiting_for_openclaw"].includes(String(conversation.status))) {
|
|
1611
|
+
return undefined;
|
|
1612
|
+
}
|
|
1613
|
+
const nativeTakeover = isRecord(conversation.native_session_takeover)
|
|
1614
|
+
? conversation.native_session_takeover
|
|
1615
|
+
: undefined;
|
|
1616
|
+
if (!terminalControlFromTakeover(nativeTakeover) ||
|
|
1617
|
+
!stringValue(nativeTakeover?.terminal_bridge_message_id)) {
|
|
1618
|
+
return undefined;
|
|
1619
|
+
}
|
|
1620
|
+
const approval = isRecord(nativeTakeover?.terminal_bridge_approval)
|
|
1621
|
+
? nativeTakeover.terminal_bridge_approval
|
|
1622
|
+
: undefined;
|
|
1623
|
+
const approvalState = isRecord(approval?.approval_state)
|
|
1624
|
+
? approval.approval_state
|
|
1625
|
+
: undefined;
|
|
1626
|
+
const fingerprint = stringValue(approval?.fingerprint);
|
|
1627
|
+
const notifiedAt = stringValue(approval?.notified_at);
|
|
1628
|
+
const notifiedAtMs = validTimestampMs(notifiedAt);
|
|
1629
|
+
if (!approvalState ||
|
|
1630
|
+
!fingerprint ||
|
|
1631
|
+
notifiedAtMs === undefined ||
|
|
1632
|
+
Date.now() - notifiedAtMs > CLAUDE_SCREEN_APPROVAL_TTL_MS) {
|
|
1633
|
+
return undefined;
|
|
1634
|
+
}
|
|
1635
|
+
return {
|
|
1636
|
+
...approvalState,
|
|
1637
|
+
fingerprint,
|
|
1638
|
+
notified_at: notifiedAt
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
function listActionContracts() {
|
|
1642
|
+
return {
|
|
1643
|
+
version: 2,
|
|
1644
|
+
instructions: [
|
|
1645
|
+
"Use only actions present in delegated[].available_actions or terminal_controlled[].available_actions.",
|
|
1646
|
+
"Never use commands for routing or tool calls. It is a deprecated, non-authoritative compatibility field with mixed legacy semantics.",
|
|
1647
|
+
"Start with the action's prefilled arguments, supply every missing_required field, and consult the top-level action's optional fields only when needed.",
|
|
1648
|
+
"Authoritative full IDs are prefilled; short_ref is for display and human input.",
|
|
1649
|
+
"Availability is a snapshot. AKK revalidates process, tmux pane, workspace, activity, approval, and recovery state before side effects."
|
|
1650
|
+
],
|
|
1651
|
+
field_semantics: {
|
|
1652
|
+
status: {
|
|
1653
|
+
delegated: "task_lifecycle",
|
|
1654
|
+
terminal_controlled: "process_liveness",
|
|
1655
|
+
authoritative_for_tool_calls: false
|
|
1656
|
+
},
|
|
1657
|
+
activity_state: {
|
|
1658
|
+
terminal_controlled: "screen_activity_classification",
|
|
1659
|
+
authoritative_for_tool_calls: false
|
|
1660
|
+
},
|
|
1661
|
+
commands: {
|
|
1662
|
+
meaning: "legacy_compatibility_flags_with_mixed_semantics",
|
|
1663
|
+
deprecated: true,
|
|
1664
|
+
authoritative_for_tool_calls: false
|
|
1665
|
+
},
|
|
1666
|
+
available_actions: {
|
|
1667
|
+
meaning: "currently_safe_actions",
|
|
1668
|
+
authoritative_for_tool_calls: true
|
|
1669
|
+
}
|
|
1670
|
+
},
|
|
1671
|
+
actions: {
|
|
1672
|
+
send: {
|
|
1673
|
+
tool: "agent_knock_knock_send",
|
|
1674
|
+
target_argument: "selector",
|
|
1675
|
+
required: ["request"],
|
|
1676
|
+
optional: [
|
|
1677
|
+
"selector",
|
|
1678
|
+
"type",
|
|
1679
|
+
"idleTimeoutMinutes",
|
|
1680
|
+
"agentTimeoutMinutes",
|
|
1681
|
+
"agentHardTimeoutMinutes"
|
|
1682
|
+
],
|
|
1683
|
+
unsupported: ["timeoutSeconds"],
|
|
1684
|
+
ordinary_use: "Add request only. Omit timeout fields unless the user explicitly asks to change monitoring limits."
|
|
1685
|
+
},
|
|
1686
|
+
status: {
|
|
1687
|
+
tool: "agent_knock_knock_status",
|
|
1688
|
+
target_argument: "conversation_id",
|
|
1689
|
+
required: ["conversation_id"],
|
|
1690
|
+
optional: ["idleTimeoutMinutes", "trace"]
|
|
1691
|
+
},
|
|
1692
|
+
approve: {
|
|
1693
|
+
tool: "agent_knock_knock_approve",
|
|
1694
|
+
target_argument: "conversation_id",
|
|
1695
|
+
required: ["conversation_id", "expected_approval_fingerprint"],
|
|
1696
|
+
requires_explicit_user_confirmation: true,
|
|
1697
|
+
requires_fresh_status: true
|
|
1698
|
+
},
|
|
1699
|
+
cancel: {
|
|
1700
|
+
tool: "agent_knock_knock_cancel",
|
|
1701
|
+
target_argument: "conversation_id",
|
|
1702
|
+
required: ["conversation_id"],
|
|
1703
|
+
optional: ["idleTimeoutMinutes"],
|
|
1704
|
+
requires_user_intent: true
|
|
1705
|
+
},
|
|
1706
|
+
renew: {
|
|
1707
|
+
tool: "agent_knock_knock_renew",
|
|
1708
|
+
target_argument: "conversation_id",
|
|
1709
|
+
required: ["conversation_id"],
|
|
1710
|
+
optional: ["minutes"]
|
|
1711
|
+
},
|
|
1712
|
+
retry_callback: {
|
|
1713
|
+
tool: "agent_knock_knock_retry_callback",
|
|
1714
|
+
target_argument: "conversation_id",
|
|
1715
|
+
required: ["conversation_id"]
|
|
1716
|
+
},
|
|
1717
|
+
close: {
|
|
1718
|
+
tool: "agent_knock_knock_close",
|
|
1719
|
+
target_argument: "conversation_id",
|
|
1720
|
+
required: ["conversation_id"],
|
|
1721
|
+
optional: ["reason", "expected_message_id"],
|
|
1722
|
+
requires_explicit_user_confirmation: true
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
function availableListActions(entry, { conversation } = {}) {
|
|
1728
|
+
const id = stringValue(entry.id ?? entry.conversation_id);
|
|
1729
|
+
if (!id) {
|
|
1730
|
+
return {};
|
|
1731
|
+
}
|
|
1732
|
+
const commands = isRecord(entry.commands) ? entry.commands : {};
|
|
1733
|
+
const actions = {
|
|
1734
|
+
status: {
|
|
1735
|
+
tool: "agent_knock_knock_status",
|
|
1736
|
+
arguments: { conversation_id: id }
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
const terminalControlled = entry.source === "terminal_control";
|
|
1740
|
+
const managed = entry.source === "akk_delegate";
|
|
1741
|
+
const approvalState = isRecord(entry.approval_state)
|
|
1742
|
+
? entry.approval_state
|
|
1743
|
+
: {};
|
|
1744
|
+
const nativeTakeover = isRecord(conversation?.native_session_takeover)
|
|
1745
|
+
? conversation.native_session_takeover
|
|
1746
|
+
: undefined;
|
|
1747
|
+
const managedApprovalPending = isRecord(nativeTakeover?.terminal_bridge_approval);
|
|
1748
|
+
const terminalBridgeReady = managed &&
|
|
1749
|
+
terminalBridgeEnabled(conversation) &&
|
|
1750
|
+
terminalControlFromTakeover(nativeTakeover) !== undefined;
|
|
1751
|
+
if (commands.send === true &&
|
|
1752
|
+
((terminalBridgeReady &&
|
|
1753
|
+
["waiting_for_openclaw", "idle"].includes(String(entry.status)) &&
|
|
1754
|
+
!managedApprovalPending &&
|
|
1755
|
+
approvalState.blocked !== true) ||
|
|
1756
|
+
(terminalControlled &&
|
|
1757
|
+
entry.activity_state === "idle" &&
|
|
1758
|
+
approvalState.blocked !== true))) {
|
|
1759
|
+
actions.send = {
|
|
1760
|
+
tool: "agent_knock_knock_send",
|
|
1761
|
+
arguments: { selector: id },
|
|
1762
|
+
missing_required: ["request"]
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
const approvalFingerprint = stringValue(approvalState.fingerprint);
|
|
1766
|
+
const managedApprovalEligible = terminalBridgeReady &&
|
|
1767
|
+
entry.status === "waiting_for_openclaw" &&
|
|
1768
|
+
(entry.agent !== "claude" ||
|
|
1769
|
+
approvalState.decision_mode === "keys");
|
|
1770
|
+
if (commands.approve === true &&
|
|
1771
|
+
approvalState.approvable === true &&
|
|
1772
|
+
approvalFingerprint &&
|
|
1773
|
+
((terminalControlled &&
|
|
1774
|
+
entry.agent === "codex") ||
|
|
1775
|
+
managedApprovalEligible)) {
|
|
1776
|
+
actions.approve = {
|
|
1777
|
+
tool: "agent_knock_knock_approve",
|
|
1778
|
+
arguments: { conversation_id: id },
|
|
1779
|
+
missing_required: ["expected_approval_fingerprint"],
|
|
1780
|
+
before_call: {
|
|
1781
|
+
tool: "agent_knock_knock_status",
|
|
1782
|
+
arguments: { conversation_id: id },
|
|
1783
|
+
use: "After explicit user confirmation, copy the latest terminal_status.approval_state.fingerprint into expected_approval_fingerprint."
|
|
1784
|
+
},
|
|
1785
|
+
requires_explicit_user_confirmation: true,
|
|
1786
|
+
requires_fresh_status: true
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
const rawCancellable = terminalControlled &&
|
|
1790
|
+
commands.cancel === true &&
|
|
1791
|
+
(entry.activity_state === "working" ||
|
|
1792
|
+
(approvalState.blocked === true &&
|
|
1793
|
+
approvalState.approvable === true));
|
|
1794
|
+
const managedCancellable = terminalBridgeReady &&
|
|
1795
|
+
["waiting_for_agent", "waiting_for_openclaw"].includes(String(entry.status)) &&
|
|
1796
|
+
!(managedApprovalPending &&
|
|
1797
|
+
approvalState.approvable !== true);
|
|
1798
|
+
if (rawCancellable || managedCancellable) {
|
|
1799
|
+
actions.cancel = {
|
|
1800
|
+
tool: "agent_knock_knock_cancel",
|
|
1801
|
+
arguments: { conversation_id: id },
|
|
1802
|
+
requires_user_intent: true
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
if (terminalBridgeReady && entry.status === "stalled") {
|
|
1806
|
+
actions.renew = {
|
|
1807
|
+
tool: "agent_knock_knock_renew",
|
|
1808
|
+
arguments: { conversation_id: id }
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
const callbackDelivery = isRecord(conversation?.callback_delivery)
|
|
1812
|
+
? conversation.callback_delivery
|
|
1813
|
+
: undefined;
|
|
1814
|
+
if (managed &&
|
|
1815
|
+
isRecord(conversation) &&
|
|
1816
|
+
isRetryableCallbackDelivery(conversation, callbackDelivery)) {
|
|
1817
|
+
actions.retry_callback = {
|
|
1818
|
+
tool: "agent_knock_knock_retry_callback",
|
|
1819
|
+
arguments: { conversation_id: id }
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
if (commands.close === true) {
|
|
1823
|
+
const orphanedDispatch = isRecord(entry.orphaned_terminal_dispatch)
|
|
1824
|
+
? entry.orphaned_terminal_dispatch
|
|
1825
|
+
: undefined;
|
|
1826
|
+
const expectedMessageId = stringValue(orphanedDispatch?.message_id);
|
|
1827
|
+
actions.close = {
|
|
1828
|
+
tool: "agent_knock_knock_close",
|
|
1829
|
+
arguments: {
|
|
1830
|
+
conversation_id: id,
|
|
1831
|
+
...(expectedMessageId
|
|
1832
|
+
? { expected_message_id: expectedMessageId }
|
|
1833
|
+
: {})
|
|
1834
|
+
},
|
|
1835
|
+
requires_explicit_user_confirmation: true
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
return actions;
|
|
1590
1839
|
}
|
|
1591
1840
|
async function resolveConversationSelectorOption(commandName, options) {
|
|
1592
1841
|
if (!SESSION_SELECTOR_COMMANDS.has(String(commandName ?? "")) ||
|
|
@@ -1617,7 +1866,13 @@ async function sessionSelectorCandidates(commandName, options) {
|
|
|
1617
1866
|
const storedConversations = listConversations(storeDir);
|
|
1618
1867
|
const workspaceConversations = storedConversations
|
|
1619
1868
|
.filter((conversation) => matchesConfiguredWorkspace(options.workspace, conversation.workspace));
|
|
1620
|
-
const
|
|
1869
|
+
const discoverableWorkspaceConversations = workspaceConversations
|
|
1870
|
+
.filter(isDiscoverableTmuxConversation);
|
|
1871
|
+
const managed = discoverableWorkspaceConversations.map((conversation) => delegatedListEntry(summarizeConversation(conversation), {
|
|
1872
|
+
terminalBridge: terminalBridgeEnabled(conversation),
|
|
1873
|
+
approvalState: managedListApprovalState(conversation),
|
|
1874
|
+
conversation
|
|
1875
|
+
}));
|
|
1621
1876
|
const managedTerminalKeys = new Set(workspaceConversations
|
|
1622
1877
|
.filter((conversation) => isActiveStatus(conversation.status))
|
|
1623
1878
|
.map((conversation) => terminalControlSelectorKey(terminalControlFromTakeover(isRecord(conversation.native_session_takeover)
|
|
@@ -7370,6 +7625,34 @@ function summarizeConversation(conversation) {
|
|
|
7370
7625
|
event_log_path: conversation.event_log_path
|
|
7371
7626
|
};
|
|
7372
7627
|
}
|
|
7628
|
+
function isDiscoverableTmuxConversation(conversation) {
|
|
7629
|
+
if (!isRecord(conversation)) {
|
|
7630
|
+
return false;
|
|
7631
|
+
}
|
|
7632
|
+
if (!isRecord(conversation.executor)) {
|
|
7633
|
+
return false;
|
|
7634
|
+
}
|
|
7635
|
+
const kind = stringValue(conversation.executor.kind)?.toLowerCase();
|
|
7636
|
+
return (kind !== undefined &&
|
|
7637
|
+
isExecutorKind(kind) &&
|
|
7638
|
+
conversation.executor.transport === "tmux");
|
|
7639
|
+
}
|
|
7640
|
+
function persistedExecutorLogFields(conversation) {
|
|
7641
|
+
if (isDiscoverableTmuxConversation(conversation)) {
|
|
7642
|
+
const executor = executorForConversation(conversation);
|
|
7643
|
+
return {
|
|
7644
|
+
agent: executor.kind,
|
|
7645
|
+
executor_session: executor.session
|
|
7646
|
+
};
|
|
7647
|
+
}
|
|
7648
|
+
const rawExecutor = isRecord(conversation?.executor)
|
|
7649
|
+
? conversation.executor
|
|
7650
|
+
: {};
|
|
7651
|
+
return {
|
|
7652
|
+
agent: stringValue(rawExecutor.kind) ?? "unsupported",
|
|
7653
|
+
executor_session: stringValue(rawExecutor.session)
|
|
7654
|
+
};
|
|
7655
|
+
}
|
|
7373
7656
|
function summarizeEvent(event) {
|
|
7374
7657
|
return {
|
|
7375
7658
|
ts: event.ts,
|
|
@@ -7986,10 +8269,10 @@ function cleanupIdleConversations(storeDir, options = {}, now = new Date()) {
|
|
|
7986
8269
|
idle_timeout_minutes: timeoutMinutes,
|
|
7987
8270
|
terminal_bridge: terminalBridge
|
|
7988
8271
|
});
|
|
8272
|
+
const executorLogFields = persistedExecutorLogFields(conversation);
|
|
7989
8273
|
runtimeLog("info", "idle_conversation_closed", {
|
|
7990
8274
|
conversation_id: conversation.conversation_id,
|
|
7991
|
-
|
|
7992
|
-
executor_session: executorForConversation(conversation).session,
|
|
8275
|
+
...executorLogFields,
|
|
7993
8276
|
state_path: statePath,
|
|
7994
8277
|
event_log_path: logPath,
|
|
7995
8278
|
idle_since: conversation.idle_since,
|