@danypops/pi-papyrus 0.43.5 → 0.44.0
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.
|
@@ -20,13 +20,21 @@ const STATUS_GLYPHS: Readonly<Record<string, string>> = {
|
|
|
20
20
|
draft: "○",
|
|
21
21
|
archived: "·",
|
|
22
22
|
deprecated: "·",
|
|
23
|
+
// Task Execution's own state vocabulary (projectTaskExecution) --
|
|
24
|
+
// distinct from an artifact's own lifecycle status above (a task's
|
|
25
|
+
// literal `status` field is always todo/in-progress/review/rejected/
|
|
26
|
+
// done/canceled; ready/blocked/invalid only ever appear as the derived
|
|
27
|
+
// execution `state` a plan computes for a still-todo task).
|
|
28
|
+
ready: "▶",
|
|
29
|
+
blocked: "◼",
|
|
30
|
+
invalid: "!",
|
|
23
31
|
};
|
|
24
32
|
|
|
25
33
|
type SemanticColor = "success" | "error" | "warning" | "accent" | "muted";
|
|
26
34
|
|
|
27
|
-
function statusColor(status: string): SemanticColor {
|
|
28
|
-
if (status === "done" || status === "active") return "success";
|
|
29
|
-
if (status === "rejected" || status === "canceled") return "error";
|
|
35
|
+
export function statusColor(status: string): SemanticColor {
|
|
36
|
+
if (status === "done" || status === "active" || status === "ready") return "success";
|
|
37
|
+
if (status === "rejected" || status === "canceled" || status === "invalid") return "error";
|
|
30
38
|
if (status === "review") return "warning";
|
|
31
39
|
if (status === "in-progress") return "accent";
|
|
32
40
|
return "muted";
|
|
@@ -13,12 +13,14 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import type { Artifact } from "@danypops/papyrus";
|
|
16
|
+
import { TOOL_COLLAPSED_ROW_LIMIT } from "@danypops/papyrus";
|
|
16
17
|
import type { VehicleToolRenderers } from "@danypops/vehicle-client-pi";
|
|
17
18
|
import { renderVehicleResult } from "@danypops/vehicle-client-pi/vehicle-render";
|
|
18
19
|
import type { VehicleOperationDescriptor } from "@danypops/vehicle-core";
|
|
19
20
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
20
21
|
import { type Component, Text } from "@earendil-works/pi-tui";
|
|
21
|
-
import {
|
|
22
|
+
import { type DagEdge, type DagNode, DagView } from "malevich-tui-components";
|
|
23
|
+
import { ArtifactCard, expandHint, statusColor, statusGlyph } from "./tool-rendering/artifact-card.ts";
|
|
22
24
|
import { ArtifactListCard } from "./tool-rendering/artifact-list.ts";
|
|
23
25
|
import { type ArtifactFocusAnnotation, createArtifactDetails, createArtifactListDetails } from "./tool-rendering/render-model.ts";
|
|
24
26
|
|
|
@@ -71,6 +73,75 @@ function renderNoFocusedTask(theme: Theme): Component {
|
|
|
71
73
|
return new Text(theme.fg("dim", "No focused task."), 0, 0);
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
/** tasks.plan's own TaskExecutionPlan shape (projectTaskExecution) -- a
|
|
77
|
+
* genuinely structured topological-execution view, never artifact-shaped,
|
|
78
|
+
* detected the same name-independent way as the others in this file. */
|
|
79
|
+
interface TaskExecutionNodeOutput {
|
|
80
|
+
id: string;
|
|
81
|
+
title: string;
|
|
82
|
+
status: string;
|
|
83
|
+
active: boolean;
|
|
84
|
+
state: string;
|
|
85
|
+
layer: number | null;
|
|
86
|
+
prerequisiteIds: string[];
|
|
87
|
+
successorIds: string[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface TaskExecutionPlanOutput {
|
|
91
|
+
nodes: TaskExecutionNodeOutput[];
|
|
92
|
+
layers: string[][];
|
|
93
|
+
cycleIds: string[];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isTaskExecutionNode(value: unknown): value is TaskExecutionNodeOutput {
|
|
97
|
+
if (typeof value !== "object" || value === null) return false;
|
|
98
|
+
const row = value as Record<string, unknown>;
|
|
99
|
+
return (
|
|
100
|
+
typeof row.id === "string" &&
|
|
101
|
+
typeof row.title === "string" &&
|
|
102
|
+
typeof row.status === "string" &&
|
|
103
|
+
typeof row.active === "boolean" &&
|
|
104
|
+
typeof row.state === "string" &&
|
|
105
|
+
(row.layer === null || typeof row.layer === "number") &&
|
|
106
|
+
Array.isArray(row.prerequisiteIds) &&
|
|
107
|
+
Array.isArray(row.successorIds)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isTaskExecutionPlan(value: unknown): value is TaskExecutionPlanOutput {
|
|
112
|
+
if (typeof value !== "object" || value === null) return false;
|
|
113
|
+
const row = value as Record<string, unknown>;
|
|
114
|
+
return (
|
|
115
|
+
Array.isArray(row.nodes) &&
|
|
116
|
+
row.nodes.every(isTaskExecutionNode) &&
|
|
117
|
+
Array.isArray(row.layers) &&
|
|
118
|
+
row.layers.every((layer) => Array.isArray(layer) && layer.every((id) => typeof id === "string")) &&
|
|
119
|
+
Array.isArray(row.cycleIds) &&
|
|
120
|
+
row.cycleIds.every((id) => typeof id === "string")
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function renderTaskExecutionPlan(plan: TaskExecutionPlanOutput, theme: Theme, expanded: boolean): Component {
|
|
125
|
+
const nodes: DagNode[] = plan.nodes.map((node) => ({
|
|
126
|
+
id: node.id,
|
|
127
|
+
label: `${theme.fg(statusColor(node.state), statusGlyph(node.state))} ${theme.fg("text", node.title)}`,
|
|
128
|
+
}));
|
|
129
|
+
const edges: DagEdge[] = plan.nodes.flatMap((node) => node.prerequisiteIds.map((from) => ({ from, to: node.id })));
|
|
130
|
+
return new DagView({
|
|
131
|
+
layers: plan.layers,
|
|
132
|
+
nodes,
|
|
133
|
+
edges,
|
|
134
|
+
cycleIds: plan.cycleIds,
|
|
135
|
+
defaultStyle: (s) => theme.fg("text", s),
|
|
136
|
+
edgeStyle: (s) => theme.fg("dim", s),
|
|
137
|
+
layerHeaderStyle: (s) => theme.fg("toolTitle", theme.bold(s)),
|
|
138
|
+
cycleHeaderStyle: (s) => theme.fg("error", theme.bold(s)),
|
|
139
|
+
expanded,
|
|
140
|
+
visibleNodeCount: TOOL_COLLAPSED_ROW_LIMIT,
|
|
141
|
+
moreLine: (hiddenCount) => theme.fg("dim", `${hiddenCount} more · ${expandHint()}`),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
74
145
|
export function papyrusVehicleRenderers(descriptor: VehicleOperationDescriptor): VehicleToolRenderers {
|
|
75
146
|
return {
|
|
76
147
|
renderResult(result, options, theme, context) {
|
|
@@ -95,6 +166,9 @@ export function papyrusVehicleRenderers(descriptor: VehicleOperationDescriptor):
|
|
|
95
166
|
if (output === null && descriptor.name === "tasks.focused") {
|
|
96
167
|
return renderNoFocusedTask(theme);
|
|
97
168
|
}
|
|
169
|
+
if (isTaskExecutionPlan(output)) {
|
|
170
|
+
return renderTaskExecutionPlan(output, theme, options.expanded);
|
|
171
|
+
}
|
|
98
172
|
}
|
|
99
173
|
return renderVehicleResult(descriptor, result, options, theme, context);
|
|
100
174
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-papyrus",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.0",
|
|
4
4
|
"description": "Pi host extension for Papyrus: native tools, TUI panels, and context injection over the daemon-backed graph store",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": ["pi-package"],
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@danypops/jittor": "^0.14.0",
|
|
21
|
-
"@danypops/papyrus": "^0.
|
|
22
|
-
"@danypops/vehicle-client": "^0.
|
|
23
|
-
"@danypops/vehicle-client-pi": "^0.
|
|
24
|
-
"@danypops/vehicle-core": "^0.
|
|
25
|
-
"@danypops/vehicle-server": "^0.
|
|
21
|
+
"@danypops/papyrus": "^0.43.0",
|
|
22
|
+
"@danypops/vehicle-client": "^0.5.0",
|
|
23
|
+
"@danypops/vehicle-client-pi": "^0.8.1",
|
|
24
|
+
"@danypops/vehicle-core": "^0.10.0",
|
|
25
|
+
"@danypops/vehicle-server": "^0.11.0",
|
|
26
26
|
"beautiful-mermaid": "1.1.3",
|
|
27
27
|
"malevich-tui-components": "^0.20.2"
|
|
28
28
|
},
|