pi-crew 0.9.41 → 0.9.44
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 +253 -0
- package/README.md +14 -0
- package/dist/build-meta.json +6450 -12419
- package/dist/index.mjs +57998 -68417
- package/dist/index.mjs.map +4 -4
- package/docs/migration/atomic-write-v2-migration.md +2 -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/build-bundle.mjs +2 -1
- 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/agents/agent-config.ts +1 -1
- package/src/config/config.ts +20 -1
- package/src/config/drift-detector.ts +97 -15
- package/src/config/resilient-parser.ts +4 -4
- package/src/config/types.ts +0 -2
- package/src/extension/crew-cleanup.ts +17 -3
- package/src/extension/crew-vibes/config.ts +1 -1
- package/src/extension/crew-vibes/font-detect.ts +16 -3
- package/src/extension/crew-vibes/index.ts +1 -1
- package/src/extension/cross-extension-rpc.ts +26 -9
- 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 +7 -2
- 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 +238 -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 +69 -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/prompt/prompt-runtime.ts +15 -0
- package/src/runtime/adaptive-plan.ts +1 -1
- 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-constants.ts +42 -0
- package/src/runtime/child-pi-kill.ts +180 -0
- package/src/runtime/child-pi-spawn.ts +234 -0
- package/src/runtime/child-pi-steering.ts +128 -0
- package/src/runtime/child-pi-streams.ts +296 -0
- package/src/runtime/child-pi-transcript.ts +169 -0
- package/src/runtime/child-pi.ts +80 -822
- package/src/runtime/coalesce-tasks.ts +1 -1
- package/src/runtime/compact-stages/tail-capture-stage.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/dwf-state-store.ts +5 -0
- package/src/runtime/dynamic-workflow-context.ts +78 -18
- package/src/runtime/dynamic-workflow-runner.ts +18 -2
- package/src/runtime/foreground-control.ts +3 -2
- package/src/runtime/goal-evaluator.ts +59 -27
- package/src/runtime/goal-loop-runner.ts +46 -13
- package/src/runtime/goal-state-store.ts +1 -1
- package/src/runtime/handoff-manager.ts +0 -2
- package/src/runtime/intercom-bridge.ts +0 -2
- package/src/runtime/live-agent-manager.ts +30 -5
- 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 +4 -3
- package/src/runtime/pi-spawn.ts +7 -1
- 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 +18 -23
- 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 +3 -3
- 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 +8 -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 +4 -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/locks.ts +132 -49
- 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/state/worker-atomic-writer.ts +1 -0
- 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/paths.ts +1 -1
- package/src/utils/project-detector.ts +1 -1
- package/src/worktree/worktree-manager.ts +47 -19
- package/src/state/atomic-write-v2.ts +0 -108
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool registration installer for pi-crew.
|
|
3
|
+
*
|
|
4
|
+
* Single entry point that wires every `pi.registerTool(...)` call site
|
|
5
|
+
* in the extension. Today that means two heavy tools:
|
|
6
|
+
* • `team` — the multi-agent orchestration tool (team-tool.ts).
|
|
7
|
+
* • subagent tools — agent spawn tool + result-join helpers
|
|
8
|
+
* (subagent-tools.ts).
|
|
9
|
+
*
|
|
10
|
+
* The actual tool handlers live in `registration/team-tool.ts` and
|
|
11
|
+
* `registration/subagent-tools.ts` (already extracted); this file is
|
|
12
|
+
* the orchestrator-level wrapper that calls them with the right deps.
|
|
13
|
+
*/
|
|
14
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import type { RegistrationContext } from "./registration-types.ts";
|
|
16
|
+
import { registerSubagentTools } from "./subagent-tools.ts";
|
|
17
|
+
import { registerTeamTool } from "./team-tool.ts";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Register all pi-crew tools on the ExtensionAPI.
|
|
21
|
+
*
|
|
22
|
+
* Pulls every dependency the underlying tools need off `ctx`. The
|
|
23
|
+
* orchestrator guarantees those fields are populated before this call
|
|
24
|
+
* (see registerPiTeams in ../register.ts).
|
|
25
|
+
*/
|
|
26
|
+
export function registerPiTools(pi: ExtensionAPI, ctx: RegistrationContext): void {
|
|
27
|
+
registerTeamTool(pi, {
|
|
28
|
+
foregroundControllers: ctx.foregroundControllers,
|
|
29
|
+
startForegroundRun: ctx.startForegroundRun,
|
|
30
|
+
abortForegroundRun: ctx.abortForegroundRun,
|
|
31
|
+
openLiveSidebar: ctx.openLiveSidebar,
|
|
32
|
+
getManifestCache: ctx.getManifestCache,
|
|
33
|
+
getRunSnapshotCache: ctx.getRunSnapshotCache,
|
|
34
|
+
getMetricRegistry: () => ctx.observabilityState.metricRegistry,
|
|
35
|
+
widgetState: ctx.widgetState,
|
|
36
|
+
onJsonEvent: (taskId, runId, event) => {
|
|
37
|
+
const record = event as Record<string, unknown>;
|
|
38
|
+
const eventType = typeof record.type === "string" ? record.type : undefined;
|
|
39
|
+
if (eventType) ctx.lifecycleState.overflowTracker?.feedEvent(taskId, runId, eventType);
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
registerSubagentTools(pi, ctx.subagentManager, {
|
|
43
|
+
ownerSessionGeneration: ctx.captureSessionGeneration,
|
|
44
|
+
// Subagent options only type-check against `unknown` for ctx; cast
|
|
45
|
+
// back to ExtensionContext since the subagent tool re-validates.
|
|
46
|
+
startForegroundRun: (subCtx, runner, runId) =>
|
|
47
|
+
ctx.startForegroundRun(subCtx as Parameters<typeof ctx.startForegroundRun>[0], runner, runId),
|
|
48
|
+
batchBarrier: ctx.batchBarrier,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-extension wiring helper.
|
|
3
|
+
*
|
|
4
|
+
* Two small wires:
|
|
5
|
+
* • wireRpc — the in-process pi-crew RPC handle (other modules in
|
|
6
|
+
* the same extension can subscribe via `pi.events`).
|
|
7
|
+
* • wireGlobalRegistry — the cross-extension crew global registry,
|
|
8
|
+
* lazily loaded from team-tool.ts (heavy module) and installed under
|
|
9
|
+
* `globalThis[Symbol.for("pi-crew:registry")]` so peer extensions
|
|
10
|
+
* (e.g., pi-subagents3) can discover pi-crew at runtime.
|
|
11
|
+
*/
|
|
12
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import { registerPiCrewRpc } from "../cross-extension-rpc.ts";
|
|
14
|
+
import type { RegistrationContext } from "./registration-types.ts";
|
|
15
|
+
|
|
16
|
+
/** Install both wires on the registration context. */
|
|
17
|
+
export function installCrossExtensionWiring(pi: ExtensionAPI, ctx: RegistrationContext): void {
|
|
18
|
+
// Wire the in-process RPC handle.
|
|
19
|
+
const getPiEvents = (): Parameters<typeof registerPiCrewRpc>[0] | undefined => {
|
|
20
|
+
if (pi && typeof pi === "object" && "events" in pi) {
|
|
21
|
+
return (pi as { events?: Parameters<typeof registerPiCrewRpc>[0] }).events;
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
};
|
|
25
|
+
ctx.rpcHandle = registerPiCrewRpc(getPiEvents(), () => ctx.currentCtx);
|
|
26
|
+
|
|
27
|
+
// Install the cross-extension crew global registry. Lazy import keeps
|
|
28
|
+
// team-tool.ts (which pulls in the entire runtime chain) out of the
|
|
29
|
+
// cold-start module graph.
|
|
30
|
+
void import("../team-tool.ts").then(({ installCrewGlobalRegistry }) => {
|
|
31
|
+
const manifestCacheForRegistry = ctx.getManifestCache(ctx.currentCtx?.cwd ?? process.cwd());
|
|
32
|
+
installCrewGlobalRegistry({
|
|
33
|
+
manifestCache: manifestCacheForRegistry,
|
|
34
|
+
cwdProvider: () => ctx.currentCtx?.cwd ?? process.cwd(),
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -53,6 +53,20 @@ export interface RpcSignaturePayload {
|
|
|
53
53
|
|
|
54
54
|
export type RpcSignedPayload = RpcSignaturePayload & { signature: string };
|
|
55
55
|
|
|
56
|
+
// Process-latched: emit a soft, non-alarming note once per process when an RPC
|
|
57
|
+
// request is actually processed without HMAC. The previous warning was emitted
|
|
58
|
+
// at extension registration (every pi load) and was considered noisy by users
|
|
59
|
+
// who never wire cross-extension RPC. Opt out with PI_CREW_SUPPRESS_RPC_WARNING=1.
|
|
60
|
+
let _rpcHmacSoftInfoEmitted = false;
|
|
61
|
+
function rpcHmacSoftInfoOnce(): void {
|
|
62
|
+
if (_rpcHmacSoftInfoEmitted) return;
|
|
63
|
+
_rpcHmacSoftInfoEmitted = true;
|
|
64
|
+
console.warn(
|
|
65
|
+
"[pi-crew] PI_CREW_RPC_SECRET not set; cross-extension RPC requests will be accepted without origin authentication. " +
|
|
66
|
+
"Operation allowlists and session checks still apply. Set PI_CREW_RPC_SECRET or PI_CREW_SUPPRESS_RPC_WARNING=1 to silence this message.",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
56
70
|
// --- Signing -----------------------------------------------------------------
|
|
57
71
|
|
|
58
72
|
/**
|
|
@@ -123,7 +137,13 @@ export function withHmacVerification<P extends { requestId: string }>(
|
|
|
123
137
|
): (params: P) => unknown | Promise<unknown> {
|
|
124
138
|
return (params: P) => {
|
|
125
139
|
if (!isHmacEnabled()) {
|
|
126
|
-
// No secret configured → backward compat: allow unsigned
|
|
140
|
+
// No secret configured → backward compat: allow unsigned.
|
|
141
|
+
// Emit a soft info note (once per process) only when an actual RPC
|
|
142
|
+
// request is being processed — the prior registration-time warning
|
|
143
|
+
// was too noisy for users who never wire cross-extension RPC.
|
|
144
|
+
if (process.env.PI_CREW_SUPPRESS_RPC_WARNING !== "1") {
|
|
145
|
+
rpcHmacSoftInfoOnce();
|
|
146
|
+
}
|
|
127
147
|
return handler(params);
|
|
128
148
|
}
|
|
129
149
|
|
|
@@ -3,6 +3,7 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { DEFAULT_PATHS } from "../config/defaults.ts";
|
|
5
5
|
import { type ConflictReport, detectImportConflicts } from "../runtime/delta-conflict.ts";
|
|
6
|
+
import { atomicWriteFile } from "../state/atomic-write.ts";
|
|
6
7
|
import { projectCrewRoot, userCrewRoot } from "../utils/paths.ts";
|
|
7
8
|
import { assertSafePathId, resolveContainedRelativePath, resolveRealContainedPath } from "../utils/safe-paths.ts";
|
|
8
9
|
import { assertRunBundle } from "./run-bundle-schema.ts";
|
|
@@ -116,8 +117,8 @@ export function importRunBundle(cwd: string, bundlePath: string, scope: "project
|
|
|
116
117
|
for (const target of [targetJson, targetSummary]) {
|
|
117
118
|
if (fs.existsSync(target) && fs.lstatSync(target).isSymbolicLink()) throw new Error(`Invalid import target: ${target}`);
|
|
118
119
|
}
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
atomicWriteFile(targetJson, `${JSON.stringify({ ...raw, importedAt, importedFrom: resolvedPath }, null, 2)}\n`);
|
|
121
|
+
atomicWriteFile(
|
|
121
122
|
targetSummary,
|
|
122
123
|
[
|
|
123
124
|
`# Imported pi-crew run ${runId}`,
|
|
@@ -136,7 +137,6 @@ export function importRunBundle(cwd: string, bundlePath: string, scope: "project
|
|
|
136
137
|
),
|
|
137
138
|
"",
|
|
138
139
|
].join("\n"),
|
|
139
|
-
"utf-8",
|
|
140
140
|
);
|
|
141
141
|
return {
|
|
142
142
|
runId,
|
|
@@ -25,14 +25,14 @@ export function handleAutoSummarizeOn(params: TeamToolParamsValue, ctx: TeamCont
|
|
|
25
25
|
// Apply config updates if provided
|
|
26
26
|
if (cfg.threshold !== undefined) {
|
|
27
27
|
const threshold = typeof cfg.threshold === "number" ? cfg.threshold : parseInt(String(cfg.threshold), 10);
|
|
28
|
-
if (!isNaN(threshold) && threshold >= 0) {
|
|
28
|
+
if (!Number.isNaN(threshold) && threshold >= 0) {
|
|
29
29
|
service.setThreshold(threshold);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
if (cfg.minTools !== undefined) {
|
|
34
34
|
const minTools = typeof cfg.minTools === "number" ? cfg.minTools : parseInt(String(cfg.minTools), 10);
|
|
35
|
-
if (!isNaN(minTools) && minTools >= 0) {
|
|
35
|
+
if (!Number.isNaN(minTools) && minTools >= 0) {
|
|
36
36
|
service.setMinToolsUsed(minTools);
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -103,14 +103,14 @@ export function handleAutoSummarizeConfig(params: TeamToolParamsValue, ctx: Team
|
|
|
103
103
|
|
|
104
104
|
if (cfg.threshold !== undefined) {
|
|
105
105
|
const threshold = typeof cfg.threshold === "number" ? cfg.threshold : parseInt(String(cfg.threshold), 10);
|
|
106
|
-
if (!isNaN(threshold) && threshold >= 0) {
|
|
106
|
+
if (!Number.isNaN(threshold) && threshold >= 0) {
|
|
107
107
|
updates.threshold = threshold;
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
if (cfg.minTools !== undefined) {
|
|
112
112
|
const minTools = typeof cfg.minTools === "number" ? cfg.minTools : parseInt(String(cfg.minTools), 10);
|
|
113
|
-
if (!isNaN(minTools) && minTools >= 0) {
|
|
113
|
+
if (!Number.isNaN(minTools) && minTools >= 0) {
|
|
114
114
|
updates.minTools = minTools;
|
|
115
115
|
}
|
|
116
116
|
}
|
|
@@ -56,7 +56,7 @@ export function abortOwned(runId: string, taskIds: string[] | undefined, ctx: Te
|
|
|
56
56
|
continue;
|
|
57
57
|
}
|
|
58
58
|
if (task.status !== "queued" && task.status !== "running" && task.status !== "waiting") continue;
|
|
59
|
-
if (foreignRun &&
|
|
59
|
+
if (foreignRun && force !== true) {
|
|
60
60
|
result.foreignIds.push(id);
|
|
61
61
|
continue;
|
|
62
62
|
}
|
|
@@ -92,7 +92,7 @@ export async function handleRetry(params: TeamToolParamsValue, ctx: TeamContext,
|
|
|
92
92
|
|
|
93
93
|
// Pre-lock ownership check: reject foreign-owned runs unless force is set
|
|
94
94
|
const foreignRun = typeof loaded.manifest.ownerSessionId === "string" && loaded.manifest.ownerSessionId !== ctx.sessionId;
|
|
95
|
-
if (foreignRun &&
|
|
95
|
+
if (foreignRun && params.force !== true) {
|
|
96
96
|
return result(
|
|
97
97
|
`Run ${loaded.manifest.runId} belongs to another session. Use force: true to override.`,
|
|
98
98
|
{ action: "retry", status: "error", runId: loaded.manifest.runId },
|
|
@@ -184,7 +184,7 @@ export async function handleCancel(params: TeamToolParamsValue, ctx: TeamContext
|
|
|
184
184
|
|
|
185
185
|
// Pre-lock ownership check: reject foreign-owned runs unless force is set
|
|
186
186
|
const preCheck = abortOwned(loaded.manifest.runId, undefined, ctx, params.force);
|
|
187
|
-
if (preCheck.abortedIds.length === 0 && preCheck.foreignIds.length > 0 &&
|
|
187
|
+
if (preCheck.abortedIds.length === 0 && preCheck.foreignIds.length > 0 && params.force !== true) {
|
|
188
188
|
return result(
|
|
189
189
|
`Run ${loaded.manifest.runId} belongs to another session. Use force: true to override.`,
|
|
190
190
|
{
|
|
@@ -230,7 +230,7 @@ export async function handleCancel(params: TeamToolParamsValue, ctx: TeamContext
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
return withRunLockSync(loaded.manifest, () => {
|
|
233
|
-
if ((loaded.manifest.status === "completed" || loaded.manifest.status === "cancelled") &&
|
|
233
|
+
if ((loaded.manifest.status === "completed" || loaded.manifest.status === "cancelled") && params.force !== true)
|
|
234
234
|
return result(
|
|
235
235
|
`Run ${loaded.manifest.runId} is already ${loaded.manifest.status}; nothing to cancel. Use force: true to mark it cancelled anyway.`,
|
|
236
236
|
{
|
|
@@ -243,7 +243,7 @@ export async function handleCancel(params: TeamToolParamsValue, ctx: TeamContext
|
|
|
243
243
|
|
|
244
244
|
// Classify tasks for foreign-aware cancellation
|
|
245
245
|
const abortResult = abortOwned(loaded.manifest.runId, undefined, ctx, params.force);
|
|
246
|
-
if (abortResult.abortedIds.length === 0 && abortResult.foreignIds.length > 0 &&
|
|
246
|
+
if (abortResult.abortedIds.length === 0 && abortResult.foreignIds.length > 0 && params.force !== true) {
|
|
247
247
|
return result(
|
|
248
248
|
`Run ${loaded.manifest.runId} belongs to another session. Use force: true to override.`,
|
|
249
249
|
{
|
|
@@ -30,6 +30,7 @@ import type { ChainTaskRunner } from "../../runtime/chain-runner.ts";
|
|
|
30
30
|
import type { Decision, TaskPacket, TaskResult } from "../../runtime/handoff-manager.ts";
|
|
31
31
|
import { readIfSmallWithTee } from "../../runtime/task-output-context.ts";
|
|
32
32
|
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
33
|
+
import { atomicWriteFile } from "../../state/atomic-write.ts";
|
|
33
34
|
import { createRunPaths, loadRunManifestById } from "../../state/state-store.ts";
|
|
34
35
|
import type { TeamRunManifest, TeamTaskState } from "../../state/types.ts";
|
|
35
36
|
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
@@ -361,7 +362,7 @@ export function writeRunFixture(
|
|
|
361
362
|
if (opts.resultText) {
|
|
362
363
|
const resultDir = path.join(paths.artifactsRoot, "results");
|
|
363
364
|
fs.mkdirSync(resultDir, { recursive: true });
|
|
364
|
-
|
|
365
|
+
atomicWriteFile(path.join(resultDir, `${taskId}.txt`), opts.resultText);
|
|
365
366
|
manifest.artifacts.push({
|
|
366
367
|
kind: "result",
|
|
367
368
|
path: `results/${taskId}.txt`,
|
|
@@ -371,8 +372,8 @@ export function writeRunFixture(
|
|
|
371
372
|
});
|
|
372
373
|
}
|
|
373
374
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
375
|
+
atomicWriteFile(paths.manifestPath, JSON.stringify(manifest));
|
|
376
|
+
atomicWriteFile(paths.tasksPath, JSON.stringify(tasks ?? []));
|
|
377
|
+
atomicWriteFile(paths.eventsPath, "");
|
|
377
378
|
return manifest;
|
|
378
379
|
}
|
|
@@ -135,5 +135,15 @@ export function buildParentContext(ctx: TeamContext): string | undefined {
|
|
|
135
135
|
|
|
136
136
|
export function configRecord(config: unknown): Record<string, unknown> {
|
|
137
137
|
if (!config || typeof config !== "object" || Array.isArray(config)) return {};
|
|
138
|
-
|
|
138
|
+
// AUDIT-07 defense-in-depth: filter prototype-pollution keys. Callers (api, doctor,
|
|
139
|
+
// lifecycle-actions, intent-policy, team-tool) pass `params.config` from the team-tool
|
|
140
|
+
// invocation which is NOT sanitized by the same loadConfig/sanitizeObject path.
|
|
141
|
+
const obj = config as Record<string, unknown>;
|
|
142
|
+
const DANGEROUS_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
143
|
+
const safe: Record<string, unknown> = {};
|
|
144
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
145
|
+
if (DANGEROUS_KEYS.has(k)) continue;
|
|
146
|
+
safe[k] = v;
|
|
147
|
+
}
|
|
148
|
+
return safe;
|
|
139
149
|
}
|
|
@@ -10,6 +10,7 @@ import { getRuntimeWarmupStatus } from "../../runtime/runtime-warmup.ts";
|
|
|
10
10
|
import { formatZombieReport, scanZombieSubagents } from "../../runtime/zombie-scanner.ts";
|
|
11
11
|
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
12
12
|
import { TeamToolParams } from "../../schema/team-tool-schema.ts";
|
|
13
|
+
import { atomicWriteFile } from "../../state/atomic-write.ts";
|
|
13
14
|
import { allTeams, discoverTeams } from "../../teams/discover-teams.ts";
|
|
14
15
|
import { projectCrewRoot, userCrewRoot } from "../../utils/paths.ts";
|
|
15
16
|
import { allWorkflows, discoverWorkflows } from "../../workflows/discover-workflows.ts";
|
|
@@ -98,7 +99,7 @@ function checkWritableDir(dir: string): { ok: boolean; detail: string } {
|
|
|
98
99
|
// fs.accessSync(W_OK) is unreliable on Windows; verify by writing a temp file.
|
|
99
100
|
const probePath = `${dir}/.pi-crew-write-test`;
|
|
100
101
|
try {
|
|
101
|
-
|
|
102
|
+
atomicWriteFile(probePath, "ok");
|
|
102
103
|
fs.rmSync(probePath, { force: true });
|
|
103
104
|
} catch {
|
|
104
105
|
return {
|
|
@@ -152,7 +152,7 @@ export function shouldGoalWrap(
|
|
|
152
152
|
message?: string;
|
|
153
153
|
} {
|
|
154
154
|
const wc = readGoalWrapConfig(cwd, workflow.name);
|
|
155
|
-
if (
|
|
155
|
+
if (wc?.enabled !== true) {
|
|
156
156
|
return { enabled: false, reason: "config-off" };
|
|
157
157
|
}
|
|
158
158
|
const validationError = validateGoalWrapConfig(wc);
|
|
@@ -181,7 +181,7 @@ export async function startGoalWrappedRun(
|
|
|
181
181
|
): Promise<ReturnType<typeof result>> {
|
|
182
182
|
const cwd = ctx.cwd;
|
|
183
183
|
const wc = readGoalWrapConfig(cwd, workflow.name);
|
|
184
|
-
if (
|
|
184
|
+
if (wc?.enabled !== true) {
|
|
185
185
|
return result(
|
|
186
186
|
`goal-wrap is not enabled for workflow '${workflow.name}' in .crew/config.json.`,
|
|
187
187
|
{ action: "run", status: "error" },
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import { GoalStore } from "../../runtime/goal-state-store.ts";
|
|
18
18
|
import { snapshotManifests } from "../../runtime/verification-integrity.ts";
|
|
19
|
-
import {
|
|
19
|
+
import { isWorkspaceBusy } from "../../runtime/workspace-lock.ts";
|
|
20
20
|
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
21
21
|
import { appendEvent } from "../../state/event-log.ts";
|
|
22
22
|
import { createRunPaths, saveRunManifestAsync } from "../../state/state-store.ts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as crypto from "node:crypto";
|
|
2
2
|
import { humanizeSchedule, nextRunTime, parseSchedule } from "../../runtime/scheduler.ts";
|
|
3
|
-
import {
|
|
3
|
+
import { type CrewSettings, updateCrewSettings } from "../../runtime/settings-store.ts";
|
|
4
4
|
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
5
5
|
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
6
6
|
import { result, type TeamContext } from "./context.ts";
|
|
@@ -50,7 +50,14 @@ function buildScheduleSpec(params: ScheduleParams): {
|
|
|
50
50
|
scheduleType: "cron" as const,
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
-
if (params.interval !== undefined && params.interval
|
|
53
|
+
if (params.interval !== undefined && (!Number.isFinite(params.interval) || params.interval <= 0)) {
|
|
54
|
+
throw new Error("interval must be a positive finite number");
|
|
55
|
+
}
|
|
56
|
+
if (params.once !== undefined) {
|
|
57
|
+
const ts = typeof params.once === "number" ? params.once : Date.parse(String(params.once));
|
|
58
|
+
if (!Number.isFinite(ts)) throw new Error("once must be a valid timestamp");
|
|
59
|
+
}
|
|
60
|
+
if (params.interval !== undefined) {
|
|
54
61
|
const specStr = `${params.interval}ms`;
|
|
55
62
|
const spec = parseSchedule(specStr);
|
|
56
63
|
if ("error" in spec) throw new Error(spec.error);
|
|
@@ -183,21 +190,16 @@ export function handleSchedule(params: TeamToolParamsValue, ctx: TeamContext): P
|
|
|
183
190
|
);
|
|
184
191
|
}
|
|
185
192
|
|
|
193
|
+
function scheduledJobsOf(settings: CrewSettings): import("../../runtime/scheduler.ts").ScheduledJob[] {
|
|
194
|
+
return Array.isArray(settings.scheduledJobs) ? (settings.scheduledJobs as import("../../runtime/scheduler.ts").ScheduledJob[]) : [];
|
|
195
|
+
}
|
|
196
|
+
|
|
186
197
|
function persistScheduledJob(cwd: string, job: import("../../runtime/scheduler.ts").ScheduledJob): void {
|
|
187
198
|
try {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
(settings
|
|
191
|
-
)
|
|
192
|
-
? ((settings as Record<string, unknown>).scheduledJobs as import("../../runtime/scheduler.ts").ScheduledJob[])
|
|
193
|
-
: [];
|
|
194
|
-
saveCrewSettings(
|
|
195
|
-
{
|
|
196
|
-
...settings,
|
|
197
|
-
scheduledJobs: [...existingJobs, job],
|
|
198
|
-
} as Parameters<typeof saveCrewSettings>[0],
|
|
199
|
-
cwd,
|
|
200
|
-
);
|
|
199
|
+
updateCrewSettings(cwd, (settings) => ({
|
|
200
|
+
...settings,
|
|
201
|
+
scheduledJobs: [...scheduledJobsOf(settings), job],
|
|
202
|
+
}));
|
|
201
203
|
} catch {
|
|
202
204
|
/* best-effort persistence */
|
|
203
205
|
}
|
|
@@ -206,14 +208,10 @@ function persistScheduledJob(cwd: string, job: import("../../runtime/scheduler.t
|
|
|
206
208
|
/** Update an existing scheduled job in persistent settings. */
|
|
207
209
|
export function persistScheduledJobUpdate(cwd: string, job: import("../../runtime/scheduler.ts").ScheduledJob): void {
|
|
208
210
|
try {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
(settings
|
|
212
|
-
)
|
|
213
|
-
? ((settings as Record<string, unknown>).scheduledJobs as import("../../runtime/scheduler.ts").ScheduledJob[])
|
|
214
|
-
: [];
|
|
215
|
-
const updated = existingJobs.map((j) => (j.id === job.id ? job : j));
|
|
216
|
-
saveCrewSettings({ ...settings, scheduledJobs: updated } as Parameters<typeof saveCrewSettings>[0], cwd);
|
|
211
|
+
updateCrewSettings(cwd, (settings) => ({
|
|
212
|
+
...settings,
|
|
213
|
+
scheduledJobs: scheduledJobsOf(settings).map((j) => (j.id === job.id ? job : j)),
|
|
214
|
+
}));
|
|
217
215
|
} catch {
|
|
218
216
|
/* best-effort persistence */
|
|
219
217
|
}
|
|
@@ -222,19 +220,10 @@ export function persistScheduledJobUpdate(cwd: string, job: import("../../runtim
|
|
|
222
220
|
/** Remove a scheduled job from persistent settings. */
|
|
223
221
|
function persistScheduledJobRemove(cwd: string, jobId: string): void {
|
|
224
222
|
try {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
(settings
|
|
228
|
-
)
|
|
229
|
-
? ((settings as Record<string, unknown>).scheduledJobs as import("../../runtime/scheduler.ts").ScheduledJob[])
|
|
230
|
-
: [];
|
|
231
|
-
saveCrewSettings(
|
|
232
|
-
{
|
|
233
|
-
...settings,
|
|
234
|
-
scheduledJobs: existingJobs.filter((j) => j.id !== jobId),
|
|
235
|
-
} as Parameters<typeof saveCrewSettings>[0],
|
|
236
|
-
cwd,
|
|
237
|
-
);
|
|
223
|
+
updateCrewSettings(cwd, (settings) => ({
|
|
224
|
+
...settings,
|
|
225
|
+
scheduledJobs: scheduledJobsOf(settings).filter((j) => j.id !== jobId),
|
|
226
|
+
}));
|
|
238
227
|
} catch {
|
|
239
228
|
/* best-effort persistence */
|
|
240
229
|
}
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* Parses a plan document and outputs agent chain commands.
|
|
5
5
|
*/
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
|
-
import * as path from "node:path";
|
|
8
7
|
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
9
8
|
import { resolveContainedPath } from "../../utils/safe-paths.ts";
|
|
10
9
|
import {
|
|
@@ -149,6 +149,11 @@ async function spawnSingleTask(
|
|
|
149
149
|
team,
|
|
150
150
|
workflow,
|
|
151
151
|
goal,
|
|
152
|
+
// EXT F1: mirror handleRun — without ownerSessionId, the foreign-run check in
|
|
153
|
+
// cancel/retry/resume (cancel.ts:50: `typeof ownerSessionId === "string"`) is
|
|
154
|
+
// skipped and any session can cancel/retry parallel-dispatched runs.
|
|
155
|
+
ownerSessionId: ctx.sessionId,
|
|
156
|
+
runKind: "team-run",
|
|
152
157
|
});
|
|
153
158
|
|
|
154
159
|
appendEvent(created.manifest.eventsPath, {
|