@lumenflow/cli 3.19.0 → 3.20.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.
@@ -34,26 +34,33 @@ const SIDEKICK_SCOPE_WRITE: PathScope = {
34
34
  };
35
35
 
36
36
  // ---------------------------------------------------------------------------
37
- // 16-tool contract (per strategy: 5 groups)
37
+ // Sidekick tool contract
38
38
  // ---------------------------------------------------------------------------
39
39
 
40
40
  const TOOL_PERMISSIONS_MAP = {
41
- // Task tools (4)
41
+ // Task tools (6)
42
42
  'task:create': TOOL_PERMISSIONS.WRITE,
43
43
  'task:list': TOOL_PERMISSIONS.READ,
44
+ 'task:update': TOOL_PERMISSIONS.WRITE,
45
+ 'task:cancel': TOOL_PERMISSIONS.ADMIN,
44
46
  'task:complete': TOOL_PERMISSIONS.WRITE,
45
47
  'task:schedule': TOOL_PERMISSIONS.WRITE,
46
- // Memory tools (3)
48
+ // Memory tools (4)
47
49
  'memory:store': TOOL_PERMISSIONS.WRITE,
48
50
  'memory:recall': TOOL_PERMISSIONS.READ,
49
- 'memory:forget': TOOL_PERMISSIONS.WRITE,
50
- // Channel tools (3)
51
+ 'memory:update': TOOL_PERMISSIONS.WRITE,
52
+ 'memory:forget': TOOL_PERMISSIONS.ADMIN,
53
+ // Channel tools (5)
51
54
  'channel:configure': TOOL_PERMISSIONS.WRITE,
55
+ 'channel:list': TOOL_PERMISSIONS.READ,
56
+ 'channel:delete': TOOL_PERMISSIONS.ADMIN,
52
57
  'channel:send': TOOL_PERMISSIONS.WRITE,
53
58
  'channel:receive': TOOL_PERMISSIONS.READ,
54
- // Routine tools (3)
59
+ // Routine tools (5)
55
60
  'routine:create': TOOL_PERMISSIONS.WRITE,
56
61
  'routine:list': TOOL_PERMISSIONS.READ,
62
+ 'routine:update': TOOL_PERMISSIONS.WRITE,
63
+ 'routine:delete': TOOL_PERMISSIONS.ADMIN,
57
64
  'routine:run': TOOL_PERMISSIONS.READ, // plan-only, no execution
58
65
  // System tools (3)
59
66
  'sidekick:init': TOOL_PERMISSIONS.WRITE,
@@ -68,20 +75,28 @@ const MEMORY_TOOLS_ENTRY = 'tool-impl/memory-tools.ts';
68
75
  const CHANNEL_TOOLS_ENTRY = 'tool-impl/channel-tools.ts';
69
76
  const ROUTINE_TOOLS_ENTRY = 'tool-impl/routine-tools.ts';
70
77
  const SYSTEM_TOOLS_ENTRY = 'tool-impl/system-tools.ts';
78
+ const POLICY_FACTORY_ENTRY = 'policy-factory.ts#createSidekickPolicyFactory';
71
79
 
72
80
  const TOOL_ENTRIES: Record<SidekickToolName, string> = {
73
81
  'task:create': TASK_TOOLS_ENTRY,
74
82
  'task:list': TASK_TOOLS_ENTRY,
83
+ 'task:update': TASK_TOOLS_ENTRY,
84
+ 'task:cancel': TASK_TOOLS_ENTRY,
75
85
  'task:complete': TASK_TOOLS_ENTRY,
76
86
  'task:schedule': TASK_TOOLS_ENTRY,
77
87
  'memory:store': MEMORY_TOOLS_ENTRY,
78
88
  'memory:recall': MEMORY_TOOLS_ENTRY,
89
+ 'memory:update': MEMORY_TOOLS_ENTRY,
79
90
  'memory:forget': MEMORY_TOOLS_ENTRY,
80
91
  'channel:configure': CHANNEL_TOOLS_ENTRY,
92
+ 'channel:list': CHANNEL_TOOLS_ENTRY,
93
+ 'channel:delete': CHANNEL_TOOLS_ENTRY,
81
94
  'channel:send': CHANNEL_TOOLS_ENTRY,
82
95
  'channel:receive': CHANNEL_TOOLS_ENTRY,
83
96
  'routine:create': ROUTINE_TOOLS_ENTRY,
84
97
  'routine:list': ROUTINE_TOOLS_ENTRY,
98
+ 'routine:update': ROUTINE_TOOLS_ENTRY,
99
+ 'routine:delete': ROUTINE_TOOLS_ENTRY,
85
100
  'routine:run': ROUTINE_TOOLS_ENTRY,
86
101
  'sidekick:init': SYSTEM_TOOLS_ENTRY,
87
102
  'sidekick:status': SYSTEM_TOOLS_ENTRY,
@@ -109,7 +124,7 @@ const TOOL_INPUT_SCHEMAS: Record<SidekickToolName, Record<string, unknown>> = {
109
124
  'task:list': {
110
125
  type: 'object',
111
126
  properties: {
112
- status: { type: 'string', enum: ['pending', 'done'] },
127
+ status: { type: 'string', enum: ['pending', 'done', 'canceled'] },
113
128
  priority: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'] },
114
129
  tags: { type: 'array', items: { type: 'string' } },
115
130
  search: { type: 'string' },
@@ -118,6 +133,30 @@ const TOOL_INPUT_SCHEMAS: Record<SidekickToolName, Record<string, unknown>> = {
118
133
  },
119
134
  additionalProperties: false,
120
135
  },
136
+ 'task:update': {
137
+ type: 'object',
138
+ properties: {
139
+ id: { type: 'string', minLength: 1 },
140
+ title: { type: 'string', minLength: 1 },
141
+ description: { type: 'string' },
142
+ priority: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'] },
143
+ tags: { type: 'array', items: { type: 'string' } },
144
+ due_at: { type: 'string' },
145
+ cron: { type: 'string' },
146
+ dry_run: { type: 'boolean' },
147
+ },
148
+ required: ['id'],
149
+ additionalProperties: false,
150
+ },
151
+ 'task:cancel': {
152
+ type: 'object',
153
+ properties: {
154
+ id: { type: 'string', minLength: 1 },
155
+ dry_run: { type: 'boolean' },
156
+ },
157
+ required: ['id'],
158
+ additionalProperties: false,
159
+ },
121
160
  'task:complete': {
122
161
  type: 'object',
123
162
  properties: {
@@ -169,6 +208,18 @@ const TOOL_INPUT_SCHEMAS: Record<SidekickToolName, Record<string, unknown>> = {
169
208
  required: ['id'],
170
209
  additionalProperties: false,
171
210
  },
211
+ 'memory:update': {
212
+ type: 'object',
213
+ properties: {
214
+ id: { type: 'string', minLength: 1 },
215
+ type: { type: 'string', enum: ['fact', 'preference', 'note', 'snippet'] },
216
+ content: { type: 'string', minLength: 1 },
217
+ tags: { type: 'array', items: { type: 'string' } },
218
+ dry_run: { type: 'boolean' },
219
+ },
220
+ required: ['id'],
221
+ additionalProperties: false,
222
+ },
172
223
  'channel:configure': {
173
224
  type: 'object',
174
225
  properties: {
@@ -179,6 +230,20 @@ const TOOL_INPUT_SCHEMAS: Record<SidekickToolName, Record<string, unknown>> = {
179
230
  required: ['name'],
180
231
  additionalProperties: false,
181
232
  },
233
+ 'channel:list': {
234
+ type: 'object',
235
+ properties: {},
236
+ additionalProperties: false,
237
+ },
238
+ 'channel:delete': {
239
+ type: 'object',
240
+ properties: {
241
+ id: { type: 'string', minLength: 1 },
242
+ dry_run: { type: 'boolean' },
243
+ },
244
+ required: ['id'],
245
+ additionalProperties: false,
246
+ },
182
247
  'channel:send': {
183
248
  type: 'object',
184
249
  properties: {
@@ -236,6 +301,40 @@ const TOOL_INPUT_SCHEMAS: Record<SidekickToolName, Record<string, unknown>> = {
236
301
  },
237
302
  additionalProperties: false,
238
303
  },
304
+ 'routine:update': {
305
+ type: 'object',
306
+ properties: {
307
+ id: { type: 'string', minLength: 1 },
308
+ name: { type: 'string', minLength: 1 },
309
+ steps: {
310
+ type: 'array',
311
+ minItems: 1,
312
+ items: {
313
+ type: 'object',
314
+ properties: {
315
+ tool: { type: 'string', minLength: 1 },
316
+ input: { type: 'object', additionalProperties: true },
317
+ },
318
+ required: ['tool'],
319
+ additionalProperties: false,
320
+ },
321
+ },
322
+ cron: { type: 'string' },
323
+ enabled: { type: 'boolean' },
324
+ dry_run: { type: 'boolean' },
325
+ },
326
+ required: ['id'],
327
+ additionalProperties: false,
328
+ },
329
+ 'routine:delete': {
330
+ type: 'object',
331
+ properties: {
332
+ id: { type: 'string', minLength: 1 },
333
+ dry_run: { type: 'boolean' },
334
+ },
335
+ required: ['id'],
336
+ additionalProperties: false,
337
+ },
239
338
  'routine:run': {
240
339
  type: 'object',
241
340
  properties: {
@@ -310,6 +409,7 @@ const SIDEKICK_MANIFEST_TEMPLATE = {
310
409
  id: SIDEKICK_PACK_ID,
311
410
  version: SIDEKICK_PACK_VERSION,
312
411
  config_key: SIDEKICK_PACK_ID,
412
+ policy_factory: POLICY_FACTORY_ENTRY,
313
413
  task_types: ['sidekick'],
314
414
  tools: SIDEKICK_TOOL_NAMES.map((name) => buildTool(name)),
315
415
  policies: [
@@ -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.19.0",
3
+ "version": "3.20.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: