pi-crew 0.1.45 → 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/README.md +5 -5
- package/agents/analyst.md +1 -1
- package/agents/critic.md +1 -1
- package/agents/executor.md +1 -1
- package/agents/explorer.md +1 -1
- package/agents/planner.md +1 -1
- package/agents/reviewer.md +1 -1
- package/agents/security-reviewer.md +1 -1
- package/agents/test-engineer.md +1 -1
- package/agents/verifier.md +1 -1
- package/agents/writer.md +1 -1
- package/docs/next-upgrade-roadmap.md +733 -0
- 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-awesome-agent-skills-distillation.md +100 -0
- package/docs/research-extension-examples.md +297 -297
- package/docs/research-extension-system.md +324 -324
- package/docs/research-oh-my-pi-distillation.md +322 -0
- 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 -83
- package/docs/usage.md +3 -3
- package/index.ts +6 -6
- 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/agent-serializer.ts +34 -34
- 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/cross-extension-rpc.ts +82 -82
- package/src/extension/project-init.ts +62 -2
- package/src/extension/register.ts +11 -9
- package/src/extension/registration/commands.ts +32 -25
- package/src/extension/registration/compaction-guard.ts +125 -125
- package/src/extension/registration/subagent-helpers.ts +8 -0
- package/src/extension/registration/subagent-tools.ts +149 -148
- package/src/extension/registration/team-tool.ts +8 -6
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-index.ts +13 -5
- package/src/extension/run-maintenance.ts +62 -43
- package/src/extension/team-tool/api.ts +25 -8
- package/src/extension/team-tool/cancel.ts +33 -4
- package/src/extension/team-tool/context.ts +5 -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/lifecycle-actions.ts +91 -79
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/respond.ts +37 -17
- package/src/extension/team-tool/run.ts +52 -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/i18n.ts +184 -184
- package/src/observability/event-to-metric.ts +8 -1
- package/src/observability/exporters/otlp-exporter.ts +77 -77
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/agent-control.ts +63 -63
- 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/attention-events.ts +28 -28
- package/src/runtime/background-runner.ts +59 -53
- package/src/runtime/cancellation.ts +51 -0
- package/src/runtime/child-pi.ts +457 -444
- package/src/runtime/completion-guard.ts +190 -190
- package/src/runtime/crash-recovery.ts +1 -0
- package/src/runtime/crew-agent-records.ts +38 -6
- package/src/runtime/deadletter.ts +1 -0
- package/src/runtime/delivery-coordinator.ts +46 -25
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/effectiveness.ts +76 -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 -87
- package/src/runtime/live-agent-manager.ts +103 -85
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-session-runtime.ts +309 -305
- package/src/runtime/manifest-cache.ts +17 -2
- package/src/runtime/model-fallback.ts +6 -4
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/pi-args.ts +18 -3
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/process-status.ts +5 -1
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/retry-executor.ts +81 -64
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/runtime-resolver.ts +22 -6
- 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/skill-instructions.ts +222 -0
- package/src/runtime/stale-reconciler.ts +4 -14
- package/src/runtime/subagent-manager.ts +3 -0
- package/src/runtime/supervisor-contact.ts +59 -59
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-output-context.ts +127 -127
- package/src/runtime/task-runner/capabilities.ts +78 -0
- package/src/runtime/task-runner/live-executor.ts +105 -101
- package/src/runtime/task-runner/progress.ts +119 -119
- 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/result-utils.ts +14 -14
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/task-runner.ts +44 -5
- package/src/runtime/team-runner.ts +78 -15
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- 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 +8 -1
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +44 -2
- 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-config.ts +1 -0
- package/src/teams/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/crew-footer.ts +101 -101
- package/src/ui/crew-select-list.ts +111 -111
- package/src/ui/crew-widget.ts +4 -3
- package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
- package/src/ui/dashboard-panes/progress-pane.ts +2 -0
- package/src/ui/dynamic-border.ts +25 -25
- package/src/ui/layout-primitives.ts +106 -106
- package/src/ui/loaders.ts +158 -158
- package/src/ui/render-diff.ts +119 -119
- package/src/ui/render-scheduler.ts +143 -143
- package/src/ui/run-snapshot-cache.ts +10 -2
- package/src/ui/snapshot-types.ts +2 -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/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 +12 -12
- package/src/utils/names.ts +27 -27
- package/src/utils/paths.ts +4 -2
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/sleep.ts +32 -32
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/workflows/workflow-config.ts +1 -0
- package/src/worktree/branch-freshness.ts +45 -45
- 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
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: systematic-debugging
|
|
3
|
+
description: Use when encountering a bug, test failure, blocked run, provider error, stale state, crash, or unexpected behavior before proposing fixes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# systematic-debugging
|
|
7
|
+
|
|
8
|
+
Core principle: no fixes without root-cause investigation first. Symptom patches create new bugs and hide the real failure.
|
|
9
|
+
|
|
10
|
+
Distilled from detailed reads of systematic-debugging, root-cause tracing, TDD, and error-analysis skill patterns.
|
|
11
|
+
|
|
12
|
+
## Four Phases
|
|
13
|
+
|
|
14
|
+
### 1. Root Cause Investigation
|
|
15
|
+
|
|
16
|
+
Before any fix:
|
|
17
|
+
|
|
18
|
+
- read error messages, stack traces, failing assertions, task status, and logs completely;
|
|
19
|
+
- reproduce narrowly and record the exact command/steps;
|
|
20
|
+
- check recent diffs, commits, config changes, dependency changes, and environment differences;
|
|
21
|
+
- trace data/control flow across component boundaries;
|
|
22
|
+
- add temporary diagnostics only when they answer a specific question.
|
|
23
|
+
|
|
24
|
+
For pi-crew, trace:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
user/tool params → config resolution → team/workflow/agent discovery → model/runtime routing → child args/env → state/events/artifacts → status/UI
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 2. Pattern Analysis
|
|
31
|
+
|
|
32
|
+
- Find a similar working path in the codebase.
|
|
33
|
+
- Compare working vs broken behavior field-by-field.
|
|
34
|
+
- Identify dependencies: config home, project root markers, env vars, locks, stale caches, provider model capabilities.
|
|
35
|
+
- Do not assume small differences are irrelevant.
|
|
36
|
+
|
|
37
|
+
### 3. Hypothesis and Test
|
|
38
|
+
|
|
39
|
+
- State one hypothesis: “I think X is the root cause because Y.”
|
|
40
|
+
- Test one variable at a time with the smallest read-only probe or targeted test.
|
|
41
|
+
- If wrong, discard the hypothesis instead of piling on fixes.
|
|
42
|
+
- After three failed fixes, question architecture or assumptions before continuing.
|
|
43
|
+
|
|
44
|
+
### 4. Implementation
|
|
45
|
+
|
|
46
|
+
- Add or identify a failing regression test when practical.
|
|
47
|
+
- Fix the root cause, not the symptom.
|
|
48
|
+
- Avoid “while I’m here” refactors.
|
|
49
|
+
- Verify targeted behavior, then broader gates.
|
|
50
|
+
|
|
51
|
+
## Evidence to Collect
|
|
52
|
+
|
|
53
|
+
- failing command and exit code;
|
|
54
|
+
- relevant manifest/tasks/events/mailbox files;
|
|
55
|
+
- effective config paths and redacted config;
|
|
56
|
+
- child Pi args/env after redaction;
|
|
57
|
+
- git diff and recent commits;
|
|
58
|
+
- provider/model/thinking resolution;
|
|
59
|
+
- async timing/race indicators.
|
|
60
|
+
|
|
61
|
+
## Anti-patterns
|
|
62
|
+
|
|
63
|
+
- Fixing before reproducing.
|
|
64
|
+
- Assuming real user global config cannot pollute tests.
|
|
65
|
+
- Treating provider errors as only transient network failures.
|
|
66
|
+
- Removing guards because they reveal a blocked state.
|
|
67
|
+
- Editing unrelated layers before checking the hypothesis.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ui-render-performance
|
|
3
|
+
description: Non-blocking Pi TUI render workflow. Use when changing widgets, powerbar/statusbar segments, dashboard panes, overlays, snapshot caches, or live UI refresh behavior.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ui-render-performance
|
|
7
|
+
|
|
8
|
+
Use this skill for Pi/pi-crew TUI work.
|
|
9
|
+
|
|
10
|
+
## Source patterns distilled
|
|
11
|
+
|
|
12
|
+
- Pi TUI is synchronous immediate-mode/string rendering: `source/pi-mono/packages/coding-agent/src/modes/interactive/interactive-mode.ts`
|
|
13
|
+
- Pi extension examples use event-driven state updates, not render-time loading.
|
|
14
|
+
- pi-crew UI: `src/extension/register.ts`, `src/ui/run-dashboard.ts`, `src/ui/run-snapshot-cache.ts`, `src/ui/crew-widget.ts`, `src/ui/powerbar-publisher.ts`, `src/ui/render-scheduler.ts`
|
|
15
|
+
|
|
16
|
+
## Rules
|
|
17
|
+
|
|
18
|
+
- Treat every `render(width)` and widget/powerbar update as a hot synchronous path.
|
|
19
|
+
- Render from in-memory snapshots only. Preload config, manifests, snapshots, agents, and mailbox counts asynchronously.
|
|
20
|
+
- Use `RenderScheduler.schedule()` to coalesce renders; avoid direct repeated rendering.
|
|
21
|
+
- Prefer `snapshotCache.get(runId)` in render paths. If a sync fallback is unavoidable, classify it as first-load/rare and document why.
|
|
22
|
+
- Keep dashboard panes pure: accept a snapshot/model and format strings; do not call `fs.readFileSync`, `fs.readdirSync`, `fs.statSync`, or network APIs from pane render methods.
|
|
23
|
+
- On session switch, cancel timers and ensure in-flight async preloads cannot update stale session UI.
|
|
24
|
+
- Watch TTL interactions: a preload interval shorter than cache TTL prevents render-time refresh gaps.
|
|
25
|
+
|
|
26
|
+
## Anti-patterns
|
|
27
|
+
|
|
28
|
+
- Do not call `loadConfig()`, `manifestCache.list()`, or `refreshIfStale()` repeatedly inside `renderTick()` unless backed by preloaded frame data.
|
|
29
|
+
- Do not do large JSON parsing or directory scans inside widget render/update functions.
|
|
30
|
+
- Do not show stale health warnings for completed/cancelled/failed runs.
|
|
31
|
+
|
|
32
|
+
## Verification
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
cd pi-crew
|
|
36
|
+
npx tsc --noEmit
|
|
37
|
+
node --experimental-strip-types --test test/unit/run-snapshot-cache.test.ts test/unit/crew-widget.test.ts test/unit/powerbar-publisher.test.ts test/unit/run-dashboard.test.ts
|
|
38
|
+
npm test
|
|
39
|
+
```
|
|
@@ -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,34 +1,34 @@
|
|
|
1
|
-
import type { AgentConfig } from "./agent-config.ts";
|
|
2
|
-
|
|
3
|
-
function line(key: string, value: string | boolean | string[] | undefined): string | undefined {
|
|
4
|
-
if (value === undefined) return undefined;
|
|
5
|
-
if (Array.isArray(value)) return `${key}: ${value.join(", ")}`;
|
|
6
|
-
return `${key}: ${String(value)}`;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function serializeAgent(agent: AgentConfig): string {
|
|
10
|
-
const lines = [
|
|
11
|
-
"---",
|
|
12
|
-
`name: ${agent.name}`,
|
|
13
|
-
`description: ${agent.description}`,
|
|
14
|
-
line("model", agent.model),
|
|
15
|
-
line("fallbackModels", agent.fallbackModels),
|
|
16
|
-
line("thinking", agent.thinking),
|
|
17
|
-
line("tools", agent.tools),
|
|
18
|
-
agent.extensions !== undefined ? line("extensions", agent.extensions) ?? "extensions:" : undefined,
|
|
19
|
-
line("skills", agent.skills),
|
|
20
|
-
line("systemPromptMode", agent.systemPromptMode),
|
|
21
|
-
line("inheritProjectContext", agent.inheritProjectContext),
|
|
22
|
-
line("inheritSkills", agent.inheritSkills),
|
|
23
|
-
line("triggers", agent.routing?.triggers),
|
|
24
|
-
line("useWhen", agent.routing?.useWhen),
|
|
25
|
-
line("avoidWhen", agent.routing?.avoidWhen),
|
|
26
|
-
line("cost", agent.routing?.cost),
|
|
27
|
-
line("category", agent.routing?.category),
|
|
28
|
-
"---",
|
|
29
|
-
"",
|
|
30
|
-
agent.systemPrompt.trim(),
|
|
31
|
-
"",
|
|
32
|
-
].filter((entry): entry is string => entry !== undefined);
|
|
33
|
-
return lines.join("\n");
|
|
34
|
-
}
|
|
1
|
+
import type { AgentConfig } from "./agent-config.ts";
|
|
2
|
+
|
|
3
|
+
function line(key: string, value: string | boolean | string[] | undefined): string | undefined {
|
|
4
|
+
if (value === undefined) return undefined;
|
|
5
|
+
if (Array.isArray(value)) return `${key}: ${value.join(", ")}`;
|
|
6
|
+
return `${key}: ${String(value)}`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function serializeAgent(agent: AgentConfig): string {
|
|
10
|
+
const lines = [
|
|
11
|
+
"---",
|
|
12
|
+
`name: ${agent.name}`,
|
|
13
|
+
`description: ${agent.description}`,
|
|
14
|
+
line("model", agent.model),
|
|
15
|
+
line("fallbackModels", agent.fallbackModels),
|
|
16
|
+
line("thinking", agent.thinking),
|
|
17
|
+
line("tools", agent.tools),
|
|
18
|
+
agent.extensions !== undefined ? line("extensions", agent.extensions) ?? "extensions:" : undefined,
|
|
19
|
+
line("skills", agent.skills),
|
|
20
|
+
line("systemPromptMode", agent.systemPromptMode),
|
|
21
|
+
line("inheritProjectContext", agent.inheritProjectContext),
|
|
22
|
+
line("inheritSkills", agent.inheritSkills),
|
|
23
|
+
line("triggers", agent.routing?.triggers),
|
|
24
|
+
line("useWhen", agent.routing?.useWhen),
|
|
25
|
+
line("avoidWhen", agent.routing?.avoidWhen),
|
|
26
|
+
line("cost", agent.routing?.cost),
|
|
27
|
+
line("category", agent.routing?.category),
|
|
28
|
+
"---",
|
|
29
|
+
"",
|
|
30
|
+
agent.systemPrompt.trim(),
|
|
31
|
+
"",
|
|
32
|
+
].filter((entry): entry is string => entry !== undefined);
|
|
33
|
+
return lines.join("\n");
|
|
34
|
+
}
|
|
@@ -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,82 +1,82 @@
|
|
|
1
|
-
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import type { TeamToolParamsValue } from "../schema/team-tool-schema.ts";
|
|
3
|
-
import { handleTeamTool } from "./team-tool.ts";
|
|
4
|
-
import { parseLiveControlRealtimeMessage, publishLiveControlRealtime } from "../runtime/live-control-realtime.ts";
|
|
5
|
-
|
|
6
|
-
export interface EventBusLike {
|
|
7
|
-
on(event: string, handler: (data: unknown) => void): (() => void) | void;
|
|
8
|
-
emit(event: string, data: unknown): void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export type RpcReply<T = unknown> = { success: true; data?: T } | { success: false; error: string };
|
|
12
|
-
export const PI_CREW_RPC_VERSION = 1;
|
|
13
|
-
|
|
14
|
-
export interface PiCrewRpcHandle {
|
|
15
|
-
unsubscribe(): void;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function requestId(raw: unknown): string | undefined {
|
|
19
|
-
return raw && typeof raw === "object" && !Array.isArray(raw) && typeof (raw as { requestId?: unknown }).requestId === "string" ? (raw as { requestId: string }).requestId : undefined;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function reply(events: EventBusLike, channel: string, id: string | undefined, payload: RpcReply): void {
|
|
23
|
-
if (!id) return;
|
|
24
|
-
events.emit(`${channel}:reply:${id}`, payload);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function textOf(result: Awaited<ReturnType<typeof handleTeamTool>>): string {
|
|
28
|
-
return result.content?.map((item) => item.type === "text" ? item.text : "").join("\n") ?? "";
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function on(events: EventBusLike, channel: string, handler: (raw: unknown) => void): () => void {
|
|
32
|
-
const unsub = events.on(channel, handler);
|
|
33
|
-
return typeof unsub === "function" ? unsub : () => {};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function registerPiCrewRpc(events: EventBusLike | undefined, getCtx: () => ExtensionContext | undefined): PiCrewRpcHandle | undefined {
|
|
37
|
-
if (!events) return undefined;
|
|
38
|
-
const unsubs = [
|
|
39
|
-
on(events, "pi-crew:rpc:ping", (raw) => reply(events, "pi-crew:rpc:ping", requestId(raw), { success: true, data: { version: PI_CREW_RPC_VERSION } })),
|
|
40
|
-
on(events, "pi-crew:rpc:run", async (raw) => {
|
|
41
|
-
const id = requestId(raw);
|
|
42
|
-
try {
|
|
43
|
-
const ctx = getCtx();
|
|
44
|
-
if (!ctx) throw new Error("No active pi-crew session context.");
|
|
45
|
-
const params: TeamToolParamsValue = raw && typeof raw === "object" && !Array.isArray(raw) ? { ...(raw as object), action: "run" } as TeamToolParamsValue : { action: "run" };
|
|
46
|
-
const result = await handleTeamTool(params, ctx);
|
|
47
|
-
reply(events, "pi-crew:rpc:run", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: result.details });
|
|
48
|
-
} catch (error) {
|
|
49
|
-
reply(events, "pi-crew:rpc:run", id, { success: false, error: error instanceof Error ? error.message : String(error) });
|
|
50
|
-
}
|
|
51
|
-
}),
|
|
52
|
-
on(events, "pi-crew:rpc:status", async (raw) => {
|
|
53
|
-
const id = requestId(raw);
|
|
54
|
-
try {
|
|
55
|
-
const ctx = getCtx();
|
|
56
|
-
if (!ctx) throw new Error("No active pi-crew session context.");
|
|
57
|
-
const runId = raw && typeof raw === "object" && !Array.isArray(raw) ? (raw as { runId?: string }).runId : undefined;
|
|
58
|
-
const result = await handleTeamTool({ action: "status", runId }, ctx);
|
|
59
|
-
reply(events, "pi-crew:rpc:status", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: { text: textOf(result), details: result.details } });
|
|
60
|
-
} catch (error) {
|
|
61
|
-
reply(events, "pi-crew:rpc:status", id, { success: false, error: error instanceof Error ? error.message : String(error) });
|
|
62
|
-
}
|
|
63
|
-
}),
|
|
64
|
-
on(events, "pi-crew:live-control", (raw) => {
|
|
65
|
-
const request = parseLiveControlRealtimeMessage(raw);
|
|
66
|
-
if (request) publishLiveControlRealtime(request);
|
|
67
|
-
}),
|
|
68
|
-
on(events, "pi-crew:rpc:live-control", async (raw) => {
|
|
69
|
-
const id = requestId(raw);
|
|
70
|
-
try {
|
|
71
|
-
const ctx = getCtx();
|
|
72
|
-
if (!ctx) throw new Error("No active pi-crew session context.");
|
|
73
|
-
const obj = raw && typeof raw === "object" && !Array.isArray(raw) ? raw as Record<string, unknown> : {};
|
|
74
|
-
const result = await handleTeamTool({ action: "api", runId: typeof obj.runId === "string" ? obj.runId : undefined, config: { operation: typeof obj.operation === "string" ? obj.operation : "steer-agent", agentId: obj.agentId, message: obj.message, prompt: obj.prompt } }, ctx);
|
|
75
|
-
reply(events, "pi-crew:rpc:live-control", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: { text: textOf(result), details: result.details } });
|
|
76
|
-
} catch (error) {
|
|
77
|
-
reply(events, "pi-crew:rpc:live-control", id, { success: false, error: error instanceof Error ? error.message : String(error) });
|
|
78
|
-
}
|
|
79
|
-
}),
|
|
80
|
-
];
|
|
81
|
-
return { unsubscribe: () => unsubs.forEach((unsub) => unsub()) };
|
|
82
|
-
}
|
|
1
|
+
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import type { TeamToolParamsValue } from "../schema/team-tool-schema.ts";
|
|
3
|
+
import { handleTeamTool } from "./team-tool.ts";
|
|
4
|
+
import { parseLiveControlRealtimeMessage, publishLiveControlRealtime } from "../runtime/live-control-realtime.ts";
|
|
5
|
+
|
|
6
|
+
export interface EventBusLike {
|
|
7
|
+
on(event: string, handler: (data: unknown) => void): (() => void) | void;
|
|
8
|
+
emit(event: string, data: unknown): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type RpcReply<T = unknown> = { success: true; data?: T } | { success: false; error: string };
|
|
12
|
+
export const PI_CREW_RPC_VERSION = 1;
|
|
13
|
+
|
|
14
|
+
export interface PiCrewRpcHandle {
|
|
15
|
+
unsubscribe(): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function requestId(raw: unknown): string | undefined {
|
|
19
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) && typeof (raw as { requestId?: unknown }).requestId === "string" ? (raw as { requestId: string }).requestId : undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function reply(events: EventBusLike, channel: string, id: string | undefined, payload: RpcReply): void {
|
|
23
|
+
if (!id) return;
|
|
24
|
+
events.emit(`${channel}:reply:${id}`, payload);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function textOf(result: Awaited<ReturnType<typeof handleTeamTool>>): string {
|
|
28
|
+
return result.content?.map((item) => item.type === "text" ? item.text : "").join("\n") ?? "";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function on(events: EventBusLike, channel: string, handler: (raw: unknown) => void): () => void {
|
|
32
|
+
const unsub = events.on(channel, handler);
|
|
33
|
+
return typeof unsub === "function" ? unsub : () => {};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function registerPiCrewRpc(events: EventBusLike | undefined, getCtx: () => ExtensionContext | undefined): PiCrewRpcHandle | undefined {
|
|
37
|
+
if (!events) return undefined;
|
|
38
|
+
const unsubs = [
|
|
39
|
+
on(events, "pi-crew:rpc:ping", (raw) => reply(events, "pi-crew:rpc:ping", requestId(raw), { success: true, data: { version: PI_CREW_RPC_VERSION } })),
|
|
40
|
+
on(events, "pi-crew:rpc:run", async (raw) => {
|
|
41
|
+
const id = requestId(raw);
|
|
42
|
+
try {
|
|
43
|
+
const ctx = getCtx();
|
|
44
|
+
if (!ctx) throw new Error("No active pi-crew session context.");
|
|
45
|
+
const params: TeamToolParamsValue = raw && typeof raw === "object" && !Array.isArray(raw) ? { ...(raw as object), action: "run" } as TeamToolParamsValue : { action: "run" };
|
|
46
|
+
const result = await handleTeamTool(params, ctx);
|
|
47
|
+
reply(events, "pi-crew:rpc:run", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: result.details });
|
|
48
|
+
} catch (error) {
|
|
49
|
+
reply(events, "pi-crew:rpc:run", id, { success: false, error: error instanceof Error ? error.message : String(error) });
|
|
50
|
+
}
|
|
51
|
+
}),
|
|
52
|
+
on(events, "pi-crew:rpc:status", async (raw) => {
|
|
53
|
+
const id = requestId(raw);
|
|
54
|
+
try {
|
|
55
|
+
const ctx = getCtx();
|
|
56
|
+
if (!ctx) throw new Error("No active pi-crew session context.");
|
|
57
|
+
const runId = raw && typeof raw === "object" && !Array.isArray(raw) ? (raw as { runId?: string }).runId : undefined;
|
|
58
|
+
const result = await handleTeamTool({ action: "status", runId }, ctx);
|
|
59
|
+
reply(events, "pi-crew:rpc:status", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: { text: textOf(result), details: result.details } });
|
|
60
|
+
} catch (error) {
|
|
61
|
+
reply(events, "pi-crew:rpc:status", id, { success: false, error: error instanceof Error ? error.message : String(error) });
|
|
62
|
+
}
|
|
63
|
+
}),
|
|
64
|
+
on(events, "pi-crew:live-control", (raw) => {
|
|
65
|
+
const request = parseLiveControlRealtimeMessage(raw);
|
|
66
|
+
if (request) publishLiveControlRealtime(request);
|
|
67
|
+
}),
|
|
68
|
+
on(events, "pi-crew:rpc:live-control", async (raw) => {
|
|
69
|
+
const id = requestId(raw);
|
|
70
|
+
try {
|
|
71
|
+
const ctx = getCtx();
|
|
72
|
+
if (!ctx) throw new Error("No active pi-crew session context.");
|
|
73
|
+
const obj = raw && typeof raw === "object" && !Array.isArray(raw) ? raw as Record<string, unknown> : {};
|
|
74
|
+
const result = await handleTeamTool({ action: "api", runId: typeof obj.runId === "string" ? obj.runId : undefined, config: { operation: typeof obj.operation === "string" ? obj.operation : "steer-agent", agentId: obj.agentId, message: obj.message, prompt: obj.prompt } }, ctx);
|
|
75
|
+
reply(events, "pi-crew:rpc:live-control", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: { text: textOf(result), details: result.details } });
|
|
76
|
+
} catch (error) {
|
|
77
|
+
reply(events, "pi-crew:rpc:live-control", id, { success: false, error: error instanceof Error ? error.message : String(error) });
|
|
78
|
+
}
|
|
79
|
+
}),
|
|
80
|
+
];
|
|
81
|
+
return { unsubscribe: () => unsubs.forEach((unsub) => unsub()) };
|
|
82
|
+
}
|