@scotthuang/agent-knock-knock 0.6.0 → 0.6.1
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 +11 -0
- package/README.md +2 -0
- package/dist/src/cli.js +273 -11
- package/dist/src/cli.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,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.1 - 2026-07-30
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- 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.
|
|
12
|
+
- Keep idle cleanup logging safe when it closes an unsupported legacy executor record.
|
|
13
|
+
|
|
3
14
|
## 0.6.0 - 2026-07-30
|
|
4
15
|
|
|
5
16
|
### 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. `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,218 @@ 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: 1,
|
|
1644
|
+
instructions: [
|
|
1645
|
+
"Use only actions present in delegated[].available_actions or terminal_controlled[].available_actions.",
|
|
1646
|
+
"Start with the action's prefilled arguments, supply every missing_required field, and consult the top-level action's optional fields only when needed.",
|
|
1647
|
+
"Authoritative full IDs are prefilled; short_ref is for display and human input.",
|
|
1648
|
+
"Availability is a snapshot. AKK revalidates process, tmux pane, workspace, activity, approval, and recovery state before side effects."
|
|
1649
|
+
],
|
|
1650
|
+
actions: {
|
|
1651
|
+
send: {
|
|
1652
|
+
tool: "agent_knock_knock_send",
|
|
1653
|
+
target_argument: "selector",
|
|
1654
|
+
required: ["request"],
|
|
1655
|
+
optional: [
|
|
1656
|
+
"selector",
|
|
1657
|
+
"type",
|
|
1658
|
+
"idleTimeoutMinutes",
|
|
1659
|
+
"agentTimeoutMinutes",
|
|
1660
|
+
"agentHardTimeoutMinutes"
|
|
1661
|
+
],
|
|
1662
|
+
unsupported: ["timeoutSeconds"],
|
|
1663
|
+
ordinary_use: "Add request only. Omit timeout fields unless the user explicitly asks to change monitoring limits."
|
|
1664
|
+
},
|
|
1665
|
+
status: {
|
|
1666
|
+
tool: "agent_knock_knock_status",
|
|
1667
|
+
target_argument: "conversation_id",
|
|
1668
|
+
required: ["conversation_id"],
|
|
1669
|
+
optional: ["idleTimeoutMinutes", "trace"]
|
|
1670
|
+
},
|
|
1671
|
+
approve: {
|
|
1672
|
+
tool: "agent_knock_knock_approve",
|
|
1673
|
+
target_argument: "conversation_id",
|
|
1674
|
+
required: ["conversation_id", "expected_approval_fingerprint"],
|
|
1675
|
+
requires_explicit_user_confirmation: true,
|
|
1676
|
+
requires_fresh_status: true
|
|
1677
|
+
},
|
|
1678
|
+
cancel: {
|
|
1679
|
+
tool: "agent_knock_knock_cancel",
|
|
1680
|
+
target_argument: "conversation_id",
|
|
1681
|
+
required: ["conversation_id"],
|
|
1682
|
+
optional: ["idleTimeoutMinutes"],
|
|
1683
|
+
requires_user_intent: true
|
|
1684
|
+
},
|
|
1685
|
+
renew: {
|
|
1686
|
+
tool: "agent_knock_knock_renew",
|
|
1687
|
+
target_argument: "conversation_id",
|
|
1688
|
+
required: ["conversation_id"],
|
|
1689
|
+
optional: ["minutes"]
|
|
1690
|
+
},
|
|
1691
|
+
retry_callback: {
|
|
1692
|
+
tool: "agent_knock_knock_retry_callback",
|
|
1693
|
+
target_argument: "conversation_id",
|
|
1694
|
+
required: ["conversation_id"]
|
|
1695
|
+
},
|
|
1696
|
+
close: {
|
|
1697
|
+
tool: "agent_knock_knock_close",
|
|
1698
|
+
target_argument: "conversation_id",
|
|
1699
|
+
required: ["conversation_id"],
|
|
1700
|
+
optional: ["reason", "expected_message_id"],
|
|
1701
|
+
requires_explicit_user_confirmation: true
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
function availableListActions(entry, { conversation } = {}) {
|
|
1707
|
+
const id = stringValue(entry.id ?? entry.conversation_id);
|
|
1708
|
+
if (!id) {
|
|
1709
|
+
return {};
|
|
1710
|
+
}
|
|
1711
|
+
const commands = isRecord(entry.commands) ? entry.commands : {};
|
|
1712
|
+
const actions = {
|
|
1713
|
+
status: {
|
|
1714
|
+
tool: "agent_knock_knock_status",
|
|
1715
|
+
arguments: { conversation_id: id }
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
const terminalControlled = entry.source === "terminal_control";
|
|
1719
|
+
const managed = entry.source === "akk_delegate";
|
|
1720
|
+
const approvalState = isRecord(entry.approval_state)
|
|
1721
|
+
? entry.approval_state
|
|
1722
|
+
: {};
|
|
1723
|
+
const nativeTakeover = isRecord(conversation?.native_session_takeover)
|
|
1724
|
+
? conversation.native_session_takeover
|
|
1725
|
+
: undefined;
|
|
1726
|
+
const managedApprovalPending = isRecord(nativeTakeover?.terminal_bridge_approval);
|
|
1727
|
+
const terminalBridgeReady = managed &&
|
|
1728
|
+
terminalBridgeEnabled(conversation) &&
|
|
1729
|
+
terminalControlFromTakeover(nativeTakeover) !== undefined;
|
|
1730
|
+
if (commands.send === true &&
|
|
1731
|
+
((terminalBridgeReady &&
|
|
1732
|
+
["waiting_for_openclaw", "idle"].includes(String(entry.status)) &&
|
|
1733
|
+
!managedApprovalPending &&
|
|
1734
|
+
approvalState.blocked !== true) ||
|
|
1735
|
+
(terminalControlled &&
|
|
1736
|
+
entry.activity_state === "idle" &&
|
|
1737
|
+
approvalState.blocked !== true))) {
|
|
1738
|
+
actions.send = {
|
|
1739
|
+
tool: "agent_knock_knock_send",
|
|
1740
|
+
arguments: { selector: id },
|
|
1741
|
+
missing_required: ["request"]
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
const approvalFingerprint = stringValue(approvalState.fingerprint);
|
|
1745
|
+
const managedApprovalEligible = terminalBridgeReady &&
|
|
1746
|
+
entry.status === "waiting_for_openclaw" &&
|
|
1747
|
+
(entry.agent !== "claude" ||
|
|
1748
|
+
approvalState.decision_mode === "keys");
|
|
1749
|
+
if (commands.approve === true &&
|
|
1750
|
+
approvalState.approvable === true &&
|
|
1751
|
+
approvalFingerprint &&
|
|
1752
|
+
((terminalControlled &&
|
|
1753
|
+
entry.agent === "codex") ||
|
|
1754
|
+
managedApprovalEligible)) {
|
|
1755
|
+
actions.approve = {
|
|
1756
|
+
tool: "agent_knock_knock_approve",
|
|
1757
|
+
arguments: { conversation_id: id },
|
|
1758
|
+
missing_required: ["expected_approval_fingerprint"],
|
|
1759
|
+
before_call: {
|
|
1760
|
+
tool: "agent_knock_knock_status",
|
|
1761
|
+
arguments: { conversation_id: id },
|
|
1762
|
+
use: "After explicit user confirmation, copy the latest terminal_status.approval_state.fingerprint into expected_approval_fingerprint."
|
|
1763
|
+
},
|
|
1764
|
+
requires_explicit_user_confirmation: true,
|
|
1765
|
+
requires_fresh_status: true
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
const rawCancellable = terminalControlled &&
|
|
1769
|
+
commands.cancel === true &&
|
|
1770
|
+
(entry.activity_state === "working" ||
|
|
1771
|
+
(approvalState.blocked === true &&
|
|
1772
|
+
approvalState.approvable === true));
|
|
1773
|
+
const managedCancellable = terminalBridgeReady &&
|
|
1774
|
+
["waiting_for_agent", "waiting_for_openclaw"].includes(String(entry.status)) &&
|
|
1775
|
+
!(managedApprovalPending &&
|
|
1776
|
+
approvalState.approvable !== true);
|
|
1777
|
+
if (rawCancellable || managedCancellable) {
|
|
1778
|
+
actions.cancel = {
|
|
1779
|
+
tool: "agent_knock_knock_cancel",
|
|
1780
|
+
arguments: { conversation_id: id },
|
|
1781
|
+
requires_user_intent: true
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
if (terminalBridgeReady && entry.status === "stalled") {
|
|
1785
|
+
actions.renew = {
|
|
1786
|
+
tool: "agent_knock_knock_renew",
|
|
1787
|
+
arguments: { conversation_id: id }
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1790
|
+
const callbackDelivery = isRecord(conversation?.callback_delivery)
|
|
1791
|
+
? conversation.callback_delivery
|
|
1792
|
+
: undefined;
|
|
1793
|
+
if (managed &&
|
|
1794
|
+
isRecord(conversation) &&
|
|
1795
|
+
isRetryableCallbackDelivery(conversation, callbackDelivery)) {
|
|
1796
|
+
actions.retry_callback = {
|
|
1797
|
+
tool: "agent_knock_knock_retry_callback",
|
|
1798
|
+
arguments: { conversation_id: id }
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
if (commands.close === true) {
|
|
1802
|
+
const orphanedDispatch = isRecord(entry.orphaned_terminal_dispatch)
|
|
1803
|
+
? entry.orphaned_terminal_dispatch
|
|
1804
|
+
: undefined;
|
|
1805
|
+
const expectedMessageId = stringValue(orphanedDispatch?.message_id);
|
|
1806
|
+
actions.close = {
|
|
1807
|
+
tool: "agent_knock_knock_close",
|
|
1808
|
+
arguments: {
|
|
1809
|
+
conversation_id: id,
|
|
1810
|
+
...(expectedMessageId
|
|
1811
|
+
? { expected_message_id: expectedMessageId }
|
|
1812
|
+
: {})
|
|
1813
|
+
},
|
|
1814
|
+
requires_explicit_user_confirmation: true
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
return actions;
|
|
1590
1818
|
}
|
|
1591
1819
|
async function resolveConversationSelectorOption(commandName, options) {
|
|
1592
1820
|
if (!SESSION_SELECTOR_COMMANDS.has(String(commandName ?? "")) ||
|
|
@@ -1617,7 +1845,13 @@ async function sessionSelectorCandidates(commandName, options) {
|
|
|
1617
1845
|
const storedConversations = listConversations(storeDir);
|
|
1618
1846
|
const workspaceConversations = storedConversations
|
|
1619
1847
|
.filter((conversation) => matchesConfiguredWorkspace(options.workspace, conversation.workspace));
|
|
1620
|
-
const
|
|
1848
|
+
const discoverableWorkspaceConversations = workspaceConversations
|
|
1849
|
+
.filter(isDiscoverableTmuxConversation);
|
|
1850
|
+
const managed = discoverableWorkspaceConversations.map((conversation) => delegatedListEntry(summarizeConversation(conversation), {
|
|
1851
|
+
terminalBridge: terminalBridgeEnabled(conversation),
|
|
1852
|
+
approvalState: managedListApprovalState(conversation),
|
|
1853
|
+
conversation
|
|
1854
|
+
}));
|
|
1621
1855
|
const managedTerminalKeys = new Set(workspaceConversations
|
|
1622
1856
|
.filter((conversation) => isActiveStatus(conversation.status))
|
|
1623
1857
|
.map((conversation) => terminalControlSelectorKey(terminalControlFromTakeover(isRecord(conversation.native_session_takeover)
|
|
@@ -7370,6 +7604,34 @@ function summarizeConversation(conversation) {
|
|
|
7370
7604
|
event_log_path: conversation.event_log_path
|
|
7371
7605
|
};
|
|
7372
7606
|
}
|
|
7607
|
+
function isDiscoverableTmuxConversation(conversation) {
|
|
7608
|
+
if (!isRecord(conversation)) {
|
|
7609
|
+
return false;
|
|
7610
|
+
}
|
|
7611
|
+
if (!isRecord(conversation.executor)) {
|
|
7612
|
+
return false;
|
|
7613
|
+
}
|
|
7614
|
+
const kind = stringValue(conversation.executor.kind)?.toLowerCase();
|
|
7615
|
+
return (kind !== undefined &&
|
|
7616
|
+
isExecutorKind(kind) &&
|
|
7617
|
+
conversation.executor.transport === "tmux");
|
|
7618
|
+
}
|
|
7619
|
+
function persistedExecutorLogFields(conversation) {
|
|
7620
|
+
if (isDiscoverableTmuxConversation(conversation)) {
|
|
7621
|
+
const executor = executorForConversation(conversation);
|
|
7622
|
+
return {
|
|
7623
|
+
agent: executor.kind,
|
|
7624
|
+
executor_session: executor.session
|
|
7625
|
+
};
|
|
7626
|
+
}
|
|
7627
|
+
const rawExecutor = isRecord(conversation?.executor)
|
|
7628
|
+
? conversation.executor
|
|
7629
|
+
: {};
|
|
7630
|
+
return {
|
|
7631
|
+
agent: stringValue(rawExecutor.kind) ?? "unsupported",
|
|
7632
|
+
executor_session: stringValue(rawExecutor.session)
|
|
7633
|
+
};
|
|
7634
|
+
}
|
|
7373
7635
|
function summarizeEvent(event) {
|
|
7374
7636
|
return {
|
|
7375
7637
|
ts: event.ts,
|
|
@@ -7986,10 +8248,10 @@ function cleanupIdleConversations(storeDir, options = {}, now = new Date()) {
|
|
|
7986
8248
|
idle_timeout_minutes: timeoutMinutes,
|
|
7987
8249
|
terminal_bridge: terminalBridge
|
|
7988
8250
|
});
|
|
8251
|
+
const executorLogFields = persistedExecutorLogFields(conversation);
|
|
7989
8252
|
runtimeLog("info", "idle_conversation_closed", {
|
|
7990
8253
|
conversation_id: conversation.conversation_id,
|
|
7991
|
-
|
|
7992
|
-
executor_session: executorForConversation(conversation).session,
|
|
8254
|
+
...executorLogFields,
|
|
7993
8255
|
state_path: statePath,
|
|
7994
8256
|
event_log_path: logPath,
|
|
7995
8257
|
idle_since: conversation.idle_since,
|