@stigmer/cli 3.19.0 → 3.20.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/commands/apply.d.ts.map +1 -1
- package/commands/apply.js +42 -100
- package/commands/apply.js.map +1 -1
- package/commands/validate.js +3 -4
- package/commands/validate.js.map +1 -1
- package/local/bootstrap.d.ts +1 -2
- package/local/bootstrap.d.ts.map +1 -1
- package/local/bootstrap.js +13 -41
- package/local/bootstrap.js.map +1 -1
- package/local/daemon/launch.d.ts +2 -2
- package/local/daemon/launch.d.ts.map +1 -1
- package/local/daemon/launch.js +2 -2
- package/local/daemon/launch.js.map +1 -1
- package/local/plugins/defaults.js +2 -2
- package/local/plugins/defaults.js.map +1 -1
- package/package.json +7 -7
- package/registry/index.d.ts +2 -2
- package/registry/index.d.ts.map +1 -1
- package/registry/index.js +2 -2
- package/registry/index.js.map +1 -1
- package/registry/metadata.d.ts +9 -0
- package/registry/metadata.d.ts.map +1 -1
- package/registry/metadata.js +14 -5
- package/registry/metadata.js.map +1 -1
- package/registry/registry.d.ts +9 -0
- package/registry/registry.d.ts.map +1 -1
- package/registry/registry.js +16 -1
- package/registry/registry.js.map +1 -1
- package/registry/verb-support.d.ts.map +1 -1
- package/registry/verb-support.js +0 -13
- package/registry/verb-support.js.map +1 -1
- package/resources/apply/apply.d.ts +11 -12
- package/resources/apply/apply.d.ts.map +1 -1
- package/resources/apply/apply.js +12 -21
- package/resources/apply/apply.js.map +1 -1
- package/resources/apply/handlers.js +2 -2
- package/resources/apply/handlers.js.map +1 -1
- package/resources/delete.d.ts.map +1 -1
- package/resources/delete.js +0 -1
- package/resources/delete.js.map +1 -1
- package/resources/get-bindings.d.ts.map +1 -1
- package/resources/get-bindings.js +0 -5
- package/resources/get-bindings.js.map +1 -1
- package/resources/list.d.ts.map +1 -1
- package/resources/list.js +0 -1
- package/resources/list.js.map +1 -1
- package/resources/validate.d.ts.map +1 -1
- package/resources/validate.js +0 -2
- package/resources/validate.js.map +1 -1
- package/src/commands/apply.ts +35 -150
- package/src/commands/validate.ts +2 -4
- package/src/local/bootstrap.test.ts +15 -59
- package/src/local/bootstrap.ts +12 -53
- package/src/local/daemon/launch.ts +4 -4
- package/src/local/plugins/defaults.ts +2 -2
- package/src/registry/index.ts +7 -1
- package/src/registry/metadata.ts +15 -5
- package/src/registry/registry.test.ts +1 -7
- package/src/registry/registry.ts +17 -1
- package/src/registry/verb-support.ts +0 -13
- package/src/resources/apply/apply.integration.test.ts +15 -3
- package/src/resources/apply/apply.ts +15 -26
- package/src/resources/apply/handlers.ts +2 -2
- package/src/resources/delete.ts +0 -1
- package/src/resources/get-bindings.ts +0 -5
- package/src/resources/list.ts +0 -1
- package/src/resources/validate.test.ts +0 -1
- package/src/resources/validate.ts +0 -2
- package/local/seedpack-retire.d.ts +0 -63
- package/local/seedpack-retire.d.ts.map +0 -1
- package/local/seedpack-retire.js +0 -361
- package/local/seedpack-retire.js.map +0 -1
- package/resources/apply/declarative.d.ts +0 -106
- package/resources/apply/declarative.d.ts.map +0 -1
- package/resources/apply/declarative.js +0 -397
- package/resources/apply/declarative.js.map +0 -1
- package/resources/apply/synth/project-track.d.ts +0 -21
- package/resources/apply/synth/project-track.d.ts.map +0 -1
- package/resources/apply/synth/project-track.js +0 -102
- package/resources/apply/synth/project-track.js.map +0 -1
- package/resources/apply/synth/reader.d.ts +0 -18
- package/resources/apply/synth/reader.d.ts.map +0 -1
- package/resources/apply/synth/reader.js +0 -55
- package/resources/apply/synth/reader.js.map +0 -1
- package/resources/apply/synth/runtime.d.ts +0 -8
- package/resources/apply/synth/runtime.d.ts.map +0 -1
- package/resources/apply/synth/runtime.js +0 -32
- package/resources/apply/synth/runtime.js.map +0 -1
- package/resources/apply/synth/synthesize.d.ts +0 -43
- package/resources/apply/synth/synthesize.d.ts.map +0 -1
- package/resources/apply/synth/synthesize.js +0 -115
- package/resources/apply/synth/synthesize.js.map +0 -1
- package/src/local/seedpack-retire.test.ts +0 -499
- package/src/local/seedpack-retire.ts +0 -474
- package/src/resources/apply/declarative.integration.test.ts +0 -204
- package/src/resources/apply/declarative.test.ts +0 -132
- package/src/resources/apply/declarative.ts +0 -491
- package/src/resources/apply/synth/capstone.integration.test.ts +0 -134
- package/src/resources/apply/synth/project-track.integration.test.ts +0 -194
- package/src/resources/apply/synth/project-track.ts +0 -126
- package/src/resources/apply/synth/reader.test.ts +0 -90
- package/src/resources/apply/synth/reader.ts +0 -68
- package/src/resources/apply/synth/runtime.test.ts +0 -29
- package/src/resources/apply/synth/runtime.ts +0 -39
- package/src/resources/apply/synth/synthesize.test.ts +0 -109
- package/src/resources/apply/synth/synthesize.ts +0 -161
package/src/commands/apply.ts
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
// `stigmer apply` — deploy resources to the backend.
|
|
2
2
|
//
|
|
3
3
|
// File mode (-f): apply individual YAML resource files (or a directory)
|
|
4
|
-
//
|
|
5
|
-
// Plugin track: a directory holding a plugin manifest (either flag,
|
|
4
|
+
// Plugin track: a directory holding a plugin manifest (the -f path,
|
|
6
5
|
// or the working directory) installs as a plugin — the
|
|
7
6
|
// same read, zip and push as `push plugin <dir>`, so the
|
|
8
7
|
// two verbs are one code path
|
|
9
8
|
//
|
|
9
|
+
// A bare `stigmer apply` in a directory that is neither prints what the
|
|
10
|
+
// command can do. A `stigmer.yaml` there is the retired Project format, and
|
|
11
|
+
// the guidance says so: a folder of resources that belong together is a
|
|
12
|
+
// plugin.
|
|
13
|
+
//
|
|
10
14
|
// Resources marshal strictly from YAML to full protos and apply through the raw
|
|
11
15
|
// command controllers (preserving metadata.id so updates aren't misrouted as
|
|
12
16
|
// creates). Heavy modules are lazy-imported so `--help` stays fast (DD-001).
|
|
17
|
+
import { existsSync } from "node:fs";
|
|
18
|
+
import { join } from "node:path";
|
|
13
19
|
import type { Command } from "commander";
|
|
14
20
|
import { ensureAuthenticated, resolveOrganization } from "../config/index.js";
|
|
15
|
-
import { UsageError } from "../errors/index.js";
|
|
16
21
|
import {
|
|
17
22
|
CommandResult,
|
|
18
23
|
type OutputFlags,
|
|
@@ -21,23 +26,22 @@ import {
|
|
|
21
26
|
} from "../output/index.js";
|
|
22
27
|
import { addResultFlags, globalOrg, resultFormat } from "./shared.js";
|
|
23
28
|
|
|
29
|
+
/**
|
|
30
|
+
* The manifest file the retired Project format kept at a directory's root.
|
|
31
|
+
* Read only to recognise an old repository and say what happened to it.
|
|
32
|
+
*/
|
|
33
|
+
const RETIRED_PROJECT_FILE = "stigmer.yaml";
|
|
34
|
+
|
|
24
35
|
interface ApplyFlags extends OutputFlags {
|
|
25
36
|
file?: string;
|
|
26
|
-
config?: string;
|
|
27
|
-
prune?: boolean;
|
|
28
37
|
dryRun?: boolean;
|
|
29
38
|
}
|
|
30
39
|
|
|
31
40
|
export function registerApply(program: Command): void {
|
|
32
41
|
const apply = program
|
|
33
42
|
.command("apply")
|
|
34
|
-
.description("apply resources from files or a
|
|
35
|
-
.option("-f, --file <path>", "path to a YAML file or directory")
|
|
36
|
-
.option("--config <path>", "path to a project directory (declarative mode)")
|
|
37
|
-
// S1: Go plumbs --prune but never sends it — the server always reconciles
|
|
38
|
-
// orphans by member set-difference. We reproduce the flag (default on) for
|
|
39
|
-
// parity; it is intentionally a no-op client-side. Tracked as a Go follow-up.
|
|
40
|
-
.option("--prune", "delete orphaned resources (declarative mode)", true)
|
|
43
|
+
.description("apply resources from files or a plugin directory")
|
|
44
|
+
.option("-f, --file <path>", "path to a YAML file, a directory of YAML files, or a plugin directory")
|
|
41
45
|
.option("--dry-run", "validate without applying")
|
|
42
46
|
.action((options: ApplyFlags, command: Command) =>
|
|
43
47
|
runApply(options, command),
|
|
@@ -56,9 +60,7 @@ async function runApply(options: ApplyFlags, command: Command): Promise<void> {
|
|
|
56
60
|
const target =
|
|
57
61
|
options.file !== undefined && options.file !== ""
|
|
58
62
|
? options.file
|
|
59
|
-
:
|
|
60
|
-
? options.config
|
|
61
|
-
: process.cwd();
|
|
63
|
+
: process.cwd();
|
|
62
64
|
const { isPluginDirectory } = await import("../resources/plugin.js");
|
|
63
65
|
if (isPluginDirectory(target)) {
|
|
64
66
|
await runPluginApply(target, orgOverride, options.dryRun === true, format);
|
|
@@ -75,7 +77,7 @@ async function runApply(options: ApplyFlags, command: Command): Promise<void> {
|
|
|
75
77
|
return;
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
renderResult(buildGuidance(target), format);
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
// Plugin track: install or upgrade the plugin folder as one unit through the
|
|
@@ -130,123 +132,6 @@ async function runPluginApply(
|
|
|
130
132
|
);
|
|
131
133
|
}
|
|
132
134
|
|
|
133
|
-
// Declarative mode: detect stigmer.yaml (walk up). Absent → atomic guidance.
|
|
134
|
-
// entry_point set → SDK/project synthesis track. Otherwise scan the project and
|
|
135
|
-
// reconcile membership. Both project tracks converge on the shared reconciler.
|
|
136
|
-
async function runDeclarativeApply(
|
|
137
|
-
options: ApplyFlags,
|
|
138
|
-
orgOverride: string | undefined,
|
|
139
|
-
format: OutputFormat,
|
|
140
|
-
): Promise<void> {
|
|
141
|
-
const { detectTrack, applyDeclarative, previewDeclarative } =
|
|
142
|
-
await import("../resources/apply/declarative.js");
|
|
143
|
-
|
|
144
|
-
const startDir =
|
|
145
|
-
options.config !== undefined && options.config !== ""
|
|
146
|
-
? options.config
|
|
147
|
-
: process.cwd();
|
|
148
|
-
const detect = detectTrack(startDir);
|
|
149
|
-
|
|
150
|
-
if (detect.track === "atomic") {
|
|
151
|
-
renderResult(buildAtomicGuidance(), format);
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (detect.track === "project") {
|
|
156
|
-
await runProjectApply(detect, orgOverride, format, options.dryRun === true);
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (options.dryRun === true) {
|
|
161
|
-
const { results, summary } = previewDeclarative(detect, {
|
|
162
|
-
controller: throwingController,
|
|
163
|
-
warn: (line) => emitWarning(line),
|
|
164
|
-
});
|
|
165
|
-
for (const result of results) renderResult(result, format);
|
|
166
|
-
renderResult(summary, format);
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const { connectBackend } = await import("../backend.js");
|
|
171
|
-
const client = connectBackend();
|
|
172
|
-
ensureAuthenticated(client.config);
|
|
173
|
-
const org = resolveDeclarativeOrg(
|
|
174
|
-
client.config,
|
|
175
|
-
detect.project?.metadata?.org,
|
|
176
|
-
orgOverride,
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
const result = await applyDeclarative(detect, {
|
|
180
|
-
controller: client.controller,
|
|
181
|
-
stigmer: client.stigmer,
|
|
182
|
-
org,
|
|
183
|
-
info: (line) => process.stderr.write(`${line}\n`),
|
|
184
|
-
warn: (line) => emitWarning(line),
|
|
185
|
-
});
|
|
186
|
-
renderResult(result, format);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Project (SDK synthesis) track: run the user's entry_point, read the `.pb`
|
|
190
|
-
// output, and reconcile membership through the same reconciler the declarative
|
|
191
|
-
// track uses. Lazy-imported to preserve the DD-001 boundary (`--help` stays
|
|
192
|
-
// fast).
|
|
193
|
-
async function runProjectApply(
|
|
194
|
-
detect: import("../resources/apply/declarative.js").DetectResult,
|
|
195
|
-
orgOverride: string | undefined,
|
|
196
|
-
format: OutputFormat,
|
|
197
|
-
dryRun: boolean,
|
|
198
|
-
): Promise<void> {
|
|
199
|
-
const { applyProjectTrack, previewProjectTrack } =
|
|
200
|
-
await import("../resources/apply/synth/project-track.js");
|
|
201
|
-
|
|
202
|
-
if (dryRun) {
|
|
203
|
-
renderResult(previewProjectTrack(detect), format);
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const { connectBackend } = await import("../backend.js");
|
|
208
|
-
const client = connectBackend();
|
|
209
|
-
ensureAuthenticated(client.config);
|
|
210
|
-
const org = resolveDeclarativeOrg(
|
|
211
|
-
client.config,
|
|
212
|
-
detect.project?.metadata?.org,
|
|
213
|
-
orgOverride,
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
const result = await applyProjectTrack(detect, {
|
|
217
|
-
controller: client.controller,
|
|
218
|
-
stigmer: client.stigmer,
|
|
219
|
-
org,
|
|
220
|
-
info: (line) => process.stderr.write(`${line}\n`),
|
|
221
|
-
warn: (line) => emitWarning(line),
|
|
222
|
-
});
|
|
223
|
-
renderResult(result, format);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Org precedence mirrors Go's resolveApplyOrganization: --org flag, then
|
|
227
|
-
// metadata.org in stigmer.yaml, then the configured context. Empty → a clear
|
|
228
|
-
// "set your org" usage error.
|
|
229
|
-
function resolveDeclarativeOrg(
|
|
230
|
-
config: import("../config/index.js").Config,
|
|
231
|
-
projectOrg: string | undefined,
|
|
232
|
-
override: string | undefined,
|
|
233
|
-
): string {
|
|
234
|
-
const resolved = resolveOrganization(
|
|
235
|
-
config,
|
|
236
|
-
override ?? (projectOrg !== "" ? projectOrg : undefined),
|
|
237
|
-
);
|
|
238
|
-
if (resolved === "") {
|
|
239
|
-
throw new UsageError(
|
|
240
|
-
"organization not set\n\n" +
|
|
241
|
-
"Specify organization in one of these ways:\n" +
|
|
242
|
-
" 1. Set metadata.org in stigmer.yaml\n" +
|
|
243
|
-
" 2. Use --org flag: stigmer apply --org <org>\n" +
|
|
244
|
-
" 3. Set context: stigmer context set --org <org>",
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
return resolved;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
135
|
async function runFileApply(
|
|
251
136
|
path: string,
|
|
252
137
|
orgOverride: string | undefined,
|
|
@@ -298,24 +183,24 @@ async function runFileApply(
|
|
|
298
183
|
);
|
|
299
184
|
}
|
|
300
185
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
186
|
+
// What a bare `stigmer apply` says when the target holds no plugin manifest:
|
|
187
|
+
// the two things the command does, and, when a `stigmer.yaml` sits there,
|
|
188
|
+
// what that file was and where its resources go now.
|
|
189
|
+
function buildGuidance(target: string): CommandResult {
|
|
190
|
+
const result = CommandResult.warning(`No plugin manifest in ${target}`);
|
|
305
191
|
result
|
|
306
|
-
.addSection("")
|
|
307
|
-
.item(
|
|
308
|
-
|
|
309
|
-
)
|
|
310
|
-
.item("
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
);
|
|
192
|
+
.addSection("What 'stigmer apply' does")
|
|
193
|
+
.item("stigmer apply -f <file> apply a resource file (agent, workflow, MCP server, ...)")
|
|
194
|
+
.item("stigmer apply -f <dir> apply every YAML file in a directory")
|
|
195
|
+
.item("stigmer push skill <dir> publish a skill folder")
|
|
196
|
+
.item("stigmer push plugin <dir> install a folder of resources as one plugin");
|
|
197
|
+
if (existsSync(join(target, RETIRED_PROJECT_FILE))) {
|
|
198
|
+
result
|
|
199
|
+
.addSection(`About the ${RETIRED_PROJECT_FILE} here`)
|
|
200
|
+
.item(
|
|
201
|
+
"It is the Project format, which Stigmer no longer has. A folder of resources that belong together is a plugin: run `stigmer push plugin <dir>`, or apply each resource file with `stigmer apply -f`.",
|
|
202
|
+
);
|
|
203
|
+
}
|
|
319
204
|
return result;
|
|
320
205
|
}
|
|
321
206
|
|
package/src/commands/validate.ts
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
type OutputFlags,
|
|
24
24
|
renderResult,
|
|
25
25
|
} from "../output/index.js";
|
|
26
|
-
import { defaultRegistry, Verb } from "../registry/index.js";
|
|
26
|
+
import { defaultRegistry, unknownKindError, Verb } from "../registry/index.js";
|
|
27
27
|
import { loadDocuments, resolveYamlFiles } from "../resources/documents.js";
|
|
28
28
|
import { addResultFlags, resultFormat } from "./shared.js";
|
|
29
29
|
|
|
@@ -70,9 +70,7 @@ async function runValidate(options: ValidateFlags): Promise<CommandResult> {
|
|
|
70
70
|
// Validate stays lenient (default) — strict parsing is reserved for apply.
|
|
71
71
|
for (const { kind, document } of loadDocuments(file)) {
|
|
72
72
|
const info = registry.getByYamlKind(kind);
|
|
73
|
-
if (info === undefined)
|
|
74
|
-
throw new UsageError(`unknown resource kind '${kind}' in ${file}`);
|
|
75
|
-
}
|
|
73
|
+
if (info === undefined) throw unknownKindError(kind, file);
|
|
76
74
|
if (!info.supportedVerbs.has(Verb.Validate)) {
|
|
77
75
|
throw new UsageError(
|
|
78
76
|
`${info.displayName} does not support validation (in ${file})`,
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
// Pins the local bootstrap's shape: prepare, then run,
|
|
2
|
-
//
|
|
3
|
-
// run that cannot start
|
|
4
|
-
//
|
|
5
|
-
// naming `stigmer bootstrap` and holds the retire back with a line saying so;
|
|
6
|
-
// a retire failure is contained by its own warning; nothing throws past the
|
|
1
|
+
// Pins the local bootstrap's shape: prepare, then run, over one client; a
|
|
2
|
+
// preparation failure skips the run with one warning naming the retry; a
|
|
3
|
+
// run that cannot start is one warning; a default that failed to land is one
|
|
4
|
+
// warning per default naming `stigmer bootstrap`; nothing throws past the
|
|
7
5
|
// bootstrap.
|
|
8
6
|
|
|
9
7
|
import { describe, expect, it } from "vitest";
|
|
@@ -24,9 +22,9 @@ function converged(): BootstrapResult {
|
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
describe("bootstrapLocalBackend", () => {
|
|
27
|
-
it("prepares,
|
|
25
|
+
it("prepares, then runs, over the same client", async () => {
|
|
28
26
|
const order: string[] = [];
|
|
29
|
-
await bootstrapLocalBackend(
|
|
27
|
+
await bootstrapLocalBackend({
|
|
30
28
|
client,
|
|
31
29
|
say: () => {},
|
|
32
30
|
prepare: async () => {
|
|
@@ -39,20 +37,14 @@ describe("bootstrapLocalBackend", () => {
|
|
|
39
37
|
order.push("run");
|
|
40
38
|
return converged();
|
|
41
39
|
},
|
|
42
|
-
retire: async (s, home) => {
|
|
43
|
-
expect(s).toBe(stigmer);
|
|
44
|
-
expect(home).toBe("/home/x");
|
|
45
|
-
order.push("retire");
|
|
46
|
-
},
|
|
47
40
|
});
|
|
48
|
-
expect(order).toEqual(["prepare", "run"
|
|
41
|
+
expect(order).toEqual(["prepare", "run"]);
|
|
49
42
|
});
|
|
50
43
|
|
|
51
|
-
it("skips the run
|
|
44
|
+
it("skips the run when preparation fails, with one warning", async () => {
|
|
52
45
|
const said: string[] = [];
|
|
53
|
-
let retired = 0;
|
|
54
46
|
let ran = 0;
|
|
55
|
-
await bootstrapLocalBackend(
|
|
47
|
+
await bootstrapLocalBackend({
|
|
56
48
|
client,
|
|
57
49
|
say: (line) => said.push(line),
|
|
58
50
|
prepare: async () => {
|
|
@@ -62,41 +54,31 @@ describe("bootstrapLocalBackend", () => {
|
|
|
62
54
|
ran += 1;
|
|
63
55
|
return converged();
|
|
64
56
|
},
|
|
65
|
-
retire: async () => {
|
|
66
|
-
retired += 1;
|
|
67
|
-
},
|
|
68
57
|
});
|
|
69
58
|
expect(ran).toBe(0);
|
|
70
|
-
expect(retired).toBe(0);
|
|
71
59
|
expect(said).toEqual([
|
|
72
|
-
"Warning: could not prepare the default plugins, so the local backend was not bootstrapped
|
|
60
|
+
"Warning: could not prepare the default plugins, so the local backend was not bootstrapped. Run 'stigmer up' again to retry.",
|
|
73
61
|
]);
|
|
74
62
|
});
|
|
75
63
|
|
|
76
|
-
it("
|
|
64
|
+
it("contains a run that cannot start in one warning naming the retry", async () => {
|
|
77
65
|
const said: string[] = [];
|
|
78
|
-
|
|
79
|
-
await bootstrapLocalBackend("/home/x", {
|
|
66
|
+
await bootstrapLocalBackend({
|
|
80
67
|
client,
|
|
81
68
|
say: (line) => said.push(line),
|
|
82
69
|
prepare: async () => prepared,
|
|
83
70
|
run: async () => {
|
|
84
71
|
throw new Error("backend unreachable");
|
|
85
72
|
},
|
|
86
|
-
retire: async () => {
|
|
87
|
-
retired += 1;
|
|
88
|
-
},
|
|
89
73
|
});
|
|
90
|
-
expect(retired).toBe(0);
|
|
91
74
|
expect(said).toEqual([
|
|
92
|
-
"Warning: failed to bootstrap the local backend
|
|
75
|
+
"Warning: failed to bootstrap the local backend. Run 'stigmer up' again to retry.",
|
|
93
76
|
]);
|
|
94
77
|
});
|
|
95
78
|
|
|
96
|
-
it("
|
|
79
|
+
it("names each default that did not land and the verb that retries it", async () => {
|
|
97
80
|
const said: string[] = [];
|
|
98
|
-
|
|
99
|
-
await bootstrapLocalBackend("/home/x", {
|
|
81
|
+
await bootstrapLocalBackend({
|
|
100
82
|
client,
|
|
101
83
|
say: (line) => said.push(line),
|
|
102
84
|
prepare: async () => prepared,
|
|
@@ -109,35 +91,9 @@ describe("bootstrapLocalBackend", () => {
|
|
|
109
91
|
failed: ["assistant"],
|
|
110
92
|
},
|
|
111
93
|
}),
|
|
112
|
-
retire: async () => {
|
|
113
|
-
retired += 1;
|
|
114
|
-
},
|
|
115
94
|
});
|
|
116
|
-
expect(retired).toBe(0);
|
|
117
95
|
expect(said).toEqual([
|
|
118
96
|
"Warning: failed to install default plugin 'assistant'. Run 'stigmer bootstrap' to retry.",
|
|
119
|
-
"Nothing was retired: the resources an older release installed stay until every default plugin is in place.",
|
|
120
|
-
]);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("contains a retire that threw in one warning, after the run succeeded", async () => {
|
|
124
|
-
const said: string[] = [];
|
|
125
|
-
let ran = 0;
|
|
126
|
-
await bootstrapLocalBackend("/home/x", {
|
|
127
|
-
client,
|
|
128
|
-
say: (line) => said.push(line),
|
|
129
|
-
prepare: async () => prepared,
|
|
130
|
-
run: async () => {
|
|
131
|
-
ran += 1;
|
|
132
|
-
return converged();
|
|
133
|
-
},
|
|
134
|
-
retire: async () => {
|
|
135
|
-
throw new Error("project read failed");
|
|
136
|
-
},
|
|
137
|
-
});
|
|
138
|
-
expect(ran).toBe(1);
|
|
139
|
-
expect(said).toEqual([
|
|
140
|
-
"Warning: failed to retire the resources an older release installed. Run 'stigmer up' again to retry.",
|
|
141
97
|
]);
|
|
142
98
|
});
|
|
143
99
|
});
|
package/src/local/bootstrap.ts
CHANGED
|
@@ -6,29 +6,20 @@
|
|
|
6
6
|
// lane. One definition, so a laptop, a container and the hosted platform
|
|
7
7
|
// cannot drift in what "ready" means.
|
|
8
8
|
//
|
|
9
|
-
// Two phases, and the order matters
|
|
9
|
+
// Two phases, and the order matters:
|
|
10
10
|
//
|
|
11
11
|
// 1. prepare: resolve the official marketplace (acquiring `@stigmer/plugins`
|
|
12
12
|
// at the CLI's version on a release's first run) and prepare every
|
|
13
13
|
// default. No backend is touched.
|
|
14
14
|
// 2. run: ensure the system org, then push the prepared defaults, each
|
|
15
|
-
// skipped when the backend already holds it at the same digest.
|
|
15
|
+
// skipped when the backend already holds it at the same digest. The
|
|
16
|
+
// push adopts a system-content row it replaces in place (a default
|
|
17
|
+
// agent an earlier release seeded keeps its id, its instances and every
|
|
18
|
+
// conversation bound to them), so nothing here ever deletes a row.
|
|
16
19
|
//
|
|
17
|
-
// `
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// the system-content rows it replaces in place (the seedpack's `assistant`
|
|
21
|
-
// keeps its id, its instances and every conversation bound to them), and a
|
|
22
|
-
// row that is now a plugin's member is never a candidate for the retire. A
|
|
23
|
-
// user who upgrades offline and cannot fetch the new catalogue, or whose
|
|
24
|
-
// backend refused a default, keeps the old rows for another day instead of
|
|
25
|
-
// losing them and getting nothing back. The retire lives here and not in
|
|
26
|
-
// `stigmer bootstrap` on purpose: a verb a scheduled lane runs against the
|
|
27
|
-
// platform must never be able to delete rows.
|
|
28
|
-
//
|
|
29
|
-
// Inside `up` every phase is best-effort with its own warning naming the
|
|
30
|
-
// retry, because the stack is already serving by the time any of them runs
|
|
31
|
-
// and a seeding failure must never tear it down. Both `up` shapes (detached
|
|
20
|
+
// Inside `up` both phases are best-effort with their own warning naming the
|
|
21
|
+
// retry, because the stack is already serving by the time either runs and a
|
|
22
|
+
// seeding failure must never tear it down. Both `up` shapes (detached
|
|
32
23
|
// and foreground) call this one function, and both seed the local server
|
|
33
24
|
// `up` just started, never the active backend context: a CLI pointed at
|
|
34
25
|
// cloud still gets its local stack seeded, and never has system resources
|
|
@@ -44,7 +35,6 @@ import type {
|
|
|
44
35
|
InstallingVerb,
|
|
45
36
|
PreparedDefault,
|
|
46
37
|
} from "./plugins/defaults.js";
|
|
47
|
-
import { dataDir } from "./paths.js";
|
|
48
38
|
import { type EnsureSystemOrgOutcome, SYSTEM_ORG } from "./system-org.js";
|
|
49
39
|
|
|
50
40
|
export type Say = (line: string) => void;
|
|
@@ -116,7 +106,6 @@ export interface BootstrapDeps {
|
|
|
116
106
|
readonly client?: BackendClient;
|
|
117
107
|
readonly say?: Say;
|
|
118
108
|
readonly prepare?: () => Promise<PreparedBootstrap>;
|
|
119
|
-
readonly retire?: (stigmer: Stigmer, home: string, say: Say) => Promise<void>;
|
|
120
109
|
readonly run?: (
|
|
121
110
|
stigmer: Stigmer,
|
|
122
111
|
prepared: PreparedBootstrap,
|
|
@@ -130,7 +119,6 @@ const sayToStderr: Say = (line) => {
|
|
|
130
119
|
|
|
131
120
|
/** What `stigmer up` does once the local server answers. Never throws past its own warnings. */
|
|
132
121
|
export async function bootstrapLocalBackend(
|
|
133
|
-
home: string,
|
|
134
122
|
deps: BootstrapDeps = {},
|
|
135
123
|
): Promise<void> {
|
|
136
124
|
const client = deps.client ?? (await freshLocalClient());
|
|
@@ -142,7 +130,7 @@ export async function bootstrapLocalBackend(
|
|
|
142
130
|
} catch (err) {
|
|
143
131
|
log.warn("bootstrap preparation failed", { error: String(err) });
|
|
144
132
|
say(
|
|
145
|
-
"Warning: could not prepare the default plugins, so the local backend was not bootstrapped
|
|
133
|
+
"Warning: could not prepare the default plugins, so the local backend was not bootstrapped. Run 'stigmer up' again to retry.",
|
|
146
134
|
);
|
|
147
135
|
return;
|
|
148
136
|
}
|
|
@@ -157,7 +145,7 @@ export async function bootstrapLocalBackend(
|
|
|
157
145
|
} catch (err) {
|
|
158
146
|
log.warn("bootstrap failed", { error: String(err) });
|
|
159
147
|
say(
|
|
160
|
-
"Warning: failed to bootstrap the local backend
|
|
148
|
+
"Warning: failed to bootstrap the local backend. Run 'stigmer up' again to retry.",
|
|
161
149
|
);
|
|
162
150
|
return;
|
|
163
151
|
}
|
|
@@ -165,24 +153,9 @@ export async function bootstrapLocalBackend(
|
|
|
165
153
|
org: result.org,
|
|
166
154
|
outcomes: result.plugins.outcomes,
|
|
167
155
|
});
|
|
168
|
-
|
|
169
|
-
for (const name of result.plugins.failed) {
|
|
170
|
-
say(
|
|
171
|
-
`Warning: failed to install default plugin '${name}'. Run 'stigmer bootstrap' to retry.`,
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
say(
|
|
175
|
-
"Nothing was retired: the resources an older release installed stay until every default plugin is in place.",
|
|
176
|
-
);
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
await (deps.retire ?? retireSeedpack)(client.stigmer, home, say);
|
|
182
|
-
} catch (err) {
|
|
183
|
-
log.warn("seedpack retire failed", { error: String(err) });
|
|
156
|
+
for (const name of result.plugins.failed) {
|
|
184
157
|
say(
|
|
185
|
-
|
|
158
|
+
`Warning: failed to install default plugin '${name}'. Run 'stigmer bootstrap' to retry.`,
|
|
186
159
|
);
|
|
187
160
|
}
|
|
188
161
|
}
|
|
@@ -197,20 +170,6 @@ function runLocalBootstrap(
|
|
|
197
170
|
return runBootstrap(stigmer, prepared, say, "stigmer up");
|
|
198
171
|
}
|
|
199
172
|
|
|
200
|
-
// The retire step in the shape the seam expects; the module is loaded only
|
|
201
|
-
// inside `up`, the one place a retire may run.
|
|
202
|
-
async function retireSeedpack(
|
|
203
|
-
stigmer: Stigmer,
|
|
204
|
-
home: string,
|
|
205
|
-
say: Say,
|
|
206
|
-
): Promise<void> {
|
|
207
|
-
const { retireSeedpack: retire, renderRetireReport } = await import(
|
|
208
|
-
"./seedpack-retire.js"
|
|
209
|
-
);
|
|
210
|
-
const result = await retire(stigmer, { markerDir: dataDir(home) });
|
|
211
|
-
renderRetireReport(result, say);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
173
|
async function freshLocalClient(): Promise<BackendClient> {
|
|
215
174
|
const [{ createBackendClient }, { getDefault }] = await Promise.all([
|
|
216
175
|
import("../client/index.js"),
|
|
@@ -72,7 +72,7 @@ export async function up(options: UpOptions = {}, home: string = homedir()): Pro
|
|
|
72
72
|
|
|
73
73
|
await waitForTcp({ port: SERVER_PORT, timeoutMs: READY_TIMEOUT_MS, label: "stigmer-server" });
|
|
74
74
|
|
|
75
|
-
await bootstrapLocalBackend(
|
|
75
|
+
await bootstrapLocalBackend();
|
|
76
76
|
|
|
77
77
|
saveStartupConfig(data, buildStartupConfig(data, logs, temporalAddress, daemonPid, options));
|
|
78
78
|
}
|
|
@@ -83,8 +83,8 @@ export interface UpForegroundDeps {
|
|
|
83
83
|
runDaemon?: (deps: InternalDaemonDeps) => Promise<number>;
|
|
84
84
|
/** Resolves when the stack should shut down (default: the first SIGTERM/SIGINT). */
|
|
85
85
|
waitForShutdown?: () => Promise<void>;
|
|
86
|
-
/** Post-readiness bootstrap (default: `bootstrapLocalBackend`: prepare the defaults,
|
|
87
|
-
bootstrap?: (
|
|
86
|
+
/** Post-readiness bootstrap (default: `bootstrapLocalBackend`: prepare the defaults, ensure the org, install). */
|
|
87
|
+
bootstrap?: () => Promise<void>;
|
|
88
88
|
/** Receives each line the server and runner write (default: a `[component]`-prefixed stdout mirror). */
|
|
89
89
|
mirror?: OutputMirror;
|
|
90
90
|
/** Invoked once the stack is serving and seeded — the moment a detached `up` would have returned. */
|
|
@@ -118,7 +118,7 @@ export async function upForeground(
|
|
|
118
118
|
host: new NodeProcessHost({ mirror: deps.mirror ?? mirrorToStdout }),
|
|
119
119
|
waitForShutdown: deps.waitForShutdown ?? waitForShutdownSignal,
|
|
120
120
|
onStarted: async () => {
|
|
121
|
-
await bootstrap(
|
|
121
|
+
await bootstrap();
|
|
122
122
|
saveStartupConfig(data, buildStartupConfig(data, logs, temporalAddress, process.pid, options));
|
|
123
123
|
await deps.onReady?.();
|
|
124
124
|
},
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
// release defect, not a condition to install around. Installing pushes the
|
|
11
11
|
// prepared archives and is best-effort per entry: one default failing to
|
|
12
12
|
// land does not stop the next, and the caller decides how to surface the
|
|
13
|
-
// failures. The split
|
|
14
|
-
//
|
|
13
|
+
// failures. The split keeps every network read (the catalogue, the
|
|
14
|
+
// archives) ahead of the first write to the backend (local/bootstrap.ts).
|
|
15
15
|
//
|
|
16
16
|
// Idempotent by the server's own identity, not a marker file: before each
|
|
17
17
|
// push the org's plugin of that name is read back; when its digest equals the
|
package/src/registry/index.ts
CHANGED
|
@@ -7,12 +7,18 @@ export {
|
|
|
7
7
|
toKebabCase,
|
|
8
8
|
toSnakeCase,
|
|
9
9
|
} from "./aliases.js";
|
|
10
|
-
export {
|
|
10
|
+
export {
|
|
11
|
+
CLI_RELEVANT_KINDS,
|
|
12
|
+
type KindMeta,
|
|
13
|
+
KIND_META,
|
|
14
|
+
RETIRED_KINDS,
|
|
15
|
+
} from "./metadata.js";
|
|
11
16
|
export {
|
|
12
17
|
defaultRegistry,
|
|
13
18
|
type Registry,
|
|
14
19
|
supportsVerb,
|
|
15
20
|
type TypeInfo,
|
|
21
|
+
unknownKindError,
|
|
16
22
|
} from "./registry.js";
|
|
17
23
|
export {
|
|
18
24
|
ALL_VERBS,
|
package/src/registry/metadata.ts
CHANGED
|
@@ -46,10 +46,6 @@ export const KIND_META: ReadonlyMap<ApiResourceKind, KindMeta> = new Map([
|
|
|
46
46
|
ApiResourceKind.mcp_server,
|
|
47
47
|
{ name: "McpServer", displayName: "MCP Server", idPrefix: "mcp" },
|
|
48
48
|
],
|
|
49
|
-
[
|
|
50
|
-
ApiResourceKind.project,
|
|
51
|
-
{ name: "Project", displayName: "Project", idPrefix: "prj" },
|
|
52
|
-
],
|
|
53
49
|
[
|
|
54
50
|
ApiResourceKind.api_key,
|
|
55
51
|
{ name: "ApiKey", displayName: "API Key", idPrefix: "key" },
|
|
@@ -122,7 +118,6 @@ export const CLI_RELEVANT_KINDS: readonly ApiResourceKind[] = [
|
|
|
122
118
|
ApiResourceKind.skill,
|
|
123
119
|
ApiResourceKind.plugin,
|
|
124
120
|
ApiResourceKind.mcp_server,
|
|
125
|
-
ApiResourceKind.project,
|
|
126
121
|
ApiResourceKind.api_key,
|
|
127
122
|
ApiResourceKind.identity_provider,
|
|
128
123
|
ApiResourceKind.oauth_app,
|
|
@@ -135,3 +130,18 @@ export const CLI_RELEVANT_KINDS: readonly ApiResourceKind[] = [
|
|
|
135
130
|
ApiResourceKind.workflow_instance,
|
|
136
131
|
ApiResourceKind.session,
|
|
137
132
|
];
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* YAML kinds the platform once served and no longer does. A manifest that
|
|
136
|
+
* still carries one is an old file, not a typo, so the refusal it meets
|
|
137
|
+
* says what happened and what to do instead of "unknown kind"; the
|
|
138
|
+
* registry's `unknownKindError` reads this table before falling back to the
|
|
139
|
+
* generic sentence. The key is the YAML `kind` value as the retired proto
|
|
140
|
+
* declared it; the value is the sentence that follows the kind's name.
|
|
141
|
+
*/
|
|
142
|
+
export const RETIRED_KINDS: ReadonlyMap<string, string> = new Map([
|
|
143
|
+
[
|
|
144
|
+
"Project",
|
|
145
|
+
"is no longer a Stigmer resource. A folder of resources that belong together is a plugin: run `stigmer push plugin <dir>` to install it as one, or apply each resource file with `stigmer apply -f <file>`.",
|
|
146
|
+
],
|
|
147
|
+
]);
|
|
@@ -241,13 +241,7 @@ describe("registry — verb/dispatch conformance", () => {
|
|
|
241
241
|
label: "apply",
|
|
242
242
|
verb: Verb.Apply,
|
|
243
243
|
wired: new Set(APPLY_HANDLERS.keys()),
|
|
244
|
-
specialCases: new Map(
|
|
245
|
-
[
|
|
246
|
-
ApiResourceKind.project,
|
|
247
|
-
"the stigmer.yaml declarative/synthesis track (resources/apply/declarative.ts); " +
|
|
248
|
-
"file mode refuses with a pointer there (resolveHandlerForKind)",
|
|
249
|
-
],
|
|
250
|
-
]),
|
|
244
|
+
specialCases: new Map(),
|
|
251
245
|
},
|
|
252
246
|
{
|
|
253
247
|
label: "get",
|