@stablekernel/opencode-cursor 0.7.1-next.2 → 0.8.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.
@@ -1,20 +1,26 @@
1
1
  import {
2
2
  acquireAgent,
3
+ activityLine,
3
4
  addUsage,
4
5
  classifyError,
5
6
  dropSessionRecord,
6
7
  extractSystemText,
7
8
  getSessionRecord,
8
9
  linkSubagentSession,
10
+ linkSubagentSessionLive,
9
11
  pluginLog,
12
+ registerSubagentCall,
13
+ renderConversationSteps,
10
14
  resolveControls,
11
15
  resolveCursorApiKey,
12
16
  resolveSystemDelivery,
17
+ resultText,
13
18
  sendAgentTurnSilently,
14
19
  setPreferredTransport,
15
20
  streamAgentTurn,
21
+ unregisterSubagentCall,
16
22
  withSessionLock
17
- } from "../chunk-RDY3H2LE.js";
23
+ } from "../chunk-YIEC27VB.js";
18
24
 
19
25
  // src/provider/index.ts
20
26
  import { NoSuchModelError } from "@ai-sdk/provider";
@@ -134,6 +140,203 @@ function trailingUserMessages(prompt, count) {
134
140
  return collected.reverse();
135
141
  }
136
142
 
143
+ // src/provider/subagent-stream.ts
144
+ var TITLE_KEYS = ["path", "command", "pattern", "query", "server"];
145
+ function toolTitle(input) {
146
+ if (typeof input !== "object" || input === null) return void 0;
147
+ const record = input;
148
+ for (const key of TITLE_KEYS) {
149
+ const value = record[key];
150
+ if (typeof value === "string" && value) return value;
151
+ }
152
+ return void 0;
153
+ }
154
+ var SubagentTranscriptSink = class _SubagentTranscriptSink {
155
+ /** Flush when this much time has elapsed since the last flush. */
156
+ static FLUSH_INTERVAL_MS = 1500;
157
+ session;
158
+ text = "";
159
+ reasoning = "";
160
+ tools = [];
161
+ pending = false;
162
+ lastFlush = 0;
163
+ timer;
164
+ done = false;
165
+ /** Nested call id → the running tool part written for it. */
166
+ partHandles = /* @__PURE__ */ new Map();
167
+ /** Serialises tool-part writes so a result never overtakes its start. */
168
+ partChain = Promise.resolve();
169
+ anonSeq = 0;
170
+ /** Correlation key for a nested event that arrived without a call id. */
171
+ nestedKey(id) {
172
+ return id || `anon-${++this.anonSeq}`;
173
+ }
174
+ /** Enqueue a tool-part write; fire-and-forget, never throws. */
175
+ enqueuePart(write) {
176
+ this.partChain = this.partChain.then(async () => {
177
+ await write();
178
+ }).catch(() => void 0);
179
+ }
180
+ constructor(session) {
181
+ this.session = session;
182
+ }
183
+ /** The linked child session id (for stamping the task card's sessionId). */
184
+ get childId() {
185
+ return this.session.childId;
186
+ }
187
+ /** Feed a normalized nested subagent event into the sink. */
188
+ push(event) {
189
+ if (this.done) return;
190
+ switch (event.type) {
191
+ case "text":
192
+ this.text += event.text;
193
+ this.pending = true;
194
+ break;
195
+ case "reasoning":
196
+ this.reasoning += event.text;
197
+ this.pending = true;
198
+ break;
199
+ case "tool-start": {
200
+ this.tools.push(`**\`${event.name}\`** ${formatArgs(event.input)}`);
201
+ this.pending = true;
202
+ const key = this.nestedKey(event.id);
203
+ const start = Date.now();
204
+ this.enqueuePart(async () => {
205
+ const partID = await this.session.toolPart({
206
+ callID: key,
207
+ tool: event.name,
208
+ status: "running",
209
+ title: toolTitle(event.input),
210
+ input: event.input,
211
+ start
212
+ });
213
+ if (partID) {
214
+ this.partHandles.set(key, {
215
+ partID,
216
+ callID: key,
217
+ tool: event.name,
218
+ title: toolTitle(event.input),
219
+ input: event.input,
220
+ start
221
+ });
222
+ }
223
+ });
224
+ break;
225
+ }
226
+ case "tool-result": {
227
+ this.tools.push(formatResult(event.name, event.result, event.isError));
228
+ this.pending = true;
229
+ const key = event.id || `result-${++this.anonSeq}`;
230
+ this.enqueuePart(async () => {
231
+ const handle = this.partHandles.get(key);
232
+ this.partHandles.delete(key);
233
+ await this.session.toolPart({
234
+ callID: handle?.callID ?? key,
235
+ tool: event.name,
236
+ status: "completed",
237
+ title: handle?.title,
238
+ input: handle?.input,
239
+ partID: handle?.partID,
240
+ start: handle?.start ?? Date.now(),
241
+ end: Date.now()
242
+ });
243
+ });
244
+ this.flushNow();
245
+ return;
246
+ }
247
+ }
248
+ this.armTimer();
249
+ }
250
+ /**
251
+ * Flush any buffered content, then append the subagent's final answer
252
+ * (`resultSuffix`), a render of its `conversationSteps` (its own
253
+ * text/thinking/tool activity), and the optional activity line, and mark
254
+ * the sink done. Further pushes and flushes become no-ops.
255
+ */
256
+ async finalize(resultValue, activity) {
257
+ if (this.done) return;
258
+ this.done = true;
259
+ if (this.timer) clearTimeout(this.timer);
260
+ this.timer = void 0;
261
+ const body = this.render();
262
+ if (body) await this.session.flush(body);
263
+ const suffix = typeof resultValue === "object" && resultValue !== null ? resultValue["resultSuffix"] : void 0;
264
+ if (typeof suffix === "string" && suffix) await this.session.flush(suffix);
265
+ const steps = renderConversationSteps(resultValue);
266
+ if (steps) await this.session.flush(steps);
267
+ await this.partChain;
268
+ for (const [, handle] of this.partHandles) {
269
+ await this.session.toolPart({
270
+ callID: handle.callID,
271
+ tool: handle.tool,
272
+ status: "completed",
273
+ title: handle.title,
274
+ input: handle.input,
275
+ partID: handle.partID,
276
+ start: handle.start,
277
+ end: Date.now()
278
+ });
279
+ }
280
+ this.partHandles.clear();
281
+ if (activity) await this.session.finalize(activity);
282
+ else await this.session.finalize();
283
+ }
284
+ armTimer() {
285
+ if (this.done || this.timer) return;
286
+ const elapsed = Date.now() - this.lastFlush;
287
+ const delay = Math.max(0, _SubagentTranscriptSink.FLUSH_INTERVAL_MS - elapsed);
288
+ this.timer = setTimeout(() => {
289
+ this.timer = void 0;
290
+ this.flushNow();
291
+ }, delay);
292
+ this.timer.unref?.();
293
+ }
294
+ flushNow() {
295
+ if (this.done) return;
296
+ if (this.timer) {
297
+ clearTimeout(this.timer);
298
+ this.timer = void 0;
299
+ }
300
+ if (!this.pending) return;
301
+ const body = this.render();
302
+ this.pending = false;
303
+ this.lastFlush = Date.now();
304
+ if (body) void this.session.flush(body);
305
+ }
306
+ /** Render the accumulated activity into a single markdown message. */
307
+ render() {
308
+ const parts = [];
309
+ if (this.text.trim()) parts.push(this.text.trim());
310
+ if (this.reasoning.trim()) parts.push(`> ${this.reasoning.trim()}`);
311
+ if (this.tools.length > 0) parts.push(this.tools.join("\n\n"));
312
+ const body = parts.join("\n\n").trim();
313
+ this.text = "";
314
+ this.reasoning = "";
315
+ this.tools.length = 0;
316
+ return body;
317
+ }
318
+ };
319
+ function formatArgs(input) {
320
+ let s = "";
321
+ try {
322
+ s = typeof input === "string" ? input : JSON.stringify(input);
323
+ } catch {
324
+ return "";
325
+ }
326
+ if (!s || s === "{}" || s === '""') return "";
327
+ return s;
328
+ }
329
+ function formatResult(name, result, isError) {
330
+ if (isError) return `**\`${name}\`** \u2014 _failed_`;
331
+ const text = resultText(result);
332
+ if (!text) return `**\`${name}\`** \u2014 _done_`;
333
+ return `**\`${name}\`**
334
+
335
+ \`\`\`
336
+ ${text}
337
+ \`\`\``;
338
+ }
339
+
137
340
  // src/provider/stream-map.ts
138
341
  var TASK_TOOL_NAME = "task";
139
342
  function injectSubagentSessionId(parts, sessionId) {
@@ -213,7 +416,10 @@ function numField(v, key) {
213
416
  return isRecord(v) && typeof v[key] === "number" ? v[key] : void 0;
214
417
  }
215
418
  function successValue(result) {
216
- return isRecord(result) && result["status"] === "success" ? result["value"] : void 0;
419
+ if (!isRecord(result) || result["status"] !== "success") return void 0;
420
+ const value = result["value"];
421
+ if (value === void 0) return void 0;
422
+ return value;
217
423
  }
218
424
  function subagentTypeField(args) {
219
425
  const sub = isRecord(args) ? args["subagentType"] : void 0;
@@ -238,7 +444,7 @@ function mcpFold(result) {
238
444
  return { title: "", metadata: {}, output: text };
239
445
  }
240
446
  function mcpInputArgs(args) {
241
- return isRecord(args) ? args["args"] : void 0;
447
+ return isRecord(args) && isRecord(args["args"]) ? args["args"] : void 0;
242
448
  }
243
449
  function webSearchProvider(args) {
244
450
  const id = (strField(args, "providerIdentifier") ?? "").toLowerCase();
@@ -253,7 +459,7 @@ var WEBSEARCH_ADAPTER = {
253
459
  tool: "websearch",
254
460
  input: (args) => {
255
461
  const query = strField(mcpInputArgs(args), "query");
256
- return query !== void 0 ? { query } : {};
462
+ return query === void 0 ? {} : { query };
257
463
  },
258
464
  result: (value, args) => {
259
465
  const provider = webSearchProvider(args);
@@ -325,15 +531,15 @@ var NATIVE_ADAPTERS = {
325
531
  const totalLines = numField(value, "totalLines");
326
532
  const fileSize = numField(value, "fileSize");
327
533
  const linesReturned = content.split("\n").length;
328
- const lineLabel = totalLines !== void 0 ? `${linesReturned}/${totalLines} lines` : `${linesReturned} lines`;
534
+ const lineLabel = totalLines === void 0 ? `${linesReturned} lines` : `${linesReturned}/${totalLines} lines`;
329
535
  return {
330
536
  title: `${filePath} (${lineLabel})`,
331
537
  metadata: {
332
538
  preview: content.split("\n").slice(0, 20).join("\n"),
333
539
  loaded: [],
334
540
  linesReturned,
335
- ...totalLines !== void 0 ? { totalLines } : {},
336
- ...fileSize !== void 0 ? { fileSize } : {}
541
+ ...totalLines === void 0 ? {} : { totalLines },
542
+ ...fileSize === void 0 ? {} : { fileSize }
337
543
  },
338
544
  output: content
339
545
  };
@@ -349,7 +555,7 @@ var NATIVE_ADAPTERS = {
349
555
  result: (value, args) => {
350
556
  const filePath = strField(args, "path") ?? "";
351
557
  const lines = numField(value, "linesCreated");
352
- const output = lines !== void 0 ? `Wrote ${lines} line${lines === 1 ? "" : "s"}.` : "Wrote file successfully.";
558
+ const output = lines === void 0 ? "Wrote file successfully." : `Wrote ${lines} line${lines === 1 ? "" : "s"}.`;
353
559
  return {
354
560
  title: filePath,
355
561
  metadata: { diagnostics: {}, filepath: filePath, exists: false },
@@ -417,9 +623,7 @@ var NATIVE_ADAPTERS = {
417
623
  current = file;
418
624
  lines.push(`${file}:`);
419
625
  }
420
- lines.push(
421
- line !== void 0 ? ` Line ${line}: ${text}` : ` ${text}`
422
- );
626
+ lines.push(line === void 0 ? ` ${text}` : ` Line ${line}: ${text}`);
423
627
  total++;
424
628
  }
425
629
  } else if (u["type"] === "files" && isRecord(output) && Array.isArray(output["files"])) {
@@ -440,11 +644,9 @@ var NATIVE_ADAPTERS = {
440
644
  return {
441
645
  title,
442
646
  metadata: { matches: total, truncated: false },
443
- output: total > 0 ? [
444
- `Found ${total} match${total === 1 ? "" : "es"}`,
445
- "",
446
- ...lines
447
- ].join("\n") : "No matches found"
647
+ output: total > 0 ? [`Found ${total} match${total === 1 ? "" : "es"}`, "", ...lines].join(
648
+ "\n"
649
+ ) : "No matches found"
448
650
  };
449
651
  }
450
652
  },
@@ -452,7 +654,9 @@ var NATIVE_ADAPTERS = {
452
654
  // + results body instead of the raw `{results}` JSON.
453
655
  semSearch: {
454
656
  input: (args) => {
455
- const out = { query: strField(args, "query") ?? "" };
657
+ const out = {
658
+ query: strField(args, "query") ?? ""
659
+ };
456
660
  const dirs = isRecord(args) && Array.isArray(args["targetDirectories"]) ? args["targetDirectories"] : void 0;
457
661
  if (dirs && dirs.length > 0) out["targetDirectories"] = dirs;
458
662
  return out;
@@ -557,7 +761,7 @@ var NATIVE_ADAPTERS = {
557
761
  const start = isRecord(d["range"]) ? d["range"]["start"] : void 0;
558
762
  const line = numField(start, "line");
559
763
  const char = numField(start, "character");
560
- const loc = line !== void 0 ? ` L${line + 1}${char !== void 0 ? `:${char + 1}` : ""}` : "";
764
+ const loc = line === void 0 ? "" : ` L${line + 1}${char === void 0 ? "" : `:${char + 1}`}`;
561
765
  lines.push(` ${severity}${loc}: ${strField(d, "message") ?? ""}`);
562
766
  total++;
563
767
  }
@@ -579,7 +783,7 @@ var NATIVE_ADAPTERS = {
579
783
  return {
580
784
  title: path,
581
785
  metadata: {},
582
- output: size !== void 0 ? `Deleted ${path} (${size} bytes).` : `Deleted ${path}.`
786
+ output: size === void 0 ? `Deleted ${path}.` : `Deleted ${path} (${size} bytes).`
583
787
  };
584
788
  }
585
789
  }
@@ -668,7 +872,8 @@ function blockToolInputPartialParts(id, name, input, state) {
668
872
  dynamic: true
669
873
  });
670
874
  const delta = prev && serialized.startsWith(prev.serialized) ? serialized.slice(prev.serialized.length) : serialized;
671
- if (delta) parts.push({ type: "tool-input-delta", id, delta });
875
+ if (delta)
876
+ parts.push({ type: "tool-input-delta", id, delta });
672
877
  return parts;
673
878
  }
674
879
  function blockToolCallParts(id, name, input, state) {
@@ -796,6 +1001,7 @@ function cursorEventsToStream(events, toolDisplay = "blocks", ctx = {}) {
796
1001
  let thinkingMs = 0;
797
1002
  let compactions = 0;
798
1003
  const toolState = newBlockToolState();
1004
+ const subagentSinks = /* @__PURE__ */ new Map();
799
1005
  const closeDanglingToolCalls = () => {
800
1006
  for (const part of blockDanglingParts(toolState)) {
801
1007
  controller.enqueue(part);
@@ -898,6 +1104,16 @@ function cursorEventsToStream(events, toolDisplay = "blocks", ctx = {}) {
898
1104
  toolState.dropped.add(event.id);
899
1105
  break;
900
1106
  }
1107
+ if (toolDisplay === "blocks" && event.name === TASK_TOOL_NAME && !subagentSinks.has(event.id) && ctx.sessionID) {
1108
+ const live = await linkSubagentSessionLive({
1109
+ parentSessionID: ctx.sessionID,
1110
+ args: event.input
1111
+ });
1112
+ if (live) {
1113
+ subagentSinks.set(event.id, new SubagentTranscriptSink(live));
1114
+ registerSubagentCall(event.id, live.childId);
1115
+ }
1116
+ }
901
1117
  if (toolDisplay === "blocks") {
902
1118
  if (toolState.partials.delete(event.id)) {
903
1119
  controller.enqueue({
@@ -915,9 +1131,7 @@ function cursorEventsToStream(events, toolDisplay = "blocks", ctx = {}) {
915
1131
  closeText();
916
1132
  closeReasoning();
917
1133
  }
918
- for (const part of parts) {
919
- controller.enqueue(part);
920
- }
1134
+ for (const part of parts) controller.enqueue(part);
921
1135
  } else {
922
1136
  reasoningLine(`
923
1137
  ${formatToolCall(event.name, event.input)}
@@ -939,13 +1153,25 @@ ${formatToolCall(event.name, event.input)}
939
1153
  closeText();
940
1154
  closeReasoning();
941
1155
  }
942
- if (event.name === TASK_TOOL_NAME && !event.isError && ctx.sessionID) {
943
- const childId = await linkSubagentSession({
944
- parentSessionID: ctx.sessionID,
945
- args: taskArgs,
946
- result: event.result
947
- });
948
- if (childId) injectSubagentSessionId(parts, childId);
1156
+ if (event.name === TASK_TOOL_NAME && !event.isError) {
1157
+ const sink = subagentSinks.get(event.id);
1158
+ if (sink) {
1159
+ const value = isRecord(event.result) && event.result["status"] === "success" ? event.result["value"] : void 0;
1160
+ await sink.finalize(value, activityLine(value));
1161
+ injectSubagentSessionId(parts, sink.childId);
1162
+ subagentSinks.delete(event.id);
1163
+ unregisterSubagentCall(event.id);
1164
+ } else if (ctx.sessionID) {
1165
+ const childId = await linkSubagentSession({
1166
+ parentSessionID: ctx.sessionID,
1167
+ args: taskArgs,
1168
+ result: event.result
1169
+ });
1170
+ if (childId) injectSubagentSessionId(parts, childId);
1171
+ }
1172
+ } else if (event.name === TASK_TOOL_NAME) {
1173
+ subagentSinks.delete(event.id);
1174
+ unregisterSubagentCall(event.id);
949
1175
  }
950
1176
  for (const part of parts) {
951
1177
  controller.enqueue(part);
@@ -955,13 +1181,17 @@ ${formatToolCall(event.name, event.input)}
955
1181
  `);
956
1182
  }
957
1183
  break;
1184
+ case "subagent-event": {
1185
+ const sink = subagentSinks.get(event.callId);
1186
+ if (sink) sink.push(event.event);
1187
+ break;
1188
+ }
958
1189
  case "usage":
959
1190
  usage = mapUsage(event.usage);
960
1191
  break;
961
1192
  case "reasoning-complete":
962
1193
  closeReasoning();
963
- if (typeof event.durationMs === "number")
964
- thinkingMs += event.durationMs;
1194
+ if (typeof event.durationMs === "number") thinkingMs += event.durationMs;
965
1195
  break;
966
1196
  case "compaction":
967
1197
  compactions++;
@@ -1078,6 +1308,8 @@ ${formatToolCall(event.name, event.input)}
1078
1308
  break;
1079
1309
  case "compaction":
1080
1310
  break;
1311
+ case "subagent-event":
1312
+ break;
1081
1313
  case "finish":
1082
1314
  if (!text && event.text) text = event.text;
1083
1315
  break;