@stigmer/server 3.19.1-dev.20260920100552 → 3.21.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/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +0 -1
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/inprocess.d.ts +0 -2
- package/dist/boot/inprocess.d.ts.map +1 -1
- package/dist/boot/inprocess.js +0 -6
- package/dist/boot/inprocess.js.map +1 -1
- package/dist/domain/agent/controller.d.ts +2 -2
- package/dist/domain/agent/controller.d.ts.map +1 -1
- package/dist/domain/agent/controller.js +1 -17
- package/dist/domain/agent/controller.js.map +1 -1
- package/dist/domain/agent/steps.d.ts +0 -1
- package/dist/domain/agent/steps.d.ts.map +1 -1
- package/dist/domain/agent/steps.js +1 -40
- package/dist/domain/agent/steps.js.map +1 -1
- package/dist/domain/agentexecution/controller.d.ts.map +1 -1
- package/dist/domain/agentexecution/controller.js +7 -9
- package/dist/domain/agentexecution/controller.js.map +1 -1
- package/dist/domain/agentexecution/create-execution-context-step.d.ts +0 -2
- package/dist/domain/agentexecution/create-execution-context-step.d.ts.map +1 -1
- package/dist/domain/agentexecution/create-execution-context-step.js +179 -60
- package/dist/domain/agentexecution/create-execution-context-step.js.map +1 -1
- package/dist/domain/agentexecution/create-steps.d.ts +16 -31
- package/dist/domain/agentexecution/create-steps.d.ts.map +1 -1
- package/dist/domain/agentexecution/create-steps.js +36 -102
- package/dist/domain/agentexecution/create-steps.js.map +1 -1
- package/dist/domain/agentexecution/run-target.d.ts +7 -3
- package/dist/domain/agentexecution/run-target.d.ts.map +1 -1
- package/dist/domain/agentexecution/run-target.js.map +1 -1
- package/dist/domain/environment/personal.d.ts +42 -0
- package/dist/domain/environment/personal.d.ts.map +1 -0
- package/dist/domain/environment/personal.js +96 -0
- package/dist/domain/environment/personal.js.map +1 -0
- package/dist/domain/mcpserver/connect.d.ts +0 -2
- package/dist/domain/mcpserver/connect.d.ts.map +1 -1
- package/dist/domain/mcpserver/connect.js +16 -72
- package/dist/domain/mcpserver/connect.js.map +1 -1
- package/dist/domain/plugin/overlay/sanitize.js +2 -2
- package/dist/domain/session/controller.d.ts +0 -7
- package/dist/domain/session/controller.d.ts.map +1 -1
- package/dist/domain/session/controller.js +11 -16
- package/dist/domain/session/controller.js.map +1 -1
- package/dist/domain/session/run-target.d.ts +9 -8
- package/dist/domain/session/run-target.d.ts.map +1 -1
- package/dist/domain/session/run-target.js.map +1 -1
- package/dist/domain/session/steps.d.ts +0 -27
- package/dist/domain/session/steps.d.ts.map +1 -1
- package/dist/domain/session/steps.js +10 -151
- package/dist/domain/session/steps.js.map +1 -1
- package/dist/domain/workflowexecution/create-steps.d.ts +4 -3
- package/dist/domain/workflowexecution/create-steps.d.ts.map +1 -1
- package/dist/domain/workflowexecution/create-steps.js +4 -3
- package/dist/domain/workflowexecution/create-steps.js.map +1 -1
- package/dist/pipeline/apiresource-labels.d.ts +2 -2
- package/dist/pipeline/apiresource-labels.js +2 -2
- package/dist/pipeline/errors.d.ts +2 -2
- package/dist/pipeline/errors.js +2 -2
- package/dist/pipeline/steps/guard-reserved-labels.d.ts.map +1 -1
- package/dist/pipeline/steps/guard-reserved-labels.js +6 -5
- package/dist/pipeline/steps/guard-reserved-labels.js.map +1 -1
- package/package.json +6 -6
- package/src/authorization/__tests__/fixtures/fga/org-admin-owner-inheritance.fga.yaml +1 -1
- package/src/boot/compose.ts +0 -1
- package/src/boot/inprocess.ts +0 -9
- package/src/domain/agent/__tests__/agent.test.ts +0 -68
- package/src/domain/agent/controller.ts +3 -38
- package/src/domain/agent/steps.ts +1 -60
- package/src/domain/agentexecution/__tests__/create-execution-context-step.test.ts +216 -0
- package/src/domain/agentexecution/__tests__/create-steps.test.ts +70 -145
- package/src/domain/agentexecution/controller.ts +6 -10
- package/src/domain/agentexecution/create-execution-context-step.ts +271 -83
- package/src/domain/agentexecution/create-steps.ts +38 -138
- package/src/domain/agentexecution/run-target.ts +7 -3
- package/src/domain/environment/__tests__/personal.test.ts +143 -0
- package/src/domain/environment/personal.ts +141 -0
- package/src/domain/mcpserver/connect.ts +21 -82
- package/src/domain/plugin/overlay/sanitize.ts +2 -2
- package/src/domain/session/__tests__/run-gate.test.ts +19 -52
- package/src/domain/session/__tests__/session.test.ts +27 -203
- package/src/domain/session/controller.ts +10 -30
- package/src/domain/session/run-target.ts +9 -8
- package/src/domain/session/steps.ts +9 -235
- package/src/domain/workflowexecution/create-steps.ts +4 -3
- package/src/pipeline/apiresource-labels.ts +2 -2
- package/src/pipeline/errors.ts +2 -2
- package/src/pipeline/steps/guard-reserved-labels.ts +6 -5
- package/dist/domain/agent/defaultagent.d.ts +0 -37
- package/dist/domain/agent/defaultagent.d.ts.map +0 -1
- package/dist/domain/agent/defaultagent.js +0 -104
- package/dist/domain/agent/defaultagent.js.map +0 -1
- package/src/domain/agent/defaultagent.ts +0 -135
|
@@ -42,6 +42,7 @@ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/
|
|
|
42
42
|
import { TokenEndpointAuthMethod } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/spec_pb";
|
|
43
43
|
|
|
44
44
|
import type { Logger } from "../../boot/logger.js";
|
|
45
|
+
import { resolveDeclaredFromPersonalEnvironment } from "../environment/personal.js";
|
|
45
46
|
import type { Authorizer } from "../../extensions/authorizer.js";
|
|
46
47
|
import type { CallerIdentity } from "../../extensions/identity.js";
|
|
47
48
|
import { authorizeDirect } from "../../pipeline/steps/authorize.js";
|
|
@@ -150,9 +151,6 @@ export const ASYNC_CONNECT_TIMEOUT: ConnectBudget = {
|
|
|
150
151
|
*/
|
|
151
152
|
export const BEST_EFFORT_CONNECT_GET_BUFFER_MS = 15_000;
|
|
152
153
|
|
|
153
|
-
/** The label selecting the user's personal environment (connect.go:74). */
|
|
154
|
-
export const PERSONAL_ENV_LABEL = "stigmer.ai/personal";
|
|
155
|
-
|
|
156
154
|
/**
|
|
157
155
|
* The narrow environment read surface the connect lanes consume for
|
|
158
156
|
* personal-environment resolution — satisfied by the composition root's
|
|
@@ -687,34 +685,29 @@ async function resolveOAuthVarsFromManagedEnv(
|
|
|
687
685
|
}
|
|
688
686
|
|
|
689
687
|
/**
|
|
690
|
-
* Reads
|
|
691
|
-
* (
|
|
692
|
-
*
|
|
693
|
-
*
|
|
694
|
-
*
|
|
688
|
+
* Reads a server's declared variables from the user's personal environment
|
|
689
|
+
* (Go resolveFromPersonalEnvironment) through the shared lookup in
|
|
690
|
+
* domain/environment/personal.ts, and gives its outcomes the connect
|
|
691
|
+
* lane's meaning: a person asked to connect a server and cannot without
|
|
692
|
+
* the credential, so a missing personal environment and a missing required
|
|
693
|
+
* key both refuse with FailedPrecondition. Optional variables are included
|
|
694
|
+
* when available and silently skipped when missing.
|
|
695
695
|
*/
|
|
696
696
|
async function resolveFromPersonalEnvironment(
|
|
697
697
|
deps: McpServerConnectDeps,
|
|
698
698
|
org: string,
|
|
699
699
|
envDecls: { [key: string]: EnvVarDeclaration },
|
|
700
700
|
): Promise<{ [key: string]: ExecutionValue }> {
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
org,
|
|
712
|
-
labels: { [PERSONAL_ENV_LABEL]: "true" },
|
|
713
|
-
});
|
|
714
|
-
} catch (error) {
|
|
715
|
-
throw internalError(error, "failed to list personal environments");
|
|
716
|
-
}
|
|
717
|
-
if (listResponse.totalCount === 0 || listResponse.items.length === 0) {
|
|
701
|
+
const resolution = await resolveDeclaredFromPersonalEnvironment(
|
|
702
|
+
deps.environmentReader,
|
|
703
|
+
deps.logger,
|
|
704
|
+
org,
|
|
705
|
+
envDecls,
|
|
706
|
+
);
|
|
707
|
+
if (resolution.kind === "no-personal-environment") {
|
|
708
|
+
const requiredKeys = Object.entries(envDecls)
|
|
709
|
+
.filter(([, decl]) => !decl.optional)
|
|
710
|
+
.map(([key]) => key);
|
|
718
711
|
if (requiredKeys.length === 0) {
|
|
719
712
|
return {};
|
|
720
713
|
}
|
|
@@ -723,66 +716,12 @@ async function resolveFromPersonalEnvironment(
|
|
|
723
716
|
`personal environment not found for org '${org}'; save required credentials first: [${requiredKeys.join(" ")}]`,
|
|
724
717
|
);
|
|
725
718
|
}
|
|
726
|
-
|
|
727
|
-
const personalEnv = listResponse.items[0];
|
|
728
|
-
const personalEnvId = personalEnv?.metadata?.id ?? "";
|
|
729
|
-
const storedKeys = new Set(Object.keys(personalEnv?.spec?.data ?? {}));
|
|
730
|
-
|
|
731
|
-
const result: { [key: string]: ExecutionValue } = {};
|
|
732
|
-
const missing: string[] = [];
|
|
733
|
-
|
|
734
|
-
for (const [key, decl] of Object.entries(envDecls)) {
|
|
735
|
-
if (!storedKeys.has(key)) {
|
|
736
|
-
if (decl.optional) {
|
|
737
|
-
deps.logger.debug(
|
|
738
|
-
"Optional env var not in personal environment — skipping",
|
|
739
|
-
{ key },
|
|
740
|
-
);
|
|
741
|
-
continue;
|
|
742
|
-
}
|
|
743
|
-
missing.push(key);
|
|
744
|
-
continue;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
let secretValue: EnvironmentSpecValue;
|
|
748
|
-
try {
|
|
749
|
-
secretValue = await deps.environmentReader.getSecretValue({
|
|
750
|
-
environmentId: personalEnvId,
|
|
751
|
-
key,
|
|
752
|
-
});
|
|
753
|
-
} catch (error) {
|
|
754
|
-
deps.logger.warn("Failed to get secret value from personal environment", {
|
|
755
|
-
key,
|
|
756
|
-
personal_env_id: personalEnvId,
|
|
757
|
-
error: error instanceof Error ? error.message : String(error),
|
|
758
|
-
});
|
|
759
|
-
if (decl.optional) {
|
|
760
|
-
continue;
|
|
761
|
-
}
|
|
762
|
-
missing.push(key);
|
|
763
|
-
continue;
|
|
764
|
-
}
|
|
765
|
-
if (secretValue.value === "") {
|
|
766
|
-
if (decl.optional) {
|
|
767
|
-
continue;
|
|
768
|
-
}
|
|
769
|
-
missing.push(key);
|
|
770
|
-
continue;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
result[key] = create(ExecutionValueSchema, {
|
|
774
|
-
value: secretValue.value,
|
|
775
|
-
isSecret: decl.isSecret,
|
|
776
|
-
});
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
if (missing.length > 0) {
|
|
719
|
+
if (resolution.missing.length > 0) {
|
|
780
720
|
throw failedPreconditionError(
|
|
781
|
-
`missing required credentials in personal environment: [${missing.join(" ")}]`,
|
|
721
|
+
`missing required credentials in personal environment: [${resolution.missing.join(" ")}]`,
|
|
782
722
|
);
|
|
783
723
|
}
|
|
784
|
-
|
|
785
|
-
return result;
|
|
724
|
+
return { ...resolution.values };
|
|
786
725
|
}
|
|
787
726
|
|
|
788
727
|
/**
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* lane passes GuardReservedLabels structurally (the controller stamps the
|
|
6
6
|
* membership labels by design), so this module is the one place plugin-
|
|
7
7
|
* provided metadata is judged before it can ride that lane. Without it an
|
|
8
|
-
* archive could carry `stigmer.ai
|
|
9
|
-
*
|
|
8
|
+
* archive could carry any `stigmer.ai/*` label in its overlays and have
|
|
9
|
+
* the server act on a platform decision the plugin's author made.
|
|
10
10
|
*
|
|
11
11
|
* Four rules, each one sentence with the document's path:
|
|
12
12
|
* - a `stigmer.ai/*` label costs `can_write_reserved_labels` on
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pins the run gate's SPLICE in session-create over the real router (P1
|
|
3
|
-
* sp.run-gate): the AuthorizeRunTarget step sits after
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
3
|
+
* sp.run-gate): the AuthorizeRunTarget step sits right after Authorize
|
|
4
|
+
* and before Persist, so (1) a denied caller answers PERMISSION_DENIED
|
|
5
|
+
* with the domain's byte-pinned copy and leaves no session row; (2) the
|
|
6
|
+
* built-in-assistant shape (empty spec) makes NO run-gate check and
|
|
7
|
+
* creates the session even under a denying run-gate authorizer — the
|
|
8
|
+
* organization's own create permission admitted it in the Authorize step;
|
|
9
9
|
* (3) an allowed caller creates the session exactly as before. The
|
|
10
10
|
* authorizer under test answers only run-gate checks (runGateOnlyAuthorizer),
|
|
11
11
|
* so every refusal here is the run gate's and nobody else's.
|
|
@@ -20,11 +20,9 @@ import { Code, ConnectError, createClient } from "@connectrpc/connect";
|
|
|
20
20
|
import { createGrpcTransport } from "@connectrpc/connect-node";
|
|
21
21
|
|
|
22
22
|
import { AgentCommandController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/command_pb";
|
|
23
|
-
import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
24
23
|
import { AgentQueryController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/query_pb";
|
|
25
24
|
import { SessionCommandController } from "@stigmer/protos/ai/stigmer/agentic/session/v1/command_pb";
|
|
26
25
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
27
|
-
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
28
26
|
import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
29
27
|
|
|
30
28
|
import { loadConfig } from "../../../boot/config.js";
|
|
@@ -33,10 +31,6 @@ import type { ComposedServer } from "../../../boot/compose.js";
|
|
|
33
31
|
import { createLogger } from "../../../boot/logger.js";
|
|
34
32
|
import type { AuthzDecision } from "../../../extensions/authorizer.js";
|
|
35
33
|
import { runGateOnlyAuthorizer } from "../../../pipeline/__tests__/support.js";
|
|
36
|
-
import {
|
|
37
|
-
DEFAULT_AGENT_LABEL,
|
|
38
|
-
DEFAULT_AGENT_LABEL_VALUE,
|
|
39
|
-
} from "../../agent/defaultagent.js";
|
|
40
34
|
import { runAgentInstanceDeniedMessage } from "../constants.js";
|
|
41
35
|
|
|
42
36
|
const silentLogger = createLogger({
|
|
@@ -99,23 +93,6 @@ async function createAgent(name: string): Promise<string> {
|
|
|
99
93
|
return agent.metadata!.id;
|
|
100
94
|
}
|
|
101
95
|
|
|
102
|
-
/** The default-agent label is operator-seeded state; stamp it on the row. */
|
|
103
|
-
async function markDefaultAgent(agentId: string): Promise<void> {
|
|
104
|
-
const stored = await server.store.getResource(
|
|
105
|
-
ApiResourceKind.agent,
|
|
106
|
-
agentId,
|
|
107
|
-
AgentSchema,
|
|
108
|
-
);
|
|
109
|
-
stored.metadata!.labels[DEFAULT_AGENT_LABEL] = DEFAULT_AGENT_LABEL_VALUE;
|
|
110
|
-
stored.metadata!.visibility = ApiResourceVisibility.visibility_public;
|
|
111
|
-
await server.store.saveResource(
|
|
112
|
-
ApiResourceKind.agent,
|
|
113
|
-
agentId,
|
|
114
|
-
AgentSchema,
|
|
115
|
-
stored,
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
96
|
async function captureError(
|
|
120
97
|
run: () => Promise<unknown>,
|
|
121
98
|
): Promise<ConnectError> {
|
|
@@ -154,33 +131,23 @@ describe("session-create run gate (composed server)", () => {
|
|
|
154
131
|
expect(await sessionCount(), "no row behind a denial").toBe(before);
|
|
155
132
|
});
|
|
156
133
|
|
|
157
|
-
it("
|
|
134
|
+
it("makes no check for the built-in assistant (empty spec) and creates the session", async () => {
|
|
158
135
|
decision = { kind: "deny", reason: "" };
|
|
159
136
|
gate.runGateChecks.length = 0;
|
|
160
|
-
const
|
|
161
|
-
await markDefaultAgent(agentId);
|
|
137
|
+
const before = await sessionCount();
|
|
162
138
|
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}),
|
|
170
|
-
);
|
|
139
|
+
const session = await sessionCommand.create({
|
|
140
|
+
apiVersion: API_VERSION,
|
|
141
|
+
kind: "Session",
|
|
142
|
+
metadata: { name: "Session-first", org: ORG },
|
|
143
|
+
spec: {},
|
|
144
|
+
});
|
|
171
145
|
|
|
172
|
-
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const resolvedInstanceId = agent.status?.defaultInstanceId ?? "";
|
|
178
|
-
expect(resolvedInstanceId).not.toBe("");
|
|
179
|
-
expect(gate.runGateChecks).toHaveLength(1);
|
|
180
|
-
expect(gate.runGateChecks[0].resourceId).toBe(resolvedInstanceId);
|
|
181
|
-
expect(err.rawMessage).toBe(
|
|
182
|
-
runAgentInstanceDeniedMessage(resolvedInstanceId),
|
|
183
|
-
);
|
|
146
|
+
// No instance, no blueprint to spend, no target: the gate is silent
|
|
147
|
+
// and the denying authorizer is never asked.
|
|
148
|
+
expect(gate.runGateChecks).toHaveLength(0);
|
|
149
|
+
expect(session.spec?.agentInstanceId).toBe("");
|
|
150
|
+
expect(await sessionCount()).toBe(before + 1);
|
|
184
151
|
});
|
|
185
152
|
|
|
186
153
|
it("not-found answers NOT_FOUND naming the instance (the stigmer#224 order)", async () => {
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pins the session domain against Go's pkg/domain/session tests — through
|
|
3
3
|
* the REAL stack: a composed server on an ephemeral port, a native gRPC
|
|
4
|
-
* client, the full interceptor chain
|
|
5
|
-
* agentinstance CREATE edge (session create's default-instance self-heal
|
|
6
|
-
* runs through the router transport).
|
|
4
|
+
* client, and the full interceptor chain.
|
|
7
5
|
*
|
|
8
6
|
* The load-bearing pins:
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
11
|
-
* session
|
|
12
|
-
*
|
|
13
|
-
* not-public arms carry Go WrapError's exact double-wrapped copy;
|
|
7
|
+
* - an empty agent_instance_id is the built-in assistant: create stores
|
|
8
|
+
* it empty (nothing resolves it into an instance), an explicit id is
|
|
9
|
+
* kept as given, and a session may gain an agent or drop back to the
|
|
10
|
+
* assistant on update — the instance is not an immutable field;
|
|
14
11
|
* - harness immutability locks only once harness_state_id is non-empty,
|
|
15
12
|
* with UNSPECIFIED==NATIVE equivalence in both directions and the
|
|
16
13
|
* exact FAILED_PRECONDITION copy;
|
|
@@ -39,11 +36,8 @@ import { createGrpcTransport } from "@connectrpc/connect-node";
|
|
|
39
36
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
40
37
|
|
|
41
38
|
import { AgentCommandController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/command_pb";
|
|
42
|
-
import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
43
39
|
import { AgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
44
40
|
import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
45
|
-
import { AgentInstanceSchema } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/api_pb";
|
|
46
|
-
import { AgentInstanceQueryController } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/query_pb";
|
|
47
41
|
import { SessionCommandController } from "@stigmer/protos/ai/stigmer/agentic/session/v1/command_pb";
|
|
48
42
|
import { SessionQueryController } from "@stigmer/protos/ai/stigmer/agentic/session/v1/query_pb";
|
|
49
43
|
import { SessionSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
|
|
@@ -53,7 +47,6 @@ import {
|
|
|
53
47
|
ExecutionTarget,
|
|
54
48
|
Harness,
|
|
55
49
|
} from "@stigmer/protos/ai/stigmer/agentic/session/v1/enum_pb";
|
|
56
|
-
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
57
50
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
58
51
|
|
|
59
52
|
import { loadConfig } from "../../../boot/config.js";
|
|
@@ -70,10 +63,6 @@ import {
|
|
|
70
63
|
ROUTING_GLOBAL,
|
|
71
64
|
newConfigFromEnv,
|
|
72
65
|
} from "../../agentexecution/temporal/config.js";
|
|
73
|
-
import {
|
|
74
|
-
DEFAULT_AGENT_LABEL,
|
|
75
|
-
DEFAULT_AGENT_LABEL_VALUE,
|
|
76
|
-
} from "../../agent/defaultagent.js";
|
|
77
66
|
import { newValidateExecutionTargetImmutabilityStep } from "../steps.js";
|
|
78
67
|
|
|
79
68
|
const silentLogger = createLogger({
|
|
@@ -92,7 +81,6 @@ let dir: string;
|
|
|
92
81
|
let server: ComposedServer;
|
|
93
82
|
let transport: Transport;
|
|
94
83
|
let agentCommand: Client<typeof AgentCommandController>;
|
|
95
|
-
let instanceQuery: Client<typeof AgentInstanceQueryController>;
|
|
96
84
|
let command: Client<typeof SessionCommandController>;
|
|
97
85
|
let query: Client<typeof SessionQueryController>;
|
|
98
86
|
|
|
@@ -120,7 +108,6 @@ beforeAll(async () => {
|
|
|
120
108
|
const port = await server.start();
|
|
121
109
|
transport = createGrpcTransport({ baseUrl: `http://127.0.0.1:${port}` });
|
|
122
110
|
agentCommand = createClient(AgentCommandController, transport);
|
|
123
|
-
instanceQuery = createClient(AgentInstanceQueryController, transport);
|
|
124
111
|
command = createClient(SessionCommandController, transport);
|
|
125
112
|
query = createClient(SessionQueryController, transport);
|
|
126
113
|
});
|
|
@@ -141,33 +128,6 @@ async function createAgent(name: string) {
|
|
|
141
128
|
});
|
|
142
129
|
}
|
|
143
130
|
|
|
144
|
-
/**
|
|
145
|
-
* Stamps the default-agent label (and optionally visibility_public)
|
|
146
|
-
* directly on the stored agent row — the reserved stigmer.ai/* label is
|
|
147
|
-
* operator-seeded state, not something the OSS write path guards, and the
|
|
148
|
-
* direct write keeps the test independent of create-time visibility rules.
|
|
149
|
-
*/
|
|
150
|
-
async function markDefaultAgent(
|
|
151
|
-
agentId: string,
|
|
152
|
-
opts: { public: boolean },
|
|
153
|
-
): Promise<void> {
|
|
154
|
-
const stored = await server.store.getResource(
|
|
155
|
-
ApiResourceKind.agent,
|
|
156
|
-
agentId,
|
|
157
|
-
AgentSchema,
|
|
158
|
-
);
|
|
159
|
-
stored.metadata!.labels[DEFAULT_AGENT_LABEL] = DEFAULT_AGENT_LABEL_VALUE;
|
|
160
|
-
if (opts.public) {
|
|
161
|
-
stored.metadata!.visibility = ApiResourceVisibility.visibility_public;
|
|
162
|
-
}
|
|
163
|
-
await server.store.saveResource(
|
|
164
|
-
ApiResourceKind.agent,
|
|
165
|
-
agentId,
|
|
166
|
-
AgentSchema,
|
|
167
|
-
stored,
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
131
|
let sessionCounter = 0;
|
|
172
132
|
async function createSession(
|
|
173
133
|
spec: Partial<Omit<SessionSpec, "$typeName">> = {},
|
|
@@ -252,108 +212,36 @@ async function grpcError(run: () => Promise<unknown>): Promise<ConnectError> {
|
|
|
252
212
|
}
|
|
253
213
|
}
|
|
254
214
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
expect(error.rawMessage).toBe(
|
|
263
|
-
"No default agent available. Ensure an agent with label " +
|
|
264
|
-
"stigmer.ai/default-agent=true and visibility_public exists: " +
|
|
265
|
-
"no default agent available on this platform: " +
|
|
266
|
-
"no agent labeled stigmer.ai/default-agent=true",
|
|
267
|
-
);
|
|
215
|
+
describe("session create and update — the built-in assistant", () => {
|
|
216
|
+
it("stores an empty agent_instance_id as given: no agent is resolved for the session", async () => {
|
|
217
|
+
const session = await createSession({});
|
|
218
|
+
expect(session.spec?.agentInstanceId).toBe("");
|
|
219
|
+
const fetched = await query.get({ value: session.metadata!.id });
|
|
220
|
+
expect(fetched.spec?.agentInstanceId).toBe("");
|
|
221
|
+
await command.delete({ value: session.metadata!.id });
|
|
268
222
|
});
|
|
269
223
|
|
|
270
|
-
it("
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const error = await grpcError(() => createSession({}));
|
|
275
|
-
expect(error.code).toBe(Code.FailedPrecondition);
|
|
276
|
-
expect(error.rawMessage).toBe(
|
|
277
|
-
"Default agent exists but is not visibility_public: " +
|
|
278
|
-
"agents labeled stigmer.ai/default-agent=true exist but none is visibility_public",
|
|
279
|
-
);
|
|
280
|
-
} finally {
|
|
281
|
-
await agentCommand.delete({ value: agent.metadata!.id });
|
|
282
|
-
}
|
|
224
|
+
it("keeps an explicit agent_instance_id as given", async () => {
|
|
225
|
+
const session = await createSession({ agentInstanceId: "ain_explicit" });
|
|
226
|
+
expect(session.spec?.agentInstanceId).toBe("ain_explicit");
|
|
227
|
+
await command.delete({ value: session.metadata!.id });
|
|
283
228
|
});
|
|
284
229
|
|
|
285
|
-
it("
|
|
286
|
-
const
|
|
287
|
-
const defaultInstanceId = agent.status!.defaultInstanceId;
|
|
288
|
-
expect(defaultInstanceId).not.toBe("");
|
|
289
|
-
await markDefaultAgent(agent.metadata!.id, { public: true });
|
|
290
|
-
try {
|
|
291
|
-
const session = await createSession({});
|
|
292
|
-
// The resolve step wrote newState.spec (the clone Persist saves) —
|
|
293
|
-
// the id survives the round trip to storage.
|
|
294
|
-
expect(session.spec?.agentInstanceId).toBe(defaultInstanceId);
|
|
295
|
-
const fetched = await query.get({ value: session.metadata!.id });
|
|
296
|
-
expect(fetched.spec?.agentInstanceId).toBe(defaultInstanceId);
|
|
297
|
-
await command.delete({ value: session.metadata!.id });
|
|
298
|
-
} finally {
|
|
299
|
-
await agentCommand.delete({ value: agent.metadata!.id });
|
|
300
|
-
}
|
|
301
|
-
});
|
|
230
|
+
it("lets a conversation gain an agent and drop back to the assistant on update", async () => {
|
|
231
|
+
const session = await createSession({});
|
|
302
232
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
// the legacy shape the self-heal path exists for.
|
|
306
|
-
const agentId = "agt_sessiontest_selfheal";
|
|
307
|
-
const seeded = create(AgentSchema, {
|
|
308
|
-
apiVersion: API_VERSION,
|
|
309
|
-
kind: "Agent",
|
|
310
|
-
metadata: {
|
|
311
|
-
id: agentId,
|
|
312
|
-
name: "Self Heal Default Agent",
|
|
313
|
-
slug: "self-heal-default-agent",
|
|
314
|
-
org: ORG,
|
|
315
|
-
visibility: ApiResourceVisibility.visibility_public,
|
|
316
|
-
labels: { [DEFAULT_AGENT_LABEL]: DEFAULT_AGENT_LABEL_VALUE },
|
|
317
|
-
},
|
|
318
|
-
spec: { instructions: "seeded for the self-heal path" },
|
|
319
|
-
});
|
|
320
|
-
await server.store.saveResource(
|
|
321
|
-
ApiResourceKind.agent,
|
|
322
|
-
agentId,
|
|
323
|
-
AgentSchema,
|
|
324
|
-
seeded,
|
|
233
|
+
const withAgent = await command.update(
|
|
234
|
+
updateInput(session, { agentInstanceId: "ain_picked" }),
|
|
325
235
|
);
|
|
326
|
-
|
|
327
|
-
const session = await createSession({});
|
|
328
|
-
const resolvedId = session.spec?.agentInstanceId ?? "";
|
|
329
|
-
expect(resolvedId).not.toBe("");
|
|
330
|
-
|
|
331
|
-
// The created instance is real (rode the in-process CREATE edge and
|
|
332
|
-
// its full pipeline) and follows the defaultinstance factory shape.
|
|
333
|
-
const instance = await instanceQuery.get({ value: resolvedId });
|
|
334
|
-
expect(instance.spec?.agentId).toBe(agentId);
|
|
335
|
-
expect(instance.metadata?.slug).toBe("self-heal-default-agent-default");
|
|
336
|
-
|
|
337
|
-
// The agent's server-owned pointer was persisted (explicit agent
|
|
338
|
-
// kind — the pipeline's own ctx kind is session).
|
|
339
|
-
const storedAgent = await server.store.getResource(
|
|
340
|
-
ApiResourceKind.agent,
|
|
341
|
-
agentId,
|
|
342
|
-
AgentSchema,
|
|
343
|
-
);
|
|
344
|
-
expect(storedAgent.status?.defaultInstanceId).toBe(resolvedId);
|
|
236
|
+
expect(withAgent.spec?.agentInstanceId).toBe("ain_picked");
|
|
345
237
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
238
|
+
const dropped = await command.update(
|
|
239
|
+
updateInput(withAgent, { agentInstanceId: "" }),
|
|
240
|
+
);
|
|
241
|
+
expect(dropped.spec?.agentInstanceId).toBe("");
|
|
242
|
+
const fetched = await query.get({ value: session.metadata!.id });
|
|
243
|
+
expect(fetched.spec?.agentInstanceId).toBe("");
|
|
351
244
|
|
|
352
|
-
it("is a no-op when agent_instance_id is provided (no default agent needed)", async () => {
|
|
353
|
-
// No labeled agent exists at this point; an explicit id must not
|
|
354
|
-
// trigger resolution.
|
|
355
|
-
const session = await createSession({ agentInstanceId: "ain_explicit" });
|
|
356
|
-
expect(session.spec?.agentInstanceId).toBe("ain_explicit");
|
|
357
245
|
await command.delete({ value: session.metadata!.id });
|
|
358
246
|
});
|
|
359
247
|
});
|
|
@@ -716,67 +604,3 @@ describe("session delete — active-execution guard and cascade", () => {
|
|
|
716
604
|
await command.delete({ value: survivor.metadata!.id });
|
|
717
605
|
});
|
|
718
606
|
});
|
|
719
|
-
|
|
720
|
-
describe("session create — CreateAsSystem failure wire contract (oss#852)", () => {
|
|
721
|
-
it("answers the inner code with Go's %w-wrapped transport-formatted message", async () => {
|
|
722
|
-
// A labeled public default agent with NO status.default_instance_id
|
|
723
|
-
// (the self-heal shape) whose deterministic default-instance slug is
|
|
724
|
-
// already occupied: the in-process CreateAsSystem hits CheckDuplicate's
|
|
725
|
-
// AlreadyExists. Go wraps that error with fmt.Errorf("%w"), so the
|
|
726
|
-
// wire message embeds grpc-go's `rpc error: code = X desc = ...`
|
|
727
|
-
// rendering of the inner error — the leak filed as stigmer/stigmer#852,
|
|
728
|
-
// mirrored byte-for-byte until the both-editions post-cutover fix.
|
|
729
|
-
const agentId = "agt_wrap_session";
|
|
730
|
-
const seeded = create(AgentSchema, {
|
|
731
|
-
apiVersion: API_VERSION,
|
|
732
|
-
kind: "Agent",
|
|
733
|
-
metadata: {
|
|
734
|
-
id: agentId,
|
|
735
|
-
name: "Wrap Mirror Default Agent",
|
|
736
|
-
slug: "wrap-mirror-default-agent",
|
|
737
|
-
org: ORG,
|
|
738
|
-
visibility: ApiResourceVisibility.visibility_public,
|
|
739
|
-
labels: { [DEFAULT_AGENT_LABEL]: DEFAULT_AGENT_LABEL_VALUE },
|
|
740
|
-
},
|
|
741
|
-
spec: { instructions: "seeded for the wrapped-error mirror test" },
|
|
742
|
-
});
|
|
743
|
-
await server.store.saveResource(
|
|
744
|
-
ApiResourceKind.agent,
|
|
745
|
-
agentId,
|
|
746
|
-
AgentSchema,
|
|
747
|
-
seeded,
|
|
748
|
-
);
|
|
749
|
-
const occupier = create(AgentInstanceSchema, {
|
|
750
|
-
apiVersion: API_VERSION,
|
|
751
|
-
kind: "AgentInstance",
|
|
752
|
-
metadata: {
|
|
753
|
-
id: "ain_wrap_occupied",
|
|
754
|
-
name: "wrap-mirror-default-agent-default",
|
|
755
|
-
slug: "wrap-mirror-default-agent-default",
|
|
756
|
-
org: ORG,
|
|
757
|
-
},
|
|
758
|
-
spec: { agentId },
|
|
759
|
-
});
|
|
760
|
-
await server.store.saveResource(
|
|
761
|
-
ApiResourceKind.agent_instance,
|
|
762
|
-
"ain_wrap_occupied",
|
|
763
|
-
AgentInstanceSchema,
|
|
764
|
-
occupier,
|
|
765
|
-
);
|
|
766
|
-
|
|
767
|
-
try {
|
|
768
|
-
const error = await grpcError(() => createSession({}));
|
|
769
|
-
expect(error.code).toBe(Code.AlreadyExists);
|
|
770
|
-
expect(error.rawMessage).toBe(
|
|
771
|
-
"failed to create default instance for default agent: rpc error: " +
|
|
772
|
-
"code = AlreadyExists desc = AgentInstance already exists: slug " +
|
|
773
|
-
"'wrap-mirror-default-agent-default' in org 'acme' " +
|
|
774
|
-
"(id: ain_wrap_occupied)",
|
|
775
|
-
);
|
|
776
|
-
} finally {
|
|
777
|
-
// The cascade sweeps the occupying instance (spec.agent_id match)
|
|
778
|
-
// and removes the default-agent label with the agent.
|
|
779
|
-
await agentCommand.delete({ value: agentId });
|
|
780
|
-
}
|
|
781
|
-
});
|
|
782
|
-
});
|
|
@@ -110,11 +110,9 @@ import {
|
|
|
110
110
|
newListAllSessionsStep,
|
|
111
111
|
newRecordHarnessStateHistoryStep,
|
|
112
112
|
newRejectDeleteWithActiveExecutionsStep,
|
|
113
|
-
newResolveDefaultAgentInstanceStep,
|
|
114
113
|
newValidateExecutionTargetImmutabilityStep,
|
|
115
114
|
newValidateHarnessImmutabilityStep,
|
|
116
115
|
} from "./steps.js";
|
|
117
|
-
import type { AgentInstanceCreatorProvider } from "./steps.js";
|
|
118
116
|
|
|
119
117
|
export interface SessionControllerDeps {
|
|
120
118
|
readonly store: Store;
|
|
@@ -129,12 +127,6 @@ export interface SessionControllerDeps {
|
|
|
129
127
|
* same deployment default dispatch uses (oss#397).
|
|
130
128
|
*/
|
|
131
129
|
readonly temporalConfig: AgentExecutionTemporalConfig;
|
|
132
|
-
/**
|
|
133
|
-
* The agentinstance in-process CREATE edge — a lazy provider because the
|
|
134
|
-
* routes↔clients definition cycle resolves at request time (the ratified
|
|
135
|
-
* DI story, DD-002).
|
|
136
|
-
*/
|
|
137
|
-
readonly agentInstanceCreator: AgentInstanceCreatorProvider;
|
|
138
130
|
/** The composed slot registrations — this domain's create slot (O4). */
|
|
139
131
|
readonly gateSteps: ResolvedGateSteps;
|
|
140
132
|
/**
|
|
@@ -177,23 +169,19 @@ function kindOf(ctx: HandlerContext): ApiResourceKind {
|
|
|
177
169
|
}
|
|
178
170
|
|
|
179
171
|
/**
|
|
180
|
-
* Create — chain per Go buildCreatePipeline.
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
* the
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
* default-instance creation INSIDE the resolution step still precedes it —
|
|
189
|
-
* the inherited pre-gate side effect recorded below, unchanged here.
|
|
172
|
+
* Create — chain per Go buildCreatePipeline. AuthorizeRunTarget (the run
|
|
173
|
+
* gate, P1 sp.run-gate) runs right after Authorize: the instance the
|
|
174
|
+
* session binds to is the caller's own spec, so a caller who may not run
|
|
175
|
+
* that instance is refused before validation reads anything further and
|
|
176
|
+
* before any step the chain owns side-effects. An empty agent_instance_id
|
|
177
|
+
* is the built-in assistant, for which the gate makes no target check:
|
|
178
|
+
* Authorize's can_create_session on the organization already admitted the
|
|
179
|
+
* conversation (sessionRunTarget's header).
|
|
190
180
|
*
|
|
191
181
|
* The pre-side-effect gate slot splices before Persist, after the last
|
|
192
182
|
* pure step (O4 plan-gate ruling Q2, mirroring the Java baseline's
|
|
193
|
-
* post-resolution gate position).
|
|
194
|
-
*
|
|
195
|
-
* created default instance behind; inherited Java semantics, not a new
|
|
196
|
-
* compromise.
|
|
183
|
+
* post-resolution gate position). Every step above it is pure, so a gate
|
|
184
|
+
* refusal leaves nothing behind.
|
|
197
185
|
*/
|
|
198
186
|
async function createSession(
|
|
199
187
|
deps: SessionControllerDeps,
|
|
@@ -213,14 +201,6 @@ async function createSession(
|
|
|
213
201
|
.addStep(
|
|
214
202
|
newAuthorizeStep(SessionCommandController.method.create, deps.authorizer),
|
|
215
203
|
)
|
|
216
|
-
.addStep(
|
|
217
|
-
newResolveDefaultAgentInstanceStep(
|
|
218
|
-
deps.store,
|
|
219
|
-
deps.agentInstanceCreator,
|
|
220
|
-
deps.logger,
|
|
221
|
-
deps.authorizationLifecycle,
|
|
222
|
-
),
|
|
223
|
-
)
|
|
224
204
|
.addStep(newAuthorizeRunTargetStep(deps.authorizer, sessionRunTarget))
|
|
225
205
|
.addStep(newValidateProtoStep())
|
|
226
206
|
.addStep(newValidateVisibilityStep())
|