@forgeax/engine-intelligence 0.1.6 → 0.1.7

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);
@@ -76,19 +78,18 @@ export function bindIntelligencePort(
76
78
  runtime.cancel(message.activityId);
77
79
  return;
78
80
  }
79
- if (message.kind === 'intelligence-close') void close(true);
81
+ if (message.kind === 'intelligence-close') void close();
80
82
  };
81
- const close = async (notifyRealm = false): Promise<void> => {
82
- if (closed) return;
83
+ const close = (): Promise<void> => {
84
+ if (closeTask !== undefined) return closeTask;
83
85
  closed = true;
84
86
  port.removeEventListener('message', listener);
85
- await runtime.close();
86
- if (notifyRealm) {
87
+ closeTask = (async () => {
88
+ await runtime.close();
87
89
  port.postMessage({ kind: 'intelligence-closed' });
88
90
  setTimeout(() => port.close(), 0);
89
- } else {
90
- port.close();
91
- }
91
+ })();
92
+ return closeTask;
92
93
  };
93
94
  port.addEventListener('message', listener);
94
95
  port.start?.();
@@ -157,6 +158,7 @@ export class IntelligencePortClient implements IntelligenceService {
157
158
  this.createSessionId = options.createSessionId ?? (() => nextPortIdentity('session'));
158
159
  this.listener = (event) => {
159
160
  const message = event.data;
161
+ if (this.closed && message.kind !== 'intelligence-closed') return;
160
162
  if (message.kind === 'intelligence-events') {
161
163
  this.pollPending = false;
162
164
  for (const item of message.events) {
@@ -167,19 +169,26 @@ export class IntelligencePortClient implements IntelligenceService {
167
169
  this.active.delete(message.activityId);
168
170
  this.rejected.set(message.activityId, message);
169
171
  } else if (message.kind === 'intelligence-closed') {
172
+ this.markClosed();
170
173
  this.port.removeEventListener('message', this.listener);
171
- this.received.length = 0;
172
- this.active.clear();
173
- this.rejected.clear();
174
174
  this.port.close();
175
175
  this.closeResolve?.();
176
176
  this.closeResolve = undefined;
177
+ this.closeTask ??= Promise.resolve();
177
178
  }
178
179
  };
179
180
  port.addEventListener('message', this.listener);
180
181
  port.start?.();
181
182
  }
182
183
 
184
+ private markClosed(): void {
185
+ this.closed = true;
186
+ this.received.length = 0;
187
+ this.active.clear();
188
+ this.rejected.clear();
189
+ this.pollPending = false;
190
+ }
191
+
183
192
  submit(request: ActivityRequest): Result<ActivityRef, IntelligenceError> {
184
193
  if (this.closed) return err(new IntelligenceError({ code: 'intelligence-closed', detail: {} }));
185
194
  if (request.input.length === 0 || request.input.length > this.limits.maxInputChars) {
@@ -264,7 +273,11 @@ export class IntelligencePortClient implements IntelligenceService {
264
273
 
265
274
  close(): Promise<void> {
266
275
  if (this.closeTask !== undefined) return this.closeTask;
267
- this.closed = true;
276
+ if (this.closed) {
277
+ this.closeTask = Promise.resolve();
278
+ return this.closeTask;
279
+ }
280
+ this.markClosed();
268
281
  this.closeTask = new Promise((resolve) => {
269
282
  this.closeResolve = resolve;
270
283
  this.port.postMessage({ kind: 'intelligence-close' });