@danypops/pi-papyrus 0.46.4 → 0.46.5
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.
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporary diagnostic instrumentation for the still-open /reload rendering-fallback
|
|
3
|
+
* investigation (papyrus task 4930cd9b): a Vehicle tool call executes and returns real
|
|
4
|
+
* data but renders as raw JSON (call args and result both) instead of through its
|
|
5
|
+
* registered renderCall/renderResult. Leading theory: a timing race between
|
|
6
|
+
* registerVehicleToolsWhenReady's fire-and-forget registration attempt and Pi resolving
|
|
7
|
+
* a tool's ToolDefinition (session.getToolDefinition(name), read live from Pi's own
|
|
8
|
+
* _toolDefinitions Map at the moment a tool-call message streams in -- see
|
|
9
|
+
* @earendil-works/pi-coding-agent's agent-session.js/interactive-mode.ts).
|
|
10
|
+
*
|
|
11
|
+
* Opt-in (PAPYRUS_RENDER_DIAG=1) and best-effort (never throws, never blocks a real
|
|
12
|
+
* call) -- appends JSONL to PAPYRUS_RENDER_DIAG_PATH (default
|
|
13
|
+
* ~/.cache/papyrus/render-diag.log). Content-safe: never logs artifact body/title text,
|
|
14
|
+
* only lengths and a small allow-listed set of short categorical fields (id/kind/status/
|
|
15
|
+
* subtype/alias), matching the same discipline as every other diagnostic log in this
|
|
16
|
+
* ecosystem (e.g. pi-web-spider's own diag.log).
|
|
17
|
+
*
|
|
18
|
+
* Remove once the investigation concludes either way.
|
|
19
|
+
*/
|
|
20
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
21
|
+
import { homedir } from "node:os";
|
|
22
|
+
import { dirname, join } from "node:path";
|
|
23
|
+
|
|
24
|
+
const SAFE_LITERAL_KEYS = new Set(["id", "kind", "status", "subtype", "alias"]);
|
|
25
|
+
|
|
26
|
+
function isEnabled(): boolean {
|
|
27
|
+
return process.env.PAPYRUS_RENDER_DIAG === "1";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function diagPath(): string {
|
|
31
|
+
return process.env.PAPYRUS_RENDER_DIAG_PATH ?? join(homedir(), ".cache", "papyrus", "render-diag.log");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Redacts to shape + length, never content -- except a small allow-listed set of short, non-sensitive categorical fields. */
|
|
35
|
+
export function shapeFingerprint(value: unknown, key?: string): unknown {
|
|
36
|
+
if (value === null || value === undefined) return value;
|
|
37
|
+
if (Array.isArray(value))
|
|
38
|
+
return { type: "array", length: value.length, sample: value.length > 0 ? shapeFingerprint(value[0]) : undefined };
|
|
39
|
+
if (typeof value === "string") return key && SAFE_LITERAL_KEYS.has(key) ? value : { type: "string", length: value.length };
|
|
40
|
+
if (typeof value === "boolean" || typeof value === "number") return value;
|
|
41
|
+
if (typeof value === "object") {
|
|
42
|
+
const row = value as Record<string, unknown>;
|
|
43
|
+
const fields: Record<string, unknown> = {};
|
|
44
|
+
for (const [fieldKey, fieldValue] of Object.entries(row)) fields[fieldKey] = shapeFingerprint(fieldValue, fieldKey);
|
|
45
|
+
return { type: "object", keys: Object.keys(row), fields };
|
|
46
|
+
}
|
|
47
|
+
return { type: typeof value };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function recordRenderDiagnostic(entry: Record<string, unknown>): void {
|
|
51
|
+
if (!isEnabled()) return;
|
|
52
|
+
try {
|
|
53
|
+
const path = diagPath();
|
|
54
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
55
|
+
appendFileSync(path, `${JSON.stringify({ ts: new Date().toISOString(), ...entry })}\n`);
|
|
56
|
+
} catch {
|
|
57
|
+
/* best-effort -- a broken diagnostic log must never break a real render or invocation */
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import type { Artifact } from "@danypops/papyrus";
|
|
16
16
|
import { TOOL_COLLAPSED_ROW_LIMIT } from "@danypops/papyrus";
|
|
17
17
|
import type { VehicleToolRenderers } from "@danypops/vehicle-client-pi";
|
|
18
|
-
import { renderVehicleResult } from "@danypops/vehicle-client-pi/vehicle-render";
|
|
18
|
+
import { renderVehicleCall, renderVehicleResult } from "@danypops/vehicle-client-pi/vehicle-render";
|
|
19
19
|
import type { VehicleOperationDescriptor } from "@danypops/vehicle-core";
|
|
20
20
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
21
21
|
import { type Component, Text, truncateToWidth } from "@earendil-works/pi-tui";
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
import { ArtifactCard, detailViewTheme, expandHint, measure, statusColor, statusGlyph } from "../tool-rendering/artifact-card.ts";
|
|
32
32
|
import { ArtifactListCard } from "../tool-rendering/artifact-list.ts";
|
|
33
33
|
import { type ArtifactFocusAnnotation, createArtifactDetails, createArtifactListDetails } from "../tool-rendering/render-model.ts";
|
|
34
|
+
import { recordRenderDiagnostic, shapeFingerprint } from "./render-diagnostics.ts";
|
|
34
35
|
|
|
35
36
|
function isArtifact(value: unknown): value is Artifact {
|
|
36
37
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -385,9 +386,29 @@ function renderTaskCompletion(result: TaskCompletionOutput, theme: Theme, expand
|
|
|
385
386
|
|
|
386
387
|
export function papyrusVehicleRenderers(descriptor: VehicleOperationDescriptor): VehicleToolRenderers {
|
|
387
388
|
return {
|
|
389
|
+
// Pure pass-through to the generic renderer -- the only reason this exists at all is
|
|
390
|
+
// the /reload investigation (papyrus task 4930cd9b): its absence from the diagnostic
|
|
391
|
+
// log for a real invocation (see onInvoked in vehicle-notes-client.ts) is itself
|
|
392
|
+
// evidence Pi never found ANY renderer -- ours or vehicle-client-pi's generic default
|
|
393
|
+
// -- for that specific tool call, distinct from this renderer running and choosing
|
|
394
|
+
// the generic path internally (which DOES show up here).
|
|
395
|
+
renderCall(args, theme, context) {
|
|
396
|
+
recordRenderDiagnostic({ event: "render-call-invoked", operation: descriptor.name });
|
|
397
|
+
return renderVehicleCall(descriptor, args, theme, context);
|
|
398
|
+
},
|
|
388
399
|
renderResult(result, options, theme, context) {
|
|
389
400
|
if (!options.isPartial && !context.isError) {
|
|
390
401
|
const output = (result.details as { output?: unknown } | undefined)?.output;
|
|
402
|
+
// /reload rendering-fallback investigation (papyrus task 4930cd9b) -- correlates
|
|
403
|
+
// against vehicle-notes-client.ts's onInvoked/vehicle-ready diagnostics by
|
|
404
|
+
// descriptor.name and wall-clock time.
|
|
405
|
+
recordRenderDiagnostic({
|
|
406
|
+
event: "render-result-dispatch",
|
|
407
|
+
operation: descriptor.name,
|
|
408
|
+
isArtifact: isArtifact(output),
|
|
409
|
+
isArtifactArray: isArtifactArray(output),
|
|
410
|
+
output: shapeFingerprint(output),
|
|
411
|
+
});
|
|
391
412
|
if (isArtifactArray(output)) {
|
|
392
413
|
return new ArtifactListCard(createArtifactListDetails(descriptor.name, output), theme, options.expanded);
|
|
393
414
|
}
|
|
@@ -428,6 +449,7 @@ export function papyrusVehicleRenderers(descriptor: VehicleOperationDescriptor):
|
|
|
428
449
|
if (isTaskCompletion(output)) {
|
|
429
450
|
return renderTaskCompletion(output, theme, options.expanded);
|
|
430
451
|
}
|
|
452
|
+
recordRenderDiagnostic({ event: "render-result-fell-through-to-generic", operation: descriptor.name });
|
|
431
453
|
}
|
|
432
454
|
return renderVehicleResult(descriptor, result, options, theme, context);
|
|
433
455
|
},
|
|
@@ -31,6 +31,7 @@ import { discussLiveFollowUp } from "../discuss/discuss-live-follow-up.ts";
|
|
|
31
31
|
import { currentVehicleClientTarget } from "../service-client.ts";
|
|
32
32
|
import { sessionSecretField } from "../session-identity.ts";
|
|
33
33
|
import { emitTaskFocusEvent } from "../task/task-focus-events.ts";
|
|
34
|
+
import { recordRenderDiagnostic, shapeFingerprint } from "./render-diagnostics.ts";
|
|
34
35
|
import { papyrusVehicleRenderers } from "./vehicle-artifact-renderers.ts";
|
|
35
36
|
|
|
36
37
|
const REGISTERED_PERMISSIONS = [
|
|
@@ -103,13 +104,21 @@ function notifyReadyEvent(event: VehicleReadyEvent): void {
|
|
|
103
104
|
* awaiting it is optional and mainly useful for tests.
|
|
104
105
|
*/
|
|
105
106
|
export function registerNotesVehicle(pi: ExtensionAPI): Promise<RegisteredPiVehicle | undefined> {
|
|
107
|
+
recordRenderDiagnostic({ event: "register-notes-vehicle-called" });
|
|
106
108
|
const client = createReconnectingVehicleClient(async () => {
|
|
107
109
|
const resolved = currentVehicleClientTarget();
|
|
108
110
|
if (!resolved) throw new Error("Papyrus daemon is not running");
|
|
109
111
|
return new RemoteVehicleClient({ baseUrl: resolved.baseUrl, token: resolved.token });
|
|
110
112
|
});
|
|
111
113
|
return registerVehicleToolsWhenReady(pi, () => Promise.resolve(currentVehicleClientTarget() ? client : undefined), {
|
|
112
|
-
log:
|
|
114
|
+
log: (event) => {
|
|
115
|
+
// Correlates against onInvoked's own timestamps below -- the /reload investigation's
|
|
116
|
+
// leading theory is a race between this fire-and-forget registration actually
|
|
117
|
+
// completing (this "registered" event) and Pi resolving a tool's ToolDefinition for
|
|
118
|
+
// rendering at the moment its tool-call message streams in.
|
|
119
|
+
recordRenderDiagnostic({ event: "vehicle-ready", kind: event.kind, ...("attempt" in event ? { attempt: event.attempt } : {}) });
|
|
120
|
+
notifyReadyEvent(event);
|
|
121
|
+
},
|
|
113
122
|
permissions: REGISTERED_PERMISSIONS,
|
|
114
123
|
principal: { id: "pi-papyrus" },
|
|
115
124
|
renderers: papyrusVehicleRenderers,
|
|
@@ -157,6 +166,9 @@ export function registerNotesVehicle(pi: ExtensionAPI): Promise<RegisteredPiVehi
|
|
|
157
166
|
// let those run before the human sees the prompt -- same reasoning here.
|
|
158
167
|
executionMode: (descriptor) => (descriptor.name === "discuss.open" || descriptor.name === "discuss.reply" ? "sequential" : undefined),
|
|
159
168
|
onInvoked: ({ descriptor }, output) => {
|
|
169
|
+
// See vehicle-notes-client.ts's log wiring above -- correlates a real invocation's
|
|
170
|
+
// timestamp against when registration actually completed.
|
|
171
|
+
recordRenderDiagnostic({ event: "invoked", operation: descriptor.name, output: shapeFingerprint(output) });
|
|
160
172
|
if (descriptor.name === "tasks.focus") {
|
|
161
173
|
const artifact = output as { id: string } | undefined;
|
|
162
174
|
if (artifact?.id) emitTaskFocusEvent({ taskId: artifact.id, status: "focused" });
|
package/package.json
CHANGED