pi-crew 0.5.1 → 0.5.2
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 +28 -0
- package/README.md +1 -1
- package/docs/actions-reference.md +87 -0
- package/docs/commands-reference.md +5 -0
- package/docs/pi-crew-bugs.md +6 -0
- package/index.ts +1 -1
- package/package.json +18 -16
- package/src/benchmark/benchmark-runner.ts +245 -0
- package/src/benchmark/feedback-loop.ts +66 -0
- package/src/extension/async-notifier.ts +1 -1
- package/src/extension/autonomous-policy.ts +1 -1
- package/src/extension/cross-extension-rpc.ts +1 -1
- package/src/extension/plan-orchestrate.ts +322 -0
- package/src/extension/register.ts +31 -41
- package/src/extension/registration/command-utils.ts +1 -1
- package/src/extension/registration/commands.ts +1 -1
- package/src/extension/registration/compaction-guard.ts +1 -1
- package/src/extension/registration/subagent-helpers.ts +1 -1
- package/src/extension/registration/subagent-tools.ts +1 -1
- package/src/extension/registration/team-tool.ts +1 -1
- package/src/extension/registration/viewers.ts +1 -1
- package/src/extension/session-summary.ts +1 -1
- package/src/extension/team-manager-command.ts +1 -1
- package/src/extension/team-tool/context.ts +1 -1
- package/src/extension/team-tool/handle-schedule.ts +183 -0
- package/src/extension/team-tool/orchestrate.ts +102 -0
- package/src/extension/team-tool/run.ts +215 -28
- package/src/extension/team-tool.ts +10 -0
- package/src/extension/tool-result.ts +1 -1
- package/src/i18n.ts +1 -1
- package/src/observability/event-to-metric.ts +1 -1
- package/src/prompt/prompt-runtime.ts +1 -1
- package/src/runtime/background-runner.ts +27 -5
- package/src/runtime/crash-recovery.ts +1 -1
- package/src/runtime/crew-hooks.ts +240 -0
- package/src/runtime/custom-tools/irc-tool.ts +1 -1
- package/src/runtime/custom-tools/submit-result-tool.ts +1 -1
- package/src/runtime/diagnostic-export.ts +38 -2
- package/src/runtime/foreground-watchdog.ts +1 -1
- package/src/runtime/live-session-runtime.ts +1 -1
- package/src/runtime/mcp-proxy.ts +1 -1
- package/src/runtime/pi-spawn.ts +20 -4
- package/src/runtime/process-status.ts +15 -2
- package/src/runtime/runtime-resolver.ts +1 -1
- package/src/runtime/session-resources.ts +1 -1
- package/src/runtime/task-runner.ts +31 -1
- package/src/runtime/team-runner.ts +6 -0
- package/src/schema/team-tool-schema.ts +24 -1
- package/src/state/crew-init.ts +56 -38
- package/src/state/decision-ledger.ts +295 -0
- package/src/state/hook-instinct-bridge.ts +90 -0
- package/src/state/hook-integrations.ts +51 -0
- package/src/state/instinct-store.ts +249 -0
- package/src/state/run-metrics.ts +135 -0
- package/src/state/tiered-eval.ts +471 -0
- package/src/state/types-eval.ts +58 -0
- package/src/state/types.ts +3 -0
- package/src/tools/safe-bash-extension.ts +5 -5
- package/src/ui/crew-widget.ts +1 -1
- package/src/ui/pi-ui-compat.ts +1 -1
- package/src/ui/run-action-dispatcher.ts +1 -1
- package/src/ui/tool-render.ts +2 -2
- package/src/utils/project-detector.ts +160 -0
- package/test-bugs-all.mjs +1 -1
- package/skills/.gitkeep +0 -0
- package/skills/REFERENCE.md +0 -136
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan Orchestrate — Decompose plan documents into agent chain commands.
|
|
3
|
+
*
|
|
4
|
+
* Parses tagged sections from markdown plan documents and builds commands
|
|
5
|
+
* for sequential agent chain execution based on ECC recommendations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Tag → Agent chain mapping from ECC recommendations.
|
|
13
|
+
*/
|
|
14
|
+
export const TAG_TO_CHAIN: Record<string, string[]> = {
|
|
15
|
+
design: ["planner", "architect"],
|
|
16
|
+
impl: ["tdd-guide", "lang-reviewer"],
|
|
17
|
+
security: ["security-reviewer", "lang-reviewer"],
|
|
18
|
+
build: ["build-error-resolver"],
|
|
19
|
+
test: ["test-engineer", "verifier"],
|
|
20
|
+
review: ["reviewer"],
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Options for plan orchestration.
|
|
25
|
+
*/
|
|
26
|
+
export interface OrchestrateOptions {
|
|
27
|
+
/** Path to the plan markdown document. */
|
|
28
|
+
planPath: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A single orchestrated step parsed from a plan section.
|
|
33
|
+
*/
|
|
34
|
+
export interface OrchestratedStep {
|
|
35
|
+
/** Unique step identifier. */
|
|
36
|
+
stepId: string;
|
|
37
|
+
/** Tag from the parsed section. */
|
|
38
|
+
tag: string;
|
|
39
|
+
/** Agent chain for this step. */
|
|
40
|
+
chain: string[];
|
|
41
|
+
/** Prompt/goal text extracted from the section. */
|
|
42
|
+
prompt: string;
|
|
43
|
+
/** Raw heading text if present. */
|
|
44
|
+
heading?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parse tagged sections from a plan markdown document.
|
|
49
|
+
*
|
|
50
|
+
* Expected format:
|
|
51
|
+
* ```markdown
|
|
52
|
+
* # Design Phase
|
|
53
|
+
* <!-- tag: design -->
|
|
54
|
+
* Design the authentication system...
|
|
55
|
+
*
|
|
56
|
+
* # Implementation
|
|
57
|
+
* <!-- tag: impl -->
|
|
58
|
+
* Implement the JWT auth...
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* @param planPath - Path to the plan markdown document.
|
|
62
|
+
* @returns Array of OrchestratedStep parsed from the document.
|
|
63
|
+
*/
|
|
64
|
+
export function parsePlanDocument(planPath: string): OrchestratedStep[] {
|
|
65
|
+
if (!fs.existsSync(planPath)) {
|
|
66
|
+
throw new Error(`Plan document not found: ${planPath}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const content = fs.readFileSync(planPath, "utf-8");
|
|
70
|
+
return parsePlanDocumentContent(content);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Parse tagged sections from plan content string.
|
|
75
|
+
* This is the core parsing logic used by both parsePlanDocument and parsePlanDocumentSimple.
|
|
76
|
+
*/
|
|
77
|
+
function parsePlanDocumentContent(content: string): OrchestratedStep[] {
|
|
78
|
+
const steps: OrchestratedStep[] = [];
|
|
79
|
+
|
|
80
|
+
// Find all tag matches with their positions
|
|
81
|
+
const tagRegex = /<!--\s*tag:\s*(\w+)\s*-->/g;
|
|
82
|
+
const tagMatches: Array<{ tag: string; start: number; end: number }> = [];
|
|
83
|
+
|
|
84
|
+
let match: RegExpExecArray | null;
|
|
85
|
+
while ((match = tagRegex.exec(content)) !== null) {
|
|
86
|
+
tagMatches.push({
|
|
87
|
+
tag: match[1],
|
|
88
|
+
start: match.index,
|
|
89
|
+
end: match.index + match[0].length,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (tagMatches.length === 0) {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// For each tag, extract the content before it (to get heading)
|
|
98
|
+
// and the content after it until either another tag or section
|
|
99
|
+
for (let i = 0; i < tagMatches.length; i++) {
|
|
100
|
+
const current = tagMatches[i];
|
|
101
|
+
const nextTagStart = i < tagMatches.length - 1 ? tagMatches[i + 1].start : content.length;
|
|
102
|
+
|
|
103
|
+
// Find the heading by looking back from the tag position.
|
|
104
|
+
// Use global regex to find ALL headings in the window, then take the LAST
|
|
105
|
+
// (nearest to the tag) — simple `.match()` only finds the FIRST heading.
|
|
106
|
+
const textBeforeTag = content.slice(
|
|
107
|
+
Math.max(0, current.start - 500),
|
|
108
|
+
current.start,
|
|
109
|
+
);
|
|
110
|
+
const headingRegex = /(^|\n)(#{1,6})\s+(.+?)(\n|$)/g;
|
|
111
|
+
let lastHeadingMatch: RegExpExecArray | null = null;
|
|
112
|
+
let hm: RegExpExecArray | null;
|
|
113
|
+
while ((hm = headingRegex.exec(textBeforeTag)) !== null) {
|
|
114
|
+
lastHeadingMatch = hm;
|
|
115
|
+
}
|
|
116
|
+
const heading = lastHeadingMatch ? lastHeadingMatch[3].trim() : undefined;
|
|
117
|
+
|
|
118
|
+
// Get content after the tag until next tag or heading
|
|
119
|
+
// Start from end of tag comment, skip any whitespace/newline
|
|
120
|
+
const afterTagContent = content.slice(current.end);
|
|
121
|
+
|
|
122
|
+
// Find the section content: capture until next heading (##) or next tag
|
|
123
|
+
const sectionEndMatch = afterTagContent.search(/(^|\n)(#{1,6}\s|\n<!--\s*tag:)/m);
|
|
124
|
+
const sectionContent =
|
|
125
|
+
sectionEndMatch >= 0
|
|
126
|
+
? afterTagContent.slice(0, sectionEndMatch)
|
|
127
|
+
: afterTagContent;
|
|
128
|
+
|
|
129
|
+
// Extract prompt text - remove the tag comment lines and empty lines
|
|
130
|
+
const promptLines: string[] = [];
|
|
131
|
+
const lines = sectionContent.split("\n");
|
|
132
|
+
for (const line of lines) {
|
|
133
|
+
const trimmed = line.trim();
|
|
134
|
+
if (!trimmed || trimmed.startsWith("<!--")) continue;
|
|
135
|
+
promptLines.push(trimmed);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const prompt = promptLines.join("\n").trim();
|
|
139
|
+
if (!prompt) continue;
|
|
140
|
+
|
|
141
|
+
const chain = TAG_TO_CHAIN[current.tag] ?? [];
|
|
142
|
+
|
|
143
|
+
steps.push({
|
|
144
|
+
stepId: `step-${(i + 1).toString().padStart(2, "0")}-${current.tag}`,
|
|
145
|
+
tag: current.tag,
|
|
146
|
+
chain,
|
|
147
|
+
prompt,
|
|
148
|
+
heading,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return steps;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Alternative simpler parser for plans that use explicit tag blocks.
|
|
157
|
+
* Actually uses the same core logic as parsePlanDocument.
|
|
158
|
+
*
|
|
159
|
+
* @param planPath - Path to the plan markdown document.
|
|
160
|
+
* @returns Array of OrchestratedStep parsed from the document.
|
|
161
|
+
*/
|
|
162
|
+
export function parsePlanDocumentSimple(planPath: string): OrchestratedStep[] {
|
|
163
|
+
if (!fs.existsSync(planPath)) {
|
|
164
|
+
throw new Error(`Plan document not found: ${planPath}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const content = fs.readFileSync(planPath, "utf-8");
|
|
168
|
+
const steps = parsePlanDocumentContent(content);
|
|
169
|
+
|
|
170
|
+
if (steps.length === 0) {
|
|
171
|
+
// Try implicit detection
|
|
172
|
+
const tag = detectImplicitTag(content);
|
|
173
|
+
if (tag) {
|
|
174
|
+
return [
|
|
175
|
+
{
|
|
176
|
+
stepId: "step-01-unknown",
|
|
177
|
+
tag,
|
|
178
|
+
chain: TAG_TO_CHAIN[tag] ?? [],
|
|
179
|
+
prompt: content.trim(),
|
|
180
|
+
},
|
|
181
|
+
];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return steps;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Detect implicit tag from content keywords when no explicit tag is present.
|
|
190
|
+
*/
|
|
191
|
+
function detectImplicitTag(content: string): string | undefined {
|
|
192
|
+
const lowerContent = content.toLowerCase();
|
|
193
|
+
// Use word-boundary regex to avoid substring false matches.
|
|
194
|
+
// E.g., "implementation" must NOT trigger "impl" — only the word "implement" should.
|
|
195
|
+
const hasWord = (word: string): boolean =>
|
|
196
|
+
new RegExp(`\\b${word}\\b`).test(lowerContent);
|
|
197
|
+
|
|
198
|
+
if (hasWord("design") || hasWord("architecture")) return "design";
|
|
199
|
+
if (hasWord("implement") || hasWord("coding")) return "impl";
|
|
200
|
+
if (hasWord("security") || hasWord("audit")) return "security";
|
|
201
|
+
if (hasWord("build") || hasWord("compile")) return "build";
|
|
202
|
+
if (hasWord("test") || hasWord("verify")) return "test";
|
|
203
|
+
if (hasWord("review") || hasWord("feedback")) return "review";
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Build agent chain command strings from orchestrated steps.
|
|
209
|
+
*
|
|
210
|
+
* @param steps - Array of OrchestratedStep to convert to commands.
|
|
211
|
+
* @returns Array of command strings for execution.
|
|
212
|
+
*/
|
|
213
|
+
export function buildAgentChain(steps: OrchestratedStep[]): string[] {
|
|
214
|
+
return steps.map((step) => {
|
|
215
|
+
const agentList = step.chain.join(",");
|
|
216
|
+
// Escape single quotes in the goal for shell safety
|
|
217
|
+
const escapedGoal = step.prompt.replace(/'/g, "'\\''");
|
|
218
|
+
return `team action='run' agent='${agentList}' goal='${escapedGoal}'`;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Build structured chain data (useful for programmatic use).
|
|
224
|
+
*
|
|
225
|
+
* @param steps - Array of OrchestratedStep to convert.
|
|
226
|
+
* @returns Array of chain objects with step data and commands.
|
|
227
|
+
*/
|
|
228
|
+
export function buildChainData(
|
|
229
|
+
steps: OrchestratedStep[],
|
|
230
|
+
): Array<{
|
|
231
|
+
step: OrchestratedStep;
|
|
232
|
+
commands: string[];
|
|
233
|
+
}> {
|
|
234
|
+
return steps.map((step) => ({
|
|
235
|
+
step,
|
|
236
|
+
commands: buildAgentChain([step]),
|
|
237
|
+
}));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Parse and return a formatted overview of the plan.
|
|
242
|
+
*
|
|
243
|
+
* @param planPath - Path to the plan markdown document.
|
|
244
|
+
* @returns Summary string with step count and breakdown by tag.
|
|
245
|
+
*/
|
|
246
|
+
export function formatPlanOverview(planPath: string): string {
|
|
247
|
+
const steps = parsePlanDocument(planPath);
|
|
248
|
+
|
|
249
|
+
if (steps.length === 0) {
|
|
250
|
+
// Try simple parser
|
|
251
|
+
const simpleSteps = parsePlanDocumentSimple(planPath);
|
|
252
|
+
if (simpleSteps.length === 0) {
|
|
253
|
+
return "No tagged sections found in plan document.";
|
|
254
|
+
}
|
|
255
|
+
return formatStepsOverview(simpleSteps);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return formatStepsOverview(steps);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function formatStepsOverview(steps: OrchestratedStep[]): string {
|
|
262
|
+
const lines: string[] = [
|
|
263
|
+
`Plan Orchestration: ${steps.length} step(s)`,
|
|
264
|
+
"",
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
const tagCounts: Record<string, number> = {};
|
|
268
|
+
for (const step of steps) {
|
|
269
|
+
tagCounts[step.tag] = (tagCounts[step.tag] ?? 0) + 1;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
lines.push("Summary by tag:");
|
|
273
|
+
for (const [tag, count] of Object.entries(tagCounts)) {
|
|
274
|
+
const chain = TAG_TO_CHAIN[tag]?.join(", ") ?? "(unknown)";
|
|
275
|
+
lines.push(` - ${tag}: ${count} step(s) → agents: ${chain}`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
lines.push("", "Steps:");
|
|
279
|
+
for (const step of steps) {
|
|
280
|
+
const preview =
|
|
281
|
+
step.prompt.length > 60
|
|
282
|
+
? step.prompt.slice(0, 60) + "..."
|
|
283
|
+
: step.prompt;
|
|
284
|
+
// Include heading if available (e.g., "Security Review" from the plan's heading)
|
|
285
|
+
const headingPrefix = step.heading ? `${step.heading}: ` : "";
|
|
286
|
+
lines.push(
|
|
287
|
+
` ${step.stepId} [${step.tag}] ${step.chain.join(",")}: ${headingPrefix}${preview}`,
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return lines.join("\n");
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Main orchestration function that parses a plan and returns pre-formatted output.
|
|
296
|
+
*/
|
|
297
|
+
export async function orchestratePlan(
|
|
298
|
+
options: OrchestrateOptions,
|
|
299
|
+
): Promise<{
|
|
300
|
+
steps: OrchestratedStep[];
|
|
301
|
+
chain: string[];
|
|
302
|
+
overview: string;
|
|
303
|
+
}> {
|
|
304
|
+
const { planPath } = options;
|
|
305
|
+
|
|
306
|
+
// Try primary parser first
|
|
307
|
+
let steps = parsePlanDocument(planPath);
|
|
308
|
+
|
|
309
|
+
// Fall back to simple parser if no results
|
|
310
|
+
if (steps.length === 0) {
|
|
311
|
+
steps = parsePlanDocumentSimple(planPath);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (steps.length === 0) {
|
|
315
|
+
throw new Error(`No tagged sections found in plan document: ${planPath}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const chain = buildAgentChain(steps);
|
|
319
|
+
const overview = formatPlanOverview(planPath);
|
|
320
|
+
|
|
321
|
+
return { steps, chain, overview };
|
|
322
|
+
}
|
|
@@ -4,8 +4,9 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
import type {
|
|
5
5
|
ExtensionAPI,
|
|
6
6
|
ExtensionContext,
|
|
7
|
-
} from "@
|
|
7
|
+
} from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import { loadConfig } from "../config/config.ts";
|
|
9
|
+
import { applyCrewSettingsToConfig, loadCrewSettings, saveCrewSettings } from "../runtime/settings-store.ts";
|
|
9
10
|
// 2.7: Lazy-load LiveRunSidebar — only constructed when the user actually opens
|
|
10
11
|
// a live run sidebar overlay. The class pulls in transcript-viewer and other
|
|
11
12
|
// heavy UI modules.
|
|
@@ -50,10 +51,6 @@ import { listLiveAgents } from "../runtime/live-agent-manager.ts";
|
|
|
50
51
|
import { createManifestCache } from "../runtime/manifest-cache.ts";
|
|
51
52
|
import { checkProcessLiveness } from "../runtime/process-status.ts";
|
|
52
53
|
import { CrewScheduler } from "../runtime/scheduler.ts";
|
|
53
|
-
import {
|
|
54
|
-
applyCrewSettingsToConfig,
|
|
55
|
-
loadCrewSettings,
|
|
56
|
-
} from "../runtime/settings-store.ts";
|
|
57
54
|
import { appendEvent } from "../state/event-log.ts";
|
|
58
55
|
import { loadRunManifestById, updateRunStatus } from "../state/state-store.ts";
|
|
59
56
|
import type { TeamRunManifest } from "../state/types.ts";
|
|
@@ -109,6 +106,7 @@ import {
|
|
|
109
106
|
} from "./registration/subagent-helpers.ts";
|
|
110
107
|
import { registerSubagentTools } from "./registration/subagent-tools.ts";
|
|
111
108
|
import { registerTeamTool } from "./registration/team-tool.ts";
|
|
109
|
+
import { handleTeamTool } from "./team-tool.ts";
|
|
112
110
|
|
|
113
111
|
let _cachedOTLPExporter: typeof OTLPExporterType | undefined;
|
|
114
112
|
async function importOTLPExporter(): Promise<typeof OTLPExporterType> {
|
|
@@ -916,34 +914,6 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
916
914
|
});
|
|
917
915
|
}
|
|
918
916
|
}
|
|
919
|
-
// Always send followUp notification regardless of ownerCurrent.
|
|
920
|
-
// The run completed — the assistant needs to know even if the
|
|
921
|
-
// originating session generation has changed (compaction, etc.).
|
|
922
|
-
if (runId) {
|
|
923
|
-
const loaded = loadRunManifestById(ctx.cwd, runId);
|
|
924
|
-
const status = loaded?.manifest.status ?? "finished";
|
|
925
|
-
const teamName = loaded?.manifest.team ?? "unknown";
|
|
926
|
-
const goalSummary = (loaded?.manifest.goal ?? "").slice(
|
|
927
|
-
0,
|
|
928
|
-
100,
|
|
929
|
-
);
|
|
930
|
-
try {
|
|
931
|
-
pi.sendUserMessage(
|
|
932
|
-
[
|
|
933
|
-
`pi-crew run ${status}: ${runId} (${teamName})`,
|
|
934
|
-
`Goal: ${goalSummary}`,
|
|
935
|
-
status === "completed"
|
|
936
|
-
? "Review the run results. If the run modified source files, run tests to verify. Summarize what was done."
|
|
937
|
-
: "The run ended with status: " +
|
|
938
|
-
status +
|
|
939
|
-
". Check the run artifacts and take appropriate action.",
|
|
940
|
-
].join("\n"),
|
|
941
|
-
{ deliverAs: "followUp" },
|
|
942
|
-
);
|
|
943
|
-
} catch {
|
|
944
|
-
/* non-critical */
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
917
|
if (ownerCurrent && currentCtx) {
|
|
948
918
|
const config = loadConfig(currentCtx.cwd).config.ui;
|
|
949
919
|
updateCrewWidget(
|
|
@@ -1325,18 +1295,43 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1325
1295
|
applyCrewSettingsToConfig(loadedConfig.config, crewSettings);
|
|
1326
1296
|
|
|
1327
1297
|
// Start scheduler with event-based executor
|
|
1298
|
+
// Resolve sessionId before the scheduler executor closure captures it.
|
|
1299
|
+
const sessionId =
|
|
1300
|
+
ctx.sessionManager?.getSessionId?.() ??
|
|
1301
|
+
(typeof ctx === "object" && ctx !== null && "sessionId" in ctx
|
|
1302
|
+
? (ctx as Record<string, unknown>).sessionId
|
|
1303
|
+
: undefined);
|
|
1328
1304
|
crewScheduler = new CrewScheduler();
|
|
1329
1305
|
crewScheduler.start({
|
|
1330
1306
|
emit: (event) => {
|
|
1331
1307
|
if (cleanedUp) return;
|
|
1308
|
+
pi.events?.emit?.("crew-scheduler", event);
|
|
1332
1309
|
},
|
|
1333
1310
|
executor: (job) => {
|
|
1311
|
+
let runParams: { action: string; team: string; goal: string };
|
|
1312
|
+
try {
|
|
1313
|
+
runParams = JSON.parse(job.prompt);
|
|
1314
|
+
} catch {
|
|
1315
|
+
runParams = { action: "run", team: "default", goal: job.prompt };
|
|
1316
|
+
}
|
|
1317
|
+
if (runParams.action !== "run") return `scheduled-${job.id}-${Date.now()}`;
|
|
1318
|
+
setImmediate(async () => {
|
|
1319
|
+
try {
|
|
1320
|
+
await handleTeamTool(
|
|
1321
|
+
{ action: "run", team: runParams.team, goal: runParams.goal, async: true },
|
|
1322
|
+
{ cwd: ctx.cwd, sessionId },
|
|
1323
|
+
);
|
|
1324
|
+
} catch (err) {
|
|
1325
|
+
logInternalError("scheduler.execute", err);
|
|
1326
|
+
}
|
|
1327
|
+
});
|
|
1334
1328
|
return `scheduled-${job.id}-${Date.now()}`;
|
|
1335
1329
|
},
|
|
1336
|
-
finalizer: (
|
|
1337
|
-
// no-op for now; future: launch team run
|
|
1338
|
-
},
|
|
1330
|
+
finalizer: () => {},
|
|
1339
1331
|
});
|
|
1332
|
+
// Wire scheduler into handle-schedule.ts so handlers can add/list jobs.
|
|
1333
|
+
// Uses a global symbol so the module doesn't need a direct circular import.
|
|
1334
|
+
(globalThis as Record<symbol | string, unknown>)[Symbol.for("pi-crew:scheduler")] = crewScheduler;
|
|
1340
1335
|
// Load scheduled jobs from settings if present
|
|
1341
1336
|
if (Array.isArray((crewSettings as any).scheduledJobs)) {
|
|
1342
1337
|
for (const job of (crewSettings as any).scheduledJobs) {
|
|
@@ -1351,11 +1346,6 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1351
1346
|
configureNotifications(ctx);
|
|
1352
1347
|
configureObservability(ctx);
|
|
1353
1348
|
configureDeliveryCoordinator();
|
|
1354
|
-
const sessionId =
|
|
1355
|
-
ctx.sessionManager?.getSessionId?.() ??
|
|
1356
|
-
(typeof ctx === "object" && ctx !== null && "sessionId" in ctx
|
|
1357
|
-
? (ctx as Record<string, unknown>).sessionId
|
|
1358
|
-
: undefined);
|
|
1359
1349
|
if (typeof sessionId === "string" && sessionId)
|
|
1360
1350
|
deliveryCoordinator?.activate(sessionId);
|
|
1361
1351
|
tryRegisterSessionCleanup(pi, () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionCommandContext } from "@
|
|
1
|
+
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
3
3
|
|
|
4
4
|
export function parseRunArgs(args: string): TeamToolParamsValue {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { loadConfig } from "../../config/config.ts";
|
|
3
3
|
// Lazy-loaded: team-tool.ts pulls in entire runtime chain (1.4s+).
|
|
4
4
|
import type { handleTeamTool as HandleTeamToolFn } from "../team-tool.ts";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { listRecentRuns } from "../run-index.ts";
|
|
3
3
|
import type { ArtifactDescriptor, TeamRunManifest } from "../../state/types.ts";
|
|
4
4
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@
|
|
2
|
+
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { loadRunManifestById } from "../../state/state-store.ts";
|
|
4
4
|
import { savePersistedSubagentRecord, type SubagentRecord, type SubagentSpawnOptions } from "../../subagents/manager.ts";
|
|
5
5
|
import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionAPI, ToolDefinition } from "@
|
|
1
|
+
import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "@sinclair/typebox";
|
|
3
3
|
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
4
4
|
// Lazy-loaded: team-tool.ts pulls in entire runtime chain.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import type { ExtensionAPI, ExtensionContext, ToolDefinition } from "@
|
|
2
|
+
import type { ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { loadConfig } from "../../config/config.ts";
|
|
4
4
|
import { TeamToolParams, type TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
5
5
|
import type { CrewWidgetState } from "../../ui/crew-widget.ts";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionCommandContext } from "@
|
|
1
|
+
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { loadRunManifestById } from "../../state/state-store.ts";
|
|
3
3
|
import { readCrewAgents } from "../../runtime/crew-agent-records.ts";
|
|
4
4
|
import { loadConfig } from "../../config/config.ts";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionContext } from "@
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { isDisplayActiveRun } from "../runtime/process-status.ts";
|
|
3
3
|
import { listRuns } from "./run-index.ts";
|
|
4
4
|
import { readCrewAgents } from "../runtime/crew-agent-records.ts";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionCommandContext } from "@
|
|
1
|
+
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { listRuns } from "./run-index.ts";
|
|
3
3
|
// Lazy-loaded: team-tool.ts pulls in entire runtime chain.
|
|
4
4
|
import type { handleTeamTool as HandleTeamToolFn } from "./team-tool.ts";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionContext } from "@
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { PiTeamsConfig } from "../../config/config.ts";
|
|
3
3
|
import type { MetricRegistry } from "../../observability/metric-registry.ts";
|
|
4
4
|
import type { TeamToolDetails } from "../team-tool-types.ts";
|