@tryarcanist/cli 0.1.13 → 0.1.14

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.
Files changed (2) hide show
  1. package/dist/index.js +129 -17
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -427,6 +427,7 @@ async function messageCommand(sessionId, promptArg, options = {}, command) {
427
427
 
428
428
  // ../../shared/transcript/projector.ts
429
429
  var DUPLICATE_TEXT_DELTA_MIN_CHARS = 24;
430
+ var SUBAGENT_EVENT_TYPES = /* @__PURE__ */ new Set(["subagent_start", "subagent_complete", "subagent_tool_call", "subagent_text"]);
430
431
  var RAW_OPENCODE_NOISE = /* @__PURE__ */ new Set([
431
432
  "session.updated",
432
433
  "session.diff",
@@ -440,6 +441,12 @@ function shouldAppendTextDelta(existingText, incomingText) {
440
441
  if (incomingText.length < DUPLICATE_TEXT_DELTA_MIN_CHARS) return true;
441
442
  return !existingText.endsWith(incomingText);
442
443
  }
444
+ function streamableKey(type, streamId) {
445
+ return `${type}:${streamId}`;
446
+ }
447
+ function resolveSegmentId(streamId, segmentOrdinal) {
448
+ return segmentOrdinal === 0 ? streamId : `${streamId}#${segmentOrdinal}`;
449
+ }
443
450
  function isRecord(value) {
444
451
  return !!value && typeof value === "object" && !Array.isArray(value);
445
452
  }
@@ -474,6 +481,8 @@ function normalizeToolStatus(value) {
474
481
  function flattenSessionEvents(raw) {
475
482
  const merged = [];
476
483
  const streamableIndexById = /* @__PURE__ */ new Map();
484
+ const segmentOrdinalByStreamId = /* @__PURE__ */ new Map();
485
+ const concatByStreamId = /* @__PURE__ */ new Map();
477
486
  const toolCallIndexById = /* @__PURE__ */ new Map();
478
487
  const questionIndexById = /* @__PURE__ */ new Map();
479
488
  for (const event of raw) {
@@ -526,7 +535,11 @@ function flattenSessionEvents(raw) {
526
535
  message: typeof data?.message === "string" ? data.message : "Retrying...",
527
536
  ...typeof data?.nextRetryAt === "string" ? { nextRetryAt: data.nextRetryAt } : {},
528
537
  ...typeof data?.provider === "string" ? { provider: data.provider } : {},
529
- ...typeof data?.errorCode === "string" ? { errorCode: data.errorCode } : {}
538
+ ...typeof data?.errorCode === "string" ? { errorCode: data.errorCode } : {},
539
+ ...typeof data?.scope === "string" ? { scope: data.scope } : {},
540
+ ...typeof data?.maxAttempts === "number" ? { maxAttempts: data.maxAttempts } : {},
541
+ ...typeof data?.reason === "string" ? { reason: data.reason } : {},
542
+ ...typeof data?.retryAfterMs === "number" ? { retryAfterMs: data.retryAfterMs } : {}
530
543
  });
531
544
  continue;
532
545
  }
@@ -565,21 +578,30 @@ function flattenSessionEvents(raw) {
565
578
  continue;
566
579
  }
567
580
  if (event.type === "reasoning") {
568
- const id = resolveEventId(data, "reasoning", merged.length);
581
+ const streamId = resolveEventId(data, "reasoning", merged.length);
582
+ const key = streamableKey("reasoning", streamId);
569
583
  const text = resolveTextValue(data);
570
584
  const promptId = resolvePromptId(data);
571
- const existingIdx = streamableIndexById.get(id);
572
- if (existingIdx !== void 0) {
585
+ const existingText = concatByStreamId.get(key) ?? "";
586
+ const existingIdx = streamableIndexById.get(key);
587
+ if (existingIdx !== void 0 && existingIdx === merged.length - 1) {
573
588
  const existing = merged[existingIdx];
574
- if (existing.type === "reasoning" && shouldAppendTextDelta(existing.text, text)) {
589
+ if (existing.type === "reasoning" && shouldAppendTextDelta(existingText, text)) {
575
590
  existing.text += text;
591
+ concatByStreamId.set(key, existingText + text);
576
592
  if (!existing.promptId && promptId) existing.promptId = promptId;
577
593
  }
578
594
  } else {
579
- streamableIndexById.set(id, merged.length);
595
+ if (existingText && !shouldAppendTextDelta(existingText, text)) continue;
596
+ const previousOrdinal = segmentOrdinalByStreamId.get(key);
597
+ const segmentOrdinal = previousOrdinal === void 0 ? 0 : previousOrdinal + 1;
598
+ segmentOrdinalByStreamId.set(key, segmentOrdinal);
599
+ streamableIndexById.set(key, merged.length);
600
+ concatByStreamId.set(key, existingText + text);
580
601
  merged.push({
581
602
  type: "reasoning",
582
- id,
603
+ id: resolveSegmentId(streamId, segmentOrdinal),
604
+ ...segmentOrdinal > 0 ? { streamId } : {},
583
605
  text,
584
606
  ...promptId ? { promptId } : {}
585
607
  });
@@ -629,21 +651,30 @@ function flattenSessionEvents(raw) {
629
651
  continue;
630
652
  }
631
653
  if (event.type === "text") {
632
- const id = resolveEventId(data, "text", merged.length);
654
+ const streamId = resolveEventId(data, "text", merged.length);
655
+ const key = streamableKey("text", streamId);
633
656
  const text = resolveTextValue(data);
634
657
  const promptId = resolvePromptId(data);
635
- const existingIdx = streamableIndexById.get(id);
636
- if (existingIdx !== void 0) {
658
+ const existingText = concatByStreamId.get(key) ?? "";
659
+ const existingIdx = streamableIndexById.get(key);
660
+ if (existingIdx !== void 0 && existingIdx === merged.length - 1) {
637
661
  const existing = merged[existingIdx];
638
- if (existing.type === "text" && shouldAppendTextDelta(existing.text, text)) {
662
+ if (existing.type === "text" && shouldAppendTextDelta(existingText, text)) {
639
663
  existing.text += text;
664
+ concatByStreamId.set(key, existingText + text);
640
665
  if (!existing.promptId && promptId) existing.promptId = promptId;
641
666
  }
642
667
  } else {
643
- streamableIndexById.set(id, merged.length);
668
+ if (existingText && !shouldAppendTextDelta(existingText, text)) continue;
669
+ const previousOrdinal = segmentOrdinalByStreamId.get(key);
670
+ const segmentOrdinal = previousOrdinal === void 0 ? 0 : previousOrdinal + 1;
671
+ segmentOrdinalByStreamId.set(key, segmentOrdinal);
672
+ streamableIndexById.set(key, merged.length);
673
+ concatByStreamId.set(key, existingText + text);
644
674
  merged.push({
645
675
  type: "text",
646
- id,
676
+ id: resolveSegmentId(streamId, segmentOrdinal),
677
+ ...segmentOrdinal > 0 ? { streamId } : {},
647
678
  text,
648
679
  ...promptId ? { promptId } : {}
649
680
  });
@@ -737,6 +768,65 @@ function getEmbeddedTerminalHistory(raw) {
737
768
  function resolveAuthoritativePromptEvents(raw) {
738
769
  return getEmbeddedTerminalHistory(raw) ?? raw;
739
770
  }
771
+ function extractSubagentInfo(raw) {
772
+ const empty = {
773
+ names: /* @__PURE__ */ new Map(),
774
+ activity: /* @__PURE__ */ new Map(),
775
+ toolToChildSessions: /* @__PURE__ */ new Map(),
776
+ subagentUsage: /* @__PURE__ */ new Map()
777
+ };
778
+ if (!raw.some((event) => SUBAGENT_EVENT_TYPES.has(event.type))) return empty;
779
+ const { names, activity, toolToChildSessions, subagentUsage } = empty;
780
+ for (const event of raw) {
781
+ const data = event.data;
782
+ if (!data) continue;
783
+ const childSessionId = typeof data.childSessionId === "string" ? data.childSessionId : void 0;
784
+ const parentToolId = typeof data.parentToolId === "string" ? data.parentToolId : void 0;
785
+ const key = childSessionId || parentToolId;
786
+ if (!key) continue;
787
+ if (event.type === "subagent_start" || event.type === "subagent_complete") {
788
+ const name = typeof data.name === "string" ? data.name : "";
789
+ if (name) names.set(key, name);
790
+ if (parentToolId && childSessionId) {
791
+ const existing = toolToChildSessions.get(parentToolId) ?? [];
792
+ if (!existing.includes(childSessionId)) existing.push(childSessionId);
793
+ toolToChildSessions.set(parentToolId, existing);
794
+ }
795
+ if (event.type === "subagent_complete" && isRecord(data.tokenUsage)) {
796
+ subagentUsage.set(key, {
797
+ input: Number(data.tokenUsage.input ?? 0),
798
+ output: Number(data.tokenUsage.output ?? 0),
799
+ cacheRead: Number(data.tokenUsage.cacheRead ?? 0),
800
+ cacheWrite: Number(data.tokenUsage.cacheWrite ?? 0)
801
+ });
802
+ }
803
+ continue;
804
+ }
805
+ if (event.type === "subagent_tool_call") {
806
+ const items = activity.get(key) ?? [];
807
+ items.push({
808
+ type: "tool",
809
+ tool: typeof data.tool === "string" ? data.tool : "unknown",
810
+ summary: typeof data.summary === "string" ? data.summary : ""
811
+ });
812
+ activity.set(key, items);
813
+ continue;
814
+ }
815
+ if (event.type === "subagent_text") {
816
+ const delta = typeof data.delta === "string" ? data.delta : typeof data.text === "string" ? data.text : "";
817
+ if (!delta) continue;
818
+ const items = activity.get(key) ?? [];
819
+ const last = items[items.length - 1];
820
+ if (last?.type === "text") {
821
+ items[items.length - 1] = { ...last, text: last.text + delta };
822
+ } else {
823
+ items.push({ type: "text", text: delta });
824
+ }
825
+ activity.set(key, items);
826
+ }
827
+ }
828
+ return { names, activity, toolToChildSessions, subagentUsage };
829
+ }
740
830
 
741
831
  // src/utils/session-output.ts
742
832
  function formatDate(value) {
@@ -744,13 +834,33 @@ function formatDate(value) {
744
834
  if (Number.isNaN(date.getTime())) return value;
745
835
  return date.toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC");
746
836
  }
747
- function renderTranscriptEvent(event) {
837
+ function renderSubagentActivityItem(item) {
838
+ if (item.type === "text") return ` - ${item.text}
839
+ `;
840
+ return ` - **Tool:** ${item.tool}${item.summary ? ` - ${item.summary}` : ""}
841
+ `;
842
+ }
843
+ function renderSubagentActivityForTool(toolId, subagents) {
844
+ const childSessionIds = subagents.toolToChildSessions.get(toolId) ?? [];
845
+ if (childSessionIds.length === 0) return "";
846
+ const lines = [];
847
+ for (const childSessionId of childSessionIds) {
848
+ const name = subagents.names.get(childSessionId);
849
+ lines.push(` - **Subagent:** ${name ?? childSessionId}`);
850
+ for (const item of subagents.activity.get(childSessionId) ?? []) {
851
+ lines.push(renderSubagentActivityItem(item).trimEnd());
852
+ }
853
+ }
854
+ return `${lines.join("\n")}
855
+ `;
856
+ }
857
+ function renderTranscriptEvent(event, subagents) {
748
858
  switch (event.type) {
749
859
  case "text":
750
860
  return event.text;
751
861
  case "tool_call":
752
862
  return `**Tool call:** ${event.tool}${event.summary ? ` - ${event.summary}` : ""}${event.toolStatus ? ` (${event.toolStatus})` : ""}
753
- `;
863
+ ${subagents ? renderSubagentActivityForTool(event.id, subagents) : ""}`;
754
864
  case "reasoning":
755
865
  return `<details><summary>Reasoning</summary>
756
866
 
@@ -813,7 +923,9 @@ function renderSessionTranscript(exportData) {
813
923
  for (let i = 0; i < exportData.prompts.length; i++) {
814
924
  const prompt = exportData.prompts[i];
815
925
  const rawEvents = eventBuckets.get(prompt.id) ?? [];
816
- const events = flattenSessionEvents(resolveAuthoritativePromptEvents(rawEvents));
926
+ const authoritativeEvents = resolveAuthoritativePromptEvents(rawEvents);
927
+ const events = flattenSessionEvents(authoritativeEvents);
928
+ const subagents = extractSubagentInfo(authoritativeEvents);
817
929
  lines.push("---\n");
818
930
  lines.push(`## Turn ${i + 1}
819
931
  `);
@@ -827,7 +939,7 @@ function renderSessionTranscript(exportData) {
827
939
  lines.push("**Assistant:**\n");
828
940
  let pendingText = "";
829
941
  for (const event of events) {
830
- const rendered = renderTranscriptEvent(event);
942
+ const rendered = renderTranscriptEvent(event, subagents);
831
943
  if (!rendered) continue;
832
944
  if (event.type === "text") {
833
945
  pendingText += rendered;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryarcanist/cli",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "CLI for Arcanist — create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {