@forgeax/engine-intelligence 0.1.6 → 0.1.19

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/src/runtime.ts CHANGED
@@ -108,7 +108,13 @@ export class IntelligenceRuntime implements IntelligenceService {
108
108
  terminal: false,
109
109
  };
110
110
  this.records.set(submission.id, record);
111
- const started = this.provider.start(submission, this.createSink(record));
111
+ let started: Result<void, IntelligenceError>;
112
+ try {
113
+ started = this.provider.start(submission, this.createSink(record));
114
+ } catch (cause) {
115
+ this.records.delete(submission.id);
116
+ return err(providerError(this.providerId, cause));
117
+ }
112
118
  if (!started.ok) {
113
119
  this.records.delete(submission.id);
114
120
  return started;
@@ -142,7 +148,11 @@ export class IntelligenceRuntime implements IntelligenceService {
142
148
  }),
143
149
  );
144
150
  }
145
- return this.provider.cancel(activity);
151
+ try {
152
+ return this.provider.cancel(activity);
153
+ } catch (cause) {
154
+ return err(providerError(this.providerId, cause));
155
+ }
146
156
  }
147
157
 
148
158
  close(): Promise<void> {
@@ -152,7 +162,11 @@ export class IntelligenceRuntime implements IntelligenceService {
152
162
 
153
163
  private async performClose(): Promise<void> {
154
164
  this.closed = true;
155
- await this.provider.close();
165
+ try {
166
+ await this.provider.close();
167
+ } catch {
168
+ // Disposal is terminal; provider failure must not strand the realm transport.
169
+ }
156
170
  this.records.clear();
157
171
  }
158
172
 
@@ -195,7 +209,7 @@ export class IntelligenceRuntime implements IntelligenceService {
195
209
  private createSink(record: ActivityRecord): ActivitySink {
196
210
  return {
197
211
  text: (text) => {
198
- if (record.terminal || text.length === 0) return;
212
+ if (this.closed || record.terminal || text.length === 0) return;
199
213
  if (record.outputChars + text.length > this.limits.maxOutputChars) {
200
214
  this.overflow(record, 'output-chars', this.limits.maxOutputChars);
201
215
  return;
@@ -208,7 +222,7 @@ export class IntelligenceRuntime implements IntelligenceService {
208
222
  this.push(record, { type: 'text-delta', text });
209
223
  },
210
224
  complete: (output) => {
211
- if (record.terminal) return;
225
+ if (this.closed || record.terminal) return;
212
226
  if (output.length > this.limits.maxOutputChars) {
213
227
  this.overflow(record, 'output-chars', this.limits.maxOutputChars);
214
228
  return;
@@ -217,13 +231,13 @@ export class IntelligenceRuntime implements IntelligenceService {
217
231
  this.push(record, { type: 'completed', session: record.ref.session, output });
218
232
  },
219
233
  fail: (cause) => {
220
- if (record.terminal) return;
234
+ if (this.closed || record.terminal) return;
221
235
  record.terminal = true;
222
236
  const error = providerError(this.providerId, cause);
223
237
  this.push(record, { type: 'failed', error: intelligenceFailure(error) });
224
238
  },
225
239
  cancelled: () => {
226
- if (record.terminal) return;
240
+ if (this.closed || record.terminal) return;
227
241
  record.terminal = true;
228
242
  this.push(record, { type: 'cancelled' });
229
243
  },
@@ -243,7 +257,11 @@ export class IntelligenceRuntime implements IntelligenceService {
243
257
  });
244
258
  if (record.events.length >= this.limits.maxPendingEventsPerActivity) record.events.pop();
245
259
  this.push(record, { type: 'failed', error: intelligenceFailure(error) });
246
- this.provider.cancel(record.ref.id);
260
+ try {
261
+ this.provider.cancel(record.ref.id);
262
+ } catch {
263
+ // The overflow terminal already owns the activity outcome.
264
+ }
247
265
  }
248
266
 
249
267
  private push(
package/src/transport.ts CHANGED
@@ -52,9 +52,11 @@ export function bindIntelligencePort(
52
52
  runtime: IntelligenceRuntime,
53
53
  ): IntelligencePortBinding {
54
54
  let closed = false;
55
+ let closeTask: Promise<void> | undefined;
55
56
  const listener = (
56
57
  event: MessageEvent<IntelligenceHostCommand | IntelligenceRealmMessage>,
57
58
  ): void => {
59
+ if (closed) return;
58
60
  const message = event.data;
59
61
  if (message.kind === 'intelligence-submit') {
60
62
  const accepted = runtime.accept(message.submission);
@@ -69,26 +71,37 @@ export function bindIntelligencePort(
69
71
  return;
70
72
  }
71
73
  if (message.kind === 'intelligence-poll') {
72
- port.postMessage({ kind: 'intelligence-events', events: runtime.poll(message.maxEvents) });
74
+ const events = runtime.poll(message.maxEvents);
75
+ try {
76
+ port.postMessage({ kind: 'intelligence-events', events });
77
+ } catch {
78
+ // Poll is destructive; a failed response must terminate the Host binding
79
+ // so the drained events cannot remain attached to a live runtime.
80
+ void close().catch(() => undefined);
81
+ }
73
82
  return;
74
83
  }
75
84
  if (message.kind === 'intelligence-cancel') {
76
85
  runtime.cancel(message.activityId);
77
86
  return;
78
87
  }
79
- if (message.kind === 'intelligence-close') void close(true);
88
+ if (message.kind === 'intelligence-close') void close();
80
89
  };
81
- const close = async (notifyRealm = false): Promise<void> => {
82
- if (closed) return;
90
+ const close = (): Promise<void> => {
91
+ if (closeTask !== undefined) return closeTask;
83
92
  closed = true;
84
93
  port.removeEventListener('message', listener);
85
- await runtime.close();
86
- if (notifyRealm) {
87
- port.postMessage({ kind: 'intelligence-closed' });
88
- setTimeout(() => port.close(), 0);
89
- } else {
94
+ closeTask = (async () => {
95
+ await runtime.close();
96
+ try {
97
+ port.postMessage({ kind: 'intelligence-closed' });
98
+ } catch {
99
+ // A failed terminal notification must not strand the physical transport.
100
+ }
101
+ await new Promise<void>((resolve) => setTimeout(resolve, 0));
90
102
  port.close();
91
- }
103
+ })();
104
+ return closeTask;
92
105
  };
93
106
  port.addEventListener('message', listener);
94
107
  port.start?.();
@@ -145,6 +158,7 @@ export class IntelligencePortClient implements IntelligenceService {
145
158
  private pollPending = false;
146
159
  private closeTask: Promise<void> | undefined;
147
160
  private closeResolve: (() => void) | undefined;
161
+ private transportReleased = false;
148
162
 
149
163
  constructor(
150
164
  readonly providerId: string,
@@ -157,6 +171,7 @@ export class IntelligencePortClient implements IntelligenceService {
157
171
  this.createSessionId = options.createSessionId ?? (() => nextPortIdentity('session'));
158
172
  this.listener = (event) => {
159
173
  const message = event.data;
174
+ if (this.closed && message.kind !== 'intelligence-closed') return;
160
175
  if (message.kind === 'intelligence-events') {
161
176
  this.pollPending = false;
162
177
  for (const item of message.events) {
@@ -167,19 +182,32 @@ export class IntelligencePortClient implements IntelligenceService {
167
182
  this.active.delete(message.activityId);
168
183
  this.rejected.set(message.activityId, message);
169
184
  } else if (message.kind === 'intelligence-closed') {
170
- this.port.removeEventListener('message', this.listener);
171
- this.received.length = 0;
172
- this.active.clear();
173
- this.rejected.clear();
174
- this.port.close();
185
+ this.markClosed();
186
+ this.releaseTransport();
175
187
  this.closeResolve?.();
176
188
  this.closeResolve = undefined;
189
+ this.closeTask ??= Promise.resolve();
177
190
  }
178
191
  };
179
192
  port.addEventListener('message', this.listener);
180
193
  port.start?.();
181
194
  }
182
195
 
196
+ private markClosed(): void {
197
+ this.closed = true;
198
+ this.received.length = 0;
199
+ this.active.clear();
200
+ this.rejected.clear();
201
+ this.pollPending = false;
202
+ }
203
+
204
+ private releaseTransport(): void {
205
+ if (this.transportReleased) return;
206
+ this.transportReleased = true;
207
+ this.port.removeEventListener('message', this.listener);
208
+ this.port.close();
209
+ }
210
+
183
211
  submit(request: ActivityRequest): Result<ActivityRef, IntelligenceError> {
184
212
  if (this.closed) return err(new IntelligenceError({ code: 'intelligence-closed', detail: {} }));
185
213
  if (request.input.length === 0 || request.input.length > this.limits.maxInputChars) {
@@ -220,10 +248,16 @@ export class IntelligencePortClient implements IntelligenceService {
220
248
  session: request.session ?? { providerId: this.providerId, id: this.createSessionId() },
221
249
  };
222
250
  this.active.add(ref.id);
223
- this.port.postMessage({
224
- kind: 'intelligence-submit',
225
- submission: { ...ref, input: request.input },
226
- });
251
+ try {
252
+ this.port.postMessage({
253
+ kind: 'intelligence-submit',
254
+ submission: { ...ref, input: request.input },
255
+ });
256
+ } catch {
257
+ this.markClosed();
258
+ this.releaseTransport();
259
+ return err(new IntelligenceError({ code: 'intelligence-closed', detail: {} }));
260
+ }
227
261
  return ok(ref);
228
262
  }
229
263
 
@@ -243,7 +277,13 @@ export class IntelligencePortClient implements IntelligenceService {
243
277
  }
244
278
  if (!this.pollPending) {
245
279
  this.pollPending = true;
246
- this.port.postMessage({ kind: 'intelligence-poll', maxEvents: count });
280
+ try {
281
+ this.port.postMessage({ kind: 'intelligence-poll', maxEvents: count });
282
+ } catch {
283
+ this.markClosed();
284
+ this.releaseTransport();
285
+ return [];
286
+ }
247
287
  }
248
288
  return events;
249
289
  }
@@ -258,16 +298,32 @@ export class IntelligencePortClient implements IntelligenceService {
258
298
  }),
259
299
  );
260
300
  }
261
- this.port.postMessage({ kind: 'intelligence-cancel', activityId: id });
301
+ try {
302
+ this.port.postMessage({ kind: 'intelligence-cancel', activityId: id });
303
+ } catch {
304
+ this.markClosed();
305
+ this.releaseTransport();
306
+ return err(new IntelligenceError({ code: 'intelligence-closed', detail: {} }));
307
+ }
262
308
  return ok(undefined);
263
309
  }
264
310
 
265
311
  close(): Promise<void> {
266
312
  if (this.closeTask !== undefined) return this.closeTask;
267
- this.closed = true;
313
+ if (this.closed) {
314
+ this.closeTask = Promise.resolve();
315
+ return this.closeTask;
316
+ }
317
+ this.markClosed();
268
318
  this.closeTask = new Promise((resolve) => {
269
319
  this.closeResolve = resolve;
270
- this.port.postMessage({ kind: 'intelligence-close' });
320
+ try {
321
+ this.port.postMessage({ kind: 'intelligence-close' });
322
+ } catch {
323
+ this.releaseTransport();
324
+ this.closeResolve = undefined;
325
+ resolve();
326
+ }
271
327
  });
272
328
  return this.closeTask;
273
329
  }