pi-crew 0.1.30 → 0.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +80 -27
  3. package/agents/analyst.md +11 -11
  4. package/agents/critic.md +11 -11
  5. package/agents/executor.md +11 -11
  6. package/agents/explorer.md +11 -11
  7. package/agents/planner.md +11 -11
  8. package/agents/reviewer.md +11 -11
  9. package/agents/security-reviewer.md +11 -11
  10. package/agents/test-engineer.md +11 -11
  11. package/agents/verifier.md +11 -11
  12. package/agents/writer.md +11 -11
  13. package/docs/architecture.md +173 -164
  14. package/docs/refactor-tasks-phase3.md +394 -394
  15. package/docs/refactor-tasks-phase4.md +564 -564
  16. package/docs/refactor-tasks-phase5.md +402 -402
  17. package/docs/refactor-tasks-phase6.md +662 -662
  18. package/docs/research-extension-examples.md +297 -0
  19. package/docs/research-extension-system.md +324 -0
  20. package/docs/research-optimization-plan.md +548 -0
  21. package/docs/research-pi-coding-agent.md +357 -0
  22. package/docs/resource-formats.md +4 -3
  23. package/docs/runtime-flow.md +148 -148
  24. package/docs/source-runtime-refactor-map.md +83 -83
  25. package/docs/usage.md +3 -3
  26. package/index.ts +6 -6
  27. package/package.json +1 -1
  28. package/schema.json +53 -1
  29. package/skills/git-master/SKILL.md +24 -19
  30. package/skills/read-only-explorer/SKILL.md +26 -21
  31. package/skills/safe-bash/SKILL.md +21 -16
  32. package/skills/task-packet/SKILL.md +28 -23
  33. package/skills/verify-evidence/SKILL.md +27 -22
  34. package/src/agents/agent-serializer.ts +34 -34
  35. package/src/agents/discover-agents.ts +102 -102
  36. package/src/config/config.ts +14 -1
  37. package/src/config/defaults.ts +3 -2
  38. package/src/extension/cross-extension-rpc.ts +82 -82
  39. package/src/extension/import-index.ts +4 -3
  40. package/src/extension/management.ts +2 -2
  41. package/src/extension/project-init.ts +9 -7
  42. package/src/extension/register.ts +63 -0
  43. package/src/extension/registration/artifact-cleanup.ts +15 -14
  44. package/src/extension/registration/commands.ts +208 -208
  45. package/src/extension/registration/compaction-guard.ts +125 -0
  46. package/src/extension/registration/subagent-tools.ts +27 -8
  47. package/src/extension/registration/team-tool.ts +61 -44
  48. package/src/extension/result-watcher.ts +98 -98
  49. package/src/extension/run-import.ts +4 -4
  50. package/src/extension/run-index.ts +14 -14
  51. package/src/extension/run-maintenance.ts +24 -24
  52. package/src/extension/team-tool/api.ts +3 -0
  53. package/src/extension/team-tool/cancel.ts +31 -31
  54. package/src/extension/team-tool/doctor.ts +179 -178
  55. package/src/extension/team-tool/inspect.ts +41 -41
  56. package/src/extension/team-tool/lifecycle-actions.ts +79 -79
  57. package/src/extension/team-tool/plan.ts +19 -19
  58. package/src/extension/team-tool/status.ts +73 -73
  59. package/src/prompt/prompt-runtime.ts +68 -68
  60. package/src/runtime/agent-control.ts +64 -64
  61. package/src/runtime/agent-memory.ts +72 -72
  62. package/src/runtime/agent-observability.ts +113 -113
  63. package/src/runtime/async-marker.ts +26 -26
  64. package/src/runtime/background-runner.ts +53 -53
  65. package/src/runtime/crew-agent-runtime.ts +58 -58
  66. package/src/runtime/direct-run.ts +35 -35
  67. package/src/runtime/foreground-control.ts +82 -82
  68. package/src/runtime/green-contract.ts +46 -46
  69. package/src/runtime/group-join.ts +88 -88
  70. package/src/runtime/live-agent-control.ts +78 -78
  71. package/src/runtime/live-agent-manager.ts +85 -85
  72. package/src/runtime/live-control-realtime.ts +36 -36
  73. package/src/runtime/live-session-runtime.ts +299 -299
  74. package/src/runtime/manifest-cache.ts +212 -214
  75. package/src/runtime/model-fallback.ts +261 -261
  76. package/src/runtime/parallel-research.ts +44 -44
  77. package/src/runtime/parallel-utils.ts +99 -99
  78. package/src/runtime/pi-json-output.ts +111 -111
  79. package/src/runtime/pi-spawn.ts +96 -96
  80. package/src/runtime/policy-engine.ts +78 -78
  81. package/src/runtime/post-exit-stdio-guard.ts +86 -86
  82. package/src/runtime/process-status.ts +56 -56
  83. package/src/runtime/progress-event-coalescer.ts +43 -43
  84. package/src/runtime/recovery-recipes.ts +74 -74
  85. package/src/runtime/role-permission.ts +39 -39
  86. package/src/runtime/session-usage.ts +79 -79
  87. package/src/runtime/sidechain-output.ts +28 -28
  88. package/src/runtime/subagent-manager.ts +29 -2
  89. package/src/runtime/task-display.ts +38 -38
  90. package/src/runtime/task-output-context.ts +106 -106
  91. package/src/runtime/task-packet.ts +84 -84
  92. package/src/runtime/task-runner/live-executor.ts +98 -98
  93. package/src/runtime/task-runner/progress.ts +111 -111
  94. package/src/runtime/task-runner/prompt-builder.ts +72 -72
  95. package/src/runtime/task-runner/result-utils.ts +14 -14
  96. package/src/runtime/task-runner/state-helpers.ts +22 -22
  97. package/src/runtime/worker-heartbeat.ts +21 -21
  98. package/src/runtime/worker-startup.ts +57 -57
  99. package/src/schema/config-schema.ts +12 -0
  100. package/src/schema/team-tool-schema.ts +100 -100
  101. package/src/state/artifact-store.ts +108 -108
  102. package/src/state/contracts.ts +105 -105
  103. package/src/state/jsonl-writer.ts +77 -77
  104. package/src/state/state-store.ts +8 -9
  105. package/src/state/task-claims.ts +42 -42
  106. package/src/state/types.ts +180 -180
  107. package/src/state/usage.ts +29 -29
  108. package/src/subagents/async-entry.ts +1 -1
  109. package/src/subagents/index.ts +3 -3
  110. package/src/subagents/live/control.ts +1 -1
  111. package/src/subagents/live/manager.ts +1 -1
  112. package/src/subagents/live/realtime.ts +1 -1
  113. package/src/subagents/live/session-runtime.ts +1 -1
  114. package/src/subagents/manager.ts +1 -1
  115. package/src/subagents/spawn.ts +1 -1
  116. package/src/teams/discover-teams.ts +2 -2
  117. package/src/teams/team-serializer.ts +36 -36
  118. package/src/types/diff.d.ts +18 -0
  119. package/src/ui/crew-footer.ts +101 -101
  120. package/src/ui/crew-select-list.ts +111 -111
  121. package/src/ui/crew-widget.ts +285 -285
  122. package/src/ui/dynamic-border.ts +25 -25
  123. package/src/ui/layout-primitives.ts +106 -106
  124. package/src/ui/loaders.ts +158 -158
  125. package/src/ui/mascot.ts +441 -441
  126. package/src/ui/powerbar-publisher.ts +94 -94
  127. package/src/ui/render-diff.ts +119 -119
  128. package/src/ui/run-dashboard.ts +372 -372
  129. package/src/ui/status-colors.ts +54 -54
  130. package/src/ui/syntax-highlight.ts +116 -116
  131. package/src/ui/transcript-viewer.ts +302 -302
  132. package/src/utils/completion-dedupe.ts +63 -63
  133. package/src/utils/file-coalescer.ts +84 -84
  134. package/src/utils/frontmatter.ts +36 -36
  135. package/src/utils/fs-watch.ts +31 -31
  136. package/src/utils/git.ts +262 -262
  137. package/src/utils/ids.ts +12 -12
  138. package/src/utils/names.ts +26 -26
  139. package/src/utils/paths.ts +34 -7
  140. package/src/utils/sleep.ts +32 -32
  141. package/src/utils/timings.ts +31 -31
  142. package/src/utils/visual.ts +159 -159
  143. package/src/workflows/discover-workflows.ts +2 -2
  144. package/src/workflows/validate-workflow.ts +40 -40
  145. package/src/worktree/branch-freshness.ts +45 -45
  146. package/src/worktree/cleanup.ts +71 -69
  147. package/src/worktree/worktree-manager.ts +3 -1
  148. package/teams/default.team.md +12 -12
  149. package/teams/fast-fix.team.md +11 -11
  150. package/teams/implementation.team.md +18 -18
  151. package/teams/parallel-research.team.md +14 -14
  152. package/teams/research.team.md +11 -11
  153. package/teams/review.team.md +12 -12
  154. package/workflows/default.workflow.md +29 -29
  155. package/workflows/fast-fix.workflow.md +22 -22
  156. package/workflows/implementation.workflow.md +38 -38
  157. package/workflows/parallel-research.workflow.md +46 -46
  158. package/workflows/research.workflow.md +22 -22
  159. package/workflows/review.workflow.md +30 -30
@@ -1,100 +1,100 @@
1
- import { Type } from "typebox";
2
-
3
- const SkillOverride = Type.Unsafe({
4
- type: ["string", "array", "boolean"],
5
- items: { type: "string" },
6
- description: "Skill name(s) to inject, array of skill names, or false to disable role defaults.",
7
- });
8
-
9
- export const TeamToolParams = Type.Object({
10
- action: Type.Optional(Type.Union([
11
- Type.Literal("run"),
12
- Type.Literal("plan"),
13
- Type.Literal("status"),
14
- Type.Literal("list"),
15
- Type.Literal("get"),
16
- Type.Literal("cancel"),
17
- Type.Literal("resume"),
18
- Type.Literal("create"),
19
- Type.Literal("update"),
20
- Type.Literal("delete"),
21
- Type.Literal("doctor"),
22
- Type.Literal("cleanup"),
23
- Type.Literal("events"),
24
- Type.Literal("artifacts"),
25
- Type.Literal("worktrees"),
26
- Type.Literal("forget"),
27
- Type.Literal("summary"),
28
- Type.Literal("prune"),
29
- Type.Literal("export"),
30
- Type.Literal("import"),
31
- Type.Literal("imports"),
32
- Type.Literal("help"),
33
- Type.Literal("validate"),
34
- Type.Literal("config"),
35
- Type.Literal("init"),
36
- Type.Literal("recommend"),
37
- Type.Literal("autonomy"),
38
- Type.Literal("api"),
39
- ], { description: "Team action. Defaults to 'list' when omitted." })),
40
- resource: Type.Optional(Type.Union([
41
- Type.Literal("agent"),
42
- Type.Literal("team"),
43
- Type.Literal("workflow"),
44
- ], { description: "Resource kind for get/create/update/delete/list. Defaults to all for list." })),
45
- team: Type.Optional(Type.String({ description: "Team name, e.g. default or implementation." })),
46
- workflow: Type.Optional(Type.String({ description: "Workflow name, e.g. default or review." })),
47
- role: Type.Optional(Type.String({ description: "Role name to run directly within a team." })),
48
- agent: Type.Optional(Type.String({ description: "Agent name to inspect or run directly." })),
49
- goal: Type.Optional(Type.String({ description: "High-level objective for a team run." })),
50
- task: Type.Optional(Type.String({ description: "Concrete task text for direct role/agent execution." })),
51
- runId: Type.Optional(Type.String({ description: "Run ID for status, cancel, or resume." })),
52
- async: Type.Optional(Type.Boolean({ description: "Run in background when execution support is enabled." })),
53
- workspaceMode: Type.Optional(Type.Union([
54
- Type.Literal("single"),
55
- Type.Literal("worktree"),
56
- ], { description: "Workspace isolation mode. Worktree mode is planned after MVP." })),
57
- context: Type.Optional(Type.Union([
58
- Type.Literal("fresh"),
59
- Type.Literal("fork"),
60
- ], { description: "Child context mode for workers." })),
61
- cwd: Type.Optional(Type.String({ description: "Working directory override." })),
62
- model: Type.Optional(Type.String({ description: "Model override for direct runs." })),
63
- skill: Type.Optional(SkillOverride),
64
- scope: Type.Optional(Type.Union([
65
- Type.Literal("user"),
66
- Type.Literal("project"),
67
- Type.Literal("both"),
68
- ], { description: "Resource scope for discovery or management." })),
69
- config: Type.Optional(Type.Unsafe({ description: "Resource config for management actions." })),
70
- dryRun: Type.Optional(Type.Boolean({ description: "Preview a management mutation without writing files." })),
71
- confirm: Type.Optional(Type.Boolean({ description: "Required for destructive management actions." })),
72
- force: Type.Optional(Type.Boolean({ description: "Override reference checks for destructive management actions." })),
73
- keep: Type.Optional(Type.Integer({ minimum: 0, description: "Number of finished runs to keep for prune." })),
74
- updateReferences: Type.Optional(Type.Boolean({ description: "When renaming agents or workflows, update team references in the same project/user scope." })),
75
- });
76
-
77
- export interface TeamToolParamsValue {
78
- action?: "run" | "plan" | "status" | "list" | "get" | "cancel" | "resume" | "create" | "update" | "delete" | "doctor" | "cleanup" | "events" | "artifacts" | "worktrees" | "forget" | "summary" | "prune" | "export" | "import" | "imports" | "help" | "validate" | "config" | "init" | "recommend" | "autonomy" | "api";
79
- resource?: "agent" | "team" | "workflow";
80
- team?: string;
81
- workflow?: string;
82
- role?: string;
83
- agent?: string;
84
- goal?: string;
85
- task?: string;
86
- runId?: string;
87
- async?: boolean;
88
- workspaceMode?: "single" | "worktree";
89
- context?: "fresh" | "fork";
90
- cwd?: string;
91
- model?: string;
92
- skill?: string | string[] | boolean;
93
- scope?: "user" | "project" | "both";
94
- config?: unknown;
95
- dryRun?: boolean;
96
- confirm?: boolean;
97
- force?: boolean;
98
- keep?: number;
99
- updateReferences?: boolean;
100
- }
1
+ import { Type } from "typebox";
2
+
3
+ const SkillOverride = Type.Unsafe({
4
+ type: ["string", "array", "boolean"],
5
+ items: { type: "string" },
6
+ description: "Skill name(s) to inject, array of skill names, or false to disable role defaults.",
7
+ });
8
+
9
+ export const TeamToolParams = Type.Object({
10
+ action: Type.Optional(Type.Union([
11
+ Type.Literal("run"),
12
+ Type.Literal("plan"),
13
+ Type.Literal("status"),
14
+ Type.Literal("list"),
15
+ Type.Literal("get"),
16
+ Type.Literal("cancel"),
17
+ Type.Literal("resume"),
18
+ Type.Literal("create"),
19
+ Type.Literal("update"),
20
+ Type.Literal("delete"),
21
+ Type.Literal("doctor"),
22
+ Type.Literal("cleanup"),
23
+ Type.Literal("events"),
24
+ Type.Literal("artifacts"),
25
+ Type.Literal("worktrees"),
26
+ Type.Literal("forget"),
27
+ Type.Literal("summary"),
28
+ Type.Literal("prune"),
29
+ Type.Literal("export"),
30
+ Type.Literal("import"),
31
+ Type.Literal("imports"),
32
+ Type.Literal("help"),
33
+ Type.Literal("validate"),
34
+ Type.Literal("config"),
35
+ Type.Literal("init"),
36
+ Type.Literal("recommend"),
37
+ Type.Literal("autonomy"),
38
+ Type.Literal("api"),
39
+ ], { description: "Team action. Defaults to 'list' when omitted." })),
40
+ resource: Type.Optional(Type.Union([
41
+ Type.Literal("agent"),
42
+ Type.Literal("team"),
43
+ Type.Literal("workflow"),
44
+ ], { description: "Resource kind for get/create/update/delete/list. Defaults to all for list." })),
45
+ team: Type.Optional(Type.String({ description: "Team name, e.g. default or implementation." })),
46
+ workflow: Type.Optional(Type.String({ description: "Workflow name, e.g. default or review." })),
47
+ role: Type.Optional(Type.String({ description: "Role name to run directly within a team." })),
48
+ agent: Type.Optional(Type.String({ description: "Agent name to inspect or run directly." })),
49
+ goal: Type.Optional(Type.String({ description: "High-level objective for a team run." })),
50
+ task: Type.Optional(Type.String({ description: "Concrete task text for direct role/agent execution." })),
51
+ runId: Type.Optional(Type.String({ description: "Run ID for status, cancel, or resume." })),
52
+ async: Type.Optional(Type.Boolean({ description: "Run in background when execution support is enabled." })),
53
+ workspaceMode: Type.Optional(Type.Union([
54
+ Type.Literal("single"),
55
+ Type.Literal("worktree"),
56
+ ], { description: "Workspace isolation mode. Worktree mode is planned after MVP." })),
57
+ context: Type.Optional(Type.Union([
58
+ Type.Literal("fresh"),
59
+ Type.Literal("fork"),
60
+ ], { description: "Child context mode for workers." })),
61
+ cwd: Type.Optional(Type.String({ description: "Working directory override." })),
62
+ model: Type.Optional(Type.String({ description: "Model override for direct runs." })),
63
+ skill: Type.Optional(SkillOverride),
64
+ scope: Type.Optional(Type.Union([
65
+ Type.Literal("user"),
66
+ Type.Literal("project"),
67
+ Type.Literal("both"),
68
+ ], { description: "Resource scope for discovery or management." })),
69
+ config: Type.Optional(Type.Unsafe({ description: "Resource config for management actions." })),
70
+ dryRun: Type.Optional(Type.Boolean({ description: "Preview a management mutation without writing files." })),
71
+ confirm: Type.Optional(Type.Boolean({ description: "Required for destructive management actions." })),
72
+ force: Type.Optional(Type.Boolean({ description: "Override reference checks for destructive management actions." })),
73
+ keep: Type.Optional(Type.Integer({ minimum: 0, description: "Number of finished runs to keep for prune." })),
74
+ updateReferences: Type.Optional(Type.Boolean({ description: "When renaming agents or workflows, update team references in the same project/user scope." })),
75
+ });
76
+
77
+ export interface TeamToolParamsValue {
78
+ action?: "run" | "plan" | "status" | "list" | "get" | "cancel" | "resume" | "create" | "update" | "delete" | "doctor" | "cleanup" | "events" | "artifacts" | "worktrees" | "forget" | "summary" | "prune" | "export" | "import" | "imports" | "help" | "validate" | "config" | "init" | "recommend" | "autonomy" | "api";
79
+ resource?: "agent" | "team" | "workflow";
80
+ team?: string;
81
+ workflow?: string;
82
+ role?: string;
83
+ agent?: string;
84
+ goal?: string;
85
+ task?: string;
86
+ runId?: string;
87
+ async?: boolean;
88
+ workspaceMode?: "single" | "worktree";
89
+ context?: "fresh" | "fork";
90
+ cwd?: string;
91
+ model?: string;
92
+ skill?: string | string[] | boolean;
93
+ scope?: "user" | "project" | "both";
94
+ config?: unknown;
95
+ dryRun?: boolean;
96
+ confirm?: boolean;
97
+ force?: boolean;
98
+ keep?: number;
99
+ updateReferences?: boolean;
100
+ }
@@ -1,108 +1,108 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import { createHash } from "node:crypto";
4
- import type { ArtifactDescriptor } from "./types.ts";
5
- import { atomicWriteFile } from "./atomic-write.ts";
6
-
7
- function hashContent(content: string): string {
8
- return createHash("sha256").update(content).digest("hex");
9
- }
10
-
11
- export const CLEANUP_MARKER_FILE = ".last-cleanup";
12
-
13
- export interface ArtifactWriteOptions {
14
- kind: ArtifactDescriptor["kind"];
15
- relativePath: string;
16
- content: string;
17
- producer: string;
18
- retention?: ArtifactDescriptor["retention"];
19
- }
20
-
21
- export interface ArtifactCleanupOptions {
22
- maxAgeDays: number;
23
- maxAgeMs?: number;
24
- markerFile?: string;
25
- scanGraceMs?: number;
26
- }
27
-
28
- function parseAgeDays(value: unknown): number | undefined {
29
- if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return undefined;
30
- return Math.floor(value);
31
- }
32
-
33
- function nowMs(): number {
34
- return Date.now();
35
- }
36
-
37
- function readMarkerMtime(artifactsRoot: string, markerFile: string): number | undefined {
38
- try {
39
- return fs.statSync(path.join(artifactsRoot, markerFile)).mtimeMs;
40
- } catch {
41
- return undefined;
42
- }
43
- }
44
-
45
- function shouldCleanup(artifactsRoot: string, markerFile: string, scanGraceMs: number): boolean {
46
- const marker = readMarkerMtime(artifactsRoot, markerFile);
47
- if (marker === undefined) return true;
48
- return nowMs() - marker >= scanGraceMs;
49
- }
50
-
51
- export function writeCleanupMarker(artifactsRoot: string, markerFile: string): void {
52
- fs.mkdirSync(artifactsRoot, { recursive: true });
53
- fs.writeFileSync(path.join(artifactsRoot, markerFile), String(nowMs()), "utf-8");
54
- }
55
-
56
- export function cleanupOldArtifacts(artifactsRoot: string, options: ArtifactCleanupOptions): void {
57
- if (!fs.existsSync(artifactsRoot)) return;
58
- const maxAgeDays = parseAgeDays(options.maxAgeDays);
59
- if (maxAgeDays === undefined) return;
60
- const markerFile = options.markerFile ?? CLEANUP_MARKER_FILE;
61
- const scanGraceMs = options.scanGraceMs ?? 24 * 60 * 60 * 1000;
62
- if (!shouldCleanup(artifactsRoot, markerFile, scanGraceMs)) return;
63
- const maxAgeMs = options.maxAgeMs ?? maxAgeDays * 24 * 60 * 60 * 1000;
64
- const cutoff = nowMs() - maxAgeMs;
65
- let didCleanup = false;
66
- try {
67
- const entries = fs.readdirSync(artifactsRoot, { withFileTypes: true });
68
- for (const entry of entries) {
69
- if (entry.name === markerFile) continue;
70
- const target = path.join(artifactsRoot, entry.name);
71
- try {
72
- const stat = fs.statSync(target);
73
- if (stat.mtimeMs >= cutoff) continue;
74
- if (stat.isDirectory()) {
75
- fs.rmSync(target, { recursive: true, force: true });
76
- } else {
77
- fs.unlinkSync(target);
78
- }
79
- didCleanup = true;
80
- } catch {
81
- // Ignore cleanup races and permission issues in best-effort mode.
82
- }
83
- }
84
- writeCleanupMarker(artifactsRoot, markerFile);
85
- } catch {
86
- // Ignore unreadable roots in best-effort mode.
87
- }
88
- if (!didCleanup) writeCleanupMarker(artifactsRoot, markerFile);
89
- }
90
-
91
- export function writeArtifact(artifactsRoot: string, options: ArtifactWriteOptions): ArtifactDescriptor {
92
- const normalizedRelativePath = options.relativePath.replaceAll("\\", "/").replace(/^\.\/+/, "");
93
- if (normalizedRelativePath.startsWith("../") || path.isAbsolute(normalizedRelativePath)) {
94
- throw new Error(`Invalid artifact path: ${options.relativePath}`);
95
- }
96
- const filePath = path.join(artifactsRoot, normalizedRelativePath);
97
- atomicWriteFile(filePath, options.content);
98
- const stats = fs.statSync(filePath);
99
- return {
100
- kind: options.kind,
101
- path: filePath,
102
- createdAt: new Date().toISOString(),
103
- producer: options.producer,
104
- sizeBytes: stats.size,
105
- contentHash: hashContent(options.content),
106
- retention: options.retention ?? "run",
107
- };
108
- }
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import type { ArtifactDescriptor } from "./types.ts";
5
+ import { atomicWriteFile } from "./atomic-write.ts";
6
+
7
+ function hashContent(content: string): string {
8
+ return createHash("sha256").update(content).digest("hex");
9
+ }
10
+
11
+ export const CLEANUP_MARKER_FILE = ".last-cleanup";
12
+
13
+ export interface ArtifactWriteOptions {
14
+ kind: ArtifactDescriptor["kind"];
15
+ relativePath: string;
16
+ content: string;
17
+ producer: string;
18
+ retention?: ArtifactDescriptor["retention"];
19
+ }
20
+
21
+ export interface ArtifactCleanupOptions {
22
+ maxAgeDays: number;
23
+ maxAgeMs?: number;
24
+ markerFile?: string;
25
+ scanGraceMs?: number;
26
+ }
27
+
28
+ function parseAgeDays(value: unknown): number | undefined {
29
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return undefined;
30
+ return Math.floor(value);
31
+ }
32
+
33
+ function nowMs(): number {
34
+ return Date.now();
35
+ }
36
+
37
+ function readMarkerMtime(artifactsRoot: string, markerFile: string): number | undefined {
38
+ try {
39
+ return fs.statSync(path.join(artifactsRoot, markerFile)).mtimeMs;
40
+ } catch {
41
+ return undefined;
42
+ }
43
+ }
44
+
45
+ function shouldCleanup(artifactsRoot: string, markerFile: string, scanGraceMs: number): boolean {
46
+ const marker = readMarkerMtime(artifactsRoot, markerFile);
47
+ if (marker === undefined) return true;
48
+ return nowMs() - marker >= scanGraceMs;
49
+ }
50
+
51
+ export function writeCleanupMarker(artifactsRoot: string, markerFile: string): void {
52
+ fs.mkdirSync(artifactsRoot, { recursive: true });
53
+ fs.writeFileSync(path.join(artifactsRoot, markerFile), String(nowMs()), "utf-8");
54
+ }
55
+
56
+ export function cleanupOldArtifacts(artifactsRoot: string, options: ArtifactCleanupOptions): void {
57
+ if (!fs.existsSync(artifactsRoot)) return;
58
+ const maxAgeDays = parseAgeDays(options.maxAgeDays);
59
+ if (maxAgeDays === undefined) return;
60
+ const markerFile = options.markerFile ?? CLEANUP_MARKER_FILE;
61
+ const scanGraceMs = options.scanGraceMs ?? 24 * 60 * 60 * 1000;
62
+ if (!shouldCleanup(artifactsRoot, markerFile, scanGraceMs)) return;
63
+ const maxAgeMs = options.maxAgeMs ?? maxAgeDays * 24 * 60 * 60 * 1000;
64
+ const cutoff = nowMs() - maxAgeMs;
65
+ let didCleanup = false;
66
+ try {
67
+ const entries = fs.readdirSync(artifactsRoot, { withFileTypes: true });
68
+ for (const entry of entries) {
69
+ if (entry.name === markerFile) continue;
70
+ const target = path.join(artifactsRoot, entry.name);
71
+ try {
72
+ const stat = fs.statSync(target);
73
+ if (stat.mtimeMs >= cutoff) continue;
74
+ if (stat.isDirectory()) {
75
+ fs.rmSync(target, { recursive: true, force: true });
76
+ } else {
77
+ fs.unlinkSync(target);
78
+ }
79
+ didCleanup = true;
80
+ } catch {
81
+ // Ignore cleanup races and permission issues in best-effort mode.
82
+ }
83
+ }
84
+ writeCleanupMarker(artifactsRoot, markerFile);
85
+ } catch {
86
+ // Ignore unreadable roots in best-effort mode.
87
+ }
88
+ if (!didCleanup) writeCleanupMarker(artifactsRoot, markerFile);
89
+ }
90
+
91
+ export function writeArtifact(artifactsRoot: string, options: ArtifactWriteOptions): ArtifactDescriptor {
92
+ const normalizedRelativePath = options.relativePath.replaceAll("\\", "/").replace(/^\.\/+/, "");
93
+ if (normalizedRelativePath.startsWith("../") || path.isAbsolute(normalizedRelativePath)) {
94
+ throw new Error(`Invalid artifact path: ${options.relativePath}`);
95
+ }
96
+ const filePath = path.join(artifactsRoot, normalizedRelativePath);
97
+ atomicWriteFile(filePath, options.content);
98
+ const stats = fs.statSync(filePath);
99
+ return {
100
+ kind: options.kind,
101
+ path: filePath,
102
+ createdAt: new Date().toISOString(),
103
+ producer: options.producer,
104
+ sizeBytes: stats.size,
105
+ contentHash: hashContent(options.content),
106
+ retention: options.retention ?? "run",
107
+ };
108
+ }
@@ -1,105 +1,105 @@
1
- export const TEAM_RUN_STATUSES = ["queued", "planning", "running", "blocked", "completed", "failed", "cancelled"] as const;
2
- export type TeamRunStatus = typeof TEAM_RUN_STATUSES[number];
3
-
4
- export const TEAM_TASK_STATUSES = ["queued", "running", "completed", "failed", "cancelled", "skipped"] as const;
5
- export type TeamTaskStatus = typeof TEAM_TASK_STATUSES[number];
6
-
7
- export const TEAM_TERMINAL_RUN_STATUSES: ReadonlySet<TeamRunStatus> = new Set(["blocked", "completed", "failed", "cancelled"]);
8
- export const TEAM_TERMINAL_TASK_STATUSES: ReadonlySet<TeamTaskStatus> = new Set(["completed", "failed", "cancelled", "skipped"]);
9
-
10
- export const TEAM_RUN_STATUS_TRANSITIONS: Readonly<Record<TeamRunStatus, readonly TeamRunStatus[]>> = {
11
- queued: ["planning", "running", "cancelled", "failed"],
12
- planning: ["running", "blocked", "cancelled", "failed"],
13
- running: ["blocked", "completed", "failed", "cancelled"],
14
- blocked: ["running", "cancelled", "failed"],
15
- completed: ["running", "cancelled"],
16
- failed: ["running", "cancelled"],
17
- cancelled: ["running"],
18
- };
19
-
20
- export const TEAM_TASK_STATUS_TRANSITIONS: Readonly<Record<TeamTaskStatus, readonly TeamTaskStatus[]>> = {
21
- queued: ["running", "cancelled", "skipped", "failed"],
22
- running: ["completed", "failed", "cancelled", "queued"],
23
- completed: ["queued"],
24
- failed: ["queued", "cancelled"],
25
- cancelled: ["queued"],
26
- skipped: ["queued", "cancelled"],
27
- };
28
-
29
- export const TEAM_EVENT_TYPES = [
30
- "run.created",
31
- "run.queued",
32
- "run.planning",
33
- "run.running",
34
- "run.blocked",
35
- "run.completed",
36
- "run.failed",
37
- "run.cancelled",
38
- "task.started",
39
- "task.progress",
40
- "task.blocked",
41
- "task.green",
42
- "task.red",
43
- "task.completed",
44
- "task.failed",
45
- "task.cancelled",
46
- "task.skipped",
47
- "review.approved",
48
- "review.rejected",
49
- "policy.action",
50
- "policy.escalated",
51
- "recovery.attempted",
52
- "recovery.escalated",
53
- "branch.stale",
54
- "mailbox.timeout",
55
- "worktree.cleanup",
56
- "worktree.dirty",
57
- "async.spawned",
58
- "async.started",
59
- "async.completed",
60
- "async.failed",
61
- "async.stale",
62
- ] as const;
63
- export type TeamEventType = typeof TEAM_EVENT_TYPES[number];
64
-
65
- export const TEAM_WAKEABLE_EVENT_TYPES: ReadonlySet<TeamEventType> = new Set([
66
- "run.blocked",
67
- "run.completed",
68
- "run.failed",
69
- "run.cancelled",
70
- "task.completed",
71
- "task.failed",
72
- "task.cancelled",
73
- "task.skipped",
74
- "async.completed",
75
- "async.failed",
76
- "async.stale",
77
- ]);
78
-
79
- export function isTeamRunStatus(value: unknown): value is TeamRunStatus {
80
- return typeof value === "string" && TEAM_RUN_STATUSES.includes(value as TeamRunStatus);
81
- }
82
-
83
- export function isTeamTaskStatus(value: unknown): value is TeamTaskStatus {
84
- return typeof value === "string" && TEAM_TASK_STATUSES.includes(value as TeamTaskStatus);
85
- }
86
-
87
- export function isTerminalRunStatus(status: TeamRunStatus): boolean {
88
- return TEAM_TERMINAL_RUN_STATUSES.has(status);
89
- }
90
-
91
- export function isTerminalTaskStatus(status: TeamTaskStatus): boolean {
92
- return TEAM_TERMINAL_TASK_STATUSES.has(status);
93
- }
94
-
95
- export function canTransitionRunStatus(from: TeamRunStatus, to: TeamRunStatus): boolean {
96
- return from === to || (TEAM_RUN_STATUS_TRANSITIONS[from]?.includes(to) ?? false);
97
- }
98
-
99
- export function canTransitionTaskStatus(from: TeamTaskStatus, to: TeamTaskStatus): boolean {
100
- return from === to || (TEAM_TASK_STATUS_TRANSITIONS[from]?.includes(to) ?? false);
101
- }
102
-
103
- export function isWakeableTeamEventType(type: TeamEventType): boolean {
104
- return TEAM_WAKEABLE_EVENT_TYPES.has(type);
105
- }
1
+ export const TEAM_RUN_STATUSES = ["queued", "planning", "running", "blocked", "completed", "failed", "cancelled"] as const;
2
+ export type TeamRunStatus = typeof TEAM_RUN_STATUSES[number];
3
+
4
+ export const TEAM_TASK_STATUSES = ["queued", "running", "completed", "failed", "cancelled", "skipped"] as const;
5
+ export type TeamTaskStatus = typeof TEAM_TASK_STATUSES[number];
6
+
7
+ export const TEAM_TERMINAL_RUN_STATUSES: ReadonlySet<TeamRunStatus> = new Set(["blocked", "completed", "failed", "cancelled"]);
8
+ export const TEAM_TERMINAL_TASK_STATUSES: ReadonlySet<TeamTaskStatus> = new Set(["completed", "failed", "cancelled", "skipped"]);
9
+
10
+ export const TEAM_RUN_STATUS_TRANSITIONS: Readonly<Record<TeamRunStatus, readonly TeamRunStatus[]>> = {
11
+ queued: ["planning", "running", "cancelled", "failed"],
12
+ planning: ["running", "blocked", "cancelled", "failed"],
13
+ running: ["blocked", "completed", "failed", "cancelled"],
14
+ blocked: ["running", "cancelled", "failed"],
15
+ completed: ["running", "cancelled"],
16
+ failed: ["running", "cancelled"],
17
+ cancelled: ["running"],
18
+ };
19
+
20
+ export const TEAM_TASK_STATUS_TRANSITIONS: Readonly<Record<TeamTaskStatus, readonly TeamTaskStatus[]>> = {
21
+ queued: ["running", "cancelled", "skipped", "failed"],
22
+ running: ["completed", "failed", "cancelled", "queued"],
23
+ completed: ["queued"],
24
+ failed: ["queued", "cancelled"],
25
+ cancelled: ["queued"],
26
+ skipped: ["queued", "cancelled"],
27
+ };
28
+
29
+ export const TEAM_EVENT_TYPES = [
30
+ "run.created",
31
+ "run.queued",
32
+ "run.planning",
33
+ "run.running",
34
+ "run.blocked",
35
+ "run.completed",
36
+ "run.failed",
37
+ "run.cancelled",
38
+ "task.started",
39
+ "task.progress",
40
+ "task.blocked",
41
+ "task.green",
42
+ "task.red",
43
+ "task.completed",
44
+ "task.failed",
45
+ "task.cancelled",
46
+ "task.skipped",
47
+ "review.approved",
48
+ "review.rejected",
49
+ "policy.action",
50
+ "policy.escalated",
51
+ "recovery.attempted",
52
+ "recovery.escalated",
53
+ "branch.stale",
54
+ "mailbox.timeout",
55
+ "worktree.cleanup",
56
+ "worktree.dirty",
57
+ "async.spawned",
58
+ "async.started",
59
+ "async.completed",
60
+ "async.failed",
61
+ "async.stale",
62
+ ] as const;
63
+ export type TeamEventType = typeof TEAM_EVENT_TYPES[number];
64
+
65
+ export const TEAM_WAKEABLE_EVENT_TYPES: ReadonlySet<TeamEventType> = new Set([
66
+ "run.blocked",
67
+ "run.completed",
68
+ "run.failed",
69
+ "run.cancelled",
70
+ "task.completed",
71
+ "task.failed",
72
+ "task.cancelled",
73
+ "task.skipped",
74
+ "async.completed",
75
+ "async.failed",
76
+ "async.stale",
77
+ ]);
78
+
79
+ export function isTeamRunStatus(value: unknown): value is TeamRunStatus {
80
+ return typeof value === "string" && TEAM_RUN_STATUSES.includes(value as TeamRunStatus);
81
+ }
82
+
83
+ export function isTeamTaskStatus(value: unknown): value is TeamTaskStatus {
84
+ return typeof value === "string" && TEAM_TASK_STATUSES.includes(value as TeamTaskStatus);
85
+ }
86
+
87
+ export function isTerminalRunStatus(status: TeamRunStatus): boolean {
88
+ return TEAM_TERMINAL_RUN_STATUSES.has(status);
89
+ }
90
+
91
+ export function isTerminalTaskStatus(status: TeamTaskStatus): boolean {
92
+ return TEAM_TERMINAL_TASK_STATUSES.has(status);
93
+ }
94
+
95
+ export function canTransitionRunStatus(from: TeamRunStatus, to: TeamRunStatus): boolean {
96
+ return from === to || (TEAM_RUN_STATUS_TRANSITIONS[from]?.includes(to) ?? false);
97
+ }
98
+
99
+ export function canTransitionTaskStatus(from: TeamTaskStatus, to: TeamTaskStatus): boolean {
100
+ return from === to || (TEAM_TASK_STATUS_TRANSITIONS[from]?.includes(to) ?? false);
101
+ }
102
+
103
+ export function isWakeableTeamEventType(type: TeamEventType): boolean {
104
+ return TEAM_WAKEABLE_EVENT_TYPES.has(type);
105
+ }