@scotthuang/agent-knock-knock 0.7.0 → 0.8.0
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 +12 -0
- package/README.md +7 -5
- package/dist/src/cli.js +582 -103
- package/dist/src/cli.js.map +1 -1
- package/dist/src/openclaw-plugin-helpers.js +84 -45
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin.js +16 -23
- package/dist/src/openclaw-plugin.js.map +1 -1
- package/dist/src/session-selector.d.ts +18 -0
- package/dist/src/session-selector.js +18 -9
- package/dist/src/session-selector.js.map +1 -1
- package/docs/quickstart-tmux.md +4 -2
- package/openclaw.plugin.json +2 -2
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +12 -10
package/dist/src/cli.js
CHANGED
|
@@ -930,7 +930,15 @@ async function runDelegate(options) {
|
|
|
930
930
|
return false;
|
|
931
931
|
}
|
|
932
932
|
});
|
|
933
|
-
const eligible = scopedCandidates.filter((candidate) =>
|
|
933
|
+
const eligible = scopedCandidates.filter((candidate) => {
|
|
934
|
+
if (candidate.activity_state !== "idle") {
|
|
935
|
+
return false;
|
|
936
|
+
}
|
|
937
|
+
const terminalControl = isRecord(candidate.terminal_control)
|
|
938
|
+
? candidate.terminal_control
|
|
939
|
+
: undefined;
|
|
940
|
+
return !terminalControl || terminalDispatchOwnership(terminalControl).state === "none";
|
|
941
|
+
});
|
|
934
942
|
if (eligible.length === 0) {
|
|
935
943
|
const observed = scopedCandidates.length > 0
|
|
936
944
|
? ` Found ${scopedCandidates.length} matching pane(s), but none is idle.`
|
|
@@ -1350,6 +1358,7 @@ function environmentWithoutGatewayTokens() {
|
|
|
1350
1358
|
}
|
|
1351
1359
|
async function runList(options) {
|
|
1352
1360
|
const storeDir = expandHome(options.storeDir ?? options.logDir ?? defaultStoreDir(process.cwd()));
|
|
1361
|
+
const store = inspectStoreCompatibility(storeDir);
|
|
1353
1362
|
const reconciliation = options.reconcile === true
|
|
1354
1363
|
? await reconcileStoreForList(storeDir, options)
|
|
1355
1364
|
: {
|
|
@@ -1360,50 +1369,40 @@ async function runList(options) {
|
|
|
1360
1369
|
const agentFilter = options.agent ? resolveExecutor({ kind: options.agent }).kind : undefined;
|
|
1361
1370
|
const statusFilter = options.status;
|
|
1362
1371
|
const allStoredConversations = listConversations(storeDir);
|
|
1363
|
-
const
|
|
1364
|
-
.filter(isDiscoverableTmuxConversation)
|
|
1372
|
+
const allManagedConversations = allStoredConversations
|
|
1373
|
+
.filter(isDiscoverableTmuxConversation);
|
|
1374
|
+
const storedConversations = allManagedConversations
|
|
1365
1375
|
.filter((conversation) => includeAll || isActiveStatus(conversation.status))
|
|
1366
1376
|
.filter((conversation) => matchesConfiguredWorkspace(options.workspace, conversation.workspace))
|
|
1367
1377
|
.filter((conversation) => !agentFilter || executorForConversation(conversation).kind === agentFilter)
|
|
1368
1378
|
.filter((conversation) => !statusFilter || conversation.status === statusFilter);
|
|
1369
|
-
const conversations = storedConversations.map((conversation) => summarizeConversation(conversation));
|
|
1370
|
-
const delegated = storedConversations.map((conversation) => delegatedListEntry(summarizeConversation(conversation), {
|
|
1371
|
-
terminalBridge: terminalBridgeEnabled(conversation),
|
|
1372
|
-
approvalState: managedListApprovalState(conversation),
|
|
1373
|
-
conversation
|
|
1374
|
-
}));
|
|
1375
1379
|
const terminalScan = await buildTerminalListGroup({ options, agentFilter, statusFilter });
|
|
1376
|
-
const
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
.
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
.
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
return false;
|
|
1386
|
-
}
|
|
1387
|
-
const key = terminalControlSelectorKey(entry.terminal_control);
|
|
1388
|
-
return key === undefined || !managedTerminalKeys.has(key);
|
|
1380
|
+
const physicalTerminals = terminalScan.terminalControlled.filter((entry) => matchesConfiguredWorkspace(options.workspace, entry.workspace ?? entry.cwd));
|
|
1381
|
+
const projection = terminalFirstListProjection({
|
|
1382
|
+
terminals: physicalTerminals,
|
|
1383
|
+
allConversations: allManagedConversations.filter((conversation) => matchesConfiguredWorkspace(options.workspace, conversation.workspace)),
|
|
1384
|
+
displayedConversations: storedConversations,
|
|
1385
|
+
includeAll,
|
|
1386
|
+
managedOnly: options.managedOnly === true,
|
|
1387
|
+
statusFilter,
|
|
1388
|
+
mutationsAllowed: store.writable === true
|
|
1389
1389
|
});
|
|
1390
1390
|
printJson({
|
|
1391
1391
|
store_dir: storeDir,
|
|
1392
|
-
store
|
|
1392
|
+
store,
|
|
1393
1393
|
reconciliation,
|
|
1394
1394
|
action_contracts: listActionContracts(),
|
|
1395
|
-
|
|
1396
|
-
|
|
1395
|
+
terminals: projection.terminals,
|
|
1396
|
+
unavailable_managed_turns: projection.unavailableManagedTurns,
|
|
1397
1397
|
terminal_scan: {
|
|
1398
1398
|
...terminalScan.summary,
|
|
1399
|
-
|
|
1400
|
-
}
|
|
1401
|
-
tasks: conversations
|
|
1399
|
+
terminal_count: projection.terminals.length
|
|
1400
|
+
}
|
|
1402
1401
|
});
|
|
1403
|
-
runtimeLog("info", "
|
|
1402
|
+
runtimeLog("info", "terminals_listed", {
|
|
1404
1403
|
store_dir: storeDir,
|
|
1405
|
-
|
|
1406
|
-
|
|
1404
|
+
terminal_count: projection.terminals.length,
|
|
1405
|
+
unavailable_managed_turn_count: projection.unavailableManagedTurns.length,
|
|
1407
1406
|
terminal_scan_error: terminalScan.summary.error,
|
|
1408
1407
|
include_all: includeAll,
|
|
1409
1408
|
agent_filter: agentFilter,
|
|
@@ -1455,16 +1454,6 @@ async function buildTerminalListGroup({ options, agentFilter, statusFilter }) {
|
|
|
1455
1454
|
if (options.managedOnly) {
|
|
1456
1455
|
return empty;
|
|
1457
1456
|
}
|
|
1458
|
-
if (statusFilter && statusFilter !== "active") {
|
|
1459
|
-
return {
|
|
1460
|
-
...empty,
|
|
1461
|
-
summary: {
|
|
1462
|
-
enabled: false,
|
|
1463
|
-
agents: [],
|
|
1464
|
-
skipped: `terminal discovery skipped for status filter ${statusFilter}`
|
|
1465
|
-
}
|
|
1466
|
-
};
|
|
1467
|
-
}
|
|
1468
1457
|
const registry = createRuntimeTerminalAgentRegistry(options);
|
|
1469
1458
|
const adapters = agentFilter
|
|
1470
1459
|
? [registry.get(agentFilter)].filter((adapter) => adapter !== undefined)
|
|
@@ -1507,7 +1496,7 @@ async function buildTerminalListGroup({ options, agentFilter, statusFilter }) {
|
|
|
1507
1496
|
enabled: true,
|
|
1508
1497
|
agents: adapters.map((adapter) => adapter.agent),
|
|
1509
1498
|
active_count: activeCount,
|
|
1510
|
-
|
|
1499
|
+
terminal_count: terminalControlled.length,
|
|
1511
1500
|
approval_scan: options.noApprovalScan ? "disabled" : "enabled",
|
|
1512
1501
|
diagnostics: terminalDiagnostics,
|
|
1513
1502
|
error: errors.length > 0 ? errors.join("; ") : undefined
|
|
@@ -1523,24 +1512,26 @@ async function terminalControlDiagnostics(provider) {
|
|
|
1523
1512
|
paneCount: (await provider.listPanes()).length
|
|
1524
1513
|
};
|
|
1525
1514
|
}
|
|
1526
|
-
function
|
|
1515
|
+
function managedTurnListEntry(task, { terminalBridge = false, approvalState, conversation } = {}) {
|
|
1527
1516
|
const entry = {
|
|
1528
1517
|
...task,
|
|
1529
1518
|
id: task.conversation_id,
|
|
1530
1519
|
short_ref: sessionShortRef(task.conversation_id),
|
|
1531
|
-
source: "
|
|
1520
|
+
source: "managed_turn",
|
|
1532
1521
|
...(approvalState ? { approval_state: approvalState } : {}),
|
|
1533
1522
|
commands: {
|
|
1534
|
-
send:
|
|
1523
|
+
send: canFollowUpManagedTurn(task.status),
|
|
1535
1524
|
cancel: isWaitingForAgent(task.status),
|
|
1536
1525
|
close: task.status !== "closed",
|
|
1537
1526
|
status: true,
|
|
1538
1527
|
approve: terminalBridge && isActiveStatus(task.status)
|
|
1539
1528
|
}
|
|
1540
1529
|
};
|
|
1530
|
+
const availableActions = availableListActions(entry, { conversation });
|
|
1531
|
+
const { commands: _commands, ...publicEntry } = entry;
|
|
1541
1532
|
return {
|
|
1542
|
-
...
|
|
1543
|
-
available_actions:
|
|
1533
|
+
...publicEntry,
|
|
1534
|
+
available_actions: availableActions
|
|
1544
1535
|
};
|
|
1545
1536
|
}
|
|
1546
1537
|
async function terminalControlledListEntry(session, activeSessions, options, bridge = createTerminalAgentBridge(options)) {
|
|
@@ -1558,9 +1549,9 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
|
|
|
1558
1549
|
const entry = {
|
|
1559
1550
|
id: bridge.terminalConversationId(session),
|
|
1560
1551
|
short_ref: sessionShortRef(bridge.terminalConversationId(session)),
|
|
1561
|
-
source: "
|
|
1552
|
+
source: "terminal",
|
|
1562
1553
|
agent: session.agent,
|
|
1563
|
-
|
|
1554
|
+
process_state: "active",
|
|
1564
1555
|
pid: session.pid,
|
|
1565
1556
|
child_pids: childPidsForRoot(session, activeSessions),
|
|
1566
1557
|
command: session.command,
|
|
@@ -1594,9 +1585,438 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
|
|
|
1594
1585
|
close: orphanedDispatch !== undefined
|
|
1595
1586
|
}
|
|
1596
1587
|
};
|
|
1588
|
+
const availableActions = availableListActions(entry);
|
|
1589
|
+
const { commands: _commands, ...publicEntry } = entry;
|
|
1590
|
+
return {
|
|
1591
|
+
...publicEntry,
|
|
1592
|
+
available_actions: availableActions
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
function terminalFirstListProjection({ terminals, allConversations, displayedConversations, includeAll, managedOnly, statusFilter, mutationsAllowed }) {
|
|
1596
|
+
const allByTerminal = managedConversationsByTerminal(allConversations);
|
|
1597
|
+
const displayedByTerminal = managedConversationsByTerminal(displayedConversations);
|
|
1598
|
+
const discoveredTerminalKeys = new Set();
|
|
1599
|
+
const projectedTerminals = terminals.map((terminal) => {
|
|
1600
|
+
const terminalControl = isRecord(terminal.terminal_control)
|
|
1601
|
+
? terminal.terminal_control
|
|
1602
|
+
: undefined;
|
|
1603
|
+
const terminalKey = terminalControlSelectorKey(terminalControl);
|
|
1604
|
+
if (terminalKey) {
|
|
1605
|
+
discoveredTerminalKeys.add(terminalKey);
|
|
1606
|
+
}
|
|
1607
|
+
const allRelated = terminalKey
|
|
1608
|
+
? [...(allByTerminal.get(terminalKey) ?? [])]
|
|
1609
|
+
: [];
|
|
1610
|
+
const displayedRelated = terminalKey
|
|
1611
|
+
? [...(displayedByTerminal.get(terminalKey) ?? [])]
|
|
1612
|
+
: [];
|
|
1613
|
+
const discoveredOwnership = terminalControl
|
|
1614
|
+
? terminalDispatchOwnership(terminalControl)
|
|
1615
|
+
: { state: "none" };
|
|
1616
|
+
const ownership = discoveredOwnership.state === "current"
|
|
1617
|
+
? localTerminalDispatchOwnership(discoveredOwnership.conversation, allRelated, terminal)
|
|
1618
|
+
: discoveredOwnership;
|
|
1619
|
+
const discoveredRawActions = isRecord(terminal.available_actions)
|
|
1620
|
+
? terminal.available_actions
|
|
1621
|
+
: {};
|
|
1622
|
+
const rawActions = mutationsAllowed
|
|
1623
|
+
? discoveredRawActions
|
|
1624
|
+
: readOnlyListActions(discoveredRawActions);
|
|
1625
|
+
const terminalCanAcceptSend = ownership.state === "none" && isRecord(rawActions.send);
|
|
1626
|
+
if (ownership.state === "current" &&
|
|
1627
|
+
!allRelated.some((conversation) => conversation.conversation_id === ownership.conversation.conversation_id)) {
|
|
1628
|
+
allRelated.push(ownership.conversation);
|
|
1629
|
+
}
|
|
1630
|
+
const currentTurnValue = ownership.state === "current"
|
|
1631
|
+
? currentManagedTurnForTerminal(ownership.conversation, terminal, rawActions)
|
|
1632
|
+
: undefined;
|
|
1633
|
+
const currentTurn = currentTurnValue && !mutationsAllowed
|
|
1634
|
+
? readOnlyManagedTurn(currentTurnValue)
|
|
1635
|
+
: currentTurnValue;
|
|
1636
|
+
const sortedDisplayed = [...displayedRelated]
|
|
1637
|
+
.filter((conversation) => conversation.conversation_id !== currentTurn?.conversation_id)
|
|
1638
|
+
.sort(compareManagedConversationRecency);
|
|
1639
|
+
const recentConversation = currentTurn ? undefined : sortedDisplayed[0];
|
|
1640
|
+
const recentTurnValue = recentConversation
|
|
1641
|
+
? historicalManagedTurnForTerminal(recentConversation, terminalCanAcceptSend, terminal)
|
|
1642
|
+
: undefined;
|
|
1643
|
+
const recentTurn = recentTurnValue && !mutationsAllowed
|
|
1644
|
+
? readOnlyManagedTurn(recentTurnValue)
|
|
1645
|
+
: recentTurnValue;
|
|
1646
|
+
const historyConversations = includeAll
|
|
1647
|
+
? sortedDisplayed.filter((conversation) => conversation.conversation_id !== recentConversation?.conversation_id)
|
|
1648
|
+
: [];
|
|
1649
|
+
const history = historyConversations.map((conversation) => {
|
|
1650
|
+
const turn = historicalManagedTurnForTerminal(conversation, terminalCanAcceptSend, terminal);
|
|
1651
|
+
return mutationsAllowed ? turn : readOnlyManagedTurn(turn);
|
|
1652
|
+
});
|
|
1653
|
+
const visibleTurnIds = new Set([currentTurn, recentTurn, ...history]
|
|
1654
|
+
.map((turn) => stringValue(turn?.conversation_id))
|
|
1655
|
+
.filter((id) => id !== undefined));
|
|
1656
|
+
const management = {
|
|
1657
|
+
current_turn: currentTurn ?? null,
|
|
1658
|
+
recent_turn: recentTurn ?? null,
|
|
1659
|
+
turn_count: allRelated.length,
|
|
1660
|
+
hidden_turn_count: allRelated.filter((conversation) => !visibleTurnIds.has(conversation.conversation_id)).length,
|
|
1661
|
+
...(includeAll ? { history } : {})
|
|
1662
|
+
};
|
|
1663
|
+
const availableActions = ownership.state === "current"
|
|
1664
|
+
? currentTerminalActions(currentTurn)
|
|
1665
|
+
: ownership.state === "conflict"
|
|
1666
|
+
? safeTerminalActionsDuringConflict(rawActions)
|
|
1667
|
+
: rawActions;
|
|
1668
|
+
return {
|
|
1669
|
+
...terminal,
|
|
1670
|
+
management_state: ownership.state === "current"
|
|
1671
|
+
? "managed"
|
|
1672
|
+
: ownership.state === "conflict"
|
|
1673
|
+
? "conflict"
|
|
1674
|
+
: "unmanaged",
|
|
1675
|
+
...(ownership.state === "conflict"
|
|
1676
|
+
? { management_conflict: ownership.conflict }
|
|
1677
|
+
: {}),
|
|
1678
|
+
managed: management,
|
|
1679
|
+
available_actions: availableActions
|
|
1680
|
+
};
|
|
1681
|
+
});
|
|
1682
|
+
const unavailableManagedTurns = displayedConversations
|
|
1683
|
+
.filter((conversation) => {
|
|
1684
|
+
const terminalKey = terminalKeyForManagedConversation(conversation);
|
|
1685
|
+
if (terminalKey && discoveredTerminalKeys.has(terminalKey)) {
|
|
1686
|
+
return false;
|
|
1687
|
+
}
|
|
1688
|
+
return (includeAll ||
|
|
1689
|
+
managedOnly ||
|
|
1690
|
+
statusFilter !== undefined ||
|
|
1691
|
+
managedTurnNeedsAttention(conversation.status));
|
|
1692
|
+
})
|
|
1693
|
+
.sort(compareManagedConversationRecency)
|
|
1694
|
+
.map((conversation) => {
|
|
1695
|
+
const managedTurn = managedTurnListEntry(summarizeConversation(conversation), {
|
|
1696
|
+
terminalBridge: terminalBridgeEnabled(conversation),
|
|
1697
|
+
approvalState: managedListApprovalState(conversation),
|
|
1698
|
+
conversation
|
|
1699
|
+
});
|
|
1700
|
+
return {
|
|
1701
|
+
...managedTurn,
|
|
1702
|
+
available_actions: mutationsAllowed
|
|
1703
|
+
? safeUnavailableManagedTurnActions(isRecord(managedTurn.available_actions)
|
|
1704
|
+
? managedTurn.available_actions
|
|
1705
|
+
: {})
|
|
1706
|
+
: readOnlyListActions(isRecord(managedTurn.available_actions)
|
|
1707
|
+
? managedTurn.available_actions
|
|
1708
|
+
: {}),
|
|
1709
|
+
terminal_availability: {
|
|
1710
|
+
available: false,
|
|
1711
|
+
reason: managedOnly
|
|
1712
|
+
? "terminal discovery was disabled by --managed-only"
|
|
1713
|
+
: "the referenced tmux pane is not currently available"
|
|
1714
|
+
}
|
|
1715
|
+
};
|
|
1716
|
+
});
|
|
1717
|
+
return {
|
|
1718
|
+
terminals: projectedTerminals,
|
|
1719
|
+
unavailableManagedTurns
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
function managedConversationsByTerminal(conversations) {
|
|
1723
|
+
const groups = new Map();
|
|
1724
|
+
for (const conversation of conversations) {
|
|
1725
|
+
const key = terminalKeyForManagedConversation(conversation);
|
|
1726
|
+
if (!key) {
|
|
1727
|
+
continue;
|
|
1728
|
+
}
|
|
1729
|
+
const group = groups.get(key) ?? [];
|
|
1730
|
+
group.push(conversation);
|
|
1731
|
+
groups.set(key, group);
|
|
1732
|
+
}
|
|
1733
|
+
return groups;
|
|
1734
|
+
}
|
|
1735
|
+
function terminalKeyForManagedConversation(conversation) {
|
|
1736
|
+
return terminalControlSelectorKey(terminalControlFromTakeover(isRecord(conversation.native_session_takeover)
|
|
1737
|
+
? conversation.native_session_takeover
|
|
1738
|
+
: undefined));
|
|
1739
|
+
}
|
|
1740
|
+
function compareManagedConversationRecency(left, right) {
|
|
1741
|
+
const leftTime = Date.parse(String(left.updated_at ?? left.created_at ?? ""));
|
|
1742
|
+
const rightTime = Date.parse(String(right.updated_at ?? right.created_at ?? ""));
|
|
1743
|
+
if (Number.isFinite(leftTime) && Number.isFinite(rightTime) && leftTime !== rightTime) {
|
|
1744
|
+
return rightTime - leftTime;
|
|
1745
|
+
}
|
|
1746
|
+
if (Number.isFinite(leftTime) !== Number.isFinite(rightTime)) {
|
|
1747
|
+
return Number.isFinite(leftTime) ? -1 : 1;
|
|
1748
|
+
}
|
|
1749
|
+
return left.conversation_id.localeCompare(right.conversation_id);
|
|
1750
|
+
}
|
|
1751
|
+
function managedTurnNeedsAttention(status) {
|
|
1752
|
+
return [
|
|
1753
|
+
"created",
|
|
1754
|
+
"running",
|
|
1755
|
+
"waiting_for_agent",
|
|
1756
|
+
"waiting_for_openclaw",
|
|
1757
|
+
"stalled",
|
|
1758
|
+
"callback_pending",
|
|
1759
|
+
"callback_failed",
|
|
1760
|
+
"cancelling"
|
|
1761
|
+
].includes(status);
|
|
1762
|
+
}
|
|
1763
|
+
function terminalDispatchOwnership(terminalControl) {
|
|
1764
|
+
let ledger;
|
|
1765
|
+
try {
|
|
1766
|
+
ledger = loadTerminalBridgeDispatchLedger(terminalControl);
|
|
1767
|
+
}
|
|
1768
|
+
catch (error) {
|
|
1769
|
+
return {
|
|
1770
|
+
state: "conflict",
|
|
1771
|
+
conflict: {
|
|
1772
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
1773
|
+
recovery: "inspect the shared tmux pane before performing a side effect"
|
|
1774
|
+
}
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
if (!ledger || ledger.status === "resolved") {
|
|
1778
|
+
return { state: "none" };
|
|
1779
|
+
}
|
|
1780
|
+
const ledgerControl = isRecord(ledger.terminal_control)
|
|
1781
|
+
? ledger.terminal_control
|
|
1782
|
+
: undefined;
|
|
1783
|
+
const ledgerPanePid = Number(ledgerControl?.pane_pid);
|
|
1784
|
+
const currentPanePid = Number(terminalControl.panePid);
|
|
1785
|
+
if (Number.isSafeInteger(ledgerPanePid) &&
|
|
1786
|
+
ledgerPanePid > 0 &&
|
|
1787
|
+
Number.isSafeInteger(currentPanePid) &&
|
|
1788
|
+
currentPanePid > 0 &&
|
|
1789
|
+
ledgerPanePid !== currentPanePid) {
|
|
1790
|
+
return { state: "none" };
|
|
1791
|
+
}
|
|
1792
|
+
if (!["prepared", "submitted", "uncertain"].includes(String(ledger.status))) {
|
|
1793
|
+
return { state: "none" };
|
|
1794
|
+
}
|
|
1795
|
+
const owner = loadTerminalDispatchLedgerOwner(ledger);
|
|
1796
|
+
if (!owner) {
|
|
1797
|
+
return {
|
|
1798
|
+
state: "conflict",
|
|
1799
|
+
conflict: terminalDispatchConflict(ledger, "dispatch owner state is unavailable")
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
if (TERMINAL_DISPATCH_RELEASE_STATUSES.has(owner.status)) {
|
|
1803
|
+
return { state: "none" };
|
|
1804
|
+
}
|
|
1805
|
+
const ownerTerminalKey = terminalKeyForManagedConversation(owner);
|
|
1806
|
+
const currentTerminalKey = terminalControlSelectorKey(terminalControl);
|
|
1807
|
+
if (!ownerTerminalKey || ownerTerminalKey !== currentTerminalKey) {
|
|
1808
|
+
return {
|
|
1809
|
+
state: "conflict",
|
|
1810
|
+
conflict: terminalDispatchConflict(ledger, "dispatch owner does not reference this tmux pane incarnation")
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
const ownerTakeover = isRecord(owner.native_session_takeover)
|
|
1814
|
+
? owner.native_session_takeover
|
|
1815
|
+
: undefined;
|
|
1816
|
+
const ledgerMessageId = stringValue(ledger.message_id);
|
|
1817
|
+
const ownerMessageId = stringValue(ownerTakeover?.terminal_bridge_message_id);
|
|
1818
|
+
if (["prepared", "submitted", "uncertain"].includes(String(ledger.status)) &&
|
|
1819
|
+
ledgerMessageId &&
|
|
1820
|
+
ownerMessageId !== ledgerMessageId) {
|
|
1821
|
+
return {
|
|
1822
|
+
state: "conflict",
|
|
1823
|
+
conflict: terminalDispatchConflict(ledger, "dispatch generation does not match the owner state")
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
return { state: "current", conversation: owner };
|
|
1827
|
+
}
|
|
1828
|
+
function terminalDispatchConflict(ledger, reason) {
|
|
1829
|
+
return {
|
|
1830
|
+
reason,
|
|
1831
|
+
dispatch_status: stringValue(ledger.status),
|
|
1832
|
+
owner_conversation_id: stringValue(ledger.conversation_id),
|
|
1833
|
+
message_id: stringValue(ledger.message_id),
|
|
1834
|
+
recovery: "inspect the shared tmux pane and explicitly resolve the current dispatch before performing a side effect"
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
function localTerminalDispatchOwnership(ledgerOwner, localConversations, terminal) {
|
|
1838
|
+
const localOwner = localConversations.find((conversation) => conversation.conversation_id === ledgerOwner.conversation_id &&
|
|
1839
|
+
sameCanonicalStatePath(conversation.state_path, ledgerOwner.state_path));
|
|
1840
|
+
if (localOwner) {
|
|
1841
|
+
if (!managedTurnMatchesLiveTerminal(localOwner, terminal)) {
|
|
1842
|
+
return {
|
|
1843
|
+
state: "conflict",
|
|
1844
|
+
conflict: {
|
|
1845
|
+
reason: "the terminal dispatch owner no longer matches the live coding-agent process identity or workspace",
|
|
1846
|
+
owner_conversation_id: ledgerOwner.conversation_id,
|
|
1847
|
+
recovery: "inspect the shared tmux pane and explicitly resolve the stale dispatch before performing a side effect"
|
|
1848
|
+
}
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1851
|
+
return { state: "current", conversation: localOwner };
|
|
1852
|
+
}
|
|
1853
|
+
return {
|
|
1854
|
+
state: "conflict",
|
|
1855
|
+
conflict: {
|
|
1856
|
+
reason: "the terminal dispatch owner belongs to another AKK store or is not supported by this list view",
|
|
1857
|
+
owner_conversation_id: ledgerOwner.conversation_id,
|
|
1858
|
+
recovery: "inspect the shared tmux pane and use the AKK store that owns the current dispatch"
|
|
1859
|
+
}
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
function sameCanonicalStatePath(left, right) {
|
|
1863
|
+
const leftPath = stringValue(left);
|
|
1864
|
+
const rightPath = stringValue(right);
|
|
1865
|
+
return Boolean(leftPath &&
|
|
1866
|
+
rightPath &&
|
|
1867
|
+
path.resolve(leftPath) === path.resolve(rightPath));
|
|
1868
|
+
}
|
|
1869
|
+
function currentTerminalActions(currentTurn) {
|
|
1870
|
+
if (!currentTurn || !isRecord(currentTurn.available_actions)) {
|
|
1871
|
+
return {};
|
|
1872
|
+
}
|
|
1873
|
+
const actions = {};
|
|
1874
|
+
for (const action of ["status", "approve", "cancel", "renew", "retry_callback"]) {
|
|
1875
|
+
if (isRecord(currentTurn.available_actions[action])) {
|
|
1876
|
+
actions[action] = currentTurn.available_actions[action];
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
return actions;
|
|
1880
|
+
}
|
|
1881
|
+
function safeTerminalActionsDuringConflict(rawActions) {
|
|
1882
|
+
const actions = {};
|
|
1883
|
+
for (const action of ["status", "close"]) {
|
|
1884
|
+
if (isRecord(rawActions[action])) {
|
|
1885
|
+
actions[action] = rawActions[action];
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
return actions;
|
|
1889
|
+
}
|
|
1890
|
+
function safeUnavailableManagedTurnActions(actionsValue) {
|
|
1891
|
+
const actions = {};
|
|
1892
|
+
for (const action of ["status", "retry_callback", "close"]) {
|
|
1893
|
+
if (isRecord(actionsValue[action])) {
|
|
1894
|
+
actions[action] = actionsValue[action];
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
return actions;
|
|
1898
|
+
}
|
|
1899
|
+
function readOnlyListActions(actionsValue) {
|
|
1900
|
+
return isRecord(actionsValue.status)
|
|
1901
|
+
? { status: actionsValue.status }
|
|
1902
|
+
: {};
|
|
1903
|
+
}
|
|
1904
|
+
function readOnlyManagedTurn(managedTurn) {
|
|
1905
|
+
return {
|
|
1906
|
+
...managedTurn,
|
|
1907
|
+
available_actions: readOnlyListActions(isRecord(managedTurn.available_actions)
|
|
1908
|
+
? managedTurn.available_actions
|
|
1909
|
+
: {})
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1912
|
+
function historicalManagedTurnForTerminal(conversation, terminalCanAcceptSend, terminal) {
|
|
1913
|
+
const managedTurn = managedTurnListEntry(summarizeConversation(conversation), {
|
|
1914
|
+
terminalBridge: terminalBridgeEnabled(conversation),
|
|
1915
|
+
approvalState: managedListApprovalState(conversation),
|
|
1916
|
+
conversation
|
|
1917
|
+
});
|
|
1918
|
+
const availableActions = isRecord(managedTurn.available_actions)
|
|
1919
|
+
? managedTurn.available_actions
|
|
1920
|
+
: {};
|
|
1921
|
+
const safeActions = safeUnavailableManagedTurnActions(availableActions);
|
|
1922
|
+
if (terminalCanAcceptSend &&
|
|
1923
|
+
managedTurnMatchesLiveTerminal(conversation, terminal) &&
|
|
1924
|
+
isRecord(availableActions.follow_up)) {
|
|
1925
|
+
safeActions.follow_up = availableActions.follow_up;
|
|
1926
|
+
}
|
|
1927
|
+
return {
|
|
1928
|
+
...managedTurn,
|
|
1929
|
+
available_actions: safeActions
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
function managedTurnMatchesLiveTerminal(conversation, terminal) {
|
|
1933
|
+
const takeover = isRecord(conversation.native_session_takeover)
|
|
1934
|
+
? conversation.native_session_takeover
|
|
1935
|
+
: undefined;
|
|
1936
|
+
const liveControl = isRecord(terminal.terminal_control)
|
|
1937
|
+
? terminal.terminal_control
|
|
1938
|
+
: undefined;
|
|
1939
|
+
const storedControl = terminalControlFromTakeover(takeover);
|
|
1940
|
+
const livePid = Number(terminal.pid);
|
|
1941
|
+
const storedPid = Number(takeover?.terminal_agent_pid);
|
|
1942
|
+
if (executorForConversation(conversation).kind !== terminal.agent ||
|
|
1943
|
+
!Number.isSafeInteger(livePid) ||
|
|
1944
|
+
livePid <= 1 ||
|
|
1945
|
+
storedPid !== livePid ||
|
|
1946
|
+
stringValue(takeover?.native_session_id) !== stringValue(terminal.id) ||
|
|
1947
|
+
terminalControlSelectorKey(storedControl) !==
|
|
1948
|
+
terminalControlSelectorKey(liveControl)) {
|
|
1949
|
+
return false;
|
|
1950
|
+
}
|
|
1951
|
+
const storedSessionId = stringValue(takeover?.terminal_agent_session_id);
|
|
1952
|
+
const liveSessionId = stringValue(terminal.session_id);
|
|
1953
|
+
if (storedSessionId && storedSessionId !== liveSessionId) {
|
|
1954
|
+
return false;
|
|
1955
|
+
}
|
|
1956
|
+
const liveWorkspace = terminal.workspace ?? terminal.cwd;
|
|
1957
|
+
if (!matchesConfiguredWorkspace(conversation.workspace, liveWorkspace)) {
|
|
1958
|
+
return false;
|
|
1959
|
+
}
|
|
1960
|
+
const livePanePath = liveControl?.currentPath;
|
|
1961
|
+
if (livePanePath !== undefined &&
|
|
1962
|
+
!matchesConfiguredWorkspace(conversation.workspace, livePanePath)) {
|
|
1963
|
+
return false;
|
|
1964
|
+
}
|
|
1965
|
+
return true;
|
|
1966
|
+
}
|
|
1967
|
+
function currentManagedTurnForTerminal(conversation, terminal, rawTerminalActions) {
|
|
1968
|
+
const managedTurn = managedTurnListEntry(summarizeConversation(conversation), {
|
|
1969
|
+
terminalBridge: terminalBridgeEnabled(conversation),
|
|
1970
|
+
approvalState: managedListApprovalState(conversation),
|
|
1971
|
+
conversation
|
|
1972
|
+
});
|
|
1973
|
+
const rawApproval = isRecord(rawTerminalActions.approve)
|
|
1974
|
+
? rawTerminalActions.approve
|
|
1975
|
+
: undefined;
|
|
1976
|
+
if (!rawApproval || executorForConversation(conversation).kind !== "codex") {
|
|
1977
|
+
return managedTurn;
|
|
1978
|
+
}
|
|
1979
|
+
const ownerId = conversation.conversation_id;
|
|
1980
|
+
const approval = retargetConversationAction(rawApproval, ownerId);
|
|
1981
|
+
const terminalApprovalState = isRecord(terminal.approval_state)
|
|
1982
|
+
? terminal.approval_state
|
|
1983
|
+
: undefined;
|
|
1597
1984
|
return {
|
|
1598
|
-
...
|
|
1599
|
-
|
|
1985
|
+
...managedTurn,
|
|
1986
|
+
...(terminalApprovalState
|
|
1987
|
+
? { approval_state: terminalApprovalState }
|
|
1988
|
+
: {}),
|
|
1989
|
+
available_actions: {
|
|
1990
|
+
...(isRecord(managedTurn.available_actions)
|
|
1991
|
+
? managedTurn.available_actions
|
|
1992
|
+
: {}),
|
|
1993
|
+
approve: approval
|
|
1994
|
+
}
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
function retargetConversationAction(action, conversationId) {
|
|
1998
|
+
const beforeCall = isRecord(action.before_call)
|
|
1999
|
+
? action.before_call
|
|
2000
|
+
: undefined;
|
|
2001
|
+
return {
|
|
2002
|
+
...action,
|
|
2003
|
+
arguments: {
|
|
2004
|
+
...(isRecord(action.arguments) ? action.arguments : {}),
|
|
2005
|
+
conversation_id: conversationId
|
|
2006
|
+
},
|
|
2007
|
+
...(beforeCall
|
|
2008
|
+
? {
|
|
2009
|
+
before_call: {
|
|
2010
|
+
...beforeCall,
|
|
2011
|
+
arguments: {
|
|
2012
|
+
...(isRecord(beforeCall.arguments)
|
|
2013
|
+
? beforeCall.arguments
|
|
2014
|
+
: {}),
|
|
2015
|
+
conversation_id: conversationId
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
: {})
|
|
1600
2020
|
};
|
|
1601
2021
|
}
|
|
1602
2022
|
async function listStateForTerminal(agent, terminalControl, options, bridge = createTerminalAgentBridge(options), runtime) {
|
|
@@ -1663,7 +2083,7 @@ function childPidsForRoot(root, processes) {
|
|
|
1663
2083
|
.filter((process) => process.agent === root.agent && process.ppid === root.pid)
|
|
1664
2084
|
.map((process) => process.pid);
|
|
1665
2085
|
}
|
|
1666
|
-
function
|
|
2086
|
+
function canFollowUpManagedTurn(status) {
|
|
1667
2087
|
return !["done", "failed", "closed", "cancelled"].includes(status);
|
|
1668
2088
|
}
|
|
1669
2089
|
function managedListApprovalState(conversation) {
|
|
@@ -1701,28 +2121,31 @@ function managedListApprovalState(conversation) {
|
|
|
1701
2121
|
}
|
|
1702
2122
|
function listActionContracts() {
|
|
1703
2123
|
return {
|
|
1704
|
-
version:
|
|
2124
|
+
version: 3,
|
|
1705
2125
|
instructions: [
|
|
1706
|
-
"
|
|
1707
|
-
"
|
|
2126
|
+
"Treat terminals[] as the primary resource and use only actions present in available_actions.",
|
|
2127
|
+
"Use a terminal send action to start a new managed turn. Use a managed turn follow_up action only when continuing that specific managed turn.",
|
|
1708
2128
|
"Start with the action's prefilled arguments, supply every missing_required field, and consult the top-level action's optional fields only when needed.",
|
|
1709
2129
|
"Authoritative full IDs are prefilled; short_ref is for display and human input.",
|
|
1710
2130
|
"Availability is a snapshot. AKK revalidates process, tmux pane, workspace, activity, approval, and recovery state before side effects."
|
|
1711
2131
|
],
|
|
1712
2132
|
field_semantics: {
|
|
2133
|
+
process_state: {
|
|
2134
|
+
terminals: "physical_terminal_process_liveness",
|
|
2135
|
+
authoritative_for_tool_calls: false
|
|
2136
|
+
},
|
|
1713
2137
|
status: {
|
|
1714
|
-
|
|
1715
|
-
terminal_controlled: "process_liveness",
|
|
2138
|
+
managed_turns: "managed_turn_lifecycle",
|
|
1716
2139
|
authoritative_for_tool_calls: false
|
|
1717
2140
|
},
|
|
1718
2141
|
activity_state: {
|
|
1719
|
-
|
|
2142
|
+
terminals: "terminal_screen_activity_classification",
|
|
1720
2143
|
authoritative_for_tool_calls: false
|
|
1721
2144
|
},
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
2145
|
+
managed: {
|
|
2146
|
+
current_turn: "the authoritative dispatch-ledger owner, never inferred from history",
|
|
2147
|
+
recent_turn: "the latest visible non-owning turn for intentional follow-up",
|
|
2148
|
+
history: "older turns, present only with --all"
|
|
1726
2149
|
},
|
|
1727
2150
|
available_actions: {
|
|
1728
2151
|
meaning: "currently_safe_actions",
|
|
@@ -1742,7 +2165,19 @@ function listActionContracts() {
|
|
|
1742
2165
|
"agentHardTimeoutMinutes"
|
|
1743
2166
|
],
|
|
1744
2167
|
unsupported: ["timeoutSeconds"],
|
|
1745
|
-
ordinary_use: "Add request only
|
|
2168
|
+
ordinary_use: "Start a new managed turn on the selected physical terminal. Add request only and omit timeout fields unless the user explicitly asks to change monitoring limits."
|
|
2169
|
+
},
|
|
2170
|
+
follow_up: {
|
|
2171
|
+
tool: "agent_knock_knock_send",
|
|
2172
|
+
target_argument: "selector",
|
|
2173
|
+
required: ["request"],
|
|
2174
|
+
optional: [
|
|
2175
|
+
"selector",
|
|
2176
|
+
"idleTimeoutMinutes",
|
|
2177
|
+
"agentTimeoutMinutes",
|
|
2178
|
+
"agentHardTimeoutMinutes"
|
|
2179
|
+
],
|
|
2180
|
+
ordinary_use: "Continue the explicitly selected managed turn. Start from its prefilled selector and add request."
|
|
1746
2181
|
},
|
|
1747
2182
|
status: {
|
|
1748
2183
|
tool: "agent_knock_knock_status",
|
|
@@ -1797,8 +2232,8 @@ function availableListActions(entry, { conversation } = {}) {
|
|
|
1797
2232
|
arguments: { conversation_id: id }
|
|
1798
2233
|
}
|
|
1799
2234
|
};
|
|
1800
|
-
const terminalControlled = entry.source === "
|
|
1801
|
-
const managed = entry.source === "
|
|
2235
|
+
const terminalControlled = entry.source === "terminal";
|
|
2236
|
+
const managed = entry.source === "managed_turn";
|
|
1802
2237
|
const approvalState = isRecord(entry.approval_state)
|
|
1803
2238
|
? entry.approval_state
|
|
1804
2239
|
: {};
|
|
@@ -1817,7 +2252,7 @@ function availableListActions(entry, { conversation } = {}) {
|
|
|
1817
2252
|
(terminalControlled &&
|
|
1818
2253
|
entry.activity_state === "idle" &&
|
|
1819
2254
|
approvalState.blocked !== true))) {
|
|
1820
|
-
actions
|
|
2255
|
+
actions[managed ? "follow_up" : "send"] = {
|
|
1821
2256
|
tool: "agent_knock_knock_send",
|
|
1822
2257
|
arguments: { selector: id },
|
|
1823
2258
|
missing_required: ["request"]
|
|
@@ -1923,52 +2358,112 @@ function isSessionSelectorSyntax(value) {
|
|
|
1923
2358
|
}
|
|
1924
2359
|
async function sessionSelectorCandidates(commandName, options) {
|
|
1925
2360
|
const storeDir = storeDirFromOptions(options);
|
|
2361
|
+
const mutationsAllowed = inspectStoreCompatibility(storeDir).writable === true;
|
|
1926
2362
|
const storedConversations = listConversations(storeDir);
|
|
1927
2363
|
const workspaceConversations = storedConversations
|
|
1928
2364
|
.filter((conversation) => matchesConfiguredWorkspace(options.workspace, conversation.workspace));
|
|
1929
2365
|
const discoverableWorkspaceConversations = workspaceConversations
|
|
1930
2366
|
.filter(isDiscoverableTmuxConversation);
|
|
1931
|
-
const managed = discoverableWorkspaceConversations.map((conversation) =>
|
|
2367
|
+
const managed = discoverableWorkspaceConversations.map((conversation) => managedTurnListEntry(summarizeConversation(conversation), {
|
|
1932
2368
|
terminalBridge: terminalBridgeEnabled(conversation),
|
|
1933
2369
|
approvalState: managedListApprovalState(conversation),
|
|
1934
2370
|
conversation
|
|
1935
2371
|
}));
|
|
1936
|
-
const managedTerminalKeys = new Set(workspaceConversations
|
|
1937
|
-
.filter((conversation) => isActiveStatus(conversation.status))
|
|
1938
|
-
.map((conversation) => terminalControlSelectorKey(terminalControlFromTakeover(isRecord(conversation.native_session_takeover)
|
|
1939
|
-
? conversation.native_session_takeover
|
|
1940
|
-
: undefined)))
|
|
1941
|
-
.filter((key) => key !== undefined));
|
|
1942
2372
|
const terminalScan = await buildTerminalListGroup({
|
|
1943
2373
|
options: {
|
|
1944
2374
|
...options,
|
|
1945
|
-
noApprovalScan:
|
|
2375
|
+
noApprovalScan: ["send", "approve", "cancel"].includes(commandName)
|
|
1946
2376
|
? options.noApprovalScan
|
|
1947
2377
|
: true
|
|
1948
2378
|
},
|
|
1949
2379
|
agentFilter: undefined,
|
|
1950
2380
|
statusFilter: undefined
|
|
1951
2381
|
});
|
|
2382
|
+
const terminalProjection = terminalFirstListProjection({
|
|
2383
|
+
terminals: terminalScan.terminalControlled.filter((entry) => matchesConfiguredWorkspace(options.workspace, entry.workspace ?? entry.cwd)),
|
|
2384
|
+
allConversations: discoverableWorkspaceConversations,
|
|
2385
|
+
displayedConversations: discoverableWorkspaceConversations,
|
|
2386
|
+
includeAll: false,
|
|
2387
|
+
managedOnly: options.managedOnly === true,
|
|
2388
|
+
statusFilter: undefined,
|
|
2389
|
+
mutationsAllowed
|
|
2390
|
+
});
|
|
1952
2391
|
const observedAtMs = Date.now();
|
|
1953
2392
|
return [
|
|
1954
|
-
...managed,
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
})
|
|
1962
|
-
]
|
|
2393
|
+
...managed.map((entry) => sessionSelectorCandidateForEntry(entry, commandName, observedAtMs, {
|
|
2394
|
+
defaultActionable: options.managedOnly === true,
|
|
2395
|
+
mutationsAllowed
|
|
2396
|
+
})),
|
|
2397
|
+
...terminalProjection.terminals.map((entry) => sessionSelectorCandidateForEntry(entry, commandName, observedAtMs, {
|
|
2398
|
+
defaultActionable: true,
|
|
2399
|
+
mutationsAllowed
|
|
2400
|
+
}))
|
|
2401
|
+
];
|
|
2402
|
+
}
|
|
2403
|
+
function sessionSelectorCandidateForEntry(entry, commandName, observedAtMs, { defaultActionable, mutationsAllowed }) {
|
|
2404
|
+
const action = mutationsAllowed || commandName === "status"
|
|
2405
|
+
? listActionForCommand(entry, commandName)
|
|
2406
|
+
: undefined;
|
|
2407
|
+
const targetId = listActionTargetId(action);
|
|
2408
|
+
return {
|
|
1963
2409
|
id: String(entry.id),
|
|
2410
|
+
...(targetId && targetId !== entry.id ? { targetId } : {}),
|
|
1964
2411
|
agent: resolveExecutor({ kind: entry.agent }).kind,
|
|
1965
|
-
actionable:
|
|
2412
|
+
actionable: action !== undefined,
|
|
2413
|
+
defaultActionable,
|
|
1966
2414
|
...sessionEntryRecency(entry, observedAtMs),
|
|
1967
2415
|
source: stringValue(entry.source),
|
|
1968
|
-
status: stringValue(entry.status),
|
|
2416
|
+
status: stringValue(entry.status ?? entry.process_state),
|
|
1969
2417
|
workspace: stringValue(entry.workspace ?? entry.cwd),
|
|
1970
2418
|
label: stringValue(entry.request ?? entry.command)
|
|
1971
|
-
}
|
|
2419
|
+
};
|
|
2420
|
+
}
|
|
2421
|
+
function listActionForCommand(entry, commandName) {
|
|
2422
|
+
const actions = isRecord(entry.available_actions)
|
|
2423
|
+
? entry.available_actions
|
|
2424
|
+
: {};
|
|
2425
|
+
const actionName = commandName === "retry-callback"
|
|
2426
|
+
? "retry_callback"
|
|
2427
|
+
: commandName === "send" && entry.source === "managed_turn"
|
|
2428
|
+
? "follow_up"
|
|
2429
|
+
: commandName;
|
|
2430
|
+
if (isRecord(actions[actionName])) {
|
|
2431
|
+
return actions[actionName];
|
|
2432
|
+
}
|
|
2433
|
+
if (commandName !== "approve") {
|
|
2434
|
+
return undefined;
|
|
2435
|
+
}
|
|
2436
|
+
if (managedTurnCanEnterApprovalPath(entry)) {
|
|
2437
|
+
return {
|
|
2438
|
+
tool: "agent_knock_knock_approve",
|
|
2439
|
+
arguments: { conversation_id: String(entry.id) }
|
|
2440
|
+
};
|
|
2441
|
+
}
|
|
2442
|
+
const managed = isRecord(entry.managed) ? entry.managed : undefined;
|
|
2443
|
+
const currentTurn = isRecord(managed?.current_turn)
|
|
2444
|
+
? managed.current_turn
|
|
2445
|
+
: undefined;
|
|
2446
|
+
if (currentTurn && managedTurnCanEnterApprovalPath(currentTurn)) {
|
|
2447
|
+
return {
|
|
2448
|
+
tool: "agent_knock_knock_approve",
|
|
2449
|
+
arguments: {
|
|
2450
|
+
conversation_id: String(currentTurn.conversation_id ?? currentTurn.id)
|
|
2451
|
+
}
|
|
2452
|
+
};
|
|
2453
|
+
}
|
|
2454
|
+
return undefined;
|
|
2455
|
+
}
|
|
2456
|
+
function managedTurnCanEnterApprovalPath(entry) {
|
|
2457
|
+
const executor = isRecord(entry.executor) ? entry.executor : undefined;
|
|
2458
|
+
return (entry.source === "managed_turn" &&
|
|
2459
|
+
executor?.transport === "tmux" &&
|
|
2460
|
+
isActiveStatus(String(entry.status)));
|
|
2461
|
+
}
|
|
2462
|
+
function listActionTargetId(action) {
|
|
2463
|
+
const actionArguments = isRecord(action?.arguments)
|
|
2464
|
+
? action.arguments
|
|
2465
|
+
: undefined;
|
|
2466
|
+
return stringValue(actionArguments?.selector ?? actionArguments?.conversation_id);
|
|
1972
2467
|
}
|
|
1973
2468
|
function terminalControlSelectorKey(value) {
|
|
1974
2469
|
if (!isRecord(value)) {
|
|
@@ -1985,22 +2480,6 @@ function terminalControlSelectorKey(value) {
|
|
|
1985
2480
|
socket_path: stringValue(value.socketPath) ?? null
|
|
1986
2481
|
});
|
|
1987
2482
|
}
|
|
1988
|
-
function sessionEntrySupportsCommand(entry, commandName) {
|
|
1989
|
-
const commands = isRecord(entry.commands) ? entry.commands : {};
|
|
1990
|
-
if (typeof commands[commandName] === "boolean") {
|
|
1991
|
-
return commands[commandName] === true;
|
|
1992
|
-
}
|
|
1993
|
-
if (entry.source !== "akk_delegate") {
|
|
1994
|
-
return false;
|
|
1995
|
-
}
|
|
1996
|
-
if (commandName === "renew") {
|
|
1997
|
-
return entry.status === "stalled";
|
|
1998
|
-
}
|
|
1999
|
-
if (commandName === "retry-callback") {
|
|
2000
|
-
return ["callback_pending", "callback_failed"].includes(entry.status);
|
|
2001
|
-
}
|
|
2002
|
-
return false;
|
|
2003
|
-
}
|
|
2004
2483
|
function sessionEntryRecency(entry, observedAtMs) {
|
|
2005
2484
|
const timestamp = Date.parse(String(entry.updated_at ?? entry.created_at ?? ""));
|
|
2006
2485
|
if (Number.isFinite(timestamp)) {
|
|
@@ -8917,7 +9396,7 @@ function usage() {
|
|
|
8917
9396
|
agent-knock-knock --help
|
|
8918
9397
|
agent-knock-knock --version
|
|
8919
9398
|
agent-knock-knock delegate --request <text> [--agent ${agentList}] [--workspace <path>] [--store-dir <dir>]
|
|
8920
|
-
agent-knock-knock list [--store-dir <dir>] [--agent ${agentList}] [--status <status>] [--all] [--
|
|
9399
|
+
agent-knock-knock list [--store-dir <dir>] [--agent ${agentList}] [--status <status>] [--all] [--reconcile] [--no-approval-scan] [--terminal-debug]
|
|
8921
9400
|
agent-knock-knock status [--conversation <id|selector>] [--store-dir <dir>] [--reconcile] [--trace]
|
|
8922
9401
|
agent-knock-knock send [--conversation <id|selector>] --message <text> [--type answer|task|control] [--agent-timeout-minutes <minutes>] [--agent-hard-timeout-minutes <minutes>]
|
|
8923
9402
|
agent-knock-knock approve [--conversation <id|selector>] --expected-approval-fingerprint <fingerprint>
|