@danypops/pi-papyrus 0.60.1 → 0.60.2

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.
@@ -76,6 +76,7 @@ export class ArtifactListCard implements Component {
76
76
  const omitted = Math.max(0, this.details.total - display.length);
77
77
  if (omitted > 0) lines.push(truncateToWidth(this.theme.fg("dim", `${omitted} more · ${expandHint()}`), safeWidth));
78
78
  }
79
+ if (this.details.hasMore) lines.push(truncateToWidth(this.theme.fg("dim", "More pages available"), safeWidth));
79
80
  this.cachedWidth = safeWidth;
80
81
  this.cachedLines = lines;
81
82
  return lines;
@@ -188,7 +189,20 @@ export class TaskHierarchyPreview implements Component {
188
189
  safeWidth,
189
190
  );
190
191
  const tree = new TreeView({ nodes: toTreeNodes(this.details, this.theme, this.expanded) });
191
- const lines = [header, ...tree.render(safeWidth)];
192
+ const rendered = tree.render(safeWidth);
193
+ const displayed = rendered.slice(0, this.expanded ? 200 : TOOL_COLLAPSED_ROW_LIMIT);
194
+ const lines = [header, ...displayed];
195
+ if (rendered.length > displayed.length) {
196
+ lines.push(
197
+ truncateToWidth(
198
+ this.theme.fg("dim", `${rendered.length - displayed.length} more lines · ${this.expanded ? "display limit" : expandHint()}`),
199
+ safeWidth,
200
+ ),
201
+ );
202
+ }
203
+ if (this.details.nodeCompleteness.truncated || this.details.edgeCompleteness.truncated) {
204
+ lines.push(truncateToWidth(this.theme.fg("dim", "Partial graph · presentation limit"), safeWidth));
205
+ }
192
206
  this.cachedWidth = safeWidth;
193
207
  this.cachedLines = lines;
194
208
  return lines;
@@ -22,6 +22,7 @@ export interface ArtifactListToolDetails extends ToolDetailsBase {
22
22
  kind: "artifact-list";
23
23
  rows: ToolArtifactSummary[];
24
24
  total: number;
25
+ hasMore?: boolean;
25
26
  completeness: ResultCompleteness;
26
27
  }
27
28
 
@@ -83,6 +83,7 @@ export function parsePapyrusToolDetails(value: unknown): PapyrusToolDetails | un
83
83
  return isBoundedArray(value.rows, TOOL_DETAILS_MAX_ITEMS, isArtifactSummary) &&
84
84
  Number.isSafeInteger(value.total) &&
85
85
  Number(value.total) >= value.rows.length &&
86
+ (value.hasMore === undefined || typeof value.hasMore === "boolean") &&
86
87
  isCompleteness(value.completeness)
87
88
  ? (value as unknown as ArtifactListToolDetails)
88
89
  : undefined;
@@ -23,7 +23,7 @@ import type { JsonValue, VehicleOperationDescriptor } from "@danypops/vehicle-co
23
23
  import type { Theme } from "@earendil-works/pi-coding-agent";
24
24
  import { type Component, Text } from "@earendil-works/pi-tui";
25
25
  import { ArtifactCard } from "../../tool-rendering/artifact-card.ts";
26
- import { ArtifactListCard } from "../../tool-rendering/artifact-list.ts";
26
+ import { ArtifactListCard, TaskHierarchyPreview } from "../../tool-rendering/artifact-list.ts";
27
27
  import {
28
28
  createArtifactDetails,
29
29
  createArtifactListDetails,
@@ -67,6 +67,7 @@ import {
67
67
  renderNoFocusedTask,
68
68
  semanticText,
69
69
  } from "./shared.ts";
70
+ import { taskGraphPresentation, taskPagePresentation } from "./task-collections.ts";
70
71
  import { isTaskCompletion, renderTaskCompletion } from "./task-completion.ts";
71
72
  import { isTaskExecutionPlan, renderTaskExecutionPlan } from "./task-execution.ts";
72
73
 
@@ -155,6 +156,11 @@ export function papyrusVehicleRenderers(descriptor: VehicleOperationDescriptor):
155
156
  * instead of silently persisting and rendering raw JSON.
156
157
  */
157
158
  function projectPapyrusPresentation(descriptor: VehicleOperationDescriptor, output: unknown): PapyrusToolDetails {
159
+ if (descriptor.name === "tasks.graph" || descriptor.name === "tasks.list_page") {
160
+ const presentation = descriptor.name === "tasks.graph" ? taskGraphPresentation(output) : taskPagePresentation(output);
161
+ if (presentation) return presentation;
162
+ throw new Error(`${descriptor.name} produced no legal presentation variant`);
163
+ }
158
164
  if (descriptor.name === "batch.execute") {
159
165
  const summary = batchOutcomeSummary(output);
160
166
  if (summary !== undefined) return createSemanticTextDetails(descriptor.name, summary);
@@ -222,8 +228,9 @@ function renderFromPapyrusPresentation(
222
228
  return new Text(theme.fg("toolOutput", presentation.content), 0, 0);
223
229
  case "semantic-text":
224
230
  return new SemanticTextCard(presentation, theme, expanded);
225
- case "transition":
226
231
  case "graph":
232
+ return new TaskHierarchyPreview(presentation, theme, expanded);
233
+ case "transition":
227
234
  case "gate-run":
228
235
  case "invocation":
229
236
  case "error":
@@ -0,0 +1,64 @@
1
+ import { TASK_EXECUTION_MAX_EDGES, TASK_EXECUTION_MAX_NODES, TASK_LIST_PAGE_MAX_LIMIT, type TaskNode } from "@danypops/papyrus";
2
+ import { type ArtifactListToolDetails, createArtifactListDetails } from "../../tool-rendering/render-model/artifact.ts";
3
+ import { createGraphDetails, type GraphToolDetails, type ToolGraphEdge } from "../../tool-rendering/render-model/graph.ts";
4
+ import { isRecord } from "../../tool-rendering/render-model/shared.ts";
5
+ import { isArtifact, isArtifactArray } from "./shared.ts";
6
+
7
+ function identifiers(value: unknown): value is string[] {
8
+ return (
9
+ Array.isArray(value) &&
10
+ value.length <= TASK_EXECUTION_MAX_NODES &&
11
+ value.every((id) => typeof id === "string" && id.length > 0 && id.length <= 500)
12
+ );
13
+ }
14
+
15
+ function graphNode(value: unknown): value is TaskNode {
16
+ return (
17
+ isRecord(value) &&
18
+ isArtifact(value.task) &&
19
+ identifiers(value.parentIds) &&
20
+ identifiers(value.childIds) &&
21
+ identifiers(value.dependencyIds)
22
+ );
23
+ }
24
+
25
+ /** Projects one cursor page while keeping its continuation token in the operation result only. */
26
+ export function taskPagePresentation(output: unknown): ArtifactListToolDetails | undefined {
27
+ if (!isRecord(output) || !Array.isArray(output.items) || output.items.length > TASK_LIST_PAGE_MAX_LIMIT || !isArtifactArray(output.items))
28
+ return undefined;
29
+ if (
30
+ output.nextCursor !== undefined &&
31
+ (typeof output.nextCursor !== "string" || output.nextCursor.length === 0 || output.nextCursor.length > 16384)
32
+ )
33
+ return undefined;
34
+ return { ...createArtifactListDetails("tasks.list_page", output.items), hasMore: output.nextCursor !== undefined };
35
+ }
36
+
37
+ /** Projects selected tasks and their typed relationships into a bounded hierarchy. */
38
+ export function taskGraphPresentation(output: unknown): GraphToolDetails | undefined {
39
+ if (
40
+ !isRecord(output) ||
41
+ !Array.isArray(output.nodes) ||
42
+ output.nodes.length > TASK_EXECUTION_MAX_NODES ||
43
+ !identifiers(output.rootIds) ||
44
+ !output.nodes.every(graphNode)
45
+ )
46
+ return undefined;
47
+ const edges = new Map<string, ToolGraphEdge>();
48
+ let linkCount = 0;
49
+ for (const node of output.nodes) {
50
+ linkCount += node.parentIds.length + node.childIds.length + node.dependencyIds.length;
51
+ if (linkCount > TASK_EXECUTION_MAX_EDGES * 3) return undefined;
52
+ const add = (from: string, relation: string, to: string) => {
53
+ edges.set(JSON.stringify([from, relation, to]), { from, relation, to });
54
+ };
55
+ for (const parent of node.parentIds) add(parent, "contains", node.task.id);
56
+ for (const child of node.childIds) add(node.task.id, "contains", child);
57
+ for (const dependency of node.dependencyIds) add(dependency, "unlocks", node.task.id);
58
+ }
59
+ return createGraphDetails(
60
+ "tasks.graph",
61
+ output.nodes.map((node) => node.task),
62
+ [...edges.values()],
63
+ );
64
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-papyrus",
3
- "version": "0.60.1",
3
+ "version": "0.60.2",
4
4
  "description": "Pi host extension for Papyrus: native tools, TUI panels, and context injection over the daemon-backed graph store",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -13,14 +13,14 @@
13
13
  "typecheck": "tsc --noEmit -p tsconfig.json"
14
14
  },
15
15
  "peerDependencies": {
16
- "@danypops/vehicle-client-pi": "^0.46.0",
16
+ "@danypops/vehicle-client-pi": "^0.48.3",
17
17
  "@earendil-works/pi-coding-agent": "*",
18
18
  "@earendil-works/pi-tui": "*",
19
19
  "typebox": "*"
20
20
  },
21
21
  "dependencies": {
22
22
  "@danypops/jittor": "^0.19.2",
23
- "@danypops/papyrus": "^0.60.12",
23
+ "@danypops/papyrus": "^0.60.13",
24
24
  "@danypops/vehicle-client": "^0.10.8",
25
25
  "@danypops/vehicle-core": "^0.19.1",
26
26
  "@danypops/vehicle-server": "^0.27.1",
@@ -30,7 +30,7 @@
30
30
  "devDependencies": {
31
31
  "@danypops/pi-extension-harness": "^0.8.3",
32
32
  "@danypops/pi-tui-harness": "^0.0.2",
33
- "@danypops/vehicle-client-pi": "^0.46.0",
33
+ "@danypops/vehicle-client-pi": "^0.48.3",
34
34
  "@danypops/vehicle-conformance": "^0.3.0",
35
35
  "@earendil-works/pi-coding-agent": "^0.80.10",
36
36
  "bun-types": "latest",