@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.
package/CHANGELOG.md CHANGED
@@ -4,11 +4,75 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
- ## [0.7.1-next.2] — 2026-08-04 (pre-release)
8
-
9
- Pre-release of the auto-compaction suppression (#92). Not yet on `latest`; install with
10
- `npm install @stablekernel/opencode-cursor@next` to test.
7
+ ## [0.8.0] — 2026-08-21
8
+
9
+ Live Cursor subagent activity: the `task` card behaves like a native opencode
10
+ subagent card navigable while running, with a live activity subtitle — and
11
+ the child session carries the subagent's full transcript (#99).
12
+
13
+ - **Cursor subagent transcripts in the TUI subagent view.** The child session
14
+ created for a Cursor subagent (`task` tool) is now seeded with the subagent's
15
+ own activity — its assistant text, thinking, and tool calls with args and
16
+ results — rendered from Cursor's `conversationSteps`, plus the final answer
17
+ and duration. Previously only a post-completion activity summary appeared.
18
+ Steps arrive as raw protobuf JSON, where `agent.v1.ConversationStep`'s `message`
19
+ oneof serialises to a single camelCase key (`{ assistantMessage: … }`,
20
+ `{ toolCall: { shellToolCall: … } }`) rather than the `{ type, message }` shape
21
+ of the SDK's public type; both are accepted. Transcript content is never
22
+ truncated — the child session carries the subagent's full output.
23
+ - **Live activity on the Cursor subagent card.** The SDK streams a local
24
+ subagent's nested activity via `taskUpdate` payloads on the parent task's
25
+ `tool-call-delta` updates (text, thinking, tool-start/tool-result with
26
+ id + name + input). Those events now write real `tool` parts into the child
27
+ session via `part.update` (an upsert — `session/processor.ts` creates parts
28
+ the same way), so the `task` card shows a live `↳ <Tool> <title>` subtitle
29
+ while the subagent runs (the TUI builds that line purely from `tool` parts
30
+ in the child session — `tui/routes/session/index.tsx:2227-2279`).
31
+ The child session is created up-front when the `task` call starts and the
32
+ task card's `state.metadata.sessionId` is stamped while the subagent is still
33
+ running (via opencode's `part.update` endpoint, mirroring the native task
34
+ tool's execute-time metadata publication), so the card is clickable /
35
+ `ctrl+x`-navigable live. Tool calls complete when their tool-result event
36
+ arrives; any call left open is completed at finalize.
37
+ `cursor_delegate` also creates a child session seeded with its transcript,
38
+ discoverable via the TUI's subagent panel.
39
+
40
+ ## [0.7.1] — 2026-08-05
41
+
42
+ The skills bridge (#90), per-model context limits and pricing (#89), and the
43
+ compaction fixes (#91, #92).
11
44
 
45
+ - **Skills bridge: opencode skills are now mirrored into `.cursor/skills/` for
46
+ the Cursor agent.** Both project-scoped and global skills are discovered
47
+ (matching opencode's resolution order: `.opencode/skills/`, `.claude/skills/`,
48
+ `.agents/skills/`, walked up to the git worktree root, plus global
49
+ `~/.config/opencode/skills/` etc.), filtered through opencode's `permission`
50
+ config, and materialised as a git-ignored mirror with a `generated:
51
+ opencode-cursor` sentinel. An `<available_skills>` catalogue is appended to
52
+ the generated system rule so the Cursor agent can discover and load skills on
53
+ demand. Works for the primary agent, Cursor sub-agents, and `cursor_delegate`
54
+ (which passes `settingSources: ["project"]`). `ask`-permissioned skills are
55
+ withheld (the ask prompt can't cross the Cursor boundary). Opt out with
56
+ `forwardSkills: false`; manual override with `skills: { include, exclude }`.
57
+ User-owned `.cursor/skills/<id>/` directories are never overwritten.
58
+ `config.skills.paths` directories are also scanned (lowest priority,
59
+ first-wins on duplicate ids). Symlinked skill directories and symlinked
60
+ supporting files are followed (broken links and symlink loops are skipped).
61
+ Mirror diagnostics (withheld skills, oversized files, write failures) route
62
+ through opencode's structured plugin logging rather than the terminal,
63
+ matching 0.7.0's logging change. `config.skills.urls` (HTTP catalogs) and
64
+ skills bundled inside opencode plugin packages are not yet supported.
65
+ Skills bridge contributed by Wayne Simpson (@WayneSimpson).
66
+ - **Per-model context limits and pricing generated from Cursor's docs.** Each
67
+ resolved model now carries a context-window limit and per-token pricing,
68
+ emitted on the config channel opencode reads so the TUI can show cost and
69
+ token counts. High-output frontier models carry a separate output-token
70
+ limit. The data ships as a generated `src/model-limits.ts`, produced from
71
+ Cursor's published tables by `npm run sync:model-limits`; a weekly CI job
72
+ re-checks the committed data against those tables and fails if it has fallen
73
+ behind (it verifies only — regenerating is a manual step).
74
+ - **Dependency bumps:** `@ai-sdk/provider` 3.0.13 → 3.0.14, `@types/node`
75
+ 26.0.0 → 26.1.2, `vitest` 4.1.9 → 4.1.10 (dev-dependencies group, #88).
12
76
  - **opencode's threshold-triggered auto-compaction is now suppressed for Cursor models by
13
77
  default.** The Cursor agent runtime already self-compacts on its own context threshold
14
78
  (`preCompact` hook with `trigger: "auto"`), so opencode-driven compaction was redundant —
@@ -31,12 +95,6 @@ Pre-release of the auto-compaction suppression (#92). Not yet on `latest`; insta
31
95
  (new session, expired agent, changed MCP set) resends everything; if that overflows the
32
96
  model the turn fails and `/compact` is the manual recovery. Opt back out with
33
97
  `provider.cursor.options.autoCompaction: true`.
34
-
35
- ## [0.7.1-next.1] — 2026-08-03 (pre-release)
36
-
37
- Pre-release of the compaction fix (#91). Not yet on `latest`; install with
38
- `npm install @stablekernel/opencode-cursor@next` to test.
39
-
40
98
  - **Fixed: auto-compaction (and manual `/compact`) failed with `Tool call not
41
99
  allowed while generating summary` whenever the Cursor agent used a tool while
42
100
  summarizing.** opencode declares zero tools on a compaction/summary turn, but
@@ -50,44 +108,6 @@ Pre-release of the compaction fix (#91). Not yet on `latest`; install with
50
108
  pre-0.7.1 opencode saw `limit.context: 0` for every Cursor model, and a zero
51
109
  context limit structurally disables the auto-compaction trigger.
52
110
 
53
- ## [0.7.1-next.0] — 2026-08-03 (pre-release)
54
-
55
- Pre-release of the skills bridge (#90) and per-model context limits + pricing
56
- (#89). Not yet on `latest`; install with
57
- `npm install @stablekernel/opencode-cursor@next` to test.
58
-
59
- - **Skills bridge: opencode skills are now mirrored into `.cursor/skills/` for
60
- the Cursor agent.** Both project-scoped and global skills are discovered
61
- (matching opencode's resolution order: `.opencode/skills/`, `.claude/skills/`,
62
- `.agents/skills/`, walked up to the git worktree root, plus global
63
- `~/.config/opencode/skills/` etc.), filtered through opencode's `permission`
64
- config, and materialised as a git-ignored mirror with a `generated:
65
- opencode-cursor` sentinel. An `<available_skills>` catalogue is appended to
66
- the generated system rule so the Cursor agent can discover and load skills on
67
- demand. Works for the primary agent, Cursor sub-agents, and `cursor_delegate`
68
- (which passes `settingSources: ["project"]`). `ask`-permissioned skills are
69
- withheld (the ask prompt can't cross the Cursor boundary). Opt out with
70
- `forwardSkills: false`; manual override with `skills: { include, exclude }`.
71
- User-owned `.cursor/skills/<id>/` directories are never overwritten.
72
- `config.skills.paths` directories are also scanned (lowest priority,
73
- first-wins on duplicate ids). Symlinked skill directories and symlinked
74
- supporting files are followed (broken links and symlink loops are skipped).
75
- Mirror diagnostics (withheld skills, oversized files, write failures) route
76
- through opencode's structured plugin logging rather than the terminal,
77
- matching 0.7.0's logging change. `config.skills.urls` (HTTP catalogs) and
78
- skills bundled inside opencode plugin packages are not yet supported.
79
- Skills bridge contributed by Wayne Simpson (@WayneSimpson).
80
- - **Per-model context limits and pricing generated from Cursor's docs.** Each
81
- resolved model now carries a context-window limit and per-token pricing,
82
- emitted on the config channel opencode reads so the TUI can show cost and
83
- token counts. High-output frontier models carry a separate output-token
84
- limit. The data ships as a generated `src/model-limits.ts`, produced from
85
- Cursor's published tables by `npm run sync:model-limits`; a weekly CI job
86
- re-checks the committed data against those tables and fails if it has fallen
87
- behind (it verifies only — regenerating is a manual step).
88
- - **Dependency bumps:** `@ai-sdk/provider` 3.0.13 → 3.0.14, `@types/node`
89
- 26.0.0 → 26.1.2, `vitest` 4.1.9 → 4.1.10 (dev-dependencies group, #88).
90
-
91
111
  ## [0.7.0] — 2026-07-30
92
112
 
93
113
  Structured logging (#85), the stream-watchdog tool-phase budget (#86), and the
@@ -542,6 +542,88 @@ function resolveSystemDelivery(options) {
542
542
  return { mode, settingSources: settingSources ?? ["project"] };
543
543
  }
544
544
 
545
+ // src/provider/child-parts.ts
546
+ import { randomBytes } from "crypto";
547
+ var BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
548
+ var RANDOM_LENGTH = 14;
549
+ var lastTimestamp = 0;
550
+ var counter = 0;
551
+ function createPartID(now) {
552
+ const timestamp = now ?? Date.now();
553
+ if (timestamp !== lastTimestamp) {
554
+ lastTimestamp = timestamp;
555
+ counter = 0;
556
+ }
557
+ counter++;
558
+ const value = BigInt(timestamp) * BigInt(4096) + BigInt(counter);
559
+ const bytes = Buffer.alloc(6);
560
+ for (let i = 0; i < 6; i++) {
561
+ bytes[i] = Number(value >> BigInt(40 - 8 * i) & BigInt(255));
562
+ }
563
+ let random = "";
564
+ const raw = randomBytes(RANDOM_LENGTH);
565
+ for (let i = 0; i < RANDOM_LENGTH; i++) random += BASE62[raw[i] % 62];
566
+ return `prt_${bytes.toString("hex")}${random}`;
567
+ }
568
+ var PART_URL = "/session/{sessionID}/message/{messageID}/part/{partID}";
569
+ async function upsertToolPart(opts) {
570
+ const bridge = getSubagentBridge();
571
+ const request = bridge?.client?._client?.request;
572
+ if (!bridge || !request) {
573
+ pluginLog("debug", "subagent: tool part skipped", {
574
+ reason: "no bridge",
575
+ tool: opts.tool
576
+ });
577
+ return false;
578
+ }
579
+ const state = {
580
+ status: opts.status,
581
+ input: opts.input ?? {},
582
+ time: opts.status === "completed" ? { start: opts.start, end: opts.end ?? Date.now() } : { start: opts.start }
583
+ };
584
+ if (opts.title) state["title"] = opts.title;
585
+ if (opts.status === "completed") {
586
+ state["output"] = opts.output ?? "";
587
+ state["metadata"] = {};
588
+ }
589
+ try {
590
+ const res = await request({
591
+ method: "PATCH",
592
+ url: PART_URL,
593
+ path: {
594
+ sessionID: opts.sessionID,
595
+ messageID: opts.messageID,
596
+ partID: opts.partID
597
+ },
598
+ ...bridge.directory ? { query: { directory: bridge.directory } } : {},
599
+ body: {
600
+ id: opts.partID,
601
+ messageID: opts.messageID,
602
+ sessionID: opts.sessionID,
603
+ type: "tool",
604
+ callID: opts.callID,
605
+ tool: opts.tool,
606
+ state
607
+ }
608
+ });
609
+ if (typeof res === "object" && res !== null && "error" in res && res.error != null) {
610
+ pluginLog("warn", "subagent: tool part upsert rejected", {
611
+ tool: opts.tool,
612
+ status: opts.status,
613
+ error: JSON.stringify(res.error).slice(0, 300)
614
+ });
615
+ return false;
616
+ }
617
+ return true;
618
+ } catch (err) {
619
+ pluginLog("warn", "subagent: tool part upsert failed", {
620
+ tool: opts.tool,
621
+ error: err instanceof Error ? err.message : String(err)
622
+ });
623
+ return false;
624
+ }
625
+ }
626
+
545
627
  // src/provider/subagent-bridge.ts
546
628
  var BRIDGE_KEY2 = /* @__PURE__ */ Symbol.for("@stablekernel/opencode-cursor:subagent-bridge");
547
629
  function setSubagentBridge(bridge) {
@@ -553,6 +635,80 @@ function clearSubagentBridge() {
553
635
  function getSubagentBridge() {
554
636
  return globalThis[BRIDGE_KEY2];
555
637
  }
638
+ var CALL_REGISTRY_KEY = /* @__PURE__ */ Symbol.for(
639
+ "@stablekernel/opencode-cursor:subagent-calls"
640
+ );
641
+ function callRegistry() {
642
+ const holder = globalThis;
643
+ if (!holder[CALL_REGISTRY_KEY]) holder[CALL_REGISTRY_KEY] = /* @__PURE__ */ new Map();
644
+ return holder[CALL_REGISTRY_KEY];
645
+ }
646
+ function registerSubagentCall(callId, childId) {
647
+ callRegistry().set(callId, childId);
648
+ }
649
+ function unregisterSubagentCall(callId) {
650
+ callRegistry().delete(callId);
651
+ }
652
+ function subagentCallChildId(callId) {
653
+ return callRegistry().get(callId);
654
+ }
655
+ async function stampTaskPartSessionId(opts) {
656
+ const bridge = getSubagentBridge();
657
+ if (!bridge) return skipStamp("no bridge", opts.childId);
658
+ if (!isRecord(opts.part)) return skipStamp("part not a record", opts.childId);
659
+ const state = isRecord(opts.part["state"]) ? opts.part["state"] : void 0;
660
+ if (!state || state["status"] !== "running") {
661
+ return skipStamp(`event state ${String(state?.["status"])}`, opts.childId);
662
+ }
663
+ const metadata = isRecord(state["metadata"]) ? { ...state["metadata"] } : {};
664
+ if (metadata["sessionId"] === opts.childId) return;
665
+ metadata["sessionId"] = opts.childId;
666
+ const rawClient = bridge.client._client;
667
+ if (!rawClient?.request)
668
+ return skipStamp("client has no request()", opts.childId);
669
+ try {
670
+ const msgRes = await bridge.client.session.message({
671
+ path: { id: opts.sessionID, messageID: opts.messageID },
672
+ ...bridge.directory ? { query: { directory: bridge.directory } } : {}
673
+ });
674
+ const current = msgRes?.data?.parts?.find((p) => isRecord(p) && p["id"] === opts.partID);
675
+ if (!isRecord(current))
676
+ return skipStamp("part not found on re-read", opts.childId);
677
+ const currentState = isRecord(current["state"]) ? current["state"] : void 0;
678
+ if (!currentState || currentState["status"] !== "running") {
679
+ return skipStamp(
680
+ `re-read state ${String(currentState?.["status"])}`,
681
+ opts.childId
682
+ );
683
+ }
684
+ const currentMetadata = isRecord(currentState["metadata"]) ? { ...currentState["metadata"] } : {};
685
+ currentMetadata["sessionId"] = opts.childId;
686
+ await rawClient.request({
687
+ method: "PATCH",
688
+ url: "/session/{sessionID}/message/{messageID}/part/{partID}",
689
+ path: {
690
+ sessionID: opts.sessionID,
691
+ messageID: opts.messageID,
692
+ partID: opts.partID
693
+ },
694
+ ...bridge.directory ? { query: { directory: bridge.directory } } : {},
695
+ body: { ...current, state: { ...currentState, metadata: currentMetadata } }
696
+ });
697
+ pluginLog("debug", "subagent: stamped task part", {
698
+ childId: opts.childId,
699
+ partID: opts.partID
700
+ });
701
+ } catch (err) {
702
+ pluginLog("warn", "subagent: task part stamp failed", {
703
+ childId: opts.childId,
704
+ partID: opts.partID,
705
+ error: err instanceof Error ? err.message : String(err)
706
+ });
707
+ }
708
+ }
709
+ function skipStamp(reason, childId) {
710
+ pluginLog("debug", "subagent: task part stamp skipped", { reason, childId });
711
+ }
556
712
  function isRecord(v) {
557
713
  return typeof v === "object" && v !== null;
558
714
  }
@@ -574,7 +730,8 @@ function activityLine(value) {
574
730
  const steps = isRecord(value) && Array.isArray(value["conversationSteps"]) ? value["conversationSteps"].length : void 0;
575
731
  const bits = [];
576
732
  if (steps && steps > 0) bits.push(`${steps} step${steps === 1 ? "" : "s"}`);
577
- if (typeof durationMs === "number") bits.push(`in ${formatDuration(durationMs)}`);
733
+ if (typeof durationMs === "number")
734
+ bits.push(`in ${formatDuration(durationMs)}`);
578
735
  return bits.length > 0 ? `_Subagent ran ${bits.join(" ")}._` : void 0;
579
736
  }
580
737
  var UNSPECIFIED_KIND = "unspecified";
@@ -586,18 +743,122 @@ function subagentLabel(args) {
586
743
  if (kind && kind !== UNSPECIFIED_KIND) return kind;
587
744
  return "general";
588
745
  }
746
+ function renderStep(step) {
747
+ if (!isRecord(step)) return void 0;
748
+ const norm = normalizeStep(step);
749
+ if (!norm) return dumpStep(step);
750
+ switch (norm.kind) {
751
+ case "assistantMessage": {
752
+ const text = strField(norm.payload, "text");
753
+ return text ? text : void 0;
754
+ }
755
+ case "thinkingMessage": {
756
+ const text = strField(norm.payload, "text");
757
+ return text ? `> ${text}` : void 0;
758
+ }
759
+ case "toolCall": {
760
+ const { name, args, result } = toolCallInfo(norm.payload);
761
+ let arg = "";
762
+ try {
763
+ const s = typeof args === "string" ? args : JSON.stringify(args);
764
+ if (s && s !== "{}" && s !== '""') arg = ` ${s}`;
765
+ } catch {
766
+ }
767
+ const head = `**\`${name}\`**${arg}`;
768
+ const out = resultText(result);
769
+ return out ? `${head}
770
+
771
+ \`\`\`
772
+ ${out}
773
+ \`\`\`` : head;
774
+ }
775
+ default:
776
+ return dumpStep(step);
777
+ }
778
+ }
779
+ function dumpStep(step) {
780
+ try {
781
+ const s = JSON.stringify(step);
782
+ return s && s !== "{}" ? `\`\`\`json
783
+ ${s}
784
+ \`\`\`` : void 0;
785
+ } catch {
786
+ return void 0;
787
+ }
788
+ }
789
+ var STEP_KINDS = ["assistantMessage", "toolCall", "thinkingMessage"];
790
+ function normalizeStep(step) {
791
+ const selected = oneofMember(step["message"]);
792
+ if (selected) return { kind: selected.kind, payload: selected.value };
793
+ const type = strField(step, "type");
794
+ if (type) return { kind: type, payload: step["message"] };
795
+ for (const kind of STEP_KINDS) {
796
+ if (kind in step) return { kind, payload: step[kind] };
797
+ }
798
+ return void 0;
799
+ }
800
+ function oneofMember(container) {
801
+ if (!isRecord(container)) return void 0;
802
+ const kind = strField(container, "case");
803
+ return kind ? { kind, value: container["value"] } : void 0;
804
+ }
805
+ var TOOL_CALL_SUFFIX = "ToolCall";
806
+ function toolCallInfo(payload) {
807
+ const rec = isRecord(payload) ? payload : void 0;
808
+ const selected = oneofMember(rec?.["tool"]);
809
+ if (selected)
810
+ return { ...toolName(selected.kind), ...toolFields(selected.value) };
811
+ const type = strField(rec, "type");
812
+ if (type) return { name: type, args: rec?.["args"], result: rec?.["result"] };
813
+ for (const [key, value] of Object.entries(rec ?? {})) {
814
+ if (!key.endsWith(TOOL_CALL_SUFFIX)) continue;
815
+ return { ...toolName(key), ...toolFields(value) };
816
+ }
817
+ return { name: "tool", args: void 0, result: void 0 };
818
+ }
819
+ function toolName(key) {
820
+ return {
821
+ name: key.endsWith(TOOL_CALL_SUFFIX) ? key.slice(0, -TOOL_CALL_SUFFIX.length) : key
822
+ };
823
+ }
824
+ function toolFields(value) {
825
+ const rec = isRecord(value) ? value : void 0;
826
+ return { args: rec?.["args"], result: rec?.["result"] };
827
+ }
828
+ function resultText(result) {
829
+ if (typeof result === "string") return result;
830
+ if (!isRecord(result)) return "";
831
+ if (typeof result["stdout"] === "string" && result["stdout"])
832
+ return result["stdout"];
833
+ if (typeof result["content"] === "string" && result["content"])
834
+ return result["content"];
835
+ if (result["status"] === "success" && typeof result["value"] === "string")
836
+ return result["value"];
837
+ const value = result["value"];
838
+ if (isRecord(value)) {
839
+ if (typeof value["stdout"] === "string") return value["stdout"];
840
+ if (typeof value["fileContentAfterWrite"] === "string")
841
+ return value["fileContentAfterWrite"];
842
+ }
843
+ try {
844
+ const s = JSON.stringify(result);
845
+ return s && s !== "{}" ? s : "";
846
+ } catch {
847
+ return "";
848
+ }
849
+ }
850
+ function renderConversationSteps(value) {
851
+ if (!isRecord(value) || !Array.isArray(value["conversationSteps"]))
852
+ return void 0;
853
+ const rendered = value["conversationSteps"].map(renderStep).filter((s) => Boolean(s));
854
+ return rendered.length > 0 ? rendered.join("\n\n") : void 0;
855
+ }
589
856
  function buildTranscript(value) {
590
857
  const parts = [];
591
858
  const suffix = strField(value, "resultSuffix");
592
859
  if (suffix) parts.push(suffix);
593
- if (isRecord(value) && Array.isArray(value["conversationSteps"])) {
594
- const steps = value["conversationSteps"];
595
- const rendered = steps.flatMap((s) => {
596
- const text = strField(s, "text") ?? strField(s, "content");
597
- return text ? [text] : [];
598
- }).join("\n\n");
599
- if (rendered) parts.push(rendered);
600
- }
860
+ const steps = renderConversationSteps(value);
861
+ if (steps) parts.push(steps);
601
862
  const activity = activityLine(value);
602
863
  if (activity) parts.push(activity);
603
864
  const body = parts.join("\n\n").trim();
@@ -642,6 +903,111 @@ async function linkSubagentSession(opts) {
642
903
  return void 0;
643
904
  }
644
905
  }
906
+ async function linkSubagentSessionLive(opts) {
907
+ const bridge = getSubagentBridge();
908
+ if (!bridge) return void 0;
909
+ const { client, directory } = bridge;
910
+ const query = directory ? { directory } : void 0;
911
+ try {
912
+ const description = strField(opts.args, "description") ?? "Subagent task";
913
+ const agent = subagentLabel(opts.args);
914
+ const created = await client.session.create({
915
+ body: {
916
+ parentID: opts.parentSessionID,
917
+ title: `${description} (@${agent} subagent)`
918
+ },
919
+ ...query ? { query } : {}
920
+ });
921
+ const childId = created?.data?.id;
922
+ if (!childId) return void 0;
923
+ let messageID;
924
+ const prompt = strField(opts.args, "prompt");
925
+ if (prompt) {
926
+ const seeded = await client.session.prompt({
927
+ path: { id: childId },
928
+ ...query ? { query } : {},
929
+ body: { noReply: true, parts: [{ type: "text", text: prompt }] }
930
+ });
931
+ messageID = strField(
932
+ seeded?.data?.info,
933
+ "id"
934
+ );
935
+ }
936
+ let done = false;
937
+ let chain = Promise.resolve();
938
+ const post = (text) => {
939
+ chain = chain.then(
940
+ () => client.session.prompt({
941
+ path: { id: childId },
942
+ ...query ? { query } : {},
943
+ body: { noReply: true, parts: [{ type: "text", text }] }
944
+ }).then(() => void 0).catch(() => void 0)
945
+ );
946
+ return chain;
947
+ };
948
+ return {
949
+ childId,
950
+ messageID,
951
+ flush: (markdown) => done ? Promise.resolve() : post(markdown),
952
+ toolPart: async (part) => {
953
+ if (done || !messageID) return void 0;
954
+ const partID = part.partID ?? createPartID();
955
+ const written = await upsertToolPart({
956
+ sessionID: childId,
957
+ messageID,
958
+ partID,
959
+ callID: part.callID,
960
+ tool: part.tool,
961
+ status: part.status,
962
+ title: part.title,
963
+ input: part.input,
964
+ output: part.output,
965
+ start: part.start,
966
+ end: part.end
967
+ });
968
+ return written ? partID : void 0;
969
+ },
970
+ finalize: async (activity) => {
971
+ if (done) return;
972
+ done = true;
973
+ if (activity) await post(activity);
974
+ }
975
+ };
976
+ } catch {
977
+ return void 0;
978
+ }
979
+ }
980
+ async function linkDelegateSession(opts) {
981
+ const bridge = getSubagentBridge();
982
+ if (!bridge) return void 0;
983
+ const { client, directory } = bridge;
984
+ const query = directory ? { directory } : void 0;
985
+ try {
986
+ const created = await client.session.create({
987
+ body: { parentID: opts.parentSessionID, title: opts.title },
988
+ ...query ? { query } : {}
989
+ });
990
+ const childId = created?.data?.id;
991
+ if (!childId) return void 0;
992
+ if (opts.prompt) {
993
+ await client.session.prompt({
994
+ path: { id: childId },
995
+ ...query ? { query } : {},
996
+ body: { noReply: true, parts: [{ type: "text", text: opts.prompt }] }
997
+ });
998
+ }
999
+ if (opts.transcript) {
1000
+ await client.session.prompt({
1001
+ path: { id: childId },
1002
+ ...query ? { query } : {},
1003
+ body: { noReply: true, parts: [{ type: "text", text: opts.transcript }] }
1004
+ });
1005
+ }
1006
+ return childId;
1007
+ } catch {
1008
+ return void 0;
1009
+ }
1010
+ }
645
1011
 
646
1012
  // src/provider/error-classify.ts
647
1013
  function classifyError(err) {
@@ -677,6 +1043,9 @@ function classifyError(err) {
677
1043
  }
678
1044
 
679
1045
  // src/provider/agent-events.ts
1046
+ function isRecord2(v) {
1047
+ return typeof v === "object" && v !== null;
1048
+ }
680
1049
  function addUsage(a, b) {
681
1050
  if (!a) return b;
682
1051
  if (!b) return a;
@@ -697,6 +1066,41 @@ function toolDisplayName(toolCall) {
697
1066
  }
698
1067
  return toolCall.type ?? "tool";
699
1068
  }
1069
+ function normalizeNestedTaskUpdate(update) {
1070
+ if (!isRecord2(update)) return void 0;
1071
+ switch (update["type"]) {
1072
+ case "text-delta":
1073
+ return typeof update["text"] === "string" ? { type: "text", text: update["text"] } : void 0;
1074
+ case "thinking-delta":
1075
+ return typeof update["text"] === "string" ? { type: "reasoning", text: update["text"] } : void 0;
1076
+ case "tool-call-started": {
1077
+ const toolCall = isRecord2(update["toolCall"]) ? update["toolCall"] : void 0;
1078
+ const id = typeof update["callId"] === "string" ? update["callId"] : "";
1079
+ return {
1080
+ type: "tool-start",
1081
+ id,
1082
+ name: toolDisplayName(toolCall),
1083
+ input: toolCall?.args ?? {}
1084
+ };
1085
+ }
1086
+ case "tool-call-completed": {
1087
+ const toolCall = isRecord2(update["toolCall"]) ? update["toolCall"] : void 0;
1088
+ const id = typeof update["callId"] === "string" ? update["callId"] : "";
1089
+ const result = toolCall?.result;
1090
+ const resultValue = isRecord2(result) ? result["value"] : void 0;
1091
+ const mcpError = toolCall?.type === "mcp" && isRecord2(resultValue) && resultValue["isError"] === true;
1092
+ return {
1093
+ type: "tool-result",
1094
+ id,
1095
+ name: toolDisplayName(toolCall),
1096
+ result: result ?? null,
1097
+ isError: isRecord2(result) && result["status"] === "error" || mcpError
1098
+ };
1099
+ }
1100
+ default:
1101
+ return void 0;
1102
+ }
1103
+ }
700
1104
  var MAX_TIMEOUT_MS = 2147483647;
701
1105
  function envMs(name, fallback) {
702
1106
  const raw = process.env[name];
@@ -787,6 +1191,17 @@ async function* streamAgentTurn(agent, message, options) {
787
1191
  });
788
1192
  break;
789
1193
  }
1194
+ case "tool-call-delta": {
1195
+ const nested = normalizeNestedTaskUpdate(update.taskUpdate);
1196
+ if (nested) {
1197
+ push({
1198
+ type: "subagent-event",
1199
+ callId: String(update.callId),
1200
+ event: nested
1201
+ });
1202
+ }
1203
+ break;
1204
+ }
790
1205
  case "turn-ended":
791
1206
  openTools.clear();
792
1207
  if (update.usage) {
@@ -977,7 +1392,7 @@ function buildModelSelection(modelId, params) {
977
1392
  const paramList = Object.entries(params ?? {}).map(([id, value]) => ({ id, value }));
978
1393
  return paramList.length > 0 ? { id: modelId, params: paramList } : { id: modelId };
979
1394
  }
980
- function isRecord2(value) {
1395
+ function isRecord3(value) {
981
1396
  return typeof value === "object" && value !== null && !Array.isArray(value);
982
1397
  }
983
1398
  function isMode(value) {
@@ -990,7 +1405,7 @@ function resolveControls(modelId, staticControls, providerOptions) {
990
1405
  ...staticControls.defaults ?? {},
991
1406
  ...staticControls.params ?? {}
992
1407
  };
993
- if (isRecord2(po["params"])) {
1408
+ if (isRecord3(po["params"])) {
994
1409
  for (const [key, value] of Object.entries(po["params"])) {
995
1410
  if (value != null) params[key] = String(value);
996
1411
  }
@@ -1145,7 +1560,16 @@ export {
1145
1560
  sendAgentTurnSilently,
1146
1561
  setSubagentBridge,
1147
1562
  clearSubagentBridge,
1563
+ registerSubagentCall,
1564
+ unregisterSubagentCall,
1565
+ subagentCallChildId,
1566
+ stampTaskPartSessionId,
1567
+ activityLine,
1568
+ resultText,
1569
+ renderConversationSteps,
1148
1570
  linkSubagentSession,
1571
+ linkSubagentSessionLive,
1572
+ linkDelegateSession,
1149
1573
  buildModelSelection,
1150
1574
  resolveControls,
1151
1575
  getSessionRecord,
@@ -1153,4 +1577,4 @@ export {
1153
1577
  withSessionLock,
1154
1578
  acquireAgent
1155
1579
  };
1156
- //# sourceMappingURL=chunk-RDY3H2LE.js.map
1580
+ //# sourceMappingURL=chunk-YIEC27VB.js.map