newmark-agent 0.5.14 → 0.6.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/dist/conversation-utility-host.bundle.cjs +4565 -2998
- package/dist/conversation-utility-host.js +1 -1
- package/dist/core/agent.d.ts +108 -16
- package/dist/core/agent.js +698 -195
- package/dist/core/agentKernel/agent.d.ts +1 -0
- package/dist/core/agentKernel/agent.js +9 -0
- package/dist/core/agentKernelDiagnostics.d.ts +4 -6
- package/dist/core/agentKernelDiagnostics.js +12 -9
- package/dist/core/agentKernelRunner.d.ts +5 -0
- package/dist/core/agentKernelRunner.js +250 -107
- package/dist/core/autoRouter.js +17 -27
- package/dist/core/config.d.ts +2 -0
- package/dist/core/config.js +7 -1
- package/dist/core/conversationCommandState.d.ts +17 -0
- package/dist/core/conversationCommandState.js +140 -0
- package/dist/core/conversationKernel.d.ts +25 -4
- package/dist/core/conversationKernel.js +360 -89
- package/dist/core/conversationListEvent.d.ts +6 -0
- package/dist/core/conversationListEvent.js +14 -0
- package/dist/core/electronUtilityAgentClient.d.ts +4 -1
- package/dist/core/electronUtilityAgentClient.js +4 -4
- package/dist/core/electronUtilityRuntimePool.d.ts +8 -2
- package/dist/core/electronUtilityRuntimePool.js +11 -5
- package/dist/core/installUpdate.js +41 -29
- package/dist/core/modelResponseHealth.d.ts +17 -0
- package/dist/core/modelResponseHealth.js +193 -0
- package/dist/core/providerUsageAccounting.d.ts +47 -0
- package/dist/core/providerUsageAccounting.js +71 -0
- package/dist/core/requestContextEstimate.d.ts +18 -0
- package/dist/core/requestContextEstimate.js +54 -0
- package/dist/core/subagent.d.ts +89 -5
- package/dist/core/subagent.js +264 -41
- package/dist/core/subagentCommunication.d.ts +43 -0
- package/dist/core/subagentCommunication.js +167 -0
- package/dist/core/types.d.ts +34 -1
- package/dist/core/utilityAgentProtocol.d.ts +4 -0
- package/dist/core/workEventCoalescer.js +4 -1
- package/dist/core/wslAgentClient.d.ts +18 -5
- package/dist/core/wslAgentClient.js +79 -27
- package/dist/core/wslAgentProtocol.d.ts +7 -0
- package/dist/core/wslAgentRuntimePool.d.ts +8 -2
- package/dist/core/wslAgentRuntimePool.js +19 -6
- package/dist/core/wslRuntimeProcessTree.d.ts +10 -0
- package/dist/core/wslRuntimeProcessTree.js +104 -0
- package/dist/llm/provider.d.ts +5 -12
- package/dist/llm/provider.js +150 -269
- package/dist/main.js +582 -356
- package/dist/preload.js +3 -3
- package/dist/providers/chat-completions.adapter.d.ts +2 -0
- package/dist/providers/chat-completions.adapter.js +84 -36
- package/dist/providers/provider-adapter.d.ts +2 -0
- package/dist/providers/provider-events.d.ts +23 -14
- package/dist/providers/provider-events.js +148 -43
- package/dist/providers/provider-headers.d.ts +7 -3
- package/dist/providers/provider-headers.js +109 -39
- package/dist/providers/provider-request-compat.d.ts +6 -0
- package/dist/providers/provider-request-compat.js +37 -0
- package/dist/providers/responses.adapter.d.ts +2 -0
- package/dist/providers/responses.adapter.js +192 -126
- package/dist/server.d.ts +9 -2
- package/dist/server.js +100 -24
- package/dist/tools/index.js +17 -1
- package/dist/ui/index.html +2515 -693
- package/dist/ui/lucide-sprite.svg +7 -0
- package/dist/ui/startup.html +6 -6
- package/dist/wsl-agent-host.bundle.cjs +4575 -3006
- package/dist/wsl-agent-host.js +9 -4
- package/package.json +23 -4
package/dist/core/subagent.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SubagentManager = void 0;
|
|
3
|
+
exports.SubagentManager = exports.SUBAGENT_RECOVERY_PROMPT = void 0;
|
|
4
4
|
exports.sharedSubagentManager = sharedSubagentManager;
|
|
5
5
|
exports.releaseSharedSubagentManager = releaseSharedSubagentManager;
|
|
6
6
|
const crypto_1 = require("crypto");
|
|
7
|
+
exports.SUBAGENT_RECOVERY_PROMPT = '[Peer runtime recovery] Continue the unfinished work from the persisted working history. Preserve completed tool results and follow the latest instruction; do not repeat completed work.';
|
|
7
8
|
function now() { return new Date().toISOString(); }
|
|
9
|
+
function cloneMailboxMessage(message) {
|
|
10
|
+
return { ...message, ...(message.control ? { control: { ...message.control } } : {}) };
|
|
11
|
+
}
|
|
8
12
|
function natureSlug(value) {
|
|
9
13
|
const normalized = String(value || 'subagent')
|
|
10
14
|
.normalize('NFKD')
|
|
@@ -15,7 +19,14 @@ function natureSlug(value) {
|
|
|
15
19
|
return normalized || 'subagent';
|
|
16
20
|
}
|
|
17
21
|
function cloneRecord(record) {
|
|
18
|
-
return {
|
|
22
|
+
return {
|
|
23
|
+
...record,
|
|
24
|
+
messages: record.messages.map(message => ({
|
|
25
|
+
...message,
|
|
26
|
+
tool_calls: message.tool_calls?.map(call => ({ ...call, function: { ...call.function } })),
|
|
27
|
+
})),
|
|
28
|
+
metadata: record.metadata ? structuredClone(record.metadata) : undefined,
|
|
29
|
+
};
|
|
19
30
|
}
|
|
20
31
|
function truncateText(value, maxChars) {
|
|
21
32
|
const text = String(value || '');
|
|
@@ -33,6 +44,7 @@ class SubagentManager {
|
|
|
33
44
|
rootInbox = [];
|
|
34
45
|
pending = [];
|
|
35
46
|
running = new Set();
|
|
47
|
+
activeJobs = new Map();
|
|
36
48
|
schedulingPaused = false;
|
|
37
49
|
nextSequence = 1;
|
|
38
50
|
concurrency;
|
|
@@ -41,18 +53,25 @@ class SubagentManager {
|
|
|
41
53
|
persist;
|
|
42
54
|
onMailboxMessage;
|
|
43
55
|
rootInboxListeners = new Set();
|
|
56
|
+
bindingOwner;
|
|
57
|
+
ownerBindings = new Map();
|
|
44
58
|
onSettled;
|
|
45
59
|
changedQueued = false;
|
|
46
60
|
settledWaiters = new Map();
|
|
47
61
|
conversationId;
|
|
48
62
|
rootAgentId;
|
|
49
63
|
hasRecords() { return this.subs.size > 0 || this.mailbox.length > 0 || this.rootInbox.length > 0; }
|
|
64
|
+
hasPendingWork() {
|
|
65
|
+
return this.running.size > 0 || this.pending.length > 0
|
|
66
|
+
|| Array.from(this.subs.values()).some(record => record.status === 'queued' || record.status === 'working');
|
|
67
|
+
}
|
|
50
68
|
reset() {
|
|
51
69
|
this.subs.clear();
|
|
52
70
|
this.mailbox = [];
|
|
53
71
|
this.rootInbox = [];
|
|
54
72
|
this.pending = [];
|
|
55
73
|
this.running.clear();
|
|
74
|
+
this.activeJobs.clear();
|
|
56
75
|
this.settledWaiters.clear();
|
|
57
76
|
this.nextSequence = 1;
|
|
58
77
|
this.schedulingPaused = false;
|
|
@@ -64,13 +83,17 @@ class SubagentManager {
|
|
|
64
83
|
this.onChange = options.onChange;
|
|
65
84
|
this.persist = options.persist;
|
|
66
85
|
this.onMailboxMessage = options.onMailboxMessage;
|
|
67
|
-
if (options.onRootInboxMessage)
|
|
86
|
+
if (options.onRootInboxMessage) {
|
|
68
87
|
this.rootInboxListeners.add(options.onRootInboxMessage);
|
|
88
|
+
this.bindingOwner = options.onRootInboxMessage;
|
|
89
|
+
}
|
|
69
90
|
this.onSettled = options.onSettled;
|
|
91
|
+
this.rememberOwnerBinding();
|
|
70
92
|
const state = options.state;
|
|
71
93
|
this.rootAgentId = String(state?.rootAgentId || options.rootAgentId || (0, crypto_1.randomUUID)());
|
|
72
94
|
this.nextSequence = Math.max(1, Number(state?.nextSequence || 1));
|
|
73
95
|
this.schedulingPaused = state?.schedulingPaused === true;
|
|
96
|
+
const savedJobs = new Map((state?.jobs || []).map(job => [job.id, job]));
|
|
74
97
|
for (const raw of state?.records || []) {
|
|
75
98
|
const record = cloneRecord(raw);
|
|
76
99
|
record.messages = record.messages.map(message => message.role === 'user'
|
|
@@ -80,13 +103,24 @@ class SubagentManager {
|
|
|
80
103
|
record.status = 'queued';
|
|
81
104
|
this.subs.set(record.id, record);
|
|
82
105
|
if (record.status === 'queued') {
|
|
83
|
-
const
|
|
106
|
+
const savedJob = savedJobs.get(record.id);
|
|
107
|
+
const sequence = Number(savedJob?.sequence || record.queueSequence || this.nextSequence++);
|
|
108
|
+
this.nextSequence = Math.max(this.nextSequence, sequence + 1);
|
|
84
109
|
record.queueSequence = sequence;
|
|
85
|
-
this.pending.push(
|
|
110
|
+
this.pending.push(savedJob
|
|
111
|
+
? { ...savedJob, sequence }
|
|
112
|
+
: { id: record.id, prompt: record.messages.filter(message => message.role === 'user').at(-1)?.content || record.prompt, flowName: record.flowName || '', sequence, reason: 'resume' });
|
|
86
113
|
}
|
|
87
114
|
}
|
|
88
|
-
|
|
89
|
-
|
|
115
|
+
// Historical messages always woke their recipient. Migrate only an absent
|
|
116
|
+
// field; an explicitly stored false must remain passive after a restart.
|
|
117
|
+
this.mailbox = (state?.mailbox || []).map(message => ({ ...cloneMailboxMessage(message),
|
|
118
|
+
wakeup: message.wakeup === undefined ? true : message.wakeup === true,
|
|
119
|
+
acceptedWhileActive: message.acceptedWhileActive === true,
|
|
120
|
+
}));
|
|
121
|
+
this.rootInbox = (state?.rootInbox || []).map(message => ({ ...message,
|
|
122
|
+
wakeup: message.wakeup === undefined ? true : message.wakeup === true,
|
|
123
|
+
}));
|
|
90
124
|
for (const record of this.subs.values())
|
|
91
125
|
this.queuePersistedUnread(record);
|
|
92
126
|
this.pending.sort((a, b) => a.sequence - b.sequence);
|
|
@@ -95,28 +129,79 @@ class SubagentManager {
|
|
|
95
129
|
bind(options) {
|
|
96
130
|
if (options.concurrency !== undefined)
|
|
97
131
|
this.setConcurrencyLimit(options.concurrency);
|
|
98
|
-
if (options.executor)
|
|
99
|
-
this.executor = options.executor;
|
|
100
|
-
if (options.onChange)
|
|
101
|
-
this.onChange = options.onChange;
|
|
102
|
-
if (options.persist)
|
|
103
|
-
this.persist = options.persist;
|
|
104
|
-
if (options.onMailboxMessage)
|
|
105
|
-
this.onMailboxMessage = options.onMailboxMessage;
|
|
106
132
|
if (options.onRootInboxMessage) {
|
|
107
133
|
const listener = options.onRootInboxMessage;
|
|
134
|
+
const previous = this.ownerBindings.get(listener);
|
|
135
|
+
const binding = {
|
|
136
|
+
executor: options.executor || previous?.executor,
|
|
137
|
+
onChange: options.onChange || previous?.onChange,
|
|
138
|
+
persist: options.persist || previous?.persist,
|
|
139
|
+
onMailboxMessage: options.onMailboxMessage || previous?.onMailboxMessage,
|
|
140
|
+
onSettled: options.onSettled || previous?.onSettled,
|
|
141
|
+
};
|
|
142
|
+
this.ownerBindings.delete(listener);
|
|
143
|
+
this.ownerBindings.set(listener, binding);
|
|
144
|
+
// A snapshot/foreground facade must not steal jobs or their settlement
|
|
145
|
+
// callbacks from the owner that is currently executing the queue.
|
|
146
|
+
if (!this.bindingOwner || this.bindingOwner === listener || !this.hasPendingWork()) {
|
|
147
|
+
this.bindingOwner = listener;
|
|
148
|
+
Object.assign(this, binding);
|
|
149
|
+
}
|
|
108
150
|
const added = !this.rootInboxListeners.has(listener);
|
|
109
151
|
this.rootInboxListeners.add(listener);
|
|
110
152
|
if (added)
|
|
111
153
|
queueMicrotask(() => this.replayRootInbox(listener));
|
|
112
154
|
}
|
|
113
|
-
|
|
114
|
-
|
|
155
|
+
else {
|
|
156
|
+
if (options.executor)
|
|
157
|
+
this.executor = options.executor;
|
|
158
|
+
if (options.onChange)
|
|
159
|
+
this.onChange = options.onChange;
|
|
160
|
+
if (options.persist)
|
|
161
|
+
this.persist = options.persist;
|
|
162
|
+
if (options.onMailboxMessage)
|
|
163
|
+
this.onMailboxMessage = options.onMailboxMessage;
|
|
164
|
+
if (options.onSettled)
|
|
165
|
+
this.onSettled = options.onSettled;
|
|
166
|
+
this.rememberOwnerBinding();
|
|
167
|
+
}
|
|
115
168
|
this.pump();
|
|
116
169
|
}
|
|
170
|
+
ownsExecutionBinding(listener) {
|
|
171
|
+
return this.bindingOwner === listener;
|
|
172
|
+
}
|
|
173
|
+
rememberOwnerBinding() {
|
|
174
|
+
if (!this.bindingOwner)
|
|
175
|
+
return;
|
|
176
|
+
this.ownerBindings.delete(this.bindingOwner);
|
|
177
|
+
this.ownerBindings.set(this.bindingOwner, {
|
|
178
|
+
executor: this.executor, onChange: this.onChange, persist: this.persist,
|
|
179
|
+
onMailboxMessage: this.onMailboxMessage, onSettled: this.onSettled,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
117
182
|
removeRootInboxListener(listener) {
|
|
118
183
|
this.rootInboxListeners.delete(listener);
|
|
119
184
|
}
|
|
185
|
+
/** Detach one facade without clearing a newer owner's callbacks or live jobs. */
|
|
186
|
+
releaseOwnerBinding(listener) {
|
|
187
|
+
if (this.bindingOwner === listener && this.hasPendingWork())
|
|
188
|
+
return false;
|
|
189
|
+
this.rootInboxListeners.delete(listener);
|
|
190
|
+
this.ownerBindings.delete(listener);
|
|
191
|
+
if (this.bindingOwner !== listener)
|
|
192
|
+
return true;
|
|
193
|
+
// A temporary facade may have rebound this shared manager while another
|
|
194
|
+
// idle facade remained usable. Restore that owner instead of stranding
|
|
195
|
+
// its next peer in the durable queue with no executor.
|
|
196
|
+
const previous = Array.from(this.ownerBindings.entries()).at(-1);
|
|
197
|
+
this.bindingOwner = previous?.[0];
|
|
198
|
+
this.executor = previous?.[1].executor;
|
|
199
|
+
this.onChange = previous?.[1].onChange;
|
|
200
|
+
this.persist = previous?.[1].persist;
|
|
201
|
+
this.onMailboxMessage = previous?.[1].onMailboxMessage;
|
|
202
|
+
this.onSettled = previous?.[1].onSettled;
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
120
205
|
create(name, prompt, model, inputMode, agentMode = 'build', createdByAgentId = this.rootAgentId, flowName = '', goalObjective = '', flowPc = 0, buildRunId = '', intelligenceTier = '') {
|
|
121
206
|
const id = (0, crypto_1.randomUUID)();
|
|
122
207
|
const shortId = id.replace(/-/g, '').slice(0, 8);
|
|
@@ -169,18 +254,44 @@ class SubagentManager {
|
|
|
169
254
|
// convenience lookup only; the id/shortId/displayName paths stay exact.
|
|
170
255
|
return [...this.subs.values()].find(item => item.name === id || item.displayName === id || item.shortId === id || item.natureSlug === natureSlug(id));
|
|
171
256
|
}
|
|
172
|
-
send(id, prompt) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
257
|
+
send(id, prompt, wakeup = false) {
|
|
258
|
+
// The compatibility entry point shares the durable mailbox and its
|
|
259
|
+
// single-executor guarantee; a direct enqueue could overlap this peer.
|
|
260
|
+
return this.sendMessage(this.rootAgentId, id, prompt, 'directive', {}, wakeup).ok;
|
|
261
|
+
}
|
|
262
|
+
broadcastStop(fromAgentId, runId, force = false) {
|
|
263
|
+
// Set the gate before publishing any control receipt, and commit the gate
|
|
264
|
+
// plus the entire batch once. No per-message callback can dispatch work.
|
|
265
|
+
const wasPaused = this.schedulingPaused;
|
|
266
|
+
this.schedulingPaused = true;
|
|
267
|
+
const ids = [];
|
|
268
|
+
const peerIds = [];
|
|
269
|
+
const stamp = now();
|
|
270
|
+
const stopRunId = String(runId || '');
|
|
271
|
+
const alreadySent = new Set(this.mailbox.filter(message => message.control?.action === 'stop'
|
|
272
|
+
&& message.control.runId === stopRunId && message.control.force === (force === true)).map(message => message.toAgentId));
|
|
273
|
+
for (const record of this.subs.values()) {
|
|
274
|
+
if (record.status === 'closed' || alreadySent.has(record.id))
|
|
275
|
+
continue;
|
|
276
|
+
const message = {
|
|
277
|
+
id: (0, crypto_1.randomUUID)(), conversationId: this.conversationId, sequence: this.nextSequence++,
|
|
278
|
+
fromAgentId, toAgentId: record.id, kind: 'directive',
|
|
279
|
+
body: force ? '[Runtime control] Force stop this conversation work.' : '[Runtime control] Stop this conversation work.',
|
|
280
|
+
wakeup: false, acceptedWhileActive: false,
|
|
281
|
+
control: { action: 'stop', runId: stopRunId, force: force === true },
|
|
282
|
+
createdAt: stamp, readAt: stamp,
|
|
283
|
+
};
|
|
284
|
+
this.mailbox.push(message);
|
|
285
|
+
ids.push(message.id);
|
|
286
|
+
peerIds.push(record.id);
|
|
287
|
+
}
|
|
288
|
+
if (!wasPaused || ids.length) {
|
|
289
|
+
this.persistNow();
|
|
290
|
+
this.changed();
|
|
291
|
+
}
|
|
292
|
+
return { count: ids.length, ids, peerIds };
|
|
182
293
|
}
|
|
183
|
-
sendMessage(fromAgentId, toAgentId, body, kind = 'directive', details = {}) {
|
|
294
|
+
sendMessage(fromAgentId, toAgentId, body, kind = 'directive', details = {}, wakeup = false) {
|
|
184
295
|
if (!body.trim())
|
|
185
296
|
return { ok: false, error: 'Message body is required.' };
|
|
186
297
|
if (fromAgentId === toAgentId)
|
|
@@ -188,6 +299,8 @@ class SubagentManager {
|
|
|
188
299
|
const target = this.get(toAgentId);
|
|
189
300
|
if (!target)
|
|
190
301
|
return { ok: false, error: `Peer agent not found: ${toAgentId}` };
|
|
302
|
+
if (fromAgentId === target.id)
|
|
303
|
+
return { ok: false, error: 'Peer agents cannot message themselves.' };
|
|
191
304
|
if (target.status === 'closed')
|
|
192
305
|
return { ok: false, error: `Peer agent is closed: ${target.qualifiedName}` };
|
|
193
306
|
const message = {
|
|
@@ -198,6 +311,8 @@ class SubagentManager {
|
|
|
198
311
|
toAgentId: target.id,
|
|
199
312
|
kind,
|
|
200
313
|
body: truncateText(body, 32000),
|
|
314
|
+
wakeup: wakeup === true,
|
|
315
|
+
acceptedWhileActive: target.status === 'working' || target.status === 'queued',
|
|
201
316
|
correlationId: details.correlationId,
|
|
202
317
|
replyTo: details.replyTo,
|
|
203
318
|
createdAt: now(),
|
|
@@ -213,7 +328,7 @@ class SubagentManager {
|
|
|
213
328
|
this.changed();
|
|
214
329
|
return { ok: true, message: { ...message } };
|
|
215
330
|
}
|
|
216
|
-
sendRootMessage(fromAgentId, body, kind = 'result') {
|
|
331
|
+
sendRootMessage(fromAgentId, body, kind = 'result', settlementRevision, wakeup = false) {
|
|
217
332
|
if (!body.trim())
|
|
218
333
|
return { ok: false, error: 'Message body is required.' };
|
|
219
334
|
if (fromAgentId === this.rootAgentId)
|
|
@@ -226,7 +341,11 @@ class SubagentManager {
|
|
|
226
341
|
toAgentId: this.rootAgentId,
|
|
227
342
|
kind,
|
|
228
343
|
body: truncateText(body, 32000),
|
|
344
|
+
wakeup: wakeup === true,
|
|
229
345
|
createdAt: now(),
|
|
346
|
+
...(kind === 'result' && Number.isSafeInteger(settlementRevision) && Number(settlementRevision) > 0
|
|
347
|
+
? { source: 'automatic-settlement', settlementRevision }
|
|
348
|
+
: {}),
|
|
230
349
|
};
|
|
231
350
|
this.rootInbox.push(message);
|
|
232
351
|
this.persistNow();
|
|
@@ -240,6 +359,31 @@ class SubagentManager {
|
|
|
240
359
|
.sort((a, b) => a.sequence - b.sequence)
|
|
241
360
|
.map(message => ({ ...message }));
|
|
242
361
|
}
|
|
362
|
+
acknowledgeSettlementResults(receipts) {
|
|
363
|
+
const observed = new Set(receipts.filter(receipt => typeof receipt.peerId === 'string'
|
|
364
|
+
&& Number.isSafeInteger(receipt.revision) && receipt.revision > 0)
|
|
365
|
+
.map(receipt => `${receipt.peerId}:${receipt.revision}`));
|
|
366
|
+
const ids = [];
|
|
367
|
+
const stamp = now();
|
|
368
|
+
let changed = false;
|
|
369
|
+
for (const message of this.rootInbox) {
|
|
370
|
+
if (message.source !== 'automatic-settlement' || message.kind !== 'result'
|
|
371
|
+
|| !observed.has(`${message.fromAgentId}:${message.settlementRevision}`))
|
|
372
|
+
continue;
|
|
373
|
+
// Include previously acknowledged IDs so cold-restored continuation
|
|
374
|
+
// copies can be retired without changing already-consumed history.
|
|
375
|
+
ids.push(message.id);
|
|
376
|
+
if (!message.readAt) {
|
|
377
|
+
message.readAt = stamp;
|
|
378
|
+
changed = true;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (changed) {
|
|
382
|
+
this.persistNow();
|
|
383
|
+
this.changed();
|
|
384
|
+
}
|
|
385
|
+
return ids;
|
|
386
|
+
}
|
|
243
387
|
acknowledgeRootInbox(messageId) {
|
|
244
388
|
const message = this.rootInbox.find(item => item.id === messageId && !item.readAt);
|
|
245
389
|
if (!message)
|
|
@@ -253,7 +397,7 @@ class SubagentManager {
|
|
|
253
397
|
const messages = this.mailbox.filter(message => message.toAgentId === agentId && !message.readAt);
|
|
254
398
|
if (messages.length)
|
|
255
399
|
this.markMailboxRead(messages);
|
|
256
|
-
return messages.map(
|
|
400
|
+
return messages.map(cloneMailboxMessage);
|
|
257
401
|
}
|
|
258
402
|
acknowledgeMailbox(agentId, messageId) {
|
|
259
403
|
const message = this.mailbox.find(item => item.id === messageId && item.toAgentId === agentId && !item.readAt);
|
|
@@ -306,6 +450,8 @@ class SubagentManager {
|
|
|
306
450
|
toAgentId: message.toAgentId,
|
|
307
451
|
kind: message.kind,
|
|
308
452
|
body: truncateText(message.body, 2000),
|
|
453
|
+
wakeup: message.wakeup,
|
|
454
|
+
acceptedWhileActive: message.acceptedWhileActive,
|
|
309
455
|
createdAt: message.createdAt,
|
|
310
456
|
readAt: message.readAt,
|
|
311
457
|
}));
|
|
@@ -371,7 +517,7 @@ class SubagentManager {
|
|
|
371
517
|
if (record)
|
|
372
518
|
record.messages.push({ role: 'assistant', content });
|
|
373
519
|
}
|
|
374
|
-
replaceContext(id, history, compression) {
|
|
520
|
+
replaceContext(id, history, compression, inputCommitted = false) {
|
|
375
521
|
const record = this.get(id);
|
|
376
522
|
if (!record)
|
|
377
523
|
return;
|
|
@@ -388,7 +534,7 @@ class SubagentManager {
|
|
|
388
534
|
if (message.name)
|
|
389
535
|
stored.name = String(message.name);
|
|
390
536
|
if (Array.isArray(message.tool_calls))
|
|
391
|
-
stored.tool_calls = message.tool_calls;
|
|
537
|
+
stored.tool_calls = structuredClone(message.tool_calls);
|
|
392
538
|
if (message.hidden_user_input)
|
|
393
539
|
stored.hidden_user_input = true;
|
|
394
540
|
if (message.goal_continuation)
|
|
@@ -406,6 +552,19 @@ class SubagentManager {
|
|
|
406
552
|
contextCompression: compression ? { ...compression } : null,
|
|
407
553
|
};
|
|
408
554
|
record.updatedAt = now();
|
|
555
|
+
const activeJob = this.activeJobs.get(record.id);
|
|
556
|
+
if (activeJob && inputCommitted)
|
|
557
|
+
activeJob.inputCommitted = true;
|
|
558
|
+
this.persistNow();
|
|
559
|
+
this.changed();
|
|
560
|
+
}
|
|
561
|
+
patchMetadata(id, patch) {
|
|
562
|
+
const record = this.get(id);
|
|
563
|
+
if (!record)
|
|
564
|
+
return;
|
|
565
|
+
record.metadata = { ...(record.metadata || {}), ...structuredClone(patch) };
|
|
566
|
+
record.updatedAt = now();
|
|
567
|
+
this.persistNow();
|
|
409
568
|
this.changed();
|
|
410
569
|
}
|
|
411
570
|
complete(id, result) {
|
|
@@ -414,6 +573,7 @@ class SubagentManager {
|
|
|
414
573
|
return;
|
|
415
574
|
this.pending = this.pending.filter(job => job.id !== record.id);
|
|
416
575
|
record.result = result;
|
|
576
|
+
record.settlementRevision = Math.max(0, Math.floor(Number(record.settlementRevision) || 0)) + 1;
|
|
417
577
|
record.status = 'completed';
|
|
418
578
|
record.error = undefined;
|
|
419
579
|
record.completedAt = now();
|
|
@@ -430,6 +590,7 @@ class SubagentManager {
|
|
|
430
590
|
return;
|
|
431
591
|
this.pending = this.pending.filter(job => job.id !== record.id);
|
|
432
592
|
record.result = `[Subagent Error] ${error}`;
|
|
593
|
+
record.settlementRevision = Math.max(0, Math.floor(Number(record.settlementRevision) || 0)) + 1;
|
|
433
594
|
record.status = 'error';
|
|
434
595
|
record.error = error;
|
|
435
596
|
record.completedAt = now();
|
|
@@ -508,6 +669,33 @@ class SubagentManager {
|
|
|
508
669
|
}
|
|
509
670
|
listActive() { return this.listAll().filter(item => item.status !== 'closed'); }
|
|
510
671
|
listAll() { return [...this.subs.values()].map(cloneRecord); }
|
|
672
|
+
/** Model-facing list: detailed transcripts and request cache remain private. */
|
|
673
|
+
listSummaries(status = '') {
|
|
674
|
+
const counts = new Map();
|
|
675
|
+
for (const message of this.mailbox) {
|
|
676
|
+
const count = counts.get(message.toAgentId) || { total: 0, unread: 0 };
|
|
677
|
+
count.total++;
|
|
678
|
+
if (!message.readAt)
|
|
679
|
+
count.unread++;
|
|
680
|
+
counts.set(message.toAgentId, count);
|
|
681
|
+
}
|
|
682
|
+
return [...this.subs.values()].filter(record => !status || record.status === status).map(record => ({
|
|
683
|
+
id: record.id, shortId: record.shortId, natureSlug: record.natureSlug,
|
|
684
|
+
displayName: record.displayName, qualifiedName: record.qualifiedName, name: record.name,
|
|
685
|
+
conversationId: record.conversationId, createdByAgentId: record.createdByAgentId,
|
|
686
|
+
buildRunId: record.buildRunId, intelligenceTier: record.intelligenceTier,
|
|
687
|
+
prompt: truncateText(record.prompt, 1200), model: record.model, inputMode: record.inputMode,
|
|
688
|
+
agentMode: record.agentMode, mode: record.agentMode, status: record.status, active: record.status !== 'closed',
|
|
689
|
+
goalObjective: record.goalObjective ? truncateText(record.goalObjective, 800) : undefined,
|
|
690
|
+
flowName: record.flowName, flowPc: record.flowPc, queueSequence: record.queueSequence,
|
|
691
|
+
resultAvailable: record.result !== null, resultChars: record.result?.length || 0,
|
|
692
|
+
settlementRevision: record.settlementRevision, messageCount: record.messages.length,
|
|
693
|
+
error: record.error ? truncateText(record.error, 512) : undefined,
|
|
694
|
+
createdAt: record.createdAt, startedAt: record.startedAt, updatedAt: record.updatedAt,
|
|
695
|
+
completedAt: record.completedAt, closedAt: record.closedAt,
|
|
696
|
+
mailbox: counts.get(record.id) || { total: 0, unread: 0 },
|
|
697
|
+
}));
|
|
698
|
+
}
|
|
511
699
|
activeCountForBuild(buildRunId) {
|
|
512
700
|
const target = String(buildRunId || '').trim();
|
|
513
701
|
if (!target)
|
|
@@ -552,8 +740,17 @@ class SubagentManager {
|
|
|
552
740
|
rootAgentId: this.rootAgentId,
|
|
553
741
|
nextSequence: this.nextSequence,
|
|
554
742
|
schedulingPaused: this.schedulingPaused,
|
|
743
|
+
jobs: [
|
|
744
|
+
...Array.from(this.activeJobs.values(), job => ({
|
|
745
|
+
...job, reason: 'resume', inputCommitted: false,
|
|
746
|
+
prompt: job.inputCommitted
|
|
747
|
+
? exports.SUBAGENT_RECOVERY_PROMPT
|
|
748
|
+
: job.prompt,
|
|
749
|
+
})),
|
|
750
|
+
...this.pending.map(job => ({ ...job })),
|
|
751
|
+
].sort((a, b) => a.sequence - b.sequence),
|
|
555
752
|
records: this.listAll().map(record => record.status === 'working' ? { ...record, status: 'queued' } : record),
|
|
556
|
-
mailbox: this.mailbox.map(
|
|
753
|
+
mailbox: this.mailbox.map(cloneMailboxMessage),
|
|
557
754
|
rootInbox: this.rootInbox.map(message => ({ ...message })),
|
|
558
755
|
};
|
|
559
756
|
}
|
|
@@ -564,6 +761,7 @@ class SubagentManager {
|
|
|
564
761
|
existing.prompt = `${existing.prompt}\n\n${prompt}`;
|
|
565
762
|
record.status = 'queued';
|
|
566
763
|
record.updatedAt = now();
|
|
764
|
+
this.persistNow();
|
|
567
765
|
this.changed();
|
|
568
766
|
this.pump();
|
|
569
767
|
return;
|
|
@@ -574,6 +772,7 @@ class SubagentManager {
|
|
|
574
772
|
record.updatedAt = now();
|
|
575
773
|
this.pending.push({ id: record.id, prompt, flowName, sequence, reason });
|
|
576
774
|
this.pending.sort((a, b) => a.sequence - b.sequence);
|
|
775
|
+
this.persistNow();
|
|
577
776
|
this.changed();
|
|
578
777
|
this.pump();
|
|
579
778
|
}
|
|
@@ -586,7 +785,8 @@ class SubagentManager {
|
|
|
586
785
|
if (record.status === 'closed')
|
|
587
786
|
return;
|
|
588
787
|
const unread = this.unreadMailbox(record.id);
|
|
589
|
-
|
|
788
|
+
const activating = unread.find(message => message.wakeup || message.acceptedWhileActive);
|
|
789
|
+
if (!activating)
|
|
590
790
|
return;
|
|
591
791
|
if (record.status === 'queued') {
|
|
592
792
|
const existing = this.pending.find(job => job.id === record.id);
|
|
@@ -598,17 +798,19 @@ class SubagentManager {
|
|
|
598
798
|
return;
|
|
599
799
|
}
|
|
600
800
|
record.status = 'queued';
|
|
601
|
-
const sequence = Number(record.queueSequence ||
|
|
801
|
+
const sequence = Number(record.queueSequence || activating.sequence || this.nextSequence++);
|
|
602
802
|
record.queueSequence = sequence;
|
|
603
|
-
this.pending.push({ id: record.id, prompt:
|
|
803
|
+
this.pending.push({ id: record.id, prompt: '', flowName: record.flowName || '', sequence, reason: 'mailbox' });
|
|
604
804
|
}
|
|
605
805
|
enqueueUnreadMailbox(record) {
|
|
606
806
|
if (record.status === 'closed' || record.status === 'queued' || this.running.has(record.id))
|
|
607
807
|
return;
|
|
608
808
|
const unread = this.unreadMailbox(record.id);
|
|
609
|
-
if (!unread.
|
|
809
|
+
if (!unread.some(message => message.wakeup || message.acceptedWhileActive))
|
|
610
810
|
return;
|
|
611
|
-
|
|
811
|
+
// Store the mailbox once. pump() builds the current batch at dispatch so
|
|
812
|
+
// later arrivals coalesce without duplicating the initial wake message.
|
|
813
|
+
this.enqueue(record, '', record.flowName || '', 'mailbox');
|
|
612
814
|
}
|
|
613
815
|
markMailboxRead(messages) {
|
|
614
816
|
const stamp = now();
|
|
@@ -655,24 +857,45 @@ class SubagentManager {
|
|
|
655
857
|
const record = this.subs.get(job.id);
|
|
656
858
|
if (!record || record.status === 'closed')
|
|
657
859
|
continue;
|
|
658
|
-
const unread = this.
|
|
659
|
-
const prompt = unread.length ?
|
|
860
|
+
const unread = this.unreadMailbox(record.id);
|
|
861
|
+
const prompt = [job.prompt, unread.length ? this.mailboxPrompt(unread) : ''].filter(Boolean).join('\n\n');
|
|
660
862
|
record.status = 'working';
|
|
661
863
|
record.queueSequence = undefined;
|
|
662
864
|
record.startedAt = record.startedAt || now();
|
|
663
865
|
record.updatedAt = now();
|
|
664
866
|
this.running.add(record.id);
|
|
867
|
+
const activeJob = { ...job, prompt };
|
|
868
|
+
this.activeJobs.set(record.id, activeJob);
|
|
869
|
+
// A read receipt must never reach disk without the precise dispatched
|
|
870
|
+
// input. Cold restore can now recover a job even before child startup.
|
|
871
|
+
if (unread.length)
|
|
872
|
+
this.markMailboxRead(unread);
|
|
873
|
+
else
|
|
874
|
+
this.persistNow();
|
|
665
875
|
this.changed();
|
|
666
|
-
|
|
876
|
+
let execution;
|
|
877
|
+
try {
|
|
878
|
+
execution = Promise.resolve(this.executor({ record: cloneRecord(record), prompt, flowName: job.flowName, reason: job.reason }));
|
|
879
|
+
}
|
|
880
|
+
catch (error) {
|
|
881
|
+
execution = Promise.reject(error);
|
|
882
|
+
}
|
|
883
|
+
void execution
|
|
667
884
|
.then(result => {
|
|
885
|
+
if (this.activeJobs.get(record.id) !== activeJob)
|
|
886
|
+
return;
|
|
668
887
|
this.complete(record.id, result || '[Subagent] Completed with empty response.');
|
|
669
888
|
this.running.delete(record.id);
|
|
889
|
+
this.activeJobs.delete(record.id);
|
|
670
890
|
this.enqueueUnreadMailbox(record);
|
|
671
891
|
this.pump();
|
|
672
892
|
})
|
|
673
893
|
.catch(error => {
|
|
894
|
+
if (this.activeJobs.get(record.id) !== activeJob)
|
|
895
|
+
return;
|
|
674
896
|
this.fail(record.id, error instanceof Error ? error.message : String(error));
|
|
675
897
|
this.running.delete(record.id);
|
|
898
|
+
this.activeJobs.delete(record.id);
|
|
676
899
|
this.enqueueUnreadMailbox(record);
|
|
677
900
|
this.pump();
|
|
678
901
|
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Sender-owned, explicit content selection for peer communication.
|
|
2
|
+
* Selection never calls a model, mutates history, or serializes runtime state.
|
|
3
|
+
*/
|
|
4
|
+
export type SubagentCommunicationContentKind = 'summary' | 'text_history' | 'tool_history';
|
|
5
|
+
export interface SubagentCommunicationHistoryMessage {
|
|
6
|
+
role?: unknown;
|
|
7
|
+
content?: unknown;
|
|
8
|
+
tool_calls?: unknown;
|
|
9
|
+
tool_call_id?: unknown;
|
|
10
|
+
name?: unknown;
|
|
11
|
+
hidden_user_input?: unknown;
|
|
12
|
+
}
|
|
13
|
+
export interface SubagentCommunicationParams {
|
|
14
|
+
message?: unknown;
|
|
15
|
+
prompt?: unknown;
|
|
16
|
+
content?: unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface SubagentCommunicationRange {
|
|
19
|
+
start: number;
|
|
20
|
+
end: number;
|
|
21
|
+
total: number;
|
|
22
|
+
}
|
|
23
|
+
export interface SubagentCommunicationSelection {
|
|
24
|
+
body: string;
|
|
25
|
+
selection: {
|
|
26
|
+
source: 'explicit' | 'default_history' | 'content';
|
|
27
|
+
segments: Array<{
|
|
28
|
+
kind: 'message' | SubagentCommunicationContentKind;
|
|
29
|
+
range?: SubagentCommunicationRange;
|
|
30
|
+
units: number;
|
|
31
|
+
}>;
|
|
32
|
+
bodyChars: number;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export type SubagentCommunicationErrorCode = 'ambiguous_content' | 'invalid_content' | 'invalid_range' | 'empty_history' | 'budget_exceeded';
|
|
36
|
+
export declare class SubagentCommunicationSelectionError extends Error {
|
|
37
|
+
readonly code: SubagentCommunicationErrorCode;
|
|
38
|
+
constructor(code: SubagentCommunicationErrorCode, message: string);
|
|
39
|
+
}
|
|
40
|
+
export declare function selectSubagentCommunication(history: readonly SubagentCommunicationHistoryMessage[], params: SubagentCommunicationParams, options?: {
|
|
41
|
+
maxBodyChars?: number;
|
|
42
|
+
}): SubagentCommunicationSelection;
|
|
43
|
+
//# sourceMappingURL=subagentCommunication.d.ts.map
|