pi-crew 0.1.44 → 0.1.46
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 +27 -0
- package/README.md +5 -5
- 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 +733 -0
- package/docs/research-awesome-agent-skills-distillation.md +100 -0
- package/docs/research-oh-my-pi-distillation.md +322 -0
- package/docs/source-runtime-refactor-map.md +24 -0
- package/docs/usage.md +3 -3
- package/install.mjs +52 -8
- package/package.json +1 -1
- package/schema.json +2 -1
- package/skills/async-worker-recovery/SKILL.md +42 -0
- package/skills/context-artifact-hygiene/SKILL.md +52 -0
- package/skills/delegation-patterns/SKILL.md +54 -0
- package/skills/mailbox-interactive/SKILL.md +40 -0
- package/skills/model-routing-context/SKILL.md +39 -0
- package/skills/multi-perspective-review/SKILL.md +58 -0
- package/skills/observability-reliability/SKILL.md +41 -0
- package/skills/ownership-session-security/SKILL.md +41 -0
- package/skills/pi-extension-lifecycle/SKILL.md +39 -0
- package/skills/requirements-to-task-packet/SKILL.md +63 -0
- package/skills/resource-discovery-config/SKILL.md +41 -0
- package/skills/runtime-state-reader/SKILL.md +44 -0
- package/skills/secure-agent-orchestration-review/SKILL.md +45 -0
- package/skills/state-mutation-locking/SKILL.md +42 -0
- package/skills/systematic-debugging/SKILL.md +67 -0
- package/skills/ui-render-performance/SKILL.md +39 -0
- package/skills/verification-before-done/SKILL.md +57 -0
- package/skills/worktree-isolation/SKILL.md +39 -0
- package/src/agents/discover-agents.ts +12 -11
- package/src/config/config.ts +48 -24
- package/src/config/defaults.ts +14 -0
- package/src/extension/project-init.ts +62 -2
- package/src/extension/register.ts +19 -10
- package/src/extension/registration/commands.ts +49 -26
- package/src/extension/registration/subagent-helpers.ts +8 -0
- package/src/extension/registration/subagent-tools.ts +2 -1
- package/src/extension/registration/team-tool.ts +28 -8
- package/src/extension/run-index.ts +13 -5
- package/src/extension/run-maintenance.ts +22 -3
- package/src/extension/team-tool/api.ts +25 -8
- package/src/extension/team-tool/cancel.ts +134 -102
- package/src/extension/team-tool/context.ts +6 -0
- package/src/extension/team-tool/lifecycle-actions.ts +17 -5
- package/src/extension/team-tool/respond.ts +103 -66
- package/src/extension/team-tool/run.ts +53 -10
- package/src/extension/team-tool/status.ts +12 -1
- package/src/extension/team-tool-types.ts +2 -0
- package/src/extension/team-tool.ts +32 -11
- package/src/observability/event-to-metric.ts +8 -1
- package/src/runtime/background-runner.ts +10 -4
- package/src/runtime/cancellation.ts +51 -0
- package/src/runtime/child-pi.ts +17 -4
- package/src/runtime/crash-recovery.ts +1 -0
- package/src/runtime/crew-agent-records.ts +41 -1
- package/src/runtime/deadletter.ts +1 -0
- package/src/runtime/delivery-coordinator.ts +174 -142
- package/src/runtime/effectiveness.ts +76 -0
- package/src/runtime/live-agent-control.ts +2 -1
- package/src/runtime/live-agent-manager.ts +20 -2
- package/src/runtime/live-control-realtime.ts +1 -1
- package/src/runtime/live-session-runtime.ts +5 -1
- package/src/runtime/manifest-cache.ts +17 -2
- package/src/runtime/model-fallback.ts +6 -4
- package/src/runtime/overflow-recovery.ts +175 -156
- package/src/runtime/pi-args.ts +18 -3
- package/src/runtime/process-status.ts +5 -1
- package/src/runtime/retry-executor.ts +26 -9
- package/src/runtime/runtime-resolver.ts +22 -6
- package/src/runtime/skill-instructions.ts +222 -0
- package/src/runtime/stale-reconciler.ts +189 -179
- package/src/runtime/subagent-manager.ts +3 -0
- package/src/runtime/task-runner/capabilities.ts +78 -0
- package/src/runtime/task-runner/live-executor.ts +4 -0
- package/src/runtime/task-runner/prompt-builder.ts +3 -1
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -0
- package/src/runtime/task-runner.ts +44 -5
- package/src/runtime/team-runner.ts +91 -19
- package/src/schema/config-schema.ts +1 -0
- package/src/schema/team-tool-schema.ts +3 -3
- package/src/state/active-run-registry.ts +165 -0
- package/src/state/contracts.ts +1 -1
- package/src/state/mailbox.ts +44 -4
- package/src/state/state-store.ts +51 -1
- package/src/state/types.ts +46 -2
- package/src/teams/team-config.ts +1 -0
- package/src/ui/crew-widget.ts +9 -4
- package/src/ui/dashboard-panes/mailbox-pane.ts +2 -1
- package/src/ui/dashboard-panes/progress-pane.ts +2 -0
- package/src/ui/powerbar-publisher.ts +1 -1
- package/src/ui/run-snapshot-cache.ts +66 -39
- package/src/ui/snapshot-types.ts +7 -0
- package/src/utils/paths.ts +4 -2
- package/src/workflows/workflow-config.ts +1 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: verification-before-done
|
|
3
|
+
description: Use when about to claim work is complete, fixed, passing, reviewed, committed, or ready to hand off.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# verification-before-done
|
|
7
|
+
|
|
8
|
+
Core principle: evidence before claims. A worker report, green-looking log, or previous run is not fresh verification.
|
|
9
|
+
|
|
10
|
+
Distilled from detailed reads of agent-skill patterns for verification-before-completion, TDD, review reception, and QA workflows.
|
|
11
|
+
|
|
12
|
+
## Gate Function
|
|
13
|
+
|
|
14
|
+
Before any completion claim:
|
|
15
|
+
|
|
16
|
+
1. Identify the command or inspection that proves the claim.
|
|
17
|
+
2. Run the full command fresh, or explicitly state why a command cannot be run.
|
|
18
|
+
3. Read the output, including exit code and failure counts.
|
|
19
|
+
4. Compare the output to the claim.
|
|
20
|
+
5. Report the claim only with the evidence.
|
|
21
|
+
|
|
22
|
+
## Claim-to-Evidence Table
|
|
23
|
+
|
|
24
|
+
| Claim | Requires | Not sufficient |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| Tests pass | Fresh test output with zero failures | Prior run, “should pass” |
|
|
27
|
+
| Typecheck passes | Typecheck command exit 0 | Lint or targeted tests only |
|
|
28
|
+
| Bug fixed | Original symptom/regression test passes | Code changed |
|
|
29
|
+
| Requirements met | Checklist against request/plan | Generic test success |
|
|
30
|
+
| Agent completed | Worker output plus artifact/diff/state inspection | Worker says DONE |
|
|
31
|
+
| Safe to commit | Relevant checks pass and status reviewed | Partial local confidence |
|
|
32
|
+
|
|
33
|
+
## Verification Ladder
|
|
34
|
+
|
|
35
|
+
Choose the smallest reliable gate, then escalate when risk requires it:
|
|
36
|
+
|
|
37
|
+
1. Read-only inspection for plans/reviews.
|
|
38
|
+
2. Targeted unit test for touched behavior.
|
|
39
|
+
3. Typecheck for TypeScript/schema/API changes.
|
|
40
|
+
4. Integration test for runtime, subprocess, state, filesystem, UI, config, or session behavior.
|
|
41
|
+
5. Full suite before commit/release or broad changes.
|
|
42
|
+
6. Real Pi smoke only when safe and needed.
|
|
43
|
+
|
|
44
|
+
## Done Report
|
|
45
|
+
|
|
46
|
+
Include:
|
|
47
|
+
|
|
48
|
+
- changed files or read-only status;
|
|
49
|
+
- commands run and pass/fail result;
|
|
50
|
+
- artifacts, run IDs, logs, or state paths inspected;
|
|
51
|
+
- behavior actually verified;
|
|
52
|
+
- skipped checks and why;
|
|
53
|
+
- risks and rollback notes.
|
|
54
|
+
|
|
55
|
+
## Red Flags
|
|
56
|
+
|
|
57
|
+
Stop before saying done if you are using words like “should”, “probably”, “looks”, “seems”, “I think”, or if you are trusting an agent report without checking evidence.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: worktree-isolation
|
|
3
|
+
description: Conflict-safe git worktree workflow. Use when running parallel implementation workers, isolating risky edits, or cleaning up task worktrees.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# worktree-isolation
|
|
7
|
+
|
|
8
|
+
Use this skill for worktree-based execution or cleanup.
|
|
9
|
+
|
|
10
|
+
## Source patterns distilled
|
|
11
|
+
|
|
12
|
+
- pi-subagents worktree runner and cleanup patterns
|
|
13
|
+
- pi-crew worktrees: `src/worktree/worktree-manager.ts`, `src/worktree/cleanup.ts`, `src/worktree/branch-freshness.ts`
|
|
14
|
+
- Team runner workspace mode: `src/runtime/team-runner.ts`, workflow/team resource fields
|
|
15
|
+
|
|
16
|
+
## Rules
|
|
17
|
+
|
|
18
|
+
- Use worktree mode for parallel or risky code-changing tasks when the repository is clean enough and merge ownership is clear.
|
|
19
|
+
- Assign one owner per file/symbol/migration path to avoid conflict-heavy merges.
|
|
20
|
+
- Name branches/worktrees deterministically from run/task IDs; avoid user-controlled path fragments without sanitization.
|
|
21
|
+
- Before cleanup, check dirty state. Preserve dirty worktrees unless `force` is explicitly set.
|
|
22
|
+
- Record worktree paths and branch metadata in artifacts/events so the operator can inspect or recover.
|
|
23
|
+
- Do not run destructive git operations without explicit confirmation and evidence of target path containment.
|
|
24
|
+
|
|
25
|
+
## Anti-patterns
|
|
26
|
+
|
|
27
|
+
- Parallel editing the same file in multiple worktrees without a merge plan.
|
|
28
|
+
- Force-removing dirty worktrees by default.
|
|
29
|
+
- Reusing stale worktrees after the base branch has moved without freshness checks.
|
|
30
|
+
- Storing worktrees outside the intended contained workspace root.
|
|
31
|
+
|
|
32
|
+
## Verification
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
cd pi-crew
|
|
36
|
+
npx tsc --noEmit
|
|
37
|
+
node --experimental-strip-types --test test/integration/worktree-mode.test.ts test/unit/run-index.test.ts
|
|
38
|
+
npm test
|
|
39
|
+
```
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { AgentConfig, ResourceSource } from "./agent-config.ts";
|
|
4
|
-
import { loadConfig } from "../config/config.ts";
|
|
4
|
+
import { loadConfig, type LoadedPiTeamsConfig } from "../config/config.ts";
|
|
5
5
|
import { parseCsv, parseFrontmatter } from "../utils/frontmatter.ts";
|
|
6
6
|
import { packageRoot, projectCrewRoot, userPiRoot } from "../utils/paths.ts";
|
|
7
7
|
|
|
@@ -36,9 +36,9 @@ function parseAgentFile(filePath: string, source: ResourceSource): AgentConfig |
|
|
|
36
36
|
source,
|
|
37
37
|
filePath,
|
|
38
38
|
systemPrompt: body.trim(),
|
|
39
|
-
model: frontmatter.model || undefined,
|
|
39
|
+
model: frontmatter.model === "false" ? undefined : frontmatter.model || undefined,
|
|
40
40
|
fallbackModels: parseCsv(frontmatter.fallbackModels),
|
|
41
|
-
thinking: frontmatter.thinking || undefined,
|
|
41
|
+
thinking: frontmatter.thinking === "false" ? undefined : frontmatter.thinking || undefined,
|
|
42
42
|
tools: parseCsv(frontmatter.tools),
|
|
43
43
|
extensions: frontmatter.extensions === "" ? [] : parseCsv(frontmatter.extensions),
|
|
44
44
|
skills: parseCsv(frontmatter.skills ?? frontmatter.skill),
|
|
@@ -63,12 +63,12 @@ function readAgentDir(dir: string, source: ResourceSource): AgentConfig[] {
|
|
|
63
63
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
function applyAgentOverrides(agents: AgentConfig[], cwd: string): AgentConfig[] {
|
|
67
|
-
const loaded = loadConfig(cwd);
|
|
68
|
-
const
|
|
69
|
-
const overrides =
|
|
66
|
+
function applyAgentOverrides(agents: AgentConfig[], cwd: string, loadedConfig?: LoadedPiTeamsConfig): AgentConfig[] {
|
|
67
|
+
const loaded = loadedConfig ?? loadConfig(cwd);
|
|
68
|
+
const agentsConfig = loaded.config.agents;
|
|
69
|
+
const overrides = agentsConfig?.overrides ?? {};
|
|
70
70
|
return agents
|
|
71
|
-
.filter((agent) => !(
|
|
71
|
+
.filter((agent) => !(agentsConfig?.disableBuiltins && agent.source === "builtin"))
|
|
72
72
|
.map((agent) => {
|
|
73
73
|
const overrideEntry = Object.entries(overrides).find(([name]) => name.toLowerCase() === agent.name.toLowerCase());
|
|
74
74
|
if (!overrideEntry) return agent;
|
|
@@ -87,10 +87,11 @@ function applyAgentOverrides(agents: AgentConfig[], cwd: string): AgentConfig[]
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
export function discoverAgents(cwd: string): AgentDiscoveryResult {
|
|
90
|
+
const loaded = loadConfig(cwd);
|
|
90
91
|
return {
|
|
91
|
-
builtin: applyAgentOverrides(readAgentDir(path.join(packageRoot(), "agents"), "builtin"), cwd),
|
|
92
|
-
user: applyAgentOverrides(readAgentDir(path.join(userPiRoot(), "agents"), "user"), cwd),
|
|
93
|
-
project: applyAgentOverrides(readAgentDir(path.join(projectCrewRoot(cwd), "agents"), "project"), cwd),
|
|
92
|
+
builtin: applyAgentOverrides(readAgentDir(path.join(packageRoot(), "agents"), "builtin"), cwd, loaded),
|
|
93
|
+
user: applyAgentOverrides(readAgentDir(path.join(userPiRoot(), "agents"), "user"), cwd, loaded),
|
|
94
|
+
project: applyAgentOverrides(readAgentDir(path.join(projectCrewRoot(cwd), "agents"), "project"), cwd, loaded),
|
|
94
95
|
};
|
|
95
96
|
}
|
|
96
97
|
|
package/src/config/config.ts
CHANGED
|
@@ -4,7 +4,7 @@ import * as fs from "node:fs";
|
|
|
4
4
|
import * as os from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { PiTeamsAutonomyProfileSchema, PiTeamsConfigSchema } from "../schema/config-schema.ts";
|
|
7
|
-
import { projectCrewRoot } from "../utils/paths.ts";
|
|
7
|
+
import { projectCrewRoot, projectPiRoot } from "../utils/paths.ts";
|
|
8
8
|
|
|
9
9
|
export type PiTeamsAutonomyProfile = "manual" | "suggested" | "assisted" | "aggressive";
|
|
10
10
|
|
|
@@ -31,6 +31,7 @@ export interface CrewLimitsConfig {
|
|
|
31
31
|
export type CrewRuntimeMode = "auto" | "scaffold" | "child-process" | "live-session";
|
|
32
32
|
|
|
33
33
|
export type CompletionMutationGuardMode = "off" | "warn" | "fail";
|
|
34
|
+
export type EffectivenessGuardMode = "off" | "warn" | "block" | "fail";
|
|
34
35
|
|
|
35
36
|
export interface CrewRuntimeConfig {
|
|
36
37
|
mode?: CrewRuntimeMode;
|
|
@@ -44,6 +45,7 @@ export interface CrewRuntimeConfig {
|
|
|
44
45
|
groupJoinAckTimeoutMs?: number;
|
|
45
46
|
requirePlanApproval?: boolean;
|
|
46
47
|
completionMutationGuard?: CompletionMutationGuardMode;
|
|
48
|
+
effectivenessGuard?: EffectivenessGuardMode;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
export interface CrewControlConfig {
|
|
@@ -182,6 +184,11 @@ export interface UpdateConfigOptions {
|
|
|
182
184
|
}
|
|
183
185
|
|
|
184
186
|
export function configPath(): string {
|
|
187
|
+
const home = process.env.PI_TEAMS_HOME?.trim() || os.homedir();
|
|
188
|
+
return path.join(home, ".pi", "agent", "pi-crew.json");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function legacyConfigPath(): string {
|
|
185
192
|
const home = process.env.PI_TEAMS_HOME?.trim() || os.homedir();
|
|
186
193
|
return path.join(home, ".pi", "agent", "extensions", "pi-crew", "config.json");
|
|
187
194
|
}
|
|
@@ -195,7 +202,7 @@ export function projectConfigPath(cwd: string): string {
|
|
|
195
202
|
* This is a convenience path alongside the standard `config.json` in crewRoot.
|
|
196
203
|
*/
|
|
197
204
|
export function projectPiCrewJsonPath(cwd: string): string {
|
|
198
|
-
return path.join(cwd, "
|
|
205
|
+
return path.join(projectPiRoot(cwd), "pi-crew.json");
|
|
199
206
|
}
|
|
200
207
|
|
|
201
208
|
function withoutUndefined<T extends Record<string, unknown>>(value: T): Partial<T> {
|
|
@@ -509,6 +516,7 @@ function parseRuntimeConfig(value: unknown): CrewRuntimeConfig | undefined {
|
|
|
509
516
|
groupJoinAckTimeoutMs: parsePositiveInteger(obj.groupJoinAckTimeoutMs, 86_400_000),
|
|
510
517
|
requirePlanApproval: parseWithSchema(Type.Boolean(), obj.requirePlanApproval),
|
|
511
518
|
completionMutationGuard: parseWithSchema(Type.Union([Type.Literal("off"), Type.Literal("warn"), Type.Literal("fail")]), obj.completionMutationGuard),
|
|
519
|
+
effectivenessGuard: parseWithSchema(Type.Union([Type.Literal("off"), Type.Literal("warn"), Type.Literal("block"), Type.Literal("fail")]), obj.effectivenessGuard),
|
|
512
520
|
};
|
|
513
521
|
return Object.values(runtime).some((entry) => entry !== undefined) ? runtime : undefined;
|
|
514
522
|
}
|
|
@@ -727,35 +735,51 @@ function readConfigRecord(filePath: string): Record<string, unknown> {
|
|
|
727
735
|
return raw as Record<string, unknown>;
|
|
728
736
|
}
|
|
729
737
|
|
|
738
|
+
function readOptionalConfig(filePath: string): { exists: boolean; config: PiTeamsConfig; warnings: string[] } {
|
|
739
|
+
if (!fs.existsSync(filePath)) return { exists: false, config: {}, warnings: [] };
|
|
740
|
+
try {
|
|
741
|
+
const raw = readConfigRecord(filePath);
|
|
742
|
+
const parsed = parseConfigWithWarnings(raw);
|
|
743
|
+
return { exists: true, config: parsed.config, warnings: parsed.warnings.map((warning) => `${filePath}: ${warning}`) };
|
|
744
|
+
} catch (error) {
|
|
745
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
746
|
+
return { exists: true, config: {}, warnings: [`${filePath}: invalid config ignored: ${message}`] };
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
730
750
|
export function loadConfig(cwd?: string): LoadedPiTeamsConfig {
|
|
731
751
|
const filePath = configPath();
|
|
752
|
+
const legacyPath = legacyConfigPath();
|
|
732
753
|
const paths = cwd ? [filePath, projectConfigPath(cwd)] : [filePath];
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
754
|
+
const warnings: string[] = [];
|
|
755
|
+
const legacyConfig = readOptionalConfig(legacyPath);
|
|
756
|
+
if (legacyConfig.exists && legacyPath !== filePath) {
|
|
757
|
+
warnings.push(...legacyConfig.warnings);
|
|
758
|
+
paths.unshift(legacyPath);
|
|
759
|
+
}
|
|
760
|
+
const userConfig = readOptionalConfig(filePath);
|
|
761
|
+
warnings.push(...userConfig.warnings);
|
|
762
|
+
let config = mergeConfig(legacyConfig.exists && legacyPath !== filePath ? legacyConfig.config : {}, userConfig.config);
|
|
763
|
+
if (cwd) {
|
|
764
|
+
const projectPath = projectConfigPath(cwd);
|
|
765
|
+
const projectConfig = readOptionalConfig(projectPath);
|
|
766
|
+
if (projectConfig.exists) {
|
|
741
767
|
const projectSafeConfig = sanitizeProjectConfig(projectPath, config, projectConfig.config);
|
|
742
|
-
warnings.push(...projectConfig.warnings
|
|
768
|
+
warnings.push(...projectConfig.warnings, ...projectSafeConfig.warnings);
|
|
743
769
|
config = mergeConfig(config, projectSafeConfig.config);
|
|
744
|
-
// Also load .pi/pi-crew.json from project root if it exists
|
|
745
|
-
const piCrewJsonPath = projectPiCrewJsonPath(cwd);
|
|
746
|
-
if (fs.existsSync(piCrewJsonPath)) {
|
|
747
|
-
const piCrewJsonConfig = parseConfigWithWarnings(readConfigRecord(piCrewJsonPath));
|
|
748
|
-
const piCrewJsonSafeConfig = sanitizeProjectConfig(piCrewJsonPath, config, piCrewJsonConfig.config);
|
|
749
|
-
warnings.push(...piCrewJsonConfig.warnings.map((warning) => `${piCrewJsonPath}: ${warning}`), ...piCrewJsonSafeConfig.warnings);
|
|
750
|
-
config = mergeConfig(config, piCrewJsonSafeConfig.config);
|
|
751
|
-
paths.push(piCrewJsonPath);
|
|
752
|
-
}
|
|
753
770
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
771
|
+
// `.pi/pi-crew.json` is the project-owned override file. If present and valid,
|
|
772
|
+
// it may override all pi-crew config fields, including agents.overrides.
|
|
773
|
+
// If missing or invalid, it is ignored and defaults/user config remain effective.
|
|
774
|
+
const piCrewJsonPath = projectPiCrewJsonPath(cwd);
|
|
775
|
+
const piCrewJsonConfig = readOptionalConfig(piCrewJsonPath);
|
|
776
|
+
if (piCrewJsonConfig.exists) {
|
|
777
|
+
warnings.push(...piCrewJsonConfig.warnings);
|
|
778
|
+
config = mergeConfig(config, piCrewJsonConfig.config);
|
|
779
|
+
paths.push(piCrewJsonPath);
|
|
780
|
+
}
|
|
758
781
|
}
|
|
782
|
+
return { path: filePath, paths, config, warnings: warnings.length > 0 ? warnings : undefined };
|
|
759
783
|
}
|
|
760
784
|
|
|
761
785
|
export function updateConfig(patch: PiTeamsConfig, options: UpdateConfigOptions = {}): SavedPiTeamsConfig {
|
package/src/config/defaults.ts
CHANGED
|
@@ -53,6 +53,20 @@ export const DEFAULT_UI = {
|
|
|
53
53
|
refreshMs: 1000,
|
|
54
54
|
notifierIntervalMs: 5000,
|
|
55
55
|
widgetDefaultFrameMs: 1000,
|
|
56
|
+
widgetPlacement: "aboveEditor" as const,
|
|
57
|
+
widgetMaxLines: 8,
|
|
58
|
+
powerbar: true,
|
|
59
|
+
dashboardPlacement: "center" as const,
|
|
60
|
+
dashboardWidth: 72,
|
|
61
|
+
dashboardLiveRefreshMs: 1000,
|
|
62
|
+
autoOpenDashboard: false,
|
|
63
|
+
autoOpenDashboardForForegroundRuns: false,
|
|
64
|
+
showModel: true,
|
|
65
|
+
showTokens: true,
|
|
66
|
+
showTools: true,
|
|
67
|
+
transcriptTailBytes: 1024 * 1024,
|
|
68
|
+
mascotStyle: "cat" as const,
|
|
69
|
+
mascotEffect: "random" as const,
|
|
56
70
|
};
|
|
57
71
|
|
|
58
72
|
export const DEFAULT_NOTIFICATIONS = {
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { configPath as globalConfigPath } from "../config/config.ts";
|
|
4
|
+
import { DEFAULT_UI } from "../config/defaults.ts";
|
|
5
|
+
import { packageRoot, projectCrewRoot, projectPiRoot } from "../utils/paths.ts";
|
|
4
6
|
|
|
5
7
|
export interface ProjectInitOptions {
|
|
6
8
|
copyBuiltins?: boolean;
|
|
7
9
|
overwrite?: boolean;
|
|
10
|
+
configScope?: "global" | "project" | "none";
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
export interface ProjectInitResult {
|
|
@@ -13,6 +16,10 @@ export interface ProjectInitResult {
|
|
|
13
16
|
skippedFiles: string[];
|
|
14
17
|
gitignorePath: string;
|
|
15
18
|
gitignoreUpdated: boolean;
|
|
19
|
+
configPath: string;
|
|
20
|
+
configScope: "global" | "project" | "none";
|
|
21
|
+
configCreated: boolean;
|
|
22
|
+
configSkipped: boolean;
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
function ensureDir(dir: string, createdDirs: string[]): void {
|
|
@@ -24,6 +31,44 @@ function ensureDir(dir: string, createdDirs: string[]): void {
|
|
|
24
31
|
}
|
|
25
32
|
}
|
|
26
33
|
|
|
34
|
+
const DEFAULT_PI_CREW_CONFIG = {
|
|
35
|
+
// Keep generated config non-invasive: do not set runtime/limits defaults here.
|
|
36
|
+
// Those are provided by pi-crew internals and should not make a normal workflow block.
|
|
37
|
+
autonomous: {
|
|
38
|
+
enabled: true,
|
|
39
|
+
injectPolicy: true,
|
|
40
|
+
preferAsyncForLongTasks: false,
|
|
41
|
+
allowWorktreeSuggestion: true,
|
|
42
|
+
},
|
|
43
|
+
agents: {
|
|
44
|
+
overrides: {
|
|
45
|
+
explorer: { model: false, thinking: "off" },
|
|
46
|
+
writer: { model: false, thinking: "off" },
|
|
47
|
+
planner: { model: false, thinking: "medium" },
|
|
48
|
+
analyst: { model: false, thinking: "off" },
|
|
49
|
+
critic: { model: false, thinking: "low" },
|
|
50
|
+
executor: { model: false, thinking: "medium" },
|
|
51
|
+
reviewer: { model: false, thinking: "off" },
|
|
52
|
+
"security-reviewer": { model: false, thinking: "medium" },
|
|
53
|
+
"test-engineer": { model: false, thinking: "low" },
|
|
54
|
+
verifier: { model: false, thinking: "off" },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
ui: {
|
|
58
|
+
widgetPlacement: DEFAULT_UI.widgetPlacement,
|
|
59
|
+
widgetMaxLines: DEFAULT_UI.widgetMaxLines,
|
|
60
|
+
powerbar: DEFAULT_UI.powerbar,
|
|
61
|
+
dashboardPlacement: DEFAULT_UI.dashboardPlacement,
|
|
62
|
+
dashboardWidth: DEFAULT_UI.dashboardWidth,
|
|
63
|
+
dashboardLiveRefreshMs: DEFAULT_UI.dashboardLiveRefreshMs,
|
|
64
|
+
autoOpenDashboard: DEFAULT_UI.autoOpenDashboard,
|
|
65
|
+
autoOpenDashboardForForegroundRuns: DEFAULT_UI.autoOpenDashboardForForegroundRuns,
|
|
66
|
+
showModel: DEFAULT_UI.showModel,
|
|
67
|
+
showTokens: DEFAULT_UI.showTokens,
|
|
68
|
+
showTools: DEFAULT_UI.showTools,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
27
72
|
function copyBuiltinDir(kind: "agents" | "teams" | "workflows", targetDir: string, overwrite: boolean, copiedFiles: string[], skippedFiles: string[]): void {
|
|
28
73
|
const sourceDir = path.join(packageRoot(), kind);
|
|
29
74
|
if (!fs.existsSync(sourceDir)) return;
|
|
@@ -50,11 +95,26 @@ export function initializeProject(cwd: string, options: ProjectInitOptions = {})
|
|
|
50
95
|
const agentsDir = path.join(crewRoot, "agents");
|
|
51
96
|
const teamsDir = path.join(crewRoot, "teams");
|
|
52
97
|
const workflowsDir = path.join(crewRoot, "workflows");
|
|
98
|
+
const configScope = options.configScope ?? "global";
|
|
99
|
+
const configPath = configScope === "project" ? path.join(projectPiRoot(cwd), "pi-crew.json") : configScope === "global" ? globalConfigPath() : "";
|
|
53
100
|
ensureDir(agentsDir, createdDirs);
|
|
54
101
|
ensureDir(teamsDir, createdDirs);
|
|
55
102
|
ensureDir(workflowsDir, createdDirs);
|
|
56
103
|
ensureDir(path.join(crewRoot, "imports"), createdDirs);
|
|
57
104
|
|
|
105
|
+
let configCreated = false;
|
|
106
|
+
let configSkipped = false;
|
|
107
|
+
if (configPath) {
|
|
108
|
+
if (configScope === "project") ensureDir(path.dirname(configPath), createdDirs);
|
|
109
|
+
else fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
110
|
+
if (!fs.existsSync(configPath) || options.overwrite === true) {
|
|
111
|
+
fs.writeFileSync(configPath, `${JSON.stringify(DEFAULT_PI_CREW_CONFIG, null, 2)}\n`, "utf-8");
|
|
112
|
+
configCreated = true;
|
|
113
|
+
} else {
|
|
114
|
+
configSkipped = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
58
118
|
if (options.copyBuiltins) {
|
|
59
119
|
copyBuiltinDir("agents", agentsDir, options.overwrite === true, copiedFiles, skippedFiles);
|
|
60
120
|
copyBuiltinDir("teams", teamsDir, options.overwrite === true, copiedFiles, skippedFiles);
|
|
@@ -72,5 +132,5 @@ export function initializeProject(cwd: string, options: ProjectInitOptions = {})
|
|
|
72
132
|
gitignoreUpdated = true;
|
|
73
133
|
}
|
|
74
134
|
|
|
75
|
-
return { createdDirs, copiedFiles, skippedFiles, gitignorePath, gitignoreUpdated };
|
|
135
|
+
return { createdDirs, copiedFiles, skippedFiles, gitignorePath, gitignoreUpdated, configPath, configScope, configCreated, configSkipped };
|
|
76
136
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { loadConfig } from "../config/config.ts";
|
|
5
6
|
import { registerAutonomousPolicy } from "./autonomous-policy.ts";
|
|
6
7
|
import { startAsyncRunNotifier, stopAsyncRunNotifier, type AsyncNotifierState } from "./async-notifier.ts";
|
|
@@ -40,6 +41,7 @@ import { detectInterruptedRuns } from "../runtime/crash-recovery.ts";
|
|
|
40
41
|
import { DeliveryCoordinator } from "../runtime/delivery-coordinator.ts";
|
|
41
42
|
import { OverflowRecoveryTracker } from "../runtime/overflow-recovery.ts";
|
|
42
43
|
import { tryRegisterSessionCleanup } from "../runtime/session-resources.ts";
|
|
44
|
+
import { createSessionSnapshot } from "../runtime/session-snapshot.ts";
|
|
43
45
|
import { initI18n } from "../i18n.ts";
|
|
44
46
|
|
|
45
47
|
export { __test__subagentSpawnParams };
|
|
@@ -252,18 +254,18 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
252
254
|
const openLiveSidebar = (ctx: ExtensionContext, runId: string): void => {
|
|
253
255
|
const uiConfig = loadConfig(ctx.cwd).config.ui;
|
|
254
256
|
const autoOpen = uiConfig?.autoOpenDashboard === true;
|
|
255
|
-
const foregroundAutoOpen = uiConfig?.autoOpenDashboardForForegroundRuns
|
|
256
|
-
if (!ctx.hasUI || !autoOpen || !foregroundAutoOpen || (uiConfig?.dashboardPlacement ??
|
|
257
|
+
const foregroundAutoOpen = uiConfig?.autoOpenDashboardForForegroundRuns ?? DEFAULT_UI.autoOpenDashboardForForegroundRuns;
|
|
258
|
+
if (!ctx.hasUI || !autoOpen || !foregroundAutoOpen || (uiConfig?.dashboardPlacement ?? DEFAULT_UI.dashboardPlacement) !== "right") return;
|
|
257
259
|
if (liveSidebarRunId === runId) return;
|
|
258
260
|
liveSidebarRunId = runId;
|
|
259
|
-
const widgetPlacement = uiConfig?.widgetPlacement ??
|
|
261
|
+
const widgetPlacement = uiConfig?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
|
|
260
262
|
setExtensionWidget(ctx, "pi-crew", undefined, { placement: widgetPlacement });
|
|
261
263
|
setExtensionWidget(ctx, "pi-crew-active", undefined, { placement: widgetPlacement });
|
|
262
264
|
widgetState.lastVisibility = "hidden";
|
|
263
265
|
widgetState.lastPlacement = widgetPlacement;
|
|
264
266
|
widgetState.lastKey = "pi-crew-active";
|
|
265
267
|
widgetState.model = undefined;
|
|
266
|
-
const width = Math.min(90, Math.max(40, uiConfig?.dashboardWidth ??
|
|
268
|
+
const width = Math.min(90, Math.max(40, uiConfig?.dashboardWidth ?? DEFAULT_UI.dashboardWidth));
|
|
267
269
|
void showCustom<undefined>(ctx, (_tui, theme, _keybindings, done) => new LiveRunSidebar({ cwd: ctx.cwd, runId, done, theme, config: uiConfig, snapshotCache: getRunSnapshotCache(ctx.cwd) }), {
|
|
268
270
|
overlay: true,
|
|
269
271
|
overlayOptions: { width, minWidth: 40, maxHeight: "100%", anchor: "top-right", offsetX: 0, offsetY: 0, margin: { top: 0, right: 0, bottom: 0, left: 0 }, visible: (termWidth: number) => termWidth >= 100 },
|
|
@@ -397,7 +399,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
397
399
|
configureNotifications(ctx);
|
|
398
400
|
configureObservability(ctx);
|
|
399
401
|
configureDeliveryCoordinator();
|
|
400
|
-
const sessionId = (ctx as unknown as Record<string, unknown>).sessionId;
|
|
402
|
+
const sessionId = ctx.sessionManager?.getSessionId?.() ?? (ctx as unknown as Record<string, unknown>).sessionId;
|
|
401
403
|
if (typeof sessionId === "string" && sessionId) deliveryCoordinator?.activate(sessionId);
|
|
402
404
|
tryRegisterSessionCleanup(pi, () => { terminateActiveChildPiProcesses(); cleanupRuntime(); });
|
|
403
405
|
registerPiCrewPowerbarSegments(pi.events, loadedConfig.config.ui);
|
|
@@ -459,7 +461,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
459
461
|
const snapshotCache = lastFrameSnapshotCache ?? getRunSnapshotCache(currentCtx.cwd);
|
|
460
462
|
const manifests = lastPreloadedManifests.length > 0 ? lastPreloadedManifests : activeCache.list(20);
|
|
461
463
|
if (liveSidebarRunId) {
|
|
462
|
-
const placement = config?.widgetPlacement ??
|
|
464
|
+
const placement = config?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
|
|
463
465
|
if (widgetState.lastVisibility !== "hidden" || widgetState.lastPlacement !== placement) {
|
|
464
466
|
setExtensionWidget(currentCtx, "pi-crew", undefined, { placement });
|
|
465
467
|
setExtensionWidget(currentCtx, "pi-crew-active", undefined, { placement });
|
|
@@ -499,7 +501,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
499
501
|
}
|
|
500
502
|
};
|
|
501
503
|
|
|
502
|
-
const fallbackMs = loadedConfig.config.ui?.dashboardLiveRefreshMs ??
|
|
504
|
+
const fallbackMs = loadedConfig.config.ui?.dashboardLiveRefreshMs ?? DEFAULT_UI.refreshMs;
|
|
503
505
|
renderScheduler = new RenderScheduler(pi.events, renderTick, {
|
|
504
506
|
fallbackMs,
|
|
505
507
|
onInvalidate: () => getRunSnapshotCache(ctx.cwd).invalidate(),
|
|
@@ -509,8 +511,14 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
509
511
|
});
|
|
510
512
|
pi.on("session_before_switch", () => {
|
|
511
513
|
sessionGeneration++;
|
|
512
|
-
// Phase 11b: Capture state before session switch
|
|
513
514
|
const pendingCount = deliveryCoordinator?.getPendingCount() ?? 0;
|
|
515
|
+
try {
|
|
516
|
+
const activeRuns = currentCtx ? getManifestCache(currentCtx.cwd).list(50).filter((run) => run.status === "running" || run.status === "queued" || run.status === "blocked") : [];
|
|
517
|
+
const snapshot = createSessionSnapshot(activeRuns, pendingCount, sessionGeneration);
|
|
518
|
+
if (pendingCount > 0 || snapshot.activeRunIds.length > 0) logInternalError("register.session-before-switch", undefined, JSON.stringify(snapshot));
|
|
519
|
+
} catch (error) {
|
|
520
|
+
logInternalError("register.session-before-switch.snapshot", error);
|
|
521
|
+
}
|
|
514
522
|
if (pendingCount > 0) {
|
|
515
523
|
logInternalError("register.session-before-switch", `Switching session with ${pendingCount} pending deliveries`);
|
|
516
524
|
}
|
|
@@ -523,8 +531,9 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
523
531
|
// Phase 11a: Dynamic resource discovery — inject pi-crew skill paths.
|
|
524
532
|
try {
|
|
525
533
|
pi.on("resources_discover", () => {
|
|
526
|
-
const
|
|
527
|
-
const
|
|
534
|
+
const sessionCwd = currentCtx?.cwd ?? process.cwd();
|
|
535
|
+
const skillDir = path.resolve(sessionCwd, "skills");
|
|
536
|
+
const extSkillDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "skills");
|
|
528
537
|
const paths: string[] = [];
|
|
529
538
|
if (fs.existsSync(extSkillDir)) paths.push(extSkillDir);
|
|
530
539
|
if (skillDir !== extSkillDir && fs.existsSync(skillDir)) paths.push(skillDir);
|