@davvidbaker/flambe-cli 0.1.3 → 0.1.5

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,94 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { CommandError, commandState } from './commands.mjs';
3
+ import { applyReviewedDecision, fail, schema, system } from './reducer-review.mjs';
4
+
5
+ const lifetime = 15 * 60_000;
6
+ const canonical = value => JSON.stringify(value, function (key, item) {
7
+ return item && typeof item === 'object' && !Array.isArray(item)
8
+ ? Object.fromEntries(Object.keys(item).sort().map(k => [k, item[k]])) : item;
9
+ });
10
+ const requireAgent = agentId => {
11
+ if (typeof agentId !== 'string' || !agentId.trim()) throw fail('AGENT_ID_REQUIRED', 'Use the same stable agent_id to request and submit a work-AI review.');
12
+ };
13
+ const instructions = `${system}
14
+ You are the current work AI, not an independent reviewer. Evaluate the user's intent
15
+ and this snapshot before continuing. For this work-AI review, ask and reparent are
16
+ also allowed on current; reparent only a running current to reparent_candidates.
17
+ Submit your decision with flambe_decide (MCP) or flambe decide (CLI), using this
18
+ review_id, trace, instance, and the same agent_id. Do not claim independent approval.
19
+ If you cannot judge safely, use direction escalate with no actions and ask the user.
20
+ A lifecycle proposal is already recorded: do not repeat it to request review.
21
+ On REDUCER_STATE_CHANGED or REVIEW_EXPIRED, request a fresh flambe_message review.`;
22
+
23
+ export function createWorkAIReviewer(store) {
24
+ return {
25
+ request(userId, { traceId, activityId, agentId, context, snapshotHash, result }) {
26
+ requireAgent(agentId);
27
+ const id = randomUUID();
28
+ const now = Date.now();
29
+ const reviewedContext = { ...context, reviewer_mode: 'agent' };
30
+ store.db.prepare('DELETE FROM reducer_reviews WHERE created_ms < ?').run(now - 86_400_000);
31
+ store.db.prepare(`INSERT INTO reducer_reviews
32
+ (id, user_id, trace_id, activity_id, agent_id, context, snapshot_hash, created_ms)
33
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(id, userId, traceId, activityId, agentId,
34
+ JSON.stringify(reviewedContext), snapshotHash, now);
35
+ return { activity_id: activityId, rules_fired: [], closed_descendants: [], actions_applied: [], ...result,
36
+ model_review: { mode: 'agent', status: 'awaiting_decision' },
37
+ review: { review_id: id, expires_at: new Date(now + lifetime).toISOString(),
38
+ context: reviewedContext, instructions, decision_schema: schema } };
39
+ },
40
+ submit(userId, attrs, agentId) {
41
+ requireAgent(agentId);
42
+ if (!attrs || typeof attrs.review_id !== 'string' || !Number.isSafeInteger(Number(attrs.trace_id)) || Number(attrs.trace_id) <= 0) {
43
+ throw fail('INVALID_INPUT', 'A review_id and positive trace_id are required.');
44
+ }
45
+ store.db.exec('BEGIN IMMEDIATE');
46
+ try {
47
+ const review = store.db.prepare('SELECT * FROM reducer_reviews WHERE id = ? AND user_id = ? AND trace_id = ? AND agent_id = ?')
48
+ .get(attrs.review_id, userId, Number(attrs.trace_id), agentId);
49
+ if (!review) throw fail('REVIEW_NOT_FOUND', 'No matching review exists for this instance, trace, and agent.', 404);
50
+ if (review.receipt) {
51
+ if (canonical(attrs.decision) !== review.decision) throw fail('REVIEW_ALREADY_DECIDED', 'This review already has a different decision.', 409);
52
+ const response = { ...JSON.parse(review.receipt), replayed: true, state: commandState(store.getTrace(userId, review.trace_id)) };
53
+ store.db.exec('COMMIT');
54
+ return response;
55
+ }
56
+ if (Date.now() > review.created_ms + lifetime) throw fail('REVIEW_EXPIRED', 'Request a fresh review before submitting a decision.', 409);
57
+ const response = { ...applyReviewedDecision(store, { userId, traceId: review.trace_id, activityId: review.activity_id,
58
+ context: JSON.parse(review.context), snapshotHash: review.snapshot_hash, decision: attrs.decision,
59
+ reviewer: { mode: 'agent', agent_id: agentId } }), review_id: review.id };
60
+ store.db.prepare('UPDATE reducer_reviews SET decision = ?, receipt = ? WHERE id = ?')
61
+ .run(canonical(attrs.decision), JSON.stringify(response), review.id);
62
+ store.db.exec('COMMIT');
63
+ return response;
64
+ } catch (error) {
65
+ store.db.exec('ROLLBACK');
66
+ if (error instanceof CommandError) throw error;
67
+ throw fail('REDUCER_PERSISTENCE_ERROR', 'The review decision could not be saved; no review changes were applied.', 503);
68
+ }
69
+ },
70
+ };
71
+ }
72
+
73
+ /** Routine replies expose current work; explicit status can still request full history. */
74
+ export function compactWorkAIResult(result, agentId) {
75
+ if (!result?.state?.activities) return result;
76
+ const activities = result.state.activities;
77
+ const byId = new Map(activities.map(a => [a.id, a]));
78
+ const keep = new Set([result.activity_id]);
79
+ for (const a of activities) if (a.agentId === agentId && a.status !== 'ended') keep.add(a.id);
80
+ for (const a of [...(result.actions_applied ?? []), ...(result.closed_descendants ?? [])]) {
81
+ if (a.activity_id != null) keep.add(a.activity_id);
82
+ if (a.parent_activity_id != null) keep.add(a.parent_activity_id);
83
+ }
84
+ for (const id of [...keep]) {
85
+ const seen = new Set();
86
+ let parent = byId.get(id)?.parentId;
87
+ while (parent != null && !seen.has(parent)) {
88
+ seen.add(parent); keep.add(parent); parent = byId.get(parent)?.parentId;
89
+ }
90
+ }
91
+ const visible = activities.filter(a => keep.has(a.id));
92
+ return { ...result, state: { ...result.state, activities: visible,
93
+ scope: 'current_activity_and_own_open_work', omitted_activity_count: activities.length - visible.length } };
94
+ }
@@ -0,0 +1,582 @@
1
+ [
2
+ {
3
+ "name": "flambe_traces",
4
+ "description": "Discover this connection's instance and available traces before the first Flambe action in each chat, even if a trace id is already known. Trace ids cannot select another instance.",
5
+ "title": "List Flambe traces",
6
+ "annotations": {
7
+ "readOnlyHint": true,
8
+ "destructiveHint": false,
9
+ "openWorldHint": false
10
+ },
11
+ "securitySchemes": [
12
+ {
13
+ "type": "oauth2",
14
+ "scopes": [
15
+ "flambe"
16
+ ]
17
+ }
18
+ ],
19
+ "inputSchema": {
20
+ "type": "object",
21
+ "required": [],
22
+ "properties": {},
23
+ "additionalProperties": false
24
+ }
25
+ },
26
+ {
27
+ "name": "flambe_start",
28
+ "description": "Start work in a flame. Omit parent_id to infer this agent's active parent, or pass null to start at the thread root. The reducer may rewrite the proposal and return direction or reply; act on either before continuing.",
29
+ "title": "Start a Flambe Activity",
30
+ "annotations": {
31
+ "readOnlyHint": false,
32
+ "destructiveHint": false,
33
+ "openWorldHint": true
34
+ },
35
+ "securitySchemes": [
36
+ {
37
+ "type": "oauth2",
38
+ "scopes": [
39
+ "flambe"
40
+ ]
41
+ }
42
+ ],
43
+ "inputSchema": {
44
+ "type": "object",
45
+ "required": [
46
+ "expected_instance_id",
47
+ "trace_id",
48
+ "name"
49
+ ],
50
+ "properties": {
51
+ "name": {
52
+ "type": "string",
53
+ "description": "Activity name",
54
+ "minLength": 1,
55
+ "maxLength": 255
56
+ },
57
+ "timestamp": {
58
+ "type": "integer",
59
+ "description": "Optional event time as Unix milliseconds"
60
+ },
61
+ "description": {
62
+ "type": "string",
63
+ "description": "Optional activity details"
64
+ },
65
+ "activity_id": {
66
+ "type": "integer",
67
+ "description": "Optional planned activity id to begin",
68
+ "minimum": 1
69
+ },
70
+ "thread_id": {
71
+ "type": "integer",
72
+ "description": "Thread id; defaults to the flame's default thread",
73
+ "minimum": 1
74
+ },
75
+ "parent_id": {
76
+ "description": "Parent activity id; omit to infer for this agent, null for a root",
77
+ "oneOf": [
78
+ {
79
+ "type": "integer",
80
+ "minimum": 1
81
+ },
82
+ {
83
+ "type": "null"
84
+ }
85
+ ]
86
+ },
87
+ "category_ids": {
88
+ "type": "array",
89
+ "items": {
90
+ "type": "integer",
91
+ "minimum": 1
92
+ }
93
+ },
94
+ "expected_instance_id": {
95
+ "type": "string",
96
+ "description": "Exact instance.id returned by flambe_traces on this connection. Never copy it from another connection.",
97
+ "minLength": 1
98
+ },
99
+ "trace_id": {
100
+ "type": "integer",
101
+ "description": "Flame/trace id",
102
+ "minimum": 1
103
+ },
104
+ "agent_id": {
105
+ "type": "string",
106
+ "description": "Stable agent id",
107
+ "minLength": 1,
108
+ "maxLength": 200
109
+ },
110
+ "agent_name": {
111
+ "type": "string",
112
+ "description": "Agent display name",
113
+ "minLength": 1,
114
+ "maxLength": 200
115
+ },
116
+ "platform": {
117
+ "type": "string",
118
+ "description": "Agent host product",
119
+ "minLength": 1,
120
+ "maxLength": 100
121
+ }
122
+ },
123
+ "additionalProperties": false
124
+ }
125
+ },
126
+ {
127
+ "name": "flambe_end",
128
+ "description": "End completed work. A message records a resolution; omitting it is a plain end. Open children must be closed first unless force is true. Act on any returned direction or reply.",
129
+ "title": "End a Flambe Activity",
130
+ "annotations": {
131
+ "readOnlyHint": false,
132
+ "destructiveHint": false,
133
+ "openWorldHint": false
134
+ },
135
+ "securitySchemes": [
136
+ {
137
+ "type": "oauth2",
138
+ "scopes": [
139
+ "flambe"
140
+ ]
141
+ }
142
+ ],
143
+ "inputSchema": {
144
+ "type": "object",
145
+ "required": [
146
+ "expected_instance_id",
147
+ "trace_id",
148
+ "activity_id"
149
+ ],
150
+ "properties": {
151
+ "force": {
152
+ "default": false,
153
+ "type": "boolean",
154
+ "description": "Also close open descendants"
155
+ },
156
+ "message": {
157
+ "type": "string",
158
+ "description": "Optional outcome or transition note"
159
+ },
160
+ "timestamp": {
161
+ "type": "integer",
162
+ "description": "Optional event time as Unix milliseconds"
163
+ },
164
+ "activity_id": {
165
+ "type": "integer",
166
+ "description": "Activity id",
167
+ "minimum": 1
168
+ },
169
+ "expected_instance_id": {
170
+ "type": "string",
171
+ "description": "Exact instance.id returned by flambe_traces on this connection. Never copy it from another connection.",
172
+ "minLength": 1
173
+ },
174
+ "trace_id": {
175
+ "type": "integer",
176
+ "description": "Flame/trace id",
177
+ "minimum": 1
178
+ },
179
+ "agent_id": {
180
+ "type": "string",
181
+ "description": "Stable agent id",
182
+ "minLength": 1,
183
+ "maxLength": 200
184
+ },
185
+ "agent_name": {
186
+ "type": "string",
187
+ "description": "Agent display name",
188
+ "minLength": 1,
189
+ "maxLength": 200
190
+ },
191
+ "platform": {
192
+ "type": "string",
193
+ "description": "Agent host product",
194
+ "minLength": 1,
195
+ "maxLength": 100
196
+ }
197
+ },
198
+ "additionalProperties": false
199
+ }
200
+ },
201
+ {
202
+ "name": "flambe_suspend",
203
+ "description": "Suspend work that is explicitly being tabled. Act on any returned direction or reply.",
204
+ "title": "Suspend a Flambe Activity",
205
+ "annotations": {
206
+ "readOnlyHint": false,
207
+ "destructiveHint": false,
208
+ "openWorldHint": false
209
+ },
210
+ "securitySchemes": [
211
+ {
212
+ "type": "oauth2",
213
+ "scopes": [
214
+ "flambe"
215
+ ]
216
+ }
217
+ ],
218
+ "inputSchema": {
219
+ "type": "object",
220
+ "required": [
221
+ "expected_instance_id",
222
+ "trace_id",
223
+ "activity_id"
224
+ ],
225
+ "properties": {
226
+ "force": {
227
+ "default": false,
228
+ "type": "boolean",
229
+ "description": "For end, also close open descendants"
230
+ },
231
+ "message": {
232
+ "type": "string",
233
+ "description": "Optional outcome or transition note"
234
+ },
235
+ "timestamp": {
236
+ "type": "integer",
237
+ "description": "Optional event time as Unix milliseconds"
238
+ },
239
+ "activity_id": {
240
+ "type": "integer",
241
+ "description": "Activity id",
242
+ "minimum": 1
243
+ },
244
+ "expected_instance_id": {
245
+ "type": "string",
246
+ "description": "Exact instance.id returned by flambe_traces on this connection. Never copy it from another connection.",
247
+ "minLength": 1
248
+ },
249
+ "trace_id": {
250
+ "type": "integer",
251
+ "description": "Flame/trace id",
252
+ "minimum": 1
253
+ },
254
+ "agent_id": {
255
+ "type": "string",
256
+ "description": "Stable agent id",
257
+ "minLength": 1,
258
+ "maxLength": 200
259
+ },
260
+ "agent_name": {
261
+ "type": "string",
262
+ "description": "Agent display name",
263
+ "minLength": 1,
264
+ "maxLength": 200
265
+ },
266
+ "platform": {
267
+ "type": "string",
268
+ "description": "Agent host product",
269
+ "minLength": 1,
270
+ "maxLength": 100
271
+ }
272
+ },
273
+ "additionalProperties": false
274
+ }
275
+ },
276
+ {
277
+ "name": "flambe_resume",
278
+ "description": "Resume suspended work. Act on any returned direction or reply.",
279
+ "title": "Resume a Flambe Activity",
280
+ "annotations": {
281
+ "readOnlyHint": false,
282
+ "destructiveHint": false,
283
+ "openWorldHint": false
284
+ },
285
+ "securitySchemes": [
286
+ {
287
+ "type": "oauth2",
288
+ "scopes": [
289
+ "flambe"
290
+ ]
291
+ }
292
+ ],
293
+ "inputSchema": {
294
+ "type": "object",
295
+ "required": [
296
+ "expected_instance_id",
297
+ "trace_id",
298
+ "activity_id"
299
+ ],
300
+ "properties": {
301
+ "force": {
302
+ "default": false,
303
+ "type": "boolean",
304
+ "description": "For end, also close open descendants"
305
+ },
306
+ "message": {
307
+ "type": "string",
308
+ "description": "Optional outcome or transition note"
309
+ },
310
+ "timestamp": {
311
+ "type": "integer",
312
+ "description": "Optional event time as Unix milliseconds"
313
+ },
314
+ "activity_id": {
315
+ "type": "integer",
316
+ "description": "Activity id",
317
+ "minimum": 1
318
+ },
319
+ "expected_instance_id": {
320
+ "type": "string",
321
+ "description": "Exact instance.id returned by flambe_traces on this connection. Never copy it from another connection.",
322
+ "minLength": 1
323
+ },
324
+ "trace_id": {
325
+ "type": "integer",
326
+ "description": "Flame/trace id",
327
+ "minimum": 1
328
+ },
329
+ "agent_id": {
330
+ "type": "string",
331
+ "description": "Stable agent id",
332
+ "minLength": 1,
333
+ "maxLength": 200
334
+ },
335
+ "agent_name": {
336
+ "type": "string",
337
+ "description": "Agent display name",
338
+ "minLength": 1,
339
+ "maxLength": 200
340
+ },
341
+ "platform": {
342
+ "type": "string",
343
+ "description": "Agent host product",
344
+ "minLength": 1,
345
+ "maxLength": 100
346
+ }
347
+ },
348
+ "additionalProperties": false
349
+ }
350
+ },
351
+ {
352
+ "name": "flambe_status",
353
+ "description": "Read the activity stack and per-thread recommended availableActions without changing it.",
354
+ "title": "Read Flambe State",
355
+ "annotations": {
356
+ "readOnlyHint": true,
357
+ "destructiveHint": false,
358
+ "openWorldHint": false
359
+ },
360
+ "securitySchemes": [
361
+ {
362
+ "type": "oauth2",
363
+ "scopes": [
364
+ "flambe"
365
+ ]
366
+ }
367
+ ],
368
+ "inputSchema": {
369
+ "type": "object",
370
+ "required": [
371
+ "expected_instance_id",
372
+ "trace_id"
373
+ ],
374
+ "properties": {
375
+ "thread_id": {
376
+ "type": "integer",
377
+ "description": "Optional thread id to focus state and availableActions",
378
+ "minimum": 1
379
+ },
380
+ "active_only": {
381
+ "default": false,
382
+ "type": "boolean"
383
+ },
384
+ "suspended_only": {
385
+ "default": false,
386
+ "type": "boolean"
387
+ },
388
+ "include_unstarted": {
389
+ "default": false,
390
+ "type": "boolean",
391
+ "description": "Include unstarted activities"
392
+ },
393
+ "expected_instance_id": {
394
+ "type": "string",
395
+ "description": "Exact instance.id returned by flambe_traces on this connection. Never copy it from another connection.",
396
+ "minLength": 1
397
+ },
398
+ "trace_id": {
399
+ "type": "integer",
400
+ "description": "Flame/trace id",
401
+ "minimum": 1
402
+ },
403
+ "agent_id": {
404
+ "type": "string",
405
+ "description": "Stable agent id",
406
+ "minLength": 1,
407
+ "maxLength": 200
408
+ },
409
+ "agent_name": {
410
+ "type": "string",
411
+ "description": "Agent display name",
412
+ "minLength": 1,
413
+ "maxLength": 200
414
+ },
415
+ "platform": {
416
+ "type": "string",
417
+ "description": "Agent host product",
418
+ "minLength": 1,
419
+ "maxLength": 100
420
+ }
421
+ },
422
+ "additionalProperties": false
423
+ }
424
+ },
425
+ {
426
+ "name": "flambe_message",
427
+ "description": "Report a meaningful update to the Reducer Agent. It may recommend direction and, when allowed, make one stack change. Follow a returned direction before continuing.",
428
+ "title": "Message the Flambe Reducer Agent",
429
+ "annotations": {
430
+ "readOnlyHint": false,
431
+ "destructiveHint": false,
432
+ "openWorldHint": true
433
+ },
434
+ "securitySchemes": [
435
+ {
436
+ "type": "oauth2",
437
+ "scopes": [
438
+ "flambe"
439
+ ]
440
+ }
441
+ ],
442
+ "inputSchema": {
443
+ "type": "object",
444
+ "required": [
445
+ "expected_instance_id",
446
+ "trace_id",
447
+ "message"
448
+ ],
449
+ "properties": {
450
+ "message": {
451
+ "type": "string",
452
+ "description": "Concise work update or question",
453
+ "minLength": 1
454
+ },
455
+ "activity_id": {
456
+ "type": "integer",
457
+ "description": "Activity id; omit to infer this agent's active activity",
458
+ "minimum": 1
459
+ },
460
+ "allow_stack_changes": {
461
+ "default": true,
462
+ "type": "boolean",
463
+ "description": "Set false for advice only"
464
+ },
465
+ "expected_instance_id": {
466
+ "type": "string",
467
+ "description": "Exact instance.id returned by flambe_traces on this connection. Never copy it from another connection.",
468
+ "minLength": 1
469
+ },
470
+ "trace_id": {
471
+ "type": "integer",
472
+ "description": "Flame/trace id",
473
+ "minimum": 1
474
+ },
475
+ "agent_id": {
476
+ "type": "string",
477
+ "description": "Stable agent id",
478
+ "minLength": 1,
479
+ "maxLength": 200
480
+ },
481
+ "agent_name": {
482
+ "type": "string",
483
+ "description": "Agent display name",
484
+ "minLength": 1,
485
+ "maxLength": 200
486
+ },
487
+ "platform": {
488
+ "type": "string",
489
+ "description": "Agent host product",
490
+ "minLength": 1,
491
+ "maxLength": 100
492
+ }
493
+ },
494
+ "additionalProperties": false
495
+ }
496
+ },
497
+ {
498
+ "name": "flambe_plan",
499
+ "description": "Create an activity before it begins. Omit both times for limbo, or pass scheduled_start and scheduled_end as Unix millisecond timestamps.",
500
+ "title": "Plan an unstarted Flambe activity",
501
+ "annotations": {
502
+ "readOnlyHint": false,
503
+ "destructiveHint": false,
504
+ "openWorldHint": true
505
+ },
506
+ "securitySchemes": [
507
+ {
508
+ "type": "oauth2",
509
+ "scopes": [
510
+ "flambe"
511
+ ]
512
+ }
513
+ ],
514
+ "inputSchema": {
515
+ "type": "object",
516
+ "required": [
517
+ "expected_instance_id",
518
+ "trace_id",
519
+ "name"
520
+ ],
521
+ "properties": {
522
+ "name": {
523
+ "type": "string",
524
+ "description": "Activity name",
525
+ "minLength": 1,
526
+ "maxLength": 255
527
+ },
528
+ "description": {
529
+ "type": "string"
530
+ },
531
+ "category_ids": {
532
+ "type": "array",
533
+ "items": {
534
+ "type": "integer",
535
+ "minimum": 1
536
+ }
537
+ },
538
+ "weight": {
539
+ "type": "integer",
540
+ "minimum": 0
541
+ },
542
+ "scheduled_start": {
543
+ "type": "integer",
544
+ "description": "Optional event time as Unix milliseconds"
545
+ },
546
+ "scheduled_end": {
547
+ "type": "integer",
548
+ "description": "Optional event time as Unix milliseconds"
549
+ },
550
+ "expected_instance_id": {
551
+ "type": "string",
552
+ "description": "Exact instance.id returned by flambe_traces on this connection. Never copy it from another connection.",
553
+ "minLength": 1
554
+ },
555
+ "trace_id": {
556
+ "type": "integer",
557
+ "description": "Flame/trace id",
558
+ "minimum": 1
559
+ },
560
+ "agent_id": {
561
+ "type": "string",
562
+ "description": "Stable agent id",
563
+ "minLength": 1,
564
+ "maxLength": 200
565
+ },
566
+ "agent_name": {
567
+ "type": "string",
568
+ "description": "Agent display name",
569
+ "minLength": 1,
570
+ "maxLength": 200
571
+ },
572
+ "platform": {
573
+ "type": "string",
574
+ "description": "Agent host product",
575
+ "minLength": 1,
576
+ "maxLength": 100
577
+ }
578
+ },
579
+ "additionalProperties": false
580
+ }
581
+ }
582
+ ]