@agenr/openclaw-plugin 3.3.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.
@@ -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
+ };