@stigmer/cli 3.11.0 → 3.12.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/delete.js +2 -2
- package/commands/delete.js.map +1 -1
- package/commands/usage.js +7 -4
- package/commands/usage.js.map +1 -1
- package/package.json +5 -5
- package/registry/aliases.d.ts +3 -1
- package/registry/aliases.d.ts.map +1 -1
- package/registry/aliases.js +18 -6
- package/registry/aliases.js.map +1 -1
- package/registry/metadata.d.ts.map +1 -1
- package/registry/metadata.js +6 -6
- package/registry/metadata.js.map +1 -1
- package/registry/registry.d.ts.map +1 -1
- package/registry/registry.js +7 -2
- package/registry/registry.js.map +1 -1
- package/registry/verb-support.d.ts.map +1 -1
- package/registry/verb-support.js +0 -4
- package/registry/verb-support.js.map +1 -1
- package/resources/delete.d.ts.map +1 -1
- package/resources/delete.js +1 -9
- package/resources/delete.js.map +1 -1
- package/resources/get-bindings.d.ts.map +1 -1
- package/resources/get-bindings.js +0 -2
- package/resources/get-bindings.js.map +1 -1
- package/resources/list.d.ts +7 -1
- package/resources/list.d.ts.map +1 -1
- package/resources/list.js +35 -16
- package/resources/list.js.map +1 -1
- package/resources/usage.d.ts +1 -1
- package/resources/usage.d.ts.map +1 -1
- package/resources/usage.js +2 -2
- package/resources/usage.js.map +1 -1
- package/resources/validate.d.ts.map +1 -1
- package/resources/validate.js +0 -2
- package/resources/validate.js.map +1 -1
- package/src/commands/delete.ts +2 -2
- package/src/commands/usage.ts +7 -4
- package/src/registry/aliases.test.ts +29 -5
- package/src/registry/aliases.ts +19 -6
- package/src/registry/metadata.ts +6 -6
- package/src/registry/registry.test.ts +48 -0
- package/src/registry/registry.ts +6 -2
- package/src/registry/verb-support.ts +0 -4
- package/src/resources/apply/apply.integration.test.ts +0 -67
- package/src/resources/delete.ts +3 -13
- package/src/resources/get-bindings.ts +0 -2
- package/src/resources/list.ts +55 -18
- package/src/resources/mutate-verbs.integration.test.ts +0 -62
- package/src/resources/read-verbs.integration.test.ts +35 -2
- package/src/resources/usage.ts +2 -1
- package/src/resources/validate.ts +0 -2
|
@@ -19,8 +19,6 @@ 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
20
|
import type { ChannelApp } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/api_pb";
|
|
21
21
|
import { ChannelAppCommandController } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/command_pb";
|
|
22
|
-
import type { Datastore } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/api_pb";
|
|
23
|
-
import { DatastoreCommandController } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/command_pb";
|
|
24
22
|
import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
25
23
|
import { McpServerCommandController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/command_pb";
|
|
26
24
|
import { createNodeTransport, normalizeEndpoint } from "@stigmer/sdk/node";
|
|
@@ -34,13 +32,11 @@ const openSessions = new Set<ServerHttp2Session>();
|
|
|
34
32
|
|
|
35
33
|
let appliedAgents: Agent[] = [];
|
|
36
34
|
let appliedMcps: McpServer[] = [];
|
|
37
|
-
let appliedDatastores: Datastore[] = [];
|
|
38
35
|
let appliedChannelApps: ChannelApp[] = [];
|
|
39
36
|
|
|
40
37
|
beforeEach(() => {
|
|
41
38
|
appliedAgents = [];
|
|
42
39
|
appliedMcps = [];
|
|
43
|
-
appliedDatastores = [];
|
|
44
40
|
appliedChannelApps = [];
|
|
45
41
|
});
|
|
46
42
|
|
|
@@ -64,12 +60,6 @@ beforeAll(async () => {
|
|
|
64
60
|
return req;
|
|
65
61
|
},
|
|
66
62
|
});
|
|
67
|
-
router.service(DatastoreCommandController, {
|
|
68
|
-
apply: (req) => {
|
|
69
|
-
appliedDatastores.push(req);
|
|
70
|
-
return req;
|
|
71
|
-
},
|
|
72
|
-
});
|
|
73
63
|
router.service(ChannelAppCommandController, {
|
|
74
64
|
apply: (req) => {
|
|
75
65
|
appliedChannelApps.push(req);
|
|
@@ -166,64 +156,7 @@ describe("file-mode apply — fidelity (raw controller preserves full proto)", (
|
|
|
166
156
|
});
|
|
167
157
|
});
|
|
168
158
|
|
|
169
|
-
const DATASTORE_YAML = [
|
|
170
|
-
"apiVersion: agentic.stigmer.ai/v1",
|
|
171
|
-
"kind: Datastore",
|
|
172
|
-
"metadata:",
|
|
173
|
-
" name: clinic-records",
|
|
174
|
-
" slug: clinic-records",
|
|
175
|
-
"spec:",
|
|
176
|
-
" timezone: Asia/Kolkata",
|
|
177
|
-
" authorization:",
|
|
178
|
-
" roles:",
|
|
179
|
-
" - name: patient",
|
|
180
|
-
" default_role: patient",
|
|
181
|
-
" collections:",
|
|
182
|
-
" - name: bookings",
|
|
183
|
-
" fields:",
|
|
184
|
-
" - name: slot_start",
|
|
185
|
-
" type: timestamp",
|
|
186
|
-
" required: true",
|
|
187
|
-
" uniques:",
|
|
188
|
-
" - name: one_per_slot",
|
|
189
|
-
" fields: [slot_start]",
|
|
190
|
-
' message: "that slot is already booked"',
|
|
191
|
-
"",
|
|
192
|
-
].join("\n");
|
|
193
|
-
|
|
194
|
-
describe("file-mode apply — datastore", () => {
|
|
195
|
-
it("applies a Datastore through the raw controller with the spec intact", async () => {
|
|
196
|
-
const dir = mkdtempSync(join(tmpdir(), "apply-it-"));
|
|
197
|
-
try {
|
|
198
|
-
writeYaml(dir, "datastore.yaml", DATASTORE_YAML);
|
|
199
|
-
const items = resolveApplyItems(dir);
|
|
200
|
-
expect(items).toHaveLength(1);
|
|
201
|
-
|
|
202
|
-
const outcome = await applyItem(controllerFn, items[0], "acme", false);
|
|
203
|
-
expect(outcome.result.message).toBe("Datastore created successfully");
|
|
204
|
-
|
|
205
|
-
expect(appliedDatastores).toHaveLength(1);
|
|
206
|
-
const spec = appliedDatastores[0].spec;
|
|
207
|
-
expect(spec?.timezone).toBe("Asia/Kolkata");
|
|
208
|
-
expect(spec?.authorization?.defaultRole).toBe("patient");
|
|
209
|
-
expect(spec?.collections[0]?.uniques[0]?.message).toBe("that slot is already booked");
|
|
210
|
-
} finally {
|
|
211
|
-
rmSync(dir, { recursive: true, force: true });
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
159
|
|
|
215
|
-
it("sorts a Datastore before the Agent that references it", () => {
|
|
216
|
-
const dir = mkdtempSync(join(tmpdir(), "apply-it-"));
|
|
217
|
-
try {
|
|
218
|
-
writeYaml(dir, "agent.yaml", AGENT_WITH_ID);
|
|
219
|
-
writeYaml(dir, "datastore.yaml", DATASTORE_YAML);
|
|
220
|
-
const items = resolveApplyItems(dir);
|
|
221
|
-
expect(items.map((i) => i.handler.displayName)).toEqual(["Datastore", "Agent"]);
|
|
222
|
-
} finally {
|
|
223
|
-
rmSync(dir, { recursive: true, force: true });
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
160
|
|
|
228
161
|
// The stigmer/stigmer#353 regression block: ChannelApp apply used to fail
|
|
229
162
|
// with "apply not implemented for Channel App" because the CLI's handler
|
package/src/resources/delete.ts
CHANGED
|
@@ -45,9 +45,8 @@ interface HasMetadata {
|
|
|
45
45
|
readonly metadata?: { readonly id?: string; readonly name?: string; readonly slug?: string; readonly org?: string };
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
// `force` is the RPC-level acknowledgment of destructive side effects
|
|
49
|
-
//
|
|
50
|
-
// `-f/--force` flag. Kinds whose delete RPC takes a plain typed ID have no
|
|
48
|
+
// `force` is the RPC-level acknowledgment of destructive side effects —
|
|
49
|
+
// distinct from, but carried by, the CLI's `-f/--force` flag. Kinds whose delete RPC takes a plain typed ID have no
|
|
51
50
|
// force field and simply ignore the argument.
|
|
52
51
|
type DeleteFn = (client: Stigmer, id: string, force: boolean) => Promise<HasMetadata>;
|
|
53
52
|
|
|
@@ -56,8 +55,7 @@ type DeleteFn = (client: Stigmer, id: string, force: boolean) => Promise<HasMeta
|
|
|
56
55
|
// documented special case) — the conformance test in registry/registry.test.ts
|
|
57
56
|
// enforces it, so the two cannot drift.
|
|
58
57
|
// Kinds whose delete takes a DeleteResourceInput (not an ID) thread the force
|
|
59
|
-
// acknowledgment through
|
|
60
|
-
// non-empty guard), but force is the contract's generic ack carrier.
|
|
58
|
+
// acknowledgment through — force is the contract's generic ack carrier.
|
|
61
59
|
export const DELETE_HANDLERS: ReadonlyMap<ApiResourceKind, DeleteFn> = new Map<ApiResourceKind, DeleteFn>([
|
|
62
60
|
[ApiResourceKind.agent, (c, id) => c.agent.delete(id)],
|
|
63
61
|
[ApiResourceKind.agent_instance, (c, id) => c.agentInstance.delete(id)],
|
|
@@ -65,7 +63,6 @@ export const DELETE_HANDLERS: ReadonlyMap<ApiResourceKind, DeleteFn> = new Map<A
|
|
|
65
63
|
[ApiResourceKind.workflow_instance, (c, id) => c.workflowInstance.delete(id)],
|
|
66
64
|
[ApiResourceKind.mcp_server, (c, id, force) => c.mcpServer.delete({ resourceId: id, force })],
|
|
67
65
|
[ApiResourceKind.project, (c, id) => c.project.delete(id)],
|
|
68
|
-
[ApiResourceKind.datastore, (c, id, force) => c.datastore.delete({ resourceId: id, force })],
|
|
69
66
|
[ApiResourceKind.environment, (c, id, force) => c.environment.delete({ resourceId: id, force })],
|
|
70
67
|
[ApiResourceKind.agent_channel, (c, id) => c.agentChannel.delete(id)],
|
|
71
68
|
[ApiResourceKind.channel_app, (c, id, force) => c.channelapp.delete({ resourceId: id, force })],
|
|
@@ -230,13 +227,6 @@ function buildDeleteWarning(info: TypeInfo, message: HasMetadata): CommandResult
|
|
|
230
227
|
warning.hint("This will delete the skill and all its versions.");
|
|
231
228
|
}
|
|
232
229
|
|
|
233
|
-
if (info.kind === ApiResourceKind.datastore) {
|
|
234
|
-
// Mirrors the server's non-empty guard (GuardNonEmptyStep): the delete of
|
|
235
|
-
// a datastore holding records is refused unless force acknowledges it.
|
|
236
|
-
warning.hint("All records held by this datastore will be destroyed.");
|
|
237
|
-
warning.hint("A datastore holding records is refused unless --force acknowledges destroying them.");
|
|
238
|
-
}
|
|
239
|
-
|
|
240
230
|
if (info.kind === ApiResourceKind.agent_channel) {
|
|
241
231
|
// The server's documented distinction: delete is the connection's full
|
|
242
232
|
// teardown; disabling is the config-preserving pause.
|
|
@@ -8,7 +8,6 @@ import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb"
|
|
|
8
8
|
import { AgentChannelSchema } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/api_pb";
|
|
9
9
|
import { AgentInstanceSchema } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/api_pb";
|
|
10
10
|
import { ChannelAppSchema } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/api_pb";
|
|
11
|
-
import { DatastoreSchema } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/api_pb";
|
|
12
11
|
import { EnvironmentSchema } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/api_pb";
|
|
13
12
|
import { McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
14
13
|
import { ScheduleSchema } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/api_pb";
|
|
@@ -39,7 +38,6 @@ export const GET_BINDINGS: ReadonlyMap<ApiResourceKind, Getter> = new Map([
|
|
|
39
38
|
[ApiResourceKind.workflow_instance, refGetter(ApiResourceKind.workflow_instance, WorkflowInstanceSchema, (c) => c.workflowInstance)],
|
|
40
39
|
[ApiResourceKind.mcp_server, refGetter(ApiResourceKind.mcp_server, McpServerSchema, (c) => c.mcpServer)],
|
|
41
40
|
[ApiResourceKind.project, refGetter(ApiResourceKind.project, ProjectSchema, (c) => c.project)],
|
|
42
|
-
[ApiResourceKind.datastore, refGetter(ApiResourceKind.datastore, DatastoreSchema, (c) => c.datastore)],
|
|
43
41
|
[ApiResourceKind.environment, refGetter(ApiResourceKind.environment, EnvironmentSchema, (c) => c.environment)],
|
|
44
42
|
[ApiResourceKind.agent_channel, refGetter(ApiResourceKind.agent_channel, AgentChannelSchema, (c) => c.agentChannel)],
|
|
45
43
|
[ApiResourceKind.channel_app, refGetter(ApiResourceKind.channel_app, ChannelAppSchema, (c) => c.channelapp)],
|
package/src/resources/list.ts
CHANGED
|
@@ -7,8 +7,14 @@
|
|
|
7
7
|
// registered in LIST_HANDLERS — the same map-dispatch shape as the get,
|
|
8
8
|
// delete, and apply registries, so the conformance test in
|
|
9
9
|
// registry/registry.test.ts can hold all four to the verb matrix.
|
|
10
|
+
//
|
|
11
|
+
// Handlers FETCH, the dispatcher RENDERS: every handler returns entries plus
|
|
12
|
+
// its schema/table pair, and listResources alone applies --limit and renders.
|
|
13
|
+
// That split is deliberate (stigmer/stigmer#312): when handlers owned
|
|
14
|
+
// rendering, honoring --limit was per-handler discipline, and the two
|
|
15
|
+
// unpaginated branches (organization, api_key) shipped silently ignoring it.
|
|
10
16
|
|
|
11
|
-
import { create } from "@bufbuild/protobuf";
|
|
17
|
+
import { create, type DescMessage, type Message } from "@bufbuild/protobuf";
|
|
12
18
|
import { AgentChannelSchema } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/api_pb";
|
|
13
19
|
import { ListAgentChannelsRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/io_pb";
|
|
14
20
|
import { AgentInstanceSchema } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/api_pb";
|
|
@@ -39,11 +45,22 @@ export const SEARCH_KINDS: ReadonlySet<ApiResourceKind> = new Set<ApiResourceKin
|
|
|
39
45
|
ApiResourceKind.mcp_server,
|
|
40
46
|
ApiResourceKind.project,
|
|
41
47
|
ApiResourceKind.skill,
|
|
42
|
-
ApiResourceKind.datastore,
|
|
43
48
|
ApiResourceKind.environment,
|
|
44
49
|
]);
|
|
45
50
|
|
|
46
|
-
|
|
51
|
+
// One fetched page of a listing: what to render (entries + schema) and how
|
|
52
|
+
// (table). Rendering and --limit truncation happen centrally in
|
|
53
|
+
// listResources — a handler cannot opt out of either.
|
|
54
|
+
interface ListPage {
|
|
55
|
+
readonly schema: DescMessage;
|
|
56
|
+
readonly entries: readonly Message[];
|
|
57
|
+
readonly table: TableShape;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Where the RPC paginates, the handler still forwards `limit` as the page
|
|
61
|
+
// size so the server does the bounding; the dispatcher's slice is then a
|
|
62
|
+
// no-op. Where it doesn't, the slice IS the bound.
|
|
63
|
+
type ListFn = (client: Stigmer, org: string, limit: number) => Promise<ListPage>;
|
|
47
64
|
|
|
48
65
|
// Dedicated-RPC list handlers for the non-search-indexed kinds. Every kind
|
|
49
66
|
// declaring List in the verb matrix must appear here or in SEARCH_KINDS —
|
|
@@ -51,49 +68,53 @@ type ListFn = (client: Stigmer, org: string, limit: number, format: OutputFormat
|
|
|
51
68
|
export const LIST_HANDLERS: ReadonlyMap<ApiResourceKind, ListFn> = new Map<ApiResourceKind, ListFn>([
|
|
52
69
|
[
|
|
53
70
|
ApiResourceKind.organization,
|
|
54
|
-
async (client
|
|
71
|
+
async (client) => {
|
|
72
|
+
// findMyOrganizations is caller-scoped and unpaginated (Empty request;
|
|
73
|
+
// a caller's org set is small by nature). The paginated `find` RPC is
|
|
74
|
+
// platform-admin-only — the wrong surface for `list organization`.
|
|
55
75
|
const result = await client.organization.findMyOrganizations();
|
|
56
|
-
return
|
|
76
|
+
return { schema: OrganizationSchema, entries: result.entries, table: ORG_TABLE };
|
|
57
77
|
},
|
|
58
78
|
],
|
|
59
79
|
[
|
|
60
80
|
ApiResourceKind.api_key,
|
|
61
|
-
async (client
|
|
81
|
+
async (client) => {
|
|
82
|
+
// findAll is caller-scoped and unpaginated (Empty request).
|
|
62
83
|
const result = await client.apiKey.findAll();
|
|
63
|
-
return
|
|
84
|
+
return { schema: ApiKeySchema, entries: result.entries, table: APIKEY_TABLE };
|
|
64
85
|
},
|
|
65
86
|
],
|
|
66
87
|
[
|
|
67
88
|
ApiResourceKind.agent_instance,
|
|
68
|
-
async (client, org, limit
|
|
89
|
+
async (client, org, limit) => {
|
|
69
90
|
const result = await client.agentInstance.list(
|
|
70
91
|
create(ListAgentInstancesRequestSchema, { org, pageInfo: create(PageInfoSchema, { num: 1, size: limit }) }),
|
|
71
92
|
);
|
|
72
|
-
return
|
|
93
|
+
return { schema: AgentInstanceSchema, entries: result.items, table: INSTANCE_TABLE };
|
|
73
94
|
},
|
|
74
95
|
],
|
|
75
96
|
[
|
|
76
97
|
ApiResourceKind.agent_channel,
|
|
77
|
-
async (client, org, limit
|
|
98
|
+
async (client, org, limit) => {
|
|
78
99
|
const result = await client.agentChannel.list(
|
|
79
100
|
create(ListAgentChannelsRequestSchema, { org, pageInfo: create(PageInfoSchema, { num: 1, size: limit }) }),
|
|
80
101
|
);
|
|
81
|
-
return
|
|
102
|
+
return { schema: AgentChannelSchema, entries: result.items, table: AGENT_CHANNEL_TABLE };
|
|
82
103
|
},
|
|
83
104
|
],
|
|
84
105
|
[
|
|
85
106
|
ApiResourceKind.channel_app,
|
|
86
|
-
async (client, org
|
|
107
|
+
async (client, org) => {
|
|
87
108
|
// listByOrg has no pagination on its contract (the per-org set is small
|
|
88
|
-
// by design); the
|
|
109
|
+
// by design); the dispatcher's slice bounds the output.
|
|
89
110
|
// `channelapp` (not `channelApp`) is a recorded SDK codegen naming quirk.
|
|
90
111
|
const result = await client.channelapp.listByOrg(create(ListChannelAppsByOrgInputSchema, { org }));
|
|
91
|
-
return
|
|
112
|
+
return { schema: ChannelAppSchema, entries: result.entries, table: CHANNEL_APP_TABLE };
|
|
92
113
|
},
|
|
93
114
|
],
|
|
94
115
|
[
|
|
95
116
|
ApiResourceKind.schedule,
|
|
96
|
-
async (client, org, limit
|
|
117
|
+
async (client, org, limit) => {
|
|
97
118
|
// ListSchedulesRequest requires an org (min_len 1), but an unset cloud
|
|
98
119
|
// context resolves to "" — refuse with actionable copy instead of
|
|
99
120
|
// relaying the server's raw validation error.
|
|
@@ -105,7 +126,7 @@ export const LIST_HANDLERS: ReadonlyMap<ApiResourceKind, ListFn> = new Map<ApiRe
|
|
|
105
126
|
const result = await client.schedule.list(
|
|
106
127
|
create(ListSchedulesRequestSchema, { org, pageInfo: create(PageInfoSchema, { num: 1, size: limit }) }),
|
|
107
128
|
);
|
|
108
|
-
return
|
|
129
|
+
return { schema: ScheduleSchema, entries: result.items, table: SCHEDULE_TABLE };
|
|
109
130
|
},
|
|
110
131
|
],
|
|
111
132
|
]);
|
|
@@ -117,15 +138,31 @@ export async function listResources(
|
|
|
117
138
|
limit: number,
|
|
118
139
|
format: OutputFormat,
|
|
119
140
|
): Promise<string> {
|
|
141
|
+
const page = await fetchListPage(client, kind, org, limit);
|
|
142
|
+
// The single point where --limit binds the output. For paginated RPCs the
|
|
143
|
+
// handler already asked the server for at most `limit` entries and this is
|
|
144
|
+
// a no-op; for unpaginated RPCs it is the truncation itself. Slicing here
|
|
145
|
+
// rather than per-handler is what keeps the flag honest for every kind —
|
|
146
|
+
// organization and api_key shipped ignoring it when handlers owned this
|
|
147
|
+
// (stigmer/stigmer#312).
|
|
148
|
+
return renderCollection(page.schema, page.entries.slice(0, limit), format, page.table);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function fetchListPage(
|
|
152
|
+
client: Stigmer,
|
|
153
|
+
kind: ApiResourceKind,
|
|
154
|
+
org: string,
|
|
155
|
+
limit: number,
|
|
156
|
+
): Promise<ListPage> {
|
|
120
157
|
const dedicated = LIST_HANDLERS.get(kind);
|
|
121
158
|
if (dedicated !== undefined) {
|
|
122
|
-
return dedicated(client, org, limit
|
|
159
|
+
return dedicated(client, org, limit);
|
|
123
160
|
}
|
|
124
161
|
if (!SEARCH_KINDS.has(kind)) {
|
|
125
162
|
throw new UsageError("list is not implemented for this resource type");
|
|
126
163
|
}
|
|
127
164
|
const result = await client.search.query({ kinds: [kind], org, page: { num: 1, size: limit } });
|
|
128
|
-
return
|
|
165
|
+
return { schema: SearchResultSchema, entries: result.entries, table: SEARCH_TABLE };
|
|
129
166
|
}
|
|
130
167
|
|
|
131
168
|
// Shared with `search` — both render SearchService results identically.
|
|
@@ -25,9 +25,6 @@ import { AgentInstanceQueryController } from "@stigmer/protos/ai/stigmer/agentic
|
|
|
25
25
|
import { ChannelAppSchema } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/api_pb";
|
|
26
26
|
import { ChannelAppCommandController } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/command_pb";
|
|
27
27
|
import { ChannelAppQueryController } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/query_pb";
|
|
28
|
-
import { DatastoreSchema } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/api_pb";
|
|
29
|
-
import { DatastoreCommandController } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/command_pb";
|
|
30
|
-
import { DatastoreQueryController } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/query_pb";
|
|
31
28
|
import { EnvironmentSchema } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/api_pb";
|
|
32
29
|
import { EnvironmentCommandController } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/command_pb";
|
|
33
30
|
import { EnvironmentQueryController } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/query_pb";
|
|
@@ -69,16 +66,6 @@ const knownWorkflow = create(WorkflowSchema, {
|
|
|
69
66
|
metadata: { name: "Deploy", slug: "deploy", org: "acme", id: "wfl_1" },
|
|
70
67
|
});
|
|
71
68
|
|
|
72
|
-
// The mock treats this datastore as NON-EMPTY: delete refuses without the
|
|
73
|
-
// force acknowledgment, mirroring the server's GuardNonEmptyStep contract.
|
|
74
|
-
const knownDatastore = create(DatastoreSchema, {
|
|
75
|
-
metadata: { name: "clinic-records", slug: "clinic-records", org: "acme", id: "dst_1" },
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// Byte-shaped like the Go server's GuardNonEmptyStep refusal (delete_guards.go).
|
|
79
|
-
const nonEmptyGuardMessage =
|
|
80
|
-
'datastore "clinic-records" holds 12 records across 3 collections; deleting destroys them — pass force to acknowledge';
|
|
81
|
-
|
|
82
69
|
const knownEnvironment = create(EnvironmentSchema, {
|
|
83
70
|
metadata: { name: "clinic-patient-db", slug: "clinic-patient-db", org: "acme", id: "env_1" },
|
|
84
71
|
});
|
|
@@ -121,7 +108,6 @@ const openSessions = new Set<ServerHttp2Session>();
|
|
|
121
108
|
let cancelCalls: string[] = [];
|
|
122
109
|
let tagCalls: { workflowId: string; versionHash: string; tag: string }[] = [];
|
|
123
110
|
// Force-carrying deletes record what actually rode the RPC.
|
|
124
|
-
let datastoreDeletes: { resourceId: string; force: boolean }[] = [];
|
|
125
111
|
let environmentDeletes: { resourceId: string; force: boolean }[] = [];
|
|
126
112
|
let channelAppDeletes: { resourceId: string; force: boolean }[] = [];
|
|
127
113
|
// agent_channel, schedule, and workflow_instance delete by typed ID — no
|
|
@@ -133,7 +119,6 @@ let workflowInstanceDeleteIds: string[] = [];
|
|
|
133
119
|
beforeEach(() => {
|
|
134
120
|
cancelCalls = [];
|
|
135
121
|
tagCalls = [];
|
|
136
|
-
datastoreDeletes = [];
|
|
137
122
|
environmentDeletes = [];
|
|
138
123
|
channelAppDeletes = [];
|
|
139
124
|
channelDeleteIds = [];
|
|
@@ -181,21 +166,6 @@ beforeAll(async () => {
|
|
|
181
166
|
},
|
|
182
167
|
});
|
|
183
168
|
|
|
184
|
-
router.service(DatastoreQueryController, {
|
|
185
|
-
getByReference: (req) => {
|
|
186
|
-
if (req.slug !== "clinic-records") throw new ConnectError("datastore not found", Code.NotFound);
|
|
187
|
-
return knownDatastore;
|
|
188
|
-
},
|
|
189
|
-
});
|
|
190
|
-
router.service(DatastoreCommandController, {
|
|
191
|
-
delete: (req) => {
|
|
192
|
-
datastoreDeletes.push({ resourceId: req.resourceId, force: req.force });
|
|
193
|
-
// Mirrors GuardNonEmptyStep: a non-empty datastore refuses without force.
|
|
194
|
-
if (!req.force) throw new ConnectError(nonEmptyGuardMessage, Code.FailedPrecondition);
|
|
195
|
-
return knownDatastore;
|
|
196
|
-
},
|
|
197
|
-
});
|
|
198
|
-
|
|
199
169
|
router.service(EnvironmentQueryController, {
|
|
200
170
|
getByReference: (req) => {
|
|
201
171
|
if (req.slug !== "clinic-patient-db") throw new ConnectError("environment not found", Code.NotFound);
|
|
@@ -374,7 +344,6 @@ describe("delete (standard kinds)", () => {
|
|
|
374
344
|
// it must name the wired kinds and never the narrowed ones (#354).
|
|
375
345
|
const message = String(err.message);
|
|
376
346
|
for (const wired of [
|
|
377
|
-
"datastore",
|
|
378
347
|
"environment",
|
|
379
348
|
"agentchannel",
|
|
380
349
|
"channelapp",
|
|
@@ -399,37 +368,6 @@ describe("delete (standard kinds)", () => {
|
|
|
399
368
|
});
|
|
400
369
|
});
|
|
401
370
|
|
|
402
|
-
describe("delete (force acknowledgment)", () => {
|
|
403
|
-
it("threads --force to the datastore delete RPC as the destroy-records ack", async () => {
|
|
404
|
-
const plan = await planDelete(client, "datastore", "clinic-records", "acme", true);
|
|
405
|
-
const result = await plan.perform();
|
|
406
|
-
|
|
407
|
-
expect(result.status).toBe("success");
|
|
408
|
-
expect(result.message).toBe("Datastore deleted successfully");
|
|
409
|
-
expect(datastoreDeletes).toEqual([{ resourceId: "dst_1", force: true }]);
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
it("surfaces the non-empty guard refusal verbatim when force is absent", async () => {
|
|
413
|
-
const plan = await planDelete(client, "datastore", "clinic-records", "acme");
|
|
414
|
-
const err = await plan.perform().catch((e) => e);
|
|
415
|
-
|
|
416
|
-
// A confirmed-but-unforced delete must NOT bypass the server guard: the
|
|
417
|
-
// RPC carries force=false and the refusal reaches the user verbatim.
|
|
418
|
-
expect(datastoreDeletes).toEqual([{ resourceId: "dst_1", force: false }]);
|
|
419
|
-
const classified = classify(err);
|
|
420
|
-
expect(classified?.exitCode).toBe(ExitCode.Usage);
|
|
421
|
-
expect(classified?.message).toBe(`Precondition failed: ${nonEmptyGuardMessage}`);
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
it("warns up front that datastore records are destroyed and force-guarded", async () => {
|
|
425
|
-
const plan = await planDelete(client, "datastore", "clinic-records", "acme");
|
|
426
|
-
expect(plan.warning.hints).toContain("All records held by this datastore will be destroyed.");
|
|
427
|
-
expect(plan.warning.hints).toContain(
|
|
428
|
-
"A datastore holding records is refused unless --force acknowledges destroying them.",
|
|
429
|
-
);
|
|
430
|
-
});
|
|
431
|
-
});
|
|
432
|
-
|
|
433
371
|
describe("delete (cutover kinds: environment, agent channel, channel app)", () => {
|
|
434
372
|
it("deletes an environment via the DeleteResourceInput shape", async () => {
|
|
435
373
|
const plan = await planDelete(client, "environment", "clinic-patient-db", "acme");
|
|
@@ -67,6 +67,23 @@ const knownApiKey = create(ApiKeySchema, {
|
|
|
67
67
|
spec: { fingerprint: "abcd", neverExpires: true },
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
+
// Second org/API-key entries so the list tests can pin the
|
|
71
|
+
// dispatcher-applied --limit slice — both RPCs are unpaginated (Empty
|
|
72
|
+
// request), the exact shape that shipped ignoring the flag when handlers
|
|
73
|
+
// owned rendering (stigmer/stigmer#312).
|
|
74
|
+
const secondOrg = create(OrganizationSchema, {
|
|
75
|
+
apiVersion: "tenancy.stigmer.ai/v1",
|
|
76
|
+
kind: "Organization",
|
|
77
|
+
metadata: { name: "Beta Clinic", slug: "beta-clinic", org: "beta-clinic", id: "beta-clinic" },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const secondApiKey = create(ApiKeySchema, {
|
|
81
|
+
apiVersion: "iam.stigmer.ai/v1",
|
|
82
|
+
kind: "ApiKey",
|
|
83
|
+
metadata: { name: "local-dev", org: "acme", id: "key_2" },
|
|
84
|
+
spec: { fingerprint: "ef01", neverExpires: true },
|
|
85
|
+
});
|
|
86
|
+
|
|
70
87
|
const knownSearchResult = create(SearchResultSchema, {
|
|
71
88
|
kind: ApiResourceKind.agent,
|
|
72
89
|
id: "agt_1",
|
|
@@ -223,10 +240,10 @@ beforeAll(async () => {
|
|
|
223
240
|
},
|
|
224
241
|
});
|
|
225
242
|
router.service(OrganizationQueryController, {
|
|
226
|
-
findMyOrganizations: () => ({ entries: [knownOrg] }),
|
|
243
|
+
findMyOrganizations: () => ({ entries: [knownOrg, secondOrg] }),
|
|
227
244
|
});
|
|
228
245
|
router.service(ApiKeyQueryController, {
|
|
229
|
-
findAll: () => ({ entries: [knownApiKey] }),
|
|
246
|
+
findAll: () => ({ entries: [knownApiKey, secondApiKey] }),
|
|
230
247
|
});
|
|
231
248
|
router.service(EnvironmentQueryController, {
|
|
232
249
|
get: (req) => {
|
|
@@ -441,11 +458,27 @@ describe("list integration", () => {
|
|
|
441
458
|
|
|
442
459
|
it("lists organizations via findMyOrganizations as JSON", async () => {
|
|
443
460
|
const out = await listResources(client, ApiResourceKind.organization, "acme", 50, "json");
|
|
461
|
+
expect(JSON.parse(out)).toEqual([
|
|
462
|
+
toJson(OrganizationSchema, knownOrg, { useProtoFieldName: true }),
|
|
463
|
+
toJson(OrganizationSchema, secondOrg, { useProtoFieldName: true }),
|
|
464
|
+
]);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it("applies --limit to organizations (findMyOrganizations has no pagination)", async () => {
|
|
468
|
+
const out = await listResources(client, ApiResourceKind.organization, "acme", 1, "json");
|
|
444
469
|
expect(JSON.parse(out)).toEqual([toJson(OrganizationSchema, knownOrg, { useProtoFieldName: true })]);
|
|
445
470
|
});
|
|
446
471
|
|
|
447
472
|
it("lists API keys via findAll as JSON", async () => {
|
|
448
473
|
const out = await listResources(client, ApiResourceKind.api_key, "acme", 50, "json");
|
|
474
|
+
expect(JSON.parse(out)).toEqual([
|
|
475
|
+
toJson(ApiKeySchema, knownApiKey, { useProtoFieldName: true }),
|
|
476
|
+
toJson(ApiKeySchema, secondApiKey, { useProtoFieldName: true }),
|
|
477
|
+
]);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it("applies --limit to API keys (findAll has no pagination)", async () => {
|
|
481
|
+
const out = await listResources(client, ApiResourceKind.api_key, "acme", 1, "json");
|
|
449
482
|
expect(JSON.parse(out)).toEqual([toJson(ApiKeySchema, knownApiKey, { useProtoFieldName: true })]);
|
|
450
483
|
});
|
|
451
484
|
|
package/src/resources/usage.ts
CHANGED
|
@@ -41,10 +41,11 @@ export async function getSessionUsageReport(client: Stigmer, sessionId: string):
|
|
|
41
41
|
export async function getAgentUsageReport(
|
|
42
42
|
client: Stigmer,
|
|
43
43
|
agentId: string,
|
|
44
|
+
orgId: string,
|
|
44
45
|
range: DateRange,
|
|
45
46
|
): Promise<GetAgentUsageReportOutput> {
|
|
46
47
|
return client.agentExecution.getAgentUsageReport(
|
|
47
|
-
create(GetAgentUsageReportInputSchema, { agentId, fromDate: range.from, toDate: range.to }),
|
|
48
|
+
create(GetAgentUsageReportInputSchema, { agentId, orgId, fromDate: range.from, toDate: range.to }),
|
|
48
49
|
);
|
|
49
50
|
}
|
|
50
51
|
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
|
|
8
8
|
import { type DescMessage, fromJson, type JsonValue } from "@bufbuild/protobuf";
|
|
9
9
|
import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
10
|
-
import { DatastoreSchema } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/api_pb";
|
|
11
10
|
import { McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
12
11
|
import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
|
|
13
12
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
@@ -22,7 +21,6 @@ export const VALIDATE_SCHEMAS: ReadonlyMap<ApiResourceKind, DescMessage> = new M
|
|
|
22
21
|
[ApiResourceKind.workflow, WorkflowSchema],
|
|
23
22
|
[ApiResourceKind.mcp_server, McpServerSchema],
|
|
24
23
|
[ApiResourceKind.project, ProjectSchema],
|
|
25
|
-
[ApiResourceKind.datastore, DatastoreSchema],
|
|
26
24
|
]);
|
|
27
25
|
|
|
28
26
|
export function schemaForValidate(kind: ApiResourceKind): DescMessage | undefined {
|