@stigmer/cli 3.5.3 → 3.7.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/agent-exec-flags.d.ts +1 -0
- package/commands/agent-exec-flags.d.ts.map +1 -1
- package/commands/agent-exec-flags.js +3 -1
- package/commands/agent-exec-flags.js.map +1 -1
- package/commands/list.js +8 -5
- package/commands/list.js.map +1 -1
- package/commands/mcp-server.js +1 -1
- package/commands/resume.js +5 -1
- package/commands/resume.js.map +1 -1
- package/commands/schedule.d.ts +3 -0
- package/commands/schedule.d.ts.map +1 -0
- package/commands/schedule.js +57 -0
- package/commands/schedule.js.map +1 -0
- package/package.json +5 -5
- package/program.d.ts.map +1 -1
- package/program.js +2 -0
- package/program.js.map +1 -1
- package/registry/metadata.d.ts.map +1 -1
- package/registry/metadata.js +2 -0
- package/registry/metadata.js.map +1 -1
- package/registry/verb-support.d.ts.map +1 -1
- package/registry/verb-support.js +29 -21
- package/registry/verb-support.js.map +1 -1
- package/registry/verbs.d.ts.map +1 -1
- package/registry/verbs.js +0 -1
- package/registry/verbs.js.map +1 -1
- package/resources/apply/apply.d.ts +6 -1
- package/resources/apply/apply.d.ts.map +1 -1
- package/resources/apply/apply.js +14 -24
- package/resources/apply/apply.js.map +1 -1
- package/resources/apply/handlers.d.ts +9 -1
- package/resources/apply/handlers.d.ts.map +1 -1
- package/resources/apply/handlers.js +50 -148
- package/resources/apply/handlers.js.map +1 -1
- package/resources/delete.d.ts +12 -0
- package/resources/delete.d.ts.map +1 -1
- package/resources/delete.js +8 -4
- package/resources/delete.js.map +1 -1
- package/resources/execution.d.ts +14 -4
- package/resources/execution.d.ts.map +1 -1
- package/resources/execution.js +16 -6
- package/resources/execution.js.map +1 -1
- package/resources/get-bindings.d.ts +1 -0
- package/resources/get-bindings.d.ts.map +1 -1
- package/resources/get-bindings.js +8 -1
- package/resources/get-bindings.js.map +1 -1
- package/resources/list.d.ts +4 -0
- package/resources/list.d.ts.map +1 -1
- package/resources/list.js +95 -27
- package/resources/list.js.map +1 -1
- package/resources/reference.d.ts +2 -0
- package/resources/reference.d.ts.map +1 -1
- package/resources/reference.js +4 -0
- package/resources/reference.js.map +1 -1
- package/resources/run/agent-exec.d.ts.map +1 -1
- package/resources/run/agent-exec.js +1 -0
- package/resources/run/agent-exec.js.map +1 -1
- package/resources/run/create.d.ts +2 -1
- package/resources/run/create.d.ts.map +1 -1
- package/resources/run/create.js +12 -6
- package/resources/run/create.js.map +1 -1
- package/resources/run/prepare.d.ts +15 -0
- package/resources/run/prepare.d.ts.map +1 -1
- package/resources/run/prepare.js +13 -0
- package/resources/run/prepare.js.map +1 -1
- package/resources/schedule.d.ts +22 -0
- package/resources/schedule.d.ts.map +1 -0
- package/resources/schedule.js +114 -0
- package/resources/schedule.js.map +1 -0
- package/src/commands/agent-exec-flags.ts +5 -2
- package/src/commands/list.ts +8 -5
- package/src/commands/mcp-server.ts +1 -1
- package/src/commands/resume.ts +8 -1
- package/src/commands/schedule.ts +70 -0
- package/src/program.ts +2 -0
- package/src/registry/metadata.ts +2 -0
- package/src/registry/registry.test.ts +113 -8
- package/src/registry/verb-support.ts +29 -21
- package/src/registry/verbs.ts +0 -1
- package/src/resources/apply/apply.integration.test.ts +132 -0
- package/src/resources/apply/apply.ts +16 -25
- package/src/resources/apply/handlers.ts +60 -150
- package/src/resources/delete.ts +8 -4
- package/src/resources/execution.ts +20 -6
- package/src/resources/get-bindings.ts +8 -1
- package/src/resources/list.ts +107 -31
- package/src/resources/mutate-verbs.integration.test.ts +78 -4
- package/src/resources/read-verbs.integration.test.ts +124 -5
- package/src/resources/reference.ts +5 -0
- package/src/resources/run/agent-exec.test.ts +1 -0
- package/src/resources/run/agent-exec.ts +1 -0
- package/src/resources/run/create.test.ts +44 -1
- package/src/resources/run/create.ts +16 -7
- package/src/resources/run/prepare.test.ts +14 -1
- package/src/resources/run/prepare.ts +25 -0
- package/src/resources/schedule.ts +132 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedule.js","sourceRoot":"","sources":["../../src/resources/schedule.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// execution flag is added here once and both surfaces pick it up.
|
|
4
4
|
|
|
5
5
|
import type { Command } from "commander";
|
|
6
|
-
import type { AgentExecFlags, RunMode } from "../resources/run/prepare.js";
|
|
6
|
+
import type { AgentExecFlags, RunMode, ServiceTierFlag } from "../resources/run/prepare.js";
|
|
7
7
|
|
|
8
8
|
/** Commander collector for repeatable string options. */
|
|
9
9
|
export const collect = (value: string, previous: string[]): string[] => [...previous, value];
|
|
@@ -25,6 +25,7 @@ export interface AgentExecOptions {
|
|
|
25
25
|
model?: string;
|
|
26
26
|
autoApprove?: boolean;
|
|
27
27
|
mode?: string;
|
|
28
|
+
serviceTier?: string;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
/**
|
|
@@ -50,7 +51,8 @@ export function addAgentExecFlags(command: Command, requireMessage = false): Com
|
|
|
50
51
|
.option("--secret-file <path>", "load secrets from file (repeatable, encrypted)", collect, [])
|
|
51
52
|
.option("--model <model>", "LLM model to use (e.g. claude-sonnet-4-6)")
|
|
52
53
|
.option("--auto-approve", "automatically approve all tool executions")
|
|
53
|
-
.option("--mode <mode>", 'interaction mode: "agent" (default) or "plan" (read-only)')
|
|
54
|
+
.option("--mode <mode>", 'interaction mode: "agent" (default) or "plan" (read-only)')
|
|
55
|
+
.option("--service-tier <tier>", 'model service tier: "standard" (default) or "fast" (requires --model naming a model with a fast tier; billed at fast rates)');
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
/** Map parsed commander options onto the shared {@link AgentExecFlags} shape. */
|
|
@@ -71,5 +73,6 @@ export function toAgentExecFlags(options: AgentExecOptions): AgentExecFlags {
|
|
|
71
73
|
model: options.model ?? "",
|
|
72
74
|
autoApprove: options.autoApprove === true,
|
|
73
75
|
mode: (options.mode ?? "") as RunMode,
|
|
76
|
+
serviceTier: (options.serviceTier ?? "") as ServiceTierFlag,
|
|
74
77
|
};
|
|
75
78
|
}
|
package/src/commands/list.ts
CHANGED
|
@@ -38,7 +38,7 @@ async function runList(type: string, options: ListFlags, command: Command): Prom
|
|
|
38
38
|
const { isExecutionAlias } = await import("../resources/execution.js");
|
|
39
39
|
const { isSessionAlias } = await import("../resources/session.js");
|
|
40
40
|
if (isExecutionAlias(type)) {
|
|
41
|
-
await runListExecutions(options);
|
|
41
|
+
await runListExecutions(options, command);
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
44
|
if (isSessionAlias(type)) {
|
|
@@ -67,8 +67,10 @@ async function runList(type: string, options: ListFlags, command: Command): Prom
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// Executions bypass the registry: they're listed by their own controllers and
|
|
70
|
-
// optionally filtered to agent (default) or workflow by `--type`.
|
|
71
|
-
|
|
70
|
+
// optionally filtered to agent (default) or workflow by `--type`. Results are
|
|
71
|
+
// scoped to the resolved org context (--org flag, env, or configured context);
|
|
72
|
+
// an unset cloud context resolves to "" = permission-bounded across orgs.
|
|
73
|
+
async function runListExecutions(options: ListFlags, command: Command): Promise<void> {
|
|
72
74
|
const [{ connectBackend }, execution] = await Promise.all([
|
|
73
75
|
import("../backend.js"),
|
|
74
76
|
import("../resources/execution.js"),
|
|
@@ -76,19 +78,20 @@ async function runListExecutions(options: ListFlags): Promise<void> {
|
|
|
76
78
|
|
|
77
79
|
const client = connectBackend();
|
|
78
80
|
ensureAuthenticated(client.config);
|
|
81
|
+
const org = resolveOrganization(client.config, globalOrg(command));
|
|
79
82
|
const limit = parseLimit(options.limit);
|
|
80
83
|
const format = readFormat(options);
|
|
81
84
|
|
|
82
85
|
const filter = (options.type ?? "").trim().toLowerCase();
|
|
83
86
|
if (filter === "workflow" || filter === "wf") {
|
|
84
|
-
const result = await execution.listWorkflowExecutions(client.stigmer, limit);
|
|
87
|
+
const result = await execution.listWorkflowExecutions(client.stigmer, limit, org);
|
|
85
88
|
process.stdout.write(execution.renderExecutionList(result, format, "workflow"));
|
|
86
89
|
return;
|
|
87
90
|
}
|
|
88
91
|
if (filter !== "" && filter !== "agent") {
|
|
89
92
|
throw new UsageError(`unknown execution type filter: ${options.type}\n\nValid values: agent, workflow`);
|
|
90
93
|
}
|
|
91
|
-
const result = await execution.listAgentExecutions(client.stigmer, limit);
|
|
94
|
+
const result = await execution.listAgentExecutions(client.stigmer, limit, org);
|
|
92
95
|
process.stdout.write(execution.renderExecutionList(result, format, "agent"));
|
|
93
96
|
}
|
|
94
97
|
|
|
@@ -35,7 +35,7 @@ Environment variables:
|
|
|
35
35
|
STIGMER_SERVER_ADDRESS gRPC address (default: CLI config, else localhost:7234)
|
|
36
36
|
STIGMER_API_KEY API key (auto-resolved from CLI config for cloud)
|
|
37
37
|
STIGMER_MCP_TRANSPORT stdio | http | both (default: stdio)
|
|
38
|
-
STIGMER_MCP_ROSTER full | records (default: full; records = the five record tools only)
|
|
38
|
+
STIGMER_MCP_ROSTER full | records | channels (default: full; records = the five record tools only, channels = send_channel_message only)
|
|
39
39
|
STIGMER_MCP_HTTP_PORT HTTP listen port (default: 8080)
|
|
40
40
|
STIGMER_MCP_LOG_FORMAT text | json (default: text)
|
|
41
41
|
STIGMER_MCP_LOG_LEVEL debug | info | warn | error (default: info)
|
package/src/commands/resume.ts
CHANGED
|
@@ -55,7 +55,7 @@ async function runResume(reference: string | undefined, options: ResumeFlags, co
|
|
|
55
55
|
throw browseUnavailableError();
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
const { isSessionId, isAgentId, isWorkflowId, hasResourceIdPrefix, validateResourceId } = await import(
|
|
58
|
+
const { isSessionId, isAgentId, isWorkflowId, isScheduleId, hasResourceIdPrefix, validateResourceId } = await import(
|
|
59
59
|
"../resources/reference.js"
|
|
60
60
|
);
|
|
61
61
|
|
|
@@ -64,6 +64,13 @@ async function runResume(reference: string | undefined, options: ResumeFlags, co
|
|
|
64
64
|
throw new UsageError(`Resource IDs like "${reference}" are not sessions\n\nTo run a resource:\n stigmer run ${reference}`);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
// Schedule IDs belong to `schedule resume` (clearing a platform pause).
|
|
68
|
+
if (isScheduleId(reference)) {
|
|
69
|
+
throw new UsageError(
|
|
70
|
+
`Resource IDs like "${reference}" are not sessions\n\nTo resume a paused schedule:\n stigmer schedule resume ${reference}`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
67
74
|
if (isSessionId(reference)) {
|
|
68
75
|
if (validateResourceId(reference) !== null) {
|
|
69
76
|
throw new UsageError(
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// `stigmer schedule resume <ref>` — clear a platform auto-pause from a
|
|
2
|
+
// schedule. `stigmer schedule trigger <ref>` — fire it once, immediately.
|
|
3
|
+
//
|
|
4
|
+
// The platform pauses a schedule after repeated failed runs and records
|
|
5
|
+
// why in status.paused_reason; resume is the owner's explicit act that
|
|
6
|
+
// clears it (the owner's enabled switch is a different lever — see
|
|
7
|
+
// docs/vocabulary.md, "Disabled vs. paused"). Trigger fires the schedule
|
|
8
|
+
// through its own clock (DD-014), so everything a cron fire does applies
|
|
9
|
+
// — including feeding the failure streak. Declarative verbs stay in the
|
|
10
|
+
// generic matrix (apply/get/list/delete); this group exists for the
|
|
11
|
+
// kind-specific operational actions, the `stigmer share` shape.
|
|
12
|
+
//
|
|
13
|
+
// Thin handler: resolve credentials/org, delegate to resources/schedule,
|
|
14
|
+
// render the result. Heavy modules are lazy-imported so `--help` stays
|
|
15
|
+
// fast (DD-001).
|
|
16
|
+
|
|
17
|
+
import type { Command } from "commander";
|
|
18
|
+
import { ensureAuthenticated, resolveOrganization } from "../config/index.js";
|
|
19
|
+
import type { OutputFlags } from "../output/index.js";
|
|
20
|
+
import { addResultFlags, globalOrg, resultFormat } from "./shared.js";
|
|
21
|
+
|
|
22
|
+
export function registerSchedule(program: Command): void {
|
|
23
|
+
const schedule = program.command("schedule").description("operate on schedules");
|
|
24
|
+
|
|
25
|
+
const resume = schedule
|
|
26
|
+
.command("resume <ref>")
|
|
27
|
+
.description("clear a platform pause so a schedule fires again (id, org/slug, or slug)")
|
|
28
|
+
.action((ref: string, options: OutputFlags, command: Command) => runScheduleResume(ref, options, command));
|
|
29
|
+
addResultFlags(resume);
|
|
30
|
+
|
|
31
|
+
const trigger = schedule
|
|
32
|
+
.command("trigger <ref>")
|
|
33
|
+
.description("fire a schedule once, immediately (id, org/slug, or slug)")
|
|
34
|
+
.action((ref: string, options: OutputFlags, command: Command) => runScheduleTrigger(ref, options, command));
|
|
35
|
+
addResultFlags(trigger);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function runScheduleTrigger(ref: string, options: OutputFlags, command: Command): Promise<void> {
|
|
39
|
+
const format = resultFormat(options);
|
|
40
|
+
|
|
41
|
+
const [{ connectBackend }, { triggerSchedule }, { renderResult }] = await Promise.all([
|
|
42
|
+
import("../backend.js"),
|
|
43
|
+
import("../resources/schedule.js"),
|
|
44
|
+
import("../output/command-result.js"),
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
const client = connectBackend();
|
|
48
|
+
ensureAuthenticated(client.config);
|
|
49
|
+
const org = resolveOrganization(client.config, globalOrg(command));
|
|
50
|
+
|
|
51
|
+
const result = await triggerSchedule(client.stigmer, ref, org);
|
|
52
|
+
renderResult(result, format);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function runScheduleResume(ref: string, options: OutputFlags, command: Command): Promise<void> {
|
|
56
|
+
const format = resultFormat(options);
|
|
57
|
+
|
|
58
|
+
const [{ connectBackend }, { resumeSchedule }, { renderResult }] = await Promise.all([
|
|
59
|
+
import("../backend.js"),
|
|
60
|
+
import("../resources/schedule.js"),
|
|
61
|
+
import("../output/command-result.js"),
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
const client = connectBackend();
|
|
65
|
+
ensureAuthenticated(client.config);
|
|
66
|
+
const org = resolveOrganization(client.config, globalOrg(command));
|
|
67
|
+
|
|
68
|
+
const result = await resumeSchedule(client.stigmer, ref, org);
|
|
69
|
+
renderResult(result, format);
|
|
70
|
+
}
|
package/src/program.ts
CHANGED
|
@@ -28,6 +28,7 @@ import { registerPush } from "./commands/push.js";
|
|
|
28
28
|
import { registerReset } from "./commands/reset.js";
|
|
29
29
|
import { registerResume } from "./commands/resume.js";
|
|
30
30
|
import { registerRun } from "./commands/run.js";
|
|
31
|
+
import { registerSchedule } from "./commands/schedule.js";
|
|
31
32
|
import { registerSearch } from "./commands/search.js";
|
|
32
33
|
import { registerSeedpack } from "./commands/seedpack.js";
|
|
33
34
|
import { registerSetup } from "./commands/setup.js";
|
|
@@ -78,6 +79,7 @@ export function buildProgram(): Command {
|
|
|
78
79
|
registerDelete(program);
|
|
79
80
|
registerTag(program);
|
|
80
81
|
registerShare(program);
|
|
82
|
+
registerSchedule(program);
|
|
81
83
|
registerDiff(program);
|
|
82
84
|
registerUsage(program);
|
|
83
85
|
registerPush(program);
|
package/src/registry/metadata.ts
CHANGED
|
@@ -36,6 +36,7 @@ export const KIND_META: ReadonlyMap<ApiResourceKind, KindMeta> = new Map([
|
|
|
36
36
|
[ApiResourceKind.agent_channel, { name: "AgentChannel", displayName: "Agent Channel", idPrefix: "ach" }],
|
|
37
37
|
[ApiResourceKind.channel_app, { name: "ChannelApp", displayName: "Channel App", idPrefix: "chapp" }],
|
|
38
38
|
[ApiResourceKind.datastore, { name: "Datastore", displayName: "Datastore", idPrefix: "dst" }],
|
|
39
|
+
[ApiResourceKind.schedule, { name: "Schedule", displayName: "Schedule", idPrefix: "sch" }],
|
|
39
40
|
[ApiResourceKind.agent_instance, { name: "AgentInstance", displayName: "Agent Instance", idPrefix: "ain" }],
|
|
40
41
|
[ApiResourceKind.workflow_instance, { name: "WorkflowInstance", displayName: "Workflow Instance", idPrefix: "win" }],
|
|
41
42
|
[ApiResourceKind.session, { name: "Session", displayName: "Session", idPrefix: "ses" }],
|
|
@@ -62,6 +63,7 @@ export const CLI_RELEVANT_KINDS: readonly ApiResourceKind[] = [
|
|
|
62
63
|
ApiResourceKind.agent_channel,
|
|
63
64
|
ApiResourceKind.channel_app,
|
|
64
65
|
ApiResourceKind.datastore,
|
|
66
|
+
ApiResourceKind.schedule,
|
|
65
67
|
ApiResourceKind.agent_instance,
|
|
66
68
|
ApiResourceKind.workflow_instance,
|
|
67
69
|
ApiResourceKind.session,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
3
|
import { APPLY_HANDLERS } from "../resources/apply/handlers.js";
|
|
4
|
+
import { DELETE_HANDLERS } from "../resources/delete.js";
|
|
5
|
+
import { GET_BINDINGS } from "../resources/get-bindings.js";
|
|
6
|
+
import { LIST_HANDLERS, SEARCH_KINDS } from "../resources/list.js";
|
|
4
7
|
import { defaultRegistry } from "./registry.js";
|
|
5
8
|
import { Verb } from "./verbs.js";
|
|
6
9
|
|
|
@@ -72,7 +75,7 @@ describe("registry — verb support matrix", () => {
|
|
|
72
75
|
expect(registry.supportsVerb(ApiResourceKind.workflow_instance, Verb.List)).toBe(false);
|
|
73
76
|
});
|
|
74
77
|
|
|
75
|
-
it("agent_channel supports the declarative
|
|
78
|
+
it("agent_channel supports the full declarative verb set", () => {
|
|
76
79
|
for (const v of [Verb.Apply, Verb.Get, Verb.List, Verb.Delete]) {
|
|
77
80
|
expect(registry.supportsVerb(ApiResourceKind.agent_channel, v)).toBe(true);
|
|
78
81
|
}
|
|
@@ -81,6 +84,23 @@ describe("registry — verb support matrix", () => {
|
|
|
81
84
|
expect(registry.supportsVerb(ApiResourceKind.agent_channel, Verb.Search)).toBe(false);
|
|
82
85
|
});
|
|
83
86
|
|
|
87
|
+
it("schedule supports the full declarative verb set", () => {
|
|
88
|
+
for (const v of [Verb.Apply, Verb.Get, Verb.List, Verb.Delete]) {
|
|
89
|
+
expect(registry.supportsVerb(ApiResourceKind.schedule, v)).toBe(true);
|
|
90
|
+
}
|
|
91
|
+
// Firing rides `stigmer schedule trigger`, never the generic run verb.
|
|
92
|
+
expect(registry.supportsVerb(ApiResourceKind.schedule, Verb.Run)).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("session and agent_share promise only apply (narrowed — stigmer/stigmer#354)", () => {
|
|
96
|
+
for (const kind of [ApiResourceKind.session, ApiResourceKind.agent_share]) {
|
|
97
|
+
expect(registry.supportsVerb(kind, Verb.Apply)).toBe(true);
|
|
98
|
+
for (const v of [Verb.Get, Verb.List, Verb.Delete]) {
|
|
99
|
+
expect(registry.supportsVerb(kind, v)).toBe(false);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
84
104
|
it("typesForVerb(get) includes the core read kinds", () => {
|
|
85
105
|
const kinds = new Set(registry.typesForVerb(Verb.Get).map((t) => t.kind));
|
|
86
106
|
expect(kinds).toContain(ApiResourceKind.agent);
|
|
@@ -98,13 +118,98 @@ describe("registry — completeness", () => {
|
|
|
98
118
|
}
|
|
99
119
|
});
|
|
100
120
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// The verb matrix is a PROMISE (`list types` prints it; the command layer
|
|
124
|
+
// gates on it), but each verb actually runs through its own dispatch
|
|
125
|
+
// registry. A pair present in the matrix but absent from its registry fails
|
|
126
|
+
// at the point of use with "not implemented" — the stigmer/stigmer#353 bug
|
|
127
|
+
// class. This suite holds the two to strict bidirectional equality so the
|
|
128
|
+
// drift is a red test instead of a runtime surprise. Verbs deliberately
|
|
129
|
+
// withheld are narrowed out of the matrix (recorded in stigmer/stigmer#354),
|
|
130
|
+
// never exempted here — an exemption category would legitimize exactly the
|
|
131
|
+
// state this suite exists to reject.
|
|
132
|
+
describe("registry — verb/dispatch conformance", () => {
|
|
133
|
+
// Special cases are kind/verb pairs that genuinely work through bespoke
|
|
134
|
+
// code instead of a registry entry; each names its dispatch site.
|
|
135
|
+
const DISPATCH: ReadonlyArray<{
|
|
136
|
+
label: string;
|
|
137
|
+
verb: Verb;
|
|
138
|
+
wired: ReadonlySet<ApiResourceKind>;
|
|
139
|
+
specialCases: ReadonlyMap<ApiResourceKind, string>;
|
|
140
|
+
}> = [
|
|
141
|
+
{
|
|
142
|
+
label: "apply",
|
|
143
|
+
verb: Verb.Apply,
|
|
144
|
+
wired: new Set(APPLY_HANDLERS.keys()),
|
|
145
|
+
specialCases: new Map([
|
|
146
|
+
[
|
|
147
|
+
ApiResourceKind.project,
|
|
148
|
+
"the stigmer.yaml declarative/synthesis track (resources/apply/declarative.ts); " +
|
|
149
|
+
"file mode refuses with a pointer there (resolveHandlerForKind)",
|
|
150
|
+
],
|
|
151
|
+
]),
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
label: "get",
|
|
155
|
+
verb: Verb.Get,
|
|
156
|
+
wired: new Set(GET_BINDINGS.keys()),
|
|
157
|
+
specialCases: new Map([
|
|
158
|
+
[
|
|
159
|
+
ApiResourceKind.organization,
|
|
160
|
+
"fetchOrganization in resources/get.ts (not org-scoped; a slug resolves via findMyOrganizations)",
|
|
161
|
+
],
|
|
162
|
+
]),
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
label: "list",
|
|
166
|
+
verb: Verb.List,
|
|
167
|
+
wired: new Set([...LIST_HANDLERS.keys(), ...SEARCH_KINDS]),
|
|
168
|
+
specialCases: new Map(),
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
label: "delete",
|
|
172
|
+
verb: Verb.Delete,
|
|
173
|
+
wired: new Set(DELETE_HANDLERS.keys()),
|
|
174
|
+
specialCases: new Map([
|
|
175
|
+
[
|
|
176
|
+
ApiResourceKind.organization,
|
|
177
|
+
"planOrganizationDelete in resources/delete.ts (not org-scoped; resolved via memberships)",
|
|
178
|
+
],
|
|
179
|
+
]),
|
|
180
|
+
},
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
it.each(DISPATCH)("every kind promising '$label' has a dispatch entry", ({ verb, wired, specialCases }) => {
|
|
184
|
+
for (const info of registry.all()) {
|
|
185
|
+
if (!info.supportedVerbs.has(verb)) continue;
|
|
186
|
+
if (specialCases.has(info.kind)) continue;
|
|
187
|
+
expect(
|
|
188
|
+
wired.has(info.kind),
|
|
189
|
+
`${info.name} promises '${verb}' in the verb matrix but has no dispatch entry — ` +
|
|
190
|
+
"wire it or narrow the matrix (stigmer/stigmer#353, #354)",
|
|
191
|
+
).toBe(true);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it.each(DISPATCH)("every '$label' dispatch entry is promised in the matrix", ({ verb, wired }) => {
|
|
196
|
+
for (const kind of wired) {
|
|
197
|
+
expect(
|
|
198
|
+
registry.supportsVerb(kind, verb),
|
|
199
|
+
`${ApiResourceKind[kind]} is wired for '${verb}' but the verb matrix does not promise it — ` +
|
|
200
|
+
"dead dispatch the command-layer gate blocks",
|
|
201
|
+
).toBe(true);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("special cases do not shadow a real dispatch entry", () => {
|
|
206
|
+
for (const { label, wired, specialCases } of DISPATCH) {
|
|
207
|
+
for (const kind of specialCases.keys()) {
|
|
208
|
+
expect(
|
|
209
|
+
wired.has(kind),
|
|
210
|
+
`${ApiResourceKind[kind]} '${label}' is documented as a special case yet also has a registry entry — drop one`,
|
|
211
|
+
).toBe(false);
|
|
212
|
+
}
|
|
108
213
|
}
|
|
109
214
|
});
|
|
110
215
|
});
|
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
// Verb-support matrix — which verbs each resource kind supports.
|
|
2
2
|
//
|
|
3
|
-
// This is CLI-specific policy (not stored in proto),
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// | McpServer | Y | Y | Y | Y | Y | - | - | - | - |
|
|
13
|
-
// | Project | Y* | Y | Y | Y | Y | - | - | - | - |
|
|
14
|
-
// | Execution | - | - | Y | Y | Y** | - | - | - | Y |
|
|
15
|
-
//
|
|
16
|
-
// * Project "apply" triggers SDK synthesis mode.
|
|
17
|
-
// ** Execution "delete" maps to a cancel operation.
|
|
3
|
+
// This is CLI-specific policy (not stored in proto), and it is a PROMISE:
|
|
4
|
+
// `stigmer list types` prints it, the docs type tables are written from it,
|
|
5
|
+
// and the command layer gates on it before dispatch. The conformance test in
|
|
6
|
+
// registry.test.ts holds every promised apply/get/list/delete verb to an
|
|
7
|
+
// actual dispatch entry — and every dispatch entry back to a promise — so a
|
|
8
|
+
// verb listed here either works or fails CI. "Advertised but not
|
|
9
|
+
// implemented" (stigmer/stigmer#353) is a rejected state, not a backlog;
|
|
10
|
+
// verbs deliberately withheld pending a real ops story are recorded in
|
|
11
|
+
// stigmer/stigmer#354, not here.
|
|
18
12
|
|
|
19
13
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
20
14
|
import { Verb } from "./verbs.js";
|
|
@@ -31,17 +25,24 @@ export const VERB_SUPPORT: ReadonlyMap<ApiResourceKind, ReadonlySet<Verb>> = new
|
|
|
31
25
|
],
|
|
32
26
|
[ApiResourceKind.skill, new Set<Verb>([Verb.Get, Verb.List, Verb.Delete, Verb.Push])],
|
|
33
27
|
[ApiResourceKind.mcp_server, new Set<Verb>([Verb.Apply, Verb.Validate, Verb.Get, Verb.List, Verb.Delete])],
|
|
28
|
+
// Project apply is the stigmer.yaml declarative/synthesis track, not
|
|
29
|
+
// `apply -f project.yaml` — resolveHandlerForKind carries the teaching
|
|
30
|
+
// refusal, and the conformance test documents it as a special case.
|
|
34
31
|
[ApiResourceKind.project, new Set<Verb>([Verb.Apply, Verb.Validate, Verb.Get, Verb.List, Verb.Delete])],
|
|
35
32
|
[ApiResourceKind.api_key, new Set<Verb>([Verb.Get, Verb.List, Verb.Delete])],
|
|
36
33
|
// agent_execution is special — uses dedicated AgentExecutionQueryController
|
|
37
34
|
// RPCs, not the unified SearchService. delete maps to cancel.
|
|
38
35
|
[ApiResourceKind.agent_execution, new Set<Verb>([Verb.Get, Verb.List, Verb.Delete, Verb.Download])],
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
// IAM apps are configured declaratively; read/ops verbs are deliberately
|
|
37
|
+
// not promised (never wired, no demand — stigmer/stigmer#354). To add one:
|
|
38
|
+
// dispatch entry + this line, in the same change.
|
|
39
|
+
[ApiResourceKind.identity_provider, new Set<Verb>([Verb.Apply])],
|
|
40
|
+
[ApiResourceKind.oauth_app, new Set<Verb>([Verb.Apply])],
|
|
41
41
|
[ApiResourceKind.environment, new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete])],
|
|
42
|
-
// Enable/disable flows through `stigmer share agent
|
|
43
|
-
//
|
|
44
|
-
|
|
42
|
+
// Enable/disable flows through `stigmer share agent`, which is the whole
|
|
43
|
+
// read/ops surface today; only the declarative apply path is promised
|
|
44
|
+
// (stigmer/stigmer#354 records the narrowing).
|
|
45
|
+
[ApiResourceKind.agent_share, new Set<Verb>([Verb.Apply])],
|
|
45
46
|
// The provider install flow is console-driven and cloud-only; the generic
|
|
46
47
|
// verbs cover the declarative path (apply a manifest, inspect, tear down).
|
|
47
48
|
[ApiResourceKind.agent_channel, new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete])],
|
|
@@ -52,10 +53,17 @@ export const VERB_SUPPORT: ReadonlyMap<ApiResourceKind, ReadonlySet<Verb>> = new
|
|
|
52
53
|
// generic verbs cover the declarative path (apply a manifest, inspect,
|
|
53
54
|
// tear down — delete is guarded server-side for non-empty datastores).
|
|
54
55
|
[ApiResourceKind.datastore, new Set<Verb>([Verb.Apply, Verb.Validate, Verb.Get, Verb.List, Verb.Delete])],
|
|
56
|
+
// Firing is the platform's job (Temporal Schedules); the generic verbs
|
|
57
|
+
// cover the declarative path, and get/list surface the state the dedicated
|
|
58
|
+
// `stigmer schedule` commands act on — spec.enabled (the owner's switch)
|
|
59
|
+
// versus status.paused_reason (the platform's failure latch).
|
|
60
|
+
[ApiResourceKind.schedule, new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete])],
|
|
55
61
|
[ApiResourceKind.agent_instance, new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete])],
|
|
56
62
|
// No list: proto exposes getByWorkflow (requires workflow_id), not a generic list.
|
|
57
63
|
[ApiResourceKind.workflow_instance, new Set<Verb>([Verb.Apply, Verb.Get, Verb.Delete])],
|
|
58
|
-
|
|
64
|
+
// Sessions are runtime conversation state with no CLI read/ops story yet;
|
|
65
|
+
// get/list/delete are deliberately not promised (stigmer/stigmer#354).
|
|
66
|
+
[ApiResourceKind.session, new Set<Verb>([Verb.Apply])],
|
|
59
67
|
]);
|
|
60
68
|
|
|
61
69
|
export function verbsForKind(kind: ApiResourceKind): ReadonlySet<Verb> {
|
package/src/registry/verbs.ts
CHANGED
|
@@ -17,6 +17,8 @@ import { type ConnectRouter, createClient } from "@connectrpc/connect";
|
|
|
17
17
|
import { connectNodeAdapter } from "@connectrpc/connect-node";
|
|
18
18
|
import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
19
19
|
import { AgentCommandController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/command_pb";
|
|
20
|
+
import type { ChannelApp } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/api_pb";
|
|
21
|
+
import { ChannelAppCommandController } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/command_pb";
|
|
20
22
|
import type { Datastore } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/api_pb";
|
|
21
23
|
import { DatastoreCommandController } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/command_pb";
|
|
22
24
|
import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
@@ -33,11 +35,13 @@ const openSessions = new Set<ServerHttp2Session>();
|
|
|
33
35
|
let appliedAgents: Agent[] = [];
|
|
34
36
|
let appliedMcps: McpServer[] = [];
|
|
35
37
|
let appliedDatastores: Datastore[] = [];
|
|
38
|
+
let appliedChannelApps: ChannelApp[] = [];
|
|
36
39
|
|
|
37
40
|
beforeEach(() => {
|
|
38
41
|
appliedAgents = [];
|
|
39
42
|
appliedMcps = [];
|
|
40
43
|
appliedDatastores = [];
|
|
44
|
+
appliedChannelApps = [];
|
|
41
45
|
});
|
|
42
46
|
|
|
43
47
|
function writeYaml(dir: string, name: string, body: string): string {
|
|
@@ -66,6 +70,12 @@ beforeAll(async () => {
|
|
|
66
70
|
return req;
|
|
67
71
|
},
|
|
68
72
|
});
|
|
73
|
+
router.service(ChannelAppCommandController, {
|
|
74
|
+
apply: (req) => {
|
|
75
|
+
appliedChannelApps.push(req);
|
|
76
|
+
return req;
|
|
77
|
+
},
|
|
78
|
+
});
|
|
69
79
|
};
|
|
70
80
|
|
|
71
81
|
backend = createHttp2Server(connectNodeAdapter({ routes }));
|
|
@@ -215,6 +225,128 @@ describe("file-mode apply — datastore", () => {
|
|
|
215
225
|
});
|
|
216
226
|
});
|
|
217
227
|
|
|
228
|
+
// The stigmer/stigmer#353 regression block: ChannelApp apply used to fail
|
|
229
|
+
// with "apply not implemented for Channel App" because the CLI's handler
|
|
230
|
+
// table was a drifted copy of the SDK manifest registry. The handlers now
|
|
231
|
+
// COME FROM that registry, so this suite pins the whole class: the kind
|
|
232
|
+
// applies, secrets round-trip verbatim, and the ordering the registry
|
|
233
|
+
// encodes (app before the channel that references it) is honored.
|
|
234
|
+
const CHANNEL_APP_YAML = [
|
|
235
|
+
"apiVersion: agentic.stigmer.ai/v1",
|
|
236
|
+
"kind: ChannelApp",
|
|
237
|
+
"metadata:",
|
|
238
|
+
" name: demo-whatsapp",
|
|
239
|
+
" slug: demo-whatsapp",
|
|
240
|
+
"spec:",
|
|
241
|
+
" whatsapp:",
|
|
242
|
+
" app_id: '108954'",
|
|
243
|
+
" app_secret: '***REDACTED***'",
|
|
244
|
+
" access_token: '***REDACTED***'",
|
|
245
|
+
" verify_token: '***REDACTED***'",
|
|
246
|
+
"",
|
|
247
|
+
].join("\n");
|
|
248
|
+
|
|
249
|
+
const AGENT_CHANNEL_YAML = [
|
|
250
|
+
"apiVersion: agentic.stigmer.ai/v1",
|
|
251
|
+
"kind: AgentChannel",
|
|
252
|
+
"metadata:",
|
|
253
|
+
" name: isc-whatsapp",
|
|
254
|
+
" slug: isc-whatsapp",
|
|
255
|
+
"spec:",
|
|
256
|
+
" enabled: true",
|
|
257
|
+
" agent_ref: { kind: 40, org: acme, slug: clinic-assistant }",
|
|
258
|
+
" whatsapp: { phone_number_id: '106540352242922' }",
|
|
259
|
+
"",
|
|
260
|
+
].join("\n");
|
|
261
|
+
|
|
262
|
+
const SCHEDULE_YAML = [
|
|
263
|
+
"apiVersion: agentic.stigmer.ai/v1",
|
|
264
|
+
"kind: Schedule",
|
|
265
|
+
"metadata:",
|
|
266
|
+
" name: daily-fee-reminders",
|
|
267
|
+
" slug: daily-fee-reminders",
|
|
268
|
+
"spec:",
|
|
269
|
+
" cron: '0 9 * * *'",
|
|
270
|
+
" time_zone: Asia/Kolkata",
|
|
271
|
+
" enabled: false",
|
|
272
|
+
" agent:",
|
|
273
|
+
" agent_ref: { kind: 40, org: acme, slug: clinic-assistant }",
|
|
274
|
+
" message: run the reminders",
|
|
275
|
+
"",
|
|
276
|
+
].join("\n");
|
|
277
|
+
|
|
278
|
+
describe("file-mode apply — channel app (#353)", () => {
|
|
279
|
+
it("applies a ChannelApp through the raw controller with the redaction markers intact", async () => {
|
|
280
|
+
const dir = mkdtempSync(join(tmpdir(), "apply-it-"));
|
|
281
|
+
try {
|
|
282
|
+
writeYaml(dir, "chapp.yaml", CHANNEL_APP_YAML);
|
|
283
|
+
const items = resolveApplyItems(dir);
|
|
284
|
+
expect(items).toHaveLength(1);
|
|
285
|
+
|
|
286
|
+
const outcome = await applyItem(controllerFn, items[0], "acme", false);
|
|
287
|
+
expect(outcome.result.message).toBe("Channel App created successfully");
|
|
288
|
+
|
|
289
|
+
expect(appliedChannelApps).toHaveLength(1);
|
|
290
|
+
const spec = appliedChannelApps[0].spec;
|
|
291
|
+
expect(spec?.providerConfig.case).toBe("whatsapp");
|
|
292
|
+
if (spec?.providerConfig.case !== "whatsapp") throw new Error("unreachable");
|
|
293
|
+
expect(spec.providerConfig.value.appId).toBe("108954");
|
|
294
|
+
// The redaction markers must reach the server verbatim — that is the
|
|
295
|
+
// convention by which applying a fetched manifest preserves stored
|
|
296
|
+
// secrets (verb-support's OAuthApp marker note).
|
|
297
|
+
expect(spec.providerConfig.value.appSecret).toBe("***REDACTED***");
|
|
298
|
+
expect(spec.providerConfig.value.accessToken).toBe("***REDACTED***");
|
|
299
|
+
} finally {
|
|
300
|
+
rmSync(dir, { recursive: true, force: true });
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("treats a ChannelApp carrying metadata.id as an update", async () => {
|
|
305
|
+
const dir = mkdtempSync(join(tmpdir(), "apply-it-"));
|
|
306
|
+
try {
|
|
307
|
+
writeYaml(
|
|
308
|
+
dir,
|
|
309
|
+
"chapp.yaml",
|
|
310
|
+
CHANNEL_APP_YAML.replace("metadata:", "metadata:\n id: chapp_existing"),
|
|
311
|
+
);
|
|
312
|
+
const items = resolveApplyItems(dir);
|
|
313
|
+
const outcome = await applyItem(controllerFn, items[0], "acme", false);
|
|
314
|
+
expect(outcome.result.message).toBe("Channel App updated successfully");
|
|
315
|
+
expect(appliedChannelApps[0].metadata?.id).toBe("chapp_existing");
|
|
316
|
+
} finally {
|
|
317
|
+
rmSync(dir, { recursive: true, force: true });
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("orders one bundle app → channel → schedule regardless of file order", () => {
|
|
322
|
+
const dir = mkdtempSync(join(tmpdir(), "apply-it-"));
|
|
323
|
+
try {
|
|
324
|
+
// File names chosen so lexical file order is exactly WRONG — before
|
|
325
|
+
// the registry unification all three kinds fell to a shared default
|
|
326
|
+
// priority and applied in this (broken) file order.
|
|
327
|
+
writeYaml(dir, "a-schedule.yaml", SCHEDULE_YAML);
|
|
328
|
+
writeYaml(dir, "b-channel.yaml", AGENT_CHANNEL_YAML);
|
|
329
|
+
writeYaml(dir, "c-chapp.yaml", CHANNEL_APP_YAML);
|
|
330
|
+
const items = resolveApplyItems(dir);
|
|
331
|
+
expect(items.map((i) => i.handler.displayName)).toEqual(["Channel App", "Agent Channel", "Schedule"]);
|
|
332
|
+
} finally {
|
|
333
|
+
rmSync(dir, { recursive: true, force: true });
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
describe("file-mode apply — project special case", () => {
|
|
339
|
+
it("refuses a Project manifest with a pointer to the stigmer.yaml flow", () => {
|
|
340
|
+
const dir = mkdtempSync(join(tmpdir(), "apply-it-"));
|
|
341
|
+
try {
|
|
342
|
+
writeYaml(dir, "project.yaml", ["kind: Project", "metadata:", " name: Demo", " slug: demo", ""].join("\n"));
|
|
343
|
+
expect(() => resolveApplyItems(dir)).toThrow(/stigmer\.yaml/);
|
|
344
|
+
} finally {
|
|
345
|
+
rmSync(dir, { recursive: true, force: true });
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
218
350
|
describe("file-mode apply — orchestration", () => {
|
|
219
351
|
it("sorts items into dependency order (mcp_server before agent)", () => {
|
|
220
352
|
const dir = mkdtempSync(join(tmpdir(), "apply-it-"));
|