@osolmaz/pi-workflows 0.1.0 → 0.2.0

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.
Files changed (62) hide show
  1. package/README.md +36 -21
  2. package/dist/extension/executor.d.ts +14 -1
  3. package/dist/extension/executor.js +11 -1
  4. package/dist/extension/executor.js.map +1 -1
  5. package/dist/extension/index.js +79 -7
  6. package/dist/extension/index.js.map +1 -1
  7. package/dist/extension/recorder.d.ts +85 -0
  8. package/dist/extension/recorder.js +525 -0
  9. package/dist/extension/recorder.js.map +1 -0
  10. package/dist/extension/session-events.d.ts +134 -0
  11. package/dist/extension/session-events.js +60 -0
  12. package/dist/extension/session-events.js.map +1 -0
  13. package/dist/extension/widget.js +25 -24
  14. package/dist/extension/widget.js.map +1 -1
  15. package/dist/render/canvas.d.ts +1 -1
  16. package/dist/render/canvas.js +5 -0
  17. package/dist/render/canvas.js.map +1 -1
  18. package/dist/render/graph-render.d.ts +5 -0
  19. package/dist/render/graph-render.js +211 -48
  20. package/dist/render/graph-render.js.map +1 -1
  21. package/dist/viewer/render.js +19 -3
  22. package/dist/viewer/render.js.map +1 -1
  23. package/dist/viewer/session-reducer.d.ts +45 -0
  24. package/dist/viewer/session-reducer.js +266 -0
  25. package/dist/viewer/session-reducer.js.map +1 -0
  26. package/dist/workflows/artifacts.d.ts +40 -0
  27. package/dist/workflows/artifacts.js +155 -0
  28. package/dist/workflows/artifacts.js.map +1 -0
  29. package/dist/workflows/engine.d.ts +2 -0
  30. package/dist/workflows/engine.js +38 -7
  31. package/dist/workflows/engine.js.map +1 -1
  32. package/dist/workflows/index.d.ts +3 -2
  33. package/dist/workflows/index.js +2 -1
  34. package/dist/workflows/index.js.map +1 -1
  35. package/dist/workflows/store.d.ts +53 -9
  36. package/dist/workflows/store.js +523 -43
  37. package/dist/workflows/store.js.map +1 -1
  38. package/dist/workflows/types.d.ts +126 -3
  39. package/docs/development.md +43 -19
  40. package/docs/live-replay-protocol.md +155 -0
  41. package/docs/plans/piw-viewer-experience-implementation-plan.md +674 -0
  42. package/docs/plans/replayable-run-bundles-implementation-plan.md +65 -0
  43. package/docs/plans/session-event-replay-implementation-plan.md +494 -0
  44. package/docs/plans/tui-viewer-implementation-plan.md +64 -0
  45. package/docs/run-bundles.md +320 -55
  46. package/docs/session-event-journal.md +470 -0
  47. package/docs/tui-viewer.md +218 -0
  48. package/package.json +2 -1
  49. package/src/extension/executor.ts +28 -1
  50. package/src/extension/index.ts +87 -7
  51. package/src/extension/recorder.ts +633 -0
  52. package/src/extension/session-events.ts +119 -0
  53. package/src/extension/widget.ts +26 -24
  54. package/src/render/canvas.ts +19 -1
  55. package/src/render/graph-render.ts +277 -44
  56. package/src/viewer/render.ts +21 -3
  57. package/src/viewer/session-reducer.ts +347 -0
  58. package/src/workflows/artifacts.ts +188 -0
  59. package/src/workflows/engine.ts +39 -7
  60. package/src/workflows/index.ts +15 -0
  61. package/src/workflows/store.ts +649 -49
  62. package/src/workflows/types.ts +141 -3
@@ -0,0 +1,119 @@
1
+ export type PublicAssistantMessageEvent =
2
+ | { type: "start"; partial: unknown }
3
+ | { type: "text_start"; contentIndex: number; partial: unknown }
4
+ | { type: "text_delta"; contentIndex: number; delta: string; partial: unknown }
5
+ | { type: "text_end"; contentIndex: number; content: string; partial: unknown }
6
+ | { type: "thinking_start"; contentIndex: number; partial: unknown }
7
+ | { type: "thinking_delta"; contentIndex: number; delta: string; partial: unknown }
8
+ | { type: "thinking_end"; contentIndex: number; content: string; partial: unknown }
9
+ | { type: "toolcall_start"; contentIndex: number; partial: unknown }
10
+ | { type: "toolcall_delta"; contentIndex: number; delta: string; partial: unknown }
11
+ | { type: "toolcall_end"; contentIndex: number; toolCall: unknown; partial: unknown }
12
+ | { type: "done"; reason: "stop" | "length" | "toolUse"; message: unknown }
13
+ | { type: "error"; reason: "aborted" | "error"; error: unknown };
14
+
15
+ export type TurnStartEventLike = { turnIndex: number };
16
+ export type TurnEndEventLike = { turnIndex: number; message: unknown };
17
+ export type MessageStartEventLike = { message: unknown };
18
+ export type MessageUpdateEventLike = {
19
+ message: unknown;
20
+ assistantMessageEvent: PublicAssistantMessageEvent;
21
+ };
22
+ export type MessageEndEventLike = { message: unknown };
23
+ export type ToolExecutionStartEventLike = {
24
+ toolCallId: string;
25
+ toolName: string;
26
+ args: unknown;
27
+ };
28
+ export type ToolExecutionUpdateEventLike = { toolCallId: string };
29
+ export type ToolExecutionEndEventLike = {
30
+ toolCallId: string;
31
+ toolName: string;
32
+ result: unknown;
33
+ isError: boolean;
34
+ };
35
+
36
+ export type NormalizedAssistantEvent =
37
+ | { type: "start" }
38
+ | { type: "text_start"; contentIndex: number }
39
+ | { type: "text_delta"; contentIndex: number; delta: string }
40
+ | { type: "text_end"; contentIndex: number; content: string }
41
+ | { type: "thinking_start"; contentIndex: number }
42
+ | { type: "thinking_delta"; contentIndex: number; delta: string }
43
+ | { type: "thinking_end"; contentIndex: number; content: string }
44
+ | { type: "toolcall_start"; contentIndex: number }
45
+ | { type: "toolcall_delta"; contentIndex: number; delta: string }
46
+ | { type: "toolcall_end"; contentIndex: number; toolCall: unknown }
47
+ | { type: "done"; reason: "stop" | "length" | "toolUse" }
48
+ | { type: "error"; reason: "aborted" | "error" };
49
+
50
+ /** Remove cumulative snapshots while preserving every semantic stream event. */
51
+ export function normalizeAssistantEvent(
52
+ event: PublicAssistantMessageEvent,
53
+ ): NormalizedAssistantEvent {
54
+ switch (event.type) {
55
+ case "start":
56
+ return { type: event.type };
57
+ case "text_start":
58
+ case "thinking_start":
59
+ case "toolcall_start":
60
+ return { type: event.type, contentIndex: event.contentIndex };
61
+ case "text_delta":
62
+ case "thinking_delta":
63
+ case "toolcall_delta":
64
+ return { type: event.type, contentIndex: event.contentIndex, delta: event.delta };
65
+ case "text_end":
66
+ case "thinking_end":
67
+ return { type: event.type, contentIndex: event.contentIndex, content: event.content };
68
+ case "toolcall_end":
69
+ return {
70
+ type: event.type,
71
+ contentIndex: event.contentIndex,
72
+ toolCall: event.toolCall,
73
+ };
74
+ case "done":
75
+ return { type: event.type, reason: event.reason };
76
+ case "error":
77
+ return { type: event.type, reason: event.reason };
78
+ default: {
79
+ const exhaustive: never = event;
80
+ return exhaustive;
81
+ }
82
+ }
83
+ }
84
+
85
+ export function messageRole(message: unknown): string {
86
+ if (typeof message !== "object" || message === null || !("role" in message)) {
87
+ return "unknown";
88
+ }
89
+ const role = (message as { role?: unknown }).role;
90
+ return typeof role === "string" && role.length > 0 ? role : "unknown";
91
+ }
92
+
93
+ export function toolCallIdFromAssistantEvent(event: NormalizedAssistantEvent): string | null {
94
+ if (event.type !== "toolcall_end" || typeof event.toolCall !== "object" || !event.toolCall) {
95
+ return null;
96
+ }
97
+ const id = (event.toolCall as { id?: unknown }).id;
98
+ return typeof id === "string" && id.length > 0 ? id : null;
99
+ }
100
+
101
+ export function toolStartedPayload(event: ToolExecutionStartEventLike): Record<string, unknown> {
102
+ return { toolName: event.toolName, args: event.args };
103
+ }
104
+
105
+ export function toolFinishedPayload(event: ToolExecutionEndEventLike): Record<string, unknown> {
106
+ return { toolName: event.toolName, isError: event.isError, result: event.result };
107
+ }
108
+
109
+ export function turnFinishedPayload(
110
+ event: TurnEndEventLike,
111
+ messageId: string | undefined,
112
+ toolCallIds: string[],
113
+ ): Record<string, unknown> {
114
+ return {
115
+ turnIndex: event.turnIndex,
116
+ ...(messageId === undefined ? {} : { messageId }),
117
+ toolCallIds,
118
+ };
119
+ }
@@ -113,16 +113,24 @@ export function buildWidgetLines(
113
113
  /** The graph row the window should center on: the active or waiting node. */
114
114
  function focusLine(graph: string[], state: WorkflowRunState): number {
115
115
  const active = graph.findIndex((line) => stripAnsi(line).includes("◐"));
116
- if (active !== -1) {
117
- return active;
116
+ const focus =
117
+ active !== -1
118
+ ? active
119
+ : state.waitingOn
120
+ ? graph.findIndex((line) => stripAnsi(line).includes(state.waitingOn as string))
121
+ : -1;
122
+ if (focus === -1) {
123
+ return graph.length - 1;
118
124
  }
119
- if (state.waitingOn) {
120
- const waiting = graph.findIndex((line) => stripAnsi(line).includes(state.waitingOn as string));
121
- if (waiting !== -1) {
122
- return waiting;
123
- }
125
+ let top = focus;
126
+ while (top > 0 && !/[┌┏]/u.test(stripAnsi(graph[top] ?? ""))) {
127
+ top -= 1;
124
128
  }
125
- return graph.length - 1;
129
+ let bottom = focus;
130
+ while (bottom + 1 < graph.length && !/[└┗]/u.test(stripAnsi(graph[bottom] ?? ""))) {
131
+ bottom += 1;
132
+ }
133
+ return Math.floor((top + bottom) / 2);
126
134
  }
127
135
 
128
136
  /**
@@ -139,24 +147,18 @@ function windowLines(
139
147
  if (lines.length <= budget) {
140
148
  return { lines, scroll: 0, maxScroll: 0 };
141
149
  }
142
- // First pass decides which markers exist; the second re-fits within the
143
- // remaining space. A marker flag can only turn on (never off) in the
144
- // second pass, so the result stays within budget.
145
- let inner = budget;
146
- for (let pass = 0; pass < 2; pass += 1) {
147
- const start = clampStart(anchor, inner, lines.length, anchorIsStart);
148
- inner = budget - (start > 0 ? 1 : 0) - (start + inner < lines.length ? 1 : 0);
149
- }
150
+ // Reserve one combined overflow row, leaving seven rows for a complete
151
+ // full card even when the widget also has an error footer.
152
+ const inner = Math.max(1, budget - 1);
150
153
  const start = clampStart(anchor, inner, lines.length, anchorIsStart);
151
154
  const end = start + inner;
152
- const out: string[] = [];
153
- if (start > 0) {
154
- out.push(ansi.dim(`↑ ${start} more`));
155
- }
156
- out.push(...lines.slice(start, end));
157
- if (end < lines.length) {
158
- out.push(ansi.dim(`↓ ${lines.length - end} more · shift+↑/↓ scroll`));
159
- }
155
+ const above = start;
156
+ const below = Math.max(0, lines.length - end);
157
+ const out = lines.slice(start, end);
158
+ const directions = [above > 0 ? `↑ ${above}` : "", below > 0 ? `↓ ${below}` : ""]
159
+ .filter(Boolean)
160
+ .join(" · ");
161
+ out.push(ansi.dim(`${directions} more · shift+↑/↓ scroll`));
160
162
  return { lines: out, scroll: start, maxScroll: Math.max(0, lines.length - inner) };
161
163
  }
162
164
 
@@ -4,7 +4,20 @@
4
4
  * edge polylines join instead of overwriting each other.
5
5
  */
6
6
 
7
- export type CanvasStyle = "plain" | "dim" | "taken" | "active" | "back" | "ok" | "fail" | "warn";
7
+ export type CanvasStyle =
8
+ | "plain"
9
+ | "dim"
10
+ | "taken"
11
+ | "active"
12
+ | "back"
13
+ | "ok"
14
+ | "fail"
15
+ | "warn"
16
+ | "branch"
17
+ | "agent"
18
+ | "compute"
19
+ | "action"
20
+ | "checkpoint";
8
21
 
9
22
  type CanvasChar = { char: string; style: CanvasStyle };
10
23
 
@@ -40,6 +53,11 @@ const STYLE_PRIORITY: CanvasStyle[] = [
40
53
  "warn",
41
54
  "ok",
42
55
  "fail",
56
+ "branch",
57
+ "agent",
58
+ "compute",
59
+ "action",
60
+ "checkpoint",
43
61
  "taken",
44
62
  "active",
45
63
  ];