@xmoon76/dsh-pi-tui 0.1.6 → 0.1.8

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/dist/index.d.mts CHANGED
@@ -177,6 +177,24 @@ declare function shouldSteerOnEnter(parsed: {
177
177
  declare function shouldConsumeAdvertisedMiss(execution: {
178
178
  readonly result: unknown;
179
179
  } | undefined, wasAdvertised: boolean): boolean;
180
+ /** One unsettled subagent delegation, in tool/call order. */
181
+ interface PendingSubagentCall {
182
+ readonly callId: string;
183
+ readonly description: string;
184
+ }
185
+ /**
186
+ * Match the child a user is about to view against the unsettled subagent
187
+ * calls (pure, exported for the headless suite). The child's durable label
188
+ * is the delegation's `description`; duplicate descriptions take the MOST
189
+ * RECENT call (the one the user is most likely watching), an empty/absent
190
+ * label falls back to a LONE pending call, and no match disables the
191
+ * auto-pop (the user exits the viewer with Esc as before — never a wrong
192
+ * pop). Mutates `pending` by removing the matched call.
193
+ * @param pending - the unsettled calls, in order (oldest first).
194
+ * @param label - the child's durable label; '' or undefined = no description.
195
+ * @returns the matched call, or undefined when nothing matches.
196
+ */
197
+ declare function matchPendingSubagentCall(pending: PendingSubagentCall[], label: string | undefined): PendingSubagentCall | undefined;
180
198
  /**
181
199
  * The pre-mount surface catalog resolution:
182
200
  * - an explicit `--session` start PREFETCHES the resumed agent's effective
@@ -405,4 +423,4 @@ declare function recomposeBlank(ctx: Context, agent: {
405
423
  */
406
424
  declare function apply(ctx: Context, config: Config): void;
407
425
  //#endregion
408
- export { AgentComposition, Config, InitialCatalogResolution, LOCAL_COMMANDS, QueueFoldResult, QueueInboxMessage, QueueNoticeSource, RecomposableSession, RecomposeOutcome, ResolveInitialCatalogOptions, SESSIONLESS_COMMANDS, SessionLockRefusedError, apply, composeAgent, dangerCommand, foldQueueRows, forkSeed, inject, isPlainExitPrompt, isSubagentSettlementNotice, name, recomposeBlank, recordedPreset, resolveInitialCatalog, resumeCommand, runningProfile, shouldConsumeAdvertisedMiss, shouldSteerOnEnter, subagentJobTranscriptId, subagentJobViewHint, subagentNoticeIsFailure };
426
+ export { AgentComposition, Config, InitialCatalogResolution, LOCAL_COMMANDS, PendingSubagentCall, QueueFoldResult, QueueInboxMessage, QueueNoticeSource, RecomposableSession, RecomposeOutcome, ResolveInitialCatalogOptions, SESSIONLESS_COMMANDS, SessionLockRefusedError, apply, composeAgent, dangerCommand, foldQueueRows, forkSeed, inject, isPlainExitPrompt, isSubagentSettlementNotice, matchPendingSubagentCall, name, recomposeBlank, recordedPreset, resolveInitialCatalog, resumeCommand, runningProfile, shouldConsumeAdvertisedMiss, shouldSteerOnEnter, subagentJobTranscriptId, subagentJobViewHint, subagentNoticeIsFailure };
package/dist/index.mjs CHANGED
@@ -29,7 +29,7 @@ import { eastAsianWidth } from "get-east-asian-width";
29
29
  import { performance } from "node:perf_hooks";
30
30
  import { fileURLToPath } from "node:url";
31
31
  import { EventEmitter } from "events";
32
- import { isUserInvocable } from "@deepseek-ai/dsh-skill";
32
+ import { isUserInvocable, renderSkillContent } from "@deepseek-ai/dsh-skill";
33
33
  //#region src/present.ts
34
34
  /** Figma row titles per variant (design literals, not translatable copy). */
35
35
  const VARIANT_TITLES = {
@@ -12859,10 +12859,10 @@ var TaskBrowserPanel = class {
12859
12859
  this.searchInput.invalidate();
12860
12860
  }
12861
12861
  handleInput(data) {
12862
- const isNavUp = data === "\x1B[A" || !this.searchEnabled && data === "k";
12863
- const isNavDown = data === "\x1B[B" || !this.searchEnabled && data === "j";
12864
- const isPage = data === "\x1B[5~" || data === "\x1B[6~";
12865
- if (!isNavUp && !isNavDown && !isPage && data !== "\r" && data !== "\n" && data !== "\x1B" && this.searchEnabled) {
12862
+ const isNavUp = matchesKey(data, "up") || !this.searchEnabled && data === "k";
12863
+ const isNavDown = matchesKey(data, "down") || !this.searchEnabled && data === "j";
12864
+ const isPage = matchesKey(data, "pageUp") || matchesKey(data, "pageDown");
12865
+ if (!isNavUp && !isNavDown && !isPage && !matchesKey(data, "enter") && !matchesKey(data, "escape") && this.searchEnabled) {
12866
12866
  this.searchInput.handleInput(data);
12867
12867
  this.applyFilter(this.searchInput.getValue() ?? "");
12868
12868
  return;
@@ -12881,17 +12881,17 @@ var TaskBrowserPanel = class {
12881
12881
  }
12882
12882
  if (isPage) {
12883
12883
  if (this.filtered.length === 0) return;
12884
- if (data === "\x1B[5~") this.selected = Math.max(0, this.selected - this.maxVisible);
12884
+ if (matchesKey(data, "pageUp")) this.selected = Math.max(0, this.selected - this.maxVisible);
12885
12885
  else this.selected = Math.min(this.filtered.length - 1, this.selected + this.maxVisible);
12886
12886
  this.ensureVisible();
12887
12887
  return;
12888
12888
  }
12889
- if (data === "\r" || data === "\n") {
12889
+ if (matchesKey(data, "enter")) {
12890
12890
  const item = this.filtered[this.selected];
12891
12891
  if (item !== void 0) this.onSelect(item.value);
12892
12892
  return;
12893
12893
  }
12894
- if (data === "\x1B") this.onCancel();
12894
+ if (matchesKey(data, "escape")) this.onCancel();
12895
12895
  }
12896
12896
  ensureVisible() {
12897
12897
  if (this.selected < this.scroll) this.scroll = this.selected;
@@ -13493,8 +13493,9 @@ var QuestionFlow = class {
13493
13493
  if (draft === void 0) return;
13494
13494
  const text = value.trim();
13495
13495
  draft.custom = text;
13496
- if (text === "") draft.skipped = true;
13497
- else {
13496
+ if (text === "") {
13497
+ if (draft.selected.size === 0) draft.skipped = true;
13498
+ } else {
13498
13499
  if (this.questions[this.tab]?.multiSelect !== true) draft.selected.clear();
13499
13500
  draft.skipped = false;
13500
13501
  }
@@ -13526,44 +13527,34 @@ var QuestionFlow = class {
13526
13527
  handleInput(data) {
13527
13528
  if (data === "\0") return;
13528
13529
  if (this.editingOther) {
13529
- if (data === "\r" || data === "\n") this.commitOther(this.otherInput.getValue());
13530
- else if (data === "\x1B") this.exitOther();
13531
- else if (data === "\x1B[5~" || data === "\x1B[6~") this.scrollBody(data === "\x1B[5~" ? -1 : 1);
13532
- else if (data === "\x1B[D") {
13530
+ if (matchesKey(data, "enter")) this.commitOther(this.otherInput.getValue());
13531
+ else if (matchesKey(data, "escape")) this.exitOther();
13532
+ else if (matchesKey(data, "pageUp")) this.scrollBody(-1);
13533
+ else if (matchesKey(data, "pageDown")) this.scrollBody(1);
13534
+ else if (matchesKey(data, "left")) {
13533
13535
  if (this.tab > 0) {
13534
13536
  this.tab -= 1;
13535
13537
  this.cursor = 0;
13536
13538
  this.submitIdx = 0;
13537
13539
  this.syncEditMode();
13538
13540
  }
13539
- } else if (data === "\x1B[C") {
13540
- if (this.tab < this.questions.length - 1) {
13541
- this.tab += 1;
13542
- this.cursor = 0;
13543
- this.submitIdx = 0;
13544
- this.syncEditMode();
13545
- } else {
13546
- this.tab = this.questions.length;
13547
- this.submitIdx = 0;
13548
- this.syncEditMode();
13549
- }
13550
- } else this.otherInput.handleInput(data);
13541
+ } else if (matchesKey(data, "right")) this.commitOther(this.otherInput.getValue());
13542
+ else this.otherInput.handleInput(data);
13551
13543
  return;
13552
13544
  }
13553
13545
  const rows = this.rows();
13554
13546
  if (this.tab >= this.questions.length) {
13555
- if (data === "\r" || data === "\n") {
13547
+ if (matchesKey(data, "enter")) {
13556
13548
  if (this.submitIdx === 0) this.submit();
13557
13549
  else this.onCancel();
13558
- } else if (data === "\x1B") this.onCancel();
13559
- else if (data === "\x1B[D" || data === "h") this.submitIdx = 0;
13560
- else if (data === "\x1B[C" || data === "l") this.submitIdx = 1;
13561
- else if (data === "b") {
13550
+ } else if (matchesKey(data, "escape")) this.onCancel();
13551
+ else if (matchesKey(data, "left") || data === "h") {
13562
13552
  this.tab = Math.max(0, this.questions.length - 1);
13563
13553
  this.cursor = 0;
13564
13554
  this.submitIdx = 0;
13565
13555
  this.syncEditMode();
13566
- }
13556
+ } else if (matchesKey(data, "up") || data === "k") this.submitIdx = 0;
13557
+ else if (matchesKey(data, "down") || data === "j") this.submitIdx = 1;
13567
13558
  return;
13568
13559
  }
13569
13560
  const digit = /^[1-9]$/.exec(data);
@@ -13576,7 +13567,7 @@ var QuestionFlow = class {
13576
13567
  }
13577
13568
  return;
13578
13569
  }
13579
- if (data === "\x1B[A" || data === "k") {
13570
+ if (matchesKey(data, "up") || data === "k") {
13580
13571
  if (rows.length === 0) return;
13581
13572
  if (this.cursor === 0 && this.bodyScroll > 0) {
13582
13573
  this.scrollBody(-1);
@@ -13586,7 +13577,7 @@ var QuestionFlow = class {
13586
13577
  this.pendingCursorScroll = true;
13587
13578
  return;
13588
13579
  }
13589
- if (data === "\x1B[B" || data === "j") {
13580
+ if (matchesKey(data, "down") || data === "j") {
13590
13581
  if (rows.length === 0) return;
13591
13582
  const atLastRow = this.cursor === rows.length - 1;
13592
13583
  const scrolled = this.lastExpandable && this.lastContentRows > this.bodyScroll + this.lastVisibleRows;
@@ -13598,19 +13589,19 @@ var QuestionFlow = class {
13598
13589
  this.pendingCursorScroll = true;
13599
13590
  return;
13600
13591
  }
13601
- if (data === "\x1B[5~" || data === "\x1B[6~") {
13602
- this.scrollBody(data === "\x1B[5~" ? -1 : 1);
13592
+ if (matchesKey(data, "pageUp") || matchesKey(data, "pageDown")) {
13593
+ this.scrollBody(matchesKey(data, "pageUp") ? -1 : 1);
13603
13594
  return;
13604
13595
  }
13605
13596
  if (data === "e") {
13606
13597
  this.toggleExpanded();
13607
13598
  return;
13608
13599
  }
13609
- if (data === "\r" || data === "\n") {
13600
+ if (matchesKey(data, "enter")) {
13610
13601
  this.confirm();
13611
13602
  return;
13612
13603
  }
13613
- if (data === "\x1B[D" || data === "h") {
13604
+ if (matchesKey(data, "left") || data === "h") {
13614
13605
  if (this.tab > 0) {
13615
13606
  this.tab -= 1;
13616
13607
  this.cursor = 0;
@@ -13619,7 +13610,11 @@ var QuestionFlow = class {
13619
13610
  }
13620
13611
  return;
13621
13612
  }
13622
- if (data === "\x1B[C" || data === "l") {
13613
+ if (matchesKey(data, "right") || data === "l") {
13614
+ if (!this.answered()) {
13615
+ this.skip();
13616
+ return;
13617
+ }
13623
13618
  if (this.tab < this.questions.length - 1) {
13624
13619
  this.tab += 1;
13625
13620
  this.cursor = 0;
@@ -13633,11 +13628,7 @@ var QuestionFlow = class {
13633
13628
  }
13634
13629
  return;
13635
13630
  }
13636
- if (data === "s") {
13637
- this.skip();
13638
- return;
13639
- }
13640
- if (data === "\x1B") this.onCancel();
13631
+ if (matchesKey(data, "escape")) this.onCancel();
13641
13632
  }
13642
13633
  invalidate() {
13643
13634
  this.otherInput.invalidate();
@@ -13680,7 +13671,7 @@ var QuestionFlow = class {
13680
13671
  const submit = this.submitIdx === 0 ? color.textStrong("Submit") : color.textDim("Submit");
13681
13672
  const cancel = this.submitIdx === 1 ? color.textStrong("Cancel") : color.textDim("Cancel");
13682
13673
  lines.push(`${this.submitIdx === 0 ? color.primary("→ ") : " "}${submit} ${cancel}`);
13683
- lines.push(color.textDim("← → choose · b back · ↵ confirm · esc cancel"));
13674
+ lines.push(color.textDim("↑↓ choose · ← back · ↵ confirm · esc cancel"));
13684
13675
  return lines;
13685
13676
  }
13686
13677
  const question = this.questions[this.tab];
@@ -13736,14 +13727,14 @@ var QuestionFlow = class {
13736
13727
  lines.push("");
13737
13728
  const optionCount = Math.min(rows.length, 9);
13738
13729
  const scrollable = this.lastExpandable;
13730
+ const arrowVerb = this.editingOther ? "→ next" : "→ skip";
13739
13731
  const hintParts = [
13740
13732
  "↑↓ select",
13741
13733
  optionCount > 0 ? `1-${optionCount} choose` : "",
13742
13734
  multi ? "↵ toggle" : "↵ confirm",
13743
13735
  scrollable ? "pgup/pgdn scroll" : "",
13744
13736
  !this.editingOther ? this.bodyExpanded ? "e collapse" : scrollable ? "e expand" : "" : "",
13745
- this.questions.length > 1 ? "←→ switch" : "",
13746
- "s skip",
13737
+ this.questions.length > 1 ? `← back · ${arrowVerb}` : arrowVerb,
13747
13738
  "esc cancel"
13748
13739
  ].filter((part) => part !== "");
13749
13740
  const cancel = "esc cancel";
@@ -15329,6 +15320,17 @@ var TuiApp = class TuiApp {
15329
15320
  this.requestRender();
15330
15321
  }
15331
15322
  /**
15323
+ * Anchor the transcript view to its END (the latest content). Fullscreen
15324
+ * scrolls its ScrollView; the regular surface has no scroll view (it draws
15325
+ * into the terminal scrollback), so a forced full repaint re-renders from
15326
+ * row 0 and the main screen's viewport tracking lands on the bottom — the
15327
+ * same mechanism the fullscreen-toggle path uses to redraw cleanly.
15328
+ */
15329
+ scrollToBottom() {
15330
+ if (this.fullscreenScroll !== void 0) this.fullscreenScroll.scrollToEnd();
15331
+ else this.tui.requestRender(true);
15332
+ }
15333
+ /**
15332
15334
  * Set the live session's auto-generated title (from the session/title
15333
15335
  * log) for the header; undefined clears it.
15334
15336
  */
@@ -17688,6 +17690,48 @@ function registerTuiCommands(runner, initial) {
17688
17690
  const sessionCwd = (agent) => agent.session.header.cwd ?? cwd;
17689
17691
  const skillTarget = (agent) => resolveLiveSkillTarget(ctx, agent, sessionCwd(agent));
17690
17692
  /**
17693
+ * Split a skill invocation's trailing input into the skill name and its
17694
+ * arguments: the first whitespace-bounded token is the name (the public
17695
+ * skill-name grammar), everything after it the arguments. Used by the
17696
+ * per-skill wrappers and `/skill`, so `/name args` never looks up a name
17697
+ * containing the whole rest of the line.
17698
+ * @param raw - the invocation's rawInput ('' for a bare command).
17699
+ * @returns the name token ('' when the input is blank) plus the args.
17700
+ */
17701
+ const splitSkillLine = (raw) => {
17702
+ const trimmed = raw.trimStart();
17703
+ const space = trimmed.search(/\s/);
17704
+ if (space === -1) return [trimmed.trim(), ...[]];
17705
+ return [trimmed.slice(0, space).trim(), trimmed.slice(space).trimStart()];
17706
+ };
17707
+ /**
17708
+ * Structurally validate a skill's optional resource base, so a malformed
17709
+ * provider-supplied value degrades to no hint instead of throwing inside
17710
+ * the fallback rendering (the adapter's conservative rule — hostile
17711
+ * entries are refused, never coerced or thrown on).
17712
+ * @param value - the loaded skill's `resourceBase` (opaque from the adapter).
17713
+ * @returns the validated resource base, or undefined when unreadable.
17714
+ */
17715
+ const readResourceBase = (value) => {
17716
+ if (typeof value !== "object" || value === null) return void 0;
17717
+ const record = value;
17718
+ switch (record.kind) {
17719
+ case "directory": return typeof record.path === "string" && record.path !== "" ? {
17720
+ kind: "directory",
17721
+ path: record.path
17722
+ } : void 0;
17723
+ case "url": return typeof record.url === "string" && record.url !== "" ? {
17724
+ kind: "url",
17725
+ url: record.url
17726
+ } : void 0;
17727
+ case "opaque": return typeof record.description === "string" && record.description !== "" ? {
17728
+ kind: "opaque",
17729
+ description: record.description
17730
+ } : void 0;
17731
+ default: return;
17732
+ }
17733
+ };
17734
+ /**
17691
17735
  * The execution boundary for loading one skill into the live session;
17692
17736
  * shared by /skill and the per-skill slash commands. The skill is fetched
17693
17737
  * from the CURRENT agent's registry and its invocation policy is RE-CHECKED
@@ -17695,7 +17739,7 @@ function registerTuiCommands(runner, initial) {
17695
17739
  * authorization. A model-only skill is refused with an explicit error and
17696
17740
  * never injected.
17697
17741
  */
17698
- const loadSkill = async (agent, name) => {
17742
+ const loadSkill = async (agent, name, args = "") => {
17699
17743
  const target = skillTarget(agent);
17700
17744
  if (target === void 0) return {
17701
17745
  kind: "error",
@@ -17717,17 +17761,37 @@ function registerTuiCommands(runner, initial) {
17717
17761
  kind: "error",
17718
17762
  text: `skill "${name}" returned a malformed definition`
17719
17763
  };
17720
- const body = typeof skill.content === "string" && skill.content !== "" ? skill.content : skill.description;
17721
- agent.inject(createUserMessage({
17764
+ const line = args.trim() === "" ? "/" + skill.name : "/" + skill.name + " " + args.trimStart();
17765
+ const userMessage = createUserMessage({
17722
17766
  content: [{
17723
17767
  type: "text",
17724
- text: "Skill loaded by the user: **" + skill.name + "**\n\n" + body
17768
+ text: line
17725
17769
  }],
17726
- source: {
17727
- kind: "plugin",
17728
- plugin: "tui-skill"
17729
- }
17730
- }));
17770
+ source: { kind: "user" }
17771
+ });
17772
+ const hostSkillLoader = ctx.get("tools")?.get?.("skill", agent);
17773
+ const hostLoadsSkillBody = hostSkillLoader !== void 0 && typeof hostSkillLoader.execute === "function";
17774
+ agent.steer(userMessage);
17775
+ if (!hostLoadsSkillBody) {
17776
+ const body = typeof skill.content === "string" && skill.content !== "" ? skill.content : skill.description;
17777
+ const resourceBase = readResourceBase(skill.resourceBase);
17778
+ agent.inject(createUserMessage({
17779
+ content: [{
17780
+ type: "text",
17781
+ text: renderSkillContent({
17782
+ name: skill.name,
17783
+ provider: typeof skill.provider === "string" && skill.provider !== "" ? skill.provider : "tui",
17784
+ ...resourceBase === void 0 ? {} : { resourceBase },
17785
+ content: body
17786
+ })
17787
+ }],
17788
+ source: {
17789
+ kind: "skill-invocation",
17790
+ name: skill.name,
17791
+ form: "instructions"
17792
+ }
17793
+ }));
17794
+ }
17731
17795
  return {
17732
17796
  kind: "success",
17733
17797
  text: "skill " + name + " loaded"
@@ -17763,9 +17827,9 @@ function registerTuiCommands(runner, initial) {
17763
17827
  const dispose = commands.register({
17764
17828
  name: skill.name,
17765
17829
  description: "[skill] " + skill.description,
17766
- handler: async () => {
17830
+ handler: async (invocation) => {
17767
17831
  const agent = await requireAgent();
17768
- return loadSkill(agent, skill.name);
17832
+ return loadSkill(agent, skill.name, invocation?.rawInput ?? "");
17769
17833
  }
17770
17834
  });
17771
17835
  skillDisposers.set(skill.name, dispose);
@@ -17811,9 +17875,9 @@ function registerTuiCommands(runner, initial) {
17811
17875
  const dispose = commands.register({
17812
17876
  name,
17813
17877
  description: `[skill: revalidating] ${name}`,
17814
- handler: async () => {
17878
+ handler: async (invocation) => {
17815
17879
  const agent = await requireAgent();
17816
- return loadSkill(agent, name);
17880
+ return loadSkill(agent, name, invocation?.rawInput ?? "");
17817
17881
  }
17818
17882
  });
17819
17883
  skillDisposers.set(name, dispose);
@@ -17835,8 +17899,8 @@ function registerTuiCommands(runner, initial) {
17835
17899
  kind: "error",
17836
17900
  text: "skill service unavailable"
17837
17901
  };
17838
- const name = invocation.rawInput.trim();
17839
- if (name !== "") return loadSkill(liveAgent, name);
17902
+ const [name, ...args] = splitSkillLine(invocation.rawInput);
17903
+ if (name !== "") return loadSkill(liveAgent, name, args.join(" "));
17840
17904
  const catalog = await readHumanSkillCatalog(target.registry, {
17841
17905
  cwd: target.cwd,
17842
17906
  scope: target.scope
@@ -20443,6 +20507,26 @@ function shouldSteerOnEnter(parsed, running, busyEnter, forceQueue) {
20443
20507
  function shouldConsumeAdvertisedMiss(execution, wasAdvertised) {
20444
20508
  return execution === void 0 && wasAdvertised;
20445
20509
  }
20510
+ /**
20511
+ * Match the child a user is about to view against the unsettled subagent
20512
+ * calls (pure, exported for the headless suite). The child's durable label
20513
+ * is the delegation's `description`; duplicate descriptions take the MOST
20514
+ * RECENT call (the one the user is most likely watching), an empty/absent
20515
+ * label falls back to a LONE pending call, and no match disables the
20516
+ * auto-pop (the user exits the viewer with Esc as before — never a wrong
20517
+ * pop). Mutates `pending` by removing the matched call.
20518
+ * @param pending - the unsettled calls, in order (oldest first).
20519
+ * @param label - the child's durable label; '' or undefined = no description.
20520
+ * @returns the matched call, or undefined when nothing matches.
20521
+ */
20522
+ function matchPendingSubagentCall(pending, label) {
20523
+ if (label !== void 0 && label !== "") {
20524
+ for (let index = pending.length - 1; index >= 0; index -= 1) if (pending[index].description === label) return pending.splice(index, 1)[0];
20525
+ if (pending.length === 1) return pending.splice(0, 1)[0];
20526
+ return;
20527
+ }
20528
+ if (pending.length === 1) return pending.splice(0, 1)[0];
20529
+ }
20446
20530
  async function resolveInitialCatalog(options) {
20447
20531
  const { liveAgent, presetId, signal, ctx, diag } = options;
20448
20532
  if (liveAgent !== void 0) try {
@@ -21564,6 +21648,8 @@ function apply(ctx, config) {
21564
21648
  };
21565
21649
  let repaintTimer;
21566
21650
  let viewing;
21651
+ const pendingSubagentCalls = [];
21652
+ const viewCallToChild = /* @__PURE__ */ new Map();
21567
21653
  const activeFolder = () => viewing?.folder ?? folder;
21568
21654
  const paintNow = () => {
21569
21655
  if (repaintTimer !== void 0) {
@@ -21586,6 +21672,8 @@ function apply(ctx, config) {
21586
21672
  const bumpSessionGeneration = () => {
21587
21673
  sessionGeneration += 1;
21588
21674
  callArgs.clear();
21675
+ pendingSubagentCalls.length = 0;
21676
+ viewCallToChild.clear();
21589
21677
  searchMatches = [];
21590
21678
  searchCurrent = -1;
21591
21679
  app.setSearchResult(0, 0);
@@ -21613,6 +21701,8 @@ function apply(ctx, config) {
21613
21701
  childFolder.apply((await persistence.inspect(childId)).events);
21614
21702
  } catch {}
21615
21703
  }
21704
+ const matched = matchPendingSubagentCall(pendingSubagentCalls, label);
21705
+ if (matched !== void 0) viewCallToChild.set(matched.callId, childId);
21616
21706
  viewing = {
21617
21707
  id: childId,
21618
21708
  folder: childFolder
@@ -21631,6 +21721,7 @@ function apply(ctx, config) {
21631
21721
  app.clearNotify();
21632
21722
  app.setViewerMode(void 0);
21633
21723
  repaint(app, folder);
21724
+ app.scrollToBottom();
21634
21725
  refreshStatus();
21635
21726
  return true;
21636
21727
  };
@@ -22518,17 +22609,42 @@ function apply(ctx, config) {
22518
22609
  ctx.on("session/event", (session, event) => {
22519
22610
  if (liveAgent === void 0) return;
22520
22611
  if (viewing !== void 0) {
22521
- if (session.id !== viewing.id) return;
22522
- viewing.folder.apply([event]);
22523
- schedulePaint();
22524
- if (event.type === "turn/end") paintNow();
22525
- return;
22612
+ if (session.id === viewing.id) {
22613
+ viewing.folder.apply([event]);
22614
+ schedulePaint();
22615
+ if (event.type === "turn/end") paintNow();
22616
+ return;
22617
+ }
22526
22618
  }
22527
22619
  if (session.id !== liveAgent.session.id) return;
22528
22620
  if (event.type === "tool/call") {
22529
22621
  callArgs.set(event.data.callId, typeof event.data.arguments === "string" ? event.data.arguments : JSON.stringify(event.data.arguments));
22530
- if (typeof event.data.name === "string" && event.data.name.startsWith("subagent")) refreshAgents();
22531
- } else if (event.type === "tool/result") callArgs.delete(event.data.message.content[0]?.toolCallId ?? "");
22622
+ if (typeof event.data.name === "string" && event.data.name.startsWith("subagent")) {
22623
+ refreshAgents();
22624
+ let description = "";
22625
+ try {
22626
+ const parsed = JSON.parse(event.data.arguments);
22627
+ if (typeof parsed === "object" && parsed !== null && typeof parsed.description === "string") description = parsed.description;
22628
+ } catch {}
22629
+ pendingSubagentCalls.push({
22630
+ callId: event.data.callId,
22631
+ description
22632
+ });
22633
+ }
22634
+ } else if (event.type === "tool/result") {
22635
+ const callId = event.data.message.content[0]?.toolCallId;
22636
+ callArgs.delete(callId ?? "");
22637
+ const callIndex = pendingSubagentCalls.findIndex((call) => call.callId === callId);
22638
+ if (callIndex !== -1) pendingSubagentCalls.splice(callIndex, 1);
22639
+ const childId = callId === void 0 ? void 0 : viewCallToChild.get(callId);
22640
+ if (childId !== void 0) viewCallToChild.delete(callId);
22641
+ if (childId !== void 0 && viewing !== void 0 && viewing.id === childId) {
22642
+ folder.apply([event]);
22643
+ statsFolder.apply([event]);
22644
+ exitView();
22645
+ return;
22646
+ }
22647
+ }
22532
22648
  folder.apply([event]);
22533
22649
  statsFolder.apply([event]);
22534
22650
  if (event.type === "goal/change") goalText = foldGoal([event]);
@@ -22620,4 +22736,4 @@ function apply(ctx, config) {
22620
22736
  });
22621
22737
  }
22622
22738
  //#endregion
22623
- export { Config, LOCAL_COMMANDS, SESSIONLESS_COMMANDS, SessionLockRefusedError, apply, composeAgent, dangerCommand, foldQueueRows, forkSeed, inject, isPlainExitPrompt, isSubagentSettlementNotice, name, recomposeBlank, recordedPreset, resolveInitialCatalog, resumeCommand, runningProfile, shouldConsumeAdvertisedMiss, shouldSteerOnEnter, subagentJobTranscriptId, subagentJobViewHint, subagentNoticeIsFailure };
22739
+ export { Config, LOCAL_COMMANDS, SESSIONLESS_COMMANDS, SessionLockRefusedError, apply, composeAgent, dangerCommand, foldQueueRows, forkSeed, inject, isPlainExitPrompt, isSubagentSettlementNotice, matchPendingSubagentCall, name, recomposeBlank, recordedPreset, resolveInitialCatalog, resumeCommand, runningProfile, shouldConsumeAdvertisedMiss, shouldSteerOnEnter, subagentJobTranscriptId, subagentJobViewHint, subagentNoticeIsFailure };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmoon76/dsh-pi-tui",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "The dsh-pi-tui bundle: a TUI surface for DeepSeek Harness profiles (dsh --profile pi-tui)",
5
5
  "license": "MIT",
6
6
  "author": "XMoon <xmoonox@gmail.com>",