pi-crew 0.1.49 → 0.2.0

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 (249) hide show
  1. package/CHANGELOG.md +74 -1
  2. package/README.md +176 -781
  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 +70 -11
  12. package/agents/writer.md +11 -11
  13. package/docs/actions-reference.md +595 -0
  14. package/docs/commands-reference.md +347 -0
  15. package/docs/runtime-flow.md +148 -148
  16. package/index.ts +6 -6
  17. package/package.json +99 -99
  18. package/skills/async-worker-recovery/SKILL.md +42 -42
  19. package/skills/context-artifact-hygiene/SKILL.md +52 -52
  20. package/skills/delegation-patterns/SKILL.md +54 -54
  21. package/skills/mailbox-interactive/SKILL.md +40 -40
  22. package/skills/model-routing-context/SKILL.md +39 -39
  23. package/skills/multi-perspective-review/SKILL.md +58 -58
  24. package/skills/observability-reliability/SKILL.md +41 -41
  25. package/skills/orchestration/SKILL.md +157 -157
  26. package/skills/ownership-session-security/SKILL.md +41 -41
  27. package/skills/pi-extension-lifecycle/SKILL.md +39 -39
  28. package/skills/requirements-to-task-packet/SKILL.md +63 -63
  29. package/skills/resource-discovery-config/SKILL.md +41 -41
  30. package/skills/runtime-state-reader/SKILL.md +44 -44
  31. package/skills/secure-agent-orchestration-review/SKILL.md +45 -45
  32. package/skills/state-mutation-locking/SKILL.md +42 -42
  33. package/skills/systematic-debugging/SKILL.md +67 -67
  34. package/skills/ui-render-performance/SKILL.md +39 -39
  35. package/skills/verification-before-done/SKILL.md +57 -57
  36. package/skills/worktree-isolation/SKILL.md +39 -39
  37. package/src/adapters/claude-adapter.ts +25 -0
  38. package/src/adapters/codex-adapter.ts +21 -0
  39. package/src/adapters/cursor-adapter.ts +17 -0
  40. package/src/adapters/export-util.ts +137 -0
  41. package/src/adapters/index.ts +15 -0
  42. package/src/adapters/registry.ts +18 -0
  43. package/src/adapters/types.ts +23 -0
  44. package/src/agents/agent-config.ts +2 -0
  45. package/src/agents/agent-search.ts +98 -98
  46. package/src/agents/discover-agents.ts +2 -1
  47. package/src/config/config.ts +14 -1
  48. package/src/config/defaults.ts +5 -5
  49. package/src/config/drift-detector.ts +211 -0
  50. package/src/config/markers.ts +327 -0
  51. package/src/config/resilient-parser.ts +108 -0
  52. package/src/config/suggestions.ts +74 -0
  53. package/src/extension/cross-extension-rpc.ts +103 -82
  54. package/src/extension/project-init.ts +36 -4
  55. package/src/extension/register.ts +67 -22
  56. package/src/extension/registration/commands.ts +77 -8
  57. package/src/extension/registration/subagent-tools.ts +10 -1
  58. package/src/extension/registration/team-tool.ts +10 -1
  59. package/src/extension/registration/viewers.ts +48 -34
  60. package/src/extension/run-bundle-schema.ts +89 -89
  61. package/src/extension/run-export.ts +26 -12
  62. package/src/extension/run-import.ts +25 -1
  63. package/src/extension/run-index.ts +5 -1
  64. package/src/extension/run-maintenance.ts +142 -68
  65. package/src/extension/team-manager-command.ts +10 -1
  66. package/src/extension/team-tool/context.ts +1 -1
  67. package/src/extension/team-tool/doctor.ts +28 -3
  68. package/src/extension/team-tool/handle-settings.ts +195 -188
  69. package/src/extension/team-tool/inspect.ts +41 -41
  70. package/src/extension/team-tool/intent-policy.ts +42 -42
  71. package/src/extension/team-tool/lifecycle-actions.ts +27 -8
  72. package/src/extension/team-tool/plan.ts +19 -19
  73. package/src/extension/team-tool/run.ts +12 -1
  74. package/src/extension/team-tool.ts +14 -3
  75. package/src/i18n.ts +184 -184
  76. package/src/observability/exporters/otlp-exporter.ts +92 -77
  77. package/src/prompt/prompt-runtime.ts +72 -72
  78. package/src/runtime/agent-memory.ts +72 -72
  79. package/src/runtime/agent-observability.ts +114 -114
  80. package/src/runtime/async-marker.ts +26 -26
  81. package/src/runtime/attention-events.ts +28 -28
  82. package/src/runtime/auto-resume.ts +100 -0
  83. package/src/runtime/background-runner.ts +11 -1
  84. package/src/runtime/cancellation-token.ts +89 -89
  85. package/src/runtime/cancellation.ts +61 -61
  86. package/src/runtime/capability-inventory.ts +116 -116
  87. package/src/runtime/child-pi.ts +7 -2
  88. package/src/runtime/compaction-summary.ts +271 -0
  89. package/src/runtime/completion-guard.ts +190 -190
  90. package/src/runtime/concurrency.ts +3 -1
  91. package/src/runtime/crash-recovery.ts +33 -0
  92. package/src/runtime/delta-conflict.ts +360 -0
  93. package/src/runtime/diagnostic-export.ts +3 -1
  94. package/src/runtime/direct-run.ts +35 -35
  95. package/src/runtime/event-stream-bridge.ts +3 -1
  96. package/src/runtime/foreground-control.ts +82 -82
  97. package/src/runtime/green-contract.ts +46 -46
  98. package/src/runtime/group-join.ts +106 -106
  99. package/src/runtime/heartbeat-gradient.ts +28 -28
  100. package/src/runtime/heartbeat-watcher.ts +124 -124
  101. package/src/runtime/iteration-hooks.ts +262 -0
  102. package/src/runtime/live-agent-control.ts +88 -88
  103. package/src/runtime/live-control-realtime.ts +36 -36
  104. package/src/runtime/live-extension-bridge.ts +150 -150
  105. package/src/runtime/live-irc.ts +92 -92
  106. package/src/runtime/live-session-health.ts +100 -100
  107. package/src/runtime/loop-gates.ts +129 -0
  108. package/src/runtime/metric-parser.ts +40 -0
  109. package/src/runtime/notebook-helpers.ts +90 -90
  110. package/src/runtime/orphan-sentinel.ts +7 -7
  111. package/src/runtime/parallel-research.ts +44 -44
  112. package/src/runtime/phase-progress.ts +217 -0
  113. package/src/runtime/pi-args.ts +38 -2
  114. package/src/runtime/pi-json-output.ts +111 -111
  115. package/src/runtime/pi-spawn.ts +74 -6
  116. package/src/runtime/policy-engine.ts +79 -79
  117. package/src/runtime/post-checks.ts +122 -0
  118. package/src/runtime/process-status.ts +14 -1
  119. package/src/runtime/progress-event-coalescer.ts +43 -43
  120. package/src/runtime/prose-compressor.ts +164 -164
  121. package/src/runtime/recovery-recipes.ts +74 -74
  122. package/src/runtime/result-extractor.ts +121 -121
  123. package/src/runtime/role-permission.ts +39 -39
  124. package/src/runtime/sensitive-paths.ts +3 -3
  125. package/src/runtime/session-resources.ts +25 -25
  126. package/src/runtime/session-snapshot.ts +59 -59
  127. package/src/runtime/session-usage.ts +79 -79
  128. package/src/runtime/sidechain-output.ts +29 -29
  129. package/src/runtime/stream-preview.ts +177 -177
  130. package/src/runtime/supervisor-contact.ts +59 -59
  131. package/src/runtime/task-display.ts +38 -38
  132. package/src/runtime/task-graph.ts +207 -0
  133. package/src/runtime/task-quality.ts +207 -0
  134. package/src/runtime/task-runner/capabilities.ts +78 -78
  135. package/src/runtime/task-runner/live-executor.ts +7 -1
  136. package/src/runtime/task-runner/progress.ts +119 -119
  137. package/src/runtime/task-runner/prompt-builder.ts +1 -1
  138. package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
  139. package/src/runtime/task-runner/result-utils.ts +14 -14
  140. package/src/runtime/task-runner/run-projection.ts +103 -103
  141. package/src/runtime/task-runner/state-helpers.ts +22 -22
  142. package/src/runtime/team-runner.ts +126 -7
  143. package/src/runtime/worker-heartbeat.ts +21 -21
  144. package/src/runtime/worker-startup.ts +57 -57
  145. package/src/runtime/workflow-state.ts +187 -0
  146. package/src/runtime/workspace-tree.ts +298 -298
  147. package/src/schema/config-schema.ts +12 -0
  148. package/src/schema/validation-types.ts +148 -0
  149. package/src/skills/skill-templates.ts +374 -0
  150. package/src/state/active-run-registry.ts +35 -11
  151. package/src/state/atomic-write.ts +33 -26
  152. package/src/state/contracts.ts +1 -0
  153. package/src/state/event-reconstructor.ts +217 -0
  154. package/src/state/locks.ts +2 -11
  155. package/src/state/mailbox.ts +4 -3
  156. package/src/state/state-store.ts +32 -14
  157. package/src/state/task-claims.ts +44 -44
  158. package/src/state/types.ts +9 -0
  159. package/src/state/usage.ts +29 -29
  160. package/src/subagents/async-entry.ts +1 -1
  161. package/src/subagents/index.ts +3 -3
  162. package/src/subagents/live/control.ts +1 -1
  163. package/src/subagents/live/manager.ts +1 -1
  164. package/src/subagents/live/realtime.ts +1 -1
  165. package/src/subagents/live/session-runtime.ts +1 -1
  166. package/src/subagents/manager.ts +1 -1
  167. package/src/subagents/spawn.ts +1 -1
  168. package/src/teams/team-serializer.ts +38 -38
  169. package/src/types/diff.d.ts +18 -18
  170. package/src/ui/crew-footer.ts +101 -101
  171. package/src/ui/crew-select-list.ts +111 -111
  172. package/src/ui/crew-widget.ts +9 -4
  173. package/src/ui/dashboard-panes/cancellation-pane.ts +42 -42
  174. package/src/ui/dashboard-panes/capability-pane.ts +59 -59
  175. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -35
  176. package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
  177. package/src/ui/dashboard-panes/progress-pane.ts +11 -0
  178. package/src/ui/dynamic-border.ts +25 -25
  179. package/src/ui/layout-primitives.ts +106 -106
  180. package/src/ui/loaders.ts +158 -158
  181. package/src/ui/powerbar-publisher.ts +6 -0
  182. package/src/ui/render-coalescer.ts +51 -51
  183. package/src/ui/render-diff.ts +119 -119
  184. package/src/ui/render-scheduler.ts +143 -143
  185. package/src/ui/run-action-dispatcher.ts +10 -1
  186. package/src/ui/spinner.ts +17 -17
  187. package/src/ui/status-colors.ts +58 -58
  188. package/src/ui/syntax-highlight.ts +116 -116
  189. package/src/ui/transcript-entries.ts +258 -258
  190. package/src/utils/completion-dedupe.ts +63 -63
  191. package/src/utils/frontmatter.ts +68 -68
  192. package/src/utils/git.ts +262 -262
  193. package/src/utils/ids.ts +17 -17
  194. package/src/utils/incremental-reader.ts +104 -104
  195. package/src/utils/names.ts +27 -27
  196. package/src/utils/redaction.ts +44 -44
  197. package/src/utils/safe-paths.ts +47 -47
  198. package/src/utils/scan-cache.ts +136 -136
  199. package/src/utils/sleep.ts +40 -26
  200. package/src/utils/task-name-generator.ts +337 -337
  201. package/src/workflows/validate-workflow.ts +40 -40
  202. package/src/worktree/branch-freshness.ts +45 -45
  203. package/src/worktree/worktree-manager.ts +11 -3
  204. package/teams/default.team.md +12 -12
  205. package/teams/fast-fix.team.md +11 -11
  206. package/teams/implementation.team.md +18 -18
  207. package/teams/parallel-research.team.md +14 -14
  208. package/teams/research.team.md +11 -11
  209. package/teams/review.team.md +12 -12
  210. package/workflows/default.workflow.md +30 -29
  211. package/workflows/fast-fix.workflow.md +23 -22
  212. package/workflows/implementation.workflow.md +43 -38
  213. package/workflows/parallel-research.workflow.md +46 -46
  214. package/workflows/research.workflow.md +22 -22
  215. package/workflows/review.workflow.md +30 -30
  216. package/docs/refactor-tasks-phase3.md +0 -394
  217. package/docs/refactor-tasks-phase4.md +0 -564
  218. package/docs/refactor-tasks-phase5.md +0 -402
  219. package/docs/refactor-tasks-phase6.md +0 -662
  220. package/docs/refactor-tasks.md +0 -1484
  221. package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +0 -261
  222. package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +0 -111
  223. package/docs/research/AUDIT_OH_MY_PI.md +0 -261
  224. package/docs/research/AUDIT_PI_CREW.md +0 -457
  225. package/docs/research/CAVEMAN-DEEP-RESEARCH.md +0 -281
  226. package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +0 -264
  227. package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +0 -343
  228. package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +0 -480
  229. package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +0 -354
  230. package/docs/research/IMPLEMENTATION_PLAN.md +0 -385
  231. package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +0 -502
  232. package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +0 -266
  233. package/docs/research/REMAINING-GAPS-PLAN.md +0 -363
  234. package/docs/research/SESSION-SUMMARY-2026-05-08.md +0 -146
  235. package/docs/research/UI-RESPONSIVENESS-AUDIT.md +0 -173
  236. package/docs/research-awesome-agent-skills-distillation.md +0 -100
  237. package/docs/research-extension-examples.md +0 -297
  238. package/docs/research-extension-system.md +0 -324
  239. package/docs/research-oh-my-pi-distillation.md +0 -369
  240. package/docs/research-optimization-plan.md +0 -548
  241. package/docs/research-phase10-distillation.md +0 -199
  242. package/docs/research-phase11-distillation.md +0 -201
  243. package/docs/research-phase8-operator-experience-plan.md +0 -819
  244. package/docs/research-phase9-observability-reliability-plan.md +0 -1190
  245. package/docs/research-pi-coding-agent.md +0 -357
  246. package/docs/research-source-pi-crew-reference.md +0 -174
  247. package/docs/research-ui-optimization-plan.md +0 -480
  248. package/docs/source-runtime-refactor-map.md +0 -107
  249. package/src/utils/atomic-write.ts +0 -33
@@ -19,7 +19,7 @@ export type TeamContext = Pick<ExtensionContext, "cwd"> & Partial<Pick<Extension
19
19
  };
20
20
 
21
21
  export function withSessionId<T extends Pick<ExtensionContext, "sessionManager">>(ctx: T): T & { sessionId?: string } {
22
- const sessionId = ctx.sessionManager.getSessionId();
22
+ const sessionId = ctx.sessionManager?.getSessionId?.();
23
23
  return sessionId ? { ...ctx, sessionId } : { ...ctx };
24
24
  }
25
25
 
@@ -10,6 +10,7 @@ import { DEFAULT_PATHS } from "../../config/defaults.ts";
10
10
  import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
11
11
  import { getPiSpawnCommand } from "../../runtime/pi-spawn.ts";
12
12
  import { validateResources } from "../validate-resources.ts";
13
+ import { detectDrift, formatDriftReport, type DriftReport } from "../../config/drift-detector.ts";
13
14
  import { TeamToolParams } from "../../schema/team-tool-schema.ts";
14
15
  import type { PiTeamsToolResult } from "../tool-result.ts";
15
16
  import { configRecord, result, type TeamContext } from "./context.ts";
@@ -112,9 +113,19 @@ export interface TeamDoctorReportInput {
112
113
  export interface TeamDoctorReport {
113
114
  text: string;
114
115
  hasErrors: boolean;
116
+ drift?: DriftReport;
115
117
  }
116
118
 
117
119
  export function buildTeamDoctorReport(input: TeamDoctorReportInput): TeamDoctorReport {
120
+ // Compute drift once — reused in both Drift section and return value
121
+ const driftResult = detectDrift(
122
+ {
123
+ agents: allAgents(discoverAgents(input.cwd)).map((a) => a.name),
124
+ teams: allTeams(discoverTeams(input.cwd)).map((t) => t.name),
125
+ workflows: allWorkflows(discoverWorkflows(input.cwd)).map((w) => w.name),
126
+ },
127
+ loadConfig(input.cwd).config,
128
+ );
118
129
  const sections = [
119
130
  section("Runtime", () => {
120
131
  const git = commandExists("git", ["--version"]);
@@ -156,6 +167,15 @@ export function buildTeamDoctorReport(input: TeamDoctorReportInput): TeamDoctorR
156
167
  ok: input.validationErrors === 0,
157
168
  detail: `${input.validationErrors} errors, ${input.validationWarnings} warnings`,
158
169
  }]),
170
+ section("Drift", () => {
171
+ const driftErrors = driftResult.items.filter((item) => item.severity === "error").length;
172
+ const driftWarnings = driftResult.items.filter((item) => item.severity === "warning").length;
173
+ return [{
174
+ label: "config drift",
175
+ ok: !driftResult.hasDrift || driftErrors === 0,
176
+ detail: driftResult.hasDrift ? `${driftErrors} errors, ${driftWarnings} warnings` : "no drift detected",
177
+ }];
178
+ }),
159
179
  section("Schema", () => {
160
180
  const schemaIssues = auditJsonSchema(TeamToolParams);
161
181
  return [{ label: "strict-provider schema", ok: schemaIssues.length === 0, detail: schemaIssues.length ? schemaIssues.slice(0, 3).join("; ") : "team tool schema compatible" }];
@@ -181,7 +201,7 @@ export function buildTeamDoctorReport(input: TeamDoctorReportInput): TeamDoctorR
181
201
  }
182
202
  if (lines.at(-1) === "") lines.pop();
183
203
  const text = lines.join("\n");
184
- return { text, hasErrors: sections.some((sectionLines) => sectionLines.some((line) => line.includes("FAIL"))) };
204
+ return { text, hasErrors: sections.some((sectionLines) => sectionLines.some((line) => line.includes("FAIL"))), drift: driftResult.hasDrift ? driftResult : undefined };
185
205
  }
186
206
 
187
207
  export function handleDoctor(ctx: TeamContext, params: TeamToolParamsValue = {}): PiTeamsToolResult {
@@ -203,7 +223,7 @@ export function handleDoctor(ctx: TeamContext, params: TeamToolParamsValue = {})
203
223
  }
204
224
  }
205
225
  const validation = validateResources(ctx.cwd);
206
- const { text, hasErrors } = buildTeamDoctorReport({
226
+ const { text, hasErrors, drift } = buildTeamDoctorReport({
207
227
  cwd: ctx.cwd,
208
228
  configPath: loadedConfig.path,
209
229
  configErrors: loadedConfig.error ? [loadedConfig.error] : [],
@@ -213,5 +233,10 @@ export function handleDoctor(ctx: TeamContext, params: TeamToolParamsValue = {})
213
233
  validationWarnings: validation.issues.filter((issue) => issue.level === "warning").length,
214
234
  smokeChildPi,
215
235
  });
216
- return result(text, { action: "doctor", status: hasErrors ? "error" : "ok" }, hasErrors);
236
+ // Append detailed drift section if any drift was detected
237
+ let finalText = text;
238
+ if (drift?.hasDrift) {
239
+ finalText = `${text}\n\nDrift details:\n${formatDriftReport(drift)}`;
240
+ }
241
+ return result(finalText, { action: "doctor", status: hasErrors ? "error" : "ok" }, hasErrors);
217
242
  }
@@ -1,188 +1,195 @@
1
- import type { TeamContext } from "../team-tool/context.ts";
2
- import { loadConfig, updateConfig } from "../../config/config.ts";
3
- import { configPatchFromConfig } from "../team-tool/config-patch.ts";
4
- import { result } from "../team-tool/context.ts";
5
- import type { PiTeamsToolResult } from "../tool-result.ts";
6
-
7
- // ---------------------------------------------------------------------------
8
- // Helpers
9
- // ---------------------------------------------------------------------------
10
-
11
- function setNested(obj: Record<string, unknown>, path: string, value: unknown): void {
12
- const keys = path.split(".");
13
- let target: Record<string, unknown> = obj;
14
- for (let i = 0; i < keys.length - 1; i++) {
15
- if (!target[keys[i]] || typeof target[keys[i]] !== "object") {
16
- target[keys[i]] = {};
17
- }
18
- target = target[keys[i]] as Record<string, unknown>;
19
- }
20
- target[keys[keys.length - 1]] = value;
21
- }
22
-
23
- function getNested(obj: Record<string, unknown>, path: string): unknown {
24
- const keys = path.split(".");
25
- let current: unknown = obj;
26
- for (const key of keys) {
27
- if (!current || typeof current !== "object") return undefined;
28
- current = (current as Record<string, unknown>)[key];
29
- }
30
- return current;
31
- }
32
-
33
- function formatValue(value: unknown): string {
34
- if (value === undefined) return "<not set>";
35
- if (typeof value === "object") return JSON.stringify(value);
36
- return String(value);
37
- }
38
-
39
- function parseValue(raw: string): unknown {
40
- // JSON handles strings (quoted), numbers, booleans, null, arrays, objects.
41
- try { return JSON.parse(raw); } catch { /* keep as string */ }
42
- return raw;
43
- }
44
-
45
- // ---------------------------------------------------------------------------
46
- // Known config keys — mirrors config-schema.ts + config.ts.
47
- // When adding new config fields, add the dotted path here so team-settings
48
- // can discover and display them.
49
- // ---------------------------------------------------------------------------
50
-
51
- const KNOWN_KEYS = new Set([
52
- // top-level
53
- "asyncByDefault",
54
- "executeWorkers",
55
- "notifierIntervalMs",
56
- "requireCleanWorktreeLeader",
57
- // runtime
58
- "runtime.mode",
59
- "runtime.preferLiveSession",
60
- "runtime.allowChildProcessFallback",
61
- "runtime.maxTurns",
62
- "runtime.graceTurns",
63
- "runtime.inheritContext",
64
- "runtime.promptMode",
65
- "runtime.groupJoin",
66
- "runtime.groupJoinAckTimeoutMs",
67
- "runtime.requirePlanApproval",
68
- "runtime.completionMutationGuard",
69
- // limits
70
- "limits.maxConcurrentWorkers",
71
- "limits.allowUnboundedConcurrency",
72
- "limits.maxTaskDepth",
73
- "limits.maxChildrenPerTask",
74
- "limits.maxRunMinutes",
75
- "limits.maxRetriesPerTask",
76
- "limits.maxTasksPerRun",
77
- "limits.heartbeatStaleMs",
78
- // control
79
- "control.enabled",
80
- "control.needsAttentionAfterMs",
81
- // autonomous
82
- "autonomous.profile",
83
- "autonomous.enabled",
84
- "autonomous.injectPolicy",
85
- "autonomous.preferAsyncForLongTasks",
86
- "autonomous.allowWorktreeSuggestion",
87
- // tools
88
- "tools.enableClaudeStyleAliases",
89
- "tools.enableSteer",
90
- "tools.terminateOnForeground",
91
- // agents
92
- "agents.disableBuiltins",
93
- // observability
94
- "observability.prometheus.enabled",
95
- "observability.otlp.enabled",
96
- // worktree
97
- "worktree.enabled",
98
- ]);
99
-
100
- const KNOWN_SORTED = [...KNOWN_KEYS].sort();
101
-
102
- // ---------------------------------------------------------------------------
103
- // Detail objects – all require { action, status } from TeamToolDetails.
104
- // Extras (count, key, value, path) are passed as never to bypass the narrow
105
- // TeamToolDetails interface (consistent with the rest of the codebase).
106
- // ---------------------------------------------------------------------------
107
-
108
- const OK = { action: "settings", status: "ok" as const };
109
- const ERR = { action: "settings", status: "error" as const };
110
-
111
- // ---------------------------------------------------------------------------
112
- // Main handler
113
- // ---------------------------------------------------------------------------
114
-
115
- export function handleSettings(params: { config?: Record<string, unknown> }, ctx: TeamContext): PiTeamsToolResult {
116
- const cfg = (params.config ?? {}) as Record<string, unknown>;
117
- const args = typeof cfg.args === "string" ? cfg.args.trim() : "";
118
- const scope = cfg.scope === "project" ? "project" : "user";
119
- const loaded = loadConfig(ctx.cwd);
120
- const effective = loaded.config as Record<string, unknown>;
121
-
122
- // team-settings list
123
- if (!args || args === "list") {
124
- const lines = ["pi-crew settings:", `Path: ${loaded.path}`, ""];
125
- for (const key of KNOWN_SORTED) {
126
- const value = getNested(effective, key);
127
- lines.push(` ${key} = ${formatValue(value)}`);
128
- }
129
- lines.push("", "Usage: team-settings [list|get <key>|set <key> <value>|unset <key>|path|scope]");
130
- return result(lines.join("\n"), { ...OK, count: KNOWN_KEYS.size } as never);
131
- }
132
-
133
- // team-settings path
134
- if (args === "path") {
135
- return result(`pi-crew config path: ${loaded.path}`, { ...OK, path: loaded.path } as never);
136
- }
137
-
138
- // team-settings scope
139
- if (args === "scope") {
140
- return result(`Current scope: ${scope}\nConfig at: ${loaded.path}`, { ...OK, scope } as never);
141
- }
142
-
143
- // team-settings get <key>
144
- if (args.startsWith("get ")) {
145
- const key = args.slice(4).trim();
146
- if (!key) return result("Usage: team-settings get <key>", { ...ERR }, true);
147
- const value = getNested(effective, key);
148
- const note = KNOWN_KEYS.has(key) ? "" : " (unknown key — may not take effect)";
149
- return result(`${key} = ${formatValue(value)}${note}`, { ...OK, key, value } as never);
150
- }
151
-
152
- // team-settings unset <key>
153
- if (args.startsWith("unset ")) {
154
- const key = args.slice(6).trim();
155
- if (!key) return result("Usage: team-settings unset <key>", { ...ERR }, true);
156
- try {
157
- const saved = updateConfig({}, { cwd: ctx.cwd, scope, unsetPaths: [key] });
158
- return result(`Unset ${key}\nPath: ${saved.path}`, { ...OK, key } as never);
159
- } catch (error) {
160
- return result(error instanceof Error ? error.message : String(error), { ...ERR }, true);
161
- }
162
- }
163
-
164
- // team-settings set <key> <value>
165
- if (args.startsWith("set ")) {
166
- const rest = args.slice(4).trim();
167
- const spaceIdx = rest.indexOf(" ");
168
- if (spaceIdx === -1) return result("Usage: team-settings set <key> <value>", { ...ERR }, true);
169
- const key = rest.slice(0, spaceIdx);
170
- const rawValue = rest.slice(spaceIdx + 1).trim();
171
- if (!key) return result("Usage: team-settings set <key> <value>", { ...ERR }, true);
172
-
173
- const value = parseValue(rawValue);
174
- const patch = {};
175
- setNested(patch as Record<string, unknown>, key, value);
176
-
177
- try {
178
- const converted = configPatchFromConfig({ config: patch as Record<string, unknown> });
179
- const saved = updateConfig(converted, { cwd: ctx.cwd, scope });
180
- const warning = KNOWN_KEYS.has(key) ? "" : "\nWarning: unknown key — verify it exists in config schema.";
181
- return result(`Set ${key} = ${formatValue(value)}\nPath: ${saved.path}${warning}`, { ...OK, key, value } as never);
182
- } catch (error) {
183
- return result(error instanceof Error ? error.message : String(error), { ...ERR }, true);
184
- }
185
- }
186
-
187
- return result("Unknown subcommand. Usage: team-settings [list|get <key>|set <key> <value>|unset <key>|path|scope]", { ...ERR }, true);
188
- }
1
+ import type { TeamContext } from "../team-tool/context.ts";
2
+ import { loadConfig, updateConfig } from "../../config/config.ts";
3
+ import { configPatchFromConfig } from "../team-tool/config-patch.ts";
4
+ import { result } from "../team-tool/context.ts";
5
+ import type { PiTeamsToolResult } from "../tool-result.ts";
6
+ import { suggestConfigKey } from "../../config/suggestions.ts";
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Helpers
10
+ // ---------------------------------------------------------------------------
11
+
12
+ function setNested(obj: Record<string, unknown>, path: string, value: unknown): void {
13
+ const keys = path.split(".");
14
+ let target: Record<string, unknown> = obj;
15
+ for (let i = 0; i < keys.length - 1; i++) {
16
+ if (!target[keys[i]] || typeof target[keys[i]] !== "object") {
17
+ target[keys[i]] = {};
18
+ }
19
+ target = target[keys[i]] as Record<string, unknown>;
20
+ }
21
+ target[keys[keys.length - 1]] = value;
22
+ }
23
+
24
+ function getNested(obj: Record<string, unknown>, path: string): unknown {
25
+ const keys = path.split(".");
26
+ let current: unknown = obj;
27
+ for (const key of keys) {
28
+ if (!current || typeof current !== "object") return undefined;
29
+ current = (current as Record<string, unknown>)[key];
30
+ }
31
+ return current;
32
+ }
33
+
34
+ function formatValue(value: unknown): string {
35
+ if (value === undefined) return "<not set>";
36
+ if (typeof value === "object") return JSON.stringify(value);
37
+ return String(value);
38
+ }
39
+
40
+ function parseValue(raw: string): unknown {
41
+ // JSON handles strings (quoted), numbers, booleans, null, arrays, objects.
42
+ try { return JSON.parse(raw); } catch { /* keep as string */ }
43
+ return raw;
44
+ }
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Known config keys mirrors config-schema.ts + config.ts.
48
+ // When adding new config fields, add the dotted path here so team-settings
49
+ // can discover and display them.
50
+ // ---------------------------------------------------------------------------
51
+
52
+ const KNOWN_KEYS = new Set([
53
+ // top-level
54
+ "asyncByDefault",
55
+ "executeWorkers",
56
+ "notifierIntervalMs",
57
+ "requireCleanWorktreeLeader",
58
+ // runtime
59
+ "runtime.mode",
60
+ "runtime.preferLiveSession",
61
+ "runtime.allowChildProcessFallback",
62
+ "runtime.maxTurns",
63
+ "runtime.graceTurns",
64
+ "runtime.inheritContext",
65
+ "runtime.promptMode",
66
+ "runtime.groupJoin",
67
+ "runtime.groupJoinAckTimeoutMs",
68
+ "runtime.requirePlanApproval",
69
+ "runtime.completionMutationGuard",
70
+ // limits
71
+ "limits.maxConcurrentWorkers",
72
+ "limits.allowUnboundedConcurrency",
73
+ "limits.maxTaskDepth",
74
+ "limits.maxChildrenPerTask",
75
+ "limits.maxRunMinutes",
76
+ "limits.maxRetriesPerTask",
77
+ "limits.maxTasksPerRun",
78
+ "limits.heartbeatStaleMs",
79
+ // control
80
+ "control.enabled",
81
+ "control.needsAttentionAfterMs",
82
+ // autonomous
83
+ "autonomous.profile",
84
+ "autonomous.enabled",
85
+ "autonomous.injectPolicy",
86
+ "autonomous.preferAsyncForLongTasks",
87
+ "autonomous.allowWorktreeSuggestion",
88
+ // tools
89
+ "tools.enableClaudeStyleAliases",
90
+ "tools.enableSteer",
91
+ "tools.terminateOnForeground",
92
+ // agents
93
+ "agents.disableBuiltins",
94
+ // observability
95
+ "observability.prometheus.enabled",
96
+ "observability.otlp.enabled",
97
+ // worktree
98
+ "worktree.enabled",
99
+ ]);
100
+
101
+ const KNOWN_SORTED = [...KNOWN_KEYS].sort();
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Detail objects all require { action, status } from TeamToolDetails.
105
+ // Extras (count, key, value, path) are passed as never to bypass the narrow
106
+ // TeamToolDetails interface (consistent with the rest of the codebase).
107
+ // ---------------------------------------------------------------------------
108
+
109
+ const OK = { action: "settings", status: "ok" as const };
110
+ const ERR = { action: "settings", status: "error" as const };
111
+
112
+ // ---------------------------------------------------------------------------
113
+ // Main handler
114
+ // ---------------------------------------------------------------------------
115
+
116
+ export function handleSettings(params: { config?: Record<string, unknown> }, ctx: TeamContext): PiTeamsToolResult {
117
+ const cfg = (params.config ?? {}) as Record<string, unknown>;
118
+ const args = typeof cfg.args === "string" ? cfg.args.trim() : "";
119
+ const scope = cfg.scope === "project" ? "project" : "user";
120
+ const loaded = loadConfig(ctx.cwd);
121
+ const effective = loaded.config as Record<string, unknown>;
122
+
123
+ // team-settings list
124
+ if (!args || args === "list") {
125
+ const lines = ["pi-crew settings:", `Path: ${loaded.path}`, ""];
126
+ for (const key of KNOWN_SORTED) {
127
+ const value = getNested(effective, key);
128
+ lines.push(` ${key} = ${formatValue(value)}`);
129
+ }
130
+ lines.push("", "Usage: team-settings [list|get <key>|set <key> <value>|unset <key>|path|scope]");
131
+ return result(lines.join("\n"), { ...OK, count: KNOWN_KEYS.size } as never);
132
+ }
133
+
134
+ // team-settings path
135
+ if (args === "path") {
136
+ return result(`pi-crew config path: ${loaded.path}`, { ...OK, path: loaded.path } as never);
137
+ }
138
+
139
+ // team-settings scope
140
+ if (args === "scope") {
141
+ return result(`Current scope: ${scope}\nConfig at: ${loaded.path}`, { ...OK, scope } as never);
142
+ }
143
+
144
+ // team-settings get <key>
145
+ if (args.startsWith("get ")) {
146
+ const key = args.slice(4).trim();
147
+ if (!key) return result("Usage: team-settings get <key>", { ...ERR }, true);
148
+ const value = getNested(effective, key);
149
+ let note = KNOWN_KEYS.has(key) ? "" : " (unknown key may not take effect)";
150
+ if (!KNOWN_KEYS.has(key)) {
151
+ const suggestion = suggestConfigKey(key, KNOWN_SORTED);
152
+ if (suggestion) note += ` (did you mean '${suggestion}'?)`;
153
+ }
154
+ return result(`${key} = ${formatValue(value)}${note}`, { ...OK, key, value } as never);
155
+ }
156
+
157
+ // team-settings unset <key>
158
+ if (args.startsWith("unset ")) {
159
+ const key = args.slice(6).trim();
160
+ if (!key) return result("Usage: team-settings unset <key>", { ...ERR }, true);
161
+ try {
162
+ const saved = updateConfig({}, { cwd: ctx.cwd, scope, unsetPaths: [key] });
163
+ return result(`Unset ${key}\nPath: ${saved.path}`, { ...OK, key } as never);
164
+ } catch (error) {
165
+ return result(error instanceof Error ? error.message : String(error), { ...ERR }, true);
166
+ }
167
+ }
168
+
169
+ // team-settings set <key> <value>
170
+ if (args.startsWith("set ")) {
171
+ const rest = args.slice(4).trim();
172
+ const spaceIdx = rest.indexOf(" ");
173
+ if (spaceIdx === -1) return result("Usage: team-settings set <key> <value>", { ...ERR }, true);
174
+ const key = rest.slice(0, spaceIdx);
175
+ const rawValue = rest.slice(spaceIdx + 1).trim();
176
+ if (!key) return result("Usage: team-settings set <key> <value>", { ...ERR }, true);
177
+
178
+ const value = parseValue(rawValue);
179
+ const patch = {};
180
+ setNested(patch as Record<string, unknown>, key, value);
181
+
182
+ try {
183
+ const converted = configPatchFromConfig({ config: patch as Record<string, unknown> });
184
+ const saved = updateConfig(converted, { cwd: ctx.cwd, scope });
185
+ const warning = KNOWN_KEYS.has(key) ? "" : "\nWarning: unknown key — verify it exists in config schema.";
186
+ const suggestion = KNOWN_KEYS.has(key) ? null : suggestConfigKey(key, KNOWN_SORTED);
187
+ const hint = suggestion ? `\nDid you mean '${suggestion}'?` : "";
188
+ return result(`Set ${key} = ${formatValue(value)}\nPath: ${saved.path}${warning}${hint}`, { ...OK, key, value } as never);
189
+ } catch (error) {
190
+ return result(error instanceof Error ? error.message : String(error), { ...ERR }, true);
191
+ }
192
+ }
193
+
194
+ return result("Unknown subcommand. Usage: team-settings [list|get <key>|set <key> <value>|unset <key>|path|scope]", { ...ERR }, true);
195
+ }
@@ -1,41 +1,41 @@
1
- import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
2
- import { readEvents } from "../../state/event-log.ts";
3
- import { loadRunManifestById } from "../../state/state-store.ts";
4
- import { aggregateUsage, formatUsage } from "../../state/usage.ts";
5
- import type { PiTeamsToolResult } from "../tool-result.ts";
6
- import { result, type TeamContext } from "./context.ts";
7
-
8
- export function handleEvents(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
9
- if (!params.runId) return result("Events requires runId.", { action: "events", status: "error" }, true);
10
- const loaded = loadRunManifestById(ctx.cwd, params.runId);
11
- if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "events", status: "error" }, true);
12
- const events = readEvents(loaded.manifest.eventsPath);
13
- const lines = [`Events for ${loaded.manifest.runId}:`, ...(events.length ? events.map((event) => `${event.time} ${event.type}${event.taskId ? ` ${event.taskId}` : ""}${event.message ? `: ${event.message}` : ""}${event.data ? ` ${JSON.stringify(event.data)}` : ""}`) : ["(none)"])];
14
- return result(lines.join("\n"), { action: "events", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
15
- }
16
-
17
- export function handleArtifacts(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
18
- if (!params.runId) return result("Artifacts requires runId.", { action: "artifacts", status: "error" }, true);
19
- const loaded = loadRunManifestById(ctx.cwd, params.runId);
20
- if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "artifacts", status: "error" }, true);
21
- const lines = [`Artifacts for ${loaded.manifest.runId}:`, ...(loaded.manifest.artifacts.length ? loaded.manifest.artifacts.map((artifact) => `- ${artifact.kind}: ${artifact.path}${artifact.sizeBytes !== undefined ? ` (${artifact.sizeBytes} bytes)` : ""}${artifact.contentHash ? ` sha256=${artifact.contentHash.slice(0, 12)}` : ""}`) : ["- (none)"])];
22
- return result(lines.join("\n"), { action: "artifacts", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
23
- }
24
-
25
- export function handleSummary(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
26
- if (!params.runId) return result("Summary requires runId.", { action: "summary", status: "error" }, true);
27
- const loaded = loadRunManifestById(ctx.cwd, params.runId);
28
- if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "summary", status: "error" }, true);
29
- const usage = aggregateUsage(loaded.tasks);
30
- const lines = [
31
- `Summary for ${loaded.manifest.runId}`,
32
- `Status: ${loaded.manifest.status}`,
33
- `Team: ${loaded.manifest.team}`,
34
- `Workflow: ${loaded.manifest.workflow ?? "(none)"}`,
35
- `Goal: ${loaded.manifest.goal}`,
36
- `Usage: ${formatUsage(usage)}`,
37
- "Tasks:",
38
- ...loaded.tasks.map((task) => `- ${task.id}: ${task.status} (${task.role} -> ${task.agent})${task.error ? ` - ${task.error}` : ""}`),
39
- ];
40
- return result(lines.join("\n"), { action: "summary", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
41
- }
1
+ import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
2
+ import { readEvents } from "../../state/event-log.ts";
3
+ import { loadRunManifestById } from "../../state/state-store.ts";
4
+ import { aggregateUsage, formatUsage } from "../../state/usage.ts";
5
+ import type { PiTeamsToolResult } from "../tool-result.ts";
6
+ import { result, type TeamContext } from "./context.ts";
7
+
8
+ export function handleEvents(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
9
+ if (!params.runId) return result("Events requires runId.", { action: "events", status: "error" }, true);
10
+ const loaded = loadRunManifestById(ctx.cwd, params.runId);
11
+ if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "events", status: "error" }, true);
12
+ const events = readEvents(loaded.manifest.eventsPath);
13
+ const lines = [`Events for ${loaded.manifest.runId}:`, ...(events.length ? events.map((event) => `${event.time} ${event.type}${event.taskId ? ` ${event.taskId}` : ""}${event.message ? `: ${event.message}` : ""}${event.data ? ` ${JSON.stringify(event.data)}` : ""}`) : ["(none)"])];
14
+ return result(lines.join("\n"), { action: "events", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
15
+ }
16
+
17
+ export function handleArtifacts(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
18
+ if (!params.runId) return result("Artifacts requires runId.", { action: "artifacts", status: "error" }, true);
19
+ const loaded = loadRunManifestById(ctx.cwd, params.runId);
20
+ if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "artifacts", status: "error" }, true);
21
+ const lines = [`Artifacts for ${loaded.manifest.runId}:`, ...(loaded.manifest.artifacts.length ? loaded.manifest.artifacts.map((artifact) => `- ${artifact.kind}: ${artifact.path}${artifact.sizeBytes !== undefined ? ` (${artifact.sizeBytes} bytes)` : ""}${artifact.contentHash ? ` sha256=${artifact.contentHash.slice(0, 12)}` : ""}`) : ["- (none)"])];
22
+ return result(lines.join("\n"), { action: "artifacts", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
23
+ }
24
+
25
+ export function handleSummary(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
26
+ if (!params.runId) return result("Summary requires runId.", { action: "summary", status: "error" }, true);
27
+ const loaded = loadRunManifestById(ctx.cwd, params.runId);
28
+ if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "summary", status: "error" }, true);
29
+ const usage = aggregateUsage(loaded.tasks);
30
+ const lines = [
31
+ `Summary for ${loaded.manifest.runId}`,
32
+ `Status: ${loaded.manifest.status}`,
33
+ `Team: ${loaded.manifest.team}`,
34
+ `Workflow: ${loaded.manifest.workflow ?? "(none)"}`,
35
+ `Goal: ${loaded.manifest.goal}`,
36
+ `Usage: ${formatUsage(usage)}`,
37
+ "Tasks:",
38
+ ...loaded.tasks.map((task) => `- ${task.id}: ${task.status} (${task.role} -> ${task.agent})${task.error ? ` - ${task.error}` : ""}`),
39
+ ];
40
+ return result(lines.join("\n"), { action: "summary", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
41
+ }