@osolmaz/pi-workflows 0.1.0 → 0.3.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 (171) hide show
  1. package/README.md +107 -26
  2. package/dist/builtins/monitor.workflow.d.ts +71 -0
  3. package/dist/builtins/monitor.workflow.js +234 -0
  4. package/dist/builtins/monitor.workflow.js.map +1 -0
  5. package/dist/controllers/conditions.d.ts +6 -0
  6. package/dist/controllers/conditions.js +68 -0
  7. package/dist/controllers/conditions.js.map +1 -0
  8. package/dist/controllers/definition.d.ts +6 -0
  9. package/dist/controllers/definition.js +45 -0
  10. package/dist/controllers/definition.js.map +1 -0
  11. package/dist/controllers/effects.d.ts +14 -0
  12. package/dist/controllers/effects.js +104 -0
  13. package/dist/controllers/effects.js.map +1 -0
  14. package/dist/controllers/errors.d.ts +12 -0
  15. package/dist/controllers/errors.js +25 -0
  16. package/dist/controllers/errors.js.map +1 -0
  17. package/dist/controllers/index.d.ts +13 -0
  18. package/dist/controllers/index.js +13 -0
  19. package/dist/controllers/index.js.map +1 -0
  20. package/dist/controllers/json.d.ts +5 -0
  21. package/dist/controllers/json.js +57 -0
  22. package/dist/controllers/json.js.map +1 -0
  23. package/dist/controllers/loader.d.ts +23 -0
  24. package/dist/controllers/loader.js +74 -0
  25. package/dist/controllers/loader.js.map +1 -0
  26. package/dist/controllers/manager.d.ts +58 -0
  27. package/dist/controllers/manager.js +399 -0
  28. package/dist/controllers/manager.js.map +1 -0
  29. package/dist/controllers/results.d.ts +5 -0
  30. package/dist/controllers/results.js +32 -0
  31. package/dist/controllers/results.js.map +1 -0
  32. package/dist/controllers/sqlite.d.ts +212 -0
  33. package/dist/controllers/sqlite.js +1009 -0
  34. package/dist/controllers/sqlite.js.map +1 -0
  35. package/dist/controllers/store.d.ts +112 -0
  36. package/dist/controllers/store.js +32 -0
  37. package/dist/controllers/store.js.map +1 -0
  38. package/dist/controllers/types.d.ts +159 -0
  39. package/dist/controllers/types.js +2 -0
  40. package/dist/controllers/types.js.map +1 -0
  41. package/dist/controllers/workflow-engine-scheduler.d.ts +25 -0
  42. package/dist/controllers/workflow-engine-scheduler.js +93 -0
  43. package/dist/controllers/workflow-engine-scheduler.js.map +1 -0
  44. package/dist/controllers/workflows.d.ts +27 -0
  45. package/dist/controllers/workflows.js +109 -0
  46. package/dist/controllers/workflows.js.map +1 -0
  47. package/dist/extension/controller-host.d.ts +47 -0
  48. package/dist/extension/controller-host.js +110 -0
  49. package/dist/extension/controller-host.js.map +1 -0
  50. package/dist/extension/executor.d.ts +14 -1
  51. package/dist/extension/executor.js +12 -2
  52. package/dist/extension/executor.js.map +1 -1
  53. package/dist/extension/index.d.ts +7 -0
  54. package/dist/extension/index.js +1118 -89
  55. package/dist/extension/index.js.map +1 -1
  56. package/dist/extension/recorder.d.ts +91 -0
  57. package/dist/extension/recorder.js +537 -0
  58. package/dist/extension/recorder.js.map +1 -0
  59. package/dist/extension/session-events.d.ts +134 -0
  60. package/dist/extension/session-events.js +60 -0
  61. package/dist/extension/session-events.js.map +1 -0
  62. package/dist/extension/widget.js +25 -24
  63. package/dist/extension/widget.js.map +1 -1
  64. package/dist/extension/workflow-tool.d.ts +28 -0
  65. package/dist/extension/workflow-tool.js +33 -0
  66. package/dist/extension/workflow-tool.js.map +1 -0
  67. package/dist/host/processes.d.ts +24 -0
  68. package/dist/host/processes.js +114 -0
  69. package/dist/host/processes.js.map +1 -0
  70. package/dist/host/rpc-bridge.d.ts +9 -0
  71. package/dist/host/rpc-bridge.js +39 -0
  72. package/dist/host/rpc-bridge.js.map +1 -0
  73. package/dist/host/rpc-executor.d.ts +38 -0
  74. package/dist/host/rpc-executor.js +254 -0
  75. package/dist/host/rpc-executor.js.map +1 -0
  76. package/dist/host/runner.d.ts +49 -0
  77. package/dist/host/runner.js +350 -0
  78. package/dist/host/runner.js.map +1 -0
  79. package/dist/render/canvas.d.ts +1 -1
  80. package/dist/render/canvas.js +5 -0
  81. package/dist/render/canvas.js.map +1 -1
  82. package/dist/render/graph-render.d.ts +5 -0
  83. package/dist/render/graph-render.js +211 -48
  84. package/dist/render/graph-render.js.map +1 -1
  85. package/dist/viewer/cli.d.ts +7 -3
  86. package/dist/viewer/cli.js +150 -19
  87. package/dist/viewer/cli.js.map +1 -1
  88. package/dist/viewer/render.js +19 -3
  89. package/dist/viewer/render.js.map +1 -1
  90. package/dist/viewer/session-reducer.d.ts +45 -0
  91. package/dist/viewer/session-reducer.js +266 -0
  92. package/dist/viewer/session-reducer.js.map +1 -0
  93. package/dist/workflows/artifacts.d.ts +40 -0
  94. package/dist/workflows/artifacts.js +155 -0
  95. package/dist/workflows/artifacts.js.map +1 -0
  96. package/dist/workflows/engine.d.ts +38 -0
  97. package/dist/workflows/engine.js +281 -20
  98. package/dist/workflows/engine.js.map +1 -1
  99. package/dist/workflows/errors.d.ts +23 -0
  100. package/dist/workflows/errors.js +38 -0
  101. package/dist/workflows/errors.js.map +1 -1
  102. package/dist/workflows/graph.js +0 -5
  103. package/dist/workflows/graph.js.map +1 -1
  104. package/dist/workflows/index.d.ts +3 -2
  105. package/dist/workflows/index.js +2 -1
  106. package/dist/workflows/index.js.map +1 -1
  107. package/dist/workflows/loader.d.ts +5 -3
  108. package/dist/workflows/loader.js +10 -1
  109. package/dist/workflows/loader.js.map +1 -1
  110. package/dist/workflows/schema.js +1 -1
  111. package/dist/workflows/schema.js.map +1 -1
  112. package/dist/workflows/store.d.ts +98 -10
  113. package/dist/workflows/store.js +921 -46
  114. package/dist/workflows/store.js.map +1 -1
  115. package/dist/workflows/types.d.ts +136 -3
  116. package/docs/CONTROLLERS.md +215 -0
  117. package/docs/development.md +51 -24
  118. package/docs/live-replay-protocol.md +155 -0
  119. package/docs/plans/2026-08-04-controller-runtime-plan.md +169 -0
  120. package/docs/plans/2026-08-05-always-on-workflows-plan.md +125 -0
  121. package/docs/plans/2026-08-10-agent-managed-monitor-workflows-plan.md +184 -0
  122. package/docs/plans/piw-viewer-experience-implementation-plan.md +674 -0
  123. package/docs/plans/replayable-run-bundles-implementation-plan.md +65 -0
  124. package/docs/plans/session-event-replay-implementation-plan.md +494 -0
  125. package/docs/plans/tui-viewer-implementation-plan.md +64 -0
  126. package/docs/run-bundles.md +359 -55
  127. package/docs/session-event-journal.md +470 -0
  128. package/docs/tui-viewer.md +218 -0
  129. package/docs/workflows.md +131 -11
  130. package/examples/controllers/pull-request.controller.ts +215 -0
  131. package/package.json +11 -2
  132. package/src/builtins/monitor.workflow.ts +278 -0
  133. package/src/controllers/conditions.ts +110 -0
  134. package/src/controllers/definition.ts +65 -0
  135. package/src/controllers/effects.ts +123 -0
  136. package/src/controllers/errors.ts +27 -0
  137. package/src/controllers/index.ts +90 -0
  138. package/src/controllers/json.ts +62 -0
  139. package/src/controllers/loader.ts +104 -0
  140. package/src/controllers/manager.ts +533 -0
  141. package/src/controllers/results.ts +46 -0
  142. package/src/controllers/sqlite.ts +1427 -0
  143. package/src/controllers/store.ts +160 -0
  144. package/src/controllers/types.ts +183 -0
  145. package/src/controllers/workflow-engine-scheduler.ts +145 -0
  146. package/src/controllers/workflows.ts +152 -0
  147. package/src/extension/controller-host.ts +163 -0
  148. package/src/extension/executor.ts +29 -2
  149. package/src/extension/index.ts +1315 -109
  150. package/src/extension/recorder.ts +662 -0
  151. package/src/extension/session-events.ts +119 -0
  152. package/src/extension/widget.ts +26 -24
  153. package/src/extension/workflow-tool.ts +59 -0
  154. package/src/host/processes.ts +119 -0
  155. package/src/host/rpc-bridge.ts +44 -0
  156. package/src/host/rpc-executor.ts +299 -0
  157. package/src/host/runner.ts +406 -0
  158. package/src/render/canvas.ts +19 -1
  159. package/src/render/graph-render.ts +277 -44
  160. package/src/viewer/cli.ts +167 -21
  161. package/src/viewer/render.ts +21 -3
  162. package/src/viewer/session-reducer.ts +347 -0
  163. package/src/workflows/artifacts.ts +188 -0
  164. package/src/workflows/engine.ts +365 -19
  165. package/src/workflows/errors.ts +45 -0
  166. package/src/workflows/graph.ts +0 -5
  167. package/src/workflows/index.ts +15 -0
  168. package/src/workflows/loader.ts +13 -3
  169. package/src/workflows/schema.ts +1 -1
  170. package/src/workflows/store.ts +1157 -48
  171. package/src/workflows/types.ts +151 -3
@@ -0,0 +1,163 @@
1
+ import {
2
+ ControllerManager,
3
+ SqliteControllerStore,
4
+ projectControllerStorePath,
5
+ loadDiscoveredControllers,
6
+ type AnyControllerDefinition,
7
+ type ControllerResource,
8
+ type WorkflowSchedulerRequest,
9
+ type WorkflowSchedulerResult,
10
+ } from "../controllers/index.js";
11
+
12
+ export type ParsedControllerArgs =
13
+ | { kind: "list" }
14
+ | { kind: "get"; controller: string; key: string }
15
+ | { kind: "apply"; controller: string; key: string; spec: unknown }
16
+ | { kind: "reconcile"; controller: string; key: string }
17
+ | { kind: "delete"; controller: string; key: string }
18
+ | { kind: "start" }
19
+ | { kind: "stop" };
20
+
21
+ export type PiChildWorkflowStarter = (
22
+ request: WorkflowSchedulerRequest,
23
+ signal: AbortSignal,
24
+ onComplete: (result: WorkflowSchedulerResult) => void,
25
+ ) => Promise<WorkflowSchedulerResult>;
26
+
27
+ export function parseControllerArgs(args: string): ParsedControllerArgs {
28
+ const trimmed = args.trim();
29
+ if (trimmed.length === 0 || trimmed === "list") {
30
+ return { kind: "list" };
31
+ }
32
+ if (trimmed === "start" || trimmed === "stop") {
33
+ return { kind: trimmed };
34
+ }
35
+ const apply = trimmed.match(/^apply\s+(\S+)\s+(\S+)\s+([\s\S]+)$/);
36
+ if (apply !== null) {
37
+ let spec: unknown;
38
+ try {
39
+ spec = JSON.parse(apply[3] as string) as unknown;
40
+ } catch (error) {
41
+ throw new Error(
42
+ `Invalid controller spec JSON: ${error instanceof Error ? error.message : String(error)}`,
43
+ );
44
+ }
45
+ return {
46
+ kind: "apply",
47
+ controller: apply[1] as string,
48
+ key: apply[2] as string,
49
+ spec,
50
+ };
51
+ }
52
+ const operation = trimmed.match(/^(get|reconcile|delete)\s+(\S+)\s+(\S+)$/);
53
+ if (operation !== null) {
54
+ return {
55
+ kind: operation[1] as "get" | "reconcile" | "delete",
56
+ controller: operation[2] as string,
57
+ key: operation[3] as string,
58
+ };
59
+ }
60
+ throw new Error(
61
+ "Usage: /controller [list|get <controller> <key>|apply <controller> <key> <json>|reconcile <controller> <key>|delete <controller> <key>|start|stop]",
62
+ );
63
+ }
64
+
65
+ export class PiControllerHost {
66
+ readonly definitions: AnyControllerDefinition[];
67
+ readonly store: SqliteControllerStore;
68
+ readonly manager: ControllerManager;
69
+ private running = false;
70
+
71
+ private constructor(
72
+ definitions: AnyControllerDefinition[],
73
+ store: SqliteControllerStore,
74
+ manager: ControllerManager,
75
+ ) {
76
+ this.definitions = definitions;
77
+ this.store = store;
78
+ this.manager = manager;
79
+ }
80
+
81
+ static async create(options: {
82
+ cwd: string;
83
+ startChild: PiChildWorkflowStarter;
84
+ }): Promise<PiControllerHost | undefined> {
85
+ const definitions = await loadDiscoveredControllers({ cwd: options.cwd });
86
+ if (definitions.length === 0) {
87
+ return undefined;
88
+ }
89
+ const store = new SqliteControllerStore(projectControllerStorePath(options.cwd));
90
+ const manager = new ControllerManager({
91
+ store,
92
+ controllers: definitions,
93
+ workflowScheduler: { ensure: options.startChild },
94
+ });
95
+ return new PiControllerHost(definitions, store, manager);
96
+ }
97
+
98
+ start(): void {
99
+ this.manager.start();
100
+ this.running = true;
101
+ }
102
+
103
+ async stop(): Promise<void> {
104
+ await this.manager.stop();
105
+ this.running = false;
106
+ }
107
+
108
+ async close(): Promise<void> {
109
+ await this.stop();
110
+ this.store.close();
111
+ }
112
+
113
+ get isRunning(): boolean {
114
+ return this.running;
115
+ }
116
+
117
+ list(): string {
118
+ const names = this.definitions.map((item) => item.name).join(", ");
119
+ const resources = this.store.listResources();
120
+ if (resources.length === 0) {
121
+ return `Controllers: ${names}. No resources.`;
122
+ }
123
+ const rows = resources.map((resource) => resourceSummary(resource)).join("; ");
124
+ return `Controllers: ${names}. Resources: ${rows}.`;
125
+ }
126
+
127
+ get(controller: string, key: string): string {
128
+ const resource = this.store.getResource({ controller, key });
129
+ if (resource === undefined) {
130
+ throw new Error(`Controller resource not found: ${controller}/${key}`);
131
+ }
132
+ return JSON.stringify(
133
+ {
134
+ resource,
135
+ effects: this.store.listEffects(resource.metadata.uid),
136
+ workflows: this.store.listWorkflows(resource.metadata.uid),
137
+ },
138
+ null,
139
+ 2,
140
+ );
141
+ }
142
+
143
+ apply(controller: string, key: string, spec: unknown): ControllerResource {
144
+ return this.manager.putResourceByName(controller, key, spec);
145
+ }
146
+
147
+ reconcile(controller: string, key: string): void {
148
+ this.manager.enqueue({ controller, key });
149
+ }
150
+
151
+ delete(controller: string, key: string): void {
152
+ this.manager.requestDeletion({ controller, key });
153
+ }
154
+ }
155
+
156
+ function resourceSummary(resource: ControllerResource): string {
157
+ const condition =
158
+ resource.status.conditions.find((item) => item.type === "Ready") ??
159
+ resource.status.conditions[0];
160
+ const state =
161
+ condition === undefined ? "unknown" : `${String(condition.status)}:${condition.reason}`;
162
+ return `${resource.metadata.controller}/${resource.metadata.key} generation=${resource.metadata.generation} ready=${state}`;
163
+ }
@@ -2,6 +2,7 @@ import type {
2
2
  AgentStepExecutor,
3
3
  AgentStepRequest,
4
4
  AgentStepSubmission,
5
+ ConversationRange,
5
6
  } from "../workflows/types.js";
6
7
 
7
8
  export type SubmissionResult =
@@ -14,11 +15,24 @@ export type PromptDelivery = {
14
15
  streaming: boolean;
15
16
  };
16
17
 
18
+ /**
19
+ * Bracketing hooks for conversation linkage: a mark is taken when the step
20
+ * prompt is first delivered, and the recorded entry range since that mark is
21
+ * attached to the accepted submission.
22
+ */
23
+ export type ConversationHooks = {
24
+ beginAttempt?: (contract: AgentStepRequest["contract"]) => void;
25
+ mark: () => number;
26
+ rangeSince: (mark: number) => ConversationRange | undefined;
27
+ };
28
+
17
29
  export type ConversationStepExecutorOptions = {
18
30
  /** Deliver a prompt into the pi conversation. */
19
31
  sendPrompt: (delivery: PromptDelivery) => void;
20
32
  /** Reminders sent when the agent settles without submitting. Default 2. */
21
33
  maxNudges?: number;
34
+ /** Conversation linkage hooks, wired to the session recorder. */
35
+ conversation?: ConversationHooks;
22
36
  };
23
37
 
24
38
  type PendingStep = {
@@ -26,6 +40,8 @@ type PendingStep = {
26
40
  resolve: (submission: AgentStepSubmission) => void;
27
41
  reject: (error: unknown) => void;
28
42
  nudgesSent: number;
43
+ /** Conversation mark taken when the prompt was first delivered. */
44
+ mark: number | null;
29
45
  cleanup: () => void;
30
46
  /** Resolves when this step stops being the pending step. */
31
47
  cleared: Promise<void>;
@@ -44,6 +60,7 @@ const DEFAULT_MAX_NUDGES = 2;
44
60
  export class ConversationStepExecutor implements AgentStepExecutor {
45
61
  private readonly sendPrompt: (delivery: PromptDelivery) => void;
46
62
  private readonly maxNudges: number;
63
+ private readonly conversation: ConversationHooks | undefined;
47
64
  private pending: PendingStep | null = null;
48
65
  private streaming = false;
49
66
  private heldByUser = false;
@@ -51,6 +68,7 @@ export class ConversationStepExecutor implements AgentStepExecutor {
51
68
  constructor(options: ConversationStepExecutorOptions) {
52
69
  this.sendPrompt = options.sendPrompt;
53
70
  this.maxNudges = options.maxNudges ?? DEFAULT_MAX_NUDGES;
71
+ this.conversation = options.conversation;
54
72
  }
55
73
 
56
74
  /** Track agent streaming state (wire to agent_start / agent_settled). */
@@ -88,6 +106,7 @@ export class ConversationStepExecutor implements AgentStepExecutor {
88
106
  return;
89
107
  }
90
108
  try {
109
+ this.conversation?.beginAttempt?.(pending.request.contract);
91
110
  this.sendPrompt({ prompt: pending.request.prompt, streaming: this.streaming });
92
111
  } catch (error) {
93
112
  this.clearPending();
@@ -110,11 +129,13 @@ export class ConversationStepExecutor implements AgentStepExecutor {
110
129
  const cleared = new Promise<void>((resolveCleared) => {
111
130
  markCleared = resolveCleared;
112
131
  });
132
+ this.conversation?.beginAttempt?.(request.contract);
113
133
  this.pending = {
114
134
  request,
115
135
  resolve,
116
136
  reject,
117
137
  nudgesSent: 0,
138
+ mark: this.conversation?.mark() ?? null,
118
139
  cleanup: () => signal.removeEventListener("abort", onAbort),
119
140
  cleared,
120
141
  markCleared,
@@ -185,7 +206,12 @@ export class ConversationStepExecutor implements AgentStepExecutor {
185
206
  };
186
207
  }
187
208
  this.clearPending();
188
- pending.resolve({ output: result.value });
209
+ const conversation =
210
+ pending.mark !== null ? this.conversation?.rangeSince(pending.mark) : undefined;
211
+ pending.resolve({
212
+ output: result.value,
213
+ ...(conversation !== undefined ? { conversation } : {}),
214
+ });
189
215
  return {
190
216
  accepted: true,
191
217
  message: [
@@ -224,11 +250,12 @@ export class ConversationStepExecutor implements AgentStepExecutor {
224
250
  pending.nudgesSent += 1;
225
251
  const { nodeId, attemptId } = pending.request.contract;
226
252
  try {
253
+ this.conversation?.beginAttempt?.(pending.request.contract);
227
254
  this.sendPrompt({
228
255
  prompt: [
229
256
  `Reminder: workflow step ${JSON.stringify(nodeId)} is still awaiting your output.`,
230
257
  "Complete it by calling the `workflow` tool with:",
231
- `{"step": ${JSON.stringify(nodeId)}, "attempt": ${JSON.stringify(attemptId)}, "output": <your result>}`,
258
+ `{"action": "submit", "step": ${JSON.stringify(nodeId)}, "attempt": ${JSON.stringify(attemptId)}, "output": <your result>}`,
232
259
  `Expected output: ${pending.request.contract.expectedOutput ?? "a JSON object with your result"}`,
233
260
  ].join("\n"),
234
261
  streaming: this.streaming,