@stigmer/runner 3.12.6 → 3.12.7
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-status.d.ts +14 -2
- package/dist/activities/call-agent-status.js +24 -7
- package/dist/activities/call-agent-status.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +10 -0
- package/dist/activities/execute-cursor/index.js +5 -0
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +12 -0
- package/dist/activities/execute-cursor/prompt-builder.js +11 -0
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +9 -0
- package/dist/activities/execute-deep-agent/prompt-builder.js +10 -0
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.js +2 -0
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/main.js +3 -0
- package/dist/main.js.map +1 -1
- package/dist/runner-manager.d.ts +2 -0
- package/dist/runner-manager.js +1 -0
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.d.ts +2 -0
- package/dist/runner.js +33 -6
- package/dist/runner.js.map +1 -1
- package/dist/shared/artifact-storage.js +7 -4
- package/dist/shared/artifact-storage.js.map +1 -1
- package/dist/shared/recalled-memories.d.ts +55 -0
- package/dist/shared/recalled-memories.js +70 -0
- package/dist/shared/recalled-memories.js.map +1 -0
- package/dist/workflows/call-agent-orchestrator.d.ts +14 -2
- package/dist/workflows/call-agent-orchestrator.js +55 -18
- package/dist/workflows/call-agent-orchestrator.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/golden-e2e.test.ts +1 -1
- package/src/activities/__tests__/call-agent-status.test.ts +30 -5
- package/src/activities/__tests__/classify-tool-approvals.test.ts +1 -0
- package/src/activities/__tests__/discover-mcp-server.test.ts +1 -0
- package/src/activities/call-agent-status.ts +25 -8
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +79 -0
- package/src/activities/execute-cursor/index.ts +15 -0
- package/src/activities/execute-cursor/prompt-builder.ts +28 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/index.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +48 -0
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
- package/src/activities/execute-deep-agent/prompt-builder.ts +22 -0
- package/src/activities/execute-deep-agent/setup.ts +4 -0
- package/src/config.ts +13 -0
- package/src/main.ts +3 -0
- package/src/runner-manager.ts +5 -0
- package/src/runner.ts +42 -6
- package/src/shared/__tests__/artifact-storage.test.ts +21 -0
- package/src/shared/__tests__/recalled-memories.test.ts +88 -0
- package/src/shared/artifact-storage.ts +9 -4
- package/src/shared/recalled-memories.ts +90 -0
- package/src/workflows/call-agent-orchestrator.ts +60 -24
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
} from "../../shared/caller-identity.js";
|
|
58
58
|
import { readSessionContext } from "../../shared/session-context.js";
|
|
59
59
|
import { readDeclaredPreferences } from "../../shared/declared-preferences.js";
|
|
60
|
+
import { readRecalledMemories } from "../../shared/recalled-memories.js";
|
|
60
61
|
import { withholdSecretContentFromMessages } from "../../shared/tool-row.js";
|
|
61
62
|
import { StallTimeoutError, formatStallFailure } from "../../shared/stall-watchdog.js";
|
|
62
63
|
import { resolveUsableArtifactStorage, loadArtifactStorageConfig, type ArtifactStorage } from "../../shared/artifact-storage.js";
|
|
@@ -1179,6 +1180,7 @@ async function executeCursorInner(
|
|
|
1179
1180
|
senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
|
|
1180
1181
|
sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
|
|
1181
1182
|
declaredPreferences: readDeclaredPreferences(spec.declaredPreferences),
|
|
1183
|
+
recalledMemories: readRecalledMemories(spec.recalledMemories),
|
|
1182
1184
|
conversationCatchup: readConversationCatchup(spec.conversationCatchup),
|
|
1183
1185
|
// The turn's recorded transcript, seeded from the persisted execution
|
|
1184
1186
|
// on a reinvocation (Phase 3). Consumed only by the HITL-recovery
|
|
@@ -1886,6 +1888,7 @@ async function executeCursorInner(
|
|
|
1886
1888
|
senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
|
|
1887
1889
|
sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
|
|
1888
1890
|
declaredPreferences: readDeclaredPreferences(spec.declaredPreferences),
|
|
1891
|
+
recalledMemories: readRecalledMemories(spec.recalledMemories),
|
|
1889
1892
|
conversationCatchup: readConversationCatchup(spec.conversationCatchup),
|
|
1890
1893
|
// Composed fresh (not reused from Phase 10): the failed primary
|
|
1891
1894
|
// stream may have appended partial work onto status.messages,
|
|
@@ -2486,6 +2489,16 @@ export interface BuildPromptInput {
|
|
|
2486
2489
|
* store with identical content.
|
|
2487
2490
|
*/
|
|
2488
2491
|
declaredPreferences?: import("../../shared/declared-preferences.js").DeclaredPreferencesContent;
|
|
2492
|
+
/**
|
|
2493
|
+
* The subject's confirmed memories from the execution spec's
|
|
2494
|
+
* `recalled_memories` (stigmer/stigmer#293 Phase 2, DD-006). Like the
|
|
2495
|
+
* preferences, only the enhanced-prompt path consumes it — deliberately
|
|
2496
|
+
* frozen per Cursor session (DD-002 D3, inherited by DD-006 D4): the
|
|
2497
|
+
* first turn delivers it into the agent's own conversation store, and
|
|
2498
|
+
* repeating it on resumed turns would bloat the store with identical
|
|
2499
|
+
* content.
|
|
2500
|
+
*/
|
|
2501
|
+
recalledMemories?: import("../../shared/recalled-memories.js").RecalledMemoriesContent;
|
|
2489
2502
|
/**
|
|
2490
2503
|
* Conversation catchup from the execution spec's `conversation_catchup`
|
|
2491
2504
|
* (cloud DD-006): what happened on the channel conversation that the
|
|
@@ -2625,6 +2638,7 @@ export function buildPrompt(input: BuildPromptInput): string {
|
|
|
2625
2638
|
senderIdentity: input.senderIdentity,
|
|
2626
2639
|
sessionContext: input.sessionContext,
|
|
2627
2640
|
declaredPreferences: input.declaredPreferences,
|
|
2641
|
+
recalledMemories: input.recalledMemories,
|
|
2628
2642
|
conversationCatchup,
|
|
2629
2643
|
},
|
|
2630
2644
|
{
|
|
@@ -2692,6 +2706,7 @@ export function buildPrompt(input: BuildPromptInput): string {
|
|
|
2692
2706
|
senderIdentity: input.senderIdentity,
|
|
2693
2707
|
sessionContext: input.sessionContext,
|
|
2694
2708
|
declaredPreferences: input.declaredPreferences,
|
|
2709
|
+
recalledMemories: input.recalledMemories,
|
|
2695
2710
|
conversationCatchup,
|
|
2696
2711
|
});
|
|
2697
2712
|
}
|
|
@@ -35,6 +35,10 @@ import {
|
|
|
35
35
|
formatDeclaredPreferencesText,
|
|
36
36
|
type DeclaredPreferencesContent,
|
|
37
37
|
} from "../../shared/declared-preferences.js";
|
|
38
|
+
import {
|
|
39
|
+
formatRecalledMemoriesText,
|
|
40
|
+
type RecalledMemoriesContent,
|
|
41
|
+
} from "../../shared/recalled-memories.js";
|
|
38
42
|
import {
|
|
39
43
|
visionDisclosureLines,
|
|
40
44
|
type NotViewableEntry,
|
|
@@ -160,6 +164,16 @@ export interface EnhancedPromptOptions {
|
|
|
160
164
|
* it every resumed turn would bloat the store with identical content.
|
|
161
165
|
*/
|
|
162
166
|
declaredPreferences?: DeclaredPreferencesContent;
|
|
167
|
+
/**
|
|
168
|
+
* The subject's confirmed memories (stigmer/stigmer#293 Phase 2, DD-006):
|
|
169
|
+
* consent-gated facts server-snapshotted onto the execution spec's
|
|
170
|
+
* `recalled_memories` at create. Like the preferences, it lands in the
|
|
171
|
+
* first message and persists in the cursor agent's own conversation
|
|
172
|
+
* store — deliberately frozen per Cursor session (DD-002 D3, inherited
|
|
173
|
+
* by DD-006 D4): repeating it every resumed turn would bloat the store
|
|
174
|
+
* with identical content.
|
|
175
|
+
*/
|
|
176
|
+
recalledMemories?: RecalledMemoriesContent;
|
|
163
177
|
/**
|
|
164
178
|
* Conversation catchup (cloud DD-006): what happened on the channel
|
|
165
179
|
* conversation that the agent has not seen, read from the execution
|
|
@@ -253,6 +267,14 @@ export function buildEnhancedPrompt(options: EnhancedPromptOptions): string {
|
|
|
253
267
|
sections.push(formatDeclaredPreferencesSection(options.declaredPreferences));
|
|
254
268
|
}
|
|
255
269
|
|
|
270
|
+
// Declared-by-humans precedes learned-and-confirmed (DD-006 D4): both
|
|
271
|
+
// are platform-authored standing background, but a preference is the
|
|
272
|
+
// user's exact words while a memory is an agent's confirmed inference —
|
|
273
|
+
// the exact statement reads first.
|
|
274
|
+
if (options.recalledMemories) {
|
|
275
|
+
sections.push(formatRecalledMemoriesSection(options.recalledMemories));
|
|
276
|
+
}
|
|
277
|
+
|
|
256
278
|
// Standing facts about the user (session context) come before the
|
|
257
279
|
// carried conversation (bridge): the bridge may refer back to them.
|
|
258
280
|
if (options.sessionContext) {
|
|
@@ -472,6 +494,12 @@ export function formatDeclaredPreferencesSection(
|
|
|
472
494
|
return `<declared_preferences>\n${formatDeclaredPreferencesText(preferences)}\n</declared_preferences>`;
|
|
473
495
|
}
|
|
474
496
|
|
|
497
|
+
export function formatRecalledMemoriesSection(
|
|
498
|
+
memories: RecalledMemoriesContent,
|
|
499
|
+
): string {
|
|
500
|
+
return `<recalled_memories>\n${formatRecalledMemoriesText(memories)}\n</recalled_memories>`;
|
|
501
|
+
}
|
|
502
|
+
|
|
475
503
|
export function formatSessionContextSection(context: string): string {
|
|
476
504
|
return `<session_context>\n${formatSessionContextText(context)}\n</session_context>`;
|
|
477
505
|
}
|
|
@@ -251,6 +251,7 @@ const httpConfig: Config = {
|
|
|
251
251
|
cloudModeEnabled: true,
|
|
252
252
|
checkpointerType: "http",
|
|
253
253
|
checkpointerProxyEndpoint: "http://localhost:7234",
|
|
254
|
+
artifactProxyEndpoint: null,
|
|
254
255
|
primaryModel: "claude-sonnet",
|
|
255
256
|
cursorStreamStallTimeoutMs: 180000,
|
|
256
257
|
agentResolveTimeoutMs: 120000,
|
|
@@ -296,6 +296,7 @@ const baseConfig: Config = {
|
|
|
296
296
|
cloudModeEnabled: true,
|
|
297
297
|
checkpointerType: "http",
|
|
298
298
|
checkpointerProxyEndpoint: "http://localhost:7234",
|
|
299
|
+
artifactProxyEndpoint: null,
|
|
299
300
|
primaryModel: "claude-sonnet",
|
|
300
301
|
cursorStreamStallTimeoutMs: 180000,
|
|
301
302
|
agentResolveTimeoutMs: 120000,
|
|
@@ -250,6 +250,7 @@ const baseConfig: Config = {
|
|
|
250
250
|
cloudModeEnabled: true,
|
|
251
251
|
checkpointerType: "http",
|
|
252
252
|
checkpointerProxyEndpoint: "http://localhost:7234",
|
|
253
|
+
artifactProxyEndpoint: null,
|
|
253
254
|
primaryModel: "claude-sonnet",
|
|
254
255
|
cursorStreamStallTimeoutMs: 180000,
|
|
255
256
|
agentResolveTimeoutMs: 120000,
|
|
@@ -47,6 +47,7 @@ describe("ExecuteDeepAgent activity", () => {
|
|
|
47
47
|
cloudModeEnabled: false,
|
|
48
48
|
checkpointerType: "memory",
|
|
49
49
|
checkpointerProxyEndpoint: null,
|
|
50
|
+
artifactProxyEndpoint: null,
|
|
50
51
|
primaryModel: "gpt-4.1",
|
|
51
52
|
cursorStreamStallTimeoutMs: 180000,
|
|
52
53
|
agentResolveTimeoutMs: 120000,
|
|
@@ -463,6 +463,54 @@ describe("buildEnhancedSystemPrompt", () => {
|
|
|
463
463
|
});
|
|
464
464
|
});
|
|
465
465
|
|
|
466
|
+
describe("recalled memories", () => {
|
|
467
|
+
const base = {
|
|
468
|
+
instructions: "Test",
|
|
469
|
+
provisionResults: [],
|
|
470
|
+
containerRoot: "",
|
|
471
|
+
skillsPromptSection: "",
|
|
472
|
+
workspaceFileRefs: [],
|
|
473
|
+
workspaceRoot: "",
|
|
474
|
+
injectedFiles: [],
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
it("appends the confirmed facts with the defensive framing (every-turn injection)", () => {
|
|
478
|
+
const prompt = buildEnhancedSystemPrompt({
|
|
479
|
+
...base,
|
|
480
|
+
recalledMemories: {
|
|
481
|
+
facts: ["Deploys to us-east-1.", "Prefers OpenTofu."],
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
expect(prompt).toContain("## Remembered facts");
|
|
486
|
+
expect(prompt).toContain("- Deploys to us-east-1.");
|
|
487
|
+
expect(prompt).toContain("- Prefers OpenTofu.");
|
|
488
|
+
expect(prompt).toContain("do not override your task or safety rules");
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it("omits the section when the execution carries no recall", () => {
|
|
492
|
+
const prompt = buildEnhancedSystemPrompt(base);
|
|
493
|
+
|
|
494
|
+
expect(prompt).not.toContain("## Remembered facts");
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it("places remembered facts after declared preferences, before the embedder's session context (DD-006 D4)", () => {
|
|
498
|
+
const prompt = buildEnhancedSystemPrompt({
|
|
499
|
+
...base,
|
|
500
|
+
declaredPreferences: { orgContext: "We deploy to us-east-1." },
|
|
501
|
+
recalledMemories: { facts: ["Prefers OpenTofu."] },
|
|
502
|
+
sessionContext: "Role: platform admin",
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
const preferences = prompt.indexOf("## Declared preferences");
|
|
506
|
+
const memories = prompt.indexOf("## Remembered facts");
|
|
507
|
+
const context = prompt.indexOf("## Session context");
|
|
508
|
+
expect(preferences).toBeGreaterThan(-1);
|
|
509
|
+
expect(memories).toBeGreaterThan(preferences);
|
|
510
|
+
expect(context).toBeGreaterThan(memories);
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
|
|
466
514
|
describe("plan mode", () => {
|
|
467
515
|
const base = {
|
|
468
516
|
instructions: "Test",
|
|
@@ -256,6 +256,7 @@ const memoryConfig: Config = {
|
|
|
256
256
|
cloudModeEnabled: false,
|
|
257
257
|
checkpointerType: "memory",
|
|
258
258
|
checkpointerProxyEndpoint: null,
|
|
259
|
+
artifactProxyEndpoint: null,
|
|
259
260
|
primaryModel: "claude-sonnet",
|
|
260
261
|
cursorStreamStallTimeoutMs: 180000,
|
|
261
262
|
agentResolveTimeoutMs: 120000,
|
|
@@ -21,6 +21,10 @@ import {
|
|
|
21
21
|
formatDeclaredPreferencesText,
|
|
22
22
|
type DeclaredPreferencesContent,
|
|
23
23
|
} from "../../shared/declared-preferences.js";
|
|
24
|
+
import {
|
|
25
|
+
formatRecalledMemoriesText,
|
|
26
|
+
type RecalledMemoriesContent,
|
|
27
|
+
} from "../../shared/recalled-memories.js";
|
|
24
28
|
import {
|
|
25
29
|
visionDisclosureLines,
|
|
26
30
|
type NotViewableEntry,
|
|
@@ -178,6 +182,14 @@ export interface PromptBuilderInput {
|
|
|
178
182
|
* edited preference reaches the very next turn.
|
|
179
183
|
*/
|
|
180
184
|
declaredPreferences?: DeclaredPreferencesContent;
|
|
185
|
+
/**
|
|
186
|
+
* The subject's confirmed memories (stigmer/stigmer#293 Phase 2, DD-006):
|
|
187
|
+
* consent-gated facts server-snapshotted onto the execution spec's
|
|
188
|
+
* `recalled_memories` at create. Injected on EVERY turn like the
|
|
189
|
+
* preferences — the native system prompt is rebuilt per invocation, so a
|
|
190
|
+
* deleted memory is gone from the very next turn.
|
|
191
|
+
*/
|
|
192
|
+
recalledMemories?: RecalledMemoriesContent;
|
|
181
193
|
}
|
|
182
194
|
|
|
183
195
|
// The prompt renders the injector's own result type — a local structural twin
|
|
@@ -251,6 +263,16 @@ export function buildEnhancedSystemPrompt(input: PromptBuilderInput): string {
|
|
|
251
263
|
formatDeclaredPreferencesText(input.declaredPreferences);
|
|
252
264
|
}
|
|
253
265
|
|
|
266
|
+
// Declared-by-humans precedes learned-and-confirmed (DD-006 D4): both
|
|
267
|
+
// are platform-authored standing background, but a preference is the
|
|
268
|
+
// user's exact words while a memory is an agent's confirmed inference —
|
|
269
|
+
// the exact statement reads first.
|
|
270
|
+
if (input.recalledMemories) {
|
|
271
|
+
prompt +=
|
|
272
|
+
"\n\n## Remembered facts\n\n" +
|
|
273
|
+
formatRecalledMemoriesText(input.recalledMemories);
|
|
274
|
+
}
|
|
275
|
+
|
|
254
276
|
// Standing facts about the user (session context) come before the
|
|
255
277
|
// carried conversation (bridge): the bridge may refer back to them.
|
|
256
278
|
if (input.sessionContext) {
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
} from "../../shared/caller-identity.js";
|
|
34
34
|
import { readSessionContext } from "../../shared/session-context.js";
|
|
35
35
|
import { readDeclaredPreferences } from "../../shared/declared-preferences.js";
|
|
36
|
+
import { readRecalledMemories } from "../../shared/recalled-memories.js";
|
|
36
37
|
import { connectMcpServers, type McpConnectionResult } from "../../shared/mcp-manager.js";
|
|
37
38
|
import { mergeMcpServerUsages, resolveMcpServers } from "../../shared/mcp-resolver.js";
|
|
38
39
|
import { resolveMcpTransportPosture } from "../../shared/mcp-transport-guard.js";
|
|
@@ -577,6 +578,9 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
577
578
|
declaredPreferences: readDeclaredPreferences(
|
|
578
579
|
execution.spec!.declaredPreferences,
|
|
579
580
|
),
|
|
581
|
+
recalledMemories: readRecalledMemories(
|
|
582
|
+
execution.spec!.recalledMemories,
|
|
583
|
+
),
|
|
580
584
|
});
|
|
581
585
|
|
|
582
586
|
// Step 9: Construct the LLM model. Resolution to the provider API id
|
package/src/config.ts
CHANGED
|
@@ -96,6 +96,16 @@ export interface Config {
|
|
|
96
96
|
readonly cloudModeEnabled: boolean;
|
|
97
97
|
readonly checkpointerType: "memory" | "http" | "sqlite";
|
|
98
98
|
readonly checkpointerProxyEndpoint: string | null;
|
|
99
|
+
/**
|
|
100
|
+
* Endpoint the proxy artifact store presigns against
|
|
101
|
+
* (STIGMER_ARTIFACT_PROXY_ENDPOINT), defaulting to {@link proxyEndpoint} —
|
|
102
|
+
* the checkpointer-override pattern (stigmer#803). Splitting the two lets
|
|
103
|
+
* artifact traffic target the real control plane while LLM traffic goes
|
|
104
|
+
* elsewhere (the conformance harness points LLM calls at a mock proxy that
|
|
105
|
+
* serves no presign routes; embedders can route artifact storage
|
|
106
|
+
* independently the same way).
|
|
107
|
+
*/
|
|
108
|
+
readonly artifactProxyEndpoint: string | null;
|
|
99
109
|
readonly primaryModel: string;
|
|
100
110
|
/**
|
|
101
111
|
* No-progress bound for the Cursor harness stream (milliseconds). If no
|
|
@@ -197,6 +207,8 @@ export function loadConfig(): Config {
|
|
|
197
207
|
?? (mode === "cloud" ? "http" : "sqlite");
|
|
198
208
|
const checkpointerProxyEndpoint = process.env.STIGMER_CHECKPOINTER_PROXY_ENDPOINT
|
|
199
209
|
?? proxyEndpoint;
|
|
210
|
+
const artifactProxyEndpoint = process.env.STIGMER_ARTIFACT_PROXY_ENDPOINT
|
|
211
|
+
?? proxyEndpoint;
|
|
200
212
|
|
|
201
213
|
const primaryModel = process.env.STIGMER_PRIMARY_MODEL ?? "gpt-4.1";
|
|
202
214
|
|
|
@@ -228,6 +240,7 @@ export function loadConfig(): Config {
|
|
|
228
240
|
cloudModeEnabled: process.env.STIGMER_CURSOR_CLOUD_MODE_ENABLED === "true",
|
|
229
241
|
checkpointerType,
|
|
230
242
|
checkpointerProxyEndpoint,
|
|
243
|
+
artifactProxyEndpoint,
|
|
231
244
|
primaryModel,
|
|
232
245
|
cursorStreamStallTimeoutMs,
|
|
233
246
|
agentResolveTimeoutMs,
|
package/src/main.ts
CHANGED
|
@@ -89,6 +89,7 @@ async function runManagerMode(config: import("./config.js").Config): Promise<voi
|
|
|
89
89
|
primaryModel: config.primaryModel,
|
|
90
90
|
checkpointerType: config.checkpointerType,
|
|
91
91
|
checkpointerProxyEndpoint: config.checkpointerProxyEndpoint ?? undefined,
|
|
92
|
+
artifactProxyEndpoint: config.artifactProxyEndpoint ?? undefined,
|
|
92
93
|
cloudModeEnabled: config.cloudModeEnabled,
|
|
93
94
|
executionMode: config.mode,
|
|
94
95
|
});
|
|
@@ -213,6 +214,7 @@ async function runPoolMode(
|
|
|
213
214
|
primaryModel: config.primaryModel,
|
|
214
215
|
checkpointerType: config.checkpointerType,
|
|
215
216
|
checkpointerProxyEndpoint: config.checkpointerProxyEndpoint ?? undefined,
|
|
217
|
+
artifactProxyEndpoint: config.artifactProxyEndpoint ?? undefined,
|
|
216
218
|
cloudModeEnabled: config.cloudModeEnabled,
|
|
217
219
|
executionMode: config.mode,
|
|
218
220
|
});
|
|
@@ -338,6 +340,7 @@ async function runStaticMode(config: import("./config.js").Config): Promise<void
|
|
|
338
340
|
primaryModel: config.primaryModel,
|
|
339
341
|
checkpointerType: config.checkpointerType,
|
|
340
342
|
checkpointerProxyEndpoint: config.checkpointerProxyEndpoint ?? undefined,
|
|
343
|
+
artifactProxyEndpoint: config.artifactProxyEndpoint ?? undefined,
|
|
341
344
|
cloudModeEnabled: config.cloudModeEnabled,
|
|
342
345
|
// Honor the operator's MODE env (resolved into config.mode) instead of
|
|
343
346
|
// re-deriving execution location from the proxy. This keeps static mode
|
package/src/runner-manager.ts
CHANGED
|
@@ -114,6 +114,9 @@ export interface RunnerManagerOptions {
|
|
|
114
114
|
/** Checkpointer proxy endpoint. Falls back to proxyEndpoint. */
|
|
115
115
|
readonly checkpointerProxyEndpoint?: string;
|
|
116
116
|
|
|
117
|
+
/** Artifact presign endpoint (stigmer#803). Falls back to proxyEndpoint. */
|
|
118
|
+
readonly artifactProxyEndpoint?: string;
|
|
119
|
+
|
|
117
120
|
/** Enable Cursor cloud mode. @default false */
|
|
118
121
|
readonly cloudModeEnabled?: boolean;
|
|
119
122
|
|
|
@@ -721,6 +724,8 @@ export function mapManagerOptionsToConfig(
|
|
|
721
724
|
options.checkpointerType ?? (proxyActive ? "http" : "sqlite"),
|
|
722
725
|
checkpointerProxyEndpoint:
|
|
723
726
|
options.checkpointerProxyEndpoint ?? options.proxyEndpoint ?? null,
|
|
727
|
+
artifactProxyEndpoint:
|
|
728
|
+
options.artifactProxyEndpoint ?? options.proxyEndpoint ?? null,
|
|
724
729
|
primaryModel: options.primaryModel ?? "gpt-4.1",
|
|
725
730
|
cursorStreamStallTimeoutMs:
|
|
726
731
|
options.cursorStreamStallTimeoutMs ?? DEFAULT_CURSOR_STREAM_STALL_TIMEOUT_MS,
|
package/src/runner.ts
CHANGED
|
@@ -18,7 +18,8 @@ import { homedir, tmpdir } from "node:os";
|
|
|
18
18
|
import type { Config } from "./config.js";
|
|
19
19
|
import { DEFAULT_CURSOR_AGENT_RESOLVE_TIMEOUT_MS, DEFAULT_CURSOR_STREAM_STALL_TIMEOUT_MS, DEFAULT_WORKSPACE_LOCK_TIMEOUT_MS } from "./config.js";
|
|
20
20
|
import type { WorkerActivities } from "./worker.js";
|
|
21
|
-
import { resolveRunnerBootstrap } from "./bootstrap.js";
|
|
21
|
+
import { resolveRunnerBootstrap, refreshRunnerAccessToken } from "./bootstrap.js";
|
|
22
|
+
import { createRunnerTokenCoordinator } from "./runner-token-coordinator.js";
|
|
22
23
|
import { assertLlmBackendsPreflight } from "./preflight.js";
|
|
23
24
|
import {
|
|
24
25
|
captureRunnerSecrets,
|
|
@@ -86,6 +87,9 @@ export interface StigmerRunnerOptions {
|
|
|
86
87
|
/** Checkpointer proxy endpoint. Falls back to proxyEndpoint. */
|
|
87
88
|
readonly checkpointerProxyEndpoint?: string;
|
|
88
89
|
|
|
90
|
+
/** Artifact presign endpoint (stigmer#803). Falls back to proxyEndpoint. */
|
|
91
|
+
readonly artifactProxyEndpoint?: string;
|
|
92
|
+
|
|
89
93
|
/** Enable Cursor cloud mode for workspace-less execution. @default false */
|
|
90
94
|
readonly cloudModeEnabled?: boolean;
|
|
91
95
|
|
|
@@ -253,11 +257,6 @@ export async function createStigmerRunner(
|
|
|
253
257
|
// Only the worker connection consumes these coordinates — no activity dials
|
|
254
258
|
// Temporal directly (emit-event, the last one, now routes signals through
|
|
255
259
|
// the server's SendSignal lane; see oss#517).
|
|
256
|
-
//
|
|
257
|
-
// The static runner brings its own already-proxy-valid token (harness/CLI),
|
|
258
|
-
// so it does not consume the minted runner token from the bootstrap response;
|
|
259
|
-
// that proxy-credential lifecycle lives in createStigmerRunnerManager (the
|
|
260
|
-
// long-lived desktop host that needs it). Only the coordinates are used here.
|
|
261
260
|
const coordinates = await resolveRunnerBootstrap({
|
|
262
261
|
explicitAddress: options.temporalAddress,
|
|
263
262
|
explicitNamespace: options.temporalNamespace,
|
|
@@ -269,11 +268,44 @@ export async function createStigmerRunner(
|
|
|
269
268
|
// activity clients read the ref per request instead of pinning the boot
|
|
270
269
|
// token for the pod's whole life.
|
|
271
270
|
const tokenRef = { current: baseConfig.stigmerToken };
|
|
271
|
+
|
|
272
|
+
// Adopt the bootstrap-minted embedded_runner credential for gRPC runner-class
|
|
273
|
+
// calls (stigmer-cloud#507). The static path historically discarded it ("the
|
|
274
|
+
// static token is already proxy-valid") — true for the PROXY lane, but the
|
|
275
|
+
// ExecutionContext decrypt lane is gated on runner-class token_type
|
|
276
|
+
// (stigmer-cloud#152/#155): a user-token static runner (conformance harness,
|
|
277
|
+
// CLI daemon with a cloud token) had its scoped-token exchange refused and
|
|
278
|
+
// silently read REDACTED secret values. The coordinator owns the mint's TTL
|
|
279
|
+
// (same module the desktop manager uses — one refresh implementation, not
|
|
280
|
+
// two); its only sink here is the gRPC runner-credential ref, because the
|
|
281
|
+
// static host's proxy token is provided by the host and stays untouched.
|
|
282
|
+
// Servers that mint nothing (explicit-address boots, tokenless OSS, cloud
|
|
283
|
+
// sandboxes with baked credentials) leave the ref null — byte-identical
|
|
284
|
+
// behavior to before.
|
|
285
|
+
const runnerTokenRef: { current: string | null } = { current: null };
|
|
286
|
+
const runnerTokenCoordinator = createRunnerTokenCoordinator({
|
|
287
|
+
applyProxyToken: (token) => {
|
|
288
|
+
runnerTokenRef.current = token;
|
|
289
|
+
},
|
|
290
|
+
reMint: () =>
|
|
291
|
+
refreshRunnerAccessToken({
|
|
292
|
+
token: tokenRef.current,
|
|
293
|
+
stigmerEndpoint: baseConfig.stigmerBackendEndpoint,
|
|
294
|
+
}),
|
|
295
|
+
});
|
|
296
|
+
if (coordinates.runnerAccessToken) {
|
|
297
|
+
runnerTokenCoordinator.adoptMintedToken(
|
|
298
|
+
coordinates.runnerAccessToken,
|
|
299
|
+
coordinates.runnerAccessTokenExpiresInSeconds,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
272
303
|
const config: Config = {
|
|
273
304
|
...baseConfig,
|
|
274
305
|
temporalAddress: coordinates.temporalAddress,
|
|
275
306
|
temporalNamespace: coordinates.temporalNamespace,
|
|
276
307
|
stigmerTokenRef: tokenRef,
|
|
308
|
+
stigmerRunnerTokenRef: runnerTokenRef,
|
|
277
309
|
};
|
|
278
310
|
markBoot("bootstrap_resolved");
|
|
279
311
|
|
|
@@ -359,6 +391,7 @@ export async function createStigmerRunner(
|
|
|
359
391
|
},
|
|
360
392
|
shutdown() {
|
|
361
393
|
tokenRenewal?.stop();
|
|
394
|
+
runnerTokenCoordinator.stop();
|
|
362
395
|
// Classification first, drain second: an in-flight activity cancelled
|
|
363
396
|
// by the drain must observe the signal already aborted (see
|
|
364
397
|
// shared/worker-shutdown.ts for the ownership contract).
|
|
@@ -418,6 +451,9 @@ export function mapOptionsToConfig(options: StigmerRunnerOptions): Config {
|
|
|
418
451
|
checkpointerProxyEndpoint: options.checkpointerProxyEndpoint
|
|
419
452
|
?? options.proxyEndpoint
|
|
420
453
|
?? null,
|
|
454
|
+
artifactProxyEndpoint: options.artifactProxyEndpoint
|
|
455
|
+
?? options.proxyEndpoint
|
|
456
|
+
?? null,
|
|
421
457
|
primaryModel: options.primaryModel ?? "gpt-4.1",
|
|
422
458
|
cursorStreamStallTimeoutMs:
|
|
423
459
|
options.cursorStreamStallTimeoutMs ?? DEFAULT_CURSOR_STREAM_STALL_TIMEOUT_MS,
|
|
@@ -654,6 +654,7 @@ describe("loadArtifactStorageConfig", () => {
|
|
|
654
654
|
runnerId: null,
|
|
655
655
|
checkpointerType: "memory" as const,
|
|
656
656
|
checkpointerProxyEndpoint: null,
|
|
657
|
+
artifactProxyEndpoint: null,
|
|
657
658
|
primaryModel: "gpt-4.1",
|
|
658
659
|
cursorStreamStallTimeoutMs: 180000,
|
|
659
660
|
agentResolveTimeoutMs: 120000,
|
|
@@ -672,10 +673,14 @@ describe("loadArtifactStorageConfig", () => {
|
|
|
672
673
|
});
|
|
673
674
|
|
|
674
675
|
it("defaults to proxy in cloud mode", () => {
|
|
676
|
+
// loadConfig derives artifactProxyEndpoint from proxyEndpoint when the
|
|
677
|
+
// STIGMER_ARTIFACT_PROXY_ENDPOINT override is unset; Config literals here
|
|
678
|
+
// mirror that invariant.
|
|
675
679
|
const cfg = loadArtifactStorageConfig({
|
|
676
680
|
...baseConfig,
|
|
677
681
|
mode: "cloud",
|
|
678
682
|
proxyEndpoint: "https://proxy.example.com",
|
|
683
|
+
artifactProxyEndpoint: "https://proxy.example.com",
|
|
679
684
|
stigmerToken: "tok",
|
|
680
685
|
});
|
|
681
686
|
expect(cfg.type).toBe("proxy");
|
|
@@ -689,6 +694,7 @@ describe("loadArtifactStorageConfig", () => {
|
|
|
689
694
|
...baseConfig,
|
|
690
695
|
mode: "local",
|
|
691
696
|
proxyEndpoint: "https://localhost:9090",
|
|
697
|
+
artifactProxyEndpoint: "https://localhost:9090",
|
|
692
698
|
stigmerToken: "tok",
|
|
693
699
|
});
|
|
694
700
|
expect(cfg.type).toBe("proxy");
|
|
@@ -696,6 +702,21 @@ describe("loadArtifactStorageConfig", () => {
|
|
|
696
702
|
expect(cfg.proxyAuthToken).toBe("tok");
|
|
697
703
|
});
|
|
698
704
|
|
|
705
|
+
it("presigns against the artifact override when split from the LLM proxy endpoint (stigmer#803)", () => {
|
|
706
|
+
// The conformance harness points LLM traffic at a mock proxy that serves
|
|
707
|
+
// no presign routes; the artifact override routes storage at the real
|
|
708
|
+
// control plane independently (the checkpointer-override pattern).
|
|
709
|
+
process.env.ARTIFACT_STORAGE_TYPE = "proxy";
|
|
710
|
+
const cfg = loadArtifactStorageConfig({
|
|
711
|
+
...baseConfig,
|
|
712
|
+
proxyEndpoint: "https://mock-llm.example.com",
|
|
713
|
+
artifactProxyEndpoint: "https://service.example.com",
|
|
714
|
+
stigmerToken: "tok",
|
|
715
|
+
});
|
|
716
|
+
expect(cfg.type).toBe("proxy");
|
|
717
|
+
expect(cfg.proxyEndpoint).toBe("https://service.example.com");
|
|
718
|
+
});
|
|
719
|
+
|
|
699
720
|
it("honors ARTIFACT_STORAGE_TYPE=none even when a proxy endpoint is configured", () => {
|
|
700
721
|
// "none" is a deliberate operator/e2e posture — it must beat the
|
|
701
722
|
// storage-follows-transport default exactly like the other overrides.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the recalled-memories module (stigmer/stigmer#293 Phase 2,
|
|
3
|
+
* DD-006). Like declared-preferences there is no string key to mirror-guard —
|
|
4
|
+
* the value rides the typed `AgentExecutionSpec.recalled_memories` proto
|
|
5
|
+
* field, so codegen enforces the cross-repo contract. What IS pinned here:
|
|
6
|
+
* the render-only-when-something-to-say read semantics (disabled OR empty
|
|
7
|
+
* renders nothing — the enabled bit with zero facts is Stage 3's remember-
|
|
8
|
+
* tool signal, not this module's concern), the server-composed fact order,
|
|
9
|
+
* the content-only rendering (memory_id never reaches the prompt), and the
|
|
10
|
+
* framing's behavioral contract.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect } from "vitest";
|
|
14
|
+
import { create } from "@bufbuild/protobuf";
|
|
15
|
+
import { RecalledMemoriesSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
formatRecalledMemoriesText,
|
|
19
|
+
readRecalledMemories,
|
|
20
|
+
} from "../recalled-memories.js";
|
|
21
|
+
|
|
22
|
+
const FACT_OLDER = "Deploys to us-east-1.";
|
|
23
|
+
const FACT_NEWER = "Prefers OpenTofu over Terraform.";
|
|
24
|
+
|
|
25
|
+
describe("readRecalledMemories", () => {
|
|
26
|
+
it("reads the facts in server-composed order (oldest-first in both editions)", () => {
|
|
27
|
+
const recalled = create(RecalledMemoriesSchema, {
|
|
28
|
+
enabled: true,
|
|
29
|
+
facts: [
|
|
30
|
+
{ memoryId: "mem_older", content: FACT_OLDER },
|
|
31
|
+
{ memoryId: "mem_newer", content: FACT_NEWER },
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
expect(readRecalledMemories(recalled)).toEqual({
|
|
35
|
+
facts: [FACT_OLDER, FACT_NEWER],
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("answers undefined when the field is absent (pre-Phase-2 executions)", () => {
|
|
40
|
+
expect(readRecalledMemories(undefined)).toBeUndefined();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("answers undefined when recall is disabled — facts on a disabled snapshot are never rendered", () => {
|
|
44
|
+
const recalled = create(RecalledMemoriesSchema, {
|
|
45
|
+
enabled: false,
|
|
46
|
+
facts: [{ memoryId: "mem_1", content: FACT_OLDER }],
|
|
47
|
+
});
|
|
48
|
+
expect(readRecalledMemories(recalled)).toBeUndefined();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("answers undefined for enabled-with-zero-facts — a meaningful snapshot state (the remember-tool signal, DD-005 D1) that renders nothing", () => {
|
|
52
|
+
const recalled = create(RecalledMemoriesSchema, { enabled: true });
|
|
53
|
+
expect(readRecalledMemories(recalled)).toBeUndefined();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("drops blank facts defensively and trims the rest — the server never stamps them (write-time min_len)", () => {
|
|
57
|
+
const recalled = create(RecalledMemoriesSchema, {
|
|
58
|
+
enabled: true,
|
|
59
|
+
facts: [
|
|
60
|
+
{ memoryId: "mem_1", content: ` ${FACT_OLDER} ` },
|
|
61
|
+
{ memoryId: "mem_2", content: " " },
|
|
62
|
+
],
|
|
63
|
+
});
|
|
64
|
+
expect(readRecalledMemories(recalled)).toEqual({ facts: [FACT_OLDER] });
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("formatRecalledMemoriesText", () => {
|
|
69
|
+
it("frames the facts as user-confirmed, user-controlled background — never authority", () => {
|
|
70
|
+
const framed = formatRecalledMemoriesText({ facts: [FACT_OLDER] });
|
|
71
|
+
|
|
72
|
+
expect(framed).toContain("this user previously confirmed");
|
|
73
|
+
expect(framed).toContain("not instructions");
|
|
74
|
+
expect(framed).toContain("do not override your task or safety rules");
|
|
75
|
+
expect(framed).toContain("review and delete them at any time");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("renders one list item per fact, preserving the snapshot's order", () => {
|
|
79
|
+
const framed = formatRecalledMemoriesText({
|
|
80
|
+
facts: [FACT_OLDER, FACT_NEWER],
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(framed).toContain(`- ${FACT_OLDER}`);
|
|
84
|
+
expect(framed).toContain(`- ${FACT_NEWER}`);
|
|
85
|
+
expect(framed.indexOf(FACT_OLDER)).toBeLessThan(framed.indexOf(FACT_NEWER));
|
|
86
|
+
expect(framed.endsWith(FACT_NEWER)).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -376,19 +376,22 @@ export function loadArtifactStorageConfig(config: Config): ArtifactStorageConfig
|
|
|
376
376
|
// configured, push artifacts through it (the proxy brokers R2). This holds for
|
|
377
377
|
// both cloud runners and the local desktop runner — the latter executes
|
|
378
378
|
// locally (mode === "local") yet still uploads via the proxy. An explicit
|
|
379
|
-
// ARTIFACT_STORAGE_TYPE always wins.
|
|
379
|
+
// ARTIFACT_STORAGE_TYPE always wins. Presigns target artifactProxyEndpoint —
|
|
380
|
+
// STIGMER_ARTIFACT_PROXY_ENDPOINT when split from the LLM proxy endpoint
|
|
381
|
+
// (stigmer#803, the checkpointer-override pattern), the plain proxy
|
|
382
|
+
// endpoint otherwise.
|
|
380
383
|
const envType = process.env.ARTIFACT_STORAGE_TYPE;
|
|
381
384
|
const type: ArtifactStorageType =
|
|
382
385
|
envType === "proxy" ? "proxy" :
|
|
383
386
|
envType === "local" ? "local" :
|
|
384
387
|
envType === "none" ? "none" :
|
|
385
|
-
config.
|
|
388
|
+
config.artifactProxyEndpoint ? "proxy" : "local";
|
|
386
389
|
|
|
387
390
|
return {
|
|
388
391
|
type,
|
|
389
392
|
localPath: process.env.LOCAL_ARTIFACT_PATH ?? defaultLocalArtifactPath(),
|
|
390
393
|
localServeUrl: process.env.LOCAL_ARTIFACT_SERVE_URL ?? "http://localhost:7235",
|
|
391
|
-
proxyEndpoint: type === "proxy" ? (config.
|
|
394
|
+
proxyEndpoint: type === "proxy" ? (config.artifactProxyEndpoint ?? null) : null,
|
|
392
395
|
// Prefer the live ref: renewal rotates the token in place and uploads
|
|
393
396
|
// must present the current credential, not the boot one.
|
|
394
397
|
proxyAuthToken: type === "proxy"
|
|
@@ -408,7 +411,9 @@ export function createArtifactStorage(cfg: ArtifactStorageConfig): ArtifactStora
|
|
|
408
411
|
}
|
|
409
412
|
if (cfg.type === "proxy") {
|
|
410
413
|
if (!cfg.proxyEndpoint) {
|
|
411
|
-
throw new Error(
|
|
414
|
+
throw new Error(
|
|
415
|
+
"Proxy artifact storage requires STIGMER_ARTIFACT_PROXY_ENDPOINT or STIGMER_PROXY_ENDPOINT",
|
|
416
|
+
);
|
|
412
417
|
}
|
|
413
418
|
const tokenAtBoot = typeof cfg.proxyAuthToken === "string"
|
|
414
419
|
? cfg.proxyAuthToken
|