pi-subagents 0.59.0 → 0.61.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 (72) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/docs/agents.md +2 -2
  3. package/docs/configuration.md +9 -5
  4. package/docs/extension-api.md +14 -7
  5. package/docs/models.md +1 -1
  6. package/docs/observability.md +1 -1
  7. package/docs/tool-reference.md +15 -3
  8. package/docs/workflows.md +15 -14
  9. package/install.mjs +2 -1
  10. package/package.json +1 -1
  11. package/skills/council-mode/SKILL.md +48 -243
  12. package/skills/council-mode/references/pass-contracts.md +150 -0
  13. package/skills/pi-subagents/SKILL.md +89 -37
  14. package/skills/pi-subagents/references/constraints-and-recipes.md +30 -234
  15. package/skills/pi-subagents/references/execution-controls.md +79 -7
  16. package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  17. package/skills/pi-subagents/references/multi-lane-orchestration.md +13 -1
  18. package/skills/pi-subagents/references/prompting-and-roles.md +35 -28
  19. package/skills/pi-subagents/references/review-and-validation.md +73 -0
  20. package/src/agents/agent-management.ts +256 -88
  21. package/src/agents/agents.ts +527 -221
  22. package/src/api/background-work.ts +7 -2
  23. package/src/api/external-runs.ts +67 -4
  24. package/src/api/preflight.ts +13 -8
  25. package/src/api/shared-types.ts +3 -0
  26. package/src/extension/index.ts +7 -4
  27. package/src/extension/public-execution.ts +48 -4
  28. package/src/extension/rpc.ts +62 -4
  29. package/src/extension/schemas.ts +12 -7
  30. package/src/extension/tool-description.ts +16 -16
  31. package/src/runs/background/async-execution.ts +53 -37
  32. package/src/runs/background/async-job-tracker.ts +65 -3
  33. package/src/runs/background/async-resume.ts +3 -1
  34. package/src/runs/background/async-status.ts +104 -11
  35. package/src/runs/background/auto-drain.ts +1 -1
  36. package/src/runs/background/control-channel.ts +3 -2
  37. package/src/runs/background/fleet-view.ts +1 -1
  38. package/src/runs/background/result-watcher.ts +1 -1
  39. package/src/runs/background/resume-guidance.ts +1 -1
  40. package/src/runs/background/run-status.ts +15 -4
  41. package/src/runs/background/subagent-runner.ts +6 -3
  42. package/src/runs/background/subagent-wait.ts +30 -23
  43. package/src/runs/background/wait-completions.ts +4 -1
  44. package/src/runs/background/wait-tool.ts +24 -18
  45. package/src/runs/foreground/execution.ts +72 -4
  46. package/src/runs/foreground/subagent-executor.ts +276 -59
  47. package/src/runs/shared/acceptance.ts +43 -18
  48. package/src/runs/shared/async-status-projection.ts +138 -4
  49. package/src/runs/shared/background-process-options.ts +9 -0
  50. package/src/runs/shared/host-step-status.ts +1 -0
  51. package/src/runs/shared/mcp-direct-tool-grant.ts +2 -5
  52. package/src/runs/shared/model-fallback.ts +61 -17
  53. package/src/runs/shared/mutation-evidence.ts +52 -3
  54. package/src/runs/shared/permissions.ts +1 -1
  55. package/src/runs/shared/pi-args.ts +47 -1
  56. package/src/runs/shared/single-output.ts +45 -18
  57. package/src/runs/shared/subagent-prompt-runtime.ts +20 -2
  58. package/src/runs/shared/tool-timeout.ts +1 -1
  59. package/src/runs/shared/workflow-graph.ts +16 -0
  60. package/src/shared/types.ts +74 -4
  61. package/src/shared/workflow-child-permit.ts +91 -0
  62. package/src/slash/prompt-template-bridge.ts +37 -1
  63. package/src/slash/slash-commands.ts +18 -26
  64. package/src/tui/fleet-status.ts +11 -3
  65. package/src/tui/render-helpers.ts +31 -0
  66. package/src/tui/render.ts +673 -145
  67. package/src/watchdog/change-signature.ts +40 -1
  68. package/src/workflows/host-command.ts +6 -1
  69. package/src/workflows/scripted-workflow.ts +206 -6
  70. package/src/workflows/workflow-child-summary.ts +1 -1
  71. package/src/workflows/workflow-receipt.ts +41 -4
  72. package/src/workflows/workflow-resources.ts +150 -0
@@ -0,0 +1,150 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { stableJsonDigest } from "../shared/launch-contract.ts";
3
+ import {
4
+ createWorkflowResourcePermit,
5
+ type WorkflowResourceAuthority,
6
+ type WorkflowResourcePermit,
7
+ } from "../shared/workflow-child-permit.ts";
8
+ import type { WorkflowResourceProvenanceV1 } from "../shared/types.ts";
9
+
10
+ const RESOURCE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
11
+ const MAX_ARGS_BYTES = 16 * 1024;
12
+ const MAX_STRING_BYTES = 16 * 1024;
13
+
14
+ export interface ResolvedWorkflowResource {
15
+ script: string;
16
+ permit: WorkflowResourcePermit;
17
+ provenance: WorkflowResourceProvenanceV1;
18
+ }
19
+
20
+ export type WorkflowResourceResolution =
21
+ | { ok: true; resource: ResolvedWorkflowResource }
22
+ | { ok: false; error: string };
23
+
24
+ type WorkflowResourceDefinition = {
25
+ name: string;
26
+ version: number;
27
+ resolve: (args: Record<string, unknown>) => { script: string; authority: WorkflowResourceAuthority } | { error: string };
28
+ };
29
+
30
+ function isPlainRecord(value: unknown): value is Record<string, unknown> {
31
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
32
+ const prototype = Object.getPrototypeOf(value);
33
+ return prototype === Object.prototype || prototype === null;
34
+ }
35
+
36
+ function jsonByteLength(value: unknown): number {
37
+ try {
38
+ const encoded = JSON.stringify(value);
39
+ if (encoded === undefined) throw new Error("must contain JSON data");
40
+ return Buffer.byteLength(encoded, "utf8");
41
+ } catch (error) {
42
+ throw new Error(`must contain plain JSON data: ${error instanceof Error ? error.message : String(error)}`);
43
+ }
44
+ }
45
+
46
+ function validatePlainJson(value: unknown, path: string, depth = 0): void {
47
+ if (depth > 8) throw new Error(`${path} is too deeply nested.`);
48
+ if (value === null || typeof value === "boolean") return;
49
+ if (typeof value === "string") {
50
+ if (!value.trim()) throw new Error(`${path} must not be empty.`);
51
+ if (Buffer.byteLength(value, "utf8") > MAX_STRING_BYTES) throw new Error(`${path} exceeds ${MAX_STRING_BYTES} bytes.`);
52
+ return;
53
+ }
54
+ if (typeof value === "number") {
55
+ if (!Number.isFinite(value)) throw new Error(`${path} must be finite.`);
56
+ return;
57
+ }
58
+ if (Array.isArray(value)) {
59
+ if (value.length > 64) throw new Error(`${path} contains too many items.`);
60
+ for (const [index, entry] of value.entries()) validatePlainJson(entry, `${path}[${index}]`, depth + 1);
61
+ return;
62
+ }
63
+ if (!isPlainRecord(value)) throw new Error(`${path} must contain plain JSON data.`);
64
+ if (Object.keys(value).length > 16) throw new Error(`${path} contains too many fields.`);
65
+ for (const [key, entry] of Object.entries(value)) {
66
+ if (!key.trim()) throw new Error(`${path} contains an empty field name.`);
67
+ validatePlainJson(entry, `${path}.${key}`, depth + 1);
68
+ }
69
+ }
70
+
71
+ function normalizeArgs(value: unknown): { args?: Record<string, unknown>; error?: string } {
72
+ if (value === undefined) return { args: {} };
73
+ if (!isPlainRecord(value)) return { error: "workflow args must be a plain JSON object." };
74
+ try {
75
+ validatePlainJson(value, "workflow args");
76
+ if (jsonByteLength(value) > MAX_ARGS_BYTES) return { error: `workflow args exceed ${MAX_ARGS_BYTES} bytes.` };
77
+ return { args: { ...value } };
78
+ } catch (error) {
79
+ return { error: error instanceof Error ? error.message : String(error) };
80
+ }
81
+ }
82
+
83
+ function resolveRunCi(args: Record<string, unknown>): { script: string; authority: WorkflowResourceAuthority } | { error: string } {
84
+ const allowed = new Set(["command", "timeoutMs"]);
85
+ const unsupported = Object.keys(args).filter((key) => !allowed.has(key));
86
+ if (unsupported.length > 0) return { error: `workflow 'run-ci' args contain unsupported fields: ${unsupported.join(", ")}.` };
87
+ const command = args.command === undefined ? "npm test" : args.command;
88
+ if (command !== "npm test" && command !== "npm run typecheck") return { error: "workflow 'run-ci' args.command must be 'npm test' or 'npm run typecheck'." };
89
+ const timeoutMs = args.timeoutMs === undefined ? 120_000 : args.timeoutMs;
90
+ if (typeof timeoutMs !== "number" || !Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 86_400_000) return { error: "workflow 'run-ci' args.timeoutMs must be an integer from 1 to 86400000." };
91
+ const params = { kind: "command", command, timeoutMs, role: "ci" };
92
+ return {
93
+ script: `return await runs.host("ci", ${JSON.stringify(params)});`,
94
+ authority: { host: { keys: ["ci"], commands: [command] } },
95
+ };
96
+ }
97
+
98
+ function resolveReview(args: Record<string, unknown>): { script: string; authority: WorkflowResourceAuthority } | { error: string } {
99
+ const unsupported = Object.keys(args).filter((key) => key !== "task");
100
+ if (unsupported.length > 0) return { error: `workflow 'review' args contain unsupported fields: ${unsupported.join(", ")}.` };
101
+ const task = args.task;
102
+ if (typeof task !== "string" || !task.trim()) return { error: "workflow 'review' requires a non-empty string args.task." };
103
+ return {
104
+ script: `return (await runs.run("review", { agent: "reviewer", task: ${JSON.stringify(task.trim())} })).output;`,
105
+ authority: {},
106
+ };
107
+ }
108
+
109
+ const WORKFLOW_RESOURCES: readonly WorkflowResourceDefinition[] = [
110
+ { name: "review", version: 1, resolve: resolveReview },
111
+ { name: "run-ci", version: 1, resolve: resolveRunCi },
112
+ ];
113
+
114
+ function findWorkflowResource(name: string): WorkflowResourceDefinition | undefined {
115
+ return WORKFLOW_RESOURCES.find((resource) => resource.name === name);
116
+ }
117
+
118
+ function listWorkflowResourceNames(): string[] {
119
+ return WORKFLOW_RESOURCES.map((resource) => resource.name);
120
+ }
121
+
122
+ /** Resolve only extension-owned resources so policy can distinguish them from raw scripts; caller-provided script text is never consulted. */
123
+ export function resolveWorkflowResource(nameValue: unknown, argsValue?: unknown): WorkflowResourceResolution {
124
+ if (typeof nameValue !== "string" || !nameValue.trim()) return { ok: false, error: "workflow must be a non-empty resource name." };
125
+ const name = nameValue.trim();
126
+ if (!RESOURCE_NAME_PATTERN.test(name)) return { ok: false, error: "workflow must use a safe resource name." };
127
+ const resource = findWorkflowResource(name);
128
+ if (!resource) return { ok: false, error: `Unknown workflow resource '${name}'. Available resources: ${listWorkflowResourceNames().join(", ")}.` };
129
+ const normalizedArgs = normalizeArgs(argsValue);
130
+ if (normalizedArgs.error) return { ok: false, error: normalizedArgs.error };
131
+ const resolved = resource.resolve(normalizedArgs.args!);
132
+ if ("error" in resolved) return { ok: false, error: resolved.error };
133
+ const resourceId = randomUUID();
134
+ const permit = createWorkflowResourcePermit({
135
+ resourceName: resource.name,
136
+ resourceVersion: resource.version,
137
+ resourceId,
138
+ scriptDigest: stableJsonDigest(resolved.script),
139
+ authority: resolved.authority,
140
+ });
141
+ const provenance: WorkflowResourceProvenanceV1 = Object.freeze({
142
+ kind: "workflow",
143
+ name: resource.name,
144
+ version: resource.version,
145
+ invocation: "named",
146
+ expansion: "resolved",
147
+ id: resourceId,
148
+ });
149
+ return { ok: true, resource: { script: resolved.script, permit, provenance } };
150
+ }