@stigmer/cli 3.15.3-dev.20260914121148 → 3.15.3-dev.20260916211208
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 +3 -4
- package/commands/apply.d.ts.map +1 -1
- package/commands/apply.js +56 -4
- package/commands/apply.js.map +1 -1
- package/commands/push.d.ts.map +1 -1
- package/commands/push.js +122 -16
- package/commands/push.js.map +1 -1
- package/commands/validate.d.ts.map +1 -1
- package/commands/validate.js +39 -7
- package/commands/validate.js.map +1 -1
- package/output/command-result.d.ts +8 -0
- package/output/command-result.d.ts.map +1 -1
- package/output/command-result.js +13 -0
- package/output/command-result.js.map +1 -1
- package/package.json +6 -5
- package/registry/aliases.d.ts.map +1 -1
- package/registry/aliases.js.map +1 -1
- package/registry/index.d.ts +3 -3
- package/registry/index.d.ts.map +1 -1
- package/registry/index.js +3 -3
- package/registry/index.js.map +1 -1
- package/registry/metadata.d.ts.map +1 -1
- package/registry/metadata.js +85 -18
- package/registry/metadata.js.map +1 -1
- package/registry/registry.d.ts.map +1 -1
- package/registry/registry.js.map +1 -1
- package/registry/verb-support.d.ts.map +1 -1
- package/registry/verb-support.js +81 -13
- package/registry/verb-support.js.map +1 -1
- package/resources/delete.d.ts.map +1 -1
- package/resources/delete.js +35 -9
- package/resources/delete.js.map +1 -1
- package/resources/get-bindings.d.ts.map +1 -1
- package/resources/get-bindings.js +56 -13
- package/resources/get-bindings.js.map +1 -1
- package/resources/list.d.ts.map +1 -1
- package/resources/list.js +62 -14
- package/resources/list.js.map +1 -1
- package/resources/plugin.d.ts +80 -0
- package/resources/plugin.d.ts.map +1 -0
- package/resources/plugin.js +305 -0
- package/resources/plugin.js.map +1 -0
- package/resources/skill.d.ts +1 -0
- package/resources/skill.d.ts.map +1 -1
- package/resources/skill.js +1 -1
- package/resources/skill.js.map +1 -1
- package/src/commands/apply.ts +133 -21
- package/src/commands/push.ts +270 -41
- package/src/commands/validate.test.ts +111 -22
- package/src/commands/validate.ts +68 -12
- package/src/output/command-result.test.ts +9 -0
- package/src/output/command-result.ts +14 -0
- package/src/registry/aliases.test.ts +24 -5
- package/src/registry/aliases.ts +6 -1
- package/src/registry/index.ts +19 -3
- package/src/registry/metadata.ts +85 -18
- package/src/registry/registry.test.ts +86 -37
- package/src/registry/registry.ts +8 -2
- package/src/registry/verb-support.ts +85 -14
- package/src/resources/delete.ts +106 -26
- package/src/resources/get-bindings.ts +95 -17
- package/src/resources/list.ts +102 -28
- package/src/resources/plugin.test.ts +233 -0
- package/src/resources/plugin.ts +432 -0
- package/src/resources/skill.ts +1 -1
|
@@ -49,7 +49,9 @@ describe("registry — alias resolution", () => {
|
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
it("resolution is case-insensitive", () => {
|
|
52
|
-
expect(registry.getByAlias("MCP-SERVER")?.kind).toBe(
|
|
52
|
+
expect(registry.getByAlias("MCP-SERVER")?.kind).toBe(
|
|
53
|
+
ApiResourceKind.mcp_server,
|
|
54
|
+
);
|
|
53
55
|
});
|
|
54
56
|
|
|
55
57
|
it("returns undefined for an unknown alias", () => {
|
|
@@ -61,7 +63,10 @@ describe("registry — alias resolution", () => {
|
|
|
61
63
|
// row in `list types` that the pre-gate route then shadows — the
|
|
62
64
|
// stigmer/stigmer#469 class (see the alias-shadowing suite below).
|
|
63
65
|
it("does not register the runtime execution kinds as addressable types", () => {
|
|
64
|
-
for (const kind of [
|
|
66
|
+
for (const kind of [
|
|
67
|
+
ApiResourceKind.agent_execution,
|
|
68
|
+
ApiResourceKind.workflow_execution,
|
|
69
|
+
]) {
|
|
65
70
|
expect(registry.getByKind(kind)).toBeUndefined();
|
|
66
71
|
}
|
|
67
72
|
});
|
|
@@ -69,14 +74,26 @@ describe("registry — alias resolution", () => {
|
|
|
69
74
|
|
|
70
75
|
describe("registry — YAML kind resolution", () => {
|
|
71
76
|
it("resolves the exact YAML kind", () => {
|
|
72
|
-
expect(registry.getByYamlKind("McpServer")?.kind).toBe(
|
|
73
|
-
|
|
77
|
+
expect(registry.getByYamlKind("McpServer")?.kind).toBe(
|
|
78
|
+
ApiResourceKind.mcp_server,
|
|
79
|
+
);
|
|
80
|
+
expect(registry.getByYamlKind("Workflow")?.kind).toBe(
|
|
81
|
+
ApiResourceKind.workflow,
|
|
82
|
+
);
|
|
74
83
|
});
|
|
75
84
|
});
|
|
76
85
|
|
|
77
86
|
describe("registry — verb support matrix", () => {
|
|
78
87
|
it("agent supports read + run + search verbs", () => {
|
|
79
|
-
for (const v of [
|
|
88
|
+
for (const v of [
|
|
89
|
+
Verb.Apply,
|
|
90
|
+
Verb.Validate,
|
|
91
|
+
Verb.Get,
|
|
92
|
+
Verb.List,
|
|
93
|
+
Verb.Delete,
|
|
94
|
+
Verb.Run,
|
|
95
|
+
Verb.Search,
|
|
96
|
+
]) {
|
|
80
97
|
expect(registry.supportsVerb(ApiResourceKind.agent, v)).toBe(true);
|
|
81
98
|
}
|
|
82
99
|
expect(registry.supportsVerb(ApiResourceKind.agent, Verb.Push)).toBe(false);
|
|
@@ -84,21 +101,33 @@ describe("registry — verb support matrix", () => {
|
|
|
84
101
|
|
|
85
102
|
it("skill supports push but not apply", () => {
|
|
86
103
|
expect(registry.supportsVerb(ApiResourceKind.skill, Verb.Push)).toBe(true);
|
|
87
|
-
expect(registry.supportsVerb(ApiResourceKind.skill, Verb.Apply)).toBe(
|
|
104
|
+
expect(registry.supportsVerb(ApiResourceKind.skill, Verb.Apply)).toBe(
|
|
105
|
+
false,
|
|
106
|
+
);
|
|
88
107
|
});
|
|
89
108
|
|
|
90
109
|
it("workflow_instance has no list verb", () => {
|
|
91
|
-
expect(
|
|
92
|
-
|
|
110
|
+
expect(
|
|
111
|
+
registry.supportsVerb(ApiResourceKind.workflow_instance, Verb.Get),
|
|
112
|
+
).toBe(true);
|
|
113
|
+
expect(
|
|
114
|
+
registry.supportsVerb(ApiResourceKind.workflow_instance, Verb.List),
|
|
115
|
+
).toBe(false);
|
|
93
116
|
});
|
|
94
117
|
|
|
95
118
|
it("agent_channel supports the full declarative verb set", () => {
|
|
96
119
|
for (const v of [Verb.Apply, Verb.Get, Verb.List, Verb.Delete]) {
|
|
97
|
-
expect(registry.supportsVerb(ApiResourceKind.agent_channel, v)).toBe(
|
|
120
|
+
expect(registry.supportsVerb(ApiResourceKind.agent_channel, v)).toBe(
|
|
121
|
+
true,
|
|
122
|
+
);
|
|
98
123
|
}
|
|
99
124
|
// The install flow is console-driven and cloud-only — never a CLI verb.
|
|
100
|
-
expect(registry.supportsVerb(ApiResourceKind.agent_channel, Verb.Run)).toBe(
|
|
101
|
-
|
|
125
|
+
expect(registry.supportsVerb(ApiResourceKind.agent_channel, Verb.Run)).toBe(
|
|
126
|
+
false,
|
|
127
|
+
);
|
|
128
|
+
expect(
|
|
129
|
+
registry.supportsVerb(ApiResourceKind.agent_channel, Verb.Search),
|
|
130
|
+
).toBe(false);
|
|
102
131
|
});
|
|
103
132
|
|
|
104
133
|
it("schedule supports the full declarative verb set", () => {
|
|
@@ -106,19 +135,27 @@ describe("registry — verb support matrix", () => {
|
|
|
106
135
|
expect(registry.supportsVerb(ApiResourceKind.schedule, v)).toBe(true);
|
|
107
136
|
}
|
|
108
137
|
// Firing rides `stigmer schedule trigger`, never the generic run verb.
|
|
109
|
-
expect(registry.supportsVerb(ApiResourceKind.schedule, Verb.Run)).toBe(
|
|
138
|
+
expect(registry.supportsVerb(ApiResourceKind.schedule, Verb.Run)).toBe(
|
|
139
|
+
false,
|
|
140
|
+
);
|
|
110
141
|
});
|
|
111
142
|
|
|
112
143
|
it("agent_share promises only apply (narrowed — stigmer/stigmer#354)", () => {
|
|
113
|
-
expect(registry.supportsVerb(ApiResourceKind.agent_share, Verb.Apply)).toBe(
|
|
144
|
+
expect(registry.supportsVerb(ApiResourceKind.agent_share, Verb.Apply)).toBe(
|
|
145
|
+
true,
|
|
146
|
+
);
|
|
114
147
|
for (const v of [Verb.Get, Verb.List, Verb.Delete]) {
|
|
115
148
|
expect(registry.supportsVerb(ApiResourceKind.agent_share, v)).toBe(false);
|
|
116
149
|
}
|
|
117
150
|
});
|
|
118
151
|
|
|
119
152
|
it("session promises apply + list; get/delete stay narrowed (stigmer/stigmer#354, #469)", () => {
|
|
120
|
-
expect(registry.supportsVerb(ApiResourceKind.session, Verb.Apply)).toBe(
|
|
121
|
-
|
|
153
|
+
expect(registry.supportsVerb(ApiResourceKind.session, Verb.Apply)).toBe(
|
|
154
|
+
true,
|
|
155
|
+
);
|
|
156
|
+
expect(registry.supportsVerb(ApiResourceKind.session, Verb.List)).toBe(
|
|
157
|
+
true,
|
|
158
|
+
);
|
|
122
159
|
for (const v of [Verb.Get, Verb.Delete]) {
|
|
123
160
|
expect(registry.supportsVerb(ApiResourceKind.session, v)).toBe(false);
|
|
124
161
|
}
|
|
@@ -248,27 +285,33 @@ describe("registry — verb/dispatch conformance", () => {
|
|
|
248
285
|
},
|
|
249
286
|
];
|
|
250
287
|
|
|
251
|
-
it.each(DISPATCH)(
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
288
|
+
it.each(DISPATCH)(
|
|
289
|
+
"every kind promising '$label' has a dispatch entry",
|
|
290
|
+
({ verb, wired, specialCases }) => {
|
|
291
|
+
for (const info of registry.all()) {
|
|
292
|
+
if (!info.supportedVerbs.has(verb)) continue;
|
|
293
|
+
if (specialCases.has(info.kind)) continue;
|
|
294
|
+
expect(
|
|
295
|
+
wired.has(info.kind),
|
|
296
|
+
`${info.name} promises '${verb}' in the verb matrix but has no dispatch entry — ` +
|
|
297
|
+
"wire it or narrow the matrix (stigmer/stigmer#353, #354)",
|
|
298
|
+
).toBe(true);
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
);
|
|
262
302
|
|
|
263
|
-
it.each(DISPATCH)(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
303
|
+
it.each(DISPATCH)(
|
|
304
|
+
"every '$label' dispatch entry is promised in the matrix",
|
|
305
|
+
({ verb, wired }) => {
|
|
306
|
+
for (const kind of wired) {
|
|
307
|
+
expect(
|
|
308
|
+
registry.supportsVerb(kind, verb),
|
|
309
|
+
`${ApiResourceKind[kind]} is wired for '${verb}' but the verb matrix does not promise it — ` +
|
|
310
|
+
"dead dispatch the command-layer gate blocks",
|
|
311
|
+
).toBe(true);
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
);
|
|
272
315
|
|
|
273
316
|
it("special cases do not shadow a real dispatch entry", () => {
|
|
274
317
|
for (const { label, wired, specialCases } of DISPATCH) {
|
|
@@ -292,8 +335,14 @@ describe("registry — verb/dispatch conformance", () => {
|
|
|
292
335
|
// alias set, so a future bypass for an addressable kind is a red test, not
|
|
293
336
|
// a silently lying `list types` row.
|
|
294
337
|
describe("registry — pre-gate list aliases cannot shadow registered kinds", () => {
|
|
295
|
-
const PRE_GATE_PREDICATES: ReadonlyArray<{
|
|
296
|
-
|
|
338
|
+
const PRE_GATE_PREDICATES: ReadonlyArray<{
|
|
339
|
+
name: string;
|
|
340
|
+
matches: (type: string) => boolean;
|
|
341
|
+
}> = [
|
|
342
|
+
{
|
|
343
|
+
name: "executions (resources/execution.ts isExecutionAlias)",
|
|
344
|
+
matches: isExecutionAlias,
|
|
345
|
+
},
|
|
297
346
|
];
|
|
298
347
|
|
|
299
348
|
it("no registered kind's alias is intercepted by a pre-gate predicate", () => {
|
package/src/registry/registry.ts
CHANGED
|
@@ -64,7 +64,12 @@ function buildTypeInfo(kind: ApiResourceKind): TypeInfo | undefined {
|
|
|
64
64
|
// ApiResourceKind[kind] reverse-maps to the proto enum value name (e.g.
|
|
65
65
|
// "oauth_app") — the canonical spelling, taken from the enum itself so it
|
|
66
66
|
// can never drift from the proto.
|
|
67
|
-
aliases: generateAliases(
|
|
67
|
+
aliases: generateAliases(
|
|
68
|
+
meta.name,
|
|
69
|
+
meta.displayName,
|
|
70
|
+
meta.idPrefix,
|
|
71
|
+
ApiResourceKind[kind],
|
|
72
|
+
),
|
|
68
73
|
supportedVerbs: verbsForKind(kind),
|
|
69
74
|
};
|
|
70
75
|
}
|
|
@@ -91,7 +96,8 @@ function buildRegistry(): Registry {
|
|
|
91
96
|
getByAlias: (input) => byAlias.get(normalizeAlias(input)),
|
|
92
97
|
getByYamlKind: (yamlKind) => byYamlKind.get(yamlKind),
|
|
93
98
|
all: () => all,
|
|
94
|
-
supportsVerb: (kind, verb) =>
|
|
99
|
+
supportsVerb: (kind, verb) =>
|
|
100
|
+
byKind.get(kind)?.supportedVerbs.has(verb) ?? false,
|
|
95
101
|
typesForVerb: (verb) => all.filter((info) => info.supportedVerbs.has(verb)),
|
|
96
102
|
};
|
|
97
103
|
}
|
|
@@ -13,50 +13,121 @@
|
|
|
13
13
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
14
14
|
import { Verb } from "./verbs.js";
|
|
15
15
|
|
|
16
|
-
export const VERB_SUPPORT: ReadonlyMap<
|
|
17
|
-
|
|
16
|
+
export const VERB_SUPPORT: ReadonlyMap<
|
|
17
|
+
ApiResourceKind,
|
|
18
|
+
ReadonlySet<Verb>
|
|
19
|
+
> = new Map([
|
|
20
|
+
[
|
|
21
|
+
ApiResourceKind.organization,
|
|
22
|
+
new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete]),
|
|
23
|
+
],
|
|
18
24
|
[
|
|
19
25
|
ApiResourceKind.agent,
|
|
20
|
-
new Set<Verb>([
|
|
26
|
+
new Set<Verb>([
|
|
27
|
+
Verb.Apply,
|
|
28
|
+
Verb.Validate,
|
|
29
|
+
Verb.Get,
|
|
30
|
+
Verb.List,
|
|
31
|
+
Verb.Delete,
|
|
32
|
+
Verb.Run,
|
|
33
|
+
Verb.Search,
|
|
34
|
+
]),
|
|
21
35
|
],
|
|
22
36
|
[
|
|
23
37
|
ApiResourceKind.workflow,
|
|
24
|
-
new Set<Verb>([
|
|
38
|
+
new Set<Verb>([
|
|
39
|
+
Verb.Apply,
|
|
40
|
+
Verb.Validate,
|
|
41
|
+
Verb.Get,
|
|
42
|
+
Verb.List,
|
|
43
|
+
Verb.Delete,
|
|
44
|
+
Verb.Run,
|
|
45
|
+
Verb.Search,
|
|
46
|
+
]),
|
|
47
|
+
],
|
|
48
|
+
[
|
|
49
|
+
ApiResourceKind.skill,
|
|
50
|
+
new Set<Verb>([Verb.Get, Verb.List, Verb.Delete, Verb.Push]),
|
|
51
|
+
],
|
|
52
|
+
// A plugin is installed from its folder (`push plugin <dir>`, or `apply`
|
|
53
|
+
// pointed at one); it is never authored as YAML, so no Validate/Apply of a
|
|
54
|
+
// document — `validate -f <dir>` reads the folder through the plugin route.
|
|
55
|
+
[
|
|
56
|
+
ApiResourceKind.plugin,
|
|
57
|
+
new Set<Verb>([Verb.Get, Verb.List, Verb.Delete, Verb.Push]),
|
|
58
|
+
],
|
|
59
|
+
[
|
|
60
|
+
ApiResourceKind.mcp_server,
|
|
61
|
+
new Set<Verb>([
|
|
62
|
+
Verb.Apply,
|
|
63
|
+
Verb.Validate,
|
|
64
|
+
Verb.Get,
|
|
65
|
+
Verb.List,
|
|
66
|
+
Verb.Delete,
|
|
67
|
+
]),
|
|
25
68
|
],
|
|
26
|
-
[ApiResourceKind.skill, new Set<Verb>([Verb.Get, Verb.List, Verb.Delete, Verb.Push])],
|
|
27
|
-
[ApiResourceKind.mcp_server, new Set<Verb>([Verb.Apply, Verb.Validate, Verb.Get, Verb.List, Verb.Delete])],
|
|
28
69
|
// Project apply is the stigmer.yaml declarative/synthesis track, not
|
|
29
70
|
// `apply -f project.yaml` — resolveHandlerForKind carries the teaching
|
|
30
71
|
// refusal, and the conformance test documents it as a special case.
|
|
31
|
-
[
|
|
72
|
+
[
|
|
73
|
+
ApiResourceKind.project,
|
|
74
|
+
new Set<Verb>([
|
|
75
|
+
Verb.Apply,
|
|
76
|
+
Verb.Validate,
|
|
77
|
+
Verb.Get,
|
|
78
|
+
Verb.List,
|
|
79
|
+
Verb.Delete,
|
|
80
|
+
]),
|
|
81
|
+
],
|
|
32
82
|
[ApiResourceKind.api_key, new Set<Verb>([Verb.Get, Verb.List, Verb.Delete])],
|
|
33
83
|
// agent_execution is special — uses dedicated AgentExecutionQueryController
|
|
34
84
|
// RPCs, not the unified SearchService. delete maps to cancel.
|
|
35
|
-
[
|
|
85
|
+
[
|
|
86
|
+
ApiResourceKind.agent_execution,
|
|
87
|
+
new Set<Verb>([Verb.Get, Verb.List, Verb.Delete, Verb.Download]),
|
|
88
|
+
],
|
|
36
89
|
// IAM apps are configured declaratively; read/ops verbs are deliberately
|
|
37
90
|
// not promised (never wired, no demand — stigmer/stigmer#354). To add one:
|
|
38
91
|
// dispatch entry + this line, in the same change.
|
|
39
92
|
[ApiResourceKind.identity_provider, new Set<Verb>([Verb.Apply])],
|
|
40
93
|
[ApiResourceKind.oauth_app, new Set<Verb>([Verb.Apply])],
|
|
41
|
-
[
|
|
94
|
+
[
|
|
95
|
+
ApiResourceKind.environment,
|
|
96
|
+
new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete]),
|
|
97
|
+
],
|
|
42
98
|
// Enable/disable flows through `stigmer share agent`, which is the whole
|
|
43
99
|
// read/ops surface today; only the declarative apply path is promised
|
|
44
100
|
// (stigmer/stigmer#354 records the narrowing).
|
|
45
101
|
[ApiResourceKind.agent_share, new Set<Verb>([Verb.Apply])],
|
|
46
102
|
// The provider install flow is console-driven and cloud-only; the generic
|
|
47
103
|
// verbs cover the declarative path (apply a manifest, inspect, tear down).
|
|
48
|
-
[
|
|
104
|
+
[
|
|
105
|
+
ApiResourceKind.agent_channel,
|
|
106
|
+
new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete]),
|
|
107
|
+
],
|
|
49
108
|
// Secret fields round-trip as ***REDACTED***; applying a fetched manifest
|
|
50
109
|
// preserves the stored secrets (the OAuthApp marker convention).
|
|
51
|
-
[
|
|
110
|
+
[
|
|
111
|
+
ApiResourceKind.channel_app,
|
|
112
|
+
new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete]),
|
|
113
|
+
],
|
|
52
114
|
// Firing is the platform's job (Temporal Schedules); the generic verbs
|
|
53
115
|
// cover the declarative path, and get/list surface the state the dedicated
|
|
54
116
|
// `stigmer schedule` commands act on — spec.enabled (the owner's switch)
|
|
55
117
|
// versus status.paused_reason (the platform's failure latch).
|
|
56
|
-
[
|
|
57
|
-
|
|
118
|
+
[
|
|
119
|
+
ApiResourceKind.schedule,
|
|
120
|
+
new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete]),
|
|
121
|
+
],
|
|
122
|
+
[
|
|
123
|
+
ApiResourceKind.agent_instance,
|
|
124
|
+
new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete]),
|
|
125
|
+
],
|
|
58
126
|
// No list: proto exposes getByWorkflow (requires workflow_id), not a generic list.
|
|
59
|
-
[
|
|
127
|
+
[
|
|
128
|
+
ApiResourceKind.workflow_instance,
|
|
129
|
+
new Set<Verb>([Verb.Apply, Verb.Get, Verb.Delete]),
|
|
130
|
+
],
|
|
60
131
|
// Session list is served by its dedicated query RPC (a LIST_HANDLERS
|
|
61
132
|
// entry); stigmer/stigmer#469 promoted it into the matrix after it shipped
|
|
62
133
|
// working-but-unadvertised through a pre-gate bypass. get/delete remain
|
package/src/resources/delete.ts
CHANGED
|
@@ -23,7 +23,12 @@ import type { Stigmer } from "@stigmer/sdk";
|
|
|
23
23
|
import { CliExitError, ExitCode, UsageError } from "../errors/index.js";
|
|
24
24
|
import { CommandResult } from "../output/index.js";
|
|
25
25
|
import { defaultRegistry, type TypeInfo, Verb } from "../registry/index.js";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
cancelAgentExecution,
|
|
28
|
+
formatAgentPhase,
|
|
29
|
+
isAgentExecutionId,
|
|
30
|
+
isExecutionAlias,
|
|
31
|
+
} from "./execution.js";
|
|
27
32
|
import { fetchResource } from "./get.js";
|
|
28
33
|
import { getterFor } from "./get-bindings.js";
|
|
29
34
|
import { parseReference } from "./reference.js";
|
|
@@ -42,13 +47,22 @@ export interface DeletePlan {
|
|
|
42
47
|
// the structural slice the delete UI needs. Reading it generically keeps the
|
|
43
48
|
// dispatch kind-agnostic without a per-kind accessor.
|
|
44
49
|
interface HasMetadata {
|
|
45
|
-
readonly metadata?: {
|
|
50
|
+
readonly metadata?: {
|
|
51
|
+
readonly id?: string;
|
|
52
|
+
readonly name?: string;
|
|
53
|
+
readonly slug?: string;
|
|
54
|
+
readonly org?: string;
|
|
55
|
+
};
|
|
46
56
|
}
|
|
47
57
|
|
|
48
58
|
// `force` is the RPC-level acknowledgment of destructive side effects —
|
|
49
59
|
// distinct from, but carried by, the CLI's `-f/--force` flag. Kinds whose delete RPC takes a plain typed ID have no
|
|
50
60
|
// force field and simply ignore the argument.
|
|
51
|
-
type DeleteFn = (
|
|
61
|
+
type DeleteFn = (
|
|
62
|
+
client: Stigmer,
|
|
63
|
+
id: string,
|
|
64
|
+
force: boolean,
|
|
65
|
+
) => Promise<HasMetadata>;
|
|
52
66
|
|
|
53
67
|
// Kinds the unified delete handles directly, each bound to its SDK delete call.
|
|
54
68
|
// Every kind declaring Delete in the verb matrix must have an entry here (or a
|
|
@@ -56,20 +70,33 @@ type DeleteFn = (client: Stigmer, id: string, force: boolean) => Promise<HasMeta
|
|
|
56
70
|
// enforces it, so the two cannot drift.
|
|
57
71
|
// Kinds whose delete takes a DeleteResourceInput (not an ID) thread the force
|
|
58
72
|
// acknowledgment through — force is the contract's generic ack carrier.
|
|
59
|
-
export const DELETE_HANDLERS: ReadonlyMap<ApiResourceKind, DeleteFn> = new Map<
|
|
73
|
+
export const DELETE_HANDLERS: ReadonlyMap<ApiResourceKind, DeleteFn> = new Map<
|
|
74
|
+
ApiResourceKind,
|
|
75
|
+
DeleteFn
|
|
76
|
+
>([
|
|
60
77
|
[ApiResourceKind.agent, (c, id) => c.agent.delete(id)],
|
|
61
78
|
[ApiResourceKind.agent_instance, (c, id) => c.agentInstance.delete(id)],
|
|
62
79
|
[ApiResourceKind.workflow, (c, id) => c.workflow.delete(id)],
|
|
63
80
|
[ApiResourceKind.workflow_instance, (c, id) => c.workflowInstance.delete(id)],
|
|
64
|
-
[
|
|
81
|
+
[
|
|
82
|
+
ApiResourceKind.mcp_server,
|
|
83
|
+
(c, id, force) => c.mcpServer.delete({ resourceId: id, force }),
|
|
84
|
+
],
|
|
65
85
|
[ApiResourceKind.project, (c, id) => c.project.delete(id)],
|
|
66
|
-
[
|
|
86
|
+
[
|
|
87
|
+
ApiResourceKind.environment,
|
|
88
|
+
(c, id, force) => c.environment.delete({ resourceId: id, force }),
|
|
89
|
+
],
|
|
67
90
|
[ApiResourceKind.agent_channel, (c, id) => c.agentChannel.delete(id)],
|
|
68
|
-
[
|
|
91
|
+
[
|
|
92
|
+
ApiResourceKind.channel_app,
|
|
93
|
+
(c, id, force) => c.channelapp.delete({ resourceId: id, force }),
|
|
94
|
+
],
|
|
69
95
|
// A schedule delete also tears down its Temporal Schedule artifact
|
|
70
96
|
// server-side; the RPC takes a bare ScheduleId, so force is ignored.
|
|
71
97
|
[ApiResourceKind.schedule, (c, id) => c.schedule.delete(id)],
|
|
72
98
|
[ApiResourceKind.skill, (c, id) => c.skill.delete(id)],
|
|
99
|
+
[ApiResourceKind.plugin, (c, id) => c.plugin.delete(id)],
|
|
73
100
|
[ApiResourceKind.api_key, (c, id) => c.apiKey.delete(id)],
|
|
74
101
|
]);
|
|
75
102
|
|
|
@@ -107,7 +134,9 @@ export async function planDelete(
|
|
|
107
134
|
|
|
108
135
|
const info = defaultRegistry().getByAlias(typeArg);
|
|
109
136
|
if (info === undefined) {
|
|
110
|
-
throw new UsageError(
|
|
137
|
+
throw new UsageError(
|
|
138
|
+
`unknown resource type: ${typeArg}\n\nAvailable types: ${availableDeleteTypes()}`,
|
|
139
|
+
);
|
|
111
140
|
}
|
|
112
141
|
|
|
113
142
|
if (info.kind === ApiResourceKind.organization) {
|
|
@@ -143,25 +172,38 @@ async function planStandardDelete(
|
|
|
143
172
|
const parsed = parseReference(reference, org, info.idPrefix);
|
|
144
173
|
// ResourceResult.message is the opaque proto Message; every resource carries
|
|
145
174
|
// the ApiResourceMetadata envelope, so read it through the structural view.
|
|
146
|
-
const resource = (await getter(client, parsed))
|
|
175
|
+
const resource = (await getter(client, parsed))
|
|
176
|
+
.message as unknown as HasMetadata;
|
|
147
177
|
const id = metaOf(resource).id;
|
|
148
178
|
|
|
149
179
|
return {
|
|
150
180
|
warning: buildDeleteWarning(info, resource),
|
|
151
181
|
confirmPrompt: "Proceed with deletion? [y/N]",
|
|
152
|
-
perform: async () =>
|
|
182
|
+
perform: async () =>
|
|
183
|
+
buildDeleteSuccess(info, await deleteFn(client, id, force)),
|
|
153
184
|
};
|
|
154
185
|
}
|
|
155
186
|
|
|
156
|
-
async function planOrganizationDelete(
|
|
187
|
+
async function planOrganizationDelete(
|
|
188
|
+
client: Stigmer,
|
|
189
|
+
reference: string,
|
|
190
|
+
): Promise<DeletePlan> {
|
|
157
191
|
// Organizations are not org-scoped: a bare token is a slug resolved against
|
|
158
192
|
// the caller's memberships (fetchResource handles the id-vs-slug split).
|
|
159
193
|
const parsed = parseReference(reference, "", "org");
|
|
160
|
-
const resource = (
|
|
194
|
+
const resource = (
|
|
195
|
+
await fetchResource(client, ApiResourceKind.organization, parsed)
|
|
196
|
+
).message as unknown as HasMetadata;
|
|
161
197
|
const meta = metaOf(resource);
|
|
162
198
|
|
|
163
|
-
const warning = CommandResult.warning(
|
|
164
|
-
|
|
199
|
+
const warning = CommandResult.warning(
|
|
200
|
+
"You are about to delete the following organization:",
|
|
201
|
+
);
|
|
202
|
+
warning
|
|
203
|
+
.addSection("")
|
|
204
|
+
.field("ID", meta.id)
|
|
205
|
+
.field("Name", meta.name)
|
|
206
|
+
.field("Slug", meta.slug);
|
|
165
207
|
warning.hint("This will delete the organization and all its resources.");
|
|
166
208
|
warning.hint("This action cannot be undone.");
|
|
167
209
|
|
|
@@ -171,7 +213,11 @@ async function planOrganizationDelete(client: Stigmer, reference: string): Promi
|
|
|
171
213
|
perform: async () => {
|
|
172
214
|
const deleted = metaOf(await client.organization.delete(meta.id));
|
|
173
215
|
const out = CommandResult.success("Organization deleted successfully");
|
|
174
|
-
out
|
|
216
|
+
out
|
|
217
|
+
.addSection("Deleted Organization")
|
|
218
|
+
.field("ID", deleted.id)
|
|
219
|
+
.field("Name", deleted.name)
|
|
220
|
+
.field("Slug", deleted.slug);
|
|
175
221
|
return out;
|
|
176
222
|
},
|
|
177
223
|
};
|
|
@@ -185,16 +231,23 @@ function planExecutionCancel(client: Stigmer, reference: string): DeletePlan {
|
|
|
185
231
|
);
|
|
186
232
|
}
|
|
187
233
|
|
|
188
|
-
const warning = CommandResult.warning(
|
|
234
|
+
const warning = CommandResult.warning(
|
|
235
|
+
`You are about to cancel execution: ${reference}`,
|
|
236
|
+
);
|
|
189
237
|
warning.hint("This will gracefully stop the running agent.");
|
|
190
238
|
|
|
191
239
|
return {
|
|
192
240
|
warning,
|
|
193
241
|
confirmPrompt: "Proceed with cancellation? [y/N]",
|
|
194
242
|
perform: async () => {
|
|
195
|
-
const { execution, wasAlreadyTerminal } = await cancelAgentExecution(
|
|
243
|
+
const { execution, wasAlreadyTerminal } = await cancelAgentExecution(
|
|
244
|
+
client,
|
|
245
|
+
reference,
|
|
246
|
+
);
|
|
196
247
|
const id = execution.metadata?.id ?? "";
|
|
197
|
-
const status = formatAgentPhase(
|
|
248
|
+
const status = formatAgentPhase(
|
|
249
|
+
execution.status?.phase ?? ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED,
|
|
250
|
+
);
|
|
198
251
|
|
|
199
252
|
const out = wasAlreadyTerminal
|
|
200
253
|
? CommandResult.warning("Execution was already in terminal state")
|
|
@@ -207,13 +260,18 @@ function planExecutionCancel(client: Stigmer, reference: string): DeletePlan {
|
|
|
207
260
|
|
|
208
261
|
// --- Warning + success rendering (mirrors Go's per-type delete handlers) ---
|
|
209
262
|
|
|
210
|
-
function buildDeleteWarning(
|
|
263
|
+
function buildDeleteWarning(
|
|
264
|
+
info: TypeInfo,
|
|
265
|
+
message: HasMetadata,
|
|
266
|
+
): CommandResult {
|
|
211
267
|
if (info.kind === ApiResourceKind.api_key) {
|
|
212
268
|
return buildApiKeyWarning(message);
|
|
213
269
|
}
|
|
214
270
|
|
|
215
271
|
const meta = metaOf(message);
|
|
216
|
-
const warning = CommandResult.warning(
|
|
272
|
+
const warning = CommandResult.warning(
|
|
273
|
+
`You are about to delete the following ${info.displayName.toLowerCase()}:`,
|
|
274
|
+
);
|
|
217
275
|
const section = warning
|
|
218
276
|
.addSection("")
|
|
219
277
|
.field("ID", meta.id)
|
|
@@ -227,10 +285,19 @@ function buildDeleteWarning(info: TypeInfo, message: HasMetadata): CommandResult
|
|
|
227
285
|
warning.hint("This will delete the skill and all its versions.");
|
|
228
286
|
}
|
|
229
287
|
|
|
288
|
+
if (info.kind === ApiResourceKind.plugin) {
|
|
289
|
+
warning.hint(
|
|
290
|
+
"This removes the plugin and every skill, MCP server, agent and workflow it installed. " +
|
|
291
|
+
"It is refused while another agent or workflow of yours still references one of them.",
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
230
295
|
if (info.kind === ApiResourceKind.agent_channel) {
|
|
231
296
|
// The server's documented distinction: delete is the connection's full
|
|
232
297
|
// teardown; disabling is the config-preserving pause.
|
|
233
|
-
warning.hint(
|
|
298
|
+
warning.hint(
|
|
299
|
+
"Delete is the connection's full teardown; to pause instead, apply with spec.enabled: false.",
|
|
300
|
+
);
|
|
234
301
|
}
|
|
235
302
|
|
|
236
303
|
warning.hint("This action cannot be undone.");
|
|
@@ -239,20 +306,28 @@ function buildDeleteWarning(info: TypeInfo, message: HasMetadata): CommandResult
|
|
|
239
306
|
|
|
240
307
|
function buildApiKeyWarning(message: HasMetadata): CommandResult {
|
|
241
308
|
const meta = metaOf(message);
|
|
242
|
-
const warning = CommandResult.warning(
|
|
309
|
+
const warning = CommandResult.warning(
|
|
310
|
+
"You are about to delete the following API key:",
|
|
311
|
+
);
|
|
243
312
|
const section = warning.addSection("").field("ID", meta.id);
|
|
244
313
|
if (meta.name) section.field("Name", meta.name);
|
|
245
|
-
const fingerprint = (message as { spec?: { fingerprint?: string } }).spec
|
|
314
|
+
const fingerprint = (message as { spec?: { fingerprint?: string } }).spec
|
|
315
|
+
?.fingerprint;
|
|
246
316
|
if (fingerprint) section.field("Fingerprint", `***${fingerprint}`);
|
|
247
317
|
warning.hint("This will permanently revoke the API key.");
|
|
248
318
|
warning.hint("This action cannot be undone.");
|
|
249
319
|
return warning;
|
|
250
320
|
}
|
|
251
321
|
|
|
252
|
-
function buildDeleteSuccess(
|
|
322
|
+
function buildDeleteSuccess(
|
|
323
|
+
info: TypeInfo,
|
|
324
|
+
deleted: HasMetadata,
|
|
325
|
+
): CommandResult {
|
|
253
326
|
const meta = metaOf(deleted);
|
|
254
327
|
const out = CommandResult.success(`${info.displayName} deleted successfully`);
|
|
255
|
-
const section = out
|
|
328
|
+
const section = out
|
|
329
|
+
.addSection(`Deleted ${info.displayName}`)
|
|
330
|
+
.field("ID", meta.id);
|
|
256
331
|
|
|
257
332
|
if (info.kind === ApiResourceKind.api_key) {
|
|
258
333
|
if (meta.name) section.field("Name", meta.name);
|
|
@@ -263,7 +338,12 @@ function buildDeleteSuccess(info: TypeInfo, deleted: HasMetadata): CommandResult
|
|
|
263
338
|
return out;
|
|
264
339
|
}
|
|
265
340
|
|
|
266
|
-
function metaOf(message: HasMetadata): {
|
|
341
|
+
function metaOf(message: HasMetadata): {
|
|
342
|
+
id: string;
|
|
343
|
+
name: string;
|
|
344
|
+
slug: string;
|
|
345
|
+
org: string;
|
|
346
|
+
} {
|
|
267
347
|
return {
|
|
268
348
|
id: message.metadata?.id ?? "",
|
|
269
349
|
name: message.metadata?.name ?? "",
|