@danypops/pi-papyrus 0.43.1 → 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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type Artifact,
|
|
2
3
|
TOOL_DETAILS_BODY_MAX_CHARACTERS,
|
|
3
4
|
TOOL_DETAILS_FIELD_MAX_CHARACTERS,
|
|
4
5
|
TOOL_DETAILS_MAX_EDGES,
|
|
@@ -6,7 +7,6 @@ import {
|
|
|
6
7
|
TOOL_DETAILS_MAX_SERIALIZED_CHARACTERS,
|
|
7
8
|
TOOL_DETAILS_ROW_OUTPUT_MAX_CHARACTERS,
|
|
8
9
|
TOOL_MODEL_CONTENT_MAX_CHARACTERS,
|
|
9
|
-
type Artifact,
|
|
10
10
|
} from "@danypops/papyrus";
|
|
11
11
|
|
|
12
12
|
export const PAPYRUS_TOOL_DETAILS_SCHEMA = "papyrus.tool-details/v1" as const;
|
|
@@ -198,11 +198,7 @@ export function createTransitionDetails(
|
|
|
198
198
|
};
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
export function createGraphDetails(
|
|
202
|
-
operation: string,
|
|
203
|
-
artifacts: readonly Artifact[],
|
|
204
|
-
edges: readonly ToolGraphEdge[],
|
|
205
|
-
): GraphToolDetails {
|
|
201
|
+
export function createGraphDetails(operation: string, artifacts: readonly Artifact[], edges: readonly ToolGraphEdge[]): GraphToolDetails {
|
|
206
202
|
const nodes = artifacts.slice(0, TOOL_DETAILS_MAX_ITEMS).map(artifactSummary);
|
|
207
203
|
const boundedEdges = edges.slice(0, TOOL_DETAILS_MAX_EDGES).map((edge) => ({ ...edge }));
|
|
208
204
|
return {
|
|
@@ -237,11 +233,7 @@ export function createGateRunDetails(
|
|
|
237
233
|
};
|
|
238
234
|
}
|
|
239
235
|
|
|
240
|
-
export function createInvocationDetails(
|
|
241
|
-
operation: string,
|
|
242
|
-
runId: string,
|
|
243
|
-
created: ToolInvocationCreated,
|
|
244
|
-
): InvocationToolDetails {
|
|
236
|
+
export function createInvocationDetails(operation: string, runId: string, created: ToolInvocationCreated): InvocationToolDetails {
|
|
245
237
|
const bounded: ToolInvocationCreated = {
|
|
246
238
|
tasks: created.tasks.slice(0, TOOL_DETAILS_MAX_ITEMS),
|
|
247
239
|
docs: created.docs.slice(0, TOOL_DETAILS_MAX_ITEMS),
|
|
@@ -318,13 +310,15 @@ function isCompleteness(value: unknown): value is ResultCompleteness {
|
|
|
318
310
|
}
|
|
319
311
|
|
|
320
312
|
function isArtifactSummary(value: unknown): value is ToolArtifactSummary {
|
|
321
|
-
return
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
313
|
+
return (
|
|
314
|
+
isRecord(value) &&
|
|
315
|
+
isBoundedString(value.id) &&
|
|
316
|
+
isBoundedString(value.kind) &&
|
|
317
|
+
isBoundedString(value.title) &&
|
|
318
|
+
isBoundedString(value.status) &&
|
|
319
|
+
isBoundedString(value.subtype) &&
|
|
320
|
+
isStringArray(value.labels)
|
|
321
|
+
);
|
|
328
322
|
}
|
|
329
323
|
|
|
330
324
|
function isToolArtifact(value: unknown): value is ToolArtifact {
|
|
@@ -332,10 +326,12 @@ function isToolArtifact(value: unknown): value is ToolArtifact {
|
|
|
332
326
|
const body = value.body;
|
|
333
327
|
const createdAt = value.createdAt;
|
|
334
328
|
const updatedAt = value.updatedAt;
|
|
335
|
-
return
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
329
|
+
return (
|
|
330
|
+
isArtifactSummary(value) &&
|
|
331
|
+
isBoundedString(body, TOOL_DETAILS_BODY_MAX_CHARACTERS) &&
|
|
332
|
+
isBoundedString(createdAt) &&
|
|
333
|
+
isBoundedString(updatedAt)
|
|
334
|
+
);
|
|
339
335
|
}
|
|
340
336
|
|
|
341
337
|
function isGraphEdge(value: unknown): value is ToolGraphEdge {
|
|
@@ -343,11 +339,13 @@ function isGraphEdge(value: unknown): value is ToolGraphEdge {
|
|
|
343
339
|
}
|
|
344
340
|
|
|
345
341
|
function isGateRow(value: unknown): value is ToolGateRow {
|
|
346
|
-
return
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
342
|
+
return (
|
|
343
|
+
isRecord(value) &&
|
|
344
|
+
typeof value.passed === "boolean" &&
|
|
345
|
+
isBoundedString(value.type) &&
|
|
346
|
+
isBoundedString(value.target) &&
|
|
347
|
+
isBoundedString(value.output, TOOL_DETAILS_ROW_OUTPUT_MAX_CHARACTERS)
|
|
348
|
+
);
|
|
351
349
|
}
|
|
352
350
|
|
|
353
351
|
function isBoundedArray<T>(value: unknown, maximum: number, predicate: (entry: unknown) => entry is T): value is T[] {
|
|
@@ -362,48 +360,64 @@ export function parsePapyrusToolDetails(value: unknown): PapyrusToolDetails | un
|
|
|
362
360
|
} catch {
|
|
363
361
|
return undefined;
|
|
364
362
|
}
|
|
365
|
-
if (
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
363
|
+
if (
|
|
364
|
+
serializedLength > TOOL_DETAILS_MAX_SERIALIZED_CHARACTERS ||
|
|
365
|
+
!isRecord(value) ||
|
|
366
|
+
value.schemaVersion !== PAPYRUS_TOOL_DETAILS_SCHEMA ||
|
|
367
|
+
!isBoundedString(value.operation) ||
|
|
368
|
+
!isBoundedString(value.kind)
|
|
369
|
+
)
|
|
370
|
+
return undefined;
|
|
369
371
|
|
|
370
372
|
switch (value.kind) {
|
|
371
373
|
case "artifact":
|
|
372
|
-
return isToolArtifact(value.artifact) && isCompleteness(value.completeness)
|
|
373
|
-
? value as unknown as ArtifactToolDetails : undefined;
|
|
374
|
+
return isToolArtifact(value.artifact) && isCompleteness(value.completeness) ? (value as unknown as ArtifactToolDetails) : undefined;
|
|
374
375
|
case "artifact-list":
|
|
375
|
-
return isBoundedArray(value.rows, TOOL_DETAILS_MAX_ITEMS, isArtifactSummary)
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
376
|
+
return isBoundedArray(value.rows, TOOL_DETAILS_MAX_ITEMS, isArtifactSummary) &&
|
|
377
|
+
Number.isSafeInteger(value.total) &&
|
|
378
|
+
Number(value.total) >= value.rows.length &&
|
|
379
|
+
isCompleteness(value.completeness)
|
|
380
|
+
? (value as unknown as ArtifactListToolDetails)
|
|
381
|
+
: undefined;
|
|
379
382
|
case "transition":
|
|
380
383
|
return isArtifactSummary(value.artifact) && isBoundedString(value.fromStatus) && isBoundedString(value.toStatus)
|
|
381
|
-
? value as unknown as TransitionToolDetails
|
|
384
|
+
? (value as unknown as TransitionToolDetails)
|
|
385
|
+
: undefined;
|
|
382
386
|
case "graph":
|
|
383
|
-
return isBoundedArray(value.nodes, TOOL_DETAILS_MAX_ITEMS, isArtifactSummary)
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
+
return isBoundedArray(value.nodes, TOOL_DETAILS_MAX_ITEMS, isArtifactSummary) &&
|
|
388
|
+
isBoundedArray(value.edges, TOOL_DETAILS_MAX_EDGES, isGraphEdge) &&
|
|
389
|
+
isCompleteness(value.nodeCompleteness) &&
|
|
390
|
+
isCompleteness(value.edgeCompleteness)
|
|
391
|
+
? (value as unknown as GraphToolDetails)
|
|
392
|
+
: undefined;
|
|
387
393
|
case "gate-run":
|
|
388
|
-
return isBoundedString(value.artifactId)
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
? value as unknown as GateRunToolDetails
|
|
394
|
+
return isBoundedString(value.artifactId) &&
|
|
395
|
+
isBoundedString(value.artifactTitle) &&
|
|
396
|
+
isBoundedArray(value.gates, TOOL_DETAILS_MAX_ITEMS, isGateRow) &&
|
|
397
|
+
isCompleteness(value.completeness)
|
|
398
|
+
? (value as unknown as GateRunToolDetails)
|
|
399
|
+
: undefined;
|
|
393
400
|
case "invocation": {
|
|
394
401
|
if (!isRecord(value.created)) return undefined;
|
|
395
|
-
return isBoundedString(value.runId)
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
402
|
+
return isBoundedString(value.runId) &&
|
|
403
|
+
isStringArray(value.created.tasks) &&
|
|
404
|
+
isStringArray(value.created.docs) &&
|
|
405
|
+
isStringArray(value.created.rules) &&
|
|
406
|
+
isStringArray(value.created.roots) &&
|
|
407
|
+
isCompleteness(value.completeness)
|
|
408
|
+
? (value as unknown as InvocationToolDetails)
|
|
409
|
+
: undefined;
|
|
400
410
|
}
|
|
401
411
|
case "preview":
|
|
402
|
-
return isBoundedString(value.title) &&
|
|
403
|
-
|
|
412
|
+
return isBoundedString(value.title) &&
|
|
413
|
+
isBoundedString(value.content, TOOL_DETAILS_BODY_MAX_CHARACTERS) &&
|
|
414
|
+
isCompleteness(value.completeness)
|
|
415
|
+
? (value as unknown as PreviewToolDetails)
|
|
416
|
+
: undefined;
|
|
404
417
|
case "error":
|
|
405
418
|
return isBoundedString(value.code) && isBoundedString(value.message, TOOL_DETAILS_BODY_MAX_CHARACTERS)
|
|
406
|
-
? value as unknown as ErrorToolDetails
|
|
419
|
+
? (value as unknown as ErrorToolDetails)
|
|
420
|
+
: undefined;
|
|
407
421
|
default:
|
|
408
422
|
return undefined;
|
|
409
423
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curated result rendering for Papyrus's own Vehicle-projected operations
|
|
3
|
+
* (notes.*, tasks.*, docs.*, rules.*, playbooks.*, artifact.*): reuses the
|
|
4
|
+
* same ArtifactCard/ArtifactListCard components the pre-Vehicle native tool
|
|
5
|
+
* used, instead of the generic Vehicle renderer's raw full-column table
|
|
6
|
+
* dump -- a human reading a task/note list doesn't need id/subtype/extra/
|
|
7
|
+
* timestamps up front, only title + status, with the rest available on
|
|
8
|
+
* expand. Detection is by output shape, not operation name: every operation
|
|
9
|
+
* registered through this client is one of Papyrus's own artifact domains,
|
|
10
|
+
* so "looks like an Artifact" is a safe, name-independent signal here.
|
|
11
|
+
* Falls back to the generic Vehicle renderer for any other output shape
|
|
12
|
+
* (progress, transitions, gate runs, errors).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { Artifact } from "@danypops/papyrus";
|
|
16
|
+
import type { VehicleToolRenderers } from "@danypops/vehicle-client-pi";
|
|
17
|
+
import { renderVehicleResult } from "@danypops/vehicle-client-pi/vehicle-render";
|
|
18
|
+
import type { VehicleOperationDescriptor } from "@danypops/vehicle-core";
|
|
19
|
+
import { ArtifactCard } from "./tool-rendering/artifact-card.ts";
|
|
20
|
+
import { ArtifactListCard } from "./tool-rendering/artifact-list.ts";
|
|
21
|
+
import { createArtifactDetails, createArtifactListDetails } from "./tool-rendering/render-model.ts";
|
|
22
|
+
|
|
23
|
+
function isArtifact(value: unknown): value is Artifact {
|
|
24
|
+
if (typeof value !== "object" || value === null) return false;
|
|
25
|
+
const row = value as Record<string, unknown>;
|
|
26
|
+
return (
|
|
27
|
+
typeof row.id === "string" &&
|
|
28
|
+
typeof row.kind === "string" &&
|
|
29
|
+
typeof row.title === "string" &&
|
|
30
|
+
typeof row.status === "string" &&
|
|
31
|
+
typeof row.subtype === "string" &&
|
|
32
|
+
typeof row.body === "string" &&
|
|
33
|
+
Array.isArray(row.labels) &&
|
|
34
|
+
typeof row.created_at === "string" &&
|
|
35
|
+
typeof row.updated_at === "string"
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isArtifactArray(value: unknown): value is Artifact[] {
|
|
40
|
+
return Array.isArray(value) && value.every(isArtifact);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function papyrusVehicleRenderers(descriptor: VehicleOperationDescriptor): VehicleToolRenderers {
|
|
44
|
+
return {
|
|
45
|
+
renderResult(result, options, theme, context) {
|
|
46
|
+
if (!options.isPartial && !context.isError) {
|
|
47
|
+
const output = (result.details as { output?: unknown } | undefined)?.output;
|
|
48
|
+
if (isArtifactArray(output)) {
|
|
49
|
+
return new ArtifactListCard(createArtifactListDetails(descriptor.name, output), theme, options.expanded);
|
|
50
|
+
}
|
|
51
|
+
if (isArtifact(output)) {
|
|
52
|
+
return new ArtifactCard(createArtifactDetails(descriptor.name, output), theme, options.expanded);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return renderVehicleResult(descriptor, result, options, theme, context);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -10,18 +10,37 @@
|
|
|
10
10
|
* Uses service-client.ts's currentVehicleClientTarget() (test-injectable) rather
|
|
11
11
|
* than resolveVehicleClientTarget() directly, so a test exercising the full
|
|
12
12
|
* extension entrypoint doesn't resolve a real daemonStateDir().
|
|
13
|
+
*
|
|
14
|
+
* The client itself is wrapped in createReconnectingVehicleClient(), re-resolving
|
|
15
|
+
* currentVehicleClientTarget() on every reconnect attempt rather than closing over
|
|
16
|
+
* one target captured here at session_start -- confirmed live: the daemon rebinds
|
|
17
|
+
* a new random port on every restart, and a bare RemoteVehicleClient built once had
|
|
18
|
+
* no way to notice its baseUrl had died, breaking every Vehicle tool call for the
|
|
19
|
+
* rest of the Pi session until a full extension reload.
|
|
13
20
|
*/
|
|
14
|
-
|
|
21
|
+
|
|
22
|
+
import { createReconnectingVehicleClient } from "@danypops/vehicle-client/daemon-client";
|
|
15
23
|
import { RemoteVehicleClient } from "@danypops/vehicle-client/http";
|
|
16
24
|
import { registerVehicleTools } from "@danypops/vehicle-client-pi";
|
|
25
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
17
26
|
import { currentVehicleClientTarget } from "./service-client.ts";
|
|
18
27
|
import { sessionSecretField } from "./session-identity.ts";
|
|
19
28
|
import { emitTaskFocusEvent } from "./task-focus-events.ts";
|
|
29
|
+
import { papyrusVehicleRenderers } from "./vehicle-artifact-renderers.ts";
|
|
20
30
|
|
|
21
31
|
const REGISTERED_PERMISSIONS = [
|
|
22
|
-
"notes:read",
|
|
23
|
-
"
|
|
24
|
-
"
|
|
32
|
+
"notes:read",
|
|
33
|
+
"notes:write",
|
|
34
|
+
"rules:read",
|
|
35
|
+
"rules:write",
|
|
36
|
+
"docs:read",
|
|
37
|
+
"docs:write",
|
|
38
|
+
"playbooks:read",
|
|
39
|
+
"playbooks:write",
|
|
40
|
+
"tasks:read",
|
|
41
|
+
"tasks:write",
|
|
42
|
+
"artifact:read",
|
|
43
|
+
"artifact:write",
|
|
25
44
|
];
|
|
26
45
|
|
|
27
46
|
/** Task Focus's own internal write needs a real, per-session secret -- see below. Every other tasks.* operation reads session_id purely for read-scoping and needs no secret. */
|
|
@@ -31,10 +50,15 @@ export async function registerNotesVehicle(pi: ExtensionAPI): Promise<void> {
|
|
|
31
50
|
const target = currentVehicleClientTarget();
|
|
32
51
|
if (!target) return;
|
|
33
52
|
try {
|
|
34
|
-
const client =
|
|
53
|
+
const client = createReconnectingVehicleClient(async () => {
|
|
54
|
+
const resolved = currentVehicleClientTarget();
|
|
55
|
+
if (!resolved) throw new Error("Papyrus daemon is not running");
|
|
56
|
+
return new RemoteVehicleClient({ baseUrl: resolved.baseUrl, token: resolved.token });
|
|
57
|
+
});
|
|
35
58
|
await registerVehicleTools(pi, client, {
|
|
36
59
|
permissions: REGISTERED_PERMISSIONS,
|
|
37
60
|
principal: { id: "pi-papyrus" },
|
|
61
|
+
renderers: papyrusVehicleRenderers,
|
|
38
62
|
// playbooks.invoke's own module handler, and tasks.focus/pause/unpause/clear_focus's
|
|
39
63
|
// own module handlers, authorize an internal Task Focus write via
|
|
40
64
|
// sessionIdentity.assertAuthorized(session_id, session_secret) -- see
|
|
@@ -52,7 +76,10 @@ export async function registerNotesVehicle(pi: ExtensionAPI): Promise<void> {
|
|
|
52
76
|
// explicitly overrides session_id to a DIFFERENT session never gets this
|
|
53
77
|
// session's secret smuggled in on its behalf.
|
|
54
78
|
const requestedSessionId = (input as { session_id?: unknown } | undefined)?.session_id;
|
|
55
|
-
const sessionId =
|
|
79
|
+
const sessionId =
|
|
80
|
+
typeof requestedSessionId === "string" && requestedSessionId.length > 0
|
|
81
|
+
? requestedSessionId
|
|
82
|
+
: context.sessionManager.getSessionId();
|
|
56
83
|
const { session_secret: sessionSecret } = sessionSecretField(sessionId);
|
|
57
84
|
// Omit sessionSecret entirely when nothing is cached (unregistered session) --
|
|
58
85
|
// {sessionSecret: null} would fail the module's own optionalString(input,
|
|
@@ -73,7 +100,8 @@ export async function registerNotesVehicle(pi: ExtensionAPI): Promise<void> {
|
|
|
73
100
|
}
|
|
74
101
|
if (descriptor.name === "tasks.pause" || descriptor.name === "tasks.unpause") {
|
|
75
102
|
const focus = output as { artifact: { id: string } } | undefined;
|
|
76
|
-
if (focus?.artifact?.id)
|
|
103
|
+
if (focus?.artifact?.id)
|
|
104
|
+
emitTaskFocusEvent({ taskId: focus.artifact.id, status: descriptor.name === "tasks.pause" ? "paused" : "unpaused" });
|
|
77
105
|
return;
|
|
78
106
|
}
|
|
79
107
|
if (descriptor.name === "tasks.clear_focus") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-papyrus",
|
|
3
|
-
"version": "0.43.
|
|
3
|
+
"version": "0.43.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"],
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@danypops/jittor": "^0.14.0",
|
|
21
21
|
"@danypops/papyrus": "^0.42.0",
|
|
22
|
-
"@danypops/vehicle-client": "^0.
|
|
23
|
-
"@danypops/vehicle-client-pi": "^0.3.
|
|
24
|
-
"@danypops/vehicle-core": "^0.
|
|
25
|
-
"@danypops/vehicle-server": "^0.
|
|
22
|
+
"@danypops/vehicle-client": "^0.2.0",
|
|
23
|
+
"@danypops/vehicle-client-pi": "^0.3.3",
|
|
24
|
+
"@danypops/vehicle-core": "^0.3.0",
|
|
25
|
+
"@danypops/vehicle-server": "^0.4.1",
|
|
26
26
|
"beautiful-mermaid": "1.1.3",
|
|
27
27
|
"malevich-tui-components": "^0.16.1"
|
|
28
28
|
},
|