@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.
Files changed (96) hide show
  1. package/commands/agent-exec-flags.d.ts +1 -0
  2. package/commands/agent-exec-flags.d.ts.map +1 -1
  3. package/commands/agent-exec-flags.js +3 -1
  4. package/commands/agent-exec-flags.js.map +1 -1
  5. package/commands/list.js +8 -5
  6. package/commands/list.js.map +1 -1
  7. package/commands/mcp-server.js +1 -1
  8. package/commands/resume.js +5 -1
  9. package/commands/resume.js.map +1 -1
  10. package/commands/schedule.d.ts +3 -0
  11. package/commands/schedule.d.ts.map +1 -0
  12. package/commands/schedule.js +57 -0
  13. package/commands/schedule.js.map +1 -0
  14. package/package.json +5 -5
  15. package/program.d.ts.map +1 -1
  16. package/program.js +2 -0
  17. package/program.js.map +1 -1
  18. package/registry/metadata.d.ts.map +1 -1
  19. package/registry/metadata.js +2 -0
  20. package/registry/metadata.js.map +1 -1
  21. package/registry/verb-support.d.ts.map +1 -1
  22. package/registry/verb-support.js +29 -21
  23. package/registry/verb-support.js.map +1 -1
  24. package/registry/verbs.d.ts.map +1 -1
  25. package/registry/verbs.js +0 -1
  26. package/registry/verbs.js.map +1 -1
  27. package/resources/apply/apply.d.ts +6 -1
  28. package/resources/apply/apply.d.ts.map +1 -1
  29. package/resources/apply/apply.js +14 -24
  30. package/resources/apply/apply.js.map +1 -1
  31. package/resources/apply/handlers.d.ts +9 -1
  32. package/resources/apply/handlers.d.ts.map +1 -1
  33. package/resources/apply/handlers.js +50 -148
  34. package/resources/apply/handlers.js.map +1 -1
  35. package/resources/delete.d.ts +12 -0
  36. package/resources/delete.d.ts.map +1 -1
  37. package/resources/delete.js +8 -4
  38. package/resources/delete.js.map +1 -1
  39. package/resources/execution.d.ts +14 -4
  40. package/resources/execution.d.ts.map +1 -1
  41. package/resources/execution.js +16 -6
  42. package/resources/execution.js.map +1 -1
  43. package/resources/get-bindings.d.ts +1 -0
  44. package/resources/get-bindings.d.ts.map +1 -1
  45. package/resources/get-bindings.js +8 -1
  46. package/resources/get-bindings.js.map +1 -1
  47. package/resources/list.d.ts +4 -0
  48. package/resources/list.d.ts.map +1 -1
  49. package/resources/list.js +95 -27
  50. package/resources/list.js.map +1 -1
  51. package/resources/reference.d.ts +2 -0
  52. package/resources/reference.d.ts.map +1 -1
  53. package/resources/reference.js +4 -0
  54. package/resources/reference.js.map +1 -1
  55. package/resources/run/agent-exec.d.ts.map +1 -1
  56. package/resources/run/agent-exec.js +1 -0
  57. package/resources/run/agent-exec.js.map +1 -1
  58. package/resources/run/create.d.ts +2 -1
  59. package/resources/run/create.d.ts.map +1 -1
  60. package/resources/run/create.js +12 -6
  61. package/resources/run/create.js.map +1 -1
  62. package/resources/run/prepare.d.ts +15 -0
  63. package/resources/run/prepare.d.ts.map +1 -1
  64. package/resources/run/prepare.js +13 -0
  65. package/resources/run/prepare.js.map +1 -1
  66. package/resources/schedule.d.ts +22 -0
  67. package/resources/schedule.d.ts.map +1 -0
  68. package/resources/schedule.js +114 -0
  69. package/resources/schedule.js.map +1 -0
  70. package/src/commands/agent-exec-flags.ts +5 -2
  71. package/src/commands/list.ts +8 -5
  72. package/src/commands/mcp-server.ts +1 -1
  73. package/src/commands/resume.ts +8 -1
  74. package/src/commands/schedule.ts +70 -0
  75. package/src/program.ts +2 -0
  76. package/src/registry/metadata.ts +2 -0
  77. package/src/registry/registry.test.ts +113 -8
  78. package/src/registry/verb-support.ts +29 -21
  79. package/src/registry/verbs.ts +0 -1
  80. package/src/resources/apply/apply.integration.test.ts +132 -0
  81. package/src/resources/apply/apply.ts +16 -25
  82. package/src/resources/apply/handlers.ts +60 -150
  83. package/src/resources/delete.ts +8 -4
  84. package/src/resources/execution.ts +20 -6
  85. package/src/resources/get-bindings.ts +8 -1
  86. package/src/resources/list.ts +107 -31
  87. package/src/resources/mutate-verbs.integration.test.ts +78 -4
  88. package/src/resources/read-verbs.integration.test.ts +124 -5
  89. package/src/resources/reference.ts +5 -0
  90. package/src/resources/run/agent-exec.test.ts +1 -0
  91. package/src/resources/run/agent-exec.ts +1 -0
  92. package/src/resources/run/create.test.ts +44 -1
  93. package/src/resources/run/create.ts +16 -7
  94. package/src/resources/run/prepare.test.ts +14 -1
  95. package/src/resources/run/prepare.ts +25 -0
  96. package/src/resources/schedule.ts +132 -0
@@ -39,26 +39,6 @@ export interface ApplyOutcome {
39
39
  readonly applied?: Message;
40
40
  }
41
41
 
42
- // Dependency apply order (ascending priority); kinds absent get 99 (applied
43
- // last). Mirrors Go's applier.DefaultApplyOrder.
44
- const APPLY_ORDER: ReadonlyMap<ApiResourceKind, number> = new Map([
45
- [ApiResourceKind.organization, 0],
46
- [ApiResourceKind.mcp_server, 1],
47
- // Before agent: an Agent references a Datastore via datastore_usages
48
- // (the McpServer-before-Agent pattern; mirrors the SDK manifest registry).
49
- [ApiResourceKind.datastore, 2],
50
- [ApiResourceKind.agent, 3],
51
- [ApiResourceKind.workflow, 4],
52
- [ApiResourceKind.environment, 5],
53
- [ApiResourceKind.identity_provider, 6],
54
- [ApiResourceKind.oauth_app, 7],
55
- [ApiResourceKind.agent_instance, 8],
56
- [ApiResourceKind.workflow_instance, 9],
57
- [ApiResourceKind.session, 10],
58
- // After agent and environment: a share references both.
59
- [ApiResourceKind.agent_share, 11],
60
- ]);
61
-
62
42
  /** Expand a path into ordered, kind-resolved apply items (strict YAML parse). */
63
43
  export function resolveApplyItems(path: string): ApplyItem[] {
64
44
  const files = resolveYamlFiles(path);
@@ -83,14 +63,28 @@ export function resolveHandlerForKind(kind: string, where: string): ApplyHandler
83
63
  const info = defaultRegistry().getByYamlKind(kind);
84
64
  if (info === undefined) throw new UsageError(`unknown resource kind '${kind}' in ${where}`);
85
65
  if (!info.supportedVerbs.has(Verb.Apply)) throw new UsageError(`${info.displayName} does not support 'apply'`);
66
+ if (info.kind === ApiResourceKind.project) {
67
+ // Project's Apply verb is the stigmer.yaml track, deliberately not a
68
+ // manifest handler: a file-mode apply would create a second, member-less
69
+ // way to apply a project, bypassing the membership reconciler.
70
+ throw new UsageError(
71
+ `a Project is not applied from a resource manifest (${where}): ` +
72
+ "run `stigmer apply` in the directory containing stigmer.yaml and the project is reconciled from its members",
73
+ );
74
+ }
86
75
  const handler = APPLY_HANDLERS.get(info.kind);
87
76
  if (handler === undefined) throw new UsageError(`apply not implemented for ${info.displayName}`);
88
77
  return handler;
89
78
  }
90
79
 
91
- /** Stable sort by dependency priority (Array.prototype.sort is stable in V8). */
80
+ /**
81
+ * Stable sort by dependency priority (Array.prototype.sort is stable in V8).
82
+ * The priority rides each handler (`applyOrder`, sourced from the SDK
83
+ * manifest registry) — there is deliberately no local ordering table to
84
+ * drift from it.
85
+ */
92
86
  export function sortApplyItems(items: ApplyItem[]): ApplyItem[] {
93
- items.sort((a, b) => priorityOf(a.handler.kind) - priorityOf(b.handler.kind));
87
+ items.sort((a, b) => a.handler.applyOrder - b.handler.applyOrder);
94
88
  return items;
95
89
  }
96
90
 
@@ -202,6 +196,3 @@ function buildDryRunResult(handler: ApplyHandler, message: Message): CommandResu
202
196
  return result;
203
197
  }
204
198
 
205
- function priorityOf(kind: ApiResourceKind): number {
206
- return APPLY_ORDER.get(kind) ?? 99;
207
- }
@@ -1,178 +1,88 @@
1
- // Explicit apply-handler registry for `apply -f` (file mode).
1
+ // Apply-handler registry for `apply -f` (file mode) and the synthesis track.
2
2
  //
3
- // Each handler binds an ApiResourceKind to its proto schema (for strict
4
- // YAML→proto marshalling) and the raw command-controller `apply` RPC. We drive
5
- // the *generated controllers* directly — not the high-level SDK `apply(input)`
6
- // methods — because the SDK's `*Input` wrappers are lossy (they drop
7
- // `metadata.id`, so an update would be misrouted as a create). The `apply` RPC
8
- // takes the full resource message, preserving every field round-tripped from
9
- // YAML. This is the keystone decision for Wave 2e.
3
+ // The declarative manifest kinds come from the SDK's manifest registry
4
+ // (`manifestKinds()` in @stigmer/sdk) — the one table binding a kind to its
5
+ // proto schema, its raw command-controller `apply` RPC, and its dependency
6
+ // apply order. The CLI consumes that table instead of keeping a copy: the
7
+ // copy this file used to carry drifted twice (ChannelApp was missing
8
+ // entirely — stigmer/stigmer#353 — and three kinds were absent from the
9
+ // local ordering map, so multi-document bundles applied on file-order luck).
10
10
  //
11
- // Registration is explicit (no init magic), mirroring Go's
12
- // newApplyHandlerRegistry — to add a kind, add a line here.
11
+ // Both registries drive the *generated controllers* directly — not the
12
+ // high-level SDK `apply(input)` methods — because the SDK's `*Input`
13
+ // wrappers are lossy (they drop `metadata.id`, so an update would be
14
+ // misrouted as a create). The `apply` RPC takes the full resource message,
15
+ // preserving every field round-tripped from YAML.
16
+ //
17
+ // CLI_EXTRA_HANDLERS carries the kinds the CLI applies but the SDK's
18
+ // manifest engine deliberately does not treat as manifest kinds:
19
+ // Organization (not org-scoped — the bootstrap resource every other kind
20
+ // lives inside) and the runtime-instance kinds (WorkflowInstance, Session).
21
+ // A kind added here must also declare Apply in the registry's verb matrix —
22
+ // the conformance test in registry/registry.test.ts enforces both
23
+ // directions, so an entry without a matrix line (or vice versa) fails CI.
13
24
 
14
- import type { Client } from "@connectrpc/connect";
15
25
  import type { DescMessage, DescService, Message } from "@bufbuild/protobuf";
16
- import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
17
-
18
- import { type Agent, AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
19
- import { AgentCommandController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/command_pb";
20
- import { type AgentChannel, AgentChannelSchema } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/api_pb";
21
- import { AgentChannelCommandController } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/command_pb";
22
- import { type AgentInstance, AgentInstanceSchema } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/api_pb";
23
- import { AgentInstanceCommandController } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/command_pb";
24
- import { type AgentShare, AgentShareSchema } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/api_pb";
25
- import { AgentShareCommandController } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/command_pb";
26
- import { type Datastore, DatastoreSchema } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/api_pb";
27
- import { DatastoreCommandController } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/command_pb";
28
- import { type Environment, EnvironmentSchema } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/api_pb";
29
- import { EnvironmentCommandController } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/command_pb";
30
- import { type McpServer, McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
31
- import { McpServerCommandController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/command_pb";
26
+ import type { Client } from "@connectrpc/connect";
32
27
  import { type Session, SessionSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
33
28
  import { SessionCommandController } from "@stigmer/protos/ai/stigmer/agentic/session/v1/command_pb";
34
- import { type Workflow, WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
35
- import { WorkflowCommandController } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/command_pb";
36
29
  import {
37
30
  type WorkflowInstance,
38
31
  WorkflowInstanceSchema,
39
32
  } from "@stigmer/protos/ai/stigmer/agentic/workflowinstance/v1/api_pb";
40
33
  import { WorkflowInstanceCommandController } from "@stigmer/protos/ai/stigmer/agentic/workflowinstance/v1/command_pb";
41
- import { type IdentityProvider, IdentityProviderSchema } from "@stigmer/protos/ai/stigmer/iam/identityprovider/v1/api_pb";
42
- import { IdentityProviderCommandController } from "@stigmer/protos/ai/stigmer/iam/identityprovider/v1/command_pb";
43
- import { type OAuthApp, OAuthAppSchema } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
44
- import { OAuthAppCommandController } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/command_pb";
34
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
45
35
  import { type Organization, OrganizationSchema } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
46
36
  import { OrganizationCommandController } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/command_pb";
37
+ import { manifestKinds } from "@stigmer/sdk";
47
38
 
48
39
  /** Accessor for a raw Connect client over a generated service controller. */
49
40
  export type ControllerFn = <Desc extends DescService>(service: Desc) => Client<Desc>;
50
41
 
51
42
  export interface ApplyHandler {
52
43
  readonly kind: ApiResourceKind;
44
+ /** Human-facing name for messages, e.g. "MCP Server". */
53
45
  readonly displayName: string;
54
46
  /** Proto schema for strict YAML→proto marshalling. */
55
47
  readonly schema: DescMessage;
48
+ /**
49
+ * Position in the dependency apply order (ascending) — referenced kinds
50
+ * apply before their dependents. Manifest kinds carry the SDK registry's
51
+ * value; the CLI extras slot around them (organization first, the
52
+ * runtime-instance kinds after every blueprint they can reference).
53
+ */
54
+ readonly applyOrder: number;
56
55
  /** Drive the controller's `apply` RPC with the full resource message. */
57
56
  apply(controller: ControllerFn, message: Message): Promise<Message>;
58
57
  }
59
58
 
59
+ // The SDK registry's applyOrder values end at schedule = 12; the extras slot
60
+ // outside that range so a future SDK kind never collides with them.
61
+ const CLI_EXTRA_HANDLERS: readonly ApplyHandler[] = [
62
+ {
63
+ kind: ApiResourceKind.organization,
64
+ displayName: "Organization",
65
+ schema: OrganizationSchema,
66
+ applyOrder: 0,
67
+ apply: (c, m) => c(OrganizationCommandController).apply(m as Organization),
68
+ },
69
+ {
70
+ kind: ApiResourceKind.workflow_instance,
71
+ displayName: "Workflow Instance",
72
+ schema: WorkflowInstanceSchema,
73
+ applyOrder: 13,
74
+ apply: (c, m) => c(WorkflowInstanceCommandController).apply(m as WorkflowInstance),
75
+ },
76
+ {
77
+ kind: ApiResourceKind.session,
78
+ displayName: "Session",
79
+ schema: SessionSchema,
80
+ applyOrder: 14,
81
+ apply: (c, m) => c(SessionCommandController).apply(m as Session),
82
+ },
83
+ ];
84
+
60
85
  export const APPLY_HANDLERS: ReadonlyMap<ApiResourceKind, ApplyHandler> = new Map<ApiResourceKind, ApplyHandler>([
61
- [
62
- ApiResourceKind.organization,
63
- {
64
- kind: ApiResourceKind.organization,
65
- displayName: "Organization",
66
- schema: OrganizationSchema,
67
- apply: (c, m) => c(OrganizationCommandController).apply(m as Organization),
68
- },
69
- ],
70
- [
71
- ApiResourceKind.mcp_server,
72
- {
73
- kind: ApiResourceKind.mcp_server,
74
- displayName: "MCP Server",
75
- schema: McpServerSchema,
76
- apply: (c, m) => c(McpServerCommandController).apply(m as McpServer),
77
- },
78
- ],
79
- [
80
- ApiResourceKind.datastore,
81
- {
82
- kind: ApiResourceKind.datastore,
83
- displayName: "Datastore",
84
- schema: DatastoreSchema,
85
- apply: (c, m) => c(DatastoreCommandController).apply(m as Datastore),
86
- },
87
- ],
88
- [
89
- ApiResourceKind.agent,
90
- {
91
- kind: ApiResourceKind.agent,
92
- displayName: "Agent",
93
- schema: AgentSchema,
94
- apply: (c, m) => c(AgentCommandController).apply(m as Agent),
95
- },
96
- ],
97
- [
98
- ApiResourceKind.workflow,
99
- {
100
- kind: ApiResourceKind.workflow,
101
- displayName: "Workflow",
102
- schema: WorkflowSchema,
103
- apply: (c, m) => c(WorkflowCommandController).apply(m as Workflow),
104
- },
105
- ],
106
- [
107
- ApiResourceKind.environment,
108
- {
109
- kind: ApiResourceKind.environment,
110
- displayName: "Environment",
111
- schema: EnvironmentSchema,
112
- apply: (c, m) => c(EnvironmentCommandController).apply(m as Environment),
113
- },
114
- ],
115
- [
116
- ApiResourceKind.identity_provider,
117
- {
118
- kind: ApiResourceKind.identity_provider,
119
- displayName: "Identity Provider",
120
- schema: IdentityProviderSchema,
121
- apply: (c, m) => c(IdentityProviderCommandController).apply(m as IdentityProvider),
122
- },
123
- ],
124
- [
125
- ApiResourceKind.oauth_app,
126
- {
127
- kind: ApiResourceKind.oauth_app,
128
- displayName: "OAuth App",
129
- schema: OAuthAppSchema,
130
- apply: (c, m) => c(OAuthAppCommandController).apply(m as OAuthApp),
131
- },
132
- ],
133
- [
134
- ApiResourceKind.agent_share,
135
- {
136
- kind: ApiResourceKind.agent_share,
137
- displayName: "Agent Share",
138
- schema: AgentShareSchema,
139
- apply: (c, m) => c(AgentShareCommandController).apply(m as AgentShare),
140
- },
141
- ],
142
- [
143
- ApiResourceKind.agent_channel,
144
- {
145
- kind: ApiResourceKind.agent_channel,
146
- displayName: "Agent Channel",
147
- schema: AgentChannelSchema,
148
- apply: (c, m) => c(AgentChannelCommandController).apply(m as AgentChannel),
149
- },
150
- ],
151
- [
152
- ApiResourceKind.agent_instance,
153
- {
154
- kind: ApiResourceKind.agent_instance,
155
- displayName: "Agent Instance",
156
- schema: AgentInstanceSchema,
157
- apply: (c, m) => c(AgentInstanceCommandController).apply(m as AgentInstance),
158
- },
159
- ],
160
- [
161
- ApiResourceKind.workflow_instance,
162
- {
163
- kind: ApiResourceKind.workflow_instance,
164
- displayName: "Workflow Instance",
165
- schema: WorkflowInstanceSchema,
166
- apply: (c, m) => c(WorkflowInstanceCommandController).apply(m as WorkflowInstance),
167
- },
168
- ],
169
- [
170
- ApiResourceKind.session,
171
- {
172
- kind: ApiResourceKind.session,
173
- displayName: "Session",
174
- schema: SessionSchema,
175
- apply: (c, m) => c(SessionCommandController).apply(m as Session),
176
- },
177
- ],
86
+ ...manifestKinds().map((handler) => [handler.kind, handler] as const),
87
+ ...CLI_EXTRA_HANDLERS.map((handler) => [handler.kind, handler] as const),
178
88
  ]);
@@ -52,22 +52,26 @@ interface HasMetadata {
52
52
  type DeleteFn = (client: Stigmer, id: string, force: boolean) => Promise<HasMetadata>;
53
53
 
54
54
  // Kinds the unified delete handles directly, each bound to its SDK delete call.
55
- // Kinds that carry a Delete verb but no entry here (identity_provider,
56
- // oauth_app, agent_share, workflow_instance, session) fall through to a
57
- // "not implemented" usage error, matching Go's default branch.
55
+ // Every kind declaring Delete in the verb matrix must have an entry here (or a
56
+ // documented special case) — the conformance test in registry/registry.test.ts
57
+ // enforces it, so the two cannot drift.
58
58
  // Kinds whose delete takes a DeleteResourceInput (not an ID) thread the force
59
59
  // acknowledgment through; today only the datastore server honors it (the
60
60
  // non-empty guard), but force is the contract's generic ack carrier.
61
- const DELETE_HANDLERS: ReadonlyMap<ApiResourceKind, DeleteFn> = new Map<ApiResourceKind, DeleteFn>([
61
+ export const DELETE_HANDLERS: ReadonlyMap<ApiResourceKind, DeleteFn> = new Map<ApiResourceKind, DeleteFn>([
62
62
  [ApiResourceKind.agent, (c, id) => c.agent.delete(id)],
63
63
  [ApiResourceKind.agent_instance, (c, id) => c.agentInstance.delete(id)],
64
64
  [ApiResourceKind.workflow, (c, id) => c.workflow.delete(id)],
65
+ [ApiResourceKind.workflow_instance, (c, id) => c.workflowInstance.delete(id)],
65
66
  [ApiResourceKind.mcp_server, (c, id, force) => c.mcpServer.delete({ resourceId: id, force })],
66
67
  [ApiResourceKind.project, (c, id) => c.project.delete(id)],
67
68
  [ApiResourceKind.datastore, (c, id, force) => c.datastore.delete({ resourceId: id, force })],
68
69
  [ApiResourceKind.environment, (c, id, force) => c.environment.delete({ resourceId: id, force })],
69
70
  [ApiResourceKind.agent_channel, (c, id) => c.agentChannel.delete(id)],
70
71
  [ApiResourceKind.channel_app, (c, id, force) => c.channelapp.delete({ resourceId: id, force })],
72
+ // A schedule delete also tears down its Temporal Schedule artifact
73
+ // server-side; the RPC takes a bare ScheduleId, so force is ignored.
74
+ [ApiResourceKind.schedule, (c, id) => c.schedule.delete(id)],
71
75
  [ApiResourceKind.skill, (c, id) => c.skill.delete(id)],
72
76
  [ApiResourceKind.api_key, (c, id) => c.apiKey.delete(id)],
73
77
  ]);
@@ -124,15 +124,29 @@ export async function getExecution(client: Stigmer, id: string): Promise<Resourc
124
124
  return { schema: WorkflowExecutionSchema, message: await client.workflowExecution.get(id) };
125
125
  }
126
126
 
127
- /** List agent executions for the current context, paginated by `limit`. */
128
- export async function listAgentExecutions(client: Stigmer, limit: number): Promise<ResourceResult> {
129
- const message = await client.agentExecution.list(create(ListAgentExecutionsRequestSchema, { pageSize: limit }));
127
+ /**
128
+ * List agent executions for the current context, paginated by `limit`.
129
+ *
130
+ * `org` scopes results to that organization; empty means permission-bounded
131
+ * (all orgs the caller can view — the OSS single-tenant behavior).
132
+ */
133
+ export async function listAgentExecutions(client: Stigmer, limit: number, org = ""): Promise<ResourceResult> {
134
+ const message = await client.agentExecution.list(
135
+ create(ListAgentExecutionsRequestSchema, { pageSize: limit, org }),
136
+ );
130
137
  return { schema: AgentExecutionListSchema, message };
131
138
  }
132
139
 
133
- /** List workflow executions for the current context, paginated by `limit`. */
134
- export async function listWorkflowExecutions(client: Stigmer, limit: number): Promise<ResourceResult> {
135
- const message = await client.workflowExecution.list(create(ListWorkflowExecutionsRequestSchema, { pageSize: limit }));
140
+ /**
141
+ * List workflow executions for the current context, paginated by `limit`.
142
+ *
143
+ * `org` scopes results to that organization; empty means permission-bounded
144
+ * (all orgs the caller can view — the OSS single-tenant behavior).
145
+ */
146
+ export async function listWorkflowExecutions(client: Stigmer, limit: number, org = ""): Promise<ResourceResult> {
147
+ const message = await client.workflowExecution.list(
148
+ create(ListWorkflowExecutionsRequestSchema, { pageSize: limit, org }),
149
+ );
136
150
  return { schema: WorkflowExecutionListSchema, message };
137
151
  }
138
152
 
@@ -11,8 +11,10 @@ import { ChannelAppSchema } from "@stigmer/protos/ai/stigmer/agentic/channelapp/
11
11
  import { DatastoreSchema } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/api_pb";
12
12
  import { EnvironmentSchema } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/api_pb";
13
13
  import { McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
14
+ import { ScheduleSchema } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/api_pb";
14
15
  import { SkillSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
15
16
  import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
17
+ import { WorkflowInstanceSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowinstance/v1/api_pb";
16
18
  import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
17
19
  import { ApiKeySchema } from "@stigmer/protos/ai/stigmer/iam/apikey/v1/api_pb";
18
20
  import { ProjectSchema } from "@stigmer/protos/ai/stigmer/tenancy/project/v1/api_pb";
@@ -27,16 +29,21 @@ export interface ResourceResult {
27
29
 
28
30
  type Getter = (client: Stigmer, ref: ParsedReference) => Promise<ResourceResult>;
29
31
 
30
- const GET_BINDINGS: ReadonlyMap<ApiResourceKind, Getter> = new Map([
32
+ // Every kind declaring Get in the verb matrix must have an entry here (or a
33
+ // documented special case, e.g. organization) — the conformance test in
34
+ // registry/registry.test.ts enforces it, so the two cannot drift.
35
+ export const GET_BINDINGS: ReadonlyMap<ApiResourceKind, Getter> = new Map([
31
36
  [ApiResourceKind.agent, refGetter(ApiResourceKind.agent, AgentSchema, (c) => c.agent)],
32
37
  [ApiResourceKind.agent_instance, refGetter(ApiResourceKind.agent_instance, AgentInstanceSchema, (c) => c.agentInstance)],
33
38
  [ApiResourceKind.workflow, refGetter(ApiResourceKind.workflow, WorkflowSchema, (c) => c.workflow)],
39
+ [ApiResourceKind.workflow_instance, refGetter(ApiResourceKind.workflow_instance, WorkflowInstanceSchema, (c) => c.workflowInstance)],
34
40
  [ApiResourceKind.mcp_server, refGetter(ApiResourceKind.mcp_server, McpServerSchema, (c) => c.mcpServer)],
35
41
  [ApiResourceKind.project, refGetter(ApiResourceKind.project, ProjectSchema, (c) => c.project)],
36
42
  [ApiResourceKind.datastore, refGetter(ApiResourceKind.datastore, DatastoreSchema, (c) => c.datastore)],
37
43
  [ApiResourceKind.environment, refGetter(ApiResourceKind.environment, EnvironmentSchema, (c) => c.environment)],
38
44
  [ApiResourceKind.agent_channel, refGetter(ApiResourceKind.agent_channel, AgentChannelSchema, (c) => c.agentChannel)],
39
45
  [ApiResourceKind.channel_app, refGetter(ApiResourceKind.channel_app, ChannelAppSchema, (c) => c.channelapp)],
46
+ [ApiResourceKind.schedule, refGetter(ApiResourceKind.schedule, ScheduleSchema, (c) => c.schedule)],
40
47
  [ApiResourceKind.skill, refGetter(ApiResourceKind.skill, SkillSchema, (c) => c.skill)],
41
48
  [ApiResourceKind.api_key, idOnlyGetter(ApiKeySchema, (c) => c.apiKey, "API keys")],
42
49
  ]);
@@ -2,8 +2,11 @@
2
2
  //
3
3
  // Most registry kinds are search-indexed and list through the unified
4
4
  // SearchService. Kinds that are deliberately not search-indexed
5
- // (organizations, API keys, agent instances, agent channels, channel apps)
6
- // use their dedicated find/list RPCs with bespoke table shapes.
5
+ // (organizations, API keys, agent instances, agent channels, channel apps,
6
+ // schedules) use their dedicated find/list RPCs with bespoke table shapes,
7
+ // registered in LIST_HANDLERS — the same map-dispatch shape as the get,
8
+ // delete, and apply registries, so the conformance test in
9
+ // registry/registry.test.ts can hold all four to the verb matrix.
7
10
 
8
11
  import { create } from "@bufbuild/protobuf";
9
12
  import { AgentChannelSchema } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/api_pb";
@@ -12,6 +15,8 @@ import { AgentInstanceSchema } from "@stigmer/protos/ai/stigmer/agentic/agentins
12
15
  import { ListAgentInstancesRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/io_pb";
13
16
  import { ChannelAppSchema } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/api_pb";
14
17
  import { ListChannelAppsByOrgInputSchema } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/io_pb";
18
+ import { ScheduleSchema } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/api_pb";
19
+ import { ListSchedulesRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/io_pb";
15
20
  import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
16
21
  import { PageInfoSchema } from "@stigmer/protos/ai/stigmer/commons/rpc/pagination_pb";
17
22
  import { ApiKeySchema } from "@stigmer/protos/ai/stigmer/iam/apikey/v1/api_pb";
@@ -26,9 +31,9 @@ import { bool, type JsonObject, obj, renderCollection, str, type TableShape } fr
26
31
  // scope). Must stay in step with the server's SearchableKinds allowlist
27
32
  // (backend/services/stigmer-server/pkg/query/search/valueobject/
28
33
  // search_criteria.go): a kind present here but absent there silently lists
29
- // as empty. agent_channel and channel_app are deliberately NOT here — they
30
- // are not_search_indexed by design and take dedicated-RPC branches below.
31
- const SEARCH_KINDS = new Set<ApiResourceKind>([
34
+ // as empty. The LIST_HANDLERS kinds are deliberately NOT here — they are
35
+ // not_search_indexed by design and take dedicated-RPC handlers below.
36
+ export const SEARCH_KINDS: ReadonlySet<ApiResourceKind> = new Set<ApiResourceKind>([
32
37
  ApiResourceKind.agent,
33
38
  ApiResourceKind.workflow,
34
39
  ApiResourceKind.mcp_server,
@@ -38,6 +43,73 @@ const SEARCH_KINDS = new Set<ApiResourceKind>([
38
43
  ApiResourceKind.environment,
39
44
  ]);
40
45
 
46
+ type ListFn = (client: Stigmer, org: string, limit: number, format: OutputFormat) => Promise<string>;
47
+
48
+ // Dedicated-RPC list handlers for the non-search-indexed kinds. Every kind
49
+ // declaring List in the verb matrix must appear here or in SEARCH_KINDS —
50
+ // the conformance test enforces it, so the two cannot drift.
51
+ export const LIST_HANDLERS: ReadonlyMap<ApiResourceKind, ListFn> = new Map<ApiResourceKind, ListFn>([
52
+ [
53
+ ApiResourceKind.organization,
54
+ async (client, _org, _limit, format) => {
55
+ const result = await client.organization.findMyOrganizations();
56
+ return renderCollection(OrganizationSchema, result.entries, format, ORG_TABLE);
57
+ },
58
+ ],
59
+ [
60
+ ApiResourceKind.api_key,
61
+ async (client, _org, _limit, format) => {
62
+ const result = await client.apiKey.findAll();
63
+ return renderCollection(ApiKeySchema, result.entries, format, APIKEY_TABLE);
64
+ },
65
+ ],
66
+ [
67
+ ApiResourceKind.agent_instance,
68
+ async (client, org, limit, format) => {
69
+ const result = await client.agentInstance.list(
70
+ create(ListAgentInstancesRequestSchema, { org, pageInfo: create(PageInfoSchema, { num: 1, size: limit }) }),
71
+ );
72
+ return renderCollection(AgentInstanceSchema, result.items, format, INSTANCE_TABLE);
73
+ },
74
+ ],
75
+ [
76
+ ApiResourceKind.agent_channel,
77
+ async (client, org, limit, format) => {
78
+ const result = await client.agentChannel.list(
79
+ create(ListAgentChannelsRequestSchema, { org, pageInfo: create(PageInfoSchema, { num: 1, size: limit }) }),
80
+ );
81
+ return renderCollection(AgentChannelSchema, result.items, format, AGENT_CHANNEL_TABLE);
82
+ },
83
+ ],
84
+ [
85
+ ApiResourceKind.channel_app,
86
+ async (client, org, limit, format) => {
87
+ // listByOrg has no pagination on its contract (the per-org set is small
88
+ // by design); the limit is applied client-side so --limit stays honest.
89
+ // `channelapp` (not `channelApp`) is a recorded SDK codegen naming quirk.
90
+ const result = await client.channelapp.listByOrg(create(ListChannelAppsByOrgInputSchema, { org }));
91
+ return renderCollection(ChannelAppSchema, result.entries.slice(0, limit), format, CHANNEL_APP_TABLE);
92
+ },
93
+ ],
94
+ [
95
+ ApiResourceKind.schedule,
96
+ async (client, org, limit, format) => {
97
+ // ListSchedulesRequest requires an org (min_len 1), but an unset cloud
98
+ // context resolves to "" — refuse with actionable copy instead of
99
+ // relaying the server's raw validation error.
100
+ if (org === "") {
101
+ throw new UsageError(
102
+ "schedules are org-scoped: pass --org <slug> or configure an organization context",
103
+ );
104
+ }
105
+ const result = await client.schedule.list(
106
+ create(ListSchedulesRequestSchema, { org, pageInfo: create(PageInfoSchema, { num: 1, size: limit }) }),
107
+ );
108
+ return renderCollection(ScheduleSchema, result.items, format, SCHEDULE_TABLE);
109
+ },
110
+ ],
111
+ ]);
112
+
41
113
  export async function listResources(
42
114
  client: Stigmer,
43
115
  kind: ApiResourceKind,
@@ -45,32 +117,9 @@ export async function listResources(
45
117
  limit: number,
46
118
  format: OutputFormat,
47
119
  ): Promise<string> {
48
- if (kind === ApiResourceKind.organization) {
49
- const result = await client.organization.findMyOrganizations();
50
- return renderCollection(OrganizationSchema, result.entries, format, ORG_TABLE);
51
- }
52
- if (kind === ApiResourceKind.api_key) {
53
- const result = await client.apiKey.findAll();
54
- return renderCollection(ApiKeySchema, result.entries, format, APIKEY_TABLE);
55
- }
56
- if (kind === ApiResourceKind.agent_instance) {
57
- const result = await client.agentInstance.list(
58
- create(ListAgentInstancesRequestSchema, { org, pageInfo: create(PageInfoSchema, { num: 1, size: limit }) }),
59
- );
60
- return renderCollection(AgentInstanceSchema, result.items, format, INSTANCE_TABLE);
61
- }
62
- if (kind === ApiResourceKind.agent_channel) {
63
- const result = await client.agentChannel.list(
64
- create(ListAgentChannelsRequestSchema, { org, pageInfo: create(PageInfoSchema, { num: 1, size: limit }) }),
65
- );
66
- return renderCollection(AgentChannelSchema, result.items, format, AGENT_CHANNEL_TABLE);
67
- }
68
- if (kind === ApiResourceKind.channel_app) {
69
- // listByOrg has no pagination on its contract (the per-org set is small
70
- // by design); the limit is applied client-side so --limit stays honest.
71
- // `channelapp` (not `channelApp`) is a recorded SDK codegen naming quirk.
72
- const result = await client.channelapp.listByOrg(create(ListChannelAppsByOrgInputSchema, { org }));
73
- return renderCollection(ChannelAppSchema, result.entries.slice(0, limit), format, CHANNEL_APP_TABLE);
120
+ const dedicated = LIST_HANDLERS.get(kind);
121
+ if (dedicated !== undefined) {
122
+ return dedicated(client, org, limit, format);
74
123
  }
75
124
  if (!SEARCH_KINDS.has(kind)) {
76
125
  throw new UsageError("list is not implemented for this resource type");
@@ -146,6 +195,33 @@ const CHANNEL_APP_TABLE: TableShape = {
146
195
  },
147
196
  };
148
197
 
198
+ // ENABLED is the owner's switch (spec.enabled); STATE is the platform's
199
+ // failure latch, derived from status.paused_reason. They are different
200
+ // states with different remedies — re-apply with `enabled: true` versus
201
+ // `stigmer schedule resume` — so the table keeps them as two columns. An
202
+ // auto-paused schedule with ENABLED true is exactly the condition this
203
+ // surface exists to make visible (stigmer/stigmer#352).
204
+ const SCHEDULE_TABLE: TableShape = {
205
+ resourceName: "schedules",
206
+ headers: ["ID", "SLUG", "TARGET", "CRON", "TZ", "ENABLED", "STATE"],
207
+ row: (json) => {
208
+ const metadata = obj(json, "metadata");
209
+ const spec = obj(json, "spec");
210
+ // The target oneof has one arm today (`agent`); a future workflow arm
211
+ // extends this accessor alongside the proto.
212
+ const agentRef = obj(obj(spec, "agent"), "agent_ref");
213
+ return [
214
+ str(metadata, "id"),
215
+ str(metadata, "slug"),
216
+ `${str(agentRef, "org")}/${str(agentRef, "slug")}`,
217
+ str(spec, "cron"),
218
+ str(spec, "time_zone"),
219
+ bool(spec, "enabled") ? "true" : "false",
220
+ str(obj(json, "status"), "paused_reason") === "" ? "active" : "paused",
221
+ ];
222
+ },
223
+ };
224
+
149
225
  // The provider_config oneof serializes as exactly one provider-named key in
150
226
  // protojson (AgentChannelSpec and ChannelAppSpec share the same oneof
151
227
  // shape). Extend this list when a new provider arm lands in the protos.