@stigmer/runner 3.12.8 → 3.13.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/.build-fingerprint +1 -1
- package/dist/activities/call-agent.d.ts +3 -2
- package/dist/activities/call-agent.js +13 -4
- package/dist/activities/call-agent.js.map +1 -1
- package/dist/activities/discover-mcp-server.js +1 -1
- package/dist/activities/execute-cursor/fetch-interceptor.js +29 -16
- package/dist/activities/execute-cursor/fetch-interceptor.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +14 -0
- package/dist/activities/execute-cursor/index.js +55 -5
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/message-translator.d.ts +15 -9
- package/dist/activities/execute-cursor/message-translator.js +15 -9
- package/dist/activities/execute-cursor/message-translator.js.map +1 -1
- package/dist/activities/execute-deep-agent/attachment-injector.d.ts +1 -1
- package/dist/activities/execute-deep-agent/attachment-injector.js +2 -2
- package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.js +9 -0
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +10 -1
- package/dist/activities/execute-deep-agent/setup.js +20 -3
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/client/stigmer-client.js +7 -2
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/payload-codecs.d.ts +1 -1
- package/dist/payload-codecs.js +6 -4
- package/dist/payload-codecs.js.map +1 -1
- package/dist/runner-manager.js +1 -1
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +1 -1
- package/dist/runner.js.map +1 -1
- package/dist/shared/memory-retrieval.d.ts +126 -0
- package/dist/shared/memory-retrieval.js +293 -0
- package/dist/shared/memory-retrieval.js.map +1 -0
- package/dist/shared/runner-credential-keys.js +2 -1
- package/dist/shared/runner-credential-keys.js.map +1 -1
- package/dist/shared/zip-extract.js +1 -1
- package/dist/shared/zip-extract.js.map +1 -1
- package/package.json +4 -2
- package/src/__tests__/history-encryption-e2e.test.ts +1 -1
- package/src/activities/call-agent.ts +14 -4
- package/src/activities/discover-mcp-server.ts +1 -1
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +40 -1
- package/src/activities/execute-cursor/__tests__/fetch-interceptor.test.ts +49 -15
- package/src/activities/execute-cursor/__tests__/skill-resolver.test.ts +1 -1
- package/src/activities/execute-cursor/fetch-interceptor.ts +37 -16
- package/src/activities/execute-cursor/index.ts +58 -5
- package/src/activities/execute-cursor/message-translator.ts +15 -9
- package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +3 -2
- package/src/activities/execute-deep-agent/attachment-injector.ts +2 -2
- package/src/activities/execute-deep-agent/index.ts +10 -0
- package/src/activities/execute-deep-agent/setup.ts +34 -6
- package/src/client/stigmer-client.ts +7 -2
- package/src/payload-codecs.ts +12 -9
- package/src/runner-manager.ts +1 -1
- package/src/runner.ts +1 -1
- package/src/shared/__tests__/memory-retrieval.test.ts +310 -0
- package/src/shared/__tests__/skill-mount.test.ts +1 -1
- package/src/shared/__tests__/skill-writer.test.ts +1 -1
- package/src/shared/__tests__/zip-extract.test.ts +1 -1
- package/src/shared/memory-retrieval.ts +383 -0
- package/src/shared/runner-credential-keys.ts +2 -1
- package/src/shared/zip-extract.ts +1 -1
- package/dist/claimcheck/compressor.d.ts +0 -2
- package/dist/claimcheck/compressor.js +0 -8
- package/dist/claimcheck/compressor.js.map +0 -1
- package/dist/claimcheck/config.d.ts +0 -7
- package/dist/claimcheck/config.js +0 -10
- package/dist/claimcheck/config.js.map +0 -1
- package/dist/claimcheck/index.d.ts +0 -3
- package/dist/claimcheck/index.js +0 -4
- package/dist/claimcheck/index.js.map +0 -1
- package/dist/claimcheck/payload-codec.d.ts +0 -23
- package/dist/claimcheck/payload-codec.js +0 -105
- package/dist/claimcheck/payload-codec.js.map +0 -1
- package/dist/encryption/config.d.ts +0 -64
- package/dist/encryption/config.js +0 -113
- package/dist/encryption/config.js.map +0 -1
- package/dist/encryption/index.d.ts +0 -3
- package/dist/encryption/index.js +0 -3
- package/dist/encryption/index.js.map +0 -1
- package/dist/encryption/payload-codec.d.ts +0 -41
- package/dist/encryption/payload-codec.js +0 -130
- package/dist/encryption/payload-codec.js.map +0 -1
- package/dist/shared/zip-structure.d.ts +0 -61
- package/dist/shared/zip-structure.js +0 -128
- package/dist/shared/zip-structure.js.map +0 -1
- package/src/__test-utils__/zip-fixtures.ts +0 -206
- package/src/__tests__/claimcheck-codec.test.ts +0 -256
- package/src/__tests__/encryption-codec.test.ts +0 -287
- package/src/__tests__/fixtures/encrypted-payload-fixture.json +0 -15
- package/src/claimcheck/compressor.ts +0 -9
- package/src/claimcheck/config.ts +0 -20
- package/src/claimcheck/index.ts +0 -3
- package/src/claimcheck/payload-codec.ts +0 -139
- package/src/encryption/config.ts +0 -161
- package/src/encryption/index.ts +0 -3
- package/src/encryption/payload-codec.ts +0 -152
- package/src/shared/zip-structure.ts +0 -181
|
@@ -234,7 +234,10 @@ export class StigmerClient {
|
|
|
234
234
|
// scoped-token exchange itself requires the embedded_runner
|
|
235
235
|
// bootstrap credential (a desktop runner's control-plane token is
|
|
236
236
|
// the user's own Auth0 token, which the server correctly treats as
|
|
237
|
-
// a browsing user)
|
|
237
|
+
// a browsing user); and the workflow child-execution create stamps
|
|
238
|
+
// the platform's workflow lineage labels, which cloud's
|
|
239
|
+
// reserved-label guard and environment composer accept only from
|
|
240
|
+
// runner-class callers — a user-token create is rejected outright.
|
|
238
241
|
//
|
|
239
242
|
// 3. Everything else uses the control-plane token. Falls through
|
|
240
243
|
// unchanged when no runner token exists (OSS/local, where the
|
|
@@ -246,7 +249,9 @@ export class StigmerClient {
|
|
|
246
249
|
const usesRunnerCredential =
|
|
247
250
|
req.service.typeName === ExecutionContextQueryController.typeName ||
|
|
248
251
|
(req.service.typeName === PlatformQueryController.typeName &&
|
|
249
|
-
req.method.name === PlatformQueryController.method.getRunnerScopedToken.name)
|
|
252
|
+
req.method.name === PlatformQueryController.method.getRunnerScopedToken.name) ||
|
|
253
|
+
(req.service.typeName === AgentExecutionCommandController.typeName &&
|
|
254
|
+
req.method.name === AgentExecutionCommandController.method.create.name);
|
|
250
255
|
const token =
|
|
251
256
|
(usesRunnerCredential ? this.runnerTokenRef?.current : null)
|
|
252
257
|
?? this.tokenRef?.current
|
package/src/payload-codecs.ts
CHANGED
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
* decode: claim-check → decrypt (restore the blob, then decrypt)
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import type { BootstrapKeyMaterial } from "@stigmer/temporal-codecs";
|
|
15
16
|
import type { PayloadCodec } from "@temporalio/common";
|
|
16
17
|
import type { Config } from "./config.js";
|
|
17
|
-
import type { BootstrapKeyMaterial } from "./encryption/config.js";
|
|
18
18
|
import { getRunnerSecret } from "./shared/runner-credential-store.js";
|
|
19
19
|
|
|
20
20
|
export async function createPayloadCodecs(
|
|
@@ -23,12 +23,18 @@ export async function createPayloadCodecs(
|
|
|
23
23
|
): Promise<PayloadCodec[] | undefined> {
|
|
24
24
|
const codecs: PayloadCodec[] = [];
|
|
25
25
|
|
|
26
|
-
const {
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const {
|
|
27
|
+
loadPayloadEncryptionConfig,
|
|
28
|
+
EncryptionPayloadCodec,
|
|
29
|
+
loadClaimcheckConfig,
|
|
30
|
+
ClaimcheckPayloadCodec,
|
|
31
|
+
} = await import("@stigmer/temporal-codecs");
|
|
29
32
|
// Env-configured keys win outright; server-managed (bootstrap) keys apply
|
|
30
|
-
// only when the env is silent — see encryption/config.ts for the
|
|
31
|
-
|
|
33
|
+
// only when the env is silent — see the lib's encryption/config.ts for the
|
|
34
|
+
// rationale. Key VALUES resolve through getRunnerSecret (the #508 boot
|
|
35
|
+
// capture moves them out of process.env — agent shells must not read
|
|
36
|
+
// them), which is why the loader takes the reader as an argument.
|
|
37
|
+
const encryptionConfig = loadPayloadEncryptionConfig(getRunnerSecret, bootstrapKeys);
|
|
32
38
|
if (encryptionConfig) {
|
|
33
39
|
codecs.push(new EncryptionPayloadCodec(encryptionConfig));
|
|
34
40
|
const source = getRunnerSecret("STIGMER_PAYLOAD_ENCRYPTION_KEY") ? "env" : "bootstrap";
|
|
@@ -41,9 +47,6 @@ export async function createPayloadCodecs(
|
|
|
41
47
|
);
|
|
42
48
|
}
|
|
43
49
|
|
|
44
|
-
const { loadClaimcheckConfig, ClaimcheckPayloadCodec } = await import(
|
|
45
|
-
"./claimcheck/index.js"
|
|
46
|
-
);
|
|
47
50
|
const claimcheckConfig = loadClaimcheckConfig();
|
|
48
51
|
if (claimcheckConfig.enabled) {
|
|
49
52
|
const { loadArtifactStorageConfig, createArtifactStorage } = await import(
|
package/src/runner-manager.ts
CHANGED
|
@@ -789,7 +789,7 @@ async function createAllActivities(config: Config): Promise<WorkerActivities> {
|
|
|
789
789
|
...createCallGrpcActivities(),
|
|
790
790
|
...createCallFunctionActivities(),
|
|
791
791
|
...createCallLlmActivities(),
|
|
792
|
-
...createCallAgentActivities(),
|
|
792
|
+
...createCallAgentActivities(config),
|
|
793
793
|
...createCallAgentStatusActivities(),
|
|
794
794
|
...createRunCommandActivities(),
|
|
795
795
|
...createHydrateWorkflowActivities(config),
|
package/src/runner.ts
CHANGED
|
@@ -524,7 +524,7 @@ async function createAllActivities(config: Config): Promise<WorkerActivities> {
|
|
|
524
524
|
...createCallGrpcActivities(),
|
|
525
525
|
...createCallFunctionActivities(),
|
|
526
526
|
...createCallLlmActivities(),
|
|
527
|
-
...createCallAgentActivities(),
|
|
527
|
+
...createCallAgentActivities(config),
|
|
528
528
|
...createCallAgentStatusActivities(),
|
|
529
529
|
...createRunCommandActivities(),
|
|
530
530
|
...createHydrateWorkflowActivities(config),
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the memory-retrieval module (stigmer/stigmer#293 Phase 3a,
|
|
3
|
+
* DD-008). What is pinned here, path by path:
|
|
4
|
+
*
|
|
5
|
+
* - The activation threshold: selection runs ONLY above RETRIEVAL_K
|
|
6
|
+
* candidates; at or below it there is NO embeddings call (most users
|
|
7
|
+
* never touch the embeddings API — the DD-008 D3 activation contract).
|
|
8
|
+
* - Top-k correctness and the presentation contract: selection is by
|
|
9
|
+
* relevance, presentation is snapshot order, ties break toward the
|
|
10
|
+
* lower snapshot index (deterministic across invocations).
|
|
11
|
+
* - The failure posture: every degraded path — no embedder, embed error,
|
|
12
|
+
* malformed response — injects wholesale with an honest
|
|
13
|
+
* selection_active=false report, never a throw into prompt build.
|
|
14
|
+
* - The written-once replay: a prior report (either polarity) replays
|
|
15
|
+
* without an embeddings call, so a re-invocation can never inject a
|
|
16
|
+
* different subset than the first invocation did.
|
|
17
|
+
* - The report contract: a report exists exactly when facts are injected;
|
|
18
|
+
* no injection, no report (absent report = wholesale by construction).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
22
|
+
import { create } from "@bufbuild/protobuf";
|
|
23
|
+
import { RecalledMemoriesSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
|
|
24
|
+
import { RecalledMemoriesReportSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
selectRecalledFacts,
|
|
28
|
+
RETRIEVAL_K,
|
|
29
|
+
EMBEDDING_MODEL,
|
|
30
|
+
QUERY_MAX_CHARS,
|
|
31
|
+
type EmbedFn,
|
|
32
|
+
} from "../memory-retrieval.js";
|
|
33
|
+
|
|
34
|
+
const QUERY = "How should I deploy the payments service?";
|
|
35
|
+
|
|
36
|
+
/** Snapshot of `count` facts: mem_0..mem_{count-1}, "fact 0".."fact N". */
|
|
37
|
+
function snapshot(count: number) {
|
|
38
|
+
return create(RecalledMemoriesSchema, {
|
|
39
|
+
enabled: true,
|
|
40
|
+
facts: Array.from({ length: count }, (_, i) => ({
|
|
41
|
+
memoryId: `mem_${i}`,
|
|
42
|
+
content: `fact ${i}`,
|
|
43
|
+
})),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A deterministic embedder over 2D unit vectors: the query embeds to
|
|
49
|
+
* [1, 0]; fact i embeds to an angle that grows with i, so relevance order
|
|
50
|
+
* is exactly snapshot order (fact 0 most similar) unless a test overrides
|
|
51
|
+
* per-input vectors.
|
|
52
|
+
*/
|
|
53
|
+
function angularEmbedder(overrides?: Map<string, number[]>): EmbedFn & {
|
|
54
|
+
calls: string[][];
|
|
55
|
+
} {
|
|
56
|
+
const calls: string[][] = [];
|
|
57
|
+
const fn: EmbedFn = async (inputs) => {
|
|
58
|
+
calls.push([...inputs]);
|
|
59
|
+
return inputs.map((input, position) => {
|
|
60
|
+
const override = overrides?.get(input);
|
|
61
|
+
if (override) return override;
|
|
62
|
+
if (position === 0) return [1, 0];
|
|
63
|
+
const angle = (position / (inputs.length + 1)) * (Math.PI / 2);
|
|
64
|
+
return [Math.cos(angle), Math.sin(angle)];
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
return Object.assign(fn, { calls });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function baseOptions(embed: EmbedFn) {
|
|
71
|
+
return {
|
|
72
|
+
proxyEndpoint: null,
|
|
73
|
+
stigmerToken: null,
|
|
74
|
+
executionId: "exec_test",
|
|
75
|
+
embed,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
afterEach(() => {
|
|
80
|
+
vi.unstubAllEnvs();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("selectRecalledFacts — no injection, no report", () => {
|
|
84
|
+
it("answers nothing for an absent snapshot (pre-Phase-2 executions)", async () => {
|
|
85
|
+
const embed = angularEmbedder();
|
|
86
|
+
const result = await selectRecalledFacts(undefined, QUERY, baseOptions(embed));
|
|
87
|
+
expect(result.content).toBeUndefined();
|
|
88
|
+
expect(result.report).toBeUndefined();
|
|
89
|
+
expect(embed.calls).toHaveLength(0);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("answers nothing when recall is disabled or enabled-with-zero-facts", async () => {
|
|
93
|
+
const embed = angularEmbedder();
|
|
94
|
+
const disabled = create(RecalledMemoriesSchema, {
|
|
95
|
+
enabled: false,
|
|
96
|
+
facts: [{ memoryId: "mem_0", content: "fact 0" }],
|
|
97
|
+
});
|
|
98
|
+
expect(
|
|
99
|
+
(await selectRecalledFacts(disabled, QUERY, baseOptions(embed))).report,
|
|
100
|
+
).toBeUndefined();
|
|
101
|
+
const empty = create(RecalledMemoriesSchema, { enabled: true });
|
|
102
|
+
expect(
|
|
103
|
+
(await selectRecalledFacts(empty, QUERY, baseOptions(embed))).report,
|
|
104
|
+
).toBeUndefined();
|
|
105
|
+
expect(embed.calls).toHaveLength(0);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("selectRecalledFacts — activation threshold (DD-008 D3)", () => {
|
|
110
|
+
it("injects wholesale with NO embeddings call at exactly k candidates", async () => {
|
|
111
|
+
const embed = angularEmbedder();
|
|
112
|
+
const result = await selectRecalledFacts(
|
|
113
|
+
snapshot(RETRIEVAL_K),
|
|
114
|
+
QUERY,
|
|
115
|
+
baseOptions(embed),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
expect(embed.calls).toHaveLength(0);
|
|
119
|
+
expect(result.content?.facts).toHaveLength(RETRIEVAL_K);
|
|
120
|
+
expect(result.report?.selectionActive).toBe(false);
|
|
121
|
+
expect(result.report?.injectedMemoryIds).toEqual([]);
|
|
122
|
+
expect(result.report?.embeddingModel).toBe("");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("activates selection at k+1: one batched call, k facts injected", async () => {
|
|
126
|
+
const embed = angularEmbedder();
|
|
127
|
+
const result = await selectRecalledFacts(
|
|
128
|
+
snapshot(RETRIEVAL_K + 1),
|
|
129
|
+
QUERY,
|
|
130
|
+
baseOptions(embed),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
expect(embed.calls).toHaveLength(1);
|
|
134
|
+
expect(embed.calls[0]).toHaveLength(RETRIEVAL_K + 2); // query + all candidates
|
|
135
|
+
expect(embed.calls[0][0]).toBe(QUERY);
|
|
136
|
+
expect(result.content?.facts).toHaveLength(RETRIEVAL_K);
|
|
137
|
+
expect(result.report?.selectionActive).toBe(true);
|
|
138
|
+
expect(result.report?.injectedMemoryIds).toHaveLength(RETRIEVAL_K);
|
|
139
|
+
expect(result.report?.embeddingModel).toBe(EMBEDDING_MODEL);
|
|
140
|
+
// The angular embedder makes the LAST fact least relevant.
|
|
141
|
+
expect(result.report?.injectedMemoryIds).not.toContain(`mem_${RETRIEVAL_K}`);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("counts only renderable candidates toward the threshold (blank facts dropped, the read-boundary semantics)", async () => {
|
|
145
|
+
const embed = angularEmbedder();
|
|
146
|
+
const recalled = create(RecalledMemoriesSchema, {
|
|
147
|
+
enabled: true,
|
|
148
|
+
facts: [
|
|
149
|
+
...Array.from({ length: RETRIEVAL_K }, (_, i) => ({
|
|
150
|
+
memoryId: `mem_${i}`,
|
|
151
|
+
content: `fact ${i}`,
|
|
152
|
+
})),
|
|
153
|
+
{ memoryId: "mem_blank", content: " " },
|
|
154
|
+
],
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const result = await selectRecalledFacts(recalled, QUERY, baseOptions(embed));
|
|
158
|
+
|
|
159
|
+
expect(embed.calls).toHaveLength(0); // 20 renderable ≤ k → wholesale
|
|
160
|
+
expect(result.content?.facts).toHaveLength(RETRIEVAL_K);
|
|
161
|
+
expect(result.report?.selectionActive).toBe(false);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe("selectRecalledFacts — ranking and presentation", () => {
|
|
166
|
+
it("selects by relevance but presents in snapshot order, ids parallel to facts", async () => {
|
|
167
|
+
// Invert relevance: the LAST fact is the most similar, fact 0 the least.
|
|
168
|
+
const count = RETRIEVAL_K + 5;
|
|
169
|
+
const overrides = new Map<string, number[]>();
|
|
170
|
+
for (let i = 0; i < count; i++) {
|
|
171
|
+
const angle = ((count - i) / (count + 1)) * (Math.PI / 2);
|
|
172
|
+
overrides.set(`fact ${i}`, [Math.cos(angle), Math.sin(angle)]);
|
|
173
|
+
}
|
|
174
|
+
const embed = angularEmbedder(overrides);
|
|
175
|
+
|
|
176
|
+
const result = await selectRecalledFacts(
|
|
177
|
+
snapshot(count),
|
|
178
|
+
QUERY,
|
|
179
|
+
baseOptions(embed),
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// The 5 least relevant under inversion are the FIRST five snapshot facts.
|
|
183
|
+
const expectedIndices = Array.from({ length: RETRIEVAL_K }, (_, i) => i + 5);
|
|
184
|
+
expect(result.content?.facts).toEqual(expectedIndices.map((i) => `fact ${i}`));
|
|
185
|
+
expect(result.report?.injectedMemoryIds).toEqual(
|
|
186
|
+
expectedIndices.map((i) => `mem_${i}`),
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("breaks ties toward the lower snapshot index — equal scores never reorder across invocations", async () => {
|
|
191
|
+
// Every input embeds identically: all scores tie.
|
|
192
|
+
const count = RETRIEVAL_K + 3;
|
|
193
|
+
const overrides = new Map<string, number[]>([[QUERY, [1, 0]]]);
|
|
194
|
+
for (let i = 0; i < count; i++) {
|
|
195
|
+
overrides.set(`fact ${i}`, [1, 0]);
|
|
196
|
+
}
|
|
197
|
+
const embed = angularEmbedder(overrides);
|
|
198
|
+
|
|
199
|
+
const first = await selectRecalledFacts(snapshot(count), QUERY, baseOptions(embed));
|
|
200
|
+
const second = await selectRecalledFacts(snapshot(count), QUERY, baseOptions(embed));
|
|
201
|
+
|
|
202
|
+
const expected = Array.from({ length: RETRIEVAL_K }, (_, i) => `mem_${i}`);
|
|
203
|
+
expect(first.report?.injectedMemoryIds).toEqual(expected);
|
|
204
|
+
expect(second.report?.injectedMemoryIds).toEqual(expected);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("truncates the query at QUERY_MAX_CHARS — a giant paste must not 400 the whole batch", async () => {
|
|
208
|
+
const embed = angularEmbedder();
|
|
209
|
+
const giant = "x".repeat(QUERY_MAX_CHARS + 500);
|
|
210
|
+
|
|
211
|
+
await selectRecalledFacts(snapshot(RETRIEVAL_K + 1), giant, baseOptions(embed));
|
|
212
|
+
|
|
213
|
+
expect(embed.calls[0][0]).toHaveLength(QUERY_MAX_CHARS);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe("selectRecalledFacts — failure posture (never worse than Phase 2)", () => {
|
|
218
|
+
it("degrades to wholesale with an honest report when the embedder throws", async () => {
|
|
219
|
+
const embed: EmbedFn = async () => {
|
|
220
|
+
throw new Error("connect ETIMEDOUT");
|
|
221
|
+
};
|
|
222
|
+
const result = await selectRecalledFacts(
|
|
223
|
+
snapshot(RETRIEVAL_K + 1),
|
|
224
|
+
QUERY,
|
|
225
|
+
baseOptions(embed),
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
expect(result.content?.facts).toHaveLength(RETRIEVAL_K + 1);
|
|
229
|
+
expect(result.report?.selectionActive).toBe(false);
|
|
230
|
+
expect(result.report?.injectedMemoryIds).toEqual([]);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("degrades to wholesale when the embedder answers the wrong vector count", async () => {
|
|
234
|
+
const embed: EmbedFn = async () => [[1, 0]];
|
|
235
|
+
const result = await selectRecalledFacts(
|
|
236
|
+
snapshot(RETRIEVAL_K + 1),
|
|
237
|
+
QUERY,
|
|
238
|
+
baseOptions(embed),
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
expect(result.content?.facts).toHaveLength(RETRIEVAL_K + 1);
|
|
242
|
+
expect(result.report?.selectionActive).toBe(false);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("injects wholesale when no embedder resolves (no proxy, no OpenAI key — the Anthropic-only OSS posture)", async () => {
|
|
246
|
+
vi.stubEnv("OPENAI_API_KEY", "");
|
|
247
|
+
|
|
248
|
+
const result = await selectRecalledFacts(snapshot(RETRIEVAL_K + 1), QUERY, {
|
|
249
|
+
proxyEndpoint: null,
|
|
250
|
+
stigmerToken: null,
|
|
251
|
+
executionId: "exec_test",
|
|
252
|
+
// No embed injected: the module resolves — and finds — nothing.
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
expect(result.content?.facts).toHaveLength(RETRIEVAL_K + 1);
|
|
256
|
+
expect(result.report?.selectionActive).toBe(false);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
describe("selectRecalledFacts — written-once replay across re-invocations", () => {
|
|
261
|
+
it("replays a selection-active prior report without an embeddings call", async () => {
|
|
262
|
+
const embed = angularEmbedder();
|
|
263
|
+
const prior = create(RecalledMemoriesReportSchema, {
|
|
264
|
+
selectionActive: true,
|
|
265
|
+
injectedMemoryIds: ["mem_1", "mem_3"],
|
|
266
|
+
embeddingModel: EMBEDDING_MODEL,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const result = await selectRecalledFacts(snapshot(RETRIEVAL_K + 1), QUERY, {
|
|
270
|
+
...baseOptions(embed),
|
|
271
|
+
priorReport: prior,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
expect(embed.calls).toHaveLength(0);
|
|
275
|
+
expect(result.content?.facts).toEqual(["fact 1", "fact 3"]);
|
|
276
|
+
expect(result.report).toBe(prior);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("replays a wholesale prior report as wholesale — a recorded embed failure is never retried into a different prompt", async () => {
|
|
280
|
+
const embed = angularEmbedder();
|
|
281
|
+
const prior = create(RecalledMemoriesReportSchema, { selectionActive: false });
|
|
282
|
+
|
|
283
|
+
const result = await selectRecalledFacts(snapshot(RETRIEVAL_K + 1), QUERY, {
|
|
284
|
+
...baseOptions(embed),
|
|
285
|
+
priorReport: prior,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
expect(embed.calls).toHaveLength(0);
|
|
289
|
+
expect(result.content?.facts).toHaveLength(RETRIEVAL_K + 1);
|
|
290
|
+
expect(result.report?.selectionActive).toBe(false);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("re-selects when a recorded id does not resolve against the snapshot (structurally impossible, defended anyway)", async () => {
|
|
294
|
+
const embed = angularEmbedder();
|
|
295
|
+
const prior = create(RecalledMemoriesReportSchema, {
|
|
296
|
+
selectionActive: true,
|
|
297
|
+
injectedMemoryIds: ["mem_1", "mem_never_existed"],
|
|
298
|
+
embeddingModel: EMBEDDING_MODEL,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const result = await selectRecalledFacts(snapshot(RETRIEVAL_K + 1), QUERY, {
|
|
302
|
+
...baseOptions(embed),
|
|
303
|
+
priorReport: prior,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
expect(embed.calls).toHaveLength(1);
|
|
307
|
+
expect(result.report?.selectionActive).toBe(true);
|
|
308
|
+
expect(result.report?.injectedMemoryIds).toHaveLength(RETRIEVAL_K);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
downloadArtifact,
|
|
10
10
|
writeSkillMount,
|
|
11
11
|
} from "../skill-mount.js";
|
|
12
|
-
import { buildZip } from "
|
|
12
|
+
import { buildZip } from "@stigmer/zip-structure/testing";
|
|
13
13
|
|
|
14
14
|
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
15
15
|
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "../skill-writer.js";
|
|
13
13
|
import type { Skill } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
|
|
14
14
|
import type { ApiResourceReference } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
|
|
15
|
-
import { buildZip } from "
|
|
15
|
+
import { buildZip } from "@stigmer/zip-structure/testing";
|
|
16
16
|
|
|
17
17
|
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
18
18
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
import { extractZipFileEntries, type ZipFileEntry } from "../zip-extract.js";
|
|
3
|
-
import { buildZip } from "
|
|
3
|
+
import { buildZip } from "@stigmer/zip-structure/testing";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Decode entries for text-content assertions. The production contract is
|