stageflow 0.27.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/builtin-skills/browser/SKILL.md +42 -0
- package/dist/agent/claudeAdapter.js +2 -2
- package/dist/agent/cursorProvider.d.ts +2 -0
- package/dist/agent/cursorProvider.js +30 -5
- package/dist/agent/fakeAgent.d.ts +1 -0
- package/dist/agent/fakeAgent.js +13 -0
- package/dist/agent/piAdapter.d.ts +3 -24
- package/dist/agent/piAdapter.js +49 -47
- package/dist/agent/piSessionFactory.d.ts +38 -0
- package/dist/agent/piSessionFactory.js +60 -0
- package/dist/agent/port.d.ts +2 -0
- package/dist/agent/streamLogRedact.d.ts +6 -0
- package/dist/agent/streamLogRedact.js +23 -1
- package/dist/browser/anchor.d.ts +27 -0
- package/dist/browser/anchor.js +127 -0
- package/dist/browser/auditSink.d.ts +38 -0
- package/dist/browser/auditSink.js +34 -0
- package/dist/browser/browserHost.d.ts +86 -0
- package/dist/browser/browserHost.js +7 -0
- package/dist/browser/browserSweep.d.ts +14 -0
- package/dist/browser/browserSweep.js +54 -0
- package/dist/browser/browserTeardown.d.ts +56 -0
- package/dist/browser/browserTeardown.js +235 -0
- package/dist/browser/fakeRemoteBrowserHost.d.ts +2 -0
- package/dist/browser/fakeRemoteBrowserHost.js +34 -0
- package/dist/browser/gateHandoff.d.ts +25 -0
- package/dist/browser/gateHandoff.js +83 -0
- package/dist/browser/humanLogin.d.ts +18 -0
- package/dist/browser/humanLogin.js +34 -0
- package/dist/browser/keyProvider.d.ts +10 -0
- package/dist/browser/keyProvider.js +39 -0
- package/dist/browser/localBrowserHost.d.ts +10 -0
- package/dist/browser/localBrowserHost.js +152 -0
- package/dist/browser/localProfileLock.d.ts +2 -0
- package/dist/browser/localProfileLock.js +218 -0
- package/dist/browser/localProfileStore.d.ts +5 -0
- package/dist/browser/localProfileStore.js +91 -0
- package/dist/browser/loginCheck.d.ts +53 -0
- package/dist/browser/loginCheck.js +147 -0
- package/dist/browser/memoryProfileLock.d.ts +2 -0
- package/dist/browser/memoryProfileLock.js +53 -0
- package/dist/browser/memoryProfileStore.d.ts +2 -0
- package/dist/browser/memoryProfileStore.js +31 -0
- package/dist/browser/navigationAudit.d.ts +16 -0
- package/dist/browser/navigationAudit.js +53 -0
- package/dist/browser/persistedEnv.d.ts +3 -0
- package/dist/browser/persistedEnv.js +15 -0
- package/dist/browser/profileLock.d.ts +40 -0
- package/dist/browser/profileLock.js +10 -0
- package/dist/browser/profileStore.d.ts +23 -0
- package/dist/browser/profileStore.js +39 -0
- package/dist/browser/runLiveness.d.ts +13 -0
- package/dist/browser/runLiveness.js +39 -0
- package/dist/browser/sitePolicy.d.ts +10 -0
- package/dist/browser/sitePolicy.js +61 -0
- package/dist/browser/stageBrowserEnv.d.ts +22 -0
- package/dist/browser/stageBrowserEnv.js +147 -0
- package/dist/browser/stageProfileLock.d.ts +17 -0
- package/dist/browser/stageProfileLock.js +39 -0
- package/dist/cli/browserCommand.d.ts +44 -0
- package/dist/cli/browserCommand.js +472 -0
- package/dist/cli/hostClient.d.ts +20 -0
- package/dist/cli/hostClient.js +36 -2
- package/dist/cli/initCommand.d.ts +1 -0
- package/dist/cli/initCommand.js +23 -7
- package/dist/cli/initTemplates.d.ts +5 -2
- package/dist/cli/initTemplates.js +25 -2
- package/dist/cli/runsCommand.js +6 -2
- package/dist/cli/triggerCommand.d.ts +12 -0
- package/dist/cli/triggerCommand.js +183 -0
- package/dist/cli/validateCommand.d.ts +1 -1
- package/dist/cli/validateCommand.js +23 -6
- package/dist/cli.js +31 -2
- package/dist/config/browseCatalog.d.ts +1 -0
- package/dist/config/browseCatalog.js +5 -4
- package/dist/config/builtinSkills.d.ts +4 -0
- package/dist/config/builtinSkills.js +23 -0
- package/dist/config/catalogRelativePath.d.ts +1 -0
- package/dist/config/catalogRelativePath.js +10 -2
- package/dist/config/createTrigger.d.ts +27 -0
- package/dist/config/createTrigger.js +232 -0
- package/dist/config/draftPackage.d.ts +117 -0
- package/dist/config/draftPackage.js +550 -0
- package/dist/config/ensureCatalogScanRoot.d.ts +16 -0
- package/dist/config/ensureCatalogScanRoot.js +73 -0
- package/dist/config/hostConfig.d.ts +2 -0
- package/dist/config/hostConfig.js +27 -0
- package/dist/config/listSkills.d.ts +2 -2
- package/dist/config/listSkills.js +17 -3
- package/dist/config/loadOutcome.d.ts +6 -0
- package/dist/config/loadPipeline.js +16 -0
- package/dist/config/loadStage.js +7 -0
- package/dist/config/loadStageflowManifest.js +25 -0
- package/dist/config/loadTrigger.d.ts +9 -0
- package/dist/config/loadTrigger.js +120 -0
- package/dist/config/multiProjectCatalog.d.ts +2 -0
- package/dist/config/multiProjectCatalog.js +26 -18
- package/dist/config/normalizePipelineStageEntry.js +8 -1
- package/dist/config/parseCompletionContract.js +6 -0
- package/dist/config/pipelineStageKeys.js +1 -0
- package/dist/config/publishDraftPackage.d.ts +7 -0
- package/dist/config/publishDraftPackage.js +25 -0
- package/dist/config/resolveCatalogContext.js +1 -1
- package/dist/config/resolvePipelineDag.js +1 -1
- package/dist/config/scanCatalogPaths.d.ts +1 -1
- package/dist/config/scanCatalogPaths.js +6 -2
- package/dist/config/stageBrowser.d.ts +12 -0
- package/dist/config/stageBrowser.js +118 -0
- package/dist/config/triggerCatalog.d.ts +10 -0
- package/dist/config/triggerCatalog.js +37 -0
- package/dist/config/validateCatalog.d.ts +13 -3
- package/dist/config/validateCatalog.js +83 -2
- package/dist/config/validateCompletionContract.js +8 -0
- package/dist/envelope/preEmitChecks.d.ts +1 -1
- package/dist/envelope/preEmitChecks.js +8 -0
- package/dist/logging/redact.d.ts +4 -0
- package/dist/logging/redact.js +17 -0
- package/dist/mcp/catalogTools.js +43 -8
- package/dist/mcp/controlTools.js +3 -1
- package/dist/mcp/deps.d.ts +2 -0
- package/dist/mcp/server.js +1 -0
- package/dist/mcp/tools.js +2 -0
- package/dist/mcp/triggerTools.d.ts +3 -0
- package/dist/mcp/triggerTools.js +176 -0
- package/dist/operatorAgent/docsRetrieval.d.ts +40 -0
- package/dist/operatorAgent/docsRetrieval.js +269 -0
- package/dist/operatorAgent/draftContext.d.ts +16 -0
- package/dist/operatorAgent/draftContext.js +68 -0
- package/dist/operatorAgent/fakeHost.d.ts +39 -0
- package/dist/operatorAgent/fakeHost.js +92 -0
- package/dist/operatorAgent/host.d.ts +40 -0
- package/dist/operatorAgent/host.js +179 -0
- package/dist/operatorAgent/index.d.ts +9 -0
- package/dist/operatorAgent/index.js +8 -0
- package/dist/operatorAgent/piModel.d.ts +80 -0
- package/dist/operatorAgent/piModel.js +528 -0
- package/dist/operatorAgent/profiles/workshopAuthor.d.ts +23 -0
- package/dist/operatorAgent/profiles/workshopAuthor.js +338 -0
- package/dist/operatorAgent/profiles/workshopAuthorPlaybook.d.ts +6 -0
- package/dist/operatorAgent/profiles/workshopAuthorPlaybook.js +103 -0
- package/dist/operatorAgent/profiles/workshopAuthorTools.d.ts +57 -0
- package/dist/operatorAgent/profiles/workshopAuthorTools.js +623 -0
- package/dist/operatorAgent/proposals.d.ts +32 -0
- package/dist/operatorAgent/proposals.js +144 -0
- package/dist/operatorAgent/types.d.ts +128 -0
- package/dist/operatorAgent/types.js +1 -0
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/project/findManifestRoot.d.ts +3 -0
- package/dist/project/findManifestRoot.js +35 -0
- package/dist/project/resolveStageflowContext.d.ts +7 -0
- package/dist/project/resolveStageflowContext.js +44 -28
- package/dist/runstore/assertTmpdir.js +6 -2
- package/dist/runstore/port.d.ts +28 -0
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +8 -1
- package/dist/runstore/sqlite/SqliteRunStore.js +90 -0
- package/dist/runstore/sqlite/migrations/008-triggers-table.d.ts +7 -0
- package/dist/runstore/sqlite/migrations/008-triggers-table.js +19 -0
- package/dist/runstore/sqlite/migrations/009-trigger-next-run.d.ts +7 -0
- package/dist/runstore/sqlite/migrations/009-trigger-next-run.js +19 -0
- package/dist/runstore/sqlite/migrations/010-trigger-adapter-state.d.ts +7 -0
- package/dist/runstore/sqlite/migrations/010-trigger-adapter-state.js +17 -0
- package/dist/runstore/sqlite/migrations/index.js +6 -0
- package/dist/runtime/answerResume.d.ts +1 -0
- package/dist/runtime/answerResume.js +3 -1
- package/dist/runtime/completionCheckRunner.d.ts +12 -1
- package/dist/runtime/completionCheckRunner.js +22 -0
- package/dist/runtime/credentialBinding.d.ts +2 -0
- package/dist/runtime/credentialBinding.js +3 -0
- package/dist/runtime/emailSource.d.ts +72 -0
- package/dist/runtime/emailSource.js +304 -0
- package/dist/runtime/githubPollSource.d.ts +86 -0
- package/dist/runtime/githubPollSource.js +284 -0
- package/dist/runtime/pipelineRunner.d.ts +4 -0
- package/dist/runtime/pipelineRunner.js +3 -0
- package/dist/runtime/pipelineScheduler.d.ts +2 -0
- package/dist/runtime/pipelineScheduler.js +85 -1
- package/dist/runtime/repositoryMaterialize.d.ts +1 -1
- package/dist/runtime/runManager.d.ts +11 -0
- package/dist/runtime/runManager.js +149 -1
- package/dist/runtime/runRetryCoordinator.d.ts +2 -0
- package/dist/runtime/runRetryCoordinator.js +1 -0
- package/dist/runtime/scheduleSource.d.ts +38 -0
- package/dist/runtime/scheduleSource.js +119 -0
- package/dist/runtime/settingsFile.d.ts +5 -0
- package/dist/runtime/settingsFile.js +61 -54
- package/dist/runtime/stageAttemptBootstrap.js +55 -3
- package/dist/runtime/stageEnvironment.js +1 -0
- package/dist/runtime/stageHitl.d.ts +1 -0
- package/dist/runtime/stageHitl.js +5 -1
- package/dist/runtime/stageProcessLauncher.d.ts +2 -0
- package/dist/runtime/stageProcessLauncher.js +44 -5
- package/dist/runtime/stageRunner.d.ts +5 -0
- package/dist/runtime/stageRunner.js +30 -6
- package/dist/runtime/stageStreamLog.d.ts +1 -0
- package/dist/runtime/stageStreamLog.js +2 -1
- package/dist/runtime/triggerCatalog.d.ts +6 -0
- package/dist/runtime/triggerCatalog.js +24 -0
- package/dist/runtime/triggerEventMatch.d.ts +2 -0
- package/dist/runtime/triggerEventMatch.js +6 -0
- package/dist/runtime/triggerPort.d.ts +14 -0
- package/dist/runtime/triggerPort.js +1 -0
- package/dist/runtime/triggerRunner.d.ts +17 -0
- package/dist/runtime/triggerRunner.js +89 -0
- package/dist/runtime/verifiedStageExecution.d.ts +7 -0
- package/dist/runtime/verifiedStageExecution.js +14 -0
- package/dist/runtime/webhookSignature.d.ts +1 -0
- package/dist/runtime/webhookSignature.js +12 -0
- package/dist/server/bootstrap.d.ts +6 -0
- package/dist/server/bootstrap.js +64 -0
- package/dist/server/controlToken.js +13 -0
- package/dist/server/createHttpHost.js +3 -0
- package/dist/server/healthSurfaces.js +31 -6
- package/dist/server/http.d.ts +15 -0
- package/dist/server/http.js +1251 -74
- package/dist/server/requestAuthContext.d.ts +1 -1
- package/dist/server/triggerRoutes.d.ts +15 -0
- package/dist/server/triggerRoutes.js +268 -0
- package/dist/tools/askOperator.d.ts +3 -2
- package/dist/types/completion.d.ts +3 -0
- package/dist/types/pipeline.d.ts +3 -1
- package/dist/types/preEmitCheck.d.ts +7 -0
- package/dist/types/stage.d.ts +14 -0
- package/dist/types/stageflowManifest.d.ts +3 -0
- package/dist/types/trigger.d.ts +18 -0
- package/dist/types/trigger.js +1 -0
- package/dist/ui/assets/{index-CSsDued1.css → index-Bv4p1aPK.css} +1 -1
- package/dist/ui/assets/index-CntB4bZR.js +133 -0
- package/dist/ui/index.html +2 -2
- package/dist/workshop/autosave.d.ts +58 -0
- package/dist/workshop/autosave.js +222 -0
- package/dist/workshop/buildStore.d.ts +79 -0
- package/dist/workshop/buildStore.js +326 -0
- package/dist/workshop/chatTurn.d.ts +232 -0
- package/dist/workshop/chatTurn.js +475 -0
- package/dist/workshop/modelSettings.d.ts +18 -0
- package/dist/workshop/modelSettings.js +44 -0
- package/dist/workshop/sessionStore.d.ts +61 -0
- package/dist/workshop/sessionStore.js +254 -0
- package/dist/workshop/workshopPin.d.ts +72 -0
- package/dist/workshop/workshopPin.js +193 -0
- package/examples/README.md +2 -1
- package/examples/browser-session/README.md +77 -0
- package/examples/browser-session/browser-session.pipeline.yaml +50 -0
- package/examples/browser-session/browser-session.task.yaml +6 -0
- package/examples/browser-session/check-login.yaml +22 -0
- package/examples/browser-session/fixture-server.mjs +64 -0
- package/examples/browser-session/human-login.yaml +31 -0
- package/examples/browser-session/merge.yaml +36 -0
- package/examples/browser-session/work-a.yaml +41 -0
- package/examples/browser-session/work-b.yaml +41 -0
- package/examples/stage-mcp/README.md +3 -2
- package/examples/stageflow.yaml +51 -0
- package/package.json +5 -1
- package/skills/stageflow/references/control-surface.md +3 -1
- package/skills/stageflow-author/references/stage-prompt-template.md +4 -4
- package/skills/stageflow-run/SKILL.md +2 -0
- package/skills/stageflow-run/references/task-and-pipeline-selection.md +3 -1
- package/skills/stageflow-setup/SKILL.md +11 -0
- package/dist/ui/assets/index-B6hlSXXn.js +0 -120
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
import { parse as parseYaml } from "yaml";
|
|
2
|
+
import { defaultWorkshopPackageDirectory } from "../../config/ensureCatalogScanRoot.js";
|
|
3
|
+
import { publishDraftPackage } from "../../config/publishDraftPackage.js";
|
|
4
|
+
import { emptyDraftPackage, isWorkshopDraftContext, readDraftFromContext, withDraft, } from "../draftContext.js";
|
|
5
|
+
import { focusUnboundWorkshopBuild, getWorkshopBuild, listWorkshopPickerRows, resolvePickerCatalogPipelines, } from "../../workshop/buildStore.js";
|
|
6
|
+
import { focusWorkshopBuildPointer, pinWorkshopBuildOnCreate, recordPinnedWorkshopSave, resolveLiveWorkshopBinding, resolvePinnedSaveDestination, } from "../../workshop/workshopPin.js";
|
|
7
|
+
import { affectedStageIds, diffDraftPackages, draftFingerprint, } from "../proposals.js";
|
|
8
|
+
export const WORKSHOP_AUTHOR_MUTATING_TOOLS = [
|
|
9
|
+
"create_pipeline",
|
|
10
|
+
"edit_pipeline",
|
|
11
|
+
"create_stage",
|
|
12
|
+
"edit_stage",
|
|
13
|
+
"create_task",
|
|
14
|
+
"edit_task",
|
|
15
|
+
];
|
|
16
|
+
export const WORKSHOP_AUTHOR_TOOL_NAMES = [
|
|
17
|
+
"list_builds",
|
|
18
|
+
"focus_build",
|
|
19
|
+
"create_build",
|
|
20
|
+
"read_draft",
|
|
21
|
+
"validate_draft",
|
|
22
|
+
"create_pipeline",
|
|
23
|
+
"edit_pipeline",
|
|
24
|
+
"create_stage",
|
|
25
|
+
"edit_stage",
|
|
26
|
+
"create_task",
|
|
27
|
+
"edit_task",
|
|
28
|
+
"save",
|
|
29
|
+
"propose_draft",
|
|
30
|
+
"retrieve_docs",
|
|
31
|
+
];
|
|
32
|
+
export const WORKSHOP_FORBIDDEN_AGENT_TOOLS = ["bash", "write", "edit"];
|
|
33
|
+
const DEFAULT_MODEL = "anthropic/claude-sonnet-4-5";
|
|
34
|
+
const REQUIRED_IO = {
|
|
35
|
+
io: {
|
|
36
|
+
input: { schema: { type: "object" } },
|
|
37
|
+
output: { schema: { type: "object" } },
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
let mutationSeq = 0;
|
|
41
|
+
function nextMutationId() {
|
|
42
|
+
mutationSeq += 1;
|
|
43
|
+
return `mutation-${mutationSeq}`;
|
|
44
|
+
}
|
|
45
|
+
function isPlainObject(value) {
|
|
46
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
47
|
+
}
|
|
48
|
+
function coercePlainObject(value) {
|
|
49
|
+
if (isPlainObject(value))
|
|
50
|
+
return value;
|
|
51
|
+
if (typeof value !== "string")
|
|
52
|
+
return null;
|
|
53
|
+
const trimmed = value.trim();
|
|
54
|
+
if (!trimmed)
|
|
55
|
+
return null;
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(trimmed);
|
|
58
|
+
if (isPlainObject(parsed))
|
|
59
|
+
return parsed;
|
|
60
|
+
}
|
|
61
|
+
catch { }
|
|
62
|
+
try {
|
|
63
|
+
const parsed = parseYaml(trimmed);
|
|
64
|
+
if (isPlainObject(parsed))
|
|
65
|
+
return parsed;
|
|
66
|
+
}
|
|
67
|
+
catch { }
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
function slugifyId(text, fallback) {
|
|
71
|
+
const slug = text
|
|
72
|
+
.toLowerCase()
|
|
73
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
74
|
+
.replace(/^-+|-+$/g, "")
|
|
75
|
+
.slice(0, 40);
|
|
76
|
+
return slug || fallback;
|
|
77
|
+
}
|
|
78
|
+
function stringArg(args, key) {
|
|
79
|
+
const value = args[key];
|
|
80
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
81
|
+
}
|
|
82
|
+
function applyMutation(ctx, summary, nextDraft) {
|
|
83
|
+
const current = readDraftFromContext(ctx.getContext());
|
|
84
|
+
const proposal = {
|
|
85
|
+
id: nextMutationId(),
|
|
86
|
+
summary,
|
|
87
|
+
nextContext: nextDraft,
|
|
88
|
+
baseContext: ctx.getContext(),
|
|
89
|
+
baseFingerprint: draftFingerprint(current),
|
|
90
|
+
artifacts: diffDraftPackages(current, nextDraft),
|
|
91
|
+
affectedStageIds: affectedStageIds(current, nextDraft),
|
|
92
|
+
};
|
|
93
|
+
ctx.emitProposal(proposal);
|
|
94
|
+
return proposal;
|
|
95
|
+
}
|
|
96
|
+
function readWorkshopContext(ctx) {
|
|
97
|
+
const raw = ctx.getContext();
|
|
98
|
+
if (isWorkshopDraftContext(raw))
|
|
99
|
+
return raw;
|
|
100
|
+
return { draft: readDraftFromContext(raw) };
|
|
101
|
+
}
|
|
102
|
+
function resolveDestination(ctx, args) {
|
|
103
|
+
const directory = stringArg(args, "directory") ??
|
|
104
|
+
readWorkshopContext(ctx).destination?.directory ??
|
|
105
|
+
defaultWorkshopPackageDirectory(readDraftFromContext(ctx.getContext()).pipeline.id);
|
|
106
|
+
const pipelineFilename = stringArg(args, "pipelineFilename") ??
|
|
107
|
+
stringArg(args, "pipeline_filename") ??
|
|
108
|
+
readWorkshopContext(ctx).destination?.pipelineFilename;
|
|
109
|
+
return pipelineFilename ? { directory, pipelineFilename } : { directory };
|
|
110
|
+
}
|
|
111
|
+
function resolveProjectRoot(ctx, defaultProjectRoot) {
|
|
112
|
+
// Never trust LLM tool args for the write root — only host-bound context
|
|
113
|
+
// (or the profile/host default passed into createSaveTool).
|
|
114
|
+
return readWorkshopContext(ctx).projectRoot ?? defaultProjectRoot ?? null;
|
|
115
|
+
}
|
|
116
|
+
export function buildCreateStageDraft(draft, args) {
|
|
117
|
+
const existingIds = new Set(draft.pipeline.stages.map((s) => (typeof s.id === "string" ? s.id : "")));
|
|
118
|
+
let stageId = stringArg(args, "id") ??
|
|
119
|
+
slugifyId(stringArg(args, "summary") ?? stringArg(args, "label") ?? "stage", "stage");
|
|
120
|
+
if (existingIds.has(stageId)) {
|
|
121
|
+
stageId = `${stageId}-${existingIds.size + 1}`;
|
|
122
|
+
}
|
|
123
|
+
const usesPath = stringArg(args, "uses") ?? `./${stageId}.yaml`;
|
|
124
|
+
const bodyFromArgs = coercePlainObject(args.body);
|
|
125
|
+
if (typeof args.body === "string" &&
|
|
126
|
+
args.body.trim() &&
|
|
127
|
+
!bodyFromArgs) {
|
|
128
|
+
return { ok: false, error: "body must be a JSON or YAML object" };
|
|
129
|
+
}
|
|
130
|
+
const stageBody = {
|
|
131
|
+
id: stageId,
|
|
132
|
+
system_prompt: stringArg(args, "system_prompt") ??
|
|
133
|
+
stringArg(args, "systemPrompt") ??
|
|
134
|
+
`Stage for: ${stringArg(args, "summary") ?? stageId}`,
|
|
135
|
+
model: stringArg(args, "model") ?? DEFAULT_MODEL,
|
|
136
|
+
...REQUIRED_IO,
|
|
137
|
+
...(bodyFromArgs ?? {}),
|
|
138
|
+
};
|
|
139
|
+
stageBody.id = stageId;
|
|
140
|
+
const ioFromArgs = coercePlainObject(args.io);
|
|
141
|
+
if (ioFromArgs) {
|
|
142
|
+
stageBody.io = ioFromArgs;
|
|
143
|
+
}
|
|
144
|
+
const entry = typeof args.entry === "boolean"
|
|
145
|
+
? args.entry
|
|
146
|
+
: draft.pipeline.stages.length === 0;
|
|
147
|
+
const pipelineEntry = {
|
|
148
|
+
id: stageId,
|
|
149
|
+
uses: usesPath,
|
|
150
|
+
...(entry ? { entry: true } : {}),
|
|
151
|
+
};
|
|
152
|
+
if (Array.isArray(args.route)) {
|
|
153
|
+
pipelineEntry.route = args.route;
|
|
154
|
+
}
|
|
155
|
+
const nextDraft = {
|
|
156
|
+
...draft,
|
|
157
|
+
pipeline: {
|
|
158
|
+
...draft.pipeline,
|
|
159
|
+
stages: [...draft.pipeline.stages, pipelineEntry],
|
|
160
|
+
},
|
|
161
|
+
stages: [...(draft.stages ?? []), { path: usesPath, body: stageBody }],
|
|
162
|
+
};
|
|
163
|
+
return { ok: true, nextDraft, stageId };
|
|
164
|
+
}
|
|
165
|
+
export function buildEditStageDraft(draft, args) {
|
|
166
|
+
const stageId = stringArg(args, "id");
|
|
167
|
+
if (!stageId) {
|
|
168
|
+
return { ok: false, error: "id is required" };
|
|
169
|
+
}
|
|
170
|
+
const pipelineIndex = draft.pipeline.stages.findIndex((s) => typeof s.id === "string" && s.id === stageId);
|
|
171
|
+
if (pipelineIndex < 0) {
|
|
172
|
+
return { ok: false, error: `stage not found: ${stageId}` };
|
|
173
|
+
}
|
|
174
|
+
const pipelineStages = draft.pipeline.stages.map((stage, index) => {
|
|
175
|
+
if (index !== pipelineIndex)
|
|
176
|
+
return { ...stage };
|
|
177
|
+
const next = { ...stage };
|
|
178
|
+
if (isPlainObject(args.pipeline_entry) || isPlainObject(args.pipelineEntry)) {
|
|
179
|
+
Object.assign(next, args.pipeline_entry ??
|
|
180
|
+
args.pipelineEntry);
|
|
181
|
+
next.id = stageId;
|
|
182
|
+
}
|
|
183
|
+
if (typeof args.entry === "boolean")
|
|
184
|
+
next.entry = args.entry;
|
|
185
|
+
if (Array.isArray(args.route))
|
|
186
|
+
next.route = args.route;
|
|
187
|
+
if (stringArg(args, "uses"))
|
|
188
|
+
next.uses = stringArg(args, "uses");
|
|
189
|
+
return next;
|
|
190
|
+
});
|
|
191
|
+
const uses = typeof pipelineStages[pipelineIndex].uses === "string"
|
|
192
|
+
? pipelineStages[pipelineIndex].uses
|
|
193
|
+
: `./${stageId}.yaml`;
|
|
194
|
+
const existingArt = (draft.stages ?? []).find((s) => s.path.replace(/^\.\//, "") === uses.replace(/^\.\//, "") ||
|
|
195
|
+
s.body.id === stageId);
|
|
196
|
+
const patch = isPlainObject(args.patch) ? args.patch : {};
|
|
197
|
+
const bodyFromArgs = coercePlainObject(args.body);
|
|
198
|
+
if (typeof args.body === "string" &&
|
|
199
|
+
args.body.trim() &&
|
|
200
|
+
!bodyFromArgs) {
|
|
201
|
+
return { ok: false, error: "body must be a JSON or YAML object" };
|
|
202
|
+
}
|
|
203
|
+
const nextBody = {
|
|
204
|
+
...(existingArt?.body ?? {
|
|
205
|
+
id: stageId,
|
|
206
|
+
system_prompt: `Stage ${stageId}`,
|
|
207
|
+
model: DEFAULT_MODEL,
|
|
208
|
+
...REQUIRED_IO,
|
|
209
|
+
}),
|
|
210
|
+
...patch,
|
|
211
|
+
...(bodyFromArgs ?? {}),
|
|
212
|
+
id: stageId,
|
|
213
|
+
};
|
|
214
|
+
if (stringArg(args, "system_prompt") || stringArg(args, "systemPrompt")) {
|
|
215
|
+
nextBody.system_prompt =
|
|
216
|
+
stringArg(args, "system_prompt") ?? stringArg(args, "systemPrompt");
|
|
217
|
+
}
|
|
218
|
+
if (stringArg(args, "model"))
|
|
219
|
+
nextBody.model = stringArg(args, "model");
|
|
220
|
+
const ioFromArgs = coercePlainObject(args.io);
|
|
221
|
+
if (ioFromArgs)
|
|
222
|
+
nextBody.io = ioFromArgs;
|
|
223
|
+
const stages = [...(draft.stages ?? [])];
|
|
224
|
+
const artIndex = stages.findIndex((s) => s.path.replace(/^\.\//, "") === uses.replace(/^\.\//, "") ||
|
|
225
|
+
s.body.id === stageId);
|
|
226
|
+
const artifact = { path: uses.startsWith("./") ? uses : `./${uses}`, body: nextBody };
|
|
227
|
+
if (artIndex >= 0) {
|
|
228
|
+
stages[artIndex] = artifact;
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
stages.push(artifact);
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
ok: true,
|
|
235
|
+
stageId,
|
|
236
|
+
nextDraft: {
|
|
237
|
+
...draft,
|
|
238
|
+
pipeline: { ...draft.pipeline, stages: pipelineStages },
|
|
239
|
+
stages,
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
export function buildCreatePipelineDraft(draft, args) {
|
|
244
|
+
const id = stringArg(args, "id");
|
|
245
|
+
if (!id)
|
|
246
|
+
return { ok: false, error: "id is required" };
|
|
247
|
+
if (isPlainObject(args.pipeline)) {
|
|
248
|
+
const pipeline = args.pipeline;
|
|
249
|
+
if (typeof pipeline.id !== "string" || !pipeline.id.trim()) {
|
|
250
|
+
return { ok: false, error: "pipeline.id is required" };
|
|
251
|
+
}
|
|
252
|
+
if (!Array.isArray(pipeline.stages)) {
|
|
253
|
+
return { ok: false, error: "pipeline.stages must be an array" };
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
ok: true,
|
|
257
|
+
nextDraft: {
|
|
258
|
+
...draft,
|
|
259
|
+
pipeline: { ...pipeline, id: pipeline.id.trim() },
|
|
260
|
+
...(Array.isArray(args.stages)
|
|
261
|
+
? { stages: args.stages }
|
|
262
|
+
: {}),
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
ok: true,
|
|
268
|
+
nextDraft: {
|
|
269
|
+
...draft,
|
|
270
|
+
pipeline: {
|
|
271
|
+
...draft.pipeline,
|
|
272
|
+
id,
|
|
273
|
+
...(stringArg(args, "model") ? { model: stringArg(args, "model") } : {}),
|
|
274
|
+
...(args.agent !== undefined ? { agent: args.agent } : {}),
|
|
275
|
+
...(Array.isArray(args.stages)
|
|
276
|
+
? { stages: args.stages }
|
|
277
|
+
: {}),
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
export function buildEditPipelineDraft(draft, args) {
|
|
283
|
+
const patch = isPlainObject(args.patch) ? args.patch : args;
|
|
284
|
+
const nextPipeline = {
|
|
285
|
+
...draft.pipeline,
|
|
286
|
+
};
|
|
287
|
+
if (stringArg(patch, "id"))
|
|
288
|
+
nextPipeline.id = stringArg(patch, "id");
|
|
289
|
+
if (stringArg(patch, "model"))
|
|
290
|
+
nextPipeline.model = stringArg(patch, "model");
|
|
291
|
+
if (patch.agent !== undefined)
|
|
292
|
+
nextPipeline.agent = patch.agent;
|
|
293
|
+
if (Array.isArray(patch.stages)) {
|
|
294
|
+
nextPipeline.stages = patch.stages;
|
|
295
|
+
}
|
|
296
|
+
if (patch.schemas !== undefined)
|
|
297
|
+
nextPipeline.schemas = patch.schemas;
|
|
298
|
+
if (patch.requires !== undefined)
|
|
299
|
+
nextPipeline.requires = patch.requires;
|
|
300
|
+
return {
|
|
301
|
+
ok: true,
|
|
302
|
+
nextDraft: { ...draft, pipeline: nextPipeline },
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
export function buildCreateTaskDraft(draft, args) {
|
|
306
|
+
const goal = stringArg(args, "goal") ?? stringArg(args, "summary") ?? "Describe the workflow goal";
|
|
307
|
+
const taskId = stringArg(args, "id") ?? slugifyId(goal, "task");
|
|
308
|
+
const filename = stringArg(args, "filename") ?? `${taskId}.task.yaml`;
|
|
309
|
+
const body = isPlainObject(args.body)
|
|
310
|
+
? { ...args.body, id: taskId, goal: stringArg(args.body, "goal") ?? goal }
|
|
311
|
+
: { id: taskId, goal };
|
|
312
|
+
return {
|
|
313
|
+
ok: true,
|
|
314
|
+
taskId,
|
|
315
|
+
nextDraft: {
|
|
316
|
+
...draft,
|
|
317
|
+
task: { filename, body },
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
export function buildEditTaskDraft(draft, args) {
|
|
322
|
+
if (!draft.task) {
|
|
323
|
+
return { ok: false, error: "no task on draft — use create_task first" };
|
|
324
|
+
}
|
|
325
|
+
const patch = isPlainObject(args.patch) ? args.patch : {};
|
|
326
|
+
const bodyFromArgs = isPlainObject(args.body) ? args.body : null;
|
|
327
|
+
const nextBody = {
|
|
328
|
+
...draft.task.body,
|
|
329
|
+
...patch,
|
|
330
|
+
...(bodyFromArgs ?? {}),
|
|
331
|
+
};
|
|
332
|
+
if (stringArg(args, "id"))
|
|
333
|
+
nextBody.id = stringArg(args, "id");
|
|
334
|
+
if (stringArg(args, "goal"))
|
|
335
|
+
nextBody.goal = stringArg(args, "goal");
|
|
336
|
+
const taskId = typeof nextBody.id === "string" && nextBody.id ? nextBody.id : "task";
|
|
337
|
+
const filename = stringArg(args, "filename") ?? draft.task.filename;
|
|
338
|
+
return {
|
|
339
|
+
ok: true,
|
|
340
|
+
taskId,
|
|
341
|
+
nextDraft: {
|
|
342
|
+
...draft,
|
|
343
|
+
task: { filename, body: nextBody },
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
const WORKSHOP_TURN_REQUIRED = "workshop chat session is required";
|
|
348
|
+
function liveWorkshopBinding(ctx) {
|
|
349
|
+
return resolveLiveWorkshopBinding(ctx.getContext());
|
|
350
|
+
}
|
|
351
|
+
async function listPickerCatalog(storeRoot) {
|
|
352
|
+
const { createRunStore } = await import("../../runstore/createStore.js");
|
|
353
|
+
const { listPipelinesMultiProject } = await import("../../config/multiProjectCatalog.js");
|
|
354
|
+
const store = createRunStore({ rootDir: storeRoot });
|
|
355
|
+
try {
|
|
356
|
+
const listed = await listPipelinesMultiProject({ store });
|
|
357
|
+
return {
|
|
358
|
+
pipelines: resolvePickerCatalogPipelines(listed.items, listed.roots),
|
|
359
|
+
roots: listed.roots,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
finally {
|
|
363
|
+
await store.close();
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
export function createWorkshopBuildTools() {
|
|
367
|
+
const listBuildsTool = {
|
|
368
|
+
name: "list_builds",
|
|
369
|
+
description: "List the same workshop rows as the studio picker (open builds and unbound disk pipelines). Does not create a build. Works when no build is focused.",
|
|
370
|
+
async handler(_args, ctx) {
|
|
371
|
+
const live = liveWorkshopBinding(ctx);
|
|
372
|
+
if (!live) {
|
|
373
|
+
return { ok: false, content: null, error: WORKSHOP_TURN_REQUIRED };
|
|
374
|
+
}
|
|
375
|
+
try {
|
|
376
|
+
const catalog = await listPickerCatalog(live.storeRoot);
|
|
377
|
+
const rows = listWorkshopPickerRows(live.storeRoot, catalog.pipelines, catalog.roots);
|
|
378
|
+
return { ok: true, content: { rows } };
|
|
379
|
+
}
|
|
380
|
+
catch (err) {
|
|
381
|
+
return {
|
|
382
|
+
ok: false,
|
|
383
|
+
content: null,
|
|
384
|
+
error: err instanceof Error ? err.message : String(err),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
const focusBuildTool = {
|
|
390
|
+
name: "focus_build",
|
|
391
|
+
description: "Move this chat onto a build by id, or onto an unbound disk pipeline by projectRoot and relativePath. A failed open returns the error, creates nothing, and leaves the pointer unchanged.",
|
|
392
|
+
async handler(args, ctx) {
|
|
393
|
+
const live = liveWorkshopBinding(ctx);
|
|
394
|
+
if (!live) {
|
|
395
|
+
return { ok: false, content: null, error: WORKSHOP_TURN_REQUIRED };
|
|
396
|
+
}
|
|
397
|
+
const buildId = stringArg(args, "buildId") ?? stringArg(args, "id");
|
|
398
|
+
const projectRoot = stringArg(args, "projectRoot") ?? stringArg(args, "project_root");
|
|
399
|
+
const relativePath = stringArg(args, "relativePath") ??
|
|
400
|
+
stringArg(args, "relative_path") ??
|
|
401
|
+
stringArg(args, "path");
|
|
402
|
+
if (!buildId && projectRoot && relativePath) {
|
|
403
|
+
let resolvedRoot = projectRoot;
|
|
404
|
+
const { createRunStore } = await import("../../runstore/createStore.js");
|
|
405
|
+
const { CatalogPathError, resolveCatalogStartInput } = await import("../../config/catalogRelativePath.js");
|
|
406
|
+
const store = createRunStore({ rootDir: live.storeRoot });
|
|
407
|
+
try {
|
|
408
|
+
const { wireRoot } = await resolveCatalogStartInput({ store }, projectRoot);
|
|
409
|
+
resolvedRoot = wireRoot.path;
|
|
410
|
+
}
|
|
411
|
+
catch (err) {
|
|
412
|
+
if (!(err instanceof CatalogPathError))
|
|
413
|
+
throw err;
|
|
414
|
+
}
|
|
415
|
+
finally {
|
|
416
|
+
await store.close();
|
|
417
|
+
}
|
|
418
|
+
const focused = await focusUnboundWorkshopBuild(live.storeRoot, {
|
|
419
|
+
projectRoot: resolvedRoot,
|
|
420
|
+
relativePath,
|
|
421
|
+
});
|
|
422
|
+
if (!focused.ok) {
|
|
423
|
+
return { ok: false, content: null, error: focused.error };
|
|
424
|
+
}
|
|
425
|
+
focusWorkshopBuildPointer({
|
|
426
|
+
sessionId: live.sessionId,
|
|
427
|
+
buildId: focused.build.id,
|
|
428
|
+
registry: live.registry,
|
|
429
|
+
storeRoot: live.storeRoot,
|
|
430
|
+
});
|
|
431
|
+
return { ok: true, content: { build: focused.build } };
|
|
432
|
+
}
|
|
433
|
+
if (!buildId) {
|
|
434
|
+
return {
|
|
435
|
+
ok: false,
|
|
436
|
+
content: null,
|
|
437
|
+
error: "build id or project root and path are required",
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
try {
|
|
441
|
+
const pointed = focusWorkshopBuildPointer({
|
|
442
|
+
sessionId: live.sessionId,
|
|
443
|
+
buildId,
|
|
444
|
+
registry: live.registry,
|
|
445
|
+
storeRoot: live.storeRoot,
|
|
446
|
+
});
|
|
447
|
+
return {
|
|
448
|
+
ok: true,
|
|
449
|
+
content: { build: getWorkshopBuild(live.storeRoot, pointed.activeBuildId) },
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
catch (err) {
|
|
453
|
+
return {
|
|
454
|
+
ok: false,
|
|
455
|
+
content: null,
|
|
456
|
+
error: err instanceof Error ? err.message : String(err),
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
const createBuildTool = {
|
|
462
|
+
name: "create_build",
|
|
463
|
+
description: "Persist a new untitled build and focus it, including when another build is already focused. No build id argument.",
|
|
464
|
+
async handler(_args, ctx) {
|
|
465
|
+
const live = liveWorkshopBinding(ctx);
|
|
466
|
+
if (!live) {
|
|
467
|
+
return { ok: false, content: null, error: WORKSHOP_TURN_REQUIRED };
|
|
468
|
+
}
|
|
469
|
+
const build = pinWorkshopBuildOnCreate({
|
|
470
|
+
sessionId: live.sessionId,
|
|
471
|
+
draft: emptyDraftPackage(),
|
|
472
|
+
registry: live.registry,
|
|
473
|
+
storeRoot: live.storeRoot,
|
|
474
|
+
});
|
|
475
|
+
return { ok: true, content: { build } };
|
|
476
|
+
},
|
|
477
|
+
};
|
|
478
|
+
return [listBuildsTool, focusBuildTool, createBuildTool];
|
|
479
|
+
}
|
|
480
|
+
export function createWorkshopAuthorMutatingTools() {
|
|
481
|
+
const createPipelineTool = {
|
|
482
|
+
name: "create_pipeline",
|
|
483
|
+
description: "Create or replace the draft pipeline (id, optional model/stages). Mutates the in-memory draft immediately.",
|
|
484
|
+
handler(args, ctx) {
|
|
485
|
+
const built = buildCreatePipelineDraft(readDraftFromContext(ctx.getContext()), args);
|
|
486
|
+
if (!built.ok)
|
|
487
|
+
return { ok: false, content: null, error: built.error };
|
|
488
|
+
const proposal = applyMutation(ctx, `Set pipeline “${built.nextDraft.pipeline.id}”`, built.nextDraft);
|
|
489
|
+
return { ok: true, content: proposal };
|
|
490
|
+
},
|
|
491
|
+
};
|
|
492
|
+
const editPipelineTool = {
|
|
493
|
+
name: "edit_pipeline",
|
|
494
|
+
description: "Edit pipeline fields on the current draft (id, model, stages wiring, etc.). Mutates immediately.",
|
|
495
|
+
handler(args, ctx) {
|
|
496
|
+
const built = buildEditPipelineDraft(readDraftFromContext(ctx.getContext()), args);
|
|
497
|
+
if (!built.ok)
|
|
498
|
+
return { ok: false, content: null, error: built.error };
|
|
499
|
+
const proposal = applyMutation(ctx, `Edit pipeline “${built.nextDraft.pipeline.id}”`, built.nextDraft);
|
|
500
|
+
return { ok: true, content: proposal };
|
|
501
|
+
},
|
|
502
|
+
};
|
|
503
|
+
const createStageTool = {
|
|
504
|
+
name: "create_stage",
|
|
505
|
+
description: "Add a file-backed stage to the draft (uses: ./id.yaml + stage body with io). Mutates immediately; Accept confirms, Reject soft-undos.",
|
|
506
|
+
handler(args, ctx) {
|
|
507
|
+
const built = buildCreateStageDraft(readDraftFromContext(ctx.getContext()), args);
|
|
508
|
+
if (!built.ok)
|
|
509
|
+
return { ok: false, content: null, error: built.error };
|
|
510
|
+
const proposal = applyMutation(ctx, `Add stage “${built.stageId}”`, built.nextDraft);
|
|
511
|
+
return { ok: true, content: proposal };
|
|
512
|
+
},
|
|
513
|
+
};
|
|
514
|
+
const editStageTool = {
|
|
515
|
+
name: "edit_stage",
|
|
516
|
+
description: "Edit an existing draft stage by id (body fields and/or pipeline entry route/entry/uses). Mutates immediately.",
|
|
517
|
+
handler(args, ctx) {
|
|
518
|
+
const built = buildEditStageDraft(readDraftFromContext(ctx.getContext()), args);
|
|
519
|
+
if (!built.ok)
|
|
520
|
+
return { ok: false, content: null, error: built.error };
|
|
521
|
+
const proposal = applyMutation(ctx, `Edit stage “${built.stageId}”`, built.nextDraft);
|
|
522
|
+
return { ok: true, content: proposal };
|
|
523
|
+
},
|
|
524
|
+
};
|
|
525
|
+
const createTaskTool = {
|
|
526
|
+
name: "create_task",
|
|
527
|
+
description: "Create or replace the optional task (id + goal) on the draft. Mutates immediately.",
|
|
528
|
+
handler(args, ctx) {
|
|
529
|
+
const built = buildCreateTaskDraft(readDraftFromContext(ctx.getContext()), args);
|
|
530
|
+
if (!built.ok)
|
|
531
|
+
return { ok: false, content: null, error: built.error };
|
|
532
|
+
const proposal = applyMutation(ctx, `Create task “${built.taskId}”`, built.nextDraft);
|
|
533
|
+
return { ok: true, content: proposal };
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
const editTaskTool = {
|
|
537
|
+
name: "edit_task",
|
|
538
|
+
description: "Edit the draft task fields (id, goal, body). Mutates immediately.",
|
|
539
|
+
handler(args, ctx) {
|
|
540
|
+
const built = buildEditTaskDraft(readDraftFromContext(ctx.getContext()), args);
|
|
541
|
+
if (!built.ok)
|
|
542
|
+
return { ok: false, content: null, error: built.error };
|
|
543
|
+
const proposal = applyMutation(ctx, `Edit task “${built.taskId}”`, built.nextDraft);
|
|
544
|
+
return { ok: true, content: proposal };
|
|
545
|
+
},
|
|
546
|
+
};
|
|
547
|
+
return [
|
|
548
|
+
createPipelineTool,
|
|
549
|
+
editPipelineTool,
|
|
550
|
+
createStageTool,
|
|
551
|
+
editStageTool,
|
|
552
|
+
createTaskTool,
|
|
553
|
+
editTaskTool,
|
|
554
|
+
];
|
|
555
|
+
}
|
|
556
|
+
export function createSaveTool(options = {}) {
|
|
557
|
+
return {
|
|
558
|
+
name: "save",
|
|
559
|
+
description: "Validate-then-write the draft via catalog facades (createDraftPackage / overwriteDraftPackage). When directory is omitted, writes under workshop/<pipeline-id>, which the host adds to the catalog so Run can see the pipeline and task. Pass directory only when the operator names a folder. Soft undo does not reverse disk. Prefer mode auto; set allowInvalid only when the operator explicitly requests saving invalid YAML. Do not pass projectRoot — the host binds it.",
|
|
560
|
+
async handler(args, ctx) {
|
|
561
|
+
const pinnedDestination = resolvePinnedSaveDestination(ctx.getContext());
|
|
562
|
+
const destination = pinnedDestination ?? resolveDestination(ctx, args);
|
|
563
|
+
if (!destination) {
|
|
564
|
+
return {
|
|
565
|
+
ok: false,
|
|
566
|
+
content: null,
|
|
567
|
+
error: "destination is required — set context.destination or pass directory (do not invent a disk path)",
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
const projectRoot = resolveProjectRoot(ctx, options.projectRoot);
|
|
571
|
+
if (!projectRoot) {
|
|
572
|
+
return {
|
|
573
|
+
ok: false,
|
|
574
|
+
content: null,
|
|
575
|
+
error: "projectRoot is required — bind it on the draft context (host-owned; do not invent from tool args)",
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
const draft = readDraftFromContext(ctx.getContext());
|
|
579
|
+
const allowInvalid = args.allowInvalid === true;
|
|
580
|
+
const modeRaw = stringArg(args, "mode") ?? "auto";
|
|
581
|
+
const mode = modeRaw === "create" || modeRaw === "overwrite" || modeRaw === "auto"
|
|
582
|
+
? modeRaw
|
|
583
|
+
: "auto";
|
|
584
|
+
const input = {
|
|
585
|
+
directory: destination.directory,
|
|
586
|
+
draft,
|
|
587
|
+
...(destination.pipelineFilename
|
|
588
|
+
? { pipelineFilename: destination.pipelineFilename }
|
|
589
|
+
: {}),
|
|
590
|
+
...(allowInvalid ? { allowInvalid: true } : {}),
|
|
591
|
+
};
|
|
592
|
+
const published = await publishDraftPackage(projectRoot, input, mode);
|
|
593
|
+
const result = published.write;
|
|
594
|
+
if (result.ok) {
|
|
595
|
+
const current = readWorkshopContext(ctx);
|
|
596
|
+
ctx.setContext({
|
|
597
|
+
...current,
|
|
598
|
+
draft: withDraft(current, draft).draft,
|
|
599
|
+
destination: {
|
|
600
|
+
directory: destination.directory,
|
|
601
|
+
...(destination.pipelineFilename
|
|
602
|
+
? { pipelineFilename: destination.pipelineFilename }
|
|
603
|
+
: {}),
|
|
604
|
+
},
|
|
605
|
+
projectRoot,
|
|
606
|
+
});
|
|
607
|
+
recordPinnedWorkshopSave(ctx.getContext(), {
|
|
608
|
+
directory: destination.directory,
|
|
609
|
+
...(destination.pipelineFilename
|
|
610
|
+
? { pipelineFilename: destination.pipelineFilename }
|
|
611
|
+
: {}),
|
|
612
|
+
projectRoot,
|
|
613
|
+
draft,
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
return {
|
|
617
|
+
ok: result.ok,
|
|
618
|
+
content: result,
|
|
619
|
+
...(result.ok ? {} : { error: result.error }),
|
|
620
|
+
};
|
|
621
|
+
},
|
|
622
|
+
};
|
|
623
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { DraftPackage } from "../config/draftPackage.js";
|
|
2
|
+
import type { OperatorAgentProposal } from "./types.js";
|
|
3
|
+
export type ProposalArtifactDiff = {
|
|
4
|
+
path: string;
|
|
5
|
+
kind: "added" | "removed" | "modified";
|
|
6
|
+
before?: string;
|
|
7
|
+
after?: string;
|
|
8
|
+
};
|
|
9
|
+
export type AcceptProposalResult = {
|
|
10
|
+
ok: true;
|
|
11
|
+
} | {
|
|
12
|
+
ok: false;
|
|
13
|
+
reason: "none" | "id_mismatch";
|
|
14
|
+
notice?: string;
|
|
15
|
+
};
|
|
16
|
+
export type UndoMutationResult = {
|
|
17
|
+
ok: true;
|
|
18
|
+
} | {
|
|
19
|
+
ok: false;
|
|
20
|
+
reason: "none" | "id_mismatch" | "conflict";
|
|
21
|
+
notice?: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function draftFingerprint(draft: DraftPackage): string;
|
|
24
|
+
export declare function formatArtifact(value: unknown): string;
|
|
25
|
+
export declare function diffDraftPackages(before: DraftPackage, after: DraftPackage): ProposalArtifactDiff[];
|
|
26
|
+
export declare function affectedStageIds(before: DraftPackage, after: DraftPackage): string[];
|
|
27
|
+
export declare function enrichProposal(proposal: OperatorAgentProposal, currentContext: unknown): OperatorAgentProposal;
|
|
28
|
+
/** True when the live draft no longer matches the post-apply fingerprint. */
|
|
29
|
+
export declare function isMutationConflict(mutation: OperatorAgentProposal, currentContext: unknown): boolean;
|
|
30
|
+
export declare const UNDO_MUTATION_CONFLICT_NOTICE = "Undo blocked: the draft changed after this mutation. Ask the agent to reverse the change instead of rejecting.";
|
|
31
|
+
/** @deprecated Prefer UNDO_MUTATION_CONFLICT_NOTICE — kept for callers mid-migration. */
|
|
32
|
+
export declare const STALE_PROPOSAL_NOTICE = "Undo blocked: the draft changed after this mutation. Ask the agent to reverse the change instead of rejecting.";
|