pi-crew 0.9.40 → 0.9.42
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 +140 -0
- package/dist/build-meta.json +7089 -6728
- package/dist/index.mjs +45845 -45192
- package/dist/index.mjs.map +4 -4
- package/docs/migration/atomic-write-v2-migration.md +2 -0
- package/docs/optimization-plan-2026-07.md +366 -0
- package/docs/phase4-triage.md +58 -0
- package/docs/superpowers/plans/2026-06-09-fallow-patterns-adoption.md +2 -0
- package/package.json +4 -2
- package/scripts/README.md +6 -0
- package/scripts/check-all-skills.ts +3 -3
- package/scripts/dev-runner.mjs +106 -0
- package/scripts/postinstall.mjs +6 -2
- package/scripts/verify-skill.ts +2 -2
- package/src/config/config.ts +19 -0
- package/src/config/drift-detector.ts +97 -15
- package/src/config/resilient-parser.ts +4 -4
- package/src/extension/crew-cleanup.ts +17 -3
- package/src/extension/crew-vibes/index.ts +1 -1
- package/src/extension/cross-extension-rpc.ts +18 -5
- package/src/extension/management.ts +6 -5
- package/src/extension/register.ts +58 -1573
- package/src/extension/registration/command-registration.ts +57 -0
- package/src/extension/registration/commands.ts +2 -1
- package/src/extension/registration/context-builder.ts +131 -0
- package/src/extension/registration/crash-recovery-cache.ts +54 -0
- package/src/extension/registration/foreground-run-controller.ts +210 -0
- package/src/extension/registration/hook-registration.ts +119 -0
- package/src/extension/registration/lazy-configurers.ts +124 -0
- package/src/extension/registration/lifecycle-handlers.ts +731 -0
- package/src/extension/registration/observability.ts +33 -4
- package/src/extension/registration/registration-types.ts +149 -0
- package/src/extension/registration/runtime-cleanup.ts +230 -0
- package/src/extension/registration/subagent-manager-setup.ts +211 -0
- package/src/extension/registration/tool-registration.ts +50 -0
- package/src/extension/registration/wire-cross-extension.ts +37 -0
- package/src/extension/rpc-hmac.ts +21 -1
- package/src/extension/run-import.ts +3 -3
- package/src/extension/team-tool/auto-summarize.ts +4 -4
- package/src/extension/team-tool/cancel.ts +5 -5
- package/src/extension/team-tool/chain-executor.ts +5 -4
- package/src/extension/team-tool/context.ts +11 -1
- package/src/extension/team-tool/doctor.ts +2 -1
- package/src/extension/team-tool/goal-wrap.ts +2 -2
- package/src/extension/team-tool/goal.ts +1 -1
- package/src/extension/team-tool/handle-schedule.ts +25 -36
- package/src/extension/team-tool/orchestrate.ts +0 -1
- package/src/extension/team-tool/parallel-dispatch.ts +5 -0
- package/src/extension/team-tool/run.ts +220 -214
- package/src/extension/team-tool/workflow-manage.ts +1 -1
- package/src/extension/team-tool.ts +64 -16
- package/src/hooks/registry.ts +7 -2
- package/src/observability/correlation.ts +4 -3
- package/src/observability/event-bus.ts +10 -1
- package/src/observability/event-to-metric.ts +8 -7
- package/src/observability/exporters/adapter.ts +6 -9
- package/src/observability/exporters/otlp-exporter.ts +81 -0
- package/src/observability/metric-retention.ts +4 -0
- package/src/observability/metric-sink.ts +14 -4
- package/src/runtime/adaptive-plan.ts +8 -8
- package/src/runtime/async-runner.ts +13 -4
- package/src/runtime/background-runner.ts +32 -23
- package/src/runtime/capability-inventory.ts +1 -1
- package/src/runtime/chain-runner.ts +5 -5
- package/src/runtime/checkpoint.ts +6 -16
- package/src/runtime/child-pi.ts +157 -71
- package/src/runtime/coalesce-tasks.ts +1 -1
- package/src/runtime/crash-recovery.ts +63 -47
- package/src/runtime/delivery-coordinator.ts +1 -1
- package/src/runtime/deterministic-ast.ts +2 -2
- package/src/runtime/diagnostic-export.ts +2 -1
- package/src/runtime/foreground-control.ts +3 -2
- package/src/runtime/goal-loop-runner.ts +6 -2
- package/src/runtime/goal-state-store.ts +1 -1
- package/src/runtime/handoff-manager.ts +0 -2
- package/src/runtime/heartbeat-watcher.ts +5 -0
- package/src/runtime/intercom-bridge.ts +0 -2
- package/src/runtime/live-agent-manager.ts +12 -32
- package/src/runtime/live-extension-bridge.ts +0 -2
- package/src/runtime/live-session-runtime.ts +65 -57
- package/src/runtime/manifest-cache.ts +59 -0
- package/src/runtime/mcp-proxy.ts +1 -1
- package/src/runtime/orphan-worker-registry.ts +3 -3
- package/src/runtime/output-validator.ts +1 -1
- package/src/runtime/path-overlap.ts +1 -1
- package/src/runtime/pi-args.ts +3 -3
- package/src/runtime/pi-json-output.ts +2 -2
- package/src/runtime/pipeline-runner.ts +1 -4
- package/src/runtime/post-checks.ts +0 -1
- package/src/runtime/retry-runner.ts +1 -1
- package/src/runtime/run-coalesced-task-group.ts +0 -13
- package/src/runtime/scheduler.ts +2 -0
- package/src/runtime/session-resources.ts +1 -1
- package/src/runtime/settings-store.ts +31 -1
- package/src/runtime/skill-effectiveness.ts +2 -2
- package/src/runtime/stale-reconciler.ts +23 -53
- package/src/runtime/subagent-manager.ts +19 -3
- package/src/runtime/task-output-context.ts +2 -1
- package/src/runtime/task-runner/prompt-builder.ts +1 -1
- package/src/runtime/task-runner/run-projection.ts +3 -1
- package/src/runtime/task-runner/state-helpers.ts +13 -1
- package/src/runtime/task-runner.ts +31 -6
- package/src/runtime/team-runner.ts +3 -2
- package/src/runtime/verification-gates.ts +54 -5
- package/src/schema/config-schema.ts +19 -1
- package/src/schema/team-tool-schema.ts +2 -0
- package/src/skills/discover-skills.ts +25 -15
- package/src/state/active-run-registry.ts +0 -1
- package/src/state/atomic-write.ts +37 -11
- package/src/state/blob-store.ts +3 -9
- package/src/state/contracts.ts +5 -1
- package/src/state/crew-init.ts +3 -2
- package/src/state/decision-ledger.ts +2 -2
- package/src/state/event-log-rotation.ts +1 -1
- package/src/state/event-log.ts +3 -3
- package/src/state/gitignore-manager.ts +2 -1
- package/src/state/health-store.ts +5 -2
- package/src/state/instinct-store.ts +11 -2
- package/src/state/mailbox.ts +3 -4
- package/src/state/observation-store.ts +1 -13
- package/src/state/run-cache.ts +12 -12
- package/src/state/run-graph.ts +2 -1
- package/src/state/run-metrics.ts +1 -1
- package/src/state/schedule.ts +7 -1
- package/src/state/state-store.ts +9 -3
- package/src/state/tiered-eval.ts +1 -1
- package/src/ui/dynamic-border.ts +13 -3
- package/src/ui/live-conversation-overlay.ts +7 -27
- package/src/ui/live-duration.ts +2 -2
- package/src/ui/live-run-sidebar.ts +56 -32
- package/src/ui/mascot.ts +19 -13
- package/src/ui/powerbar-publisher.ts +244 -178
- package/src/ui/render-coalescer.ts +44 -3
- package/src/ui/render-diff.ts +2 -2
- package/src/ui/run-dashboard.ts +122 -21
- package/src/ui/run-event-bus.ts +66 -0
- package/src/ui/run-snapshot-cache.ts +84 -81
- package/src/ui/settings-overlay.ts +3 -3
- package/src/ui/status-colors.ts +24 -0
- package/src/ui/tool-progress-formatter.ts +1 -1
- package/src/ui/tool-render.ts +3 -3
- package/src/ui/transcript-cache.ts +5 -10
- package/src/ui/transcript-entries.ts +68 -72
- package/src/ui/transcript-viewer.ts +54 -14
- package/src/ui/widget/index.ts +26 -15
- package/src/ui/widget/widget-model.ts +18 -9
- package/src/ui/widget/widget-renderer.ts +2 -5
- package/src/utils/fingerprint.ts +1 -1
- package/src/utils/project-detector.ts +1 -1
- package/src/state/atomic-write-v2.ts +0 -108
package/src/config/config.ts
CHANGED
|
@@ -676,6 +676,7 @@ function parseLimitsConfig(value: unknown): CrewLimitsConfig | undefined {
|
|
|
676
676
|
maxRetriesPerTask: parsePositiveInteger(obj.maxRetriesPerTask, LIMIT_CEILINGS.maxRetriesPerTask),
|
|
677
677
|
maxTasksPerRun: parsePositiveInteger(obj.maxTasksPerRun, LIMIT_CEILINGS.maxTasksPerRun),
|
|
678
678
|
heartbeatStaleMs: parsePositiveInteger(obj.heartbeatStaleMs, LIMIT_CEILINGS.heartbeatStaleMs),
|
|
679
|
+
serializeOnPathOverlap: parseWithSchema(Type.Boolean(), obj.serializeOnPathOverlap),
|
|
679
680
|
};
|
|
680
681
|
return Object.values(limits).some((entry) => entry !== undefined) ? limits : undefined;
|
|
681
682
|
}
|
|
@@ -717,6 +718,17 @@ function parseRuntimeConfig(value: unknown): CrewRuntimeConfig | undefined {
|
|
|
717
718
|
Type.Union([Type.Literal("off"), Type.Literal("warn"), Type.Literal("block"), Type.Literal("fail")]),
|
|
718
719
|
obj.effectivenessGuard,
|
|
719
720
|
),
|
|
721
|
+
yield: (() => {
|
|
722
|
+
const y = asRecord(obj.yield);
|
|
723
|
+
if (!y) return undefined;
|
|
724
|
+
const parsed: NonNullable<CrewRuntimeConfig["yield"]> = {
|
|
725
|
+
enabled: parseWithSchema(Type.Boolean(), y.enabled),
|
|
726
|
+
maxReminders: parseWithSchema(Type.Integer({ minimum: 0 }), y.maxReminders),
|
|
727
|
+
reminderPrompt: parseWithSchema(Type.String({ maxLength: 1000 }), y.reminderPrompt),
|
|
728
|
+
};
|
|
729
|
+
return Object.values(parsed).some((v) => v !== undefined) ? parsed : undefined;
|
|
730
|
+
})(),
|
|
731
|
+
excludeContextBash: parseWithSchema(Type.Boolean(), obj.excludeContextBash),
|
|
720
732
|
isolationPolicy: parseIsolationPolicy(obj.isolationPolicy),
|
|
721
733
|
};
|
|
722
734
|
return Object.values(runtime).some((entry) => entry !== undefined) ? runtime : undefined;
|
|
@@ -815,6 +827,7 @@ function parseUiConfig(value: unknown): CrewUiConfig | undefined {
|
|
|
815
827
|
dashboardLiveRefreshMs: parseIntegerInRange(obj.dashboardLiveRefreshMs, 250, 60_000),
|
|
816
828
|
autoOpenDashboard: parseWithSchema(Type.Boolean(), obj.autoOpenDashboard),
|
|
817
829
|
autoOpenDashboardForForegroundRuns: parseWithSchema(Type.Boolean(), obj.autoOpenDashboardForForegroundRuns),
|
|
830
|
+
autoCloseDashboardMs: parseWithSchema(Type.Integer({ minimum: 0 }), obj.autoCloseDashboardMs),
|
|
818
831
|
showModel: parseWithSchema(Type.Boolean(), obj.showModel),
|
|
819
832
|
showTokens: parseWithSchema(Type.Boolean(), obj.showTokens),
|
|
820
833
|
showTools: parseWithSchema(Type.Boolean(), obj.showTools),
|
|
@@ -932,6 +945,11 @@ function parseReliabilityConfig(value: unknown): CrewReliabilityConfig | undefin
|
|
|
932
945
|
autoRecover: parseWithSchema(Type.Boolean(), obj.autoRecover),
|
|
933
946
|
deadletterThreshold: parsePositiveInteger(obj.deadletterThreshold),
|
|
934
947
|
cleanupOrphanedTempDirs: parseWithSchema(Type.Boolean(), obj.cleanupOrphanedTempDirs),
|
|
948
|
+
autoRepairIntervalMs: parseWithSchema(Type.Integer({ minimum: 0 }), obj.autoRepairIntervalMs),
|
|
949
|
+
forcePreflight: parseWithSchema(Type.Boolean(), obj.forcePreflight),
|
|
950
|
+
ambientStatusInjection: parseWithSchema(Type.Boolean(), obj.ambientStatusInjection),
|
|
951
|
+
perWriteValidation: parseWithSchema(Type.Boolean(), obj.perWriteValidation),
|
|
952
|
+
scopeModels: parseWithSchema(Type.Boolean(), obj.scopeModels),
|
|
935
953
|
};
|
|
936
954
|
return Object.values(reliability).some((entry) => entry !== undefined) ? reliability : undefined;
|
|
937
955
|
}
|
|
@@ -985,6 +1003,7 @@ export function parseConfig(raw: unknown): PiTeamsConfig {
|
|
|
985
1003
|
executeWorkers: parseWithSchema(Type.Boolean(), obj.executeWorkers),
|
|
986
1004
|
notifierIntervalMs: parseWithSchema(Type.Number({ minimum: 1_000 }), obj.notifierIntervalMs),
|
|
987
1005
|
requireCleanWorktreeLeader: parseWithSchema(Type.Boolean(), obj.requireCleanWorktreeLeader),
|
|
1006
|
+
ignoreMethod: parseWithSchema(Type.Union([Type.Literal("gitignore"), Type.Literal("exclude")]), obj.ignoreMethod),
|
|
988
1007
|
autonomous: parseAutonomousConfig(obj.autonomous),
|
|
989
1008
|
limits: parseLimitsConfig(obj.limits),
|
|
990
1009
|
runtime: parseRuntimeConfig(obj.runtime),
|
|
@@ -50,10 +50,92 @@ export interface ConfigReferences {
|
|
|
50
50
|
workflows: Set<string>;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
const MAX_CONFIG_KEY_DEPTH = 3;
|
|
54
|
+
|
|
55
|
+
type ConfigSchemaNode = Record<string, unknown>;
|
|
56
|
+
|
|
57
|
+
interface SchemaKeyResolution {
|
|
58
|
+
allowed: boolean;
|
|
59
|
+
childSchema?: unknown;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
63
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function schemaAlternatives(schema: unknown): ConfigSchemaNode[] {
|
|
67
|
+
if (!isRecord(schema)) return [];
|
|
68
|
+
const alternatives = Array.isArray(schema.anyOf) ? schema.anyOf.flatMap((entry) => schemaAlternatives(entry)) : [];
|
|
69
|
+
return [schema, ...alternatives];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resolveSchemaKey(schema: unknown, key: string): SchemaKeyResolution {
|
|
73
|
+
let sawObjectSchema = false;
|
|
74
|
+
let allowsAdditionalProperties = false;
|
|
75
|
+
const childSchemas: unknown[] = [];
|
|
76
|
+
|
|
77
|
+
for (const candidate of schemaAlternatives(schema)) {
|
|
78
|
+
const properties = isRecord(candidate.properties) ? candidate.properties : undefined;
|
|
79
|
+
const patternProperties = isRecord(candidate.patternProperties) ? candidate.patternProperties : undefined;
|
|
80
|
+
if (candidate.type !== "object" && !properties && !patternProperties) continue;
|
|
81
|
+
sawObjectSchema = true;
|
|
82
|
+
|
|
83
|
+
if (properties && Object.hasOwn(properties, key)) {
|
|
84
|
+
childSchemas.push(properties[key]);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let matchedPattern = false;
|
|
89
|
+
if (patternProperties) {
|
|
90
|
+
for (const [pattern, childSchema] of Object.entries(patternProperties)) {
|
|
91
|
+
try {
|
|
92
|
+
if (new RegExp(pattern).test(key)) {
|
|
93
|
+
childSchemas.push(childSchema);
|
|
94
|
+
matchedPattern = true;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
// Schema patterns are trusted constants; an invalid one simply cannot match.
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (!matchedPattern && candidate.additionalProperties !== false) {
|
|
103
|
+
allowsAdditionalProperties = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
allowed: !sawObjectSchema || childSchemas.length > 0 || allowsAdditionalProperties,
|
|
109
|
+
childSchema: childSchemas[0],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
53
113
|
/**
|
|
54
|
-
*
|
|
114
|
+
* Recursively compare object keys with their TypeBox schema nodes. Root is
|
|
115
|
+
* depth 0; nested objects are traversed through depth 3, which covers the
|
|
116
|
+
* deepest current config shapes (`goalWrap.<workflow>.verification` and
|
|
117
|
+
* `agents.overrides.<agent>`) without allowing attacker-controlled recursion.
|
|
55
118
|
*/
|
|
56
|
-
|
|
119
|
+
function findUnknownConfigKeyPaths(value: unknown, schema: unknown, depth = 0, path: readonly string[] = []): string[][] {
|
|
120
|
+
if (!isRecord(value) || depth > MAX_CONFIG_KEY_DEPTH) return [];
|
|
121
|
+
const unknownPaths: string[][] = [];
|
|
122
|
+
for (const [key, childValue] of Object.entries(value)) {
|
|
123
|
+
const childPath = [...path, key];
|
|
124
|
+
const resolution = resolveSchemaKey(schema, key);
|
|
125
|
+
if (!resolution.allowed) {
|
|
126
|
+
unknownPaths.push(childPath);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (resolution.childSchema !== undefined && depth < MAX_CONFIG_KEY_DEPTH) {
|
|
130
|
+
unknownPaths.push(...findUnknownConfigKeyPaths(childValue, resolution.childSchema, depth + 1, childPath));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return unknownPaths;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function isAgentOverrideKeyPath(path: readonly string[]): boolean {
|
|
137
|
+
return path.length === 4 && path[0] === "agents" && path[1] === "overrides";
|
|
138
|
+
}
|
|
57
139
|
|
|
58
140
|
/**
|
|
59
141
|
* Extract all agent, team, and workflow names referenced in a raw config object.
|
|
@@ -94,7 +176,7 @@ function extractConfigReferences(config: Record<string, unknown>): ConfigReferen
|
|
|
94
176
|
* 1. **Missing**: resource referenced in config (e.g. agents.overrides) but not discovered → error
|
|
95
177
|
* 2. **Extra**: discovered resource not referenced anywhere in config → warning
|
|
96
178
|
* 3. **Mismatched**: resource exists in both but with structural differences → warning
|
|
97
|
-
* 4. **Config keys**: config keys not present in the schema → warning
|
|
179
|
+
* 4. **Config keys**: config keys not present in the schema, recursively through depth 3 → warning
|
|
98
180
|
*
|
|
99
181
|
* For agents: compares against `agents.overrides` in config. Discovered agents not in overrides
|
|
100
182
|
* are NOT considered extra (they may be built-in). Only agents referenced in overrides but not
|
|
@@ -140,18 +222,18 @@ export function detectDrift(discovered: DiscoveredResources, config: PiTeamsConf
|
|
|
140
222
|
// No extra/missing checks for workflows from config alone.
|
|
141
223
|
|
|
142
224
|
// --- Config-key drift ---
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
225
|
+
for (const keyPath of findUnknownConfigKeyPaths(configRaw, PiTeamsConfigSchema)) {
|
|
226
|
+
// Preserve the existing, more specific agent/mismatched finding for
|
|
227
|
+
// agents.overrides.<agent>.<key> instead of reporting the same key twice.
|
|
228
|
+
if (isAgentOverrideKeyPath(keyPath)) continue;
|
|
229
|
+
items.push({
|
|
230
|
+
kind: "config-key",
|
|
231
|
+
name: keyPath.join("."),
|
|
232
|
+
status: "extra",
|
|
233
|
+
expected: `key not in schema`,
|
|
234
|
+
actual: `present in config`,
|
|
235
|
+
severity: "warning",
|
|
236
|
+
});
|
|
155
237
|
}
|
|
156
238
|
|
|
157
239
|
// --- Mismatched config-key check ---
|
|
@@ -86,11 +86,11 @@ export function parseConfigResilient(raw: unknown): ResilientParseResult {
|
|
|
86
86
|
const value = raw[key];
|
|
87
87
|
const subSchema = (PiTeamsConfigSchema.properties as Record<string, unknown>)[key];
|
|
88
88
|
if (subSchema && !Value.Check(subSchema as TSchema, value)) {
|
|
89
|
-
// Collect
|
|
89
|
+
// Collect ALL errors for this field
|
|
90
90
|
const fieldErrors = [...Value.Errors(subSchema as TSchema, value)];
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
const msg = typeof
|
|
91
|
+
for (const err of fieldErrors) {
|
|
92
|
+
const errObj = err as unknown as Record<string, unknown>;
|
|
93
|
+
const msg = typeof errObj.message === "string" ? errObj.message : "invalid value";
|
|
94
94
|
errors.push({
|
|
95
95
|
field: key,
|
|
96
96
|
message: msg,
|
|
@@ -93,11 +93,25 @@ export function registerCleanupHandler(pi: ExtensionAPI): void {
|
|
|
93
93
|
|
|
94
94
|
async function cleanupChildProcesses(): Promise<void> {
|
|
95
95
|
const pids = childProcessRegistry.getAllPids();
|
|
96
|
-
|
|
96
|
+
if (pids.length === 0) return;
|
|
97
|
+
// Lazy-import to avoid eagerly pulling the heavy runtime/child-pi chain into the
|
|
98
|
+
// extension load path (check-lazy-imports) AND to avoid a static import cycle
|
|
99
|
+
// (child-pi.ts already imports registerChildProcess from this module).
|
|
100
|
+
// LAZY: defer child-pi.ts load (heavy runtime chain) + avoid static import cycle.
|
|
101
|
+
const { killProcessPid } = await import("../runtime/child-pi.ts");
|
|
97
102
|
for (const pid of pids) {
|
|
103
|
+
// killProcessPid signals the whole process GROUP (-pid on POSIX, taskkill /T on
|
|
104
|
+
// Windows) and escalates to SIGKILL after HARD_KILL_MS. A bare
|
|
105
|
+
// process.kill(pid, "SIGTERM") only reaches the direct child — grandchildren
|
|
106
|
+
// (bash/MCP the worker spawned) in the child's group survive as orphans, and
|
|
107
|
+
// a child that lags SIGTERM would linger forever with no escalation.
|
|
108
|
+
// NOTE: on graceful session_shutdown (reason=quit/reload) register.ts ALSO runs
|
|
109
|
+
// terminateActiveChildPiProcesses(); both are idempotent for a dying pid. This
|
|
110
|
+
// signal handler (SIGTERM/SIGHUP) is the SOLE teardown path when the Pi process
|
|
111
|
+
// is killed by signal, so it must be robust on its own.
|
|
98
112
|
try {
|
|
99
|
-
|
|
100
|
-
console.log(`[pi-crew] Sent SIGTERM to child process ${pid}`);
|
|
113
|
+
killProcessPid(pid);
|
|
114
|
+
console.log(`[pi-crew] Sent group SIGTERM (+SIGKILL escalation) to child process ${pid}`);
|
|
101
115
|
} catch (error: unknown) {
|
|
102
116
|
// Process may already be dead or not exist
|
|
103
117
|
const err = error as NodeJS.ErrnoException;
|
|
@@ -19,7 +19,7 @@ function safeUiCall(scope: string, fn: () => void): void {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
import { type CrewVibesConfig, loadConfig,
|
|
22
|
+
import { type CrewVibesConfig, loadConfig, saveConfig } from "./config.ts";
|
|
23
23
|
import { intervalForSpeed } from "./figures.ts";
|
|
24
24
|
import { type CrewVibesFooterSource, createCrewVibesFooter } from "./footer.ts";
|
|
25
25
|
import { clearProviderUsageCache, fetchProviderUsage, type ProviderUsage } from "./provider-usage.ts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { TeamToolParamsValue } from "../schema/team-tool-schema.ts";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveRealContainedPath } from "../utils/safe-paths.ts";
|
|
4
4
|
import { withHmacVerification } from "./rpc-hmac.ts";
|
|
5
5
|
// Lazy-loaded to avoid pulling team-tool.ts (and its entire runtime chain) into module load.
|
|
6
6
|
import type { handleTeamTool as HandleTeamToolFn } from "./team-tool.ts";
|
|
@@ -109,7 +109,10 @@ export function resetRpcRateLimit(): void {
|
|
|
109
109
|
rpcRunTimestamps.length = 0;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
function isAllowedRpcRunParams(
|
|
112
|
+
export function isAllowedRpcRunParams(
|
|
113
|
+
params: TeamToolParamsValue,
|
|
114
|
+
ctx: ExtensionContext,
|
|
115
|
+
): {
|
|
113
116
|
ok: boolean;
|
|
114
117
|
error?: string;
|
|
115
118
|
} {
|
|
@@ -123,10 +126,16 @@ function isAllowedRpcRunParams(params: TeamToolParamsValue): {
|
|
|
123
126
|
error: "RPC run requires config.intent (a non-empty intent string)",
|
|
124
127
|
};
|
|
125
128
|
}
|
|
126
|
-
// SECURITY: Validate cwd
|
|
129
|
+
// SECURITY: Validate cwd, if provided, resolves WITHIN the project directory (ctx.cwd).
|
|
130
|
+
// Arg order is (baseDir=ctx.cwd, targetPath=params.cwd): the project dir is the container,
|
|
131
|
+
// the caller-supplied cwd is the contained target. (Previously the args were swapped —
|
|
132
|
+
// resolveContainedPath(params.cwd, ".") — which validates that "." is inside params.cwd,
|
|
133
|
+
// trivially true for ANY path, so the check never rejected anything.)
|
|
134
|
+
// NOTE: defense-in-depth — handleTeamTool currently uses ctx.cwd and ignores params.cwd,
|
|
135
|
+
// but a correct guard here prevents a future regression from silently enabling cwd escape.
|
|
127
136
|
if (params.cwd && typeof params.cwd === "string") {
|
|
128
137
|
try {
|
|
129
|
-
|
|
138
|
+
resolveRealContainedPath(ctx.cwd, params.cwd);
|
|
130
139
|
} catch {
|
|
131
140
|
return {
|
|
132
141
|
ok: false,
|
|
@@ -152,6 +161,10 @@ export function registerPiCrewRpc(
|
|
|
152
161
|
getCtx: () => ExtensionContext | undefined,
|
|
153
162
|
): PiCrewRpcHandle | undefined {
|
|
154
163
|
if (!events) return undefined;
|
|
164
|
+
// Note: the prior loud startup warning has been replaced by a soft, opt-out
|
|
165
|
+
// info note emitted lazily by withHmacVerification only when an actual RPC
|
|
166
|
+
// request is processed (see rpc-hmac.ts). This avoids noisy output during
|
|
167
|
+
// every pi load for users who never wire cross-extension RPC.
|
|
155
168
|
const unsubs = [
|
|
156
169
|
on(
|
|
157
170
|
events,
|
|
@@ -214,7 +227,7 @@ export function registerPiCrewRpc(
|
|
|
214
227
|
} else {
|
|
215
228
|
params = { action: "run" };
|
|
216
229
|
}
|
|
217
|
-
const permission = isAllowedRpcRunParams(params);
|
|
230
|
+
const permission = isAllowedRpcRunParams(params, ctx);
|
|
218
231
|
if (!permission.ok) {
|
|
219
232
|
reply(events, "pi-crew:rpc:run", id, {
|
|
220
233
|
success: false,
|
|
@@ -6,6 +6,7 @@ import { serializeAgent } from "../agents/agent-serializer.ts";
|
|
|
6
6
|
import { discoverAgents, invalidateAgentDiscoveryCache } from "../agents/discover-agents.ts";
|
|
7
7
|
import type { PiTeamsConfig } from "../config/config.ts";
|
|
8
8
|
import type { TeamToolParamsValue } from "../schema/team-tool-schema.ts";
|
|
9
|
+
import { atomicWriteFile } from "../state/atomic-write.ts";
|
|
9
10
|
import { allTeams, discoverTeams, invalidateTeamDiscoveryCache } from "../teams/discover-teams.ts";
|
|
10
11
|
import type { TeamConfig, TeamRole } from "../teams/team-config.ts";
|
|
11
12
|
import { serializeTeam } from "../teams/team-serializer.ts";
|
|
@@ -258,7 +259,7 @@ function updateReferencesForRename(
|
|
|
258
259
|
changed.push(team.filePath);
|
|
259
260
|
if (!dryRun) {
|
|
260
261
|
backupFile(team.filePath);
|
|
261
|
-
|
|
262
|
+
atomicWriteFile(team.filePath, serializeTeam(nextTeam));
|
|
262
263
|
}
|
|
263
264
|
}
|
|
264
265
|
// L12 fix: also update workflow step role references when renaming agents.
|
|
@@ -276,7 +277,7 @@ function updateReferencesForRename(
|
|
|
276
277
|
changed.push(workflow.filePath);
|
|
277
278
|
if (!dryRun) {
|
|
278
279
|
backupFile(workflow.filePath);
|
|
279
|
-
|
|
280
|
+
atomicWriteFile(workflow.filePath, serializeWorkflow({ ...workflow, steps: newSteps }));
|
|
280
281
|
}
|
|
281
282
|
}
|
|
282
283
|
// L12 fix: update agent references in test fixtures.
|
|
@@ -290,7 +291,7 @@ function updateReferencesForRename(
|
|
|
290
291
|
if (newContent !== content) {
|
|
291
292
|
changed.push(fixture);
|
|
292
293
|
if (!dryRun) {
|
|
293
|
-
|
|
294
|
+
atomicWriteFile(fixture, newContent);
|
|
294
295
|
}
|
|
295
296
|
}
|
|
296
297
|
}
|
|
@@ -388,7 +389,7 @@ export function handleCreate(params: TeamToolParamsValue, ctx: ManagementContext
|
|
|
388
389
|
|
|
389
390
|
if (params.dryRun) return result(`[dry-run] Would create ${params.resource} '${name}' at ${filePath}:\n\n${content}`);
|
|
390
391
|
try {
|
|
391
|
-
|
|
392
|
+
atomicWriteFile(filePath, content);
|
|
392
393
|
} catch (writeError) {
|
|
393
394
|
return result(
|
|
394
395
|
`Failed to create ${params.resource}: ${writeError instanceof Error ? writeError.message : String(writeError)}`,
|
|
@@ -513,7 +514,7 @@ export function handleUpdate(params: TeamToolParamsValue, ctx: ManagementContext
|
|
|
513
514
|
}
|
|
514
515
|
}
|
|
515
516
|
}
|
|
516
|
-
|
|
517
|
+
atomicWriteFile(nextPath, content);
|
|
517
518
|
} catch (updateError) {
|
|
518
519
|
return result(
|
|
519
520
|
`Failed to update ${params.resource}: ${updateError instanceof Error ? updateError.message : String(updateError)}`,
|