@stigmer/runner 3.12.8 → 3.12.9
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/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/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/payload-codecs.d.ts +1 -1
- package/dist/payload-codecs.js +6 -4
- package/dist/payload-codecs.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/package.json +3 -2
- package/src/__tests__/history-encryption-e2e.test.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/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/index.ts +10 -0
- package/src/activities/execute-deep-agent/setup.ts +34 -6
- package/src/payload-codecs.ts +12 -9
- package/src/shared/__tests__/memory-retrieval.test.ts +310 -0
- package/src/shared/memory-retrieval.ts +383 -0
- package/src/shared/runner-credential-keys.ts +2 -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/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
|
@@ -171,23 +171,26 @@ describe("fetch-interceptor", () => {
|
|
|
171
171
|
});
|
|
172
172
|
});
|
|
173
173
|
|
|
174
|
-
describe("
|
|
174
|
+
describe("timed REST path timing", () => {
|
|
175
175
|
beforeEach(() => {
|
|
176
176
|
installFetchInterceptor({ proxyEndpoint: PROXY_ENDPOINT, stigmerToken: STIGMER_TOKEN });
|
|
177
177
|
});
|
|
178
178
|
|
|
179
179
|
/**
|
|
180
|
-
* Collect emitted
|
|
181
|
-
* spy, ignoring every other log line (install banner,
|
|
182
|
-
* timelines).
|
|
180
|
+
* Collect emitted timing lines for one timeline event from a
|
|
181
|
+
* console.log spy, ignoring every other log line (install banner,
|
|
182
|
+
* warnings, other timelines).
|
|
183
183
|
*/
|
|
184
|
-
function timingLines(
|
|
184
|
+
function timingLines(
|
|
185
|
+
spy: ReturnType<typeof vi.spyOn>,
|
|
186
|
+
event: string,
|
|
187
|
+
): Array<Record<string, unknown>> {
|
|
185
188
|
const lines: Array<Record<string, unknown>> = [];
|
|
186
189
|
for (const call of spy.mock.calls) {
|
|
187
190
|
if (typeof call[0] !== "string") continue;
|
|
188
191
|
try {
|
|
189
192
|
const parsed = JSON.parse(call[0]) as Record<string, unknown>;
|
|
190
|
-
if (parsed.stigmer_timing ===
|
|
193
|
+
if (parsed.stigmer_timing === event) lines.push(parsed);
|
|
191
194
|
} catch {
|
|
192
195
|
// Not a JSON log line — ignore.
|
|
193
196
|
}
|
|
@@ -195,7 +198,7 @@ describe("fetch-interceptor", () => {
|
|
|
195
198
|
return lines;
|
|
196
199
|
}
|
|
197
200
|
|
|
198
|
-
it("emits one
|
|
201
|
+
it("emits one cursor_models_fetch line for a Cursor-domain /v1/models call, carrying execution_id", async () => {
|
|
199
202
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
200
203
|
const executionContext = getExecutionContext();
|
|
201
204
|
|
|
@@ -203,7 +206,7 @@ describe("fetch-interceptor", () => {
|
|
|
203
206
|
await globalThis.fetch("https://api.cursor.com/v1/models", { method: "GET" });
|
|
204
207
|
});
|
|
205
208
|
|
|
206
|
-
const lines = timingLines(spy);
|
|
209
|
+
const lines = timingLines(spy, "cursor_models_fetch");
|
|
207
210
|
expect(lines).toHaveLength(1);
|
|
208
211
|
expect(lines[0]!.execution_id).toBe("exec-models-1");
|
|
209
212
|
expect(lines[0]!.http_status).toBe(200);
|
|
@@ -212,16 +215,46 @@ describe("fetch-interceptor", () => {
|
|
|
212
215
|
expect(segments.map((s) => s.name)).toEqual(["models_fetch"]);
|
|
213
216
|
});
|
|
214
217
|
|
|
215
|
-
it("emits for the
|
|
218
|
+
it("emits one cursor_token_exchange line for the SDK's token exchange, carrying execution_id (cloud#484)", async () => {
|
|
219
|
+
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
220
|
+
const executionContext = getExecutionContext();
|
|
221
|
+
|
|
222
|
+
await executionContext.run({ executionId: "exec-exchange-1" }, async () => {
|
|
223
|
+
await globalThis.fetch(
|
|
224
|
+
"https://api2.cursor.sh/auth/exchange_user_api_key",
|
|
225
|
+
{ method: "POST" },
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const lines = timingLines(spy, "cursor_token_exchange");
|
|
230
|
+
expect(lines).toHaveLength(1);
|
|
231
|
+
expect(lines[0]!.execution_id).toBe("exec-exchange-1");
|
|
232
|
+
expect(lines[0]!.http_status).toBe(200);
|
|
233
|
+
expect(lines[0]!.total_ms).toBeTypeOf("number");
|
|
234
|
+
const segments = lines[0]!.segments as Array<{ name: string }>;
|
|
235
|
+
expect(segments.map((s) => s.name)).toEqual(["token_exchange"]);
|
|
236
|
+
// The exchange emits ONLY its own timeline, never the models one.
|
|
237
|
+
expect(timingLines(spy, "cursor_models_fetch")).toHaveLength(0);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("emits for the proxy-endpoint-targeted forms too", async () => {
|
|
216
241
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
217
242
|
|
|
218
243
|
await globalThis.fetch(`${PROXY_ENDPOINT}/v1/models`, { method: "GET" });
|
|
244
|
+
await globalThis.fetch(
|
|
245
|
+
`${PROXY_ENDPOINT}/auth/exchange_user_api_key`,
|
|
246
|
+
{ method: "POST" },
|
|
247
|
+
);
|
|
219
248
|
|
|
220
|
-
//
|
|
249
|
+
// Both fetches were rewritten through the proxy path AND timed.
|
|
221
250
|
expect(calls[0]!.url).toBe(
|
|
222
251
|
`${PROXY_ENDPOINT}/v1/proxy/cursor/api.cursor.com/v1/models`,
|
|
223
252
|
);
|
|
224
|
-
expect(
|
|
253
|
+
expect(calls[1]!.url).toBe(
|
|
254
|
+
`${PROXY_ENDPOINT}/v1/proxy/cursor/api2.cursor.sh/auth/exchange_user_api_key`,
|
|
255
|
+
);
|
|
256
|
+
expect(timingLines(spy, "cursor_models_fetch")).toHaveLength(1);
|
|
257
|
+
expect(timingLines(spy, "cursor_token_exchange")).toHaveLength(1);
|
|
225
258
|
});
|
|
226
259
|
|
|
227
260
|
it("omits execution_id (rather than fabricating one) outside an execution context", async () => {
|
|
@@ -229,22 +262,23 @@ describe("fetch-interceptor", () => {
|
|
|
229
262
|
|
|
230
263
|
await globalThis.fetch("https://api.cursor.com/v1/models", { method: "GET" });
|
|
231
264
|
|
|
232
|
-
const lines = timingLines(spy);
|
|
265
|
+
const lines = timingLines(spy, "cursor_models_fetch");
|
|
233
266
|
expect(lines).toHaveLength(1);
|
|
234
267
|
// undefined context values are dropped by JSON.stringify.
|
|
235
268
|
expect("execution_id" in lines[0]!).toBe(false);
|
|
236
269
|
});
|
|
237
270
|
|
|
238
|
-
it("does NOT emit for
|
|
271
|
+
it("does NOT emit for rewritten REST paths outside the timed set", async () => {
|
|
239
272
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
240
273
|
|
|
241
274
|
await globalThis.fetch(
|
|
242
|
-
"https://
|
|
275
|
+
"https://api.cursor.com/v1/agents",
|
|
243
276
|
{ method: "POST" },
|
|
244
277
|
);
|
|
245
278
|
|
|
246
279
|
expect(calls).toHaveLength(1);
|
|
247
|
-
expect(timingLines(spy)).toHaveLength(0);
|
|
280
|
+
expect(timingLines(spy, "cursor_models_fetch")).toHaveLength(0);
|
|
281
|
+
expect(timingLines(spy, "cursor_token_exchange")).toHaveLength(0);
|
|
248
282
|
});
|
|
249
283
|
});
|
|
250
284
|
|
|
@@ -19,15 +19,34 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
19
19
|
|
|
20
20
|
import { TimingRecorder, emitTimingLog } from "../../shared/cold-start-timing.js";
|
|
21
21
|
|
|
22
|
+
/** One REST path's timing identity: the emitted timeline event and its
|
|
23
|
+
* single segment name. */
|
|
24
|
+
interface TimedRestPath {
|
|
25
|
+
readonly event: string;
|
|
26
|
+
readonly segment: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
22
29
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
30
|
+
* REST paths worth timing individually — both sit inside Agent.create/
|
|
31
|
+
* Agent.resume on the user-visible resolve_agent setup path, and in proxy
|
|
32
|
+
* mode each is a runner → Stigmer → Cursor double hop. The emitted
|
|
33
|
+
* timelines split that network cost out of the segment total without
|
|
34
|
+
* touching the SDK:
|
|
35
|
+
*
|
|
36
|
+
* - GET /v1/models (`cursor_models_fetch`): the SDK's model-id validation
|
|
37
|
+
* read, ~0.95s of the pre-cache resolve_agent segment (issue #209).
|
|
38
|
+
* - POST /auth/exchange_user_api_key (`cursor_token_exchange`): the SDK's
|
|
39
|
+
* API-key → access-token exchange, the strongest suspect for the
|
|
40
|
+
* remaining unexplained 0.6–1.3s inside Agent.create
|
|
41
|
+
* (stigmer-cloud#484 — this timeline is that issue's Step 1, measure).
|
|
29
42
|
*/
|
|
30
|
-
const
|
|
43
|
+
const TIMED_REST_PATHS: ReadonlyMap<string, TimedRestPath> = new Map([
|
|
44
|
+
["/v1/models", { event: "cursor_models_fetch", segment: "models_fetch" }],
|
|
45
|
+
[
|
|
46
|
+
"/auth/exchange_user_api_key",
|
|
47
|
+
{ event: "cursor_token_exchange", segment: "token_exchange" },
|
|
48
|
+
],
|
|
49
|
+
]);
|
|
31
50
|
|
|
32
51
|
const CURSOR_DOMAINS = [
|
|
33
52
|
"api2.cursor.sh",
|
|
@@ -258,13 +277,14 @@ async function fetchWithUrlRewrite(
|
|
|
258
277
|
const rewrittenUrl = rewriteUrl(url, config.proxyEndpoint);
|
|
259
278
|
const rewrittenInit = replaceAuth(init, config);
|
|
260
279
|
const path = extractPath(url);
|
|
261
|
-
const
|
|
280
|
+
const timedPath = TIMED_REST_PATHS.get(path);
|
|
281
|
+
const timing = timedPath ? new TimingRecorder() : undefined;
|
|
262
282
|
|
|
263
283
|
try {
|
|
264
284
|
const response = await originalFetch(rewrittenUrl, rewrittenInit);
|
|
265
285
|
|
|
266
|
-
if (
|
|
267
|
-
|
|
286
|
+
if (timedPath && timing) {
|
|
287
|
+
emitRestTiming(timedPath, timing, config, response.status);
|
|
268
288
|
}
|
|
269
289
|
|
|
270
290
|
if (!response.ok) {
|
|
@@ -290,22 +310,23 @@ async function fetchWithUrlRewrite(
|
|
|
290
310
|
}
|
|
291
311
|
|
|
292
312
|
/**
|
|
293
|
-
* Emit the
|
|
313
|
+
* Emit the timeline for one proxied REST call on a timed path.
|
|
294
314
|
*
|
|
295
315
|
* `execution_id` comes from the AsyncLocalStorage execution context — the
|
|
296
316
|
* whole ExecuteCursor activity runs inside runWithExecutionContext, so the
|
|
297
317
|
* value is correct even with concurrent activities on one runner process.
|
|
298
|
-
* `recordTimingMetric` deliberately ignores
|
|
299
|
-
*
|
|
318
|
+
* `recordTimingMetric` deliberately ignores these events (no mapped OTel
|
|
319
|
+
* instruments): they are forensic stdout lines only, joined to the
|
|
300
320
|
* execution_setup timeline by execution_id in cold-start-baseline analysis.
|
|
301
321
|
*/
|
|
302
|
-
function
|
|
322
|
+
function emitRestTiming(
|
|
323
|
+
timedPath: TimedRestPath,
|
|
303
324
|
timing: TimingRecorder,
|
|
304
325
|
config: ProxyConfig,
|
|
305
326
|
httpStatus: number,
|
|
306
327
|
): void {
|
|
307
|
-
timing.mark(
|
|
308
|
-
emitTimingLog(
|
|
328
|
+
timing.mark(timedPath.segment);
|
|
329
|
+
emitTimingLog(timedPath.event, {
|
|
309
330
|
execution_id: executionContext.getStore()?.executionId ?? config.executionId,
|
|
310
331
|
http_status: httpStatus,
|
|
311
332
|
}, timing);
|
|
@@ -57,7 +57,8 @@ 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 {
|
|
60
|
+
import type { RecalledMemoriesContent } from "../../shared/recalled-memories.js";
|
|
61
|
+
import { selectRecalledFacts } from "../../shared/memory-retrieval.js";
|
|
61
62
|
import { withholdSecretContentFromMessages } from "../../shared/tool-row.js";
|
|
62
63
|
import { StallTimeoutError, formatStallFailure } from "../../shared/stall-watchdog.js";
|
|
63
64
|
import { resolveUsableArtifactStorage, loadArtifactStorageConfig, type ArtifactStorage } from "../../shared/artifact-storage.js";
|
|
@@ -1182,6 +1183,38 @@ async function executeCursorInner(
|
|
|
1182
1183
|
const structuredOutputSchema = spec.executionConfig?.structuredOutputSchema as
|
|
1183
1184
|
Record<string, unknown> | undefined;
|
|
1184
1185
|
|
|
1186
|
+
// Phase 9c: Semantic memory selection (DD-008), memoized to at most one
|
|
1187
|
+
// run per invocation. Deliberately NOT decided by the Phase-10
|
|
1188
|
+
// resolution alone: a resumed-agent primary send carries no memories,
|
|
1189
|
+
// but a mid-send poisoned-handle failure rebuilds on a FRESH agent
|
|
1190
|
+
// whose recovery prompt does — the buildFromPlan drop-hazard class —
|
|
1191
|
+
// so every memory-carrying build site awaits this lazily instead.
|
|
1192
|
+
// Selection runs against the frozen first message's semantics: above
|
|
1193
|
+
// the activation threshold it picks top-k for spec.message, otherwise
|
|
1194
|
+
// (and on any failure) it injects the full candidate set — Phase 2
|
|
1195
|
+
// behavior. The outcome report is stamped on the turn's status ONCE,
|
|
1196
|
+
// picked up by the next persist; a re-invocation replays the report
|
|
1197
|
+
// already persisted on the execution rather than re-selecting (the
|
|
1198
|
+
// written-once rule).
|
|
1199
|
+
let memorySelection: Promise<RecalledMemoriesContent | undefined> | undefined;
|
|
1200
|
+
const selectMemoriesOnce = (): Promise<RecalledMemoriesContent | undefined> => {
|
|
1201
|
+
memorySelection ??= selectRecalledFacts(spec.recalledMemories, spec.message, {
|
|
1202
|
+
proxyEndpoint: config.proxyEndpoint,
|
|
1203
|
+
stigmerToken: config.stigmerToken,
|
|
1204
|
+
executionId,
|
|
1205
|
+
priorReport: execution.status?.recalledMemoriesReport,
|
|
1206
|
+
}).then((selection) => {
|
|
1207
|
+
if (selection.report !== undefined) {
|
|
1208
|
+
status.recalledMemoriesReport = selection.report;
|
|
1209
|
+
}
|
|
1210
|
+
return selection.content;
|
|
1211
|
+
});
|
|
1212
|
+
return memorySelection;
|
|
1213
|
+
};
|
|
1214
|
+
const recalledMemories = promptCarriesStandingContext(resolution.reason)
|
|
1215
|
+
? await selectMemoriesOnce()
|
|
1216
|
+
: undefined;
|
|
1217
|
+
|
|
1185
1218
|
// Phase 10: Build the prompt
|
|
1186
1219
|
const interactionMode = spec.executionConfig?.interactionMode
|
|
1187
1220
|
?? InteractionMode.UNSPECIFIED;
|
|
@@ -1208,7 +1241,7 @@ async function executeCursorInner(
|
|
|
1208
1241
|
senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
|
|
1209
1242
|
sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
|
|
1210
1243
|
declaredPreferences: readDeclaredPreferences(spec.declaredPreferences),
|
|
1211
|
-
recalledMemories
|
|
1244
|
+
recalledMemories,
|
|
1212
1245
|
conversationCatchup: readConversationCatchup(spec.conversationCatchup),
|
|
1213
1246
|
// The turn's recorded transcript, seeded from the persisted execution
|
|
1214
1247
|
// on a reinvocation (Phase 3). Consumed only by the HITL-recovery
|
|
@@ -1916,7 +1949,10 @@ async function executeCursorInner(
|
|
|
1916
1949
|
senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
|
|
1917
1950
|
sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
|
|
1918
1951
|
declaredPreferences: readDeclaredPreferences(spec.declaredPreferences),
|
|
1919
|
-
|
|
1952
|
+
// Lazily selected: the primary send may have been a resumed-agent
|
|
1953
|
+
// shape that carried no memories, but this fresh agent's prompt
|
|
1954
|
+
// must (see the Phase 9c memoized selection).
|
|
1955
|
+
recalledMemories: await selectMemoriesOnce(),
|
|
1920
1956
|
conversationCatchup: readConversationCatchup(spec.conversationCatchup),
|
|
1921
1957
|
// Composed fresh (not reused from Phase 10): the failed primary
|
|
1922
1958
|
// stream may have appended partial work onto status.messages,
|
|
@@ -2606,6 +2642,23 @@ export function primarySendCarriesImages(
|
|
|
2606
2642
|
return !(isHitlReinvocation(approvalDecisions) && reason === "resumed_successfully");
|
|
2607
2643
|
}
|
|
2608
2644
|
|
|
2645
|
+
/**
|
|
2646
|
+
* Whether a prompt built for this resolution carries the STANDING context —
|
|
2647
|
+
* instructions, skills, declared preferences, recalled memories, session
|
|
2648
|
+
* context. Exactly one resolution shape does not: a successfully RESUMED
|
|
2649
|
+
* agent, whose native conversation already holds the first message's
|
|
2650
|
+
* context (both its prompt shapes — the raw follow-up and the
|
|
2651
|
+
* decisions-only HITL reinvocation — send no standing sections).
|
|
2652
|
+
*
|
|
2653
|
+
* The named authority for that routing property (the
|
|
2654
|
+
* primarySendCarriesImages idiom): buildPrompt's internal routing and the
|
|
2655
|
+
* activity's standing-context preparation (e.g. the memory selection gate)
|
|
2656
|
+
* both consult THIS predicate, so the two can never drift.
|
|
2657
|
+
*/
|
|
2658
|
+
export function promptCarriesStandingContext(reason: AgentResolutionReason): boolean {
|
|
2659
|
+
return reason !== "resumed_successfully";
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2609
2662
|
/**
|
|
2610
2663
|
* Append the structured-output contract to a prompt when the execution
|
|
2611
2664
|
* requests one. A per-turn directive (the buildFromPlan rule): it must ride
|
|
@@ -2647,7 +2700,7 @@ export function buildPrompt(input: BuildPromptInput): string {
|
|
|
2647
2700
|
// an empty conversation strand the agent with instructions and no story,
|
|
2648
2701
|
// and the session inherits that amnesia permanently (issue #366).
|
|
2649
2702
|
if (isHitlReinvocation(approvalDecisions)) {
|
|
2650
|
-
if (resolution.reason
|
|
2703
|
+
if (promptCarriesStandingContext(resolution.reason)) {
|
|
2651
2704
|
return buildHitlRecoveryPrompt(
|
|
2652
2705
|
{
|
|
2653
2706
|
instructions,
|
|
@@ -2698,7 +2751,7 @@ export function buildPrompt(input: BuildPromptInput): string {
|
|
|
2698
2751
|
// cloud DD-006). Catchup last: it is context, and context sits closest to
|
|
2699
2752
|
// the task (the enhanced prompt's own ordering doctrine); the input files
|
|
2700
2753
|
// precede it because they are this turn's payload, not background.
|
|
2701
|
-
if (resolution.reason
|
|
2754
|
+
if (!promptCarriesStandingContext(resolution.reason)) {
|
|
2702
2755
|
const prefixes = [
|
|
2703
2756
|
formatInteractionModePrefix(interactionMode),
|
|
2704
2757
|
formatImplementPlanSection(buildFromPlan, attachments),
|
|
@@ -501,15 +501,21 @@ function blockText(b: Record<string, unknown>): string | undefined {
|
|
|
501
501
|
*
|
|
502
502
|
* The Cursor SDK returns sub-agent work as a blob in the task tool's
|
|
503
503
|
* completed event (not as streaming events with a distinct agent_id).
|
|
504
|
-
* Re-verified 2026-
|
|
505
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
*
|
|
509
|
-
*
|
|
510
|
-
*
|
|
511
|
-
*
|
|
512
|
-
*
|
|
504
|
+
* Re-verified 2026-08-23 on the pinned SDK (1.0.13) AND the latest (1.0.28),
|
|
505
|
+
* this time including the write side (stigmer/stigmer#839): an injected
|
|
506
|
+
* platform.eventStore/eventNotifier receives parent events only on 1.0.13 and
|
|
507
|
+
* is bypassed entirely on 1.0.28 (persistence moved into the executor daemon's
|
|
508
|
+
* own state dir); zero events reach the parent's run.stream() mid-task; the
|
|
509
|
+
* agentId in the task args is not a platform-store agent id at all, so it can
|
|
510
|
+
* never be addressable via Agent.listRuns / Agent.getRun (this is WHY the
|
|
511
|
+
* 2026-07-02 read-side polls all returned not-found). On 1.0.28 the child
|
|
512
|
+
* transcript lands at a deterministic path knowable at spawn
|
|
513
|
+
* (~/.cursor/projects/<slug>/agent-transcripts/<parentId>/subagents/<argsAgentId>.jsonl)
|
|
514
|
+
* but is flushed only at completion — verified with 200ms sampling across a
|
|
515
|
+
* full sub-agent run. Live nested visibility is therefore still an upstream
|
|
516
|
+
* SDK limitation — do not try to fake it here; the UI shows an elapsed-time
|
|
517
|
+
* affordance instead (SubAgentSection). Probe scripts and recordings:
|
|
518
|
+
* stigmer-cloud _projects/2026-08/20260823.03.cursor-subagent-live-progress.
|
|
513
519
|
* The result shape is:
|
|
514
520
|
*
|
|
515
521
|
* { status: "success", value: { conversationSteps: ConversationStep[] } }
|
|
@@ -180,6 +180,16 @@ export function createDeepAgentActivities(config: Config) {
|
|
|
180
180
|
? seedStatusFromExecution(setup.execution)
|
|
181
181
|
: create(AgentExecutionStatusSchema, {});
|
|
182
182
|
|
|
183
|
+
// The semantic retriever's injection outcome (DD-008 D5), computed at
|
|
184
|
+
// prompt build in setup Step 8. Stamped here — before the first
|
|
185
|
+
// persist — because setup predates this status object; the server's
|
|
186
|
+
// presence-guarded merge preserves it across report-less writes. On a
|
|
187
|
+
// re-invocation the seeded status already carries the (replayed)
|
|
188
|
+
// report, so this stamp is idempotent by construction.
|
|
189
|
+
if (setup.recalledMemoriesReport !== undefined) {
|
|
190
|
+
initialStatus.recalledMemoriesReport = setup.recalledMemoriesReport;
|
|
191
|
+
}
|
|
192
|
+
|
|
183
193
|
const statusBuilder = new StatusBuilder(executionId, initialStatus);
|
|
184
194
|
|
|
185
195
|
statusBuilder.setApprovalProvider({
|
|
@@ -15,7 +15,7 @@ import { z } from "zod";
|
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
16
|
type AgentGraph = any;
|
|
17
17
|
|
|
18
|
-
import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
18
|
+
import type { AgentExecution, RecalledMemoriesReport } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
19
19
|
import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
20
20
|
import type { Session } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
|
|
21
21
|
import type { DynamicStructuredTool } from "@langchain/core/tools";
|
|
@@ -33,7 +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 {
|
|
36
|
+
import { selectRecalledFacts } from "../../shared/memory-retrieval.js";
|
|
37
37
|
import { connectMcpServers, type McpConnectionResult } from "../../shared/mcp-manager.js";
|
|
38
38
|
import { mergeMcpServerUsages, resolveMcpServers } from "../../shared/mcp-resolver.js";
|
|
39
39
|
import { resolveMcpTransportPosture } from "../../shared/mcp-transport-guard.js";
|
|
@@ -201,6 +201,15 @@ export interface SetupResult {
|
|
|
201
201
|
* baseline/candidate/reconcile seam (`capture.ts`) and the CAS capture-class.
|
|
202
202
|
*/
|
|
203
203
|
readonly gitWorkspace: boolean;
|
|
204
|
+
/**
|
|
205
|
+
* The semantic retriever's injection outcome (DD-008 D5), produced at
|
|
206
|
+
* prompt build (Step 8) — the one place selection runs. The activity
|
|
207
|
+
* stamps it onto the turn's status proto before the first persist; the
|
|
208
|
+
* server's presence-guarded merge preserves it across report-less
|
|
209
|
+
* writes. Undefined when nothing was injected (recall absent, disabled,
|
|
210
|
+
* or empty) — absent report = wholesale, true by construction.
|
|
211
|
+
*/
|
|
212
|
+
readonly recalledMemoriesReport: RecalledMemoriesReport | undefined;
|
|
204
213
|
}
|
|
205
214
|
|
|
206
215
|
export interface SetupDependencies {
|
|
@@ -590,7 +599,27 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
590
599
|
}
|
|
591
600
|
timing.mark("inject_attachments");
|
|
592
601
|
|
|
593
|
-
// Step 8: Build enhanced system prompt
|
|
602
|
+
// Step 8: Build enhanced system prompt.
|
|
603
|
+
//
|
|
604
|
+
// Recalled memories go through the semantic retriever (DD-008), not the
|
|
605
|
+
// raw snapshot read: above the activation threshold it selects the
|
|
606
|
+
// top-k facts for THIS turn's message (one batched embeddings call);
|
|
607
|
+
// otherwise — and on any failure — it injects the full candidate set,
|
|
608
|
+
// exactly the Phase 2 behavior. A re-invocation of this execution
|
|
609
|
+
// (approval resume) replays the outcome recorded on the execution's
|
|
610
|
+
// status instead of re-selecting, so the prompt never shifts
|
|
611
|
+
// mid-execution. The report returned here is stamped onto the turn's
|
|
612
|
+
// status by the activity (index.ts) — setup has no status object yet.
|
|
613
|
+
const memorySelection = await selectRecalledFacts(
|
|
614
|
+
execution.spec!.recalledMemories,
|
|
615
|
+
execution.spec!.message,
|
|
616
|
+
{
|
|
617
|
+
proxyEndpoint: config.proxyEndpoint,
|
|
618
|
+
stigmerToken: config.stigmerToken,
|
|
619
|
+
executionId,
|
|
620
|
+
priorReport: execution.status?.recalledMemoriesReport,
|
|
621
|
+
},
|
|
622
|
+
);
|
|
594
623
|
const systemPrompt = buildEnhancedSystemPrompt({
|
|
595
624
|
instructions,
|
|
596
625
|
provisionResults,
|
|
@@ -614,9 +643,7 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
614
643
|
declaredPreferences: readDeclaredPreferences(
|
|
615
644
|
execution.spec!.declaredPreferences,
|
|
616
645
|
),
|
|
617
|
-
recalledMemories:
|
|
618
|
-
execution.spec!.recalledMemories,
|
|
619
|
-
),
|
|
646
|
+
recalledMemories: memorySelection.content,
|
|
620
647
|
});
|
|
621
648
|
|
|
622
649
|
// Step 9: Construct the LLM model. Resolution to the provider API id
|
|
@@ -964,6 +991,7 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
964
991
|
casObserver,
|
|
965
992
|
captureMode,
|
|
966
993
|
gitWorkspace,
|
|
994
|
+
recalledMemoriesReport: memorySelection.report,
|
|
967
995
|
};
|
|
968
996
|
} catch (err) {
|
|
969
997
|
if (mcpConnection) {
|
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(
|