agenr 2.0.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3899 @@
1
+ import {
2
+ CLOSE_EVENT_HISTORY_LIMIT,
3
+ EPISODE_SUMMARY_TIMEOUT_MS,
4
+ RECALL_TOOL_PARAMETERS,
5
+ SESSION_LINEAGE_REASONS,
6
+ STORE_TOOL_PARAMETERS,
7
+ UPDATE_TOOL_PARAMETERS,
8
+ WORKING_CANDIDATE_PROMOTION_STATUSES,
9
+ asRecord,
10
+ buildRecallToolServices,
11
+ composeHostPluginServices,
12
+ createClaimExtractionFromAgenrConfig,
13
+ createDeadlineAwareEpisodeSummaryLlm,
14
+ createSessionStartTracker,
15
+ embedEpisodeSummaryWithinBudget,
16
+ extractRecentTurnsFromMessages,
17
+ formatAgenrSessionStartRecall,
18
+ formatErrorMessage,
19
+ formatTargetSelector,
20
+ formatUnifiedRecallResults,
21
+ isCloseManagedStatus,
22
+ isModelVisibleOperationType,
23
+ isMutableWorkingSetStatus,
24
+ isTrustedHostMutationSource,
25
+ isTrustedHostOnlyWorkingOperation,
26
+ normalizeEventLimit,
27
+ normalizeListLimit,
28
+ normalizeOptionalString,
29
+ normalizePluginInjectionMemoryPolicyConfig,
30
+ normalizePromptText,
31
+ parseRecallToolParams,
32
+ parseStoreToolParams,
33
+ parseUpdateToolParams,
34
+ resolveBeforeTurnPolicy,
35
+ resolveSessionIdentityKey,
36
+ resolveSessionStartPolicy,
37
+ resolveWorkingContextGate,
38
+ runRecallMemoryTool,
39
+ runSessionStart,
40
+ runStoreMemoryTool,
41
+ runUpdateMemoryTool,
42
+ sanitizeUpdateToolParams,
43
+ writeBoundedSingleTranscriptEpisode
44
+ } from "../../chunk-MYZ2CWY6.js";
45
+ import {
46
+ createSingleTranscriptDiscoveryPort
47
+ } from "../../chunk-LAXNNWHM.js";
48
+ import {
49
+ buildRecallToolDetails,
50
+ formatAgenrBeforeTurnRecall,
51
+ runBeforeTurn
52
+ } from "../../chunk-575MUIW5.js";
53
+ import {
54
+ AGENR_FEATURE_FLAG_KEYS,
55
+ DEFAULT_AGENR_FEATURE_FLAGS,
56
+ createLlmClient,
57
+ isRecord,
58
+ readOptionalFiniteNumber,
59
+ readOptionalTrimmedString,
60
+ resolveLlmApiKey,
61
+ resolveModel
62
+ } from "../../chunk-ELR2HSVC.js";
63
+ import "../../chunk-5LADPJ4C.js";
64
+
65
+ // src/adapters/skeln/config.ts
66
+ function parseSkelnMemoryPolicyJson(raw) {
67
+ try {
68
+ const parsed = JSON.parse(raw);
69
+ const normalized = normalizePluginInjectionMemoryPolicyConfig(parsed);
70
+ if (!normalized.ok) {
71
+ return { ok: false, error: normalized.errors.join("; ") };
72
+ }
73
+ return { ok: true, value: normalized.value };
74
+ } catch (error) {
75
+ const message = error instanceof Error ? error.message : String(error);
76
+ return { ok: false, error: `memoryPolicy must be valid JSON: ${message}` };
77
+ }
78
+ }
79
+ function readSkelnMemoryPolicySetting(skeln) {
80
+ const value = skeln.getSetting("memoryPolicy");
81
+ if (value === void 0) {
82
+ return { ok: true, value: void 0 };
83
+ }
84
+ if (typeof value !== "string" || value.trim().length === 0) {
85
+ return { ok: false, error: "memoryPolicy must be a non-empty JSON string when provided" };
86
+ }
87
+ return parseSkelnMemoryPolicyJson(value.trim());
88
+ }
89
+ function mergeSkelnMemoryPolicy(fromSettings, fromOptions) {
90
+ if (!fromSettings) {
91
+ return fromOptions;
92
+ }
93
+ if (!fromOptions) {
94
+ return fromSettings;
95
+ }
96
+ const slotPolicies = mergeSlotPolicies(fromSettings.slotPolicies, fromOptions.slotPolicies);
97
+ const sessionStart = fromSettings.sessionStart || fromOptions.sessionStart ? { ...fromSettings.sessionStart, ...fromOptions.sessionStart } : void 0;
98
+ const beforeTurn = fromSettings.beforeTurn || fromOptions.beforeTurn ? { ...fromSettings.beforeTurn, ...fromOptions.beforeTurn } : void 0;
99
+ const workingContext = fromSettings.workingContext || fromOptions.workingContext ? { ...fromSettings.workingContext, ...fromOptions.workingContext } : void 0;
100
+ if (!slotPolicies && !sessionStart && !beforeTurn && !workingContext) {
101
+ return void 0;
102
+ }
103
+ return {
104
+ ...slotPolicies ? { slotPolicies } : {},
105
+ ...sessionStart ? { sessionStart } : {},
106
+ ...beforeTurn ? { beforeTurn } : {},
107
+ ...workingContext ? { workingContext } : {}
108
+ };
109
+ }
110
+ function mergeSlotPolicies(fromSettings, fromOptions) {
111
+ if (!fromSettings) {
112
+ return fromOptions;
113
+ }
114
+ if (!fromOptions) {
115
+ return fromSettings;
116
+ }
117
+ const attributeHeads = fromSettings.attributeHeads || fromOptions.attributeHeads ? {
118
+ ...fromSettings.attributeHeads,
119
+ ...fromOptions.attributeHeads
120
+ } : void 0;
121
+ if (!attributeHeads) {
122
+ return void 0;
123
+ }
124
+ return { attributeHeads };
125
+ }
126
+
127
+ // src/app/working-memory/projection-render.ts
128
+ var UTF8_BYTE_LENGTH = new TextEncoder();
129
+ function byteLength(content) {
130
+ return UTF8_BYTE_LENGTH.encode(content).length;
131
+ }
132
+ function createWorkingContextStubProjection(input) {
133
+ const content = [
134
+ "<agenr_work_context>",
135
+ "Working memory is unavailable for this turn.",
136
+ `Reason: ${input.reason}`,
137
+ "Treat this as transient task-state metadata, not durable truth.",
138
+ "</agenr_work_context>"
139
+ ].join("\n");
140
+ return {
141
+ kind: "working_set",
142
+ renderMode: "stub",
143
+ content,
144
+ ...input.workingSetId ? { workingSetId: input.workingSetId } : {},
145
+ ...input.revision !== void 0 ? { revision: input.revision } : {},
146
+ sourceRef: input.sourceRef,
147
+ byteLength: byteLength(content)
148
+ };
149
+ }
150
+ function createWorkingContextFullProjection(workingSet, sourceRef) {
151
+ const snapshot = workingSet.snapshot;
152
+ const lines = [
153
+ "<agenr_work_context>",
154
+ "This is transient working memory for the current task, not durable truth.",
155
+ "It may be stale or hypothetical. Prefer current filesystem, git, tests, tool output, and the user's latest message for current-state claims.",
156
+ "",
157
+ `Scope: ${escapeText(workingSet.scopeKind)} ${escapeText(workingSet.scopeKey)}`,
158
+ `Working set: ${escapeText(workingSet.id)}`,
159
+ `Revision: ${workingSet.revision}`,
160
+ `Status: ${escapeText(workingSet.status)}`,
161
+ ...optionalLine("Objective", snapshot.objective ?? workingSet.title),
162
+ ...optionalLine("Summary", snapshot.summary),
163
+ ...renderStringList("Current plan", snapshot.currentPlan),
164
+ ...renderCheckpoint(snapshot.checkpoint?.summary),
165
+ ...renderNextActions(snapshot.nextActions),
166
+ ...renderLabeledNotes(
167
+ "Touched files",
168
+ snapshot.files,
169
+ (file) => file.path.trim().length > 0,
170
+ (file) => {
171
+ const note = file.note ? ` - ${escapeText(file.note)}` : "";
172
+ return `${escapeText(file.path)}${note}`;
173
+ }
174
+ ),
175
+ ...renderLabeledNotes(
176
+ "Recent commands",
177
+ snapshot.commands,
178
+ (command) => command.command.trim().length > 0,
179
+ (command) => {
180
+ const outcome = command.outcome ? ` - ${escapeText(command.outcome)}` : "";
181
+ return `${escapeText(command.command)}${outcome}`;
182
+ }
183
+ ),
184
+ ...renderLabeledNotes(
185
+ "Decisions",
186
+ snapshot.decisions,
187
+ (decision) => decision.decision.trim().length > 0,
188
+ (decision) => {
189
+ const rationale = decision.rationale ? ` - ${escapeText(decision.rationale)}` : "";
190
+ return `${escapeText(decision.decision)}${rationale}`;
191
+ }
192
+ ),
193
+ ...renderLabeledNotes(
194
+ "Assumptions",
195
+ snapshot.assumptions,
196
+ (assumption) => assumption.assumption.trim().length > 0,
197
+ (assumption) => {
198
+ const confidence = assumption.confidence ? ` [${assumption.confidence}]` : "";
199
+ const validated = assumption.validated === true ? " (validated)" : assumption.validated === false ? " (unvalidated)" : "";
200
+ return `${escapeText(assumption.assumption)}${confidence}${validated}`;
201
+ }
202
+ ),
203
+ ...renderStringList("Blockers", snapshot.blockers),
204
+ ...renderStringList("Open questions", snapshot.openQuestions),
205
+ ...renderCandidates(snapshot.candidates),
206
+ "",
207
+ "Rules:",
208
+ "- Update this working set when material task state changes.",
209
+ "- Leave a checkpoint before pausing, handing off, compacting, forking, or waiting.",
210
+ "- Do not close this working set; only the user clears goals with /goal clear.",
211
+ "- Do not store transient WIP with agenr_store.",
212
+ "- Promote only durable facts, decisions, preferences, or reusable procedures explicitly.",
213
+ "</agenr_work_context>"
214
+ ];
215
+ const content = lines.join("\n");
216
+ return {
217
+ kind: "working_set",
218
+ renderMode: "full",
219
+ content,
220
+ workingSetId: workingSet.id,
221
+ revision: workingSet.revision,
222
+ sourceRef,
223
+ byteLength: byteLength(content)
224
+ };
225
+ }
226
+ function escapeText(value) {
227
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
228
+ }
229
+ function optionalLine(label, value) {
230
+ const normalized = value?.trim();
231
+ return normalized ? [`${label}: ${escapeText(normalized)}`] : [];
232
+ }
233
+ function renderStringList(title, values) {
234
+ const items = (values ?? []).map((value) => value.trim()).filter((value) => value.length > 0);
235
+ if (items.length === 0) {
236
+ return [];
237
+ }
238
+ return ["", `${title}:`, ...items.map((item) => `- ${escapeText(item)}`)];
239
+ }
240
+ function renderCheckpoint(summary) {
241
+ const normalized = summary?.trim();
242
+ return normalized ? ["", "Last checkpoint:", escapeText(normalized)] : [];
243
+ }
244
+ function renderLabeledNotes(title, items, isValid, formatLine) {
245
+ const filtered = (items ?? []).filter(isValid);
246
+ if (filtered.length === 0) {
247
+ return [];
248
+ }
249
+ return ["", `${title}:`, ...filtered.map((item) => `- ${formatLine(item)}`)];
250
+ }
251
+ function renderNextActions(actions) {
252
+ const items = (actions ?? []).filter((action) => action.text.trim().length > 0);
253
+ if (items.length === 0) {
254
+ return [];
255
+ }
256
+ return [
257
+ "",
258
+ "Next actions:",
259
+ ...items.map((action) => {
260
+ const status = action.status ? ` [${action.status}]` : "";
261
+ const ref = action.ref ? ` (${escapeText(action.ref)})` : "";
262
+ return `- ${escapeText(action.text)}${status}${ref}`;
263
+ })
264
+ ];
265
+ }
266
+ function renderCandidates(candidates) {
267
+ const pending = (candidates ?? []).filter((candidate) => candidate.promotionStatus === "pending");
268
+ if (pending.length === 0) {
269
+ return [];
270
+ }
271
+ return [
272
+ "",
273
+ "Pending memory candidates:",
274
+ ...pending.map((candidate) => {
275
+ if (candidate.kind === "episodic") {
276
+ return `- episodic: ${escapeText(candidate.summary)}`;
277
+ }
278
+ return `- ${candidate.kind}: ${escapeText(candidate.subject)}`;
279
+ })
280
+ ];
281
+ }
282
+
283
+ // src/app/working-memory/projection.ts
284
+ function createToolSuccessProjection(workingSet, action, timestamp) {
285
+ return createWorkingContextFullProjection(workingSet, `agenr_work:${action}:${timestamp}`);
286
+ }
287
+ function shouldInjectWorkingContext(projection) {
288
+ return projection.renderMode === "full" && projection.content.trim().length > 0;
289
+ }
290
+ function toWorkingContextAuditPointer(projection) {
291
+ if (projection.workingSetId === void 0 || projection.revision === void 0) {
292
+ return void 0;
293
+ }
294
+ return {
295
+ source: "agenr_work",
296
+ workingSetId: projection.workingSetId,
297
+ revision: projection.revision,
298
+ sourceRef: projection.sourceRef,
299
+ bytes: projection.byteLength,
300
+ summary: projection.renderMode === "full" ? `Working set ${projection.workingSetId} rev ${projection.revision}` : `Working memory stub (${projection.sourceRef})`
301
+ };
302
+ }
303
+
304
+ // src/adapters/skeln/session/scope.ts
305
+ function resolveSkelnSessionKey(sessionId, cwd) {
306
+ const normalizedSessionId = sessionId.trim();
307
+ const normalizedCwd = cwd.trim();
308
+ if (!normalizedSessionId) {
309
+ throw new Error("Skeln session id is required to derive a session key.");
310
+ }
311
+ if (!normalizedCwd) {
312
+ return `skeln:session:${normalizedSessionId}`;
313
+ }
314
+ return `skeln:session:${normalizedSessionId}:cwd:${normalizedCwd}`;
315
+ }
316
+ function normalizeSkelnScopeField(value) {
317
+ const trimmed = value?.trim();
318
+ return trimmed && trimmed.length > 0 ? trimmed : void 0;
319
+ }
320
+ function buildSkelnHostContext(input) {
321
+ const cwd = input.cwd.trim();
322
+ if (!cwd) {
323
+ throw new Error("Skeln cwd is required to build host context.");
324
+ }
325
+ const gitRoot = normalizeSkelnScopeField(input.gitRoot);
326
+ const gitBranch = normalizeSkelnScopeField(input.gitBranch);
327
+ const project = normalizeSkelnScopeField(input.project);
328
+ return {
329
+ cwd,
330
+ sessionKey: resolveSkelnSessionKey(input.sessionId, cwd),
331
+ conversationKey: input.sessionId.trim(),
332
+ ...gitRoot ? { gitRoot } : {},
333
+ ...gitBranch ? { gitBranch } : {},
334
+ ...project ? { project } : {}
335
+ };
336
+ }
337
+ function mergeSkelnHostContext(defaults, override) {
338
+ if (!override) {
339
+ return defaults;
340
+ }
341
+ const merged = {
342
+ cwd: normalizeSkelnScopeField(override.cwd) ?? defaults.cwd,
343
+ sessionKey: normalizeSkelnScopeField(override.sessionKey) ?? defaults.sessionKey,
344
+ conversationKey: normalizeSkelnScopeField(override.conversationKey) ?? defaults.conversationKey
345
+ };
346
+ const gitRoot = normalizeSkelnScopeField(override.gitRoot) ?? defaults.gitRoot;
347
+ if (gitRoot) {
348
+ merged.gitRoot = gitRoot;
349
+ }
350
+ const gitBranch = normalizeSkelnScopeField(override.gitBranch) ?? defaults.gitBranch;
351
+ if (gitBranch) {
352
+ merged.gitBranch = gitBranch;
353
+ }
354
+ const project = normalizeSkelnScopeField(override.project) ?? defaults.project;
355
+ if (project) {
356
+ merged.project = project;
357
+ }
358
+ return merged;
359
+ }
360
+ function toSkelnSessionScope(hostContext, sessionId) {
361
+ const normalizedSessionId = sessionId.trim();
362
+ if (!normalizedSessionId) {
363
+ throw new Error("Skeln session id is required to build session scope.");
364
+ }
365
+ return {
366
+ sessionId: normalizedSessionId,
367
+ sessionKey: hostContext.sessionKey,
368
+ cwd: hostContext.cwd,
369
+ conversationKey: hostContext.conversationKey ?? normalizedSessionId,
370
+ ...hostContext.gitRoot ? { gitRoot: hostContext.gitRoot } : {},
371
+ ...hostContext.gitBranch ? { gitBranch: hostContext.gitBranch } : {},
372
+ ...hostContext.project ? { project: hostContext.project } : {}
373
+ };
374
+ }
375
+ function toWorkingScopeFromSkelnSession(scope) {
376
+ return {
377
+ sessionId: scope.sessionId,
378
+ conversationKey: scope.conversationKey ?? scope.sessionId,
379
+ cwd: scope.cwd,
380
+ ...scope.gitRoot ? { gitRoot: scope.gitRoot } : {},
381
+ ...scope.gitBranch ? { gitBranch: scope.gitBranch } : {},
382
+ ...scope.project ? { project: scope.project } : {}
383
+ };
384
+ }
385
+
386
+ // src/adapters/skeln/session/branch-compaction.ts
387
+ function extractSkelnBeforeTurnBranchMessages(branch) {
388
+ return selectEntriesAfterLatestCompaction(branch).flatMap((entry) => isSkelnMessageEntry(entry) ? [entry.message] : []);
389
+ }
390
+ function selectEntriesAfterLatestCompaction(branch) {
391
+ const latestCompaction = findLatestCompactionEntry(branch);
392
+ if (!latestCompaction) {
393
+ return branch;
394
+ }
395
+ const firstKeptEntryId = typeof latestCompaction.firstKeptEntryId === "string" ? latestCompaction.firstKeptEntryId.trim() : "";
396
+ if (!firstKeptEntryId) {
397
+ return branch;
398
+ }
399
+ const keptStart = branch.findIndex((entry) => entry.id === firstKeptEntryId);
400
+ const compactionIndex = branch.findIndex((entry) => entry === latestCompaction);
401
+ if (keptStart < 0 || compactionIndex < 0 || keptStart >= compactionIndex) {
402
+ return branch;
403
+ }
404
+ return branch.slice(keptStart, compactionIndex).concat(branch.slice(compactionIndex + 1));
405
+ }
406
+ function findLatestCompactionEntry(branch) {
407
+ for (let index = branch.length - 1; index >= 0; index -= 1) {
408
+ const entry = branch[index];
409
+ if (entry?.type === "compaction") {
410
+ return entry;
411
+ }
412
+ }
413
+ return void 0;
414
+ }
415
+ function isSkelnMessageEntry(entry) {
416
+ if (entry.type !== "message" || !entry.message || typeof entry.message !== "object") {
417
+ return false;
418
+ }
419
+ const role = entry.message.role;
420
+ return role === "user" || role === "assistant" || role === "system" || role === "tool";
421
+ }
422
+
423
+ // src/adapters/skeln/format/prompt-section.ts
424
+ function buildAgenrSkelnMemoryPromptSection() {
425
+ return [
426
+ "## Memory Recall",
427
+ "Before answering anything about prior work, decisions, preferences, people, dates, unfinished work, or past sessions, call agenr_recall first. Session-start recall is automatic, and conservative before-turn recall may also appear as injected background context; use agenr_recall mid-session when you need context you do not already have.",
428
+ "agenr_recall supports exact fact recall plus historical and episodic recall behind one tool: use mode=entries for exact facts, decisions, thresholds, and versions; use mode=auto for prior-state questions like what was the previous approach, what did we use before, or what changed from X to Y; use mode=episodes when you explicitly want session narrative recall.",
429
+ "When Agenr injects memory automatically, treat it as non-user background context and use it silently when relevant rather than forcing it into the reply.",
430
+ "Use agenr_store for durable memory, not for logging. Store only the durable takeaway, standing rule, preference, risk, lesson, or relationship - not progress logs or data already canonical elsewhere.",
431
+ "Use agenr_update to correct metadata on an existing entry. Use agenr_store with supersedes for substantive content replacement.",
432
+ ""
433
+ ];
434
+ }
435
+
436
+ // src/adapters/skeln/memory-trace.ts
437
+ var AGENR_MEMORY_DOCTRINE_HEADER = "## Memory Recall";
438
+ function traceMemoryInjected(kind, input) {
439
+ return {
440
+ kind,
441
+ action: "injected",
442
+ ...input.bytes !== void 0 ? { bytes: input.bytes } : {},
443
+ ...input.summary ? { summary: input.summary } : {},
444
+ ...input.preview ? { preview: input.preview } : {},
445
+ ...input.workingSetId ? { workingSetId: input.workingSetId } : {},
446
+ ...input.revision !== void 0 ? { revision: input.revision } : {},
447
+ ...input.sourceRef ? { sourceRef: input.sourceRef } : {}
448
+ };
449
+ }
450
+ function traceMemorySkipped(kind, reason, summary) {
451
+ return {
452
+ kind,
453
+ action: "skipped",
454
+ reason,
455
+ ...summary ? { summary } : {}
456
+ };
457
+ }
458
+ function traceMemoryFailed(kind, reason) {
459
+ return {
460
+ kind,
461
+ action: "failed",
462
+ reason
463
+ };
464
+ }
465
+ function buildBeforeAgentStartMemoryTrace(input) {
466
+ const recallText = input.recallText?.trim();
467
+ const hasRecall = recallText !== void 0 && recallText.length > 0;
468
+ const memoryTrace = [];
469
+ if (input.baseSystemPrompt !== void 0 && input.systemPrompt !== void 0) {
470
+ const doctrineTrace = traceSystemPromptDoctrineInjected(input.baseSystemPrompt, input.systemPrompt);
471
+ if (doctrineTrace) {
472
+ memoryTrace.push(doctrineTrace);
473
+ }
474
+ }
475
+ if (input.workingContextTrace) {
476
+ memoryTrace.push(input.workingContextTrace);
477
+ }
478
+ if (input.recallKind) {
479
+ if (input.recallFailureReason) {
480
+ memoryTrace.push(traceMemoryFailed(input.recallKind, input.recallFailureReason));
481
+ } else if (input.recallSkippedReason) {
482
+ memoryTrace.push(traceMemorySkipped(input.recallKind, input.recallSkippedReason));
483
+ } else if (hasRecall && recallText) {
484
+ memoryTrace.push(
485
+ traceMemoryInjected(input.recallKind, {
486
+ bytes: recallText.length,
487
+ summary: input.recallKind === "session_start_recall" ? "session-start recall injected" : "before-turn recall injected",
488
+ preview: recallText
489
+ })
490
+ );
491
+ } else {
492
+ memoryTrace.push(traceMemorySkipped(input.recallKind, "no matching entries"));
493
+ }
494
+ }
495
+ return memoryTrace;
496
+ }
497
+ function traceWorkingContextInjected(audit, content) {
498
+ return traceMemoryInjected("working_context", {
499
+ bytes: audit?.bytes ?? content.length,
500
+ summary: audit?.summary,
501
+ preview: content,
502
+ workingSetId: audit?.workingSetId,
503
+ revision: audit?.revision,
504
+ sourceRef: audit?.sourceRef
505
+ });
506
+ }
507
+ function traceSystemPromptDoctrineInjected(baseSystemPrompt, updatedSystemPrompt) {
508
+ if (updatedSystemPrompt === baseSystemPrompt || !updatedSystemPrompt.includes(AGENR_MEMORY_DOCTRINE_HEADER)) {
509
+ return void 0;
510
+ }
511
+ const doctrineIndex = updatedSystemPrompt.indexOf(AGENR_MEMORY_DOCTRINE_HEADER);
512
+ const doctrine = doctrineIndex === -1 ? "" : updatedSystemPrompt.slice(doctrineIndex).trim();
513
+ return traceMemoryInjected("system_prompt", {
514
+ bytes: doctrine.length,
515
+ summary: "agenr memory doctrine appended",
516
+ preview: doctrine
517
+ });
518
+ }
519
+
520
+ // src/adapters/skeln/hooks/before-agent-start.ts
521
+ async function handleAgenrSkelnBeforeAgentStart(event, context, deps) {
522
+ const scope = await deps.resolveScope(context);
523
+ const doctrine = buildAgenrSkelnMemoryPromptSection().join("\n");
524
+ const systemPromptWithDoctrine = appendPromptSection(event.systemPrompt, doctrine);
525
+ const trackerState = deps.sessionStartTracker.consume(scope.sessionId, scope.sessionKey);
526
+ if (trackerState.isFirst) {
527
+ return resolveSessionStartInjection(scope, event.systemPrompt, systemPromptWithDoctrine, deps.servicesPromise);
528
+ }
529
+ return resolveBeforeTurnInjection(event, scope, event.systemPrompt, systemPromptWithDoctrine, context, deps.servicesPromise);
530
+ }
531
+ function buildAgenrSkelnInjectionMessage(content) {
532
+ return {
533
+ role: "user",
534
+ content: [{ type: "text", text: content }],
535
+ timestamp: Date.now()
536
+ };
537
+ }
538
+ function appendPromptSection(systemPrompt, section) {
539
+ const trimmedSection = section.trim();
540
+ if (trimmedSection.length === 0 || systemPrompt.includes(trimmedSection)) {
541
+ return systemPrompt;
542
+ }
543
+ return `${systemPrompt.trimEnd()}
544
+
545
+ ${trimmedSection}`;
546
+ }
547
+ async function resolveSessionStartInjection(scope, baseSystemPrompt, systemPrompt, servicesPromise) {
548
+ let workingInjection;
549
+ try {
550
+ const services = await servicesPromise;
551
+ workingInjection = await resolveWorkingContextInjection(services, scope, `skeln:session-start:${scope.sessionKey}`);
552
+ if (services.skelnConfig.memoryPolicy?.sessionStart?.enabled === false) {
553
+ return composeSkelnBeforeAgentStartResult({
554
+ baseSystemPrompt,
555
+ systemPrompt,
556
+ recallKind: "session_start_recall",
557
+ recallSkippedReason: "memoryPolicy.sessionStart.enabled=false",
558
+ workingInjection
559
+ });
560
+ }
561
+ const sessionStartPatch = await runSessionStart(
562
+ {
563
+ sessionKey: scope.sessionKey,
564
+ policy: resolveSessionStartPolicy(services.skelnConfig.memoryPolicy)
565
+ },
566
+ services.sessionStart
567
+ );
568
+ return composeSkelnBeforeAgentStartResult({
569
+ baseSystemPrompt,
570
+ systemPrompt,
571
+ recallKind: "session_start_recall",
572
+ recallText: formatAgenrSessionStartRecall(sessionStartPatch),
573
+ workingInjection
574
+ });
575
+ } catch (error) {
576
+ logInjectionFailure("session-start", scope, error);
577
+ return composeSkelnBeforeAgentStartResult({
578
+ baseSystemPrompt,
579
+ systemPrompt,
580
+ recallKind: "session_start_recall",
581
+ recallFailureReason: error instanceof Error ? error.message : String(error),
582
+ workingInjection
583
+ });
584
+ }
585
+ }
586
+ async function resolveBeforeTurnInjection(event, scope, baseSystemPrompt, systemPrompt, context, servicesPromise) {
587
+ const services = await servicesPromise;
588
+ const workingInjection = await resolveWorkingContextInjection(services, scope, `skeln:before-turn:${scope.sessionKey}`);
589
+ if (services.skelnConfig.memoryPolicy?.beforeTurn?.enabled === false) {
590
+ return composeSkelnBeforeAgentStartResult({
591
+ baseSystemPrompt,
592
+ systemPrompt,
593
+ recallKind: "before_turn_recall",
594
+ recallSkippedReason: "memoryPolicy.beforeTurn.enabled=false",
595
+ workingInjection
596
+ });
597
+ }
598
+ const currentTurnText = normalizePromptText(event.prompt);
599
+ if (!currentTurnText) {
600
+ return composeSkelnBeforeAgentStartResult({
601
+ baseSystemPrompt,
602
+ systemPrompt,
603
+ recallKind: "before_turn_recall",
604
+ recallSkippedReason: "empty turn prompt",
605
+ workingInjection
606
+ });
607
+ }
608
+ try {
609
+ const branchMessages = extractSkelnBeforeTurnBranchMessages(context.sessionManager.getBranch());
610
+ const beforeTurnPatch = await runBeforeTurn(
611
+ {
612
+ sessionKey: scope.sessionKey,
613
+ currentTurnText,
614
+ recentTurns: extractRecentTurnsFromMessages(branchMessages),
615
+ policy: resolveBeforeTurnPolicy(services.skelnConfig.memoryPolicy)
616
+ },
617
+ services.beforeTurn
618
+ );
619
+ return composeSkelnBeforeAgentStartResult({
620
+ baseSystemPrompt,
621
+ systemPrompt,
622
+ recallKind: "before_turn_recall",
623
+ recallText: formatAgenrBeforeTurnRecall(beforeTurnPatch),
624
+ workingInjection
625
+ });
626
+ } catch (error) {
627
+ logInjectionFailure("before-turn", scope, error);
628
+ return composeSkelnBeforeAgentStartResult({
629
+ baseSystemPrompt,
630
+ systemPrompt,
631
+ recallKind: "before_turn_recall",
632
+ recallFailureReason: error instanceof Error ? error.message : String(error),
633
+ workingInjection
634
+ });
635
+ }
636
+ }
637
+ function composeSkelnBeforeAgentStartResult(input) {
638
+ const recallText = input.recallText?.trim();
639
+ const memoryTrace = buildBeforeAgentStartMemoryTrace({
640
+ baseSystemPrompt: input.baseSystemPrompt,
641
+ systemPrompt: input.systemPrompt,
642
+ recallKind: input.recallKind,
643
+ recallText,
644
+ recallSkippedReason: input.recallSkippedReason,
645
+ recallFailureReason: input.recallFailureReason,
646
+ workingContextTrace: input.workingInjection?.trace
647
+ });
648
+ return {
649
+ systemPrompt: input.systemPrompt,
650
+ ...recallText ? { message: buildAgenrSkelnInjectionMessage(recallText) } : {},
651
+ ...input.workingInjection?.transientMessages ? { transientMessages: input.workingInjection.transientMessages } : {},
652
+ ...input.workingInjection?.workingContextAudit ? { workingContextAudit: input.workingInjection.workingContextAudit } : {},
653
+ ...memoryTrace.length > 0 ? { memoryTrace } : {}
654
+ };
655
+ }
656
+ async function resolveWorkingContextInjection(services, scope, sourceRef) {
657
+ const gate = resolveWorkingContextGate(services.capabilities.workingMemory, services.skelnConfig.memoryPolicy);
658
+ if (!gate.ok) {
659
+ return { trace: traceMemorySkipped("working_context", gate.reason) };
660
+ }
661
+ return loadWorkingContextProjection(services, scope, sourceRef);
662
+ }
663
+ async function loadWorkingContextProjection(services, scope, sourceRef) {
664
+ try {
665
+ const projection = await services.workingMemory.renderProjection({
666
+ sourceRef,
667
+ scope: toWorkingScopeFromSkelnSession(scope)
668
+ });
669
+ if (!shouldInjectWorkingContext(projection)) {
670
+ const reason = projection.renderMode !== "full" ? "working projection stub" : "empty working projection";
671
+ return { trace: traceMemorySkipped("working_context", reason) };
672
+ }
673
+ const workingContextAudit = toWorkingContextAuditPointer(projection);
674
+ return {
675
+ transientMessages: [buildAgenrSkelnInjectionMessage(projection.content)],
676
+ ...workingContextAudit ? { workingContextAudit } : {},
677
+ trace: traceWorkingContextInjected(workingContextAudit, projection.content)
678
+ };
679
+ } catch (error) {
680
+ const message = error instanceof Error ? error.message : String(error);
681
+ console.warn(`[agenr] working-context projection failed for session=${scope.sessionId} key=${scope.sessionKey}: ${message}`);
682
+ return { trace: traceMemoryFailed("working_context", message) };
683
+ }
684
+ }
685
+ function logInjectionFailure(phase, scope, error) {
686
+ const message = error instanceof Error ? error.message : String(error);
687
+ console.warn(`[agenr] ${phase} recall failed for session=${scope.sessionId} key=${scope.sessionKey}: ${message}`);
688
+ }
689
+
690
+ // src/adapters/skeln/hooks/session-memory.ts
691
+ var LINEAGE_SESSION_START_REASONS = /* @__PURE__ */ new Set(["fork", "clone", "resume"]);
692
+ function buildSkelnSessionStartTriggerEvent(scope, transition = {}) {
693
+ const previousSessionFile = transition.previousSessionFile?.trim();
694
+ const transitionReason = resolveSessionStartTransitionReason(transition.reason, previousSessionFile);
695
+ const predecessor = buildSessionStartPredecessor(transitionReason, previousSessionFile);
696
+ return {
697
+ type: "session_start",
698
+ sessionKey: scope.sessionKey,
699
+ childSessionKey: scope.sessionKey,
700
+ transitionReason,
701
+ ...predecessor ? { predecessor } : {},
702
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
703
+ };
704
+ }
705
+ function buildSkelnSessionBeforeForkTriggerEvent(scope, event) {
706
+ const forkEntryId = event.entryId?.trim();
707
+ return {
708
+ type: "session_before_fork",
709
+ sessionKey: scope.sessionKey,
710
+ ...forkEntryId ? {
711
+ predecessor: {
712
+ forkEntryId,
713
+ forkPosition: event.position
714
+ }
715
+ } : {},
716
+ payload: event,
717
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
718
+ };
719
+ }
720
+ function buildSkelnSessionBeforeCompactTriggerEvent(scope) {
721
+ return {
722
+ type: "session_before_compact",
723
+ sessionKey: scope.sessionKey,
724
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
725
+ };
726
+ }
727
+ function buildSkelnSessionCompactTriggerEvent(scope, event) {
728
+ const compactionEntry = event.compactionEntry;
729
+ return {
730
+ type: "session_compact",
731
+ sessionKey: scope.sessionKey,
732
+ artifact: buildCompactionCheckpointArtifact(scope.sessionKey, compactionEntry, event.fromExtension),
733
+ workingScope: toWorkingScopeFromSkelnSession(scope),
734
+ payload: event,
735
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
736
+ };
737
+ }
738
+ function buildSkelnSessionBeforeTreeTriggerEvent(scope, event) {
739
+ return {
740
+ type: "session_before_tree",
741
+ sessionKey: scope.sessionKey,
742
+ payload: event,
743
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
744
+ };
745
+ }
746
+ function buildSkelnSessionTreeTriggerEvent(scope, event) {
747
+ const summaryEntry = event.summaryEntry;
748
+ const artifact = summaryEntry ? {
749
+ kind: "branch_abandonment",
750
+ source: "skeln",
751
+ sourceId: summaryEntry.id,
752
+ sourceRef: `branch_summary:${summaryEntry.id}`,
753
+ summary: summaryEntry.summary,
754
+ metadata: {
755
+ fromId: summaryEntry.fromId,
756
+ oldLeafId: event.oldLeafId,
757
+ newLeafId: event.newLeafId,
758
+ ...event.fromExtension !== void 0 ? { fromExtension: event.fromExtension } : {}
759
+ }
760
+ } : void 0;
761
+ return {
762
+ type: "session_tree",
763
+ sessionKey: scope.sessionKey,
764
+ ...artifact ? { artifact } : {},
765
+ payload: event,
766
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
767
+ };
768
+ }
769
+ function buildSkelnSessionShutdownTriggerEvent(scope, event) {
770
+ return {
771
+ type: "session_shutdown",
772
+ sessionKey: scope.sessionKey,
773
+ workingScope: toWorkingScopeFromSkelnSession(scope),
774
+ shutdownReason: event.reason,
775
+ payload: event,
776
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
777
+ };
778
+ }
779
+ function logSessionMemoryTriggerResult(result) {
780
+ if (result.accepted || result.reason === "feature_disabled") {
781
+ return;
782
+ }
783
+ console.warn(`[agenr] session-memory trigger rejected: ${result.reason} (${result.message})`);
784
+ }
785
+ function resolveSessionStartTransitionReason(reason, previousSessionFile) {
786
+ if (isLineageSessionStartReason(reason)) {
787
+ return reason;
788
+ }
789
+ return previousSessionFile ? "resume" : "new";
790
+ }
791
+ function isLineageSessionStartReason(reason) {
792
+ return reason !== void 0 && LINEAGE_SESSION_START_REASONS.has(reason);
793
+ }
794
+ function buildSessionStartPredecessor(transitionReason, previousSessionFile) {
795
+ if (transitionReason === "new") {
796
+ return void 0;
797
+ }
798
+ const sourceRef = previousSessionFile?.trim();
799
+ if (!sourceRef) {
800
+ return void 0;
801
+ }
802
+ return { sourceRef };
803
+ }
804
+ function buildCompactionCheckpointArtifact(sessionKey, compactionEntry, fromExtension) {
805
+ return {
806
+ kind: "compaction_checkpoint",
807
+ sessionKey,
808
+ source: "skeln",
809
+ sourceId: compactionEntry.id,
810
+ sourceRef: `compaction:${compactionEntry.id}`,
811
+ summary: compactionEntry.summary,
812
+ metadata: {
813
+ firstKeptEntryId: compactionEntry.firstKeptEntryId,
814
+ fromExtension
815
+ }
816
+ };
817
+ }
818
+
819
+ // src/adapters/skeln/hooks/subagent-findings.ts
820
+ var SUBAGENT_OUTCOME_MAX_CHARS = 3e3;
821
+ var SUBAGENT_RESULT_LIMIT = 6;
822
+ async function recordSkelnSubagentFindings(servicesPromise, resolveScope, context, event) {
823
+ const commandNote = buildSkelnSubagentCommandNote(event, (/* @__PURE__ */ new Date()).toISOString());
824
+ if (!commandNote) {
825
+ return;
826
+ }
827
+ try {
828
+ const [services, scope] = await Promise.all([servicesPromise, resolveScope(context)]);
829
+ const result = await services.workingMemory.run({
830
+ action: "update",
831
+ scope: toWorkingScopeFromSkelnSession(scope),
832
+ operation: {
833
+ type: "add_command_note",
834
+ command: commandNote
835
+ },
836
+ updateReason: "Recorded bounded subagent findings on the parent working set.",
837
+ actor: "runtime",
838
+ source: "lifecycle_hook"
839
+ });
840
+ if (!result.ok && !isExpectedSubagentFindingSkip(result.code)) {
841
+ console.warn(`[agenr] subagent findings working-memory update failed: ${result.message}`);
842
+ }
843
+ } catch (error) {
844
+ console.warn(`[agenr] subagent findings capture failed: ${formatErrorMessage(error)}`);
845
+ }
846
+ }
847
+ function buildSkelnSubagentCommandNote(event, observedAt) {
848
+ if (event.toolName !== "subagent" || event.isError) {
849
+ return void 0;
850
+ }
851
+ const details = isRecord(event.details) ? event.details : void 0;
852
+ if (!details) {
853
+ return void 0;
854
+ }
855
+ const mode = readOptionalTrimmedString(details.mode) ?? "unknown";
856
+ const artifactPath = readOptionalTrimmedString(details.artifactPath);
857
+ const results = Array.isArray(details.results) ? details.results.flatMap(normalizeDelegationResult).slice(0, SUBAGENT_RESULT_LIMIT) : [];
858
+ if (results.length === 0 && !artifactPath) {
859
+ return void 0;
860
+ }
861
+ const resultLines = results.map(
862
+ (result) => `- ${result.agent}${result.name ? `/${result.name}` : ""}: ${result.status}${result.summary ? ` - ${result.summary}` : ""}`
863
+ );
864
+ const outcome = truncateText(
865
+ [
866
+ `mode=${mode}`,
867
+ ...resultLines,
868
+ ...artifactPath ? [`artifact=${artifactPath}`] : [],
869
+ ...results.length >= SUBAGENT_RESULT_LIMIT ? ["additional subagent results omitted"] : []
870
+ ].join("\n"),
871
+ SUBAGENT_OUTCOME_MAX_CHARS
872
+ );
873
+ return {
874
+ command: `subagent ${mode}`,
875
+ outcome,
876
+ observedAt
877
+ };
878
+ }
879
+ function normalizeDelegationResult(value) {
880
+ const record = isRecord(value) ? value : void 0;
881
+ if (!record) {
882
+ return [];
883
+ }
884
+ const agent = readOptionalTrimmedString(record.agent);
885
+ const status = readOptionalTrimmedString(record.status);
886
+ if (!agent || !status) {
887
+ return [];
888
+ }
889
+ const stdout = readOptionalTrimmedString(record.stdout);
890
+ const stderr = readOptionalTrimmedString(record.stderr);
891
+ const name = readOptionalTrimmedString(record.name);
892
+ const summary = firstMeaningfulLine(stdout ?? stderr);
893
+ return [
894
+ {
895
+ agent,
896
+ ...name ? { name } : {},
897
+ status,
898
+ ...summary ? { summary } : {}
899
+ }
900
+ ];
901
+ }
902
+ function isExpectedSubagentFindingSkip(code) {
903
+ return code === "feature_disabled" || code === "missing_active_set" || code === "missing_scope" || code === "misconfigured";
904
+ }
905
+ function firstMeaningfulLine(value) {
906
+ const line = value?.split(/\r?\n/u).map((part) => part.trim()).find((part) => part.length > 0);
907
+ return line ? truncateText(line, 500) : void 0;
908
+ }
909
+ function truncateText(value, maxChars) {
910
+ if (value.length <= maxChars) {
911
+ return value;
912
+ }
913
+ return `${value.slice(0, Math.max(0, maxChars - 15)).trimEnd()} [truncated]`;
914
+ }
915
+
916
+ // src/adapters/shared/agenr-episode-summary-llm.ts
917
+ function createAgenrEpisodeSummaryLlm(provider, modelId, apiKey) {
918
+ const client = createLlmClient(provider, modelId, { apiKey });
919
+ return {
920
+ complete: client.complete,
921
+ completeJson: client.completeJson,
922
+ metadata: {
923
+ modelRef: `${provider}/${modelId}`,
924
+ pricing: {
925
+ input: client.metadata.model.cost?.input ?? 0,
926
+ output: client.metadata.model.cost?.output ?? 0,
927
+ cacheRead: client.metadata.model.cost?.cacheRead ?? 0,
928
+ cacheWrite: client.metadata.model.cost?.cacheWrite ?? 0
929
+ },
930
+ usage: client.metadata.usage
931
+ }
932
+ };
933
+ }
934
+
935
+ // src/adapters/skeln/transcript/parser.ts
936
+ import { createHash } from "crypto";
937
+ import * as fs from "fs/promises";
938
+ import path from "path";
939
+ var SkelnTranscriptParser = class {
940
+ /**
941
+ * Parses one Skeln session JSONL file into normalized transcript messages.
942
+ *
943
+ * @param filePath - Absolute or relative Skeln session file.
944
+ * @returns Parsed transcript payload consumed by shared episode ingest.
945
+ */
946
+ async parseFile(filePath) {
947
+ const raw = await fs.readFile(filePath, "utf8");
948
+ const transcriptHash = createHash("sha256").update(raw).digest("hex");
949
+ const warnings = [];
950
+ const messages = [];
951
+ const records = parseJsonlRecords(raw, filePath, warnings);
952
+ const header = records[0] ? parseHeaderRecord(records[0], filePath, warnings) : {};
953
+ const sessionId = header.sessionId ?? deriveSkelnSessionIdFromFilePath(filePath);
954
+ const headerTimestamp = toIsoTimestamp(header.timestamp);
955
+ for (const record of records.slice(1)) {
956
+ if (record.type !== "message") {
957
+ continue;
958
+ }
959
+ const normalized = normalizeSkelnMessage(record.message);
960
+ if (!normalized) {
961
+ continue;
962
+ }
963
+ messages.push({
964
+ index: messages.length,
965
+ role: normalized.role,
966
+ text: normalized.text,
967
+ ...normalized.timestamp ? { timestamp: normalized.timestamp } : {}
968
+ });
969
+ }
970
+ const startedAt = messages[0]?.timestamp ?? headerTimestamp;
971
+ const endedAt = messages.at(-1)?.timestamp;
972
+ return {
973
+ messages,
974
+ metadata: {
975
+ ...sessionId ? { sessionId } : {},
976
+ ...header.cwd ? { workingDirectory: header.cwd, project: path.basename(header.cwd) } : {},
977
+ ...startedAt ? { startedAt } : {},
978
+ ...endedAt ? { endedAt } : {},
979
+ messageCount: messages.length,
980
+ transcriptHash,
981
+ reconstructedSurface: "skeln",
982
+ surfaceReconstructionSource: "reconstructed",
983
+ sourceIdentity: path.resolve(filePath),
984
+ sourceIdentityKind: "skeln_session_file"
985
+ },
986
+ warnings
987
+ };
988
+ }
989
+ };
990
+ var skelnTranscriptParser = new SkelnTranscriptParser();
991
+ function parseJsonlRecords(raw, filePath, warnings) {
992
+ const records = [];
993
+ const lines = raw.split(/\r?\n/u);
994
+ for (let index = 0; index < lines.length; index += 1) {
995
+ const line = lines[index]?.trim();
996
+ if (!line) {
997
+ continue;
998
+ }
999
+ try {
1000
+ const parsed = JSON.parse(line);
1001
+ const record = isRecord(parsed) ? parsed : void 0;
1002
+ if (!record) {
1003
+ warnings.push(`Skipped non-object Skeln JSONL line ${index + 1} in ${filePath}.`);
1004
+ continue;
1005
+ }
1006
+ records.push(record);
1007
+ } catch (error) {
1008
+ warnings.push(`Skipped malformed Skeln JSONL line ${index + 1} in ${filePath}: ${formatErrorMessage(error)}`);
1009
+ }
1010
+ }
1011
+ if (records.length === 0) {
1012
+ warnings.push(`Skipped empty Skeln transcript file: ${filePath}`);
1013
+ }
1014
+ return records;
1015
+ }
1016
+ function parseHeaderRecord(record, filePath, warnings) {
1017
+ if (!record || record.type !== "session") {
1018
+ warnings.push(`Skeln transcript ${filePath} is missing a session header.`);
1019
+ return {};
1020
+ }
1021
+ return {
1022
+ sessionId: readOptionalTrimmedString(record.id),
1023
+ timestamp: readOptionalFiniteNumber(record.timestamp),
1024
+ cwd: readOptionalTrimmedString(record.cwd)
1025
+ };
1026
+ }
1027
+ function normalizeSkelnMessage(value) {
1028
+ const message = isRecord(value) ? value : void 0;
1029
+ if (!message) {
1030
+ return void 0;
1031
+ }
1032
+ const role = normalizeRole(readOptionalTrimmedString(message.role));
1033
+ if (!role) {
1034
+ return void 0;
1035
+ }
1036
+ const text = normalizeMessageText(message.content);
1037
+ if (!text) {
1038
+ return void 0;
1039
+ }
1040
+ const timestamp = toIsoTimestamp(readOptionalFiniteNumber(message.timestamp));
1041
+ return {
1042
+ role,
1043
+ text,
1044
+ ...timestamp ? { timestamp } : {}
1045
+ };
1046
+ }
1047
+ function normalizeRole(role) {
1048
+ if (role === "user") {
1049
+ return "user";
1050
+ }
1051
+ if (role === "assistant") {
1052
+ return "assistant";
1053
+ }
1054
+ return void 0;
1055
+ }
1056
+ function normalizeMessageText(value) {
1057
+ if (typeof value === "string") {
1058
+ return value.trim();
1059
+ }
1060
+ if (!Array.isArray(value)) {
1061
+ return "";
1062
+ }
1063
+ const parts = [];
1064
+ for (const part of value) {
1065
+ if (typeof part === "string") {
1066
+ parts.push(part);
1067
+ continue;
1068
+ }
1069
+ const record = isRecord(part) ? part : void 0;
1070
+ const text = readOptionalTrimmedString(record?.text) ?? readOptionalTrimmedString(record?.content);
1071
+ if (text) {
1072
+ parts.push(text);
1073
+ }
1074
+ }
1075
+ return parts.map((part) => part.trim()).filter(Boolean).join("\n").trim();
1076
+ }
1077
+ function toIsoTimestamp(timestamp) {
1078
+ if (timestamp === void 0) {
1079
+ return void 0;
1080
+ }
1081
+ const date = new Date(timestamp);
1082
+ return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
1083
+ }
1084
+ function deriveSkelnSessionIdFromFilePath(filePath) {
1085
+ const basename = path.basename(filePath).replace(/\.jsonl(?:\..*)?$/iu, "").trim();
1086
+ return basename.length > 0 ? basename : void 0;
1087
+ }
1088
+
1089
+ // src/adapters/skeln/episode/bounded-session-episode.ts
1090
+ async function writeSkelnBoundedSessionEpisode(params) {
1091
+ const logger = params.logger ?? console;
1092
+ const sessionFile = resolveSessionFile(params.context);
1093
+ const sessionId = String(params.context.sessionManager.getSessionId());
1094
+ const summaryDeadlineMs = Date.now() + EPISODE_SUMMARY_TIMEOUT_MS;
1095
+ if (!sessionFile) {
1096
+ logger.info(`[agenr] ${params.actionLabel} skipped for ${params.skipDetails} reason=no_session_file`);
1097
+ return;
1098
+ }
1099
+ const episodeModel = resolveModel(params.services.agenrConfig, "episode");
1100
+ const llmApiKey = resolveLlmApiKey(params.services.agenrConfig, episodeModel.provider);
1101
+ const summaryLlm = createDeadlineAwareEpisodeSummaryLlm(
1102
+ createAgenrEpisodeSummaryLlm(episodeModel.provider, episodeModel.modelId, llmApiKey),
1103
+ summaryDeadlineMs
1104
+ );
1105
+ await writeBoundedSingleTranscriptEpisode({
1106
+ filePath: sessionFile,
1107
+ context: params.logContext,
1108
+ actionLabel: params.actionLabel,
1109
+ logger,
1110
+ summaryDeadlineMs,
1111
+ ports: {
1112
+ files: createSingleTranscriptDiscoveryPort(sessionFile),
1113
+ transcript: skelnTranscriptParser,
1114
+ episodes: params.services.episodes,
1115
+ createSummaryLlm: () => summaryLlm,
1116
+ embedSummary: (summary) => embedEpisodeSummaryWithinBudget({
1117
+ summary,
1118
+ embedding: params.services.embedding,
1119
+ embeddingAvailable: params.services.embeddingStatus.available,
1120
+ deadlineMs: summaryDeadlineMs,
1121
+ logger,
1122
+ logContext: `[agenr] ${params.actionLabel} embedding skipped for ${params.logContext} file=${sessionFile}`
1123
+ })
1124
+ },
1125
+ ingestOptions: {
1126
+ source: "skeln",
1127
+ genVersion: params.genVersion,
1128
+ skipActiveSessionCheck: true,
1129
+ activityThreshold: params.activityThreshold,
1130
+ candidateOverrides: {
1131
+ sessionId,
1132
+ sourceRef: params.buildSourceRef(sessionFile),
1133
+ agentId: null,
1134
+ surface: "skeln",
1135
+ metadataSource: "reconstructed"
1136
+ }
1137
+ }
1138
+ });
1139
+ }
1140
+ function resolveSessionFile(context) {
1141
+ try {
1142
+ const sessionFile = context.sessionManager.getSessionFile().trim();
1143
+ return sessionFile || void 0;
1144
+ } catch {
1145
+ return void 0;
1146
+ }
1147
+ }
1148
+
1149
+ // src/adapters/skeln/episode/episode-writer.ts
1150
+ var SKELN_EPISODE_GENERATOR_VERSION = "skeln-episodic-summary-v1";
1151
+ var SKELN_PHASE4_SHUTDOWN_EPISODE_ACTIVITY_THRESHOLD = {
1152
+ minMaterialTurns: 8,
1153
+ minDurationMs: 20 * 60 * 1e3
1154
+ };
1155
+ async function writeSkelnShutdownEpisode(params) {
1156
+ const sessionId = String(params.context.sessionManager.getSessionId());
1157
+ await writeSkelnBoundedSessionEpisode({
1158
+ context: params.context,
1159
+ services: params.services,
1160
+ logger: params.logger,
1161
+ actionLabel: "skeln shutdown episode write",
1162
+ genVersion: SKELN_EPISODE_GENERATOR_VERSION,
1163
+ activityThreshold: SKELN_PHASE4_SHUTDOWN_EPISODE_ACTIVITY_THRESHOLD,
1164
+ buildSourceRef: (sessionFile) => sessionFile,
1165
+ logContext: `session=${sessionId} key=skeln:${sessionId}`,
1166
+ skipDetails: `session=${sessionId}`
1167
+ });
1168
+ }
1169
+
1170
+ // src/app/features/resolve.ts
1171
+ function resolveAgenrFeatureFlags(features) {
1172
+ const resolved = { ...DEFAULT_AGENR_FEATURE_FLAGS };
1173
+ for (const key of AGENR_FEATURE_FLAG_KEYS) {
1174
+ if (features?.[key] !== void 0) {
1175
+ resolved[key] = features[key];
1176
+ }
1177
+ }
1178
+ return resolved;
1179
+ }
1180
+
1181
+ // src/app/features/capabilities.ts
1182
+ function resolveRuntimeCapabilities(featureFlags, input = {}) {
1183
+ const sessionMemoryFlagEnabled = featureFlags.sessionTreeLineage || featureFlags.sessionTreeCompaction;
1184
+ const sessionMemory = resolveRepositoryBackedCapability(sessionMemoryFlagEnabled, input.sessionMemoryRepository);
1185
+ return {
1186
+ workingMemory: resolveRepositoryBackedCapability(featureFlags.workingMemory, input.workingMemoryRepository),
1187
+ sessionMemory,
1188
+ shutdownEpisodes: sessionMemory === "enabled",
1189
+ goalContinuation: resolveGoalContinuationCapability(featureFlags.goalContinuation, input.goalContinuationHostPort)
1190
+ };
1191
+ }
1192
+ function resolveRepositoryBackedCapability(featureEnabled, repository) {
1193
+ if (!featureEnabled) {
1194
+ return "disabled";
1195
+ }
1196
+ return repository ? "enabled" : "misconfigured";
1197
+ }
1198
+ function resolveGoalContinuationCapability(featureEnabled, hostPort) {
1199
+ if (!featureEnabled) {
1200
+ return "disabled";
1201
+ }
1202
+ return hostPort ? "enabled" : "misconfigured";
1203
+ }
1204
+
1205
+ // src/app/features/runtime-policy.ts
1206
+ function resolveRuntimePolicy(featureFlags, input = {}) {
1207
+ return {
1208
+ featureFlags,
1209
+ capabilities: resolveRuntimeCapabilities(featureFlags, {
1210
+ workingMemoryRepository: input.workingMemoryRepository,
1211
+ sessionMemoryRepository: input.sessionMemoryRepository,
1212
+ goalContinuationHostPort: input.goalContinuationHostPort
1213
+ }),
1214
+ ...input.memoryPolicy ? { memoryPolicy: input.memoryPolicy } : {}
1215
+ };
1216
+ }
1217
+
1218
+ // src/app/goal-continuation/service.ts
1219
+ var GOAL_CONTINUATION_FEATURE_DISABLED_MESSAGE = "Goal continuation is disabled by the goalContinuation feature flag.";
1220
+ var GOAL_CONTINUATION_HOST_MISSING_MESSAGE = "Goal continuation is host-owned; no host callback was registered for this Agenr runtime.";
1221
+ function createGoalContinuationService(featureFlags, hostPort) {
1222
+ const featureEnabled = featureFlags.goalContinuation;
1223
+ return {
1224
+ async runCommand(params) {
1225
+ if (!featureEnabled) {
1226
+ return {
1227
+ ok: false,
1228
+ code: "feature_disabled",
1229
+ message: GOAL_CONTINUATION_FEATURE_DISABLED_MESSAGE
1230
+ };
1231
+ }
1232
+ if (!hostPort) {
1233
+ return {
1234
+ ok: false,
1235
+ code: "host_callback_missing",
1236
+ message: GOAL_CONTINUATION_HOST_MISSING_MESSAGE
1237
+ };
1238
+ }
1239
+ return hostPort.runCommand(params);
1240
+ }
1241
+ };
1242
+ }
1243
+
1244
+ // src/app/session-memory/trigger-router.ts
1245
+ import { createHash as createHash2 } from "crypto";
1246
+
1247
+ // src/app/working-memory/lifecycle-checkpoint.ts
1248
+ async function attachWorkingCheckpointRefresh(event, result, workingMemory) {
1249
+ const refreshRequest = resolveWorkingCheckpointRefreshRequest(event, result);
1250
+ if (!refreshRequest) {
1251
+ return result;
1252
+ }
1253
+ const workingCheckpointRefresh = workingMemory ? await mergeWorkingCheckpoint(refreshRequest, workingMemory) : skippedRefreshWithoutWorkingMemory(refreshRequest.attachLabel);
1254
+ return {
1255
+ ...result,
1256
+ workingCheckpointRefresh
1257
+ };
1258
+ }
1259
+ async function mergeWorkingCheckpoint(request, workingMemory) {
1260
+ const scope = resolveWorkingRefreshScope(request.event);
1261
+ if (!scope) {
1262
+ return {
1263
+ ok: false,
1264
+ reason: "missing_scope",
1265
+ message: `${request.lifecycleLabel} requires working-scope facts.`
1266
+ };
1267
+ }
1268
+ const result = await workingMemory.run({
1269
+ action: "update",
1270
+ scope,
1271
+ operation: {
1272
+ type: "merge_checkpoint",
1273
+ checkpoint: {
1274
+ summary: request.summary,
1275
+ recordedAt: request.event.observedAt
1276
+ }
1277
+ },
1278
+ updateReason: request.updateReason,
1279
+ actor: "runtime",
1280
+ source: "lifecycle_hook"
1281
+ });
1282
+ if (!result.ok) {
1283
+ return mapWorkingMemoryRefreshFailure(result.code, result.message, request.lifecycleLabel);
1284
+ }
1285
+ if (result.action !== "update") {
1286
+ return {
1287
+ ok: false,
1288
+ reason: "working_memory_unavailable",
1289
+ message: `Expected working-memory update result, received ${result.action}.`
1290
+ };
1291
+ }
1292
+ return {
1293
+ ok: true,
1294
+ action: "working_checkpoint_refreshed",
1295
+ workingSetId: result.workingSet.id,
1296
+ revision: result.workingSet.revision
1297
+ };
1298
+ }
1299
+ function resolveWorkingCheckpointRefreshRequest(event, result) {
1300
+ const compactionEvent = resolveCompactionCheckpointRefreshEvent(event, result);
1301
+ if (compactionEvent) {
1302
+ const summary = normalizeOptionalString(compactionEvent.artifact.summary);
1303
+ if (!summary) {
1304
+ return void 0;
1305
+ }
1306
+ const sourceId = normalizeOptionalString(compactionEvent.artifact.sourceId) ?? "unknown";
1307
+ return {
1308
+ event: compactionEvent,
1309
+ summary,
1310
+ updateReason: `Refreshed working checkpoint from session compaction ${sourceId}.`,
1311
+ lifecycleLabel: "compaction checkpoint refresh",
1312
+ attachLabel: "Compaction checkpoint refresh"
1313
+ };
1314
+ }
1315
+ const shutdownEvent = resolveShutdownCheckpointRefreshEvent(event, result);
1316
+ if (!shutdownEvent) {
1317
+ return void 0;
1318
+ }
1319
+ const reason = normalizeShutdownReason(shutdownEvent.shutdownReason);
1320
+ return {
1321
+ event: shutdownEvent,
1322
+ summary: `Session shutdown (${reason}) recorded. Resume from the latest working-set snapshot; no implicit close was performed.`,
1323
+ updateReason: `Recorded working checkpoint from session shutdown (${reason}).`,
1324
+ lifecycleLabel: "shutdown checkpoint refresh",
1325
+ attachLabel: "Shutdown checkpoint refresh"
1326
+ };
1327
+ }
1328
+ function resolveCompactionCheckpointRefreshEvent(event, result) {
1329
+ if (event.type !== "session_compact" || result.artifact?.kind !== "compaction_checkpoint") {
1330
+ return void 0;
1331
+ }
1332
+ const artifact = event.artifact;
1333
+ if (!isCompactionCheckpointArtifact(artifact)) {
1334
+ return void 0;
1335
+ }
1336
+ return {
1337
+ ...event,
1338
+ type: "session_compact",
1339
+ artifact
1340
+ };
1341
+ }
1342
+ function resolveShutdownCheckpointRefreshEvent(event, result) {
1343
+ if (event.type !== "session_shutdown" || result.accepted !== true) {
1344
+ return void 0;
1345
+ }
1346
+ return {
1347
+ ...event,
1348
+ type: "session_shutdown"
1349
+ };
1350
+ }
1351
+ function isCompactionCheckpointArtifact(artifact) {
1352
+ return artifact?.kind === "compaction_checkpoint";
1353
+ }
1354
+ function resolveWorkingRefreshScope(event) {
1355
+ if (event.workingScope && Object.keys(event.workingScope).length > 0) {
1356
+ return event.workingScope;
1357
+ }
1358
+ return void 0;
1359
+ }
1360
+ function mapWorkingMemoryRefreshFailure(code, message, lifecycleLabel) {
1361
+ if (code === "feature_disabled") {
1362
+ return {
1363
+ ok: false,
1364
+ reason: "not_applicable",
1365
+ message: `Working memory is disabled; ${lifecycleLabel} was skipped.`
1366
+ };
1367
+ }
1368
+ if (code === "missing_active_set") {
1369
+ return {
1370
+ ok: false,
1371
+ reason: "no_active_working_set",
1372
+ code,
1373
+ message
1374
+ };
1375
+ }
1376
+ return {
1377
+ ok: false,
1378
+ reason: "working_memory_unavailable",
1379
+ code,
1380
+ message
1381
+ };
1382
+ }
1383
+ function normalizeShutdownReason(shutdownReason) {
1384
+ const trimmed = shutdownReason?.trim();
1385
+ return trimmed && trimmed.length > 0 ? trimmed : "unknown";
1386
+ }
1387
+ function skippedRefreshWithoutWorkingMemory(label) {
1388
+ return {
1389
+ ok: false,
1390
+ reason: "not_applicable",
1391
+ message: `${label} requires a working-memory service, but none was wired into the runtime.`
1392
+ };
1393
+ }
1394
+
1395
+ // src/app/session-memory/trigger-router.ts
1396
+ var SESSION_MEMORY_TRIGGER_FLAGS = {
1397
+ session_start: "sessionTreeLineage",
1398
+ session_before_fork: "sessionTreeLineage",
1399
+ session_before_compact: "sessionTreeCompaction",
1400
+ session_compact: "sessionTreeCompaction",
1401
+ session_before_tree: "sessionTreeCompaction",
1402
+ session_tree: "sessionTreeLineage",
1403
+ session_shutdown: "sessionTreeLineage"
1404
+ };
1405
+ var LINEAGE_REASONS = new Set(SESSION_LINEAGE_REASONS);
1406
+ async function routeSessionMemoryTrigger(event, featureFlags, deps = {}) {
1407
+ const flagKey = SESSION_MEMORY_TRIGGER_FLAGS[event.type];
1408
+ if (!featureFlags[flagKey]) {
1409
+ return {
1410
+ accepted: false,
1411
+ reason: "feature_disabled",
1412
+ message: `Session-memory trigger ${event.type} is disabled by feature flags.`
1413
+ };
1414
+ }
1415
+ if (!deps.repository) {
1416
+ return {
1417
+ accepted: false,
1418
+ reason: "misconfigured",
1419
+ message: `Session-memory trigger ${event.type} is enabled, but no session-memory repository was wired into the runtime.`
1420
+ };
1421
+ }
1422
+ const artifactInput = normalizeArtifactInput(event);
1423
+ if (artifactInput.kind === "invalid") {
1424
+ return invalidEvent(artifactInput.message);
1425
+ }
1426
+ const lineageInput = normalizeLineageInput(event);
1427
+ if (lineageInput.kind === "invalid") {
1428
+ return invalidEvent(lineageInput.message);
1429
+ }
1430
+ if (artifactInput.kind === "none" && lineageInput.kind === "none") {
1431
+ return attachWorkingCheckpointRefresh(
1432
+ event,
1433
+ {
1434
+ accepted: true,
1435
+ action: isCheckpointRelevantTrigger(event.type) ? "checkpoint_relevant" : "no_lineage",
1436
+ message: isCheckpointRelevantTrigger(event.type) ? `Session-memory trigger ${event.type} was accepted for checkpoint-relevant lifecycle handling.` : `Session-memory trigger ${event.type} did not include lineage or artifact facts.`
1437
+ },
1438
+ deps.workingMemory
1439
+ );
1440
+ }
1441
+ const intakeInput = {
1442
+ ...artifactInput.kind === "artifact" ? { artifact: artifactInput.input } : {},
1443
+ ...lineageInput.kind === "lineage" ? { lineage: lineageInput.input } : {}
1444
+ };
1445
+ const intake = await deps.repository.recordTriggerIntake(intakeInput);
1446
+ const artifact = intake.artifact;
1447
+ const lineageEdge = intake.lineageEdge;
1448
+ const action = resolveTriggerAction(artifact, lineageEdge);
1449
+ return attachWorkingCheckpointRefresh(
1450
+ event,
1451
+ {
1452
+ accepted: true,
1453
+ action,
1454
+ message: buildAcceptedMessage(event.type, action, artifact, lineageEdge),
1455
+ ...lineageEdge ? { lineageEdge } : {},
1456
+ ...artifact ? { artifact } : {}
1457
+ },
1458
+ deps.workingMemory
1459
+ );
1460
+ }
1461
+ function normalizeLineageInput(event) {
1462
+ if (!event.transitionReason || event.transitionReason === "new" || event.transitionReason === "unknown") {
1463
+ return { kind: "none" };
1464
+ }
1465
+ if (!LINEAGE_REASONS.has(event.transitionReason)) {
1466
+ return { kind: "invalid", message: `Unsupported session lineage reason "${event.transitionReason}".` };
1467
+ }
1468
+ const childSessionKey = normalizeOptionalString(event.childSessionKey) ?? normalizeOptionalString(event.sessionKey);
1469
+ if (!childSessionKey) {
1470
+ return { kind: "invalid", message: "Session lineage requires a child session key." };
1471
+ }
1472
+ const parentSessionKey = normalizeOptionalString(event.predecessor?.sessionKey);
1473
+ const parentSourceRef = normalizeOptionalString(event.predecessor?.sourceRef);
1474
+ if (!parentSessionKey && !parentSourceRef) {
1475
+ return { kind: "none" };
1476
+ }
1477
+ const forkEntryId = normalizeOptionalString(event.predecessor?.forkEntryId);
1478
+ const forkPosition = normalizeOptionalString(event.predecessor?.forkPosition);
1479
+ return {
1480
+ kind: "lineage",
1481
+ input: {
1482
+ childSessionKey,
1483
+ ...parentSessionKey ? { parentSessionKey } : {},
1484
+ ...parentSourceRef ? { parentSourceRef } : {},
1485
+ reason: event.transitionReason,
1486
+ ...forkEntryId ? { forkEntryId } : {},
1487
+ ...forkPosition ? { forkPosition } : {},
1488
+ observedAt: event.observedAt
1489
+ }
1490
+ };
1491
+ }
1492
+ function normalizeArtifactInput(event) {
1493
+ if (!event.artifact) {
1494
+ return { kind: "none" };
1495
+ }
1496
+ const sessionKey = normalizeOptionalString(event.artifact.sessionKey) ?? normalizeOptionalString(event.sessionKey);
1497
+ if (!sessionKey) {
1498
+ return { kind: "invalid", message: "Session artifact intake requires a session key." };
1499
+ }
1500
+ const source = normalizeOptionalString(event.artifact.source);
1501
+ const sourceId = normalizeOptionalString(event.artifact.sourceId);
1502
+ const summary = normalizeOptionalString(event.artifact.summary);
1503
+ if (!source || !sourceId || !summary) {
1504
+ return { kind: "invalid", message: "Session artifact intake requires source, sourceId, and summary." };
1505
+ }
1506
+ const contentHash = normalizeOptionalString(event.artifact.contentHash) ?? hashArtifactContent(event.artifact);
1507
+ return {
1508
+ kind: "artifact",
1509
+ input: {
1510
+ ...event.artifact,
1511
+ sessionKey,
1512
+ source,
1513
+ sourceId,
1514
+ summary,
1515
+ contentHash
1516
+ }
1517
+ };
1518
+ }
1519
+ function hashArtifactContent(artifact) {
1520
+ const payload = JSON.stringify({
1521
+ kind: artifact.kind,
1522
+ source: artifact.source,
1523
+ sourceId: artifact.sourceId,
1524
+ sourceRef: artifact.sourceRef,
1525
+ summary: artifact.summary,
1526
+ metadata: artifact.metadata
1527
+ });
1528
+ return createHash2("sha256").update(payload).digest("hex");
1529
+ }
1530
+ function resolveTriggerAction(artifact, lineageEdge) {
1531
+ if (artifact && lineageEdge) {
1532
+ return "recorded";
1533
+ }
1534
+ if (artifact) {
1535
+ return "artifact_recorded";
1536
+ }
1537
+ return "lineage_recorded";
1538
+ }
1539
+ function buildAcceptedMessage(triggerType, action, artifact, lineageEdge) {
1540
+ if (action === "recorded" && artifact && lineageEdge) {
1541
+ return `Session-memory trigger ${triggerType} recorded ${lineageEdge.reason} lineage edge ${lineageEdge.id} and ${artifact.kind} artifact ${artifact.id}.`;
1542
+ }
1543
+ if (action === "artifact_recorded" && artifact) {
1544
+ return `Session-memory trigger ${triggerType} recorded ${artifact.kind} artifact ${artifact.id}.`;
1545
+ }
1546
+ if (lineageEdge) {
1547
+ return `Session-memory trigger ${triggerType} recorded ${lineageEdge.reason} lineage edge ${lineageEdge.id}.`;
1548
+ }
1549
+ return `Session-memory trigger ${triggerType} was accepted.`;
1550
+ }
1551
+ function invalidEvent(message) {
1552
+ return {
1553
+ accepted: false,
1554
+ reason: "invalid_event",
1555
+ message
1556
+ };
1557
+ }
1558
+ function isCheckpointRelevantTrigger(type) {
1559
+ return type === "session_before_fork" || type === "session_before_compact" || type === "session_before_tree" || type === "session_shutdown";
1560
+ }
1561
+
1562
+ // src/app/working-memory/close-service.ts
1563
+ function resolveCloseTerminalStatus(closeMode) {
1564
+ return closeMode === "abandon" ? "abandoned" : "closed";
1565
+ }
1566
+ function buildWorkingCloseSnapshot(input) {
1567
+ const finalCheckpoint = buildFinalCheckpoint(input);
1568
+ const candidates = [...input.snapshot.candidates ?? []];
1569
+ if (input.createEpisode && !candidates.some((candidate) => candidate.kind === "episodic")) {
1570
+ candidates.push({
1571
+ kind: "episodic",
1572
+ summary: finalCheckpoint.summary,
1573
+ provenance: {
1574
+ evidenceEventSequences: input.eventSequences,
1575
+ sourceRef: `working_set:${input.workingSetId}#rev:${input.currentRevision}`,
1576
+ note: input.closeReason
1577
+ },
1578
+ promotionStatus: "pending"
1579
+ });
1580
+ }
1581
+ return {
1582
+ snapshot: {
1583
+ ...input.snapshot,
1584
+ checkpoint: finalCheckpoint,
1585
+ candidates: candidates.length > 0 ? candidates : void 0,
1586
+ lastMaterialChange: input.closeReason
1587
+ },
1588
+ candidates
1589
+ };
1590
+ }
1591
+ function buildFinalCheckpoint(input) {
1592
+ const summary = normalizeSummary(input.closeReason) ?? input.snapshot.summary ?? input.snapshot.objective ?? "Working set closed.";
1593
+ return {
1594
+ summary,
1595
+ recordedAt: input.now,
1596
+ ...input.snapshot.nextActions && input.snapshot.nextActions.length > 0 ? { nextActions: input.snapshot.nextActions.map((action) => action.text).filter((text) => text.trim().length > 0) } : {},
1597
+ ...input.snapshot.blockers && input.snapshot.blockers.length > 0 ? { blockers: input.snapshot.blockers } : {}
1598
+ };
1599
+ }
1600
+ function normalizeSummary(value) {
1601
+ const trimmed = value.trim();
1602
+ return trimmed.length > 0 ? trimmed : void 0;
1603
+ }
1604
+
1605
+ // src/app/working-memory/repository.ts
1606
+ function isWorkingSetCreateFailure(result) {
1607
+ return "kind" in result;
1608
+ }
1609
+ function isWorkingSetWriteFailure(result) {
1610
+ return "kind" in result;
1611
+ }
1612
+
1613
+ // src/app/working-memory/results.ts
1614
+ function createFailure(code, message, details) {
1615
+ return {
1616
+ ok: false,
1617
+ code,
1618
+ message,
1619
+ ...details ? { details } : {}
1620
+ };
1621
+ }
1622
+ function writeFailureToResult(workingSetId, failure) {
1623
+ switch (failure.kind) {
1624
+ case "not_found":
1625
+ return createFailure("not_found", "Working set was not found.", workingSetId ? { workingSetId } : void 0);
1626
+ case "revision_conflict":
1627
+ return createFailure("revision_conflict", "Working-set revision conflict.", {
1628
+ actualRevision: failure.actualRevision,
1629
+ ...workingSetId ? { workingSetId } : {}
1630
+ });
1631
+ case "terminal_status":
1632
+ return createFailure("terminal_status", `Working set is already ${failure.status}.`, {
1633
+ status: failure.status,
1634
+ ...workingSetId ? { workingSetId } : {}
1635
+ });
1636
+ }
1637
+ }
1638
+
1639
+ // src/app/working-memory/scope-resolver.ts
1640
+ function resolveWorkingScope(input) {
1641
+ const scope = normalizeWorkingScope(input);
1642
+ if (scope.taskId) {
1643
+ return {
1644
+ ok: true,
1645
+ scope: {
1646
+ ...scope,
1647
+ scopeKind: "task",
1648
+ scopeKey: `task:${scope.taskId}`
1649
+ }
1650
+ };
1651
+ }
1652
+ if (scope.conversationKey) {
1653
+ return {
1654
+ ok: true,
1655
+ scope: {
1656
+ ...scope,
1657
+ scopeKind: "conversation",
1658
+ scopeKey: `conversation:${scope.conversationKey}`
1659
+ }
1660
+ };
1661
+ }
1662
+ if (scope.gitRoot && scope.gitBranch) {
1663
+ return {
1664
+ ok: true,
1665
+ scope: {
1666
+ ...scope,
1667
+ scopeKind: "git_branch",
1668
+ scopeKey: buildScopeKey("git_branch", [scope.project, scope.gitRoot, scope.gitBranch])
1669
+ }
1670
+ };
1671
+ }
1672
+ if (scope.gitRoot && scope.cwd) {
1673
+ return {
1674
+ ok: true,
1675
+ scope: {
1676
+ ...scope,
1677
+ scopeKind: "git_cwd",
1678
+ scopeKey: buildScopeKey("git_cwd", [scope.project, scope.gitRoot, scope.cwd])
1679
+ }
1680
+ };
1681
+ }
1682
+ return {
1683
+ ok: false,
1684
+ code: "missing_scope",
1685
+ message: "Working memory needs a task, conversation, or git scope."
1686
+ };
1687
+ }
1688
+ function normalizeWorkingScope(input) {
1689
+ const scope = input ?? {};
1690
+ return {
1691
+ ...normalizeField("sessionId", scope.sessionId),
1692
+ ...normalizeField("gitRoot", scope.gitRoot),
1693
+ ...normalizeField("gitBranch", scope.gitBranch),
1694
+ ...normalizeField("cwd", scope.cwd),
1695
+ ...normalizeField("project", scope.project),
1696
+ ...normalizeField("taskId", scope.taskId),
1697
+ ...normalizeField("conversationKey", scope.conversationKey)
1698
+ };
1699
+ }
1700
+ function buildScopeKey(prefix, parts) {
1701
+ return [prefix, ...parts.filter((part) => part !== void 0)].join(":");
1702
+ }
1703
+ function normalizeField(key, value) {
1704
+ if (typeof value !== "string") {
1705
+ return {};
1706
+ }
1707
+ const trimmed = value.trim();
1708
+ return trimmed.length > 0 ? { [key]: trimmed } : {};
1709
+ }
1710
+
1711
+ // src/app/working-memory/find-current-set.ts
1712
+ async function lookupCurrentWorkingSets(scope, repository) {
1713
+ const scopeResolution = resolveWorkingScope(scope);
1714
+ if (!scopeResolution.ok) {
1715
+ return createFailure("missing_scope", scopeResolution.message);
1716
+ }
1717
+ const matches = await repository.findCurrentWorkingSets(scopeResolution.scope);
1718
+ if (matches.length > 1) {
1719
+ return createFailure("ambiguous_scope", "Multiple current working sets matched the resolved scope.", {
1720
+ scopeKey: scopeResolution.scope.scopeKey,
1721
+ workingSetIds: matches.map((match) => match.id)
1722
+ });
1723
+ }
1724
+ return {
1725
+ ok: true,
1726
+ scope: scopeResolution.scope,
1727
+ matches
1728
+ };
1729
+ }
1730
+ async function findUniqueCurrentWorkingSet(scope, repository) {
1731
+ const lookup = await lookupCurrentWorkingSets(scope, repository);
1732
+ if (!lookup.ok) {
1733
+ return lookup;
1734
+ }
1735
+ if (lookup.matches.length === 0) {
1736
+ return createFailure("missing_active_set", "No current working set matched the resolved scope.", {
1737
+ scopeKey: lookup.scope.scopeKey
1738
+ });
1739
+ }
1740
+ return {
1741
+ ok: true,
1742
+ workingSet: lookup.matches[0],
1743
+ scope: lookup.scope
1744
+ };
1745
+ }
1746
+
1747
+ // src/app/working-memory/select-working-set.ts
1748
+ async function selectWorkingSet(params, repository) {
1749
+ const workingSetId = params.workingSetId?.trim();
1750
+ if (workingSetId) {
1751
+ const workingSet = await repository.getWorkingSet(workingSetId);
1752
+ if (!workingSet) {
1753
+ return createFailure("not_found", `Working set ${workingSetId} was not found.`, { workingSetId });
1754
+ }
1755
+ return { ok: true, workingSet };
1756
+ }
1757
+ const current = await findUniqueCurrentWorkingSet(params.scope, repository);
1758
+ if (!current.ok) {
1759
+ return current;
1760
+ }
1761
+ return {
1762
+ ok: true,
1763
+ workingSet: current.workingSet,
1764
+ scope: current.scope
1765
+ };
1766
+ }
1767
+
1768
+ // src/app/working-memory/validation.ts
1769
+ function normalizeRequiredString(value, message) {
1770
+ const trimmed = value?.trim();
1771
+ if (!trimmed) {
1772
+ return createFailure("invalid_request", message);
1773
+ }
1774
+ return { ok: true, value: trimmed };
1775
+ }
1776
+ function normalizeExpectedRevision(value) {
1777
+ if (value === void 0 || !Number.isInteger(value) || value < 0) {
1778
+ return createFailure("invalid_request", "expectedRevision must be a non-negative integer.");
1779
+ }
1780
+ return { ok: true, value };
1781
+ }
1782
+ function canDefaultExpectedRevision(source) {
1783
+ return isTrustedHostMutationSource(source);
1784
+ }
1785
+ function resolveExpectedRevision(selectedRevision, providedRevision, source) {
1786
+ if (providedRevision === void 0) {
1787
+ if (!canDefaultExpectedRevision(source)) {
1788
+ return createFailure("invalid_request", "expectedRevision must be a non-negative integer.");
1789
+ }
1790
+ return { ok: true, value: selectedRevision };
1791
+ }
1792
+ return normalizeExpectedRevision(providedRevision);
1793
+ }
1794
+ function validateWorkingBudgetState(budget) {
1795
+ const entries = [
1796
+ ["tokenBudget", budget.tokenBudget],
1797
+ ["tokenUsed", budget.tokenUsed],
1798
+ ["wallClockBudgetSeconds", budget.wallClockBudgetSeconds],
1799
+ ["wallClockUsedSeconds", budget.wallClockUsedSeconds],
1800
+ ["turnBudget", budget.turnBudget],
1801
+ ["turnsUsed", budget.turnsUsed],
1802
+ ["requireReviewAfterSeconds", budget.requireReviewAfterSeconds]
1803
+ ];
1804
+ for (const [key, value] of entries) {
1805
+ if (value !== void 0 && (!Number.isFinite(value) || value < 0)) {
1806
+ return createFailure("invalid_request", `${key} must be a non-negative finite number.`);
1807
+ }
1808
+ }
1809
+ return { ok: true };
1810
+ }
1811
+ function validateWorkingUsageDelta(usage) {
1812
+ const entries = [
1813
+ ["tokenDelta", usage.tokenDelta],
1814
+ ["wallClockSecondsDelta", usage.wallClockSecondsDelta],
1815
+ ["turnDelta", usage.turnDelta]
1816
+ ];
1817
+ const hasDelta = entries.some(([, value]) => value !== void 0);
1818
+ if (!hasDelta) {
1819
+ return createFailure("invalid_request", "account_usage requires at least one usage delta.");
1820
+ }
1821
+ for (const [key, value] of entries) {
1822
+ if (value !== void 0 && (!Number.isFinite(value) || value < 0)) {
1823
+ return createFailure("invalid_request", `${key} must be a non-negative finite number.`);
1824
+ }
1825
+ }
1826
+ return { ok: true };
1827
+ }
1828
+
1829
+ // src/app/working-memory/handlers/close.ts
1830
+ async function handleClose(params, repository, timestamp) {
1831
+ if (!isTrustedHostMutationSource(params.source)) {
1832
+ return createFailure(
1833
+ "close_not_allowed",
1834
+ "agenr_work close is reserved for /goal clear. Record progress with merge_checkpoint and leave the working set open."
1835
+ );
1836
+ }
1837
+ const closeReason = normalizeRequiredString(params.closeReason, "agenr_work close requires closeReason.");
1838
+ if (!closeReason.ok) {
1839
+ return closeReason;
1840
+ }
1841
+ const selection = await selectWorkingSet(params, repository);
1842
+ if (!selection.ok) {
1843
+ return selection;
1844
+ }
1845
+ const expectedRevision = resolveExpectedRevision(selection.workingSet.revision, params.expectedRevision, params.source);
1846
+ if (!expectedRevision.ok) {
1847
+ return expectedRevision;
1848
+ }
1849
+ if (isCloseManagedStatus(selection.workingSet.status)) {
1850
+ return createFailure("terminal_status", `Working set ${selection.workingSet.id} is already ${selection.workingSet.status}.`, {
1851
+ workingSetId: selection.workingSet.id,
1852
+ status: selection.workingSet.status
1853
+ });
1854
+ }
1855
+ const events = await repository.listWorkingEvents(selection.workingSet.id, CLOSE_EVENT_HISTORY_LIMIT);
1856
+ const terminalStatus = resolveCloseTerminalStatus(params.closeMode);
1857
+ const closePayload = buildWorkingCloseSnapshot({
1858
+ workingSetId: selection.workingSet.id,
1859
+ snapshot: selection.workingSet.snapshot,
1860
+ currentRevision: selection.workingSet.revision,
1861
+ closeReason: closeReason.value,
1862
+ createEpisode: params.createEpisode,
1863
+ eventSequences: events.map((event) => event.sequence),
1864
+ now: timestamp
1865
+ });
1866
+ const writeResult = await repository.updateWorkingSet({
1867
+ workingSetId: selection.workingSet.id,
1868
+ expectedRevision: expectedRevision.value,
1869
+ eventType: terminalStatus,
1870
+ payload: {
1871
+ closeReason: closeReason.value,
1872
+ closeMode: params.closeMode ?? "close",
1873
+ candidates: closePayload.candidates,
1874
+ sourceRef: `working_set:${selection.workingSet.id}#rev:${selection.workingSet.revision}`
1875
+ },
1876
+ status: terminalStatus,
1877
+ snapshot: closePayload.snapshot,
1878
+ title: selection.workingSet.title,
1879
+ objective: selection.workingSet.snapshot.objective,
1880
+ closedAt: timestamp,
1881
+ closeReason: closeReason.value,
1882
+ actor: params.actor,
1883
+ source: params.source,
1884
+ now: timestamp
1885
+ });
1886
+ return toCloseResult(selection.workingSet.id, writeResult, closePayload.candidates);
1887
+ }
1888
+ function toCloseResult(workingSetId, writeResult, candidates) {
1889
+ if (isWorkingSetWriteFailure(writeResult)) {
1890
+ return writeFailureToResult(workingSetId, writeResult);
1891
+ }
1892
+ return {
1893
+ ok: true,
1894
+ action: "close",
1895
+ workingSet: writeResult.workingSet,
1896
+ event: writeResult.event,
1897
+ candidates
1898
+ };
1899
+ }
1900
+
1901
+ // src/app/working-memory/goal-generation.ts
1902
+ var INITIAL_GOAL_GENERATION = 1;
1903
+ function readGoalGeneration(snapshot) {
1904
+ return snapshot?.goalGeneration ?? INITIAL_GOAL_GENERATION;
1905
+ }
1906
+ function nextGoalGenerationAfterObjectiveChange(snapshot, nextObjective) {
1907
+ if (snapshot.objective === nextObjective) {
1908
+ return readGoalGeneration(snapshot);
1909
+ }
1910
+ return readGoalGeneration(snapshot) + 1;
1911
+ }
1912
+
1913
+ // src/app/working-memory/resolve-create-scope.ts
1914
+ async function resolveCreateScope(params, repository) {
1915
+ const workingSetId = params.workingSetId?.trim();
1916
+ if (workingSetId) {
1917
+ const workingSet = await repository.getWorkingSet(workingSetId);
1918
+ if (workingSet) {
1919
+ return createFailure("active_set_exists", "A working set is already active for this scope.", {
1920
+ workingSetId: workingSet.id,
1921
+ scopeKey: workingSet.scopeKey
1922
+ });
1923
+ }
1924
+ return createFailure("not_found", `Working set ${workingSetId} was not found.`, { workingSetId });
1925
+ }
1926
+ const lookup = await lookupCurrentWorkingSets(params.scope, repository);
1927
+ if (!lookup.ok) {
1928
+ return lookup;
1929
+ }
1930
+ if (lookup.matches.length === 1) {
1931
+ const existing = lookup.matches[0];
1932
+ return createFailure("active_set_exists", "A working set already exists for this scope.", {
1933
+ workingSetId: existing.id,
1934
+ scopeKey: lookup.scope.scopeKey
1935
+ });
1936
+ }
1937
+ return {
1938
+ ok: true,
1939
+ scope: lookup.scope
1940
+ };
1941
+ }
1942
+
1943
+ // src/app/working-memory/handlers/create.ts
1944
+ async function handleCreate(params, repository, timestamp, sourceLabel) {
1945
+ const operation = params.operation;
1946
+ if (!operation || operation.type !== "set_objective") {
1947
+ return createFailure("invalid_request", "agenr_work create requires a set_objective operation.");
1948
+ }
1949
+ const updateReason = normalizeRequiredString(params.updateReason, "agenr_work create requires updateReason.");
1950
+ if (!updateReason.ok) {
1951
+ return updateReason;
1952
+ }
1953
+ const scopeResolution = await resolveCreateScope(params, repository);
1954
+ if (!scopeResolution.ok) {
1955
+ return scopeResolution;
1956
+ }
1957
+ const { scope } = scopeResolution;
1958
+ const initialBudget = params.initialBudget ? validateWorkingBudgetState(params.initialBudget) : { ok: true };
1959
+ if (!initialBudget.ok) {
1960
+ return initialBudget;
1961
+ }
1962
+ const created = await repository.createWorkingSet({
1963
+ scope,
1964
+ title: operation.title,
1965
+ objective: operation.objective,
1966
+ status: "active",
1967
+ snapshot: {
1968
+ goalGeneration: INITIAL_GOAL_GENERATION,
1969
+ objective: operation.objective,
1970
+ continuation: { policy: params.continuationPolicy ?? "manual" },
1971
+ ...params.initialBudget ? { budgets: params.initialBudget } : {},
1972
+ lastMaterialChange: updateReason.value
1973
+ },
1974
+ actor: params.actor,
1975
+ source: params.source,
1976
+ sourceLabel,
1977
+ sessionId: scope.sessionId,
1978
+ now: timestamp
1979
+ });
1980
+ if (isWorkingSetCreateFailure(created)) {
1981
+ return createFailure("active_set_exists", "A working set already exists for this scope.", {
1982
+ scopeKey: created.scopeKey
1983
+ });
1984
+ }
1985
+ return {
1986
+ ok: true,
1987
+ action: "create",
1988
+ workingSet: created.workingSet,
1989
+ event: created.event,
1990
+ projection: createToolSuccessProjection(created.workingSet, "create", timestamp)
1991
+ };
1992
+ }
1993
+
1994
+ // src/app/working-memory/handlers/get.ts
1995
+ async function handleGet(params, repository, timestamp) {
1996
+ const selection = await selectWorkingSet(params, repository);
1997
+ if (!selection.ok) {
1998
+ return selection;
1999
+ }
2000
+ const events = params.includeEvents ? await repository.listWorkingEvents(selection.workingSet.id, normalizeEventLimit(params.eventLimit)) : void 0;
2001
+ return {
2002
+ ok: true,
2003
+ action: "get",
2004
+ workingSet: selection.workingSet,
2005
+ ...events ? { events } : {},
2006
+ projection: createToolSuccessProjection(selection.workingSet, "get", timestamp)
2007
+ };
2008
+ }
2009
+
2010
+ // src/app/working-memory/handlers/list.ts
2011
+ async function handleList(params, repository) {
2012
+ const scopeResolution = params.scope ? resolveWorkingScope(params.scope) : void 0;
2013
+ if (scopeResolution && !scopeResolution.ok) {
2014
+ return createFailure("missing_scope", scopeResolution.message);
2015
+ }
2016
+ const workingSets = await repository.listWorkingSets({
2017
+ ...scopeResolution?.ok ? { scope: scopeResolution.scope } : {},
2018
+ limit: normalizeListLimit(params.listLimit)
2019
+ });
2020
+ return {
2021
+ ok: true,
2022
+ action: "list",
2023
+ workingSets
2024
+ };
2025
+ }
2026
+
2027
+ // src/app/working-memory/apply-operation.ts
2028
+ function applyOperation(record, operation, updateReason) {
2029
+ const snapshot = { ...record.snapshot, lastMaterialChange: updateReason };
2030
+ let status = record.status;
2031
+ let title = record.title;
2032
+ let objective = record.snapshot.objective;
2033
+ switch (operation.type) {
2034
+ case "set_objective":
2035
+ snapshot.goalGeneration = nextGoalGenerationAfterObjectiveChange(snapshot, operation.objective);
2036
+ snapshot.objective = operation.objective;
2037
+ objective = operation.objective;
2038
+ title = operation.title ?? title;
2039
+ break;
2040
+ case "replace_plan":
2041
+ snapshot.currentPlan = operation.currentPlan;
2042
+ snapshot.nextActions = operation.nextActions;
2043
+ break;
2044
+ case "merge_checkpoint":
2045
+ snapshot.checkpoint = operation.checkpoint;
2046
+ if (operation.checkpoint.nextActions) {
2047
+ snapshot.nextActions = operation.checkpoint.nextActions.map((text) => ({ text, status: "pending" }));
2048
+ }
2049
+ if (operation.checkpoint.blockers) {
2050
+ snapshot.blockers = operation.checkpoint.blockers;
2051
+ }
2052
+ break;
2053
+ case "add_file_note":
2054
+ snapshot.files = [...snapshot.files ?? [], operation.file];
2055
+ break;
2056
+ case "add_command_note":
2057
+ snapshot.commands = [...snapshot.commands ?? [], operation.command];
2058
+ break;
2059
+ case "record_decision":
2060
+ snapshot.decisions = [...snapshot.decisions ?? [], operation.decision];
2061
+ break;
2062
+ case "record_assumption":
2063
+ snapshot.assumptions = [...snapshot.assumptions ?? [], operation.assumption];
2064
+ break;
2065
+ case "set_next_actions":
2066
+ snapshot.nextActions = operation.nextActions;
2067
+ break;
2068
+ case "set_status":
2069
+ if (isCloseManagedStatus(operation.status)) {
2070
+ return createFailure("invalid_request", "Use agenr_work close for closed or abandoned terminal states.");
2071
+ }
2072
+ status = operation.status;
2073
+ break;
2074
+ case "add_candidate":
2075
+ snapshot.candidates = [...snapshot.candidates ?? [], operation.candidate];
2076
+ break;
2077
+ case "configure_budget": {
2078
+ const budget = mergeBudgetState(snapshot.budgets, operation.budget);
2079
+ if (!budget.ok) {
2080
+ return budget;
2081
+ }
2082
+ const limited = applyBudgetLimitedStatus(status, budget.value, updateReason);
2083
+ snapshot.budgets = limited.budgets;
2084
+ status = limited.status;
2085
+ break;
2086
+ }
2087
+ case "account_usage": {
2088
+ const budget = applyUsageDelta(snapshot.budgets, operation.usage, updateReason);
2089
+ if (!budget.ok) {
2090
+ return budget;
2091
+ }
2092
+ const limited = applyBudgetLimitedStatus(status, budget.value, operation.usage.recordedAt ?? updateReason);
2093
+ snapshot.budgets = limited.budgets;
2094
+ status = limited.status;
2095
+ break;
2096
+ }
2097
+ case "set_continuation_policy":
2098
+ snapshot.continuation = pruneContinuation({
2099
+ ...snapshot.continuation,
2100
+ policy: operation.policy,
2101
+ ...operation.resumeAfter !== void 0 ? { resumeAfter: operation.resumeAfter } : {},
2102
+ ...operation.staleAfter !== void 0 ? { staleAfter: operation.staleAfter } : {},
2103
+ ...operation.stopReason !== void 0 ? { stopReason: operation.stopReason } : {}
2104
+ });
2105
+ break;
2106
+ }
2107
+ return {
2108
+ ok: true,
2109
+ snapshot,
2110
+ status,
2111
+ title,
2112
+ objective
2113
+ };
2114
+ }
2115
+ function mergeBudgetState(current, update) {
2116
+ const validation = validateWorkingBudgetState(update);
2117
+ if (!validation.ok) {
2118
+ return validation;
2119
+ }
2120
+ return {
2121
+ ok: true,
2122
+ value: pruneBudget({
2123
+ ...current ?? {},
2124
+ ...update
2125
+ })
2126
+ };
2127
+ }
2128
+ function applyUsageDelta(current, usage, limitedAt) {
2129
+ const validation = validateWorkingUsageDelta(usage);
2130
+ if (!validation.ok) {
2131
+ return validation;
2132
+ }
2133
+ const next = {
2134
+ ...current ?? {},
2135
+ tokenUsed: addDelta(current?.tokenUsed, usage.tokenDelta),
2136
+ wallClockUsedSeconds: addDelta(current?.wallClockUsedSeconds, usage.wallClockSecondsDelta),
2137
+ turnsUsed: addDelta(current?.turnsUsed, usage.turnDelta)
2138
+ };
2139
+ const limitReason = resolveBudgetLimitReason(next);
2140
+ return {
2141
+ ok: true,
2142
+ value: pruneBudget({
2143
+ ...next,
2144
+ ...limitReason ? { limitReason, limitedAt: usage.recordedAt ?? limitedAt } : {}
2145
+ })
2146
+ };
2147
+ }
2148
+ function applyBudgetLimitedStatus(currentStatus, budget, limitedAt) {
2149
+ const limitReason = resolveBudgetLimitReason(budget);
2150
+ if (!limitReason || !budget) {
2151
+ return { status: currentStatus, budgets: budget };
2152
+ }
2153
+ return {
2154
+ status: "budget_limited",
2155
+ budgets: pruneBudget({
2156
+ ...budget,
2157
+ limitReason: budget.limitReason ?? limitReason,
2158
+ limitedAt: budget.limitedAt ?? limitedAt
2159
+ })
2160
+ };
2161
+ }
2162
+ function resolveBudgetLimitReason(budget) {
2163
+ if (!budget) {
2164
+ return void 0;
2165
+ }
2166
+ if (budget.tokenBudget !== void 0 && (budget.tokenUsed ?? 0) >= budget.tokenBudget) {
2167
+ return "token";
2168
+ }
2169
+ if (budget.wallClockBudgetSeconds !== void 0 && (budget.wallClockUsedSeconds ?? 0) >= budget.wallClockBudgetSeconds) {
2170
+ return "wall_clock";
2171
+ }
2172
+ if (budget.turnBudget !== void 0 && (budget.turnsUsed ?? 0) >= budget.turnBudget) {
2173
+ return "turn";
2174
+ }
2175
+ return void 0;
2176
+ }
2177
+ function addDelta(current, delta) {
2178
+ if (delta === void 0) {
2179
+ return current;
2180
+ }
2181
+ return (current ?? 0) + delta;
2182
+ }
2183
+ function pruneBudget(budget) {
2184
+ return Object.fromEntries(Object.entries(budget).filter(([, value]) => value !== void 0));
2185
+ }
2186
+ function pruneContinuation(continuation) {
2187
+ if (!continuation) {
2188
+ return void 0;
2189
+ }
2190
+ const pruned = Object.fromEntries(Object.entries(continuation).filter(([, value]) => value !== void 0));
2191
+ return Object.keys(pruned).length > 0 ? pruned : void 0;
2192
+ }
2193
+
2194
+ // src/app/working-memory/handlers/commit-applied-change.ts
2195
+ function isAppliedWorkingSetCommitFailure(result) {
2196
+ return "kind" in result;
2197
+ }
2198
+ async function commitAppliedWorkingSetChange(repository, input) {
2199
+ if (input.operation.type === "account_usage") {
2200
+ const writeResult2 = await repository.patchWorkingSetUsage({
2201
+ workingSetId: input.workingSetId,
2202
+ expectedRevision: input.expectedRevision,
2203
+ status: input.applied.status,
2204
+ snapshot: input.applied.snapshot,
2205
+ title: input.applied.title,
2206
+ objective: input.applied.objective,
2207
+ now: input.now
2208
+ });
2209
+ if (isWorkingSetWriteFailure(writeResult2)) {
2210
+ return writeResult2;
2211
+ }
2212
+ return {
2213
+ type: "usage_patch",
2214
+ workingSet: writeResult2.workingSet
2215
+ };
2216
+ }
2217
+ const writeResult = await repository.updateWorkingSet({
2218
+ workingSetId: input.workingSetId,
2219
+ expectedRevision: input.expectedRevision,
2220
+ eventType: input.operation.type,
2221
+ payload: {
2222
+ operation: input.operation,
2223
+ updateReason: input.updateReason
2224
+ },
2225
+ status: input.applied.status,
2226
+ snapshot: input.applied.snapshot,
2227
+ title: input.applied.title,
2228
+ objective: input.applied.objective,
2229
+ actor: input.actor,
2230
+ source: input.source,
2231
+ now: input.now
2232
+ });
2233
+ if (isWorkingSetWriteFailure(writeResult)) {
2234
+ return writeResult;
2235
+ }
2236
+ return {
2237
+ type: "semantic",
2238
+ workingSet: writeResult.workingSet,
2239
+ event: writeResult.event
2240
+ };
2241
+ }
2242
+
2243
+ // src/app/working-memory/handlers/prepare-external-mutation.ts
2244
+ async function handlePrepareExternalGoalMutation(params, repository, timestamp) {
2245
+ if (!isTrustedHostMutationSource(params.source)) {
2246
+ return createFailure("invalid_request", "prepare_external_goal_mutation is reserved for trusted host runtime paths.");
2247
+ }
2248
+ const selection = await selectWorkingSet(params, repository);
2249
+ if (!selection.ok) {
2250
+ if (selection.code === "missing_active_set") {
2251
+ return {
2252
+ ok: true,
2253
+ action: "prepare_external_goal_mutation",
2254
+ prepared: false,
2255
+ events: []
2256
+ };
2257
+ }
2258
+ return selection;
2259
+ }
2260
+ if (params.requireCheckpoint && !params.checkpoint && !selection.workingSet.snapshot.checkpoint) {
2261
+ return createFailure("invalid_request", `Active goal requires a checkpoint before ${params.mutationKind}.`, {
2262
+ workingSetId: selection.workingSet.id,
2263
+ revision: selection.workingSet.revision,
2264
+ mutationKind: params.mutationKind
2265
+ });
2266
+ }
2267
+ if (!isMutableWorkingSetStatus(selection.workingSet.status)) {
2268
+ return {
2269
+ ok: true,
2270
+ action: "prepare_external_goal_mutation",
2271
+ prepared: true,
2272
+ workingSet: selection.workingSet,
2273
+ events: []
2274
+ };
2275
+ }
2276
+ const events = [];
2277
+ let workingSet = selection.workingSet;
2278
+ for (const { operation, updateReason } of resolvePrepareOperations(params)) {
2279
+ const applied = applyOperation(workingSet, operation, updateReason);
2280
+ if (!applied.ok) {
2281
+ return applied;
2282
+ }
2283
+ const writeResult = await commitAppliedWorkingSetChange(repository, {
2284
+ workingSetId: workingSet.id,
2285
+ expectedRevision: workingSet.revision,
2286
+ operation,
2287
+ updateReason,
2288
+ applied,
2289
+ actor: params.actor,
2290
+ source: params.source,
2291
+ now: timestamp
2292
+ });
2293
+ if (isAppliedWorkingSetCommitFailure(writeResult)) {
2294
+ return writeFailureToResult(workingSet.id, writeResult);
2295
+ }
2296
+ workingSet = writeResult.workingSet;
2297
+ if (writeResult.type === "semantic") {
2298
+ events.push(writeResult.event);
2299
+ }
2300
+ }
2301
+ return {
2302
+ ok: true,
2303
+ action: "prepare_external_goal_mutation",
2304
+ prepared: true,
2305
+ workingSet,
2306
+ events
2307
+ };
2308
+ }
2309
+ function resolvePrepareOperations(params) {
2310
+ const operations = [];
2311
+ if (params.usage) {
2312
+ operations.push({
2313
+ operation: { type: "account_usage", usage: params.usage },
2314
+ updateReason: params.updateReason ?? `Accounted progress before external goal mutation (${params.mutationKind}).`
2315
+ });
2316
+ }
2317
+ if (params.checkpoint) {
2318
+ operations.push({
2319
+ operation: { type: "merge_checkpoint", checkpoint: params.checkpoint },
2320
+ updateReason: params.updateReason ?? `Recorded checkpoint before external goal mutation (${params.mutationKind}).`
2321
+ });
2322
+ }
2323
+ return operations;
2324
+ }
2325
+
2326
+ // src/app/working-memory/handlers/update.ts
2327
+ async function handleUpdate(params, repository, timestamp) {
2328
+ const operation = params.operation;
2329
+ if (!operation) {
2330
+ return createFailure("invalid_request", "agenr_work update requires a typed operation.");
2331
+ }
2332
+ if (isTrustedHostOnlyWorkingOperation(operation.type) && !isTrustedHostMutationSource(params.source)) {
2333
+ return createFailure("invalid_request", `${operation.type} is reserved for trusted host runtime paths.`);
2334
+ }
2335
+ const updateReason = normalizeRequiredString(params.updateReason, "agenr_work update requires updateReason.");
2336
+ if (!updateReason.ok) {
2337
+ return updateReason;
2338
+ }
2339
+ const selection = await selectWorkingSet(params, repository);
2340
+ if (!selection.ok) {
2341
+ return selection;
2342
+ }
2343
+ const expectedRevision = resolveExpectedRevision(selection.workingSet.revision, params.expectedRevision, params.source);
2344
+ if (!expectedRevision.ok) {
2345
+ return expectedRevision;
2346
+ }
2347
+ if (!isMutableWorkingSetStatus(selection.workingSet.status)) {
2348
+ return createFailure("terminal_status", `Working set ${selection.workingSet.id} is already ${selection.workingSet.status}.`, {
2349
+ workingSetId: selection.workingSet.id,
2350
+ status: selection.workingSet.status
2351
+ });
2352
+ }
2353
+ const applied = applyOperation(selection.workingSet, operation, updateReason.value);
2354
+ if (!applied.ok) {
2355
+ return applied;
2356
+ }
2357
+ const writeResult = await commitAppliedWorkingSetChange(repository, {
2358
+ workingSetId: selection.workingSet.id,
2359
+ expectedRevision: expectedRevision.value,
2360
+ operation,
2361
+ updateReason: updateReason.value,
2362
+ applied,
2363
+ actor: params.actor,
2364
+ source: params.source,
2365
+ now: timestamp
2366
+ });
2367
+ if (isAppliedWorkingSetCommitFailure(writeResult)) {
2368
+ return writeFailureToResult(selection.workingSet.id, writeResult);
2369
+ }
2370
+ return {
2371
+ ok: true,
2372
+ action: "update",
2373
+ workingSet: writeResult.workingSet,
2374
+ ...writeResult.type === "semantic" ? { event: writeResult.event } : {},
2375
+ projection: createToolSuccessProjection(writeResult.workingSet, "update", timestamp)
2376
+ };
2377
+ }
2378
+
2379
+ // src/app/working-memory/ready.ts
2380
+ var WORKING_MEMORY_DISABLED_MESSAGE = "Working memory is disabled by the workingMemory feature flag.";
2381
+ var WORKING_MEMORY_MISCONFIGURED_MESSAGE = "Working memory is enabled, but no working-memory repository was wired into the runtime.";
2382
+ function workingMemoryNotReadyFailure(input) {
2383
+ if (!input.featureEnabled) {
2384
+ return createFailure("feature_disabled", WORKING_MEMORY_DISABLED_MESSAGE);
2385
+ }
2386
+ if (!input.repository) {
2387
+ return createFailure("misconfigured", WORKING_MEMORY_MISCONFIGURED_MESSAGE);
2388
+ }
2389
+ return null;
2390
+ }
2391
+
2392
+ // src/app/working-memory/service.ts
2393
+ function createWorkingMemoryService(featureFlags, deps = {}) {
2394
+ const featureEnabled = featureFlags.workingMemory;
2395
+ const repository = deps.repository;
2396
+ const now = () => deps.now ? deps.now().toISOString() : (/* @__PURE__ */ new Date()).toISOString();
2397
+ const readiness = () => workingMemoryNotReadyFailure({ featureEnabled, repository });
2398
+ return {
2399
+ async run(params) {
2400
+ const notReady = readiness();
2401
+ if (notReady) {
2402
+ return notReady;
2403
+ }
2404
+ switch (params.action) {
2405
+ case "get":
2406
+ return handleGet(params, repository, now());
2407
+ case "list":
2408
+ return handleList(params, repository);
2409
+ case "create":
2410
+ return handleCreate(params, repository, now(), deps.sourceLabel);
2411
+ case "update":
2412
+ return handleUpdate(params, repository, now());
2413
+ case "close":
2414
+ return handleClose(params, repository, now());
2415
+ }
2416
+ },
2417
+ async prepareExternalGoalMutation(params) {
2418
+ const notReady = readiness();
2419
+ if (notReady) {
2420
+ return notReady;
2421
+ }
2422
+ return handlePrepareExternalGoalMutation(params, repository, now());
2423
+ },
2424
+ async renderProjection(input) {
2425
+ const request = typeof input === "string" ? { sourceRef: input } : input;
2426
+ if (!featureEnabled) {
2427
+ return createWorkingContextStubProjection({
2428
+ reason: "feature_disabled",
2429
+ sourceRef: request.sourceRef
2430
+ });
2431
+ }
2432
+ if (!repository) {
2433
+ return createWorkingContextStubProjection({
2434
+ reason: "misconfigured",
2435
+ sourceRef: request.sourceRef
2436
+ });
2437
+ }
2438
+ const selection = await selectWorkingSet({ workingSetId: request.workingSetId, scope: request.scope }, repository);
2439
+ if (!selection.ok) {
2440
+ return createWorkingContextStubProjection({
2441
+ reason: selection.code === "ambiguous_scope" ? "ambiguous_scope" : "missing_active_set",
2442
+ sourceRef: request.sourceRef
2443
+ });
2444
+ }
2445
+ return createWorkingContextFullProjection(selection.workingSet, request.sourceRef);
2446
+ }
2447
+ };
2448
+ }
2449
+
2450
+ // src/app/host-memory/create-host-memory-services.ts
2451
+ function createHostMemoryServices(featureFlags, options = {}) {
2452
+ const workingMemory = createWorkingMemoryService(featureFlags, {
2453
+ repository: options.workingMemoryRepository,
2454
+ sourceLabel: options.workingMemorySourceLabel
2455
+ });
2456
+ return {
2457
+ workingMemory,
2458
+ goalContinuation: createGoalContinuationService(featureFlags, options.goalContinuationHostPort),
2459
+ routeSessionMemoryTrigger: (event) => routeSessionMemoryTrigger(event, featureFlags, {
2460
+ repository: options.sessionMemoryRepository,
2461
+ workingMemory
2462
+ })
2463
+ };
2464
+ }
2465
+
2466
+ // src/app/skeln/runtime.ts
2467
+ async function createAgenrSkelnServices(config = {}) {
2468
+ return composeHostPluginServices({
2469
+ config,
2470
+ readSlotPolicies: (hostConfig) => hostConfig.memoryPolicy?.slotPolicies,
2471
+ resolveClaimExtraction: ({ agenrConfig }) => createClaimExtractionFromAgenrConfig(agenrConfig),
2472
+ extend: ({ config: hostConfig, resolvedConfig, agenrConfig, runtimeServices }) => {
2473
+ const featureFlags = resolveAgenrFeatureFlags({
2474
+ ...agenrConfig.features,
2475
+ ...hostConfig.featureFlags
2476
+ });
2477
+ const goalContinuationHostPort = void 0;
2478
+ const hostMemory = createHostMemoryServices(featureFlags, {
2479
+ workingMemoryRepository: runtimeServices.workingMemoryRepository,
2480
+ sessionMemoryRepository: runtimeServices.sessionMemoryRepository,
2481
+ workingMemorySourceLabel: "skeln",
2482
+ goalContinuationHostPort
2483
+ });
2484
+ const runtimePolicy = resolveRuntimePolicy(featureFlags, {
2485
+ workingMemoryRepository: runtimeServices.workingMemoryRepository,
2486
+ sessionMemoryRepository: runtimeServices.sessionMemoryRepository,
2487
+ memoryPolicy: hostConfig.memoryPolicy,
2488
+ goalContinuationHostPort
2489
+ });
2490
+ return {
2491
+ ...runtimeServices,
2492
+ ...hostMemory,
2493
+ capabilities: runtimePolicy.capabilities,
2494
+ runtimePolicy,
2495
+ config: resolvedConfig,
2496
+ skelnConfig: hostConfig,
2497
+ agenrConfig
2498
+ };
2499
+ }
2500
+ });
2501
+ }
2502
+
2503
+ // src/adapters/skeln/session/state.ts
2504
+ function createSkelnSessionScopeTracker() {
2505
+ const scopesByIdentity = /* @__PURE__ */ new Map();
2506
+ return {
2507
+ rememberSessionStart(scope) {
2508
+ const identityKey = resolveSessionIdentityKey(scope.sessionId, scope.sessionKey);
2509
+ if (!identityKey) {
2510
+ return;
2511
+ }
2512
+ scopesByIdentity.set(identityKey, scope);
2513
+ },
2514
+ getSessionScope(sessionId) {
2515
+ const normalizedSessionId = sessionId.trim();
2516
+ if (!normalizedSessionId) {
2517
+ return void 0;
2518
+ }
2519
+ return scopesByIdentity.get(`session:${normalizedSessionId}`);
2520
+ },
2521
+ clear(sessionId, sessionKey) {
2522
+ const identityKey = resolveSessionIdentityKey(sessionId, sessionKey);
2523
+ return identityKey ? scopesByIdentity.delete(identityKey) : false;
2524
+ }
2525
+ };
2526
+ }
2527
+
2528
+ // src/adapters/shared/goal-tool-presentations.ts
2529
+ var COMPLETION_BUDGET_REPORT = "Goal achieved. Report final usage from this tool result's structured goal fields. If `goal.tokenBudget` is present, include token usage from `goal.tokensUsed` and `goal.tokenBudget`. If `goal.timeUsedSeconds` is greater than 0, summarize elapsed time in a concise, human-friendly form appropriate to the response language.";
2530
+ function toGoalToolGoal(workingSet) {
2531
+ const budgets = workingSet.snapshot.budgets;
2532
+ return {
2533
+ workingSetId: workingSet.id,
2534
+ revision: workingSet.revision,
2535
+ goalGeneration: readGoalGeneration(workingSet.snapshot),
2536
+ objective: workingSet.snapshot.objective ?? workingSet.objective ?? "",
2537
+ status: workingSet.status,
2538
+ ...budgets?.tokenBudget !== void 0 ? { tokenBudget: budgets.tokenBudget } : {},
2539
+ tokensUsed: budgets?.tokenUsed ?? 0,
2540
+ timeUsedSeconds: budgets?.wallClockUsedSeconds ?? 0,
2541
+ ...workingSet.snapshot.checkpoint ? { checkpoint: workingSet.snapshot.checkpoint } : {},
2542
+ ...workingSet.snapshot.continuation?.policy ? { continuationPolicy: workingSet.snapshot.continuation.policy } : {},
2543
+ ...workingSet.snapshot.continuation?.resumeAfter ? { resumeAfter: workingSet.snapshot.continuation.resumeAfter } : {},
2544
+ ...workingSet.snapshot.continuation?.staleAfter ? { staleAfter: workingSet.snapshot.continuation.staleAfter } : {},
2545
+ ...budgets?.limitReason ? { budgetLimitReason: budgets.limitReason } : {},
2546
+ ...budgets?.turnBudget !== void 0 ? { turnBudget: budgets.turnBudget } : {},
2547
+ turnsUsed: budgets?.turnsUsed ?? 0,
2548
+ createdAt: workingSet.createdAt,
2549
+ updatedAt: workingSet.updatedAt
2550
+ };
2551
+ }
2552
+ function toGoalToolResponse(workingSet, includeCompletionReport) {
2553
+ if (!workingSet) {
2554
+ return {
2555
+ goal: null,
2556
+ remainingTokens: null,
2557
+ completionBudgetReport: null
2558
+ };
2559
+ }
2560
+ const budgets = workingSet.snapshot.budgets;
2561
+ const goal = toGoalToolGoal(workingSet);
2562
+ return {
2563
+ goal,
2564
+ remainingTokens: budgets?.tokenBudget !== void 0 ? Math.max(budgets.tokenBudget - goal.tokensUsed, 0) : null,
2565
+ completionBudgetReport: includeCompletionReport && hasUsageToReport(goal) ? COMPLETION_BUDGET_REPORT : null
2566
+ };
2567
+ }
2568
+ function hasUsageToReport(goal) {
2569
+ return goal.tokenBudget !== void 0 || goal.timeUsedSeconds > 0;
2570
+ }
2571
+
2572
+ // src/adapters/shared/goal-tools.ts
2573
+ var GET_GOAL_TOOL_PARAMETERS = {
2574
+ type: "object",
2575
+ additionalProperties: false,
2576
+ properties: {}
2577
+ };
2578
+ var CREATE_GOAL_TOOL_PARAMETERS = {
2579
+ type: "object",
2580
+ additionalProperties: false,
2581
+ properties: {
2582
+ objective: {
2583
+ type: "string",
2584
+ description: "Required. The concrete objective to start pursuing. This starts a new active goal only when no goal is currently defined; if a goal already exists, this tool fails."
2585
+ },
2586
+ token_budget: {
2587
+ type: "integer",
2588
+ minimum: 1,
2589
+ description: "Positive token budget for the new goal. Omit unless explicitly requested."
2590
+ }
2591
+ },
2592
+ required: ["objective"]
2593
+ };
2594
+ var UPDATE_GOAL_TOOL_PARAMETERS = {
2595
+ type: "object",
2596
+ additionalProperties: false,
2597
+ properties: {
2598
+ status: {
2599
+ type: "string",
2600
+ enum: ["complete", "blocked"],
2601
+ description: "Required. Set to complete only when the objective is achieved and no required work remains. Set to blocked only after the same blocking condition has recurred for at least three consecutive goal turns and the agent is at an impasse."
2602
+ }
2603
+ },
2604
+ required: ["status"]
2605
+ };
2606
+ var CREATE_GOAL_EXISTS_MESSAGE = "cannot create a new goal because this thread already has a goal; use update_goal only when the existing goal is complete";
2607
+ var UPDATE_GOAL_UNSUPPORTED_STATUS_MESSAGE = "update_goal can only mark the existing goal complete or blocked; pause, resume, budget-limited, and usage-limited status changes are controlled by the user or system";
2608
+ async function runGoalAliasTool(toolName, rawParams, defaultScope, reader, workingMemory) {
2609
+ switch (toolName) {
2610
+ case "get_goal":
2611
+ return runGetGoalTool(defaultScope, workingMemory);
2612
+ case "create_goal":
2613
+ return runCreateGoalTool(rawParams, defaultScope, reader, workingMemory);
2614
+ case "update_goal":
2615
+ return runUpdateGoalTool(rawParams, defaultScope, reader, workingMemory);
2616
+ }
2617
+ }
2618
+ async function runGetGoalTool(defaultScope, workingMemory) {
2619
+ const result = await workingMemory.run({ action: "get", scope: defaultScope });
2620
+ if (!result.ok && result.code === "missing_active_set") {
2621
+ return goalSuccess(null, false);
2622
+ }
2623
+ return resultToGoalOutcome(result, false);
2624
+ }
2625
+ async function runCreateGoalTool(rawParams, defaultScope, reader, workingMemory) {
2626
+ const params = asRecord(rawParams);
2627
+ const objective = reader.readString(params, "objective", { required: true }) ?? "";
2628
+ const tokenBudget = reader.readNumber(params, "token_budget", { integer: true, strict: true });
2629
+ if (tokenBudget !== void 0 && tokenBudget <= 0) {
2630
+ return goalFailure("invalid_request", "token_budget must be positive.");
2631
+ }
2632
+ const result = await workingMemory.run({
2633
+ action: "create",
2634
+ scope: defaultScope,
2635
+ operation: {
2636
+ type: "set_objective",
2637
+ objective
2638
+ },
2639
+ ...tokenBudget !== void 0 ? { initialBudget: { tokenBudget } } : {},
2640
+ updateReason: "Model created goal via create_goal.",
2641
+ actor: "model",
2642
+ source: "goal_command"
2643
+ });
2644
+ if (!result.ok && result.code === "active_set_exists") {
2645
+ return goalFailure(result.code, CREATE_GOAL_EXISTS_MESSAGE, result.details);
2646
+ }
2647
+ return resultToGoalOutcome(result, false);
2648
+ }
2649
+ async function runUpdateGoalTool(rawParams, defaultScope, reader, workingMemory) {
2650
+ const params = asRecord(rawParams);
2651
+ const status = reader.readString(params, "status", { required: true });
2652
+ if (!isGoalAliasUpdateStatus(status)) {
2653
+ return goalFailure("invalid_request", UPDATE_GOAL_UNSUPPORTED_STATUS_MESSAGE);
2654
+ }
2655
+ const update = await workingMemory.run({
2656
+ action: "update",
2657
+ scope: defaultScope,
2658
+ operation: {
2659
+ type: "set_status",
2660
+ status
2661
+ },
2662
+ updateReason: status === "complete" ? "Model marked goal complete via update_goal." : "Model marked goal blocked via update_goal.",
2663
+ actor: "model",
2664
+ source: "goal_command"
2665
+ });
2666
+ return resultToGoalOutcome(update, status === "complete");
2667
+ }
2668
+ function isGoalAliasUpdateStatus(status) {
2669
+ return status === "complete" || status === "blocked";
2670
+ }
2671
+ function resultToGoalOutcome(result, includeCompletionReport) {
2672
+ if (!result.ok) {
2673
+ return goalFailure(result.code, result.message, result.details);
2674
+ }
2675
+ switch (result.action) {
2676
+ case "get":
2677
+ case "create":
2678
+ case "update":
2679
+ return goalSuccess(result.workingSet, includeCompletionReport);
2680
+ case "list":
2681
+ case "close":
2682
+ case "prepare_external_goal_mutation":
2683
+ return goalFailure("invalid_request", `Goal alias cannot return agenr_work ${result.action} results.`);
2684
+ }
2685
+ }
2686
+ function goalSuccess(workingSet, includeCompletionReport) {
2687
+ const response = toGoalToolResponse(workingSet, includeCompletionReport);
2688
+ return {
2689
+ text: JSON.stringify(response, null, 2),
2690
+ details: {
2691
+ goal: response.goal,
2692
+ remainingTokens: response.remainingTokens,
2693
+ completionBudgetReport: response.completionBudgetReport
2694
+ },
2695
+ failed: false
2696
+ };
2697
+ }
2698
+ function goalFailure(code, message, details) {
2699
+ return {
2700
+ text: message,
2701
+ details: {
2702
+ status: "failed",
2703
+ code,
2704
+ ...details ? { details } : {}
2705
+ },
2706
+ failed: true
2707
+ };
2708
+ }
2709
+
2710
+ // src/adapters/shared/param-readers.ts
2711
+ function readStringParam(params, key, options = {}) {
2712
+ const value = params[key];
2713
+ if (value === void 0 || value === null) {
2714
+ if (options.required) {
2715
+ throw new Error(`${options.label ?? key} is required.`);
2716
+ }
2717
+ return void 0;
2718
+ }
2719
+ if (typeof value !== "string") {
2720
+ throw new Error(`${options.label ?? key} must be a string.`);
2721
+ }
2722
+ const normalized = options.trim === false ? value : value.trim();
2723
+ if (options.required && normalized.length === 0) {
2724
+ throw new Error(`${options.label ?? key} is required.`);
2725
+ }
2726
+ return normalized.length > 0 || options.trim === false ? normalized : void 0;
2727
+ }
2728
+ function readNumberParam(params, key, options = {}) {
2729
+ const value = params[key];
2730
+ if (value === void 0 || value === null) {
2731
+ return void 0;
2732
+ }
2733
+ if (typeof value !== "number" || !Number.isFinite(value)) {
2734
+ throw new Error(`${key} must be a number.`);
2735
+ }
2736
+ if (options.integer && !Number.isInteger(value)) {
2737
+ throw new Error(`${key} must be an integer.`);
2738
+ }
2739
+ if (options.strict && value < 0) {
2740
+ throw new Error(`${key} must be non-negative.`);
2741
+ }
2742
+ return value;
2743
+ }
2744
+ function readStringArrayParam(params, key) {
2745
+ const value = params[key];
2746
+ if (value === void 0 || value === null) {
2747
+ return void 0;
2748
+ }
2749
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
2750
+ throw new Error(`${key} must be an array of strings.`);
2751
+ }
2752
+ return value;
2753
+ }
2754
+
2755
+ // src/adapters/skeln/tools/shared.ts
2756
+ var SKELN_PARAM_READER = {
2757
+ readString: readStringParam,
2758
+ readNumber: readNumberParam,
2759
+ readStringArray: readStringArrayParam
2760
+ };
2761
+ function toolSchema(value) {
2762
+ return value;
2763
+ }
2764
+ function textToolResult(text, details) {
2765
+ return {
2766
+ content: [{ type: "text", text }],
2767
+ details
2768
+ };
2769
+ }
2770
+ function toSkelnToolResult(outcome) {
2771
+ return textToolResult(outcome.text, outcome.details);
2772
+ }
2773
+ function toolFailureResult(error) {
2774
+ return textToolResult(formatErrorMessage(error), {
2775
+ status: "failed"
2776
+ });
2777
+ }
2778
+
2779
+ // src/adapters/skeln/tools/goal.ts
2780
+ function registerAgenrSkelnGoalAliasTools(skeln, servicesPromise, resolveScope) {
2781
+ registerGoalAliasTool(skeln, servicesPromise, resolveScope, {
2782
+ name: "get_goal",
2783
+ label: "Get Goal",
2784
+ description: "Get the current goal for this thread, including status, budgets, usage, and remaining token budget.",
2785
+ parameters: GET_GOAL_TOOL_PARAMETERS
2786
+ });
2787
+ registerGoalAliasTool(skeln, servicesPromise, resolveScope, {
2788
+ name: "create_goal",
2789
+ label: "Create Goal",
2790
+ description: "Create a goal only when explicitly requested by the user or system/developer instructions. Fails if a goal already exists; use update_goal only for status.",
2791
+ parameters: CREATE_GOAL_TOOL_PARAMETERS
2792
+ });
2793
+ registerGoalAliasTool(skeln, servicesPromise, resolveScope, {
2794
+ name: "update_goal",
2795
+ label: "Update Goal",
2796
+ description: "Update the existing goal. Use this tool only to mark the goal achieved or genuinely blocked; pause, resume, and budget states are controlled by the user or system.",
2797
+ parameters: UPDATE_GOAL_TOOL_PARAMETERS
2798
+ });
2799
+ }
2800
+ function registerGoalAliasTool(skeln, servicesPromise, resolveScope, definition) {
2801
+ skeln.registerTool({
2802
+ name: definition.name,
2803
+ label: definition.label,
2804
+ description: definition.description,
2805
+ promptSnippet: `${definition.name} is a Codex-compatible alias over Agenr working memory.`,
2806
+ parameters: toolSchema(definition.parameters),
2807
+ execute: async (_toolCallId, rawParams, _signal, _onUpdate, context) => {
2808
+ try {
2809
+ const [services, scope] = await Promise.all([servicesPromise, resolveScope(context)]);
2810
+ const outcome = await runGoalAliasTool(definition.name, rawParams, toWorkingScopeFromSkelnSession(scope), SKELN_PARAM_READER, services.workingMemory);
2811
+ return textToolResult(outcome.text, outcome.details);
2812
+ } catch (error) {
2813
+ return toolFailureResult(error);
2814
+ }
2815
+ }
2816
+ });
2817
+ }
2818
+
2819
+ // src/adapters/skeln/tools/recall.ts
2820
+ function registerAgenrSkelnRecallTool(skeln, servicesPromise, resolveScope) {
2821
+ skeln.registerTool({
2822
+ name: "agenr_recall",
2823
+ label: "Agenr Recall",
2824
+ description: "Retrieve knowledge from agenr long-term memory. Use mode=auto for normal use, including exact facts, historical-state questions, time-bounded episode questions, and procedural questions.",
2825
+ promptSnippet: "Use agenr_recall to retrieve durable memory, prior episode summaries, or canonical procedures from agenr.",
2826
+ promptGuidelines: [
2827
+ "Use focused natural-language queries instead of broad 'everything' searches.",
2828
+ "Use mode=procedures for how-to or checklist questions, and mode=episodes for what-happened questions tied to time or sessions.",
2829
+ "Use asOf when the user asks what was true at an earlier point in time."
2830
+ ],
2831
+ parameters: toolSchema(RECALL_TOOL_PARAMETERS),
2832
+ execute: async (_toolCallId, rawParams, _signal, _onUpdate, context) => {
2833
+ try {
2834
+ const params = parseRecallToolParams(rawParams, SKELN_PARAM_READER);
2835
+ const [services, scope] = await Promise.all([servicesPromise, resolveScope(context)]);
2836
+ const result = await runRecallMemoryTool(params, buildRecallToolServices(services), {
2837
+ sessionKey: scope.sessionKey,
2838
+ slotPolicyConfig: services.skelnConfig.memoryPolicy?.slotPolicies
2839
+ });
2840
+ return textToolResult(formatUnifiedRecallResults(result), buildRecallToolDetails(result, { sessionKey: scope.sessionKey }));
2841
+ } catch (error) {
2842
+ return toolFailureResult(error);
2843
+ }
2844
+ }
2845
+ });
2846
+ }
2847
+
2848
+ // src/adapters/skeln/tools/store.ts
2849
+ function registerAgenrSkelnStoreTool(skeln, servicesPromise, resolveScope) {
2850
+ skeln.registerTool({
2851
+ name: "agenr_store",
2852
+ label: "Agenr Store",
2853
+ description: "Store a new durable memory entry in agenr. Store only durable facts, decisions, preferences, lessons, milestones, and relationships that help a future Skeln session make a better decision.",
2854
+ promptSnippet: "Use agenr_store to persist durable memory that should survive across Skeln sessions.",
2855
+ promptGuidelines: [
2856
+ "Do not store progress logs, plans, or data already canonical in git, tickets, calendars, signed docs, chat/email, or databases.",
2857
+ "Store the durable takeaway, standing rule, preference, risk, lesson, or relationship instead of raw activity.",
2858
+ "Use claimKey for slot-like facts that may be superseded later, such as versions, strategies, owners, or limits."
2859
+ ],
2860
+ parameters: toolSchema(STORE_TOOL_PARAMETERS),
2861
+ execute: async (_toolCallId, rawParams, _signal, _onUpdate, context) => {
2862
+ try {
2863
+ const params = parseStoreToolParams(rawParams, SKELN_PARAM_READER);
2864
+ const [services, scope] = await Promise.all([servicesPromise, resolveScope(context)]);
2865
+ return toSkelnToolResult(
2866
+ await runStoreMemoryTool(params, services, {
2867
+ session: scope,
2868
+ sourcePrefix: "skeln-session",
2869
+ defaultSourceContext: "Stored via agenr_store from Skeln.",
2870
+ extraDetails: { sessionKey: scope.sessionKey },
2871
+ onWarning: (warning) => console.warn(`[agenr] tool=agenr_store session=${scope.sessionId} warning: ${warning}`)
2872
+ })
2873
+ );
2874
+ } catch (error) {
2875
+ return toolFailureResult(error);
2876
+ }
2877
+ }
2878
+ });
2879
+ }
2880
+
2881
+ // src/adapters/skeln/tools/update.ts
2882
+ function registerAgenrSkelnUpdateTool(skeln, servicesPromise, resolveScope) {
2883
+ skeln.registerTool({
2884
+ name: "agenr_update",
2885
+ label: "Agenr Update",
2886
+ description: "Update an existing memory entry in place. Supports importance, expiry, claimKey, validFrom, and validTo.",
2887
+ promptSnippet: "Use agenr_update to correct metadata on an existing durable memory entry.",
2888
+ promptGuidelines: ["Provide exactly one target selector: id or subject.", "Use agenr_store with supersedes for substantive content replacement."],
2889
+ parameters: toolSchema(UPDATE_TOOL_PARAMETERS),
2890
+ execute: async (_toolCallId, rawParams, _signal, _onUpdate, context) => {
2891
+ try {
2892
+ const params = parseUpdateToolParams(rawParams, SKELN_PARAM_READER);
2893
+ const [services, scope] = await Promise.all([servicesPromise, resolveScope(context)]);
2894
+ const outcome = await runUpdateMemoryTool(params, services, {
2895
+ session: scope,
2896
+ sourcePrefix: "skeln-session",
2897
+ successDetails: { sessionKey: scope.sessionKey },
2898
+ failureDetails: { sessionKey: scope.sessionKey }
2899
+ });
2900
+ return toSkelnToolResult({
2901
+ ...outcome,
2902
+ details: {
2903
+ ...outcome.details,
2904
+ target: formatTargetSelector(params.id, params.subject),
2905
+ sanitized: sanitizeUpdateToolParams({
2906
+ id: params.id,
2907
+ subject: params.subject,
2908
+ importance: params.importance,
2909
+ expiry: params.expiry,
2910
+ claimKey: params.claimKeyInput,
2911
+ validFrom: params.validFrom,
2912
+ validTo: params.validTo
2913
+ })
2914
+ }
2915
+ });
2916
+ } catch (error) {
2917
+ return toolFailureResult(error);
2918
+ }
2919
+ }
2920
+ });
2921
+ }
2922
+
2923
+ // src/adapters/shared/work-tool-presentations.ts
2924
+ function buildWorkingSetGetDetails(workingSet, eventsReturned = 0) {
2925
+ return {
2926
+ status: "ok",
2927
+ action: "get",
2928
+ workingSetId: workingSet.id,
2929
+ revision: workingSet.revision,
2930
+ goalGeneration: readGoalGeneration(workingSet.snapshot),
2931
+ goalStatus: workingSet.status,
2932
+ objective: workingSet.snapshot.objective ?? workingSet.objective ?? null,
2933
+ checkpoint: workingSet.snapshot.checkpoint ?? null,
2934
+ continuation: workingSet.snapshot.continuation ?? null,
2935
+ budgets: workingSet.snapshot.budgets ?? null,
2936
+ eventsReturned
2937
+ };
2938
+ }
2939
+ function buildWorkingMemoryDetails(result) {
2940
+ if (!result.ok) {
2941
+ return {
2942
+ status: "failed",
2943
+ code: result.code,
2944
+ ...result.details ? { details: result.details } : {}
2945
+ };
2946
+ }
2947
+ switch (result.action) {
2948
+ case "get":
2949
+ return buildWorkingSetGetDetails(result.workingSet, result.events?.length ?? 0);
2950
+ case "list":
2951
+ return {
2952
+ status: "ok",
2953
+ action: "list",
2954
+ count: result.workingSets.length,
2955
+ workingSetIds: result.workingSets.map((set) => set.id)
2956
+ };
2957
+ case "create":
2958
+ return {
2959
+ status: "ok",
2960
+ action: "create",
2961
+ workingSetId: result.workingSet.id,
2962
+ revision: result.workingSet.revision,
2963
+ goalGeneration: readGoalGeneration(result.workingSet.snapshot)
2964
+ };
2965
+ case "update":
2966
+ return {
2967
+ status: "ok",
2968
+ action: "update",
2969
+ workingSetId: result.workingSet.id,
2970
+ revision: result.workingSet.revision,
2971
+ goalGeneration: readGoalGeneration(result.workingSet.snapshot)
2972
+ };
2973
+ case "close":
2974
+ return {
2975
+ status: "ok",
2976
+ action: "close",
2977
+ workingSetId: result.workingSet.id,
2978
+ revision: result.workingSet.revision,
2979
+ goalGeneration: readGoalGeneration(result.workingSet.snapshot),
2980
+ candidateCount: result.candidates.length
2981
+ };
2982
+ case "prepare_external_goal_mutation":
2983
+ return {
2984
+ status: "ok",
2985
+ action: "prepare_external_goal_mutation",
2986
+ prepared: result.prepared,
2987
+ workingSetId: result.workingSet?.id ?? null,
2988
+ revision: result.workingSet?.revision ?? null,
2989
+ eventsReturned: result.events.length
2990
+ };
2991
+ }
2992
+ }
2993
+ function formatWorkingMemoryResultText(result) {
2994
+ if (!result.ok) {
2995
+ return `agenr_work failed: ${result.message}`;
2996
+ }
2997
+ switch (result.action) {
2998
+ case "get":
2999
+ return result.projection.content;
3000
+ case "list":
3001
+ if (result.workingSets.length === 0) {
3002
+ return "No working sets matched.";
3003
+ }
3004
+ return result.workingSets.map((set) => `${set.id} rev ${set.revision} ${set.status}: ${set.snapshot.objective ?? set.title ?? set.scopeKey}`).join("\n");
3005
+ case "create":
3006
+ return `Created working set ${result.workingSet.id} at revision ${result.workingSet.revision}.`;
3007
+ case "update":
3008
+ return `Updated working set ${result.workingSet.id} at revision ${result.workingSet.revision}.`;
3009
+ case "close":
3010
+ return `Closed working set ${result.workingSet.id} at revision ${result.workingSet.revision}. Candidates: ${result.candidates.length}.`;
3011
+ case "prepare_external_goal_mutation":
3012
+ if (!result.prepared || !result.workingSet) {
3013
+ return "No active working set required external mutation preparation.";
3014
+ }
3015
+ return `Prepared working set ${result.workingSet.id} at revision ${result.workingSet.revision}. Events: ${result.events.length}.`;
3016
+ }
3017
+ }
3018
+
3019
+ // src/adapters/shared/work-tool-policy.ts
3020
+ var MODEL_VISIBLE_WORK_ACTIONS = ["get", "list", "create", "update"];
3021
+ var RESERVED_MODEL_WORK_MESSAGES = {
3022
+ close: "agenr_work failed: agenr_work close is reserved for /goal clear and host lifecycle paths.",
3023
+ set_status: "agenr_work failed: status changes are reserved for get_goal/create_goal/update_goal and trusted host lifecycle paths."
3024
+ };
3025
+ function getModelReservedWorkAction(params) {
3026
+ if (params.source !== void 0 && params.source !== "tool") {
3027
+ return null;
3028
+ }
3029
+ if (params.action === "close") {
3030
+ return "close";
3031
+ }
3032
+ if (params.operation?.type === "set_status") {
3033
+ return "set_status";
3034
+ }
3035
+ return null;
3036
+ }
3037
+ function isModelVisibleWorkAction(value) {
3038
+ return value !== void 0 && MODEL_VISIBLE_WORK_ACTIONS.includes(value);
3039
+ }
3040
+
3041
+ // src/adapters/shared/work-tool-operation-parsers.ts
3042
+ function optionalStringParam(record, key, reader) {
3043
+ const value = reader.readString(record, key);
3044
+ return value ? { [key]: value } : {};
3045
+ }
3046
+ function requiredString(record, key, reader) {
3047
+ return reader.readString(record, key, { required: true }) ?? "";
3048
+ }
3049
+ function requiredStringArray(record, key, reader) {
3050
+ const value = reader.readStringArray(record, key);
3051
+ if (!value) {
3052
+ throw new Error(`${key} is required.`);
3053
+ }
3054
+ return value;
3055
+ }
3056
+ function requiredNumberArray(record, key) {
3057
+ const value = record[key];
3058
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "number" || !Number.isInteger(item))) {
3059
+ throw new Error(`${key} must be an array of integers.`);
3060
+ }
3061
+ return value;
3062
+ }
3063
+ function requiredBoolean(record, key) {
3064
+ const value = record[key];
3065
+ if (typeof value !== "boolean") {
3066
+ throw new Error(`${key} must be a boolean.`);
3067
+ }
3068
+ return value;
3069
+ }
3070
+ function parseArray(value, parse, key, reader) {
3071
+ if (!Array.isArray(value)) {
3072
+ throw new Error(`${key} must be an array.`);
3073
+ }
3074
+ return value.map((item) => parse(item, reader));
3075
+ }
3076
+ function optionalNumberParam(record, key, reader) {
3077
+ const value = reader.readNumber(record, key, { integer: true, strict: true });
3078
+ return value !== void 0 ? { [key]: value } : {};
3079
+ }
3080
+ function optionalBooleanParam(record, key) {
3081
+ const value = record[key];
3082
+ if (value === void 0 || value === null) {
3083
+ return {};
3084
+ }
3085
+ if (typeof value !== "boolean") {
3086
+ throw new Error(`${key} must be a boolean.`);
3087
+ }
3088
+ return { [key]: value };
3089
+ }
3090
+ function parseNextActionStatus(value) {
3091
+ if (value === "pending" || value === "in_progress" || value === "blocked" || value === "done") {
3092
+ return value;
3093
+ }
3094
+ throw new Error(`Unsupported next-action status "${value}".`);
3095
+ }
3096
+ function parseConfidence(value) {
3097
+ if (value === "low" || value === "medium" || value === "high") {
3098
+ return value;
3099
+ }
3100
+ throw new Error(`Unsupported assumption confidence "${value}".`);
3101
+ }
3102
+ function parsePromotionStatus(value) {
3103
+ if (WORKING_CANDIDATE_PROMOTION_STATUSES.includes(value)) {
3104
+ return value;
3105
+ }
3106
+ throw new Error(`Unsupported candidate promotion status "${value}".`);
3107
+ }
3108
+ function parseNextAction(value, reader) {
3109
+ const record = asRecord(value);
3110
+ const status = reader.readString(record, "status");
3111
+ return {
3112
+ text: requiredString(record, "text", reader),
3113
+ ...status ? { status: parseNextActionStatus(status) } : {},
3114
+ ...optionalStringParam(record, "ref", reader)
3115
+ };
3116
+ }
3117
+ function parseCheckpoint(value, reader) {
3118
+ const record = asRecord(value);
3119
+ return {
3120
+ summary: requiredString(record, "summary", reader),
3121
+ recordedAt: requiredString(record, "recordedAt", reader),
3122
+ ...record.nextActions !== void 0 ? { nextActions: requiredStringArray(record, "nextActions", reader) } : {},
3123
+ ...record.blockers !== void 0 ? { blockers: requiredStringArray(record, "blockers", reader) } : {}
3124
+ };
3125
+ }
3126
+ function parseFileNote(value, reader) {
3127
+ const record = asRecord(value);
3128
+ return {
3129
+ path: requiredString(record, "path", reader),
3130
+ ...optionalStringParam(record, "note", reader),
3131
+ ...optionalStringParam(record, "observedAt", reader)
3132
+ };
3133
+ }
3134
+ function parseCommandNote(value, reader) {
3135
+ const record = asRecord(value);
3136
+ return {
3137
+ command: requiredString(record, "command", reader),
3138
+ ...optionalStringParam(record, "outcome", reader),
3139
+ ...optionalStringParam(record, "observedAt", reader)
3140
+ };
3141
+ }
3142
+ function parseDecisionNote(value, reader) {
3143
+ const record = asRecord(value);
3144
+ return {
3145
+ decision: requiredString(record, "decision", reader),
3146
+ ...optionalStringParam(record, "rationale", reader),
3147
+ ...optionalStringParam(record, "decidedAt", reader)
3148
+ };
3149
+ }
3150
+ function parseAssumptionNote(value, reader) {
3151
+ const record = asRecord(value);
3152
+ const confidence = reader.readString(record, "confidence");
3153
+ return {
3154
+ assumption: requiredString(record, "assumption", reader),
3155
+ ...confidence ? { confidence: parseConfidence(confidence) } : {},
3156
+ ...record.validated !== void 0 ? { validated: requiredBoolean(record, "validated") } : {}
3157
+ };
3158
+ }
3159
+ function parseCandidateProvenance(value, reader) {
3160
+ const record = asRecord(value);
3161
+ return {
3162
+ evidenceEventSequences: requiredNumberArray(record, "evidenceEventSequences"),
3163
+ ...optionalStringParam(record, "sourceRef", reader),
3164
+ ...optionalStringParam(record, "note", reader)
3165
+ };
3166
+ }
3167
+ function parseCandidate(value, reader) {
3168
+ const record = asRecord(value);
3169
+ const kind = requiredString(record, "kind", reader);
3170
+ const provenance = parseCandidateProvenance(record.provenance, reader);
3171
+ const promotionStatus = parsePromotionStatus(requiredString(record, "promotionStatus", reader));
3172
+ if (kind === "episodic") {
3173
+ return {
3174
+ kind,
3175
+ summary: requiredString(record, "summary", reader),
3176
+ provenance,
3177
+ promotionStatus
3178
+ };
3179
+ }
3180
+ if (kind === "semantic" || kind === "procedural") {
3181
+ return {
3182
+ kind,
3183
+ subject: requiredString(record, "subject", reader),
3184
+ content: requiredString(record, "content", reader),
3185
+ ...optionalStringParam(record, "suggestedClaimKey", reader),
3186
+ provenance,
3187
+ promotionStatus
3188
+ };
3189
+ }
3190
+ throw new Error(`Unsupported working candidate kind "${kind}".`);
3191
+ }
3192
+
3193
+ // src/adapters/shared/work-tool-operation-schemas.ts
3194
+ var NEXT_ACTION_STATUSES = ["pending", "in_progress", "blocked", "done"];
3195
+ var ASSUMPTION_CONFIDENCE_VALUES = ["low", "medium", "high"];
3196
+ function operationVariant(required, properties) {
3197
+ return {
3198
+ type: "object",
3199
+ additionalProperties: false,
3200
+ properties,
3201
+ required
3202
+ };
3203
+ }
3204
+ function constString(value) {
3205
+ return { type: "string", const: value };
3206
+ }
3207
+ function stringArray(description) {
3208
+ return {
3209
+ type: "array",
3210
+ description,
3211
+ items: { type: "string" }
3212
+ };
3213
+ }
3214
+ function nextActionsSchema() {
3215
+ return {
3216
+ type: "array",
3217
+ items: {
3218
+ type: "object",
3219
+ additionalProperties: false,
3220
+ properties: {
3221
+ text: { type: "string" },
3222
+ status: { type: "string", enum: [...NEXT_ACTION_STATUSES] },
3223
+ ref: { type: "string" }
3224
+ },
3225
+ required: ["text"]
3226
+ }
3227
+ };
3228
+ }
3229
+ function checkpointSchema() {
3230
+ return {
3231
+ type: "object",
3232
+ additionalProperties: false,
3233
+ properties: {
3234
+ summary: { type: "string", description: "Compact summary of current progress." },
3235
+ recordedAt: { type: "string", description: "ISO timestamp when the checkpoint was recorded." },
3236
+ nextActions: stringArray("Expected next actions after the checkpoint."),
3237
+ blockers: stringArray("Known blockers at checkpoint time.")
3238
+ },
3239
+ required: ["summary", "recordedAt"]
3240
+ };
3241
+ }
3242
+ function fileNoteSchema() {
3243
+ return {
3244
+ type: "object",
3245
+ additionalProperties: false,
3246
+ properties: {
3247
+ path: { type: "string" },
3248
+ note: { type: "string" },
3249
+ observedAt: { type: "string" }
3250
+ },
3251
+ required: ["path"]
3252
+ };
3253
+ }
3254
+ function commandNoteSchema() {
3255
+ return {
3256
+ type: "object",
3257
+ additionalProperties: false,
3258
+ properties: {
3259
+ command: { type: "string" },
3260
+ outcome: { type: "string" },
3261
+ observedAt: { type: "string" }
3262
+ },
3263
+ required: ["command"]
3264
+ };
3265
+ }
3266
+ function decisionNoteSchema() {
3267
+ return {
3268
+ type: "object",
3269
+ additionalProperties: false,
3270
+ properties: {
3271
+ decision: { type: "string" },
3272
+ rationale: { type: "string" },
3273
+ decidedAt: { type: "string" }
3274
+ },
3275
+ required: ["decision"]
3276
+ };
3277
+ }
3278
+ function assumptionNoteSchema() {
3279
+ return {
3280
+ type: "object",
3281
+ additionalProperties: false,
3282
+ properties: {
3283
+ assumption: { type: "string" },
3284
+ confidence: { type: "string", enum: [...ASSUMPTION_CONFIDENCE_VALUES] },
3285
+ validated: { type: "boolean" }
3286
+ },
3287
+ required: ["assumption"]
3288
+ };
3289
+ }
3290
+ function candidateProvenanceSchema() {
3291
+ return {
3292
+ type: "object",
3293
+ additionalProperties: false,
3294
+ properties: {
3295
+ evidenceEventSequences: {
3296
+ type: "array",
3297
+ items: { type: "integer" }
3298
+ },
3299
+ sourceRef: { type: "string" },
3300
+ note: { type: "string" }
3301
+ },
3302
+ required: ["evidenceEventSequences"]
3303
+ };
3304
+ }
3305
+ function candidatePromotionSchema() {
3306
+ return { type: "string", enum: [...WORKING_CANDIDATE_PROMOTION_STATUSES] };
3307
+ }
3308
+ function candidateSchema() {
3309
+ return {
3310
+ oneOf: [
3311
+ operationVariant(["kind", "summary", "provenance", "promotionStatus"], {
3312
+ kind: constString("episodic"),
3313
+ summary: { type: "string" },
3314
+ provenance: candidateProvenanceSchema(),
3315
+ promotionStatus: candidatePromotionSchema()
3316
+ }),
3317
+ operationVariant(["kind", "subject", "content", "provenance", "promotionStatus"], {
3318
+ kind: constString("semantic"),
3319
+ subject: { type: "string" },
3320
+ content: { type: "string" },
3321
+ suggestedClaimKey: { type: "string" },
3322
+ provenance: candidateProvenanceSchema(),
3323
+ promotionStatus: candidatePromotionSchema()
3324
+ }),
3325
+ operationVariant(["kind", "subject", "content", "provenance", "promotionStatus"], {
3326
+ kind: constString("procedural"),
3327
+ subject: { type: "string" },
3328
+ content: { type: "string" },
3329
+ suggestedClaimKey: { type: "string" },
3330
+ provenance: candidateProvenanceSchema(),
3331
+ promotionStatus: candidatePromotionSchema()
3332
+ })
3333
+ ]
3334
+ };
3335
+ }
3336
+ function createOperationSchema(schemas) {
3337
+ return {
3338
+ description: "Typed working-memory operation for create and update. Always include operation.type and the required payload field for that variant.",
3339
+ oneOf: schemas
3340
+ };
3341
+ }
3342
+
3343
+ // src/adapters/shared/work-tool-operation-registry.ts
3344
+ var MODEL_VISIBLE_OPERATIONS = {
3345
+ set_objective: {
3346
+ buildSchema: () => operationVariant(["type", "objective"], {
3347
+ type: constString("set_objective"),
3348
+ objective: { type: "string", description: "Current concrete objective." },
3349
+ title: { type: "string", description: "Optional display title." }
3350
+ }),
3351
+ parse: (record, reader) => ({
3352
+ type: "set_objective",
3353
+ objective: requiredString(record, "objective", reader),
3354
+ ...optionalStringParam(record, "title", reader)
3355
+ })
3356
+ },
3357
+ replace_plan: {
3358
+ buildSchema: () => operationVariant(["type", "currentPlan"], {
3359
+ type: constString("replace_plan"),
3360
+ currentPlan: stringArray("Ordered current plan steps."),
3361
+ nextActions: nextActionsSchema()
3362
+ }),
3363
+ parse: (record, reader) => ({
3364
+ type: "replace_plan",
3365
+ currentPlan: requiredStringArray(record, "currentPlan", reader),
3366
+ ...record.nextActions !== void 0 ? { nextActions: parseArray(record.nextActions, parseNextAction, "nextActions", reader) } : {}
3367
+ })
3368
+ },
3369
+ merge_checkpoint: {
3370
+ buildSchema: () => operationVariant(["type", "checkpoint"], {
3371
+ type: constString("merge_checkpoint"),
3372
+ checkpoint: checkpointSchema()
3373
+ }),
3374
+ parse: (record, reader) => ({
3375
+ type: "merge_checkpoint",
3376
+ checkpoint: parseCheckpoint(record.checkpoint, reader)
3377
+ })
3378
+ },
3379
+ add_file_note: {
3380
+ buildSchema: () => operationVariant(["type", "file"], {
3381
+ type: constString("add_file_note"),
3382
+ file: fileNoteSchema()
3383
+ }),
3384
+ parse: (record, reader) => ({
3385
+ type: "add_file_note",
3386
+ file: parseFileNote(record.file, reader)
3387
+ })
3388
+ },
3389
+ add_command_note: {
3390
+ buildSchema: () => operationVariant(["type", "command"], {
3391
+ type: constString("add_command_note"),
3392
+ command: commandNoteSchema()
3393
+ }),
3394
+ parse: (record, reader) => ({
3395
+ type: "add_command_note",
3396
+ command: parseCommandNote(record.command, reader)
3397
+ })
3398
+ },
3399
+ record_decision: {
3400
+ buildSchema: () => operationVariant(["type", "decision"], {
3401
+ type: constString("record_decision"),
3402
+ decision: decisionNoteSchema()
3403
+ }),
3404
+ parse: (record, reader) => ({
3405
+ type: "record_decision",
3406
+ decision: parseDecisionNote(record.decision, reader)
3407
+ })
3408
+ },
3409
+ record_assumption: {
3410
+ buildSchema: () => operationVariant(["type", "assumption"], {
3411
+ type: constString("record_assumption"),
3412
+ assumption: assumptionNoteSchema()
3413
+ }),
3414
+ parse: (record, reader) => ({
3415
+ type: "record_assumption",
3416
+ assumption: parseAssumptionNote(record.assumption, reader)
3417
+ })
3418
+ },
3419
+ set_next_actions: {
3420
+ buildSchema: () => operationVariant(["type", "nextActions"], {
3421
+ type: constString("set_next_actions"),
3422
+ nextActions: nextActionsSchema()
3423
+ }),
3424
+ parse: (record, reader) => ({
3425
+ type: "set_next_actions",
3426
+ nextActions: parseArray(record.nextActions, parseNextAction, "nextActions", reader)
3427
+ })
3428
+ },
3429
+ add_candidate: {
3430
+ buildSchema: () => operationVariant(["type", "candidate"], {
3431
+ type: constString("add_candidate"),
3432
+ candidate: candidateSchema()
3433
+ }),
3434
+ parse: (record, reader) => ({
3435
+ type: "add_candidate",
3436
+ candidate: parseCandidate(record.candidate, reader)
3437
+ })
3438
+ }
3439
+ };
3440
+ var MODEL_VISIBLE_OPERATION_SCHEMAS = Object.keys(MODEL_VISIBLE_OPERATIONS).map(
3441
+ (operationType) => MODEL_VISIBLE_OPERATIONS[operationType].buildSchema()
3442
+ );
3443
+
3444
+ // src/adapters/shared/work-tool-operations.ts
3445
+ var WORK_TOOL_PARAMETERS = {
3446
+ type: "object",
3447
+ additionalProperties: false,
3448
+ properties: {
3449
+ action: {
3450
+ type: "string",
3451
+ enum: [...MODEL_VISIBLE_WORK_ACTIONS],
3452
+ description: "Working-memory action to run. Close is reserved for /goal clear and host lifecycle paths."
3453
+ },
3454
+ workingSetId: {
3455
+ type: "string",
3456
+ description: "Explicit working-set id when known."
3457
+ },
3458
+ scope: {
3459
+ type: "object",
3460
+ description: "Working-memory scope facts. Session-scoped goals require conversationKey (or taskId / gitRoot+gitBranch / gitRoot+cwd). sessionKey and scopeKey are not accepted.",
3461
+ additionalProperties: false,
3462
+ properties: {
3463
+ sessionId: {
3464
+ type: "string",
3465
+ description: "Host session id stored as provenance; does not select scope by itself."
3466
+ },
3467
+ conversationKey: {
3468
+ type: "string",
3469
+ description: "Primary session/thread identity for goal binding (preferred for Skeln sessions)."
3470
+ },
3471
+ gitRoot: { type: "string" },
3472
+ gitBranch: { type: "string" },
3473
+ cwd: { type: "string" },
3474
+ project: { type: "string" },
3475
+ taskId: { type: "string" }
3476
+ }
3477
+ },
3478
+ operation: createOperationSchema(MODEL_VISIBLE_OPERATION_SCHEMAS),
3479
+ expectedRevision: {
3480
+ type: "integer",
3481
+ minimum: 0,
3482
+ description: "Required for update. Revision observed before update."
3483
+ },
3484
+ updateReason: {
3485
+ type: "string",
3486
+ description: "Human-readable audit reason for create or update."
3487
+ },
3488
+ includeEvents: {
3489
+ type: "boolean",
3490
+ description: "Include recent events in get output."
3491
+ },
3492
+ eventLimit: {
3493
+ type: "integer",
3494
+ minimum: 1,
3495
+ maximum: 200,
3496
+ description: "Maximum events for get output."
3497
+ },
3498
+ listLimit: {
3499
+ type: "integer",
3500
+ minimum: 1,
3501
+ maximum: 100,
3502
+ description: "Maximum working sets for list output."
3503
+ }
3504
+ },
3505
+ required: ["action"]
3506
+ };
3507
+ function parseWorkToolParams(rawParams, defaultScope, reader) {
3508
+ const params = asRecord(rawParams);
3509
+ const action = parseAction(reader.readString(params, "action", { required: true }));
3510
+ return {
3511
+ action,
3512
+ ...optionalStringParam(params, "workingSetId", reader),
3513
+ scope: mergeWorkingScope(defaultScope, parseScope(params.scope, reader)),
3514
+ ...params.operation !== void 0 ? { operation: parseOperation(params.operation, reader) } : {},
3515
+ ...optionalNumberParam(params, "expectedRevision", reader),
3516
+ ...optionalStringParam(params, "updateReason", reader),
3517
+ ...optionalBooleanParam(params, "includeEvents"),
3518
+ ...optionalNumberParam(params, "eventLimit", reader),
3519
+ ...optionalNumberParam(params, "listLimit", reader),
3520
+ actor: "model",
3521
+ source: "tool"
3522
+ };
3523
+ }
3524
+ function mergeWorkingScope(defaults, overrides) {
3525
+ return {
3526
+ ...defaults,
3527
+ ...overrides
3528
+ };
3529
+ }
3530
+ function parseAction(value) {
3531
+ if (isModelVisibleWorkAction(value)) {
3532
+ return value;
3533
+ }
3534
+ throw new Error(`Unsupported agenr_work action "${value ?? ""}".`);
3535
+ }
3536
+ function parseScope(value, reader) {
3537
+ if (value === void 0 || value === null) {
3538
+ return {};
3539
+ }
3540
+ const record = asRecord(value);
3541
+ return {
3542
+ ...optionalStringParam(record, "sessionId", reader),
3543
+ ...optionalStringParam(record, "gitRoot", reader),
3544
+ ...optionalStringParam(record, "gitBranch", reader),
3545
+ ...optionalStringParam(record, "cwd", reader),
3546
+ ...optionalStringParam(record, "project", reader),
3547
+ ...optionalStringParam(record, "taskId", reader),
3548
+ ...optionalStringParam(record, "conversationKey", reader)
3549
+ };
3550
+ }
3551
+ function parseOperation(value, reader) {
3552
+ const record = asRecord(value);
3553
+ const type = reader.readString(record, "type", { required: true });
3554
+ if (type && isModelVisibleOperationType(type)) {
3555
+ return MODEL_VISIBLE_OPERATIONS[type].parse(record, reader);
3556
+ }
3557
+ throw new Error(`Unsupported agenr_work operation "${type ?? ""}".`);
3558
+ }
3559
+
3560
+ // src/adapters/shared/work-tools.ts
3561
+ async function runWorkMemoryTool(params, workingMemory) {
3562
+ const reserved = getModelReservedWorkAction(params);
3563
+ if (reserved) {
3564
+ return {
3565
+ text: RESERVED_MODEL_WORK_MESSAGES[reserved],
3566
+ details: {
3567
+ status: "failed",
3568
+ code: reserved === "close" ? "reserved_close" : "reserved_status"
3569
+ },
3570
+ failed: true
3571
+ };
3572
+ }
3573
+ const result = await workingMemory.run(params);
3574
+ return workingMemoryResultToToolOutcome(result);
3575
+ }
3576
+ function workingMemoryResultToToolOutcome(result) {
3577
+ return {
3578
+ text: formatWorkingMemoryResultText(result),
3579
+ details: buildWorkingMemoryDetails(result),
3580
+ failed: !result.ok
3581
+ };
3582
+ }
3583
+
3584
+ // src/adapters/skeln/tools/work.ts
3585
+ function registerAgenrSkelnWorkTool(skeln, servicesPromise, resolveScope) {
3586
+ skeln.registerTool({
3587
+ name: "agenr_work",
3588
+ label: "Agenr Work",
3589
+ description: "Read or update transient working memory for the active task. Use this for task state, checkpoints, next actions, files, commands, blockers, and close handoff candidates, not durable facts.",
3590
+ promptSnippet: "Use agenr_work to keep active task state current without storing transient work in durable memory.",
3591
+ promptGuidelines: [
3592
+ "Use create with set_objective to start a new scoped working set only when no goal exists for the scope, including completed goals awaiting user clear.",
3593
+ "Use update only for material task-state changes, and always pass expectedRevision plus updateReason.",
3594
+ "Use merge_checkpoint before pausing, compacting, handing off, forking, or waiting.",
3595
+ "Do not call close; only the user clears goals with /goal clear.",
3596
+ "After delivering results, leave the working set open and record progress with merge_checkpoint.",
3597
+ "Do not use agenr_store for transient WIP."
3598
+ ],
3599
+ parameters: toolSchema(WORK_TOOL_PARAMETERS),
3600
+ execute: async (_toolCallId, rawParams, _signal, _onUpdate, context) => {
3601
+ try {
3602
+ const [services, scope] = await Promise.all([servicesPromise, resolveScope(context)]);
3603
+ const params = parseWorkToolParams(rawParams, toWorkingScopeFromSkelnSession(scope), SKELN_PARAM_READER);
3604
+ const outcome = await runWorkMemoryTool(params, services.workingMemory);
3605
+ return textToolResult(outcome.text, outcome.details);
3606
+ } catch (error) {
3607
+ return toolFailureResult(error);
3608
+ }
3609
+ }
3610
+ });
3611
+ }
3612
+
3613
+ // src/adapters/skeln/tools/index.ts
3614
+ function registerAgenrSkelnTools(skeln, servicesPromise, resolveScope) {
3615
+ registerAgenrSkelnStoreTool(skeln, servicesPromise, resolveScope);
3616
+ registerAgenrSkelnRecallTool(skeln, servicesPromise, resolveScope);
3617
+ registerAgenrSkelnUpdateTool(skeln, servicesPromise, resolveScope);
3618
+ registerAgenrSkelnWorkTool(skeln, servicesPromise, resolveScope);
3619
+ registerAgenrSkelnGoalAliasTools(skeln, servicesPromise, resolveScope);
3620
+ }
3621
+
3622
+ // src/adapters/skeln/episode/goal-close-episode.ts
3623
+ var SKELN_GOAL_CLOSE_EPISODE_GENERATOR_VERSION = "skeln-goal-close-episodic-v1";
3624
+ var SKELN_GOAL_CLOSE_EPISODE_ACTIVITY_THRESHOLD = {
3625
+ minMaterialTurns: 2,
3626
+ minDurationMs: 0
3627
+ };
3628
+ function scheduleSkelnGoalCloseEpisodePromotion(params) {
3629
+ const pendingEpisodic = params.closeResult.candidates.some((candidate) => candidate.kind === "episodic" && candidate.promotionStatus === "pending");
3630
+ if (!pendingEpisodic) {
3631
+ return;
3632
+ }
3633
+ void writeSkelnGoalCloseEpisode({
3634
+ context: params.context,
3635
+ services: params.services,
3636
+ workingSetId: params.closeResult.workingSet.id,
3637
+ logger: params.logger
3638
+ }).catch((error) => {
3639
+ const logger = params.logger ?? console;
3640
+ logger.warn(`[agenr] skeln goal close episode promotion failed: ${error instanceof Error ? error.message : String(error)}`);
3641
+ });
3642
+ }
3643
+ async function writeSkelnGoalCloseEpisode(params) {
3644
+ const sessionId = String(params.context.sessionManager.getSessionId());
3645
+ await writeSkelnBoundedSessionEpisode({
3646
+ context: params.context,
3647
+ services: params.services,
3648
+ logger: params.logger,
3649
+ actionLabel: "skeln goal close episode promotion",
3650
+ genVersion: SKELN_GOAL_CLOSE_EPISODE_GENERATOR_VERSION,
3651
+ activityThreshold: SKELN_GOAL_CLOSE_EPISODE_ACTIVITY_THRESHOLD,
3652
+ buildSourceRef: (sessionFile) => `${sessionFile}#working_set:${params.workingSetId}`,
3653
+ logContext: `session=${sessionId} workingSet=${params.workingSetId}`,
3654
+ skipDetails: `session=${sessionId} workingSet=${params.workingSetId}`
3655
+ });
3656
+ }
3657
+
3658
+ // src/adapters/skeln/work-command.ts
3659
+ async function executeAgenrSkelnWorkCommand(servicesPromise, resolveScope, context, params) {
3660
+ const [services, scope] = await Promise.all([servicesPromise, resolveScope(context)]);
3661
+ const workingScope = {
3662
+ ...toWorkingScopeFromSkelnSession(scope),
3663
+ ...params.scope ?? {}
3664
+ };
3665
+ const result = params.action === "prepare_external_goal_mutation" ? await services.workingMemory.prepareExternalGoalMutation({
3666
+ ...params,
3667
+ scope: workingScope
3668
+ }) : await services.workingMemory.run({
3669
+ ...toAgenrWorkParams(params),
3670
+ scope: workingScope
3671
+ });
3672
+ if (!result.ok) {
3673
+ return workingMemoryResultToToolOutcome(result);
3674
+ }
3675
+ if (params.action === "close" && params.createEpisode && result.action === "close") {
3676
+ scheduleSkelnGoalCloseEpisodePromotion({
3677
+ context,
3678
+ services,
3679
+ closeResult: result
3680
+ });
3681
+ }
3682
+ return workingMemoryResultToToolOutcome(result);
3683
+ }
3684
+ function toAgenrWorkParams(params) {
3685
+ switch (params.action) {
3686
+ case "get":
3687
+ return {
3688
+ action: "get",
3689
+ ...params.workingSetId !== void 0 ? { workingSetId: params.workingSetId } : {},
3690
+ ...params.includeEvents !== void 0 ? { includeEvents: params.includeEvents } : {},
3691
+ ...params.eventLimit !== void 0 ? { eventLimit: params.eventLimit } : {}
3692
+ };
3693
+ case "list":
3694
+ return {
3695
+ action: "list",
3696
+ ...params.listLimit !== void 0 ? { listLimit: params.listLimit } : {}
3697
+ };
3698
+ case "create":
3699
+ return {
3700
+ action: "create",
3701
+ operation: params.operation,
3702
+ updateReason: params.updateReason,
3703
+ source: params.source,
3704
+ ...params.actor !== void 0 ? { actor: params.actor } : {},
3705
+ ...params.initialBudget !== void 0 ? { initialBudget: params.initialBudget } : {},
3706
+ ...params.continuationPolicy !== void 0 ? { continuationPolicy: params.continuationPolicy } : {}
3707
+ };
3708
+ case "update":
3709
+ return {
3710
+ action: "update",
3711
+ operation: params.operation,
3712
+ updateReason: params.updateReason,
3713
+ source: params.source,
3714
+ ...params.expectedRevision !== void 0 ? { expectedRevision: params.expectedRevision } : {},
3715
+ ...params.workingSetId !== void 0 ? { workingSetId: params.workingSetId } : {},
3716
+ ...params.actor !== void 0 ? { actor: params.actor } : {}
3717
+ };
3718
+ case "close":
3719
+ return {
3720
+ action: "close",
3721
+ closeReason: params.closeReason,
3722
+ source: params.source,
3723
+ ...params.expectedRevision !== void 0 ? { expectedRevision: params.expectedRevision } : {},
3724
+ ...params.workingSetId !== void 0 ? { workingSetId: params.workingSetId } : {},
3725
+ ...params.actor !== void 0 ? { actor: params.actor } : {},
3726
+ ...params.closeMode !== void 0 ? { closeMode: params.closeMode } : {},
3727
+ ...params.createEpisode !== void 0 ? { createEpisode: params.createEpisode } : {}
3728
+ };
3729
+ case "prepare_external_goal_mutation":
3730
+ throw new Error("prepare_external_goal_mutation must be executed through prepareExternalGoalMutation.");
3731
+ }
3732
+ }
3733
+
3734
+ // src/adapters/skeln/index.ts
3735
+ function registerAgenrSkelnMemory(skeln, options = {}) {
3736
+ const memoryPolicySetting = readSkelnMemoryPolicySetting(skeln);
3737
+ if (!memoryPolicySetting.ok) {
3738
+ console.warn(`[agenr] invalid memoryPolicy setting: ${memoryPolicySetting.error}`);
3739
+ }
3740
+ const config = {
3741
+ dbPath: options.dbPath ?? readStringSetting(skeln, "dbPath"),
3742
+ configPath: options.configPath ?? readStringSetting(skeln, "configPath"),
3743
+ memoryPolicy: mergeSkelnMemoryPolicy(memoryPolicySetting.ok ? memoryPolicySetting.value : void 0, options.memoryPolicy),
3744
+ ...options.featureFlags ? { featureFlags: options.featureFlags } : {}
3745
+ };
3746
+ const servicesPromise = createAgenrSkelnServices(config);
3747
+ const scopeTracker = createSkelnSessionScopeTracker();
3748
+ const sessionStartTracker = createSessionStartTracker();
3749
+ const lifecycle = skeln;
3750
+ const resolveScope = async (context) => resolveCurrentSkelnSessionScope(context, scopeTracker, options);
3751
+ void servicesPromise.catch((error) => {
3752
+ console.error(`[agenr] startup failed: ${formatErrorMessage(error)}`);
3753
+ });
3754
+ registerAgenrSkelnTools(skeln, servicesPromise, resolveScope);
3755
+ registerAgenrSkelnFailureBoundary(lifecycle);
3756
+ registerAgenrSkelnInjectionHooks(lifecycle, scopeTracker, servicesPromise, sessionStartTracker, resolveScope);
3757
+ registerAgenrSkelnSessionMemoryHooks(lifecycle, scopeTracker, servicesPromise, resolveScope);
3758
+ registerAgenrSkelnSubagentFindingHooks(lifecycle, servicesPromise, resolveScope);
3759
+ return {
3760
+ executeWorkCommand: (context, params) => executeAgenrSkelnWorkCommand(servicesPromise, resolveScope, context, params)
3761
+ };
3762
+ }
3763
+ function extension(skeln) {
3764
+ registerAgenrSkelnMemory(skeln);
3765
+ }
3766
+ function registerAgenrSkelnInjectionHooks(skeln, scopeTracker, servicesPromise, sessionStartTracker, resolveScope) {
3767
+ skeln.on("session_start", async (event, context) => {
3768
+ try {
3769
+ const scope = await resolveScope(context);
3770
+ rememberSkelnSessionStart(scopeTracker, sessionStartTracker, scope, event.previousSessionFile);
3771
+ const services = await servicesPromise;
3772
+ logSessionMemoryTriggerResult(await services.routeSessionMemoryTrigger(buildSkelnSessionStartTriggerEvent(scope, event)));
3773
+ } catch (error) {
3774
+ console.warn(`[agenr] session_start scope failed: ${formatErrorMessage(error)}`);
3775
+ }
3776
+ });
3777
+ skeln.on(
3778
+ "before_agent_start",
3779
+ async (event, context) => handleAgenrSkelnBeforeAgentStart(event, context, {
3780
+ servicesPromise,
3781
+ sessionStartTracker,
3782
+ resolveScope
3783
+ })
3784
+ );
3785
+ }
3786
+ function registerAgenrSkelnSessionMemoryHooks(skeln, scopeTracker, servicesPromise, resolveScope) {
3787
+ skeln.on("session_before_fork", async (event, context) => {
3788
+ await routeScopedSessionMemoryTrigger(servicesPromise, resolveScope, context, (scope) => buildSkelnSessionBeforeForkTriggerEvent(scope, event));
3789
+ });
3790
+ skeln.on("session_before_compact", async (_event, context) => {
3791
+ await routeScopedSessionMemoryTrigger(servicesPromise, resolveScope, context, (scope) => buildSkelnSessionBeforeCompactTriggerEvent(scope));
3792
+ });
3793
+ skeln.on("session_compact", async (event, context) => {
3794
+ await routeScopedSessionMemoryTrigger(servicesPromise, resolveScope, context, (scope) => buildSkelnSessionCompactTriggerEvent(scope, event));
3795
+ });
3796
+ skeln.on("session_before_tree", async (event, context) => {
3797
+ await routeScopedSessionMemoryTrigger(servicesPromise, resolveScope, context, (scope) => buildSkelnSessionBeforeTreeTriggerEvent(scope, event));
3798
+ });
3799
+ skeln.on("session_tree", async (event, context) => {
3800
+ await routeScopedSessionMemoryTrigger(servicesPromise, resolveScope, context, (scope) => buildSkelnSessionTreeTriggerEvent(scope, event));
3801
+ });
3802
+ skeln.on("session_shutdown", async (event, context) => {
3803
+ await handleSkelnSessionShutdown(servicesPromise, scopeTracker, resolveScope, event, context);
3804
+ });
3805
+ }
3806
+ async function handleSkelnSessionShutdown(servicesPromise, scopeTracker, resolveScope, event, context) {
3807
+ await routeScopedSessionMemoryTrigger(servicesPromise, resolveScope, context, (scope) => buildSkelnSessionShutdownTriggerEvent(scope, event));
3808
+ await writeScopedSkelnShutdownEpisode(servicesPromise, context);
3809
+ clearTrackedSkelnScope(scopeTracker, context);
3810
+ if (event.reason !== "quit") {
3811
+ return;
3812
+ }
3813
+ try {
3814
+ const services = await servicesPromise;
3815
+ await services.close();
3816
+ } catch {
3817
+ }
3818
+ }
3819
+ function registerAgenrSkelnSubagentFindingHooks(skeln, servicesPromise, resolveScope) {
3820
+ skeln.on("tool_result", async (event, context) => {
3821
+ await recordSkelnSubagentFindings(servicesPromise, resolveScope, context, event);
3822
+ return void 0;
3823
+ });
3824
+ }
3825
+ async function routeScopedSessionMemoryTrigger(servicesPromise, resolveScope, context, buildEvent) {
3826
+ try {
3827
+ const scope = await resolveScope(context);
3828
+ const services = await servicesPromise;
3829
+ logSessionMemoryTriggerResult(await services.routeSessionMemoryTrigger(buildEvent(scope)));
3830
+ } catch (error) {
3831
+ console.warn(`[agenr] session-memory trigger failed: ${formatErrorMessage(error)}`);
3832
+ }
3833
+ }
3834
+ async function writeScopedSkelnShutdownEpisode(servicesPromise, context) {
3835
+ try {
3836
+ const services = await servicesPromise;
3837
+ if (!services.capabilities.shutdownEpisodes) {
3838
+ return;
3839
+ }
3840
+ await writeSkelnShutdownEpisode({ context, services });
3841
+ } catch (error) {
3842
+ console.warn(`[agenr] skeln shutdown episode failed: ${formatErrorMessage(error)}`);
3843
+ }
3844
+ }
3845
+ async function resolveCurrentSkelnSessionScope(context, scopeTracker, options) {
3846
+ const sessionId = String(context.sessionManager.getSessionId());
3847
+ const tracked = scopeTracker.getSessionScope(sessionId);
3848
+ const defaults = tracked ? {
3849
+ cwd: tracked.cwd,
3850
+ sessionKey: tracked.sessionKey,
3851
+ ...tracked.gitRoot ? { gitRoot: tracked.gitRoot } : {},
3852
+ ...tracked.gitBranch ? { gitBranch: tracked.gitBranch } : {},
3853
+ ...tracked.project ? { project: tracked.project } : {},
3854
+ ...tracked.conversationKey ? { conversationKey: tracked.conversationKey } : {}
3855
+ } : buildSkelnHostContext({
3856
+ sessionId,
3857
+ cwd: resolveContextCwd(context)
3858
+ });
3859
+ const override = options.getHostContext ? await options.getHostContext(context) : void 0;
3860
+ return toSkelnSessionScope(mergeSkelnHostContext(defaults, override), sessionId);
3861
+ }
3862
+ function rememberSkelnSessionStart(scopeTracker, sessionStartTracker, scope, previousSessionFile) {
3863
+ scopeTracker.rememberSessionStart(scope);
3864
+ sessionStartTracker.rememberSessionStart(scope.sessionId, scope.sessionKey, previousSessionFile);
3865
+ }
3866
+ function resolveContextCwd(context) {
3867
+ return context.cwd || context.sessionManager.getCwd();
3868
+ }
3869
+ function clearTrackedSkelnScope(scopeTracker, context) {
3870
+ try {
3871
+ const sessionId = String(context.sessionManager.getSessionId());
3872
+ scopeTracker.clear(sessionId);
3873
+ } catch {
3874
+ }
3875
+ }
3876
+ function readStringSetting(skeln, key) {
3877
+ const value = skeln.getSetting(key);
3878
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
3879
+ }
3880
+ function registerAgenrSkelnFailureBoundary(skeln) {
3881
+ skeln.on("tool_result", (event) => {
3882
+ if (event.isError || !isAgenrToolName(event.toolName) || !isFailedAgenrToolDetails(event.details)) {
3883
+ return void 0;
3884
+ }
3885
+ return { isError: true };
3886
+ });
3887
+ }
3888
+ function isAgenrToolName(toolName) {
3889
+ return toolName === "agenr_store" || toolName === "agenr_recall" || toolName === "agenr_update" || toolName === "agenr_work" || toolName === "get_goal" || toolName === "create_goal" || toolName === "update_goal";
3890
+ }
3891
+ function isFailedAgenrToolDetails(details) {
3892
+ return typeof details === "object" && details !== null && "status" in details && details.status === "failed";
3893
+ }
3894
+ export {
3895
+ createAgenrSkelnServices,
3896
+ extension as default,
3897
+ handleAgenrSkelnBeforeAgentStart,
3898
+ registerAgenrSkelnMemory
3899
+ };