pi-crew 0.9.68 → 0.10.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.
- package/CHANGELOG.md +222 -0
- package/NOTICE.md +21 -0
- package/README.md +44 -2
- package/agents/analyst.md +1 -1
- package/agents/cold-verifier.md +3 -1
- package/agents/critic.md +1 -1
- package/agents/executor.md +1 -1
- package/agents/explorer.md +1 -1
- package/agents/planner.md +1 -1
- package/agents/reviewer.md +1 -1
- package/agents/security-reviewer.md +1 -1
- package/agents/test-engineer.md +1 -1
- package/agents/verifier.md +1 -1
- package/agents/writer.md +1 -1
- package/dist/index.mjs +68113 -60774
- package/docs/README.md +2 -0
- package/docs/actions-reference.md +31 -0
- package/docs/commands-reference.md +17 -6
- package/docs/resource-formats.md +13 -0
- package/package.json +4 -2
- package/schema.json +503 -91
- package/scripts/resource-sampler.mjs +36 -2
- package/skills/requirements-to-task-packet/SKILL.md +26 -0
- package/skills/widget-rendering/SKILL.md +7 -7
- package/src/agents/agent-config.ts +2 -1
- package/src/agents/discover-agents.ts +23 -14
- package/src/config/config-merge.ts +183 -0
- package/src/config/config-validation.ts +687 -0
- package/src/config/config.ts +22 -864
- package/src/config/defaults.ts +43 -2
- package/src/config/drift-detector.ts +1 -1
- package/src/config/env-vars.ts +691 -0
- package/src/config/role-tools.ts +11 -9
- package/src/config/sanitize-project-config.ts +172 -0
- package/src/config/types.ts +49 -1
- package/src/extension/async-notifier.ts +25 -2
- package/src/extension/crew-cleanup.ts +13 -0
- package/src/extension/crew-vibes/config.ts +2 -1
- package/src/extension/crew-vibes/footer.ts +19 -0
- package/src/extension/crew-vibes/index.ts +11 -1
- package/src/extension/plan-orchestrate.ts +132 -0
- package/src/extension/register.ts +8 -0
- package/src/extension/registration/command-registration.ts +1 -0
- package/src/extension/registration/commands/dashboard.ts +158 -0
- package/src/extension/registration/commands/index.ts +35 -0
- package/src/extension/registration/commands/manage.ts +303 -0
- package/src/extension/registration/commands/run.ts +228 -0
- package/src/extension/registration/commands/shared.ts +639 -0
- package/src/extension/registration/commands/status.ts +60 -0
- package/src/extension/registration/commands.ts +13 -1224
- package/src/extension/registration/foreground-run-controller.ts +10 -2
- package/src/extension/registration/lifecycle-handlers.ts +178 -17
- package/src/extension/registration/runtime-cleanup.ts +23 -5
- package/src/extension/registration/subagent-tools.ts +218 -9
- package/src/extension/registration/team-tool.ts +5 -1
- package/src/extension/registration/ui.ts +5 -4
- package/src/extension/rpc-hmac.ts +5 -3
- package/src/extension/team-tool/api/heartbeat.ts +47 -10
- package/src/extension/team-tool/api/plan-approval.ts +9 -0
- package/src/extension/team-tool/api/task-claims.ts +109 -40
- package/src/extension/team-tool/cancel.ts +84 -50
- package/src/extension/team-tool/dispatch/index.ts +1 -0
- package/src/extension/team-tool/dispatch/run.ts +4 -1
- package/src/extension/team-tool/doctor.ts +103 -1
- package/src/extension/team-tool/orchestrate.ts +66 -1
- package/src/extension/team-tool/plans.ts +192 -0
- package/src/extension/team-tool/respond.ts +197 -65
- package/src/extension/team-tool/run-deadline.ts +35 -3
- package/src/extension/team-tool/run-intent.ts +63 -0
- package/src/extension/team-tool/run.ts +74 -20
- package/src/extension/team-tool/status.ts +84 -26
- package/src/extension/team-tool.ts +11 -2
- package/src/hooks/registry.ts +1 -6
- package/src/i18n.ts +9 -0
- package/src/prompt/prompt-runtime.ts +521 -2
- package/src/prompt/worker-events-channel.ts +173 -0
- package/src/runtime/README.md +8 -8
- package/src/runtime/async-runner.ts +7 -3
- package/src/runtime/background-runner.ts +42 -14
- package/src/runtime/broker/broker-issuer.ts +9 -2
- package/src/runtime/broker/crew-broker-tokens.ts +43 -6
- package/src/runtime/broker/crew-broker.ts +838 -10
- package/src/runtime/broker/wait-status-cache.ts +157 -0
- package/src/runtime/budget-enforcement.ts +281 -0
- package/src/runtime/child-pi/child-pi-constants.ts +8 -0
- package/src/runtime/child-pi/child-pi-spawn.ts +60 -14
- package/src/runtime/child-pi/child-pi-streams.ts +21 -1
- package/src/runtime/child-pi/child-pi-timers.ts +324 -0
- package/src/runtime/child-pi/child-pi.ts +97 -201
- package/src/runtime/child-pi/mock-fixtures.ts +16 -2
- package/src/runtime/crew-agent-records.ts +259 -14
- package/src/runtime/delegate-spawn.ts +148 -0
- package/src/runtime/detached-run-results.ts +90 -0
- package/src/runtime/deterministic-ast.ts +2 -1
- package/src/runtime/dispatch-batch.ts +945 -0
- package/src/runtime/finalize-run.ts +557 -0
- package/src/runtime/goal-workflow/adaptive-plan.ts +116 -15
- package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +8 -3
- package/src/runtime/goal-workflow/goal-state-store.ts +1 -1
- package/src/runtime/group-join.ts +11 -125
- package/src/runtime/live-session/live-session-runtime.ts +26 -1
- package/src/runtime/merge-gate.ts +32 -10
- package/src/runtime/merge-loop.ts +130 -0
- package/src/runtime/model/model-budget-summary.ts +53 -0
- package/src/runtime/model/model-fallback.ts +36 -2
- package/src/runtime/model/pi-args.ts +10 -0
- package/src/runtime/model/provider-extensions.ts +10 -0
- package/src/runtime/orphan-worker-registry.ts +1 -1
- package/src/runtime/output/output-validator.ts +45 -0
- package/src/runtime/parent-guard.ts +3 -1
- package/src/runtime/peer-dep.ts +2 -1
- package/src/runtime/per-write-validator.ts +0 -5
- package/src/runtime/pi-spawn.ts +61 -15
- package/src/runtime/plan-approval.ts +125 -0
- package/src/runtime/plan-replan.ts +151 -0
- package/src/runtime/process-status.ts +16 -1
- package/src/runtime/recovery/checkpoint.ts +0 -18
- package/src/runtime/recovery/crash-recovery.ts +111 -46
- package/src/runtime/run-tracker.ts +77 -10
- package/src/runtime/scheduler-context.ts +98 -0
- package/src/runtime/scheduling/coalesce-tasks.ts +5 -0
- package/src/runtime/scheduling/global-worker-cap.ts +2 -1
- package/src/runtime/scheduling/nested-slots.ts +70 -0
- package/src/runtime/scheduling/run-coalesced-task-group.ts +64 -13
- package/src/runtime/scheduling/task-graph-scheduler.ts +0 -10
- package/src/runtime/settings-store.ts +219 -0
- package/src/runtime/spawn-policy.ts +217 -0
- package/src/runtime/stale-reconciler.ts +87 -6
- package/src/runtime/subagent-manager.ts +25 -1
- package/src/runtime/task-output-context.ts +230 -9
- package/src/runtime/task-packet.ts +23 -1
- package/src/runtime/task-runner/child-executor.ts +106 -7
- package/src/runtime/task-runner/post-execution.ts +125 -1
- package/src/runtime/task-runner/pre-execution.ts +39 -1
- package/src/runtime/task-runner/prompt-builder.ts +51 -1
- package/src/runtime/task-runner/retrieval-orchestrator.ts +72 -18
- package/src/runtime/task-runner/spec-evidence.ts +403 -0
- package/src/runtime/task-runner/state-helpers.ts +26 -24
- package/src/runtime/task-runner.ts +11 -0
- package/src/runtime/team-runner.ts +132 -1673
- package/src/runtime/verification/spec-sandbox.ts +255 -0
- package/src/runtime/verification/verification-gates.ts +3 -2
- package/src/runtime/verification/verification-worktree.ts +2 -1
- package/src/runtime/workflow-phase-advance.ts +100 -0
- package/src/runtime/workspace-tree.ts +9 -0
- package/src/schema/config-schema.ts +66 -26
- package/src/schema/sensitive-config-paths.ts +64 -0
- package/src/schema/team-tool-schema.ts +13 -3
- package/src/state/README.md +4 -10
- package/src/state/atomic-write.ts +20 -3
- package/src/state/contracts.ts +38 -0
- package/src/state/coordination/mailbox.ts +12 -2
- package/src/state/event-log/cursor.ts +223 -0
- package/src/state/event-log/event-log-rotation.ts +12 -4
- package/src/state/event-log/event-log.ts +152 -369
- package/src/state/event-log/sequence-cache.ts +373 -0
- package/src/state/event-log/worker-atomic-writer.ts +2 -1
- package/src/state/stores/active-run-registry.ts +3 -2
- package/src/state/stores/manifest-io.ts +237 -0
- package/src/state/stores/ownership-map.ts +162 -0
- package/src/state/stores/plan-store.ts +241 -0
- package/src/state/stores/run-cache.ts +0 -90
- package/src/state/stores/spec-store.ts +189 -0
- package/src/state/stores/state-store.ts +139 -232
- package/src/state/types.ts +199 -0
- package/src/ui/dashboard-panes/plan-pane.ts +136 -0
- package/src/ui/dashboard-panes/progress-pane.ts +6 -0
- package/src/ui/dashboard-panes/transcript-pane.ts +31 -0
- package/src/ui/dock-footer.ts +49 -0
- package/src/ui/heartbeat-aggregator.ts +9 -1
- package/src/ui/inline-panel/agent-pane.ts +375 -0
- package/src/ui/inline-panel/agent-transcript.ts +338 -0
- package/src/ui/inline-panel/agent-view-overlay.ts +225 -0
- package/src/ui/inline-panel/crew-editor.ts +192 -0
- package/src/ui/inline-panel/index.ts +290 -0
- package/src/ui/inline-panel/panel-rows.ts +37 -0
- package/src/ui/inline-panel/panel-selection.ts +157 -0
- package/src/ui/inline-panel/panel-store.ts +111 -0
- package/src/ui/inline-panel/view-session-store.ts +36 -0
- package/src/ui/keybinding-map.ts +54 -13
- package/src/ui/pi-ui-compat.ts +9 -0
- package/src/ui/powerbar-publisher.ts +52 -1
- package/src/ui/run-dashboard.ts +31 -5
- package/src/ui/run-snapshot-cache.ts +57 -30
- package/src/ui/snapshot-types.ts +6 -1
- package/src/ui/widget/index.ts +176 -22
- package/src/ui/widget/task-list.ts +198 -0
- package/src/ui/widget/widget-formatters.ts +240 -4
- package/src/ui/widget/widget-renderer.ts +243 -38
- package/src/ui/widget/widget-types.ts +11 -0
- package/src/utils/child-process-shield.ts +106 -0
- package/src/utils/file-coalescer.ts +0 -4
- package/src/utils/fs-errno.ts +66 -0
- package/src/utils/fs-watch.ts +1 -1
- package/src/utils/internal-error.ts +3 -1
- package/src/utils/paths.ts +11 -3
- package/src/utils/redaction.ts +7 -0
- package/src/utils/safe-abort.ts +45 -0
- package/src/utils/task-name-generator.ts +1 -8
- package/src/workflows/discover-workflows.ts +20 -2
- package/src/workflows/validate-workflow.ts +7 -1
- package/src/workflows/workflow-config.ts +17 -0
- package/src/workflows/workflow-serializer.ts +3 -0
- package/src/worktree/worktree-manager.ts +22 -0
- package/workflows/default.workflow.md +36 -26
- package/workflows/strict-fast-fix.workflow.md +26 -0
- package/src/agents/agent-search.ts +0 -98
- package/src/benchmark/benchmark-runner.ts +0 -313
- package/src/benchmark/feedback-loop.ts +0 -73
- package/src/config/resilient-parser.ts +0 -117
- package/src/extension/crew-vibes/cat-frames.ts +0 -18
- package/src/extension/result-watcher.ts +0 -139
- package/src/observability/exporters/prometheus-exporter.ts +0 -54
- package/src/observability/metric-retention.ts +0 -64
- package/src/runtime/compaction/compaction-summary.ts +0 -278
- package/src/runtime/errors/crew-errors.ts +0 -162
- package/src/runtime/live-session/intercom-bridge.ts +0 -187
- package/src/runtime/loop-gates.ts +0 -128
- package/src/runtime/metric-parser.ts +0 -36
- package/src/runtime/output/stream-preview.ts +0 -184
- package/src/runtime/output/tool-progress.ts +0 -278
- package/src/runtime/phase-tracker.ts +0 -385
- package/src/runtime/pipeline-runner.ts +0 -523
- package/src/runtime/process/process-lifecycle.ts +0 -491
- package/src/runtime/recovery/retry-runner.ts +0 -330
- package/src/runtime/run-drift.ts +0 -219
- package/src/runtime/task-quality.ts +0 -199
- package/src/runtime/task-runner/run-projection.ts +0 -128
- package/src/runtime/verification/post-checks.ts +0 -142
- package/src/state/coordination/schedule.ts +0 -166
- package/src/state/event-log/jsonl-writer.ts +0 -115
- package/src/state/hook-instinct-bridge.ts +0 -94
- package/src/state/hook-integrations.ts +0 -51
- package/src/state/session-state-map.ts +0 -51
- package/src/state/stores/blob-store.ts +0 -308
- package/src/state/stores/instinct-store.ts +0 -275
- package/src/state/stores/observation-store.ts +0 -176
- package/src/state/tiered-eval.ts +0 -480
- package/src/state/types-eval.ts +0 -58
- package/src/tools/safe-bash-extension.ts +0 -54
- package/src/tools/safe-bash.ts +0 -505
- package/src/ui/agent-management-overlay.ts +0 -160
- package/src/ui/crew-footer.ts +0 -102
- package/src/ui/crew-select-list.ts +0 -114
- package/src/ui/dashboard-panes/capability-pane.ts +0 -77
- package/src/ui/transcript-entries.ts +0 -256
- package/src/utils/conflict-detect.ts +0 -721
- package/src/utils/fingerprint.ts +0 -180
- package/src/utils/gh-protocol.ts +0 -556
- package/src/utils/project-detector.ts +0 -160
- package/src/utils/sse-parser.ts +0 -131
- package/src/workflows/cost-estimator.ts +0 -34
- package/src/workflows/intermediate-store.ts +0 -166
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* spec-sandbox.ts — hardened re-run sandbox for strict-mode spec checks
|
|
3
|
+
* (ADR-6 §4, WP-6 step 4).
|
|
4
|
+
*
|
|
5
|
+
* Executes ONLY snapshot-frozen acceptance commands (never the live
|
|
6
|
+
* state/specs/<id>.json) under layered isolation:
|
|
7
|
+
* - env: BASE_ALLOWLIST pattern minus credential-carrying keys — NO
|
|
8
|
+
* provider API keys, NO PI_CREW_BROKER_*, NO session tokens (asserted by
|
|
9
|
+
* capturing the child env in tests);
|
|
10
|
+
* - cwd: pinned to the run's workspace root;
|
|
11
|
+
* - resources: `sh -c 'ulimit -v 262144; ulimit -t 30; exec …'`
|
|
12
|
+
* (256 MiB address space, 30 CPU-seconds);
|
|
13
|
+
* - wall-clock: 60s — SIGTERM → 200ms → SIGKILL escalation;
|
|
14
|
+
* - network: `unshare -rn` wrapper on Linux (user-namespace + map-root;
|
|
15
|
+
* plain -n needs CAP_SYS_ADMIN and fails EPERM unprivileged, which would
|
|
16
|
+
* brick every strict re-run).
|
|
17
|
+
*
|
|
18
|
+
* FAIL-CLOSED: wrapper-launch failure fails that acceptance closed — NEVER
|
|
19
|
+
* silently degrades to pass or to running without isolation. macOS has no
|
|
20
|
+
* unshare equivalent → launch-failed there too (platform honesty: the gate
|
|
21
|
+
* emits a loud platform warning; strict re-runs are Linux-only in v1).
|
|
22
|
+
*
|
|
23
|
+
* LEAK DISCIPLINE: results are DIGEST-ONLY. stdout/stderr text never leaves
|
|
24
|
+
* this module — events, logs, and artifacts see exit code / signal / digest /
|
|
25
|
+
* stderr-LENGTH only (error paths included: timeouts, signals, launch
|
|
26
|
+
* failures).
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { spawn } from "node:child_process";
|
|
30
|
+
import { createHash } from "node:crypto";
|
|
31
|
+
import { sanitizeEnvSecrets } from "../../utils/env-filter.ts";
|
|
32
|
+
import { BASE_ALLOWLIST } from "../child-pi/child-pi-spawn.ts";
|
|
33
|
+
|
|
34
|
+
/** Config-capped ceilings (ADR-6 §4) — NOT user-facing knobs in v1. The
|
|
35
|
+
* `limits` override on runSpecCheck is an internal test seam. */
|
|
36
|
+
export interface SpecSandboxLimits {
|
|
37
|
+
addressSpaceKb: number; // ulimit -v (256 MiB)
|
|
38
|
+
cpuSeconds: number; // ulimit -t
|
|
39
|
+
wallClockMs: number; // 60s hard wall clock
|
|
40
|
+
sigkillGraceMs: number; // SIGTERM → 200ms → SIGKILL
|
|
41
|
+
maxOutputBytes: number; // stdout buffer cap (digest still computed over capped buffer)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const SPEC_SANDBOX_LIMITS: SpecSandboxLimits = {
|
|
45
|
+
addressSpaceKb: 262_144,
|
|
46
|
+
cpuSeconds: 30,
|
|
47
|
+
wallClockMs: 60_000,
|
|
48
|
+
sigkillGraceMs: 200,
|
|
49
|
+
maxOutputBytes: 4 * 1024 * 1024,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** Credential-shaped keys are scrubbed EVEN IF an allowlist entry matches —
|
|
53
|
+
* belt-and-suspenders against future BASE_ALLOWLIST additions. */
|
|
54
|
+
const CREDENTIAL_KEY_PATTERN = /(API_?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|_BEARER|^PI_CREW_BROKER)/i;
|
|
55
|
+
|
|
56
|
+
/** Sandbox env: BASE_ALLOWLIST pattern minus credential-carrying keys, then
|
|
57
|
+
* the generic secret scrubber (deny-list). The scrubbed map IS the result —
|
|
58
|
+
* never merged back into the selection (a merge-back resurrects keys the
|
|
59
|
+
* scrubber dropped). */
|
|
60
|
+
export function buildSpecSandboxEnv(env: NodeJS.ProcessEnv = process.env): Record<string, string> {
|
|
61
|
+
const selected: Record<string, string> = {};
|
|
62
|
+
for (const key of BASE_ALLOWLIST) {
|
|
63
|
+
if (CREDENTIAL_KEY_PATTERN.test(key)) continue;
|
|
64
|
+
const value = env[key];
|
|
65
|
+
if (value !== undefined) selected[key] = value;
|
|
66
|
+
}
|
|
67
|
+
const scrubbed = sanitizeEnvSecrets(selected);
|
|
68
|
+
const result: Record<string, string> = {};
|
|
69
|
+
for (const [key, value] of Object.entries(scrubbed)) {
|
|
70
|
+
if (CREDENTIAL_KEY_PATTERN.test(key)) continue;
|
|
71
|
+
result[key] = value;
|
|
72
|
+
}
|
|
73
|
+
result.FORCE_COLOR = "0";
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type SpecCheckOutcomeKind =
|
|
78
|
+
| "passed"
|
|
79
|
+
| "digest-mismatch"
|
|
80
|
+
| "exit-mismatch"
|
|
81
|
+
| "output-capped"
|
|
82
|
+
| "timeout"
|
|
83
|
+
| "launch-failed"
|
|
84
|
+
| "network-blocked";
|
|
85
|
+
|
|
86
|
+
/** Digest-only result — raw command output NEVER persists (leak discipline). */
|
|
87
|
+
export interface SpecCheckOutcome {
|
|
88
|
+
outcome: SpecCheckOutcomeKind;
|
|
89
|
+
actualDigest?: string;
|
|
90
|
+
exitCode?: number | null;
|
|
91
|
+
signal?: string;
|
|
92
|
+
durationMs: number;
|
|
93
|
+
stderrLength?: number;
|
|
94
|
+
/** stdout exceeded maxOutputBytes — digest comparison invalid (the authored
|
|
95
|
+
* expectedDigest is over FULL stdout); surfaced so the failure is
|
|
96
|
+
* distinguishable from real tampering (round-1 review). */
|
|
97
|
+
stdoutCapped?: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface SpecCheckCommand {
|
|
101
|
+
command: string;
|
|
102
|
+
/** sha-256 hex of raw stdout. Optional — exit-code-only checks omit it. */
|
|
103
|
+
expectedDigest?: string;
|
|
104
|
+
/** Default 0 when neither digest nor exit code is declared. */
|
|
105
|
+
expectedExitCode?: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** `unshare -rn` is Linux-only; strict re-runs fail closed elsewhere. */
|
|
109
|
+
export function isSpecSandboxSupported(): boolean {
|
|
110
|
+
return process.platform === "linux";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function runSpecCheck(
|
|
114
|
+
check: SpecCheckCommand,
|
|
115
|
+
options: { cwd: string; limits?: Partial<SpecSandboxLimits>; wrapperOverride?: string },
|
|
116
|
+
): Promise<SpecCheckOutcome> {
|
|
117
|
+
const limits = { ...SPEC_SANDBOX_LIMITS, ...options.limits };
|
|
118
|
+
const wrapper = options.wrapperOverride ?? "unshare";
|
|
119
|
+
const start = Date.now();
|
|
120
|
+
if (!isSpecSandboxSupported()) {
|
|
121
|
+
// Platform honesty: macOS/Windows have no unshare equivalent → fail
|
|
122
|
+
// closed, never silently run without isolation.
|
|
123
|
+
return { outcome: "launch-failed", durationMs: Date.now() - start, stderrLength: 0 };
|
|
124
|
+
}
|
|
125
|
+
// `exec` so ulimits apply to the command's own process. The user command
|
|
126
|
+
// rides as $0 (argv, never string-interpolated — arbitrary quoting is safe
|
|
127
|
+
// and compound commands like `a && b` keep their shell semantics).
|
|
128
|
+
const inner = `ulimit -v ${limits.addressSpaceKb}; ulimit -t ${limits.cpuSeconds}; exec sh -c "$0"`;
|
|
129
|
+
return await new Promise<SpecCheckOutcome>((resolve) => {
|
|
130
|
+
const child = spawn(wrapper, ["-rn", "sh", "-c", inner, check.command], {
|
|
131
|
+
cwd: options.cwd,
|
|
132
|
+
env: buildSpecSandboxEnv(),
|
|
133
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
134
|
+
detached: true, // own process group — group-wide kill reaches grandchildren holding the stdio pipes (CORE-10 pattern)
|
|
135
|
+
});
|
|
136
|
+
let stdout = "";
|
|
137
|
+
let stderrLen = 0;
|
|
138
|
+
let stdoutCapped = false;
|
|
139
|
+
let settled = false;
|
|
140
|
+
let termTimer: NodeJS.Timeout | undefined;
|
|
141
|
+
let killTimer: NodeJS.Timeout | undefined;
|
|
142
|
+
const finish = (outcome: SpecCheckOutcome): void => {
|
|
143
|
+
if (settled) return;
|
|
144
|
+
settled = true;
|
|
145
|
+
if (termTimer) clearTimeout(termTimer);
|
|
146
|
+
if (killTimer) clearTimeout(killTimer);
|
|
147
|
+
resolve(outcome);
|
|
148
|
+
};
|
|
149
|
+
termTimer = setTimeout(() => {
|
|
150
|
+
try {
|
|
151
|
+
if (child.pid) process.kill(-child.pid, "SIGTERM");
|
|
152
|
+
} catch {
|
|
153
|
+
child.kill("SIGTERM");
|
|
154
|
+
}
|
|
155
|
+
}, limits.wallClockMs);
|
|
156
|
+
termTimer.unref();
|
|
157
|
+
killTimer = setTimeout(() => {
|
|
158
|
+
try {
|
|
159
|
+
if (child.pid) process.kill(-child.pid, "SIGKILL");
|
|
160
|
+
} catch {
|
|
161
|
+
child.kill("SIGKILL");
|
|
162
|
+
}
|
|
163
|
+
}, limits.wallClockMs + limits.sigkillGraceMs);
|
|
164
|
+
killTimer.unref();
|
|
165
|
+
child.stdout?.on("data", (chunk: Buffer) => {
|
|
166
|
+
if (stdout.length >= limits.maxOutputBytes) {
|
|
167
|
+
stdoutCapped = true;
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
stdout += chunk.toString("utf8");
|
|
171
|
+
if (stdout.length > limits.maxOutputBytes) {
|
|
172
|
+
stdout = stdout.slice(0, limits.maxOutputBytes);
|
|
173
|
+
stdoutCapped = true;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
child.stderr?.on("data", (chunk: Buffer) => {
|
|
177
|
+
stderrLen += chunk.length;
|
|
178
|
+
});
|
|
179
|
+
child.on("error", (err) => {
|
|
180
|
+
// Wrapper-launch failure (unshare missing/EACCES/EPERM) — fail closed.
|
|
181
|
+
const outcome: SpecCheckOutcome =
|
|
182
|
+
(err as NodeJS.ErrnoException).code === "ENOENT"
|
|
183
|
+
? { outcome: "launch-failed", durationMs: Date.now() - start, stderrLength: stderrLen }
|
|
184
|
+
: { outcome: "launch-failed", durationMs: Date.now() - start, stderrLength: stderrLen };
|
|
185
|
+
finish(outcome);
|
|
186
|
+
});
|
|
187
|
+
child.on("exit", () => {
|
|
188
|
+
// Survivor kill (round-1): a passing command can background a process
|
|
189
|
+
// that outlives the check. Must run on EXIT, not CLOSE — a survivor
|
|
190
|
+
// holding the stdout pipe delays `close` until it dies, which would
|
|
191
|
+
// make this kill a no-op by construction.
|
|
192
|
+
try {
|
|
193
|
+
if (child.pid) process.kill(-child.pid, "SIGKILL");
|
|
194
|
+
} catch {
|
|
195
|
+
/* group already reaped — fine */
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
child.on("close", (code, signal) => {
|
|
199
|
+
const durationMs = Date.now() - start;
|
|
200
|
+
if (signal === "SIGTERM" || signal === "SIGKILL") {
|
|
201
|
+
finish({ outcome: "timeout", signal: signal ?? undefined, durationMs, stderrLength: stderrLen });
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const actualDigest = createHash("sha256").update(stdout, "utf8").digest("hex");
|
|
205
|
+
const expectedExit = check.expectedExitCode ?? 0;
|
|
206
|
+
if (stdoutCapped && check.expectedDigest !== undefined) {
|
|
207
|
+
// The authored expectedDigest is over FULL stdout — comparing the
|
|
208
|
+
// capped prefix is invalid. Fail with the distinct honest outcome.
|
|
209
|
+
finish({
|
|
210
|
+
outcome: "output-capped",
|
|
211
|
+
actualDigest,
|
|
212
|
+
exitCode: code,
|
|
213
|
+
signal: signal ?? undefined,
|
|
214
|
+
durationMs,
|
|
215
|
+
stderrLength: stderrLen,
|
|
216
|
+
stdoutCapped: true,
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const exitOk = code === expectedExit;
|
|
221
|
+
const digestOk = check.expectedDigest === undefined || actualDigest === check.expectedDigest;
|
|
222
|
+
if (!exitOk) {
|
|
223
|
+
finish({
|
|
224
|
+
outcome: "exit-mismatch",
|
|
225
|
+
actualDigest: check.expectedDigest !== undefined ? actualDigest : undefined,
|
|
226
|
+
exitCode: code,
|
|
227
|
+
signal: signal ?? undefined,
|
|
228
|
+
durationMs,
|
|
229
|
+
stderrLength: stderrLen,
|
|
230
|
+
});
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (!digestOk) {
|
|
234
|
+
finish({
|
|
235
|
+
outcome: "digest-mismatch",
|
|
236
|
+
actualDigest,
|
|
237
|
+
exitCode: code,
|
|
238
|
+
signal: signal ?? undefined,
|
|
239
|
+
durationMs,
|
|
240
|
+
stderrLength: stderrLen,
|
|
241
|
+
});
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
finish({
|
|
245
|
+
outcome: "passed",
|
|
246
|
+
actualDigest: check.expectedDigest !== undefined || stdoutCapped ? actualDigest : undefined,
|
|
247
|
+
exitCode: code,
|
|
248
|
+
signal: signal ?? undefined,
|
|
249
|
+
durationMs,
|
|
250
|
+
stderrLength: stderrLen,
|
|
251
|
+
...(stdoutCapped ? { stdoutCapped: true } : {}),
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { spawn } from "node:child_process";
|
|
13
13
|
import * as fs from "node:fs";
|
|
14
14
|
import * as path from "node:path";
|
|
15
|
+
import { getCrewEnv } from "../../config/env-vars.ts";
|
|
15
16
|
import { writeArtifact } from "../../state/stores/artifact-store.ts";
|
|
16
17
|
import type { GreenLevel, VerificationCommandResult, VerificationContract } from "../../state/types.ts";
|
|
17
18
|
import { WINDOWS_ESSENTIAL_ENV_VARS } from "../../utils/env-allowlist.ts";
|
|
@@ -66,7 +67,7 @@ const VERIFICATION_ENV_ALLOWLIST: readonly string[] = [
|
|
|
66
67
|
/** Whether env sanitization for verification is enabled (opt-out: enabled by default). */
|
|
67
68
|
export function isVerificationEnvSanitizeEnabled(): boolean {
|
|
68
69
|
// Opt-out: explicitly disabled via env var set to '0'.
|
|
69
|
-
if (
|
|
70
|
+
if (getCrewEnv("PI_CREW_VERIFICATION_SANITIZE_ENV") === "0" || getCrewEnv("PI_TEAMS_VERIFICATION_SANITIZE_ENV") === "0") {
|
|
70
71
|
return false;
|
|
71
72
|
}
|
|
72
73
|
return true;
|
|
@@ -82,7 +83,7 @@ function buildVerificationEnv(): Record<string, string> {
|
|
|
82
83
|
if (!isVerificationEnvSanitizeEnabled()) {
|
|
83
84
|
return { ...process.env, FORCE_COLOR: "0" };
|
|
84
85
|
}
|
|
85
|
-
const preserveRaw =
|
|
86
|
+
const preserveRaw = getCrewEnv("PI_CREW_VERIFICATION_PRESERVE_ENV") ?? "";
|
|
86
87
|
const preserve = preserveRaw
|
|
87
88
|
.split(",")
|
|
88
89
|
.map((s) => s.trim())
|
|
@@ -25,6 +25,7 @@ import { execFileSync } from "node:child_process";
|
|
|
25
25
|
import * as fs from "node:fs";
|
|
26
26
|
import * as os from "node:os";
|
|
27
27
|
import * as path from "node:path";
|
|
28
|
+
import { getCrewEnv } from "../../config/env-vars.ts";
|
|
28
29
|
|
|
29
30
|
export interface VerificationWorktree {
|
|
30
31
|
/** Absolute path to the pristine worktree directory. */
|
|
@@ -37,7 +38,7 @@ export interface VerificationWorktree {
|
|
|
37
38
|
|
|
38
39
|
/** Whether the worktree sandbox is enabled (env var opt-in). */
|
|
39
40
|
export function isWorktreeSandboxEnabled(): boolean {
|
|
40
|
-
const v =
|
|
41
|
+
const v = getCrewEnv("PI_CREW_VERIFICATION_WORKTREE");
|
|
41
42
|
return v === "1" || v === "true";
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow-phase advancement for the team-run scheduler loop.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from team-runner.ts (2026-08, Phase 2.6 maintainability split —
|
|
5
|
+
* CORE-4 extraction 6). Pure code motion: advanceWorkflowPhases moved
|
|
6
|
+
* verbatim.
|
|
7
|
+
*/
|
|
8
|
+
import { appendEventAsync } from "../state/event-log/event-log.ts";
|
|
9
|
+
import type { SchedulerContext } from "./scheduler-context.ts";
|
|
10
|
+
import { type PhaseGuardContext, transitionPhase } from "./workflow-state.ts";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* CORE-4 extraction 6: advance workflow phases whose tasks are all in
|
|
14
|
+
* terminal state.
|
|
15
|
+
*
|
|
16
|
+
* Iterates phases starting at `ctx.wfMachine.currentPhaseIndex`; for each phase
|
|
17
|
+
* whose tasks are all terminal, determines the transition status (failed if
|
|
18
|
+
* any task failed/cancelled, else completed), applies the phase transition,
|
|
19
|
+
* emits `workflow.phase_completed`/`workflow.phase_failed`/
|
|
20
|
+
* `workflow.phase_guard_blocked` events, and advances `currentPhaseIndex`.
|
|
21
|
+
*
|
|
22
|
+
* Reads `ctx.tasks`, `ctx.manifest` (read-only). Mutates `ctx.wfMachine`
|
|
23
|
+
* in-place (phase status + currentPhaseIndex). The caller syncs the local
|
|
24
|
+
* `wfMachine` from ctx after the call.
|
|
25
|
+
*
|
|
26
|
+
* @param ctx The scheduler context.
|
|
27
|
+
*/
|
|
28
|
+
export async function advanceWorkflowPhases(ctx: SchedulerContext): Promise<void> {
|
|
29
|
+
let wfMachine = ctx.wfMachine;
|
|
30
|
+
const tasks = ctx.tasks;
|
|
31
|
+
const manifest = ctx.manifest;
|
|
32
|
+
// Advance workflow phases whose tasks are all in terminal state
|
|
33
|
+
const terminalStatuses = new Set(["completed", "failed", "skipped", "cancelled", "needs_attention"]);
|
|
34
|
+
const phaseTaskMap = new Map<string, string[]>();
|
|
35
|
+
for (const task of tasks) {
|
|
36
|
+
if (!task.stepId) continue;
|
|
37
|
+
const existing = phaseTaskMap.get(task.stepId) ?? [];
|
|
38
|
+
existing.push(task.id);
|
|
39
|
+
phaseTaskMap.set(task.stepId, existing);
|
|
40
|
+
}
|
|
41
|
+
// W5-4: by-id map once for the phase loop (was O(phases × phaseTasks × tasks)).
|
|
42
|
+
const taskById = new Map(tasks.map((t) => [t.id, t] as const));
|
|
43
|
+
for (let pi = wfMachine.currentPhaseIndex; pi < wfMachine.phases.length; pi++) {
|
|
44
|
+
const phase = wfMachine.phases[pi]!;
|
|
45
|
+
const phaseTaskIds = phaseTaskMap.get(phase.name) ?? [];
|
|
46
|
+
if (phaseTaskIds.length === 0) continue;
|
|
47
|
+
const allTerminal = phaseTaskIds.every((taskId) => {
|
|
48
|
+
const task = taskById.get(taskId);
|
|
49
|
+
return task ? terminalStatuses.has(task.status) : false;
|
|
50
|
+
});
|
|
51
|
+
if (!allTerminal) break;
|
|
52
|
+
if (phase.status !== "completed" && phase.status !== "failed" && phase.status !== "skipped") {
|
|
53
|
+
const completedArtifacts = manifest.artifacts.filter((a) => a.kind === "result" || a.kind === "summary").map((a) => a.path);
|
|
54
|
+
const previousPhaseStatus = pi > 0 ? (wfMachine.phases[pi - 1]?.status ?? "pending") : "completed";
|
|
55
|
+
const wfContext: PhaseGuardContext = {
|
|
56
|
+
completedArtifacts,
|
|
57
|
+
previousPhaseStatus,
|
|
58
|
+
taskResults: tasks
|
|
59
|
+
.filter((t) => t.status === "completed" || t.status === "needs_attention")
|
|
60
|
+
.map((t) => ({
|
|
61
|
+
taskId: t.id,
|
|
62
|
+
status: t.status,
|
|
63
|
+
outputPath: t.resultArtifact?.path,
|
|
64
|
+
})),
|
|
65
|
+
};
|
|
66
|
+
// Determine phase transition status based on individual task outcomes
|
|
67
|
+
const phaseTasks = phaseTaskIds
|
|
68
|
+
.map((taskId) => taskById.get(taskId))
|
|
69
|
+
.filter((t): t is NonNullable<typeof t> => t !== undefined);
|
|
70
|
+
const hasFailedOrCancelled = phaseTasks.some((t) => t.status === "failed" || t.status === "cancelled");
|
|
71
|
+
const phaseStatus = hasFailedOrCancelled ? "failed" : "completed";
|
|
72
|
+
const transition = transitionPhase(wfMachine, pi, phaseStatus, wfContext);
|
|
73
|
+
wfMachine = transition.machine;
|
|
74
|
+
if (transition.guardResult && !transition.guardResult.allowed) {
|
|
75
|
+
await appendEventAsync(manifest.eventsPath, {
|
|
76
|
+
type: "workflow.phase_guard_blocked",
|
|
77
|
+
runId: manifest.runId,
|
|
78
|
+
message: `Workflow phase '${phase.name}' guard blocked: ${transition.guardResult.reason ?? "unknown"}`,
|
|
79
|
+
data: {
|
|
80
|
+
phaseIndex: pi,
|
|
81
|
+
phaseName: phase.name,
|
|
82
|
+
reason: transition.guardResult.reason,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
await appendEventAsync(manifest.eventsPath, {
|
|
88
|
+
type: phaseStatus === "failed" ? "workflow.phase_failed" : "workflow.phase_completed",
|
|
89
|
+
runId: manifest.runId,
|
|
90
|
+
message: `Workflow phase '${phase.name}' ${phaseStatus}.`,
|
|
91
|
+
data: { phaseIndex: pi, phaseStatus },
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
wfMachine = { ...wfMachine, currentPhaseIndex: pi + 1 };
|
|
95
|
+
}
|
|
96
|
+
ctx.wfMachine = wfMachine;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** @internal 1.9(b) test export — exercise advanceWorkflowPhases directly. */
|
|
100
|
+
export const __test__advanceWorkflowPhases = advanceWorkflowPhases;
|
|
@@ -262,6 +262,11 @@ const emptyResult = (rootPath: string): WorkspaceTree => ({
|
|
|
262
262
|
* long active runs while eliminating redundant walks.
|
|
263
263
|
*/
|
|
264
264
|
const TREE_CACHE_TTL_MS = 30_000;
|
|
265
|
+
// R5-L3 (Round 5 LOW-3): FIFO cap at insertion (mirrors knowledgeCache in
|
|
266
|
+
// knowledge-injection.ts). Expired entries are only overwritten on re-access,
|
|
267
|
+
// so distinct cwd/option keys would otherwise accumulate for the process
|
|
268
|
+
// lifetime. A miss just re-walks the tree — same as a TTL expiry.
|
|
269
|
+
const TREE_CACHE_MAX_ENTRIES = 64;
|
|
265
270
|
interface CachedTree {
|
|
266
271
|
tree: WorkspaceTree;
|
|
267
272
|
expiresAt: number;
|
|
@@ -305,6 +310,10 @@ export async function buildWorkspaceTree(cwd: string, options?: WorkspaceTreeOpt
|
|
|
305
310
|
tree: result,
|
|
306
311
|
expiresAt: Date.now() + TREE_CACHE_TTL_MS,
|
|
307
312
|
});
|
|
313
|
+
if (treeCache.size > TREE_CACHE_MAX_ENTRIES) {
|
|
314
|
+
const oldest = treeCache.keys().next().value;
|
|
315
|
+
if (oldest !== undefined) treeCache.delete(oldest);
|
|
316
|
+
}
|
|
308
317
|
return result;
|
|
309
318
|
} catch {
|
|
310
319
|
return emptyResult(rootPath);
|
|
@@ -17,12 +17,21 @@ export const PiTeamsAutonomyProfileSchema = Type.Union([
|
|
|
17
17
|
|
|
18
18
|
export const PiTeamsAutonomousConfigSchema = Type.Object(
|
|
19
19
|
{
|
|
20
|
-
profile: Type.Optional(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
profile: Type.Optional(
|
|
21
|
+
Type.Union([Type.Literal("manual"), Type.Literal("suggested"), Type.Literal("assisted"), Type.Literal("aggressive")], {
|
|
22
|
+
sensitive: true,
|
|
23
|
+
}),
|
|
24
|
+
),
|
|
25
|
+
enabled: Type.Optional(Type.Boolean({ sensitive: true })),
|
|
26
|
+
injectPolicy: Type.Optional(Type.Boolean({ sensitive: true })),
|
|
27
|
+
preferAsyncForLongTasks: Type.Optional(Type.Boolean({ sensitive: true })),
|
|
28
|
+
allowWorktreeSuggestion: Type.Optional(Type.Boolean({ sensitive: true })),
|
|
29
|
+
// S19-5 (Wave 1A): magicKeywords alone flips effective autonomous mode on
|
|
30
|
+
// (effectiveAutonomousConfig defaults profile "suggested" when only
|
|
31
|
+
// magicKeywords is present) — untrusted project config must not set it.
|
|
32
|
+
magicKeywords: Type.Optional(
|
|
33
|
+
Type.Record(Type.String({ minLength: 1 }), Type.Array(Type.String({ minLength: 1 })), { sensitive: true }),
|
|
34
|
+
),
|
|
26
35
|
},
|
|
27
36
|
{ additionalProperties: false },
|
|
28
37
|
);
|
|
@@ -56,14 +65,16 @@ export const PiTeamsModelFallbackConfigSchema = Type.Object(
|
|
|
56
65
|
export const PiTeamsRuntimeConfigSchema = Type.Object(
|
|
57
66
|
{
|
|
58
67
|
mode: Type.Optional(
|
|
59
|
-
Type.Union([Type.Literal("auto"), Type.Literal("scaffold"), Type.Literal("child-process"), Type.Literal("live-session")]
|
|
68
|
+
Type.Union([Type.Literal("auto"), Type.Literal("scaffold"), Type.Literal("child-process"), Type.Literal("live-session")], {
|
|
69
|
+
sensitive: true,
|
|
70
|
+
}),
|
|
60
71
|
),
|
|
61
|
-
preferLiveSession: Type.Optional(Type.Boolean()),
|
|
62
|
-
allowChildProcessFallback: Type.Optional(Type.Boolean()),
|
|
72
|
+
preferLiveSession: Type.Optional(Type.Boolean({ sensitive: true })),
|
|
73
|
+
allowChildProcessFallback: Type.Optional(Type.Boolean({ sensitive: true })),
|
|
63
74
|
maxTurns: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
64
75
|
graceTurns: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
65
76
|
taskTimeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
66
|
-
inheritContext: Type.Optional(Type.Boolean()),
|
|
77
|
+
inheritContext: Type.Optional(Type.Boolean({ sensitive: true })),
|
|
67
78
|
promptMode: Type.Optional(Type.Union([Type.Literal("replace"), Type.Literal("append")])),
|
|
68
79
|
groupJoin: Type.Optional(Type.Union([Type.Literal("off"), Type.Literal("group"), Type.Literal("smart")])),
|
|
69
80
|
groupJoinAckTimeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
@@ -83,14 +94,14 @@ export const PiTeamsRuntimeConfigSchema = Type.Object(
|
|
|
83
94
|
),
|
|
84
95
|
),
|
|
85
96
|
excludeContextBash: Type.Optional(Type.Boolean()),
|
|
86
|
-
agentExtensions: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
97
|
+
agentExtensions: Type.Optional(Type.Array(Type.String({ minLength: 1 }), { sensitive: true })),
|
|
87
98
|
isolationPolicy: Type.Optional(
|
|
88
99
|
Type.Object(
|
|
89
100
|
{
|
|
90
101
|
isolatedRoles: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
91
102
|
defaultRuntime: Type.Optional(Type.Union([Type.Literal("live-session"), Type.Literal("child-process")])),
|
|
92
103
|
},
|
|
93
|
-
{ additionalProperties: false },
|
|
104
|
+
{ additionalProperties: false, sensitive: true },
|
|
94
105
|
),
|
|
95
106
|
),
|
|
96
107
|
modelFallback: Type.Optional(PiTeamsModelFallbackConfigSchema),
|
|
@@ -102,16 +113,19 @@ export const PiTeamsControlConfigSchema = Type.Object(
|
|
|
102
113
|
{
|
|
103
114
|
enabled: Type.Optional(Type.Boolean()),
|
|
104
115
|
needsAttentionAfterMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
116
|
+
// F19-3 (Round 19 parity): read at agent-control.ts with defaults 3/10.
|
|
117
|
+
consecutiveFailureThreshold: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
118
|
+
longRunningMinutes: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
105
119
|
},
|
|
106
120
|
{ additionalProperties: false },
|
|
107
121
|
);
|
|
108
122
|
|
|
109
123
|
export const PiTeamsWorktreeConfigSchema = Type.Object(
|
|
110
124
|
{
|
|
111
|
-
setupHook: Type.Optional(Type.String({ minLength: 1 })),
|
|
125
|
+
setupHook: Type.Optional(Type.String({ minLength: 1, sensitive: true })),
|
|
112
126
|
setupHookTimeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
113
127
|
linkNodeModules: Type.Optional(Type.Boolean()),
|
|
114
|
-
seedPaths: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
128
|
+
seedPaths: Type.Optional(Type.Array(Type.String({ minLength: 1 }), { sensitive: true })),
|
|
115
129
|
},
|
|
116
130
|
{ additionalProperties: false },
|
|
117
131
|
);
|
|
@@ -144,7 +158,11 @@ export const GoalWrapWorkflowConfigSchema = Type.Object(
|
|
|
144
158
|
{ additionalProperties: false },
|
|
145
159
|
);
|
|
146
160
|
|
|
147
|
-
|
|
161
|
+
// S19-2 (Wave 1A): the whole goalWrap subtree is user-only — a project config
|
|
162
|
+
// must not enable silent auto-wrap, pick the evaluator model, or set
|
|
163
|
+
// budgetUnlimited (unbounded provider spend). Marking the Record is terminal:
|
|
164
|
+
// the walk collapses the subtree to the single top-level path `goalWrap`.
|
|
165
|
+
export const PiTeamsGoalWrapConfigSchema = Type.Record(Type.String({ minLength: 1 }), GoalWrapWorkflowConfigSchema, { sensitive: true });
|
|
148
166
|
|
|
149
167
|
export const AgentOverrideSchema = Type.Object(
|
|
150
168
|
{
|
|
@@ -160,8 +178,8 @@ export const AgentOverrideSchema = Type.Object(
|
|
|
160
178
|
|
|
161
179
|
export const PiTeamsAgentsConfigSchema = Type.Object(
|
|
162
180
|
{
|
|
163
|
-
disableBuiltins: Type.Optional(Type.Boolean()),
|
|
164
|
-
overrides: Type.Optional(Type.Record(Type.String({ minLength: 1 }), AgentOverrideSchema)),
|
|
181
|
+
disableBuiltins: Type.Optional(Type.Boolean({ sensitive: true })),
|
|
182
|
+
overrides: Type.Optional(Type.Record(Type.String({ minLength: 1 }), AgentOverrideSchema, { sensitive: true })),
|
|
165
183
|
},
|
|
166
184
|
{ additionalProperties: false },
|
|
167
185
|
);
|
|
@@ -169,8 +187,8 @@ export const PiTeamsAgentsConfigSchema = Type.Object(
|
|
|
169
187
|
export const PiTeamsToolsConfigSchema = Type.Object(
|
|
170
188
|
{
|
|
171
189
|
enableClaudeStyleAliases: Type.Optional(Type.Boolean()),
|
|
172
|
-
enableSteer: Type.Optional(Type.Boolean()),
|
|
173
|
-
terminateOnForeground: Type.Optional(Type.Boolean()),
|
|
190
|
+
enableSteer: Type.Optional(Type.Boolean({ sensitive: true })),
|
|
191
|
+
terminateOnForeground: Type.Optional(Type.Boolean({ sensitive: true })),
|
|
174
192
|
},
|
|
175
193
|
{ additionalProperties: false },
|
|
176
194
|
);
|
|
@@ -184,8 +202,8 @@ export const PiTeamsTelemetryConfigSchema = Type.Object(
|
|
|
184
202
|
|
|
185
203
|
export const PiTeamsPolicyConfigSchema = Type.Object(
|
|
186
204
|
{
|
|
187
|
-
requireIntentForDestructiveActions: Type.Optional(Type.Boolean()),
|
|
188
|
-
disabledCapabilities: Type.Optional(Type.Array(Type.String())),
|
|
205
|
+
requireIntentForDestructiveActions: Type.Optional(Type.Boolean({ sensitive: true })),
|
|
206
|
+
disabledCapabilities: Type.Optional(Type.Array(Type.String(), { sensitive: true })),
|
|
189
207
|
},
|
|
190
208
|
{ additionalProperties: false },
|
|
191
209
|
);
|
|
@@ -224,6 +242,9 @@ export const PiTeamsReliabilityConfigSchema = Type.Object(
|
|
|
224
242
|
jitterRatio: Type.Optional(Type.Number({ minimum: 0, maximum: 1 })),
|
|
225
243
|
exponentialFactor: Type.Optional(Type.Number({ minimum: 1, maximum: 5 })),
|
|
226
244
|
retryableErrors: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
245
|
+
// F19-2 (Round 19 parity): parsed at config-validation.ts but
|
|
246
|
+
// additionalProperties:false previously rejected it at the schema level.
|
|
247
|
+
maxTotalSpawns: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
227
248
|
},
|
|
228
249
|
{ additionalProperties: false },
|
|
229
250
|
),
|
|
@@ -248,9 +269,12 @@ export const PiTeamsOtlpConfigSchema = Type.Object(
|
|
|
248
269
|
minLength: 1,
|
|
249
270
|
maxLength: 2048,
|
|
250
271
|
pattern: "^https?://",
|
|
272
|
+
sensitive: true,
|
|
251
273
|
}),
|
|
252
274
|
),
|
|
253
|
-
headers: Type.Optional(
|
|
275
|
+
headers: Type.Optional(
|
|
276
|
+
Type.Record(Type.String({ minLength: 1, maxLength: 256 }), Type.String({ maxLength: 4096 }), { sensitive: true }),
|
|
277
|
+
),
|
|
254
278
|
intervalMs: Type.Optional(Type.Integer({ minimum: 5000 })),
|
|
255
279
|
},
|
|
256
280
|
{ additionalProperties: false },
|
|
@@ -258,7 +282,7 @@ export const PiTeamsOtlpConfigSchema = Type.Object(
|
|
|
258
282
|
|
|
259
283
|
export const PiTeamsUiConfigSchema = Type.Object(
|
|
260
284
|
{
|
|
261
|
-
widgetPlacement: Type.Optional(Type.Union([Type.Literal("aboveEditor"), Type.Literal("belowEditor")])),
|
|
285
|
+
widgetPlacement: Type.Optional(Type.Union([Type.Literal("aboveEditor"), Type.Literal("belowEditor"), Type.Literal("bottom")])),
|
|
262
286
|
widgetMaxLines: Type.Optional(Type.Integer({ minimum: 1, maximum: 50 })),
|
|
263
287
|
powerbar: Type.Optional(Type.Boolean()),
|
|
264
288
|
dashboardPlacement: Type.Optional(Type.Union([Type.Literal("center"), Type.Literal("right")])),
|
|
@@ -298,16 +322,31 @@ export const CrewBrokerConfigSchema = Type.Object(
|
|
|
298
322
|
pathHashLen: Type.Optional(Type.Integer({ minimum: 4, maximum: 32 })),
|
|
299
323
|
maxFrameBytes: Type.Optional(Type.Integer({ minimum: 1024, maximum: 1_048_576 })),
|
|
300
324
|
outboundQueueCap: Type.Optional(Type.Integer({ minimum: 32, maximum: 4096 })),
|
|
325
|
+
/** WP-2/R2 (ADR-0 2026-08-17-waiting-producer-ask item 7): gate for the
|
|
326
|
+
* broker wait.* methods. Default false — fail-closed. */
|
|
327
|
+
waitMethodsEnabled: Type.Optional(Type.Boolean()),
|
|
328
|
+
},
|
|
329
|
+
{ additionalProperties: false },
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
export const PiTeamsNestingConfigSchema = Type.Object(
|
|
333
|
+
{
|
|
334
|
+
// Privilege-raising flag (spawns grandchildren): project-level config must
|
|
335
|
+
// not be able to enable it — sensitive → user config only (ADR-5 §12 posture,
|
|
336
|
+
// same treatment as autonomous.enabled / broker gates).
|
|
337
|
+
enabled: Type.Optional(Type.Boolean({ sensitive: true })),
|
|
338
|
+
maxSlots: Type.Optional(Type.Integer({ minimum: 1, maximum: 64 })),
|
|
339
|
+
maxDepth: Type.Optional(Type.Integer({ minimum: 1, maximum: 10 })),
|
|
301
340
|
},
|
|
302
341
|
{ additionalProperties: false },
|
|
303
342
|
);
|
|
304
343
|
|
|
305
344
|
export const PiTeamsConfigSchema = Type.Object(
|
|
306
345
|
{
|
|
307
|
-
asyncByDefault: Type.Optional(Type.Boolean()),
|
|
308
|
-
executeWorkers: Type.Optional(Type.Boolean()),
|
|
346
|
+
asyncByDefault: Type.Optional(Type.Boolean({ sensitive: true })),
|
|
347
|
+
executeWorkers: Type.Optional(Type.Boolean({ sensitive: true })),
|
|
309
348
|
notifierIntervalMs: Type.Optional(Type.Number({ minimum: 1000 })),
|
|
310
|
-
requireCleanWorktreeLeader: Type.Optional(Type.Boolean()),
|
|
349
|
+
requireCleanWorktreeLeader: Type.Optional(Type.Boolean({ sensitive: true })),
|
|
311
350
|
ignoreMethod: Type.Optional(Type.Union([Type.Literal("gitignore"), Type.Literal("exclude")])),
|
|
312
351
|
autonomous: Type.Optional(PiTeamsAutonomousConfigSchema),
|
|
313
352
|
limits: Type.Optional(PiTeamsLimitsConfigSchema),
|
|
@@ -325,6 +364,7 @@ export const PiTeamsConfigSchema = Type.Object(
|
|
|
325
364
|
otlp: Type.Optional(PiTeamsOtlpConfigSchema),
|
|
326
365
|
ui: Type.Optional(PiTeamsUiConfigSchema),
|
|
327
366
|
broker: Type.Optional(CrewBrokerConfigSchema),
|
|
367
|
+
nesting: Type.Optional(PiTeamsNestingConfigSchema),
|
|
328
368
|
},
|
|
329
369
|
{ additionalProperties: false },
|
|
330
370
|
);
|