@danypops/pi-papyrus 0.59.2 → 0.59.4
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/index.ts +13 -12
- package/extension/src/tool-rendering/index.ts +2 -0
- package/extension/src/tool-rendering/render-model/index.ts +8 -1
- package/extension/src/tool-rendering/render-model/semantic-text.ts +20 -0
- package/extension/src/tools/renderers/index.ts +39 -25
- package/extension/src/tools/renderers/shared.ts +25 -0
- package/extension/src/tools/vehicle-notes-client.ts +1 -1
- package/package.json +1 -1
package/extension/src/index.ts
CHANGED
|
@@ -924,16 +924,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
924
924
|
// agent_settled is intentionally later than agent_end: Pi guarantees that
|
|
925
925
|
// retry, compaction retry, and queued follow-up processing have finished.
|
|
926
926
|
|
|
927
|
-
pi.on("input",
|
|
927
|
+
pi.on("input", (event, ctx) => {
|
|
928
928
|
if (event.source === "extension") return;
|
|
929
929
|
taskContinuation.onHumanInput();
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
930
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
931
|
+
// Input handlers are on Pi's prompt-submission path. Resume bookkeeping is
|
|
932
|
+
// opportunistic and must never hold the user's message behind daemon I/O.
|
|
933
|
+
void callService<Record<string, unknown>, { artifact: Artifact; status: string; pauseReason?: string } | null>("tasks.focused", {
|
|
934
|
+
session_id: sessionId,
|
|
935
|
+
})
|
|
936
|
+
.then(async (focus) => {
|
|
937
|
+
if (!focus || !shouldResumeFocusOnHumanInput(focus.status, focus.pauseReason)) return;
|
|
937
938
|
await callService("tasks.unpause", {
|
|
938
939
|
actor: "system",
|
|
939
940
|
source: "task-continuation",
|
|
@@ -947,10 +948,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
947
948
|
status: "unpaused",
|
|
948
949
|
effort: extractDeclaredEffort(focus.artifact.extra),
|
|
949
950
|
});
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
951
|
+
})
|
|
952
|
+
.catch(() => {
|
|
953
|
+
// The daemon may be unavailable during startup, reload, or shutdown.
|
|
954
|
+
});
|
|
954
955
|
});
|
|
955
956
|
pi.on("agent_start", () => {
|
|
956
957
|
taskContinuation.onAgentStart();
|
|
@@ -53,6 +53,8 @@ function simpleDetailsText(details: Exclude<PapyrusToolDetails, { kind: "artifac
|
|
|
53
53
|
].join("\n");
|
|
54
54
|
case "preview":
|
|
55
55
|
return `${details.title}\n${details.content}${details.completeness.truncated ? `\n[truncated ${details.completeness.omitted} characters]` : ""}`;
|
|
56
|
+
case "semantic-text":
|
|
57
|
+
return details.text;
|
|
56
58
|
case "error":
|
|
57
59
|
return `${details.code}: ${details.message}`;
|
|
58
60
|
case "execution-plan":
|
|
@@ -18,6 +18,7 @@ import { type GraphToolDetails, isGraphEdge } from "./graph.ts";
|
|
|
18
18
|
import type { LeaseToolDetails } from "./lease.ts";
|
|
19
19
|
import type { ErrorToolDetails, NoFocusToolDetails, PreviewToolDetails } from "./misc.ts";
|
|
20
20
|
import type { InvocationToolDetails, PlaybookInvocationToolDetails, PlaybookMissingArgumentsToolDetails } from "./playbook.ts";
|
|
21
|
+
import type { SemanticTextToolDetails } from "./semantic-text.ts";
|
|
21
22
|
import {
|
|
22
23
|
isArtifactSummary,
|
|
23
24
|
isBoundedArray,
|
|
@@ -51,7 +52,8 @@ export type PapyrusToolDetails =
|
|
|
51
52
|
| DiscussionToolDetails
|
|
52
53
|
| TaskCompletionToolDetails
|
|
53
54
|
| NoFocusToolDetails
|
|
54
|
-
| LeaseToolDetails
|
|
55
|
+
| LeaseToolDetails
|
|
56
|
+
| SemanticTextToolDetails;
|
|
55
57
|
|
|
56
58
|
/** Validate renderer details restored from session history before using them as typed presentation state. */
|
|
57
59
|
export function parsePapyrusToolDetails(value: unknown): PapyrusToolDetails | undefined {
|
|
@@ -176,6 +178,10 @@ export function parsePapyrusToolDetails(value: unknown): PapyrusToolDetails | un
|
|
|
176
178
|
(value.note === undefined || isBoundedString(value.note))
|
|
177
179
|
? (value as unknown as LeaseToolDetails)
|
|
178
180
|
: undefined;
|
|
181
|
+
case "semantic-text":
|
|
182
|
+
return isBoundedString(value.text, TOOL_DETAILS_BODY_MAX_CHARACTERS) && isCompleteness(value.completeness)
|
|
183
|
+
? (value as unknown as SemanticTextToolDetails)
|
|
184
|
+
: undefined;
|
|
179
185
|
default:
|
|
180
186
|
return undefined;
|
|
181
187
|
}
|
|
@@ -212,6 +218,7 @@ export {
|
|
|
212
218
|
type PlaybookMissingArgumentsToolDetails,
|
|
213
219
|
type ToolInvocationCreated,
|
|
214
220
|
} from "./playbook.ts";
|
|
221
|
+
export { createSemanticTextDetails, type SemanticTextToolDetails } from "./semantic-text.ts";
|
|
215
222
|
export {
|
|
216
223
|
type ArtifactFocusAnnotation,
|
|
217
224
|
createModelContent,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { TOOL_DETAILS_BODY_MAX_CHARACTERS } from "@danypops/papyrus";
|
|
2
|
+
import { boundedText, PAPYRUS_TOOL_DETAILS_SCHEMA, type ResultCompleteness, type ToolDetailsBase } from "./shared.ts";
|
|
3
|
+
|
|
4
|
+
/** Carries an operation's bounded semantic text channel for human presentation. */
|
|
5
|
+
export interface SemanticTextToolDetails extends ToolDetailsBase {
|
|
6
|
+
kind: "semantic-text";
|
|
7
|
+
text: string;
|
|
8
|
+
completeness: ResultCompleteness;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createSemanticTextDetails(operation: string, text: string): SemanticTextToolDetails {
|
|
12
|
+
const bounded = boundedText(text, TOOL_DETAILS_BODY_MAX_CHARACTERS);
|
|
13
|
+
return {
|
|
14
|
+
schemaVersion: PAPYRUS_TOOL_DETAILS_SCHEMA,
|
|
15
|
+
kind: "semantic-text",
|
|
16
|
+
operation,
|
|
17
|
+
text: bounded.value,
|
|
18
|
+
completeness: bounded.completeness,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -33,8 +33,9 @@ import {
|
|
|
33
33
|
createNoFocusDetails,
|
|
34
34
|
createPlaybookInvocationDetails,
|
|
35
35
|
createPlaybookMissingArgumentsDetails,
|
|
36
|
-
|
|
36
|
+
createSemanticTextDetails,
|
|
37
37
|
createTaskCompletionDetails,
|
|
38
|
+
type PapyrusToolDetails,
|
|
38
39
|
parsePapyrusToolDetails,
|
|
39
40
|
} from "../../tool-rendering/render-model.ts";
|
|
40
41
|
import { recordRenderDiagnostic, shapeFingerprint } from "../render-diagnostics.ts";
|
|
@@ -52,7 +53,16 @@ import {
|
|
|
52
53
|
renderPlaybookInvocationResult,
|
|
53
54
|
renderPlaybookMissingArguments,
|
|
54
55
|
} from "./playbook.ts";
|
|
55
|
-
import {
|
|
56
|
+
import {
|
|
57
|
+
boundedJsonPreview,
|
|
58
|
+
focusAnnotation,
|
|
59
|
+
isArtifact,
|
|
60
|
+
isArtifactArray,
|
|
61
|
+
isSemanticTextOutput,
|
|
62
|
+
isTaskFocus,
|
|
63
|
+
renderNoFocusedTask,
|
|
64
|
+
semanticText,
|
|
65
|
+
} from "./shared.ts";
|
|
56
66
|
import { isTaskCompletion, renderTaskCompletion } from "./task-completion.ts";
|
|
57
67
|
import { isTaskExecutionPlan, renderTaskExecutionPlan } from "./task-execution.ts";
|
|
58
68
|
|
|
@@ -133,17 +143,16 @@ export function papyrusVehicleRenderers(descriptor: VehicleOperationDescriptor):
|
|
|
133
143
|
* before Pi ever persists it -- the seam papyrusVehicleRenderers' own renderResult never had
|
|
134
144
|
* (it only converts shape at render time, from whatever the legacy {vehicle, output} path
|
|
135
145
|
* already persisted verbatim, lease tokens and all). Every branch here mirrors the same
|
|
136
|
-
* shape-detection papyrusVehicleRenderers's own renderResult uses,
|
|
137
|
-
*
|
|
138
|
-
*
|
|
146
|
+
* shape-detection papyrusVehicleRenderers's own renderResult uses, plus the server's explicit
|
|
147
|
+
* semantic text channel. An output matching no legal discriminated-union variant fails closed
|
|
148
|
+
* instead of silently persisting and rendering raw JSON.
|
|
139
149
|
*/
|
|
140
|
-
function projectPapyrusPresentation(descriptor: VehicleOperationDescriptor, output: unknown):
|
|
141
|
-
if (isArtifactArray(output)) return createArtifactListDetails(descriptor.name, output)
|
|
142
|
-
if (isArtifact(output)) return createArtifactDetails(descriptor.name, output)
|
|
143
|
-
if (isTaskFocus(output)) return createArtifactDetails(descriptor.name, output.artifact, focusAnnotation(output))
|
|
144
|
-
if (output === null && descriptor.name === "tasks.focused") return createNoFocusDetails(descriptor.name)
|
|
145
|
-
if (isTaskExecutionPlan(output))
|
|
146
|
-
return createExecutionPlanDetails(descriptor.name, output.nodes, output.layers, output.cycleIds) as unknown as JsonValue;
|
|
150
|
+
function projectPapyrusPresentation(descriptor: VehicleOperationDescriptor, output: unknown): PapyrusToolDetails {
|
|
151
|
+
if (isArtifactArray(output)) return createArtifactListDetails(descriptor.name, output);
|
|
152
|
+
if (isArtifact(output)) return createArtifactDetails(descriptor.name, output);
|
|
153
|
+
if (isTaskFocus(output)) return createArtifactDetails(descriptor.name, output.artifact, focusAnnotation(output));
|
|
154
|
+
if (output === null && descriptor.name === "tasks.focused") return createNoFocusDetails(descriptor.name);
|
|
155
|
+
if (isTaskExecutionPlan(output)) return createExecutionPlanDetails(descriptor.name, output.nodes, output.layers, output.cycleIds);
|
|
147
156
|
if (isPlaybookInvocationResult(output)) {
|
|
148
157
|
return createPlaybookInvocationDetails(descriptor.name, {
|
|
149
158
|
playbookId: output.playbookId,
|
|
@@ -152,18 +161,18 @@ function projectPapyrusPresentation(descriptor: VehicleOperationDescriptor, outp
|
|
|
152
161
|
rootTaskIds: output.rootTaskIds,
|
|
153
162
|
entryTaskId: output.entryTaskId,
|
|
154
163
|
execution: output.execution,
|
|
155
|
-
})
|
|
164
|
+
});
|
|
156
165
|
}
|
|
157
166
|
if (isPlaybookMissingArguments(output)) {
|
|
158
|
-
return createPlaybookMissingArgumentsDetails(descriptor.name, output.playbookId, output.missingArguments)
|
|
167
|
+
return createPlaybookMissingArgumentsDetails(descriptor.name, output.playbookId, output.missingArguments);
|
|
159
168
|
}
|
|
160
|
-
if (isDiscussionAndRounds(output))
|
|
161
|
-
|
|
162
|
-
if (
|
|
163
|
-
if (
|
|
164
|
-
if (
|
|
165
|
-
if (
|
|
166
|
-
|
|
169
|
+
if (isDiscussionAndRounds(output)) return createDiscussionDetails(descriptor.name, output.rounds, output.discussion);
|
|
170
|
+
if (isDiscussionRoundsOnly(output)) return createDiscussionDetails(descriptor.name, output.rounds);
|
|
171
|
+
if (isDiscussionListOutput(output)) return createArtifactListDetails(descriptor.name, output.discussions);
|
|
172
|
+
if (isTaskCompletion(output)) return createTaskCompletionDetails(descriptor.name, output);
|
|
173
|
+
if (isTaskLeaseView(output)) return createLeaseDetails(descriptor.name, output);
|
|
174
|
+
if (isSemanticTextOutput(output)) return createSemanticTextDetails(descriptor.name, semanticText(output));
|
|
175
|
+
throw new Error(`${descriptor.name} produced no legal presentation variant`);
|
|
167
176
|
}
|
|
168
177
|
|
|
169
178
|
function renderFromPapyrusPresentation(
|
|
@@ -194,15 +203,19 @@ function renderFromPapyrusPresentation(
|
|
|
194
203
|
return renderLease(presentation, theme);
|
|
195
204
|
case "preview":
|
|
196
205
|
return new Text(theme.fg("toolOutput", presentation.content), 0, 0);
|
|
206
|
+
case "semantic-text":
|
|
207
|
+
return new Text(theme.fg("toolOutput", presentation.text), 0, 0);
|
|
197
208
|
case "transition":
|
|
198
209
|
case "graph":
|
|
199
210
|
case "gate-run":
|
|
200
211
|
case "invocation":
|
|
201
212
|
case "error":
|
|
202
|
-
//
|
|
203
|
-
// (today's Papyrus Vehicle outputs never do) -- a bounded JSON preview is still a
|
|
204
|
-
// real, safe rendering rather than a crash.
|
|
213
|
+
// These variants are produced by native tools and remain valid when replayed through this shared renderer.
|
|
205
214
|
return new Text(theme.fg("toolOutput", boundedJsonPreview(presentation)), 0, 0);
|
|
215
|
+
default: {
|
|
216
|
+
const exhaustive: never = presentation;
|
|
217
|
+
return exhaustive;
|
|
218
|
+
}
|
|
206
219
|
}
|
|
207
220
|
}
|
|
208
221
|
|
|
@@ -218,7 +231,8 @@ export function papyrusVehiclePresentations(descriptor: VehicleOperationDescript
|
|
|
218
231
|
return {
|
|
219
232
|
projector: {
|
|
220
233
|
maxBytes: TOOL_DETAILS_MAX_SERIALIZED_CHARACTERS,
|
|
221
|
-
project: (output: unknown, _request: PiVehicleInvocationRequest) =>
|
|
234
|
+
project: (output: unknown, _request: PiVehicleInvocationRequest) =>
|
|
235
|
+
projectPapyrusPresentation(descriptor, output) as unknown as JsonValue,
|
|
222
236
|
},
|
|
223
237
|
renderResult(result, options, theme, context) {
|
|
224
238
|
if (!options.isPartial && !context.isError) {
|
|
@@ -72,6 +72,31 @@ export function renderNoFocusedTask(theme: Theme): Component {
|
|
|
72
72
|
return new Text(theme.fg("dim", "No focused task."), 0, 0);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
export interface SemanticTextOutput {
|
|
76
|
+
content: Array<{ type: "text"; text: string }>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Recognizes the server's explicit model-facing semantic channel without projecting sibling payload fields. */
|
|
80
|
+
export function isSemanticTextOutput(value: unknown): value is SemanticTextOutput {
|
|
81
|
+
if (typeof value !== "object" || value === null) return false;
|
|
82
|
+
const content = (value as Record<string, unknown>).content;
|
|
83
|
+
return (
|
|
84
|
+
Array.isArray(content) &&
|
|
85
|
+
content.length > 0 &&
|
|
86
|
+
content.every(
|
|
87
|
+
(block) =>
|
|
88
|
+
typeof block === "object" &&
|
|
89
|
+
block !== null &&
|
|
90
|
+
(block as Record<string, unknown>).type === "text" &&
|
|
91
|
+
typeof (block as Record<string, unknown>).text === "string",
|
|
92
|
+
)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function semanticText(output: SemanticTextOutput): string {
|
|
97
|
+
return output.content.map((block) => block.text).join("\n");
|
|
98
|
+
}
|
|
99
|
+
|
|
75
100
|
/** What renderDiscussionAndRounds/renderTaskCompletion actually read -- satisfied by both a raw
|
|
76
101
|
* Artifact (the live duck-typed output path) and the leaner projected ToolArtifactSummary (the
|
|
77
102
|
* typed-DTO path), with no cast needed at either call site. */
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
* had died.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import { createAgentNotifier } from "@danypops/vehicle-client-pi/agent-poll-ticker";
|
|
27
26
|
import { createReconnectingVehicleClient, daemonInstanceIdentity } from "@danypops/vehicle-client/daemon-client";
|
|
28
27
|
import { RemoteVehicleClient } from "@danypops/vehicle-client/http";
|
|
29
28
|
import {
|
|
@@ -32,6 +31,7 @@ import {
|
|
|
32
31
|
registerVehicleToolsWhenReady,
|
|
33
32
|
type VehicleReadyEvent,
|
|
34
33
|
} from "@danypops/vehicle-client-pi";
|
|
34
|
+
import { createAgentNotifier } from "@danypops/vehicle-client-pi/agent-poll-ticker";
|
|
35
35
|
import { VehicleApprovalOutcomePoll } from "@danypops/vehicle-client-pi/vehicle-approval-outcome-poll";
|
|
36
36
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
37
37
|
import { discussLiveFollowUp } from "../discuss/discuss-live-follow-up.ts";
|
package/package.json
CHANGED