@telora/daemon 0.22.87 → 0.22.97
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/build-info.json +6 -3
- package/dist/backends/index.d.ts +1 -1
- package/dist/backends/index.d.ts.map +1 -1
- package/dist/backends/index.js +1 -1
- package/dist/backends/index.js.map +1 -1
- package/dist/backends/resolve.d.ts +37 -8
- package/dist/backends/resolve.d.ts.map +1 -1
- package/dist/backends/resolve.js +62 -28
- package/dist/backends/resolve.js.map +1 -1
- package/dist/cli/connect/config-write.d.ts +7 -4
- package/dist/cli/connect/config-write.d.ts.map +1 -1
- package/dist/cli/connect/config-write.js +13 -7
- package/dist/cli/connect/config-write.js.map +1 -1
- package/dist/cli/connect.d.ts.map +1 -1
- package/dist/cli/connect.js +11 -1
- package/dist/cli/connect.js.map +1 -1
- package/dist/cli/update-commands.d.ts.map +1 -1
- package/dist/cli/update-commands.js +2 -1
- package/dist/cli/update-commands.js.map +1 -1
- package/dist/completion/event-phases.js +5 -5
- package/dist/completion/event-phases.js.map +1 -1
- package/dist/completion/event.d.ts +7 -0
- package/dist/completion/event.d.ts.map +1 -1
- package/dist/completion/event.js.map +1 -1
- package/dist/completion/review-exit-phase.d.ts.map +1 -1
- package/dist/completion/review-exit-phase.js +2 -1
- package/dist/completion/review-exit-phase.js.map +1 -1
- package/dist/delivery/external-hold.js +1 -1
- package/dist/delivery/external-hold.js.map +1 -1
- package/dist/delivery/operator-hold.d.ts +14 -0
- package/dist/delivery/operator-hold.d.ts.map +1 -1
- package/dist/delivery/operator-hold.js +31 -1
- package/dist/delivery/operator-hold.js.map +1 -1
- package/dist/directive/directive-dispatch.js +2 -2
- package/dist/directive/directive-dispatch.js.map +1 -1
- package/dist/directive/directive-executor.d.ts.map +1 -1
- package/dist/directive/directive-executor.js +2 -1
- package/dist/directive/directive-executor.js.map +1 -1
- package/dist/directive/directive-verify-hold-gate.d.ts +32 -8
- package/dist/directive/directive-verify-hold-gate.d.ts.map +1 -1
- package/dist/directive/directive-verify-hold-gate.js +20 -21
- package/dist/directive/directive-verify-hold-gate.js.map +1 -1
- package/dist/drift/staleness-sensor.d.ts +6 -0
- package/dist/drift/staleness-sensor.d.ts.map +1 -1
- package/dist/drift/staleness-sensor.js +24 -20
- package/dist/drift/staleness-sensor.js.map +1 -1
- package/dist/drift/staleness.d.ts +18 -5
- package/dist/drift/staleness.d.ts.map +1 -1
- package/dist/drift/staleness.js +28 -15
- package/dist/drift/staleness.js.map +1 -1
- package/dist/focus/focus-engine.d.ts.map +1 -1
- package/dist/focus/focus-engine.js +6 -9
- package/dist/focus/focus-engine.js.map +1 -1
- package/dist/focus/focus-executor.js +2 -2
- package/dist/focus/focus-executor.js.map +1 -1
- package/dist/focus/listener-spawn.d.ts +31 -0
- package/dist/focus/listener-spawn.d.ts.map +1 -1
- package/dist/focus/listener-spawn.js +44 -22
- package/dist/focus/listener-spawn.js.map +1 -1
- package/dist/focus/listener.d.ts.map +1 -1
- package/dist/focus/listener.js +4 -0
- package/dist/focus/listener.js.map +1 -1
- package/dist/focus/team-actionable-work.d.ts +76 -20
- package/dist/focus/team-actionable-work.d.ts.map +1 -1
- package/dist/focus/team-actionable-work.js +85 -20
- package/dist/focus/team-actionable-work.js.map +1 -1
- package/dist/nexus/incoming.js +1 -1
- package/dist/nexus/incoming.js.map +1 -1
- package/dist/nexus/pending-returns.d.ts +124 -0
- package/dist/nexus/pending-returns.d.ts.map +1 -0
- package/dist/nexus/pending-returns.js +194 -0
- package/dist/nexus/pending-returns.js.map +1 -0
- package/dist/nexus/return-routing.d.ts +89 -0
- package/dist/nexus/return-routing.d.ts.map +1 -0
- package/dist/nexus/return-routing.js +148 -0
- package/dist/nexus/return-routing.js.map +1 -0
- package/dist/nexus/return.d.ts +66 -89
- package/dist/nexus/return.d.ts.map +1 -1
- package/dist/nexus/return.js +116 -279
- package/dist/nexus/return.js.map +1 -1
- package/dist/prompt/focus-prompt-builder.d.ts.map +1 -1
- package/dist/prompt/focus-prompt-builder.js +1 -0
- package/dist/prompt/focus-prompt-builder.js.map +1 -1
- package/dist/queries/delivery-gate-state.d.ts +23 -0
- package/dist/queries/delivery-gate-state.d.ts.map +1 -1
- package/dist/queries/delivery-gate-state.js +31 -4
- package/dist/queries/delivery-gate-state.js.map +1 -1
- package/dist/queries/focus-discovery.d.ts +7 -1
- package/dist/queries/focus-discovery.d.ts.map +1 -1
- package/dist/queries/focus-discovery.js +9 -3
- package/dist/queries/focus-discovery.js.map +1 -1
- package/dist/queries/requests.d.ts +51 -3
- package/dist/queries/requests.d.ts.map +1 -1
- package/dist/queries/requests.js +63 -10
- package/dist/queries/requests.js.map +1 -1
- package/dist/queries/shared.js +1 -1
- package/dist/queries/verification.d.ts.map +1 -1
- package/dist/queries/verification.js +1 -0
- package/dist/queries/verification.js.map +1 -1
- package/dist/shell/heartbeat-metadata.d.ts +18 -0
- package/dist/shell/heartbeat-metadata.d.ts.map +1 -1
- package/dist/shell/heartbeat-metadata.js +24 -0
- package/dist/shell/heartbeat-metadata.js.map +1 -1
- package/dist/shell/heartbeat.d.ts.map +1 -1
- package/dist/shell/heartbeat.js +6 -17
- package/dist/shell/heartbeat.js.map +1 -1
- package/dist/shell/model-resolution.d.ts +9 -4
- package/dist/shell/model-resolution.d.ts.map +1 -1
- package/dist/shell/model-resolution.js +46 -7
- package/dist/shell/model-resolution.js.map +1 -1
- package/dist/shell/unified-init.d.ts +24 -0
- package/dist/shell/unified-init.d.ts.map +1 -1
- package/dist/shell/unified-init.js +22 -17
- package/dist/shell/unified-init.js.map +1 -1
- package/dist/spawn/model-id-guards.d.ts +50 -0
- package/dist/spawn/model-id-guards.d.ts.map +1 -0
- package/dist/spawn/model-id-guards.js +110 -0
- package/dist/spawn/model-id-guards.js.map +1 -0
- package/dist/spawn/model-routing.d.ts +1 -43
- package/dist/spawn/model-routing.d.ts.map +1 -1
- package/dist/spawn/model-routing.js +3 -94
- package/dist/spawn/model-routing.js.map +1 -1
- package/dist/spawn/prompt-assembly.js +1 -1
- package/dist/spawn/prompt-assembly.js.map +1 -1
- package/dist/spawn/respawn-cycle-escalation.d.ts.map +1 -1
- package/dist/spawn/respawn-cycle-escalation.js +11 -3
- package/dist/spawn/respawn-cycle-escalation.js.map +1 -1
- package/dist/spawn/review-hold-park.d.ts +31 -0
- package/dist/spawn/review-hold-park.d.ts.map +1 -0
- package/dist/spawn/review-hold-park.js +120 -0
- package/dist/spawn/review-hold-park.js.map +1 -0
- package/dist/spawn/shared.d.ts +1 -1
- package/dist/spawn/shared.d.ts.map +1 -1
- package/dist/spawn/spawn-cooldown.d.ts +27 -4
- package/dist/spawn/spawn-cooldown.d.ts.map +1 -1
- package/dist/spawn/spawn-cooldown.js +88 -3
- package/dist/spawn/spawn-cooldown.js.map +1 -1
- package/dist/spawn/spawn-decision.d.ts +1 -0
- package/dist/spawn/spawn-decision.d.ts.map +1 -1
- package/dist/spawn/spawn-decision.js +7 -0
- package/dist/spawn/spawn-decision.js.map +1 -1
- package/dist/spawn/worktree-session.d.ts +4 -0
- package/dist/spawn/worktree-session.d.ts.map +1 -1
- package/dist/spawn/worktree-session.js +19 -15
- package/dist/spawn/worktree-session.js.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/update/auto-update-idle.d.ts.map +1 -1
- package/dist/update/auto-update-idle.js +2 -1
- package/dist/update/auto-update-idle.js.map +1 -1
- package/dist/update/auto-update.d.ts +20 -3
- package/dist/update/auto-update.d.ts.map +1 -1
- package/dist/update/auto-update.js +8 -24
- package/dist/update/auto-update.js.map +1 -1
- package/dist/update/self-update.d.ts +43 -4
- package/dist/update/self-update.d.ts.map +1 -1
- package/dist/update/self-update.js +61 -38
- package/dist/update/self-update.js.map +1 -1
- package/dist/update/version-check.d.ts +29 -45
- package/dist/update/version-check.d.ts.map +1 -1
- package/dist/update/version-check.js +48 -79
- package/dist/update/version-check.js.map +1 -1
- package/dist/verify/failure-signature.d.ts.map +1 -1
- package/dist/verify/failure-signature.js +1 -0
- package/dist/verify/failure-signature.js.map +1 -1
- package/dist/verify/verification-checkout.d.ts +58 -0
- package/dist/verify/verification-checkout.d.ts.map +1 -0
- package/dist/verify/verification-checkout.js +266 -0
- package/dist/verify/verification-checkout.js.map +1 -0
- package/dist/verify/verification-default-on.d.ts +27 -3
- package/dist/verify/verification-default-on.d.ts.map +1 -1
- package/dist/verify/verification-default-on.js +98 -5
- package/dist/verify/verification-default-on.js.map +1 -1
- package/dist/verify/verification-deps.d.ts +11 -0
- package/dist/verify/verification-deps.d.ts.map +1 -1
- package/dist/verify/verification-deterministic.d.ts +22 -1
- package/dist/verify/verification-deterministic.d.ts.map +1 -1
- package/dist/verify/verification-deterministic.js +6 -7
- package/dist/verify/verification-deterministic.js.map +1 -1
- package/dist/verify/verification-engine.d.ts.map +1 -1
- package/dist/verify/verification-engine.js +10 -8
- package/dist/verify/verification-engine.js.map +1 -1
- package/dist/verify/verification-feedback.d.ts +3 -0
- package/dist/verify/verification-feedback.d.ts.map +1 -1
- package/dist/verify/verification-feedback.js +5 -2
- package/dist/verify/verification-feedback.js.map +1 -1
- package/dist/worktree/verify-interlock.d.ts +9 -1
- package/dist/worktree/verify-interlock.d.ts.map +1 -1
- package/dist/worktree/verify-interlock.js +40 -3
- package/dist/worktree/verify-interlock.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
|
|
2
|
+
// Pinned by: packages/daemon/src/nexus/nexus-pending-returns-paging.test.ts,
|
|
3
|
+
// packages/daemon/src/nexus/nexus-pending-returns.test.ts,
|
|
4
|
+
// packages/daemon/src/nexus/nexus-return-path-e2e.test.ts,
|
|
5
|
+
// packages/daemon/src/nexus/nexus-return-routing.test.ts,
|
|
6
|
+
// packages/daemon/src/spawn/build-spawn-prompt.test.ts
|
|
7
|
+
// PINNED-BY-END
|
|
8
|
+
/**
|
|
9
|
+
* Nexus pending returns on the REQUESTING focus: what that focus is waiting on
|
|
10
|
+
* across products, and what came back.
|
|
11
|
+
*
|
|
12
|
+
* A pending return (product_request_pending_returns) is one return -- a
|
|
13
|
+
* discovery, question or findings entry the requested team sent, or the
|
|
14
|
+
* request's close / decline -- carried on the requesting focus until the
|
|
15
|
+
* requesting team acknowledges it. This module owns the READ + RENDER half:
|
|
16
|
+
*
|
|
17
|
+
* - resolveNexusReturnsSection: loads the focus's unacknowledged returns and
|
|
18
|
+
* the open outgoing requests the focus waits on (filed from the focus OR
|
|
19
|
+
* from one of its deliveries), and renders ONE prompt section with every
|
|
20
|
+
* return's FULL content (never truncated). The spawn path folds it into the
|
|
21
|
+
* carried section every pass receives -- fresh coding, scoping, resume and
|
|
22
|
+
* review -- so no pass on the focus misses a return.
|
|
23
|
+
* - buildNexusReturnHandOff: the message handed to a LIVE team when a new
|
|
24
|
+
* return arrives mid-session (nexus/return-routing.ts decides when).
|
|
25
|
+
*
|
|
26
|
+
* Leaf module: imports only query helpers, never the return / routing passes,
|
|
27
|
+
* so both the spawn path and the poll pass can import it without a cycle.
|
|
28
|
+
*/
|
|
29
|
+
import { callApi } from '../queries/shared.js';
|
|
30
|
+
import { listAllOutgoingRequests } from '../queries/requests.js';
|
|
31
|
+
import { createLogger } from '../shell/log.js';
|
|
32
|
+
const log = createLogger({ module: 'nexus/pending-returns' });
|
|
33
|
+
/** Rows per pending-return page (the product-api's max `limit`). */
|
|
34
|
+
export const PENDING_RETURNS_PAGE_SIZE = 500;
|
|
35
|
+
/**
|
|
36
|
+
* Read EVERY pending return matching the filter, oldest first. One list call
|
|
37
|
+
* serves at most a page, so a burst larger than a page would otherwise leave
|
|
38
|
+
* returns out of a prompt or out of an engagement pass. Walks the keyset cursor
|
|
39
|
+
* (the last row's createdAt + id) until a page comes back shorter than the limit
|
|
40
|
+
* the server applied (it echoes `limit`), so an earlier return acknowledged
|
|
41
|
+
* mid-walk never shifts a later one out of the read and a server cap below the
|
|
42
|
+
* requested page never ends the walk early. A full page that does not advance
|
|
43
|
+
* the cursor THROWS (no partial list read as complete). THROWS on a hard API
|
|
44
|
+
* error. `deps.callApi` is the test seam.
|
|
45
|
+
*/
|
|
46
|
+
export async function listAllPendingReturns(filter, deps = {}) {
|
|
47
|
+
const call = deps.callApi ?? callApi;
|
|
48
|
+
const all = [];
|
|
49
|
+
let cursor = null;
|
|
50
|
+
for (;;) {
|
|
51
|
+
const params = { ...filter, limit: PENDING_RETURNS_PAGE_SIZE, ...(cursor ?? {}) };
|
|
52
|
+
const res = await call('product_request_list_pending_returns', params);
|
|
53
|
+
const rows = res.pendingReturns ?? [];
|
|
54
|
+
all.push(...rows);
|
|
55
|
+
const served = typeof res.limit === 'number' && res.limit > 0 ? Math.min(res.limit, PENDING_RETURNS_PAGE_SIZE) : PENDING_RETURNS_PAGE_SIZE;
|
|
56
|
+
if (rows.length < served)
|
|
57
|
+
return all;
|
|
58
|
+
const last = rows[rows.length - 1];
|
|
59
|
+
if (cursor && cursor.afterCreatedAt === last.createdAt && cursor.afterId === last.id) {
|
|
60
|
+
throw new Error(`listAllPendingReturns: the page after ${last.id} did not advance; refusing a partial read`);
|
|
61
|
+
}
|
|
62
|
+
cursor = { afterCreatedAt: last.createdAt, afterId: last.id };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Request statuses in which an outgoing request is still open: the requesting
|
|
67
|
+
* work waits on it. Single definition shared with the source-delivery gate
|
|
68
|
+
* (nexus/return.ts).
|
|
69
|
+
*/
|
|
70
|
+
export const WAITING_REQUEST_STATUSES = new Set(['open', 'accepted', 'in_progress', 'delivered']);
|
|
71
|
+
/**
|
|
72
|
+
* Pure: the open outgoing requests a focus waits on -- filed from the focus
|
|
73
|
+
* itself (with or without a source delivery) or from one of its deliveries.
|
|
74
|
+
*/
|
|
75
|
+
export function selectWaitingOnRequests(outgoing, focusId, deliveryIds) {
|
|
76
|
+
return outgoing.filter((r) => {
|
|
77
|
+
if (!r.status || !WAITING_REQUEST_STATUSES.has(r.status))
|
|
78
|
+
return false;
|
|
79
|
+
if (r.sourceFocusId === focusId)
|
|
80
|
+
return true;
|
|
81
|
+
return !!r.sourceDeliveryId && deliveryIds.has(r.sourceDeliveryId);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/** The acknowledge instruction, shared by the spawn section and the live hand-off. */
|
|
85
|
+
function acknowledgeInstructionLines() {
|
|
86
|
+
return [
|
|
87
|
+
'For each return:',
|
|
88
|
+
'1. Act on it in this pass. Fold a discovery or findings into your plan. Answer a question by',
|
|
89
|
+
' updating the request or filing a follow-up request. A CLOSED request releases the work that',
|
|
90
|
+
' waited on it. A DECLINED request hands that work back to you to re-plan: re-scope the waiting',
|
|
91
|
+
' delivery, or file a different request.',
|
|
92
|
+
'2. Acknowledge it once handled: `telora_product_request` (action: "acknowledge_return",',
|
|
93
|
+
' pendingReturnId: "<id>", note: "<what you did>"). An acknowledged return stops appearing in',
|
|
94
|
+
' your prompts and never re-engages this focus.',
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
/** Pure: one return rendered in full (its content is never truncated). */
|
|
98
|
+
export function renderPendingReturnBlock(ret) {
|
|
99
|
+
const title = ret.requestTitle ? `"${ret.requestTitle}"` : `request ${ret.requestId}`;
|
|
100
|
+
const anchor = ret.sourceDeliveryId ? ` (for delivery ${ret.sourceDeliveryId})` : '';
|
|
101
|
+
return [
|
|
102
|
+
`#### [${ret.returnKind}] ${title}${anchor} -- ${ret.createdAt}`,
|
|
103
|
+
`pendingReturnId: ${ret.id}`,
|
|
104
|
+
'',
|
|
105
|
+
ret.content,
|
|
106
|
+
'',
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Pure: the Nexus section for a focus's prompt. Empty (no lines) when the focus
|
|
111
|
+
* waits on nothing and has no pending return.
|
|
112
|
+
*/
|
|
113
|
+
export function buildNexusReturnsSection(args) {
|
|
114
|
+
const { waitingOn, pendingReturns } = args;
|
|
115
|
+
if (waitingOn.length === 0 && pendingReturns.length === 0)
|
|
116
|
+
return '';
|
|
117
|
+
const lines = ['## Nexus: cross-product requests this focus waits on, and what came back', ''];
|
|
118
|
+
if (waitingOn.length > 0) {
|
|
119
|
+
lines.push('### Waiting on another product', '', 'This focus filed these requests and they are still open, so the work that asked for them is', 'waiting on another product\'s team. Do not build around the dependency. Plan the work that does', 'not need it, and act on the returns below as they arrive.', '');
|
|
120
|
+
for (const r of waitingOn) {
|
|
121
|
+
const origin = r.sourceDeliveryId ? `filed by delivery ${r.sourceDeliveryId}` : 'filed by this focus';
|
|
122
|
+
lines.push(`- "${r.title ?? r.id}" (request ${r.id}, status: ${r.status ?? 'unknown'}; ${origin})`);
|
|
123
|
+
}
|
|
124
|
+
lines.push('');
|
|
125
|
+
}
|
|
126
|
+
if (pendingReturns.length > 0) {
|
|
127
|
+
lines.push('### Returns pending your acknowledgement', '', 'The requested products sent these back on this focus\'s requests, or the request closed or was', 'declined. Each is kept here, in full, in every pass on this focus until you acknowledge it.', '', ...acknowledgeInstructionLines(), '');
|
|
128
|
+
for (const ret of pendingReturns)
|
|
129
|
+
lines.push(...renderPendingReturnBlock(ret));
|
|
130
|
+
}
|
|
131
|
+
return lines.join('\n').trimEnd();
|
|
132
|
+
}
|
|
133
|
+
/** Pure: the message handed to a live team when new returns arrive mid-session. */
|
|
134
|
+
export function buildNexusReturnHandOff(returns) {
|
|
135
|
+
const lines = [
|
|
136
|
+
'## Nexus return arrived while you are working',
|
|
137
|
+
'',
|
|
138
|
+
`${returns.length} new return(s) arrived on this focus's cross-product request(s).`,
|
|
139
|
+
'',
|
|
140
|
+
...acknowledgeInstructionLines(),
|
|
141
|
+
'',
|
|
142
|
+
];
|
|
143
|
+
for (const ret of returns)
|
|
144
|
+
lines.push(...renderPendingReturnBlock(ret));
|
|
145
|
+
return lines.join('\n').trimEnd();
|
|
146
|
+
}
|
|
147
|
+
/** The production reads over a product-api client (`apiDeps.callApi` is the test seam). */
|
|
148
|
+
export function nexusReturnsSectionDeps(apiDeps = {}) {
|
|
149
|
+
return {
|
|
150
|
+
listFocusPendingReturns: (focusId) => listAllPendingReturns({ focusId }, apiDeps),
|
|
151
|
+
listOutgoing: (productId) => listAllOutgoingRequests(productId, apiDeps),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
export const defaultNexusReturnsSectionDeps = nexusReturnsSectionDeps();
|
|
155
|
+
/**
|
|
156
|
+
* Load + render the focus's Nexus section. Best-effort: each read degrades to
|
|
157
|
+
* empty on failure (logged) and never blocks a spawn.
|
|
158
|
+
*/
|
|
159
|
+
export async function resolveNexusReturnsSection(args, deps = defaultNexusReturnsSectionDeps) {
|
|
160
|
+
const { focusId, productId, deliveryIds } = args;
|
|
161
|
+
const [returnsResult, outgoingResult] = await Promise.allSettled([
|
|
162
|
+
deps.listFocusPendingReturns(focusId),
|
|
163
|
+
productId ? deps.listOutgoing(productId) : Promise.resolve([]),
|
|
164
|
+
]);
|
|
165
|
+
if (returnsResult.status === 'rejected') {
|
|
166
|
+
log.warn(`nexus pending returns read failed for focus ${focusId.slice(0, 8)}: ${returnsResult.reason.message}`);
|
|
167
|
+
}
|
|
168
|
+
if (outgoingResult.status === 'rejected') {
|
|
169
|
+
log.warn(`nexus outgoing requests read failed for focus ${focusId.slice(0, 8)}: ${outgoingResult.reason.message}`);
|
|
170
|
+
}
|
|
171
|
+
const pendingReturns = returnsResult.status === 'fulfilled' ? returnsResult.value : [];
|
|
172
|
+
const outgoing = outgoingResult.status === 'fulfilled' ? outgoingResult.value : [];
|
|
173
|
+
return buildNexusReturnsSection({
|
|
174
|
+
waitingOn: selectWaitingOnRequests(outgoing, focusId, new Set(deliveryIds)),
|
|
175
|
+
pendingReturns,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Fold the focus's Nexus section into the section every spawn shape carries
|
|
180
|
+
* (the answered-escalations carrier: appended to fresh directive + legacy
|
|
181
|
+
* prompts, the scoping message and the resume delta, for coding AND review
|
|
182
|
+
* passes). Ids are untouched: they name escalations to consume, not returns --
|
|
183
|
+
* a return is consumed only by the team's acknowledgement.
|
|
184
|
+
*/
|
|
185
|
+
export async function foldNexusReturnsSection(carried, args, deps = defaultNexusReturnsSectionDeps) {
|
|
186
|
+
const nexusSection = await resolveNexusReturnsSection(args, deps);
|
|
187
|
+
if (!nexusSection)
|
|
188
|
+
return carried;
|
|
189
|
+
return {
|
|
190
|
+
ids: carried.ids,
|
|
191
|
+
section: [carried.section, nexusSection].filter((s) => s.trim()).join('\n\n'),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=pending-returns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-returns.js","sourceRoot":"","sources":["../../src/nexus/pending-returns.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nexus return routing: every return reaches the REQUESTING focus within one
|
|
3
|
+
* poll, whatever pass that focus is in, and engages it at most once.
|
|
4
|
+
*
|
|
5
|
+
* Runs per product in the poll loop (listener runNexusPasses), right after the
|
|
6
|
+
* resolve pass so a close decided this tick routes this tick, and BEFORE the
|
|
7
|
+
* spawn pass so a re-armed focus spawns the same tick.
|
|
8
|
+
*
|
|
9
|
+
* 1. ROUTE -- product_request_route_returns materializes every not-yet-routed
|
|
10
|
+
* return of the product's outgoing requests (new entries, closes, declines)
|
|
11
|
+
* into pending returns on the requesting focus. Set-based over EVERY
|
|
12
|
+
* outgoing request, idempotent by dedupe key, so it is safe each tick and no
|
|
13
|
+
* request's end goes unread however many requests the product has.
|
|
14
|
+
* 2. ENGAGE -- each not-yet-engaged return is engaged ONCE, by focus state:
|
|
15
|
+
* live team -> 'handed': the return is written to the lead's stdin
|
|
16
|
+
* now, without waiting for a respawn;
|
|
17
|
+
* armed, no team -> 'rearmed': the focus's re-arm signal is set (the
|
|
18
|
+
* escalation-answer path), so it spawns once for it
|
|
19
|
+
* even with no actionable delivery;
|
|
20
|
+
* un-armed -> left pending: visible on the focus, no spawn. It is
|
|
21
|
+
* engaged if and when the focus is armed.
|
|
22
|
+
* A stamp is written only once its engagement is PROVEN, so no return is
|
|
23
|
+
* ever marked engaged with nothing carrying it:
|
|
24
|
+
* handed -- the hand-off is written to stdin FIRST; only a landed write
|
|
25
|
+
* is stamped. A write that did not land stamps nothing and is
|
|
26
|
+
* retried next poll; a stamp that fails after a landed write
|
|
27
|
+
* re-hands next poll (at-least-once to a live team, never lost).
|
|
28
|
+
* rearmed -- the stamp and the re-arm signal are one transaction
|
|
29
|
+
* (engage_product_request_returns): no signal, no stamp. It
|
|
30
|
+
* writes a fresh signal value per engaged batch, and the spawn
|
|
31
|
+
* side clears only the value it spawned for, so a batch engaged
|
|
32
|
+
* after a spawn rendered its prompt gets its own spawn.
|
|
33
|
+
* Every unengaged return is read (paged) and engaged in batches of at most
|
|
34
|
+
* ENGAGE_BATCH_SIZE, so a burst of any size is engaged in one poll. A stamp
|
|
35
|
+
* covers only returns not already engaged or acknowledged, so one return is
|
|
36
|
+
* handed / re-arms at most once -- the billed-respawn-loop hazard cannot form
|
|
37
|
+
* around an unconsumed input.
|
|
38
|
+
*
|
|
39
|
+
* Every pass that spawns on the focus renders the unacknowledged returns in full
|
|
40
|
+
* (nexus/pending-returns.ts), and the requesting team's acknowledgement
|
|
41
|
+
* (telora_product_request acknowledge_return) is the durable consumed state.
|
|
42
|
+
*
|
|
43
|
+
* This replaces the nexus_request_resolved / nexus_clarification_requested
|
|
44
|
+
* escalations the return path used to file: those reached an agent only inside a
|
|
45
|
+
* coding spawn, truncated findings to the escalation cap, and spawned nothing.
|
|
46
|
+
*/
|
|
47
|
+
import type { Writable } from 'node:stream';
|
|
48
|
+
import type { DaemonConfig } from '../types/index.js';
|
|
49
|
+
import { type CallApiDeps } from '../queries/shared.js';
|
|
50
|
+
import { type PendingReturn } from './pending-returns.js';
|
|
51
|
+
/** Ids per engage call (the product-api's per-call cap). */
|
|
52
|
+
export declare const ENGAGE_BATCH_SIZE = 200;
|
|
53
|
+
/** How a not-yet-engaged return is engaged this tick. */
|
|
54
|
+
export type ReturnEngagementDecision = 'handed' | 'rearmed' | 'leave_pending';
|
|
55
|
+
/** Pure: the engagement a return gets from its requesting focus's state. */
|
|
56
|
+
export declare function decideReturnEngagement(state: {
|
|
57
|
+
hasLiveTeam: boolean;
|
|
58
|
+
armed: boolean;
|
|
59
|
+
}): ReturnEngagementDecision;
|
|
60
|
+
/** Pure: group returns by requesting focus; a return with no focus has no team to engage. */
|
|
61
|
+
export declare function groupReturnsByFocus(returns: readonly PendingReturn[]): Map<string, PendingReturn[]>;
|
|
62
|
+
export interface NexusReturnRoutingDeps {
|
|
63
|
+
/** Route the product's not-yet-routed returns; returns how many were created. */
|
|
64
|
+
routeReturns: (productId: string) => Promise<number>;
|
|
65
|
+
/** The product's pending returns no team has been engaged for yet. */
|
|
66
|
+
listUnengagedReturns: (productId: string) => Promise<PendingReturn[]>;
|
|
67
|
+
/** focusId -> armed (active + assigned agent role), for the product's active focuses. */
|
|
68
|
+
listFocusArmState: (productId: string) => Promise<Map<string, boolean>>;
|
|
69
|
+
/** The live team lead's stdin for a focus, or null when no team is live. */
|
|
70
|
+
getLiveTeamStdin: (focusId: string) => Writable | null;
|
|
71
|
+
/** Stamp returns engaged (only not-yet-engaged ones); returns the ids actually stamped. */
|
|
72
|
+
engageReturns: (focusId: string, pendingReturnIds: string[], engagement: 'handed' | 'rearmed') => Promise<string[]>;
|
|
73
|
+
/** Write a message to a live team's stdin; false when the write did not land. */
|
|
74
|
+
handToLiveTeam: (focusId: string, stdin: Writable, message: string) => boolean;
|
|
75
|
+
log?: (msg: string) => void;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The production deps. `apiDeps.callApi` replaces the product-api client for
|
|
79
|
+
* every API-backed dep (route, list, engage), so an end-to-end test drives this
|
|
80
|
+
* exact wiring over one shared fake API.
|
|
81
|
+
*/
|
|
82
|
+
export declare function defaultNexusReturnRoutingDeps(apiDeps?: CallApiDeps): NexusReturnRoutingDeps;
|
|
83
|
+
/**
|
|
84
|
+
* Poll one product: route its returns, then engage each requesting focus once.
|
|
85
|
+
* Best-effort throughout: a route failure still engages already-routed returns,
|
|
86
|
+
* and one focus's failure never aborts the others.
|
|
87
|
+
*/
|
|
88
|
+
export declare function routeNexusReturns(config: DaemonConfig, deps?: NexusReturnRoutingDeps): Promise<void>;
|
|
89
|
+
//# sourceMappingURL=return-routing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"return-routing.d.ts","sourceRoot":"","sources":["../../src/nexus/return-routing.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAW,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAIjE,OAAO,EAAkD,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAK1G,4DAA4D;AAC5D,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,yDAAyD;AACzD,MAAM,MAAM,wBAAwB,GAAG,QAAQ,GAAG,SAAS,GAAG,eAAe,CAAC;AAE9E,4EAA4E;AAC5E,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAAE,WAAW,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,wBAAwB,CAIhH;AAED,6FAA6F;AAC7F,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,CASnG;AAED,MAAM,WAAW,sBAAsB;IACrC,iFAAiF;IACjF,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,sEAAsE;IACtE,oBAAoB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IACtE,yFAAyF;IACzF,iBAAiB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxE,4EAA4E;IAC5E,gBAAgB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,QAAQ,GAAG,IAAI,CAAC;IACvD,2FAA2F;IAC3F,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,QAAQ,GAAG,SAAS,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACpH,iFAAiF;IACjF,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAC/E,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,GAAE,WAAgB,GAAG,sBAAsB,CA4B/F;AA4CD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,YAAY,EACpB,IAAI,GAAE,sBAAwD,GAC7D,OAAO,CAAC,IAAI,CAAC,CAqCf"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
|
|
2
|
+
// Pinned by: packages/daemon/src/nexus/nexus-return-path-e2e.test.ts,
|
|
3
|
+
// packages/daemon/src/nexus/nexus-return-routing.test.ts
|
|
4
|
+
// PINNED-BY-END
|
|
5
|
+
import { sendMessage } from '@telora/daemon-core';
|
|
6
|
+
import { callApi } from '../queries/shared.js';
|
|
7
|
+
import { getActiveFocuses } from '../queries/focus-discovery.js';
|
|
8
|
+
import { getActiveTeams } from '../focus/focus-team-state.js';
|
|
9
|
+
import { recordStdinMessageSent } from '../completion/stdin-turn-tracker.js';
|
|
10
|
+
import { buildNexusReturnHandOff, listAllPendingReturns } from './pending-returns.js';
|
|
11
|
+
import { createLogger } from '../shell/log.js';
|
|
12
|
+
const dlog = createLogger({ module: 'nexus/return-routing' });
|
|
13
|
+
/** Ids per engage call (the product-api's per-call cap). */
|
|
14
|
+
export const ENGAGE_BATCH_SIZE = 200;
|
|
15
|
+
/** Pure: the engagement a return gets from its requesting focus's state. */
|
|
16
|
+
export function decideReturnEngagement(state) {
|
|
17
|
+
if (state.hasLiveTeam)
|
|
18
|
+
return 'handed';
|
|
19
|
+
if (state.armed)
|
|
20
|
+
return 'rearmed';
|
|
21
|
+
return 'leave_pending';
|
|
22
|
+
}
|
|
23
|
+
/** Pure: group returns by requesting focus; a return with no focus has no team to engage. */
|
|
24
|
+
export function groupReturnsByFocus(returns) {
|
|
25
|
+
const byFocus = new Map();
|
|
26
|
+
for (const ret of returns) {
|
|
27
|
+
if (!ret.sourceFocusId)
|
|
28
|
+
continue;
|
|
29
|
+
const list = byFocus.get(ret.sourceFocusId);
|
|
30
|
+
if (list)
|
|
31
|
+
list.push(ret);
|
|
32
|
+
else
|
|
33
|
+
byFocus.set(ret.sourceFocusId, [ret]);
|
|
34
|
+
}
|
|
35
|
+
return byFocus;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The production deps. `apiDeps.callApi` replaces the product-api client for
|
|
39
|
+
* every API-backed dep (route, list, engage), so an end-to-end test drives this
|
|
40
|
+
* exact wiring over one shared fake API.
|
|
41
|
+
*/
|
|
42
|
+
export function defaultNexusReturnRoutingDeps(apiDeps = {}) {
|
|
43
|
+
const call = apiDeps.callApi ?? callApi;
|
|
44
|
+
return {
|
|
45
|
+
routeReturns: async (productId) => {
|
|
46
|
+
const res = await call('product_request_route_returns', { productId });
|
|
47
|
+
return res.routed ?? 0;
|
|
48
|
+
},
|
|
49
|
+
listUnengagedReturns: (productId) => listAllPendingReturns({ productId, unengagedOnly: true }, apiDeps),
|
|
50
|
+
listFocusArmState: async (productId) => {
|
|
51
|
+
const focuses = await getActiveFocuses('', productId);
|
|
52
|
+
return new Map(focuses.map((f) => [f.focus_id, f.status === 'active' && !!f.assigned_agent_role_id]));
|
|
53
|
+
},
|
|
54
|
+
getLiveTeamStdin: (focusId) => getActiveTeams().get(focusId)?.leadStdin ?? null,
|
|
55
|
+
engageReturns: async (focusId, pendingReturnIds, engagement) => {
|
|
56
|
+
const res = await call('product_request_engage_returns', {
|
|
57
|
+
focusId,
|
|
58
|
+
pendingReturnIds,
|
|
59
|
+
engagement,
|
|
60
|
+
});
|
|
61
|
+
return res.engagedIds ?? [];
|
|
62
|
+
},
|
|
63
|
+
handToLiveTeam: (focusId, stdin, message) => {
|
|
64
|
+
const sent = sendMessage(stdin, message);
|
|
65
|
+
if (sent)
|
|
66
|
+
recordStdinMessageSent(focusId);
|
|
67
|
+
return sent;
|
|
68
|
+
},
|
|
69
|
+
log: (msg) => dlog.info(msg),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** Stamp ids in batches the engage call accepts; returns every id actually stamped. */
|
|
73
|
+
async function engageInBatches(focusId, ids, engagement, deps) {
|
|
74
|
+
const stamped = [];
|
|
75
|
+
for (let i = 0; i < ids.length; i += ENGAGE_BATCH_SIZE) {
|
|
76
|
+
stamped.push(...(await deps.engageReturns(focusId, ids.slice(i, i + ENGAGE_BATCH_SIZE), engagement)));
|
|
77
|
+
}
|
|
78
|
+
return stamped;
|
|
79
|
+
}
|
|
80
|
+
/** Engage one focus's not-yet-engaged returns per its state; a stamp follows proof. */
|
|
81
|
+
async function engageFocusReturns(focusId, returns, armed, deps, log) {
|
|
82
|
+
const stdin = deps.getLiveTeamStdin(focusId);
|
|
83
|
+
const decision = decideReturnEngagement({ hasLiveTeam: stdin !== null, armed });
|
|
84
|
+
if (decision === 'leave_pending')
|
|
85
|
+
return;
|
|
86
|
+
const ids = returns.map((r) => r.id);
|
|
87
|
+
if (decision === 'rearmed') {
|
|
88
|
+
const engagedIds = await engageInBatches(focusId, ids, 'rearmed', deps);
|
|
89
|
+
if (engagedIds.length > 0) {
|
|
90
|
+
log(`[nexus] re-armed focus ${focusId.slice(0, 8)} for ${engagedIds.length} pending return(s)`);
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const landed = stdin !== null && deps.handToLiveTeam(focusId, stdin, buildNexusReturnHandOff(returns));
|
|
95
|
+
if (!landed) {
|
|
96
|
+
log(`[nexus] live hand-off to focus ${focusId.slice(0, 8)} did not land; ${ids.length} return(s) stay unengaged and are retried next poll`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const engagedIds = await engageInBatches(focusId, ids, 'handed', deps);
|
|
100
|
+
log(`[nexus] handed ${ids.length} pending return(s) to the live team on focus ${focusId.slice(0, 8)} (${engagedIds.length} stamped)`);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Poll one product: route its returns, then engage each requesting focus once.
|
|
104
|
+
* Best-effort throughout: a route failure still engages already-routed returns,
|
|
105
|
+
* and one focus's failure never aborts the others.
|
|
106
|
+
*/
|
|
107
|
+
export async function routeNexusReturns(config, deps = defaultNexusReturnRoutingDeps()) {
|
|
108
|
+
const productId = config.productId;
|
|
109
|
+
if (!productId)
|
|
110
|
+
return;
|
|
111
|
+
const log = deps.log ?? ((msg) => dlog.info(msg));
|
|
112
|
+
try {
|
|
113
|
+
const routed = await deps.routeReturns(productId);
|
|
114
|
+
if (routed > 0)
|
|
115
|
+
log(`[nexus] routed ${routed} new return(s) to requesting focuses for ${productId}`);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
log(`[nexus] failed to route returns for ${productId}: ${err.message}`);
|
|
119
|
+
}
|
|
120
|
+
let unengaged = [];
|
|
121
|
+
try {
|
|
122
|
+
unengaged = await deps.listUnengagedReturns(productId);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
log(`[nexus] failed to list unengaged returns for ${productId}: ${err.message}`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const byFocus = groupReturnsByFocus(unengaged);
|
|
129
|
+
if (byFocus.size === 0)
|
|
130
|
+
return;
|
|
131
|
+
let armState = new Map();
|
|
132
|
+
try {
|
|
133
|
+
armState = await deps.listFocusArmState(productId);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
// Unknown arm state: a live team is still handed its returns; nothing re-arms.
|
|
137
|
+
log(`[nexus] failed to read focus arm state for ${productId}: ${err.message}`);
|
|
138
|
+
}
|
|
139
|
+
for (const [focusId, returns] of byFocus) {
|
|
140
|
+
try {
|
|
141
|
+
await engageFocusReturns(focusId, returns, armState.get(focusId) === true, deps, log);
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
log(`[nexus] failed to engage returns on focus ${focusId.slice(0, 8)}: ${err.message}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=return-routing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"return-routing.js","sourceRoot":"","sources":["../../src/nexus/return-routing.ts"],"names":[],"mappings":"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"}
|