@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.
- package/README.md +107 -26
- package/dist/builtins/monitor.workflow.d.ts +71 -0
- package/dist/builtins/monitor.workflow.js +234 -0
- package/dist/builtins/monitor.workflow.js.map +1 -0
- package/dist/controllers/conditions.d.ts +6 -0
- package/dist/controllers/conditions.js +68 -0
- package/dist/controllers/conditions.js.map +1 -0
- package/dist/controllers/definition.d.ts +6 -0
- package/dist/controllers/definition.js +45 -0
- package/dist/controllers/definition.js.map +1 -0
- package/dist/controllers/effects.d.ts +14 -0
- package/dist/controllers/effects.js +104 -0
- package/dist/controllers/effects.js.map +1 -0
- package/dist/controllers/errors.d.ts +12 -0
- package/dist/controllers/errors.js +25 -0
- package/dist/controllers/errors.js.map +1 -0
- package/dist/controllers/index.d.ts +13 -0
- package/dist/controllers/index.js +13 -0
- package/dist/controllers/index.js.map +1 -0
- package/dist/controllers/json.d.ts +5 -0
- package/dist/controllers/json.js +57 -0
- package/dist/controllers/json.js.map +1 -0
- package/dist/controllers/loader.d.ts +23 -0
- package/dist/controllers/loader.js +74 -0
- package/dist/controllers/loader.js.map +1 -0
- package/dist/controllers/manager.d.ts +58 -0
- package/dist/controllers/manager.js +399 -0
- package/dist/controllers/manager.js.map +1 -0
- package/dist/controllers/results.d.ts +5 -0
- package/dist/controllers/results.js +32 -0
- package/dist/controllers/results.js.map +1 -0
- package/dist/controllers/sqlite.d.ts +212 -0
- package/dist/controllers/sqlite.js +1009 -0
- package/dist/controllers/sqlite.js.map +1 -0
- package/dist/controllers/store.d.ts +112 -0
- package/dist/controllers/store.js +32 -0
- package/dist/controllers/store.js.map +1 -0
- package/dist/controllers/types.d.ts +159 -0
- package/dist/controllers/types.js +2 -0
- package/dist/controllers/types.js.map +1 -0
- package/dist/controllers/workflow-engine-scheduler.d.ts +25 -0
- package/dist/controllers/workflow-engine-scheduler.js +93 -0
- package/dist/controllers/workflow-engine-scheduler.js.map +1 -0
- package/dist/controllers/workflows.d.ts +27 -0
- package/dist/controllers/workflows.js +109 -0
- package/dist/controllers/workflows.js.map +1 -0
- package/dist/extension/controller-host.d.ts +47 -0
- package/dist/extension/controller-host.js +110 -0
- package/dist/extension/controller-host.js.map +1 -0
- package/dist/extension/executor.d.ts +14 -1
- package/dist/extension/executor.js +12 -2
- package/dist/extension/executor.js.map +1 -1
- package/dist/extension/index.d.ts +7 -0
- package/dist/extension/index.js +1118 -89
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/recorder.d.ts +91 -0
- package/dist/extension/recorder.js +537 -0
- package/dist/extension/recorder.js.map +1 -0
- package/dist/extension/session-events.d.ts +134 -0
- package/dist/extension/session-events.js +60 -0
- package/dist/extension/session-events.js.map +1 -0
- package/dist/extension/widget.js +25 -24
- package/dist/extension/widget.js.map +1 -1
- package/dist/extension/workflow-tool.d.ts +28 -0
- package/dist/extension/workflow-tool.js +33 -0
- package/dist/extension/workflow-tool.js.map +1 -0
- package/dist/host/processes.d.ts +24 -0
- package/dist/host/processes.js +114 -0
- package/dist/host/processes.js.map +1 -0
- package/dist/host/rpc-bridge.d.ts +9 -0
- package/dist/host/rpc-bridge.js +39 -0
- package/dist/host/rpc-bridge.js.map +1 -0
- package/dist/host/rpc-executor.d.ts +38 -0
- package/dist/host/rpc-executor.js +254 -0
- package/dist/host/rpc-executor.js.map +1 -0
- package/dist/host/runner.d.ts +49 -0
- package/dist/host/runner.js +350 -0
- package/dist/host/runner.js.map +1 -0
- package/dist/render/canvas.d.ts +1 -1
- package/dist/render/canvas.js +5 -0
- package/dist/render/canvas.js.map +1 -1
- package/dist/render/graph-render.d.ts +5 -0
- package/dist/render/graph-render.js +211 -48
- package/dist/render/graph-render.js.map +1 -1
- package/dist/viewer/cli.d.ts +7 -3
- package/dist/viewer/cli.js +150 -19
- package/dist/viewer/cli.js.map +1 -1
- package/dist/viewer/render.js +19 -3
- package/dist/viewer/render.js.map +1 -1
- package/dist/viewer/session-reducer.d.ts +45 -0
- package/dist/viewer/session-reducer.js +266 -0
- package/dist/viewer/session-reducer.js.map +1 -0
- package/dist/workflows/artifacts.d.ts +40 -0
- package/dist/workflows/artifacts.js +155 -0
- package/dist/workflows/artifacts.js.map +1 -0
- package/dist/workflows/engine.d.ts +38 -0
- package/dist/workflows/engine.js +281 -20
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/errors.d.ts +23 -0
- package/dist/workflows/errors.js +38 -0
- package/dist/workflows/errors.js.map +1 -1
- package/dist/workflows/graph.js +0 -5
- package/dist/workflows/graph.js.map +1 -1
- package/dist/workflows/index.d.ts +3 -2
- package/dist/workflows/index.js +2 -1
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/loader.d.ts +5 -3
- package/dist/workflows/loader.js +10 -1
- package/dist/workflows/loader.js.map +1 -1
- package/dist/workflows/schema.js +1 -1
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/store.d.ts +98 -10
- package/dist/workflows/store.js +921 -46
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/types.d.ts +136 -3
- package/docs/CONTROLLERS.md +215 -0
- package/docs/development.md +51 -24
- package/docs/live-replay-protocol.md +155 -0
- package/docs/plans/2026-08-04-controller-runtime-plan.md +169 -0
- package/docs/plans/2026-08-05-always-on-workflows-plan.md +125 -0
- package/docs/plans/2026-08-10-agent-managed-monitor-workflows-plan.md +184 -0
- package/docs/plans/piw-viewer-experience-implementation-plan.md +674 -0
- package/docs/plans/replayable-run-bundles-implementation-plan.md +65 -0
- package/docs/plans/session-event-replay-implementation-plan.md +494 -0
- package/docs/plans/tui-viewer-implementation-plan.md +64 -0
- package/docs/run-bundles.md +359 -55
- package/docs/session-event-journal.md +470 -0
- package/docs/tui-viewer.md +218 -0
- package/docs/workflows.md +131 -11
- package/examples/controllers/pull-request.controller.ts +215 -0
- package/package.json +11 -2
- package/src/builtins/monitor.workflow.ts +278 -0
- package/src/controllers/conditions.ts +110 -0
- package/src/controllers/definition.ts +65 -0
- package/src/controllers/effects.ts +123 -0
- package/src/controllers/errors.ts +27 -0
- package/src/controllers/index.ts +90 -0
- package/src/controllers/json.ts +62 -0
- package/src/controllers/loader.ts +104 -0
- package/src/controllers/manager.ts +533 -0
- package/src/controllers/results.ts +46 -0
- package/src/controllers/sqlite.ts +1427 -0
- package/src/controllers/store.ts +160 -0
- package/src/controllers/types.ts +183 -0
- package/src/controllers/workflow-engine-scheduler.ts +145 -0
- package/src/controllers/workflows.ts +152 -0
- package/src/extension/controller-host.ts +163 -0
- package/src/extension/executor.ts +29 -2
- package/src/extension/index.ts +1315 -109
- package/src/extension/recorder.ts +662 -0
- package/src/extension/session-events.ts +119 -0
- package/src/extension/widget.ts +26 -24
- package/src/extension/workflow-tool.ts +59 -0
- package/src/host/processes.ts +119 -0
- package/src/host/rpc-bridge.ts +44 -0
- package/src/host/rpc-executor.ts +299 -0
- package/src/host/runner.ts +406 -0
- package/src/render/canvas.ts +19 -1
- package/src/render/graph-render.ts +277 -44
- package/src/viewer/cli.ts +167 -21
- package/src/viewer/render.ts +21 -3
- package/src/viewer/session-reducer.ts +347 -0
- package/src/workflows/artifacts.ts +188 -0
- package/src/workflows/engine.ts +365 -19
- package/src/workflows/errors.ts +45 -0
- package/src/workflows/graph.ts +0 -5
- package/src/workflows/index.ts +15 -0
- package/src/workflows/loader.ts +13 -3
- package/src/workflows/schema.ts +1 -1
- package/src/workflows/store.ts +1157 -48
- 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
|
-
|
|
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,
|