@stigmer/runner 3.12.7 → 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 +76 -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/mcp-gate.d.ts +2 -0
- package/dist/activities/execute-deep-agent/mcp-gate.js +2 -1
- package/dist/activities/execute-deep-agent/mcp-gate.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +10 -1
- package/dist/activities/execute-deep-agent/setup.js +45 -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-attachment.d.ts +97 -0
- package/dist/shared/memory-attachment.js +136 -0
- package/dist/shared/memory-attachment.js.map +1 -0
- 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/tool-kind.js +9 -0
- package/dist/shared/tool-kind.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 +86 -5
- package/src/activities/execute-cursor/message-translator.ts +15 -9
- package/src/activities/execute-deep-agent/__tests__/mcp-gate.test.ts +7 -0
- package/src/activities/execute-deep-agent/index.ts +10 -0
- package/src/activities/execute-deep-agent/mcp-gate.ts +4 -1
- package/src/activities/execute-deep-agent/setup.ts +70 -6
- package/src/payload-codecs.ts +12 -9
- package/src/shared/__tests__/memory-attachment.test.ts +167 -0
- package/src/shared/__tests__/memory-retrieval.test.ts +310 -0
- package/src/shared/memory-attachment.ts +167 -0
- package/src/shared/memory-retrieval.ts +383 -0
- package/src/shared/runner-credential-keys.ts +2 -1
- package/src/shared/tool-kind.ts +10 -0
- 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
|
@@ -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";
|
|
@@ -84,6 +85,7 @@ import {
|
|
|
84
85
|
readChannelConversationId,
|
|
85
86
|
synthesizeConversationAttachment,
|
|
86
87
|
} from "../../shared/conversation-attachment.js";
|
|
88
|
+
import { synthesizeMemoryAttachment } from "../../shared/memory-attachment.js";
|
|
87
89
|
import { injectSynthesizedAttachment } from "../../shared/synthesized-attachment.js";
|
|
88
90
|
import { mergeApprovalPolicies } from "./approval-policy.js";
|
|
89
91
|
import { deriveActiveLeases, isUnattendedApprovalMode } from "../../shared/approval-policy.js";
|
|
@@ -717,6 +719,33 @@ async function executeCursorInner(
|
|
|
717
719
|
resolvedMcpServers, conversationAttachment, "conversation participation",
|
|
718
720
|
);
|
|
719
721
|
}
|
|
722
|
+
|
|
723
|
+
// Phase 4a5: Synthesize the memory capture attachment (DD-005 D1) —
|
|
724
|
+
// the fourth sibling. The recall snapshot's enabled bit IS the
|
|
725
|
+
// attachment decision (server-stamped at execution create; a free
|
|
726
|
+
// local read, like the conversation attachment's session label). The
|
|
727
|
+
// capture context is attribution the server verifies or trusts per
|
|
728
|
+
// edition (Stage 3 provenance decision); the subject is never
|
|
729
|
+
// threaded — it derives from the credential.
|
|
730
|
+
const memoryAttachment = synthesizeMemoryAttachment(
|
|
731
|
+
execution.spec?.recalledMemories,
|
|
732
|
+
{
|
|
733
|
+
org: session.metadata?.org ?? "",
|
|
734
|
+
agentId: blueprint.agent.metadata?.id ?? "",
|
|
735
|
+
sessionId,
|
|
736
|
+
agentExecutionId: executionId,
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
bridgeEndpoint: config.mcpBridgeEndpoint,
|
|
740
|
+
credential: attachmentCredential,
|
|
741
|
+
backendEndpoint: config.stigmerBackendEndpoint,
|
|
742
|
+
},
|
|
743
|
+
);
|
|
744
|
+
if (memoryAttachment) {
|
|
745
|
+
resolvedMcpServers = injectSynthesizedAttachment(
|
|
746
|
+
resolvedMcpServers, memoryAttachment, "memory capture",
|
|
747
|
+
);
|
|
748
|
+
}
|
|
720
749
|
// The one projection point: every mutation above is now visible in the
|
|
721
750
|
// Cursor SDK config by construction (no per-mutation rebuild to forget).
|
|
722
751
|
const mcpConfig = toCursorMcpConfig(resolvedMcpServers);
|
|
@@ -1154,6 +1183,38 @@ async function executeCursorInner(
|
|
|
1154
1183
|
const structuredOutputSchema = spec.executionConfig?.structuredOutputSchema as
|
|
1155
1184
|
Record<string, unknown> | undefined;
|
|
1156
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
|
+
|
|
1157
1218
|
// Phase 10: Build the prompt
|
|
1158
1219
|
const interactionMode = spec.executionConfig?.interactionMode
|
|
1159
1220
|
?? InteractionMode.UNSPECIFIED;
|
|
@@ -1180,7 +1241,7 @@ async function executeCursorInner(
|
|
|
1180
1241
|
senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
|
|
1181
1242
|
sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
|
|
1182
1243
|
declaredPreferences: readDeclaredPreferences(spec.declaredPreferences),
|
|
1183
|
-
recalledMemories
|
|
1244
|
+
recalledMemories,
|
|
1184
1245
|
conversationCatchup: readConversationCatchup(spec.conversationCatchup),
|
|
1185
1246
|
// The turn's recorded transcript, seeded from the persisted execution
|
|
1186
1247
|
// on a reinvocation (Phase 3). Consumed only by the HITL-recovery
|
|
@@ -1888,7 +1949,10 @@ async function executeCursorInner(
|
|
|
1888
1949
|
senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
|
|
1889
1950
|
sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
|
|
1890
1951
|
declaredPreferences: readDeclaredPreferences(spec.declaredPreferences),
|
|
1891
|
-
|
|
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(),
|
|
1892
1956
|
conversationCatchup: readConversationCatchup(spec.conversationCatchup),
|
|
1893
1957
|
// Composed fresh (not reused from Phase 10): the failed primary
|
|
1894
1958
|
// stream may have appended partial work onto status.messages,
|
|
@@ -2578,6 +2642,23 @@ export function primarySendCarriesImages(
|
|
|
2578
2642
|
return !(isHitlReinvocation(approvalDecisions) && reason === "resumed_successfully");
|
|
2579
2643
|
}
|
|
2580
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
|
+
|
|
2581
2662
|
/**
|
|
2582
2663
|
* Append the structured-output contract to a prompt when the execution
|
|
2583
2664
|
* requests one. A per-turn directive (the buildFromPlan rule): it must ride
|
|
@@ -2619,7 +2700,7 @@ export function buildPrompt(input: BuildPromptInput): string {
|
|
|
2619
2700
|
// an empty conversation strand the agent with instructions and no story,
|
|
2620
2701
|
// and the session inherits that amnesia permanently (issue #366).
|
|
2621
2702
|
if (isHitlReinvocation(approvalDecisions)) {
|
|
2622
|
-
if (resolution.reason
|
|
2703
|
+
if (promptCarriesStandingContext(resolution.reason)) {
|
|
2623
2704
|
return buildHitlRecoveryPrompt(
|
|
2624
2705
|
{
|
|
2625
2706
|
instructions,
|
|
@@ -2670,7 +2751,7 @@ export function buildPrompt(input: BuildPromptInput): string {
|
|
|
2670
2751
|
// cloud DD-006). Catchup last: it is context, and context sits closest to
|
|
2671
2752
|
// the task (the enhanced prompt's own ordering doctrine); the input files
|
|
2672
2753
|
// precede it because they are this turn's payload, not background.
|
|
2673
|
-
if (resolution.reason
|
|
2754
|
+
if (!promptCarriesStandingContext(resolution.reason)) {
|
|
2674
2755
|
const prefixes = [
|
|
2675
2756
|
formatInteractionModePrefix(interactionMode),
|
|
2676
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[] } }
|
|
@@ -14,6 +14,7 @@ const nothing = {
|
|
|
14
14
|
mcpServerUsageCount: 0,
|
|
15
15
|
channelMessagingCount: 0,
|
|
16
16
|
conversationChannelId: undefined,
|
|
17
|
+
memoryCaptureEnabled: false,
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
describe("shouldConnectMcp", () => {
|
|
@@ -34,4 +35,10 @@ describe("shouldConnectMcp", () => {
|
|
|
34
35
|
// channel — the escalation tool is the ONLY source.
|
|
35
36
|
expect(shouldConnectMcp({ ...nothing, conversationChannelId: "agch_1" })).toBe(true);
|
|
36
37
|
});
|
|
38
|
+
|
|
39
|
+
it("enters on memory capture alone (the memory attachment)", () => {
|
|
40
|
+
// A tool-less agent whose user has memory on: the remember tool is
|
|
41
|
+
// the ONLY source, and skipping the block would silently drop it.
|
|
42
|
+
expect(shouldConnectMcp({ ...nothing, memoryCaptureEnabled: true })).toBe(true);
|
|
43
|
+
});
|
|
37
44
|
});
|
|
@@ -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({
|
|
@@ -22,6 +22,8 @@ export interface McpToolSources {
|
|
|
22
22
|
/** The serving channel id when this session IS a live channel
|
|
23
23
|
* conversation (the conversation attachment, DD-008). */
|
|
24
24
|
readonly conversationChannelId: string | undefined;
|
|
25
|
+
/** The recall snapshot's enabled bit (the memory attachment, DD-005 D1). */
|
|
26
|
+
readonly memoryCaptureEnabled: boolean;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/** True when any tool source demands MCP resolution and connect. */
|
|
@@ -29,6 +31,7 @@ export function shouldConnectMcp(sources: McpToolSources): boolean {
|
|
|
29
31
|
return (
|
|
30
32
|
sources.mcpServerUsageCount > 0 ||
|
|
31
33
|
sources.channelMessagingCount > 0 ||
|
|
32
|
-
sources.conversationChannelId !== undefined
|
|
34
|
+
sources.conversationChannelId !== undefined ||
|
|
35
|
+
sources.memoryCaptureEnabled
|
|
33
36
|
);
|
|
34
37
|
}
|
|
@@ -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";
|
|
@@ -47,6 +47,10 @@ import {
|
|
|
47
47
|
readChannelConversationId,
|
|
48
48
|
synthesizeConversationAttachment,
|
|
49
49
|
} from "../../shared/conversation-attachment.js";
|
|
50
|
+
import {
|
|
51
|
+
memoryCaptureEnabled,
|
|
52
|
+
synthesizeMemoryAttachment,
|
|
53
|
+
} from "../../shared/memory-attachment.js";
|
|
50
54
|
import { injectSynthesizedAttachment } from "../../shared/synthesized-attachment.js";
|
|
51
55
|
import { shouldConnectMcp } from "./mcp-gate.js";
|
|
52
56
|
import { WorkspaceProvisioner } from "../../shared/workspace/provisioner.js";
|
|
@@ -197,6 +201,15 @@ export interface SetupResult {
|
|
|
197
201
|
* baseline/candidate/reconcile seam (`capture.ts`) and the CAS capture-class.
|
|
198
202
|
*/
|
|
199
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;
|
|
200
213
|
}
|
|
201
214
|
|
|
202
215
|
export interface SetupDependencies {
|
|
@@ -390,11 +403,17 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
390
403
|
// free, synchronous read, so no hoisted discovery needed.
|
|
391
404
|
const conversationChannelId = readChannelConversationId(session.metadata?.labels);
|
|
392
405
|
|
|
406
|
+
// The memory-attachment decision (DD-005 D1): the recall snapshot's
|
|
407
|
+
// enabled bit, stamped server-side at execution create — like the
|
|
408
|
+
// conversation attachment, a free, synchronous spec read.
|
|
409
|
+
const captureMemories = memoryCaptureEnabled(execution.spec?.recalledMemories);
|
|
410
|
+
|
|
393
411
|
let resolvedMcpServers: Awaited<ReturnType<typeof resolveMcpServers>> | null = null;
|
|
394
412
|
if (shouldConnectMcp({
|
|
395
413
|
mcpServerUsageCount: mcpServerUsages.length,
|
|
396
414
|
channelMessagingCount: channelMessaging.length,
|
|
397
415
|
conversationChannelId,
|
|
416
|
+
memoryCaptureEnabled: captureMemories,
|
|
398
417
|
})) {
|
|
399
418
|
await reportSetupProgress(client, executionId, "Connecting tools…");
|
|
400
419
|
const transportPosture = resolveMcpTransportPosture(config.mode);
|
|
@@ -456,6 +475,32 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
456
475
|
backfilledServers, conversationAttachment, "conversation participation",
|
|
457
476
|
);
|
|
458
477
|
}
|
|
478
|
+
|
|
479
|
+
// The memory capture attachment (DD-005 D1) — same after-backfill
|
|
480
|
+
// rule. The capture context is attribution the server verifies or
|
|
481
|
+
// trusts per edition (Stage 3 provenance decision); the subject is
|
|
482
|
+
// never threaded — it derives from the credential.
|
|
483
|
+
if (captureMemories) {
|
|
484
|
+
const memoryAttachment = synthesizeMemoryAttachment(
|
|
485
|
+
execution.spec?.recalledMemories,
|
|
486
|
+
{
|
|
487
|
+
org: session.metadata?.org ?? "",
|
|
488
|
+
agentId: agent.metadata?.id ?? "",
|
|
489
|
+
sessionId,
|
|
490
|
+
agentExecutionId: executionId,
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
bridgeEndpoint: config.mcpBridgeEndpoint,
|
|
494
|
+
credential: attachmentCredential,
|
|
495
|
+
backendEndpoint: config.stigmerBackendEndpoint,
|
|
496
|
+
},
|
|
497
|
+
);
|
|
498
|
+
if (memoryAttachment) {
|
|
499
|
+
backfilledServers = injectSynthesizedAttachment(
|
|
500
|
+
backfilledServers, memoryAttachment, "memory capture",
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
459
504
|
resolvedMcpServers = { resolvedServers: backfilledServers };
|
|
460
505
|
timing.mark("backfill_mcp");
|
|
461
506
|
|
|
@@ -554,7 +599,27 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
554
599
|
}
|
|
555
600
|
timing.mark("inject_attachments");
|
|
556
601
|
|
|
557
|
-
// 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
|
+
);
|
|
558
623
|
const systemPrompt = buildEnhancedSystemPrompt({
|
|
559
624
|
instructions,
|
|
560
625
|
provisionResults,
|
|
@@ -578,9 +643,7 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
578
643
|
declaredPreferences: readDeclaredPreferences(
|
|
579
644
|
execution.spec!.declaredPreferences,
|
|
580
645
|
),
|
|
581
|
-
recalledMemories:
|
|
582
|
-
execution.spec!.recalledMemories,
|
|
583
|
-
),
|
|
646
|
+
recalledMemories: memorySelection.content,
|
|
584
647
|
});
|
|
585
648
|
|
|
586
649
|
// Step 9: Construct the LLM model. Resolution to the provider API id
|
|
@@ -928,6 +991,7 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
928
991
|
casObserver,
|
|
929
992
|
captureMode,
|
|
930
993
|
gitWorkspace,
|
|
994
|
+
recalledMemoriesReport: memorySelection.report,
|
|
931
995
|
};
|
|
932
996
|
} catch (err) {
|
|
933
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(
|