pi-crew 0.1.46 → 0.1.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -0
- package/agents/analyst.md +11 -11
- package/agents/critic.md +11 -11
- package/agents/executor.md +11 -11
- package/agents/explorer.md +11 -11
- package/agents/planner.md +11 -11
- package/agents/reviewer.md +11 -11
- package/agents/security-reviewer.md +11 -11
- package/agents/test-engineer.md +11 -11
- package/agents/verifier.md +11 -11
- package/agents/writer.md +11 -11
- package/docs/next-upgrade-roadmap.md +117 -42
- package/docs/refactor-tasks-phase3.md +394 -394
- package/docs/refactor-tasks-phase4.md +564 -564
- package/docs/refactor-tasks-phase5.md +402 -402
- package/docs/refactor-tasks-phase6.md +662 -662
- package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +261 -0
- package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +111 -0
- package/docs/research/AUDIT_OH_MY_PI.md +261 -0
- package/docs/research/AUDIT_PI_CREW.md +457 -0
- package/docs/research/CAVEMAN-DEEP-RESEARCH.md +281 -0
- package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +264 -0
- package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +343 -0
- package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +480 -0
- package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +354 -0
- package/docs/research/IMPLEMENTATION_PLAN.md +385 -0
- package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +502 -0
- package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +266 -0
- package/docs/research/REMAINING-GAPS-PLAN.md +363 -0
- package/docs/research/SESSION-SUMMARY-2026-05-08.md +146 -0
- package/docs/research/UI-RESPONSIVENESS-AUDIT.md +173 -0
- package/docs/research-awesome-agent-skills-distillation.md +100 -100
- package/docs/research-extension-examples.md +297 -297
- package/docs/research-extension-system.md +324 -324
- package/docs/research-oh-my-pi-distillation.md +56 -9
- package/docs/research-optimization-plan.md +548 -548
- package/docs/research-phase10-distillation.md +198 -198
- package/docs/research-phase11-distillation.md +201 -201
- package/docs/research-pi-coding-agent.md +357 -357
- package/docs/research-source-pi-crew-reference.md +174 -174
- package/docs/runtime-flow.md +148 -148
- package/docs/source-runtime-refactor-map.md +107 -107
- package/index.ts +6 -6
- package/package.json +99 -98
- package/schema.json +8 -0
- package/skills/async-worker-recovery/SKILL.md +42 -42
- package/skills/context-artifact-hygiene/SKILL.md +52 -52
- package/skills/delegation-patterns/SKILL.md +54 -54
- package/skills/mailbox-interactive/SKILL.md +40 -40
- package/skills/model-routing-context/SKILL.md +39 -39
- package/skills/multi-perspective-review/SKILL.md +58 -58
- package/skills/observability-reliability/SKILL.md +41 -41
- package/skills/orchestration/SKILL.md +157 -0
- package/skills/ownership-session-security/SKILL.md +41 -41
- package/skills/pi-extension-lifecycle/SKILL.md +39 -39
- package/skills/requirements-to-task-packet/SKILL.md +63 -63
- package/skills/resource-discovery-config/SKILL.md +41 -41
- package/skills/runtime-state-reader/SKILL.md +44 -44
- package/skills/secure-agent-orchestration-review/SKILL.md +45 -45
- package/skills/state-mutation-locking/SKILL.md +42 -42
- package/skills/systematic-debugging/SKILL.md +67 -67
- package/skills/ui-render-performance/SKILL.md +39 -39
- package/skills/verification-before-done/SKILL.md +57 -57
- package/skills/worktree-isolation/SKILL.md +39 -39
- package/src/agents/agent-config.ts +6 -0
- package/src/agents/agent-search.ts +98 -0
- package/src/agents/agent-serializer.ts +4 -0
- package/src/agents/discover-agents.ts +17 -4
- package/src/config/config.ts +24 -0
- package/src/config/defaults.ts +11 -0
- package/src/extension/autonomous-policy.ts +26 -33
- package/src/extension/cross-extension-rpc.ts +82 -82
- package/src/extension/help.ts +1 -0
- package/src/extension/management.ts +5 -0
- package/src/extension/register.ts +58 -13
- package/src/extension/registration/commands.ts +33 -1
- package/src/extension/registration/compaction-guard.ts +125 -125
- package/src/extension/registration/team-tool.ts +6 -4
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-index.ts +24 -18
- package/src/extension/run-maintenance.ts +68 -62
- package/src/extension/team-tool/api.ts +23 -2
- package/src/extension/team-tool/cancel.ts +86 -11
- package/src/extension/team-tool/context.ts +3 -0
- package/src/extension/team-tool/handle-settings.ts +188 -188
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/intent-policy.ts +42 -0
- package/src/extension/team-tool/lifecycle-actions.ts +47 -18
- package/src/extension/team-tool/parallel-dispatch.ts +156 -0
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/respond.ts +10 -2
- package/src/extension/team-tool/run.ts +3 -2
- package/src/extension/team-tool/status.ts +1 -1
- package/src/extension/team-tool-types.ts +1 -0
- package/src/extension/team-tool.ts +13 -3
- package/src/hooks/registry.ts +61 -0
- package/src/hooks/types.ts +41 -0
- package/src/i18n.ts +184 -184
- package/src/observability/exporters/otlp-exporter.ts +77 -77
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/agent-control.ts +108 -2
- package/src/runtime/agent-memory.ts +72 -72
- package/src/runtime/agent-observability.ts +114 -114
- package/src/runtime/async-marker.ts +26 -26
- package/src/runtime/async-runner.ts +3 -1
- package/src/runtime/attention-events.ts +28 -28
- package/src/runtime/background-runner.ts +19 -0
- package/src/runtime/cancellation-token.ts +89 -0
- package/src/runtime/cancellation.ts +61 -51
- package/src/runtime/capability-inventory.ts +116 -0
- package/src/runtime/child-pi.ts +2 -1
- package/src/runtime/code-summary.ts +247 -0
- package/src/runtime/completion-guard.ts +190 -190
- package/src/runtime/crash-recovery.ts +181 -0
- package/src/runtime/crew-agent-records.ts +35 -7
- package/src/runtime/crew-agent-runtime.ts +1 -0
- package/src/runtime/custom-tools/irc-tool.ts +201 -0
- package/src/runtime/custom-tools/submit-result-tool.ts +90 -0
- package/src/runtime/delivery-coordinator.ts +3 -1
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/effectiveness.ts +81 -76
- package/src/runtime/event-stream-bridge.ts +90 -0
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +106 -106
- package/src/runtime/heartbeat-gradient.ts +28 -28
- package/src/runtime/heartbeat-watcher.ts +124 -124
- package/src/runtime/live-agent-control.ts +88 -88
- package/src/runtime/live-agent-manager.ts +78 -2
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-extension-bridge.ts +150 -0
- package/src/runtime/live-irc.ts +92 -0
- package/src/runtime/live-session-health.ts +100 -0
- package/src/runtime/live-session-runtime.ts +297 -7
- package/src/runtime/mcp-proxy.ts +113 -0
- package/src/runtime/notebook-helpers.ts +90 -0
- package/src/runtime/orphan-sentinel.ts +7 -0
- package/src/runtime/output-validator.ts +187 -0
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/parallel-utils.ts +57 -0
- package/src/runtime/parent-guard.ts +80 -0
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/prose-compressor.ts +164 -0
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/result-extractor.ts +121 -0
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/runtime-resolver.ts +1 -4
- package/src/runtime/semaphore.ts +131 -0
- package/src/runtime/sensitive-paths.ts +92 -0
- package/src/runtime/session-resources.ts +25 -25
- package/src/runtime/session-snapshot.ts +59 -59
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/sidechain-output.ts +29 -29
- package/src/runtime/stream-preview.ts +177 -0
- package/src/runtime/subagent-manager.ts +3 -2
- package/src/runtime/subprocess-tool-registry.ts +67 -0
- package/src/runtime/supervisor-contact.ts +59 -59
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-output-context.ts +59 -9
- package/src/runtime/task-runner/capabilities.ts +78 -78
- package/src/runtime/task-runner/live-executor.ts +2 -0
- package/src/runtime/task-runner/progress.ts +119 -119
- package/src/runtime/task-runner/prompt-builder.ts +70 -8
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/run-projection.ts +104 -0
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/task-runner.ts +75 -4
- package/src/runtime/team-runner.ts +60 -8
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/runtime/workspace-tree.ts +298 -0
- package/src/runtime/yield-handler.ts +189 -0
- package/src/schema/config-schema.ts +6 -0
- package/src/schema/team-tool-schema.ts +11 -1
- package/src/skills/discover-skills.ts +67 -0
- package/src/state/active-run-registry.ts +4 -2
- package/src/state/artifact-store.ts +4 -1
- package/src/state/atomic-write.ts +50 -1
- package/src/state/blob-store.ts +117 -0
- package/src/state/contracts.ts +1 -0
- package/src/state/event-log-rotation.ts +158 -0
- package/src/state/event-log.ts +52 -2
- package/src/state/mailbox.ts +87 -7
- package/src/state/state-store.ts +24 -4
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +20 -0
- package/src/state/usage.ts +29 -29
- package/src/subagents/async-entry.ts +1 -1
- package/src/subagents/index.ts +3 -3
- package/src/subagents/live/control.ts +1 -1
- package/src/subagents/live/manager.ts +1 -1
- package/src/subagents/live/realtime.ts +1 -1
- package/src/subagents/live/session-runtime.ts +1 -1
- package/src/subagents/manager.ts +1 -1
- package/src/subagents/spawn.ts +1 -1
- package/src/teams/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/agent-management-overlay.ts +144 -0
- package/src/ui/crew-footer.ts +101 -101
- package/src/ui/crew-select-list.ts +111 -111
- package/src/ui/crew-widget.ts +11 -2
- package/src/ui/dashboard-panes/cancellation-pane.ts +43 -0
- package/src/ui/dashboard-panes/capability-pane.ts +60 -0
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -11
- package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
- package/src/ui/dynamic-border.ts +25 -25
- package/src/ui/layout-primitives.ts +106 -106
- package/src/ui/live-run-sidebar.ts +4 -0
- package/src/ui/loaders.ts +158 -158
- package/src/ui/powerbar-publisher.ts +77 -15
- package/src/ui/render-coalescer.ts +51 -0
- package/src/ui/render-diff.ts +119 -119
- package/src/ui/render-scheduler.ts +143 -143
- package/src/ui/run-dashboard.ts +4 -0
- package/src/ui/run-event-bus.ts +209 -0
- package/src/ui/run-snapshot-cache.ts +68 -16
- package/src/ui/snapshot-types.ts +8 -0
- package/src/ui/spinner.ts +17 -17
- package/src/ui/status-colors.ts +58 -58
- package/src/ui/syntax-highlight.ts +116 -116
- package/src/ui/transcript-entries.ts +258 -0
- package/src/utils/atomic-write.ts +33 -33
- package/src/utils/completion-dedupe.ts +63 -63
- package/src/utils/frontmatter.ts +68 -68
- package/src/utils/git.ts +262 -262
- package/src/utils/ids.ts +17 -12
- package/src/utils/incremental-reader.ts +104 -0
- package/src/utils/names.ts +27 -27
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/scan-cache.ts +137 -0
- package/src/utils/sleep.ts +32 -32
- package/src/utils/sse-parser.ts +134 -0
- package/src/utils/task-name-generator.ts +337 -0
- package/src/utils/visual.ts +33 -2
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/worktree/branch-freshness.ts +45 -45
- package/src/worktree/cleanup.ts +2 -1
- package/teams/default.team.md +12 -12
- package/teams/fast-fix.team.md +11 -11
- package/teams/implementation.team.md +18 -18
- package/teams/parallel-research.team.md +14 -14
- package/teams/research.team.md +11 -11
- package/teams/review.team.md +12 -12
- package/workflows/default.workflow.md +29 -29
- package/workflows/fast-fix.workflow.md +22 -22
- package/workflows/implementation.workflow.md +38 -38
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
|
@@ -1,188 +1,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
|
-
|
|
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
|
+
|
|
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,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
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { PiTeamsConfig } from "../../config/config.ts";
|
|
2
|
+
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
3
|
+
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
4
|
+
import { configRecord, result } from "./context.ts";
|
|
5
|
+
|
|
6
|
+
export type DestructiveIntentAction = "cancel" | "cleanup" | "delete" | "forget" | "prune";
|
|
7
|
+
|
|
8
|
+
const DESTRUCTIVE_ACTION_LABELS: Record<DestructiveIntentAction, string> = {
|
|
9
|
+
cancel: "cancel",
|
|
10
|
+
cleanup: "forced cleanup",
|
|
11
|
+
delete: "delete",
|
|
12
|
+
forget: "forget",
|
|
13
|
+
prune: "prune",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function intentFromConfig(config: unknown): string | undefined {
|
|
17
|
+
const cfg = configRecord(config);
|
|
18
|
+
const rawIntent = cfg.intent ?? cfg._intent;
|
|
19
|
+
if (typeof rawIntent !== "string") return undefined;
|
|
20
|
+
const intent = rawIntent.replace(/\s+/g, " ").trim();
|
|
21
|
+
return intent ? intent.slice(0, 500) : undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function shouldRequireIntent(config: PiTeamsConfig | undefined): boolean {
|
|
25
|
+
return config?.policy?.requireIntentForDestructiveActions === true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function enforceDestructiveIntent(
|
|
29
|
+
action: DestructiveIntentAction,
|
|
30
|
+
params: TeamToolParamsValue,
|
|
31
|
+
config: PiTeamsConfig | undefined,
|
|
32
|
+
): PiTeamsToolResult | undefined {
|
|
33
|
+
if (!shouldRequireIntent(config)) return undefined;
|
|
34
|
+
if (action === "cleanup" && params.force !== true) return undefined;
|
|
35
|
+
if (intentFromConfig(params.config)) return undefined;
|
|
36
|
+
const label = DESTRUCTIVE_ACTION_LABELS[action];
|
|
37
|
+
return result(
|
|
38
|
+
`Destructive action '${label}' requires config.intent when policy.requireIntentForDestructiveActions is enabled.`,
|
|
39
|
+
{ action: action === "delete" ? "management" : action, status: "error" },
|
|
40
|
+
true,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -9,14 +9,8 @@ import { importRunBundle } from "../run-import.ts";
|
|
|
9
9
|
import { pruneFinishedRuns } from "../run-maintenance.ts";
|
|
10
10
|
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
11
11
|
import { configRecord, result, type TeamContext } from "./context.ts";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const cfg = configRecord(params.config);
|
|
15
|
-
const rawIntent = cfg.intent ?? cfg._intent;
|
|
16
|
-
if (typeof rawIntent !== "string") return undefined;
|
|
17
|
-
const intent = rawIntent.replace(/\s+/g, " ").trim();
|
|
18
|
-
return intent ? intent.slice(0, 500) : undefined;
|
|
19
|
-
}
|
|
12
|
+
import { enforceDestructiveIntent, intentFromConfig } from "./intent-policy.ts";
|
|
13
|
+
import { executeHook, appendHookEvent } from "../../hooks/registry.ts";
|
|
20
14
|
|
|
21
15
|
export function handleWorktrees(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
|
22
16
|
if (!params.runId) return result("Worktrees requires runId.", { action: "worktrees", status: "error" }, true);
|
|
@@ -47,44 +41,79 @@ export function handleImport(params: TeamToolParamsValue, ctx: TeamContext): PiT
|
|
|
47
41
|
}
|
|
48
42
|
}
|
|
49
43
|
|
|
50
|
-
export function handleExport(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
|
44
|
+
export async function handleExport(params: TeamToolParamsValue, ctx: TeamContext): Promise<PiTeamsToolResult> {
|
|
51
45
|
if (!params.runId) return result("Export requires runId.", { action: "export", status: "error" }, true);
|
|
52
46
|
const loaded = loadRunManifestById(ctx.cwd, params.runId);
|
|
53
47
|
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "export", status: "error" }, true);
|
|
48
|
+
|
|
49
|
+
const hookReport = await executeHook("before_publish", { runId: loaded.manifest.runId, cwd: ctx.cwd });
|
|
50
|
+
appendHookEvent(loaded.manifest, hookReport);
|
|
51
|
+
if (hookReport.outcome === "block") {
|
|
52
|
+
return result(`Export blocked by hook: ${hookReport.reason ?? "before_publish hook blocked the operation."}`, { action: "export", status: "error", runId: loaded.manifest.runId }, true);
|
|
53
|
+
}
|
|
54
|
+
|
|
54
55
|
const exported = exportRunBundle(loaded.manifest, loaded.tasks);
|
|
55
56
|
appendEvent(loaded.manifest.eventsPath, { type: "run.exported", runId: loaded.manifest.runId, data: exported });
|
|
56
57
|
return result([`Exported run ${loaded.manifest.runId}.`, `JSON: ${exported.jsonPath}`, `Markdown: ${exported.markdownPath}`].join("\n"), { action: "export", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
export function handlePrune(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
|
60
|
-
const
|
|
60
|
+
export async function handlePrune(params: TeamToolParamsValue, ctx: TeamContext): Promise<PiTeamsToolResult> {
|
|
61
|
+
const intentError = enforceDestructiveIntent("prune", params, ctx.config);
|
|
62
|
+
if (intentError) return intentError;
|
|
61
63
|
if (!params.confirm) return result("prune requires confirm: true.", { action: "prune", status: "error" }, true);
|
|
64
|
+
const keep = params.keep ?? 20;
|
|
62
65
|
if (keep < 0 || !Number.isInteger(keep)) return result("keep must be an integer >= 0.", { action: "prune", status: "error" }, true);
|
|
63
|
-
const intent =
|
|
64
|
-
const pruned = pruneFinishedRuns(ctx.cwd, keep, { intent });
|
|
66
|
+
const intent = intentFromConfig(params.config);
|
|
67
|
+
const pruned = pruneFinishedRuns(ctx.cwd, keep, { intent, signal: ctx.signal });
|
|
68
|
+
const firstRunId = pruned.kept[0] ?? pruned.removed[0];
|
|
69
|
+
if (firstRunId) {
|
|
70
|
+
const manifest = loadRunManifestById(ctx.cwd, firstRunId)?.manifest;
|
|
71
|
+
if (manifest) {
|
|
72
|
+
const hookReport = await executeHook("before_cleanup", { runId: manifest.runId, cwd: ctx.cwd });
|
|
73
|
+
appendHookEvent(manifest, hookReport);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
65
76
|
return result([`Pruned finished pi-crew runs.`, `Kept: ${pruned.kept.length}`, `Removed: ${pruned.removed.length}`, ...(pruned.auditPath ? [`Audit: ${pruned.auditPath}`] : []), ...(pruned.removed.length ? ["Removed runs:", ...pruned.removed.map((runId) => `- ${runId}`)] : [])].join("\n"), { action: "prune", status: "ok", intent });
|
|
66
77
|
}
|
|
67
78
|
|
|
68
|
-
export function handleForget(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
|
79
|
+
export async function handleForget(params: TeamToolParamsValue, ctx: TeamContext): Promise<PiTeamsToolResult> {
|
|
80
|
+
const intentError = enforceDestructiveIntent("forget", params, ctx.config);
|
|
81
|
+
if (intentError) return intentError;
|
|
69
82
|
if (!params.runId) return result("Forget requires runId.", { action: "forget", status: "error" }, true);
|
|
70
83
|
if (!params.confirm) return result("forget requires confirm: true.", { action: "forget", status: "error" }, true);
|
|
71
84
|
const loaded = loadRunManifestById(ctx.cwd, params.runId);
|
|
72
85
|
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "forget", status: "error" }, true);
|
|
86
|
+
|
|
87
|
+
const hookReport = await executeHook("before_forget", { runId: loaded.manifest.runId, cwd: ctx.cwd });
|
|
88
|
+
appendHookEvent(loaded.manifest, hookReport);
|
|
89
|
+
if (hookReport.outcome === "block") {
|
|
90
|
+
return result(`Forget blocked by hook: ${hookReport.reason ?? "before_forget hook blocked the operation."}`, { action: "forget", status: "error", runId: loaded.manifest.runId }, true);
|
|
91
|
+
}
|
|
92
|
+
|
|
73
93
|
const cleanup = cleanupRunWorktrees(loaded.manifest, { force: params.force });
|
|
74
94
|
if (cleanup.preserved.length > 0 && !params.force) return result([`Run '${params.runId}' has preserved worktrees. Use force: true to forget anyway.`, ...cleanup.preserved.map((item) => `- ${item.path}: ${item.reason}`)].join("\n"), { action: "forget", status: "error", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot }, true);
|
|
75
|
-
const intent =
|
|
95
|
+
const intent = intentFromConfig(params.config);
|
|
76
96
|
appendEvent(loaded.manifest.eventsPath, { type: "run.forget_requested", runId: loaded.manifest.runId, message: "Run state and artifacts are being forgotten.", data: { force: params.force === true, removedWorktrees: cleanup.removed, preservedWorktrees: cleanup.preserved, intent } });
|
|
77
97
|
fs.rmSync(loaded.manifest.stateRoot, { recursive: true, force: true });
|
|
78
98
|
fs.rmSync(loaded.manifest.artifactsRoot, { recursive: true, force: true });
|
|
79
99
|
return result([`Forgot run ${loaded.manifest.runId}.`, `Removed state: ${loaded.manifest.stateRoot}`, `Removed artifacts: ${loaded.manifest.artifactsRoot}`, ...(cleanup.removed.length ? ["Removed worktrees:", ...cleanup.removed.map((item) => `- ${item}`)] : [])].join("\n"), { action: "forget", status: "ok", runId: loaded.manifest.runId, intent });
|
|
80
100
|
}
|
|
81
101
|
|
|
82
|
-
export function handleCleanup(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
|
102
|
+
export async function handleCleanup(params: TeamToolParamsValue, ctx: TeamContext): Promise<PiTeamsToolResult> {
|
|
103
|
+
const intentError = enforceDestructiveIntent("cleanup", params, ctx.config);
|
|
104
|
+
if (intentError) return intentError;
|
|
83
105
|
if (!params.runId) return result("Cleanup requires runId.", { action: "cleanup", status: "error" }, true);
|
|
84
106
|
const loaded = loadRunManifestById(ctx.cwd, params.runId);
|
|
85
107
|
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "cleanup", status: "error" }, true);
|
|
86
|
-
|
|
87
|
-
const
|
|
108
|
+
|
|
109
|
+
const hookReport = await executeHook("before_cleanup", { runId: loaded.manifest.runId, cwd: ctx.cwd });
|
|
110
|
+
appendHookEvent(loaded.manifest, hookReport);
|
|
111
|
+
if (hookReport.outcome === "block") {
|
|
112
|
+
return result(`Cleanup blocked by hook: ${hookReport.reason ?? "before_cleanup hook blocked the operation."}`, { action: "cleanup", status: "error", runId: loaded.manifest.runId }, true);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const cleanup = cleanupRunWorktrees(loaded.manifest, { force: params.force, signal: ctx.signal });
|
|
116
|
+
const intent = intentFromConfig(params.config);
|
|
88
117
|
appendEvent(loaded.manifest.eventsPath, { type: "worktree.cleanup", runId: loaded.manifest.runId, data: { removed: cleanup.removed, preserved: cleanup.preserved, artifacts: cleanup.artifactPaths, intent } });
|
|
89
118
|
const lines = [`Worktree cleanup for ${loaded.manifest.runId}:`, "Removed:", ...(cleanup.removed.length ? cleanup.removed.map((item) => `- ${item}`) : ["- (none)"]), "Preserved:", ...(cleanup.preserved.length ? cleanup.preserved.map((item) => `- ${item.path}: ${item.reason}`) : ["- (none)"]), "Artifacts:", ...(cleanup.artifactPaths.length ? cleanup.artifactPaths.map((item) => `- ${item}`) : ["- (none)"])];
|
|
90
119
|
return result(lines.join("\n"), { action: "cleanup", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot, intent });
|