pi-crew 0.1.5 → 0.1.7
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/package.json +2 -1
- package/schema.json +24 -0
- package/src/agents/agent-config.ts +2 -0
- package/src/agents/discover-agents.ts +29 -5
- package/src/config/config.ts +148 -9
- package/src/extension/register.ts +10 -2
- package/src/extension/team-tool.ts +113 -10
- package/src/prompt/prompt-runtime.ts +12 -2
- package/src/runtime/agent-control.ts +64 -0
- package/src/runtime/agent-observability.ts +88 -0
- package/src/runtime/async-runner.ts +30 -1
- package/src/runtime/background-runner.ts +4 -2
- package/src/runtime/child-pi.ts +137 -7
- package/src/runtime/crew-agent-records.ts +137 -0
- package/src/runtime/crew-agent-runtime.ts +54 -0
- package/src/runtime/foreground-control.ts +82 -0
- package/src/runtime/group-join.ts +88 -0
- package/src/runtime/live-session-runtime.ts +33 -0
- package/src/runtime/pi-args.ts +29 -0
- package/src/runtime/policy-engine.ts +23 -0
- package/src/runtime/recovery-recipes.ts +74 -0
- package/src/runtime/role-permission.ts +28 -0
- package/src/runtime/runtime-resolver.ts +75 -0
- package/src/runtime/session-usage.ts +79 -0
- package/src/runtime/task-graph-scheduler.ts +107 -0
- package/src/runtime/task-output-context.ts +106 -0
- package/src/runtime/task-runner.ts +220 -4
- package/src/runtime/team-runner.ts +86 -14
- package/src/runtime/worker-startup.ts +57 -0
- package/src/state/contracts.ts +7 -0
- package/src/state/event-log.ts +103 -2
- package/src/state/state-store.ts +23 -2
- package/src/state/types.ts +3 -0
- package/src/ui/run-dashboard.ts +82 -10
- package/src/worktree/branch-freshness.ts +45 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-crew",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Pi extension for coordinated AI teams, workflows, worktrees, and async task orchestration",
|
|
5
5
|
"author": "baphuongna",
|
|
6
6
|
"license": "MIT",
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"@mariozechner/pi-tui": "*"
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
|
+
"jiti": "^2.6.1",
|
|
71
72
|
"typebox": "^1.1.24"
|
|
72
73
|
},
|
|
73
74
|
"devDependencies": {
|
package/schema.json
CHANGED
|
@@ -54,6 +54,30 @@
|
|
|
54
54
|
"maxTasksPerRun": { "type": "integer", "minimum": 1 },
|
|
55
55
|
"heartbeatStaleMs": { "type": "integer", "minimum": 1 }
|
|
56
56
|
}
|
|
57
|
+
},
|
|
58
|
+
"runtime": {
|
|
59
|
+
"type": "object",
|
|
60
|
+
"additionalProperties": false,
|
|
61
|
+
"description": "Crew runtime selection and live-agent behavior knobs.",
|
|
62
|
+
"properties": {
|
|
63
|
+
"mode": { "type": "string", "enum": ["auto", "scaffold", "child-process", "live-session"] },
|
|
64
|
+
"preferLiveSession": { "type": "boolean" },
|
|
65
|
+
"allowChildProcessFallback": { "type": "boolean" },
|
|
66
|
+
"maxTurns": { "type": "integer", "minimum": 1 },
|
|
67
|
+
"graceTurns": { "type": "integer", "minimum": 1 },
|
|
68
|
+
"inheritContext": { "type": "boolean" },
|
|
69
|
+
"promptMode": { "type": "string", "enum": ["replace", "append"] },
|
|
70
|
+
"groupJoin": { "type": "string", "enum": ["off", "group", "smart"] }
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"control": {
|
|
74
|
+
"type": "object",
|
|
75
|
+
"additionalProperties": false,
|
|
76
|
+
"description": "Agent control-plane settings for attention/stale activity detection.",
|
|
77
|
+
"properties": {
|
|
78
|
+
"enabled": { "type": "boolean" },
|
|
79
|
+
"needsAttentionAfterMs": { "type": "integer", "minimum": 1 }
|
|
80
|
+
}
|
|
57
81
|
}
|
|
58
82
|
}
|
|
59
83
|
}
|
|
@@ -1,6 +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
5
|
import { parseCsv, parseFrontmatter } from "../utils/frontmatter.ts";
|
|
5
6
|
import { packageRoot, projectPiRoot, userPiRoot } from "../utils/paths.ts";
|
|
6
7
|
|
|
@@ -40,6 +41,7 @@ function parseAgentFile(filePath: string, source: ResourceSource): AgentConfig |
|
|
|
40
41
|
systemPromptMode: frontmatter.systemPromptMode === "append" ? "append" : "replace",
|
|
41
42
|
inheritProjectContext: frontmatter.inheritProjectContext === "true",
|
|
42
43
|
inheritSkills: frontmatter.inheritSkills === "true",
|
|
44
|
+
disabled: frontmatter.disabled === "true" || frontmatter.enabled === "false",
|
|
43
45
|
routing: triggers || useWhen || avoidWhen || cost || category ? { triggers, useWhen, avoidWhen, cost, category } : undefined,
|
|
44
46
|
};
|
|
45
47
|
} catch {
|
|
@@ -56,18 +58,40 @@ function readAgentDir(dir: string, source: ResourceSource): AgentConfig[] {
|
|
|
56
58
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
function applyAgentOverrides(agents: AgentConfig[], cwd: string): AgentConfig[] {
|
|
62
|
+
const loaded = loadConfig(cwd);
|
|
63
|
+
const config = loaded.config.agents;
|
|
64
|
+
const overrides = config?.overrides ?? {};
|
|
65
|
+
return agents
|
|
66
|
+
.filter((agent) => !(config?.disableBuiltins && agent.source === "builtin"))
|
|
67
|
+
.map((agent) => {
|
|
68
|
+
const overrideEntry = Object.entries(overrides).find(([name]) => name.toLowerCase() === agent.name.toLowerCase());
|
|
69
|
+
if (!overrideEntry) return agent;
|
|
70
|
+
const [, override] = overrideEntry;
|
|
71
|
+
return {
|
|
72
|
+
...agent,
|
|
73
|
+
disabled: override.disabled ?? agent.disabled,
|
|
74
|
+
model: override.model === false ? undefined : override.model ?? agent.model,
|
|
75
|
+
fallbackModels: override.fallbackModels === false ? undefined : override.fallbackModels ?? agent.fallbackModels,
|
|
76
|
+
thinking: override.thinking === false ? undefined : override.thinking ?? agent.thinking,
|
|
77
|
+
tools: override.tools === false ? undefined : override.tools ?? agent.tools,
|
|
78
|
+
override: { source: "config", path: loaded.path },
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
59
83
|
export function discoverAgents(cwd: string): AgentDiscoveryResult {
|
|
60
84
|
return {
|
|
61
|
-
builtin: readAgentDir(path.join(packageRoot(), "agents"), "builtin"),
|
|
62
|
-
user: readAgentDir(path.join(userPiRoot(), "agents"), "user"),
|
|
63
|
-
project: readAgentDir(path.join(projectPiRoot(cwd), "agents"), "project"),
|
|
85
|
+
builtin: applyAgentOverrides(readAgentDir(path.join(packageRoot(), "agents"), "builtin"), cwd),
|
|
86
|
+
user: applyAgentOverrides(readAgentDir(path.join(userPiRoot(), "agents"), "user"), cwd),
|
|
87
|
+
project: applyAgentOverrides(readAgentDir(path.join(projectPiRoot(cwd), "agents"), "project"), cwd),
|
|
64
88
|
};
|
|
65
89
|
}
|
|
66
90
|
|
|
67
91
|
export function allAgents(discovery: AgentDiscoveryResult): AgentConfig[] {
|
|
68
92
|
const byName = new Map<string, AgentConfig>();
|
|
69
93
|
for (const agent of [...discovery.builtin, ...discovery.user, ...discovery.project]) {
|
|
70
|
-
byName.set(agent.name, agent);
|
|
94
|
+
byName.set(agent.name.toLowerCase(), agent);
|
|
71
95
|
}
|
|
72
|
-
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
96
|
+
return [...byName.values()].filter((agent) => !agent.disabled).sort((a, b) => a.name.localeCompare(b.name));
|
|
73
97
|
}
|
package/src/config/config.ts
CHANGED
|
@@ -23,6 +23,37 @@ export interface CrewLimitsConfig {
|
|
|
23
23
|
heartbeatStaleMs?: number;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
export type CrewRuntimeMode = "auto" | "scaffold" | "child-process" | "live-session";
|
|
27
|
+
|
|
28
|
+
export interface CrewRuntimeConfig {
|
|
29
|
+
mode?: CrewRuntimeMode;
|
|
30
|
+
preferLiveSession?: boolean;
|
|
31
|
+
allowChildProcessFallback?: boolean;
|
|
32
|
+
maxTurns?: number;
|
|
33
|
+
graceTurns?: number;
|
|
34
|
+
inheritContext?: boolean;
|
|
35
|
+
promptMode?: "replace" | "append";
|
|
36
|
+
groupJoin?: "off" | "group" | "smart";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface CrewControlConfig {
|
|
40
|
+
enabled?: boolean;
|
|
41
|
+
needsAttentionAfterMs?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface AgentOverrideConfig {
|
|
45
|
+
disabled?: boolean;
|
|
46
|
+
model?: string | false;
|
|
47
|
+
fallbackModels?: string[] | false;
|
|
48
|
+
thinking?: string | false;
|
|
49
|
+
tools?: string[] | false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface CrewAgentsConfig {
|
|
53
|
+
disableBuiltins?: boolean;
|
|
54
|
+
overrides?: Record<string, AgentOverrideConfig>;
|
|
55
|
+
}
|
|
56
|
+
|
|
26
57
|
export interface PiTeamsConfig {
|
|
27
58
|
asyncByDefault?: boolean;
|
|
28
59
|
executeWorkers?: boolean;
|
|
@@ -30,6 +61,9 @@ export interface PiTeamsConfig {
|
|
|
30
61
|
requireCleanWorktreeLeader?: boolean;
|
|
31
62
|
autonomous?: PiTeamsAutonomousConfig;
|
|
32
63
|
limits?: CrewLimitsConfig;
|
|
64
|
+
runtime?: CrewRuntimeConfig;
|
|
65
|
+
control?: CrewControlConfig;
|
|
66
|
+
agents?: CrewAgentsConfig;
|
|
33
67
|
}
|
|
34
68
|
|
|
35
69
|
export interface LoadedPiTeamsConfig {
|
|
@@ -77,6 +111,29 @@ function mergeConfig(base: PiTeamsConfig, override: PiTeamsConfig): PiTeamsConfi
|
|
|
77
111
|
...withoutUndefined((override.limits ?? {}) as Record<string, unknown>),
|
|
78
112
|
};
|
|
79
113
|
}
|
|
114
|
+
if (base.runtime || override.runtime) {
|
|
115
|
+
merged.runtime = {
|
|
116
|
+
...(base.runtime ?? {}),
|
|
117
|
+
...withoutUndefined((override.runtime ?? {}) as Record<string, unknown>),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (base.control || override.control) {
|
|
121
|
+
merged.control = {
|
|
122
|
+
...(base.control ?? {}),
|
|
123
|
+
...withoutUndefined((override.control ?? {}) as Record<string, unknown>),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (base.agents || override.agents) {
|
|
127
|
+
merged.agents = {
|
|
128
|
+
...(base.agents ?? {}),
|
|
129
|
+
...withoutUndefined((override.agents ?? {}) as Record<string, unknown>),
|
|
130
|
+
overrides: {
|
|
131
|
+
...(base.agents?.overrides ?? {}),
|
|
132
|
+
...(override.agents?.overrides ?? {}),
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (merged.agents?.overrides && Object.keys(merged.agents.overrides).length === 0) delete merged.agents.overrides;
|
|
80
137
|
return merged;
|
|
81
138
|
}
|
|
82
139
|
|
|
@@ -127,25 +184,104 @@ function parseAutonomousConfig(value: unknown): PiTeamsAutonomousConfig | undefi
|
|
|
127
184
|
};
|
|
128
185
|
}
|
|
129
186
|
|
|
130
|
-
|
|
131
|
-
|
|
187
|
+
const LIMIT_CEILINGS = {
|
|
188
|
+
maxConcurrentWorkers: 1024,
|
|
189
|
+
maxTaskDepth: 100,
|
|
190
|
+
maxChildrenPerTask: 1000,
|
|
191
|
+
maxRunMinutes: 1440,
|
|
192
|
+
maxRetriesPerTask: 100,
|
|
193
|
+
maxTasksPerRun: 10_000,
|
|
194
|
+
heartbeatStaleMs: 24 * 60 * 60 * 1000,
|
|
195
|
+
runtimeMaxTurns: 10_000,
|
|
196
|
+
runtimeGraceTurns: 1_000,
|
|
197
|
+
} as const;
|
|
198
|
+
|
|
199
|
+
function parsePositiveInteger(value: unknown, max = Number.MAX_SAFE_INTEGER): number | undefined {
|
|
200
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= max ? value : undefined;
|
|
132
201
|
}
|
|
133
202
|
|
|
134
203
|
function parseLimitsConfig(value: unknown): CrewLimitsConfig | undefined {
|
|
135
204
|
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
136
205
|
const obj = value as Record<string, unknown>;
|
|
137
206
|
const limits: CrewLimitsConfig = {
|
|
138
|
-
maxConcurrentWorkers: parsePositiveInteger(obj.maxConcurrentWorkers),
|
|
139
|
-
maxTaskDepth: parsePositiveInteger(obj.maxTaskDepth),
|
|
140
|
-
maxChildrenPerTask: parsePositiveInteger(obj.maxChildrenPerTask),
|
|
141
|
-
maxRunMinutes: parsePositiveInteger(obj.maxRunMinutes),
|
|
142
|
-
maxRetriesPerTask: parsePositiveInteger(obj.maxRetriesPerTask),
|
|
143
|
-
maxTasksPerRun: parsePositiveInteger(obj.maxTasksPerRun),
|
|
144
|
-
heartbeatStaleMs: parsePositiveInteger(obj.heartbeatStaleMs),
|
|
207
|
+
maxConcurrentWorkers: parsePositiveInteger(obj.maxConcurrentWorkers, LIMIT_CEILINGS.maxConcurrentWorkers),
|
|
208
|
+
maxTaskDepth: parsePositiveInteger(obj.maxTaskDepth, LIMIT_CEILINGS.maxTaskDepth),
|
|
209
|
+
maxChildrenPerTask: parsePositiveInteger(obj.maxChildrenPerTask, LIMIT_CEILINGS.maxChildrenPerTask),
|
|
210
|
+
maxRunMinutes: parsePositiveInteger(obj.maxRunMinutes, LIMIT_CEILINGS.maxRunMinutes),
|
|
211
|
+
maxRetriesPerTask: parsePositiveInteger(obj.maxRetriesPerTask, LIMIT_CEILINGS.maxRetriesPerTask),
|
|
212
|
+
maxTasksPerRun: parsePositiveInteger(obj.maxTasksPerRun, LIMIT_CEILINGS.maxTasksPerRun),
|
|
213
|
+
heartbeatStaleMs: parsePositiveInteger(obj.heartbeatStaleMs, LIMIT_CEILINGS.heartbeatStaleMs),
|
|
145
214
|
};
|
|
146
215
|
return Object.values(limits).some((entry) => entry !== undefined) ? limits : undefined;
|
|
147
216
|
}
|
|
148
217
|
|
|
218
|
+
function parseRuntimeMode(value: unknown): CrewRuntimeMode | undefined {
|
|
219
|
+
return value === "auto" || value === "scaffold" || value === "child-process" || value === "live-session" ? value : undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function parseRuntimeConfig(value: unknown): CrewRuntimeConfig | undefined {
|
|
223
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
224
|
+
const obj = value as Record<string, unknown>;
|
|
225
|
+
const runtime: CrewRuntimeConfig = {
|
|
226
|
+
mode: parseRuntimeMode(obj.mode),
|
|
227
|
+
preferLiveSession: typeof obj.preferLiveSession === "boolean" ? obj.preferLiveSession : undefined,
|
|
228
|
+
allowChildProcessFallback: typeof obj.allowChildProcessFallback === "boolean" ? obj.allowChildProcessFallback : undefined,
|
|
229
|
+
maxTurns: parsePositiveInteger(obj.maxTurns, LIMIT_CEILINGS.runtimeMaxTurns),
|
|
230
|
+
graceTurns: parsePositiveInteger(obj.graceTurns, LIMIT_CEILINGS.runtimeGraceTurns),
|
|
231
|
+
inheritContext: typeof obj.inheritContext === "boolean" ? obj.inheritContext : undefined,
|
|
232
|
+
promptMode: obj.promptMode === "replace" || obj.promptMode === "append" ? obj.promptMode : undefined,
|
|
233
|
+
groupJoin: obj.groupJoin === "off" || obj.groupJoin === "group" || obj.groupJoin === "smart" ? obj.groupJoin : undefined,
|
|
234
|
+
};
|
|
235
|
+
return Object.values(runtime).some((entry) => entry !== undefined) ? runtime : undefined;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function parseControlConfig(value: unknown): CrewControlConfig | undefined {
|
|
239
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
240
|
+
const obj = value as Record<string, unknown>;
|
|
241
|
+
const control: CrewControlConfig = {
|
|
242
|
+
enabled: typeof obj.enabled === "boolean" ? obj.enabled : undefined,
|
|
243
|
+
needsAttentionAfterMs: parsePositiveInteger(obj.needsAttentionAfterMs),
|
|
244
|
+
};
|
|
245
|
+
return Object.values(control).some((entry) => entry !== undefined) ? control : undefined;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function parseStringArrayOrFalse(value: unknown): string[] | false | undefined {
|
|
249
|
+
if (value === false) return false;
|
|
250
|
+
if (typeof value === "string") return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
251
|
+
if (Array.isArray(value)) return value.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0).map((entry) => entry.trim());
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function parseAgentOverride(value: unknown): AgentOverrideConfig | undefined {
|
|
256
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
257
|
+
const obj = value as Record<string, unknown>;
|
|
258
|
+
const override: AgentOverrideConfig = {
|
|
259
|
+
disabled: typeof obj.disabled === "boolean" ? obj.disabled : undefined,
|
|
260
|
+
model: typeof obj.model === "string" || obj.model === false ? obj.model : undefined,
|
|
261
|
+
fallbackModels: parseStringArrayOrFalse(obj.fallbackModels),
|
|
262
|
+
thinking: typeof obj.thinking === "string" || obj.thinking === false ? obj.thinking : undefined,
|
|
263
|
+
tools: parseStringArrayOrFalse(obj.tools),
|
|
264
|
+
};
|
|
265
|
+
return Object.values(override).some((entry) => entry !== undefined) ? override : undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function parseAgentsConfig(value: unknown): CrewAgentsConfig | undefined {
|
|
269
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
270
|
+
const obj = value as Record<string, unknown>;
|
|
271
|
+
const overrides: Record<string, AgentOverrideConfig> = {};
|
|
272
|
+
if (obj.overrides && typeof obj.overrides === "object" && !Array.isArray(obj.overrides)) {
|
|
273
|
+
for (const [name, rawOverride] of Object.entries(obj.overrides)) {
|
|
274
|
+
const parsed = parseAgentOverride(rawOverride);
|
|
275
|
+
if (parsed) overrides[name] = parsed;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const agents: CrewAgentsConfig = {
|
|
279
|
+
disableBuiltins: typeof obj.disableBuiltins === "boolean" ? obj.disableBuiltins : undefined,
|
|
280
|
+
overrides: Object.keys(overrides).length > 0 ? overrides : undefined,
|
|
281
|
+
};
|
|
282
|
+
return Object.values(agents).some((entry) => entry !== undefined) ? agents : undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
149
285
|
function parseConfig(raw: unknown): PiTeamsConfig {
|
|
150
286
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
151
287
|
const obj = raw as Record<string, unknown>;
|
|
@@ -156,6 +292,9 @@ function parseConfig(raw: unknown): PiTeamsConfig {
|
|
|
156
292
|
requireCleanWorktreeLeader: typeof obj.requireCleanWorktreeLeader === "boolean" ? obj.requireCleanWorktreeLeader : undefined,
|
|
157
293
|
autonomous: parseAutonomousConfig(obj.autonomous),
|
|
158
294
|
limits: parseLimitsConfig(obj.limits),
|
|
295
|
+
runtime: parseRuntimeConfig(obj.runtime),
|
|
296
|
+
control: parseControlConfig(obj.control),
|
|
297
|
+
agents: parseAgentsConfig(obj.agents),
|
|
159
298
|
};
|
|
160
299
|
}
|
|
161
300
|
|
|
@@ -167,7 +167,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
167
167
|
const config: Record<string, unknown> = { operation };
|
|
168
168
|
for (const token of tokens.filter((item) => item.includes("="))) {
|
|
169
169
|
const [key, ...rest] = token.split("=");
|
|
170
|
-
if (key) config[key] = rest.join("=");
|
|
170
|
+
if (key) config[key] = parseScalar(rest.join("="));
|
|
171
171
|
}
|
|
172
172
|
const result = await handleTeamTool({ action: "api", runId, config }, ctx);
|
|
173
173
|
await notifyCommandResult(ctx, commandText(result));
|
|
@@ -255,7 +255,15 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
255
255
|
if (selection.action === "reload") continue;
|
|
256
256
|
const result = selection.action === "api"
|
|
257
257
|
? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "read-manifest" } }, ctx)
|
|
258
|
-
:
|
|
258
|
+
: selection.action === "agents"
|
|
259
|
+
? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "agent-dashboard" } }, ctx)
|
|
260
|
+
: selection.action === "agent-events"
|
|
261
|
+
? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "read-agent-events", limit: 50 } }, ctx)
|
|
262
|
+
: selection.action === "agent-output"
|
|
263
|
+
? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "read-agent-output", maxBytes: 32_000 } }, ctx)
|
|
264
|
+
: selection.action === "agent-transcript"
|
|
265
|
+
? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "read-agent-transcript" } }, ctx)
|
|
266
|
+
: await handleTeamTool({ action: selection.action, runId: selection.runId }, ctx);
|
|
259
267
|
await notifyCommandResult(ctx, commandText(result));
|
|
260
268
|
return;
|
|
261
269
|
}
|
|
@@ -21,7 +21,7 @@ import { getPiSpawnCommand } from "../runtime/pi-spawn.ts";
|
|
|
21
21
|
import { executeTeamRun } from "../runtime/team-runner.ts";
|
|
22
22
|
import { spawnBackgroundTeamRun } from "../runtime/async-runner.ts";
|
|
23
23
|
import { checkProcessLiveness, isActiveRunStatus } from "../runtime/process-status.ts";
|
|
24
|
-
import { appendEvent, readEvents } from "../state/event-log.ts";
|
|
24
|
+
import { appendEvent, readEvents, readEventsCursor } from "../state/event-log.ts";
|
|
25
25
|
import { cleanupRunWorktrees } from "../worktree/cleanup.ts";
|
|
26
26
|
import { piTeamsHelp } from "./help.ts";
|
|
27
27
|
import { initializeProject } from "./project-init.ts";
|
|
@@ -35,6 +35,12 @@ import { formatValidationReport, validateResources } from "./validate-resources.
|
|
|
35
35
|
import { formatRecommendation, recommendTeam } from "./team-recommendation.ts";
|
|
36
36
|
import { toolResult, type PiTeamsToolResult } from "./tool-result.ts";
|
|
37
37
|
import { touchWorkerHeartbeat } from "../runtime/worker-heartbeat.ts";
|
|
38
|
+
import { agentEventsPath, agentOutputPath, readCrewAgentEvents, readCrewAgentEventsCursor, readCrewAgentStatus, readCrewAgents } from "../runtime/crew-agent-records.ts";
|
|
39
|
+
import { resolveCrewRuntime } from "../runtime/runtime-resolver.ts";
|
|
40
|
+
import { probeLiveSessionRuntime } from "../runtime/live-session-runtime.ts";
|
|
41
|
+
import { applyAttentionState, formatActivityAge, resolveCrewControlConfig } from "../runtime/agent-control.ts";
|
|
42
|
+
import { buildAgentDashboard, readAgentOutput } from "../runtime/agent-observability.ts";
|
|
43
|
+
import { readForegroundControlStatus, writeForegroundInterruptRequest } from "../runtime/foreground-control.ts";
|
|
38
44
|
|
|
39
45
|
export interface TeamToolDetails {
|
|
40
46
|
action: string;
|
|
@@ -255,8 +261,9 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
255
261
|
return result(text, { action: "run", status: "ok", runId: updatedManifest.runId, artifactsRoot: updatedManifest.artifactsRoot });
|
|
256
262
|
}
|
|
257
263
|
|
|
258
|
-
const
|
|
259
|
-
const
|
|
264
|
+
const runtime = await resolveCrewRuntime(loadedConfig.config);
|
|
265
|
+
const executeWorkers = runtime.kind === "child-process";
|
|
266
|
+
const executed = await executeTeamRun({ manifest: updatedManifest, tasks, team, workflow, agents, executeWorkers, limits: loadedConfig.config.limits, runtime, runtimeConfig: loadedConfig.config.runtime });
|
|
260
267
|
const text = [
|
|
261
268
|
`Created pi-crew run ${executed.manifest.runId}.`,
|
|
262
269
|
`Team: ${team.name}`,
|
|
@@ -266,9 +273,10 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
266
273
|
`State: ${executed.manifest.stateRoot}`,
|
|
267
274
|
`Artifacts: ${executed.manifest.artifactsRoot}`,
|
|
268
275
|
"",
|
|
276
|
+
`Runtime: ${runtime.kind}${runtime.fallback ? ` (fallback from ${runtime.requestedMode})` : ""}${runtime.reason ? ` - ${runtime.reason}` : ""}`,
|
|
269
277
|
executeWorkers
|
|
270
|
-
? "Child Pi worker execution was enabled
|
|
271
|
-
: "Safe scaffold mode: child Pi workers were not launched. Set PI_TEAMS_EXECUTE_WORKERS=1 to enable real worker execution.",
|
|
278
|
+
? "Child Pi worker execution was enabled."
|
|
279
|
+
: "Safe scaffold mode: child Pi workers were not launched. Set PI_TEAMS_EXECUTE_WORKERS=1 or runtime.mode=child-process to enable real worker execution.",
|
|
272
280
|
].join("\n");
|
|
273
281
|
return result(text, { action: "run", status: executed.manifest.status === "failed" ? "error" : "ok", runId: executed.manifest.runId, artifactsRoot: executed.manifest.artifactsRoot }, executed.manifest.status === "failed");
|
|
274
282
|
}
|
|
@@ -291,6 +299,8 @@ export function handleStatus(params: TeamToolParamsValue, ctx: TeamContext): PiT
|
|
|
291
299
|
const counts = new Map<string, number>();
|
|
292
300
|
for (const task of tasks) counts.set(task.status, (counts.get(task.status) ?? 0) + 1);
|
|
293
301
|
const events = readEvents(manifest.eventsPath).slice(-8);
|
|
302
|
+
const controlConfig = resolveCrewControlConfig(loadConfig(ctx.cwd).config);
|
|
303
|
+
const crewAgents = readCrewAgents(manifest).map((agent) => applyAttentionState(manifest, agent, controlConfig));
|
|
294
304
|
const artifactLines = manifest.artifacts.slice(-10).map((artifact) => `- ${artifact.kind}: ${artifact.path}${artifact.sizeBytes !== undefined ? ` (${artifact.sizeBytes} bytes)` : ""}`);
|
|
295
305
|
const totalUsage = aggregateUsage(tasks);
|
|
296
306
|
const lines = [
|
|
@@ -308,6 +318,8 @@ export function handleStatus(params: TeamToolParamsValue, ctx: TeamContext): PiT
|
|
|
308
318
|
"Tasks:",
|
|
309
319
|
...(tasks.length ? tasks.map((task) => `- ${task.id} [${task.status}] ${task.role} -> ${task.agent}${task.taskPacket ? ` scope=${task.taskPacket.scope}` : ""}${task.verification ? ` green=${task.verification.observedGreenLevel}/${task.verification.requiredGreenLevel}` : ""}${task.modelAttempts?.length ? ` attempts=${task.modelAttempts.length}` : ""}${task.jsonEvents !== undefined ? ` jsonEvents=${task.jsonEvents}` : ""}${task.usage ? ` usage=${JSON.stringify(task.usage)}` : ""}${task.worktree ? ` worktree=${task.worktree.path}` : ""}${task.error ? ` error=${task.error}` : ""}`) : ["- (none)"]),
|
|
310
320
|
`Task counts: ${[...counts.entries()].map(([status, count]) => `${status}=${count}`).join(", ") || "none"}`,
|
|
321
|
+
"Agents:",
|
|
322
|
+
...(crewAgents.length ? crewAgents.map((agent) => `- ${agent.id} [${agent.status}] ${agent.role} -> ${agent.agent} runtime=${agent.runtime}${agent.progress?.activityState === "needs_attention" ? " needs_attention" : ""}${formatActivityAge(agent) ? ` activity=${formatActivityAge(agent)}` : ""}${agent.progress?.currentTool ? ` tool=${agent.progress.currentTool}` : ""}${agent.toolUses ? ` tools=${agent.toolUses}` : ""}${agent.progress?.tokens ? ` tokens=${agent.progress.tokens}` : ""}${agent.progress?.turns ? ` turns=${agent.progress.turns}` : ""}${agent.jsonEvents !== undefined ? ` jsonEvents=${agent.jsonEvents}` : ""}${agent.statusPath ? ` status=${agent.statusPath}` : ""}${agent.error ? ` error=${agent.error}` : ""}`) : ["- (none)"]),
|
|
311
323
|
"Policy decisions:",
|
|
312
324
|
...(manifest.policyDecisions?.length ? manifest.policyDecisions.map((item) => `- ${item.action} (${item.reason})${item.taskId ? ` ${item.taskId}` : ""}: ${item.message}`) : ["- (none)"]),
|
|
313
325
|
`Total usage: ${formatUsage(totalUsage)}`,
|
|
@@ -370,8 +382,9 @@ export async function handleResume(params: TeamToolParamsValue, ctx: TeamContext
|
|
|
370
382
|
saveRunTasks(loaded.manifest, resetTasks);
|
|
371
383
|
appendEvent(loaded.manifest.eventsPath, { type: "run.resume_requested", runId: loaded.manifest.runId });
|
|
372
384
|
const loadedConfig = loadConfig(ctx.cwd);
|
|
373
|
-
const
|
|
374
|
-
const
|
|
385
|
+
const runtime = await resolveCrewRuntime(loadedConfig.config);
|
|
386
|
+
const executeWorkers = runtime.kind === "child-process";
|
|
387
|
+
const executed = await executeTeamRun({ manifest: loaded.manifest, tasks: resetTasks, team, workflow, agents: allAgents(discoverAgents(ctx.cwd)), executeWorkers, limits: loadedConfig.config.limits, runtime, runtimeConfig: loadedConfig.config.runtime });
|
|
375
388
|
return result([`Resumed run ${executed.manifest.runId}.`, `Status: ${executed.manifest.status}`, `Tasks: ${executed.tasks.length}`, `Artifacts: ${executed.manifest.artifactsRoot}`].join("\n"), { action: "resume", status: executed.manifest.status === "failed" ? "error" : "ok", runId: executed.manifest.runId, artifactsRoot: executed.manifest.artifactsRoot }, executed.manifest.status === "failed");
|
|
376
389
|
});
|
|
377
390
|
}
|
|
@@ -446,12 +459,17 @@ function autonomousPatchFromConfig(config: unknown): PiTeamsAutonomousConfig {
|
|
|
446
459
|
|
|
447
460
|
function configPatchFromConfig(config: unknown): PiTeamsConfig {
|
|
448
461
|
const cfg = configRecord(config);
|
|
462
|
+
const control = configRecord(cfg.control);
|
|
449
463
|
return {
|
|
450
464
|
asyncByDefault: typeof cfg.asyncByDefault === "boolean" ? cfg.asyncByDefault : undefined,
|
|
451
465
|
executeWorkers: typeof cfg.executeWorkers === "boolean" ? cfg.executeWorkers : undefined,
|
|
452
466
|
notifierIntervalMs: typeof cfg.notifierIntervalMs === "number" && Number.isFinite(cfg.notifierIntervalMs) ? cfg.notifierIntervalMs : undefined,
|
|
453
467
|
requireCleanWorktreeLeader: typeof cfg.requireCleanWorktreeLeader === "boolean" ? cfg.requireCleanWorktreeLeader : undefined,
|
|
454
468
|
autonomous: typeof cfg.autonomous === "object" && cfg.autonomous !== null && !Array.isArray(cfg.autonomous) ? autonomousPatchFromConfig(cfg.autonomous) : undefined,
|
|
469
|
+
control: Object.keys(control).length > 0 ? {
|
|
470
|
+
enabled: typeof control.enabled === "boolean" ? control.enabled : undefined,
|
|
471
|
+
needsAttentionAfterMs: typeof control.needsAttentionAfterMs === "number" && Number.isInteger(control.needsAttentionAfterMs) && control.needsAttentionAfterMs > 0 ? control.needsAttentionAfterMs : undefined,
|
|
472
|
+
} : undefined,
|
|
455
473
|
};
|
|
456
474
|
}
|
|
457
475
|
|
|
@@ -540,7 +558,7 @@ export function handleCleanup(params: TeamToolParamsValue, ctx: TeamContext): Pi
|
|
|
540
558
|
return result(lines.join("\n"), { action: "cleanup", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
541
559
|
}
|
|
542
560
|
|
|
543
|
-
export function handleApi(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
|
561
|
+
export async function handleApi(params: TeamToolParamsValue, ctx: TeamContext): Promise<PiTeamsToolResult> {
|
|
544
562
|
if (!params.runId) return result("API requires runId.", { action: "api", status: "error" }, true);
|
|
545
563
|
const loaded = loadRunManifestById(ctx.cwd, params.runId);
|
|
546
564
|
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "api", status: "error" }, true);
|
|
@@ -559,7 +577,92 @@ export function handleApi(params: TeamToolParamsValue, ctx: TeamContext): PiTeam
|
|
|
559
577
|
return result(JSON.stringify(task, null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
560
578
|
}
|
|
561
579
|
if (operation === "read-events") {
|
|
562
|
-
|
|
580
|
+
const sinceSeq = typeof cfg.sinceSeq === "number" ? cfg.sinceSeq : undefined;
|
|
581
|
+
const limit = typeof cfg.limit === "number" ? cfg.limit : undefined;
|
|
582
|
+
const payload = sinceSeq !== undefined || limit !== undefined
|
|
583
|
+
? readEventsCursor(loaded.manifest.eventsPath, { sinceSeq, limit })
|
|
584
|
+
: { events: readEvents(loaded.manifest.eventsPath), nextSeq: undefined, total: undefined };
|
|
585
|
+
return result(JSON.stringify(payload, null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
586
|
+
}
|
|
587
|
+
if (operation === "runtime-capabilities") {
|
|
588
|
+
const loadedConfig = loadConfig(ctx.cwd);
|
|
589
|
+
return result(JSON.stringify(await resolveCrewRuntime(loadedConfig.config), null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
590
|
+
}
|
|
591
|
+
if (operation === "probe-live-session") {
|
|
592
|
+
return result(JSON.stringify(await probeLiveSessionRuntime(), null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
593
|
+
}
|
|
594
|
+
if (operation === "list-agents") {
|
|
595
|
+
return result(JSON.stringify(readCrewAgents(loaded.manifest), null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
596
|
+
}
|
|
597
|
+
if (operation === "get-agent-result") {
|
|
598
|
+
const agentId = typeof cfg.agentId === "string" ? cfg.agentId : undefined;
|
|
599
|
+
const agent = readCrewAgents(loaded.manifest).find((item) => item.id === agentId || item.taskId === agentId);
|
|
600
|
+
if (!agent) return result("API get-agent-result requires config.agentId matching an agent id or task id.", { action: "api", status: "error", runId: loaded.manifest.runId }, true);
|
|
601
|
+
const task = loaded.tasks.find((item) => item.id === agent.taskId);
|
|
602
|
+
const text = task?.resultArtifact && fs.existsSync(task.resultArtifact.path) ? fs.readFileSync(task.resultArtifact.path, "utf-8") : JSON.stringify(agent, null, 2);
|
|
603
|
+
return result(text, { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
604
|
+
}
|
|
605
|
+
if (operation === "read-agent-status") {
|
|
606
|
+
const agentId = typeof cfg.agentId === "string" ? cfg.agentId : undefined;
|
|
607
|
+
const agent = agentId ? readCrewAgents(loaded.manifest).find((item) => item.id === agentId || item.taskId === agentId) : undefined;
|
|
608
|
+
const status = agent ? readCrewAgentStatus(loaded.manifest, agent.taskId) ?? agent : undefined;
|
|
609
|
+
if (!status) return result("API read-agent-status requires config.agentId matching an agent id or task id.", { action: "api", status: "error", runId: loaded.manifest.runId }, true);
|
|
610
|
+
return result(JSON.stringify(status, null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
611
|
+
}
|
|
612
|
+
if (operation === "read-agent-events") {
|
|
613
|
+
const agentId = typeof cfg.agentId === "string" ? cfg.agentId : undefined;
|
|
614
|
+
const agents = readCrewAgents(loaded.manifest);
|
|
615
|
+
const agent = agentId ? agents.find((item) => item.id === agentId || item.taskId === agentId) : agents[0];
|
|
616
|
+
if (!agent) return result("API read-agent-events requires config.agentId matching an agent id or task id, or at least one agent in the run.", { action: "api", status: "error", runId: loaded.manifest.runId }, true);
|
|
617
|
+
const sinceSeq = typeof cfg.sinceSeq === "number" ? cfg.sinceSeq : undefined;
|
|
618
|
+
const limit = typeof cfg.limit === "number" ? cfg.limit : undefined;
|
|
619
|
+
const payload = sinceSeq !== undefined || limit !== undefined
|
|
620
|
+
? readCrewAgentEventsCursor(loaded.manifest, agent.taskId, { sinceSeq, limit })
|
|
621
|
+
: { path: agentEventsPath(loaded.manifest, agent.taskId), events: readCrewAgentEvents(loaded.manifest, agent.taskId) };
|
|
622
|
+
return result(JSON.stringify(payload, null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
623
|
+
}
|
|
624
|
+
if (operation === "read-agent-transcript") {
|
|
625
|
+
const agentId = typeof cfg.agentId === "string" ? cfg.agentId : undefined;
|
|
626
|
+
const agents = readCrewAgents(loaded.manifest);
|
|
627
|
+
const agent = agentId ? agents.find((item) => item.id === agentId || item.taskId === agentId) : agents[0];
|
|
628
|
+
if (!agent) return result("API read-agent-transcript requires config.agentId matching an agent id or task id, or at least one agent in the run.", { action: "api", status: "error", runId: loaded.manifest.runId }, true);
|
|
629
|
+
const transcriptPath = agent.transcriptPath && fs.existsSync(agent.transcriptPath) ? agent.transcriptPath : agentOutputPath(loaded.manifest, agent.taskId);
|
|
630
|
+
const text = fs.existsSync(transcriptPath) ? fs.readFileSync(transcriptPath, "utf-8") : "";
|
|
631
|
+
return result(text || `(no transcript at ${transcriptPath})`, { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
632
|
+
}
|
|
633
|
+
if (operation === "read-agent-output") {
|
|
634
|
+
const agentId = typeof cfg.agentId === "string" ? cfg.agentId : undefined;
|
|
635
|
+
const agents = readCrewAgents(loaded.manifest);
|
|
636
|
+
const agent = agentId ? agents.find((item) => item.id === agentId || item.taskId === agentId) : agents[0];
|
|
637
|
+
if (!agent) return result("API read-agent-output requires config.agentId matching an agent id or task id, or at least one agent in the run.", { action: "api", status: "error", runId: loaded.manifest.runId }, true);
|
|
638
|
+
const maxBytes = typeof cfg.maxBytes === "number" ? cfg.maxBytes : undefined;
|
|
639
|
+
return result(JSON.stringify(readAgentOutput(loaded.manifest, agent.taskId, maxBytes), null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
640
|
+
}
|
|
641
|
+
if (operation === "agent-dashboard") {
|
|
642
|
+
return result(buildAgentDashboard(loaded.manifest).text, { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
643
|
+
}
|
|
644
|
+
if (operation === "foreground-status") {
|
|
645
|
+
return result(JSON.stringify(readForegroundControlStatus(loaded.manifest, loaded.tasks), null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
646
|
+
}
|
|
647
|
+
if (operation === "foreground-interrupt") {
|
|
648
|
+
const reason = typeof cfg.reason === "string" && cfg.reason.trim() ? cfg.reason.trim() : undefined;
|
|
649
|
+
return result(JSON.stringify(writeForegroundInterruptRequest(loaded.manifest, reason), null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
650
|
+
}
|
|
651
|
+
if (operation === "nudge-agent") {
|
|
652
|
+
const agentId = typeof cfg.agentId === "string" ? cfg.agentId : undefined;
|
|
653
|
+
const agent = readCrewAgents(loaded.manifest).find((item) => item.id === agentId || item.taskId === agentId);
|
|
654
|
+
if (!agent) return result("API nudge-agent requires config.agentId matching an agent id or task id.", { action: "api", status: "error", runId: loaded.manifest.runId }, true);
|
|
655
|
+
const messageText = typeof cfg.message === "string" && cfg.message.trim() ? cfg.message.trim() : "Please report your current status, blocker, or smallest next step.";
|
|
656
|
+
const message = appendMailboxMessage(loaded.manifest, { direction: "inbox", from: "leader", to: agent.taskId, taskId: agent.taskId, body: messageText });
|
|
657
|
+
appendEvent(loaded.manifest.eventsPath, { type: "agent.nudged", runId: loaded.manifest.runId, taskId: agent.taskId, message: messageText, data: { agentId: agent.id, mailboxMessageId: message.id } });
|
|
658
|
+
return result(JSON.stringify({ agentId: agent.id, mailboxMessage: message }, null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
659
|
+
}
|
|
660
|
+
if (operation === "steer-agent" || operation === "stop-agent" || operation === "resume-agent" || operation === "interrupt-agent") {
|
|
661
|
+
const runtime = await resolveCrewRuntime(loadConfig(ctx.cwd).config);
|
|
662
|
+
if (!runtime.steer && operation === "steer-agent") return result(`Runtime '${runtime.kind}' does not support live steering. Use nudge-agent for mailbox-based child-process coordination.`, { action: "api", status: "error", runId: loaded.manifest.runId }, true);
|
|
663
|
+
if (!runtime.resume && operation === "resume-agent") return result(`Runtime '${runtime.kind}' does not support live resume.`, { action: "api", status: "error", runId: loaded.manifest.runId }, true);
|
|
664
|
+
if (operation === "interrupt-agent" && runtime.kind !== "live-session") return result(`Runtime '${runtime.kind}' does not expose per-agent interrupt yet. Use nudge-agent or cancel the run.`, { action: "api", status: "error", runId: loaded.manifest.runId }, true);
|
|
665
|
+
return result(`Operation '${operation}' is reserved for live-session runtime and is not active for this run yet.`, { action: "api", status: "error", runId: loaded.manifest.runId }, true);
|
|
563
666
|
}
|
|
564
667
|
if (operation === "read-mailbox") {
|
|
565
668
|
const direction = cfg.direction === "inbox" || cfg.direction === "outbox" ? cfg.direction as MailboxDirection : undefined;
|
|
@@ -762,7 +865,7 @@ export async function handleTeamTool(params: TeamToolParamsValue, ctx: TeamConte
|
|
|
762
865
|
}
|
|
763
866
|
case "doctor": return handleDoctor(ctx, params);
|
|
764
867
|
case "cleanup": return handleCleanup(params, ctx);
|
|
765
|
-
case "api": return handleApi(params, ctx);
|
|
868
|
+
case "api": return await handleApi(params, ctx);
|
|
766
869
|
case "events": return handleEvents(params, ctx);
|
|
767
870
|
case "artifacts": return handleArtifacts(params, ctx);
|
|
768
871
|
case "worktrees": return handleWorktrees(params, ctx);
|
|
@@ -2,6 +2,8 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
|
2
2
|
|
|
3
3
|
export const PI_TEAMS_INHERIT_PROJECT_CONTEXT_ENV = "PI_TEAMS_INHERIT_PROJECT_CONTEXT";
|
|
4
4
|
export const PI_TEAMS_INHERIT_SKILLS_ENV = "PI_TEAMS_INHERIT_SKILLS";
|
|
5
|
+
export const PI_CREW_INHERIT_PROJECT_CONTEXT_ENV = "PI_CREW_INHERIT_PROJECT_CONTEXT";
|
|
6
|
+
export const PI_CREW_INHERIT_SKILLS_ENV = "PI_CREW_INHERIT_SKILLS";
|
|
5
7
|
|
|
6
8
|
const PROJECT_CONTEXT_HEADER = "\n\n# Project Context\n\nProject-specific instructions and guidelines:\n\n";
|
|
7
9
|
const SKILLS_HEADER = "\n\nThe following skills provide specialized instructions for specific tasks.";
|
|
@@ -13,6 +15,14 @@ function readBooleanEnv(name: string): boolean | undefined {
|
|
|
13
15
|
return value !== "0";
|
|
14
16
|
}
|
|
15
17
|
|
|
18
|
+
function readBooleanEnvAny(...names: string[]): boolean | undefined {
|
|
19
|
+
for (const name of names) {
|
|
20
|
+
const value = readBooleanEnv(name);
|
|
21
|
+
if (value !== undefined) return value;
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
16
26
|
function findSectionEnd(prompt: string, startIndex: number, nextHeaders: string[]): number {
|
|
17
27
|
let endIndex = prompt.length;
|
|
18
28
|
for (const header of nextHeaders) {
|
|
@@ -45,8 +55,8 @@ export function rewriteTeamWorkerPrompt(prompt: string, options: { inheritProjec
|
|
|
45
55
|
|
|
46
56
|
export default function registerPiTeamsPromptRuntime(pi: ExtensionAPI): void {
|
|
47
57
|
pi.on("before_agent_start", (event) => {
|
|
48
|
-
const inheritProjectContext =
|
|
49
|
-
const inheritSkills =
|
|
58
|
+
const inheritProjectContext = readBooleanEnvAny(PI_CREW_INHERIT_PROJECT_CONTEXT_ENV, PI_TEAMS_INHERIT_PROJECT_CONTEXT_ENV);
|
|
59
|
+
const inheritSkills = readBooleanEnvAny(PI_CREW_INHERIT_SKILLS_ENV, PI_TEAMS_INHERIT_SKILLS_ENV);
|
|
50
60
|
if (inheritProjectContext === undefined && inheritSkills === undefined) return;
|
|
51
61
|
const rewritten = rewriteTeamWorkerPrompt(event.systemPrompt, {
|
|
52
62
|
inheritProjectContext: inheritProjectContext ?? true,
|