@link-assistant/agent 0.26.1 → 0.26.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/agent",
3
- "version": "0.26.1",
3
+ "version": "0.26.2",
4
4
  "description": "A minimal, public domain AI CLI agent compatible with OpenCode's JSON interface. Bun-only runtime.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -971,4 +971,9 @@ async function main() {
971
971
  }
972
972
  }
973
973
 
974
- main();
974
+ // Awaited on purpose: everything the CLI does happens inside this promise, and
975
+ // on Windows nothing else holds the process open while startup waits on async
976
+ // filesystem I/O — the event loop drains and Bun exits 0 mid-run, so the CLI
977
+ // prints its startup logs, never contacts the provider and reports success.
978
+ // See: https://github.com/link-assistant/agent/issues/306
979
+ await main();
@@ -508,9 +508,20 @@ export namespace SessionProcessor {
508
508
  }
509
509
  snapshot = undefined;
510
510
  }
511
- SessionSummary.summarize({
511
+ // Fire-and-forget on purpose, and guarded: a summarization
512
+ // failure must not become an unhandled rejection that exits
513
+ // the process while this stream is still being processed.
514
+ // See: https://github.com/link-assistant/agent/issues/304
515
+ void SessionSummary.summarize({
512
516
  sessionID: input.sessionID,
513
517
  messageID: input.assistantMessage.parentID,
518
+ }).catch((error) => {
519
+ log.warn(() => ({
520
+ message: 'session summarization failed',
521
+ sessionID: input.sessionID,
522
+ error:
523
+ error instanceof Error ? error.message : String(error),
524
+ }));
514
525
  });
515
526
  break;
516
527
 
@@ -833,9 +833,19 @@ export namespace SessionPrompt {
833
833
  };
834
834
 
835
835
  if (step === 1) {
836
- SessionSummary.summarize({
836
+ // Fire-and-forget on purpose: the summary runs alongside the turn. The
837
+ // `void` marks that, and the `.catch` keeps a summarization failure
838
+ // from becoming an unhandled rejection that would exit the process and
839
+ // abort this turn. See: https://github.com/link-assistant/agent/issues/304
840
+ void SessionSummary.summarize({
837
841
  sessionID: sessionID,
838
842
  messageID: lastUser.id,
843
+ }).catch((error) => {
844
+ log.warn(() => ({
845
+ message: 'session summarization failed',
846
+ sessionID,
847
+ error: error instanceof Error ? error.message : String(error),
848
+ }));
839
849
  });
840
850
  }
841
851
 
@@ -25,11 +25,29 @@ export namespace SessionSummary {
25
25
  messageID: z.string(),
26
26
  }),
27
27
  async (input) => {
28
- const all = await Session.messages({ sessionID: input.sessionID });
29
- await Promise.all([
30
- summarizeSession({ sessionID: input.sessionID, messages: all }),
31
- summarizeMessage({ messageID: input.messageID, messages: all }),
32
- ]);
28
+ // Summarization is a side quest: its whole product is a session title, a
29
+ // body and a diff stat, and it runs concurrently with the turn it
30
+ // describes. Neither caller awaits it, so a rejection escaping this
31
+ // function has no handler anywhere and becomes an `unhandledRejection`,
32
+ // which exits the process with status 1 and aborts the streaming turn.
33
+ // A failed title must never be a failed run.
34
+ // See: https://github.com/link-assistant/agent/issues/304
35
+ try {
36
+ const all = await Session.messages({ sessionID: input.sessionID });
37
+ await Promise.all([
38
+ summarizeSession({ sessionID: input.sessionID, messages: all }),
39
+ summarizeMessage({ messageID: input.messageID, messages: all }),
40
+ ]);
41
+ } catch (error) {
42
+ log.warn(() => ({
43
+ message: 'session summarization failed',
44
+ hint: 'The turn is unaffected; only the session summary is missing',
45
+ sessionID: input.sessionID,
46
+ messageID: input.messageID,
47
+ error: error instanceof Error ? error.message : String(error),
48
+ stack: error instanceof Error ? error.stack : undefined,
49
+ }));
50
+ }
33
51
  }
34
52
  );
35
53
 
@@ -202,20 +220,34 @@ export namespace SessionSummary {
202
220
  ],
203
221
  headers: model.info.headers,
204
222
  model: model.language,
205
- });
206
-
207
- if (isVerbose()) {
208
- log.info(() => ({
209
- message: 'title API response received',
223
+ }).catch((err) => {
224
+ // A provider that resolves a model but errors on the request must cost
225
+ // the run its title, not its exit status.
226
+ // See: https://github.com/link-assistant/agent/issues/304
227
+ log.warn(() => ({
228
+ message: 'title API call failed',
210
229
  providerID: model.providerID,
211
230
  modelID: model.modelID,
212
- titleLength: result.text.length,
213
- usage: result.usage,
231
+ error: err instanceof Error ? err.message : String(err),
232
+ stack: err instanceof Error ? err.stack : undefined,
214
233
  }));
234
+ return undefined;
235
+ });
236
+
237
+ if (result) {
238
+ if (isVerbose()) {
239
+ log.info(() => ({
240
+ message: 'title API response received',
241
+ providerID: model.providerID,
242
+ modelID: model.modelID,
243
+ titleLength: result.text.length,
244
+ usage: result.usage,
245
+ }));
246
+ }
247
+ log.info(() => ({ message: 'title', title: result.text }));
248
+ userMsg.summary.title = result.text;
249
+ await Session.updateMessage(userMsg);
215
250
  }
216
- log.info(() => ({ message: 'title', title: result.text }));
217
- userMsg.summary.title = result.text;
218
- await Session.updateMessage(userMsg);
219
251
  }
220
252
 
221
253
  if (