@danypops/pi-papyrus 0.52.1 → 0.52.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.
|
@@ -46,7 +46,9 @@ import {
|
|
|
46
46
|
} from "../tool-rendering/render-model.ts";
|
|
47
47
|
import { recordRenderDiagnostic, shapeFingerprint } from "./render-diagnostics.ts";
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
/** Every field an Artifact and its lean list-default ArtifactSummary (tasks.list/docs.list/
|
|
50
|
+
* rules.list/playbooks.list without full:true -- see summarizeArtifact()) both always carry. */
|
|
51
|
+
function hasArtifactCoreFields(value: unknown): value is Omit<Artifact, "body" | "extra"> {
|
|
50
52
|
if (typeof value !== "object" || value === null) return false;
|
|
51
53
|
const row = value as Record<string, unknown>;
|
|
52
54
|
return (
|
|
@@ -55,15 +57,27 @@ function isArtifact(value: unknown): value is Artifact {
|
|
|
55
57
|
typeof row.title === "string" &&
|
|
56
58
|
typeof row.status === "string" &&
|
|
57
59
|
typeof row.subtype === "string" &&
|
|
58
|
-
typeof row.body === "string" &&
|
|
59
60
|
Array.isArray(row.labels) &&
|
|
60
61
|
typeof row.created_at === "string" &&
|
|
61
62
|
typeof row.updated_at === "string"
|
|
62
63
|
);
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
function isArtifact(value: unknown): value is Artifact {
|
|
67
|
+
return hasArtifactCoreFields(value) && typeof (value as Record<string, unknown>).body === "string";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Regression (real, live-observed): tasks.list's own documented default ("Returns a lean
|
|
72
|
+
* summary (no body/extra) unless full: true is passed") returns ArtifactSummary rows, which
|
|
73
|
+
* omit body entirely. createArtifactListDetails/artifactSummary (render-model.ts) never read
|
|
74
|
+
* .body for list rendering -- only single-artifact createArtifactDetails does -- so requiring
|
|
75
|
+
* body here (matching isArtifact) silently fell every default (lean, the common case) list
|
|
76
|
+
* call for tasks.list/docs.list/rules.list/playbooks.list through to the generic raw Vehicle
|
|
77
|
+
* table renderer instead of the curated ArtifactListCard.
|
|
78
|
+
*/
|
|
65
79
|
function isArtifactArray(value: unknown): value is Artifact[] {
|
|
66
|
-
return Array.isArray(value) && value.every(
|
|
80
|
+
return Array.isArray(value) && value.every(hasArtifactCoreFields);
|
|
67
81
|
}
|
|
68
82
|
|
|
69
83
|
/** tasks.focused/tasks.pause/tasks.unpause's own wrapper shape -- an Artifact
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-papyrus",
|
|
3
|
-
"version": "0.52.
|
|
3
|
+
"version": "0.52.3",
|
|
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"],
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"@danypops/jittor": "^0.14.0",
|
|
21
21
|
"@danypops/papyrus": "^0.52.0",
|
|
22
22
|
"@danypops/vehicle-client": "^0.7.0",
|
|
23
|
-
"@danypops/vehicle-client-pi": "^0.
|
|
23
|
+
"@danypops/vehicle-client-pi": "^0.27.1",
|
|
24
24
|
"@danypops/vehicle-core": "^0.12.3",
|
|
25
25
|
"@danypops/vehicle-server": "^0.18.1",
|
|
26
26
|
"beautiful-mermaid": "1.1.3",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"publishConfig": {
|
|
47
47
|
"access": "public"
|
|
48
48
|
},
|
|
49
|
-
"files": ["extension", "README.md"
|
|
49
|
+
"files": ["extension", "README.md"]
|
|
50
50
|
}
|
package/THIRD_PARTY_LICENSES.md
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# Third-party licenses
|
|
2
|
-
|
|
3
|
-
## pi-ask-user (MIT)
|
|
4
|
-
|
|
5
|
-
`extension/src/discuss-ask-view.ts` and `extension/src/discuss-ask-layout.ts` are substantially
|
|
6
|
-
adapted from [pi-ask-user](https://github.com/edlsh/pi-ask-user), used here as the interactive
|
|
7
|
-
UI for Discuss's own `live:true` synchronous ask.
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
MIT License
|
|
11
|
-
|
|
12
|
-
Copyright (c) 2026 Enzo Lucchesi
|
|
13
|
-
|
|
14
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
-
in the Software without restriction, including without limitation the rights
|
|
17
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
-
furnished to do so, subject to the following conditions:
|
|
20
|
-
|
|
21
|
-
The above copyright notice and this permission notice shall be included in all
|
|
22
|
-
copies or substantial portions of the Software.
|
|
23
|
-
|
|
24
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
-
SOFTWARE.
|
|
31
|
-
```
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* discuss-ask-layout.ts — row layout for the searchable single-select list used by Discuss's
|
|
3
|
-
* live:true ask UI (discuss-ask-view.ts). Wraps long option titles/descriptions to a target
|
|
4
|
-
* width and returns flat annotated rows, windowed around the current selection when the full
|
|
5
|
-
* list would overflow the available rows.
|
|
6
|
-
*
|
|
7
|
-
* Adapted from pi-ask-user's single-select-layout.ts (MIT, Copyright (c) 2026 Enzo Lucchesi --
|
|
8
|
-
* see THIRD_PARTY_LICENSES.md) so Discuss owns this UI directly instead of depending on that
|
|
9
|
-
* package at runtime.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
export interface AskOption {
|
|
13
|
-
title: string;
|
|
14
|
-
description?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface AnnotatedRow {
|
|
18
|
-
line: string;
|
|
19
|
-
selected: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface RenderSingleSelectRowsParams {
|
|
23
|
-
options: AskOption[];
|
|
24
|
-
selectedIndex: number;
|
|
25
|
-
width: number;
|
|
26
|
-
allowFreeform: boolean;
|
|
27
|
-
allowComment?: boolean;
|
|
28
|
-
commentEnabled?: boolean;
|
|
29
|
-
maxRows?: number;
|
|
30
|
-
hideDescriptions?: boolean;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function wrapText(text: string, width: number): string[] {
|
|
34
|
-
const normalized = text.replace(/\s+/g, " ").trim();
|
|
35
|
-
if (!normalized) return [""];
|
|
36
|
-
if (width <= 1) return normalized.split("");
|
|
37
|
-
|
|
38
|
-
const words = normalized.split(" ");
|
|
39
|
-
const lines: string[] = [];
|
|
40
|
-
let current = "";
|
|
41
|
-
|
|
42
|
-
for (const word of words) {
|
|
43
|
-
if (!current) {
|
|
44
|
-
if (word.length <= width) {
|
|
45
|
-
current = word;
|
|
46
|
-
} else {
|
|
47
|
-
for (let i = 0; i < word.length; i += width) {
|
|
48
|
-
lines.push(word.slice(i, i + width));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const candidate = `${current} ${word}`;
|
|
55
|
-
if (candidate.length <= width) {
|
|
56
|
-
current = candidate;
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
lines.push(current);
|
|
61
|
-
if (word.length <= width) {
|
|
62
|
-
current = word;
|
|
63
|
-
} else {
|
|
64
|
-
current = "";
|
|
65
|
-
for (let i = 0; i < word.length; i += width) {
|
|
66
|
-
const chunk = word.slice(i, i + width);
|
|
67
|
-
if (chunk.length === width || i + width < word.length) lines.push(chunk);
|
|
68
|
-
else current = chunk;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (current) lines.push(current);
|
|
74
|
-
return lines;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function padLine(prefix: string, content: string): string {
|
|
78
|
-
return `${prefix}${content}`.trimEnd();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
interface ItemBlock {
|
|
82
|
-
itemIndex: number;
|
|
83
|
-
lines: string[];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
type ListItem =
|
|
87
|
-
| { type: "option"; option: AskOption }
|
|
88
|
-
| { type: "comment-toggle"; option: AskOption }
|
|
89
|
-
| { type: "freeform"; option: AskOption };
|
|
90
|
-
|
|
91
|
-
function buildItemBlocks(
|
|
92
|
-
options: AskOption[],
|
|
93
|
-
width: number,
|
|
94
|
-
allowFreeform: boolean,
|
|
95
|
-
allowComment: boolean,
|
|
96
|
-
commentEnabled: boolean,
|
|
97
|
-
selectedIndex: number,
|
|
98
|
-
hideDescriptions = false,
|
|
99
|
-
): ItemBlock[] {
|
|
100
|
-
const normalizedWidth = Math.max(12, width);
|
|
101
|
-
const freeformLabel = "Type something. — Enter a custom response";
|
|
102
|
-
const commentToggleLabel = `${commentEnabled ? "[✓]" : "[ ]"} Add extra context after selection`;
|
|
103
|
-
const allItems: ListItem[] = options.map((option) => ({ type: "option", option }));
|
|
104
|
-
if (allowComment) allItems.push({ type: "comment-toggle", option: { title: commentToggleLabel } });
|
|
105
|
-
if (allowFreeform) allItems.push({ type: "freeform", option: { title: freeformLabel } });
|
|
106
|
-
|
|
107
|
-
return allItems.map((item, itemIndex) => {
|
|
108
|
-
const pointer = itemIndex === selectedIndex ? "→" : " ";
|
|
109
|
-
const lines: string[] = [];
|
|
110
|
-
|
|
111
|
-
if (item.type === "comment-toggle" || item.type === "freeform") {
|
|
112
|
-
const prefix = `${pointer} `;
|
|
113
|
-
const wrapped = wrapText(item.option.title, Math.max(8, normalizedWidth - prefix.length));
|
|
114
|
-
wrapped.forEach((line, lineIndex) => {
|
|
115
|
-
lines.push(padLine(lineIndex === 0 ? prefix : " ".repeat(prefix.length), line));
|
|
116
|
-
});
|
|
117
|
-
return { itemIndex, lines };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const numberPrefix = `${pointer} ${itemIndex + 1}. `;
|
|
121
|
-
const continuationPrefix = " ".repeat(numberPrefix.length);
|
|
122
|
-
const titleLines = wrapText(item.option.title, Math.max(8, normalizedWidth - numberPrefix.length));
|
|
123
|
-
titleLines.forEach((line, lineIndex) => {
|
|
124
|
-
lines.push(padLine(lineIndex === 0 ? numberPrefix : continuationPrefix, line));
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
if (item.option.description && !hideDescriptions) {
|
|
128
|
-
const descriptionPrefix = " ";
|
|
129
|
-
const descriptionLines = wrapText(item.option.description, Math.max(8, normalizedWidth - descriptionPrefix.length));
|
|
130
|
-
descriptionLines.forEach((line) => {
|
|
131
|
-
lines.push(padLine(descriptionPrefix, line));
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return { itemIndex, lines };
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function flatten(blocks: ItemBlock[], selectedIndex: number): AnnotatedRow[] {
|
|
140
|
-
return blocks.flatMap((block) => block.lines.map((line) => ({ line, selected: block.itemIndex === selectedIndex })));
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export function renderSingleSelectRows({
|
|
144
|
-
options,
|
|
145
|
-
selectedIndex,
|
|
146
|
-
width,
|
|
147
|
-
allowFreeform,
|
|
148
|
-
allowComment = false,
|
|
149
|
-
commentEnabled = false,
|
|
150
|
-
maxRows,
|
|
151
|
-
hideDescriptions,
|
|
152
|
-
}: RenderSingleSelectRowsParams): AnnotatedRow[] {
|
|
153
|
-
const itemCount = options.length + (allowComment ? 1 : 0) + (allowFreeform ? 1 : 0);
|
|
154
|
-
const blocks = buildItemBlocks(options, width, allowFreeform, allowComment, commentEnabled, selectedIndex, hideDescriptions);
|
|
155
|
-
const allRows = flatten(blocks, selectedIndex);
|
|
156
|
-
|
|
157
|
-
if (!Number.isFinite(maxRows) || !maxRows || maxRows <= 0 || allRows.length <= maxRows) return allRows;
|
|
158
|
-
|
|
159
|
-
const safeMaxRows = Math.max(1, Math.floor(maxRows));
|
|
160
|
-
const selectedBlock = blocks[selectedIndex] ?? blocks[0];
|
|
161
|
-
if (!selectedBlock) return [];
|
|
162
|
-
|
|
163
|
-
const indicator = ` (${selectedIndex + 1}/${itemCount})`;
|
|
164
|
-
const availableRows = safeMaxRows > 1 ? safeMaxRows - 1 : 1;
|
|
165
|
-
|
|
166
|
-
if (selectedBlock.lines.length >= availableRows) {
|
|
167
|
-
const visible = selectedBlock.lines.slice(0, availableRows).map((line) => ({ line, selected: true }));
|
|
168
|
-
if (safeMaxRows > 1) visible.push({ line: indicator, selected: false });
|
|
169
|
-
return visible.slice(0, safeMaxRows);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
let start = selectedIndex;
|
|
173
|
-
let end = selectedIndex + 1;
|
|
174
|
-
let usedRows = selectedBlock.lines.length;
|
|
175
|
-
|
|
176
|
-
while (true) {
|
|
177
|
-
const nextCanFit = end < blocks.length && usedRows + blocks[end]!.lines.length <= availableRows;
|
|
178
|
-
if (nextCanFit) {
|
|
179
|
-
usedRows += blocks[end]!.lines.length;
|
|
180
|
-
end += 1;
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
const prevCanFit = start > 0 && usedRows + blocks[start - 1]!.lines.length <= availableRows;
|
|
184
|
-
if (prevCanFit) {
|
|
185
|
-
start -= 1;
|
|
186
|
-
usedRows += blocks[start]!.lines.length;
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
break;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const visible = flatten(blocks.slice(start, end), selectedIndex);
|
|
193
|
-
visible.push({ line: indicator, selected: false });
|
|
194
|
-
return visible.slice(0, safeMaxRows);
|
|
195
|
-
}
|