@grackle-ai/core 0.75.4

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.
Files changed (130) hide show
  1. package/README.md +30 -0
  2. package/dist/adapter-config.d.ts +6 -0
  3. package/dist/adapter-config.d.ts.map +1 -0
  4. package/dist/adapter-config.js +19 -0
  5. package/dist/adapter-config.js.map +1 -0
  6. package/dist/adapter-manager.d.ts +22 -0
  7. package/dist/adapter-manager.d.ts.map +1 -0
  8. package/dist/adapter-manager.js +81 -0
  9. package/dist/adapter-manager.js.map +1 -0
  10. package/dist/auto-reconnect.d.ts +23 -0
  11. package/dist/auto-reconnect.d.ts.map +1 -0
  12. package/dist/auto-reconnect.js +164 -0
  13. package/dist/auto-reconnect.js.map +1 -0
  14. package/dist/compute-task-status.d.ts +28 -0
  15. package/dist/compute-task-status.d.ts.map +1 -0
  16. package/dist/compute-task-status.js +70 -0
  17. package/dist/compute-task-status.js.map +1 -0
  18. package/dist/credential-bundle.d.ts +12 -0
  19. package/dist/credential-bundle.d.ts.map +1 -0
  20. package/dist/credential-bundle.js +183 -0
  21. package/dist/credential-bundle.js.map +1 -0
  22. package/dist/event-bus.d.ts +37 -0
  23. package/dist/event-bus.d.ts.map +1 -0
  24. package/dist/event-bus.js +65 -0
  25. package/dist/event-bus.js.map +1 -0
  26. package/dist/event-processor.d.ts +36 -0
  27. package/dist/event-processor.d.ts.map +1 -0
  28. package/dist/event-processor.js +312 -0
  29. package/dist/event-processor.js.map +1 -0
  30. package/dist/grpc-service.d.ts +22 -0
  31. package/dist/grpc-service.d.ts.map +1 -0
  32. package/dist/grpc-service.js +1724 -0
  33. package/dist/grpc-service.js.map +1 -0
  34. package/dist/index.d.ts +16 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +25 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/knowledge-init.d.ts +27 -0
  39. package/dist/knowledge-init.d.ts.map +1 -0
  40. package/dist/knowledge-init.js +212 -0
  41. package/dist/knowledge-init.js.map +1 -0
  42. package/dist/lifecycle.d.ts +36 -0
  43. package/dist/lifecycle.d.ts.map +1 -0
  44. package/dist/lifecycle.js +112 -0
  45. package/dist/lifecycle.js.map +1 -0
  46. package/dist/log-writer.d.ts +32 -0
  47. package/dist/log-writer.d.ts.map +1 -0
  48. package/dist/log-writer.js +104 -0
  49. package/dist/log-writer.js.map +1 -0
  50. package/dist/logger.d.ts +4 -0
  51. package/dist/logger.d.ts.map +1 -0
  52. package/dist/logger.js +10 -0
  53. package/dist/logger.js.map +1 -0
  54. package/dist/pipe-delivery.d.ts +41 -0
  55. package/dist/pipe-delivery.d.ts.map +1 -0
  56. package/dist/pipe-delivery.js +186 -0
  57. package/dist/pipe-delivery.js.map +1 -0
  58. package/dist/processor-registry.d.ts +25 -0
  59. package/dist/processor-registry.d.ts.map +1 -0
  60. package/dist/processor-registry.js +58 -0
  61. package/dist/processor-registry.js.map +1 -0
  62. package/dist/reanimate-agent.d.ts +12 -0
  63. package/dist/reanimate-agent.d.ts.map +1 -0
  64. package/dist/reanimate-agent.js +76 -0
  65. package/dist/reanimate-agent.js.map +1 -0
  66. package/dist/session-recovery.d.ts +16 -0
  67. package/dist/session-recovery.d.ts.map +1 -0
  68. package/dist/session-recovery.js +129 -0
  69. package/dist/session-recovery.js.map +1 -0
  70. package/dist/signals/sigchld.d.ts +7 -0
  71. package/dist/signals/sigchld.d.ts.map +1 -0
  72. package/dist/signals/sigchld.js +167 -0
  73. package/dist/signals/sigchld.js.map +1 -0
  74. package/dist/signals/signal-delivery.d.ts +14 -0
  75. package/dist/signals/signal-delivery.d.ts.map +1 -0
  76. package/dist/signals/signal-delivery.js +166 -0
  77. package/dist/signals/signal-delivery.js.map +1 -0
  78. package/dist/stream-hub.d.ts +14 -0
  79. package/dist/stream-hub.d.ts.map +1 -0
  80. package/dist/stream-hub.js +95 -0
  81. package/dist/stream-hub.js.map +1 -0
  82. package/dist/stream-registry.d.ts +84 -0
  83. package/dist/stream-registry.d.ts.map +1 -0
  84. package/dist/stream-registry.js +363 -0
  85. package/dist/stream-registry.js.map +1 -0
  86. package/dist/test-utils/integration-setup.d.ts +11 -0
  87. package/dist/test-utils/integration-setup.d.ts.map +1 -0
  88. package/dist/test-utils/integration-setup.js +32 -0
  89. package/dist/test-utils/integration-setup.js.map +1 -0
  90. package/dist/test-utils/mock-database.d.ts +130 -0
  91. package/dist/test-utils/mock-database.d.ts.map +1 -0
  92. package/dist/test-utils/mock-database.js +147 -0
  93. package/dist/test-utils/mock-database.js.map +1 -0
  94. package/dist/token-push.d.ts +22 -0
  95. package/dist/token-push.d.ts.map +1 -0
  96. package/dist/token-push.js +78 -0
  97. package/dist/token-push.js.map +1 -0
  98. package/dist/transcript.d.ts +5 -0
  99. package/dist/transcript.d.ts.map +1 -0
  100. package/dist/transcript.js +71 -0
  101. package/dist/transcript.js.map +1 -0
  102. package/dist/utils/exec.d.ts +17 -0
  103. package/dist/utils/exec.d.ts.map +1 -0
  104. package/dist/utils/exec.js +21 -0
  105. package/dist/utils/exec.js.map +1 -0
  106. package/dist/utils/format-gh-error.d.ts +6 -0
  107. package/dist/utils/format-gh-error.d.ts.map +1 -0
  108. package/dist/utils/format-gh-error.js +30 -0
  109. package/dist/utils/format-gh-error.js.map +1 -0
  110. package/dist/utils/network.d.ts +7 -0
  111. package/dist/utils/network.d.ts.map +1 -0
  112. package/dist/utils/network.js +21 -0
  113. package/dist/utils/network.js.map +1 -0
  114. package/dist/utils/ports.d.ts +3 -0
  115. package/dist/utils/ports.d.ts.map +1 -0
  116. package/dist/utils/ports.js +19 -0
  117. package/dist/utils/ports.js.map +1 -0
  118. package/dist/utils/sleep.d.ts +3 -0
  119. package/dist/utils/sleep.d.ts.map +1 -0
  120. package/dist/utils/sleep.js +5 -0
  121. package/dist/utils/sleep.js.map +1 -0
  122. package/dist/ws-bridge.d.ts +30 -0
  123. package/dist/ws-bridge.d.ts.map +1 -0
  124. package/dist/ws-bridge.js +372 -0
  125. package/dist/ws-bridge.js.map +1 -0
  126. package/dist/ws-broadcast.d.ts +19 -0
  127. package/dist/ws-broadcast.d.ts.map +1 -0
  128. package/dist/ws-broadcast.js +60 -0
  129. package/dist/ws-broadcast.js.map +1 -0
  130. package/package.json +57 -0
@@ -0,0 +1,84 @@
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
+ /** Callback invoked when a session has zero remaining subscriptions (orphaned). */
46
+ type OrphanCallback = (sessionId: string) => void;
47
+ /** Create a new named stream. Names must be unique — throws if a stream with the same name exists. */
48
+ export declare function createStream(name: string): Stream;
49
+ /** Retrieve a stream by ID. */
50
+ export declare function getStream(id: string): Stream | undefined;
51
+ /** Retrieve a stream by name. */
52
+ export declare function getStreamByName(name: string): Stream | undefined;
53
+ /** Remove a stream and all its subscriptions. */
54
+ export declare function deleteStream(id: string): void;
55
+ /** Create a subscription (fd) for a session on a stream. */
56
+ export declare function subscribe(streamId: string, sessionId: string, permission: Permission, deliveryMode: DeliveryMode, createdBySpawn: boolean): Subscription;
57
+ /** Remove a subscription. Deletes the stream if it was the last subscription. */
58
+ export declare function unsubscribe(subscriptionId: string): void;
59
+ /** Look up a subscription by session ID and fd number. */
60
+ export declare function getSubscription(sessionId: string, fd: number): Subscription | undefined;
61
+ /** Get all subscriptions for a session. */
62
+ export declare function getSubscriptionsForSession(sessionId: string): Subscription[];
63
+ /** Get only subscriptions that this session opened via spawn() (not inherited). */
64
+ export declare function getOwnedSubscriptions(sessionId: string): Subscription[];
65
+ /** Publish a message to a stream. Notifies async subscribers and enqueues for sync subscribers. */
66
+ export declare function publish(streamId: string, senderId: string, content: string): StreamMessage;
67
+ /** Block until an undelivered message is available for this sync subscription. */
68
+ export declare function consumeSync(subscriptionId: string): Promise<StreamMessage>;
69
+ /** Check if there are messages in the stream buffer not yet delivered to this subscription. */
70
+ export declare function hasUndeliveredMessages(subscriptionId: string): boolean;
71
+ /**
72
+ * Register a callback invoked when a message arrives on any async subscription
73
+ * for the given session. Returns an unsubscribe function.
74
+ */
75
+ export declare function registerAsyncListener(sessionId: string, callback: AsyncMessageListener): () => void;
76
+ /**
77
+ * Register a callback invoked when a session has zero remaining subscriptions.
78
+ * Used by the lifecycle manager to auto-hibernate orphaned sessions.
79
+ */
80
+ export declare function onSessionOrphaned(cb: OrphanCallback): void;
81
+ /** Clear all state. For testing only. */
82
+ export declare function _resetForTesting(): void;
83
+ export {};
84
+ //# 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,363 @@
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
+ let orphanCallback;
69
+ // ─── Internal Helpers ─────────────────────────────────────────────────────────
70
+ /** Allocate the next fd number for a session. */
71
+ function nextFd(sessionId) {
72
+ const current = fdCounters.get(sessionId) ?? 3;
73
+ fdCounters.set(sessionId, current + 1);
74
+ return current;
75
+ }
76
+ /** Get or create the fd map for a session. */
77
+ function getSessionFdMap(sessionId) {
78
+ let fdMap = subscriptionsBySession.get(sessionId);
79
+ if (!fdMap) {
80
+ fdMap = new Map();
81
+ subscriptionsBySession.set(sessionId, fdMap);
82
+ }
83
+ return fdMap;
84
+ }
85
+ /** Clean up session state when it has no more subscriptions. Fires orphan callback. */
86
+ function cleanupSessionIfEmpty(sessionId) {
87
+ const fdMap = subscriptionsBySession.get(sessionId);
88
+ if (fdMap?.size === 0) {
89
+ subscriptionsBySession.delete(sessionId);
90
+ fdCounters.delete(sessionId);
91
+ try {
92
+ orphanCallback?.(sessionId);
93
+ }
94
+ catch (err) {
95
+ // Best-effort — orphan callback errors must not break stream-registry cleanup.
96
+ // Log at debug level for diagnosability.
97
+ try {
98
+ console.debug("stream-registry: orphan callback error for", sessionId, err);
99
+ }
100
+ catch { /* ignore */ }
101
+ }
102
+ }
103
+ }
104
+ /** Check if a subscription can receive messages (has read permission). */
105
+ function canReceive(sub) {
106
+ return sub.permission === "rw" || sub.permission === "r";
107
+ }
108
+ /**
109
+ * Prune messages that have been delivered to all readable subscriptions.
110
+ * Keeps memory bounded by removing messages no longer needed for hasUndeliveredMessages.
111
+ */
112
+ function pruneDeliveredMessages(stream) {
113
+ const readableSubs = Array.from(stream.subscriptions.values()).filter(canReceive);
114
+ if (readableSubs.length === 0) {
115
+ stream.messages.length = 0;
116
+ return;
117
+ }
118
+ let pruneCount = 0;
119
+ for (const msg of stream.messages) {
120
+ const allDelivered = readableSubs.every((sub) => msg.deliveredTo.has(sub.id) || msg.senderId === sub.sessionId);
121
+ if (allDelivered) {
122
+ pruneCount++;
123
+ }
124
+ else {
125
+ break; // Messages are ordered; stop at first undelivered
126
+ }
127
+ }
128
+ if (pruneCount > 0) {
129
+ stream.messages.splice(0, pruneCount);
130
+ }
131
+ }
132
+ // ─── Stream Lifecycle ─────────────────────────────────────────────────────────
133
+ /** Create a new named stream. Names must be unique — throws if a stream with the same name exists. */
134
+ export function createStream(name) {
135
+ if (streamsByName.has(name)) {
136
+ throw new Error(`Stream with name "${name}" already exists`);
137
+ }
138
+ const stream = {
139
+ id: uuid(),
140
+ name,
141
+ messages: [],
142
+ subscriptions: new Map(),
143
+ };
144
+ streams.set(stream.id, stream);
145
+ streamsByName.set(name, stream.id);
146
+ return stream;
147
+ }
148
+ /** Retrieve a stream by ID. */
149
+ export function getStream(id) {
150
+ return streams.get(id);
151
+ }
152
+ /** Retrieve a stream by name. */
153
+ export function getStreamByName(name) {
154
+ const id = streamsByName.get(name);
155
+ return id ? streams.get(id) : undefined;
156
+ }
157
+ /** Remove a stream and all its subscriptions. */
158
+ export function deleteStream(id) {
159
+ const stream = streams.get(id);
160
+ if (!stream) {
161
+ return;
162
+ }
163
+ // Clean up all subscriptions on this stream
164
+ for (const sub of stream.subscriptions.values()) {
165
+ subscriptionsById.delete(sub.id);
166
+ const queue = syncQueues.get(sub.id);
167
+ if (queue) {
168
+ queue.close();
169
+ syncQueues.delete(sub.id);
170
+ }
171
+ const fdMap = subscriptionsBySession.get(sub.sessionId);
172
+ if (fdMap) {
173
+ fdMap.delete(sub.fd);
174
+ cleanupSessionIfEmpty(sub.sessionId);
175
+ }
176
+ }
177
+ streamsByName.delete(stream.name);
178
+ streams.delete(id);
179
+ }
180
+ // ─── Subscriptions ────────────────────────────────────────────────────────────
181
+ /** Create a subscription (fd) for a session on a stream. */
182
+ export function subscribe(streamId, sessionId, permission, deliveryMode, createdBySpawn) {
183
+ const stream = streams.get(streamId);
184
+ if (!stream) {
185
+ throw new Error(`Stream not found: ${streamId}`);
186
+ }
187
+ // w-only subscriptions cannot have sync or async delivery (they never receive)
188
+ if (permission === "w" && (deliveryMode === "sync" || deliveryMode === "async")) {
189
+ throw new Error(`Write-only subscription cannot use "${deliveryMode}" delivery mode`);
190
+ }
191
+ const fd = nextFd(sessionId);
192
+ const sub = {
193
+ id: uuid(),
194
+ fd,
195
+ streamId,
196
+ sessionId,
197
+ permission,
198
+ deliveryMode,
199
+ createdBySpawn,
200
+ };
201
+ stream.subscriptions.set(sub.id, sub);
202
+ getSessionFdMap(sessionId).set(fd, sub);
203
+ subscriptionsById.set(sub.id, sub);
204
+ // Create a blocking queue for sync subscriptions (only readable ones)
205
+ if (deliveryMode === "sync" && canReceive(sub)) {
206
+ syncQueues.set(sub.id, new AsyncQueue());
207
+ }
208
+ return sub;
209
+ }
210
+ /** Remove a subscription. Deletes the stream if it was the last subscription. */
211
+ export function unsubscribe(subscriptionId) {
212
+ const sub = subscriptionsById.get(subscriptionId);
213
+ if (!sub) {
214
+ return;
215
+ }
216
+ // Close and remove sync queue (unblocks any pending consumeSync)
217
+ const queue = syncQueues.get(sub.id);
218
+ if (queue) {
219
+ queue.close();
220
+ syncQueues.delete(sub.id);
221
+ }
222
+ // Remove from stream
223
+ const stream = streams.get(sub.streamId);
224
+ if (stream) {
225
+ stream.subscriptions.delete(sub.id);
226
+ if (stream.subscriptions.size === 0) {
227
+ streamsByName.delete(stream.name);
228
+ streams.delete(sub.streamId);
229
+ }
230
+ }
231
+ // Remove from session fd map
232
+ const fdMap = subscriptionsBySession.get(sub.sessionId);
233
+ if (fdMap) {
234
+ fdMap.delete(sub.fd);
235
+ cleanupSessionIfEmpty(sub.sessionId);
236
+ }
237
+ // Remove from lookup maps
238
+ subscriptionsById.delete(sub.id);
239
+ }
240
+ /** Look up a subscription by session ID and fd number. */
241
+ export function getSubscription(sessionId, fd) {
242
+ return subscriptionsBySession.get(sessionId)?.get(fd);
243
+ }
244
+ /** Get all subscriptions for a session. */
245
+ export function getSubscriptionsForSession(sessionId) {
246
+ const fdMap = subscriptionsBySession.get(sessionId);
247
+ if (!fdMap) {
248
+ return [];
249
+ }
250
+ return Array.from(fdMap.values());
251
+ }
252
+ /** Get only subscriptions that this session opened via spawn() (not inherited). */
253
+ export function getOwnedSubscriptions(sessionId) {
254
+ return getSubscriptionsForSession(sessionId).filter((s) => s.createdBySpawn);
255
+ }
256
+ // ─── Messaging ────────────────────────────────────────────────────────────────
257
+ /** Publish a message to a stream. Notifies async subscribers and enqueues for sync subscribers. */
258
+ export function publish(streamId, senderId, content) {
259
+ const stream = streams.get(streamId);
260
+ if (!stream) {
261
+ throw new Error(`Stream not found: ${streamId}`);
262
+ }
263
+ const msg = {
264
+ id: uuid(),
265
+ senderId,
266
+ content,
267
+ timestamp: new Date().toISOString(),
268
+ deliveredTo: new Set(),
269
+ };
270
+ stream.messages.push(msg);
271
+ // Notify subscribers (skip the sender and write-only subscriptions)
272
+ for (const sub of stream.subscriptions.values()) {
273
+ if (sub.sessionId === senderId) {
274
+ continue;
275
+ }
276
+ if (!canReceive(sub)) {
277
+ continue;
278
+ }
279
+ if (sub.deliveryMode === "async") {
280
+ // Only mark as delivered if the listener exists and succeeds
281
+ const listener = asyncListeners.get(sub.sessionId);
282
+ if (listener) {
283
+ try {
284
+ listener(sub, msg);
285
+ msg.deliveredTo.add(sub.id);
286
+ }
287
+ catch (err) {
288
+ logger.warn({ err, subscriptionId: sub.id }, "Async listener threw — message left undelivered");
289
+ }
290
+ }
291
+ // No listener registered: message stays undelivered (buffered)
292
+ }
293
+ else if (sub.deliveryMode === "sync") {
294
+ // Enqueue for blocking consumeSync()
295
+ const queue = syncQueues.get(sub.id);
296
+ if (queue) {
297
+ queue.push(msg);
298
+ }
299
+ }
300
+ // "detach" mode: message stays in buffer, no notification
301
+ }
302
+ // Prune messages that have been fully delivered
303
+ pruneDeliveredMessages(stream);
304
+ return msg;
305
+ }
306
+ /** Block until an undelivered message is available for this sync subscription. */
307
+ export async function consumeSync(subscriptionId) {
308
+ const queue = syncQueues.get(subscriptionId);
309
+ if (!queue) {
310
+ throw new Error(`No sync queue for subscription: ${subscriptionId}. Is it a sync subscription?`);
311
+ }
312
+ const msg = await queue.shift();
313
+ msg.deliveredTo.add(subscriptionId);
314
+ return msg;
315
+ }
316
+ /** Check if there are messages in the stream buffer not yet delivered to this subscription. */
317
+ export function hasUndeliveredMessages(subscriptionId) {
318
+ const sub = subscriptionsById.get(subscriptionId);
319
+ if (!sub) {
320
+ return false;
321
+ }
322
+ const stream = streams.get(sub.streamId);
323
+ if (!stream) {
324
+ return false;
325
+ }
326
+ return stream.messages.some((msg) => !msg.deliveredTo.has(subscriptionId) && msg.senderId !== sub.sessionId);
327
+ }
328
+ // ─── Notification Registration ────────────────────────────────────────────────
329
+ /**
330
+ * Register a callback invoked when a message arrives on any async subscription
331
+ * for the given session. Returns an unsubscribe function.
332
+ */
333
+ export function registerAsyncListener(sessionId, callback) {
334
+ asyncListeners.set(sessionId, callback);
335
+ return () => {
336
+ asyncListeners.delete(sessionId);
337
+ };
338
+ }
339
+ // ─── Lifecycle Callbacks ──────────────────────────────────────────────────────
340
+ /**
341
+ * Register a callback invoked when a session has zero remaining subscriptions.
342
+ * Used by the lifecycle manager to auto-hibernate orphaned sessions.
343
+ */
344
+ export function onSessionOrphaned(cb) {
345
+ orphanCallback = cb;
346
+ }
347
+ // ─── Testing ──────────────────────────────────────────────────────────────────
348
+ /** Clear all state. For testing only. */
349
+ export function _resetForTesting() {
350
+ streams.clear();
351
+ streamsByName.clear();
352
+ subscriptionsBySession.clear();
353
+ subscriptionsById.clear();
354
+ fdCounters.clear();
355
+ asyncListeners.clear();
356
+ // Close all sync queues before clearing
357
+ for (const queue of syncQueues.values()) {
358
+ queue.close();
359
+ }
360
+ syncQueues.clear();
361
+ orphanCallback = undefined;
362
+ }
363
+ //# 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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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Initialize an in-memory SQLite database with all tables and seed data.
3
+ * Call once in `beforeAll`.
4
+ */
5
+ export declare function initTestDatabase(): void;
6
+ /**
7
+ * Extract the service handler map from `registerGrackleRoutes` by
8
+ * calling it with a fake router that captures the method implementations.
9
+ */
10
+ export declare function getHandlers(): Record<string, (...args: unknown[]) => unknown>;
11
+ //# sourceMappingURL=integration-setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration-setup.d.ts","sourceRoot":"","sources":["../../src/test-utils/integration-setup.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAIvC;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,CAS7E"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Shared setup for gRPC handler integration tests.
3
+ *
4
+ * Provides an in-memory SQLite database (real stores, no database mocks)
5
+ * and a helper to extract handler methods from {@link registerGrackleRoutes}.
6
+ */
7
+ import { openDatabase, initDatabase, sqlite, seedDatabase } from "@grackle-ai/database";
8
+ import { registerGrackleRoutes } from "../grpc-service.js";
9
+ /**
10
+ * Initialize an in-memory SQLite database with all tables and seed data.
11
+ * Call once in `beforeAll`.
12
+ */
13
+ export function initTestDatabase() {
14
+ openDatabase(":memory:");
15
+ initDatabase();
16
+ seedDatabase(sqlite);
17
+ }
18
+ /**
19
+ * Extract the service handler map from `registerGrackleRoutes` by
20
+ * calling it with a fake router that captures the method implementations.
21
+ */
22
+ export function getHandlers() {
23
+ let handlers = {};
24
+ const fakeRouter = {
25
+ service(_def, impl) {
26
+ handlers = impl;
27
+ },
28
+ };
29
+ registerGrackleRoutes(fakeRouter);
30
+ return handlers;
31
+ }
32
+ //# sourceMappingURL=integration-setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration-setup.js","sourceRoot":"","sources":["../../src/test-utils/integration-setup.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAG3D;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAC9B,YAAY,CAAC,UAAU,CAAC,CAAC;IACzB,YAAY,EAAE,CAAC;IACf,YAAY,CAAC,MAAO,CAAC,CAAC;AACxB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW;IACzB,IAAI,QAAQ,GAAoD,EAAE,CAAC;IACnE,MAAM,UAAU,GAAG;QACjB,OAAO,CAAC,IAAa,EAAE,IAAqD;YAC1E,QAAQ,GAAG,IAAI,CAAC;QAClB,CAAC;KAC0B,CAAC;IAC9B,qBAAqB,CAAC,UAAU,CAAC,CAAC;IAClC,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,130 @@
1
+ /** Create a complete mock of the `@grackle-ai/database` barrel export. */
2
+ export declare function createDatabaseMock(): {
3
+ db: {};
4
+ sqlite: undefined;
5
+ openDatabase: import("vitest").Mock<(...args: any[]) => any>;
6
+ initDatabase: import("vitest").Mock<(...args: any[]) => any>;
7
+ seedDatabase: import("vitest").Mock<(...args: any[]) => any>;
8
+ schema: {};
9
+ sessionStore: {
10
+ createSession: import("vitest").Mock<(...args: any[]) => any>;
11
+ getSession: import("vitest").Mock<() => undefined>;
12
+ listSessions: import("vitest").Mock<() => never[]>;
13
+ listByEnv: import("vitest").Mock<() => never[]>;
14
+ listSessionsForTask: import("vitest").Mock<() => never[]>;
15
+ listSessionsByTaskIds: import("vitest").Mock<() => never[]>;
16
+ getLatestSessionForTask: import("vitest").Mock<() => undefined>;
17
+ getActiveForEnv: import("vitest").Mock<() => undefined>;
18
+ getActiveSessionsForTask: import("vitest").Mock<() => never[]>;
19
+ getSuspendedForEnv: import("vitest").Mock<() => never[]>;
20
+ getChildSessions: import("vitest").Mock<() => never[]>;
21
+ updateSession: import("vitest").Mock<(...args: any[]) => any>;
22
+ updateSessionStatus: import("vitest").Mock<(...args: any[]) => any>;
23
+ updateSessionUsage: import("vitest").Mock<(...args: any[]) => any>;
24
+ updateRuntimeSessionId: import("vitest").Mock<(...args: any[]) => any>;
25
+ incrementTurns: import("vitest").Mock<(...args: any[]) => any>;
26
+ suspendSession: import("vitest").Mock<(...args: any[]) => any>;
27
+ reanimateSession: import("vitest").Mock<(...args: any[]) => any>;
28
+ setSessionTask: import("vitest").Mock<(...args: any[]) => any>;
29
+ deleteByEnvironment: import("vitest").Mock<(...args: any[]) => any>;
30
+ aggregateUsage: import("vitest").Mock<() => {
31
+ inputTokens: number;
32
+ outputTokens: number;
33
+ costUsd: number;
34
+ sessionCount: number;
35
+ }>;
36
+ };
37
+ taskStore: {
38
+ createTask: import("vitest").Mock<(...args: any[]) => any>;
39
+ insertTask: import("vitest").Mock<(...args: any[]) => any>;
40
+ getTask: import("vitest").Mock<() => undefined>;
41
+ listTasks: import("vitest").Mock<() => never[]>;
42
+ updateTask: import("vitest").Mock<(...args: any[]) => any>;
43
+ updateTaskStatus: import("vitest").Mock<(...args: any[]) => any>;
44
+ markTaskComplete: import("vitest").Mock<(...args: any[]) => any>;
45
+ deleteTask: import("vitest").Mock<(...args: any[]) => any>;
46
+ setTaskWorkspace: import("vitest").Mock<(...args: any[]) => any>;
47
+ setTaskDependsOn: import("vitest").Mock<(...args: any[]) => any>;
48
+ getUnblockedTasks: import("vitest").Mock<() => never[]>;
49
+ checkAndUnblock: import("vitest").Mock<() => never[]>;
50
+ areDependenciesMet: import("vitest").Mock<() => boolean>;
51
+ buildChildIdsMap: import("vitest").Mock<() => Map<any, any>>;
52
+ getChildren: import("vitest").Mock<() => never[]>;
53
+ getDescendants: import("vitest").Mock<() => never[]>;
54
+ getAncestors: import("vitest").Mock<() => never[]>;
55
+ getChildStatusCounts: import("vitest").Mock<() => {}>;
56
+ };
57
+ workspaceStore: {
58
+ createWorkspace: import("vitest").Mock<(...args: any[]) => any>;
59
+ getWorkspace: import("vitest").Mock<() => undefined>;
60
+ listWorkspaces: import("vitest").Mock<() => never[]>;
61
+ updateWorkspace: import("vitest").Mock<(...args: any[]) => any>;
62
+ archiveWorkspace: import("vitest").Mock<(...args: any[]) => any>;
63
+ countWorkspacesByEnvironment: import("vitest").Mock<() => number>;
64
+ };
65
+ personaStore: {
66
+ createPersona: import("vitest").Mock<(...args: any[]) => any>;
67
+ getPersona: import("vitest").Mock<() => undefined>;
68
+ getPersonaByName: import("vitest").Mock<() => undefined>;
69
+ listPersonas: import("vitest").Mock<() => never[]>;
70
+ updatePersona: import("vitest").Mock<(...args: any[]) => any>;
71
+ deletePersona: import("vitest").Mock<(...args: any[]) => any>;
72
+ };
73
+ findingStore: {
74
+ postFinding: import("vitest").Mock<(...args: any[]) => any>;
75
+ queryFindings: import("vitest").Mock<() => never[]>;
76
+ };
77
+ settingsStore: {
78
+ getSetting: import("vitest").Mock<() => undefined>;
79
+ setSetting: import("vitest").Mock<(...args: any[]) => any>;
80
+ isAllowedSettingKey: import("vitest").Mock<() => boolean>;
81
+ WRITABLE_SETTING_KEYS: Set<string>;
82
+ };
83
+ envRegistry: {
84
+ listEnvironments: import("vitest").Mock<() => never[]>;
85
+ getEnvironment: import("vitest").Mock<() => undefined>;
86
+ addEnvironment: import("vitest").Mock<(...args: any[]) => any>;
87
+ removeEnvironment: import("vitest").Mock<(...args: any[]) => any>;
88
+ updateEnvironmentStatus: import("vitest").Mock<(...args: any[]) => any>;
89
+ markBootstrapped: import("vitest").Mock<(...args: any[]) => any>;
90
+ setEnvInfo: import("vitest").Mock<(...args: any[]) => any>;
91
+ updateAdapterConfig: import("vitest").Mock<(...args: any[]) => any>;
92
+ updateEnvironment: import("vitest").Mock<(...args: any[]) => any>;
93
+ resetAllStatuses: import("vitest").Mock<(...args: any[]) => any>;
94
+ };
95
+ tokenStore: {
96
+ setToken: import("vitest").Mock<(...args: any[]) => any>;
97
+ deleteToken: import("vitest").Mock<(...args: any[]) => any>;
98
+ listTokens: import("vitest").Mock<() => never[]>;
99
+ getBundle: import("vitest").Mock<() => {
100
+ tokens: never[];
101
+ }>;
102
+ };
103
+ credentialProviders: {
104
+ getCredentialProviders: import("vitest").Mock<() => {
105
+ claude: string;
106
+ github: string;
107
+ copilot: string;
108
+ codex: string;
109
+ goose: string;
110
+ }>;
111
+ setCredentialProviders: import("vitest").Mock<(...args: any[]) => any>;
112
+ parseCredentialProviderConfig: import("vitest").Mock<(...args: any[]) => any>;
113
+ isValidCredentialProviderConfig: import("vitest").Mock<() => boolean>;
114
+ VALID_PROVIDERS: string[];
115
+ VALID_CLAUDE_VALUES: Set<string>;
116
+ VALID_TOGGLE_VALUES: Set<string>;
117
+ };
118
+ isAllowedSettingKey: import("vitest").Mock<() => boolean>;
119
+ WRITABLE_SETTING_KEYS: Set<string>;
120
+ VALID_PROVIDERS: string[];
121
+ VALID_CLAUDE_VALUES: Set<string>;
122
+ VALID_TOGGLE_VALUES: Set<string>;
123
+ persistEvent: import("vitest").Mock<(...args: any[]) => any>;
124
+ grackleHome: string;
125
+ safeParseJsonArray: (value: unknown) => string[];
126
+ slugify: (text: string) => string;
127
+ encrypt: import("vitest").Mock<(x: unknown) => unknown>;
128
+ decrypt: import("vitest").Mock<(x: unknown) => unknown>;
129
+ };
130
+ //# sourceMappingURL=mock-database.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-database.d.ts","sourceRoot":"","sources":["../../src/test-utils/mock-database.ts"],"names":[],"mappings":"AAeA,0EAA0E;AAE1E,wBAAgB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCA4HF,OAAO,KAAG,MAAM,EAAE;oBAS9B,MAAM,KAAG,MAAM;uCACZ,OAAO;uCACP,OAAO;EAE7B"}