@stigmer/mcp-server 3.3.0 → 3.4.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/README.md +60 -13
- package/cli/mcp-server-stigmer.js +5 -1
- package/domains/agentexecutions/approve.d.ts +9 -0
- package/domains/agentexecutions/approve.d.ts.map +1 -0
- package/domains/agentexecutions/approve.js +46 -0
- package/domains/agentexecutions/approve.js.map +1 -0
- package/domains/agentexecutions/fetch.d.ts +28 -0
- package/domains/agentexecutions/fetch.d.ts.map +1 -0
- package/domains/agentexecutions/fetch.js +82 -0
- package/domains/agentexecutions/fetch.js.map +1 -0
- package/domains/agentexecutions/run.d.ts +27 -0
- package/domains/agentexecutions/run.d.ts.map +1 -0
- package/domains/agentexecutions/run.js +87 -0
- package/domains/agentexecutions/run.js.map +1 -0
- package/domains/agentexecutions/tools.d.ts +5 -0
- package/domains/agentexecutions/tools.d.ts.map +1 -0
- package/domains/agentexecutions/tools.js +97 -0
- package/domains/agentexecutions/tools.js.map +1 -0
- package/domains/datastores/apply.d.ts +4 -0
- package/domains/datastores/apply.d.ts.map +1 -0
- package/domains/datastores/apply.js +28 -0
- package/domains/datastores/apply.js.map +1 -0
- package/domains/datastores/delete.d.ts +3 -0
- package/domains/datastores/delete.d.ts.map +1 -0
- package/domains/datastores/delete.js +38 -0
- package/domains/datastores/delete.js.map +1 -0
- package/domains/datastores/fetch.d.ts +3 -0
- package/domains/datastores/fetch.d.ts.map +1 -0
- package/domains/datastores/fetch.js +23 -0
- package/domains/datastores/fetch.js.map +1 -0
- package/domains/datastores/resources.d.ts +5 -0
- package/domains/datastores/resources.d.ts.map +1 -0
- package/domains/datastores/resources.js +16 -0
- package/domains/datastores/resources.js.map +1 -0
- package/domains/datastores/tools.d.ts +5 -0
- package/domains/datastores/tools.d.ts.map +1 -0
- package/domains/datastores/tools.js +46 -0
- package/domains/datastores/tools.js.map +1 -0
- package/domains/environments/apply.d.ts +4 -0
- package/domains/environments/apply.d.ts.map +1 -0
- package/domains/environments/apply.js +30 -0
- package/domains/environments/apply.js.map +1 -0
- package/domains/environments/delete.d.ts +3 -0
- package/domains/environments/delete.d.ts.map +1 -0
- package/domains/environments/delete.js +36 -0
- package/domains/environments/delete.js.map +1 -0
- package/domains/environments/fetch.d.ts +6 -0
- package/domains/environments/fetch.d.ts.map +1 -0
- package/domains/environments/fetch.js +30 -0
- package/domains/environments/fetch.js.map +1 -0
- package/domains/environments/resources.d.ts +5 -0
- package/domains/environments/resources.d.ts.map +1 -0
- package/domains/environments/resources.js +16 -0
- package/domains/environments/resources.js.map +1 -0
- package/domains/environments/tools.d.ts +5 -0
- package/domains/environments/tools.d.ts.map +1 -0
- package/domains/environments/tools.js +51 -0
- package/domains/environments/tools.js.map +1 -0
- package/domains/executions/cancel.d.ts +9 -0
- package/domains/executions/cancel.d.ts.map +1 -0
- package/domains/executions/cancel.js +114 -0
- package/domains/executions/cancel.js.map +1 -0
- package/domains/executions/tools.d.ts +5 -0
- package/domains/executions/tools.d.ts.map +1 -0
- package/domains/executions/tools.js +28 -0
- package/domains/executions/tools.js.map +1 -0
- package/domains/records/tools.d.ts.map +1 -1
- package/domains/records/tools.js +7 -3
- package/domains/records/tools.js.map +1 -1
- package/domains/resourceuri.d.ts.map +1 -1
- package/domains/resourceuri.js +2 -0
- package/domains/resourceuri.js.map +1 -1
- package/domains/search/tools.d.ts.map +1 -1
- package/domains/search/tools.js +10 -7
- package/domains/search/tools.js.map +1 -1
- package/domains/skills/tools.d.ts.map +1 -1
- package/domains/skills/tools.js +25 -1
- package/domains/skills/tools.js.map +1 -1
- package/domains/skills/versions.d.ts +9 -0
- package/domains/skills/versions.d.ts.map +1 -0
- package/domains/skills/versions.js +31 -0
- package/domains/skills/versions.js.map +1 -0
- package/domains/workflowexecutions/approvals.d.ts +17 -0
- package/domains/workflowexecutions/approvals.d.ts.map +1 -0
- package/domains/workflowexecutions/approvals.js +61 -0
- package/domains/workflowexecutions/approvals.js.map +1 -0
- package/domains/workflowexecutions/run.d.ts +12 -0
- package/domains/workflowexecutions/run.d.ts.map +1 -0
- package/domains/workflowexecutions/run.js +69 -0
- package/domains/workflowexecutions/run.js.map +1 -0
- package/domains/workflowexecutions/tools.d.ts.map +1 -1
- package/domains/workflowexecutions/tools.js +89 -6
- package/domains/workflowexecutions/tools.js.map +1 -1
- package/domains/workflows/tools.d.ts.map +1 -1
- package/domains/workflows/tools.js +69 -5
- package/domains/workflows/tools.js.map +1 -1
- package/domains/workflows/versions.d.ts +29 -0
- package/domains/workflows/versions.d.ts.map +1 -0
- package/domains/workflows/versions.js +99 -0
- package/domains/workflows/versions.js.map +1 -0
- package/gen/datastore.d.ts +861 -0
- package/gen/datastore.d.ts.map +1 -0
- package/gen/datastore.js +267 -0
- package/gen/datastore.js.map +1 -0
- package/gen/environment.d.ts +77 -0
- package/gen/environment.d.ts.map +1 -0
- package/gen/environment.js +62 -0
- package/gen/environment.js.map +1 -0
- package/package.json +3 -3
- package/server.d.ts.map +1 -1
- package/server.js +12 -0
- package/server.js.map +1 -1
- package/src/domains/agentexecutions/approve.ts +67 -0
- package/src/domains/agentexecutions/fetch.ts +112 -0
- package/src/domains/agentexecutions/run.ts +112 -0
- package/src/domains/agentexecutions/tools.ts +139 -0
- package/src/domains/apply.integration.test.ts +82 -6
- package/src/domains/datastores/apply.ts +33 -0
- package/src/domains/datastores/delete.ts +47 -0
- package/src/domains/datastores/fetch.ts +32 -0
- package/src/domains/datastores/resources.ts +21 -0
- package/src/domains/datastores/tools.ts +76 -0
- package/src/domains/deletes.integration.test.ts +49 -2
- package/src/domains/environments/apply.ts +40 -0
- package/src/domains/environments/delete.ts +45 -0
- package/src/domains/environments/fetch.ts +39 -0
- package/src/domains/environments/resources.ts +21 -0
- package/src/domains/environments/tools.ts +81 -0
- package/src/domains/executions/cancel.ts +148 -0
- package/src/domains/executions/tools.ts +45 -0
- package/src/domains/executions.integration.test.ts +376 -0
- package/src/domains/reads.integration.test.ts +51 -5
- package/src/domains/records/tools.ts +7 -3
- package/src/domains/resources.integration.test.ts +38 -2
- package/src/domains/resourceuri.test.ts +11 -2
- package/src/domains/resourceuri.ts +2 -0
- package/src/domains/search/search.integration.test.ts +18 -2
- package/src/domains/search/tools.ts +12 -7
- package/src/domains/skills/tools.ts +34 -1
- package/src/domains/skills/versions.ts +47 -0
- package/src/domains/versions.integration.test.ts +212 -0
- package/src/domains/workflowexecutions/approvals.ts +102 -0
- package/src/domains/workflowexecutions/run.ts +87 -0
- package/src/domains/workflowexecutions/tools.ts +120 -6
- package/src/domains/workflows/tools.ts +90 -7
- package/src/domains/workflows/versions.ts +147 -0
- package/src/gen/datastore.ts +264 -0
- package/src/gen/environment.ts +66 -0
- package/src/server.ts +12 -0
|
@@ -24,12 +24,15 @@ import { rpcError } from "../rpcerr.js";
|
|
|
24
24
|
import { textOrError } from "../toolresult.js";
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* The searchable kinds and their proto enum values.
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* The searchable kinds and their proto enum values. Deliberately a curated
|
|
28
|
+
* subset (not every ApiResourceKind): the kinds an MCP client can also read
|
|
29
|
+
* and manage through tools. The backend's extractor registry supports more
|
|
30
|
+
* (sessions, executions, ...) — add here only alongside a tool surface.
|
|
30
31
|
*/
|
|
31
32
|
const knownKinds: Readonly<Record<string, ApiResourceKind>> = {
|
|
32
33
|
agent: ApiResourceKind.agent,
|
|
34
|
+
datastore: ApiResourceKind.datastore,
|
|
35
|
+
environment: ApiResourceKind.environment,
|
|
33
36
|
skill: ApiResourceKind.skill,
|
|
34
37
|
mcp_server: ApiResourceKind.mcp_server,
|
|
35
38
|
workflow: ApiResourceKind.workflow,
|
|
@@ -50,14 +53,16 @@ export function registerSearchTools(server: McpServer, target: BackendTarget): s
|
|
|
50
53
|
"search",
|
|
51
54
|
{
|
|
52
55
|
description:
|
|
53
|
-
"Search and list Stigmer resources (agents, skills, MCP servers, workflows
|
|
54
|
-
"Set 'kinds' to filter by resource type. Set 'query' for full-text search. " +
|
|
56
|
+
"Search and list Stigmer resources (agents, skills, MCP servers, workflows, environments, " +
|
|
57
|
+
"datastores). Set 'kinds' to filter by resource type. Set 'query' for full-text search. " +
|
|
55
58
|
"Set 'org' to scope to an organization. Omit 'query' to list all accessible resources.",
|
|
56
59
|
inputSchema: {
|
|
57
60
|
kinds: z
|
|
58
61
|
.array(z.string())
|
|
59
62
|
.optional()
|
|
60
|
-
.describe(
|
|
63
|
+
.describe(
|
|
64
|
+
"Resource kinds to search. Valid: agent, skill, mcp_server, workflow, environment, datastore. Empty searches all.",
|
|
65
|
+
),
|
|
61
66
|
query: z
|
|
62
67
|
.string()
|
|
63
68
|
.optional()
|
|
@@ -125,7 +130,7 @@ function parseKinds(raw: string[] | undefined): ApiResourceKind[] {
|
|
|
125
130
|
const kind = knownKinds[s];
|
|
126
131
|
if (kind === undefined) {
|
|
127
132
|
throw new Error(
|
|
128
|
-
`unknown resource kind "${s}"; valid kinds: agent, skill, mcp_server, workflow`,
|
|
133
|
+
`unknown resource kind "${s}"; valid kinds: agent, skill, mcp_server, workflow, environment, datastore`,
|
|
129
134
|
);
|
|
130
135
|
}
|
|
131
136
|
return kind;
|
|
@@ -12,6 +12,7 @@ import { resolveToken, type BackendTarget } from "../client.js";
|
|
|
12
12
|
import { textOrError } from "../toolresult.js";
|
|
13
13
|
import { deleteSkill } from "./delete.js";
|
|
14
14
|
import { fetchSkill } from "./fetch.js";
|
|
15
|
+
import { listSkillVersions } from "./versions.js";
|
|
15
16
|
|
|
16
17
|
/** Register every Skill-domain tool; returns the registered tool names. */
|
|
17
18
|
export function registerSkillTools(server: McpServer, target: BackendTarget): string[] {
|
|
@@ -63,5 +64,37 @@ export function registerSkillTools(server: McpServer, target: BackendTarget): st
|
|
|
63
64
|
),
|
|
64
65
|
);
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
server.registerTool(
|
|
68
|
+
"list_skill_versions",
|
|
69
|
+
{
|
|
70
|
+
description:
|
|
71
|
+
"List a skill's version history (newest first): version hash, tag, who pushed it and " +
|
|
72
|
+
"when, and which version is current. Fetch one version's full content with get_skill and " +
|
|
73
|
+
"its hash or tag.",
|
|
74
|
+
inputSchema: {
|
|
75
|
+
org: z.string().describe("Organization slug that owns the skill."),
|
|
76
|
+
slug: z.string().describe("Skill slug — unique identifier within the org."),
|
|
77
|
+
page_size: z
|
|
78
|
+
.number()
|
|
79
|
+
.int()
|
|
80
|
+
.optional()
|
|
81
|
+
.describe("Maximum versions per page (default 50)."),
|
|
82
|
+
page_token: z
|
|
83
|
+
.string()
|
|
84
|
+
.optional()
|
|
85
|
+
.describe("Pagination token from a previous response's next_page_token."),
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
(args, extra) =>
|
|
89
|
+
textOrError(() =>
|
|
90
|
+
listSkillVersions(target.serverAddress, resolveToken(extra, target.apiKey), {
|
|
91
|
+
org: args.org,
|
|
92
|
+
slug: args.slug,
|
|
93
|
+
pageSize: args.page_size,
|
|
94
|
+
pageToken: args.page_token,
|
|
95
|
+
}),
|
|
96
|
+
),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return ["get_skill", "delete_skill", "list_skill_versions"];
|
|
67
100
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Skill version-timeline path (SkillQueryController.listVersions). Each push
|
|
2
|
+
// creates an immutable version identified by the artifact's SHA-256 hash;
|
|
3
|
+
// entries carry hash, tag, push audit, and provenance (no artifact content, so
|
|
4
|
+
// no projection is needed — unlike the workflow timeline).
|
|
5
|
+
|
|
6
|
+
import type { MessageInitShape } from "@bufbuild/protobuf";
|
|
7
|
+
import {
|
|
8
|
+
type ListSkillVersionsInputSchema,
|
|
9
|
+
ListSkillVersionsResponseSchema,
|
|
10
|
+
} from "@stigmer/protos/ai/stigmer/agentic/skill/v1/io_pb";
|
|
11
|
+
import { SkillQueryController } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/query_pb";
|
|
12
|
+
|
|
13
|
+
import { withClient } from "../client.js";
|
|
14
|
+
import { toProtoJson } from "../marshal.js";
|
|
15
|
+
import { rpcError } from "../rpcerr.js";
|
|
16
|
+
|
|
17
|
+
export interface ListSkillVersionsArgs {
|
|
18
|
+
readonly org: string;
|
|
19
|
+
readonly slug: string;
|
|
20
|
+
readonly pageSize?: number;
|
|
21
|
+
readonly pageToken?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** List a skill's version history (newest first) as protojson. */
|
|
25
|
+
export async function listSkillVersions(
|
|
26
|
+
serverAddress: string,
|
|
27
|
+
token: string,
|
|
28
|
+
args: ListSkillVersionsArgs,
|
|
29
|
+
): Promise<string> {
|
|
30
|
+
return withClient(SkillQueryController, serverAddress, token, async (client, callOptions) => {
|
|
31
|
+
const req: MessageInitShape<typeof ListSkillVersionsInputSchema> = {
|
|
32
|
+
org: args.org,
|
|
33
|
+
slug: args.slug,
|
|
34
|
+
pageToken: args.pageToken ?? "",
|
|
35
|
+
};
|
|
36
|
+
// Forward page_size only when set, letting the server apply its default.
|
|
37
|
+
if ((args.pageSize ?? 0) > 0) {
|
|
38
|
+
req.pageSize = args.pageSize;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const resp = await client.listVersions(req, callOptions);
|
|
42
|
+
return toProtoJson(ListSkillVersionsResponseSchema, resp);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
throw rpcError(err, `versions of skill "${args.slug}" in org "${args.org}"`);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// In-process integration test for the versioning tools: list_workflow_versions
|
|
2
|
+
// (timeline projection — validated_yaml stripped), get_workflow's version
|
|
3
|
+
// argument (slug→ID two-step into getVersion), tag_workflow_version, and
|
|
4
|
+
// list_skill_versions. Same harness as reads.integration.test.ts.
|
|
5
|
+
|
|
6
|
+
import { create, toJson } from "@bufbuild/protobuf";
|
|
7
|
+
import type { ConnectRouter } from "@connectrpc/connect";
|
|
8
|
+
import { connectNodeAdapter } from "@connectrpc/connect-node";
|
|
9
|
+
import {
|
|
10
|
+
createServer as createHttp2Server,
|
|
11
|
+
type Http2Server,
|
|
12
|
+
type ServerHttp2Session,
|
|
13
|
+
} from "node:http2";
|
|
14
|
+
import type { AddressInfo } from "node:net";
|
|
15
|
+
|
|
16
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
17
|
+
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
18
|
+
import { SkillQueryController } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/query_pb";
|
|
19
|
+
import {
|
|
20
|
+
ListSkillVersionsResponseSchema,
|
|
21
|
+
type ListSkillVersionsInput,
|
|
22
|
+
} from "@stigmer/protos/ai/stigmer/agentic/skill/v1/io_pb";
|
|
23
|
+
import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
|
|
24
|
+
import { WorkflowCommandController } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/command_pb";
|
|
25
|
+
import { WorkflowQueryController } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/query_pb";
|
|
26
|
+
import {
|
|
27
|
+
ListWorkflowVersionsResponseSchema,
|
|
28
|
+
WorkflowVersionEntrySchema,
|
|
29
|
+
type GetWorkflowVersionInput,
|
|
30
|
+
type TagWorkflowVersionInput,
|
|
31
|
+
} from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/version_pb";
|
|
32
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
33
|
+
|
|
34
|
+
import { configureLogger } from "../logger";
|
|
35
|
+
import { createServer } from "../server";
|
|
36
|
+
|
|
37
|
+
configureLogger({ level: "error", format: "text" });
|
|
38
|
+
|
|
39
|
+
const HASH_A = "a".repeat(64);
|
|
40
|
+
const HASH_B = "b".repeat(64);
|
|
41
|
+
|
|
42
|
+
const knownWorkflow = create(WorkflowSchema, {
|
|
43
|
+
apiVersion: "v1",
|
|
44
|
+
kind: "workflow",
|
|
45
|
+
metadata: { name: "Release", slug: "release", org: "acme", id: "wkf_1" },
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const workflowVersions = create(ListWorkflowVersionsResponseSchema, {
|
|
49
|
+
versions: [
|
|
50
|
+
{ versionHash: HASH_A, tag: "stable", isCurrent: true, validatedYaml: "document: {}" },
|
|
51
|
+
{ versionHash: HASH_B, validatedYaml: "document: {old: true}" },
|
|
52
|
+
],
|
|
53
|
+
totalCount: 2,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const versionEntry = create(WorkflowVersionEntrySchema, {
|
|
57
|
+
versionHash: HASH_B,
|
|
58
|
+
validatedYaml: "document: {old: true}",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const skillVersions = create(ListSkillVersionsResponseSchema, {
|
|
62
|
+
versions: [{ versionHash: HASH_A, tag: "stable", isCurrent: true }],
|
|
63
|
+
totalCount: 1,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
let backend: Http2Server;
|
|
67
|
+
let client: Client;
|
|
68
|
+
let lastGetVersion: GetWorkflowVersionInput | undefined;
|
|
69
|
+
let lastTagVersion: TagWorkflowVersionInput | undefined;
|
|
70
|
+
let lastSkillVersionsRequest: ListSkillVersionsInput | undefined;
|
|
71
|
+
const openSessions = new Set<ServerHttp2Session>();
|
|
72
|
+
|
|
73
|
+
interface ToolResult {
|
|
74
|
+
content: Array<{ type: string; text?: string }>;
|
|
75
|
+
isError?: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function callTool(name: string, args: Record<string, unknown>): Promise<ToolResult> {
|
|
79
|
+
return (await client.callTool({ name, arguments: args })) as ToolResult;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseText(result: ToolResult): Record<string, unknown> {
|
|
83
|
+
return JSON.parse(result.content[0]?.text ?? "{}") as Record<string, unknown>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
beforeAll(async () => {
|
|
87
|
+
const routes = (router: ConnectRouter) => {
|
|
88
|
+
router.service(WorkflowQueryController, {
|
|
89
|
+
getByReference: () => knownWorkflow,
|
|
90
|
+
listVersions: () => workflowVersions,
|
|
91
|
+
getVersion: (req) => {
|
|
92
|
+
lastGetVersion = req;
|
|
93
|
+
return versionEntry;
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
router.service(WorkflowCommandController, {
|
|
97
|
+
tagVersion: (req) => {
|
|
98
|
+
lastTagVersion = req;
|
|
99
|
+
return knownWorkflow;
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
router.service(SkillQueryController, {
|
|
103
|
+
listVersions: (req) => {
|
|
104
|
+
lastSkillVersionsRequest = req;
|
|
105
|
+
return skillVersions;
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
backend = createHttp2Server(connectNodeAdapter({ routes }));
|
|
110
|
+
backend.on("session", (session) => {
|
|
111
|
+
openSessions.add(session);
|
|
112
|
+
session.on("close", () => openSessions.delete(session));
|
|
113
|
+
});
|
|
114
|
+
await new Promise<void>((resolve) => backend.listen(0, "127.0.0.1", resolve));
|
|
115
|
+
const port = (backend.address() as AddressInfo).port;
|
|
116
|
+
|
|
117
|
+
const mcp = createServer({ serverAddress: `127.0.0.1:${port}`, apiKey: "" });
|
|
118
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
119
|
+
client = new Client({ name: "versions-integration", version: "test" });
|
|
120
|
+
await Promise.all([mcp.connect(serverTransport), client.connect(clientTransport)]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
beforeEach(() => {
|
|
124
|
+
lastGetVersion = undefined;
|
|
125
|
+
lastTagVersion = undefined;
|
|
126
|
+
lastSkillVersionsRequest = undefined;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
afterAll(async () => {
|
|
130
|
+
await client?.close();
|
|
131
|
+
for (const session of openSessions) session.destroy();
|
|
132
|
+
await new Promise<void>((resolve) => backend.close(() => resolve()));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe("versioning tools integration", () => {
|
|
136
|
+
it("advertises the versioning tools", async () => {
|
|
137
|
+
const { tools } = await client.listTools();
|
|
138
|
+
expect(tools.map((t) => t.name)).toEqual(
|
|
139
|
+
expect.arrayContaining([
|
|
140
|
+
"list_workflow_versions",
|
|
141
|
+
"tag_workflow_version",
|
|
142
|
+
"list_skill_versions",
|
|
143
|
+
]),
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("list_workflow_versions strips validated_yaml from the timeline", async () => {
|
|
148
|
+
const result = await callTool("list_workflow_versions", { org: "acme", slug: "release" });
|
|
149
|
+
expect(result.isError).toBeFalsy();
|
|
150
|
+
|
|
151
|
+
const body = parseText(result);
|
|
152
|
+
expect(body.total_count).toBe(2);
|
|
153
|
+
const versions = body.versions as Array<Record<string, unknown>>;
|
|
154
|
+
expect(versions).toHaveLength(2);
|
|
155
|
+
expect(versions[0]).toMatchObject({ version_hash: HASH_A, tag: "stable", is_current: true });
|
|
156
|
+
for (const entry of versions) {
|
|
157
|
+
expect(entry.validated_yaml).toBeUndefined();
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("get_workflow with a version resolves the id and fetches that version", async () => {
|
|
162
|
+
const result = await callTool("get_workflow", {
|
|
163
|
+
org: "acme",
|
|
164
|
+
slug: "release",
|
|
165
|
+
version: HASH_B,
|
|
166
|
+
});
|
|
167
|
+
expect(result.isError).toBeFalsy();
|
|
168
|
+
expect(lastGetVersion?.workflowId).toBe("wkf_1");
|
|
169
|
+
expect(lastGetVersion?.versionHash).toBe(HASH_B);
|
|
170
|
+
// The version entry carries the YAML the timeline omits.
|
|
171
|
+
expect(parseText(result)).toEqual(
|
|
172
|
+
toJson(WorkflowVersionEntrySchema, versionEntry, { useProtoFieldName: true }),
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("get_workflow without a version returns the live workflow", async () => {
|
|
177
|
+
const result = await callTool("get_workflow", { org: "acme", slug: "release" });
|
|
178
|
+
expect(result.isError).toBeFalsy();
|
|
179
|
+
expect(lastGetVersion).toBeUndefined();
|
|
180
|
+
expect(parseText(result)).toEqual(
|
|
181
|
+
toJson(WorkflowSchema, knownWorkflow, { useProtoFieldName: true }),
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("tag_workflow_version resolves the id and forwards hash and tag", async () => {
|
|
186
|
+
const result = await callTool("tag_workflow_version", {
|
|
187
|
+
org: "acme",
|
|
188
|
+
slug: "release",
|
|
189
|
+
version: HASH_B,
|
|
190
|
+
tag: "stable",
|
|
191
|
+
});
|
|
192
|
+
expect(result.isError).toBeFalsy();
|
|
193
|
+
expect(lastTagVersion?.workflowId).toBe("wkf_1");
|
|
194
|
+
expect(lastTagVersion?.versionHash).toBe(HASH_B);
|
|
195
|
+
expect(lastTagVersion?.tag).toBe("stable");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("list_skill_versions forwards the reference and returns the timeline", async () => {
|
|
199
|
+
const result = await callTool("list_skill_versions", {
|
|
200
|
+
org: "acme",
|
|
201
|
+
slug: "code-review",
|
|
202
|
+
page_size: 10,
|
|
203
|
+
});
|
|
204
|
+
expect(result.isError).toBeFalsy();
|
|
205
|
+
expect(lastSkillVersionsRequest?.org).toBe("acme");
|
|
206
|
+
expect(lastSkillVersionsRequest?.slug).toBe("code-review");
|
|
207
|
+
expect(lastSkillVersionsRequest?.pageSize).toBe(10);
|
|
208
|
+
expect(parseText(result)).toEqual(
|
|
209
|
+
toJson(ListSkillVersionsResponseSchema, skillVersions, { useProtoFieldName: true }),
|
|
210
|
+
);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Workflow-execution approval paths: the org-wide pending-approvals inbox
|
|
2
|
+
// (listPendingApprovals) and the reviewer decision for a human_input task
|
|
3
|
+
// (submitWorkflowTaskApproval).
|
|
4
|
+
//
|
|
5
|
+
// listPendingApprovals is org-scoped by contract (io.proto), not
|
|
6
|
+
// execution-scoped — it answers "what is waiting on a human right now"
|
|
7
|
+
// across the org. Each entry carries everything a decision needs: the
|
|
8
|
+
// execution ID, task name, requester, timeout, and the form schema when the
|
|
9
|
+
// task defines one. Agent-execution approvals are a different surface
|
|
10
|
+
// entirely (embedded in get_agent_execution status; see
|
|
11
|
+
// agentexecutions/approve.ts).
|
|
12
|
+
|
|
13
|
+
import type { JsonObject, MessageInitShape } from "@bufbuild/protobuf";
|
|
14
|
+
import { WorkflowExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
|
|
15
|
+
import { WorkflowExecutionCommandController } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/command_pb";
|
|
16
|
+
import {
|
|
17
|
+
type ListPendingApprovalsRequestSchema,
|
|
18
|
+
PendingApprovalsListSchema,
|
|
19
|
+
type SubmitWorkflowTaskApprovalInputSchema,
|
|
20
|
+
} from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/io_pb";
|
|
21
|
+
import { WorkflowExecutionQueryController } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/query_pb";
|
|
22
|
+
|
|
23
|
+
import { withClient } from "../client.js";
|
|
24
|
+
import { toProtoJson } from "../marshal.js";
|
|
25
|
+
import { rpcError } from "../rpcerr.js";
|
|
26
|
+
|
|
27
|
+
export interface ListPendingApprovalsArgs {
|
|
28
|
+
readonly org: string;
|
|
29
|
+
readonly pageSize?: number;
|
|
30
|
+
readonly pageToken?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** List human_input tasks awaiting a reviewer decision across an org. */
|
|
34
|
+
export async function listPendingApprovals(
|
|
35
|
+
serverAddress: string,
|
|
36
|
+
token: string,
|
|
37
|
+
args: ListPendingApprovalsArgs,
|
|
38
|
+
): Promise<string> {
|
|
39
|
+
return withClient(
|
|
40
|
+
WorkflowExecutionQueryController,
|
|
41
|
+
serverAddress,
|
|
42
|
+
token,
|
|
43
|
+
async (client, callOptions) => {
|
|
44
|
+
const req: MessageInitShape<typeof ListPendingApprovalsRequestSchema> = {
|
|
45
|
+
org: args.org,
|
|
46
|
+
pageToken: args.pageToken ?? "",
|
|
47
|
+
};
|
|
48
|
+
// Forward page_size only when set, letting the server apply its default.
|
|
49
|
+
if ((args.pageSize ?? 0) > 0) {
|
|
50
|
+
req.pageSize = args.pageSize;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const resp = await client.listPendingApprovals(req, callOptions);
|
|
54
|
+
return toProtoJson(PendingApprovalsListSchema, resp);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
throw rpcError(err, `pending approvals in org "${args.org}"`);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface SubmitWorkflowTaskApprovalArgs {
|
|
63
|
+
readonly executionId: string;
|
|
64
|
+
readonly taskName: string;
|
|
65
|
+
readonly outcome: string;
|
|
66
|
+
readonly comment?: string;
|
|
67
|
+
readonly formData?: Record<string, unknown>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Submit a reviewer decision for a waiting human_input task. */
|
|
71
|
+
export async function submitWorkflowTaskApproval(
|
|
72
|
+
serverAddress: string,
|
|
73
|
+
token: string,
|
|
74
|
+
args: SubmitWorkflowTaskApprovalArgs,
|
|
75
|
+
): Promise<string> {
|
|
76
|
+
return withClient(
|
|
77
|
+
WorkflowExecutionCommandController,
|
|
78
|
+
serverAddress,
|
|
79
|
+
token,
|
|
80
|
+
async (client, callOptions) => {
|
|
81
|
+
const req: MessageInitShape<typeof SubmitWorkflowTaskApprovalInputSchema> = {
|
|
82
|
+
executionId: args.executionId,
|
|
83
|
+
taskName: args.taskName,
|
|
84
|
+
outcome: args.outcome,
|
|
85
|
+
comment: args.comment ?? "",
|
|
86
|
+
};
|
|
87
|
+
if (args.formData !== undefined) {
|
|
88
|
+
// google.protobuf.Struct fields are plain JSON objects in protobuf-es v2.
|
|
89
|
+
req.formData = args.formData as JsonObject;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const execution = await client.submitWorkflowTaskApproval(req, callOptions);
|
|
93
|
+
return toProtoJson(WorkflowExecutionSchema, execution);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
throw rpcError(
|
|
96
|
+
err,
|
|
97
|
+
`approval for task "${args.taskName}" in workflow execution "${args.executionId}"`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Workflow-execution start path for the run_workflow tool.
|
|
2
|
+
//
|
|
3
|
+
// Mirrors the CLI's workflow run branch (client-apps/cli/src/commands/run.ts +
|
|
4
|
+
// resources/run/create.ts): resolve the org/slug reference to the workflow ID,
|
|
5
|
+
// then create the execution. Asynchronous like run_agent — the tool returns
|
|
6
|
+
// the created execution (wex_* ID) and observation happens through the
|
|
7
|
+
// existing get_workflow_execution / get_workflow_execution_events tools.
|
|
8
|
+
|
|
9
|
+
import { createClient } from "@connectrpc/connect";
|
|
10
|
+
import { create as createMessage } from "@bufbuild/protobuf";
|
|
11
|
+
import { WorkflowQueryController } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/query_pb";
|
|
12
|
+
import { WorkflowExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
|
|
13
|
+
import { WorkflowExecutionCommandController } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/command_pb";
|
|
14
|
+
import { WorkflowExecutionSpecSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/spec_pb";
|
|
15
|
+
import { ExecutionValueSchema } from "@stigmer/protos/ai/stigmer/agentic/executioncontext/v1/spec_pb";
|
|
16
|
+
import { ApiResourceMetadataSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
|
|
17
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
18
|
+
|
|
19
|
+
import { executionName, toExecutionValues } from "../agentexecutions/run.js";
|
|
20
|
+
import { withTransport } from "../client.js";
|
|
21
|
+
import { toProtoJson } from "../marshal.js";
|
|
22
|
+
import { rpcError } from "../rpcerr.js";
|
|
23
|
+
|
|
24
|
+
/** apiVersion stamped on created executions; mirrors the CLI's run stack. */
|
|
25
|
+
const API_VERSION = "agentic.stigmer.ai/v1";
|
|
26
|
+
|
|
27
|
+
export interface RunWorkflowArgs {
|
|
28
|
+
readonly org: string;
|
|
29
|
+
readonly workflow: string;
|
|
30
|
+
readonly message?: string;
|
|
31
|
+
readonly runtimeEnv?: Record<string, string>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Start a workflow execution: resolve org/slug → workflow ID, then create the
|
|
36
|
+
* execution. Returns the created execution as protojson.
|
|
37
|
+
*/
|
|
38
|
+
export async function runWorkflow(
|
|
39
|
+
serverAddress: string,
|
|
40
|
+
token: string,
|
|
41
|
+
args: RunWorkflowArgs,
|
|
42
|
+
): Promise<string> {
|
|
43
|
+
const desc = `workflow "${args.workflow}" in org "${args.org}"`;
|
|
44
|
+
return withTransport(serverAddress, token, async (transport, callOptions) => {
|
|
45
|
+
const query = createClient(WorkflowQueryController, transport);
|
|
46
|
+
let workflowId: string;
|
|
47
|
+
try {
|
|
48
|
+
const workflow = await query.getByReference(
|
|
49
|
+
{ org: args.org, kind: ApiResourceKind.workflow, slug: args.workflow },
|
|
50
|
+
callOptions,
|
|
51
|
+
);
|
|
52
|
+
workflowId = workflow.metadata?.id ?? "";
|
|
53
|
+
} catch (err) {
|
|
54
|
+
throw rpcError(err, desc);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Workflow tasks resolve their org through the runtime env; the CLI
|
|
58
|
+
// injects STIGMER_ORG_ID the same way, so a caller-supplied value wins.
|
|
59
|
+
const runtimeEnv = toExecutionValues(args.runtimeEnv);
|
|
60
|
+
if (runtimeEnv.STIGMER_ORG_ID === undefined) {
|
|
61
|
+
runtimeEnv.STIGMER_ORG_ID = createMessage(ExecutionValueSchema, {
|
|
62
|
+
value: args.org,
|
|
63
|
+
isSecret: false,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const execution = createMessage(WorkflowExecutionSchema, {
|
|
68
|
+
apiVersion: API_VERSION,
|
|
69
|
+
kind: "WorkflowExecution",
|
|
70
|
+
metadata: createMessage(ApiResourceMetadataSchema, { name: executionName(), org: args.org }),
|
|
71
|
+
spec: createMessage(WorkflowExecutionSpecSchema, {
|
|
72
|
+
workflowId,
|
|
73
|
+
// Empty message means "just run" — the CLI applies the same default.
|
|
74
|
+
triggerMessage: (args.message ?? "") === "" ? "execute" : args.message,
|
|
75
|
+
runtimeEnv,
|
|
76
|
+
}),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const command = createClient(WorkflowExecutionCommandController, transport);
|
|
80
|
+
try {
|
|
81
|
+
const created = await command.create(execution, callOptions);
|
|
82
|
+
return toProtoJson(WorkflowExecutionSchema, created);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
throw rpcError(err, `execution of ${desc}`);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|