@narumitw/pi-subagents 3.4.3 → 3.4.4

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
@@ -1,5 +1,11 @@
1
1
  # @narumitw/pi-subagents
2
2
 
3
+ ## 3.4.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 49c68d4: Show readable tool summaries and job notifications in the conversation. Keep job identity, state, and message direction visible when collapsed, and render expanded subagent results as Markdown without changing model data or widget behavior.
8
+
3
9
  ## 3.4.3
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -73,10 +73,8 @@ Use messaging only when needed:
73
73
  - If `subagent_wait` returns `reason: "subagent_message"`, handle the visible request or response and wait for the job again only when needed.
74
74
  - Answer a child-originated request by calling `subagent_send` with its `requestId`.
75
75
 
76
- Completion messages and child requests and responses follow Pi's global tool-output expansion state and the `app.tools.expand` binding (`Ctrl+O` by default).
77
- Collapsed messages show a summary and the host's configured expansion keys.
78
- The hint is omitted when the action is unbound or an older host does not supply renderer key formatting.
79
- Expanded messages show the full content; this display state does not change the content sent to the model.
76
+ Expand tool results and notifications with Pi's `app.tools.expand` binding (`Ctrl+O` by default).
77
+ See [Conversation presentation](./docs/conversation-display.md) for tool summaries, notification details, and expansion behavior.
80
78
 
81
79
  ## 💬 Commands
82
80
 
@@ -337,6 +335,9 @@ packages/pi-subagents/
337
335
  ├── src/ # Authoritative implementation and helpers
338
336
  │ ├── index.ts # Thin Pi entrypoint
339
337
  │ ├── subagents.ts # Commands and session lifecycle
338
+ │ ├── tool-renderers.ts # Conversation tool summaries and expanded details
339
+ │ ├── message-renderers.ts # Conversation completion and broker notifications
340
+ │ ├── conversation-view.ts # Shared bounded, sanitized conversation rendering
340
341
  │ ├── menu.ts # Job manager and session-local display controls
341
342
  │ ├── widget.ts # Retained widget ownership and event policies
342
343
  │ ├── widget-renderer.ts # Bounded rows and active-job summaries
package/dist/index.ts CHANGED
@@ -38,6 +38,7 @@ import {
38
38
  import {
39
39
  CHILD_CORE_TOOL_NAMES,
40
40
  DEFAULT_SUBAGENT_TOOLS,
41
+ JOB_STATES,
41
42
  SUBAGENT_THINKING_LEVELS,
42
43
  TERMINAL_JOB_STATES
43
44
  } from "./chunks/chunk-5OXDVXXO.ts";
@@ -53,49 +54,206 @@ function sanitizeSubagentDisplayText(value) {
53
54
  }
54
55
 
55
56
  // src/message-renderers.ts
57
+ import { Box } from "@earendil-works/pi-tui";
58
+
59
+ // src/conversation-view.ts
56
60
  import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
57
- import { Box, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
61
+ import { Markdown, truncateToWidth } from "@earendil-works/pi-tui";
62
+ import { hardWrapTerminalDocument, sanitizeTerminalDocument } from "@narumitw/pi-tui-kit/terminal-document";
63
+ function boundedView(render, invalidate = () => {
64
+ }) {
65
+ return {
66
+ render(width) {
67
+ if (!Number.isFinite(width) || width < 1) return [];
68
+ const columns = Math.floor(width);
69
+ return render(columns).map((line) => truncateToWidth(line, columns, ""));
70
+ },
71
+ invalidate
72
+ };
73
+ }
74
+ function conversationView(children) {
75
+ return boundedView(
76
+ (width) => children.flatMap((child) => child.render(width)),
77
+ () => {
78
+ for (const child of children) child.invalidate();
79
+ }
80
+ );
81
+ }
82
+ function singleLine(text) {
83
+ return sanitizeTerminalDocument(text).replace(/\s+/gu, " ").trim();
84
+ }
85
+ function displayLine(theme, text, role = "toolOutput") {
86
+ const safe = singleLine(text);
87
+ return boundedView((width) => [theme.fg(role, truncateToWidth(safe, width, "\u2026"))]);
88
+ }
89
+ function displayHeading(theme, title, detail = "", role = "toolTitle") {
90
+ const safeTitle = singleLine(title);
91
+ const safeDetail = singleLine(detail);
92
+ return boundedView((width) => [
93
+ truncateToWidth(
94
+ theme.fg(role, theme.bold(safeTitle)) + (safeDetail ? theme.fg("muted", ` \xB7 ${safeDetail}`) : ""),
95
+ width,
96
+ "\u2026"
97
+ )
98
+ ]);
99
+ }
100
+ function displayDocument(theme, text, role = "toolOutput") {
101
+ const safe = sanitizeTerminalDocument(text);
102
+ return boundedView((width) => hardWrapTerminalDocument(safe, width).map((line) => theme.fg(role, line)));
103
+ }
104
+ function displayJson(theme, value) {
105
+ const text = JSON.stringify(
106
+ value,
107
+ (_key, item) => typeof item === "string" ? sanitizeTerminalDocument(item) : item,
108
+ 2
109
+ );
110
+ return displayDocument(theme, text ?? "");
111
+ }
112
+ function displayMarkdown(theme, text, role = "toolOutput") {
113
+ const safe = sanitizeTerminalDocument(text);
114
+ const markdown = new Markdown(safe, 0, 0, getMarkdownTheme(), { color: (value) => theme.fg(role, value) });
115
+ return boundedView(
116
+ (width) => markdown.render(width),
117
+ () => markdown.invalidate()
118
+ );
119
+ }
120
+ function expansionHint(theme, hints) {
121
+ const key = hints.keyText?.("app.tools.expand");
122
+ return conversationView(key ? [displayLine(theme, `(${key} to expand)`, "muted")] : []);
123
+ }
124
+ function textContent(content) {
125
+ return typeof content === "string" ? content : content.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text).join("\n");
126
+ }
127
+ function asRecord(value) {
128
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
129
+ }
130
+ function parseRecord(text) {
131
+ try {
132
+ return asRecord(JSON.parse(text));
133
+ } catch {
134
+ return void 0;
135
+ }
136
+ }
137
+ function jobPayload(value) {
138
+ const record = asRecord(value);
139
+ if (!record || typeof record.jobId !== "string" || !JOB_STATES.some((state) => state === record.state))
140
+ return void 0;
141
+ if (["result", "error"].some((key) => record[key] !== void 0 && typeof record[key] !== "string")) return void 0;
142
+ if (record.limitations !== void 0 && (!Array.isArray(record.limitations) || record.limitations.some((value2) => typeof value2 !== "string")))
143
+ return void 0;
144
+ if (["timedOut", "interrupted"].some((key) => record[key] !== void 0 && typeof record[key] !== "boolean"))
145
+ return void 0;
146
+ return record;
147
+ }
148
+ function stateLabel(state) {
149
+ return state === "timed_out" ? "timed out" : state;
150
+ }
151
+ function stateRole(state) {
152
+ switch (state) {
153
+ case "completed":
154
+ return "success";
155
+ case "failed":
156
+ case "timed_out":
157
+ return "error";
158
+ case "partial":
159
+ case "cancelled":
160
+ return "warning";
161
+ case "running":
162
+ return "accent";
163
+ case "queued":
164
+ return "muted";
165
+ }
166
+ }
167
+ function jobSummary(theme, payload) {
168
+ const waitStatus = payload.timedOut === true ? " \xB7 wait timed out (job not cancelled)" : "";
169
+ const interrupted = payload.interrupted === true ? " \xB7 interrupted by subagent message" : "";
170
+ const error = typeof payload.error === "string" && payload.error ? ` \xB7 ${payload.error}` : "";
171
+ const limitations = Array.isArray(payload.limitations) ? payload.limitations.length : 0;
172
+ return displayLine(
173
+ theme,
174
+ `${payload.jobId} \xB7 ${stateLabel(payload.state)}${waitStatus}${interrupted}${error}${limitations ? ` \xB7 ${limitations} limitation${limitations === 1 ? "" : "s"}` : ""}`,
175
+ payload.state === "failed" || payload.state === "timed_out" ? "error" : payload.timedOut === true || payload.interrupted === true ? "warning" : stateRole(payload.state)
176
+ );
177
+ }
178
+ function jobBody(theme, payload) {
179
+ const children = [];
180
+ if (typeof payload.error === "string" && payload.error) {
181
+ children.push(displayDocument(theme, `Error: ${payload.error}`, "error"));
182
+ }
183
+ if (Array.isArray(payload.limitations)) {
184
+ for (const limitation of payload.limitations) {
185
+ if (typeof limitation === "string") children.push(displayDocument(theme, `Limitation: ${limitation}`, "warning"));
186
+ }
187
+ }
188
+ if (typeof payload.result === "string" && payload.result) children.push(displayMarkdown(theme, payload.result));
189
+ return conversationView(children);
190
+ }
191
+
192
+ // src/message-renderers.ts
58
193
  var COMPLETION_MESSAGE_TYPE = "pi-subagents-completion";
59
194
  var BROKER_MESSAGE_TYPE = "pi-subagents-message";
195
+ var COMPLETION_PREFIX = "Subagent job completion:\n";
196
+ function brokerDetails(value) {
197
+ const details = asRecord(value);
198
+ return details && (details.kind === "request" || details.kind === "response") && typeof details.jobId === "string" && typeof details.requestId === "string" ? details : void 0;
199
+ }
200
+ function legacyBrokerDetails(content) {
201
+ const [type, protocol, request, job] = content.split("\n", 4);
202
+ if (protocol !== "Protocol: pi-subagents:main-message:v1" || !request?.startsWith("Request ID: ") || !job?.startsWith("Job ID: "))
203
+ return void 0;
204
+ const kind = type === "Message Type: SUBAGENT_REQUEST" ? "request" : type === "Message Type: SUBAGENT_RESPONSE" ? "response" : void 0;
205
+ return brokerDetails({ kind, requestId: request.slice("Request ID: ".length), jobId: job.slice("Job ID: ".length) });
206
+ }
60
207
  function createMessageBox(children, outputPad, background) {
61
- return {
62
- render(width) {
63
- if (width <= 0) return [];
64
- const maxPadding = Math.floor((width - 1) / 2);
65
- const box = new Box(Math.min(Math.max(0, outputPad), maxPadding), 1, background);
208
+ return boundedView(
209
+ (width) => {
210
+ const padding = Number.isFinite(outputPad) ? Math.max(0, Math.floor(outputPad)) : 0;
211
+ const box = new Box(Math.min(padding, Math.floor((width - 1) / 2)), 1, background);
66
212
  for (const child of children) box.addChild(child);
67
213
  return box.render(width);
68
214
  },
69
- invalidate() {
215
+ () => {
70
216
  for (const child of children) child.invalidate();
71
217
  }
72
- };
218
+ );
73
219
  }
74
220
  function registerMessageRenderers(pi) {
75
- for (const [messageType, summary] of [
76
- [COMPLETION_MESSAGE_TYPE, "Subagent job completion"],
77
- [BROKER_MESSAGE_TYPE, "Subagent message"]
78
- ]) {
79
- pi.registerMessageRenderer(messageType, (message, options, theme) => {
80
- const children = [
81
- new Text(theme.fg("customMessageLabel", theme.bold(`[${messageType}]`)), 0, 0),
82
- new Spacer(1)
83
- ];
84
- if (!options.expanded) {
85
- const expansionKey = options.keyText?.("app.tools.expand");
86
- const hint = expansionKey ? theme.fg("muted", ` (${expansionKey} to expand)`) : "";
87
- children.push(new Text(theme.fg("customMessageText", summary) + hint, 0, 0));
88
- } else {
89
- const content = typeof message.content === "string" ? message.content : message.content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
90
- children.push(
91
- new Markdown(sanitizeTerminalText(content), 0, 0, getMarkdownTheme(), {
92
- color: (text) => theme.fg("customMessageText", text)
93
- })
94
- );
95
- }
96
- return createMessageBox(children, options.outputPad, (text) => theme.bg("customMessageBg", text));
97
- });
98
- }
221
+ pi.registerMessageRenderer(COMPLETION_MESSAGE_TYPE, (message, options, theme) => {
222
+ const content = textContent(message.content);
223
+ const payload = jobPayload(message.details) ?? jobPayload(
224
+ parseRecord(content.startsWith(COMPLETION_PREFIX) ? content.slice(COMPLETION_PREFIX.length) : content)
225
+ );
226
+ const children = [displayHeading(theme, "Subagent job completion", "", "customMessageLabel")];
227
+ if (payload) children.push(jobSummary(theme, payload));
228
+ if (options.expanded) {
229
+ children.push(payload ? jobBody(theme, payload) : displayDocument(theme, content, "customMessageText"));
230
+ } else {
231
+ children.push(expansionHint(theme, options));
232
+ }
233
+ return createMessageBox(children, options.outputPad, (text) => theme.bg("customMessageBg", text));
234
+ });
235
+ pi.registerMessageRenderer(BROKER_MESSAGE_TYPE, (message, options, theme) => {
236
+ const content = textContent(message.content);
237
+ const details = brokerDetails(message.details) ?? legacyBrokerDetails(content);
238
+ const title = details ? `Subagent ${details.kind}` : "Subagent message";
239
+ const source = details ? `from ${details.jobId}` : "";
240
+ const children = [displayHeading(theme, title, source, "customMessageLabel")];
241
+ if (details) {
242
+ children.push(
243
+ displayLine(
244
+ theme,
245
+ `${details.requestId} \xB7 ${details.kind === "request" ? "needs reply" : "response received"}`,
246
+ "muted"
247
+ )
248
+ );
249
+ }
250
+ if (options.expanded) {
251
+ children.push(displayDocument(theme, content, "customMessageText"));
252
+ } else {
253
+ children.push(expansionHint(theme, options));
254
+ }
255
+ return createMessageBox(children, options.outputPad, (text) => theme.bg("customMessageBg", text));
256
+ });
99
257
  }
100
258
 
101
259
  // src/settings.ts
@@ -2227,6 +2385,126 @@ function abortError2(message) {
2227
2385
  return error;
2228
2386
  }
2229
2387
 
2388
+ // src/tool-renderers.ts
2389
+ var LABELS = { spawn: "Spawn", inspect: "Inspect", cancel: "Cancel", wait: "Wait", send: "Send" };
2390
+ var PENDING = {
2391
+ spawn: "Starting subagent\u2026",
2392
+ inspect: "Inspecting jobs\u2026",
2393
+ cancel: "Cancelling job\u2026",
2394
+ wait: "Waiting for job or subagent message\u2026",
2395
+ send: "Sending message\u2026"
2396
+ };
2397
+ var INSPECT_PREVIEW_JOBS = 5;
2398
+ var STATE_PRIORITY = { failed: 0, timed_out: 1, partial: 2, running: 3, queued: 4, cancelled: 5, completed: 6 };
2399
+ function subagentToolRenderers(action) {
2400
+ return {
2401
+ renderCall(input, theme, context) {
2402
+ const args = asRecord(input) ?? {};
2403
+ const target = action === "send" ? typeof args.requestId === "string" ? `reply to ${args.requestId}` : typeof args.recipient === "string" ? `to ${args.recipient}` : "" : typeof args.jobId === "string" ? args.jobId : "";
2404
+ const children = [displayHeading(theme, `Subagent \xB7 ${LABELS[action]}`, target)];
2405
+ if (context.expanded) {
2406
+ for (const [key, value] of Object.entries(args)) {
2407
+ if (typeof value === "string") {
2408
+ children.push(displayDocument(theme, `${key}: ${value}`, "muted"));
2409
+ } else if (value !== void 0) {
2410
+ children.push(displayLine(theme, `${key}:`, "muted"), displayJson(theme, value));
2411
+ }
2412
+ }
2413
+ } else {
2414
+ const preview = action === "spawn" ? args.task : action === "send" ? args.message : void 0;
2415
+ if (typeof preview === "string") children.push(displayLine(theme, preview, "muted"));
2416
+ }
2417
+ return conversationView(children);
2418
+ },
2419
+ renderResult(result, { expanded, isPartial }, theme, context) {
2420
+ const raw = textContent(result.content);
2421
+ const children = [];
2422
+ if (context.isError) {
2423
+ children.push(
2424
+ displayLine(theme, `${LABELS[action]} failed`, "error"),
2425
+ expanded ? displayDocument(theme, raw, "error") : displayLine(theme, raw, "error")
2426
+ );
2427
+ } else if (isPartial) {
2428
+ return displayLine(theme, PENDING[action], "muted");
2429
+ } else {
2430
+ const payload = toolPayload(action, result);
2431
+ if (!payload) {
2432
+ children.push(
2433
+ expanded ? displayDocument(theme, raw || "No result returned.") : displayLine(theme, raw || "No result returned.")
2434
+ );
2435
+ } else if (action === "inspect") {
2436
+ children.push(...inspectSummary(theme, payload, expanded));
2437
+ if (expanded) children.push(displayJson(theme, payload));
2438
+ } else if (action === "send") {
2439
+ const accepted = payload.accepted === true;
2440
+ const reply = typeof asRecord(context.args)?.requestId === "string";
2441
+ children.push(
2442
+ displayLine(
2443
+ theme,
2444
+ `${reply ? "Reply" : "Request"} ${accepted ? "accepted" : "not accepted"}${payload.duplicate === true ? " \xB7 duplicate" : ""} \xB7 ${payload.requestId}`,
2445
+ accepted ? "success" : "warning"
2446
+ )
2447
+ );
2448
+ if (expanded) children.push(displayJson(theme, payload));
2449
+ } else {
2450
+ const job = jobPayload(payload);
2451
+ if (job) children.push(jobSummary(theme, job));
2452
+ if (expanded) {
2453
+ if (action === "wait" && job) {
2454
+ const { result: _result, error: _error, limitations: _limitations, ...metadata } = job;
2455
+ children.push(displayJson(theme, metadata), jobBody(theme, job));
2456
+ } else {
2457
+ children.push(displayJson(theme, payload));
2458
+ }
2459
+ }
2460
+ }
2461
+ }
2462
+ if (!expanded) children.push(expansionHint(theme, context));
2463
+ return conversationView(children);
2464
+ }
2465
+ };
2466
+ }
2467
+ function toolPayload(action, result) {
2468
+ for (const value of [result.details, parseRecord(textContent(result.content))]) {
2469
+ const record = asRecord(value);
2470
+ if (!record) continue;
2471
+ if (action === "inspect") {
2472
+ if (Array.isArray(record.jobs) && record.jobs.every((job) => jobPayload(job))) return record;
2473
+ } else if (action === "send") {
2474
+ if (typeof record.requestId === "string" && typeof record.accepted === "boolean") return record;
2475
+ } else if (jobPayload(record)) {
2476
+ return record;
2477
+ }
2478
+ }
2479
+ return void 0;
2480
+ }
2481
+ function inspectSummary(theme, payload, expanded) {
2482
+ const jobs = payload.jobs.map(jobPayload).filter((job) => job !== void 0);
2483
+ const counts = JOB_STATES.map((state) => {
2484
+ const count = jobs.filter((job) => job.state === state).length;
2485
+ return count ? `${count} ${stateLabel(state)}` : void 0;
2486
+ }).filter(Boolean);
2487
+ const children = [
2488
+ displayLine(
2489
+ theme,
2490
+ jobs.length ? `${jobs.length} job${jobs.length === 1 ? "" : "s"} \xB7 ${counts.join(" \xB7 ")}` : "No retained jobs.",
2491
+ "muted"
2492
+ )
2493
+ ];
2494
+ if (!expanded) {
2495
+ const sorted = [...jobs].sort((left, right) => STATE_PRIORITY[left.state] - STATE_PRIORITY[right.state]);
2496
+ for (const job of sorted.slice(0, INSPECT_PREVIEW_JOBS)) children.push(jobSummary(theme, job));
2497
+ if (jobs.length > INSPECT_PREVIEW_JOBS) {
2498
+ children.push(displayLine(theme, `${jobs.length - INSPECT_PREVIEW_JOBS} more jobs`, "muted"));
2499
+ }
2500
+ }
2501
+ const omitted = asRecord(payload.omitted)?.jobs;
2502
+ if (typeof omitted === "number" && omitted > 0) {
2503
+ children.push(displayLine(theme, `${omitted} expired or removed jobs not retained`, "muted"));
2504
+ }
2505
+ return children;
2506
+ }
2507
+
2230
2508
  // src/tools.ts
2231
2509
  var MAX_TASK_BYTES = 50 * 1024;
2232
2510
  var MAX_TOOLS = MAX_SELECTED_TOOLS;
@@ -2367,6 +2645,7 @@ function registerSubagentTools(pi, dependencies = {}) {
2367
2645
  pi.registerTool({
2368
2646
  name: "subagent_spawn",
2369
2647
  label: "Subagent \xB7 Spawn",
2648
+ ...subagentToolRenderers("spawn"),
2370
2649
  description: "Use subagent_spawn to start one Pi subagent job and return its jobId immediately. The task defines the child's specialization; selected core tools and explicit trusted local skills or extensions define its initial capabilities. The job may ask the main agent questions and publishes one asynchronous completion when terminal.",
2371
2650
  promptSnippet: "Use subagent_spawn to start one Pi subagent job",
2372
2651
  parameters: SpawnParameters,
@@ -2432,6 +2711,7 @@ function registerSubagentTools(pi, dependencies = {}) {
2432
2711
  pi.registerTool({
2433
2712
  name: "subagent_inspect",
2434
2713
  label: "Subagent \xB7 Inspect",
2714
+ ...subagentToolRenderers("inspect"),
2435
2715
  description: "Use subagent_inspect to return one privacy-filtered snapshot of retained jobs without exposing task text, complete child output, prompts, selected tools, context, credentials, or broker messages.",
2436
2716
  promptSnippet: "Use subagent_inspect to inspect retained subagent jobs",
2437
2717
  parameters: InspectParameters,
@@ -2444,6 +2724,7 @@ function registerSubagentTools(pi, dependencies = {}) {
2444
2724
  pi.registerTool({
2445
2725
  name: "subagent_cancel",
2446
2726
  label: "Subagent \xB7 Cancel",
2727
+ ...subagentToolRenderers("cancel"),
2447
2728
  description: "Use subagent_cancel to idempotently cancel one queued or running job and release its process, timer, broker credentials, and temporary resources. Terminal jobs remain unchanged.",
2448
2729
  promptSnippet: "Use subagent_cancel to cancel one active subagent job",
2449
2730
  parameters: CancelParameters,
@@ -2455,6 +2736,7 @@ function registerSubagentTools(pi, dependencies = {}) {
2455
2736
  pi.registerTool({
2456
2737
  name: "subagent_wait",
2457
2738
  label: "Subagent \xB7 Wait",
2739
+ ...subagentToolRenderers("wait"),
2458
2740
  description: "Use subagent_wait to wait for one job to become terminal. An incoming child request or response interrupts the wait without cancelling the job. A timeout or caller cancellation stops only this wait.",
2459
2741
  promptSnippet: "Use subagent_wait to wait for one subagent job or incoming message",
2460
2742
  parameters: WaitParameters,
@@ -2467,6 +2749,7 @@ function registerSubagentTools(pi, dependencies = {}) {
2467
2749
  pi.registerTool({
2468
2750
  name: "subagent_send",
2469
2751
  label: "Subagent \xB7 Send",
2752
+ ...subagentToolRenderers("send"),
2470
2753
  description: "Use subagent_send to send one request to an active job or answer one pending child request. For a new request, provide recipient and omit requestId. To answer a request, provide requestId and omit recipient. Provide exactly one of recipient or requestId. An accepted new request interrupts any active child response wait so delivery can proceed without consuming the child's original request.",
2471
2754
  promptSnippet: "Use subagent_send to send or answer one subagent message",
2472
2755
  parameters: SendParameters,
@@ -2655,7 +2938,7 @@ import {
2655
2938
  } from "@narumitw/pi-tui-kit/editor-status-view";
2656
2939
 
2657
2940
  // src/widget-renderer.ts
2658
- import { stripTerminalSequences, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
2941
+ import { stripTerminalSequences, truncateToWidth as truncateToWidth2, visibleWidth } from "@earendil-works/pi-tui";
2659
2942
  import { EditorStatusWidget } from "@narumitw/pi-tui-kit/editor-status-widget";
2660
2943
  var TASK_PREVIEW_WIDTH = 40;
2661
2944
  var SUBAGENT_WIDGET_RESERVED_ROWS = 8;
@@ -2714,7 +2997,7 @@ function renderJob(job, theme, settings, compact) {
2714
2997
  const state = theme.fg(running ? "accent" : "muted", job.state);
2715
2998
  const jobId = sanitizeSubagentDisplayText(job.jobId);
2716
2999
  const taskText = sanitizeSubagentDisplayText(job.task);
2717
- const task = stripTerminalSequences(truncateToWidth(taskText, TASK_PREVIEW_WIDTH, "\u2026"));
3000
+ const task = stripTerminalSequences(truncateToWidth2(taskText, TASK_PREVIEW_WIDTH, "\u2026"));
2718
3001
  const labels = [
2719
3002
  ...settings.showJobId && visibleWidth(jobId) > 0 ? [theme.fg("text", jobId)] : [],
2720
3003
  ...visibleWidth(task) > 0 ? [theme.fg("text", task)] : []