@signalridge/pi-workflows 1.2.1 → 1.3.1
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/CHANGELOG.md +36 -0
- package/README.md +142 -17
- package/package.json +14 -8
- package/skills/workflow-authoring/SKILL.md +30 -0
- package/skills/workflow-authoring/examples/adversarial-verification.js +63 -0
- package/skills/workflow-authoring/examples/bounded-semantic-retry.js +56 -0
- package/skills/workflow-authoring/examples/classify-and-act.js +67 -0
- package/skills/workflow-authoring/examples/defensive-json-parsing.js +63 -0
- package/skills/workflow-authoring/examples/fan-out-and-synthesize.js +47 -0
- package/skills/workflow-authoring/examples/generate-and-filter.js +68 -0
- package/skills/workflow-authoring/examples/loop-until-done.js +68 -0
- package/skills/workflow-authoring/examples/phased-budgets.js +75 -0
- package/skills/workflow-authoring/examples/saved-nested-workflows.js +44 -0
- package/skills/workflow-authoring/examples/structured-output.js +37 -0
- package/skills/workflow-authoring/examples/tournament.js +81 -0
- package/skills/workflow-authoring/examples/validated-gate.js +63 -0
- package/skills/workflow-authoring/references/capabilities.md +44 -0
- package/skills/workflow-authoring/references/capability-details.md +294 -0
- package/skills/workflow-authoring/references/common-helpers.md +4 -0
- package/skills/workflow-authoring/references/debugging.md +29 -0
- package/skills/workflow-authoring/references/focused-recipes.md +12 -0
- package/skills/workflow-authoring/references/helpers.md +9 -0
- package/skills/workflow-authoring/references/lifecycle.md +37 -0
- package/skills/workflow-authoring/references/pattern-selection.md +15 -0
- package/skills/workflow-authoring/references/quality-helpers.md +8 -0
- package/skills/workflow-authoring/references/registry-ownership.md +17 -0
- package/skills/workflow-authoring/references/retry-helper.md +5 -0
- package/skills/workflow-authoring/references/review.md +43 -0
- package/skills/workflow-authoring/references/runtime.md +29 -0
- package/skills/workflow-authoring/references/specialized-helpers.md +19 -0
- package/skills/workflow-authoring/references/versions.md +13 -0
- package/skills/workflow-patterns/SKILL.md +51 -0
- package/src/arming.ts +69 -0
- package/src/builtins.ts +331 -0
- package/src/capabilities.ts +318 -0
- package/src/code-review-scope.ts +144 -0
- package/src/engine.ts +1158 -2561
- package/src/errors.ts +136 -0
- package/src/index.ts +750 -103
- package/src/journal.ts +594 -1001
- package/src/rpc-client.ts +95 -24
- package/src/runtime.ts +1708 -0
- package/src/saved-workflows.ts +121 -0
- package/src/shared-store.ts +132 -0
- package/src/ui.ts +77 -40
- package/src/workflow-settings.ts +100 -0
- package/src/schema.ts +0 -215
- package/src/state-machine.ts +0 -79
- package/src/synthesis.ts +0 -140
package/src/rpc-client.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import type {
|
|
3
3
|
ManagedSpawnResponse as ProtocolManagedSpawnResponse,
|
|
4
4
|
ManagedTerminalSnapshot as ProtocolManagedTerminalSnapshot,
|
|
5
|
+
WorkflowTier,
|
|
5
6
|
} from "@signalridge/pi-subagents-protocol";
|
|
6
7
|
import {
|
|
7
8
|
PROTOCOL_VERSION,
|
|
@@ -11,7 +12,26 @@ import {
|
|
|
11
12
|
workflowTierCapabilityMatch,
|
|
12
13
|
} from "@signalridge/pi-subagents-protocol";
|
|
13
14
|
import type { WorkflowOwner } from "./journal.js";
|
|
14
|
-
|
|
15
|
+
|
|
16
|
+
/** Minimal dispatch task for a script-run agent() call. */
|
|
17
|
+
export interface DispatchTask {
|
|
18
|
+
subagent_type: string;
|
|
19
|
+
prompt: string;
|
|
20
|
+
description: string;
|
|
21
|
+
tier?: WorkflowTier;
|
|
22
|
+
/** Exact provider/model reference; resolution remains inside pi-subagents. */
|
|
23
|
+
model?: string;
|
|
24
|
+
/** Optional thinking suffix/override resolved by pi-subagents. */
|
|
25
|
+
thinking?: "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | "off";
|
|
26
|
+
/** Per-call worktree request owned by pi-subagents. */
|
|
27
|
+
isolation?: "worktree";
|
|
28
|
+
/** Forwarded toolset hint (e.g. "web-research") for pi-subagents to augment tools. */
|
|
29
|
+
toolset?: string;
|
|
30
|
+
/** Thread hint for sequential same-thread calls within one run. */
|
|
31
|
+
thread?: string;
|
|
32
|
+
/** Denied tools — prevents recursive workflow fan-out. */
|
|
33
|
+
excludeTools?: string[];
|
|
34
|
+
}
|
|
15
35
|
|
|
16
36
|
export interface WorkflowEventBus {
|
|
17
37
|
on(event: string, handler: (data: unknown) => void): () => void;
|
|
@@ -85,7 +105,7 @@ function callRpc<T>(
|
|
|
85
105
|
|
|
86
106
|
export const REQUIRED_SUBAGENTS_PROTOCOL = PROTOCOL_VERSION;
|
|
87
107
|
export const PROTOCOL_DIAGNOSTIC =
|
|
88
|
-
"@signalridge/pi-workflows requires @signalridge/pi-subagents protocol v3 with managedSpawn, lifecycleOwner, ownedStop, ownedQuiescence, and
|
|
108
|
+
"@signalridge/pi-workflows requires @signalridge/pi-subagents protocol v3 with managedSpawn, lifecycleOwner, ownedStop, ownedQuiescence, workflowTiers, and managedPolicy capabilities. " +
|
|
89
109
|
"Install @signalridge/pi-subagents and @signalridge/pi-workflows exactly once; Pi loads both from their configured pi.extensions manifests.";
|
|
90
110
|
|
|
91
111
|
export const CHILD_CONTEXT_QUERY_TIMEOUT_MS = 250;
|
|
@@ -217,10 +237,11 @@ export async function checkManagedSpawnProtocol(events: WorkflowEventBus, signal
|
|
|
217
237
|
reply.capabilities.managedSpawn !== true ||
|
|
218
238
|
reply.capabilities.lifecycleOwner !== true ||
|
|
219
239
|
reply.capabilities.ownedStop !== true ||
|
|
220
|
-
reply.capabilities.ownedQuiescence !== true
|
|
240
|
+
reply.capabilities.ownedQuiescence !== true ||
|
|
241
|
+
reply.capabilities.managedPolicy !== true
|
|
221
242
|
) {
|
|
222
243
|
throw new Error(
|
|
223
|
-
`subagents protocol v${REQUIRED_SUBAGENTS_PROTOCOL} with managed spawning, owned stop,
|
|
244
|
+
`subagents protocol v${REQUIRED_SUBAGENTS_PROTOCOL} with managed spawning, owned stop, owned quiescence, and managed policy capabilities is required`,
|
|
224
245
|
);
|
|
225
246
|
}
|
|
226
247
|
throw new Error("subagents protocol ping did not return a valid version/capability envelope");
|
|
@@ -233,6 +254,9 @@ export async function checkManagedSpawnProtocol(events: WorkflowEventBus, signal
|
|
|
233
254
|
if (!workflowTierCapabilityMatch(ping.capabilities)) {
|
|
234
255
|
throw new Error("subagents workflow tier capability is required before tiered managed spawning");
|
|
235
256
|
}
|
|
257
|
+
if (ping.capabilities.managedPolicy !== true) {
|
|
258
|
+
throw new Error("subagents managed policy capability is required before policy-bearing managed spawning");
|
|
259
|
+
}
|
|
236
260
|
}
|
|
237
261
|
|
|
238
262
|
export type ManagedTerminalSnapshot = ProtocolManagedTerminalSnapshot;
|
|
@@ -247,16 +271,19 @@ export interface ManagedSpawnClient {
|
|
|
247
271
|
* runtime context and is not sent over RPC.
|
|
248
272
|
*/
|
|
249
273
|
spawn(
|
|
250
|
-
task:
|
|
274
|
+
task: DispatchTask,
|
|
251
275
|
runId: string,
|
|
252
276
|
nodeId: string,
|
|
253
277
|
attemptId?: string,
|
|
254
278
|
legacyAttemptId?: string,
|
|
279
|
+
signal?: AbortSignal,
|
|
255
280
|
): Promise<ManagedSpawnResponse | string>;
|
|
256
281
|
/** Unscoped v2 stop, retained for existing callers. */
|
|
257
282
|
stop(agentId: string): Promise<void>;
|
|
258
283
|
/** Owner-scoped v3 stop used by workflow lifecycle operations. */
|
|
259
284
|
stopOwned?(agentId: string, owner: WorkflowOwner): Promise<void>;
|
|
285
|
+
/** Best-effort reconciliation for a spawn whose reply was lost after allocation. */
|
|
286
|
+
reconcileManaged?(spawnKey: string, owner: WorkflowOwner): Promise<ManagedSpawnResponse | string | undefined>;
|
|
260
287
|
quiesceOwned?(
|
|
261
288
|
runId: string,
|
|
262
289
|
agentIds: string[],
|
|
@@ -281,26 +308,60 @@ export function createManagedSpawnClient(
|
|
|
281
308
|
): Promise<T> => callRpc(events, channel, payload, timeoutMs, signal);
|
|
282
309
|
|
|
283
310
|
return {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
311
|
+
/**
|
|
312
|
+
* Dispatch one managed agent call.
|
|
313
|
+
*
|
|
314
|
+
* `nodeId` is the runtime's `call-${callIndex}` and `attemptId` is
|
|
315
|
+
* `attempt-${generation}`. The spawnKey is opaque to pi-subagents, so the
|
|
316
|
+
* generation is the client-side rotation knob: when a resume replays a
|
|
317
|
+
* changed call (local callHash differs from the journaled one), the engine
|
|
318
|
+
* bumps the generation and this key changes — pi-subagents then treats the
|
|
319
|
+
* new key as a fresh spawn instead of throwing its fingerprint-conflict
|
|
320
|
+
* error for the same key with a different fingerprint. Same key + same
|
|
321
|
+
* fingerprint still reuses the persisted tombstone (per-call cache).
|
|
322
|
+
*/
|
|
323
|
+
async spawn(task, runId, nodeId, attemptId, _legacyAttemptId, signal) {
|
|
324
|
+
const effectiveAttemptId = attemptId ?? `attempt-1`;
|
|
325
|
+
const spawnKey = `${runId}/${nodeId}/${effectiveAttemptId}`;
|
|
326
|
+
const requestSignal =
|
|
327
|
+
signal && sessionSignal ? AbortSignal.any([signal, sessionSignal]) : (signal ?? sessionSignal);
|
|
328
|
+
let data: unknown;
|
|
329
|
+
try {
|
|
330
|
+
data = await call<unknown>(
|
|
331
|
+
"subagents:rpc:spawn-managed",
|
|
332
|
+
{
|
|
333
|
+
requestId: requestId(),
|
|
334
|
+
spawnKey,
|
|
335
|
+
type: task.subagent_type,
|
|
336
|
+
prompt: task.prompt,
|
|
337
|
+
description: task.description,
|
|
338
|
+
...(task.tier === undefined ? {} : { tier: task.tier }),
|
|
339
|
+
...(task.toolset === undefined ? {} : { toolset: task.toolset }),
|
|
340
|
+
...(task.thread === undefined ? {} : { thread: task.thread }),
|
|
341
|
+
...(task.excludeTools === undefined ? {} : { excludeTools: task.excludeTools }),
|
|
342
|
+
...(task.model === undefined ? {} : { model: task.model }),
|
|
343
|
+
...(task.thinking === undefined ? {} : { thinking: task.thinking }),
|
|
344
|
+
...(task.isolation === undefined ? {} : { isolation: task.isolation }),
|
|
345
|
+
owner: { extension: "pi-workflows", runId, nodeId, attemptId: effectiveAttemptId },
|
|
300
346
|
},
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
)
|
|
347
|
+
requestSignal,
|
|
348
|
+
);
|
|
349
|
+
} catch (error: unknown) {
|
|
350
|
+
// Reconciliation must not inherit the signal that caused the original
|
|
351
|
+
// request to fail: shutdown/abort can happen after allocation but before
|
|
352
|
+
// the reply. Fire it independently and preserve the original error.
|
|
353
|
+
void call<unknown>(
|
|
354
|
+
"subagents:rpc:reconcile-managed",
|
|
355
|
+
{
|
|
356
|
+
requestId: requestId(),
|
|
357
|
+
spawnKey,
|
|
358
|
+
owner: { extension: "pi-workflows", runId, nodeId, attemptId: effectiveAttemptId },
|
|
359
|
+
},
|
|
360
|
+
undefined,
|
|
361
|
+
5_000,
|
|
362
|
+
).catch(() => undefined);
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
304
365
|
if (!isRecord(data) || typeof data.id !== "string" || data.id.length === 0) {
|
|
305
366
|
throw new Error("subagents managed spawn returned an invalid agent id");
|
|
306
367
|
}
|
|
@@ -320,6 +381,16 @@ export function createManagedSpawnClient(
|
|
|
320
381
|
sessionSignal,
|
|
321
382
|
);
|
|
322
383
|
},
|
|
384
|
+
async reconcileManaged(spawnKey, owner) {
|
|
385
|
+
const data = await call<unknown>(
|
|
386
|
+
"subagents:rpc:reconcile-managed",
|
|
387
|
+
{ requestId: requestId(), spawnKey, owner },
|
|
388
|
+
undefined,
|
|
389
|
+
5_000,
|
|
390
|
+
);
|
|
391
|
+
if (!isRecord(data) || typeof data.id !== "string") return undefined;
|
|
392
|
+
return parseManagedSpawnResponse(data);
|
|
393
|
+
},
|
|
323
394
|
async quiesceOwned(runId, agentIds, timeoutMs = 5_000, owners) {
|
|
324
395
|
const data = await call<unknown>(
|
|
325
396
|
"subagents:rpc:quiesce-owned",
|