@osolmaz/pi-workflows 0.2.0 → 0.4.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 (152) hide show
  1. package/README.md +72 -5
  2. package/dist/builtins/catalog.d.ts +2 -0
  3. package/dist/builtins/catalog.js +22 -0
  4. package/dist/builtins/catalog.js.map +1 -0
  5. package/dist/builtins/monitor.workflow.d.ts +71 -0
  6. package/dist/builtins/monitor.workflow.js +250 -0
  7. package/dist/builtins/monitor.workflow.js.map +1 -0
  8. package/dist/controllers/conditions.d.ts +6 -0
  9. package/dist/controllers/conditions.js +68 -0
  10. package/dist/controllers/conditions.js.map +1 -0
  11. package/dist/controllers/definition.d.ts +6 -0
  12. package/dist/controllers/definition.js +45 -0
  13. package/dist/controllers/definition.js.map +1 -0
  14. package/dist/controllers/effects.d.ts +14 -0
  15. package/dist/controllers/effects.js +104 -0
  16. package/dist/controllers/effects.js.map +1 -0
  17. package/dist/controllers/errors.d.ts +12 -0
  18. package/dist/controllers/errors.js +25 -0
  19. package/dist/controllers/errors.js.map +1 -0
  20. package/dist/controllers/index.d.ts +13 -0
  21. package/dist/controllers/index.js +13 -0
  22. package/dist/controllers/index.js.map +1 -0
  23. package/dist/controllers/json.d.ts +5 -0
  24. package/dist/controllers/json.js +57 -0
  25. package/dist/controllers/json.js.map +1 -0
  26. package/dist/controllers/loader.d.ts +23 -0
  27. package/dist/controllers/loader.js +74 -0
  28. package/dist/controllers/loader.js.map +1 -0
  29. package/dist/controllers/manager.d.ts +58 -0
  30. package/dist/controllers/manager.js +399 -0
  31. package/dist/controllers/manager.js.map +1 -0
  32. package/dist/controllers/results.d.ts +5 -0
  33. package/dist/controllers/results.js +32 -0
  34. package/dist/controllers/results.js.map +1 -0
  35. package/dist/controllers/sqlite.d.ts +235 -0
  36. package/dist/controllers/sqlite.js +1087 -0
  37. package/dist/controllers/sqlite.js.map +1 -0
  38. package/dist/controllers/store.d.ts +112 -0
  39. package/dist/controllers/store.js +32 -0
  40. package/dist/controllers/store.js.map +1 -0
  41. package/dist/controllers/types.d.ts +159 -0
  42. package/dist/controllers/types.js +2 -0
  43. package/dist/controllers/types.js.map +1 -0
  44. package/dist/controllers/workflow-engine-scheduler.d.ts +25 -0
  45. package/dist/controllers/workflow-engine-scheduler.js +95 -0
  46. package/dist/controllers/workflow-engine-scheduler.js.map +1 -0
  47. package/dist/controllers/workflows.d.ts +27 -0
  48. package/dist/controllers/workflows.js +109 -0
  49. package/dist/controllers/workflows.js.map +1 -0
  50. package/dist/extension/controller-host.d.ts +47 -0
  51. package/dist/extension/controller-host.js +110 -0
  52. package/dist/extension/controller-host.js.map +1 -0
  53. package/dist/extension/executor.d.ts +3 -0
  54. package/dist/extension/executor.js +12 -2
  55. package/dist/extension/executor.js.map +1 -1
  56. package/dist/extension/index.d.ts +7 -0
  57. package/dist/extension/index.js +1118 -106
  58. package/dist/extension/index.js.map +1 -1
  59. package/dist/extension/recorder.d.ts +6 -0
  60. package/dist/extension/recorder.js +20 -8
  61. package/dist/extension/recorder.js.map +1 -1
  62. package/dist/extension/workflow-tool.d.ts +28 -0
  63. package/dist/extension/workflow-tool.js +33 -0
  64. package/dist/extension/workflow-tool.js.map +1 -0
  65. package/dist/host/processes.d.ts +24 -0
  66. package/dist/host/processes.js +114 -0
  67. package/dist/host/processes.js.map +1 -0
  68. package/dist/host/rpc-bridge.d.ts +9 -0
  69. package/dist/host/rpc-bridge.js +39 -0
  70. package/dist/host/rpc-bridge.js.map +1 -0
  71. package/dist/host/rpc-executor.d.ts +38 -0
  72. package/dist/host/rpc-executor.js +254 -0
  73. package/dist/host/rpc-executor.js.map +1 -0
  74. package/dist/host/runner.d.ts +50 -0
  75. package/dist/host/runner.js +379 -0
  76. package/dist/host/runner.js.map +1 -0
  77. package/dist/viewer/cli.d.ts +7 -3
  78. package/dist/viewer/cli.js +150 -19
  79. package/dist/viewer/cli.js.map +1 -1
  80. package/dist/workflows/catalog.d.ts +43 -0
  81. package/dist/workflows/catalog.js +79 -0
  82. package/dist/workflows/catalog.js.map +1 -0
  83. package/dist/workflows/engine.d.ts +37 -2
  84. package/dist/workflows/engine.js +297 -29
  85. package/dist/workflows/engine.js.map +1 -1
  86. package/dist/workflows/errors.d.ts +23 -0
  87. package/dist/workflows/errors.js +38 -0
  88. package/dist/workflows/errors.js.map +1 -1
  89. package/dist/workflows/graph.js +0 -5
  90. package/dist/workflows/graph.js.map +1 -1
  91. package/dist/workflows/index.d.ts +2 -2
  92. package/dist/workflows/index.js +1 -1
  93. package/dist/workflows/index.js.map +1 -1
  94. package/dist/workflows/loader.d.ts +19 -15
  95. package/dist/workflows/loader.js +63 -19
  96. package/dist/workflows/loader.js.map +1 -1
  97. package/dist/workflows/migrate-sources.d.ts +41 -0
  98. package/dist/workflows/migrate-sources.js +129 -0
  99. package/dist/workflows/migrate-sources.js.map +1 -0
  100. package/dist/workflows/schema.js +3 -2
  101. package/dist/workflows/schema.js.map +1 -1
  102. package/dist/workflows/store.d.ts +50 -6
  103. package/dist/workflows/store.js +448 -53
  104. package/dist/workflows/store.js.map +1 -1
  105. package/dist/workflows/types.d.ts +27 -3
  106. package/docs/CONTROLLERS.md +215 -0
  107. package/docs/development.md +14 -9
  108. package/docs/plans/2026-08-04-controller-runtime-plan.md +169 -0
  109. package/docs/plans/2026-08-05-always-on-workflows-plan.md +125 -0
  110. package/docs/plans/2026-08-10-agent-managed-monitor-workflows-plan.md +184 -0
  111. package/docs/plans/2026-08-12-coordinated-workflow-timeouts-plan.md +74 -0
  112. package/docs/plans/2026-08-13-built-in-workflow-catalog-plan.md +97 -0
  113. package/docs/run-bundles.md +85 -28
  114. package/docs/workflows.md +161 -17
  115. package/examples/controllers/pull-request.controller.ts +215 -0
  116. package/package.json +10 -2
  117. package/src/builtins/catalog.ts +22 -0
  118. package/src/builtins/monitor.workflow.ts +302 -0
  119. package/src/controllers/conditions.ts +110 -0
  120. package/src/controllers/definition.ts +65 -0
  121. package/src/controllers/effects.ts +123 -0
  122. package/src/controllers/errors.ts +27 -0
  123. package/src/controllers/index.ts +90 -0
  124. package/src/controllers/json.ts +62 -0
  125. package/src/controllers/loader.ts +104 -0
  126. package/src/controllers/manager.ts +533 -0
  127. package/src/controllers/results.ts +46 -0
  128. package/src/controllers/sqlite.ts +1545 -0
  129. package/src/controllers/store.ts +160 -0
  130. package/src/controllers/types.ts +183 -0
  131. package/src/controllers/workflow-engine-scheduler.ts +148 -0
  132. package/src/controllers/workflows.ts +152 -0
  133. package/src/extension/controller-host.ts +163 -0
  134. package/src/extension/executor.ts +13 -2
  135. package/src/extension/index.ts +1325 -130
  136. package/src/extension/recorder.ts +65 -36
  137. package/src/extension/workflow-tool.ts +59 -0
  138. package/src/host/processes.ts +119 -0
  139. package/src/host/rpc-bridge.ts +44 -0
  140. package/src/host/rpc-executor.ts +299 -0
  141. package/src/host/runner.ts +445 -0
  142. package/src/viewer/cli.ts +167 -21
  143. package/src/workflows/catalog.ts +135 -0
  144. package/src/workflows/engine.ts +404 -41
  145. package/src/workflows/errors.ts +45 -0
  146. package/src/workflows/graph.ts +0 -5
  147. package/src/workflows/index.ts +2 -0
  148. package/src/workflows/loader.ts +76 -22
  149. package/src/workflows/migrate-sources.ts +167 -0
  150. package/src/workflows/schema.ts +3 -2
  151. package/src/workflows/store.ts +557 -48
  152. package/src/workflows/types.ts +22 -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
+ }
@@ -33,6 +33,8 @@ export type ConversationStepExecutorOptions = {
33
33
  maxNudges?: number;
34
34
  /** Conversation linkage hooks, wired to the session recorder. */
35
35
  conversation?: ConversationHooks;
36
+ /** Called when the engine aborts a pending agent step. */
37
+ onAbort?: (contract: AgentStepRequest["contract"], reason: unknown) => void;
36
38
  };
37
39
 
38
40
  type PendingStep = {
@@ -61,6 +63,7 @@ export class ConversationStepExecutor implements AgentStepExecutor {
61
63
  private readonly sendPrompt: (delivery: PromptDelivery) => void;
62
64
  private readonly maxNudges: number;
63
65
  private readonly conversation: ConversationHooks | undefined;
66
+ private readonly onAbort: ConversationStepExecutorOptions["onAbort"];
64
67
  private pending: PendingStep | null = null;
65
68
  private streaming = false;
66
69
  private heldByUser = false;
@@ -69,6 +72,7 @@ export class ConversationStepExecutor implements AgentStepExecutor {
69
72
  this.sendPrompt = options.sendPrompt;
70
73
  this.maxNudges = options.maxNudges ?? DEFAULT_MAX_NUDGES;
71
74
  this.conversation = options.conversation;
75
+ this.onAbort = options.onAbort;
72
76
  }
73
77
 
74
78
  /** Track agent streaming state (wire to agent_start / agent_settled). */
@@ -121,8 +125,15 @@ export class ConversationStepExecutor implements AgentStepExecutor {
121
125
  return await new Promise<AgentStepSubmission>((resolve, reject) => {
122
126
  const onAbort = () => {
123
127
  const reason: unknown = signal.reason ?? new Error("Workflow step aborted");
128
+ if (this.pending?.request !== request) {
129
+ return;
130
+ }
124
131
  this.clearPending();
125
- reject(reason);
132
+ try {
133
+ this.onAbort?.(request.contract, reason);
134
+ } finally {
135
+ reject(reason);
136
+ }
126
137
  };
127
138
  signal.addEventListener("abort", onAbort, { once: true });
128
139
  let markCleared!: () => void;
@@ -255,7 +266,7 @@ export class ConversationStepExecutor implements AgentStepExecutor {
255
266
  prompt: [
256
267
  `Reminder: workflow step ${JSON.stringify(nodeId)} is still awaiting your output.`,
257
268
  "Complete it by calling the `workflow` tool with:",
258
- `{"step": ${JSON.stringify(nodeId)}, "attempt": ${JSON.stringify(attemptId)}, "output": <your result>}`,
269
+ `{"action": "submit", "step": ${JSON.stringify(nodeId)}, "attempt": ${JSON.stringify(attemptId)}, "output": <your result>}`,
259
270
  `Expected output: ${pending.request.contract.expectedOutput ?? "a JSON object with your result"}`,
260
271
  ].join("\n"),
261
272
  streaming: this.streaming,