@danypops/pi-papyrus 0.54.0 → 0.54.2

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.
@@ -1,827 +1,17 @@
1
- import {
2
- type Artifact,
3
- TOOL_DETAILS_BODY_MAX_CHARACTERS,
4
- TOOL_DETAILS_FIELD_MAX_CHARACTERS,
5
- TOOL_DETAILS_MAX_EDGES,
6
- TOOL_DETAILS_MAX_ITEMS,
7
- TOOL_DETAILS_MAX_SERIALIZED_CHARACTERS,
8
- TOOL_DETAILS_ROW_OUTPUT_MAX_CHARACTERS,
9
- TOOL_MODEL_CONTENT_MAX_CHARACTERS,
10
- } from "@danypops/papyrus";
11
-
12
- export const PAPYRUS_TOOL_DETAILS_SCHEMA = "papyrus.tool-details/v1" as const;
13
-
14
- export interface ResultCompleteness {
15
- truncated: boolean;
16
- omitted: number;
17
- }
18
-
19
- export interface ToolArtifactSummary {
20
- id: string;
21
- /** Optional only for a detail object persisted before this field existed -- ArtifactCard falls back to id when absent. */
22
- alias?: string;
23
- kind: string;
24
- title: string;
25
- status: string;
26
- subtype: string;
27
- labels: string[];
28
- }
29
-
30
- export interface ToolArtifact extends ToolArtifactSummary {
31
- body: string;
32
- createdAt: string;
33
- updatedAt: string;
34
- }
35
-
36
- interface ToolDetailsBase {
37
- schemaVersion: typeof PAPYRUS_TOOL_DETAILS_SCHEMA;
38
- operation: string;
39
- kind: string;
40
- }
41
-
42
- /** Distinct from the artifact's own lifecycle status (todo/in-progress/done/...) --
43
- * this is Task Focus's own separate active/paused dimension, carried by
44
- * tasks.focused/tasks.pause/tasks.unpause's {artifact, status, updatedAt}
45
- * wrapper shape. Never conflate the two: an artifact can be "in-progress"
46
- * while its focus is "paused". */
47
- export interface ArtifactFocusAnnotation {
48
- status: string;
49
- updatedAt: string;
50
- pauseReason?: string;
51
- }
52
-
53
- export interface ArtifactToolDetails extends ToolDetailsBase {
54
- kind: "artifact";
55
- artifact: ToolArtifact;
56
- completeness: ResultCompleteness;
57
- focus?: ArtifactFocusAnnotation;
58
- }
59
-
60
- export interface ArtifactListToolDetails extends ToolDetailsBase {
61
- kind: "artifact-list";
62
- rows: ToolArtifactSummary[];
63
- total: number;
64
- completeness: ResultCompleteness;
65
- }
66
-
67
- export interface TransitionToolDetails extends ToolDetailsBase {
68
- kind: "transition";
69
- artifact: ToolArtifactSummary;
70
- fromStatus: string;
71
- toStatus: string;
72
- }
73
-
74
- export interface ToolGraphEdge {
75
- from: string;
76
- relation: string;
77
- to: string;
78
- }
79
-
80
- export interface GraphToolDetails extends ToolDetailsBase {
81
- kind: "graph";
82
- nodes: ToolArtifactSummary[];
83
- edges: ToolGraphEdge[];
84
- nodeCompleteness: ResultCompleteness;
85
- edgeCompleteness: ResultCompleteness;
86
- }
87
-
88
- export interface ToolGateRow {
89
- passed: boolean;
90
- type: string;
91
- target: string;
92
- output: string;
93
- }
94
-
95
- export interface GateRunToolDetails extends ToolDetailsBase {
96
- kind: "gate-run";
97
- artifactId: string;
98
- artifactTitle: string;
99
- gates: ToolGateRow[];
100
- completeness: ResultCompleteness;
101
- }
102
-
103
- export interface ToolInvocationCreated {
104
- tasks: string[];
105
- docs: string[];
106
- rules: string[];
107
- roots: string[];
108
- }
109
-
110
- export interface InvocationToolDetails extends ToolDetailsBase {
111
- kind: "invocation";
112
- runId: string;
113
- created: ToolInvocationCreated;
114
- completeness: ResultCompleteness;
115
- }
116
-
117
- export interface PreviewToolDetails extends ToolDetailsBase {
118
- kind: "preview";
119
- title: string;
120
- content: string;
121
- completeness: ResultCompleteness;
122
- }
123
-
124
- export interface ErrorToolDetails extends ToolDetailsBase {
125
- kind: "error";
126
- code: string;
127
- message: string;
128
- }
129
-
130
- export interface ExecutionPlanNode {
131
- id: string;
132
- title: string;
133
- status: string;
134
- active: boolean;
135
- state: string;
136
- layer: number | null;
137
- prerequisiteIds: string[];
138
- successorIds: string[];
139
- }
140
-
141
- export interface ExecutionPlanToolDetails extends ToolDetailsBase {
142
- kind: "execution-plan";
143
- nodes: ExecutionPlanNode[];
144
- layers: string[][];
145
- cycleIds: string[];
146
- completeness: ResultCompleteness;
147
- }
148
-
149
- export interface PlaybookInvocationCreated {
150
- docs: string[];
151
- rules: string[];
152
- tasks: string[];
153
- }
154
-
155
- export interface PlaybookInvocationToolDetails extends ToolDetailsBase {
156
- kind: "playbook-invocation";
157
- playbookId: string;
158
- runId: string;
159
- created: PlaybookInvocationCreated;
160
- rootTaskIds: string[];
161
- entryTaskId: string;
162
- execution: { nodes: ExecutionPlanNode[]; layers: string[][]; cycleIds: string[] };
163
- completeness: ResultCompleteness;
164
- }
165
-
166
- export interface PlaybookMissingArgumentsToolDetails extends ToolDetailsBase {
167
- kind: "playbook-missing-arguments";
168
- playbookId: string;
169
- missingArguments: string[];
170
- }
171
-
172
- export interface DiscussionRoundSummary {
173
- roundNumber: number;
174
- actor: string;
175
- content: string;
176
- }
177
-
178
- export interface DiscussionToolDetails extends ToolDetailsBase {
179
- kind: "discussion";
180
- /** Absent for discuss.rounds, which returns rounds with no parent discussion in its own output. */
181
- discussion?: ToolArtifactSummary;
182
- rounds: DiscussionRoundSummary[];
183
- completeness: ResultCompleteness;
184
- }
185
-
186
- export interface TaskGateResultSummary {
187
- passed: boolean;
188
- output: string;
189
- }
190
-
191
- export interface TaskChecklistReviewSummary {
192
- item: string;
193
- accepted: boolean;
194
- reason?: string;
195
- }
196
-
197
- export interface TaskBlockageSummary {
198
- artifact: ToolArtifactSummary;
199
- dependencyIds: string[];
200
- }
201
-
202
- export interface TaskCompletionToolDetails extends ToolDetailsBase {
203
- kind: "task-completion";
204
- artifact: ToolArtifactSummary;
205
- gates: TaskGateResultSummary[];
206
- checklist: TaskChecklistReviewSummary[];
207
- completed: boolean;
208
- focused?: ToolArtifactSummary;
209
- blocked: TaskBlockageSummary[];
210
- completeness: ResultCompleteness;
211
- }
212
-
213
- export interface NoFocusToolDetails extends ToolDetailsBase {
214
- kind: "no-focus";
215
- }
216
-
217
- /** tasks.claim/heartbeat_lease/release_lease/lease's own name-first view -- deliberately never carries the raw lease token; the model channel (which needs the real token for a later heartbeat/release call) is a separate, independent channel from this persisted, human-facing one. */
218
- export interface LeaseToolDetails extends ToolDetailsBase {
219
- kind: "lease";
220
- taskName: string;
221
- taskTitle: string;
222
- owner: string;
223
- claimedAt: string;
224
- leaseExpiresAt: string;
225
- heartbeatAt?: string;
226
- note?: string;
227
- }
228
-
229
- export type PapyrusToolDetails =
230
- | ArtifactToolDetails
231
- | ArtifactListToolDetails
232
- | TransitionToolDetails
233
- | GraphToolDetails
234
- | GateRunToolDetails
235
- | InvocationToolDetails
236
- | PreviewToolDetails
237
- | ErrorToolDetails
238
- | ExecutionPlanToolDetails
239
- | PlaybookInvocationToolDetails
240
- | PlaybookMissingArgumentsToolDetails
241
- | DiscussionToolDetails
242
- | TaskCompletionToolDetails
243
- | NoFocusToolDetails
244
- | LeaseToolDetails;
245
-
246
- export interface ModelContent {
247
- text: string;
248
- truncated: boolean;
249
- omitted: number;
250
- }
251
-
252
- function completeness(total: number, returned: number): ResultCompleteness {
253
- const omitted = Math.max(0, total - returned);
254
- return { truncated: omitted > 0, omitted };
255
- }
256
-
257
- function boundedText(value: string, maximum: number): { value: string; completeness: ResultCompleteness } {
258
- const clipped = value.slice(0, maximum);
259
- return { value: clipped, completeness: completeness(value.length, clipped.length) };
260
- }
261
-
262
- type ArtifactSummarySource = Pick<Artifact, "id" | "kind" | "title" | "status" | "subtype" | "labels"> & { alias?: string };
263
-
264
- function artifactSummary(artifact: ArtifactSummarySource): ToolArtifactSummary {
265
- return {
266
- id: artifact.id,
267
- alias: artifact.alias,
268
- kind: artifact.kind,
269
- title: artifact.title,
270
- status: artifact.status,
271
- subtype: artifact.subtype,
272
- labels: artifact.labels.slice(0, TOOL_DETAILS_MAX_ITEMS),
273
- };
274
- }
275
-
276
- export function createArtifactDetails(operation: string, artifact: Artifact, focus?: ArtifactFocusAnnotation): ArtifactToolDetails {
277
- const body = boundedText(artifact.body, TOOL_DETAILS_BODY_MAX_CHARACTERS);
278
- return {
279
- schemaVersion: PAPYRUS_TOOL_DETAILS_SCHEMA,
280
- kind: "artifact",
281
- operation,
282
- artifact: {
283
- ...artifactSummary(artifact),
284
- body: body.value,
285
- createdAt: artifact.created_at,
286
- updatedAt: artifact.updated_at,
287
- },
288
- completeness: body.completeness,
289
- ...(focus ? { focus } : {}),
290
- };
291
- }
292
-
293
- export function createArtifactListDetails(
294
- operation: string,
295
- artifacts: readonly Artifact[],
296
- total = artifacts.length,
297
- ): ArtifactListToolDetails {
298
- const rows = artifacts.slice(0, TOOL_DETAILS_MAX_ITEMS).map(artifactSummary);
299
- return {
300
- schemaVersion: PAPYRUS_TOOL_DETAILS_SCHEMA,
301
- kind: "artifact-list",
302
- operation,
303
- rows,
304
- total,
305
- completeness: completeness(Math.max(total, artifacts.length), rows.length),
306
- };
307
- }
308
-
309
- export function createTransitionDetails(
310
- operation: string,
311
- artifact: Artifact,
312
- fromStatus: string,
313
- toStatus: string,
314
- ): TransitionToolDetails {
315
- return {
316
- schemaVersion: PAPYRUS_TOOL_DETAILS_SCHEMA,
317
- kind: "transition",
318
- operation,
319
- artifact: artifactSummary(artifact),
320
- fromStatus,
321
- toStatus,
322
- };
323
- }
324
-
325
- export function createGraphDetails(operation: string, artifacts: readonly Artifact[], edges: readonly ToolGraphEdge[]): GraphToolDetails {
326
- const nodes = artifacts.slice(0, TOOL_DETAILS_MAX_ITEMS).map(artifactSummary);
327
- const boundedEdges = edges.slice(0, TOOL_DETAILS_MAX_EDGES).map((edge) => ({ ...edge }));
328
- return {
329
- schemaVersion: PAPYRUS_TOOL_DETAILS_SCHEMA,
330
- kind: "graph",
331
- operation,
332
- nodes,
333
- edges: boundedEdges,
334
- nodeCompleteness: completeness(artifacts.length, nodes.length),
335
- edgeCompleteness: completeness(edges.length, boundedEdges.length),
336
- };
337
- }
338
-
339
- export function createGateRunDetails(
340
- operation: string,
341
- artifactId: string,
342
- artifactTitle: string,
343
- gates: readonly ToolGateRow[],
344
- ): GateRunToolDetails {
345
- const boundedGates = gates.slice(0, TOOL_DETAILS_MAX_ITEMS).map((gate) => ({
346
- ...gate,
347
- output: gate.output.slice(0, TOOL_DETAILS_ROW_OUTPUT_MAX_CHARACTERS),
348
- }));
349
- return {
350
- schemaVersion: PAPYRUS_TOOL_DETAILS_SCHEMA,
351
- kind: "gate-run",
352
- operation,
353
- artifactId,
354
- artifactTitle,
355
- gates: boundedGates,
356
- completeness: completeness(gates.length, boundedGates.length),
357
- };
358
- }
359
-
360
- export function createInvocationDetails(operation: string, runId: string, created: ToolInvocationCreated): InvocationToolDetails {
361
- const bounded: ToolInvocationCreated = {
362
- tasks: created.tasks.slice(0, TOOL_DETAILS_MAX_ITEMS),
363
- docs: created.docs.slice(0, TOOL_DETAILS_MAX_ITEMS),
364
- rules: created.rules.slice(0, TOOL_DETAILS_MAX_ITEMS),
365
- roots: created.roots.slice(0, TOOL_DETAILS_MAX_ITEMS),
366
- };
367
- const total = created.tasks.length + created.docs.length + created.rules.length + created.roots.length;
368
- const returned = bounded.tasks.length + bounded.docs.length + bounded.rules.length + bounded.roots.length;
369
- return {
370
- schemaVersion: PAPYRUS_TOOL_DETAILS_SCHEMA,
371
- kind: "invocation",
372
- operation,
373
- runId,
374
- created: bounded,
375
- completeness: completeness(total, returned),
376
- };
377
- }
378
-
379
- export function createPreviewDetails(operation: string, title: string, content: string): PreviewToolDetails {
380
- const bounded = boundedText(content, TOOL_DETAILS_BODY_MAX_CHARACTERS);
381
- return {
382
- schemaVersion: PAPYRUS_TOOL_DETAILS_SCHEMA,
383
- kind: "preview",
384
- operation,
385
- title,
386
- content: bounded.value,
387
- completeness: bounded.completeness,
388
- };
389
- }
390
-
391
- export function createErrorDetails(operation: string, code: string, message: string): ErrorToolDetails {
392
- return {
393
- schemaVersion: PAPYRUS_TOOL_DETAILS_SCHEMA,
394
- kind: "error",
395
- operation,
396
- code: code.slice(0, TOOL_DETAILS_FIELD_MAX_CHARACTERS),
397
- message: message.slice(0, TOOL_DETAILS_BODY_MAX_CHARACTERS),
398
- };
399
- }
400
-
401
- function boundedStringArray(values: readonly string[]): string[] {
402
- return values.slice(0, TOOL_DETAILS_MAX_ITEMS);
403
- }
404
-
405
- function boundedExecutionPlanNodes(nodes: readonly ExecutionPlanNode[]): ExecutionPlanNode[] {
406
- return nodes.slice(0, TOOL_DETAILS_MAX_ITEMS).map((node) => ({
407
- ...node,
408
- prerequisiteIds: boundedStringArray(node.prerequisiteIds),
409
- successorIds: boundedStringArray(node.successorIds),
410
- }));
411
- }
412
-
413
- function boundedLayers(layers: readonly (readonly string[])[]): string[][] {
414
- return layers.slice(0, TOOL_DETAILS_MAX_ITEMS).map((layer) => boundedStringArray(layer));
415
- }
416
-
417
- export function createExecutionPlanDetails(
418
- operation: string,
419
- nodes: readonly ExecutionPlanNode[],
420
- layers: readonly (readonly string[])[],
421
- cycleIds: readonly string[],
422
- ): ExecutionPlanToolDetails {
423
- const boundedNodes = boundedExecutionPlanNodes(nodes);
424
- return {
425
- schemaVersion: PAPYRUS_TOOL_DETAILS_SCHEMA,
426
- kind: "execution-plan",
427
- operation,
428
- nodes: boundedNodes,
429
- layers: boundedLayers(layers),
430
- cycleIds: boundedStringArray(cycleIds),
431
- completeness: completeness(nodes.length, boundedNodes.length),
432
- };
433
- }
434
-
435
- export function createPlaybookInvocationDetails(
436
- operation: string,
437
- fields: {
438
- playbookId: string;
439
- runId: string;
440
- created: PlaybookInvocationCreated;
441
- rootTaskIds: readonly string[];
442
- entryTaskId: string;
443
- execution: { nodes: readonly ExecutionPlanNode[]; layers: readonly (readonly string[])[]; cycleIds: readonly string[] };
444
- },
445
- ): PlaybookInvocationToolDetails {
446
- const boundedNodes = boundedExecutionPlanNodes(fields.execution.nodes);
447
- return {
448
- schemaVersion: PAPYRUS_TOOL_DETAILS_SCHEMA,
449
- kind: "playbook-invocation",
450
- operation,
451
- playbookId: fields.playbookId,
452
- runId: fields.runId,
453
- created: {
454
- docs: boundedStringArray(fields.created.docs),
455
- rules: boundedStringArray(fields.created.rules),
456
- tasks: boundedStringArray(fields.created.tasks),
457
- },
458
- rootTaskIds: boundedStringArray(fields.rootTaskIds),
459
- entryTaskId: fields.entryTaskId,
460
- execution: {
461
- nodes: boundedNodes,
462
- layers: boundedLayers(fields.execution.layers),
463
- cycleIds: boundedStringArray(fields.execution.cycleIds),
464
- },
465
- completeness: completeness(fields.execution.nodes.length, boundedNodes.length),
466
- };
467
- }
468
-
469
- export function createPlaybookMissingArgumentsDetails(
470
- operation: string,
471
- playbookId: string,
472
- missingArguments: readonly string[],
473
- ): PlaybookMissingArgumentsToolDetails {
474
- return {
475
- schemaVersion: PAPYRUS_TOOL_DETAILS_SCHEMA,
476
- kind: "playbook-missing-arguments",
477
- operation,
478
- playbookId,
479
- missingArguments: boundedStringArray(missingArguments),
480
- };
481
- }
482
-
483
- export function createDiscussionDetails(
484
- operation: string,
485
- rounds: readonly DiscussionRoundSummary[],
486
- discussion?: ArtifactSummarySource,
487
- ): DiscussionToolDetails {
488
- const boundedRounds = rounds.slice(0, TOOL_DETAILS_MAX_ITEMS).map((round) => ({
489
- ...round,
490
- content: round.content.slice(0, TOOL_DETAILS_ROW_OUTPUT_MAX_CHARACTERS),
491
- }));
492
- return {
493
- schemaVersion: PAPYRUS_TOOL_DETAILS_SCHEMA,
494
- kind: "discussion",
495
- operation,
496
- ...(discussion ? { discussion: artifactSummary(discussion) } : {}),
497
- rounds: boundedRounds,
498
- completeness: completeness(rounds.length, boundedRounds.length),
499
- };
500
- }
501
-
502
- export function createTaskCompletionDetails(
503
- operation: string,
504
- fields: {
505
- artifact: Artifact;
506
- gates: readonly TaskGateResultSummary[];
507
- checklist: readonly TaskChecklistReviewSummary[];
508
- completed: boolean;
509
- focused: Artifact | null;
510
- blocked: readonly { artifact: Artifact; dependencyIds: readonly string[] }[];
511
- },
512
- ): TaskCompletionToolDetails {
513
- const boundedGates = fields.gates.slice(0, TOOL_DETAILS_MAX_ITEMS).map((gate) => ({
514
- ...gate,
515
- output: gate.output.slice(0, TOOL_DETAILS_ROW_OUTPUT_MAX_CHARACTERS),
516
- }));
517
- const boundedChecklist = fields.checklist.slice(0, TOOL_DETAILS_MAX_ITEMS);
518
- const boundedBlocked = fields.blocked.slice(0, TOOL_DETAILS_MAX_ITEMS).map((entry) => ({
519
- artifact: artifactSummary(entry.artifact),
520
- dependencyIds: boundedStringArray(entry.dependencyIds),
521
- }));
522
- return {
523
- schemaVersion: PAPYRUS_TOOL_DETAILS_SCHEMA,
524
- kind: "task-completion",
525
- operation,
526
- artifact: artifactSummary(fields.artifact),
527
- gates: boundedGates,
528
- checklist: boundedChecklist,
529
- completed: fields.completed,
530
- ...(fields.focused ? { focused: artifactSummary(fields.focused) } : {}),
531
- blocked: boundedBlocked,
532
- completeness: completeness(fields.blocked.length, boundedBlocked.length),
533
- };
534
- }
535
-
536
- export function createNoFocusDetails(operation: string): NoFocusToolDetails {
537
- return { schemaVersion: PAPYRUS_TOOL_DETAILS_SCHEMA, kind: "no-focus", operation };
538
- }
539
-
540
- export function createLeaseDetails(
541
- operation: string,
542
- lease: {
543
- taskName: string;
544
- taskTitle: string;
545
- owner: string;
546
- claimedAt: string;
547
- leaseExpiresAt: string;
548
- heartbeatAt?: string;
549
- note?: string;
550
- },
551
- ): LeaseToolDetails {
552
- return {
553
- schemaVersion: PAPYRUS_TOOL_DETAILS_SCHEMA,
554
- kind: "lease",
555
- operation,
556
- taskName: lease.taskName,
557
- taskTitle: lease.taskTitle,
558
- owner: lease.owner,
559
- claimedAt: lease.claimedAt,
560
- leaseExpiresAt: lease.leaseExpiresAt,
561
- ...(lease.heartbeatAt ? { heartbeatAt: lease.heartbeatAt } : {}),
562
- ...(lease.note ? { note: lease.note.slice(0, TOOL_DETAILS_FIELD_MAX_CHARACTERS) } : {}),
563
- };
564
- }
565
-
566
- export function createModelContent(value: string): ModelContent {
567
- if (value.length <= TOOL_MODEL_CONTENT_MAX_CHARACTERS) {
568
- return { text: value, truncated: false, omitted: 0 };
569
- }
570
- let omitted = value.length - TOOL_MODEL_CONTENT_MAX_CHARACTERS;
571
- let marker = "";
572
- let kept = 0;
573
- for (let iteration = 0; iteration < 5; iteration += 1) {
574
- const nextMarker = `\n[truncated ${omitted} characters]`;
575
- const nextKept = Math.max(0, TOOL_MODEL_CONTENT_MAX_CHARACTERS - nextMarker.length);
576
- const nextOmitted = value.length - nextKept;
577
- marker = nextMarker;
578
- kept = nextKept;
579
- if (nextOmitted === omitted) break;
580
- omitted = nextOmitted;
581
- }
582
- return { text: `${value.slice(0, kept)}${marker}`, truncated: true, omitted: value.length - kept };
583
- }
584
-
585
- function isRecord(value: unknown): value is Record<string, unknown> {
586
- return typeof value === "object" && value !== null && !Array.isArray(value);
587
- }
588
-
589
- function isBoundedString(value: unknown, maximum = TOOL_DETAILS_FIELD_MAX_CHARACTERS): value is string {
590
- return typeof value === "string" && value.length <= maximum;
591
- }
592
-
593
- function isStringArray(value: unknown): value is string[] {
594
- return Array.isArray(value) && value.length <= TOOL_DETAILS_MAX_ITEMS && value.every((item) => isBoundedString(item));
595
- }
596
-
597
- function isCompleteness(value: unknown): value is ResultCompleteness {
598
- return isRecord(value) && typeof value.truncated === "boolean" && Number.isSafeInteger(value.omitted) && Number(value.omitted) >= 0;
599
- }
600
-
601
- function isArtifactSummary(value: unknown): value is ToolArtifactSummary {
602
- return (
603
- isRecord(value) &&
604
- isBoundedString(value.id) &&
605
- isBoundedString(value.kind) &&
606
- isBoundedString(value.title) &&
607
- isBoundedString(value.status) &&
608
- isBoundedString(value.subtype) &&
609
- isStringArray(value.labels)
610
- );
611
- }
612
-
613
- function isToolArtifact(value: unknown): value is ToolArtifact {
614
- if (!isRecord(value)) return false;
615
- const body = value.body;
616
- const createdAt = value.createdAt;
617
- const updatedAt = value.updatedAt;
618
- return (
619
- isArtifactSummary(value) &&
620
- isBoundedString(body, TOOL_DETAILS_BODY_MAX_CHARACTERS) &&
621
- isBoundedString(createdAt) &&
622
- isBoundedString(updatedAt)
623
- );
624
- }
625
-
626
- function isFocusAnnotation(value: unknown): value is ArtifactFocusAnnotation {
627
- return (
628
- isRecord(value) &&
629
- isBoundedString(value.status) &&
630
- isBoundedString(value.updatedAt) &&
631
- (value.pauseReason === undefined || isBoundedString(value.pauseReason))
632
- );
633
- }
634
-
635
- function isGraphEdge(value: unknown): value is ToolGraphEdge {
636
- return isRecord(value) && isBoundedString(value.from) && isBoundedString(value.relation) && isBoundedString(value.to);
637
- }
638
-
639
- function isGateRow(value: unknown): value is ToolGateRow {
640
- return (
641
- isRecord(value) &&
642
- typeof value.passed === "boolean" &&
643
- isBoundedString(value.type) &&
644
- isBoundedString(value.target) &&
645
- isBoundedString(value.output, TOOL_DETAILS_ROW_OUTPUT_MAX_CHARACTERS)
646
- );
647
- }
648
-
649
- function isBoundedArray<T>(value: unknown, maximum: number, predicate: (entry: unknown) => entry is T): value is T[] {
650
- return Array.isArray(value) && value.length <= maximum && value.every(predicate);
651
- }
652
-
653
- function isExecutionPlanNode(value: unknown): value is ExecutionPlanNode {
654
- return (
655
- isRecord(value) &&
656
- isBoundedString(value.id) &&
657
- isBoundedString(value.title) &&
658
- isBoundedString(value.status) &&
659
- typeof value.active === "boolean" &&
660
- isBoundedString(value.state) &&
661
- (value.layer === null || Number.isSafeInteger(value.layer)) &&
662
- isStringArray(value.prerequisiteIds) &&
663
- isStringArray(value.successorIds)
664
- );
665
- }
666
-
667
- function isLayers(value: unknown): value is string[][] {
668
- return isBoundedArray(value, TOOL_DETAILS_MAX_ITEMS, (entry): entry is string[] => isStringArray(entry));
669
- }
670
-
671
- function isExecution(value: unknown): value is { nodes: ExecutionPlanNode[]; layers: string[][]; cycleIds: string[] } {
672
- return (
673
- isRecord(value) &&
674
- isBoundedArray(value.nodes, TOOL_DETAILS_MAX_ITEMS, isExecutionPlanNode) &&
675
- isLayers(value.layers) &&
676
- isStringArray(value.cycleIds)
677
- );
678
- }
679
-
680
- function isDiscussionRoundSummary(value: unknown): value is DiscussionRoundSummary {
681
- return isRecord(value) && Number.isSafeInteger(value.roundNumber) && isBoundedString(value.actor) && isBoundedString(value.content);
682
- }
683
-
684
- function isTaskGateResultSummary(value: unknown): value is TaskGateResultSummary {
685
- return isRecord(value) && typeof value.passed === "boolean" && isBoundedString(value.output, TOOL_DETAILS_ROW_OUTPUT_MAX_CHARACTERS);
686
- }
687
-
688
- function isTaskChecklistReviewSummary(value: unknown): value is TaskChecklistReviewSummary {
689
- return (
690
- isRecord(value) &&
691
- isBoundedString(value.item) &&
692
- typeof value.accepted === "boolean" &&
693
- (value.reason === undefined || isBoundedString(value.reason))
694
- );
695
- }
696
-
697
- function isTaskBlockageSummary(value: unknown): value is TaskBlockageSummary {
698
- return isRecord(value) && isArtifactSummary(value.artifact) && isStringArray(value.dependencyIds);
699
- }
700
-
701
- /** Validate renderer details restored from session history before using them as typed presentation state. */
702
- export function parsePapyrusToolDetails(value: unknown): PapyrusToolDetails | undefined {
703
- let serializedLength: number;
704
- try {
705
- serializedLength = JSON.stringify(value).length;
706
- } catch {
707
- return undefined;
708
- }
709
- if (
710
- serializedLength > TOOL_DETAILS_MAX_SERIALIZED_CHARACTERS ||
711
- !isRecord(value) ||
712
- value.schemaVersion !== PAPYRUS_TOOL_DETAILS_SCHEMA ||
713
- !isBoundedString(value.operation) ||
714
- !isBoundedString(value.kind)
715
- )
716
- return undefined;
717
-
718
- switch (value.kind) {
719
- case "artifact":
720
- return isToolArtifact(value.artifact) &&
721
- isCompleteness(value.completeness) &&
722
- (value.focus === undefined || isFocusAnnotation(value.focus))
723
- ? (value as unknown as ArtifactToolDetails)
724
- : undefined;
725
- case "artifact-list":
726
- return isBoundedArray(value.rows, TOOL_DETAILS_MAX_ITEMS, isArtifactSummary) &&
727
- Number.isSafeInteger(value.total) &&
728
- Number(value.total) >= value.rows.length &&
729
- isCompleteness(value.completeness)
730
- ? (value as unknown as ArtifactListToolDetails)
731
- : undefined;
732
- case "transition":
733
- return isArtifactSummary(value.artifact) && isBoundedString(value.fromStatus) && isBoundedString(value.toStatus)
734
- ? (value as unknown as TransitionToolDetails)
735
- : undefined;
736
- case "graph":
737
- return isBoundedArray(value.nodes, TOOL_DETAILS_MAX_ITEMS, isArtifactSummary) &&
738
- isBoundedArray(value.edges, TOOL_DETAILS_MAX_EDGES, isGraphEdge) &&
739
- isCompleteness(value.nodeCompleteness) &&
740
- isCompleteness(value.edgeCompleteness)
741
- ? (value as unknown as GraphToolDetails)
742
- : undefined;
743
- case "gate-run":
744
- return isBoundedString(value.artifactId) &&
745
- isBoundedString(value.artifactTitle) &&
746
- isBoundedArray(value.gates, TOOL_DETAILS_MAX_ITEMS, isGateRow) &&
747
- isCompleteness(value.completeness)
748
- ? (value as unknown as GateRunToolDetails)
749
- : undefined;
750
- case "invocation": {
751
- if (!isRecord(value.created)) return undefined;
752
- return isBoundedString(value.runId) &&
753
- isStringArray(value.created.tasks) &&
754
- isStringArray(value.created.docs) &&
755
- isStringArray(value.created.rules) &&
756
- isStringArray(value.created.roots) &&
757
- isCompleteness(value.completeness)
758
- ? (value as unknown as InvocationToolDetails)
759
- : undefined;
760
- }
761
- case "preview":
762
- return isBoundedString(value.title) &&
763
- isBoundedString(value.content, TOOL_DETAILS_BODY_MAX_CHARACTERS) &&
764
- isCompleteness(value.completeness)
765
- ? (value as unknown as PreviewToolDetails)
766
- : undefined;
767
- case "error":
768
- return isBoundedString(value.code) && isBoundedString(value.message, TOOL_DETAILS_BODY_MAX_CHARACTERS)
769
- ? (value as unknown as ErrorToolDetails)
770
- : undefined;
771
- case "execution-plan":
772
- return isBoundedArray(value.nodes, TOOL_DETAILS_MAX_ITEMS, isExecutionPlanNode) &&
773
- isLayers(value.layers) &&
774
- isStringArray(value.cycleIds) &&
775
- isCompleteness(value.completeness)
776
- ? (value as unknown as ExecutionPlanToolDetails)
777
- : undefined;
778
- case "playbook-invocation": {
779
- if (!isRecord(value.created)) return undefined;
780
- return isBoundedString(value.playbookId) &&
781
- isBoundedString(value.runId) &&
782
- isStringArray(value.created.docs) &&
783
- isStringArray(value.created.rules) &&
784
- isStringArray(value.created.tasks) &&
785
- isStringArray(value.rootTaskIds) &&
786
- isBoundedString(value.entryTaskId) &&
787
- isExecution(value.execution) &&
788
- isCompleteness(value.completeness)
789
- ? (value as unknown as PlaybookInvocationToolDetails)
790
- : undefined;
791
- }
792
- case "playbook-missing-arguments":
793
- return isBoundedString(value.playbookId) && isStringArray(value.missingArguments)
794
- ? (value as unknown as PlaybookMissingArgumentsToolDetails)
795
- : undefined;
796
- case "discussion":
797
- return (value.discussion === undefined || isArtifactSummary(value.discussion)) &&
798
- isBoundedArray(value.rounds, TOOL_DETAILS_MAX_ITEMS, isDiscussionRoundSummary) &&
799
- isCompleteness(value.completeness)
800
- ? (value as unknown as DiscussionToolDetails)
801
- : undefined;
802
- case "task-completion":
803
- return isArtifactSummary(value.artifact) &&
804
- isBoundedArray(value.gates, TOOL_DETAILS_MAX_ITEMS, isTaskGateResultSummary) &&
805
- isBoundedArray(value.checklist, TOOL_DETAILS_MAX_ITEMS, isTaskChecklistReviewSummary) &&
806
- typeof value.completed === "boolean" &&
807
- (value.focused === undefined || isArtifactSummary(value.focused)) &&
808
- isBoundedArray(value.blocked, TOOL_DETAILS_MAX_ITEMS, isTaskBlockageSummary) &&
809
- isCompleteness(value.completeness)
810
- ? (value as unknown as TaskCompletionToolDetails)
811
- : undefined;
812
- case "no-focus":
813
- return value as unknown as NoFocusToolDetails;
814
- case "lease":
815
- return isBoundedString(value.taskName) &&
816
- isBoundedString(value.taskTitle) &&
817
- isBoundedString(value.owner) &&
818
- isBoundedString(value.claimedAt) &&
819
- isBoundedString(value.leaseExpiresAt) &&
820
- (value.heartbeatAt === undefined || isBoundedString(value.heartbeatAt)) &&
821
- (value.note === undefined || isBoundedString(value.note))
822
- ? (value as unknown as LeaseToolDetails)
823
- : undefined;
824
- default:
825
- return undefined;
826
- }
827
- }
1
+ /**
2
+ * Barrel re-exporting the typed, bounded Vehicle-result render details, one file per result-kind,
3
+ * from ./render-model/. Kept as a real file (not relying on directory-index resolution) so every
4
+ * existing `from "./render-model.ts"` / `from "../tool-rendering/render-model.ts"` import across
5
+ * this package's 5 real consumers (tool-rendering/index.ts, tool-rendering/artifact-card.ts,
6
+ * tool-rendering/artifact-list.ts, tools/vehicle-artifact-renderers.ts, index.ts) keeps resolving
7
+ * unchanged -- this codebase's imports always carry an explicit `.ts` extension, which does not
8
+ * implicitly resolve a bare specifier to a directory's own index file the way Node's CJS
9
+ * `require()` does.
10
+ *
11
+ * The real implementation now lives in ./render-model/ (one file per result-kind, already
12
+ * Strategy-shaped in spirit) instead of one 827-line file -- see Doc "Modularity playbook:
13
+ * building-block-shaped TypeScript modules for papyrus/pi-papyrus" and the "pi-papyrus
14
+ * render-model.ts split" child of "Epic: Modularize papyrus/pi-papyrus god-files into
15
+ * building-block modules".
16
+ */
17
+ export * from "./render-model/index.ts";