@danypops/pi-papyrus 0.43.0 → 0.43.3
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/extension/src/artifact-browser.ts +54 -31
- package/extension/src/artifact-detail-view.ts +52 -42
- package/extension/src/artifact-format.ts +11 -5
- package/extension/src/artifact-relationship-lines.ts +4 -5
- package/extension/src/beautiful-mermaid-renderer.ts +3 -5
- package/extension/src/context-budget.ts +8 -5
- package/extension/src/context-hub-contribution.ts +6 -2
- package/extension/src/context-injection-telemetry.ts +2 -2
- package/extension/src/discuss-ask-layout.ts +3 -1
- package/extension/src/discuss-ask-view.ts +437 -111
- package/extension/src/discuss.ts +64 -15
- package/extension/src/discussion-detail-view.ts +44 -22
- package/extension/src/docs.ts +3 -2
- package/extension/src/domain-tools.ts +79 -34
- package/extension/src/index.ts +170 -66
- package/extension/src/markdown.ts +3 -7
- package/extension/src/note-widget.ts +1 -1
- package/extension/src/notes.ts +3 -8
- package/extension/src/playbook-bridge.ts +17 -6
- package/extension/src/playbooks.ts +17 -7
- package/extension/src/rules.ts +5 -5
- package/extension/src/service-client.ts +23 -8
- package/extension/src/skill-catalog-footprint.ts +1 -1
- package/extension/src/task-detail-format.ts +9 -9
- package/extension/src/task-detail-view.ts +36 -29
- package/extension/src/task-focus-events.ts +3 -2
- package/extension/src/task-graph.ts +16 -12
- package/extension/src/task-presentation.ts +2 -6
- package/extension/src/task-widget.ts +12 -8
- package/extension/src/tasks.ts +148 -57
- package/extension/src/tool-rendering/artifact-card.ts +1 -4
- package/extension/src/tool-rendering/artifact-list.ts +23 -24
- package/extension/src/tool-rendering/index.ts +2 -6
- package/extension/src/tool-rendering/render-model.ts +69 -55
- package/extension/src/vehicle-artifact-renderers.ts +58 -0
- package/extension/src/vehicle-notes-client.ts +35 -7
- package/package.json +5 -5
package/extension/src/tasks.ts
CHANGED
|
@@ -5,19 +5,20 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import { DynamicBorder, rawKeyHint } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import { Container, Input,
|
|
8
|
+
import { Container, Input, matchesKey, Spacer, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
9
9
|
import { callService } from "./service-client.ts";
|
|
10
|
-
import { emitTaskFocusEvent } from "./task-focus-events.ts";
|
|
11
10
|
import { sessionSecretField } from "./session-identity.ts";
|
|
12
11
|
import { showTaskDetails } from "./task-detail-view.ts";
|
|
12
|
+
import { emitTaskFocusEvent } from "./task-focus-events.ts";
|
|
13
13
|
import { showTaskGraph } from "./task-graph.ts";
|
|
14
14
|
|
|
15
15
|
export { taskDetailsText } from "./task-detail-format.ts";
|
|
16
16
|
export { showTaskDetails } from "./task-detail-view.ts";
|
|
17
|
+
|
|
17
18
|
import {
|
|
18
|
-
projectTaskExecution,
|
|
19
19
|
type Artifact,
|
|
20
20
|
type GateResult,
|
|
21
|
+
projectTaskExecution,
|
|
21
22
|
type TaskCompletion,
|
|
22
23
|
type TaskGraph,
|
|
23
24
|
type TaskHistoryPage,
|
|
@@ -39,7 +40,7 @@ type TaskRow = Artifact;
|
|
|
39
40
|
function taskChoiceLabels(tasks: readonly Artifact[]): string[] {
|
|
40
41
|
const titleCounts = new Map<string, number>();
|
|
41
42
|
for (const task of tasks) titleCounts.set(task.title, (titleCounts.get(task.title) ?? 0) + 1);
|
|
42
|
-
return tasks.map((task) => titleCounts.get(task.title)! > 1 ? `${task.title} (${task.id})` : task.title);
|
|
43
|
+
return tasks.map((task) => (titleCounts.get(task.title)! > 1 ? `${task.title} (${task.id})` : task.title));
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
export interface TaskHierarchyRow {
|
|
@@ -60,7 +61,13 @@ export function buildTaskHierarchy(graph: TaskGraph): TaskHierarchyRow[] {
|
|
|
60
61
|
if (!node) return;
|
|
61
62
|
visited.add(id);
|
|
62
63
|
const children = node.childIds.filter((childId) => byId.has(childId));
|
|
63
|
-
result.push({
|
|
64
|
+
result.push({
|
|
65
|
+
task: node.task,
|
|
66
|
+
depth,
|
|
67
|
+
childCount: children.length,
|
|
68
|
+
dependencies: [...node.dependencyIds],
|
|
69
|
+
active: node.active === true,
|
|
70
|
+
});
|
|
64
71
|
for (const childId of children) visit(childId, depth + 1);
|
|
65
72
|
};
|
|
66
73
|
for (const rootId of graph.rootIds) visit(rootId, 0);
|
|
@@ -68,7 +75,12 @@ export function buildTaskHierarchy(graph: TaskGraph): TaskHierarchyRow[] {
|
|
|
68
75
|
return result;
|
|
69
76
|
}
|
|
70
77
|
|
|
71
|
-
async function loadTaskGraph(
|
|
78
|
+
async function loadTaskGraph(
|
|
79
|
+
projectRoot: string,
|
|
80
|
+
sessionId: string,
|
|
81
|
+
scope?: "project" | "graph" | "all",
|
|
82
|
+
rootTaskId?: string,
|
|
83
|
+
): Promise<TaskGraph> {
|
|
72
84
|
return callService<Record<string, unknown>, TaskGraph>("tasks.graph", {
|
|
73
85
|
limit: 200,
|
|
74
86
|
project_root: projectRoot,
|
|
@@ -102,7 +114,10 @@ export async function showTasks(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
102
114
|
for (;;) {
|
|
103
115
|
const action = await renderPanel(ctx, graph);
|
|
104
116
|
if (!action) return;
|
|
105
|
-
if (action.type === "refresh") {
|
|
117
|
+
if (action.type === "refresh") {
|
|
118
|
+
graph = await loadTaskGraph(ctx.cwd, sessionId);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
106
121
|
if (action.type === "scope") {
|
|
107
122
|
const choice = await ctx.ui.select("Task scope", ["Current project", "Focused graph", "All projects"]);
|
|
108
123
|
if (!choice) continue;
|
|
@@ -110,7 +125,9 @@ export async function showTasks(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
110
125
|
let rootTaskId: string | undefined;
|
|
111
126
|
if (scope === "graph") {
|
|
112
127
|
const projectGraph = await loadTaskGraph(ctx.cwd, sessionId, "project");
|
|
113
|
-
const roots = projectGraph.rootIds
|
|
128
|
+
const roots = projectGraph.rootIds
|
|
129
|
+
.map((id) => projectGraph.nodes.find((node) => node.task.id === id)?.task)
|
|
130
|
+
.filter((task): task is Artifact => task !== undefined);
|
|
114
131
|
const rootLabels = taskChoiceLabels(roots);
|
|
115
132
|
const selected = await ctx.ui.select("Focused root or epic", rootLabels);
|
|
116
133
|
if (!selected) continue;
|
|
@@ -121,7 +138,10 @@ export async function showTasks(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
121
138
|
graph = await loadTaskGraph(ctx.cwd, sessionId);
|
|
122
139
|
continue;
|
|
123
140
|
}
|
|
124
|
-
if (action.type === "graph") {
|
|
141
|
+
if (action.type === "graph") {
|
|
142
|
+
await showTaskGraph(ctx, graph);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
125
145
|
if (action.type !== "action" || !action.row) continue;
|
|
126
146
|
|
|
127
147
|
const rowId = action.row.id;
|
|
@@ -143,19 +163,36 @@ export async function showTasks(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
143
163
|
|
|
144
164
|
if ((choice === "Remove dependency" || choice === "Remove from parent") && node) {
|
|
145
165
|
const relatedIds = choice === "Remove dependency" ? node.dependencyIds : node.parentIds;
|
|
146
|
-
const relatedTasks = relatedIds
|
|
166
|
+
const relatedTasks = relatedIds
|
|
167
|
+
.map((relatedId) => graph.nodes.find((entry) => entry.task.id === relatedId)?.task)
|
|
168
|
+
.filter((task): task is Artifact => task !== undefined);
|
|
147
169
|
const relatedTitles = taskChoiceLabels(relatedTasks);
|
|
148
|
-
const selected = await ctx.ui.select(
|
|
170
|
+
const selected = await ctx.ui.select(
|
|
171
|
+
choice === "Remove dependency" ? "Remove which dependency?" : "Remove from which parent?",
|
|
172
|
+
relatedTitles,
|
|
173
|
+
);
|
|
149
174
|
if (!selected) continue;
|
|
150
175
|
const relatedTask = relatedTasks[relatedTitles.indexOf(selected)];
|
|
151
176
|
if (!relatedTask) continue;
|
|
152
177
|
const relatedId = relatedTask.id;
|
|
153
178
|
try {
|
|
154
179
|
if (choice === "Remove dependency") {
|
|
155
|
-
await callService("tasks.undepend", {
|
|
180
|
+
await callService("tasks.undepend", {
|
|
181
|
+
id: action.row.id,
|
|
182
|
+
dependency_id: relatedId,
|
|
183
|
+
actor: "user",
|
|
184
|
+
source: "tasks-tui",
|
|
185
|
+
session_id: sessionId,
|
|
186
|
+
});
|
|
156
187
|
ctx.ui.notify(`Removed dependency on ${relatedTask.title}`, "info");
|
|
157
188
|
} else {
|
|
158
|
-
await callService("tasks.uncontain", {
|
|
189
|
+
await callService("tasks.uncontain", {
|
|
190
|
+
parent_id: relatedId,
|
|
191
|
+
child_id: action.row.id,
|
|
192
|
+
actor: "user",
|
|
193
|
+
source: "tasks-tui",
|
|
194
|
+
session_id: sessionId,
|
|
195
|
+
});
|
|
159
196
|
ctx.ui.notify(`Removed from parent ${relatedTask.title}`, "info");
|
|
160
197
|
}
|
|
161
198
|
} catch (error) {
|
|
@@ -167,7 +204,10 @@ export async function showTasks(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
167
204
|
|
|
168
205
|
if (choice === "Show details") {
|
|
169
206
|
const art = await callService<Record<string, unknown>, Artifact | null>("tasks.show", { id: action.row.id });
|
|
170
|
-
if (!art) {
|
|
207
|
+
if (!art) {
|
|
208
|
+
ctx.ui.notify("Not found", "error");
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
171
211
|
const history = await callService<Record<string, unknown>, TaskHistoryPage>("tasks.history", { id: art.id, direction: "desc" });
|
|
172
212
|
await showTaskDetails(ctx, art, graph, undefined, [...history.events].reverse());
|
|
173
213
|
} else if (choice === "Edit task") {
|
|
@@ -191,7 +231,13 @@ export async function showTasks(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
191
231
|
}
|
|
192
232
|
} else if (choice === "Make active") {
|
|
193
233
|
try {
|
|
194
|
-
const focused = await callService<Record<string, unknown>, Artifact>("tasks.focus", {
|
|
234
|
+
const focused = await callService<Record<string, unknown>, Artifact>("tasks.focus", {
|
|
235
|
+
id: action.row.id,
|
|
236
|
+
actor: "user",
|
|
237
|
+
source: "tasks-tui",
|
|
238
|
+
session_id: sessionId,
|
|
239
|
+
...sessionSecretField(sessionId),
|
|
240
|
+
});
|
|
195
241
|
emitTaskFocusEvent({ taskId: focused.id, sessionId, status: "focused" });
|
|
196
242
|
ctx.ui.notify(`Active: ${action.row.title}`, "info");
|
|
197
243
|
} catch (error) {
|
|
@@ -200,11 +246,21 @@ export async function showTasks(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
200
246
|
} else if (choice === "Pause focus" || choice === "Resume focus" || choice === "Clear focus") {
|
|
201
247
|
try {
|
|
202
248
|
if (choice === "Clear focus") {
|
|
203
|
-
await callService("tasks.clear_focus", {
|
|
249
|
+
await callService("tasks.clear_focus", {
|
|
250
|
+
actor: "user",
|
|
251
|
+
source: "tasks-tui",
|
|
252
|
+
session_id: sessionId,
|
|
253
|
+
...sessionSecretField(sessionId),
|
|
254
|
+
});
|
|
204
255
|
emitTaskFocusEvent({ taskId: null, sessionId, status: "cleared" });
|
|
205
256
|
} else {
|
|
206
257
|
const operation = choice === "Pause focus" ? "tasks.pause" : "tasks.unpause";
|
|
207
|
-
const result = await callService<Record<string, unknown>, { artifact: Artifact; status: string }>(operation, {
|
|
258
|
+
const result = await callService<Record<string, unknown>, { artifact: Artifact; status: string }>(operation, {
|
|
259
|
+
actor: "user",
|
|
260
|
+
source: "tasks-tui",
|
|
261
|
+
session_id: sessionId,
|
|
262
|
+
...sessionSecretField(sessionId),
|
|
263
|
+
});
|
|
208
264
|
emitTaskFocusEvent({ taskId: result.artifact.id, sessionId, status: choice === "Pause focus" ? "paused" : "unpaused" });
|
|
209
265
|
}
|
|
210
266
|
ctx.ui.notify(choice === "Clear focus" ? "Task focus cleared" : choice, "info");
|
|
@@ -213,34 +269,48 @@ export async function showTasks(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
213
269
|
}
|
|
214
270
|
} else if (choice === "Run gates") {
|
|
215
271
|
try {
|
|
216
|
-
const results = await callService<Record<string, unknown>, GateResult[]>("tasks.run_gates", {
|
|
217
|
-
|
|
272
|
+
const results = await callService<Record<string, unknown>, GateResult[]>("tasks.run_gates", {
|
|
273
|
+
id: action.row.id,
|
|
274
|
+
actor: "user",
|
|
275
|
+
source: "tasks-tui",
|
|
276
|
+
});
|
|
277
|
+
ctx.ui.notify(
|
|
278
|
+
`Gates:\n${results.map((gate) => `${gate.passed ? "✓" : "✗"} ${gate.gate.type}: ${gate.gate.target} — ${gate.output}`).join("\n")}`,
|
|
279
|
+
"info",
|
|
280
|
+
);
|
|
218
281
|
} catch (error) {
|
|
219
282
|
ctx.ui.notify(`Gates failed: ${error instanceof Error ? error.message : error}`, "error");
|
|
220
283
|
}
|
|
221
284
|
} else {
|
|
222
285
|
try {
|
|
223
|
-
const operation =
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
286
|
+
const operation =
|
|
287
|
+
choice === "Start"
|
|
288
|
+
? "tasks.start"
|
|
289
|
+
: choice === "Submit for review"
|
|
290
|
+
? "tasks.submit"
|
|
291
|
+
: choice === "Reject"
|
|
292
|
+
? "tasks.reject"
|
|
293
|
+
: choice === "Retry"
|
|
294
|
+
? "tasks.retry"
|
|
295
|
+
: choice === "Cancel"
|
|
296
|
+
? "tasks.cancel"
|
|
297
|
+
: "tasks.complete";
|
|
234
298
|
if (operation === "tasks.complete") {
|
|
235
|
-
const result = await callService<Record<string, unknown>, TaskCompletion>(operation, {
|
|
299
|
+
const result = await callService<Record<string, unknown>, TaskCompletion>(operation, {
|
|
300
|
+
id: action.row.id,
|
|
301
|
+
actor: "user",
|
|
302
|
+
source: "tasks-tui",
|
|
303
|
+
session_id: sessionId,
|
|
304
|
+
});
|
|
236
305
|
action.row.status = result.artifact.status;
|
|
237
306
|
const gates = result.gates.map((gate) => `${gate.passed ? "✓" : "✗"} ${gate.gate.type}: ${gate.gate.target}`).join("\n");
|
|
238
307
|
const checklist = result.checklist.map((item) => `${item.accepted ? "✓" : "✗"} proof: ${item.item}`).join("\n");
|
|
239
308
|
const focused = result.focused ? `\nActive: ${result.focused.title}` : "";
|
|
240
309
|
const taskById = new Map(graph.nodes.map((entry) => [entry.task.id, entry.task]));
|
|
241
|
-
const blocked =
|
|
242
|
-
|
|
243
|
-
|
|
310
|
+
const blocked =
|
|
311
|
+
result.blocked.length > 0
|
|
312
|
+
? `\nWaiting: ${result.blocked.map((entry) => `${entry.artifact.title} needs ${entry.dependencyIds.map((id) => taskById.get(id)?.title ?? "unknown task").join(", ")}`).join("; ")}`
|
|
313
|
+
: "";
|
|
244
314
|
ctx.ui.notify(
|
|
245
315
|
result.completed
|
|
246
316
|
? `Completed ${result.artifact.title}${focused}${blocked}${checklist ? `\n${checklist}` : ""}${gates ? `\n${gates}` : ""}`
|
|
@@ -248,7 +318,12 @@ export async function showTasks(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
248
318
|
result.completed ? "info" : "warning",
|
|
249
319
|
);
|
|
250
320
|
} else {
|
|
251
|
-
const updated = await callService<Record<string, unknown>, Artifact>(operation, {
|
|
321
|
+
const updated = await callService<Record<string, unknown>, Artifact>(operation, {
|
|
322
|
+
id: action.row.id,
|
|
323
|
+
actor: "user",
|
|
324
|
+
source: "tasks-tui",
|
|
325
|
+
session_id: sessionId,
|
|
326
|
+
});
|
|
252
327
|
action.row.status = updated.status;
|
|
253
328
|
ctx.ui.notify(`${updated.title} → [${updated.status}]`, "info");
|
|
254
329
|
}
|
|
@@ -279,9 +354,9 @@ function renderPanel(ctx: ExtensionCommandContext, graph: TaskGraph): Promise<Pa
|
|
|
279
354
|
|
|
280
355
|
function applyFilter(): void {
|
|
281
356
|
const q = searchInput.getValue().trim().toLowerCase();
|
|
282
|
-
filtered = q
|
|
283
|
-
task.title.toLowerCase().includes(q) || task.id.toLowerCase().includes(q)
|
|
284
|
-
|
|
357
|
+
filtered = q
|
|
358
|
+
? hierarchy.filter(({ task }) => task.title.toLowerCase().includes(q) || task.id.toLowerCase().includes(q))
|
|
359
|
+
: [...hierarchy];
|
|
285
360
|
selectedIndex = 0;
|
|
286
361
|
}
|
|
287
362
|
|
|
@@ -345,26 +420,28 @@ function renderPanel(ctx: ExtensionCommandContext, graph: TaskGraph): Promise<Pa
|
|
|
345
420
|
const execution = executionById.get(row.id);
|
|
346
421
|
const state = execution?.state ?? row.status;
|
|
347
422
|
const presentation = TASK_STATUS_PRESENTATION[row.status as TaskStatus];
|
|
348
|
-
const glyphStyled =
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
423
|
+
const glyphStyled =
|
|
424
|
+
state === "invalid"
|
|
425
|
+
? theme.fg("error", "!")
|
|
426
|
+
: presentation
|
|
427
|
+
? theme.fg(presentation.color, presentation.glyph)
|
|
428
|
+
: theme.fg("muted", "?");
|
|
353
429
|
const title = selected ? theme.bold(row.title) : row.title;
|
|
354
430
|
let laterSibling = false;
|
|
355
431
|
for (let candidate = i + 1; candidate < filtered.length; candidate++) {
|
|
356
432
|
if (filtered[candidate]!.depth < entry.depth) break;
|
|
357
|
-
if (filtered[candidate]!.depth === entry.depth) {
|
|
433
|
+
if (filtered[candidate]!.depth === entry.depth) {
|
|
434
|
+
laterSibling = true;
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
358
437
|
}
|
|
359
438
|
const connector = taskTreeConnector({
|
|
360
439
|
depth: entry.depth,
|
|
361
440
|
hasChildren: entry.childCount > 0,
|
|
362
441
|
hasLaterSibling: laterSibling,
|
|
363
442
|
});
|
|
364
|
-
const node = entry.depth === 0 && entry.childCount > 0
|
|
365
|
-
|
|
366
|
-
: theme.fg("dim", connector);
|
|
367
|
-
const gates = (row.extra?.["gates"] as any[])?.length;
|
|
443
|
+
const node = entry.depth === 0 && entry.childCount > 0 ? theme.fg("accent", connector) : theme.fg("dim", connector);
|
|
444
|
+
const gates = (row.extra?.gates as any[])?.length;
|
|
368
445
|
const relationParts: string[] = [];
|
|
369
446
|
if (execution) relationParts.push(execution.layer === null ? state : `layer ${execution.layer + 1} · ${state}`);
|
|
370
447
|
if (entry.childCount > 0) relationParts.push(`${entry.childCount} subtask${entry.childCount === 1 ? "" : "s"}`);
|
|
@@ -397,24 +474,38 @@ function renderPanel(ctx: ExtensionCommandContext, graph: TaskGraph): Promise<Pa
|
|
|
397
474
|
invalidate: () => container.invalidate(),
|
|
398
475
|
handleInput(data: string) {
|
|
399
476
|
if (searchActive) {
|
|
400
|
-
if (matchesKey(data, "escape")) {
|
|
401
|
-
|
|
402
|
-
|
|
477
|
+
if (matchesKey(data, "escape")) {
|
|
478
|
+
searchActive = false;
|
|
479
|
+
applyFilter();
|
|
480
|
+
} else if (matchesKey(data, "enter")) {
|
|
481
|
+
searchActive = false;
|
|
482
|
+
} else {
|
|
483
|
+
searchInput.handleInput(data);
|
|
484
|
+
applyFilter();
|
|
485
|
+
}
|
|
403
486
|
tui.requestRender();
|
|
404
487
|
return;
|
|
405
488
|
}
|
|
406
489
|
if (matchesKey(data, "up")) selectedIndex = (selectedIndex - 1 + filtered.length) % Math.max(filtered.length, 1);
|
|
407
490
|
else if (matchesKey(data, "down")) selectedIndex = (selectedIndex + 1) % Math.max(filtered.length, 1);
|
|
408
491
|
else if (data === "/") searchActive = true;
|
|
409
|
-
else if (data === "g") {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
else if (
|
|
492
|
+
else if (data === "g") {
|
|
493
|
+
done({ type: "graph" });
|
|
494
|
+
return;
|
|
495
|
+
} else if (data === "s") {
|
|
496
|
+
done({ type: "scope" });
|
|
497
|
+
return;
|
|
498
|
+
} else if (data === "r") {
|
|
499
|
+
done({ type: "refresh" });
|
|
500
|
+
return;
|
|
501
|
+
} else if (matchesKey(data, "enter")) {
|
|
413
502
|
const entry = filtered[selectedIndex];
|
|
414
503
|
if (entry) done({ type: "action", row: entry.task });
|
|
415
504
|
return;
|
|
416
|
-
} else if (matchesKey(data, "escape")) {
|
|
417
|
-
|
|
505
|
+
} else if (matchesKey(data, "escape")) {
|
|
506
|
+
done(undefined);
|
|
507
|
+
return;
|
|
508
|
+
} else return;
|
|
418
509
|
tui.requestRender();
|
|
419
510
|
},
|
|
420
511
|
};
|
|
@@ -96,10 +96,7 @@ export class ArtifactCard implements Component {
|
|
|
96
96
|
if (metadata) lines.push(truncateToWidth(this.theme.fg("muted", metadata), safeWidth));
|
|
97
97
|
if (artifact.body) lines.push(...wrapTextWithAnsi(artifact.body, safeWidth));
|
|
98
98
|
if (this.details.completeness.truncated) {
|
|
99
|
-
lines.push(truncateToWidth(
|
|
100
|
-
this.theme.fg("warning", `[truncated ${this.details.completeness.omitted} characters]`),
|
|
101
|
-
safeWidth,
|
|
102
|
-
));
|
|
99
|
+
lines.push(truncateToWidth(this.theme.fg("warning", `[truncated ${this.details.completeness.omitted} characters]`), safeWidth));
|
|
103
100
|
}
|
|
104
101
|
} else if (artifact.body || artifact.labels.length > 0) {
|
|
105
102
|
lines.push(truncateToWidth(this.theme.fg("dim", expandHint()), safeWidth));
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
+
import { TOOL_COLLAPSED_ROW_LIMIT } from "@danypops/papyrus";
|
|
1
2
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
3
|
import { type Component, truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
-
import { TOOL_COLLAPSED_ROW_LIMIT } from "@danypops/papyrus";
|
|
4
4
|
import { countSummary, expandHint, kindGlyph, statusGlyph, treeConnector } from "./artifact-card.ts";
|
|
5
|
-
import type {
|
|
6
|
-
ArtifactListToolDetails,
|
|
7
|
-
GraphToolDetails,
|
|
8
|
-
ToolArtifactSummary,
|
|
9
|
-
} from "./render-model.ts";
|
|
5
|
+
import type { ArtifactListToolDetails, GraphToolDetails, ToolArtifactSummary } from "./render-model.ts";
|
|
10
6
|
|
|
11
7
|
function pluralKind(rows: readonly ToolArtifactSummary[]): string {
|
|
12
8
|
const kind = rows[0]?.kind ?? "artifact";
|
|
@@ -25,11 +21,9 @@ function statusSummary(rows: readonly ToolArtifactSummary[]): string {
|
|
|
25
21
|
|
|
26
22
|
function rowLine(row: ToolArtifactSummary, expanded: boolean, theme: Theme): string {
|
|
27
23
|
const identity = expanded ? `${row.id} ` : "";
|
|
28
|
-
return [
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
theme.fg("text", row.title),
|
|
32
|
-
].join(" ");
|
|
24
|
+
return [theme.fg("muted", `${statusGlyph(row.status)} ${row.status}`), theme.fg("accent", identity), theme.fg("text", row.title)].join(
|
|
25
|
+
" ",
|
|
26
|
+
);
|
|
33
27
|
}
|
|
34
28
|
|
|
35
29
|
function rowMetadata(row: ToolArtifactSummary): string {
|
|
@@ -62,10 +56,9 @@ export class ArtifactListCard implements Component {
|
|
|
62
56
|
if (this.cachedLines && this.cachedWidth === safeWidth) return this.cachedLines;
|
|
63
57
|
const rows = this.details.rows;
|
|
64
58
|
const noun = pluralKind(rows);
|
|
65
|
-
const lines = [
|
|
66
|
-
this.theme.fg("toolTitle", this.theme.bold(`${countSummary(rows.length, this.details.total)} ${noun}`)),
|
|
67
|
-
|
|
68
|
-
)];
|
|
59
|
+
const lines = [
|
|
60
|
+
truncateToWidth(this.theme.fg("toolTitle", this.theme.bold(`${countSummary(rows.length, this.details.total)} ${noun}`)), safeWidth),
|
|
61
|
+
];
|
|
69
62
|
if (rows.length === 0) {
|
|
70
63
|
lines.push(truncateToWidth(this.theme.fg("dim", `No ${noun}.`), safeWidth));
|
|
71
64
|
} else {
|
|
@@ -116,7 +109,9 @@ function hierarchyRows(details: GraphToolDetails): HierarchyRow[] {
|
|
|
116
109
|
if (visited.has(node.id)) return;
|
|
117
110
|
visited.add(node.id);
|
|
118
111
|
rows.push({ node, prefix, connector });
|
|
119
|
-
const children = (childIds.get(node.id) ?? [])
|
|
112
|
+
const children = (childIds.get(node.id) ?? [])
|
|
113
|
+
.map((id) => byId.get(id))
|
|
114
|
+
.filter((child): child is ToolArtifactSummary => child !== undefined);
|
|
120
115
|
children.forEach((child, index) => {
|
|
121
116
|
const last = index === children.length - 1;
|
|
122
117
|
visit(child, `${prefix}${connector ? (connector === "└─" ? " " : "│ ") : ""}`, treeConnector(last));
|
|
@@ -152,16 +147,20 @@ export class TaskHierarchyPreview implements Component {
|
|
|
152
147
|
const safeWidth = Math.max(1, width);
|
|
153
148
|
if (this.cachedLines && this.cachedWidth === safeWidth) return this.cachedLines;
|
|
154
149
|
const rows = hierarchyRows(this.details);
|
|
155
|
-
const lines = [
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
150
|
+
const lines = [
|
|
151
|
+
truncateToWidth(
|
|
152
|
+
this.theme.fg("toolTitle", this.theme.bold(`${this.details.nodes.length} tasks · ${this.details.edges.length} edges`)),
|
|
153
|
+
safeWidth,
|
|
154
|
+
),
|
|
155
|
+
];
|
|
159
156
|
for (const row of rows) {
|
|
160
157
|
const identity = this.expanded ? `${row.node.id} ` : "";
|
|
161
|
-
lines.push(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
158
|
+
lines.push(
|
|
159
|
+
truncateToWidth(
|
|
160
|
+
`${row.prefix}${row.connector}${row.connector ? " " : ""}${this.theme.fg("accent", kindGlyph(row.node.kind))} ${this.theme.fg("muted", statusGlyph(row.node.status))} ${this.theme.fg("accent", identity)}${this.theme.fg("text", row.node.title)}`,
|
|
161
|
+
safeWidth,
|
|
162
|
+
),
|
|
163
|
+
);
|
|
165
164
|
if (this.expanded) {
|
|
166
165
|
const metadata = rowMetadata(row.node);
|
|
167
166
|
if (metadata) lines.push(truncateToWidth(this.theme.fg("dim", `${row.prefix} ${metadata}`), safeWidth));
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AgentToolResult,
|
|
3
|
-
Theme,
|
|
4
|
-
ToolRenderResultOptions,
|
|
5
|
-
} from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { AgentToolResult, Theme, ToolRenderResultOptions } from "@earendil-works/pi-coding-agent";
|
|
6
2
|
import { type Component, Text } from "@earendil-works/pi-tui";
|
|
7
3
|
import { ArtifactCard } from "./artifact-card.ts";
|
|
8
4
|
import { ArtifactListCard, TaskHierarchyPreview } from "./artifact-list.ts";
|
|
9
|
-
import {
|
|
5
|
+
import { type PapyrusToolDetails, parsePapyrusToolDetails } from "./render-model.ts";
|
|
10
6
|
|
|
11
7
|
const CALL_VALUE_MAX_CHARACTERS = 80;
|
|
12
8
|
|