@grackle-ai/server 0.54.1 → 0.56.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/adapters/codespace.d.ts.map +1 -1
- package/dist/adapters/codespace.js +1 -0
- package/dist/adapters/codespace.js.map +1 -1
- package/dist/adapters/docker.d.ts.map +1 -1
- package/dist/adapters/docker.js +1 -0
- package/dist/adapters/docker.js.map +1 -1
- package/dist/adapters/ssh.d.ts.map +1 -1
- package/dist/adapters/ssh.js +1 -0
- package/dist/adapters/ssh.js.map +1 -1
- package/dist/grpc-service.d.ts.map +1 -1
- package/dist/grpc-service.js +10 -0
- package/dist/grpc-service.js.map +1 -1
- package/dist/orchestrator-context.d.ts +31 -0
- package/dist/orchestrator-context.d.ts.map +1 -0
- package/dist/orchestrator-context.js +69 -0
- package/dist/orchestrator-context.js.map +1 -0
- package/dist/stream-registry.d.ts +76 -0
- package/dist/stream-registry.d.ts.map +1 -0
- package/dist/stream-registry.js +342 -0
- package/dist/stream-registry.js.map +1 -0
- package/dist/system-prompt-builder.d.ts +97 -2
- package/dist/system-prompt-builder.d.ts.map +1 -1
- package/dist/system-prompt-builder.js +212 -7
- package/dist/system-prompt-builder.js.map +1 -1
- package/dist/ws-bridge.d.ts.map +1 -1
- package/dist/ws-bridge.js +8 -2
- package/dist/ws-bridge.js.map +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory stream registry for agent-to-agent IPC.
|
|
3
|
+
*
|
|
4
|
+
* Streams are global named message channels. Sessions interact with streams
|
|
5
|
+
* through subscriptions (fds) that have permissions (rw/r/w) and delivery
|
|
6
|
+
* modes (sync/async/detach). This is separate from stream-hub.ts, which
|
|
7
|
+
* handles UI event broadcasting.
|
|
8
|
+
*
|
|
9
|
+
* Streams are ephemeral — they don't survive server restart. The session
|
|
10
|
+
* JSONL is the durable state; streams are recreated on reanimate.
|
|
11
|
+
*/
|
|
12
|
+
/** Permission level for a subscription. */
|
|
13
|
+
export type Permission = "rw" | "r" | "w";
|
|
14
|
+
/** How a subscriber receives messages. */
|
|
15
|
+
export type DeliveryMode = "sync" | "async" | "detach";
|
|
16
|
+
/** A global named message channel. */
|
|
17
|
+
export interface Stream {
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly messages: StreamMessage[];
|
|
21
|
+
readonly subscriptions: Map<string, Subscription>;
|
|
22
|
+
}
|
|
23
|
+
/** A message published to a stream. */
|
|
24
|
+
export interface StreamMessage {
|
|
25
|
+
readonly id: string;
|
|
26
|
+
readonly senderId: string;
|
|
27
|
+
readonly content: string;
|
|
28
|
+
readonly timestamp: string;
|
|
29
|
+
/** Subscription IDs that have consumed this message. */
|
|
30
|
+
readonly deliveredTo: Set<string>;
|
|
31
|
+
}
|
|
32
|
+
/** A session's reference to a stream (an "fd"). */
|
|
33
|
+
export interface Subscription {
|
|
34
|
+
readonly id: string;
|
|
35
|
+
readonly fd: number;
|
|
36
|
+
readonly streamId: string;
|
|
37
|
+
readonly sessionId: string;
|
|
38
|
+
readonly permission: Permission;
|
|
39
|
+
readonly deliveryMode: DeliveryMode;
|
|
40
|
+
/** True if the session opened this fd via spawn(); false if inherited from parent. */
|
|
41
|
+
readonly createdBySpawn: boolean;
|
|
42
|
+
}
|
|
43
|
+
/** Callback invoked when a message arrives on an async subscription. */
|
|
44
|
+
export type AsyncMessageListener = (sub: Subscription, msg: StreamMessage) => void;
|
|
45
|
+
/** Create a new named stream. Names must be unique — throws if a stream with the same name exists. */
|
|
46
|
+
export declare function createStream(name: string): Stream;
|
|
47
|
+
/** Retrieve a stream by ID. */
|
|
48
|
+
export declare function getStream(id: string): Stream | undefined;
|
|
49
|
+
/** Retrieve a stream by name. */
|
|
50
|
+
export declare function getStreamByName(name: string): Stream | undefined;
|
|
51
|
+
/** Remove a stream and all its subscriptions. */
|
|
52
|
+
export declare function deleteStream(id: string): void;
|
|
53
|
+
/** Create a subscription (fd) for a session on a stream. */
|
|
54
|
+
export declare function subscribe(streamId: string, sessionId: string, permission: Permission, deliveryMode: DeliveryMode, createdBySpawn: boolean): Subscription;
|
|
55
|
+
/** Remove a subscription. Deletes the stream if it was the last subscription. */
|
|
56
|
+
export declare function unsubscribe(subscriptionId: string): void;
|
|
57
|
+
/** Look up a subscription by session ID and fd number. */
|
|
58
|
+
export declare function getSubscription(sessionId: string, fd: number): Subscription | undefined;
|
|
59
|
+
/** Get all subscriptions for a session. */
|
|
60
|
+
export declare function getSubscriptionsForSession(sessionId: string): Subscription[];
|
|
61
|
+
/** Get only subscriptions that this session opened via spawn() (not inherited). */
|
|
62
|
+
export declare function getOwnedSubscriptions(sessionId: string): Subscription[];
|
|
63
|
+
/** Publish a message to a stream. Notifies async subscribers and enqueues for sync subscribers. */
|
|
64
|
+
export declare function publish(streamId: string, senderId: string, content: string): StreamMessage;
|
|
65
|
+
/** Block until an undelivered message is available for this sync subscription. */
|
|
66
|
+
export declare function consumeSync(subscriptionId: string): Promise<StreamMessage>;
|
|
67
|
+
/** Check if there are messages in the stream buffer not yet delivered to this subscription. */
|
|
68
|
+
export declare function hasUndeliveredMessages(subscriptionId: string): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Register a callback invoked when a message arrives on any async subscription
|
|
71
|
+
* for the given session. Returns an unsubscribe function.
|
|
72
|
+
*/
|
|
73
|
+
export declare function registerAsyncListener(sessionId: string, callback: AsyncMessageListener): () => void;
|
|
74
|
+
/** Clear all state. For testing only. */
|
|
75
|
+
export declare function _resetForTesting(): void;
|
|
76
|
+
//# sourceMappingURL=stream-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-registry.d.ts","sourceRoot":"","sources":["../src/stream-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory stream registry for agent-to-agent IPC.
|
|
3
|
+
*
|
|
4
|
+
* Streams are global named message channels. Sessions interact with streams
|
|
5
|
+
* through subscriptions (fds) that have permissions (rw/r/w) and delivery
|
|
6
|
+
* modes (sync/async/detach). This is separate from stream-hub.ts, which
|
|
7
|
+
* handles UI event broadcasting.
|
|
8
|
+
*
|
|
9
|
+
* Streams are ephemeral — they don't survive server restart. The session
|
|
10
|
+
* JSONL is the durable state; streams are recreated on reanimate.
|
|
11
|
+
*/
|
|
12
|
+
import { v4 as uuid } from "uuid";
|
|
13
|
+
import { logger } from "./logger.js";
|
|
14
|
+
// ─── Async Queue (blocking reads for sync subscriptions) ──────────────────────
|
|
15
|
+
/** Simple async queue for blocking consume. Rejects pending waiters on close. */
|
|
16
|
+
class AsyncQueue {
|
|
17
|
+
queue = [];
|
|
18
|
+
waiters = [];
|
|
19
|
+
closed = false;
|
|
20
|
+
push(item) {
|
|
21
|
+
if (this.closed) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (this.waiters.length > 0) {
|
|
25
|
+
const waiter = this.waiters.shift();
|
|
26
|
+
waiter.resolve(item);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
this.queue.push(item);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async shift() {
|
|
33
|
+
if (this.queue.length > 0) {
|
|
34
|
+
return this.queue.shift();
|
|
35
|
+
}
|
|
36
|
+
if (this.closed) {
|
|
37
|
+
throw new Error("Queue is closed");
|
|
38
|
+
}
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
this.waiters.push({ resolve, reject });
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** Close the queue. Rejects all pending waiters so blocked consumers unblock. */
|
|
44
|
+
close() {
|
|
45
|
+
this.closed = true;
|
|
46
|
+
const err = new Error("Subscription closed");
|
|
47
|
+
for (const waiter of this.waiters) {
|
|
48
|
+
waiter.reject(err);
|
|
49
|
+
}
|
|
50
|
+
this.waiters.length = 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// ─── Module State ─────────────────────────────────────────────────────────────
|
|
54
|
+
/** All active streams, keyed by stream ID. */
|
|
55
|
+
const streams = new Map();
|
|
56
|
+
/** Name → stream ID index for unique-name lookup. */
|
|
57
|
+
const streamsByName = new Map();
|
|
58
|
+
/** All subscriptions for each session, keyed by sessionId → fd → Subscription. */
|
|
59
|
+
const subscriptionsBySession = new Map();
|
|
60
|
+
/** Subscription ID → Subscription (for fast lookup by ID). */
|
|
61
|
+
const subscriptionsById = new Map();
|
|
62
|
+
/** Next fd number for each session (starts at 3, increments). */
|
|
63
|
+
const fdCounters = new Map();
|
|
64
|
+
/** Async message listeners keyed by session ID. Invoked when a message arrives on an async subscription. */
|
|
65
|
+
const asyncListeners = new Map();
|
|
66
|
+
/** Blocking queues for sync subscriptions, keyed by subscription ID. */
|
|
67
|
+
const syncQueues = new Map();
|
|
68
|
+
// ─── Internal Helpers ─────────────────────────────────────────────────────────
|
|
69
|
+
/** Allocate the next fd number for a session. */
|
|
70
|
+
function nextFd(sessionId) {
|
|
71
|
+
const current = fdCounters.get(sessionId) ?? 3;
|
|
72
|
+
fdCounters.set(sessionId, current + 1);
|
|
73
|
+
return current;
|
|
74
|
+
}
|
|
75
|
+
/** Get or create the fd map for a session. */
|
|
76
|
+
function getSessionFdMap(sessionId) {
|
|
77
|
+
let fdMap = subscriptionsBySession.get(sessionId);
|
|
78
|
+
if (!fdMap) {
|
|
79
|
+
fdMap = new Map();
|
|
80
|
+
subscriptionsBySession.set(sessionId, fdMap);
|
|
81
|
+
}
|
|
82
|
+
return fdMap;
|
|
83
|
+
}
|
|
84
|
+
/** Clean up session state when it has no more subscriptions. */
|
|
85
|
+
function cleanupSessionIfEmpty(sessionId) {
|
|
86
|
+
const fdMap = subscriptionsBySession.get(sessionId);
|
|
87
|
+
if (fdMap?.size === 0) {
|
|
88
|
+
subscriptionsBySession.delete(sessionId);
|
|
89
|
+
fdCounters.delete(sessionId);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** Check if a subscription can receive messages (has read permission). */
|
|
93
|
+
function canReceive(sub) {
|
|
94
|
+
return sub.permission === "rw" || sub.permission === "r";
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Prune messages that have been delivered to all readable subscriptions.
|
|
98
|
+
* Keeps memory bounded by removing messages no longer needed for hasUndeliveredMessages.
|
|
99
|
+
*/
|
|
100
|
+
function pruneDeliveredMessages(stream) {
|
|
101
|
+
const readableSubs = Array.from(stream.subscriptions.values()).filter(canReceive);
|
|
102
|
+
if (readableSubs.length === 0) {
|
|
103
|
+
stream.messages.length = 0;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
let pruneCount = 0;
|
|
107
|
+
for (const msg of stream.messages) {
|
|
108
|
+
const allDelivered = readableSubs.every((sub) => msg.deliveredTo.has(sub.id) || msg.senderId === sub.sessionId);
|
|
109
|
+
if (allDelivered) {
|
|
110
|
+
pruneCount++;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
break; // Messages are ordered; stop at first undelivered
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (pruneCount > 0) {
|
|
117
|
+
stream.messages.splice(0, pruneCount);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// ─── Stream Lifecycle ─────────────────────────────────────────────────────────
|
|
121
|
+
/** Create a new named stream. Names must be unique — throws if a stream with the same name exists. */
|
|
122
|
+
export function createStream(name) {
|
|
123
|
+
if (streamsByName.has(name)) {
|
|
124
|
+
throw new Error(`Stream with name "${name}" already exists`);
|
|
125
|
+
}
|
|
126
|
+
const stream = {
|
|
127
|
+
id: uuid(),
|
|
128
|
+
name,
|
|
129
|
+
messages: [],
|
|
130
|
+
subscriptions: new Map(),
|
|
131
|
+
};
|
|
132
|
+
streams.set(stream.id, stream);
|
|
133
|
+
streamsByName.set(name, stream.id);
|
|
134
|
+
return stream;
|
|
135
|
+
}
|
|
136
|
+
/** Retrieve a stream by ID. */
|
|
137
|
+
export function getStream(id) {
|
|
138
|
+
return streams.get(id);
|
|
139
|
+
}
|
|
140
|
+
/** Retrieve a stream by name. */
|
|
141
|
+
export function getStreamByName(name) {
|
|
142
|
+
const id = streamsByName.get(name);
|
|
143
|
+
return id ? streams.get(id) : undefined;
|
|
144
|
+
}
|
|
145
|
+
/** Remove a stream and all its subscriptions. */
|
|
146
|
+
export function deleteStream(id) {
|
|
147
|
+
const stream = streams.get(id);
|
|
148
|
+
if (!stream) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// Clean up all subscriptions on this stream
|
|
152
|
+
for (const sub of stream.subscriptions.values()) {
|
|
153
|
+
subscriptionsById.delete(sub.id);
|
|
154
|
+
const queue = syncQueues.get(sub.id);
|
|
155
|
+
if (queue) {
|
|
156
|
+
queue.close();
|
|
157
|
+
syncQueues.delete(sub.id);
|
|
158
|
+
}
|
|
159
|
+
const fdMap = subscriptionsBySession.get(sub.sessionId);
|
|
160
|
+
if (fdMap) {
|
|
161
|
+
fdMap.delete(sub.fd);
|
|
162
|
+
cleanupSessionIfEmpty(sub.sessionId);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
streamsByName.delete(stream.name);
|
|
166
|
+
streams.delete(id);
|
|
167
|
+
}
|
|
168
|
+
// ─── Subscriptions ────────────────────────────────────────────────────────────
|
|
169
|
+
/** Create a subscription (fd) for a session on a stream. */
|
|
170
|
+
export function subscribe(streamId, sessionId, permission, deliveryMode, createdBySpawn) {
|
|
171
|
+
const stream = streams.get(streamId);
|
|
172
|
+
if (!stream) {
|
|
173
|
+
throw new Error(`Stream not found: ${streamId}`);
|
|
174
|
+
}
|
|
175
|
+
// w-only subscriptions cannot have sync or async delivery (they never receive)
|
|
176
|
+
if (permission === "w" && (deliveryMode === "sync" || deliveryMode === "async")) {
|
|
177
|
+
throw new Error(`Write-only subscription cannot use "${deliveryMode}" delivery mode`);
|
|
178
|
+
}
|
|
179
|
+
const fd = nextFd(sessionId);
|
|
180
|
+
const sub = {
|
|
181
|
+
id: uuid(),
|
|
182
|
+
fd,
|
|
183
|
+
streamId,
|
|
184
|
+
sessionId,
|
|
185
|
+
permission,
|
|
186
|
+
deliveryMode,
|
|
187
|
+
createdBySpawn,
|
|
188
|
+
};
|
|
189
|
+
stream.subscriptions.set(sub.id, sub);
|
|
190
|
+
getSessionFdMap(sessionId).set(fd, sub);
|
|
191
|
+
subscriptionsById.set(sub.id, sub);
|
|
192
|
+
// Create a blocking queue for sync subscriptions (only readable ones)
|
|
193
|
+
if (deliveryMode === "sync" && canReceive(sub)) {
|
|
194
|
+
syncQueues.set(sub.id, new AsyncQueue());
|
|
195
|
+
}
|
|
196
|
+
return sub;
|
|
197
|
+
}
|
|
198
|
+
/** Remove a subscription. Deletes the stream if it was the last subscription. */
|
|
199
|
+
export function unsubscribe(subscriptionId) {
|
|
200
|
+
const sub = subscriptionsById.get(subscriptionId);
|
|
201
|
+
if (!sub) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
// Close and remove sync queue (unblocks any pending consumeSync)
|
|
205
|
+
const queue = syncQueues.get(sub.id);
|
|
206
|
+
if (queue) {
|
|
207
|
+
queue.close();
|
|
208
|
+
syncQueues.delete(sub.id);
|
|
209
|
+
}
|
|
210
|
+
// Remove from stream
|
|
211
|
+
const stream = streams.get(sub.streamId);
|
|
212
|
+
if (stream) {
|
|
213
|
+
stream.subscriptions.delete(sub.id);
|
|
214
|
+
if (stream.subscriptions.size === 0) {
|
|
215
|
+
streamsByName.delete(stream.name);
|
|
216
|
+
streams.delete(sub.streamId);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Remove from session fd map
|
|
220
|
+
const fdMap = subscriptionsBySession.get(sub.sessionId);
|
|
221
|
+
if (fdMap) {
|
|
222
|
+
fdMap.delete(sub.fd);
|
|
223
|
+
cleanupSessionIfEmpty(sub.sessionId);
|
|
224
|
+
}
|
|
225
|
+
// Remove from lookup maps
|
|
226
|
+
subscriptionsById.delete(sub.id);
|
|
227
|
+
}
|
|
228
|
+
/** Look up a subscription by session ID and fd number. */
|
|
229
|
+
export function getSubscription(sessionId, fd) {
|
|
230
|
+
return subscriptionsBySession.get(sessionId)?.get(fd);
|
|
231
|
+
}
|
|
232
|
+
/** Get all subscriptions for a session. */
|
|
233
|
+
export function getSubscriptionsForSession(sessionId) {
|
|
234
|
+
const fdMap = subscriptionsBySession.get(sessionId);
|
|
235
|
+
if (!fdMap) {
|
|
236
|
+
return [];
|
|
237
|
+
}
|
|
238
|
+
return Array.from(fdMap.values());
|
|
239
|
+
}
|
|
240
|
+
/** Get only subscriptions that this session opened via spawn() (not inherited). */
|
|
241
|
+
export function getOwnedSubscriptions(sessionId) {
|
|
242
|
+
return getSubscriptionsForSession(sessionId).filter((s) => s.createdBySpawn);
|
|
243
|
+
}
|
|
244
|
+
// ─── Messaging ────────────────────────────────────────────────────────────────
|
|
245
|
+
/** Publish a message to a stream. Notifies async subscribers and enqueues for sync subscribers. */
|
|
246
|
+
export function publish(streamId, senderId, content) {
|
|
247
|
+
const stream = streams.get(streamId);
|
|
248
|
+
if (!stream) {
|
|
249
|
+
throw new Error(`Stream not found: ${streamId}`);
|
|
250
|
+
}
|
|
251
|
+
const msg = {
|
|
252
|
+
id: uuid(),
|
|
253
|
+
senderId,
|
|
254
|
+
content,
|
|
255
|
+
timestamp: new Date().toISOString(),
|
|
256
|
+
deliveredTo: new Set(),
|
|
257
|
+
};
|
|
258
|
+
stream.messages.push(msg);
|
|
259
|
+
// Notify subscribers (skip the sender and write-only subscriptions)
|
|
260
|
+
for (const sub of stream.subscriptions.values()) {
|
|
261
|
+
if (sub.sessionId === senderId) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (!canReceive(sub)) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (sub.deliveryMode === "async") {
|
|
268
|
+
// Only mark as delivered if the listener exists and succeeds
|
|
269
|
+
const listener = asyncListeners.get(sub.sessionId);
|
|
270
|
+
if (listener) {
|
|
271
|
+
try {
|
|
272
|
+
listener(sub, msg);
|
|
273
|
+
msg.deliveredTo.add(sub.id);
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
logger.warn({ err, subscriptionId: sub.id }, "Async listener threw — message left undelivered");
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// No listener registered: message stays undelivered (buffered)
|
|
280
|
+
}
|
|
281
|
+
else if (sub.deliveryMode === "sync") {
|
|
282
|
+
// Enqueue for blocking consumeSync()
|
|
283
|
+
const queue = syncQueues.get(sub.id);
|
|
284
|
+
if (queue) {
|
|
285
|
+
queue.push(msg);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// "detach" mode: message stays in buffer, no notification
|
|
289
|
+
}
|
|
290
|
+
// Prune messages that have been fully delivered
|
|
291
|
+
pruneDeliveredMessages(stream);
|
|
292
|
+
return msg;
|
|
293
|
+
}
|
|
294
|
+
/** Block until an undelivered message is available for this sync subscription. */
|
|
295
|
+
export async function consumeSync(subscriptionId) {
|
|
296
|
+
const queue = syncQueues.get(subscriptionId);
|
|
297
|
+
if (!queue) {
|
|
298
|
+
throw new Error(`No sync queue for subscription: ${subscriptionId}. Is it a sync subscription?`);
|
|
299
|
+
}
|
|
300
|
+
const msg = await queue.shift();
|
|
301
|
+
msg.deliveredTo.add(subscriptionId);
|
|
302
|
+
return msg;
|
|
303
|
+
}
|
|
304
|
+
/** Check if there are messages in the stream buffer not yet delivered to this subscription. */
|
|
305
|
+
export function hasUndeliveredMessages(subscriptionId) {
|
|
306
|
+
const sub = subscriptionsById.get(subscriptionId);
|
|
307
|
+
if (!sub) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
const stream = streams.get(sub.streamId);
|
|
311
|
+
if (!stream) {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
return stream.messages.some((msg) => !msg.deliveredTo.has(subscriptionId) && msg.senderId !== sub.sessionId);
|
|
315
|
+
}
|
|
316
|
+
// ─── Notification Registration ────────────────────────────────────────────────
|
|
317
|
+
/**
|
|
318
|
+
* Register a callback invoked when a message arrives on any async subscription
|
|
319
|
+
* for the given session. Returns an unsubscribe function.
|
|
320
|
+
*/
|
|
321
|
+
export function registerAsyncListener(sessionId, callback) {
|
|
322
|
+
asyncListeners.set(sessionId, callback);
|
|
323
|
+
return () => {
|
|
324
|
+
asyncListeners.delete(sessionId);
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
// ─── Testing ──────────────────────────────────────────────────────────────────
|
|
328
|
+
/** Clear all state. For testing only. */
|
|
329
|
+
export function _resetForTesting() {
|
|
330
|
+
streams.clear();
|
|
331
|
+
streamsByName.clear();
|
|
332
|
+
subscriptionsBySession.clear();
|
|
333
|
+
subscriptionsById.clear();
|
|
334
|
+
fdCounters.clear();
|
|
335
|
+
asyncListeners.clear();
|
|
336
|
+
// Close all sync queues before clearing
|
|
337
|
+
for (const queue of syncQueues.values()) {
|
|
338
|
+
queue.close();
|
|
339
|
+
}
|
|
340
|
+
syncQueues.clear();
|
|
341
|
+
}
|
|
342
|
+
//# sourceMappingURL=stream-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-registry.js","sourceRoot":"","sources":["../src/stream-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Builds system prompts for agent sessions, assembling sections
|
|
3
3
|
* dynamically based on the session type (task vs ad-hoc).
|
|
4
|
+
*
|
|
5
|
+
* Orchestrator tasks (canDecompose + depth <= 1) receive rich project
|
|
6
|
+
* context (task tree, persona roster, environments, findings).
|
|
7
|
+
* Leaf tasks receive the existing completion-contract template.
|
|
4
8
|
*/
|
|
9
|
+
/** Lightweight task node for rendering the task tree in orchestrator prompts. */
|
|
10
|
+
export interface TaskTreeNode {
|
|
11
|
+
/** Task ID. */
|
|
12
|
+
id: string;
|
|
13
|
+
/** Task title. */
|
|
14
|
+
title: string;
|
|
15
|
+
/** Current lifecycle status (not_started, working, paused, complete, failed). */
|
|
16
|
+
status: string;
|
|
17
|
+
/** Nesting depth in the hierarchy (0 = root). */
|
|
18
|
+
depth: number;
|
|
19
|
+
/** Parent task ID (empty string for root-level tasks). */
|
|
20
|
+
parentTaskId: string;
|
|
21
|
+
/** IDs of tasks this task depends on. */
|
|
22
|
+
dependsOn: string[];
|
|
23
|
+
/** Resolved persona display name (empty if none assigned). */
|
|
24
|
+
personaName: string;
|
|
25
|
+
/** Git branch name (empty if none). */
|
|
26
|
+
branch: string;
|
|
27
|
+
/** Whether this task can create subtasks. */
|
|
28
|
+
canDecompose: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** Persona summary for the available-personas prompt section. */
|
|
31
|
+
export interface PersonaSummary {
|
|
32
|
+
/** Display name. */
|
|
33
|
+
name: string;
|
|
34
|
+
/** Short description. */
|
|
35
|
+
description: string;
|
|
36
|
+
/** Runtime backend (claude-code, copilot, codex, etc.). */
|
|
37
|
+
runtime: string;
|
|
38
|
+
/** Default model (e.g. "opus", "sonnet"). */
|
|
39
|
+
model: string;
|
|
40
|
+
}
|
|
41
|
+
/** Environment summary for the available-environments prompt section. */
|
|
42
|
+
export interface EnvironmentSummary {
|
|
43
|
+
/** Human-readable name. */
|
|
44
|
+
displayName: string;
|
|
45
|
+
/** Adapter backend (local, ssh, codespace, docker). */
|
|
46
|
+
adapterType: string;
|
|
47
|
+
/** Connection status (connected, disconnected, etc.). */
|
|
48
|
+
status: string;
|
|
49
|
+
/** Default runtime for this environment. */
|
|
50
|
+
defaultRuntime: string;
|
|
51
|
+
}
|
|
5
52
|
/** Options for building a system prompt. */
|
|
6
53
|
export interface SystemPromptOptions {
|
|
7
54
|
/** Task metadata. When absent, this is an ad-hoc session. */
|
|
@@ -10,15 +57,36 @@ export interface SystemPromptOptions {
|
|
|
10
57
|
description: string;
|
|
11
58
|
notes: string;
|
|
12
59
|
};
|
|
60
|
+
/** ID of the current task (used to mark it in the task tree). */
|
|
61
|
+
taskId?: string;
|
|
13
62
|
/** Whether the agent is allowed to create subtasks. */
|
|
14
63
|
canDecompose?: boolean;
|
|
15
64
|
/** Persona behavioral instructions (prepended when non-empty). */
|
|
16
65
|
personaPrompt?: string;
|
|
66
|
+
/** Task depth in the hierarchy (0 = root, 1 = first child, etc.). */
|
|
67
|
+
taskDepth?: number;
|
|
68
|
+
/** Workspace metadata for the orchestrator context section. */
|
|
69
|
+
workspace?: {
|
|
70
|
+
name: string;
|
|
71
|
+
description: string;
|
|
72
|
+
repoUrl: string;
|
|
73
|
+
};
|
|
74
|
+
/** All tasks in the workspace, for rendering the task tree. */
|
|
75
|
+
taskTree?: TaskTreeNode[];
|
|
76
|
+
/** Available personas for the orchestrator to assign. */
|
|
77
|
+
availablePersonas?: PersonaSummary[];
|
|
78
|
+
/** Available environments for the orchestrator to route work to. */
|
|
79
|
+
availableEnvironments?: EnvironmentSummary[];
|
|
80
|
+
/** Pre-built findings context string (from buildFindingsContext). */
|
|
81
|
+
findingsContext?: string;
|
|
82
|
+
/** Invocation mode: fresh (first time) or resume (re-invoked after child completion). */
|
|
83
|
+
triggerMode?: "fresh" | "resume";
|
|
17
84
|
}
|
|
18
85
|
/**
|
|
19
86
|
* Assembles a system prompt from discrete sections based on session type.
|
|
20
87
|
*
|
|
21
|
-
*
|
|
88
|
+
* Orchestrator tasks get project state, task tree, persona roster, and
|
|
89
|
+
* decomposition guidelines. Leaf tasks get the existing completion contract.
|
|
22
90
|
* Ad-hoc sessions get only the MCP note and persona prompt.
|
|
23
91
|
*/
|
|
24
92
|
export declare class SystemPromptBuilder {
|
|
@@ -26,6 +94,33 @@ export declare class SystemPromptBuilder {
|
|
|
26
94
|
constructor(options: SystemPromptOptions);
|
|
27
95
|
/** Build the complete system prompt string. */
|
|
28
96
|
build(): string;
|
|
97
|
+
/**
|
|
98
|
+
* Determine whether this is an orchestrator task.
|
|
99
|
+
* Requires canDecompose, shallow depth, AND orchestrator data fields to be
|
|
100
|
+
* present. This ensures existing callers that pass canDecompose without
|
|
101
|
+
* the new fields still get the leaf template.
|
|
102
|
+
*/
|
|
103
|
+
private isOrchestrator;
|
|
104
|
+
/** Orchestrator task context with role framing. */
|
|
105
|
+
private buildOrchestratorTaskContext;
|
|
106
|
+
/** Workspace metadata section. */
|
|
107
|
+
private buildWorkspaceContext;
|
|
108
|
+
/** Hierarchical task tree with statuses, personas, and dependencies. */
|
|
109
|
+
private buildTaskTree;
|
|
110
|
+
/** Available personas table. */
|
|
111
|
+
private buildAvailablePersonas;
|
|
112
|
+
/** Available environments table. */
|
|
113
|
+
private buildAvailableEnvironments;
|
|
114
|
+
/** Orchestrator findings section with actual findings data. */
|
|
115
|
+
private buildOrchestratorFindingsSection;
|
|
116
|
+
/** Trigger context describing why this invocation happened. */
|
|
117
|
+
private buildTriggerContext;
|
|
118
|
+
/** Decomposition heuristics and guardrails. */
|
|
119
|
+
private buildDecompositionGuidelines;
|
|
120
|
+
/** Orchestrator-specific MCP tool documentation. */
|
|
121
|
+
private buildOrchestratorTools;
|
|
122
|
+
/** Orchestrator-specific completion contract. */
|
|
123
|
+
private buildOrchestratorCompletionContract;
|
|
29
124
|
/** Task title, description, and notes. */
|
|
30
125
|
private buildTaskContext;
|
|
31
126
|
/** Contract for signaling task completion. */
|
|
@@ -34,7 +129,7 @@ export declare class SystemPromptBuilder {
|
|
|
34
129
|
private buildSubtaskSection;
|
|
35
130
|
/** SIGCHLD signal documentation. */
|
|
36
131
|
private buildSignalSection;
|
|
37
|
-
/** Guidance on using findings. */
|
|
132
|
+
/** Guidance on using findings (leaf agents). */
|
|
38
133
|
private buildFindingsSection;
|
|
39
134
|
/** MCP note (always included). */
|
|
40
135
|
private buildMcpNote;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-prompt-builder.d.ts","sourceRoot":"","sources":["../src/system-prompt-builder.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"system-prompt-builder.d.ts","sourceRoot":"","sources":["../src/system-prompt-builder.ts"],"names":[],"mappings":"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"}
|