poe-code 3.0.182 → 3.0.184
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/dist/bin/poe-agent.js +1 -1
- package/dist/cli/commands/dashboard-loop-shared.d.ts +2 -11
- package/dist/cli/commands/dashboard-loop-shared.js +2 -10
- package/dist/cli/commands/dashboard-loop-shared.js.map +1 -1
- package/dist/cli/commands/memory.d.ts +3 -0
- package/dist/cli/commands/memory.js +181 -0
- package/dist/cli/commands/memory.js.map +1 -0
- package/dist/cli/commands/pipeline-init.d.ts +16 -0
- package/dist/cli/commands/pipeline-init.js +168 -0
- package/dist/cli/commands/pipeline-init.js.map +1 -0
- package/dist/cli/commands/pipeline.js +264 -89
- package/dist/cli/commands/pipeline.js.map +1 -1
- package/dist/cli/commands/plan.d.ts +1 -0
- package/dist/cli/commands/plan.js +1 -1
- package/dist/cli/commands/plan.js.map +1 -1
- package/dist/cli/poe-agent-main.d.ts +10 -1
- package/dist/cli/poe-agent-main.js +89 -47
- package/dist/cli/poe-agent-main.js.map +1 -1
- package/dist/cli/program.js +5 -0
- package/dist/cli/program.js.map +1 -1
- package/dist/corpus/001-archaeoastronomy.md +479 -0
- package/dist/corpus/002-magnetohydrodynamics.md +475 -0
- package/dist/corpus/003-biosemiotics.md +483 -0
- package/dist/corpus/004-cryopedology.md +483 -0
- package/dist/corpus/005-geomicrobiology.md +479 -0
- package/dist/corpus/006-aeronomy.md +487 -0
- package/dist/corpus/007-paleoclimatology.md +479 -0
- package/dist/corpus/008-hydrogeophysics.md +479 -0
- package/dist/corpus/009-magnetostratigraphy.md +475 -0
- package/dist/corpus/010-isotope-hydrology.md +481 -0
- package/dist/corpus/011-speleothem-geochemistry.md +474 -0
- package/dist/corpus/012-astrobiogeochemistry.md +475 -0
- package/dist/corpus/013-neuroethology.md +483 -0
- package/dist/corpus/014-chronophysiology.md +483 -0
- package/dist/corpus/015-limnogeochemistry.md +475 -0
- package/dist/corpus/016-palynology.md +483 -0
- package/dist/corpus/017-volcanotectonics.md +473 -0
- package/dist/corpus/018-seismotectonics.md +473 -0
- package/dist/corpus/019-biogeomorphology.md +475 -0
- package/dist/corpus/020-geobiophysics.md +479 -0
- package/dist/corpus/021-phytolith-analysis.md +481 -0
- package/dist/corpus/022-archaeometallurgy.md +479 -0
- package/dist/corpus/023-paleomagnetism.md +479 -0
- package/dist/corpus/024-biocalorimetry.md +475 -0
- package/dist/corpus/025-atmospheric-chemiluminescence.md +473 -0
- package/dist/corpus/026-cryoseismology.md +479 -0
- package/dist/corpus/027-extremophile-radiobiology.md +475 -0
- package/dist/corpus/028-heliophysics.md +479 -0
- package/dist/corpus/029-astroparticle-geophysics.md +474 -0
- package/dist/corpus/030-glaciohydrology.md +479 -0
- package/dist/corpus/031-permafrost-microbiology.md +477 -0
- package/dist/corpus/032-ecoacoustics.md +479 -0
- package/dist/corpus/033-dendroclimatology.md +473 -0
- package/dist/corpus/034-ionospheric-tomography.md +477 -0
- package/dist/corpus/035-marine-geodesy.md +481 -0
- package/dist/corpus/036-sedimentary-ancient-dna.md +481 -0
- package/dist/corpus/037-myrmecochory-dynamics.md +474 -0
- package/dist/corpus/038-chemosensory-ecology.md +477 -0
- package/dist/corpus/039-spintronics-materials.md +479 -0
- package/dist/corpus/040-nanotoxicology.md +483 -0
- package/dist/corpus/041-cosmochemistry.md +483 -0
- package/dist/corpus/042-quaternary-geochronology.md +471 -0
- package/dist/corpus/043-biophotonics.md +479 -0
- package/dist/corpus/044-evolutionary-morphometrics.md +481 -0
- package/dist/corpus/045-cryovolcanology.md +475 -0
- package/dist/corpus/046-exoplanet-atmospheric-dynamics.md +479 -0
- package/dist/corpus/047-microbial-electrosynthesis.md +477 -0
- package/dist/corpus/048-paleoseismology.md +479 -0
- package/dist/corpus/049-actinide-geochemistry.md +477 -0
- package/dist/corpus/050-quantum-biology.md +489 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +28342 -24810
- package/dist/index.js.map +4 -4
- package/dist/providers/poe-agent.js +3716 -1863
- package/dist/providers/poe-agent.js.map +4 -4
- package/dist/sdk/pipeline.d.ts +29 -2
- package/dist/sdk/pipeline.js +209 -2
- package/dist/sdk/pipeline.js.map +1 -1
- package/dist/services/config.d.ts +30 -1
- package/dist/services/config.js +11 -0
- package/dist/services/config.js.map +1 -1
- package/dist/templates/pipeline/SKILL_plan.md +26 -13
- package/dist/templates/pipeline/steps.yaml.mustache +5 -2
- package/package.json +4 -1
- package/packages/design-system/dist/dashboard/components/stats-pane.js +2 -1
- package/packages/design-system/dist/dashboard/index.d.ts +1 -0
- package/packages/design-system/dist/dashboard/index.js +1 -0
- package/packages/design-system/dist/dashboard/should-use-dashboard.d.ts +10 -0
- package/packages/design-system/dist/dashboard/should-use-dashboard.js +7 -0
- package/packages/design-system/dist/dashboard/types.d.ts +1 -0
- package/packages/design-system/dist/index.d.ts +1 -1
- package/packages/design-system/dist/index.js +1 -1
|
@@ -3,15 +3,16 @@ import { readFile, stat } from "node:fs/promises";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { acp, cancel, createDashboard, isCancel, multiselect, promptText, select } from "@poe-code/design-system";
|
|
5
5
|
import { resolveAgentId, parseAgentSpecifier, formatAgentSpecifier, allAgents } from "@poe-code/agent-defs";
|
|
6
|
-
import {
|
|
6
|
+
import { renderAcpEvent } from "@poe-code/agent-spawn";
|
|
7
7
|
import { installSkill, resolveAgentSupport, supportedAgents } from "@poe-code/agent-skill-config";
|
|
8
8
|
import { readMergedDocument, resolveScope } from "@poe-code/poe-code-config";
|
|
9
9
|
import { pipelineConfigScope, planConfigScope } from "../../services/config.js";
|
|
10
10
|
import { ValidationError } from "../errors.js";
|
|
11
|
+
import { discoverPipelineInitSources } from "./pipeline-init.js";
|
|
11
12
|
import { createExecutionResources, resolveCommandFlags, resolveDefaultAgent } from "./shared.js";
|
|
12
|
-
import { runPipeline as sdkRunPipeline } from "../../sdk/pipeline.js";
|
|
13
|
+
import { runPipelineInit as sdkRunPipelineInit, runPipeline as sdkRunPipeline } from "../../sdk/pipeline.js";
|
|
13
14
|
import { spawn as sdkSpawn } from "../../sdk/spawn.js";
|
|
14
|
-
import { buildExecutionPrompt,
|
|
15
|
+
import { buildExecutionPrompt, interpolatePipelineVars, loadResolvedSteps, parsePlan, resolveAbsolutePlanPath, resolveFileIncludes, resolvePipelineVars, resolvePlanDirectory, resolvePlanPaths, validateResolvedPromptVars } from "@poe-code/pipeline";
|
|
15
16
|
import { createDashboardLineBuffer, formatDashboardDuration, formatDashboardTimestamp, registerDashboardQuitCommands, shouldUseInteractiveDashboard } from "./dashboard-loop-shared.js";
|
|
16
17
|
async function resolvePipelineCommandConfig(container) {
|
|
17
18
|
const configDoc = await readMergedDocument(container.fs, container.env.configPath, container.env.projectConfigPath);
|
|
@@ -47,11 +48,24 @@ function resolveMaxRuns(value) {
|
|
|
47
48
|
}
|
|
48
49
|
return parsed;
|
|
49
50
|
}
|
|
51
|
+
function resolvePipelineInitSourcePath(container, sourcePath) {
|
|
52
|
+
const absolutePath = sourcePath.startsWith("~/")
|
|
53
|
+
? path.join(container.env.homeDir, sourcePath.slice(2))
|
|
54
|
+
: path.isAbsolute(sourcePath)
|
|
55
|
+
? sourcePath
|
|
56
|
+
: path.resolve(container.env.cwd, sourcePath);
|
|
57
|
+
return {
|
|
58
|
+
absolutePath,
|
|
59
|
+
relativePath: sourcePath,
|
|
60
|
+
title: path.basename(sourcePath, path.extname(sourcePath))
|
|
61
|
+
};
|
|
62
|
+
}
|
|
50
63
|
function formatRunSummary(result) {
|
|
51
64
|
const metrics = result.metrics;
|
|
52
65
|
return [
|
|
53
66
|
`Runs: ${result.runsCompleted}`,
|
|
54
|
-
`
|
|
67
|
+
`Tasks: ${metrics.tasksCompleted} completed, ${metrics.tasksFailed} failed`,
|
|
68
|
+
`Steps: ${metrics.stepsCompleted} completed`,
|
|
55
69
|
`Total tokens: ${metrics.totalInputTokens} input, ${metrics.totalOutputTokens} output, ${metrics.totalCachedTokens} cached`,
|
|
56
70
|
`Duration: ${formatDashboardDuration(result.totalDurationMs)}`
|
|
57
71
|
].join("\n ");
|
|
@@ -96,6 +110,14 @@ function formatTaskCompleteMessage(progress) {
|
|
|
96
110
|
if (progress.phase) {
|
|
97
111
|
return `${progress.taskTitle} ${status} in ${duration}${usage}`;
|
|
98
112
|
}
|
|
113
|
+
if (progress.stepName) {
|
|
114
|
+
if (progress.success && !progress.taskCompleted) {
|
|
115
|
+
return `Task ${progress.taskId} (${progress.stepName}) step done in ${duration}${usage}`;
|
|
116
|
+
}
|
|
117
|
+
if (!progress.success) {
|
|
118
|
+
return `Task ${progress.taskId} (${progress.stepName}) failed in ${duration}${usage}`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
99
121
|
return `Task ${progress.taskId} ${status} in ${duration}${usage}`;
|
|
100
122
|
}
|
|
101
123
|
function formatDashboardCurrentAction(progress) {
|
|
@@ -174,69 +196,64 @@ async function streamAcpEventsToDashboard(options) {
|
|
|
174
196
|
}
|
|
175
197
|
function createPipelineDashboardRunAgent(options) {
|
|
176
198
|
return async (input) => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
toolBuffer.push(chunk);
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
stderr: {
|
|
203
|
-
write(chunk) {
|
|
204
|
-
sawStderr = true;
|
|
205
|
-
errorBuffer.push(chunk);
|
|
206
|
-
}
|
|
199
|
+
const toolBuffer = createDashboardLineBuffer((line) => {
|
|
200
|
+
options.appendOutput("tool", `[${options.activeStage()}] ${line}`);
|
|
201
|
+
});
|
|
202
|
+
const errorBuffer = createDashboardLineBuffer((line) => {
|
|
203
|
+
options.appendOutput("error", `[${options.activeStage()}] ${line}`);
|
|
204
|
+
});
|
|
205
|
+
let sawStdout = false;
|
|
206
|
+
let sawStderr = false;
|
|
207
|
+
try {
|
|
208
|
+
const { events, result } = sdkSpawn(input.agent, {
|
|
209
|
+
prompt: input.prompt,
|
|
210
|
+
cwd: input.cwd,
|
|
211
|
+
logDir: input.logDir,
|
|
212
|
+
model: input.model,
|
|
213
|
+
mode: input.mode,
|
|
214
|
+
...(input.mcpServers ? { mcpServers: input.mcpServers } : {}),
|
|
215
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
216
|
+
tee: {
|
|
217
|
+
stdout: {
|
|
218
|
+
write(chunk) {
|
|
219
|
+
sawStdout = true;
|
|
220
|
+
toolBuffer.push(chunk);
|
|
207
221
|
}
|
|
208
222
|
},
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
toolBuffer.push(chunk);
|
|
215
|
-
},
|
|
216
|
-
onErrorOutput(chunk) {
|
|
217
|
-
errorBuffer.push(chunk);
|
|
223
|
+
stderr: {
|
|
224
|
+
write(chunk) {
|
|
225
|
+
sawStderr = true;
|
|
226
|
+
errorBuffer.push(chunk);
|
|
227
|
+
}
|
|
218
228
|
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
229
|
+
},
|
|
230
|
+
activityTimeoutMs: 10 * 60 * 1000
|
|
231
|
+
});
|
|
232
|
+
const eventStream = streamAcpEventsToDashboard({
|
|
233
|
+
events,
|
|
234
|
+
onToolOutput(chunk) {
|
|
235
|
+
toolBuffer.push(chunk);
|
|
236
|
+
},
|
|
237
|
+
onErrorOutput(chunk) {
|
|
238
|
+
errorBuffer.push(chunk);
|
|
226
239
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
240
|
+
});
|
|
241
|
+
const [spawnResult, sawEvents] = await Promise.all([result, eventStream]);
|
|
242
|
+
if (!sawEvents && !sawStdout && spawnResult.stdout.length > 0) {
|
|
243
|
+
toolBuffer.push(spawnResult.stdout);
|
|
230
244
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
errorBuffer.flush();
|
|
234
|
-
if (!isActivityTimeoutError(error) || attempt === 3) {
|
|
235
|
-
throw error;
|
|
236
|
-
}
|
|
245
|
+
if (!sawStderr && spawnResult.stderr.length > 0) {
|
|
246
|
+
errorBuffer.push(spawnResult.stderr);
|
|
237
247
|
}
|
|
248
|
+
toolBuffer.flush();
|
|
249
|
+
errorBuffer.flush();
|
|
250
|
+
return spawnResult;
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
toolBuffer.flush();
|
|
254
|
+
errorBuffer.flush();
|
|
255
|
+
throw error;
|
|
238
256
|
}
|
|
239
|
-
throw new Error("Unreachable");
|
|
240
257
|
};
|
|
241
258
|
}
|
|
242
259
|
function dashboardStatusForResult(result) {
|
|
@@ -261,15 +278,18 @@ async function runPipelineWithDashboard(options) {
|
|
|
261
278
|
let currentAction;
|
|
262
279
|
let currentStage = "pipeline";
|
|
263
280
|
let status = "running";
|
|
281
|
+
let waitingForLock = false;
|
|
264
282
|
const syncStats = () => {
|
|
265
|
-
|
|
283
|
+
const stats = {
|
|
266
284
|
status,
|
|
267
285
|
iterations,
|
|
286
|
+
iterationsLabel: "Tasks",
|
|
268
287
|
tokensIn,
|
|
269
288
|
tokensOut,
|
|
270
289
|
elapsedMs: Math.max(0, Date.now() - startedAt),
|
|
271
290
|
...(currentAction ? { currentAction } : {})
|
|
272
|
-
}
|
|
291
|
+
};
|
|
292
|
+
dashboard.updateStats(stats);
|
|
273
293
|
};
|
|
274
294
|
const appendOutput = (kind, message) => {
|
|
275
295
|
dashboard.appendOutput({
|
|
@@ -312,6 +332,18 @@ async function runPipelineWithDashboard(options) {
|
|
|
312
332
|
onPlanReloadError(error) {
|
|
313
333
|
appendOutput("error", `Plan reload failed, using last good state: ${error.message}`);
|
|
314
334
|
},
|
|
335
|
+
onLockStatusChange(lockStatus) {
|
|
336
|
+
appendOutput("status", lockStatus.message);
|
|
337
|
+
if (lockStatus.state === "waiting") {
|
|
338
|
+
waitingForLock = true;
|
|
339
|
+
currentAction = lockStatus.message;
|
|
340
|
+
}
|
|
341
|
+
else if (waitingForLock) {
|
|
342
|
+
waitingForLock = false;
|
|
343
|
+
currentAction = undefined;
|
|
344
|
+
}
|
|
345
|
+
syncStats();
|
|
346
|
+
},
|
|
315
347
|
onPlanResolved(summary) {
|
|
316
348
|
appendOutput("info", `Config · ${formatPipelineConfigSummary({
|
|
317
349
|
agent: options.agent,
|
|
@@ -330,7 +362,9 @@ async function runPipelineWithDashboard(options) {
|
|
|
330
362
|
syncStats();
|
|
331
363
|
},
|
|
332
364
|
onTaskComplete(progress) {
|
|
333
|
-
|
|
365
|
+
if (progress.taskCompleted) {
|
|
366
|
+
iterations += 1;
|
|
367
|
+
}
|
|
334
368
|
if (progress.usage) {
|
|
335
369
|
tokensIn += progress.usage.inputTokens;
|
|
336
370
|
tokensOut += progress.usage.outputTokens;
|
|
@@ -362,11 +396,16 @@ function resolvePipelinePaths(scope, cwd, homeDir) {
|
|
|
362
396
|
? path.join(homeDir, ".poe-code", "pipeline")
|
|
363
397
|
: path.join(cwd, ".poe-code", "pipeline");
|
|
364
398
|
const displayRoot = scope === "global" ? "~/.poe-code/pipeline" : ".poe-code/pipeline";
|
|
399
|
+
const stepsDirectoryPath = path.join(rootPath, "steps");
|
|
365
400
|
return {
|
|
366
401
|
plansPath: path.join(rootPath, "plans"),
|
|
367
|
-
|
|
402
|
+
stepsDirectoryPath,
|
|
403
|
+
defaultStepsPath: path.join(stepsDirectoryPath, "default.yaml"),
|
|
404
|
+
legacyStepsPath: path.join(rootPath, "steps.yaml"),
|
|
368
405
|
displayPlansPath: `${displayRoot}/plans`,
|
|
369
|
-
|
|
406
|
+
displayStepsDirectoryPath: `${displayRoot}/steps`,
|
|
407
|
+
displayDefaultStepsPath: `${displayRoot}/steps/default.yaml`,
|
|
408
|
+
displayLegacyStepsPath: `${displayRoot}/steps.yaml`
|
|
370
409
|
};
|
|
371
410
|
}
|
|
372
411
|
async function loadPipelineTemplates() {
|
|
@@ -541,6 +580,9 @@ export function registerPipelineCommand(program, container) {
|
|
|
541
580
|
onPlanReloadError(error) {
|
|
542
581
|
resources.logger.warn(`Plan reload failed, using last good state: ${error.message}`);
|
|
543
582
|
},
|
|
583
|
+
onLockStatusChange(lockStatus) {
|
|
584
|
+
resources.logger.info(lockStatus.message);
|
|
585
|
+
},
|
|
544
586
|
onPlanResolved(summary) {
|
|
545
587
|
resources.logger.resolved("Config", formatPipelineConfigSummary({
|
|
546
588
|
agent,
|
|
@@ -589,6 +631,122 @@ export function registerPipelineCommand(program, container) {
|
|
|
589
631
|
resources.context.finalize();
|
|
590
632
|
}
|
|
591
633
|
});
|
|
634
|
+
pipeline
|
|
635
|
+
.command("init")
|
|
636
|
+
.description("Initialize pipeline plans from source Markdown docs.")
|
|
637
|
+
.argument("[question]", "Optional user question to forward to the plan generator")
|
|
638
|
+
.option("--agent <name>", "Agent to generate the plan with")
|
|
639
|
+
.option("--model <model>", "Model override passed to the agent")
|
|
640
|
+
.option("--source <path>", "Single source Markdown doc to convert")
|
|
641
|
+
.option("--sources <paths...>", "Multiple source Markdown docs to convert")
|
|
642
|
+
.action(async function (question) {
|
|
643
|
+
const flags = resolveCommandFlags(program);
|
|
644
|
+
const resources = createExecutionResources(container, flags, "pipeline:init");
|
|
645
|
+
const options = this.opts();
|
|
646
|
+
resources.logger.intro("pipeline init");
|
|
647
|
+
try {
|
|
648
|
+
let sourcePaths = options.sources;
|
|
649
|
+
let resolvedQuestion = question;
|
|
650
|
+
if (!resolvedQuestion && sourcePaths && sourcePaths.length > 0) {
|
|
651
|
+
const trailingArgument = sourcePaths[sourcePaths.length - 1];
|
|
652
|
+
if (trailingArgument && !trailingArgument.toLowerCase().endsWith(".md")) {
|
|
653
|
+
resolvedQuestion = trailingArgument;
|
|
654
|
+
sourcePaths = sourcePaths.slice(0, -1);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
let agent;
|
|
658
|
+
if (options.agent) {
|
|
659
|
+
agent = resolvePipelineAgent(options.agent);
|
|
660
|
+
}
|
|
661
|
+
else if (flags.assumeYes) {
|
|
662
|
+
agent = DEFAULT_PIPELINE_AGENT;
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
const selected = await select({
|
|
666
|
+
message: "Select agent to generate pipeline plans with:",
|
|
667
|
+
options: supportedAgents.map((value) => ({
|
|
668
|
+
value,
|
|
669
|
+
label: value
|
|
670
|
+
}))
|
|
671
|
+
});
|
|
672
|
+
if (isCancel(selected)) {
|
|
673
|
+
cancel("Pipeline init cancelled.");
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
agent = resolvePipelineAgent(selected);
|
|
677
|
+
}
|
|
678
|
+
if (options.source && sourcePaths && sourcePaths.length > 0) {
|
|
679
|
+
throw new ValidationError("Use either --source or --sources, not both.");
|
|
680
|
+
}
|
|
681
|
+
let sources;
|
|
682
|
+
if (options.source) {
|
|
683
|
+
sources = [resolvePipelineInitSourcePath(container, options.source)];
|
|
684
|
+
}
|
|
685
|
+
else if (sourcePaths && sourcePaths.length > 0) {
|
|
686
|
+
sources = sourcePaths.map((sourcePath) => resolvePipelineInitSourcePath(container, sourcePath));
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
if (flags.assumeYes) {
|
|
690
|
+
throw new ValidationError("Provide --source or --sources when using --yes.");
|
|
691
|
+
}
|
|
692
|
+
const discoveredSources = await discoverPipelineInitSources({ container });
|
|
693
|
+
if (discoveredSources.length === 0) {
|
|
694
|
+
resources.logger.info("No source documents available to initialize.");
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
const selected = await multiselect({
|
|
698
|
+
message: "Select source Markdown docs to convert:",
|
|
699
|
+
options: discoveredSources.map((source) => ({
|
|
700
|
+
value: source.relativePath,
|
|
701
|
+
label: `${source.title} (${source.relativePath})`
|
|
702
|
+
})),
|
|
703
|
+
required: true
|
|
704
|
+
});
|
|
705
|
+
if (isCancel(selected)) {
|
|
706
|
+
cancel("Pipeline init cancelled.");
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
const selectedSourcePaths = Array.isArray(selected)
|
|
710
|
+
? new Set(selected)
|
|
711
|
+
: new Set();
|
|
712
|
+
sources = discoveredSources.filter((source) => selectedSourcePaths.has(source.relativePath));
|
|
713
|
+
if (sources.length === 0) {
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
const result = await sdkRunPipelineInit({
|
|
718
|
+
agent,
|
|
719
|
+
cwd: container.env.cwd,
|
|
720
|
+
homeDir: container.env.homeDir,
|
|
721
|
+
...(options.model ? { model: options.model } : {}),
|
|
722
|
+
...(resolvedQuestion ? { question: resolvedQuestion } : {}),
|
|
723
|
+
sources,
|
|
724
|
+
assumeYes: flags.assumeYes,
|
|
725
|
+
onSourceStart(source, index, total) {
|
|
726
|
+
resources.logger.info(`Source ${index}/${total}: ${source.relativePath}`);
|
|
727
|
+
},
|
|
728
|
+
onSourceComplete(source, index, total) {
|
|
729
|
+
resources.logger.success(`Completed ${index}/${total}: ${source.relativePath}`);
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
if (result.stopReason === "failed") {
|
|
733
|
+
process.exitCode = 1;
|
|
734
|
+
resources.logger.error(result.failedSource
|
|
735
|
+
? `Pipeline init failed at ${result.failedSource}.`
|
|
736
|
+
: "Pipeline init failed.");
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (result.stopReason === "cancelled") {
|
|
740
|
+
process.exitCode = 130;
|
|
741
|
+
resources.logger.warn("Pipeline init cancelled.");
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
resources.logger.success("Pipeline init finished.");
|
|
745
|
+
}
|
|
746
|
+
finally {
|
|
747
|
+
resources.context.finalize();
|
|
748
|
+
}
|
|
749
|
+
});
|
|
592
750
|
pipeline
|
|
593
751
|
.command("validate")
|
|
594
752
|
.description("Validate a pipeline plan markdown file without running it.")
|
|
@@ -601,38 +759,43 @@ export function registerPipelineCommand(program, container) {
|
|
|
601
759
|
resources.logger.intro("pipeline validate");
|
|
602
760
|
const absolutePath = resolveAbsolutePlanPath(file, container.env.cwd, container.env.homeDir);
|
|
603
761
|
const content = await container.fs.readFile(absolutePath, "utf8");
|
|
762
|
+
const draftPlan = parsePlan(content);
|
|
604
763
|
const steps = await loadResolvedSteps({
|
|
605
764
|
cwd: container.env.cwd,
|
|
606
765
|
homeDir: container.env.homeDir,
|
|
607
|
-
fs: container.fs
|
|
766
|
+
fs: container.fs,
|
|
767
|
+
name: draftPlan.extends,
|
|
768
|
+
stepOverrides: draftPlan.stepOverrides
|
|
608
769
|
});
|
|
609
|
-
const
|
|
610
|
-
const plan = parsePlan(content, hasSteps ? { availableSteps: steps.steps } : {});
|
|
770
|
+
const plan = parsePlan(content, { availableSteps: steps.steps });
|
|
611
771
|
const total = plan.tasks.length;
|
|
612
772
|
const done = plan.tasks.filter((t) => {
|
|
613
773
|
if (typeof t.status === "string")
|
|
614
774
|
return t.status === "done";
|
|
615
775
|
return Object.values(t.status).every((s) => s === "done");
|
|
616
776
|
}).length;
|
|
777
|
+
const readFile = container.fs.readFile.bind(container.fs);
|
|
778
|
+
const resolvedVars = await resolvePipelineVars(plan.vars ?? {}, container.env.cwd, readFile);
|
|
779
|
+
const resolvedSetup = plan.setup === null ? undefined : (plan.setup ?? steps.setup);
|
|
780
|
+
const resolvedTeardown = plan.teardown === null ? undefined : (plan.teardown ?? steps.teardown);
|
|
781
|
+
validateResolvedPromptVars({
|
|
782
|
+
plan,
|
|
783
|
+
steps: steps.steps,
|
|
784
|
+
planPath: file,
|
|
785
|
+
vars: resolvedVars,
|
|
786
|
+
setup: resolvedSetup,
|
|
787
|
+
teardown: resolvedTeardown
|
|
788
|
+
});
|
|
617
789
|
resources.logger.resolved("Plan", file);
|
|
618
790
|
resources.logger.resolved("Tasks", `${total} tasks (${done} done)`);
|
|
619
|
-
if (
|
|
791
|
+
if (Object.keys(steps.steps).length > 0) {
|
|
620
792
|
resources.logger.resolved("Steps", Object.keys(steps.steps).join(", "));
|
|
621
793
|
}
|
|
622
794
|
resources.logger.success("Plan is valid.");
|
|
623
795
|
const opts = this.opts();
|
|
624
796
|
if (opts.preview) {
|
|
625
|
-
const readFile = container.fs.readFile.bind(container.fs);
|
|
626
|
-
const resolvedVars = {};
|
|
627
|
-
for (const [key, value] of Object.entries(plan.vars ?? {})) {
|
|
628
|
-
resolvedVars[key] = await resolveFileIncludes(value, container.env.cwd, readFile);
|
|
629
|
-
}
|
|
630
|
-
const resolvedSetup = plan.setup === null ? undefined : (plan.setup ?? steps.setup);
|
|
631
|
-
const resolvedTeardown = plan.teardown === null ? undefined : (plan.teardown ?? steps.teardown);
|
|
632
797
|
if (resolvedSetup) {
|
|
633
|
-
const raw =
|
|
634
|
-
? interpolate(resolvedSetup.prompt, resolvedVars)
|
|
635
|
-
: resolvedSetup.prompt;
|
|
798
|
+
const raw = interpolatePipelineVars(resolvedSetup.prompt, resolvedVars, "setup");
|
|
636
799
|
const expanded = await resolveFileIncludes(raw, container.env.cwd, readFile);
|
|
637
800
|
resources.logger.resolved("setup", expanded);
|
|
638
801
|
}
|
|
@@ -659,9 +822,7 @@ export function registerPipelineCommand(program, container) {
|
|
|
659
822
|
}
|
|
660
823
|
}
|
|
661
824
|
if (resolvedTeardown) {
|
|
662
|
-
const raw =
|
|
663
|
-
? interpolate(resolvedTeardown.prompt, resolvedVars)
|
|
664
|
-
: resolvedTeardown.prompt;
|
|
825
|
+
const raw = interpolatePipelineVars(resolvedTeardown.prompt, resolvedVars, "teardown");
|
|
665
826
|
const expanded = await resolveFileIncludes(raw, container.env.cwd, readFile);
|
|
666
827
|
resources.logger.resolved("teardown", expanded);
|
|
667
828
|
}
|
|
@@ -689,7 +850,7 @@ export function registerPipelineCommand(program, container) {
|
|
|
689
850
|
.option("--agent <name>", "Agent to install the Pipeline skill for")
|
|
690
851
|
.option("--local", "Install project-local skill and pipeline files")
|
|
691
852
|
.option("--global", "Install user-global skill and pipeline files")
|
|
692
|
-
.option("--force", "Overwrite an existing
|
|
853
|
+
.option("--force", "Overwrite an existing default step config scaffold")
|
|
693
854
|
.action(async function () {
|
|
694
855
|
const flags = resolveCommandFlags(program);
|
|
695
856
|
const resources = createExecutionResources(container, flags, "pipeline:install");
|
|
@@ -778,21 +939,35 @@ export function registerPipelineCommand(program, container) {
|
|
|
778
939
|
resources.logger.info(`Create: ${pipelinePaths.displayPlansPath}`);
|
|
779
940
|
}
|
|
780
941
|
}
|
|
781
|
-
const
|
|
942
|
+
const legacyStepsExists = await pathExists(container.fs, pipelinePaths.legacyStepsPath);
|
|
943
|
+
let stepsExists = await pathExists(container.fs, pipelinePaths.defaultStepsPath);
|
|
944
|
+
if (legacyStepsExists && !stepsExists) {
|
|
945
|
+
if (flags.dryRun) {
|
|
946
|
+
resources.logger.dryRun(`Would rename: ${pipelinePaths.displayLegacyStepsPath} -> ${pipelinePaths.displayDefaultStepsPath}`);
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
await container.fs.mkdir(pipelinePaths.stepsDirectoryPath, {
|
|
950
|
+
recursive: true
|
|
951
|
+
});
|
|
952
|
+
await container.fs.rename(pipelinePaths.legacyStepsPath, pipelinePaths.defaultStepsPath);
|
|
953
|
+
resources.logger.info(`Rename: ${pipelinePaths.displayLegacyStepsPath} -> ${pipelinePaths.displayDefaultStepsPath}`);
|
|
954
|
+
}
|
|
955
|
+
stepsExists = true;
|
|
956
|
+
}
|
|
782
957
|
if (stepsExists && !options.force) {
|
|
783
|
-
resources.logger.info(`Skip: ${pipelinePaths.
|
|
958
|
+
resources.logger.info(`Skip: ${pipelinePaths.displayDefaultStepsPath} (already exists)`);
|
|
784
959
|
}
|
|
785
960
|
else if (flags.dryRun) {
|
|
786
|
-
resources.logger.dryRun(`Would ${stepsExists ? "overwrite" : "create"}: ${pipelinePaths.
|
|
961
|
+
resources.logger.dryRun(`Would ${stepsExists ? "overwrite" : "create"}: ${pipelinePaths.displayDefaultStepsPath}`);
|
|
787
962
|
}
|
|
788
963
|
else {
|
|
789
|
-
await container.fs.mkdir(path.dirname(pipelinePaths.
|
|
964
|
+
await container.fs.mkdir(path.dirname(pipelinePaths.defaultStepsPath), {
|
|
790
965
|
recursive: true
|
|
791
966
|
});
|
|
792
|
-
await container.fs.writeFile(pipelinePaths.
|
|
967
|
+
await container.fs.writeFile(pipelinePaths.defaultStepsPath, templates.steps, {
|
|
793
968
|
encoding: "utf8"
|
|
794
969
|
});
|
|
795
|
-
resources.logger.info(`${stepsExists ? "Overwrite" : "Create"}: ${pipelinePaths.
|
|
970
|
+
resources.logger.info(`${stepsExists ? "Overwrite" : "Create"}: ${pipelinePaths.displayDefaultStepsPath}`);
|
|
796
971
|
}
|
|
797
972
|
resources.context.complete({
|
|
798
973
|
success: `Installed Pipeline skill for ${support.id} and scaffolded ${scope} pipeline files`,
|