@lumenflow/cli 3.19.0 → 3.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gates-runners.js +5 -4
- package/dist/gates-runners.js.map +1 -1
- package/dist/gates-utils.js +71 -0
- package/dist/gates-utils.js.map +1 -1
- package/dist/init-templates.js +30 -22
- package/dist/init-templates.js.map +1 -1
- package/dist/wu-prune.js +2 -2
- package/dist/wu-prune.js.map +1 -1
- package/dist/wu-verify.js +22 -17
- package/dist/wu-verify.js.map +1 -1
- package/package.json +8 -8
- package/packs/agent-runtime/.turbo/turbo-build.log +1 -1
- package/packs/agent-runtime/package.json +1 -1
- package/packs/sidekick/.turbo/turbo-build.log +1 -1
- package/packs/sidekick/README.md +118 -113
- package/packs/sidekick/manifest-schema.ts +15 -228
- package/packs/sidekick/manifest.ts +107 -7
- package/packs/sidekick/manifest.yaml +199 -1
- package/packs/sidekick/package.json +4 -1
- package/packs/sidekick/policy-factory.ts +38 -0
- package/packs/sidekick/tool-impl/channel-tools.ts +99 -0
- package/packs/sidekick/tool-impl/memory-tools.ts +86 -1
- package/packs/sidekick/tool-impl/routine-tools.ts +156 -2
- package/packs/sidekick/tool-impl/storage.ts +6 -5
- package/packs/sidekick/tool-impl/task-tools.ts +186 -4
- package/packs/software-delivery/.turbo/turbo-build.log +1 -1
- package/packs/software-delivery/package.json +1 -1
- package/templates/core/.lumenflow/constraints.md.template +68 -3
- package/templates/core/LUMENFLOW.md.template +26 -26
- package/templates/core/_frameworks/lumenflow/wu-sizing-guide.md.template +3 -5
- package/templates/core/ai/onboarding/agent-safety-card.md.template +14 -4
- package/templates/core/ai/onboarding/first-wu-mistakes.md.template +96 -0
- package/templates/core/ai/onboarding/quick-ref-commands.md.template +50 -39
- package/templates/core/ai/onboarding/rapid-prototyping.md +2 -1
- package/templates/core/ai/onboarding/starting-prompt.md.template +5 -4
- package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +69 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
id: sidekick
|
|
2
2
|
version: 0.1.0
|
|
3
3
|
config_key: sidekick
|
|
4
|
+
policy_factory: policy-factory.ts#createSidekickPolicyFactory
|
|
4
5
|
task_types:
|
|
5
6
|
- sidekick
|
|
6
7
|
tools:
|
|
@@ -49,7 +50,7 @@ tools:
|
|
|
49
50
|
properties:
|
|
50
51
|
status:
|
|
51
52
|
type: string
|
|
52
|
-
enum: [pending, done]
|
|
53
|
+
enum: [pending, done, canceled]
|
|
53
54
|
priority:
|
|
54
55
|
type: string
|
|
55
56
|
enum: [P0, P1, P2, P3]
|
|
@@ -67,6 +68,67 @@ tools:
|
|
|
67
68
|
output_schema:
|
|
68
69
|
type: object
|
|
69
70
|
additionalProperties: true
|
|
71
|
+
- name: task:update
|
|
72
|
+
entry: tool-impl/task-tools.ts
|
|
73
|
+
permission: write
|
|
74
|
+
required_scopes:
|
|
75
|
+
- type: path
|
|
76
|
+
pattern: .sidekick/**
|
|
77
|
+
access: read
|
|
78
|
+
- type: path
|
|
79
|
+
pattern: .sidekick/**
|
|
80
|
+
access: write
|
|
81
|
+
input_schema:
|
|
82
|
+
type: object
|
|
83
|
+
properties:
|
|
84
|
+
id:
|
|
85
|
+
type: string
|
|
86
|
+
minLength: 1
|
|
87
|
+
title:
|
|
88
|
+
type: string
|
|
89
|
+
minLength: 1
|
|
90
|
+
description:
|
|
91
|
+
type: string
|
|
92
|
+
priority:
|
|
93
|
+
type: string
|
|
94
|
+
enum: [P0, P1, P2, P3]
|
|
95
|
+
tags:
|
|
96
|
+
type: array
|
|
97
|
+
items: { type: string }
|
|
98
|
+
due_at:
|
|
99
|
+
type: string
|
|
100
|
+
cron:
|
|
101
|
+
type: string
|
|
102
|
+
dry_run:
|
|
103
|
+
type: boolean
|
|
104
|
+
required: [id]
|
|
105
|
+
additionalProperties: false
|
|
106
|
+
output_schema:
|
|
107
|
+
type: object
|
|
108
|
+
additionalProperties: true
|
|
109
|
+
- name: task:cancel
|
|
110
|
+
entry: tool-impl/task-tools.ts
|
|
111
|
+
permission: admin
|
|
112
|
+
required_scopes:
|
|
113
|
+
- type: path
|
|
114
|
+
pattern: .sidekick/**
|
|
115
|
+
access: read
|
|
116
|
+
- type: path
|
|
117
|
+
pattern: .sidekick/**
|
|
118
|
+
access: write
|
|
119
|
+
input_schema:
|
|
120
|
+
type: object
|
|
121
|
+
properties:
|
|
122
|
+
id:
|
|
123
|
+
type: string
|
|
124
|
+
minLength: 1
|
|
125
|
+
dry_run:
|
|
126
|
+
type: boolean
|
|
127
|
+
required: [id]
|
|
128
|
+
additionalProperties: false
|
|
129
|
+
output_schema:
|
|
130
|
+
type: object
|
|
131
|
+
additionalProperties: true
|
|
70
132
|
- name: task:complete
|
|
71
133
|
entry: tool-impl/task-tools.ts
|
|
72
134
|
permission: write
|
|
@@ -174,6 +236,29 @@ tools:
|
|
|
174
236
|
type: object
|
|
175
237
|
additionalProperties: true
|
|
176
238
|
- name: memory:forget
|
|
239
|
+
entry: tool-impl/memory-tools.ts
|
|
240
|
+
permission: admin
|
|
241
|
+
required_scopes:
|
|
242
|
+
- type: path
|
|
243
|
+
pattern: .sidekick/**
|
|
244
|
+
access: read
|
|
245
|
+
- type: path
|
|
246
|
+
pattern: .sidekick/**
|
|
247
|
+
access: write
|
|
248
|
+
input_schema:
|
|
249
|
+
type: object
|
|
250
|
+
properties:
|
|
251
|
+
id:
|
|
252
|
+
type: string
|
|
253
|
+
minLength: 1
|
|
254
|
+
dry_run:
|
|
255
|
+
type: boolean
|
|
256
|
+
required: [id]
|
|
257
|
+
additionalProperties: false
|
|
258
|
+
output_schema:
|
|
259
|
+
type: object
|
|
260
|
+
additionalProperties: true
|
|
261
|
+
- name: memory:update
|
|
177
262
|
entry: tool-impl/memory-tools.ts
|
|
178
263
|
permission: write
|
|
179
264
|
required_scopes:
|
|
@@ -189,6 +274,15 @@ tools:
|
|
|
189
274
|
id:
|
|
190
275
|
type: string
|
|
191
276
|
minLength: 1
|
|
277
|
+
type:
|
|
278
|
+
type: string
|
|
279
|
+
enum: [fact, preference, note, snippet]
|
|
280
|
+
content:
|
|
281
|
+
type: string
|
|
282
|
+
minLength: 1
|
|
283
|
+
tags:
|
|
284
|
+
type: array
|
|
285
|
+
items: { type: string }
|
|
192
286
|
dry_run:
|
|
193
287
|
type: boolean
|
|
194
288
|
required: [id]
|
|
@@ -222,6 +316,43 @@ tools:
|
|
|
222
316
|
output_schema:
|
|
223
317
|
type: object
|
|
224
318
|
additionalProperties: true
|
|
319
|
+
- name: channel:list
|
|
320
|
+
entry: tool-impl/channel-tools.ts
|
|
321
|
+
permission: read
|
|
322
|
+
required_scopes:
|
|
323
|
+
- type: path
|
|
324
|
+
pattern: .sidekick/**
|
|
325
|
+
access: read
|
|
326
|
+
input_schema:
|
|
327
|
+
type: object
|
|
328
|
+
properties: {}
|
|
329
|
+
additionalProperties: false
|
|
330
|
+
output_schema:
|
|
331
|
+
type: object
|
|
332
|
+
additionalProperties: true
|
|
333
|
+
- name: channel:delete
|
|
334
|
+
entry: tool-impl/channel-tools.ts
|
|
335
|
+
permission: admin
|
|
336
|
+
required_scopes:
|
|
337
|
+
- type: path
|
|
338
|
+
pattern: .sidekick/**
|
|
339
|
+
access: read
|
|
340
|
+
- type: path
|
|
341
|
+
pattern: .sidekick/**
|
|
342
|
+
access: write
|
|
343
|
+
input_schema:
|
|
344
|
+
type: object
|
|
345
|
+
properties:
|
|
346
|
+
id:
|
|
347
|
+
type: string
|
|
348
|
+
minLength: 1
|
|
349
|
+
dry_run:
|
|
350
|
+
type: boolean
|
|
351
|
+
required: [id]
|
|
352
|
+
additionalProperties: false
|
|
353
|
+
output_schema:
|
|
354
|
+
type: object
|
|
355
|
+
additionalProperties: true
|
|
225
356
|
- name: channel:send
|
|
226
357
|
entry: tool-impl/channel-tools.ts
|
|
227
358
|
permission: write
|
|
@@ -342,6 +473,73 @@ tools:
|
|
|
342
473
|
output_schema:
|
|
343
474
|
type: object
|
|
344
475
|
additionalProperties: true
|
|
476
|
+
- name: routine:update
|
|
477
|
+
entry: tool-impl/routine-tools.ts
|
|
478
|
+
permission: write
|
|
479
|
+
required_scopes:
|
|
480
|
+
- type: path
|
|
481
|
+
pattern: .sidekick/**
|
|
482
|
+
access: read
|
|
483
|
+
- type: path
|
|
484
|
+
pattern: .sidekick/**
|
|
485
|
+
access: write
|
|
486
|
+
input_schema:
|
|
487
|
+
type: object
|
|
488
|
+
properties:
|
|
489
|
+
id:
|
|
490
|
+
type: string
|
|
491
|
+
minLength: 1
|
|
492
|
+
name:
|
|
493
|
+
type: string
|
|
494
|
+
minLength: 1
|
|
495
|
+
steps:
|
|
496
|
+
type: array
|
|
497
|
+
minItems: 1
|
|
498
|
+
items:
|
|
499
|
+
type: object
|
|
500
|
+
properties:
|
|
501
|
+
tool:
|
|
502
|
+
type: string
|
|
503
|
+
minLength: 1
|
|
504
|
+
input:
|
|
505
|
+
type: object
|
|
506
|
+
additionalProperties: true
|
|
507
|
+
required: [tool]
|
|
508
|
+
additionalProperties: false
|
|
509
|
+
cron:
|
|
510
|
+
type: string
|
|
511
|
+
enabled:
|
|
512
|
+
type: boolean
|
|
513
|
+
dry_run:
|
|
514
|
+
type: boolean
|
|
515
|
+
required: [id]
|
|
516
|
+
additionalProperties: false
|
|
517
|
+
output_schema:
|
|
518
|
+
type: object
|
|
519
|
+
additionalProperties: true
|
|
520
|
+
- name: routine:delete
|
|
521
|
+
entry: tool-impl/routine-tools.ts
|
|
522
|
+
permission: admin
|
|
523
|
+
required_scopes:
|
|
524
|
+
- type: path
|
|
525
|
+
pattern: .sidekick/**
|
|
526
|
+
access: read
|
|
527
|
+
- type: path
|
|
528
|
+
pattern: .sidekick/**
|
|
529
|
+
access: write
|
|
530
|
+
input_schema:
|
|
531
|
+
type: object
|
|
532
|
+
properties:
|
|
533
|
+
id:
|
|
534
|
+
type: string
|
|
535
|
+
minLength: 1
|
|
536
|
+
dry_run:
|
|
537
|
+
type: boolean
|
|
538
|
+
required: [id]
|
|
539
|
+
additionalProperties: false
|
|
540
|
+
output_schema:
|
|
541
|
+
type: object
|
|
542
|
+
additionalProperties: true
|
|
345
543
|
- name: routine:run
|
|
346
544
|
entry: tool-impl/routine-tools.ts
|
|
347
545
|
permission: read
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumenflow/packs-sidekick",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.21.0",
|
|
4
4
|
"description": "Sidekick personal assistant pack for LumenFlow — 16 tools for task management, typed memory, channels, routines, and audit",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lumenflow",
|
|
@@ -50,6 +50,9 @@
|
|
|
50
50
|
"test": "vitest run",
|
|
51
51
|
"typecheck": "tsc --noEmit"
|
|
52
52
|
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@lumenflow/kernel": "workspace:^"
|
|
55
|
+
},
|
|
53
56
|
"devDependencies": {
|
|
54
57
|
"typescript": "^5.9.3",
|
|
55
58
|
"vitest": "^4.0.18"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Copyright (c) 2026 Hellmai Ltd
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
+
|
|
4
|
+
import { POLICY_TRIGGERS, type PackPolicyFactory, type PolicyRule } from '@lumenflow/kernel';
|
|
5
|
+
import { SIDEKICK_POLICY_ID_PREFIX } from './constants.js';
|
|
6
|
+
|
|
7
|
+
export const SIDEKICK_APPROVAL_REQUIRED_TOOL_NAMES = [
|
|
8
|
+
'task:cancel',
|
|
9
|
+
'memory:forget',
|
|
10
|
+
'channel:delete',
|
|
11
|
+
'routine:delete',
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
const SIDEKICK_APPROVAL_REQUIRED_TOOL_NAME_SET = new Set<string>(
|
|
15
|
+
SIDEKICK_APPROVAL_REQUIRED_TOOL_NAMES,
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export function isSidekickApprovalRequiredToolName(toolName: string): boolean {
|
|
19
|
+
return SIDEKICK_APPROVAL_REQUIRED_TOOL_NAME_SET.has(toolName);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const createSidekickPolicyFactory: PackPolicyFactory = async () => {
|
|
23
|
+
if (SIDEKICK_APPROVAL_REQUIRED_TOOL_NAME_SET.size === 0) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const approvalRule: PolicyRule = {
|
|
28
|
+
id: `${SIDEKICK_POLICY_ID_PREFIX}.destructive-approval`,
|
|
29
|
+
trigger: POLICY_TRIGGERS.ON_TOOL_REQUEST,
|
|
30
|
+
decision: 'approval_required',
|
|
31
|
+
reason: 'Destructive Sidekick tools require explicit approval before execution.',
|
|
32
|
+
when: (context) =>
|
|
33
|
+
typeof context.tool_name === 'string' &&
|
|
34
|
+
isSidekickApprovalRequiredToolName(context.tool_name.trim()),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return [approvalRule];
|
|
38
|
+
};
|
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
|
|
24
24
|
const TOOL_NAMES = {
|
|
25
25
|
CONFIGURE: 'channel:configure',
|
|
26
|
+
LIST: 'channel:list',
|
|
27
|
+
DELETE: 'channel:delete',
|
|
26
28
|
SEND: 'channel:send',
|
|
27
29
|
RECEIVE: 'channel:receive',
|
|
28
30
|
} as const;
|
|
@@ -116,6 +118,99 @@ async function channelConfigureTool(
|
|
|
116
118
|
return success({ channel: resolvedChannel as unknown as Record<string, unknown> });
|
|
117
119
|
}
|
|
118
120
|
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// channel:list
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
async function channelListTool(_input: unknown, _context?: ToolContextLike): Promise<ToolOutput> {
|
|
126
|
+
const storage = getStoragePort();
|
|
127
|
+
const channels = await storage.readStore('channels');
|
|
128
|
+
const messages = await storage.readStore('messages');
|
|
129
|
+
|
|
130
|
+
const items = channels
|
|
131
|
+
.map((channel) => {
|
|
132
|
+
const channelMessages = messages.filter((message) => message.channel_id === channel.id);
|
|
133
|
+
const lastMessage = channelMessages.toSorted(
|
|
134
|
+
(left, right) => Date.parse(right.created_at) - Date.parse(left.created_at),
|
|
135
|
+
)[0];
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
...channel,
|
|
139
|
+
message_count: channelMessages.length,
|
|
140
|
+
last_message_at: lastMessage?.created_at,
|
|
141
|
+
};
|
|
142
|
+
})
|
|
143
|
+
.toSorted((left, right) => {
|
|
144
|
+
const leftTs = left.last_message_at ?? left.updated_at;
|
|
145
|
+
const rightTs = right.last_message_at ?? right.updated_at;
|
|
146
|
+
return Date.parse(rightTs) - Date.parse(leftTs);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return success({
|
|
150
|
+
items: items as unknown as Record<string, unknown>,
|
|
151
|
+
count: items.length,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// channel:delete
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
async function channelDeleteTool(input: unknown, context?: ToolContextLike): Promise<ToolOutput> {
|
|
160
|
+
const parsed = toRecord(input);
|
|
161
|
+
const id = asNonEmptyString(parsed.id);
|
|
162
|
+
|
|
163
|
+
if (!id) {
|
|
164
|
+
return failure('INVALID_INPUT', 'id is required.');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const storage = getStoragePort();
|
|
168
|
+
const channels = await storage.readStore('channels');
|
|
169
|
+
const channel = channels.find((entry) => entry.id === id);
|
|
170
|
+
|
|
171
|
+
if (!channel) {
|
|
172
|
+
return failure('NOT_FOUND', `channel ${id} was not found.`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const messages = await storage.readStore('messages');
|
|
176
|
+
const deletedMessages = messages.filter((message) => message.channel_id === id);
|
|
177
|
+
|
|
178
|
+
if (isDryRun(parsed)) {
|
|
179
|
+
return success({
|
|
180
|
+
dry_run: true,
|
|
181
|
+
deleted_id: id,
|
|
182
|
+
deleted_message_count: deletedMessages.length,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
await storage.withLock(async () => {
|
|
187
|
+
const latestChannels = await storage.readStore('channels');
|
|
188
|
+
const latestMessages = await storage.readStore('messages');
|
|
189
|
+
await storage.writeStore(
|
|
190
|
+
'channels',
|
|
191
|
+
latestChannels.filter((entry) => entry.id !== id),
|
|
192
|
+
);
|
|
193
|
+
await storage.writeStore(
|
|
194
|
+
'messages',
|
|
195
|
+
latestMessages.filter((message) => message.channel_id !== id),
|
|
196
|
+
);
|
|
197
|
+
await storage.appendAudit(
|
|
198
|
+
buildAuditEvent({
|
|
199
|
+
tool: TOOL_NAMES.DELETE,
|
|
200
|
+
op: 'delete',
|
|
201
|
+
context,
|
|
202
|
+
ids: [id],
|
|
203
|
+
details: { deleted_message_count: deletedMessages.length },
|
|
204
|
+
}),
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
return success({
|
|
209
|
+
deleted_id: id,
|
|
210
|
+
deleted_message_count: deletedMessages.length,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
119
214
|
// ---------------------------------------------------------------------------
|
|
120
215
|
// channel:send
|
|
121
216
|
// ---------------------------------------------------------------------------
|
|
@@ -390,6 +485,10 @@ export default async function channelTools(
|
|
|
390
485
|
switch (context?.tool_name) {
|
|
391
486
|
case TOOL_NAMES.CONFIGURE:
|
|
392
487
|
return channelConfigureTool(input, context);
|
|
488
|
+
case TOOL_NAMES.LIST:
|
|
489
|
+
return channelListTool(input, context);
|
|
490
|
+
case TOOL_NAMES.DELETE:
|
|
491
|
+
return channelDeleteTool(input, context);
|
|
393
492
|
case TOOL_NAMES.SEND:
|
|
394
493
|
return channelSendTool(input, context);
|
|
395
494
|
case TOOL_NAMES.RECEIVE:
|
|
@@ -26,10 +26,11 @@ import {
|
|
|
26
26
|
const TOOL_NAMES = {
|
|
27
27
|
STORE: 'memory:store',
|
|
28
28
|
RECALL: 'memory:recall',
|
|
29
|
+
UPDATE: 'memory:update',
|
|
29
30
|
FORGET: 'memory:forget',
|
|
30
31
|
} as const;
|
|
31
32
|
|
|
32
|
-
const VALID_MEMORY_TYPES: MemoryType[] = ['fact', 'preference', 'note'];
|
|
33
|
+
const VALID_MEMORY_TYPES: MemoryType[] = ['fact', 'preference', 'note', 'snippet'];
|
|
33
34
|
|
|
34
35
|
// ---------------------------------------------------------------------------
|
|
35
36
|
// Helpers
|
|
@@ -126,6 +127,88 @@ async function memoryRecallTool(input: unknown, _context?: ToolContextLike): Pro
|
|
|
126
127
|
});
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// memory:update
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
async function memoryUpdateTool(input: unknown, context?: ToolContextLike): Promise<ToolOutput> {
|
|
135
|
+
const parsed = toRecord(input);
|
|
136
|
+
const id = asNonEmptyString(parsed.id);
|
|
137
|
+
|
|
138
|
+
if (!id) {
|
|
139
|
+
return failure('INVALID_INPUT', 'id is required.');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const patch: Partial<MemoryRecord> = {};
|
|
143
|
+
if (parsed.type !== undefined) {
|
|
144
|
+
const type = asMemoryType(parsed.type);
|
|
145
|
+
if (!type) {
|
|
146
|
+
return failure('INVALID_INPUT', 'type must be one of fact, preference, note, snippet.');
|
|
147
|
+
}
|
|
148
|
+
patch.type = type;
|
|
149
|
+
}
|
|
150
|
+
if (parsed.content !== undefined) {
|
|
151
|
+
const content = asNonEmptyString(parsed.content);
|
|
152
|
+
if (!content) {
|
|
153
|
+
return failure('INVALID_INPUT', 'content must be a non-empty string when provided.');
|
|
154
|
+
}
|
|
155
|
+
patch.content = content;
|
|
156
|
+
}
|
|
157
|
+
if (parsed.tags !== undefined) {
|
|
158
|
+
patch.tags = asStringArray(parsed.tags);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (Object.keys(patch).length === 0) {
|
|
162
|
+
return failure(
|
|
163
|
+
'INVALID_INPUT',
|
|
164
|
+
'memory:update requires at least one of type, content, or tags.',
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const storage = getStoragePort();
|
|
169
|
+
const memories = await storage.readStore('memories');
|
|
170
|
+
const memory = memories.find((entry) => entry.id === id);
|
|
171
|
+
|
|
172
|
+
if (!memory) {
|
|
173
|
+
return failure('NOT_FOUND', `memory ${id} was not found.`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const preview: MemoryRecord = {
|
|
177
|
+
...memory,
|
|
178
|
+
...patch,
|
|
179
|
+
updated_at: nowIso(),
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
if (isDryRun(parsed)) {
|
|
183
|
+
return success({
|
|
184
|
+
dry_run: true,
|
|
185
|
+
memory: preview as unknown as Record<string, unknown>,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
await storage.withLock(async () => {
|
|
190
|
+
const latest = await storage.readStore('memories');
|
|
191
|
+
const target = latest.find((entry) => entry.id === id);
|
|
192
|
+
if (!target) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
Object.assign(target, preview);
|
|
196
|
+
await storage.writeStore('memories', latest);
|
|
197
|
+
await storage.appendAudit(
|
|
198
|
+
buildAuditEvent({
|
|
199
|
+
tool: TOOL_NAMES.UPDATE,
|
|
200
|
+
op: 'update',
|
|
201
|
+
context,
|
|
202
|
+
ids: [id],
|
|
203
|
+
}),
|
|
204
|
+
);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const updated = await storage.readStore('memories');
|
|
208
|
+
const updatedMemory = updated.find((entry) => entry.id === id);
|
|
209
|
+
return success({ memory: updatedMemory as unknown as Record<string, unknown> });
|
|
210
|
+
}
|
|
211
|
+
|
|
129
212
|
// ---------------------------------------------------------------------------
|
|
130
213
|
// memory:forget
|
|
131
214
|
// ---------------------------------------------------------------------------
|
|
@@ -180,6 +263,8 @@ export default async function memoryTools(
|
|
|
180
263
|
return memoryStoreTool(input, context);
|
|
181
264
|
case TOOL_NAMES.RECALL:
|
|
182
265
|
return memoryRecallTool(input, context);
|
|
266
|
+
case TOOL_NAMES.UPDATE:
|
|
267
|
+
return memoryUpdateTool(input, context);
|
|
183
268
|
case TOOL_NAMES.FORGET:
|
|
184
269
|
return memoryForgetTool(input, context);
|
|
185
270
|
default:
|