@agenr/openclaw-plugin 3.3.0 → 2026.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-before-turn-artifact-NPUHVWFE.js +71 -0
- package/dist/build-recall-artifact-F3LS3PZX.js +62 -0
- package/dist/chunk-5AXMFBHR.js +14 -0
- package/dist/chunk-5AYIXQRF.js +4452 -0
- package/dist/chunk-5TIP2EPP.js +6944 -0
- package/dist/chunk-GAERET5Q.js +2070 -0
- package/dist/chunk-GF3PX3VM.js +41 -0
- package/dist/chunk-GKZQ5AG5.js +44 -0
- package/dist/chunk-IBPS64W3.js +1069 -0
- package/dist/chunk-MC3C2XM5.js +148 -0
- package/dist/chunk-NSLTJBUC.js +270 -0
- package/dist/chunk-OJSIZDZD.js +9 -0
- package/dist/chunk-OWGQWQUP.js +45 -0
- package/dist/chunk-SIY3JA7T.js +3062 -0
- package/dist/chunk-SOQW7356.js +2416 -0
- package/dist/chunk-U74RE3L7.js +3233 -0
- package/dist/chunk-VBPYU7GO.js +597 -0
- package/dist/chunk-VTHBPXDQ.js +1750 -0
- package/dist/chunk-XFJ4S4G2.js +1679 -0
- package/dist/chunk-Y5NB3FTH.js +106 -0
- package/dist/chunk-ZX55JBV2.js +4451 -0
- package/dist/index.js +1855 -19846
- package/dist/lifecycle-checkpoint-IAC5FCQU.js +154 -0
- 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/openclaw.plugin.json +2 -11
- package/package.json +1 -1
|
@@ -0,0 +1,861 @@
|
|
|
1
|
+
import {
|
|
2
|
+
INITIAL_GOAL_GENERATION,
|
|
3
|
+
createToolSuccessProjection,
|
|
4
|
+
nextGoalGenerationAfterObjectiveChange
|
|
5
|
+
} from "./chunk-OWGQWQUP.js";
|
|
6
|
+
import {
|
|
7
|
+
CLOSE_EVENT_HISTORY_LIMIT,
|
|
8
|
+
WORKING_MEMORY_MISCONFIGURED_MESSAGE,
|
|
9
|
+
createFailure,
|
|
10
|
+
createWorkingContextFullProjection,
|
|
11
|
+
createWorkingContextStubProjection,
|
|
12
|
+
isCloseManagedStatus,
|
|
13
|
+
isMutableWorkingSetStatus,
|
|
14
|
+
isTrustedHostMutationSource,
|
|
15
|
+
isTrustedHostOnlyWorkingOperation,
|
|
16
|
+
normalizeEventLimit,
|
|
17
|
+
normalizeListLimit,
|
|
18
|
+
workingMemoryNotReadyFailure,
|
|
19
|
+
writeFailureToResult
|
|
20
|
+
} from "./chunk-NSLTJBUC.js";
|
|
21
|
+
|
|
22
|
+
// src/app/working-memory/close-service.ts
|
|
23
|
+
function resolveCloseTerminalStatus(closeMode) {
|
|
24
|
+
return closeMode === "abandon" ? "abandoned" : "closed";
|
|
25
|
+
}
|
|
26
|
+
function buildWorkingCloseSnapshot(input) {
|
|
27
|
+
const finalCheckpoint = buildFinalCheckpoint(input);
|
|
28
|
+
const candidates = [...input.snapshot.candidates ?? []];
|
|
29
|
+
if (input.createEpisode && !candidates.some((candidate) => candidate.kind === "episodic")) {
|
|
30
|
+
candidates.push({
|
|
31
|
+
kind: "episodic",
|
|
32
|
+
summary: finalCheckpoint.summary,
|
|
33
|
+
provenance: {
|
|
34
|
+
evidenceEventSequences: input.eventSequences,
|
|
35
|
+
sourceRef: `working_set:${input.workingSetId}#rev:${input.currentRevision}`,
|
|
36
|
+
note: input.closeReason
|
|
37
|
+
},
|
|
38
|
+
promotionStatus: "pending"
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
snapshot: {
|
|
43
|
+
...input.snapshot,
|
|
44
|
+
checkpoint: finalCheckpoint,
|
|
45
|
+
candidates: candidates.length > 0 ? candidates : void 0,
|
|
46
|
+
lastMaterialChange: input.closeReason
|
|
47
|
+
},
|
|
48
|
+
candidates
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function buildFinalCheckpoint(input) {
|
|
52
|
+
const summary = normalizeSummary(input.closeReason) ?? input.snapshot.summary ?? input.snapshot.objective ?? "Working set closed.";
|
|
53
|
+
return {
|
|
54
|
+
summary,
|
|
55
|
+
recordedAt: input.now,
|
|
56
|
+
...input.snapshot.nextActions && input.snapshot.nextActions.length > 0 ? { nextActions: input.snapshot.nextActions.map((action) => action.text).filter((text) => text.trim().length > 0) } : {},
|
|
57
|
+
...input.snapshot.blockers && input.snapshot.blockers.length > 0 ? { blockers: input.snapshot.blockers } : {}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function normalizeSummary(value) {
|
|
61
|
+
const trimmed = value.trim();
|
|
62
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/app/working-memory/repository.ts
|
|
66
|
+
function isWorkingSetCreateFailure(result) {
|
|
67
|
+
return "kind" in result;
|
|
68
|
+
}
|
|
69
|
+
function isWorkingSetWriteFailure(result) {
|
|
70
|
+
return "kind" in result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/app/working-memory/scope-resolver.ts
|
|
74
|
+
function resolveWorkingScope(input) {
|
|
75
|
+
const scope = normalizeWorkingScope(input);
|
|
76
|
+
if (scope.taskId) {
|
|
77
|
+
return {
|
|
78
|
+
ok: true,
|
|
79
|
+
scope: {
|
|
80
|
+
...scope,
|
|
81
|
+
scopeKind: "task",
|
|
82
|
+
scopeKey: `task:${scope.taskId}`
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (scope.conversationKey) {
|
|
87
|
+
return {
|
|
88
|
+
ok: true,
|
|
89
|
+
scope: {
|
|
90
|
+
...scope,
|
|
91
|
+
scopeKind: "conversation",
|
|
92
|
+
scopeKey: `conversation:${scope.conversationKey}`
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (scope.gitRoot && scope.gitBranch) {
|
|
97
|
+
return {
|
|
98
|
+
ok: true,
|
|
99
|
+
scope: {
|
|
100
|
+
...scope,
|
|
101
|
+
scopeKind: "git_branch",
|
|
102
|
+
scopeKey: buildScopeKey("git_branch", [scope.project, scope.gitRoot, scope.gitBranch])
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (scope.gitRoot && scope.cwd) {
|
|
107
|
+
return {
|
|
108
|
+
ok: true,
|
|
109
|
+
scope: {
|
|
110
|
+
...scope,
|
|
111
|
+
scopeKind: "git_cwd",
|
|
112
|
+
scopeKey: buildScopeKey("git_cwd", [scope.project, scope.gitRoot, scope.cwd])
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
ok: false,
|
|
118
|
+
code: "missing_scope",
|
|
119
|
+
message: "Working memory needs a task, conversation, or git scope."
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function normalizeWorkingScope(input) {
|
|
123
|
+
const scope = input ?? {};
|
|
124
|
+
return {
|
|
125
|
+
...normalizeField("sessionId", scope.sessionId),
|
|
126
|
+
...normalizeField("gitRoot", scope.gitRoot),
|
|
127
|
+
...normalizeField("gitBranch", scope.gitBranch),
|
|
128
|
+
...normalizeField("cwd", scope.cwd),
|
|
129
|
+
...normalizeField("project", scope.project),
|
|
130
|
+
...normalizeField("taskId", scope.taskId),
|
|
131
|
+
...normalizeField("conversationKey", scope.conversationKey)
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function buildScopeKey(prefix, parts) {
|
|
135
|
+
return [prefix, ...parts.filter((part) => part !== void 0)].join(":");
|
|
136
|
+
}
|
|
137
|
+
function normalizeField(key, value) {
|
|
138
|
+
if (typeof value !== "string") {
|
|
139
|
+
return {};
|
|
140
|
+
}
|
|
141
|
+
const trimmed = value.trim();
|
|
142
|
+
return trimmed.length > 0 ? { [key]: trimmed } : {};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/app/working-memory/find-current-set.ts
|
|
146
|
+
async function lookupCurrentWorkingSets(scope, repository) {
|
|
147
|
+
const scopeResolution = resolveWorkingScope(scope);
|
|
148
|
+
if (!scopeResolution.ok) {
|
|
149
|
+
return createFailure("missing_scope", scopeResolution.message);
|
|
150
|
+
}
|
|
151
|
+
const matches = await repository.findCurrentWorkingSets(scopeResolution.scope);
|
|
152
|
+
if (matches.length > 1) {
|
|
153
|
+
return createFailure("ambiguous_scope", "Multiple current working sets matched the resolved scope.", {
|
|
154
|
+
scopeKey: scopeResolution.scope.scopeKey,
|
|
155
|
+
workingSetIds: matches.map((match) => match.id)
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
ok: true,
|
|
160
|
+
scope: scopeResolution.scope,
|
|
161
|
+
matches
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
async function findUniqueCurrentWorkingSet(scope, repository) {
|
|
165
|
+
const lookup = await lookupCurrentWorkingSets(scope, repository);
|
|
166
|
+
if (!lookup.ok) {
|
|
167
|
+
return lookup;
|
|
168
|
+
}
|
|
169
|
+
if (lookup.matches.length === 0) {
|
|
170
|
+
return createFailure("missing_active_set", "No current working set matched the resolved scope.", {
|
|
171
|
+
scopeKey: lookup.scope.scopeKey
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
ok: true,
|
|
176
|
+
workingSet: lookup.matches[0],
|
|
177
|
+
scope: lookup.scope
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/app/working-memory/select-working-set.ts
|
|
182
|
+
async function selectWorkingSet(params, repository) {
|
|
183
|
+
const workingSetId = params.workingSetId?.trim();
|
|
184
|
+
if (workingSetId) {
|
|
185
|
+
const workingSet = await repository.getWorkingSet(workingSetId);
|
|
186
|
+
if (!workingSet) {
|
|
187
|
+
return createFailure("not_found", `Working set ${workingSetId} was not found.`, { workingSetId });
|
|
188
|
+
}
|
|
189
|
+
return { ok: true, workingSet };
|
|
190
|
+
}
|
|
191
|
+
const current = await findUniqueCurrentWorkingSet(params.scope, repository);
|
|
192
|
+
if (!current.ok) {
|
|
193
|
+
return current;
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
ok: true,
|
|
197
|
+
workingSet: current.workingSet,
|
|
198
|
+
scope: current.scope
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/app/working-memory/validation.ts
|
|
203
|
+
function normalizeRequiredString(value, message) {
|
|
204
|
+
const trimmed = value?.trim();
|
|
205
|
+
if (!trimmed) {
|
|
206
|
+
return createFailure("invalid_request", message);
|
|
207
|
+
}
|
|
208
|
+
return { ok: true, value: trimmed };
|
|
209
|
+
}
|
|
210
|
+
function normalizeExpectedRevision(value) {
|
|
211
|
+
if (value === void 0 || !Number.isInteger(value) || value < 0) {
|
|
212
|
+
return createFailure("invalid_request", "expectedRevision must be a non-negative integer.");
|
|
213
|
+
}
|
|
214
|
+
return { ok: true, value };
|
|
215
|
+
}
|
|
216
|
+
function canDefaultExpectedRevision(source) {
|
|
217
|
+
return isTrustedHostMutationSource(source);
|
|
218
|
+
}
|
|
219
|
+
function resolveExpectedRevision(selectedRevision, providedRevision, source) {
|
|
220
|
+
if (providedRevision === void 0) {
|
|
221
|
+
if (!canDefaultExpectedRevision(source)) {
|
|
222
|
+
return createFailure("invalid_request", "expectedRevision must be a non-negative integer.");
|
|
223
|
+
}
|
|
224
|
+
return { ok: true, value: selectedRevision };
|
|
225
|
+
}
|
|
226
|
+
return normalizeExpectedRevision(providedRevision);
|
|
227
|
+
}
|
|
228
|
+
function validateWorkingBudgetState(budget) {
|
|
229
|
+
const entries = [
|
|
230
|
+
["tokenBudget", budget.tokenBudget],
|
|
231
|
+
["tokenUsed", budget.tokenUsed],
|
|
232
|
+
["wallClockBudgetSeconds", budget.wallClockBudgetSeconds],
|
|
233
|
+
["wallClockUsedSeconds", budget.wallClockUsedSeconds],
|
|
234
|
+
["turnBudget", budget.turnBudget],
|
|
235
|
+
["turnsUsed", budget.turnsUsed],
|
|
236
|
+
["requireReviewAfterSeconds", budget.requireReviewAfterSeconds]
|
|
237
|
+
];
|
|
238
|
+
for (const [key, value] of entries) {
|
|
239
|
+
if (value !== void 0 && (!Number.isFinite(value) || value < 0)) {
|
|
240
|
+
return createFailure("invalid_request", `${key} must be a non-negative finite number.`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return { ok: true };
|
|
244
|
+
}
|
|
245
|
+
function validateWorkingUsageDelta(usage) {
|
|
246
|
+
const entries = [
|
|
247
|
+
["tokenDelta", usage.tokenDelta],
|
|
248
|
+
["wallClockSecondsDelta", usage.wallClockSecondsDelta],
|
|
249
|
+
["turnDelta", usage.turnDelta]
|
|
250
|
+
];
|
|
251
|
+
const hasDelta = entries.some(([, value]) => value !== void 0);
|
|
252
|
+
if (!hasDelta) {
|
|
253
|
+
return createFailure("invalid_request", "account_usage requires at least one usage delta.");
|
|
254
|
+
}
|
|
255
|
+
for (const [key, value] of entries) {
|
|
256
|
+
if (value !== void 0 && (!Number.isFinite(value) || value < 0)) {
|
|
257
|
+
return createFailure("invalid_request", `${key} must be a non-negative finite number.`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return { ok: true };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/app/working-memory/handlers/close.ts
|
|
264
|
+
async function handleClose(params, repository, timestamp) {
|
|
265
|
+
if (!isTrustedHostMutationSource(params.source)) {
|
|
266
|
+
return createFailure(
|
|
267
|
+
"close_not_allowed",
|
|
268
|
+
"agenr_work close is reserved for /goal clear. Record progress with merge_checkpoint and leave the working set open."
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
const closeReason = normalizeRequiredString(params.closeReason, "agenr_work close requires closeReason.");
|
|
272
|
+
if (!closeReason.ok) {
|
|
273
|
+
return closeReason;
|
|
274
|
+
}
|
|
275
|
+
const selection = await selectWorkingSet(params, repository);
|
|
276
|
+
if (!selection.ok) {
|
|
277
|
+
return selection;
|
|
278
|
+
}
|
|
279
|
+
const expectedRevision = resolveExpectedRevision(selection.workingSet.revision, params.expectedRevision, params.source);
|
|
280
|
+
if (!expectedRevision.ok) {
|
|
281
|
+
return expectedRevision;
|
|
282
|
+
}
|
|
283
|
+
if (isCloseManagedStatus(selection.workingSet.status)) {
|
|
284
|
+
return createFailure("terminal_status", `Working set ${selection.workingSet.id} is already ${selection.workingSet.status}.`, {
|
|
285
|
+
workingSetId: selection.workingSet.id,
|
|
286
|
+
status: selection.workingSet.status
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
const events = await repository.listWorkingEvents(selection.workingSet.id, CLOSE_EVENT_HISTORY_LIMIT);
|
|
290
|
+
const terminalStatus = resolveCloseTerminalStatus(params.closeMode);
|
|
291
|
+
const closePayload = buildWorkingCloseSnapshot({
|
|
292
|
+
workingSetId: selection.workingSet.id,
|
|
293
|
+
snapshot: selection.workingSet.snapshot,
|
|
294
|
+
currentRevision: selection.workingSet.revision,
|
|
295
|
+
closeReason: closeReason.value,
|
|
296
|
+
createEpisode: params.createEpisode,
|
|
297
|
+
eventSequences: events.map((event) => event.sequence),
|
|
298
|
+
now: timestamp
|
|
299
|
+
});
|
|
300
|
+
const writeResult = await repository.updateWorkingSet({
|
|
301
|
+
workingSetId: selection.workingSet.id,
|
|
302
|
+
expectedRevision: expectedRevision.value,
|
|
303
|
+
eventType: terminalStatus,
|
|
304
|
+
payload: {
|
|
305
|
+
closeReason: closeReason.value,
|
|
306
|
+
closeMode: params.closeMode ?? "close",
|
|
307
|
+
candidates: closePayload.candidates,
|
|
308
|
+
sourceRef: `working_set:${selection.workingSet.id}#rev:${selection.workingSet.revision}`
|
|
309
|
+
},
|
|
310
|
+
status: terminalStatus,
|
|
311
|
+
snapshot: closePayload.snapshot,
|
|
312
|
+
title: selection.workingSet.title,
|
|
313
|
+
objective: selection.workingSet.snapshot.objective,
|
|
314
|
+
closedAt: timestamp,
|
|
315
|
+
closeReason: closeReason.value,
|
|
316
|
+
actor: params.actor,
|
|
317
|
+
source: params.source,
|
|
318
|
+
now: timestamp
|
|
319
|
+
});
|
|
320
|
+
return toCloseResult(selection.workingSet.id, writeResult, closePayload.candidates);
|
|
321
|
+
}
|
|
322
|
+
function toCloseResult(workingSetId, writeResult, candidates) {
|
|
323
|
+
if (isWorkingSetWriteFailure(writeResult)) {
|
|
324
|
+
return writeFailureToResult(workingSetId, writeResult);
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
ok: true,
|
|
328
|
+
action: "close",
|
|
329
|
+
workingSet: writeResult.workingSet,
|
|
330
|
+
event: writeResult.event,
|
|
331
|
+
candidates
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/app/working-memory/resolve-create-scope.ts
|
|
336
|
+
async function resolveCreateScope(params, repository) {
|
|
337
|
+
const workingSetId = params.workingSetId?.trim();
|
|
338
|
+
if (workingSetId) {
|
|
339
|
+
const workingSet = await repository.getWorkingSet(workingSetId);
|
|
340
|
+
if (workingSet) {
|
|
341
|
+
return createFailure("active_set_exists", "A working set is already active for this scope.", {
|
|
342
|
+
workingSetId: workingSet.id,
|
|
343
|
+
scopeKey: workingSet.scopeKey
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
return createFailure("not_found", `Working set ${workingSetId} was not found.`, { workingSetId });
|
|
347
|
+
}
|
|
348
|
+
const lookup = await lookupCurrentWorkingSets(params.scope, repository);
|
|
349
|
+
if (!lookup.ok) {
|
|
350
|
+
return lookup;
|
|
351
|
+
}
|
|
352
|
+
if (lookup.matches.length === 1) {
|
|
353
|
+
const existing = lookup.matches[0];
|
|
354
|
+
return createFailure("active_set_exists", "A working set already exists for this scope.", {
|
|
355
|
+
workingSetId: existing.id,
|
|
356
|
+
scopeKey: lookup.scope.scopeKey
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
ok: true,
|
|
361
|
+
scope: lookup.scope
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/app/working-memory/handlers/create.ts
|
|
366
|
+
async function handleCreate(params, repository, timestamp, sourceLabel) {
|
|
367
|
+
const operation = params.operation;
|
|
368
|
+
if (!operation || operation.type !== "set_objective") {
|
|
369
|
+
return createFailure("invalid_request", "agenr_work create requires a set_objective operation.");
|
|
370
|
+
}
|
|
371
|
+
const updateReason = normalizeRequiredString(params.updateReason, "agenr_work create requires updateReason.");
|
|
372
|
+
if (!updateReason.ok) {
|
|
373
|
+
return updateReason;
|
|
374
|
+
}
|
|
375
|
+
const scopeResolution = await resolveCreateScope(params, repository);
|
|
376
|
+
if (!scopeResolution.ok) {
|
|
377
|
+
return scopeResolution;
|
|
378
|
+
}
|
|
379
|
+
const { scope } = scopeResolution;
|
|
380
|
+
const initialBudget = params.initialBudget ? validateWorkingBudgetState(params.initialBudget) : { ok: true };
|
|
381
|
+
if (!initialBudget.ok) {
|
|
382
|
+
return initialBudget;
|
|
383
|
+
}
|
|
384
|
+
const created = await repository.createWorkingSet({
|
|
385
|
+
scope,
|
|
386
|
+
title: operation.title,
|
|
387
|
+
objective: operation.objective,
|
|
388
|
+
status: "active",
|
|
389
|
+
snapshot: {
|
|
390
|
+
goalGeneration: INITIAL_GOAL_GENERATION,
|
|
391
|
+
objective: operation.objective,
|
|
392
|
+
continuation: { policy: params.continuationPolicy ?? "manual" },
|
|
393
|
+
...params.initialBudget ? { budgets: params.initialBudget } : {},
|
|
394
|
+
lastMaterialChange: updateReason.value
|
|
395
|
+
},
|
|
396
|
+
actor: params.actor,
|
|
397
|
+
source: params.source,
|
|
398
|
+
sourceLabel,
|
|
399
|
+
sessionId: scope.sessionId,
|
|
400
|
+
now: timestamp
|
|
401
|
+
});
|
|
402
|
+
if (isWorkingSetCreateFailure(created)) {
|
|
403
|
+
return createFailure("active_set_exists", "A working set already exists for this scope.", {
|
|
404
|
+
scopeKey: created.scopeKey
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
ok: true,
|
|
409
|
+
action: "create",
|
|
410
|
+
workingSet: created.workingSet,
|
|
411
|
+
event: created.event,
|
|
412
|
+
projection: createToolSuccessProjection(created.workingSet, "create", timestamp)
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/app/working-memory/handlers/get.ts
|
|
417
|
+
async function handleGet(params, repository, timestamp) {
|
|
418
|
+
const selection = await selectWorkingSet(params, repository);
|
|
419
|
+
if (!selection.ok) {
|
|
420
|
+
return selection;
|
|
421
|
+
}
|
|
422
|
+
const events = params.includeEvents ? await repository.listWorkingEvents(selection.workingSet.id, normalizeEventLimit(params.eventLimit)) : void 0;
|
|
423
|
+
return {
|
|
424
|
+
ok: true,
|
|
425
|
+
action: "get",
|
|
426
|
+
workingSet: selection.workingSet,
|
|
427
|
+
...events ? { events } : {},
|
|
428
|
+
projection: createToolSuccessProjection(selection.workingSet, "get", timestamp)
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/app/working-memory/handlers/list.ts
|
|
433
|
+
async function handleList(params, repository) {
|
|
434
|
+
const scopeResolution = params.scope ? resolveWorkingScope(params.scope) : void 0;
|
|
435
|
+
if (scopeResolution && !scopeResolution.ok) {
|
|
436
|
+
return createFailure("missing_scope", scopeResolution.message);
|
|
437
|
+
}
|
|
438
|
+
const workingSets = await repository.listWorkingSets({
|
|
439
|
+
...scopeResolution?.ok ? { scope: scopeResolution.scope } : {},
|
|
440
|
+
limit: normalizeListLimit(params.listLimit)
|
|
441
|
+
});
|
|
442
|
+
return {
|
|
443
|
+
ok: true,
|
|
444
|
+
action: "list",
|
|
445
|
+
workingSets
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// src/app/working-memory/apply-operation.ts
|
|
450
|
+
function applyOperation(record, operation, updateReason) {
|
|
451
|
+
const snapshot = { ...record.snapshot, lastMaterialChange: updateReason };
|
|
452
|
+
let status = record.status;
|
|
453
|
+
let title = record.title;
|
|
454
|
+
let objective = record.snapshot.objective;
|
|
455
|
+
switch (operation.type) {
|
|
456
|
+
case "set_objective":
|
|
457
|
+
snapshot.goalGeneration = nextGoalGenerationAfterObjectiveChange(snapshot, operation.objective);
|
|
458
|
+
snapshot.objective = operation.objective;
|
|
459
|
+
objective = operation.objective;
|
|
460
|
+
title = operation.title ?? title;
|
|
461
|
+
break;
|
|
462
|
+
case "replace_plan":
|
|
463
|
+
snapshot.currentPlan = operation.currentPlan;
|
|
464
|
+
snapshot.nextActions = operation.nextActions;
|
|
465
|
+
break;
|
|
466
|
+
case "merge_checkpoint":
|
|
467
|
+
snapshot.checkpoint = operation.checkpoint;
|
|
468
|
+
if (operation.checkpoint.nextActions) {
|
|
469
|
+
snapshot.nextActions = operation.checkpoint.nextActions.map((text) => ({ text, status: "pending" }));
|
|
470
|
+
}
|
|
471
|
+
if (operation.checkpoint.blockers) {
|
|
472
|
+
snapshot.blockers = operation.checkpoint.blockers;
|
|
473
|
+
}
|
|
474
|
+
break;
|
|
475
|
+
case "add_file_note":
|
|
476
|
+
snapshot.files = [...snapshot.files ?? [], operation.file];
|
|
477
|
+
break;
|
|
478
|
+
case "add_command_note":
|
|
479
|
+
snapshot.commands = [...snapshot.commands ?? [], operation.command];
|
|
480
|
+
break;
|
|
481
|
+
case "record_decision":
|
|
482
|
+
snapshot.decisions = [...snapshot.decisions ?? [], operation.decision];
|
|
483
|
+
break;
|
|
484
|
+
case "record_assumption":
|
|
485
|
+
snapshot.assumptions = [...snapshot.assumptions ?? [], operation.assumption];
|
|
486
|
+
break;
|
|
487
|
+
case "set_next_actions":
|
|
488
|
+
snapshot.nextActions = operation.nextActions;
|
|
489
|
+
break;
|
|
490
|
+
case "set_status":
|
|
491
|
+
if (isCloseManagedStatus(operation.status)) {
|
|
492
|
+
return createFailure("invalid_request", "Use agenr_work close for closed or abandoned terminal states.");
|
|
493
|
+
}
|
|
494
|
+
status = operation.status;
|
|
495
|
+
break;
|
|
496
|
+
case "add_candidate":
|
|
497
|
+
snapshot.candidates = [...snapshot.candidates ?? [], operation.candidate];
|
|
498
|
+
break;
|
|
499
|
+
case "configure_budget": {
|
|
500
|
+
const budget = mergeBudgetState(snapshot.budgets, operation.budget);
|
|
501
|
+
if (!budget.ok) {
|
|
502
|
+
return budget;
|
|
503
|
+
}
|
|
504
|
+
const limited = applyBudgetLimitedStatus(status, budget.value, updateReason);
|
|
505
|
+
snapshot.budgets = limited.budgets;
|
|
506
|
+
status = limited.status;
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
case "account_usage": {
|
|
510
|
+
const budget = applyUsageDelta(snapshot.budgets, operation.usage, updateReason);
|
|
511
|
+
if (!budget.ok) {
|
|
512
|
+
return budget;
|
|
513
|
+
}
|
|
514
|
+
const limited = applyBudgetLimitedStatus(status, budget.value, operation.usage.recordedAt ?? updateReason);
|
|
515
|
+
snapshot.budgets = limited.budgets;
|
|
516
|
+
status = limited.status;
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
case "set_continuation_policy":
|
|
520
|
+
snapshot.continuation = pruneContinuation({
|
|
521
|
+
...snapshot.continuation,
|
|
522
|
+
policy: operation.policy,
|
|
523
|
+
...operation.resumeAfter !== void 0 ? { resumeAfter: operation.resumeAfter } : {},
|
|
524
|
+
...operation.staleAfter !== void 0 ? { staleAfter: operation.staleAfter } : {},
|
|
525
|
+
...operation.stopReason !== void 0 ? { stopReason: operation.stopReason } : {}
|
|
526
|
+
});
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
ok: true,
|
|
531
|
+
snapshot,
|
|
532
|
+
status,
|
|
533
|
+
title,
|
|
534
|
+
objective
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function mergeBudgetState(current, update) {
|
|
538
|
+
const validation = validateWorkingBudgetState(update);
|
|
539
|
+
if (!validation.ok) {
|
|
540
|
+
return validation;
|
|
541
|
+
}
|
|
542
|
+
return {
|
|
543
|
+
ok: true,
|
|
544
|
+
value: pruneBudget({
|
|
545
|
+
...current ?? {},
|
|
546
|
+
...update
|
|
547
|
+
})
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
function applyUsageDelta(current, usage, limitedAt) {
|
|
551
|
+
const validation = validateWorkingUsageDelta(usage);
|
|
552
|
+
if (!validation.ok) {
|
|
553
|
+
return validation;
|
|
554
|
+
}
|
|
555
|
+
const next = {
|
|
556
|
+
...current ?? {},
|
|
557
|
+
tokenUsed: addDelta(current?.tokenUsed, usage.tokenDelta),
|
|
558
|
+
wallClockUsedSeconds: addDelta(current?.wallClockUsedSeconds, usage.wallClockSecondsDelta),
|
|
559
|
+
turnsUsed: addDelta(current?.turnsUsed, usage.turnDelta)
|
|
560
|
+
};
|
|
561
|
+
const limitReason = resolveBudgetLimitReason(next);
|
|
562
|
+
return {
|
|
563
|
+
ok: true,
|
|
564
|
+
value: pruneBudget({
|
|
565
|
+
...next,
|
|
566
|
+
...limitReason ? { limitReason, limitedAt: usage.recordedAt ?? limitedAt } : {}
|
|
567
|
+
})
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
function applyBudgetLimitedStatus(currentStatus, budget, limitedAt) {
|
|
571
|
+
const limitReason = resolveBudgetLimitReason(budget);
|
|
572
|
+
if (!limitReason || !budget) {
|
|
573
|
+
return { status: currentStatus, budgets: budget };
|
|
574
|
+
}
|
|
575
|
+
return {
|
|
576
|
+
status: "budget_limited",
|
|
577
|
+
budgets: pruneBudget({
|
|
578
|
+
...budget,
|
|
579
|
+
limitReason: budget.limitReason ?? limitReason,
|
|
580
|
+
limitedAt: budget.limitedAt ?? limitedAt
|
|
581
|
+
})
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
function resolveBudgetLimitReason(budget) {
|
|
585
|
+
if (!budget) {
|
|
586
|
+
return void 0;
|
|
587
|
+
}
|
|
588
|
+
if (budget.tokenBudget !== void 0 && (budget.tokenUsed ?? 0) >= budget.tokenBudget) {
|
|
589
|
+
return "token";
|
|
590
|
+
}
|
|
591
|
+
if (budget.wallClockBudgetSeconds !== void 0 && (budget.wallClockUsedSeconds ?? 0) >= budget.wallClockBudgetSeconds) {
|
|
592
|
+
return "wall_clock";
|
|
593
|
+
}
|
|
594
|
+
if (budget.turnBudget !== void 0 && (budget.turnsUsed ?? 0) >= budget.turnBudget) {
|
|
595
|
+
return "turn";
|
|
596
|
+
}
|
|
597
|
+
return void 0;
|
|
598
|
+
}
|
|
599
|
+
function addDelta(current, delta) {
|
|
600
|
+
if (delta === void 0) {
|
|
601
|
+
return current;
|
|
602
|
+
}
|
|
603
|
+
return (current ?? 0) + delta;
|
|
604
|
+
}
|
|
605
|
+
function pruneBudget(budget) {
|
|
606
|
+
return Object.fromEntries(Object.entries(budget).filter(([, value]) => value !== void 0));
|
|
607
|
+
}
|
|
608
|
+
function pruneContinuation(continuation) {
|
|
609
|
+
if (!continuation) {
|
|
610
|
+
return void 0;
|
|
611
|
+
}
|
|
612
|
+
const pruned = Object.fromEntries(Object.entries(continuation).filter(([, value]) => value !== void 0));
|
|
613
|
+
return Object.keys(pruned).length > 0 ? pruned : void 0;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// src/app/working-memory/handlers/commit-applied-change.ts
|
|
617
|
+
function isAppliedWorkingSetCommitFailure(result) {
|
|
618
|
+
return "kind" in result;
|
|
619
|
+
}
|
|
620
|
+
async function commitAppliedWorkingSetChange(repository, input) {
|
|
621
|
+
if (input.operation.type === "account_usage") {
|
|
622
|
+
const writeResult2 = await repository.patchWorkingSetUsage({
|
|
623
|
+
workingSetId: input.workingSetId,
|
|
624
|
+
expectedRevision: input.expectedRevision,
|
|
625
|
+
status: input.applied.status,
|
|
626
|
+
snapshot: input.applied.snapshot,
|
|
627
|
+
title: input.applied.title,
|
|
628
|
+
objective: input.applied.objective,
|
|
629
|
+
now: input.now
|
|
630
|
+
});
|
|
631
|
+
if (isWorkingSetWriteFailure(writeResult2)) {
|
|
632
|
+
return writeResult2;
|
|
633
|
+
}
|
|
634
|
+
return {
|
|
635
|
+
type: "usage_patch",
|
|
636
|
+
workingSet: writeResult2.workingSet
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
const writeResult = await repository.updateWorkingSet({
|
|
640
|
+
workingSetId: input.workingSetId,
|
|
641
|
+
expectedRevision: input.expectedRevision,
|
|
642
|
+
eventType: input.operation.type,
|
|
643
|
+
payload: {
|
|
644
|
+
operation: input.operation,
|
|
645
|
+
updateReason: input.updateReason
|
|
646
|
+
},
|
|
647
|
+
status: input.applied.status,
|
|
648
|
+
snapshot: input.applied.snapshot,
|
|
649
|
+
title: input.applied.title,
|
|
650
|
+
objective: input.applied.objective,
|
|
651
|
+
actor: input.actor,
|
|
652
|
+
source: input.source,
|
|
653
|
+
now: input.now
|
|
654
|
+
});
|
|
655
|
+
if (isWorkingSetWriteFailure(writeResult)) {
|
|
656
|
+
return writeResult;
|
|
657
|
+
}
|
|
658
|
+
return {
|
|
659
|
+
type: "semantic",
|
|
660
|
+
workingSet: writeResult.workingSet,
|
|
661
|
+
event: writeResult.event
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// src/app/working-memory/handlers/prepare-external-mutation.ts
|
|
666
|
+
async function handlePrepareExternalGoalMutation(params, repository, timestamp) {
|
|
667
|
+
if (!isTrustedHostMutationSource(params.source)) {
|
|
668
|
+
return createFailure("invalid_request", "prepare_external_goal_mutation is reserved for trusted host runtime paths.");
|
|
669
|
+
}
|
|
670
|
+
const selection = await selectWorkingSet(params, repository);
|
|
671
|
+
if (!selection.ok) {
|
|
672
|
+
if (selection.code === "missing_active_set") {
|
|
673
|
+
return {
|
|
674
|
+
ok: true,
|
|
675
|
+
action: "prepare_external_goal_mutation",
|
|
676
|
+
prepared: false,
|
|
677
|
+
events: []
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
return selection;
|
|
681
|
+
}
|
|
682
|
+
if (params.requireCheckpoint && !params.checkpoint && !selection.workingSet.snapshot.checkpoint) {
|
|
683
|
+
return createFailure("invalid_request", `Active goal requires a checkpoint before ${params.mutationKind}.`, {
|
|
684
|
+
workingSetId: selection.workingSet.id,
|
|
685
|
+
revision: selection.workingSet.revision,
|
|
686
|
+
mutationKind: params.mutationKind
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
if (!isMutableWorkingSetStatus(selection.workingSet.status)) {
|
|
690
|
+
return {
|
|
691
|
+
ok: true,
|
|
692
|
+
action: "prepare_external_goal_mutation",
|
|
693
|
+
prepared: true,
|
|
694
|
+
workingSet: selection.workingSet,
|
|
695
|
+
events: []
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
const events = [];
|
|
699
|
+
let workingSet = selection.workingSet;
|
|
700
|
+
for (const { operation, updateReason } of resolvePrepareOperations(params)) {
|
|
701
|
+
const applied = applyOperation(workingSet, operation, updateReason);
|
|
702
|
+
if (!applied.ok) {
|
|
703
|
+
return applied;
|
|
704
|
+
}
|
|
705
|
+
const writeResult = await commitAppliedWorkingSetChange(repository, {
|
|
706
|
+
workingSetId: workingSet.id,
|
|
707
|
+
expectedRevision: workingSet.revision,
|
|
708
|
+
operation,
|
|
709
|
+
updateReason,
|
|
710
|
+
applied,
|
|
711
|
+
actor: params.actor,
|
|
712
|
+
source: params.source,
|
|
713
|
+
now: timestamp
|
|
714
|
+
});
|
|
715
|
+
if (isAppliedWorkingSetCommitFailure(writeResult)) {
|
|
716
|
+
return writeFailureToResult(workingSet.id, writeResult);
|
|
717
|
+
}
|
|
718
|
+
workingSet = writeResult.workingSet;
|
|
719
|
+
if (writeResult.type === "semantic") {
|
|
720
|
+
events.push(writeResult.event);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return {
|
|
724
|
+
ok: true,
|
|
725
|
+
action: "prepare_external_goal_mutation",
|
|
726
|
+
prepared: true,
|
|
727
|
+
workingSet,
|
|
728
|
+
events
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
function resolvePrepareOperations(params) {
|
|
732
|
+
const operations = [];
|
|
733
|
+
if (params.usage) {
|
|
734
|
+
operations.push({
|
|
735
|
+
operation: { type: "account_usage", usage: params.usage },
|
|
736
|
+
updateReason: params.updateReason ?? `Accounted progress before external goal mutation (${params.mutationKind}).`
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
if (params.checkpoint) {
|
|
740
|
+
operations.push({
|
|
741
|
+
operation: { type: "merge_checkpoint", checkpoint: params.checkpoint },
|
|
742
|
+
updateReason: params.updateReason ?? `Recorded checkpoint before external goal mutation (${params.mutationKind}).`
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
return operations;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// src/app/working-memory/handlers/update.ts
|
|
749
|
+
async function handleUpdate(params, repository, timestamp) {
|
|
750
|
+
const operation = params.operation;
|
|
751
|
+
if (!operation) {
|
|
752
|
+
return createFailure("invalid_request", "agenr_work update requires a typed operation.");
|
|
753
|
+
}
|
|
754
|
+
if (isTrustedHostOnlyWorkingOperation(operation.type) && !isTrustedHostMutationSource(params.source)) {
|
|
755
|
+
return createFailure("invalid_request", `${operation.type} is reserved for trusted host runtime paths.`);
|
|
756
|
+
}
|
|
757
|
+
const updateReason = normalizeRequiredString(params.updateReason, "agenr_work update requires updateReason.");
|
|
758
|
+
if (!updateReason.ok) {
|
|
759
|
+
return updateReason;
|
|
760
|
+
}
|
|
761
|
+
const selection = await selectWorkingSet(params, repository);
|
|
762
|
+
if (!selection.ok) {
|
|
763
|
+
return selection;
|
|
764
|
+
}
|
|
765
|
+
const expectedRevision = resolveExpectedRevision(selection.workingSet.revision, params.expectedRevision, params.source);
|
|
766
|
+
if (!expectedRevision.ok) {
|
|
767
|
+
return expectedRevision;
|
|
768
|
+
}
|
|
769
|
+
if (!isMutableWorkingSetStatus(selection.workingSet.status)) {
|
|
770
|
+
return createFailure("terminal_status", `Working set ${selection.workingSet.id} is already ${selection.workingSet.status}.`, {
|
|
771
|
+
workingSetId: selection.workingSet.id,
|
|
772
|
+
status: selection.workingSet.status
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
const applied = applyOperation(selection.workingSet, operation, updateReason.value);
|
|
776
|
+
if (!applied.ok) {
|
|
777
|
+
return applied;
|
|
778
|
+
}
|
|
779
|
+
const writeResult = await commitAppliedWorkingSetChange(repository, {
|
|
780
|
+
workingSetId: selection.workingSet.id,
|
|
781
|
+
expectedRevision: expectedRevision.value,
|
|
782
|
+
operation,
|
|
783
|
+
updateReason: updateReason.value,
|
|
784
|
+
applied,
|
|
785
|
+
actor: params.actor,
|
|
786
|
+
source: params.source,
|
|
787
|
+
now: timestamp
|
|
788
|
+
});
|
|
789
|
+
if (isAppliedWorkingSetCommitFailure(writeResult)) {
|
|
790
|
+
return writeFailureToResult(selection.workingSet.id, writeResult);
|
|
791
|
+
}
|
|
792
|
+
return {
|
|
793
|
+
ok: true,
|
|
794
|
+
action: "update",
|
|
795
|
+
workingSet: writeResult.workingSet,
|
|
796
|
+
...writeResult.type === "semantic" ? { event: writeResult.event } : {},
|
|
797
|
+
projection: createToolSuccessProjection(writeResult.workingSet, "update", timestamp)
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// src/app/working-memory/service.ts
|
|
802
|
+
function createWorkingMemoryService(featureFlags, deps = {}) {
|
|
803
|
+
const featureEnabled = featureFlags.workingMemory;
|
|
804
|
+
const repository = deps.repository;
|
|
805
|
+
const now = () => deps.now ? deps.now().toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
806
|
+
const readiness = () => workingMemoryNotReadyFailure({ featureEnabled, repository });
|
|
807
|
+
return {
|
|
808
|
+
async run(params) {
|
|
809
|
+
const notReady = readiness();
|
|
810
|
+
if (notReady) {
|
|
811
|
+
return notReady;
|
|
812
|
+
}
|
|
813
|
+
switch (params.action) {
|
|
814
|
+
case "get":
|
|
815
|
+
return handleGet(params, repository, now());
|
|
816
|
+
case "list":
|
|
817
|
+
return handleList(params, repository);
|
|
818
|
+
case "create":
|
|
819
|
+
return handleCreate(params, repository, now(), deps.sourceLabel);
|
|
820
|
+
case "update":
|
|
821
|
+
return handleUpdate(params, repository, now());
|
|
822
|
+
case "close":
|
|
823
|
+
return handleClose(params, repository, now());
|
|
824
|
+
}
|
|
825
|
+
},
|
|
826
|
+
async prepareExternalGoalMutation(params) {
|
|
827
|
+
const notReady = readiness();
|
|
828
|
+
if (notReady) {
|
|
829
|
+
return notReady;
|
|
830
|
+
}
|
|
831
|
+
return handlePrepareExternalGoalMutation(params, repository, now());
|
|
832
|
+
},
|
|
833
|
+
async renderProjection(input) {
|
|
834
|
+
const request = typeof input === "string" ? { sourceRef: input } : input;
|
|
835
|
+
if (!featureEnabled) {
|
|
836
|
+
return createWorkingContextStubProjection({
|
|
837
|
+
reason: "feature_disabled",
|
|
838
|
+
sourceRef: request.sourceRef
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
if (!repository) {
|
|
842
|
+
return createWorkingContextStubProjection({
|
|
843
|
+
reason: "misconfigured",
|
|
844
|
+
sourceRef: request.sourceRef
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
const selection = await selectWorkingSet({ workingSetId: request.workingSetId, scope: request.scope }, repository);
|
|
848
|
+
if (!selection.ok) {
|
|
849
|
+
return createWorkingContextStubProjection({
|
|
850
|
+
reason: selection.code === "ambiguous_scope" ? "ambiguous_scope" : "missing_active_set",
|
|
851
|
+
sourceRef: request.sourceRef
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
return createWorkingContextFullProjection(selection.workingSet, request.sourceRef);
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
export {
|
|
859
|
+
WORKING_MEMORY_MISCONFIGURED_MESSAGE,
|
|
860
|
+
createWorkingMemoryService
|
|
861
|
+
};
|