@pi-unipi/subagents 2.6.1 → 2.6.2

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 (107) hide show
  1. package/README.md +89 -78
  2. package/agents/delegate.md +14 -0
  3. package/agents/oracle.md +78 -0
  4. package/agents/researcher.md +52 -0
  5. package/agents/reviewer.md +79 -0
  6. package/agents/scout.md +50 -0
  7. package/agents/worker.md +59 -0
  8. package/package.json +19 -17
  9. package/prompts/council.md +48 -0
  10. package/prompts/gather-context-and-clarify.md +13 -0
  11. package/prompts/parallel-cleanup.md +59 -0
  12. package/prompts/parallel-research.md +50 -0
  13. package/prompts/parallel-review.md +54 -0
  14. package/prompts/review-loop.md +43 -0
  15. package/skills/council-mode/SKILL.md +230 -0
  16. package/skills/subagents/SKILL.md +49 -0
  17. package/skills/subagents/references/constraints-and-recipes.md +259 -0
  18. package/skills/subagents/references/execution-controls.md +454 -0
  19. package/skills/subagents/references/management-authoring-rpc.md +161 -0
  20. package/skills/subagents/references/multi-lane-orchestration.md +39 -0
  21. package/skills/subagents/references/prompting-and-roles.md +267 -0
  22. package/src/acceptance.ts +331 -0
  23. package/src/agent-manager.ts +405 -0
  24. package/src/agent-memory.ts +300 -0
  25. package/src/agent-overrides.ts +183 -0
  26. package/src/agent-runner.ts +265 -0
  27. package/src/async-runner.ts +417 -0
  28. package/src/authority-policy.ts +47 -0
  29. package/src/budgets.ts +285 -0
  30. package/src/child-safety.ts +140 -0
  31. package/src/config.ts +226 -0
  32. package/src/conversation-viewer.ts +281 -0
  33. package/src/core-compat.ts +94 -0
  34. package/src/custom-agents.ts +323 -0
  35. package/src/file-system-retry.ts +50 -0
  36. package/src/fleet-data.ts +61 -0
  37. package/src/fleet-view.ts +316 -0
  38. package/src/foreground-detach.ts +59 -0
  39. package/src/fork-context.ts +285 -0
  40. package/src/global.d.ts +7 -0
  41. package/src/guide.ts +129 -0
  42. package/src/index.ts +1149 -0
  43. package/src/mission-state.ts +133 -0
  44. package/src/mission-store.ts +434 -0
  45. package/src/model-resolver.ts +79 -0
  46. package/src/output-limits.ts +142 -0
  47. package/src/parity-types.ts +315 -0
  48. package/src/pi-args.ts +161 -0
  49. package/src/pi-spawn.ts +147 -0
  50. package/src/result-files.ts +232 -0
  51. package/src/result-watcher.ts +221 -0
  52. package/src/retained-children.ts +176 -0
  53. package/src/run-fanout-budget.ts +356 -0
  54. package/src/scheduled-runs.ts +351 -0
  55. package/src/schemas.ts +263 -0
  56. package/src/slash-commands.ts +77 -0
  57. package/src/supervisor-channel.ts +249 -0
  58. package/src/tool-handler.ts +1144 -0
  59. package/src/types.ts +238 -0
  60. package/src/widget.ts +447 -0
  61. package/src/workflow-script.ts +737 -0
  62. package/src/workflow-worker.ts +384 -0
  63. package/src/worktree.ts +614 -0
  64. package/dist/agent-manager.d.ts +0 -81
  65. package/dist/agent-manager.d.ts.map +0 -1
  66. package/dist/agent-manager.js +0 -295
  67. package/dist/agent-manager.js.map +0 -1
  68. package/dist/agent-runner.d.ts +0 -51
  69. package/dist/agent-runner.d.ts.map +0 -1
  70. package/dist/agent-runner.js +0 -262
  71. package/dist/agent-runner.js.map +0 -1
  72. package/dist/config.d.ts +0 -24
  73. package/dist/config.d.ts.map +0 -1
  74. package/dist/config.js +0 -132
  75. package/dist/config.js.map +0 -1
  76. package/dist/conversation-viewer.d.ts +0 -40
  77. package/dist/conversation-viewer.d.ts.map +0 -1
  78. package/dist/conversation-viewer.js +0 -276
  79. package/dist/conversation-viewer.js.map +0 -1
  80. package/dist/core-compat.d.ts +0 -21
  81. package/dist/core-compat.d.ts.map +0 -1
  82. package/dist/core-compat.js +0 -86
  83. package/dist/core-compat.js.map +0 -1
  84. package/dist/custom-agents.d.ts +0 -14
  85. package/dist/custom-agents.d.ts.map +0 -1
  86. package/dist/custom-agents.js +0 -113
  87. package/dist/custom-agents.js.map +0 -1
  88. package/dist/file-lock.d.ts +0 -42
  89. package/dist/file-lock.d.ts.map +0 -1
  90. package/dist/file-lock.js +0 -91
  91. package/dist/file-lock.js.map +0 -1
  92. package/dist/index.d.ts +0 -10
  93. package/dist/index.d.ts.map +0 -1
  94. package/dist/index.js +0 -758
  95. package/dist/index.js.map +0 -1
  96. package/dist/model-resolver.d.ts +0 -19
  97. package/dist/model-resolver.d.ts.map +0 -1
  98. package/dist/model-resolver.js +0 -61
  99. package/dist/model-resolver.js.map +0 -1
  100. package/dist/types.d.ts +0 -98
  101. package/dist/types.d.ts.map +0 -1
  102. package/dist/types.js +0 -47
  103. package/dist/types.js.map +0 -1
  104. package/dist/widget.d.ts +0 -56
  105. package/dist/widget.d.ts.map +0 -1
  106. package/dist/widget.js +0 -396
  107. package/dist/widget.js.map +0 -1
@@ -0,0 +1,384 @@
1
+ /**
2
+ * @pi-unipi/subagents — workflowScript sandbox worker
3
+ *
4
+ * Ported from pi-subagents src/workflows/scripted-workflow.ts (worker half).
5
+ * Runs inside a Worker thread: validates the script (acorn AST — no nested
6
+ * async helpers), compiles it into a vm context with { runs, state, emit,
7
+ * console, Promise } globals, and bridges calls back to the host via
8
+ * parentPort RPC. No filesystem, shell, or host globals reach the script.
9
+ */
10
+
11
+ import { parentPort, workerData } from "node:worker_threads";
12
+ import { inspect } from "node:util";
13
+ import { createRequire } from "node:module";
14
+ import vm from "node:vm";
15
+
16
+ const { parse } = createRequire(import.meta.url)(workerData.acornPath as string) as {
17
+ parse: (source: string, options: { ecmaVersion: string; sourceType: string }) => unknown;
18
+ };
19
+
20
+ type PendingEntry = { resolve: (value: unknown) => void; reject: (error: Error) => void };
21
+ const pending = new Map<number, PendingEntry>();
22
+ let nextCallId = 0;
23
+
24
+ const runKeyPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
25
+
26
+ const NESTED_ASYNC_WORKFLOW_ERROR =
27
+ "workflowScript does not support nested async functions. Use top-level await, plain helper functions that return runs.run(...), or explicit Promise chains so workflows stay portable across Node and Bun.";
28
+ const AST_SCALAR_KEYS = new Set(["type", "start", "end"]);
29
+
30
+ function isAsyncFunctionNode(node: unknown): boolean {
31
+ return (
32
+ !!node &&
33
+ typeof node === "object" &&
34
+ (node as { async?: unknown }).async === true &&
35
+ ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].includes(
36
+ (node as { type?: unknown }).type as string,
37
+ )
38
+ );
39
+ }
40
+
41
+ function walkWorkflowAst(node: unknown, allowedAsyncFunction: unknown): void {
42
+ if (!node || typeof node !== "object") return;
43
+ if (Array.isArray(node)) {
44
+ for (const item of node) walkWorkflowAst(item, allowedAsyncFunction);
45
+ return;
46
+ }
47
+ if (node !== allowedAsyncFunction && isAsyncFunctionNode(node)) {
48
+ throw new Error(NESTED_ASYNC_WORKFLOW_ERROR);
49
+ }
50
+ for (const [key, child] of Object.entries(node as Record<string, unknown>)) {
51
+ if (AST_SCALAR_KEYS.has(key)) continue;
52
+ walkWorkflowAst(child, allowedAsyncFunction);
53
+ }
54
+ }
55
+
56
+ function workflowWrapperFunction(ast: unknown): unknown {
57
+ const wrapper = (
58
+ ast as { body?: Array<{ expression?: { callee?: unknown } }> }
59
+ ).body?.[0]?.expression?.callee;
60
+ if (
61
+ !wrapper ||
62
+ (wrapper as { type?: unknown }).type !== "ArrowFunctionExpression"
63
+ ) {
64
+ throw new Error("workflowScript wrapper parse invariant failed.");
65
+ }
66
+ return wrapper;
67
+ }
68
+
69
+ function assertPortableWorkflowScript(source: string): void {
70
+ const wrapped = "(async () => {\n" + source + "\n})()";
71
+ const ast = parse(wrapped, { ecmaVersion: "latest", sourceType: "script" });
72
+ const wrapper = workflowWrapperFunction(ast);
73
+ walkWorkflowAst(
74
+ (wrapper as { body?: unknown }).body,
75
+ wrapper,
76
+ );
77
+ }
78
+
79
+ let contextObjectPrototype: object | undefined;
80
+
81
+ function assertJsonValue(value: unknown, path = "emit", seen = new Set<unknown>()): void {
82
+ if (value === null || typeof value === "string" || typeof value === "boolean") return;
83
+ if (typeof value === "number") {
84
+ if (!Number.isFinite(value)) throw new Error(path + " must contain only finite JSON numbers.");
85
+ return;
86
+ }
87
+ if (typeof value !== "object") throw new Error(path + " must be a JSON value; received " + typeof value + ".");
88
+ if (seen.has(value)) throw new Error(path + " must not contain cycles.");
89
+ seen.add(value);
90
+ if (Array.isArray(value)) {
91
+ for (let index = 0; index < value.length; index++) {
92
+ if (!Object.prototype.hasOwnProperty.call(value, index)) {
93
+ throw new Error(path + " must not contain sparse array entries.");
94
+ }
95
+ assertJsonValue(value[index], path + "[" + index + "]", seen);
96
+ }
97
+ } else {
98
+ const prototype = Object.getPrototypeOf(value);
99
+ if (
100
+ prototype !== null &&
101
+ prototype !== Object.prototype &&
102
+ prototype !== contextObjectPrototype
103
+ ) {
104
+ throw new Error(path + " must contain only plain JSON objects.");
105
+ }
106
+ if (Object.getOwnPropertySymbols(value).length > 0) {
107
+ throw new Error(path + " must not contain symbol keys.");
108
+ }
109
+ for (const [key, entry] of Object.entries(value)) {
110
+ assertJsonValue(entry, path + "." + key, seen);
111
+ }
112
+ }
113
+ seen.delete(value);
114
+ }
115
+
116
+ function stableRunJson(value: unknown): string {
117
+ if (Array.isArray(value)) return `[${value.map(stableRunJson).join(",")}]`;
118
+ if (value && typeof value === "object" && !Array.isArray(value)) {
119
+ const record = value as Record<string, unknown>;
120
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableRunJson(record[key])}`).join(",")}}`;
121
+ }
122
+ return JSON.stringify(value) ?? "undefined";
123
+ }
124
+
125
+ function hostCall(method: string, args: unknown, observation?: { key?: string; operation?: string }): Promise<unknown> {
126
+ const callId = ++nextCallId;
127
+ const promise = new Promise((resolve, reject) => {
128
+ pending.set(callId, { resolve, reject });
129
+ parentPort!.postMessage({ type: "call", callId, method, args });
130
+ });
131
+ if (!observation) return promise;
132
+ // Await-observation: the script consuming .then/.catch/.finally marks the
133
+ // call observed so the host can enforce the unawaited-launch contract.
134
+ let observed = false;
135
+ const markObserved = () => {
136
+ if (observed) return;
137
+ observed = true;
138
+ parentPort!.postMessage({ type: "callObserved", callId, key: observation.key, operation: observation.operation });
139
+ };
140
+ return new Proxy(promise, {
141
+ get(target, prop, receiver) {
142
+ if (prop === "then" || prop === "catch" || prop === "finally") {
143
+ const fn = Reflect.get(target, prop, target) as (...a: unknown[]) => unknown;
144
+ return function (this: unknown, ...args: unknown[]): unknown {
145
+ markObserved();
146
+ return fn.apply(target, args);
147
+ };
148
+ }
149
+ const value = Reflect.get(target, prop, target);
150
+ return typeof value === "function" ? (value as (...a: unknown[]) => unknown).bind(target) : value;
151
+ },
152
+ }) as Promise<unknown>;
153
+ }
154
+
155
+ function validateRunCall(key: unknown, params: unknown, label: string, fingerprints: Map<string, string>): void {
156
+ if (typeof key !== "string" || !runKeyPattern.test(key)) throw new Error(label + " has an invalid key.");
157
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
158
+ throw new Error(label + " requires a params object.");
159
+ }
160
+ const p = params as Record<string, unknown>;
161
+ for (const forbidden of ["action", "workflowScript", "tasks", "chain", "parallel", "concurrency", "chainDir"]) {
162
+ if (Object.prototype.hasOwnProperty.call(p, forbidden)) {
163
+ const hint = label === "runs.run" ? "; use runs.all(...) and JavaScript control flow for orchestration." : ".";
164
+ throw new Error(label + " accepts one child via { agent, task } and execution controls only" + hint);
165
+ }
166
+ }
167
+ if (Object.prototype.hasOwnProperty.call(p, "clarify")) {
168
+ throw new Error(label + " does not support clarify UI.");
169
+ }
170
+ if (p.worktree !== undefined && typeof p.worktree !== "boolean") {
171
+ throw new Error(label + " worktree must be true or false.");
172
+ }
173
+ if (p.resume !== undefined && p.agent !== undefined) {
174
+ throw new Error(label + " resume and agent are mutually exclusive.");
175
+ }
176
+ if (typeof p.resume === "string" && !p.resume.trim()) {
177
+ throw new Error(label + " resume must be a non-empty retained run id.");
178
+ }
179
+ if (p.resume !== undefined && (typeof p.task !== "string" || !(p.task as string).trim())) {
180
+ throw new Error(label + " resume requires a non-empty task follow-up.");
181
+ }
182
+ assertJsonValue(params, label + " params");
183
+ const fingerprint = stableRunJson(params);
184
+ const existing = fingerprints.get(key);
185
+ if (existing !== undefined && existing !== fingerprint) {
186
+ throw new Error("Duplicate workflow key '" + key + "' used with incompatible launch params.");
187
+ }
188
+ fingerprints.set(key, fingerprint);
189
+ }
190
+
191
+ const runs = Object.freeze({
192
+ run(key: unknown, params: unknown): Promise<unknown> {
193
+ validateRunCall(key, params, "runs.run", runFingerprints);
194
+ return hostCall("run", { key, params }, { key: key as string, operation: "run" });
195
+ },
196
+ all(items: unknown): Promise<unknown> {
197
+ if (!Array.isArray(items)) throw new Error("runs.all(items) requires an array.");
198
+ const fingerprints = new Map(runFingerprints);
199
+ const calls: Array<{ key: string; params: Record<string, unknown> }> = [];
200
+ for (let index = 0; index < items.length; index++) {
201
+ if (!Object.prototype.hasOwnProperty.call(items, index)) {
202
+ throw new Error("runs.all items must not contain sparse entries.");
203
+ }
204
+ const item = items[index];
205
+ if (!item || typeof item !== "object" || Array.isArray(item)) {
206
+ throw new Error("runs.all item " + index + " must be an object.");
207
+ }
208
+ const { key, ...params } = item as { key: unknown };
209
+ validateRunCall(key, params, "runs.all item " + index, fingerprints);
210
+ calls.push({ key: key as string, params: params as Record<string, unknown> });
211
+ }
212
+ runFingerprints = fingerprints;
213
+ return hostCall("all", { calls }, { operation: "all" });
214
+ },
215
+ steer(key: unknown, message: unknown, options: unknown = {}): Promise<unknown> {
216
+ if (typeof key !== "string" || !runKeyPattern.test(key)) throw new Error("runs.steer has an invalid key.");
217
+ if (typeof message !== "string" || !message.trim()) throw new Error("runs.steer message must be a non-empty string.");
218
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
219
+ throw new Error("runs.steer options must be an object.");
220
+ }
221
+ const allowed = new Set(["mode", "index", "ackTimeoutMs"]);
222
+ for (const option of Object.keys(options as Record<string, unknown>)) {
223
+ if (!allowed.has(option)) throw new Error("runs.steer options contain unsupported field '" + option + "'.");
224
+ }
225
+ const o = options as Record<string, unknown>;
226
+ if (o.mode !== undefined && o.mode !== "steer" && o.mode !== "follow_up" && o.mode !== "auto") {
227
+ throw new Error("runs.steer mode must be 'steer', 'follow_up', or 'auto'.");
228
+ }
229
+ return hostCall("steer", { key, message: (message as string).trim(), options }, { key: key as string, operation: "steer" });
230
+ },
231
+ status(keyOrRunId: unknown): Promise<unknown> {
232
+ return hostCall("status", { keyOrRunId });
233
+ },
234
+ ref(result: unknown): string {
235
+ if (!result || typeof result !== "object") throw new Error("runs.ref(result) requires a run result object.");
236
+ const r = result as { key?: unknown; runId?: unknown };
237
+ const parts = ["run " + (r.key || "unknown")];
238
+ if (r.runId) parts.push("id=" + String(r.runId).slice(0, 8));
239
+ return "[" + parts.join("; ") + "]";
240
+ },
241
+ refs(results: unknown): string {
242
+ if (!Array.isArray(results)) throw new Error("runs.refs(results) requires an array.");
243
+ return results.map((result) => runs.ref(result)).join("\n");
244
+ },
245
+ });
246
+
247
+ let runFingerprints = new Map<string, string>();
248
+
249
+ function validateStateKey(key: unknown): string {
250
+ if (typeof key !== "string" || !runKeyPattern.test(key)) {
251
+ throw new Error("state key must be 1-128 characters using letters, numbers, '.', '_' or '-', and start with a letter or number.");
252
+ }
253
+ return key;
254
+ }
255
+
256
+ const state = Object.freeze({
257
+ get(key: unknown): Promise<unknown> {
258
+ return hostCall("state.get", { key: validateStateKey(key) });
259
+ },
260
+ set(key: unknown, value: unknown): Promise<unknown> {
261
+ const validKey = validateStateKey(key);
262
+ assertJsonValue(value, "state.set('" + validKey + "') value");
263
+ return hostCall("state.set", { key: validKey, value });
264
+ },
265
+ });
266
+
267
+ const capturedConsole = Object.freeze(
268
+ Object.fromEntries(
269
+ (["log", "info", "warn", "error"] as const).map((level) => [
270
+ level,
271
+ (...args: unknown[]) => {
272
+ parentPort!.postMessage({
273
+ type: "console",
274
+ level,
275
+ text: args
276
+ .map((value) => (typeof value === "string" ? value : inspect(value, { depth: 4, breakLength: 120 })))
277
+ .join(" "),
278
+ });
279
+ },
280
+ ]),
281
+ ),
282
+ );
283
+
284
+ function formatWorkflowScriptError(error: unknown): string {
285
+ const message = error && typeof (error as Error).message === "string" ? (error as Error).message : String(error);
286
+ const stack = error && typeof (error as Error).stack === "string" ? (error as Error).stack : "";
287
+ if (!stack) return message;
288
+ return stack.includes(message) ? stack : message + "\n" + stack;
289
+ }
290
+
291
+ function isSyntaxError(error: unknown): boolean {
292
+ return error instanceof SyntaxError || (error as { name?: unknown } | null)?.name === "SyntaxError";
293
+ }
294
+
295
+ function formatWorkflowScriptSyntaxError(error: unknown): string {
296
+ const details = formatWorkflowScriptError(error);
297
+ return [
298
+ "workflowScript must be valid JavaScript.",
299
+ 'If task text contains Markdown fences or backticks, use an array joined with "\\n" or escaped strings instead of a raw backtick template literal.',
300
+ "",
301
+ "Original SyntaxError:",
302
+ details,
303
+ ].join("\n");
304
+ }
305
+
306
+ parentPort!.on("message", (message: Record<string, unknown>) => {
307
+ if (message.type === "response") {
308
+ const entry = pending.get(message.callId as number);
309
+ if (!entry) return;
310
+ pending.delete(message.callId as number);
311
+ if (message.ok) entry.resolve(message.value);
312
+ else {
313
+ const error = new Error(String(message.error));
314
+ if (message.errorKind === "detached-child") {
315
+ (error as Error & { workflowErrorKind?: string }).workflowErrorKind = "detached-child";
316
+ }
317
+ entry.reject(error);
318
+ }
319
+ return;
320
+ }
321
+ if (message.type !== "start") return;
322
+
323
+ try {
324
+ const sandbox: Record<string, unknown> = {
325
+ runs,
326
+ emit(value: unknown): void {
327
+ assertJsonValue(value);
328
+ parentPort!.postMessage({ type: "emit", value });
329
+ },
330
+ console: capturedConsole,
331
+ };
332
+ if (message.stateEnabled) sandbox.state = state;
333
+ const context = vm.createContext(sandbox, { codeGeneration: { strings: false, wasm: false } });
334
+ contextObjectPrototype = vm.runInContext("Object.prototype", context) as object;
335
+
336
+ let compiled: vm.Script;
337
+ try {
338
+ assertPortableWorkflowScript(String(message.script));
339
+ compiled = new vm.Script("(async () => {\n" + String(message.script) + "\n})()", {
340
+ filename: "workflow-script.js",
341
+ });
342
+ } catch (error) {
343
+ parentPort!.postMessage({
344
+ type: "error",
345
+ error: isSyntaxError(error) ? formatWorkflowScriptSyntaxError(error) : formatWorkflowScriptError(error),
346
+ });
347
+ return;
348
+ }
349
+
350
+ compiled
351
+ .runInContext(context)
352
+ .then(
353
+ (value: unknown) => {
354
+ try {
355
+ // Ordinary statement-body semantics: a script without an explicit
356
+ // return resolves undefined, which serializes as null (reference test).
357
+ const normalized = value === undefined ? null : value;
358
+ assertJsonValue(normalized, "return");
359
+ parentPort!.postMessage({ type: "complete", value: normalized });
360
+ } catch (error) {
361
+ parentPort!.postMessage({
362
+ type: "error",
363
+ error:
364
+ "Workflow return could not be persisted: " +
365
+ (error instanceof Error ? error.message : String(error)),
366
+ });
367
+ }
368
+ },
369
+ (error: unknown) => {
370
+ const e = error as Error & { workflowErrorKind?: unknown };
371
+ parentPort!.postMessage({
372
+ type: "error",
373
+ error: formatWorkflowScriptError(error),
374
+ ...(e?.workflowErrorKind === "detached-child" ? { errorKind: "detached-child" } : {}),
375
+ });
376
+ },
377
+ )
378
+ .catch(() => {
379
+ // Post-message failures above are already best-effort; never crash the worker.
380
+ });
381
+ } catch (error) {
382
+ parentPort!.postMessage({ type: "error", error: formatWorkflowScriptError(error) });
383
+ }
384
+ });