pi-crew 0.1.46 → 0.1.49
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 +97 -0
- package/agents/analyst.md +11 -11
- package/agents/critic.md +11 -11
- package/agents/executor.md +11 -11
- package/agents/explorer.md +11 -11
- package/agents/planner.md +11 -11
- package/agents/reviewer.md +11 -11
- package/agents/security-reviewer.md +11 -11
- package/agents/test-engineer.md +11 -11
- package/agents/verifier.md +11 -11
- package/agents/writer.md +11 -11
- package/docs/next-upgrade-roadmap.md +117 -42
- package/docs/refactor-tasks-phase3.md +394 -394
- package/docs/refactor-tasks-phase4.md +564 -564
- package/docs/refactor-tasks-phase5.md +402 -402
- package/docs/refactor-tasks-phase6.md +662 -662
- package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +261 -0
- package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +111 -0
- package/docs/research/AUDIT_OH_MY_PI.md +261 -0
- package/docs/research/AUDIT_PI_CREW.md +457 -0
- package/docs/research/CAVEMAN-DEEP-RESEARCH.md +281 -0
- package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +264 -0
- package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +343 -0
- package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +480 -0
- package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +354 -0
- package/docs/research/IMPLEMENTATION_PLAN.md +385 -0
- package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +502 -0
- package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +266 -0
- package/docs/research/REMAINING-GAPS-PLAN.md +363 -0
- package/docs/research/SESSION-SUMMARY-2026-05-08.md +146 -0
- package/docs/research/UI-RESPONSIVENESS-AUDIT.md +173 -0
- package/docs/research-awesome-agent-skills-distillation.md +100 -100
- package/docs/research-extension-examples.md +297 -297
- package/docs/research-extension-system.md +324 -324
- package/docs/research-oh-my-pi-distillation.md +56 -9
- package/docs/research-optimization-plan.md +548 -548
- package/docs/research-phase10-distillation.md +198 -198
- package/docs/research-phase11-distillation.md +201 -201
- package/docs/research-pi-coding-agent.md +357 -357
- package/docs/research-source-pi-crew-reference.md +174 -174
- package/docs/runtime-flow.md +148 -148
- package/docs/source-runtime-refactor-map.md +107 -107
- package/index.ts +6 -6
- package/package.json +99 -98
- package/schema.json +8 -0
- package/skills/async-worker-recovery/SKILL.md +42 -42
- package/skills/context-artifact-hygiene/SKILL.md +52 -52
- package/skills/delegation-patterns/SKILL.md +54 -54
- package/skills/mailbox-interactive/SKILL.md +40 -40
- package/skills/model-routing-context/SKILL.md +39 -39
- package/skills/multi-perspective-review/SKILL.md +58 -58
- package/skills/observability-reliability/SKILL.md +41 -41
- package/skills/orchestration/SKILL.md +157 -0
- package/skills/ownership-session-security/SKILL.md +41 -41
- package/skills/pi-extension-lifecycle/SKILL.md +39 -39
- package/skills/requirements-to-task-packet/SKILL.md +63 -63
- package/skills/resource-discovery-config/SKILL.md +41 -41
- package/skills/runtime-state-reader/SKILL.md +44 -44
- package/skills/secure-agent-orchestration-review/SKILL.md +45 -45
- package/skills/state-mutation-locking/SKILL.md +42 -42
- package/skills/systematic-debugging/SKILL.md +67 -67
- package/skills/ui-render-performance/SKILL.md +39 -39
- package/skills/verification-before-done/SKILL.md +57 -57
- package/skills/worktree-isolation/SKILL.md +39 -39
- package/src/agents/agent-config.ts +6 -0
- package/src/agents/agent-search.ts +98 -0
- package/src/agents/agent-serializer.ts +4 -0
- package/src/agents/discover-agents.ts +17 -4
- package/src/config/config.ts +24 -0
- package/src/config/defaults.ts +11 -0
- package/src/extension/autonomous-policy.ts +26 -33
- package/src/extension/cross-extension-rpc.ts +82 -82
- package/src/extension/help.ts +1 -0
- package/src/extension/management.ts +5 -0
- package/src/extension/register.ts +58 -13
- package/src/extension/registration/commands.ts +33 -1
- package/src/extension/registration/compaction-guard.ts +125 -125
- package/src/extension/registration/team-tool.ts +6 -4
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-index.ts +24 -18
- package/src/extension/run-maintenance.ts +68 -62
- package/src/extension/team-tool/api.ts +23 -2
- package/src/extension/team-tool/cancel.ts +86 -11
- package/src/extension/team-tool/context.ts +3 -0
- package/src/extension/team-tool/handle-settings.ts +188 -188
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/intent-policy.ts +42 -0
- package/src/extension/team-tool/lifecycle-actions.ts +47 -18
- package/src/extension/team-tool/parallel-dispatch.ts +156 -0
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/respond.ts +10 -2
- package/src/extension/team-tool/run.ts +3 -2
- package/src/extension/team-tool/status.ts +1 -1
- package/src/extension/team-tool-types.ts +1 -0
- package/src/extension/team-tool.ts +13 -3
- package/src/hooks/registry.ts +61 -0
- package/src/hooks/types.ts +41 -0
- package/src/i18n.ts +184 -184
- package/src/observability/exporters/otlp-exporter.ts +77 -77
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/agent-control.ts +108 -2
- package/src/runtime/agent-memory.ts +72 -72
- package/src/runtime/agent-observability.ts +114 -114
- package/src/runtime/async-marker.ts +26 -26
- package/src/runtime/async-runner.ts +3 -1
- package/src/runtime/attention-events.ts +28 -28
- package/src/runtime/background-runner.ts +19 -0
- package/src/runtime/cancellation-token.ts +89 -0
- package/src/runtime/cancellation.ts +61 -51
- package/src/runtime/capability-inventory.ts +116 -0
- package/src/runtime/child-pi.ts +2 -1
- package/src/runtime/code-summary.ts +247 -0
- package/src/runtime/completion-guard.ts +190 -190
- package/src/runtime/crash-recovery.ts +181 -0
- package/src/runtime/crew-agent-records.ts +35 -7
- package/src/runtime/crew-agent-runtime.ts +1 -0
- package/src/runtime/custom-tools/irc-tool.ts +201 -0
- package/src/runtime/custom-tools/submit-result-tool.ts +90 -0
- package/src/runtime/delivery-coordinator.ts +3 -1
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/effectiveness.ts +81 -76
- package/src/runtime/event-stream-bridge.ts +90 -0
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +106 -106
- package/src/runtime/heartbeat-gradient.ts +28 -28
- package/src/runtime/heartbeat-watcher.ts +124 -124
- package/src/runtime/live-agent-control.ts +88 -88
- package/src/runtime/live-agent-manager.ts +78 -2
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-extension-bridge.ts +150 -0
- package/src/runtime/live-irc.ts +92 -0
- package/src/runtime/live-session-health.ts +100 -0
- package/src/runtime/live-session-runtime.ts +297 -7
- package/src/runtime/mcp-proxy.ts +113 -0
- package/src/runtime/notebook-helpers.ts +90 -0
- package/src/runtime/orphan-sentinel.ts +7 -0
- package/src/runtime/output-validator.ts +187 -0
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/parallel-utils.ts +57 -0
- package/src/runtime/parent-guard.ts +80 -0
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/prose-compressor.ts +164 -0
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/result-extractor.ts +121 -0
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/runtime-resolver.ts +1 -4
- package/src/runtime/semaphore.ts +131 -0
- package/src/runtime/sensitive-paths.ts +92 -0
- package/src/runtime/session-resources.ts +25 -25
- package/src/runtime/session-snapshot.ts +59 -59
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/sidechain-output.ts +29 -29
- package/src/runtime/stream-preview.ts +177 -0
- package/src/runtime/subagent-manager.ts +3 -2
- package/src/runtime/subprocess-tool-registry.ts +67 -0
- package/src/runtime/supervisor-contact.ts +59 -59
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-output-context.ts +59 -9
- package/src/runtime/task-runner/capabilities.ts +78 -78
- package/src/runtime/task-runner/live-executor.ts +2 -0
- package/src/runtime/task-runner/progress.ts +119 -119
- package/src/runtime/task-runner/prompt-builder.ts +70 -8
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/run-projection.ts +104 -0
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/task-runner.ts +75 -4
- package/src/runtime/team-runner.ts +60 -8
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/runtime/workspace-tree.ts +298 -0
- package/src/runtime/yield-handler.ts +189 -0
- package/src/schema/config-schema.ts +6 -0
- package/src/schema/team-tool-schema.ts +11 -1
- package/src/skills/discover-skills.ts +67 -0
- package/src/state/active-run-registry.ts +4 -2
- package/src/state/artifact-store.ts +4 -1
- package/src/state/atomic-write.ts +50 -1
- package/src/state/blob-store.ts +117 -0
- package/src/state/contracts.ts +1 -0
- package/src/state/event-log-rotation.ts +158 -0
- package/src/state/event-log.ts +52 -2
- package/src/state/mailbox.ts +87 -7
- package/src/state/state-store.ts +24 -4
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +20 -0
- package/src/state/usage.ts +29 -29
- package/src/subagents/async-entry.ts +1 -1
- package/src/subagents/index.ts +3 -3
- package/src/subagents/live/control.ts +1 -1
- package/src/subagents/live/manager.ts +1 -1
- package/src/subagents/live/realtime.ts +1 -1
- package/src/subagents/live/session-runtime.ts +1 -1
- package/src/subagents/manager.ts +1 -1
- package/src/subagents/spawn.ts +1 -1
- package/src/teams/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/agent-management-overlay.ts +144 -0
- package/src/ui/crew-footer.ts +101 -101
- package/src/ui/crew-select-list.ts +111 -111
- package/src/ui/crew-widget.ts +11 -2
- package/src/ui/dashboard-panes/cancellation-pane.ts +43 -0
- package/src/ui/dashboard-panes/capability-pane.ts +60 -0
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -11
- package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
- package/src/ui/dynamic-border.ts +25 -25
- package/src/ui/layout-primitives.ts +106 -106
- package/src/ui/live-run-sidebar.ts +4 -0
- package/src/ui/loaders.ts +158 -158
- package/src/ui/powerbar-publisher.ts +77 -15
- package/src/ui/render-coalescer.ts +51 -0
- package/src/ui/render-diff.ts +119 -119
- package/src/ui/render-scheduler.ts +143 -143
- package/src/ui/run-dashboard.ts +4 -0
- package/src/ui/run-event-bus.ts +209 -0
- package/src/ui/run-snapshot-cache.ts +68 -16
- package/src/ui/snapshot-types.ts +8 -0
- package/src/ui/spinner.ts +17 -17
- package/src/ui/status-colors.ts +58 -58
- package/src/ui/syntax-highlight.ts +116 -116
- package/src/ui/transcript-entries.ts +258 -0
- package/src/utils/atomic-write.ts +33 -33
- package/src/utils/completion-dedupe.ts +63 -63
- package/src/utils/frontmatter.ts +68 -68
- package/src/utils/git.ts +262 -262
- package/src/utils/ids.ts +17 -12
- package/src/utils/incremental-reader.ts +104 -0
- package/src/utils/names.ts +27 -27
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/scan-cache.ts +137 -0
- package/src/utils/sleep.ts +32 -32
- package/src/utils/sse-parser.ts +134 -0
- package/src/utils/task-name-generator.ts +337 -0
- package/src/utils/visual.ts +33 -2
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/worktree/branch-freshness.ts +45 -45
- package/src/worktree/cleanup.ts +2 -1
- package/teams/default.team.md +12 -12
- package/teams/fast-fix.team.md +11 -11
- package/teams/implementation.team.md +18 -18
- package/teams/parallel-research.team.md +14 -14
- package/teams/research.team.md +11 -11
- package/teams/review.team.md +12 -12
- package/workflows/default.workflow.md +29 -29
- package/workflows/fast-fix.workflow.md +22 -22
- package/workflows/implementation.workflow.md +38 -38
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { atomicWriteJson } from "../state/atomic-write.ts";
|
|
4
|
-
import type { TeamRunManifest } from "../state/types.ts";
|
|
5
|
-
|
|
6
|
-
export interface AsyncStartMarker {
|
|
7
|
-
pid: number;
|
|
8
|
-
startedAt: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function asyncStartMarkerPath(manifest: Pick<TeamRunManifest, "stateRoot">): string {
|
|
12
|
-
return path.join(manifest.stateRoot, "async.pid");
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function writeAsyncStartMarker(manifest: Pick<TeamRunManifest, "stateRoot">, marker: AsyncStartMarker): void {
|
|
16
|
-
atomicWriteJson(asyncStartMarkerPath(manifest), marker);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function hasAsyncStartMarker(manifest: Pick<TeamRunManifest, "stateRoot">): boolean {
|
|
20
|
-
try {
|
|
21
|
-
const raw = JSON.parse(fs.readFileSync(asyncStartMarkerPath(manifest), "utf-8")) as Partial<AsyncStartMarker>;
|
|
22
|
-
return typeof raw.pid === "number" && Number.isInteger(raw.pid) && raw.pid > 0 && typeof raw.startedAt === "string" && raw.startedAt.length > 0;
|
|
23
|
-
} catch {
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { atomicWriteJson } from "../state/atomic-write.ts";
|
|
4
|
+
import type { TeamRunManifest } from "../state/types.ts";
|
|
5
|
+
|
|
6
|
+
export interface AsyncStartMarker {
|
|
7
|
+
pid: number;
|
|
8
|
+
startedAt: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function asyncStartMarkerPath(manifest: Pick<TeamRunManifest, "stateRoot">): string {
|
|
12
|
+
return path.join(manifest.stateRoot, "async.pid");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function writeAsyncStartMarker(manifest: Pick<TeamRunManifest, "stateRoot">, marker: AsyncStartMarker): void {
|
|
16
|
+
atomicWriteJson(asyncStartMarkerPath(manifest), marker);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function hasAsyncStartMarker(manifest: Pick<TeamRunManifest, "stateRoot">): boolean {
|
|
20
|
+
try {
|
|
21
|
+
const raw = JSON.parse(fs.readFileSync(asyncStartMarkerPath(manifest), "utf-8")) as Partial<AsyncStartMarker>;
|
|
22
|
+
return typeof raw.pid === "number" && Number.isInteger(raw.pid) && raw.pid > 0 && typeof raw.startedAt === "string" && raw.startedAt.length > 0;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -6,6 +6,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
6
6
|
import { appendEvent } from "../state/event-log.ts";
|
|
7
7
|
import type { TeamRunManifest } from "../state/types.ts";
|
|
8
8
|
|
|
9
|
+
|
|
9
10
|
export type FileExists = (filePath: string) => boolean;
|
|
10
11
|
|
|
11
12
|
const requireFromHere = createRequire(import.meta.url);
|
|
@@ -49,7 +50,7 @@ export function buildBackgroundSpawnOptions(manifest: TeamRunManifest, logFd: nu
|
|
|
49
50
|
cwd: manifest.cwd,
|
|
50
51
|
detached: true,
|
|
51
52
|
stdio: ["ignore", logFd, logFd],
|
|
52
|
-
env: { ...process.env },
|
|
53
|
+
env: { ...process.env, PI_CREW_PARENT_PID: String(process.pid) },
|
|
53
54
|
windowsHide: true,
|
|
54
55
|
};
|
|
55
56
|
}
|
|
@@ -70,6 +71,7 @@ export function spawnBackgroundTeamRun(manifest: TeamRunManifest): SpawnBackgrou
|
|
|
70
71
|
fs.appendFileSync(logPath, `[pi-crew] background loader=${command.loader}\n`, "utf-8");
|
|
71
72
|
const child = spawn(process.execPath, command.args, buildBackgroundSpawnOptions(manifest, logFd));
|
|
72
73
|
child.unref();
|
|
74
|
+
|
|
73
75
|
return { pid: child.pid, logPath };
|
|
74
76
|
} finally {
|
|
75
77
|
fs.closeSync(logFd);
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import { appendEvent, readEvents } from "../state/event-log.ts";
|
|
2
|
-
import type { CrewAttentionEventData, TeamRunManifest } from "../state/types.ts";
|
|
3
|
-
|
|
4
|
-
export interface AppendTaskAttentionInput {
|
|
5
|
-
manifest: TeamRunManifest;
|
|
6
|
-
taskId?: string;
|
|
7
|
-
message: string;
|
|
8
|
-
data: CrewAttentionEventData;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function appendTaskAttentionEvent(input: AppendTaskAttentionInput): boolean {
|
|
12
|
-
const recent = readEvents(input.manifest.eventsPath).slice(-200);
|
|
13
|
-
const dedupKey = `${input.taskId ?? ""}:${input.data.reason}:${input.data.activityState}`;
|
|
14
|
-
const duplicate = recent.some(
|
|
15
|
-
(event) =>
|
|
16
|
-
event.type === "task.attention" &&
|
|
17
|
-
`${event.taskId ?? ""}:${event.data?.reason ?? ""}:${event.data?.activityState ?? ""}` === dedupKey,
|
|
18
|
-
);
|
|
19
|
-
if (duplicate) return false;
|
|
20
|
-
appendEvent(input.manifest.eventsPath, {
|
|
21
|
-
type: "task.attention",
|
|
22
|
-
runId: input.manifest.runId,
|
|
23
|
-
taskId: input.taskId,
|
|
24
|
-
message: input.message,
|
|
25
|
-
data: { ...input.data },
|
|
26
|
-
});
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
1
|
+
import { appendEvent, readEvents } from "../state/event-log.ts";
|
|
2
|
+
import type { CrewAttentionEventData, TeamRunManifest } from "../state/types.ts";
|
|
3
|
+
|
|
4
|
+
export interface AppendTaskAttentionInput {
|
|
5
|
+
manifest: TeamRunManifest;
|
|
6
|
+
taskId?: string;
|
|
7
|
+
message: string;
|
|
8
|
+
data: CrewAttentionEventData;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function appendTaskAttentionEvent(input: AppendTaskAttentionInput): boolean {
|
|
12
|
+
const recent = readEvents(input.manifest.eventsPath).slice(-200);
|
|
13
|
+
const dedupKey = `${input.taskId ?? ""}:${input.data.reason}:${input.data.activityState}`;
|
|
14
|
+
const duplicate = recent.some(
|
|
15
|
+
(event) =>
|
|
16
|
+
event.type === "task.attention" &&
|
|
17
|
+
`${event.taskId ?? ""}:${event.data?.reason ?? ""}:${event.data?.activityState ?? ""}` === dedupKey,
|
|
18
|
+
);
|
|
19
|
+
if (duplicate) return false;
|
|
20
|
+
appendEvent(input.manifest.eventsPath, {
|
|
21
|
+
type: "task.attention",
|
|
22
|
+
runId: input.manifest.runId,
|
|
23
|
+
taskId: input.taskId,
|
|
24
|
+
message: input.message,
|
|
25
|
+
data: { ...input.data },
|
|
26
|
+
});
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
@@ -9,6 +9,16 @@ import { resolveCrewRuntime, runtimeResolutionState } from "./runtime-resolver.t
|
|
|
9
9
|
import { directTeamAndWorkflowFromRun } from "./direct-run.ts";
|
|
10
10
|
import { expandParallelResearchWorkflow } from "./parallel-research.ts";
|
|
11
11
|
import { writeAsyncStartMarker } from "./async-marker.ts";
|
|
12
|
+
import { startParentGuard, stopParentGuard } from "./parent-guard.ts";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Remove macOS malloc-stack-logging vars that get inherited by child shells.
|
|
16
|
+
* Without this, every subprocess prints "MallocStackLogging: can't turn off..." to stderr.
|
|
17
|
+
*/
|
|
18
|
+
function scrubProcessEnv(): void {
|
|
19
|
+
delete process.env.MallocStackLogging;
|
|
20
|
+
delete process.env.MallocStackLoggingNoCompact;
|
|
21
|
+
}
|
|
12
22
|
|
|
13
23
|
function argValue(name: string): string | undefined {
|
|
14
24
|
const index = process.argv.indexOf(name);
|
|
@@ -17,6 +27,13 @@ function argValue(name: string): string | undefined {
|
|
|
17
27
|
}
|
|
18
28
|
|
|
19
29
|
async function main(): Promise<void> {
|
|
30
|
+
// Scrub macOS malloc vars BEFORE anything else — must be clean for all child processes
|
|
31
|
+
scrubProcessEnv();
|
|
32
|
+
|
|
33
|
+
// Start parent guard FIRST — if parent is already dead, exit immediately
|
|
34
|
+
const parentPid = Number(process.env.PI_CREW_PARENT_PID);
|
|
35
|
+
if (parentPid > 0) startParentGuard(parentPid);
|
|
36
|
+
|
|
20
37
|
const cwd = argValue("--cwd");
|
|
21
38
|
const runId = argValue("--run-id");
|
|
22
39
|
if (!cwd || !runId) throw new Error("Usage: background-runner.ts --cwd <cwd> --run-id <runId>");
|
|
@@ -53,6 +70,8 @@ async function main(): Promise<void> {
|
|
|
53
70
|
manifest = updateRunStatus(manifest, "failed", message);
|
|
54
71
|
appendEvent(manifest.eventsPath, { type: "async.failed", runId: manifest.runId, message });
|
|
55
72
|
process.exitCode = 1;
|
|
73
|
+
} finally {
|
|
74
|
+
stopParentGuard();
|
|
56
75
|
}
|
|
57
76
|
}
|
|
58
77
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { CrewCancellationError, type CancellationReason, cancellationReasonFromUnknown } from "./cancellation.ts";
|
|
2
|
+
|
|
3
|
+
export interface CancellationTokenState {
|
|
4
|
+
aborted: boolean;
|
|
5
|
+
reason?: CancellationReason;
|
|
6
|
+
lastHeartbeatAt?: string;
|
|
7
|
+
lastHeartbeatStage?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CancellationTokenOptions {
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
onHeartbeat?: (state: CancellationTokenState) => void;
|
|
13
|
+
now?: () => Date;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class CancellationToken {
|
|
17
|
+
readonly #controller = new AbortController();
|
|
18
|
+
readonly #onHeartbeat?: (state: CancellationTokenState) => void;
|
|
19
|
+
readonly #now: () => Date;
|
|
20
|
+
#reason?: CancellationReason;
|
|
21
|
+
#lastHeartbeatAt?: string;
|
|
22
|
+
#lastHeartbeatStage?: string;
|
|
23
|
+
|
|
24
|
+
constructor(options: CancellationTokenOptions = {}) {
|
|
25
|
+
this.#onHeartbeat = options.onHeartbeat;
|
|
26
|
+
this.#now = options.now ?? (() => new Date());
|
|
27
|
+
if (options.signal?.aborted) this.abort(options.signal.reason);
|
|
28
|
+
else if (options.signal) options.signal.addEventListener("abort", () => this.abort(options.signal?.reason), { once: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get signal(): AbortSignal { return this.#controller.signal; }
|
|
32
|
+
get aborted(): boolean { return this.#controller.signal.aborted; }
|
|
33
|
+
get reason(): CancellationReason | undefined { return this.#reason; }
|
|
34
|
+
get lastHeartbeatAt(): string | undefined { return this.#lastHeartbeatAt; }
|
|
35
|
+
get lastHeartbeatStage(): string | undefined { return this.#lastHeartbeatStage; }
|
|
36
|
+
|
|
37
|
+
heartbeat(stage?: string): CancellationTokenState {
|
|
38
|
+
this.throwIfCancelled();
|
|
39
|
+
this.#lastHeartbeatAt = this.#now().toISOString();
|
|
40
|
+
this.#lastHeartbeatStage = stage;
|
|
41
|
+
const state = this.state();
|
|
42
|
+
this.#onHeartbeat?.(state);
|
|
43
|
+
return state;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
throwIfCancelled(): void {
|
|
47
|
+
if (this.aborted) throw new CrewCancellationError(this.#reason ?? cancellationReasonFromUnknown(this.#controller.signal.reason));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
abort(reason?: unknown): void {
|
|
51
|
+
if (this.aborted) return;
|
|
52
|
+
this.#reason = cancellationReasonFromUnknown(reason);
|
|
53
|
+
this.#controller.abort(this.#reason);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
wait(ms: number): Promise<void> {
|
|
57
|
+
this.throwIfCancelled();
|
|
58
|
+
if (ms <= 0) return Promise.resolve();
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
let timeout: NodeJS.Timeout | undefined;
|
|
61
|
+
const cleanup = (): void => {
|
|
62
|
+
if (timeout) clearTimeout(timeout);
|
|
63
|
+
this.signal.removeEventListener("abort", onAbort);
|
|
64
|
+
};
|
|
65
|
+
const onAbort = (): void => {
|
|
66
|
+
cleanup();
|
|
67
|
+
reject(new CrewCancellationError(this.#reason ?? cancellationReasonFromUnknown(this.signal.reason)));
|
|
68
|
+
};
|
|
69
|
+
timeout = setTimeout(() => {
|
|
70
|
+
cleanup();
|
|
71
|
+
resolve();
|
|
72
|
+
}, ms);
|
|
73
|
+
this.signal.addEventListener("abort", onAbort, { once: true });
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
state(): CancellationTokenState {
|
|
78
|
+
return {
|
|
79
|
+
aborted: this.aborted,
|
|
80
|
+
...(this.#reason ? { reason: this.#reason } : {}),
|
|
81
|
+
...(this.#lastHeartbeatAt ? { lastHeartbeatAt: this.#lastHeartbeatAt } : {}),
|
|
82
|
+
...(this.#lastHeartbeatStage ? { lastHeartbeatStage: this.#lastHeartbeatStage } : {}),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function createCancellationToken(options: CancellationTokenOptions = {}): CancellationToken {
|
|
88
|
+
return new CancellationToken(options);
|
|
89
|
+
}
|
|
@@ -1,51 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
1
|
+
import type { OperationTerminalEvidence } from "../state/types.ts";
|
|
2
|
+
|
|
3
|
+
export type CancellationReasonCode = "caller_cancelled" | "leader_interrupted" | "provider_timeout" | "worker_timeout" | "tool_timeout" | "shutdown" | "unknown";
|
|
4
|
+
|
|
5
|
+
export interface CancellationReason {
|
|
6
|
+
code: CancellationReasonCode;
|
|
7
|
+
message: string;
|
|
8
|
+
cause?: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function buildSyntheticTerminalEvidence(
|
|
12
|
+
operation: "worker" | "tool" | "model",
|
|
13
|
+
reason: CancellationReason,
|
|
14
|
+
startedAt?: string,
|
|
15
|
+
): OperationTerminalEvidence {
|
|
16
|
+
return { operation, status: "cancelled", startedAt, finishedAt: new Date().toISOString(), reason };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const KNOWN_CODES: ReadonlySet<string> = new Set(["caller_cancelled", "leader_interrupted", "provider_timeout", "worker_timeout", "tool_timeout", "shutdown", "unknown"]);
|
|
20
|
+
|
|
21
|
+
export class CrewCancellationError extends Error {
|
|
22
|
+
readonly reason: CancellationReason;
|
|
23
|
+
|
|
24
|
+
constructor(reason: CancellationReason) {
|
|
25
|
+
super(reason.message);
|
|
26
|
+
this.name = "CrewCancellationError";
|
|
27
|
+
this.reason = reason;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function reasonFromString(value: string): CancellationReason {
|
|
32
|
+
const trimmed = value.trim();
|
|
33
|
+
if (KNOWN_CODES.has(trimmed)) return { code: trimmed as CancellationReasonCode, message: `Cancelled: ${trimmed}` };
|
|
34
|
+
return { code: "caller_cancelled", message: trimmed || "Cancelled by caller." };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function cancellationReasonFromUnknown(value: unknown): CancellationReason {
|
|
38
|
+
if (value instanceof CrewCancellationError) return value.reason;
|
|
39
|
+
if (value instanceof Error) return { code: "caller_cancelled", message: value.message || "Cancelled by caller.", cause: value };
|
|
40
|
+
if (typeof value === "string") return reasonFromString(value);
|
|
41
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
42
|
+
const record = value as { code?: unknown; reason?: unknown; message?: unknown; cause?: unknown };
|
|
43
|
+
const rawCode = typeof record.code === "string" ? record.code : typeof record.reason === "string" ? record.reason : undefined;
|
|
44
|
+
const code = rawCode && KNOWN_CODES.has(rawCode) ? rawCode as CancellationReasonCode : "caller_cancelled";
|
|
45
|
+
const message = typeof record.message === "string" && record.message.trim() ? record.message.trim() : `Cancelled: ${code}`;
|
|
46
|
+
return { code, message, cause: record.cause ?? value };
|
|
47
|
+
}
|
|
48
|
+
return { code: "caller_cancelled", message: "Cancelled by caller." };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function cancellationReasonFromSignal(signal: AbortSignal | undefined): CancellationReason {
|
|
52
|
+
return cancellationReasonFromUnknown(signal?.reason);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function cancellationErrorFromSignal(signal: AbortSignal | undefined): CrewCancellationError {
|
|
56
|
+
return new CrewCancellationError(cancellationReasonFromSignal(signal));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function throwIfCancelled(signal: AbortSignal | undefined): void {
|
|
60
|
+
if (signal?.aborted) throw cancellationErrorFromSignal(signal);
|
|
61
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { AgentConfig, ResourceSource } from "../agents/agent-config.ts";
|
|
2
|
+
import { discoverAgents } from "../agents/discover-agents.ts";
|
|
3
|
+
import { discoverTeams } from "../teams/discover-teams.ts";
|
|
4
|
+
import { discoverWorkflows } from "../workflows/discover-workflows.ts";
|
|
5
|
+
import { discoverSkills } from "../skills/discover-skills.ts";
|
|
6
|
+
import type { PiTeamsConfig } from "../config/config.ts";
|
|
7
|
+
|
|
8
|
+
export type CapabilityKind = "team" | "workflow" | "agent" | "skill" | "tool" | "runtime";
|
|
9
|
+
export type CapabilitySource = "builtin" | "project" | "user" | "package" | "git";
|
|
10
|
+
export type CapabilityState = "active" | "disabled" | "shadowed" | "missing";
|
|
11
|
+
|
|
12
|
+
export interface CapabilityItem {
|
|
13
|
+
id: string;
|
|
14
|
+
kind: CapabilityKind;
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
source: CapabilitySource;
|
|
18
|
+
path?: string;
|
|
19
|
+
state: CapabilityState;
|
|
20
|
+
disabledReason?: string;
|
|
21
|
+
shadowedBy?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeAgents(agents: AgentConfig[], source: CapabilitySource, disabledIds: Set<string>): CapabilityItem[] {
|
|
25
|
+
return agents.map((agent) => {
|
|
26
|
+
const id = `agent:${agent.name}`;
|
|
27
|
+
const configDisabled = disabledIds.has(id);
|
|
28
|
+
const agentDisabled = agent.disabled || configDisabled;
|
|
29
|
+
return {
|
|
30
|
+
id,
|
|
31
|
+
kind: "agent" as const,
|
|
32
|
+
name: agent.name,
|
|
33
|
+
description: agent.description,
|
|
34
|
+
source,
|
|
35
|
+
path: agent.filePath,
|
|
36
|
+
state: agentDisabled ? "disabled" : "active",
|
|
37
|
+
disabledReason: configDisabled ? "disabled by policy" : agent.disabled ? "disabled in config" : undefined,
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function normalizeSkills(cwd: string, disabledIds: Set<string>): CapabilityItem[] {
|
|
43
|
+
const skills = discoverSkills(cwd);
|
|
44
|
+
return skills.map((skill) => {
|
|
45
|
+
const id = `skill:${skill.name}`;
|
|
46
|
+
const configDisabled = disabledIds.has(id);
|
|
47
|
+
return {
|
|
48
|
+
id,
|
|
49
|
+
kind: "skill" as const,
|
|
50
|
+
name: skill.name,
|
|
51
|
+
description: skill.description,
|
|
52
|
+
source: skill.source as CapabilitySource,
|
|
53
|
+
path: skill.path,
|
|
54
|
+
state: configDisabled ? "disabled" : "active",
|
|
55
|
+
disabledReason: configDisabled ? "disabled by policy" : undefined,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizeTeams(cwd: string, disabledIds: Set<string>): CapabilityItem[] {
|
|
61
|
+
const result = discoverTeams(cwd);
|
|
62
|
+
return [...result.builtin, ...result.user, ...result.project].map((team) => {
|
|
63
|
+
const id = `team:${team.name}`;
|
|
64
|
+
const configDisabled = disabledIds.has(id);
|
|
65
|
+
return {
|
|
66
|
+
id,
|
|
67
|
+
kind: "team" as const,
|
|
68
|
+
name: team.name,
|
|
69
|
+
description: team.description,
|
|
70
|
+
source: team.source as CapabilitySource,
|
|
71
|
+
path: team.filePath,
|
|
72
|
+
state: configDisabled ? "disabled" : "active",
|
|
73
|
+
disabledReason: configDisabled ? "disabled by policy" : undefined,
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeWorkflows(cwd: string, disabledIds: Set<string>): CapabilityItem[] {
|
|
79
|
+
const result = discoverWorkflows(cwd);
|
|
80
|
+
return [...result.builtin, ...result.user, ...result.project].map((workflow) => {
|
|
81
|
+
const id = `workflow:${workflow.name}`;
|
|
82
|
+
const configDisabled = disabledIds.has(id);
|
|
83
|
+
return {
|
|
84
|
+
id,
|
|
85
|
+
kind: "workflow" as const,
|
|
86
|
+
name: workflow.name,
|
|
87
|
+
description: workflow.description,
|
|
88
|
+
source: workflow.source as CapabilitySource,
|
|
89
|
+
path: workflow.filePath,
|
|
90
|
+
state: configDisabled ? "disabled" : "active",
|
|
91
|
+
disabledReason: configDisabled ? "disabled by policy" : undefined,
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function buildCapabilityInventory(cwd: string, config?: PiTeamsConfig): CapabilityItem[] {
|
|
97
|
+
const disabledIds = new Set<string>(config?.policy?.disabledCapabilities ?? []);
|
|
98
|
+
const agents = discoverAgents(cwd);
|
|
99
|
+
const items = [
|
|
100
|
+
...normalizeTeams(cwd, disabledIds),
|
|
101
|
+
...normalizeWorkflows(cwd, disabledIds),
|
|
102
|
+
...normalizeAgents([...agents.builtin, ...agents.user, ...agents.project], "builtin", disabledIds),
|
|
103
|
+
...normalizeSkills(cwd, disabledIds),
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
// Mark shadowed resources: project/user items with same kind:name as a builtin
|
|
107
|
+
const builtinNames = new Set(items.filter((item) => item.source === "builtin" || item.source === "package").map((item) => `${item.kind}:${item.name}`));
|
|
108
|
+
for (const item of items) {
|
|
109
|
+
if (item.source !== "builtin" && item.source !== "package" && builtinNames.has(`${item.kind}:${item.name}`)) {
|
|
110
|
+
item.state = "shadowed";
|
|
111
|
+
item.shadowedBy = `builtin:${item.kind}:${item.name}`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return items.sort((a, b) => a.id.localeCompare(b.id));
|
|
116
|
+
}
|
package/src/runtime/child-pi.ts
CHANGED
|
@@ -112,7 +112,7 @@ export interface ChildPiRunResult {
|
|
|
112
112
|
export function buildChildPiSpawnOptions(cwd: string, env: NodeJS.ProcessEnv): SpawnOptions {
|
|
113
113
|
return {
|
|
114
114
|
cwd,
|
|
115
|
-
env,
|
|
115
|
+
env: { ...env, PI_CREW_PARENT_PID: String(process.pid) },
|
|
116
116
|
stdio: ["ignore", "pipe", "pipe"],
|
|
117
117
|
detached: process.platform !== "win32",
|
|
118
118
|
windowsHide: true,
|
|
@@ -243,6 +243,7 @@ export class ChildPiLineObserver {
|
|
|
243
243
|
}
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
/** Mock-only path — real code path reuses a single observer. */
|
|
246
247
|
function observeStdoutChunk(input: ChildPiRunInput, text: string): void {
|
|
247
248
|
const observer = new ChildPiLineObserver(input);
|
|
248
249
|
observer.observe(text);
|