agenr 3.2.0 → 2026.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +104 -3
- package/README.md +40 -42
- package/dist/adapters/skeln/index.d.ts +629 -51
- package/dist/adapters/skeln/index.js +782 -2039
- package/dist/build-before-turn-artifact-NPUHVWFE.js +71 -0
- package/dist/build-recall-artifact-F3LS3PZX.js +62 -0
- package/dist/chunk-5AYIXQRF.js +4452 -0
- package/dist/{chunk-FMQTRTWE.js → chunk-5TIP2EPP.js} +1453 -2563
- package/dist/{chunk-5LADPJ4C.js → chunk-GAERET5Q.js} +138 -504
- package/dist/chunk-GF3PX3VM.js +41 -0
- package/dist/chunk-GKZQ5AG5.js +44 -0
- package/dist/chunk-LDJN7CVU.js +3231 -0
- package/dist/{chunk-ZYADFKX3.js → chunk-MC3C2XM5.js} +34 -1
- package/dist/{chunk-KL6X2E3I.js → chunk-NBS62ES5.js} +1168 -816
- package/dist/chunk-NSLTJBUC.js +270 -0
- package/dist/chunk-OJSIZDZD.js +9 -0
- package/dist/chunk-OWGQWQUP.js +45 -0
- package/dist/{chunk-ZAX3YSTU.js → chunk-Q5UTJXHZ.js} +2 -140
- package/dist/{chunk-TMDNFBBC.js → chunk-SOQW7356.js} +271 -1935
- package/dist/chunk-VBPYU7GO.js +597 -0
- package/dist/chunk-VTHBPXDQ.js +1750 -0
- package/dist/{chunk-UEGURBBW.js → chunk-XFJ4S4G2.js} +844 -39
- package/dist/chunk-Y5NB3FTH.js +106 -0
- package/dist/{chunk-6HY5F5FE.js → chunk-ZX55JBV2.js} +1704 -314
- package/dist/cli.js +1125 -9537
- package/dist/core/recall/index.d.ts +64 -6
- package/dist/core/recall/index.js +8 -1
- package/dist/internal-eval-server.js +9 -6
- package/dist/internal-recall-eval-server.js +9 -6
- package/dist/lifecycle-checkpoint-IAC5FCQU.js +154 -0
- package/dist/{ports-CpzWESmZ.d.ts → ports-C4QkwDBS.d.ts} +114 -69
- package/dist/scan-6JKPOQHD.js +6 -0
- package/dist/service-EKFACEN6.js +15 -0
- package/dist/service-RHNB5AEQ.js +861 -0
- package/dist/sink-AUAAWC5O.js +8 -0
- package/package.json +7 -4
- package/dist/adapters/openclaw/index.d.ts +0 -32
- package/dist/adapters/openclaw/index.js +0 -3112
- /package/dist/{chunk-GELCEVFA.js → chunk-5AXMFBHR.js} +0 -0
|
@@ -1,70 +1,95 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
readGoalGeneration,
|
|
3
|
+
shouldInjectWorkingContext,
|
|
4
|
+
toWorkingContextAuditPointer
|
|
5
|
+
} from "../../chunk-OWGQWQUP.js";
|
|
6
|
+
import {
|
|
3
7
|
EPISODE_SUMMARY_TIMEOUT_MS,
|
|
4
8
|
FETCH_TOOL_PARAMETERS,
|
|
5
9
|
RECALL_TOOL_PARAMETERS,
|
|
6
|
-
SESSION_LINEAGE_REASONS,
|
|
7
10
|
STORE_TOOL_PARAMETERS,
|
|
8
11
|
UPDATE_TOOL_PARAMETERS,
|
|
9
|
-
|
|
12
|
+
buildCompactionSourceRef,
|
|
10
13
|
buildRecallToolServices,
|
|
14
|
+
buildSessionFileSourceRef,
|
|
11
15
|
composeHostPluginServices,
|
|
12
16
|
createClaimExtractionFromAgenrConfig,
|
|
17
|
+
createCompactionPromptTracker,
|
|
13
18
|
createDeadlineAwareEpisodeSummaryLlm,
|
|
19
|
+
createHostMemoryServices,
|
|
20
|
+
createSessionLifecycleIntakeTracker,
|
|
21
|
+
createSessionMemoryTriggerRouter,
|
|
14
22
|
createSessionStartTracker,
|
|
15
23
|
embedEpisodeSummaryWithinBudget,
|
|
16
24
|
extractRecentTurnsFromMessages,
|
|
17
25
|
formatAgenrSessionStartRecall,
|
|
18
26
|
formatUnifiedRecallResults,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
isTrustedHostOnlyWorkingOperation,
|
|
24
|
-
normalizeEventLimit,
|
|
25
|
-
normalizeListLimit,
|
|
26
|
-
normalizeOptionalString,
|
|
27
|
+
isPluginEpisodeWriteEnabled,
|
|
28
|
+
logSessionMemoryTriggerResult,
|
|
29
|
+
maybeRunLightDream,
|
|
30
|
+
mergeInjectionContent,
|
|
27
31
|
normalizePluginInjectionMemoryPolicyConfig,
|
|
28
32
|
normalizePromptText,
|
|
29
33
|
parseFetchToolParams,
|
|
30
34
|
parseRecallToolParams,
|
|
31
35
|
parseStoreToolParams,
|
|
32
36
|
parseUpdateToolParams,
|
|
37
|
+
resolveAgenrFeatureFlags,
|
|
33
38
|
resolveBeforeTurnPolicy,
|
|
39
|
+
resolveCompactionPromptContext,
|
|
34
40
|
resolveSessionIdentityKey,
|
|
35
41
|
resolveSessionStartPolicy,
|
|
36
42
|
resolveWorkingContextGate,
|
|
43
|
+
routeSessionMemoryTriggerSafely,
|
|
37
44
|
runFetchMemoryTool,
|
|
38
45
|
runRecallMemoryTool,
|
|
39
|
-
runSessionStart,
|
|
40
46
|
runStoreMemoryTool,
|
|
41
47
|
runUpdateMemoryTool,
|
|
48
|
+
scheduleGuardedEpisodeWrite,
|
|
42
49
|
writeBoundedSingleTranscriptEpisode
|
|
43
|
-
} from "../../chunk-
|
|
50
|
+
} from "../../chunk-NBS62ES5.js";
|
|
51
|
+
import "../../chunk-OJSIZDZD.js";
|
|
52
|
+
import {
|
|
53
|
+
WORKING_CANDIDATE_PROMOTION_STATUSES,
|
|
54
|
+
isModelVisibleOperationType
|
|
55
|
+
} from "../../chunk-NSLTJBUC.js";
|
|
44
56
|
import {
|
|
57
|
+
MEMORY_DOCTRINE,
|
|
58
|
+
MEMORY_RECALL_SECTION_HEADER,
|
|
45
59
|
asRecord,
|
|
60
|
+
buildSkelnRecallToolDescription,
|
|
61
|
+
buildSkelnRecallToolGuidelines,
|
|
62
|
+
buildSkelnStoreToolDescription,
|
|
63
|
+
buildStoreToolGuidelines,
|
|
64
|
+
buildUpdateToolDescription,
|
|
65
|
+
buildUpdateToolGuidelines,
|
|
46
66
|
createSingleTranscriptDiscoveryPort,
|
|
47
67
|
formatErrorMessage,
|
|
48
68
|
formatTargetSelector,
|
|
49
69
|
sanitizeFetchToolParams,
|
|
50
70
|
sanitizeUpdateToolParams
|
|
51
|
-
} from "../../chunk-
|
|
71
|
+
} from "../../chunk-LDJN7CVU.js";
|
|
52
72
|
import {
|
|
53
73
|
formatAgenrBeforeTurnRecall,
|
|
54
|
-
runBeforeTurn
|
|
55
|
-
|
|
74
|
+
runBeforeTurn,
|
|
75
|
+
runSessionStart
|
|
76
|
+
} from "../../chunk-XFJ4S4G2.js";
|
|
56
77
|
import {
|
|
57
|
-
AGENR_FEATURE_FLAG_KEYS,
|
|
58
|
-
DEFAULT_AGENR_FEATURE_FLAGS,
|
|
59
78
|
buildRecallToolDetails,
|
|
60
79
|
createLlmClient,
|
|
61
|
-
isRecord,
|
|
62
|
-
readOptionalFiniteNumber,
|
|
63
|
-
readOptionalTrimmedString,
|
|
64
80
|
resolveLlmApiKey,
|
|
65
81
|
resolveModel
|
|
66
|
-
} from "../../chunk-
|
|
67
|
-
import "../../chunk-
|
|
82
|
+
} from "../../chunk-5TIP2EPP.js";
|
|
83
|
+
import "../../chunk-GAERET5Q.js";
|
|
84
|
+
import {
|
|
85
|
+
withEpisodeWriteGuard
|
|
86
|
+
} from "../../chunk-SOQW7356.js";
|
|
87
|
+
import {
|
|
88
|
+
isRecord,
|
|
89
|
+
readOptionalFiniteNumber,
|
|
90
|
+
readOptionalTrimmedString
|
|
91
|
+
} from "../../chunk-VTHBPXDQ.js";
|
|
92
|
+
import "../../chunk-VBPYU7GO.js";
|
|
68
93
|
|
|
69
94
|
// src/adapters/skeln/config.ts
|
|
70
95
|
function parseSkelnMemoryPolicyJson(raw) {
|
|
@@ -101,14 +126,16 @@ function mergeSkelnMemoryPolicy(fromSettings, fromOptions) {
|
|
|
101
126
|
const sessionStart = fromSettings.sessionStart || fromOptions.sessionStart ? { ...fromSettings.sessionStart, ...fromOptions.sessionStart } : void 0;
|
|
102
127
|
const beforeTurn = fromSettings.beforeTurn || fromOptions.beforeTurn ? { ...fromSettings.beforeTurn, ...fromOptions.beforeTurn } : void 0;
|
|
103
128
|
const workingContext = fromSettings.workingContext || fromOptions.workingContext ? { ...fromSettings.workingContext, ...fromOptions.workingContext } : void 0;
|
|
104
|
-
|
|
129
|
+
const episodes = fromSettings.episodes || fromOptions.episodes ? { ...fromSettings.episodes, ...fromOptions.episodes } : void 0;
|
|
130
|
+
if (!slotPolicies && !sessionStart && !beforeTurn && !workingContext && !episodes) {
|
|
105
131
|
return void 0;
|
|
106
132
|
}
|
|
107
133
|
return {
|
|
108
134
|
...slotPolicies ? { slotPolicies } : {},
|
|
109
135
|
...sessionStart ? { sessionStart } : {},
|
|
110
136
|
...beforeTurn ? { beforeTurn } : {},
|
|
111
|
-
...workingContext ? { workingContext } : {}
|
|
137
|
+
...workingContext ? { workingContext } : {},
|
|
138
|
+
...episodes ? { episodes } : {}
|
|
112
139
|
};
|
|
113
140
|
}
|
|
114
141
|
function mergeSlotPolicies(fromSettings, fromOptions) {
|
|
@@ -128,183 +155,6 @@ function mergeSlotPolicies(fromSettings, fromOptions) {
|
|
|
128
155
|
return { attributeHeads };
|
|
129
156
|
}
|
|
130
157
|
|
|
131
|
-
// src/app/working-memory/projection-render.ts
|
|
132
|
-
var UTF8_BYTE_LENGTH = new TextEncoder();
|
|
133
|
-
function byteLength(content) {
|
|
134
|
-
return UTF8_BYTE_LENGTH.encode(content).length;
|
|
135
|
-
}
|
|
136
|
-
function createWorkingContextStubProjection(input) {
|
|
137
|
-
const content = [
|
|
138
|
-
"<agenr_work_context>",
|
|
139
|
-
"Working memory is unavailable for this turn.",
|
|
140
|
-
`Reason: ${input.reason}`,
|
|
141
|
-
"Treat this as transient task-state metadata, not durable truth.",
|
|
142
|
-
"</agenr_work_context>"
|
|
143
|
-
].join("\n");
|
|
144
|
-
return {
|
|
145
|
-
kind: "working_set",
|
|
146
|
-
renderMode: "stub",
|
|
147
|
-
content,
|
|
148
|
-
...input.workingSetId ? { workingSetId: input.workingSetId } : {},
|
|
149
|
-
...input.revision !== void 0 ? { revision: input.revision } : {},
|
|
150
|
-
sourceRef: input.sourceRef,
|
|
151
|
-
byteLength: byteLength(content)
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
function createWorkingContextFullProjection(workingSet, sourceRef) {
|
|
155
|
-
const snapshot = workingSet.snapshot;
|
|
156
|
-
const lines = [
|
|
157
|
-
"<agenr_work_context>",
|
|
158
|
-
"This is transient working memory for the current task, not durable truth.",
|
|
159
|
-
"It may be stale or hypothetical. Prefer current filesystem, git, tests, tool output, and the user's latest message for current-state claims.",
|
|
160
|
-
"",
|
|
161
|
-
`Scope: ${escapeText(workingSet.scopeKind)} ${escapeText(workingSet.scopeKey)}`,
|
|
162
|
-
`Working set: ${escapeText(workingSet.id)}`,
|
|
163
|
-
`Revision: ${workingSet.revision}`,
|
|
164
|
-
`Status: ${escapeText(workingSet.status)}`,
|
|
165
|
-
...optionalLine("Objective", snapshot.objective ?? workingSet.title),
|
|
166
|
-
...optionalLine("Summary", snapshot.summary),
|
|
167
|
-
...renderStringList("Current plan", snapshot.currentPlan),
|
|
168
|
-
...renderCheckpoint(snapshot.checkpoint?.summary),
|
|
169
|
-
...renderNextActions(snapshot.nextActions),
|
|
170
|
-
...renderLabeledNotes(
|
|
171
|
-
"Touched files",
|
|
172
|
-
snapshot.files,
|
|
173
|
-
(file) => file.path.trim().length > 0,
|
|
174
|
-
(file) => {
|
|
175
|
-
const note = file.note ? ` - ${escapeText(file.note)}` : "";
|
|
176
|
-
return `${escapeText(file.path)}${note}`;
|
|
177
|
-
}
|
|
178
|
-
),
|
|
179
|
-
...renderLabeledNotes(
|
|
180
|
-
"Recent commands",
|
|
181
|
-
snapshot.commands,
|
|
182
|
-
(command) => command.command.trim().length > 0,
|
|
183
|
-
(command) => {
|
|
184
|
-
const outcome = command.outcome ? ` - ${escapeText(command.outcome)}` : "";
|
|
185
|
-
return `${escapeText(command.command)}${outcome}`;
|
|
186
|
-
}
|
|
187
|
-
),
|
|
188
|
-
...renderLabeledNotes(
|
|
189
|
-
"Decisions",
|
|
190
|
-
snapshot.decisions,
|
|
191
|
-
(decision) => decision.decision.trim().length > 0,
|
|
192
|
-
(decision) => {
|
|
193
|
-
const rationale = decision.rationale ? ` - ${escapeText(decision.rationale)}` : "";
|
|
194
|
-
return `${escapeText(decision.decision)}${rationale}`;
|
|
195
|
-
}
|
|
196
|
-
),
|
|
197
|
-
...renderLabeledNotes(
|
|
198
|
-
"Assumptions",
|
|
199
|
-
snapshot.assumptions,
|
|
200
|
-
(assumption) => assumption.assumption.trim().length > 0,
|
|
201
|
-
(assumption) => {
|
|
202
|
-
const confidence = assumption.confidence ? ` [${assumption.confidence}]` : "";
|
|
203
|
-
const validated = assumption.validated === true ? " (validated)" : assumption.validated === false ? " (unvalidated)" : "";
|
|
204
|
-
return `${escapeText(assumption.assumption)}${confidence}${validated}`;
|
|
205
|
-
}
|
|
206
|
-
),
|
|
207
|
-
...renderStringList("Blockers", snapshot.blockers),
|
|
208
|
-
...renderStringList("Open questions", snapshot.openQuestions),
|
|
209
|
-
...renderCandidates(snapshot.candidates),
|
|
210
|
-
"",
|
|
211
|
-
"Rules:",
|
|
212
|
-
"- Update this working set when material task state changes.",
|
|
213
|
-
"- Leave a checkpoint before pausing, handing off, compacting, forking, or waiting.",
|
|
214
|
-
"- Do not close this working set; only the user clears goals with /goal clear.",
|
|
215
|
-
"- Do not store transient WIP with agenr_store.",
|
|
216
|
-
"- Promote only durable facts, decisions, preferences, or reusable procedures explicitly.",
|
|
217
|
-
"</agenr_work_context>"
|
|
218
|
-
];
|
|
219
|
-
const content = lines.join("\n");
|
|
220
|
-
return {
|
|
221
|
-
kind: "working_set",
|
|
222
|
-
renderMode: "full",
|
|
223
|
-
content,
|
|
224
|
-
workingSetId: workingSet.id,
|
|
225
|
-
revision: workingSet.revision,
|
|
226
|
-
sourceRef,
|
|
227
|
-
byteLength: byteLength(content)
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
function escapeText(value) {
|
|
231
|
-
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
232
|
-
}
|
|
233
|
-
function optionalLine(label, value) {
|
|
234
|
-
const normalized = value?.trim();
|
|
235
|
-
return normalized ? [`${label}: ${escapeText(normalized)}`] : [];
|
|
236
|
-
}
|
|
237
|
-
function renderStringList(title, values) {
|
|
238
|
-
const items = (values ?? []).map((value) => value.trim()).filter((value) => value.length > 0);
|
|
239
|
-
if (items.length === 0) {
|
|
240
|
-
return [];
|
|
241
|
-
}
|
|
242
|
-
return ["", `${title}:`, ...items.map((item) => `- ${escapeText(item)}`)];
|
|
243
|
-
}
|
|
244
|
-
function renderCheckpoint(summary) {
|
|
245
|
-
const normalized = summary?.trim();
|
|
246
|
-
return normalized ? ["", "Last checkpoint:", escapeText(normalized)] : [];
|
|
247
|
-
}
|
|
248
|
-
function renderLabeledNotes(title, items, isValid, formatLine) {
|
|
249
|
-
const filtered = (items ?? []).filter(isValid);
|
|
250
|
-
if (filtered.length === 0) {
|
|
251
|
-
return [];
|
|
252
|
-
}
|
|
253
|
-
return ["", `${title}:`, ...filtered.map((item) => `- ${formatLine(item)}`)];
|
|
254
|
-
}
|
|
255
|
-
function renderNextActions(actions) {
|
|
256
|
-
const items = (actions ?? []).filter((action) => action.text.trim().length > 0);
|
|
257
|
-
if (items.length === 0) {
|
|
258
|
-
return [];
|
|
259
|
-
}
|
|
260
|
-
return [
|
|
261
|
-
"",
|
|
262
|
-
"Next actions:",
|
|
263
|
-
...items.map((action) => {
|
|
264
|
-
const status = action.status ? ` [${action.status}]` : "";
|
|
265
|
-
const ref = action.ref ? ` (${escapeText(action.ref)})` : "";
|
|
266
|
-
return `- ${escapeText(action.text)}${status}${ref}`;
|
|
267
|
-
})
|
|
268
|
-
];
|
|
269
|
-
}
|
|
270
|
-
function renderCandidates(candidates) {
|
|
271
|
-
const pending = (candidates ?? []).filter((candidate) => candidate.promotionStatus === "pending");
|
|
272
|
-
if (pending.length === 0) {
|
|
273
|
-
return [];
|
|
274
|
-
}
|
|
275
|
-
return [
|
|
276
|
-
"",
|
|
277
|
-
"Pending memory candidates:",
|
|
278
|
-
...pending.map((candidate) => {
|
|
279
|
-
if (candidate.kind === "episodic") {
|
|
280
|
-
return `- episodic: ${escapeText(candidate.summary)}`;
|
|
281
|
-
}
|
|
282
|
-
return `- ${candidate.kind}: ${escapeText(candidate.subject)}`;
|
|
283
|
-
})
|
|
284
|
-
];
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// src/app/working-memory/projection.ts
|
|
288
|
-
function createToolSuccessProjection(workingSet, action, timestamp) {
|
|
289
|
-
return createWorkingContextFullProjection(workingSet, `agenr_work:${action}:${timestamp}`);
|
|
290
|
-
}
|
|
291
|
-
function shouldInjectWorkingContext(projection) {
|
|
292
|
-
return projection.renderMode === "full" && projection.content.trim().length > 0;
|
|
293
|
-
}
|
|
294
|
-
function toWorkingContextAuditPointer(projection) {
|
|
295
|
-
if (projection.workingSetId === void 0 || projection.revision === void 0) {
|
|
296
|
-
return void 0;
|
|
297
|
-
}
|
|
298
|
-
return {
|
|
299
|
-
source: "agenr_work",
|
|
300
|
-
workingSetId: projection.workingSetId,
|
|
301
|
-
revision: projection.revision,
|
|
302
|
-
sourceRef: projection.sourceRef,
|
|
303
|
-
bytes: projection.byteLength,
|
|
304
|
-
summary: projection.renderMode === "full" ? `Working set ${projection.workingSetId} rev ${projection.revision}` : `Working memory stub (${projection.sourceRef})`
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
|
|
308
158
|
// src/adapters/skeln/session/scope.ts
|
|
309
159
|
function resolveSkelnSessionKey(sessionId, cwd) {
|
|
310
160
|
const normalizedSessionId = sessionId.trim();
|
|
@@ -427,13 +277,14 @@ function isSkelnMessageEntry(entry) {
|
|
|
427
277
|
// src/adapters/skeln/format/prompt-section.ts
|
|
428
278
|
function buildAgenrSkelnMemoryPromptSection() {
|
|
429
279
|
return [
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
280
|
+
MEMORY_RECALL_SECTION_HEADER,
|
|
281
|
+
MEMORY_DOCTRINE.recall.first,
|
|
282
|
+
MEMORY_DOCTRINE.recall.modes,
|
|
283
|
+
MEMORY_DOCTRINE.recall.truncatedPreviewsWithFetch,
|
|
284
|
+
MEMORY_DOCTRINE.recall.injectedContext,
|
|
285
|
+
MEMORY_DOCTRINE.store.skelnNotLogging,
|
|
286
|
+
MEMORY_DOCTRINE.store.claimKeyPromptLine,
|
|
287
|
+
MEMORY_DOCTRINE.update.vsSupersedes,
|
|
437
288
|
""
|
|
438
289
|
];
|
|
439
290
|
}
|
|
@@ -522,174 +373,276 @@ function traceSystemPromptDoctrineInjected(baseSystemPrompt, updatedSystemPrompt
|
|
|
522
373
|
});
|
|
523
374
|
}
|
|
524
375
|
|
|
525
|
-
// src/adapters/
|
|
526
|
-
|
|
527
|
-
const
|
|
528
|
-
const doctrine = buildAgenrSkelnMemoryPromptSection().join("\n");
|
|
529
|
-
const systemPromptWithDoctrine = appendPromptSection(event.systemPrompt, doctrine);
|
|
530
|
-
const trackerState = deps.sessionStartTracker.consume(scope.sessionId, scope.sessionKey);
|
|
531
|
-
if (trackerState.isFirst) {
|
|
532
|
-
return resolveSessionStartInjection(scope, event.systemPrompt, systemPromptWithDoctrine, deps.servicesPromise);
|
|
533
|
-
}
|
|
534
|
-
return resolveBeforeTurnInjection(event, scope, event.systemPrompt, systemPromptWithDoctrine, context, deps.servicesPromise);
|
|
535
|
-
}
|
|
536
|
-
function buildAgenrSkelnInjectionMessage(content) {
|
|
376
|
+
// src/adapters/shared/agenr-episode-summary-llm.ts
|
|
377
|
+
function createAgenrEpisodeSummaryLlm(provider, modelId, apiKey) {
|
|
378
|
+
const client = createLlmClient(provider, modelId, { apiKey });
|
|
537
379
|
return {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
380
|
+
complete: client.complete,
|
|
381
|
+
completeJson: client.completeJson,
|
|
382
|
+
metadata: {
|
|
383
|
+
modelRef: `${provider}/${modelId}`,
|
|
384
|
+
pricing: {
|
|
385
|
+
input: client.metadata.model.cost?.input ?? 0,
|
|
386
|
+
output: client.metadata.model.cost?.output ?? 0,
|
|
387
|
+
cacheRead: client.metadata.model.cost?.cacheRead ?? 0,
|
|
388
|
+
cacheWrite: client.metadata.model.cost?.cacheWrite ?? 0
|
|
389
|
+
},
|
|
390
|
+
usage: client.metadata.usage
|
|
391
|
+
}
|
|
541
392
|
};
|
|
542
393
|
}
|
|
543
|
-
function appendPromptSection(systemPrompt, section) {
|
|
544
|
-
const trimmedSection = section.trim();
|
|
545
|
-
if (trimmedSection.length === 0 || systemPrompt.includes(trimmedSection)) {
|
|
546
|
-
return systemPrompt;
|
|
547
|
-
}
|
|
548
|
-
return `${systemPrompt.trimEnd()}
|
|
549
394
|
|
|
550
|
-
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
395
|
+
// src/adapters/skeln/transcript/parser.ts
|
|
396
|
+
import { createHash } from "crypto";
|
|
397
|
+
import * as fs from "fs/promises";
|
|
398
|
+
import path from "path";
|
|
399
|
+
var SkelnTranscriptParser = class {
|
|
400
|
+
/**
|
|
401
|
+
* Parses one Skeln session JSONL file into normalized transcript messages.
|
|
402
|
+
*
|
|
403
|
+
* @param filePath - Absolute or relative Skeln session file.
|
|
404
|
+
* @returns Parsed transcript payload consumed by shared episode ingest.
|
|
405
|
+
*/
|
|
406
|
+
async parseFile(filePath) {
|
|
407
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
408
|
+
const transcriptHash = createHash("sha256").update(raw).digest("hex");
|
|
409
|
+
const warnings = [];
|
|
410
|
+
const messages = [];
|
|
411
|
+
const records = parseJsonlRecords(raw, filePath, warnings);
|
|
412
|
+
const header = records[0] ? parseHeaderRecord(records[0], filePath, warnings) : {};
|
|
413
|
+
const sessionId = header.sessionId ?? deriveSkelnSessionIdFromFilePath(filePath);
|
|
414
|
+
const headerTimestamp = toIsoTimestamp(header.timestamp);
|
|
415
|
+
for (const record of records.slice(1)) {
|
|
416
|
+
if (record.type !== "message") {
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
const normalized = normalizeSkelnMessage(record.message);
|
|
420
|
+
if (!normalized) {
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
messages.push({
|
|
424
|
+
index: messages.length,
|
|
425
|
+
role: normalized.role,
|
|
426
|
+
text: normalized.text,
|
|
427
|
+
...normalized.timestamp ? { timestamp: normalized.timestamp } : {}
|
|
564
428
|
});
|
|
565
429
|
}
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
430
|
+
const startedAt = messages[0]?.timestamp ?? headerTimestamp;
|
|
431
|
+
const endedAt = messages.at(-1)?.timestamp;
|
|
432
|
+
return {
|
|
433
|
+
messages,
|
|
434
|
+
metadata: {
|
|
435
|
+
...sessionId ? { sessionId } : {},
|
|
436
|
+
...header.cwd ? { workingDirectory: header.cwd, project: path.basename(header.cwd) } : {},
|
|
437
|
+
...startedAt ? { startedAt } : {},
|
|
438
|
+
...endedAt ? { endedAt } : {},
|
|
439
|
+
messageCount: messages.length,
|
|
440
|
+
transcriptHash,
|
|
441
|
+
reconstructedSurface: "skeln",
|
|
442
|
+
surfaceReconstructionSource: "reconstructed",
|
|
443
|
+
sourceIdentity: path.resolve(filePath),
|
|
444
|
+
sourceIdentityKind: "skeln_session_file"
|
|
570
445
|
},
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
return composeSkelnBeforeAgentStartResult({
|
|
574
|
-
baseSystemPrompt,
|
|
575
|
-
systemPrompt,
|
|
576
|
-
recallKind: "session_start_recall",
|
|
577
|
-
recallText: formatAgenrSessionStartRecall(sessionStartPatch),
|
|
578
|
-
workingInjection
|
|
579
|
-
});
|
|
580
|
-
} catch (error) {
|
|
581
|
-
logInjectionFailure("session-start", scope, error);
|
|
582
|
-
return composeSkelnBeforeAgentStartResult({
|
|
583
|
-
baseSystemPrompt,
|
|
584
|
-
systemPrompt,
|
|
585
|
-
recallKind: "session_start_recall",
|
|
586
|
-
recallFailureReason: error instanceof Error ? error.message : String(error),
|
|
587
|
-
workingInjection
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
async function resolveBeforeTurnInjection(event, scope, baseSystemPrompt, systemPrompt, context, servicesPromise) {
|
|
592
|
-
const services = await servicesPromise;
|
|
593
|
-
const workingInjection = await resolveWorkingContextInjection(services, scope, `skeln:before-turn:${scope.sessionKey}`);
|
|
594
|
-
if (services.skelnConfig.memoryPolicy?.beforeTurn?.enabled === false) {
|
|
595
|
-
return composeSkelnBeforeAgentStartResult({
|
|
596
|
-
baseSystemPrompt,
|
|
597
|
-
systemPrompt,
|
|
598
|
-
recallKind: "before_turn_recall",
|
|
599
|
-
recallSkippedReason: "memoryPolicy.beforeTurn.enabled=false",
|
|
600
|
-
workingInjection
|
|
601
|
-
});
|
|
446
|
+
warnings
|
|
447
|
+
};
|
|
602
448
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
449
|
+
};
|
|
450
|
+
var skelnTranscriptParser = new SkelnTranscriptParser();
|
|
451
|
+
function parseJsonlRecords(raw, filePath, warnings) {
|
|
452
|
+
const records = [];
|
|
453
|
+
const lines = raw.split(/\r?\n/u);
|
|
454
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
455
|
+
const line = lines[index]?.trim();
|
|
456
|
+
if (!line) {
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
try {
|
|
460
|
+
const parsed = JSON.parse(line);
|
|
461
|
+
const record = isRecord(parsed) ? parsed : void 0;
|
|
462
|
+
if (!record) {
|
|
463
|
+
warnings.push(`Skipped non-object Skeln JSONL line ${index + 1} in ${filePath}.`);
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
records.push(record);
|
|
467
|
+
} catch (error) {
|
|
468
|
+
warnings.push(`Skipped malformed Skeln JSONL line ${index + 1} in ${filePath}: ${formatErrorMessage(error)}`);
|
|
469
|
+
}
|
|
612
470
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
const beforeTurnPatch = await runBeforeTurn(
|
|
616
|
-
{
|
|
617
|
-
sessionKey: scope.sessionKey,
|
|
618
|
-
currentTurnText,
|
|
619
|
-
recentTurns: extractRecentTurnsFromMessages(branchMessages),
|
|
620
|
-
policy: resolveBeforeTurnPolicy(services.skelnConfig.memoryPolicy)
|
|
621
|
-
},
|
|
622
|
-
services.beforeTurn
|
|
623
|
-
);
|
|
624
|
-
return composeSkelnBeforeAgentStartResult({
|
|
625
|
-
baseSystemPrompt,
|
|
626
|
-
systemPrompt,
|
|
627
|
-
recallKind: "before_turn_recall",
|
|
628
|
-
recallText: formatAgenrBeforeTurnRecall(beforeTurnPatch),
|
|
629
|
-
workingInjection
|
|
630
|
-
});
|
|
631
|
-
} catch (error) {
|
|
632
|
-
logInjectionFailure("before-turn", scope, error);
|
|
633
|
-
return composeSkelnBeforeAgentStartResult({
|
|
634
|
-
baseSystemPrompt,
|
|
635
|
-
systemPrompt,
|
|
636
|
-
recallKind: "before_turn_recall",
|
|
637
|
-
recallFailureReason: error instanceof Error ? error.message : String(error),
|
|
638
|
-
workingInjection
|
|
639
|
-
});
|
|
471
|
+
if (records.length === 0) {
|
|
472
|
+
warnings.push(`Skipped empty Skeln transcript file: ${filePath}`);
|
|
640
473
|
}
|
|
474
|
+
return records;
|
|
641
475
|
}
|
|
642
|
-
function
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
recallKind: input.recallKind,
|
|
648
|
-
recallText,
|
|
649
|
-
recallSkippedReason: input.recallSkippedReason,
|
|
650
|
-
recallFailureReason: input.recallFailureReason,
|
|
651
|
-
workingContextTrace: input.workingInjection?.trace
|
|
652
|
-
});
|
|
476
|
+
function parseHeaderRecord(record, filePath, warnings) {
|
|
477
|
+
if (!record || record.type !== "session") {
|
|
478
|
+
warnings.push(`Skeln transcript ${filePath} is missing a session header.`);
|
|
479
|
+
return {};
|
|
480
|
+
}
|
|
653
481
|
return {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
...input.workingInjection?.workingContextAudit ? { workingContextAudit: input.workingInjection.workingContextAudit } : {},
|
|
658
|
-
...memoryTrace.length > 0 ? { memoryTrace } : {}
|
|
482
|
+
sessionId: readOptionalTrimmedString(record.id),
|
|
483
|
+
timestamp: readOptionalFiniteNumber(record.timestamp),
|
|
484
|
+
cwd: readOptionalTrimmedString(record.cwd)
|
|
659
485
|
};
|
|
660
486
|
}
|
|
661
|
-
|
|
662
|
-
const
|
|
663
|
-
if (!
|
|
664
|
-
return
|
|
487
|
+
function normalizeSkelnMessage(value) {
|
|
488
|
+
const message = isRecord(value) ? value : void 0;
|
|
489
|
+
if (!message) {
|
|
490
|
+
return void 0;
|
|
665
491
|
}
|
|
666
|
-
|
|
492
|
+
const role = normalizeRole(readOptionalTrimmedString(message.role));
|
|
493
|
+
if (!role) {
|
|
494
|
+
return void 0;
|
|
495
|
+
}
|
|
496
|
+
const text = normalizeMessageText(message.content);
|
|
497
|
+
if (!text) {
|
|
498
|
+
return void 0;
|
|
499
|
+
}
|
|
500
|
+
const timestamp = toIsoTimestamp(readOptionalFiniteNumber(message.timestamp));
|
|
501
|
+
return {
|
|
502
|
+
role,
|
|
503
|
+
text,
|
|
504
|
+
...timestamp ? { timestamp } : {}
|
|
505
|
+
};
|
|
667
506
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
507
|
+
function normalizeRole(role) {
|
|
508
|
+
if (role === "user") {
|
|
509
|
+
return "user";
|
|
510
|
+
}
|
|
511
|
+
if (role === "assistant") {
|
|
512
|
+
return "assistant";
|
|
513
|
+
}
|
|
514
|
+
return void 0;
|
|
515
|
+
}
|
|
516
|
+
function normalizeMessageText(value) {
|
|
517
|
+
if (typeof value === "string") {
|
|
518
|
+
return value.trim();
|
|
519
|
+
}
|
|
520
|
+
if (!Array.isArray(value)) {
|
|
521
|
+
return "";
|
|
522
|
+
}
|
|
523
|
+
const parts = [];
|
|
524
|
+
for (const part of value) {
|
|
525
|
+
if (typeof part === "string") {
|
|
526
|
+
parts.push(part);
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
const record = isRecord(part) ? part : void 0;
|
|
530
|
+
const text = readOptionalTrimmedString(record?.text) ?? readOptionalTrimmedString(record?.content);
|
|
531
|
+
if (text) {
|
|
532
|
+
parts.push(text);
|
|
677
533
|
}
|
|
678
|
-
const workingContextAudit = toWorkingContextAuditPointer(projection);
|
|
679
|
-
return {
|
|
680
|
-
transientMessages: [buildAgenrSkelnInjectionMessage(projection.content)],
|
|
681
|
-
...workingContextAudit ? { workingContextAudit } : {},
|
|
682
|
-
trace: traceWorkingContextInjected(workingContextAudit, projection.content)
|
|
683
|
-
};
|
|
684
|
-
} catch (error) {
|
|
685
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
686
|
-
console.warn(`[agenr] working-context projection failed for session=${scope.sessionId} key=${scope.sessionKey}: ${message}`);
|
|
687
|
-
return { trace: traceMemoryFailed("working_context", message) };
|
|
688
534
|
}
|
|
535
|
+
return parts.map((part) => part.trim()).filter(Boolean).join("\n").trim();
|
|
689
536
|
}
|
|
690
|
-
function
|
|
691
|
-
|
|
692
|
-
|
|
537
|
+
function toIsoTimestamp(timestamp) {
|
|
538
|
+
if (timestamp === void 0) {
|
|
539
|
+
return void 0;
|
|
540
|
+
}
|
|
541
|
+
const date = new Date(timestamp);
|
|
542
|
+
return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
|
|
543
|
+
}
|
|
544
|
+
function deriveSkelnSessionIdFromFilePath(filePath) {
|
|
545
|
+
const basename = path.basename(filePath).replace(/\.jsonl(?:\..*)?$/iu, "").trim();
|
|
546
|
+
return basename.length > 0 ? basename : void 0;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/adapters/skeln/episode/bounded-session-episode.ts
|
|
550
|
+
function resolveSkelnSessionEpisodeTarget(context) {
|
|
551
|
+
return {
|
|
552
|
+
sessionId: String(context.sessionManager.getSessionId()),
|
|
553
|
+
sessionFile: resolveSessionFile(context)
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
async function writeSkelnBoundedSessionEpisode(params) {
|
|
557
|
+
const logger = params.logger ?? console;
|
|
558
|
+
const sessionFile = params.target.sessionFile;
|
|
559
|
+
const sessionId = params.target.sessionId;
|
|
560
|
+
const summaryDeadlineMs = Date.now() + EPISODE_SUMMARY_TIMEOUT_MS;
|
|
561
|
+
if (!sessionFile) {
|
|
562
|
+
logger.info(`[agenr] ${params.actionLabel} skipped for ${params.skipDetails} reason=no_session_file`);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
const episodeModel = resolveModel(params.services.agenrConfig, "episode");
|
|
566
|
+
const llmApiKey = resolveLlmApiKey(params.services.agenrConfig, episodeModel.provider);
|
|
567
|
+
const summaryLlm = createDeadlineAwareEpisodeSummaryLlm(
|
|
568
|
+
createAgenrEpisodeSummaryLlm(episodeModel.provider, episodeModel.modelId, llmApiKey),
|
|
569
|
+
summaryDeadlineMs
|
|
570
|
+
);
|
|
571
|
+
await writeBoundedSingleTranscriptEpisode({
|
|
572
|
+
filePath: sessionFile,
|
|
573
|
+
context: params.logContext,
|
|
574
|
+
actionLabel: params.actionLabel,
|
|
575
|
+
logger,
|
|
576
|
+
summaryDeadlineMs,
|
|
577
|
+
ports: {
|
|
578
|
+
files: createSingleTranscriptDiscoveryPort(sessionFile),
|
|
579
|
+
transcript: skelnTranscriptParser,
|
|
580
|
+
episodes: params.services.episodes,
|
|
581
|
+
createSummaryLlm: () => summaryLlm,
|
|
582
|
+
embedSummary: (summary) => embedEpisodeSummaryWithinBudget({
|
|
583
|
+
summary,
|
|
584
|
+
embedding: params.services.embedding,
|
|
585
|
+
embeddingAvailable: params.services.embeddingStatus.available,
|
|
586
|
+
deadlineMs: summaryDeadlineMs,
|
|
587
|
+
logger,
|
|
588
|
+
logContext: `[agenr] ${params.actionLabel} embedding skipped for ${params.logContext} file=${sessionFile}`
|
|
589
|
+
})
|
|
590
|
+
},
|
|
591
|
+
ingestOptions: {
|
|
592
|
+
source: "skeln",
|
|
593
|
+
genVersion: params.genVersion,
|
|
594
|
+
skipActiveSessionCheck: true,
|
|
595
|
+
...params.activityThreshold ? { activityThreshold: params.activityThreshold } : {},
|
|
596
|
+
candidateOverrides: {
|
|
597
|
+
sessionId: params.sourceSessionId ?? sessionId,
|
|
598
|
+
sourceRef: params.buildSourceRef(sessionFile),
|
|
599
|
+
agentId: null,
|
|
600
|
+
surface: "skeln",
|
|
601
|
+
metadataSource: "reconstructed"
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
function resolveSessionFile(context) {
|
|
607
|
+
try {
|
|
608
|
+
const sessionFile = context.sessionManager.getSessionFile().trim();
|
|
609
|
+
return sessionFile || void 0;
|
|
610
|
+
} catch {
|
|
611
|
+
return void 0;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// src/adapters/skeln/episode/episode-writer.ts
|
|
616
|
+
var SKELN_EPISODE_GENERATOR_VERSION = "skeln-episodic-summary-v1";
|
|
617
|
+
var SKELN_PHASE4_SHUTDOWN_EPISODE_ACTIVITY_THRESHOLD = {
|
|
618
|
+
minMaterialTurns: 8,
|
|
619
|
+
minDurationMs: 20 * 60 * 1e3
|
|
620
|
+
};
|
|
621
|
+
async function writeSkelnShutdownEpisode(params) {
|
|
622
|
+
await writeSkelnBoundedSessionEpisode({
|
|
623
|
+
target: params.target,
|
|
624
|
+
services: params.services,
|
|
625
|
+
logger: params.logger,
|
|
626
|
+
actionLabel: "skeln shutdown episode write",
|
|
627
|
+
genVersion: SKELN_EPISODE_GENERATOR_VERSION,
|
|
628
|
+
activityThreshold: SKELN_PHASE4_SHUTDOWN_EPISODE_ACTIVITY_THRESHOLD,
|
|
629
|
+
buildSourceRef: (sessionFile) => sessionFile,
|
|
630
|
+
logContext: `session=${params.target.sessionId} key=skeln:${params.target.sessionId}`,
|
|
631
|
+
skipDetails: `session=${params.target.sessionId}`
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
async function writeSkelnPreCompactionEpisode(params) {
|
|
635
|
+
await writeSkelnBoundedSessionEpisode({
|
|
636
|
+
target: params.target,
|
|
637
|
+
services: params.services,
|
|
638
|
+
logger: params.logger,
|
|
639
|
+
actionLabel: "skeln pre-compaction episode write",
|
|
640
|
+
genVersion: SKELN_EPISODE_GENERATOR_VERSION,
|
|
641
|
+
buildSourceRef: (sessionFile) => sessionFile,
|
|
642
|
+
sourceSessionId: `${params.target.sessionId}:pre-compaction:${params.messageCount}`,
|
|
643
|
+
logContext: `session=${params.target.sessionId}:pre-compaction:${params.messageCount}`,
|
|
644
|
+
skipDetails: `session=${params.target.sessionId}`
|
|
645
|
+
});
|
|
693
646
|
}
|
|
694
647
|
|
|
695
648
|
// src/adapters/skeln/hooks/session-memory.ts
|
|
@@ -722,10 +675,11 @@ function buildSkelnSessionBeforeForkTriggerEvent(scope, event) {
|
|
|
722
675
|
observedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
723
676
|
};
|
|
724
677
|
}
|
|
725
|
-
function buildSkelnSessionBeforeCompactTriggerEvent(scope) {
|
|
678
|
+
function buildSkelnSessionBeforeCompactTriggerEvent(scope, event = {}) {
|
|
726
679
|
return {
|
|
727
680
|
type: "session_before_compact",
|
|
728
681
|
sessionKey: scope.sessionKey,
|
|
682
|
+
...Object.keys(event).length > 0 ? { payload: event } : {},
|
|
729
683
|
observedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
730
684
|
};
|
|
731
685
|
}
|
|
@@ -781,12 +735,6 @@ function buildSkelnSessionShutdownTriggerEvent(scope, event) {
|
|
|
781
735
|
observedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
782
736
|
};
|
|
783
737
|
}
|
|
784
|
-
function logSessionMemoryTriggerResult(result) {
|
|
785
|
-
if (result.accepted || result.reason === "feature_disabled") {
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
console.warn(`[agenr] session-memory trigger rejected: ${result.reason} (${result.message})`);
|
|
789
|
-
}
|
|
790
738
|
function resolveSessionStartTransitionReason(reason, previousSessionFile) {
|
|
791
739
|
if (isLineageSessionStartReason(reason)) {
|
|
792
740
|
return reason;
|
|
@@ -804,7 +752,7 @@ function buildSessionStartPredecessor(transitionReason, previousSessionFile) {
|
|
|
804
752
|
if (!sourceRef) {
|
|
805
753
|
return void 0;
|
|
806
754
|
}
|
|
807
|
-
return { sourceRef };
|
|
755
|
+
return { sourceRef: buildSessionFileSourceRef(sourceRef) };
|
|
808
756
|
}
|
|
809
757
|
function buildCompactionCheckpointArtifact(sessionKey, compactionEntry, fromExtension) {
|
|
810
758
|
return {
|
|
@@ -812,7 +760,7 @@ function buildCompactionCheckpointArtifact(sessionKey, compactionEntry, fromExte
|
|
|
812
760
|
sessionKey,
|
|
813
761
|
source: "skeln",
|
|
814
762
|
sourceId: compactionEntry.id,
|
|
815
|
-
sourceRef:
|
|
763
|
+
sourceRef: buildCompactionSourceRef(compactionEntry.id),
|
|
816
764
|
summary: compactionEntry.summary,
|
|
817
765
|
metadata: {
|
|
818
766
|
firstKeptEntryId: compactionEntry.firstKeptEntryId,
|
|
@@ -821,1694 +769,437 @@ function buildCompactionCheckpointArtifact(sessionKey, compactionEntry, fromExte
|
|
|
821
769
|
};
|
|
822
770
|
}
|
|
823
771
|
|
|
824
|
-
// src/adapters/skeln/hooks/
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
772
|
+
// src/adapters/skeln/hooks/session-memory-routing.ts
|
|
773
|
+
async function routeSkelnSessionMemoryTrigger(servicesPromise, resolveScope, context, buildEvent) {
|
|
774
|
+
await routeSessionMemoryTriggerSafely({
|
|
775
|
+
resolveScope: () => resolveScope(context),
|
|
776
|
+
routeTrigger: createSessionMemoryTriggerRouter(servicesPromise, buildEvent)
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// src/adapters/skeln/hooks/compaction-handlers.ts
|
|
781
|
+
async function handleSkelnSessionBeforeCompact(event, context, servicesPromise, resolveScope) {
|
|
782
|
+
await routeSkelnSessionMemoryTrigger(servicesPromise, resolveScope, context, (scope) => buildSkelnSessionBeforeCompactTriggerEvent(scope, event));
|
|
783
|
+
await scheduleSkelnPreCompactionEpisodeWrite(event, context, servicesPromise);
|
|
784
|
+
}
|
|
785
|
+
async function resolveSkelnCompactionPromptContext(scope, services, tracker) {
|
|
786
|
+
return resolveCompactionPromptContext({
|
|
787
|
+
sessionId: scope.sessionId,
|
|
788
|
+
sessionKey: scope.sessionKey,
|
|
789
|
+
features: {
|
|
790
|
+
...services.agenrConfig.features,
|
|
791
|
+
...services.skelnConfig.featureFlags
|
|
792
|
+
},
|
|
793
|
+
sessionMemoryRepository: services.sessionMemoryRepository,
|
|
794
|
+
tracker
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
async function scheduleSkelnPreCompactionEpisodeWrite(event, context, servicesPromise) {
|
|
798
|
+
const target = resolveSkelnSessionEpisodeTarget(context);
|
|
799
|
+
if (!target.sessionFile) {
|
|
830
800
|
return;
|
|
831
801
|
}
|
|
832
802
|
try {
|
|
833
|
-
const
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
scope: toWorkingScopeFromSkelnSession(scope),
|
|
837
|
-
operation: {
|
|
838
|
-
type: "add_command_note",
|
|
839
|
-
command: commandNote
|
|
840
|
-
},
|
|
841
|
-
updateReason: "Recorded bounded subagent findings on the parent working set.",
|
|
842
|
-
actor: "runtime",
|
|
843
|
-
source: "lifecycle_hook"
|
|
844
|
-
});
|
|
845
|
-
if (!result.ok && !isExpectedSubagentFindingSkip(result.code)) {
|
|
846
|
-
console.warn(`[agenr] subagent findings working-memory update failed: ${result.message}`);
|
|
803
|
+
const services = await servicesPromise;
|
|
804
|
+
if (!isPluginEpisodeWriteEnabled(services.skelnConfig.memoryPolicy)) {
|
|
805
|
+
return;
|
|
847
806
|
}
|
|
807
|
+
const messageCount = resolveSkelnPreCompactionMessageCount(context, event);
|
|
808
|
+
scheduleGuardedEpisodeWrite({
|
|
809
|
+
dreaming: services.dreaming,
|
|
810
|
+
dbPath: services.config.dbPath,
|
|
811
|
+
write: async () => writeSkelnPreCompactionEpisode({
|
|
812
|
+
target,
|
|
813
|
+
messageCount,
|
|
814
|
+
services
|
|
815
|
+
}),
|
|
816
|
+
onFailure: (error) => {
|
|
817
|
+
console.warn(`[agenr] skeln pre-compaction episode write failed for session=${target.sessionId}: ${formatErrorMessage(error)}`);
|
|
818
|
+
}
|
|
819
|
+
});
|
|
848
820
|
} catch (error) {
|
|
849
|
-
console.warn(`[agenr]
|
|
821
|
+
console.warn(`[agenr] skeln pre-compaction episode write skipped: ${formatErrorMessage(error)}`);
|
|
850
822
|
}
|
|
851
823
|
}
|
|
852
|
-
function
|
|
853
|
-
if (event.
|
|
854
|
-
return
|
|
824
|
+
function resolveSkelnPreCompactionMessageCount(context, event) {
|
|
825
|
+
if (typeof event.messageCount === "number" && Number.isFinite(event.messageCount)) {
|
|
826
|
+
return event.messageCount;
|
|
855
827
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
828
|
+
try {
|
|
829
|
+
return context.sessionManager.getBranch().length;
|
|
830
|
+
} catch {
|
|
831
|
+
return 0;
|
|
859
832
|
}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// src/adapters/skeln/hooks/before-agent-start.ts
|
|
836
|
+
async function handleAgenrSkelnBeforeAgentStart(event, context, deps) {
|
|
837
|
+
const scope = await deps.resolveScope(context);
|
|
838
|
+
await deps.lifecycleIntakeTracker.wait(scope.sessionId, scope.sessionKey);
|
|
839
|
+
const doctrine = buildAgenrSkelnMemoryPromptSection().join("\n");
|
|
840
|
+
const systemPromptWithDoctrine = appendPromptSection(event.systemPrompt, doctrine);
|
|
841
|
+
const trackerState = deps.sessionStartTracker.consume(scope.sessionId, scope.sessionKey);
|
|
842
|
+
if (trackerState.isFirst) {
|
|
843
|
+
return resolveSessionStartInjection(scope, event.systemPrompt, systemPromptWithDoctrine, deps.servicesPromise);
|
|
865
844
|
}
|
|
866
|
-
|
|
867
|
-
(result) => `- ${result.agent}${result.name ? `/${result.name}` : ""}: ${result.status}${result.summary ? ` - ${result.summary}` : ""}`
|
|
868
|
-
);
|
|
869
|
-
const outcome = truncateText(
|
|
870
|
-
[
|
|
871
|
-
`mode=${mode}`,
|
|
872
|
-
...resultLines,
|
|
873
|
-
...artifactPath ? [`artifact=${artifactPath}`] : [],
|
|
874
|
-
...results.length >= SUBAGENT_RESULT_LIMIT ? ["additional subagent results omitted"] : []
|
|
875
|
-
].join("\n"),
|
|
876
|
-
SUBAGENT_OUTCOME_MAX_CHARS
|
|
877
|
-
);
|
|
878
|
-
return {
|
|
879
|
-
command: `subagent ${mode}`,
|
|
880
|
-
outcome,
|
|
881
|
-
observedAt
|
|
882
|
-
};
|
|
883
|
-
}
|
|
884
|
-
function normalizeDelegationResult(value) {
|
|
885
|
-
const record = isRecord(value) ? value : void 0;
|
|
886
|
-
if (!record) {
|
|
887
|
-
return [];
|
|
888
|
-
}
|
|
889
|
-
const agent = readOptionalTrimmedString(record.agent);
|
|
890
|
-
const status = readOptionalTrimmedString(record.status);
|
|
891
|
-
if (!agent || !status) {
|
|
892
|
-
return [];
|
|
893
|
-
}
|
|
894
|
-
const stdout = readOptionalTrimmedString(record.stdout);
|
|
895
|
-
const stderr = readOptionalTrimmedString(record.stderr);
|
|
896
|
-
const name = readOptionalTrimmedString(record.name);
|
|
897
|
-
const summary = firstMeaningfulLine(stdout ?? stderr);
|
|
898
|
-
return [
|
|
899
|
-
{
|
|
900
|
-
agent,
|
|
901
|
-
...name ? { name } : {},
|
|
902
|
-
status,
|
|
903
|
-
...summary ? { summary } : {}
|
|
904
|
-
}
|
|
905
|
-
];
|
|
906
|
-
}
|
|
907
|
-
function isExpectedSubagentFindingSkip(code) {
|
|
908
|
-
return code === "feature_disabled" || code === "missing_active_set" || code === "missing_scope" || code === "misconfigured";
|
|
909
|
-
}
|
|
910
|
-
function firstMeaningfulLine(value) {
|
|
911
|
-
const line = value?.split(/\r?\n/u).map((part) => part.trim()).find((part) => part.length > 0);
|
|
912
|
-
return line ? truncateText(line, 500) : void 0;
|
|
913
|
-
}
|
|
914
|
-
function truncateText(value, maxChars) {
|
|
915
|
-
if (value.length <= maxChars) {
|
|
916
|
-
return value;
|
|
917
|
-
}
|
|
918
|
-
return `${value.slice(0, Math.max(0, maxChars - 15)).trimEnd()} [truncated]`;
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
// src/adapters/shared/agenr-episode-summary-llm.ts
|
|
922
|
-
function createAgenrEpisodeSummaryLlm(provider, modelId, apiKey) {
|
|
923
|
-
const client = createLlmClient(provider, modelId, { apiKey });
|
|
924
|
-
return {
|
|
925
|
-
complete: client.complete,
|
|
926
|
-
completeJson: client.completeJson,
|
|
927
|
-
metadata: {
|
|
928
|
-
modelRef: `${provider}/${modelId}`,
|
|
929
|
-
pricing: {
|
|
930
|
-
input: client.metadata.model.cost?.input ?? 0,
|
|
931
|
-
output: client.metadata.model.cost?.output ?? 0,
|
|
932
|
-
cacheRead: client.metadata.model.cost?.cacheRead ?? 0,
|
|
933
|
-
cacheWrite: client.metadata.model.cost?.cacheWrite ?? 0
|
|
934
|
-
},
|
|
935
|
-
usage: client.metadata.usage
|
|
936
|
-
}
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
// src/adapters/skeln/transcript/parser.ts
|
|
941
|
-
import { createHash } from "crypto";
|
|
942
|
-
import * as fs from "fs/promises";
|
|
943
|
-
import path from "path";
|
|
944
|
-
var SkelnTranscriptParser = class {
|
|
945
|
-
/**
|
|
946
|
-
* Parses one Skeln session JSONL file into normalized transcript messages.
|
|
947
|
-
*
|
|
948
|
-
* @param filePath - Absolute or relative Skeln session file.
|
|
949
|
-
* @returns Parsed transcript payload consumed by shared episode ingest.
|
|
950
|
-
*/
|
|
951
|
-
async parseFile(filePath) {
|
|
952
|
-
const raw = await fs.readFile(filePath, "utf8");
|
|
953
|
-
const transcriptHash = createHash("sha256").update(raw).digest("hex");
|
|
954
|
-
const warnings = [];
|
|
955
|
-
const messages = [];
|
|
956
|
-
const records = parseJsonlRecords(raw, filePath, warnings);
|
|
957
|
-
const header = records[0] ? parseHeaderRecord(records[0], filePath, warnings) : {};
|
|
958
|
-
const sessionId = header.sessionId ?? deriveSkelnSessionIdFromFilePath(filePath);
|
|
959
|
-
const headerTimestamp = toIsoTimestamp(header.timestamp);
|
|
960
|
-
for (const record of records.slice(1)) {
|
|
961
|
-
if (record.type !== "message") {
|
|
962
|
-
continue;
|
|
963
|
-
}
|
|
964
|
-
const normalized = normalizeSkelnMessage(record.message);
|
|
965
|
-
if (!normalized) {
|
|
966
|
-
continue;
|
|
967
|
-
}
|
|
968
|
-
messages.push({
|
|
969
|
-
index: messages.length,
|
|
970
|
-
role: normalized.role,
|
|
971
|
-
text: normalized.text,
|
|
972
|
-
...normalized.timestamp ? { timestamp: normalized.timestamp } : {}
|
|
973
|
-
});
|
|
974
|
-
}
|
|
975
|
-
const startedAt = messages[0]?.timestamp ?? headerTimestamp;
|
|
976
|
-
const endedAt = messages.at(-1)?.timestamp;
|
|
977
|
-
return {
|
|
978
|
-
messages,
|
|
979
|
-
metadata: {
|
|
980
|
-
...sessionId ? { sessionId } : {},
|
|
981
|
-
...header.cwd ? { workingDirectory: header.cwd, project: path.basename(header.cwd) } : {},
|
|
982
|
-
...startedAt ? { startedAt } : {},
|
|
983
|
-
...endedAt ? { endedAt } : {},
|
|
984
|
-
messageCount: messages.length,
|
|
985
|
-
transcriptHash,
|
|
986
|
-
reconstructedSurface: "skeln",
|
|
987
|
-
surfaceReconstructionSource: "reconstructed",
|
|
988
|
-
sourceIdentity: path.resolve(filePath),
|
|
989
|
-
sourceIdentityKind: "skeln_session_file"
|
|
990
|
-
},
|
|
991
|
-
warnings
|
|
992
|
-
};
|
|
993
|
-
}
|
|
994
|
-
};
|
|
995
|
-
var skelnTranscriptParser = new SkelnTranscriptParser();
|
|
996
|
-
function parseJsonlRecords(raw, filePath, warnings) {
|
|
997
|
-
const records = [];
|
|
998
|
-
const lines = raw.split(/\r?\n/u);
|
|
999
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
1000
|
-
const line = lines[index]?.trim();
|
|
1001
|
-
if (!line) {
|
|
1002
|
-
continue;
|
|
1003
|
-
}
|
|
1004
|
-
try {
|
|
1005
|
-
const parsed = JSON.parse(line);
|
|
1006
|
-
const record = isRecord(parsed) ? parsed : void 0;
|
|
1007
|
-
if (!record) {
|
|
1008
|
-
warnings.push(`Skipped non-object Skeln JSONL line ${index + 1} in ${filePath}.`);
|
|
1009
|
-
continue;
|
|
1010
|
-
}
|
|
1011
|
-
records.push(record);
|
|
1012
|
-
} catch (error) {
|
|
1013
|
-
warnings.push(`Skipped malformed Skeln JSONL line ${index + 1} in ${filePath}: ${formatErrorMessage(error)}`);
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
if (records.length === 0) {
|
|
1017
|
-
warnings.push(`Skipped empty Skeln transcript file: ${filePath}`);
|
|
1018
|
-
}
|
|
1019
|
-
return records;
|
|
1020
|
-
}
|
|
1021
|
-
function parseHeaderRecord(record, filePath, warnings) {
|
|
1022
|
-
if (!record || record.type !== "session") {
|
|
1023
|
-
warnings.push(`Skeln transcript ${filePath} is missing a session header.`);
|
|
1024
|
-
return {};
|
|
1025
|
-
}
|
|
1026
|
-
return {
|
|
1027
|
-
sessionId: readOptionalTrimmedString(record.id),
|
|
1028
|
-
timestamp: readOptionalFiniteNumber(record.timestamp),
|
|
1029
|
-
cwd: readOptionalTrimmedString(record.cwd)
|
|
1030
|
-
};
|
|
1031
|
-
}
|
|
1032
|
-
function normalizeSkelnMessage(value) {
|
|
1033
|
-
const message = isRecord(value) ? value : void 0;
|
|
1034
|
-
if (!message) {
|
|
1035
|
-
return void 0;
|
|
1036
|
-
}
|
|
1037
|
-
const role = normalizeRole(readOptionalTrimmedString(message.role));
|
|
1038
|
-
if (!role) {
|
|
1039
|
-
return void 0;
|
|
1040
|
-
}
|
|
1041
|
-
const text = normalizeMessageText(message.content);
|
|
1042
|
-
if (!text) {
|
|
1043
|
-
return void 0;
|
|
1044
|
-
}
|
|
1045
|
-
const timestamp = toIsoTimestamp(readOptionalFiniteNumber(message.timestamp));
|
|
1046
|
-
return {
|
|
1047
|
-
role,
|
|
1048
|
-
text,
|
|
1049
|
-
...timestamp ? { timestamp } : {}
|
|
1050
|
-
};
|
|
1051
|
-
}
|
|
1052
|
-
function normalizeRole(role) {
|
|
1053
|
-
if (role === "user") {
|
|
1054
|
-
return "user";
|
|
1055
|
-
}
|
|
1056
|
-
if (role === "assistant") {
|
|
1057
|
-
return "assistant";
|
|
1058
|
-
}
|
|
1059
|
-
return void 0;
|
|
1060
|
-
}
|
|
1061
|
-
function normalizeMessageText(value) {
|
|
1062
|
-
if (typeof value === "string") {
|
|
1063
|
-
return value.trim();
|
|
1064
|
-
}
|
|
1065
|
-
if (!Array.isArray(value)) {
|
|
1066
|
-
return "";
|
|
1067
|
-
}
|
|
1068
|
-
const parts = [];
|
|
1069
|
-
for (const part of value) {
|
|
1070
|
-
if (typeof part === "string") {
|
|
1071
|
-
parts.push(part);
|
|
1072
|
-
continue;
|
|
1073
|
-
}
|
|
1074
|
-
const record = isRecord(part) ? part : void 0;
|
|
1075
|
-
const text = readOptionalTrimmedString(record?.text) ?? readOptionalTrimmedString(record?.content);
|
|
1076
|
-
if (text) {
|
|
1077
|
-
parts.push(text);
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
return parts.map((part) => part.trim()).filter(Boolean).join("\n").trim();
|
|
1081
|
-
}
|
|
1082
|
-
function toIsoTimestamp(timestamp) {
|
|
1083
|
-
if (timestamp === void 0) {
|
|
1084
|
-
return void 0;
|
|
1085
|
-
}
|
|
1086
|
-
const date = new Date(timestamp);
|
|
1087
|
-
return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
|
|
1088
|
-
}
|
|
1089
|
-
function deriveSkelnSessionIdFromFilePath(filePath) {
|
|
1090
|
-
const basename = path.basename(filePath).replace(/\.jsonl(?:\..*)?$/iu, "").trim();
|
|
1091
|
-
return basename.length > 0 ? basename : void 0;
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
// src/adapters/skeln/episode/bounded-session-episode.ts
|
|
1095
|
-
function resolveSkelnSessionEpisodeTarget(context) {
|
|
1096
|
-
return {
|
|
1097
|
-
sessionId: String(context.sessionManager.getSessionId()),
|
|
1098
|
-
sessionFile: resolveSessionFile(context)
|
|
1099
|
-
};
|
|
1100
|
-
}
|
|
1101
|
-
async function writeSkelnBoundedSessionEpisode(params) {
|
|
1102
|
-
const logger = params.logger ?? console;
|
|
1103
|
-
const sessionFile = params.target.sessionFile;
|
|
1104
|
-
const sessionId = params.target.sessionId;
|
|
1105
|
-
const summaryDeadlineMs = Date.now() + EPISODE_SUMMARY_TIMEOUT_MS;
|
|
1106
|
-
if (!sessionFile) {
|
|
1107
|
-
logger.info(`[agenr] ${params.actionLabel} skipped for ${params.skipDetails} reason=no_session_file`);
|
|
1108
|
-
return;
|
|
1109
|
-
}
|
|
1110
|
-
const episodeModel = resolveModel(params.services.agenrConfig, "episode");
|
|
1111
|
-
const llmApiKey = resolveLlmApiKey(params.services.agenrConfig, episodeModel.provider);
|
|
1112
|
-
const summaryLlm = createDeadlineAwareEpisodeSummaryLlm(
|
|
1113
|
-
createAgenrEpisodeSummaryLlm(episodeModel.provider, episodeModel.modelId, llmApiKey),
|
|
1114
|
-
summaryDeadlineMs
|
|
1115
|
-
);
|
|
1116
|
-
await writeBoundedSingleTranscriptEpisode({
|
|
1117
|
-
filePath: sessionFile,
|
|
1118
|
-
context: params.logContext,
|
|
1119
|
-
actionLabel: params.actionLabel,
|
|
1120
|
-
logger,
|
|
1121
|
-
summaryDeadlineMs,
|
|
1122
|
-
ports: {
|
|
1123
|
-
files: createSingleTranscriptDiscoveryPort(sessionFile),
|
|
1124
|
-
transcript: skelnTranscriptParser,
|
|
1125
|
-
episodes: params.services.episodes,
|
|
1126
|
-
createSummaryLlm: () => summaryLlm,
|
|
1127
|
-
embedSummary: (summary) => embedEpisodeSummaryWithinBudget({
|
|
1128
|
-
summary,
|
|
1129
|
-
embedding: params.services.embedding,
|
|
1130
|
-
embeddingAvailable: params.services.embeddingStatus.available,
|
|
1131
|
-
deadlineMs: summaryDeadlineMs,
|
|
1132
|
-
logger,
|
|
1133
|
-
logContext: `[agenr] ${params.actionLabel} embedding skipped for ${params.logContext} file=${sessionFile}`
|
|
1134
|
-
})
|
|
1135
|
-
},
|
|
1136
|
-
ingestOptions: {
|
|
1137
|
-
source: "skeln",
|
|
1138
|
-
genVersion: params.genVersion,
|
|
1139
|
-
skipActiveSessionCheck: true,
|
|
1140
|
-
activityThreshold: params.activityThreshold,
|
|
1141
|
-
candidateOverrides: {
|
|
1142
|
-
sessionId,
|
|
1143
|
-
sourceRef: params.buildSourceRef(sessionFile),
|
|
1144
|
-
agentId: null,
|
|
1145
|
-
surface: "skeln",
|
|
1146
|
-
metadataSource: "reconstructed"
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
});
|
|
1150
|
-
}
|
|
1151
|
-
function resolveSessionFile(context) {
|
|
1152
|
-
try {
|
|
1153
|
-
const sessionFile = context.sessionManager.getSessionFile().trim();
|
|
1154
|
-
return sessionFile || void 0;
|
|
1155
|
-
} catch {
|
|
1156
|
-
return void 0;
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
// src/adapters/skeln/episode/episode-writer.ts
|
|
1161
|
-
var SKELN_EPISODE_GENERATOR_VERSION = "skeln-episodic-summary-v1";
|
|
1162
|
-
var SKELN_PHASE4_SHUTDOWN_EPISODE_ACTIVITY_THRESHOLD = {
|
|
1163
|
-
minMaterialTurns: 8,
|
|
1164
|
-
minDurationMs: 20 * 60 * 1e3
|
|
1165
|
-
};
|
|
1166
|
-
async function writeSkelnShutdownEpisode(params) {
|
|
1167
|
-
await writeSkelnBoundedSessionEpisode({
|
|
1168
|
-
target: params.target,
|
|
1169
|
-
services: params.services,
|
|
1170
|
-
logger: params.logger,
|
|
1171
|
-
actionLabel: "skeln shutdown episode write",
|
|
1172
|
-
genVersion: SKELN_EPISODE_GENERATOR_VERSION,
|
|
1173
|
-
activityThreshold: SKELN_PHASE4_SHUTDOWN_EPISODE_ACTIVITY_THRESHOLD,
|
|
1174
|
-
buildSourceRef: (sessionFile) => sessionFile,
|
|
1175
|
-
logContext: `session=${params.target.sessionId} key=skeln:${params.target.sessionId}`,
|
|
1176
|
-
skipDetails: `session=${params.target.sessionId}`
|
|
1177
|
-
});
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
// src/adapters/skeln/episode/shutdown-episode-write.ts
|
|
1181
|
-
function buildSkelnSessionShutdownEpisodeWork(params) {
|
|
1182
|
-
const target = resolveSkelnSessionEpisodeTarget(params.context);
|
|
1183
|
-
const shutdownWork = params.event.reason === "quit" ? writeScopedSkelnShutdownEpisode(params.servicesPromise, target, params.logger).finally(() => closeSkelnServicesAfterShutdown(params.servicesPromise)) : writeScopedSkelnShutdownEpisode(params.servicesPromise, target, params.logger);
|
|
1184
|
-
return shutdownWork.catch((error) => {
|
|
1185
|
-
logSkelnShutdownEpisodeFailure(error, params.logger);
|
|
1186
|
-
});
|
|
1187
|
-
}
|
|
1188
|
-
function scheduleSkelnSessionShutdownEpisodeWrite(params) {
|
|
1189
|
-
const work = buildSkelnSessionShutdownEpisodeWork(params);
|
|
1190
|
-
if (params.event.reason === "quit" && params.event.deferWork) {
|
|
1191
|
-
params.event.deferWork(work);
|
|
1192
|
-
return Promise.resolve();
|
|
1193
|
-
}
|
|
1194
|
-
if (params.event.reason === "quit") {
|
|
1195
|
-
return work;
|
|
1196
|
-
}
|
|
1197
|
-
void work;
|
|
1198
|
-
return Promise.resolve();
|
|
1199
|
-
}
|
|
1200
|
-
function logSkelnShutdownEpisodeFailure(error, logger) {
|
|
1201
|
-
const log = logger ?? console;
|
|
1202
|
-
log.warn(`[agenr] skeln shutdown episode failed: ${formatErrorMessage(error)}`);
|
|
1203
|
-
}
|
|
1204
|
-
async function closeSkelnServicesAfterShutdown(servicesPromise) {
|
|
1205
|
-
try {
|
|
1206
|
-
const services = await servicesPromise;
|
|
1207
|
-
await services.close();
|
|
1208
|
-
} catch {
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
async function writeScopedSkelnShutdownEpisode(servicesPromise, target, logger) {
|
|
1212
|
-
const services = await servicesPromise;
|
|
1213
|
-
if (!services.capabilities.shutdownEpisodes) {
|
|
1214
|
-
return;
|
|
1215
|
-
}
|
|
1216
|
-
await writeSkelnShutdownEpisode({ target, services, logger });
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
// src/app/features/resolve.ts
|
|
1220
|
-
function resolveAgenrFeatureFlags(features) {
|
|
1221
|
-
const resolved = { ...DEFAULT_AGENR_FEATURE_FLAGS };
|
|
1222
|
-
for (const key of AGENR_FEATURE_FLAG_KEYS) {
|
|
1223
|
-
if (features?.[key] !== void 0) {
|
|
1224
|
-
resolved[key] = features[key];
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
return resolved;
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
// src/app/features/capabilities.ts
|
|
1231
|
-
function resolveRuntimeCapabilities(featureFlags, input = {}) {
|
|
1232
|
-
const sessionMemoryFlagEnabled = featureFlags.sessionTreeLineage || featureFlags.sessionTreeCompaction;
|
|
1233
|
-
const sessionMemory = resolveRepositoryBackedCapability(sessionMemoryFlagEnabled, input.sessionMemoryRepository);
|
|
1234
|
-
return {
|
|
1235
|
-
workingMemory: resolveRepositoryBackedCapability(featureFlags.workingMemory, input.workingMemoryRepository),
|
|
1236
|
-
sessionMemory,
|
|
1237
|
-
shutdownEpisodes: sessionMemory === "enabled",
|
|
1238
|
-
goalContinuation: resolveGoalContinuationCapability(featureFlags.goalContinuation, input.goalContinuationHostPort)
|
|
1239
|
-
};
|
|
1240
|
-
}
|
|
1241
|
-
function resolveRepositoryBackedCapability(featureEnabled, repository) {
|
|
1242
|
-
if (!featureEnabled) {
|
|
1243
|
-
return "disabled";
|
|
1244
|
-
}
|
|
1245
|
-
return repository ? "enabled" : "misconfigured";
|
|
1246
|
-
}
|
|
1247
|
-
function resolveGoalContinuationCapability(featureEnabled, hostPort) {
|
|
1248
|
-
if (!featureEnabled) {
|
|
1249
|
-
return "disabled";
|
|
1250
|
-
}
|
|
1251
|
-
return hostPort ? "enabled" : "misconfigured";
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
// src/app/features/runtime-policy.ts
|
|
1255
|
-
function resolveRuntimePolicy(featureFlags, input = {}) {
|
|
1256
|
-
return {
|
|
1257
|
-
featureFlags,
|
|
1258
|
-
capabilities: resolveRuntimeCapabilities(featureFlags, {
|
|
1259
|
-
workingMemoryRepository: input.workingMemoryRepository,
|
|
1260
|
-
sessionMemoryRepository: input.sessionMemoryRepository,
|
|
1261
|
-
goalContinuationHostPort: input.goalContinuationHostPort
|
|
1262
|
-
}),
|
|
1263
|
-
...input.memoryPolicy ? { memoryPolicy: input.memoryPolicy } : {}
|
|
1264
|
-
};
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
// src/app/goal-continuation/service.ts
|
|
1268
|
-
var GOAL_CONTINUATION_FEATURE_DISABLED_MESSAGE = "Goal continuation is disabled by the goalContinuation feature flag.";
|
|
1269
|
-
var GOAL_CONTINUATION_HOST_MISSING_MESSAGE = "Goal continuation is host-owned; no host callback was registered for this Agenr runtime.";
|
|
1270
|
-
function createGoalContinuationService(featureFlags, hostPort) {
|
|
1271
|
-
const featureEnabled = featureFlags.goalContinuation;
|
|
1272
|
-
return {
|
|
1273
|
-
async runCommand(params) {
|
|
1274
|
-
if (!featureEnabled) {
|
|
1275
|
-
return {
|
|
1276
|
-
ok: false,
|
|
1277
|
-
code: "feature_disabled",
|
|
1278
|
-
message: GOAL_CONTINUATION_FEATURE_DISABLED_MESSAGE
|
|
1279
|
-
};
|
|
1280
|
-
}
|
|
1281
|
-
if (!hostPort) {
|
|
1282
|
-
return {
|
|
1283
|
-
ok: false,
|
|
1284
|
-
code: "host_callback_missing",
|
|
1285
|
-
message: GOAL_CONTINUATION_HOST_MISSING_MESSAGE
|
|
1286
|
-
};
|
|
1287
|
-
}
|
|
1288
|
-
return hostPort.runCommand(params);
|
|
1289
|
-
}
|
|
1290
|
-
};
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
// src/app/session-memory/trigger-router.ts
|
|
1294
|
-
import { createHash as createHash2 } from "crypto";
|
|
1295
|
-
|
|
1296
|
-
// src/app/working-memory/lifecycle-checkpoint.ts
|
|
1297
|
-
async function attachWorkingCheckpointRefresh(event, result, workingMemory) {
|
|
1298
|
-
const refreshRequest = resolveWorkingCheckpointRefreshRequest(event, result);
|
|
1299
|
-
if (!refreshRequest) {
|
|
1300
|
-
return result;
|
|
1301
|
-
}
|
|
1302
|
-
const workingCheckpointRefresh = workingMemory ? await mergeWorkingCheckpoint(refreshRequest, workingMemory) : skippedRefreshWithoutWorkingMemory(refreshRequest.attachLabel);
|
|
1303
|
-
return {
|
|
1304
|
-
...result,
|
|
1305
|
-
workingCheckpointRefresh
|
|
1306
|
-
};
|
|
1307
|
-
}
|
|
1308
|
-
async function mergeWorkingCheckpoint(request, workingMemory) {
|
|
1309
|
-
const scope = resolveWorkingRefreshScope(request.event);
|
|
1310
|
-
if (!scope) {
|
|
1311
|
-
return {
|
|
1312
|
-
ok: false,
|
|
1313
|
-
reason: "missing_scope",
|
|
1314
|
-
message: `${request.lifecycleLabel} requires working-scope facts.`
|
|
1315
|
-
};
|
|
1316
|
-
}
|
|
1317
|
-
const result = await workingMemory.run({
|
|
1318
|
-
action: "update",
|
|
1319
|
-
scope,
|
|
1320
|
-
operation: {
|
|
1321
|
-
type: "merge_checkpoint",
|
|
1322
|
-
checkpoint: {
|
|
1323
|
-
summary: request.summary,
|
|
1324
|
-
recordedAt: request.event.observedAt
|
|
1325
|
-
}
|
|
1326
|
-
},
|
|
1327
|
-
updateReason: request.updateReason,
|
|
1328
|
-
actor: "runtime",
|
|
1329
|
-
source: "lifecycle_hook"
|
|
1330
|
-
});
|
|
1331
|
-
if (!result.ok) {
|
|
1332
|
-
return mapWorkingMemoryRefreshFailure(result.code, result.message, request.lifecycleLabel);
|
|
1333
|
-
}
|
|
1334
|
-
if (result.action !== "update") {
|
|
1335
|
-
return {
|
|
1336
|
-
ok: false,
|
|
1337
|
-
reason: "working_memory_unavailable",
|
|
1338
|
-
message: `Expected working-memory update result, received ${result.action}.`
|
|
1339
|
-
};
|
|
1340
|
-
}
|
|
1341
|
-
return {
|
|
1342
|
-
ok: true,
|
|
1343
|
-
action: "working_checkpoint_refreshed",
|
|
1344
|
-
workingSetId: result.workingSet.id,
|
|
1345
|
-
revision: result.workingSet.revision
|
|
1346
|
-
};
|
|
1347
|
-
}
|
|
1348
|
-
function resolveWorkingCheckpointRefreshRequest(event, result) {
|
|
1349
|
-
const compactionEvent = resolveCompactionCheckpointRefreshEvent(event, result);
|
|
1350
|
-
if (compactionEvent) {
|
|
1351
|
-
const summary = normalizeOptionalString(compactionEvent.artifact.summary);
|
|
1352
|
-
if (!summary) {
|
|
1353
|
-
return void 0;
|
|
1354
|
-
}
|
|
1355
|
-
const sourceId = normalizeOptionalString(compactionEvent.artifact.sourceId) ?? "unknown";
|
|
1356
|
-
return {
|
|
1357
|
-
event: compactionEvent,
|
|
1358
|
-
summary,
|
|
1359
|
-
updateReason: `Refreshed working checkpoint from session compaction ${sourceId}.`,
|
|
1360
|
-
lifecycleLabel: "compaction checkpoint refresh",
|
|
1361
|
-
attachLabel: "Compaction checkpoint refresh"
|
|
1362
|
-
};
|
|
1363
|
-
}
|
|
1364
|
-
const shutdownEvent = resolveShutdownCheckpointRefreshEvent(event, result);
|
|
1365
|
-
if (!shutdownEvent) {
|
|
1366
|
-
return void 0;
|
|
1367
|
-
}
|
|
1368
|
-
const reason = normalizeShutdownReason(shutdownEvent.shutdownReason);
|
|
1369
|
-
return {
|
|
1370
|
-
event: shutdownEvent,
|
|
1371
|
-
summary: `Session shutdown (${reason}) recorded. Resume from the latest working-set snapshot; no implicit close was performed.`,
|
|
1372
|
-
updateReason: `Recorded working checkpoint from session shutdown (${reason}).`,
|
|
1373
|
-
lifecycleLabel: "shutdown checkpoint refresh",
|
|
1374
|
-
attachLabel: "Shutdown checkpoint refresh"
|
|
1375
|
-
};
|
|
1376
|
-
}
|
|
1377
|
-
function resolveCompactionCheckpointRefreshEvent(event, result) {
|
|
1378
|
-
if (event.type !== "session_compact" || result.artifact?.kind !== "compaction_checkpoint") {
|
|
1379
|
-
return void 0;
|
|
1380
|
-
}
|
|
1381
|
-
const artifact = event.artifact;
|
|
1382
|
-
if (!isCompactionCheckpointArtifact(artifact)) {
|
|
1383
|
-
return void 0;
|
|
1384
|
-
}
|
|
1385
|
-
return {
|
|
1386
|
-
...event,
|
|
1387
|
-
type: "session_compact",
|
|
1388
|
-
artifact
|
|
1389
|
-
};
|
|
1390
|
-
}
|
|
1391
|
-
function resolveShutdownCheckpointRefreshEvent(event, result) {
|
|
1392
|
-
if (event.type !== "session_shutdown" || result.accepted !== true) {
|
|
1393
|
-
return void 0;
|
|
1394
|
-
}
|
|
1395
|
-
return {
|
|
1396
|
-
...event,
|
|
1397
|
-
type: "session_shutdown"
|
|
1398
|
-
};
|
|
1399
|
-
}
|
|
1400
|
-
function isCompactionCheckpointArtifact(artifact) {
|
|
1401
|
-
return artifact?.kind === "compaction_checkpoint";
|
|
1402
|
-
}
|
|
1403
|
-
function resolveWorkingRefreshScope(event) {
|
|
1404
|
-
if (event.workingScope && Object.keys(event.workingScope).length > 0) {
|
|
1405
|
-
return event.workingScope;
|
|
1406
|
-
}
|
|
1407
|
-
return void 0;
|
|
1408
|
-
}
|
|
1409
|
-
function mapWorkingMemoryRefreshFailure(code, message, lifecycleLabel) {
|
|
1410
|
-
if (code === "feature_disabled") {
|
|
1411
|
-
return {
|
|
1412
|
-
ok: false,
|
|
1413
|
-
reason: "not_applicable",
|
|
1414
|
-
message: `Working memory is disabled; ${lifecycleLabel} was skipped.`
|
|
1415
|
-
};
|
|
1416
|
-
}
|
|
1417
|
-
if (code === "missing_active_set") {
|
|
1418
|
-
return {
|
|
1419
|
-
ok: false,
|
|
1420
|
-
reason: "no_active_working_set",
|
|
1421
|
-
code,
|
|
1422
|
-
message
|
|
1423
|
-
};
|
|
1424
|
-
}
|
|
1425
|
-
return {
|
|
1426
|
-
ok: false,
|
|
1427
|
-
reason: "working_memory_unavailable",
|
|
1428
|
-
code,
|
|
1429
|
-
message
|
|
1430
|
-
};
|
|
1431
|
-
}
|
|
1432
|
-
function normalizeShutdownReason(shutdownReason) {
|
|
1433
|
-
const trimmed = shutdownReason?.trim();
|
|
1434
|
-
return trimmed && trimmed.length > 0 ? trimmed : "unknown";
|
|
1435
|
-
}
|
|
1436
|
-
function skippedRefreshWithoutWorkingMemory(label) {
|
|
1437
|
-
return {
|
|
1438
|
-
ok: false,
|
|
1439
|
-
reason: "not_applicable",
|
|
1440
|
-
message: `${label} requires a working-memory service, but none was wired into the runtime.`
|
|
1441
|
-
};
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
// src/app/session-memory/trigger-router.ts
|
|
1445
|
-
var SESSION_MEMORY_TRIGGER_FLAGS = {
|
|
1446
|
-
session_start: "sessionTreeLineage",
|
|
1447
|
-
session_before_fork: "sessionTreeLineage",
|
|
1448
|
-
session_before_compact: "sessionTreeCompaction",
|
|
1449
|
-
session_compact: "sessionTreeCompaction",
|
|
1450
|
-
session_before_tree: "sessionTreeCompaction",
|
|
1451
|
-
session_tree: "sessionTreeLineage",
|
|
1452
|
-
session_shutdown: "sessionTreeLineage"
|
|
1453
|
-
};
|
|
1454
|
-
var LINEAGE_REASONS = new Set(SESSION_LINEAGE_REASONS);
|
|
1455
|
-
async function routeSessionMemoryTrigger(event, featureFlags, deps = {}) {
|
|
1456
|
-
const flagKey = SESSION_MEMORY_TRIGGER_FLAGS[event.type];
|
|
1457
|
-
if (!featureFlags[flagKey]) {
|
|
1458
|
-
return {
|
|
1459
|
-
accepted: false,
|
|
1460
|
-
reason: "feature_disabled",
|
|
1461
|
-
message: `Session-memory trigger ${event.type} is disabled by feature flags.`
|
|
1462
|
-
};
|
|
1463
|
-
}
|
|
1464
|
-
if (!deps.repository) {
|
|
1465
|
-
return {
|
|
1466
|
-
accepted: false,
|
|
1467
|
-
reason: "misconfigured",
|
|
1468
|
-
message: `Session-memory trigger ${event.type} is enabled, but no session-memory repository was wired into the runtime.`
|
|
1469
|
-
};
|
|
1470
|
-
}
|
|
1471
|
-
const artifactInput = normalizeArtifactInput(event);
|
|
1472
|
-
if (artifactInput.kind === "invalid") {
|
|
1473
|
-
return invalidEvent(artifactInput.message);
|
|
1474
|
-
}
|
|
1475
|
-
const lineageInput = normalizeLineageInput(event);
|
|
1476
|
-
if (lineageInput.kind === "invalid") {
|
|
1477
|
-
return invalidEvent(lineageInput.message);
|
|
1478
|
-
}
|
|
1479
|
-
if (artifactInput.kind === "none" && lineageInput.kind === "none") {
|
|
1480
|
-
return attachWorkingCheckpointRefresh(
|
|
1481
|
-
event,
|
|
1482
|
-
{
|
|
1483
|
-
accepted: true,
|
|
1484
|
-
action: isCheckpointRelevantTrigger(event.type) ? "checkpoint_relevant" : "no_lineage",
|
|
1485
|
-
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.`
|
|
1486
|
-
},
|
|
1487
|
-
deps.workingMemory
|
|
1488
|
-
);
|
|
1489
|
-
}
|
|
1490
|
-
const intakeInput = {
|
|
1491
|
-
...artifactInput.kind === "artifact" ? { artifact: artifactInput.input } : {},
|
|
1492
|
-
...lineageInput.kind === "lineage" ? { lineage: lineageInput.input } : {}
|
|
1493
|
-
};
|
|
1494
|
-
const intake = await deps.repository.recordTriggerIntake(intakeInput);
|
|
1495
|
-
const artifact = intake.artifact;
|
|
1496
|
-
const lineageEdge = intake.lineageEdge;
|
|
1497
|
-
const action = resolveTriggerAction(artifact, lineageEdge);
|
|
1498
|
-
return attachWorkingCheckpointRefresh(
|
|
1499
|
-
event,
|
|
1500
|
-
{
|
|
1501
|
-
accepted: true,
|
|
1502
|
-
action,
|
|
1503
|
-
message: buildAcceptedMessage(event.type, action, artifact, lineageEdge),
|
|
1504
|
-
...lineageEdge ? { lineageEdge } : {},
|
|
1505
|
-
...artifact ? { artifact } : {}
|
|
1506
|
-
},
|
|
1507
|
-
deps.workingMemory
|
|
1508
|
-
);
|
|
1509
|
-
}
|
|
1510
|
-
function normalizeLineageInput(event) {
|
|
1511
|
-
if (!event.transitionReason || event.transitionReason === "new" || event.transitionReason === "unknown") {
|
|
1512
|
-
return { kind: "none" };
|
|
1513
|
-
}
|
|
1514
|
-
if (!LINEAGE_REASONS.has(event.transitionReason)) {
|
|
1515
|
-
return { kind: "invalid", message: `Unsupported session lineage reason "${event.transitionReason}".` };
|
|
1516
|
-
}
|
|
1517
|
-
const childSessionKey = normalizeOptionalString(event.childSessionKey) ?? normalizeOptionalString(event.sessionKey);
|
|
1518
|
-
if (!childSessionKey) {
|
|
1519
|
-
return { kind: "invalid", message: "Session lineage requires a child session key." };
|
|
1520
|
-
}
|
|
1521
|
-
const parentSessionKey = normalizeOptionalString(event.predecessor?.sessionKey);
|
|
1522
|
-
const parentSourceRef = normalizeOptionalString(event.predecessor?.sourceRef);
|
|
1523
|
-
if (!parentSessionKey && !parentSourceRef) {
|
|
1524
|
-
return { kind: "none" };
|
|
1525
|
-
}
|
|
1526
|
-
const forkEntryId = normalizeOptionalString(event.predecessor?.forkEntryId);
|
|
1527
|
-
const forkPosition = normalizeOptionalString(event.predecessor?.forkPosition);
|
|
1528
|
-
return {
|
|
1529
|
-
kind: "lineage",
|
|
1530
|
-
input: {
|
|
1531
|
-
childSessionKey,
|
|
1532
|
-
...parentSessionKey ? { parentSessionKey } : {},
|
|
1533
|
-
...parentSourceRef ? { parentSourceRef } : {},
|
|
1534
|
-
reason: event.transitionReason,
|
|
1535
|
-
...forkEntryId ? { forkEntryId } : {},
|
|
1536
|
-
...forkPosition ? { forkPosition } : {},
|
|
1537
|
-
observedAt: event.observedAt
|
|
1538
|
-
}
|
|
1539
|
-
};
|
|
1540
|
-
}
|
|
1541
|
-
function normalizeArtifactInput(event) {
|
|
1542
|
-
if (!event.artifact) {
|
|
1543
|
-
return { kind: "none" };
|
|
1544
|
-
}
|
|
1545
|
-
const sessionKey = normalizeOptionalString(event.artifact.sessionKey) ?? normalizeOptionalString(event.sessionKey);
|
|
1546
|
-
if (!sessionKey) {
|
|
1547
|
-
return { kind: "invalid", message: "Session artifact intake requires a session key." };
|
|
1548
|
-
}
|
|
1549
|
-
const source = normalizeOptionalString(event.artifact.source);
|
|
1550
|
-
const sourceId = normalizeOptionalString(event.artifact.sourceId);
|
|
1551
|
-
const summary = normalizeOptionalString(event.artifact.summary);
|
|
1552
|
-
if (!source || !sourceId || !summary) {
|
|
1553
|
-
return { kind: "invalid", message: "Session artifact intake requires source, sourceId, and summary." };
|
|
1554
|
-
}
|
|
1555
|
-
const contentHash = normalizeOptionalString(event.artifact.contentHash) ?? hashArtifactContent(event.artifact);
|
|
1556
|
-
return {
|
|
1557
|
-
kind: "artifact",
|
|
1558
|
-
input: {
|
|
1559
|
-
...event.artifact,
|
|
1560
|
-
sessionKey,
|
|
1561
|
-
source,
|
|
1562
|
-
sourceId,
|
|
1563
|
-
summary,
|
|
1564
|
-
contentHash
|
|
1565
|
-
}
|
|
1566
|
-
};
|
|
1567
|
-
}
|
|
1568
|
-
function hashArtifactContent(artifact) {
|
|
1569
|
-
const payload = JSON.stringify({
|
|
1570
|
-
kind: artifact.kind,
|
|
1571
|
-
source: artifact.source,
|
|
1572
|
-
sourceId: artifact.sourceId,
|
|
1573
|
-
sourceRef: artifact.sourceRef,
|
|
1574
|
-
summary: artifact.summary,
|
|
1575
|
-
metadata: artifact.metadata
|
|
1576
|
-
});
|
|
1577
|
-
return createHash2("sha256").update(payload).digest("hex");
|
|
1578
|
-
}
|
|
1579
|
-
function resolveTriggerAction(artifact, lineageEdge) {
|
|
1580
|
-
if (artifact && lineageEdge) {
|
|
1581
|
-
return "recorded";
|
|
1582
|
-
}
|
|
1583
|
-
if (artifact) {
|
|
1584
|
-
return "artifact_recorded";
|
|
1585
|
-
}
|
|
1586
|
-
return "lineage_recorded";
|
|
1587
|
-
}
|
|
1588
|
-
function buildAcceptedMessage(triggerType, action, artifact, lineageEdge) {
|
|
1589
|
-
if (action === "recorded" && artifact && lineageEdge) {
|
|
1590
|
-
return `Session-memory trigger ${triggerType} recorded ${lineageEdge.reason} lineage edge ${lineageEdge.id} and ${artifact.kind} artifact ${artifact.id}.`;
|
|
1591
|
-
}
|
|
1592
|
-
if (action === "artifact_recorded" && artifact) {
|
|
1593
|
-
return `Session-memory trigger ${triggerType} recorded ${artifact.kind} artifact ${artifact.id}.`;
|
|
1594
|
-
}
|
|
1595
|
-
if (lineageEdge) {
|
|
1596
|
-
return `Session-memory trigger ${triggerType} recorded ${lineageEdge.reason} lineage edge ${lineageEdge.id}.`;
|
|
1597
|
-
}
|
|
1598
|
-
return `Session-memory trigger ${triggerType} was accepted.`;
|
|
845
|
+
return resolveBeforeTurnInjection(event, scope, event.systemPrompt, systemPromptWithDoctrine, context, deps);
|
|
1599
846
|
}
|
|
1600
|
-
function
|
|
847
|
+
function buildAgenrSkelnInjectionMessage(content) {
|
|
1601
848
|
return {
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
849
|
+
role: "user",
|
|
850
|
+
content: [{ type: "text", text: content }],
|
|
851
|
+
timestamp: Date.now()
|
|
1605
852
|
};
|
|
1606
853
|
}
|
|
1607
|
-
function
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
// src/app/working-memory/close-service.ts
|
|
1612
|
-
function resolveCloseTerminalStatus(closeMode) {
|
|
1613
|
-
return closeMode === "abandon" ? "abandoned" : "closed";
|
|
1614
|
-
}
|
|
1615
|
-
function buildWorkingCloseSnapshot(input) {
|
|
1616
|
-
const finalCheckpoint = buildFinalCheckpoint(input);
|
|
1617
|
-
const candidates = [...input.snapshot.candidates ?? []];
|
|
1618
|
-
if (input.createEpisode && !candidates.some((candidate) => candidate.kind === "episodic")) {
|
|
1619
|
-
candidates.push({
|
|
1620
|
-
kind: "episodic",
|
|
1621
|
-
summary: finalCheckpoint.summary,
|
|
1622
|
-
provenance: {
|
|
1623
|
-
evidenceEventSequences: input.eventSequences,
|
|
1624
|
-
sourceRef: `working_set:${input.workingSetId}#rev:${input.currentRevision}`,
|
|
1625
|
-
note: input.closeReason
|
|
1626
|
-
},
|
|
1627
|
-
promotionStatus: "pending"
|
|
1628
|
-
});
|
|
854
|
+
function appendPromptSection(systemPrompt, section) {
|
|
855
|
+
const trimmedSection = section.trim();
|
|
856
|
+
if (trimmedSection.length === 0 || systemPrompt.includes(trimmedSection)) {
|
|
857
|
+
return systemPrompt;
|
|
1629
858
|
}
|
|
1630
|
-
return {
|
|
1631
|
-
snapshot: {
|
|
1632
|
-
...input.snapshot,
|
|
1633
|
-
checkpoint: finalCheckpoint,
|
|
1634
|
-
candidates: candidates.length > 0 ? candidates : void 0,
|
|
1635
|
-
lastMaterialChange: input.closeReason
|
|
1636
|
-
},
|
|
1637
|
-
candidates
|
|
1638
|
-
};
|
|
1639
|
-
}
|
|
1640
|
-
function buildFinalCheckpoint(input) {
|
|
1641
|
-
const summary = normalizeSummary(input.closeReason) ?? input.snapshot.summary ?? input.snapshot.objective ?? "Working set closed.";
|
|
1642
|
-
return {
|
|
1643
|
-
summary,
|
|
1644
|
-
recordedAt: input.now,
|
|
1645
|
-
...input.snapshot.nextActions && input.snapshot.nextActions.length > 0 ? { nextActions: input.snapshot.nextActions.map((action) => action.text).filter((text) => text.trim().length > 0) } : {},
|
|
1646
|
-
...input.snapshot.blockers && input.snapshot.blockers.length > 0 ? { blockers: input.snapshot.blockers } : {}
|
|
1647
|
-
};
|
|
1648
|
-
}
|
|
1649
|
-
function normalizeSummary(value) {
|
|
1650
|
-
const trimmed = value.trim();
|
|
1651
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
1652
|
-
}
|
|
1653
|
-
|
|
1654
|
-
// src/app/working-memory/repository.ts
|
|
1655
|
-
function isWorkingSetCreateFailure(result) {
|
|
1656
|
-
return "kind" in result;
|
|
1657
|
-
}
|
|
1658
|
-
function isWorkingSetWriteFailure(result) {
|
|
1659
|
-
return "kind" in result;
|
|
1660
|
-
}
|
|
859
|
+
return `${systemPrompt.trimEnd()}
|
|
1661
860
|
|
|
1662
|
-
|
|
1663
|
-
function createFailure(code, message, details) {
|
|
1664
|
-
return {
|
|
1665
|
-
ok: false,
|
|
1666
|
-
code,
|
|
1667
|
-
message,
|
|
1668
|
-
...details ? { details } : {}
|
|
1669
|
-
};
|
|
861
|
+
${trimmedSection}`;
|
|
1670
862
|
}
|
|
1671
|
-
function
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
...workingSetId ? { workingSetId } : {}
|
|
863
|
+
async function resolveSessionStartInjection(scope, baseSystemPrompt, systemPrompt, servicesPromise) {
|
|
864
|
+
let workingInjection;
|
|
865
|
+
try {
|
|
866
|
+
const services = await servicesPromise;
|
|
867
|
+
workingInjection = await resolveWorkingContextInjection(services, scope, `skeln:session-start:${scope.sessionKey}`);
|
|
868
|
+
if (services.skelnConfig.memoryPolicy?.sessionStart?.enabled === false) {
|
|
869
|
+
return composeSkelnBeforeAgentStartResult({
|
|
870
|
+
baseSystemPrompt,
|
|
871
|
+
systemPrompt,
|
|
872
|
+
recallKind: "session_start_recall",
|
|
873
|
+
recallSkippedReason: "memoryPolicy.sessionStart.enabled=false",
|
|
874
|
+
workingInjection
|
|
1684
875
|
});
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
};
|
|
1700
|
-
}
|
|
1701
|
-
if (scope.conversationKey) {
|
|
1702
|
-
return {
|
|
1703
|
-
ok: true,
|
|
1704
|
-
scope: {
|
|
1705
|
-
...scope,
|
|
1706
|
-
scopeKind: "conversation",
|
|
1707
|
-
scopeKey: `conversation:${scope.conversationKey}`
|
|
1708
|
-
}
|
|
1709
|
-
};
|
|
1710
|
-
}
|
|
1711
|
-
if (scope.gitRoot && scope.gitBranch) {
|
|
1712
|
-
return {
|
|
1713
|
-
ok: true,
|
|
1714
|
-
scope: {
|
|
1715
|
-
...scope,
|
|
1716
|
-
scopeKind: "git_branch",
|
|
1717
|
-
scopeKey: buildScopeKey("git_branch", [scope.project, scope.gitRoot, scope.gitBranch])
|
|
1718
|
-
}
|
|
1719
|
-
};
|
|
1720
|
-
}
|
|
1721
|
-
if (scope.gitRoot && scope.cwd) {
|
|
1722
|
-
return {
|
|
1723
|
-
ok: true,
|
|
1724
|
-
scope: {
|
|
1725
|
-
...scope,
|
|
1726
|
-
scopeKind: "git_cwd",
|
|
1727
|
-
scopeKey: buildScopeKey("git_cwd", [scope.project, scope.gitRoot, scope.cwd])
|
|
1728
|
-
}
|
|
1729
|
-
};
|
|
1730
|
-
}
|
|
1731
|
-
return {
|
|
1732
|
-
ok: false,
|
|
1733
|
-
code: "missing_scope",
|
|
1734
|
-
message: "Working memory needs a task, conversation, or git scope."
|
|
1735
|
-
};
|
|
1736
|
-
}
|
|
1737
|
-
function normalizeWorkingScope(input) {
|
|
1738
|
-
const scope = input ?? {};
|
|
1739
|
-
return {
|
|
1740
|
-
...normalizeField("sessionId", scope.sessionId),
|
|
1741
|
-
...normalizeField("gitRoot", scope.gitRoot),
|
|
1742
|
-
...normalizeField("gitBranch", scope.gitBranch),
|
|
1743
|
-
...normalizeField("cwd", scope.cwd),
|
|
1744
|
-
...normalizeField("project", scope.project),
|
|
1745
|
-
...normalizeField("taskId", scope.taskId),
|
|
1746
|
-
...normalizeField("conversationKey", scope.conversationKey)
|
|
1747
|
-
};
|
|
1748
|
-
}
|
|
1749
|
-
function buildScopeKey(prefix, parts) {
|
|
1750
|
-
return [prefix, ...parts.filter((part) => part !== void 0)].join(":");
|
|
1751
|
-
}
|
|
1752
|
-
function normalizeField(key, value) {
|
|
1753
|
-
if (typeof value !== "string") {
|
|
1754
|
-
return {};
|
|
1755
|
-
}
|
|
1756
|
-
const trimmed = value.trim();
|
|
1757
|
-
return trimmed.length > 0 ? { [key]: trimmed } : {};
|
|
1758
|
-
}
|
|
1759
|
-
|
|
1760
|
-
// src/app/working-memory/find-current-set.ts
|
|
1761
|
-
async function lookupCurrentWorkingSets(scope, repository) {
|
|
1762
|
-
const scopeResolution = resolveWorkingScope(scope);
|
|
1763
|
-
if (!scopeResolution.ok) {
|
|
1764
|
-
return createFailure("missing_scope", scopeResolution.message);
|
|
1765
|
-
}
|
|
1766
|
-
const matches = await repository.findCurrentWorkingSets(scopeResolution.scope);
|
|
1767
|
-
if (matches.length > 1) {
|
|
1768
|
-
return createFailure("ambiguous_scope", "Multiple current working sets matched the resolved scope.", {
|
|
1769
|
-
scopeKey: scopeResolution.scope.scopeKey,
|
|
1770
|
-
workingSetIds: matches.map((match) => match.id)
|
|
876
|
+
}
|
|
877
|
+
const sessionStartPatch = await runSessionStart(
|
|
878
|
+
{
|
|
879
|
+
sessionKey: scope.sessionKey,
|
|
880
|
+
policy: resolveSessionStartPolicy(services.skelnConfig.memoryPolicy)
|
|
881
|
+
},
|
|
882
|
+
services.sessionStart
|
|
883
|
+
);
|
|
884
|
+
return composeSkelnBeforeAgentStartResult({
|
|
885
|
+
baseSystemPrompt,
|
|
886
|
+
systemPrompt,
|
|
887
|
+
recallKind: "session_start_recall",
|
|
888
|
+
recallText: formatAgenrSessionStartRecall(sessionStartPatch),
|
|
889
|
+
workingInjection
|
|
1771
890
|
});
|
|
1772
|
-
}
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
const lookup = await lookupCurrentWorkingSets(scope, repository);
|
|
1781
|
-
if (!lookup.ok) {
|
|
1782
|
-
return lookup;
|
|
1783
|
-
}
|
|
1784
|
-
if (lookup.matches.length === 0) {
|
|
1785
|
-
return createFailure("missing_active_set", "No current working set matched the resolved scope.", {
|
|
1786
|
-
scopeKey: lookup.scope.scopeKey
|
|
891
|
+
} catch (error) {
|
|
892
|
+
logInjectionFailure("session-start", scope, error);
|
|
893
|
+
return composeSkelnBeforeAgentStartResult({
|
|
894
|
+
baseSystemPrompt,
|
|
895
|
+
systemPrompt,
|
|
896
|
+
recallKind: "session_start_recall",
|
|
897
|
+
recallFailureReason: error instanceof Error ? error.message : String(error),
|
|
898
|
+
workingInjection
|
|
1787
899
|
});
|
|
1788
900
|
}
|
|
1789
|
-
return {
|
|
1790
|
-
ok: true,
|
|
1791
|
-
workingSet: lookup.matches[0],
|
|
1792
|
-
scope: lookup.scope
|
|
1793
|
-
};
|
|
1794
|
-
}
|
|
1795
|
-
|
|
1796
|
-
// src/app/working-memory/select-working-set.ts
|
|
1797
|
-
async function selectWorkingSet(params, repository) {
|
|
1798
|
-
const workingSetId = params.workingSetId?.trim();
|
|
1799
|
-
if (workingSetId) {
|
|
1800
|
-
const workingSet = await repository.getWorkingSet(workingSetId);
|
|
1801
|
-
if (!workingSet) {
|
|
1802
|
-
return createFailure("not_found", `Working set ${workingSetId} was not found.`, { workingSetId });
|
|
1803
|
-
}
|
|
1804
|
-
return { ok: true, workingSet };
|
|
1805
|
-
}
|
|
1806
|
-
const current = await findUniqueCurrentWorkingSet(params.scope, repository);
|
|
1807
|
-
if (!current.ok) {
|
|
1808
|
-
return current;
|
|
1809
|
-
}
|
|
1810
|
-
return {
|
|
1811
|
-
ok: true,
|
|
1812
|
-
workingSet: current.workingSet,
|
|
1813
|
-
scope: current.scope
|
|
1814
|
-
};
|
|
1815
|
-
}
|
|
1816
|
-
|
|
1817
|
-
// src/app/working-memory/validation.ts
|
|
1818
|
-
function normalizeRequiredString(value, message) {
|
|
1819
|
-
const trimmed = value?.trim();
|
|
1820
|
-
if (!trimmed) {
|
|
1821
|
-
return createFailure("invalid_request", message);
|
|
1822
|
-
}
|
|
1823
|
-
return { ok: true, value: trimmed };
|
|
1824
|
-
}
|
|
1825
|
-
function normalizeExpectedRevision(value) {
|
|
1826
|
-
if (value === void 0 || !Number.isInteger(value) || value < 0) {
|
|
1827
|
-
return createFailure("invalid_request", "expectedRevision must be a non-negative integer.");
|
|
1828
|
-
}
|
|
1829
|
-
return { ok: true, value };
|
|
1830
|
-
}
|
|
1831
|
-
function canDefaultExpectedRevision(source) {
|
|
1832
|
-
return isTrustedHostMutationSource(source);
|
|
1833
|
-
}
|
|
1834
|
-
function resolveExpectedRevision(selectedRevision, providedRevision, source) {
|
|
1835
|
-
if (providedRevision === void 0) {
|
|
1836
|
-
if (!canDefaultExpectedRevision(source)) {
|
|
1837
|
-
return createFailure("invalid_request", "expectedRevision must be a non-negative integer.");
|
|
1838
|
-
}
|
|
1839
|
-
return { ok: true, value: selectedRevision };
|
|
1840
|
-
}
|
|
1841
|
-
return normalizeExpectedRevision(providedRevision);
|
|
1842
|
-
}
|
|
1843
|
-
function validateWorkingBudgetState(budget) {
|
|
1844
|
-
const entries = [
|
|
1845
|
-
["tokenBudget", budget.tokenBudget],
|
|
1846
|
-
["tokenUsed", budget.tokenUsed],
|
|
1847
|
-
["wallClockBudgetSeconds", budget.wallClockBudgetSeconds],
|
|
1848
|
-
["wallClockUsedSeconds", budget.wallClockUsedSeconds],
|
|
1849
|
-
["turnBudget", budget.turnBudget],
|
|
1850
|
-
["turnsUsed", budget.turnsUsed],
|
|
1851
|
-
["requireReviewAfterSeconds", budget.requireReviewAfterSeconds]
|
|
1852
|
-
];
|
|
1853
|
-
for (const [key, value] of entries) {
|
|
1854
|
-
if (value !== void 0 && (!Number.isFinite(value) || value < 0)) {
|
|
1855
|
-
return createFailure("invalid_request", `${key} must be a non-negative finite number.`);
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
return { ok: true };
|
|
1859
901
|
}
|
|
1860
|
-
function
|
|
1861
|
-
const
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
902
|
+
async function resolveBeforeTurnInjection(event, scope, baseSystemPrompt, systemPrompt, context, deps) {
|
|
903
|
+
const services = await deps.servicesPromise;
|
|
904
|
+
const compactionContext = await resolveSkelnCompactionPromptContext(scope, services, deps.compactionPromptTracker);
|
|
905
|
+
const workingInjection = await resolveWorkingContextInjection(services, scope, `skeln:before-turn:${scope.sessionKey}`);
|
|
906
|
+
if (services.skelnConfig.memoryPolicy?.beforeTurn?.enabled === false) {
|
|
907
|
+
return composeSkelnBeforeAgentStartResult({
|
|
908
|
+
baseSystemPrompt,
|
|
909
|
+
systemPrompt,
|
|
910
|
+
recallKind: "before_turn_recall",
|
|
911
|
+
recallSkippedReason: "memoryPolicy.beforeTurn.enabled=false",
|
|
912
|
+
recallText: compactionContext,
|
|
913
|
+
workingInjection
|
|
914
|
+
});
|
|
1869
915
|
}
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
916
|
+
const currentTurnText = normalizePromptText(event.prompt);
|
|
917
|
+
if (!currentTurnText) {
|
|
918
|
+
return composeSkelnBeforeAgentStartResult({
|
|
919
|
+
baseSystemPrompt,
|
|
920
|
+
systemPrompt,
|
|
921
|
+
recallKind: "before_turn_recall",
|
|
922
|
+
recallSkippedReason: "empty turn prompt",
|
|
923
|
+
recallText: compactionContext,
|
|
924
|
+
workingInjection
|
|
925
|
+
});
|
|
1874
926
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
927
|
+
try {
|
|
928
|
+
const branchMessages = extractSkelnBeforeTurnBranchMessages(context.sessionManager.getBranch());
|
|
929
|
+
const beforeTurnPatch = await runBeforeTurn(
|
|
930
|
+
{
|
|
931
|
+
sessionKey: scope.sessionKey,
|
|
932
|
+
currentTurnText,
|
|
933
|
+
recentTurns: extractRecentTurnsFromMessages(branchMessages),
|
|
934
|
+
policy: resolveBeforeTurnPolicy(services.skelnConfig.memoryPolicy)
|
|
935
|
+
},
|
|
936
|
+
services.beforeTurn
|
|
1884
937
|
);
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
status: selection.workingSet.status
|
|
938
|
+
return composeSkelnBeforeAgentStartResult({
|
|
939
|
+
baseSystemPrompt,
|
|
940
|
+
systemPrompt,
|
|
941
|
+
recallKind: "before_turn_recall",
|
|
942
|
+
recallText: mergeInjectionContent(compactionContext, formatAgenrBeforeTurnRecall(beforeTurnPatch)),
|
|
943
|
+
workingInjection
|
|
944
|
+
});
|
|
945
|
+
} catch (error) {
|
|
946
|
+
logInjectionFailure("before-turn", scope, error);
|
|
947
|
+
return composeSkelnBeforeAgentStartResult({
|
|
948
|
+
baseSystemPrompt,
|
|
949
|
+
systemPrompt,
|
|
950
|
+
recallKind: "before_turn_recall",
|
|
951
|
+
recallFailureReason: error instanceof Error ? error.message : String(error),
|
|
952
|
+
recallText: compactionContext,
|
|
953
|
+
workingInjection
|
|
1902
954
|
});
|
|
1903
955
|
}
|
|
1904
|
-
const events = await repository.listWorkingEvents(selection.workingSet.id, CLOSE_EVENT_HISTORY_LIMIT);
|
|
1905
|
-
const terminalStatus = resolveCloseTerminalStatus(params.closeMode);
|
|
1906
|
-
const closePayload = buildWorkingCloseSnapshot({
|
|
1907
|
-
workingSetId: selection.workingSet.id,
|
|
1908
|
-
snapshot: selection.workingSet.snapshot,
|
|
1909
|
-
currentRevision: selection.workingSet.revision,
|
|
1910
|
-
closeReason: closeReason.value,
|
|
1911
|
-
createEpisode: params.createEpisode,
|
|
1912
|
-
eventSequences: events.map((event) => event.sequence),
|
|
1913
|
-
now: timestamp
|
|
1914
|
-
});
|
|
1915
|
-
const writeResult = await repository.updateWorkingSet({
|
|
1916
|
-
workingSetId: selection.workingSet.id,
|
|
1917
|
-
expectedRevision: expectedRevision.value,
|
|
1918
|
-
eventType: terminalStatus,
|
|
1919
|
-
payload: {
|
|
1920
|
-
closeReason: closeReason.value,
|
|
1921
|
-
closeMode: params.closeMode ?? "close",
|
|
1922
|
-
candidates: closePayload.candidates,
|
|
1923
|
-
sourceRef: `working_set:${selection.workingSet.id}#rev:${selection.workingSet.revision}`
|
|
1924
|
-
},
|
|
1925
|
-
status: terminalStatus,
|
|
1926
|
-
snapshot: closePayload.snapshot,
|
|
1927
|
-
title: selection.workingSet.title,
|
|
1928
|
-
objective: selection.workingSet.snapshot.objective,
|
|
1929
|
-
closedAt: timestamp,
|
|
1930
|
-
closeReason: closeReason.value,
|
|
1931
|
-
actor: params.actor,
|
|
1932
|
-
source: params.source,
|
|
1933
|
-
now: timestamp
|
|
1934
|
-
});
|
|
1935
|
-
return toCloseResult(selection.workingSet.id, writeResult, closePayload.candidates);
|
|
1936
956
|
}
|
|
1937
|
-
function
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
957
|
+
function composeSkelnBeforeAgentStartResult(input) {
|
|
958
|
+
const recallText = input.recallText?.trim();
|
|
959
|
+
const memoryTrace = buildBeforeAgentStartMemoryTrace({
|
|
960
|
+
baseSystemPrompt: input.baseSystemPrompt,
|
|
961
|
+
systemPrompt: input.systemPrompt,
|
|
962
|
+
recallKind: input.recallKind,
|
|
963
|
+
recallText,
|
|
964
|
+
recallSkippedReason: input.recallSkippedReason,
|
|
965
|
+
recallFailureReason: input.recallFailureReason,
|
|
966
|
+
workingContextTrace: input.workingInjection?.trace
|
|
967
|
+
});
|
|
1941
968
|
return {
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
969
|
+
systemPrompt: input.systemPrompt,
|
|
970
|
+
...recallText ? { message: buildAgenrSkelnInjectionMessage(recallText) } : {},
|
|
971
|
+
...input.workingInjection?.transientMessages ? { transientMessages: input.workingInjection.transientMessages } : {},
|
|
972
|
+
...input.workingInjection?.workingContextAudit ? { workingContextAudit: input.workingInjection.workingContextAudit } : {},
|
|
973
|
+
...memoryTrace.length > 0 ? { memoryTrace } : {}
|
|
1947
974
|
};
|
|
1948
975
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
return snapshot?.goalGeneration ?? INITIAL_GOAL_GENERATION;
|
|
1954
|
-
}
|
|
1955
|
-
function nextGoalGenerationAfterObjectiveChange(snapshot, nextObjective) {
|
|
1956
|
-
if (snapshot.objective === nextObjective) {
|
|
1957
|
-
return readGoalGeneration(snapshot);
|
|
976
|
+
async function resolveWorkingContextInjection(services, scope, sourceRef) {
|
|
977
|
+
const gate = resolveWorkingContextGate(services.capabilities.workingMemory, services.skelnConfig.memoryPolicy);
|
|
978
|
+
if (!gate.ok) {
|
|
979
|
+
return { trace: traceMemorySkipped("working_context", gate.reason) };
|
|
1958
980
|
}
|
|
1959
|
-
return
|
|
981
|
+
return loadWorkingContextProjection(services, scope, sourceRef);
|
|
1960
982
|
}
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
const workingSet = await repository.getWorkingSet(workingSetId);
|
|
1967
|
-
if (workingSet) {
|
|
1968
|
-
return createFailure("active_set_exists", "A working set is already active for this scope.", {
|
|
1969
|
-
workingSetId: workingSet.id,
|
|
1970
|
-
scopeKey: workingSet.scopeKey
|
|
1971
|
-
});
|
|
1972
|
-
}
|
|
1973
|
-
return createFailure("not_found", `Working set ${workingSetId} was not found.`, { workingSetId });
|
|
1974
|
-
}
|
|
1975
|
-
const lookup = await lookupCurrentWorkingSets(params.scope, repository);
|
|
1976
|
-
if (!lookup.ok) {
|
|
1977
|
-
return lookup;
|
|
1978
|
-
}
|
|
1979
|
-
if (lookup.matches.length === 1) {
|
|
1980
|
-
const existing = lookup.matches[0];
|
|
1981
|
-
return createFailure("active_set_exists", "A working set already exists for this scope.", {
|
|
1982
|
-
workingSetId: existing.id,
|
|
1983
|
-
scopeKey: lookup.scope.scopeKey
|
|
983
|
+
async function loadWorkingContextProjection(services, scope, sourceRef) {
|
|
984
|
+
try {
|
|
985
|
+
const projection = await services.workingMemory.renderProjection({
|
|
986
|
+
sourceRef,
|
|
987
|
+
scope: toWorkingScopeFromSkelnSession(scope)
|
|
1984
988
|
});
|
|
989
|
+
if (!shouldInjectWorkingContext(projection)) {
|
|
990
|
+
const reason = projection.renderMode !== "full" ? "working projection stub" : "empty working projection";
|
|
991
|
+
return { trace: traceMemorySkipped("working_context", reason) };
|
|
992
|
+
}
|
|
993
|
+
const workingContextAudit = toWorkingContextAuditPointer(projection);
|
|
994
|
+
return {
|
|
995
|
+
transientMessages: [buildAgenrSkelnInjectionMessage(projection.content)],
|
|
996
|
+
...workingContextAudit ? { workingContextAudit } : {},
|
|
997
|
+
trace: traceWorkingContextInjected(workingContextAudit, projection.content)
|
|
998
|
+
};
|
|
999
|
+
} catch (error) {
|
|
1000
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1001
|
+
console.warn(`[agenr] working-context projection failed for session=${scope.sessionId} key=${scope.sessionKey}: ${message}`);
|
|
1002
|
+
return { trace: traceMemoryFailed("working_context", message) };
|
|
1985
1003
|
}
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
};
|
|
1004
|
+
}
|
|
1005
|
+
function logInjectionFailure(phase, scope, error) {
|
|
1006
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1007
|
+
console.warn(`[agenr] ${phase} recall failed for session=${scope.sessionId} key=${scope.sessionKey}: ${message}`);
|
|
1990
1008
|
}
|
|
1991
1009
|
|
|
1992
|
-
// src/
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
scope,
|
|
2013
|
-
title: operation.title,
|
|
2014
|
-
objective: operation.objective,
|
|
2015
|
-
status: "active",
|
|
2016
|
-
snapshot: {
|
|
2017
|
-
goalGeneration: INITIAL_GOAL_GENERATION,
|
|
2018
|
-
objective: operation.objective,
|
|
2019
|
-
continuation: { policy: params.continuationPolicy ?? "manual" },
|
|
2020
|
-
...params.initialBudget ? { budgets: params.initialBudget } : {},
|
|
2021
|
-
lastMaterialChange: updateReason.value
|
|
2022
|
-
},
|
|
2023
|
-
actor: params.actor,
|
|
2024
|
-
source: params.source,
|
|
2025
|
-
sourceLabel,
|
|
2026
|
-
sessionId: scope.sessionId,
|
|
2027
|
-
now: timestamp
|
|
2028
|
-
});
|
|
2029
|
-
if (isWorkingSetCreateFailure(created)) {
|
|
2030
|
-
return createFailure("active_set_exists", "A working set already exists for this scope.", {
|
|
2031
|
-
scopeKey: created.scopeKey
|
|
1010
|
+
// src/adapters/skeln/hooks/subagent-findings.ts
|
|
1011
|
+
var SUBAGENT_OUTCOME_MAX_CHARS = 3e3;
|
|
1012
|
+
var SUBAGENT_RESULT_LIMIT = 6;
|
|
1013
|
+
async function recordSkelnSubagentFindings(servicesPromise, resolveScope, context, event) {
|
|
1014
|
+
const commandNote = buildSkelnSubagentCommandNote(event, (/* @__PURE__ */ new Date()).toISOString());
|
|
1015
|
+
if (!commandNote) {
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
try {
|
|
1019
|
+
const [services, scope] = await Promise.all([servicesPromise, resolveScope(context)]);
|
|
1020
|
+
const result = await services.workingMemory.run({
|
|
1021
|
+
action: "update",
|
|
1022
|
+
scope: toWorkingScopeFromSkelnSession(scope),
|
|
1023
|
+
operation: {
|
|
1024
|
+
type: "add_command_note",
|
|
1025
|
+
command: commandNote
|
|
1026
|
+
},
|
|
1027
|
+
updateReason: "Recorded bounded subagent findings on the parent working set.",
|
|
1028
|
+
actor: "runtime",
|
|
1029
|
+
source: "lifecycle_hook"
|
|
2032
1030
|
});
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
ok: true,
|
|
2036
|
-
action: "create",
|
|
2037
|
-
workingSet: created.workingSet,
|
|
2038
|
-
event: created.event,
|
|
2039
|
-
projection: createToolSuccessProjection(created.workingSet, "create", timestamp)
|
|
2040
|
-
};
|
|
2041
|
-
}
|
|
2042
|
-
|
|
2043
|
-
// src/app/working-memory/handlers/get.ts
|
|
2044
|
-
async function handleGet(params, repository, timestamp) {
|
|
2045
|
-
const selection = await selectWorkingSet(params, repository);
|
|
2046
|
-
if (!selection.ok) {
|
|
2047
|
-
return selection;
|
|
2048
|
-
}
|
|
2049
|
-
const events = params.includeEvents ? await repository.listWorkingEvents(selection.workingSet.id, normalizeEventLimit(params.eventLimit)) : void 0;
|
|
2050
|
-
return {
|
|
2051
|
-
ok: true,
|
|
2052
|
-
action: "get",
|
|
2053
|
-
workingSet: selection.workingSet,
|
|
2054
|
-
...events ? { events } : {},
|
|
2055
|
-
projection: createToolSuccessProjection(selection.workingSet, "get", timestamp)
|
|
2056
|
-
};
|
|
2057
|
-
}
|
|
2058
|
-
|
|
2059
|
-
// src/app/working-memory/handlers/list.ts
|
|
2060
|
-
async function handleList(params, repository) {
|
|
2061
|
-
const scopeResolution = params.scope ? resolveWorkingScope(params.scope) : void 0;
|
|
2062
|
-
if (scopeResolution && !scopeResolution.ok) {
|
|
2063
|
-
return createFailure("missing_scope", scopeResolution.message);
|
|
2064
|
-
}
|
|
2065
|
-
const workingSets = await repository.listWorkingSets({
|
|
2066
|
-
...scopeResolution?.ok ? { scope: scopeResolution.scope } : {},
|
|
2067
|
-
limit: normalizeListLimit(params.listLimit)
|
|
2068
|
-
});
|
|
2069
|
-
return {
|
|
2070
|
-
ok: true,
|
|
2071
|
-
action: "list",
|
|
2072
|
-
workingSets
|
|
2073
|
-
};
|
|
2074
|
-
}
|
|
2075
|
-
|
|
2076
|
-
// src/app/working-memory/apply-operation.ts
|
|
2077
|
-
function applyOperation(record, operation, updateReason) {
|
|
2078
|
-
const snapshot = { ...record.snapshot, lastMaterialChange: updateReason };
|
|
2079
|
-
let status = record.status;
|
|
2080
|
-
let title = record.title;
|
|
2081
|
-
let objective = record.snapshot.objective;
|
|
2082
|
-
switch (operation.type) {
|
|
2083
|
-
case "set_objective":
|
|
2084
|
-
snapshot.goalGeneration = nextGoalGenerationAfterObjectiveChange(snapshot, operation.objective);
|
|
2085
|
-
snapshot.objective = operation.objective;
|
|
2086
|
-
objective = operation.objective;
|
|
2087
|
-
title = operation.title ?? title;
|
|
2088
|
-
break;
|
|
2089
|
-
case "replace_plan":
|
|
2090
|
-
snapshot.currentPlan = operation.currentPlan;
|
|
2091
|
-
snapshot.nextActions = operation.nextActions;
|
|
2092
|
-
break;
|
|
2093
|
-
case "merge_checkpoint":
|
|
2094
|
-
snapshot.checkpoint = operation.checkpoint;
|
|
2095
|
-
if (operation.checkpoint.nextActions) {
|
|
2096
|
-
snapshot.nextActions = operation.checkpoint.nextActions.map((text) => ({ text, status: "pending" }));
|
|
2097
|
-
}
|
|
2098
|
-
if (operation.checkpoint.blockers) {
|
|
2099
|
-
snapshot.blockers = operation.checkpoint.blockers;
|
|
2100
|
-
}
|
|
2101
|
-
break;
|
|
2102
|
-
case "add_file_note":
|
|
2103
|
-
snapshot.files = [...snapshot.files ?? [], operation.file];
|
|
2104
|
-
break;
|
|
2105
|
-
case "add_command_note":
|
|
2106
|
-
snapshot.commands = [...snapshot.commands ?? [], operation.command];
|
|
2107
|
-
break;
|
|
2108
|
-
case "record_decision":
|
|
2109
|
-
snapshot.decisions = [...snapshot.decisions ?? [], operation.decision];
|
|
2110
|
-
break;
|
|
2111
|
-
case "record_assumption":
|
|
2112
|
-
snapshot.assumptions = [...snapshot.assumptions ?? [], operation.assumption];
|
|
2113
|
-
break;
|
|
2114
|
-
case "set_next_actions":
|
|
2115
|
-
snapshot.nextActions = operation.nextActions;
|
|
2116
|
-
break;
|
|
2117
|
-
case "set_status":
|
|
2118
|
-
if (isCloseManagedStatus(operation.status)) {
|
|
2119
|
-
return createFailure("invalid_request", "Use agenr_work close for closed or abandoned terminal states.");
|
|
2120
|
-
}
|
|
2121
|
-
status = operation.status;
|
|
2122
|
-
break;
|
|
2123
|
-
case "add_candidate":
|
|
2124
|
-
snapshot.candidates = [...snapshot.candidates ?? [], operation.candidate];
|
|
2125
|
-
break;
|
|
2126
|
-
case "configure_budget": {
|
|
2127
|
-
const budget = mergeBudgetState(snapshot.budgets, operation.budget);
|
|
2128
|
-
if (!budget.ok) {
|
|
2129
|
-
return budget;
|
|
2130
|
-
}
|
|
2131
|
-
const limited = applyBudgetLimitedStatus(status, budget.value, updateReason);
|
|
2132
|
-
snapshot.budgets = limited.budgets;
|
|
2133
|
-
status = limited.status;
|
|
2134
|
-
break;
|
|
2135
|
-
}
|
|
2136
|
-
case "account_usage": {
|
|
2137
|
-
const budget = applyUsageDelta(snapshot.budgets, operation.usage, updateReason);
|
|
2138
|
-
if (!budget.ok) {
|
|
2139
|
-
return budget;
|
|
2140
|
-
}
|
|
2141
|
-
const limited = applyBudgetLimitedStatus(status, budget.value, operation.usage.recordedAt ?? updateReason);
|
|
2142
|
-
snapshot.budgets = limited.budgets;
|
|
2143
|
-
status = limited.status;
|
|
2144
|
-
break;
|
|
1031
|
+
if (!result.ok && !isExpectedSubagentFindingSkip(result.code)) {
|
|
1032
|
+
console.warn(`[agenr] subagent findings working-memory update failed: ${result.message}`);
|
|
2145
1033
|
}
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
...snapshot.continuation,
|
|
2149
|
-
policy: operation.policy,
|
|
2150
|
-
...operation.resumeAfter !== void 0 ? { resumeAfter: operation.resumeAfter } : {},
|
|
2151
|
-
...operation.staleAfter !== void 0 ? { staleAfter: operation.staleAfter } : {},
|
|
2152
|
-
...operation.stopReason !== void 0 ? { stopReason: operation.stopReason } : {}
|
|
2153
|
-
});
|
|
2154
|
-
break;
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
console.warn(`[agenr] subagent findings capture failed: ${formatErrorMessage(error)}`);
|
|
2155
1036
|
}
|
|
2156
|
-
return {
|
|
2157
|
-
ok: true,
|
|
2158
|
-
snapshot,
|
|
2159
|
-
status,
|
|
2160
|
-
title,
|
|
2161
|
-
objective
|
|
2162
|
-
};
|
|
2163
1037
|
}
|
|
2164
|
-
function
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
return validation;
|
|
1038
|
+
function buildSkelnSubagentCommandNote(event, observedAt) {
|
|
1039
|
+
if (event.toolName !== "subagent" || event.isError) {
|
|
1040
|
+
return void 0;
|
|
2168
1041
|
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
...current ?? {},
|
|
2173
|
-
...update
|
|
2174
|
-
})
|
|
2175
|
-
};
|
|
2176
|
-
}
|
|
2177
|
-
function applyUsageDelta(current, usage, limitedAt) {
|
|
2178
|
-
const validation = validateWorkingUsageDelta(usage);
|
|
2179
|
-
if (!validation.ok) {
|
|
2180
|
-
return validation;
|
|
1042
|
+
const details = isRecord(event.details) ? event.details : void 0;
|
|
1043
|
+
if (!details) {
|
|
1044
|
+
return void 0;
|
|
2181
1045
|
}
|
|
2182
|
-
const
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
};
|
|
2188
|
-
const limitReason = resolveBudgetLimitReason(next);
|
|
2189
|
-
return {
|
|
2190
|
-
ok: true,
|
|
2191
|
-
value: pruneBudget({
|
|
2192
|
-
...next,
|
|
2193
|
-
...limitReason ? { limitReason, limitedAt: usage.recordedAt ?? limitedAt } : {}
|
|
2194
|
-
})
|
|
2195
|
-
};
|
|
2196
|
-
}
|
|
2197
|
-
function applyBudgetLimitedStatus(currentStatus, budget, limitedAt) {
|
|
2198
|
-
const limitReason = resolveBudgetLimitReason(budget);
|
|
2199
|
-
if (!limitReason || !budget) {
|
|
2200
|
-
return { status: currentStatus, budgets: budget };
|
|
1046
|
+
const mode = readOptionalTrimmedString(details.mode) ?? "unknown";
|
|
1047
|
+
const artifactPath = readOptionalTrimmedString(details.artifactPath);
|
|
1048
|
+
const results = Array.isArray(details.results) ? details.results.flatMap(normalizeDelegationResult).slice(0, SUBAGENT_RESULT_LIMIT) : [];
|
|
1049
|
+
if (results.length === 0 && !artifactPath) {
|
|
1050
|
+
return void 0;
|
|
2201
1051
|
}
|
|
1052
|
+
const resultLines = results.map(
|
|
1053
|
+
(result) => `- ${result.agent}${result.name ? `/${result.name}` : ""}: ${result.status}${result.summary ? ` - ${result.summary}` : ""}`
|
|
1054
|
+
);
|
|
1055
|
+
const outcome = truncateText(
|
|
1056
|
+
[
|
|
1057
|
+
`mode=${mode}`,
|
|
1058
|
+
...resultLines,
|
|
1059
|
+
...artifactPath ? [`artifact=${artifactPath}`] : [],
|
|
1060
|
+
...results.length >= SUBAGENT_RESULT_LIMIT ? ["additional subagent results omitted"] : []
|
|
1061
|
+
].join("\n"),
|
|
1062
|
+
SUBAGENT_OUTCOME_MAX_CHARS
|
|
1063
|
+
);
|
|
2202
1064
|
return {
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
limitReason: budget.limitReason ?? limitReason,
|
|
2207
|
-
limitedAt: budget.limitedAt ?? limitedAt
|
|
2208
|
-
})
|
|
1065
|
+
command: `subagent ${mode}`,
|
|
1066
|
+
outcome,
|
|
1067
|
+
observedAt
|
|
2209
1068
|
};
|
|
2210
1069
|
}
|
|
2211
|
-
function
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
if (budget.tokenBudget !== void 0 && (budget.tokenUsed ?? 0) >= budget.tokenBudget) {
|
|
2216
|
-
return "token";
|
|
2217
|
-
}
|
|
2218
|
-
if (budget.wallClockBudgetSeconds !== void 0 && (budget.wallClockUsedSeconds ?? 0) >= budget.wallClockBudgetSeconds) {
|
|
2219
|
-
return "wall_clock";
|
|
1070
|
+
function normalizeDelegationResult(value) {
|
|
1071
|
+
const record = isRecord(value) ? value : void 0;
|
|
1072
|
+
if (!record) {
|
|
1073
|
+
return [];
|
|
2220
1074
|
}
|
|
2221
|
-
|
|
2222
|
-
|
|
1075
|
+
const agent = readOptionalTrimmedString(record.agent);
|
|
1076
|
+
const status = readOptionalTrimmedString(record.status);
|
|
1077
|
+
if (!agent || !status) {
|
|
1078
|
+
return [];
|
|
2223
1079
|
}
|
|
2224
|
-
|
|
1080
|
+
const stdout = readOptionalTrimmedString(record.stdout);
|
|
1081
|
+
const stderr = readOptionalTrimmedString(record.stderr);
|
|
1082
|
+
const name = readOptionalTrimmedString(record.name);
|
|
1083
|
+
const summary = firstMeaningfulLine(stdout ?? stderr);
|
|
1084
|
+
return [
|
|
1085
|
+
{
|
|
1086
|
+
agent,
|
|
1087
|
+
...name ? { name } : {},
|
|
1088
|
+
status,
|
|
1089
|
+
...summary ? { summary } : {}
|
|
1090
|
+
}
|
|
1091
|
+
];
|
|
2225
1092
|
}
|
|
2226
|
-
function
|
|
2227
|
-
|
|
2228
|
-
return current;
|
|
2229
|
-
}
|
|
2230
|
-
return (current ?? 0) + delta;
|
|
1093
|
+
function isExpectedSubagentFindingSkip(code) {
|
|
1094
|
+
return code === "feature_disabled" || code === "missing_active_set" || code === "missing_scope" || code === "misconfigured";
|
|
2231
1095
|
}
|
|
2232
|
-
function
|
|
2233
|
-
|
|
1096
|
+
function firstMeaningfulLine(value) {
|
|
1097
|
+
const line = value?.split(/\r?\n/u).map((part) => part.trim()).find((part) => part.length > 0);
|
|
1098
|
+
return line ? truncateText(line, 500) : void 0;
|
|
2234
1099
|
}
|
|
2235
|
-
function
|
|
2236
|
-
if (
|
|
2237
|
-
return
|
|
1100
|
+
function truncateText(value, maxChars) {
|
|
1101
|
+
if (value.length <= maxChars) {
|
|
1102
|
+
return value;
|
|
2238
1103
|
}
|
|
2239
|
-
|
|
2240
|
-
return Object.keys(pruned).length > 0 ? pruned : void 0;
|
|
1104
|
+
return `${value.slice(0, Math.max(0, maxChars - 15)).trimEnd()} [truncated]`;
|
|
2241
1105
|
}
|
|
2242
1106
|
|
|
2243
|
-
// src/
|
|
2244
|
-
function
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
const writeResult2 = await repository.patchWorkingSetUsage({
|
|
2250
|
-
workingSetId: input.workingSetId,
|
|
2251
|
-
expectedRevision: input.expectedRevision,
|
|
2252
|
-
status: input.applied.status,
|
|
2253
|
-
snapshot: input.applied.snapshot,
|
|
2254
|
-
title: input.applied.title,
|
|
2255
|
-
objective: input.applied.objective,
|
|
2256
|
-
now: input.now
|
|
2257
|
-
});
|
|
2258
|
-
if (isWorkingSetWriteFailure(writeResult2)) {
|
|
2259
|
-
return writeResult2;
|
|
2260
|
-
}
|
|
2261
|
-
return {
|
|
2262
|
-
type: "usage_patch",
|
|
2263
|
-
workingSet: writeResult2.workingSet
|
|
2264
|
-
};
|
|
2265
|
-
}
|
|
2266
|
-
const writeResult = await repository.updateWorkingSet({
|
|
2267
|
-
workingSetId: input.workingSetId,
|
|
2268
|
-
expectedRevision: input.expectedRevision,
|
|
2269
|
-
eventType: input.operation.type,
|
|
2270
|
-
payload: {
|
|
2271
|
-
operation: input.operation,
|
|
2272
|
-
updateReason: input.updateReason
|
|
2273
|
-
},
|
|
2274
|
-
status: input.applied.status,
|
|
2275
|
-
snapshot: input.applied.snapshot,
|
|
2276
|
-
title: input.applied.title,
|
|
2277
|
-
objective: input.applied.objective,
|
|
2278
|
-
actor: input.actor,
|
|
2279
|
-
source: input.source,
|
|
2280
|
-
now: input.now
|
|
1107
|
+
// src/adapters/skeln/episode/shutdown-episode-write.ts
|
|
1108
|
+
function buildSkelnSessionShutdownEpisodeWork(params) {
|
|
1109
|
+
const target = resolveSkelnSessionEpisodeTarget(params.context);
|
|
1110
|
+
const shutdownWork = params.event.reason === "quit" ? writeScopedSkelnShutdownEpisode(params.servicesPromise, target, params.logger).finally(() => closeSkelnServicesAfterShutdown(params.servicesPromise)) : writeScopedSkelnShutdownEpisode(params.servicesPromise, target, params.logger);
|
|
1111
|
+
return shutdownWork.catch((error) => {
|
|
1112
|
+
logSkelnShutdownEpisodeFailure(error, params.logger);
|
|
2281
1113
|
});
|
|
2282
|
-
if (isWorkingSetWriteFailure(writeResult)) {
|
|
2283
|
-
return writeResult;
|
|
2284
|
-
}
|
|
2285
|
-
return {
|
|
2286
|
-
type: "semantic",
|
|
2287
|
-
workingSet: writeResult.workingSet,
|
|
2288
|
-
event: writeResult.event
|
|
2289
|
-
};
|
|
2290
1114
|
}
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
return
|
|
2296
|
-
}
|
|
2297
|
-
const selection = await selectWorkingSet(params, repository);
|
|
2298
|
-
if (!selection.ok) {
|
|
2299
|
-
if (selection.code === "missing_active_set") {
|
|
2300
|
-
return {
|
|
2301
|
-
ok: true,
|
|
2302
|
-
action: "prepare_external_goal_mutation",
|
|
2303
|
-
prepared: false,
|
|
2304
|
-
events: []
|
|
2305
|
-
};
|
|
2306
|
-
}
|
|
2307
|
-
return selection;
|
|
2308
|
-
}
|
|
2309
|
-
if (params.requireCheckpoint && !params.checkpoint && !selection.workingSet.snapshot.checkpoint) {
|
|
2310
|
-
return createFailure("invalid_request", `Active goal requires a checkpoint before ${params.mutationKind}.`, {
|
|
2311
|
-
workingSetId: selection.workingSet.id,
|
|
2312
|
-
revision: selection.workingSet.revision,
|
|
2313
|
-
mutationKind: params.mutationKind
|
|
2314
|
-
});
|
|
1115
|
+
function scheduleSkelnSessionShutdownEpisodeWrite(params) {
|
|
1116
|
+
const work = buildSkelnSessionShutdownEpisodeWork(params);
|
|
1117
|
+
if (params.event.reason === "quit" && params.event.deferWork) {
|
|
1118
|
+
params.event.deferWork(work);
|
|
1119
|
+
return Promise.resolve();
|
|
2315
1120
|
}
|
|
2316
|
-
if (
|
|
2317
|
-
return
|
|
2318
|
-
ok: true,
|
|
2319
|
-
action: "prepare_external_goal_mutation",
|
|
2320
|
-
prepared: true,
|
|
2321
|
-
workingSet: selection.workingSet,
|
|
2322
|
-
events: []
|
|
2323
|
-
};
|
|
1121
|
+
if (params.event.reason === "quit") {
|
|
1122
|
+
return work;
|
|
2324
1123
|
}
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
applied,
|
|
2338
|
-
actor: params.actor,
|
|
2339
|
-
source: params.source,
|
|
2340
|
-
now: timestamp
|
|
2341
|
-
});
|
|
2342
|
-
if (isAppliedWorkingSetCommitFailure(writeResult)) {
|
|
2343
|
-
return writeFailureToResult(workingSet.id, writeResult);
|
|
2344
|
-
}
|
|
2345
|
-
workingSet = writeResult.workingSet;
|
|
2346
|
-
if (writeResult.type === "semantic") {
|
|
2347
|
-
events.push(writeResult.event);
|
|
2348
|
-
}
|
|
1124
|
+
void work;
|
|
1125
|
+
return Promise.resolve();
|
|
1126
|
+
}
|
|
1127
|
+
function logSkelnShutdownEpisodeFailure(error, logger) {
|
|
1128
|
+
const log = logger ?? console;
|
|
1129
|
+
log.warn(`[agenr] skeln shutdown episode failed: ${formatErrorMessage(error)}`);
|
|
1130
|
+
}
|
|
1131
|
+
async function closeSkelnServicesAfterShutdown(servicesPromise) {
|
|
1132
|
+
try {
|
|
1133
|
+
const services = await servicesPromise;
|
|
1134
|
+
await services.close();
|
|
1135
|
+
} catch {
|
|
2349
1136
|
}
|
|
2350
|
-
return {
|
|
2351
|
-
ok: true,
|
|
2352
|
-
action: "prepare_external_goal_mutation",
|
|
2353
|
-
prepared: true,
|
|
2354
|
-
workingSet,
|
|
2355
|
-
events
|
|
2356
|
-
};
|
|
2357
1137
|
}
|
|
2358
|
-
function
|
|
2359
|
-
const
|
|
2360
|
-
if (
|
|
2361
|
-
|
|
2362
|
-
operation: { type: "account_usage", usage: params.usage },
|
|
2363
|
-
updateReason: params.updateReason ?? `Accounted progress before external goal mutation (${params.mutationKind}).`
|
|
2364
|
-
});
|
|
1138
|
+
async function writeScopedSkelnShutdownEpisode(servicesPromise, target, logger) {
|
|
1139
|
+
const services = await servicesPromise;
|
|
1140
|
+
if (!isPluginEpisodeWriteEnabled(services.skelnConfig.memoryPolicy)) {
|
|
1141
|
+
return;
|
|
2365
1142
|
}
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
1143
|
+
await withEpisodeWriteGuard({ port: services.dreaming, dbPath: services.config.dbPath }, async () => writeSkelnShutdownEpisode({ target, services, logger }));
|
|
1144
|
+
await runSkelnPostSessionLightDream(services, logger);
|
|
1145
|
+
}
|
|
1146
|
+
async function runSkelnPostSessionLightDream(services, logger) {
|
|
1147
|
+
const log = logger ?? console;
|
|
1148
|
+
try {
|
|
1149
|
+
const result = await maybeRunLightDream(
|
|
1150
|
+
{ trigger: "post_session" },
|
|
1151
|
+
{
|
|
1152
|
+
port: services.dreaming,
|
|
1153
|
+
dbPath: services.config.dbPath,
|
|
1154
|
+
config: services.agenrConfig,
|
|
1155
|
+
embedding: services.embedding,
|
|
1156
|
+
...services.claimExtraction ? { createClaimExtractionLlm: () => services.claimExtraction.llm } : {}
|
|
1157
|
+
}
|
|
1158
|
+
);
|
|
1159
|
+
if (result.status === "ran") {
|
|
1160
|
+
log.info(`[agenr] skeln shutdown light dream completed run=${result.result.runId}`);
|
|
1161
|
+
} else if (result.reason === "run_in_progress" || result.reason === "episode_write_in_progress") {
|
|
1162
|
+
log.info(`[agenr] skeln shutdown light dream skipped reason=${result.reason}`);
|
|
1163
|
+
}
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
log.warn(`[agenr] skeln shutdown light dream failed: ${formatErrorMessage(error)}`);
|
|
2371
1166
|
}
|
|
2372
|
-
return operations;
|
|
2373
1167
|
}
|
|
2374
1168
|
|
|
2375
|
-
// src/app/
|
|
2376
|
-
|
|
2377
|
-
const
|
|
2378
|
-
|
|
2379
|
-
return createFailure("invalid_request", "agenr_work update requires a typed operation.");
|
|
2380
|
-
}
|
|
2381
|
-
if (isTrustedHostOnlyWorkingOperation(operation.type) && !isTrustedHostMutationSource(params.source)) {
|
|
2382
|
-
return createFailure("invalid_request", `${operation.type} is reserved for trusted host runtime paths.`);
|
|
2383
|
-
}
|
|
2384
|
-
const updateReason = normalizeRequiredString(params.updateReason, "agenr_work update requires updateReason.");
|
|
2385
|
-
if (!updateReason.ok) {
|
|
2386
|
-
return updateReason;
|
|
2387
|
-
}
|
|
2388
|
-
const selection = await selectWorkingSet(params, repository);
|
|
2389
|
-
if (!selection.ok) {
|
|
2390
|
-
return selection;
|
|
2391
|
-
}
|
|
2392
|
-
const expectedRevision = resolveExpectedRevision(selection.workingSet.revision, params.expectedRevision, params.source);
|
|
2393
|
-
if (!expectedRevision.ok) {
|
|
2394
|
-
return expectedRevision;
|
|
2395
|
-
}
|
|
2396
|
-
if (!isMutableWorkingSetStatus(selection.workingSet.status)) {
|
|
2397
|
-
return createFailure("terminal_status", `Working set ${selection.workingSet.id} is already ${selection.workingSet.status}.`, {
|
|
2398
|
-
workingSetId: selection.workingSet.id,
|
|
2399
|
-
status: selection.workingSet.status
|
|
2400
|
-
});
|
|
2401
|
-
}
|
|
2402
|
-
const applied = applyOperation(selection.workingSet, operation, updateReason.value);
|
|
2403
|
-
if (!applied.ok) {
|
|
2404
|
-
return applied;
|
|
2405
|
-
}
|
|
2406
|
-
const writeResult = await commitAppliedWorkingSetChange(repository, {
|
|
2407
|
-
workingSetId: selection.workingSet.id,
|
|
2408
|
-
expectedRevision: expectedRevision.value,
|
|
2409
|
-
operation,
|
|
2410
|
-
updateReason: updateReason.value,
|
|
2411
|
-
applied,
|
|
2412
|
-
actor: params.actor,
|
|
2413
|
-
source: params.source,
|
|
2414
|
-
now: timestamp
|
|
2415
|
-
});
|
|
2416
|
-
if (isAppliedWorkingSetCommitFailure(writeResult)) {
|
|
2417
|
-
return writeFailureToResult(selection.workingSet.id, writeResult);
|
|
2418
|
-
}
|
|
1169
|
+
// src/app/features/capabilities.ts
|
|
1170
|
+
function resolveRuntimeCapabilities(featureFlags, input = {}) {
|
|
1171
|
+
const sessionMemoryFlagEnabled = featureFlags.sessionTreeLineage || featureFlags.sessionTreeCompaction;
|
|
1172
|
+
const sessionMemory = resolveRepositoryBackedCapability(sessionMemoryFlagEnabled, input.sessionMemoryRepository);
|
|
2419
1173
|
return {
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
projection: createToolSuccessProjection(writeResult.workingSet, "update", timestamp)
|
|
1174
|
+
workingMemory: resolveRepositoryBackedCapability(featureFlags.workingMemory, input.workingMemoryRepository),
|
|
1175
|
+
sessionMemory,
|
|
1176
|
+
shutdownEpisodes: sessionMemory === "enabled",
|
|
1177
|
+
goalContinuation: resolveGoalContinuationCapability(featureFlags.goalContinuation, input.goalContinuationHostPort)
|
|
2425
1178
|
};
|
|
2426
1179
|
}
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
var WORKING_MEMORY_MISCONFIGURED_MESSAGE = "Working memory is enabled, but no working-memory repository was wired into the runtime.";
|
|
2431
|
-
function workingMemoryNotReadyFailure(input) {
|
|
2432
|
-
if (!input.featureEnabled) {
|
|
2433
|
-
return createFailure("feature_disabled", WORKING_MEMORY_DISABLED_MESSAGE);
|
|
2434
|
-
}
|
|
2435
|
-
if (!input.repository) {
|
|
2436
|
-
return createFailure("misconfigured", WORKING_MEMORY_MISCONFIGURED_MESSAGE);
|
|
1180
|
+
function resolveRepositoryBackedCapability(featureEnabled, repository) {
|
|
1181
|
+
if (!featureEnabled) {
|
|
1182
|
+
return "disabled";
|
|
2437
1183
|
}
|
|
2438
|
-
return
|
|
1184
|
+
return repository ? "enabled" : "misconfigured";
|
|
2439
1185
|
}
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
const now = () => deps.now ? deps.now().toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
2446
|
-
const readiness = () => workingMemoryNotReadyFailure({ featureEnabled, repository });
|
|
2447
|
-
return {
|
|
2448
|
-
async run(params) {
|
|
2449
|
-
const notReady = readiness();
|
|
2450
|
-
if (notReady) {
|
|
2451
|
-
return notReady;
|
|
2452
|
-
}
|
|
2453
|
-
switch (params.action) {
|
|
2454
|
-
case "get":
|
|
2455
|
-
return handleGet(params, repository, now());
|
|
2456
|
-
case "list":
|
|
2457
|
-
return handleList(params, repository);
|
|
2458
|
-
case "create":
|
|
2459
|
-
return handleCreate(params, repository, now(), deps.sourceLabel);
|
|
2460
|
-
case "update":
|
|
2461
|
-
return handleUpdate(params, repository, now());
|
|
2462
|
-
case "close":
|
|
2463
|
-
return handleClose(params, repository, now());
|
|
2464
|
-
}
|
|
2465
|
-
},
|
|
2466
|
-
async prepareExternalGoalMutation(params) {
|
|
2467
|
-
const notReady = readiness();
|
|
2468
|
-
if (notReady) {
|
|
2469
|
-
return notReady;
|
|
2470
|
-
}
|
|
2471
|
-
return handlePrepareExternalGoalMutation(params, repository, now());
|
|
2472
|
-
},
|
|
2473
|
-
async renderProjection(input) {
|
|
2474
|
-
const request = typeof input === "string" ? { sourceRef: input } : input;
|
|
2475
|
-
if (!featureEnabled) {
|
|
2476
|
-
return createWorkingContextStubProjection({
|
|
2477
|
-
reason: "feature_disabled",
|
|
2478
|
-
sourceRef: request.sourceRef
|
|
2479
|
-
});
|
|
2480
|
-
}
|
|
2481
|
-
if (!repository) {
|
|
2482
|
-
return createWorkingContextStubProjection({
|
|
2483
|
-
reason: "misconfigured",
|
|
2484
|
-
sourceRef: request.sourceRef
|
|
2485
|
-
});
|
|
2486
|
-
}
|
|
2487
|
-
const selection = await selectWorkingSet({ workingSetId: request.workingSetId, scope: request.scope }, repository);
|
|
2488
|
-
if (!selection.ok) {
|
|
2489
|
-
return createWorkingContextStubProjection({
|
|
2490
|
-
reason: selection.code === "ambiguous_scope" ? "ambiguous_scope" : "missing_active_set",
|
|
2491
|
-
sourceRef: request.sourceRef
|
|
2492
|
-
});
|
|
2493
|
-
}
|
|
2494
|
-
return createWorkingContextFullProjection(selection.workingSet, request.sourceRef);
|
|
2495
|
-
}
|
|
2496
|
-
};
|
|
1186
|
+
function resolveGoalContinuationCapability(featureEnabled, hostPort) {
|
|
1187
|
+
if (!featureEnabled) {
|
|
1188
|
+
return "disabled";
|
|
1189
|
+
}
|
|
1190
|
+
return hostPort ? "enabled" : "misconfigured";
|
|
2497
1191
|
}
|
|
2498
1192
|
|
|
2499
|
-
// src/app/
|
|
2500
|
-
function
|
|
2501
|
-
const workingMemory = createWorkingMemoryService(featureFlags, {
|
|
2502
|
-
repository: options.workingMemoryRepository,
|
|
2503
|
-
sourceLabel: options.workingMemorySourceLabel
|
|
2504
|
-
});
|
|
1193
|
+
// src/app/features/runtime-policy.ts
|
|
1194
|
+
function resolveRuntimePolicy(featureFlags, input = {}) {
|
|
2505
1195
|
return {
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
})
|
|
1196
|
+
featureFlags,
|
|
1197
|
+
capabilities: resolveRuntimeCapabilities(featureFlags, {
|
|
1198
|
+
workingMemoryRepository: input.workingMemoryRepository,
|
|
1199
|
+
sessionMemoryRepository: input.sessionMemoryRepository,
|
|
1200
|
+
goalContinuationHostPort: input.goalContinuationHostPort
|
|
1201
|
+
}),
|
|
1202
|
+
...input.memoryPolicy ? { memoryPolicy: input.memoryPolicy } : {}
|
|
2512
1203
|
};
|
|
2513
1204
|
}
|
|
2514
1205
|
|
|
@@ -2518,13 +1209,13 @@ async function createAgenrSkelnServices(config = {}) {
|
|
|
2518
1209
|
config,
|
|
2519
1210
|
readSlotPolicies: (hostConfig) => hostConfig.memoryPolicy?.slotPolicies,
|
|
2520
1211
|
resolveClaimExtraction: ({ agenrConfig }) => createClaimExtractionFromAgenrConfig(agenrConfig),
|
|
2521
|
-
extend: ({ config: hostConfig, resolvedConfig, agenrConfig, runtimeServices }) => {
|
|
1212
|
+
extend: async ({ config: hostConfig, resolvedConfig, agenrConfig, runtimeServices }) => {
|
|
2522
1213
|
const featureFlags = resolveAgenrFeatureFlags({
|
|
2523
1214
|
...agenrConfig.features,
|
|
2524
1215
|
...hostConfig.featureFlags
|
|
2525
1216
|
});
|
|
2526
1217
|
const goalContinuationHostPort = void 0;
|
|
2527
|
-
const hostMemory = createHostMemoryServices(featureFlags, {
|
|
1218
|
+
const hostMemory = await createHostMemoryServices(featureFlags, {
|
|
2528
1219
|
workingMemoryRepository: runtimeServices.workingMemoryRepository,
|
|
2529
1220
|
sessionMemoryRepository: runtimeServices.sessionMemoryRepository,
|
|
2530
1221
|
workingMemorySourceLabel: "skeln",
|
|
@@ -2650,10 +1341,7 @@ function registerAgenrSkelnFetchTool(skeln, servicesPromise, resolveScope) {
|
|
|
2650
1341
|
label: "Agenr Fetch",
|
|
2651
1342
|
description: "Fetch the full body and metadata for one durable memory entry by id or subject.",
|
|
2652
1343
|
promptSnippet: "Use agenr_fetch after agenr_recall when you need the full entry body for an id shown in recall results.",
|
|
2653
|
-
promptGuidelines: [
|
|
2654
|
-
"Provide exactly one target selector: id or subject.",
|
|
2655
|
-
"Prefer id from agenr_recall when preview_truncated=true or when exact wording matters."
|
|
2656
|
-
],
|
|
1344
|
+
promptGuidelines: [MEMORY_DOCTRINE.update.targetSelector, MEMORY_DOCTRINE.fetch.preferId],
|
|
2657
1345
|
parameters: toolSchema(FETCH_TOOL_PARAMETERS),
|
|
2658
1346
|
execute: async (_toolCallId, rawParams, _signal, _onUpdate, context) => {
|
|
2659
1347
|
try {
|
|
@@ -2904,13 +1592,9 @@ function registerAgenrSkelnRecallTool(skeln, servicesPromise, resolveScope) {
|
|
|
2904
1592
|
skeln.registerTool({
|
|
2905
1593
|
name: "agenr_recall",
|
|
2906
1594
|
label: "Agenr Recall",
|
|
2907
|
-
description:
|
|
1595
|
+
description: buildSkelnRecallToolDescription(),
|
|
2908
1596
|
promptSnippet: "Use agenr_recall to retrieve durable memory, prior episode summaries, or canonical procedures from agenr.",
|
|
2909
|
-
promptGuidelines:
|
|
2910
|
-
"Use focused natural-language queries instead of broad 'everything' searches.",
|
|
2911
|
-
"Use mode=procedures for how-to or checklist questions, and mode=episodes for what-happened questions tied to time or sessions.",
|
|
2912
|
-
"Use asOf when the user asks what was true at an earlier point in time."
|
|
2913
|
-
],
|
|
1597
|
+
promptGuidelines: buildSkelnRecallToolGuidelines(),
|
|
2914
1598
|
parameters: toolSchema(RECALL_TOOL_PARAMETERS),
|
|
2915
1599
|
execute: async (_toolCallId, rawParams, _signal, _onUpdate, context) => {
|
|
2916
1600
|
try {
|
|
@@ -2933,42 +1617,61 @@ function registerAgenrSkelnStoreTool(skeln, servicesPromise, resolveScope) {
|
|
|
2933
1617
|
skeln.registerTool({
|
|
2934
1618
|
name: "agenr_store",
|
|
2935
1619
|
label: "Agenr Store",
|
|
2936
|
-
description:
|
|
1620
|
+
description: buildSkelnStoreToolDescription(),
|
|
2937
1621
|
promptSnippet: "Use agenr_store to persist durable memory that should survive across Skeln sessions.",
|
|
2938
|
-
promptGuidelines:
|
|
2939
|
-
"Do not store progress logs, plans, or data already canonical in git, tickets, calendars, signed docs, chat/email, or databases.",
|
|
2940
|
-
"Store the durable takeaway, standing rule, preference, risk, lesson, or relationship instead of raw activity.",
|
|
2941
|
-
"Use claimKey for slot-like facts that may be superseded later, such as versions, strategies, owners, or limits."
|
|
2942
|
-
],
|
|
1622
|
+
promptGuidelines: buildStoreToolGuidelines(),
|
|
2943
1623
|
parameters: toolSchema(STORE_TOOL_PARAMETERS),
|
|
2944
1624
|
execute: async (_toolCallId, rawParams, _signal, _onUpdate, context) => {
|
|
2945
1625
|
try {
|
|
2946
1626
|
const params = parseStoreToolParams(rawParams, SKELN_PARAM_READER);
|
|
2947
1627
|
const [services, scope] = await Promise.all([servicesPromise, resolveScope(context)]);
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
);
|
|
1628
|
+
const outcome = await runStoreMemoryTool(params, services, {
|
|
1629
|
+
session: scope,
|
|
1630
|
+
sourcePrefix: "skeln-session",
|
|
1631
|
+
defaultSourceContext: "Stored via agenr_store from Skeln.",
|
|
1632
|
+
extraDetails: { sessionKey: scope.sessionKey },
|
|
1633
|
+
onWarning: (warning) => console.warn(`[agenr] tool=agenr_store session=${scope.sessionId} warning: ${warning}`)
|
|
1634
|
+
});
|
|
1635
|
+
triggerSkelnImportanceLightDream(services, outcome.details.status);
|
|
1636
|
+
return toSkelnToolResult(outcome);
|
|
2957
1637
|
} catch (error) {
|
|
2958
1638
|
return toolFailureResult(error);
|
|
2959
1639
|
}
|
|
2960
1640
|
}
|
|
2961
1641
|
});
|
|
2962
1642
|
}
|
|
1643
|
+
function triggerSkelnImportanceLightDream(services, status) {
|
|
1644
|
+
if (status !== "stored") {
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
void maybeRunLightDream(
|
|
1648
|
+
{ trigger: "importance" },
|
|
1649
|
+
{
|
|
1650
|
+
port: services.dreaming,
|
|
1651
|
+
dbPath: services.config.dbPath,
|
|
1652
|
+
config: services.agenrConfig,
|
|
1653
|
+
embedding: services.embedding,
|
|
1654
|
+
...services.claimExtraction ? { createClaimExtractionLlm: () => services.claimExtraction.llm } : {}
|
|
1655
|
+
}
|
|
1656
|
+
).then((result) => {
|
|
1657
|
+
if (result.status === "ran") {
|
|
1658
|
+
console.info(`[agenr] skeln importance light dream completed run=${result.result.runId}`);
|
|
1659
|
+
} else if (result.reason === "run_in_progress" || result.reason === "episode_write_in_progress") {
|
|
1660
|
+
console.info(`[agenr] skeln importance light dream skipped reason=${result.reason}`);
|
|
1661
|
+
}
|
|
1662
|
+
}).catch((error) => {
|
|
1663
|
+
console.warn(`[agenr] skeln importance light dream failed: ${formatErrorMessage(error)}`);
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
2963
1666
|
|
|
2964
1667
|
// src/adapters/skeln/tools/update.ts
|
|
2965
1668
|
function registerAgenrSkelnUpdateTool(skeln, servicesPromise, resolveScope) {
|
|
2966
1669
|
skeln.registerTool({
|
|
2967
1670
|
name: "agenr_update",
|
|
2968
1671
|
label: "Agenr Update",
|
|
2969
|
-
description:
|
|
1672
|
+
description: buildUpdateToolDescription(),
|
|
2970
1673
|
promptSnippet: "Use agenr_update to correct metadata on an existing durable memory entry.",
|
|
2971
|
-
promptGuidelines:
|
|
1674
|
+
promptGuidelines: buildUpdateToolGuidelines(),
|
|
2972
1675
|
parameters: toolSchema(UPDATE_TOOL_PARAMETERS),
|
|
2973
1676
|
execute: async (_toolCallId, rawParams, _signal, _onUpdate, context) => {
|
|
2974
1677
|
try {
|
|
@@ -2992,7 +1695,8 @@ function registerAgenrSkelnUpdateTool(skeln, servicesPromise, resolveScope) {
|
|
|
2992
1695
|
expiry: params.expiry,
|
|
2993
1696
|
claimKey: params.claimKeyInput,
|
|
2994
1697
|
validFrom: params.validFrom,
|
|
2995
|
-
validTo: params.validTo
|
|
1698
|
+
validTo: params.validTo,
|
|
1699
|
+
project: params.project
|
|
2996
1700
|
})
|
|
2997
1701
|
}
|
|
2998
1702
|
});
|
|
@@ -3830,6 +2534,8 @@ function registerAgenrSkelnMemory(skeln, options = {}) {
|
|
|
3830
2534
|
const servicesPromise = createAgenrSkelnServices(config);
|
|
3831
2535
|
const scopeTracker = createSkelnSessionScopeTracker();
|
|
3832
2536
|
const sessionStartTracker = createSessionStartTracker();
|
|
2537
|
+
const compactionPromptTracker = createCompactionPromptTracker();
|
|
2538
|
+
const lifecycleIntakeTracker = createSessionLifecycleIntakeTracker();
|
|
3833
2539
|
const lifecycle = skeln;
|
|
3834
2540
|
const resolveScope = async (context) => resolveCurrentSkelnSessionScope(context, scopeTracker, options);
|
|
3835
2541
|
void servicesPromise.catch((error) => {
|
|
@@ -3837,8 +2543,16 @@ function registerAgenrSkelnMemory(skeln, options = {}) {
|
|
|
3837
2543
|
});
|
|
3838
2544
|
registerAgenrSkelnTools(skeln, servicesPromise, resolveScope);
|
|
3839
2545
|
registerAgenrSkelnFailureBoundary(lifecycle);
|
|
3840
|
-
registerAgenrSkelnInjectionHooks(
|
|
3841
|
-
|
|
2546
|
+
registerAgenrSkelnInjectionHooks(
|
|
2547
|
+
lifecycle,
|
|
2548
|
+
scopeTracker,
|
|
2549
|
+
servicesPromise,
|
|
2550
|
+
sessionStartTracker,
|
|
2551
|
+
compactionPromptTracker,
|
|
2552
|
+
lifecycleIntakeTracker,
|
|
2553
|
+
resolveScope
|
|
2554
|
+
);
|
|
2555
|
+
registerAgenrSkelnSessionMemoryHooks(lifecycle, scopeTracker, servicesPromise, resolveScope, compactionPromptTracker, lifecycleIntakeTracker);
|
|
3842
2556
|
registerAgenrSkelnSubagentFindingHooks(lifecycle, servicesPromise, resolveScope);
|
|
3843
2557
|
return {
|
|
3844
2558
|
executeWorkCommand: (context, params) => executeAgenrSkelnWorkCommand(servicesPromise, resolveScope, context, params)
|
|
@@ -3847,13 +2561,19 @@ function registerAgenrSkelnMemory(skeln, options = {}) {
|
|
|
3847
2561
|
function extension(skeln) {
|
|
3848
2562
|
registerAgenrSkelnMemory(skeln);
|
|
3849
2563
|
}
|
|
3850
|
-
function registerAgenrSkelnInjectionHooks(skeln, scopeTracker, servicesPromise, sessionStartTracker, resolveScope) {
|
|
2564
|
+
function registerAgenrSkelnInjectionHooks(skeln, scopeTracker, servicesPromise, sessionStartTracker, compactionPromptTracker, lifecycleIntakeTracker, resolveScope) {
|
|
3851
2565
|
skeln.on("session_start", async (event, context) => {
|
|
3852
2566
|
try {
|
|
3853
2567
|
const scope = await resolveScope(context);
|
|
3854
|
-
rememberSkelnSessionStart(scopeTracker,
|
|
3855
|
-
|
|
3856
|
-
|
|
2568
|
+
rememberSkelnSessionStart(scopeTracker, scope);
|
|
2569
|
+
await lifecycleIntakeTracker.track(
|
|
2570
|
+
scope.sessionId,
|
|
2571
|
+
scope.sessionKey,
|
|
2572
|
+
(async () => {
|
|
2573
|
+
const services = await servicesPromise;
|
|
2574
|
+
logSessionMemoryTriggerResult(await services.routeSessionMemoryTrigger(buildSkelnSessionStartTriggerEvent(scope, event)));
|
|
2575
|
+
})()
|
|
2576
|
+
);
|
|
3857
2577
|
} catch (error) {
|
|
3858
2578
|
console.warn(`[agenr] session_start scope failed: ${formatErrorMessage(error)}`);
|
|
3859
2579
|
}
|
|
@@ -3863,32 +2583,65 @@ function registerAgenrSkelnInjectionHooks(skeln, scopeTracker, servicesPromise,
|
|
|
3863
2583
|
async (event, context) => handleAgenrSkelnBeforeAgentStart(event, context, {
|
|
3864
2584
|
servicesPromise,
|
|
3865
2585
|
sessionStartTracker,
|
|
2586
|
+
compactionPromptTracker,
|
|
2587
|
+
lifecycleIntakeTracker,
|
|
3866
2588
|
resolveScope
|
|
3867
2589
|
})
|
|
3868
2590
|
);
|
|
3869
2591
|
}
|
|
3870
|
-
function registerAgenrSkelnSessionMemoryHooks(skeln, scopeTracker, servicesPromise, resolveScope) {
|
|
2592
|
+
function registerAgenrSkelnSessionMemoryHooks(skeln, scopeTracker, servicesPromise, resolveScope, compactionPromptTracker, lifecycleIntakeTracker) {
|
|
3871
2593
|
skeln.on("session_before_fork", async (event, context) => {
|
|
3872
|
-
|
|
2594
|
+
const scope = await resolveScope(context);
|
|
2595
|
+
await lifecycleIntakeTracker.track(
|
|
2596
|
+
scope.sessionId,
|
|
2597
|
+
scope.sessionKey,
|
|
2598
|
+
routeSkelnSessionMemoryTrigger(servicesPromise, resolveScope, context, (scope2) => buildSkelnSessionBeforeForkTriggerEvent(scope2, event))
|
|
2599
|
+
);
|
|
3873
2600
|
});
|
|
3874
|
-
skeln.on("session_before_compact", async (
|
|
3875
|
-
|
|
2601
|
+
skeln.on("session_before_compact", async (event, context) => {
|
|
2602
|
+
const scope = await resolveScope(context);
|
|
2603
|
+
await lifecycleIntakeTracker.track(scope.sessionId, scope.sessionKey, handleSkelnSessionBeforeCompact(event, context, servicesPromise, resolveScope));
|
|
3876
2604
|
});
|
|
3877
2605
|
skeln.on("session_compact", async (event, context) => {
|
|
3878
|
-
|
|
2606
|
+
const scope = await resolveScope(context);
|
|
2607
|
+
await lifecycleIntakeTracker.track(
|
|
2608
|
+
scope.sessionId,
|
|
2609
|
+
scope.sessionKey,
|
|
2610
|
+
routeSkelnSessionMemoryTrigger(servicesPromise, resolveScope, context, (scope2) => buildSkelnSessionCompactTriggerEvent(scope2, event))
|
|
2611
|
+
);
|
|
3879
2612
|
});
|
|
3880
2613
|
skeln.on("session_before_tree", async (event, context) => {
|
|
3881
|
-
|
|
2614
|
+
const scope = await resolveScope(context);
|
|
2615
|
+
await lifecycleIntakeTracker.track(
|
|
2616
|
+
scope.sessionId,
|
|
2617
|
+
scope.sessionKey,
|
|
2618
|
+
routeSkelnSessionMemoryTrigger(servicesPromise, resolveScope, context, (scope2) => buildSkelnSessionBeforeTreeTriggerEvent(scope2, event))
|
|
2619
|
+
);
|
|
3882
2620
|
});
|
|
3883
2621
|
skeln.on("session_tree", async (event, context) => {
|
|
3884
|
-
|
|
2622
|
+
const scope = await resolveScope(context);
|
|
2623
|
+
await lifecycleIntakeTracker.track(
|
|
2624
|
+
scope.sessionId,
|
|
2625
|
+
scope.sessionKey,
|
|
2626
|
+
routeSkelnSessionMemoryTrigger(servicesPromise, resolveScope, context, (scope2) => buildSkelnSessionTreeTriggerEvent(scope2, event))
|
|
2627
|
+
);
|
|
3885
2628
|
});
|
|
3886
2629
|
skeln.on("session_shutdown", async (event, context) => {
|
|
3887
|
-
await handleSkelnSessionShutdown(servicesPromise, scopeTracker, resolveScope, event, context);
|
|
2630
|
+
await handleSkelnSessionShutdown(servicesPromise, scopeTracker, resolveScope, event, context, compactionPromptTracker, lifecycleIntakeTracker);
|
|
3888
2631
|
});
|
|
3889
2632
|
}
|
|
3890
|
-
async function handleSkelnSessionShutdown(servicesPromise, scopeTracker, resolveScope, event, context) {
|
|
3891
|
-
|
|
2633
|
+
async function handleSkelnSessionShutdown(servicesPromise, scopeTracker, resolveScope, event, context, compactionPromptTracker, lifecycleIntakeTracker) {
|
|
2634
|
+
try {
|
|
2635
|
+
const scope = await resolveScope(context);
|
|
2636
|
+
await lifecycleIntakeTracker.track(
|
|
2637
|
+
scope.sessionId,
|
|
2638
|
+
scope.sessionKey,
|
|
2639
|
+
routeSkelnSessionMemoryTrigger(servicesPromise, resolveScope, context, (scope2) => buildSkelnSessionShutdownTriggerEvent(scope2, event))
|
|
2640
|
+
);
|
|
2641
|
+
compactionPromptTracker.clear(scope.sessionId, scope.sessionKey);
|
|
2642
|
+
await lifecycleIntakeTracker.clear(scope.sessionId, scope.sessionKey);
|
|
2643
|
+
} catch {
|
|
2644
|
+
}
|
|
3892
2645
|
clearTrackedSkelnScope(scopeTracker, context);
|
|
3893
2646
|
await scheduleSkelnSessionShutdownEpisodeWrite({ event, context, servicesPromise });
|
|
3894
2647
|
}
|
|
@@ -3898,15 +2651,6 @@ function registerAgenrSkelnSubagentFindingHooks(skeln, servicesPromise, resolveS
|
|
|
3898
2651
|
return void 0;
|
|
3899
2652
|
});
|
|
3900
2653
|
}
|
|
3901
|
-
async function routeScopedSessionMemoryTrigger(servicesPromise, resolveScope, context, buildEvent) {
|
|
3902
|
-
try {
|
|
3903
|
-
const scope = await resolveScope(context);
|
|
3904
|
-
const services = await servicesPromise;
|
|
3905
|
-
logSessionMemoryTriggerResult(await services.routeSessionMemoryTrigger(buildEvent(scope)));
|
|
3906
|
-
} catch (error) {
|
|
3907
|
-
console.warn(`[agenr] session-memory trigger failed: ${formatErrorMessage(error)}`);
|
|
3908
|
-
}
|
|
3909
|
-
}
|
|
3910
2654
|
async function resolveCurrentSkelnSessionScope(context, scopeTracker, options) {
|
|
3911
2655
|
const sessionId = String(context.sessionManager.getSessionId());
|
|
3912
2656
|
const tracked = scopeTracker.getSessionScope(sessionId);
|
|
@@ -3924,9 +2668,8 @@ async function resolveCurrentSkelnSessionScope(context, scopeTracker, options) {
|
|
|
3924
2668
|
const override = options.getHostContext ? await options.getHostContext(context) : void 0;
|
|
3925
2669
|
return toSkelnSessionScope(mergeSkelnHostContext(defaults, override), sessionId);
|
|
3926
2670
|
}
|
|
3927
|
-
function rememberSkelnSessionStart(scopeTracker,
|
|
2671
|
+
function rememberSkelnSessionStart(scopeTracker, scope) {
|
|
3928
2672
|
scopeTracker.rememberSessionStart(scope);
|
|
3929
|
-
sessionStartTracker.rememberSessionStart(scope.sessionId, scope.sessionKey, previousSessionFile);
|
|
3930
2673
|
}
|
|
3931
2674
|
function resolveContextCwd(context) {
|
|
3932
2675
|
return context.cwd || context.sessionManager.getCwd();
|