@jungjaehoon/mama-os 0.27.6 → 0.28.1

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.
Files changed (65) hide show
  1. package/dist/agent/agent-loop.d.ts.map +1 -1
  2. package/dist/agent/agent-loop.js +7 -7
  3. package/dist/agent/agent-loop.js.map +1 -1
  4. package/dist/agent/code-act/host-bridge.d.ts.map +1 -1
  5. package/dist/agent/code-act/host-bridge.js +7 -0
  6. package/dist/agent/code-act/host-bridge.js.map +1 -1
  7. package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
  8. package/dist/agent/gateway-tool-executor.js +30 -10
  9. package/dist/agent/gateway-tool-executor.js.map +1 -1
  10. package/dist/agent/gateway-tools.md +1 -0
  11. package/dist/agent/tool-registry.d.ts.map +1 -1
  12. package/dist/agent/tool-registry.js +6 -0
  13. package/dist/agent/tool-registry.js.map +1 -1
  14. package/dist/agent/types.d.ts +1 -9
  15. package/dist/agent/types.d.ts.map +1 -1
  16. package/dist/agent/types.js.map +1 -1
  17. package/dist/cli/commands/start.d.ts.map +1 -1
  18. package/dist/cli/commands/start.js +11 -44
  19. package/dist/cli/commands/start.js.map +1 -1
  20. package/dist/cli/config/types.d.ts.map +1 -1
  21. package/dist/cli/config/types.js +1 -0
  22. package/dist/cli/config/types.js.map +1 -1
  23. package/dist/cli/runtime/agent-loop-init.js +8 -8
  24. package/dist/cli/runtime/agent-loop-init.js.map +1 -1
  25. package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
  26. package/dist/cli/runtime/api-routes-init.js +56 -409
  27. package/dist/cli/runtime/api-routes-init.js.map +1 -1
  28. package/dist/cli/runtime/temporal-init.d.ts +0 -2
  29. package/dist/cli/runtime/temporal-init.d.ts.map +1 -1
  30. package/dist/cli/runtime/temporal-init.js +1 -18
  31. package/dist/cli/runtime/temporal-init.js.map +1 -1
  32. package/dist/connectors/trello/index.d.ts +13 -2
  33. package/dist/connectors/trello/index.d.ts.map +1 -1
  34. package/dist/connectors/trello/index.js +96 -19
  35. package/dist/connectors/trello/index.js.map +1 -1
  36. package/dist/gateways/message-router.d.ts.map +1 -1
  37. package/dist/gateways/message-router.js +14 -1
  38. package/dist/gateways/message-router.js.map +1 -1
  39. package/dist/operator/console-brief.d.ts +24 -0
  40. package/dist/operator/console-brief.d.ts.map +1 -0
  41. package/dist/operator/console-brief.js +139 -0
  42. package/dist/operator/console-brief.js.map +1 -0
  43. package/dist/operator/task-ledger.d.ts +1 -1
  44. package/dist/operator/task-ledger.js +1 -1
  45. package/dist/operator/temporal-runtime.d.ts +1 -4
  46. package/dist/operator/temporal-runtime.d.ts.map +1 -1
  47. package/dist/operator/temporal-runtime.js +8 -16
  48. package/dist/operator/temporal-runtime.js.map +1 -1
  49. package/dist/operator/worker-run.d.ts +2 -3
  50. package/dist/operator/worker-run.d.ts.map +1 -1
  51. package/dist/operator/worker-run.js +1 -2
  52. package/dist/operator/worker-run.js.map +1 -1
  53. package/dist/operator/workorder-consumer.d.ts +5 -7
  54. package/dist/operator/workorder-consumer.d.ts.map +1 -1
  55. package/dist/operator/workorder-consumer.js +3 -4
  56. package/dist/operator/workorder-consumer.js.map +1 -1
  57. package/dist/operator/workorder-publishers.d.ts +12 -31
  58. package/dist/operator/workorder-publishers.d.ts.map +1 -1
  59. package/dist/operator/workorder-publishers.js +19 -48
  60. package/dist/operator/workorder-publishers.js.map +1 -1
  61. package/package.json +1 -1
  62. package/dist/operator/shadow-capture.d.ts +0 -20
  63. package/dist/operator/shadow-capture.d.ts.map +0 -1
  64. package/dist/operator/shadow-capture.js +0 -34
  65. package/dist/operator/shadow-capture.js.map +0 -1
@@ -49,7 +49,6 @@ const node_os_1 = require("node:os");
49
49
  const express_1 = __importDefault(require("express"));
50
50
  const node_path_1 = __importDefault(require("node:path"));
51
51
  const node_http_1 = __importDefault(require("node:http"));
52
- const session_service_js_1 = require("../../validation/session-service.js");
53
52
  const agent_store_js_1 = require("../../db/agent-store.js");
54
53
  const memory_agent_dashboard_js_1 = require("../../memory/memory-agent-dashboard.js");
55
54
  const upload_handler_js_1 = require("../../api/upload-handler.js");
@@ -64,13 +63,11 @@ const { DebugLogger } = debugLogger;
64
63
  const routesLogger = new DebugLogger('api-routes');
65
64
  async function registerApiRoutes(params) {
66
65
  const { config, apiServer, eventBus, oauthManager: _oauthManager, mamaApi: _mamaApi, messageRouter, agentLoop, toolExecutor, discordGateway, slackGateway, graphHandler, getAdapter, sessionsDb, workOrderConsumer, } = params;
67
- // ── Stage-2 publisher gate (plan S2-T2) ───────────────────────────────
68
- // Throws at boot on a malformed flag value (no-fallback: a typo must not
69
- // silently revert a believed-active migration to legacy).
70
- const stage2Flag = (0, workorder_publishers_js_1.readStage2Flag)();
71
- if (stage2Flag !== 'off') {
72
- console.log(`[stage2] workorder publishers active (flag=${stage2Flag})`);
73
- }
66
+ // ── Stage-2 workorder publishers ──────────────────────────────────────
67
+ // The ONLY system run path since v0.28.0: every scheduled/boot/manual run
68
+ // below enqueues an occurrence-keyed workorder for the consumer lane (the
69
+ // legacy executeValidatedRun persona runs were removed after the 2026-07-22
70
+ // production cutover).
74
71
  const enqueueWorkOrderOrThrow = (workKind, idempotencyKey, payload, priority) => {
75
72
  const ledger = toolExecutor.getTaskLedger();
76
73
  if (!ledger) {
@@ -80,144 +77,6 @@ async function registerApiRoutes(params) {
80
77
  const wo = ledger.enqueueWorkOrder({ workKind, idempotencyKey, input: payload, priority });
81
78
  console.log(`[stage2] enqueued workorder ${workKind}#${wo.id} key=${idempotencyKey}`);
82
79
  };
83
- // ── Validation Session Service ────────────────────────────────────────
84
- const validationService = sessionsDb ? new session_service_js_1.ValidationSessionService(sessionsDb) : null;
85
- /**
86
- * executeValidatedRun — wraps pm.getSharedProcess().sendMessage()
87
- * with validation session lifecycle (before → execute → after → classify).
88
- */
89
- async function executeValidatedRun(agentId, prompt, opts) {
90
- const pm = toolExecutor.getAgentProcessManager();
91
- if (!pm)
92
- throw new Error(`AgentProcessManager not available`);
93
- let agentVersion = 0;
94
- let session = null;
95
- const startTime = Date.now();
96
- try {
97
- const ver = sessionsDb ? (0, agent_store_js_1.getLatestVersion)(sessionsDb, agentId) : null;
98
- agentVersion = ver?.version ?? 0;
99
- session = validationService?.startSession(agentId, agentVersion, 'system_run') ?? null;
100
- }
101
- catch (bootstrapErr) {
102
- routesLogger.warn('[executeValidatedRun] Failed to initialize validation bootstrap:', bootstrapErr);
103
- agentVersion = 0;
104
- session = null;
105
- }
106
- if (sessionsDb) {
107
- try {
108
- const startRow = (0, agent_store_js_1.logActivity)(sessionsDb, {
109
- agent_id: agentId,
110
- agent_version: agentVersion,
111
- type: 'task_start',
112
- input_summary: prompt.slice(0, 200),
113
- run_id: session?.id,
114
- execution_status: 'started',
115
- trigger_reason: 'system_run',
116
- });
117
- if (session) {
118
- try {
119
- validationService?.recordRun(session.id, { activityId: startRow.id });
120
- }
121
- catch (telemetryErr) {
122
- routesLogger.warn('[executeValidatedRun] Failed to link startup activity to validation session:', telemetryErr);
123
- }
124
- }
125
- }
126
- catch (telemetryErr) {
127
- routesLogger.warn('[executeValidatedRun] Failed to write startup telemetry:', telemetryErr);
128
- }
129
- }
130
- try {
131
- const process = await pm.getSharedProcess(agentId, opts);
132
- const result = await process.sendMessage(prompt);
133
- const durationMs = Date.now() - startTime;
134
- const noUpdate = result?.response?.includes('NO_UPDATE');
135
- const usage = result?.usage;
136
- const tokensUsed = usage ? (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0) : 0;
137
- try {
138
- if (sessionsDb) {
139
- const completeRow = (0, agent_store_js_1.logActivity)(sessionsDb, {
140
- agent_id: agentId,
141
- agent_version: agentVersion,
142
- type: noUpdate ? 'task_skipped' : 'task_complete',
143
- input_summary: prompt.slice(0, 200),
144
- output_summary: result?.response?.slice(0, 500),
145
- duration_ms: durationMs,
146
- tokens_used: tokensUsed,
147
- run_id: session?.id,
148
- execution_status: 'completed',
149
- trigger_reason: 'system_run',
150
- });
151
- if (session) {
152
- validationService?.recordRun(session.id, {
153
- activityId: completeRow.id,
154
- duration_ms: durationMs,
155
- tokens_used: tokensUsed,
156
- });
157
- }
158
- }
159
- }
160
- catch (telemetryErr) {
161
- routesLogger.warn('[executeValidatedRun] Failed to write completion telemetry:', telemetryErr);
162
- }
163
- try {
164
- if (session && validationService) {
165
- validationService.finalizeSession(session.id, {
166
- execution_status: 'completed',
167
- metrics: {
168
- duration_ms: durationMs,
169
- token_cost: tokensUsed,
170
- },
171
- });
172
- }
173
- }
174
- catch (telemetryErr) {
175
- routesLogger.warn('[executeValidatedRun] Failed to finalize validation session:', telemetryErr);
176
- }
177
- return { response: result?.response, noUpdate };
178
- }
179
- catch (err) {
180
- const durationMs = Date.now() - startTime;
181
- const originalError = err;
182
- try {
183
- if (sessionsDb) {
184
- const errorRow = (0, agent_store_js_1.logActivity)(sessionsDb, {
185
- agent_id: agentId,
186
- agent_version: agentVersion,
187
- type: 'task_error',
188
- input_summary: prompt.slice(0, 200),
189
- error_message: err instanceof Error ? err.message : String(err),
190
- duration_ms: durationMs,
191
- run_id: session?.id,
192
- execution_status: 'failed',
193
- trigger_reason: 'system_run',
194
- });
195
- if (session) {
196
- validationService?.recordRun(session.id, {
197
- activityId: errorRow.id,
198
- duration_ms: durationMs,
199
- });
200
- }
201
- }
202
- }
203
- catch (telemetryErr) {
204
- routesLogger.warn('[executeValidatedRun] Failed to write error telemetry:', telemetryErr);
205
- }
206
- try {
207
- if (session && validationService) {
208
- validationService.finalizeSession(session.id, {
209
- execution_status: 'failed',
210
- error_message: err instanceof Error ? err.message : String(err),
211
- metrics: { duration_ms: durationMs },
212
- });
213
- }
214
- }
215
- catch (telemetryErr) {
216
- routesLogger.warn('[executeValidatedRun] Failed to finalize failed validation session:', telemetryErr);
217
- }
218
- throw originalError;
219
- }
220
- }
221
80
  // Wire EventBus to tool executor for agent_notices tool
222
81
  toolExecutor.setAgentEventBus(eventBus);
223
82
  (0, operator_tasks_handler_js_1.registerOperatorTaskRoutes)(apiServer.app, {
@@ -296,123 +155,49 @@ async function registerApiRoutes(params) {
296
155
  });
297
156
  }
298
157
  if (dashboardAgentConfigured) {
299
- // ── Dashboard Agent ───────────────────────────────────────────────
158
+ // ── Dashboard board publisher ─────────────────────────────────────
159
+ // Persona seeding stays: the optional legacy dashboard-agent config is
160
+ // still usable via multi-agent delegation; only its RUN path here was
161
+ // replaced by board workorders (consumer lane, brief-owned prompt).
300
162
  const { ensureDashboardPersona } = await import('../../multi-agent/dashboard-agent-persona.js');
301
163
  ensureDashboardPersona();
302
164
  routesLogger.debug('[Dashboard Agent] Persona ensured at ~/.mama/personas/dashboard.md');
303
- // Dashboard cron: 30-min interval via AgentProcessManager
304
- // Built PER RUN: today's date gives report context and auxiliary D-day.
305
- // Temporal categories still come only from task_list.temporal_state.
306
- const buildDashboardPrompt = () => `You are triggered on a schedule. Today is ${new Date().toISOString().slice(0, 10)}. Before writing anything, determine if an update is needed:
307
-
308
- 1. Use agent_notices({limit: 50}) to find the most recent dashboard-agent publish/task_complete notice. Treat that as the last briefing boundary.
309
- 2. Use context_compile first to find recent substantive decisions (limit 20, max_tool_calls 2, strictness "balanced").
310
- Use this exact task text for context_compile: "recent substantive project decisions, task progress, agent alerts, and major changes".
311
- Do not include dashboard_briefing, wiki_compilation, system-audit, or audit-log labels in the context_compile task text; filter those operational summaries after the packet returns.
312
- If context_compile is unavailable because there is no active worker envelope, fall back to mama_search once (limit 20).
313
- 3. If NO substantive decisions or agent alerts exist since the last dashboard publish → respond "NO_UPDATE" and stop. Do NOT call report_publish.
314
- 4. If new substantive information exists -> analyze it and publish ALL FOUR board slots (briefing, action_required, decisions, pipeline) in a SINGLE report_publish call, using the board HTML vocabulary from your persona. The pipeline slot is the item tracker projected from task_list (see your persona); use temporal_state as the canonical time category and D-day only as an auxiliary display computed from today's date.
315
- 5. Do NOT call mama_save for the briefing; report_publish and agent_activity are the durable operational record.
316
-
317
- This saves resources. Only publish when there is genuinely new information to report.`;
318
- // Owner-initiated refresh skips the NO_UPDATE delta gate: an explicit
319
- // request means "rebuild the board now", not "tell me nothing changed".
320
- const buildForcedDashboardPrompt = () => buildDashboardPrompt().replace(/3\. If NO substantive decisions[^\n]*\n/, '3. The owner explicitly requested a fresh board: do NOT reply NO_UPDATE. Rebuild and publish even if nothing changed since the last publish.\n');
321
- const doDashboardRun = async (opts) => {
322
- // Stage-2 gate at the FUNCTION level: schedule, boot, manual REST all
323
- // funnel here, so this single gate covers every entry path (plan F4).
324
- const boardAction = (0, workorder_publishers_js_1.resolvePublishAction)(stage2Flag, 'board');
325
- if (boardAction !== 'legacy') {
326
- try {
327
- const now = Date.now();
328
- enqueueWorkOrderOrThrow('board', opts?.force ? (0, workorder_publishers_js_1.boardManualKey)(now) : (0, workorder_publishers_js_1.boardFullKey)(now), { mode: 'full', ...(opts?.force ? { force: true } : {}) }, opts?.force ? 'high' : undefined);
329
- }
330
- catch (err) {
331
- routesLogger.error('[stage2] board enqueue failed:', err instanceof Error ? err.message : err);
332
- }
333
- // 'enqueue' (on): legacy stops - an enqueue failure must NOT silently
334
- // fall back to a legacy run; the next occurrence retries.
335
- // 'both' (shadow): legacy keeps publishing live below.
336
- if (boardAction === 'enqueue')
337
- return;
338
- }
339
- const pm = toolExecutor.getAgentProcessManager();
340
- if (!pm) {
341
- routesLogger.warn('[Dashboard Agent] AgentProcessManager not available yet');
342
- return;
343
- }
165
+ // Schedule/boot/manual all funnel here: one occurrence-keyed board
166
+ // workorder per entry. An enqueue failure is logged loudly and the next
167
+ // occurrence retries - there is no fallback run path.
168
+ const runDashboardAgent = (opts) => {
344
169
  try {
345
- // console.log on run outcomes: the DebugLogger only surfaces warn/error in the
346
- // daemon log, and a silent outcome reads as a hang from the outside.
347
- console.log(`[Dashboard Agent] run started${opts?.force ? ' (owner-forced, delta gate bypassed)' : ''}`);
348
- const { noUpdate } = await executeValidatedRun('dashboard-agent', opts?.force ? buildForcedDashboardPrompt() : buildDashboardPrompt());
349
- console.log(noUpdate
350
- ? '[Dashboard Agent] no changes detected, publish skipped'
351
- : '[Dashboard Agent] board published');
170
+ const now = Date.now();
171
+ enqueueWorkOrderOrThrow('board', opts?.force ? (0, workorder_publishers_js_1.boardManualKey)(now) : (0, workorder_publishers_js_1.boardFullKey)(now), { mode: 'full', ...(opts?.force ? { force: true } : {}) }, opts?.force ? 'high' : undefined);
352
172
  }
353
173
  catch (err) {
354
- routesLogger.error('[Dashboard Agent] Error:', err instanceof Error ? err.message : err);
174
+ routesLogger.error('[stage2] board enqueue failed:', err instanceof Error ? err.message : err);
355
175
  }
356
176
  };
357
- // ONE board-writer queue: dashboard cron, manual refresh, AND reconcile runs
358
- // all serialize here -- the shared agent process rejects concurrent requests
359
- // ('Process is busy' class). Per-job rejection propagates to the caller
360
- // while the chain itself survives (M8 review: a caller must be able to
361
- // distinguish its own job's failure).
362
- let boardWriterChain = Promise.resolve();
363
- const boardWriterQueue = {
364
- push(job) {
365
- const jobPromise = boardWriterChain.then(job);
366
- boardWriterChain = jobPromise.catch(() => { });
367
- return jobPromise;
368
- },
369
- };
370
- const runDashboardAgent = (opts) => boardWriterQueue.push(() => doDashboardRun(opts)).catch(() => { });
371
177
  // First run after 10s (let connectors poll first), then every 30 min
372
178
  setTimeout(runDashboardAgent, 10_000);
373
179
  setInterval(runDashboardAgent, 30 * 60 * 1000);
374
- // Manual trigger (owner-forced: bypasses the delta gate)
180
+ // Manual trigger (owner-forced: the workorder carries force=true so the
181
+ // worker brief skips its NO_UPDATE delta gate)
375
182
  apiServer.app.post('/api/report/agent-refresh', auth_middleware_js_1.requireAuth, async (_req, res) => {
376
- runDashboardAgent({ force: true }).catch(() => { });
377
- res.json({ ok: true, message: 'Dashboard agent triggered (forced refresh)' });
183
+ runDashboardAgent({ force: true });
184
+ res.json({ ok: true, message: 'Board refresh workorder enqueued (forced)' });
378
185
  });
379
186
  // -- M8 board reconcile leg (freshness layer; default OFF, opt-in like
380
187
  // MAMA_TRIGGER_LOOP). The trigger loop emits operator:channel-delta after
381
188
  // committing its cursor; the 30-min cron above remains the repair pass.
382
189
  if (process.env.MAMA_BOARD_RECONCILE === '1') {
383
- const { buildReconcilePrompt, ReconcileScheduler } = await import('../../operator/board-reconcile.js');
384
- const { captureSnapshot, verifyAfterRun, OBLIGATED_TOOLS } = await import('../../operator/action-verifier.js');
385
- const kagemushaContext = (process.env.MAMA_RECONCILE_TASK_CONTEXT ?? 'native') === 'kagemusha';
386
- // Verifier deps (M8 Phase 2): run-bound signals only -- trace rows from
387
- // the dashboard agent's gateway_tool_call activity, notes from the
388
- // operator ledger. Observe, never block.
190
+ const { ReconcileScheduler } = await import('../../operator/board-reconcile.js');
191
+ const { captureSnapshot, verifyAfterRun } = await import('../../operator/action-verifier.js');
192
+ // Verifier deps (M8 Phase 2): run-bound signals only -- notes from the
193
+ // operator ledger, board slots. Observe, never block. Trace queries are
194
+ // worker-keyed (buildWorkerTraceQueries below): reconcile runs execute
195
+ // on the workorder consumer lane only.
389
196
  const reconcileLedger = toolExecutor.getTaskLedger();
390
- // gateway_tool_call rows now populate normalized_tool_name at the logging
391
- // site; the input_summary fallback covers rows written before that fix.
392
- const traceToolList = OBLIGATED_TOOLS.map((t) => `'${t}'`).join(',');
393
197
  const verifierDeps = {
394
198
  getSlots: () => apiServer.reportStore.getAllSorted(),
395
199
  getLedgerHash: () => reconcileLedger?.payloadHash() ?? '',
396
200
  getScopedNoteMaxId: (scope) => reconcileLedger?.maxNoUpdateId(scope) ?? 0,
397
- getTraceMaxId: () => {
398
- if (!sessionsDb)
399
- return 0;
400
- const row = sessionsDb
401
- .prepare(`SELECT MAX(id) AS max_id FROM agent_activity
402
- WHERE type = 'gateway_tool_call' AND agent_id = 'dashboard-agent'`)
403
- .get();
404
- return row.max_id ?? 0;
405
- },
406
- countObligatedTraceRowsSince: (maxId) => {
407
- if (!sessionsDb)
408
- return 0;
409
- const row = sessionsDb
410
- .prepare(`SELECT COUNT(*) AS n FROM agent_activity
411
- WHERE type = 'gateway_tool_call' AND agent_id = 'dashboard-agent'
412
- AND id > ? AND (normalized_tool_name IN (${traceToolList}) OR input_summary IN (${traceToolList}))`)
413
- .get(maxId);
414
- return row.n;
415
- },
416
201
  };
417
202
  // Stage-2 board completion hook (plan D1/E3/G1): the consumer wraps
418
203
  // reconcile workorders with the SAME bracket the legacy closure owns,
@@ -477,66 +262,22 @@ This saves resources. Only publish when there is genuinely new information to re
477
262
  globalMaxPerHour: Number(process.env.MAMA_RECONCILE_MAX_PER_HOUR) || undefined,
478
263
  log: (line) => console.log(line),
479
264
  run: (channelKey, deltaLines) => {
480
- // Stage-2: at 'on' the reconcile leg becomes a board workorder (its
481
- // bracket verification moves to the consumer's completion hook);
482
- // shadow/off keep the legacy bracket-verified path (plan C1).
483
- if ((0, workorder_publishers_js_1.resolveReconcileAction)(stage2Flag) === 'enqueue') {
484
- try {
485
- enqueueWorkOrderOrThrow('board', (0, workorder_publishers_js_1.boardReconcileKey)(channelKey, Date.now()), {
486
- mode: 'reconcile',
487
- channelKey,
488
- deltaLines,
489
- });
490
- }
491
- catch (err) {
492
- routesLogger.error('[stage2] reconcile enqueue failed:', err instanceof Error ? err.message : err);
493
- // REJECT so ReconcileScheduler restores pendingLines - a
494
- // resolved failure silently drops this delta (PR bot round).
495
- return Promise.reject(err instanceof Error ? err : new Error(String(err)));
496
- }
497
- return Promise.resolve();
498
- }
499
- return boardWriterQueue.push(async () => {
500
- const scope = `reconcile:${channelKey}`;
501
- const before = captureSnapshot(verifierDeps, scope);
502
- const prompt = buildReconcilePrompt({
265
+ // The reconcile leg is a board workorder; its bracket verification
266
+ // runs in the consumer's completion hook (registered above).
267
+ try {
268
+ enqueueWorkOrderOrThrow('board', (0, workorder_publishers_js_1.boardReconcileKey)(channelKey, Date.now()), {
269
+ mode: 'reconcile',
503
270
  channelKey,
504
271
  deltaLines,
505
- todayIso: new Date().toISOString().slice(0, 10),
506
- kagemushaContext,
507
- });
508
- await executeValidatedRun('dashboard-agent', prompt, {
509
- requestTimeout: 300_000,
510
272
  });
511
- const verdict = verifyAfterRun(verifierDeps, before, scope);
512
- const outcome = verdict.verified ? 'reconcile_verified' : 'reconcile_unverified';
513
- console.log(`[reconcile] ${outcome} channel=${channelKey}${verdict.effects.length > 0 ? ` (${verdict.effects.join('; ')})` : ''}`);
514
- try {
515
- if (sessionsDb) {
516
- (0, agent_store_js_1.logActivity)(sessionsDb, {
517
- agent_id: 'dashboard-agent',
518
- agent_version: 0,
519
- type: outcome,
520
- input_summary: `reconcile ${channelKey}`,
521
- output_summary: verdict.effects.join('; '),
522
- execution_status: 'completed',
523
- trigger_reason: 'reconcile',
524
- });
525
- }
526
- }
527
- catch {
528
- /* telemetry only */
529
- }
530
- if (!verdict.verified) {
531
- // Loud, never blocking (observability over restriction).
532
- eventBus.emit({
533
- type: 'agent:action',
534
- agent: 'Dashboard Agent',
535
- action: 'reconcile_unverified',
536
- target: channelKey,
537
- });
538
- }
539
- });
273
+ }
274
+ catch (err) {
275
+ routesLogger.error('[stage2] reconcile enqueue failed:', err instanceof Error ? err.message : err);
276
+ // REJECT so ReconcileScheduler restores pendingLines - a
277
+ // resolved failure silently drops this delta (PR bot round).
278
+ return Promise.reject(err instanceof Error ? err : new Error(String(err)));
279
+ }
280
+ return Promise.resolve();
540
281
  },
541
282
  });
542
283
  eventBus.on('operator:channel-delta', (event) => {
@@ -611,53 +352,15 @@ This saves resources. Only publish when there is genuinely new information to re
611
352
  pages: pages.map((p) => p.path || ''),
612
353
  });
613
354
  });
614
- // Wiki trigger via executeValidatedRun
615
- const doWikiRun = async (trigger) => {
616
- // Stage-2 gate (function level, plan F4). Wiki never enters shadow
617
- // (resolvePublishAction returns 'legacy' there) - its Obsidian writes
618
- // have no capture seam.
619
- if ((0, workorder_publishers_js_1.resolvePublishAction)(stage2Flag, 'wiki') === 'enqueue') {
620
- try {
621
- const now = Date.now();
622
- enqueueWorkOrderOrThrow('wiki', (0, workorder_publishers_js_1.wikiBatchKey)(trigger, now), { batchId: `${now}-${trigger}`, events: [trigger] }, trigger === 'manual' ? 'high' : undefined);
623
- }
624
- catch (err) {
625
- routesLogger.error('[stage2] wiki enqueue failed:', err instanceof Error ? err.message : err);
626
- }
627
- return;
628
- }
629
- if (!toolExecutor.getAgentProcessManager()) {
630
- routesLogger.warn('[Wiki Agent] AgentProcessManager not available yet');
631
- return;
632
- }
355
+ // Wiki compile runs are workorders: enqueue-only, the consumer lane runs
356
+ // them serially under the wiki brief (no direct persona run path).
357
+ const runWikiAgent = (trigger) => {
633
358
  try {
634
- routesLogger.debug('[Wiki Agent] Checking for updates...');
635
- const wikiPrompt = `You are triggered on a schedule. Before writing anything, determine if an update is needed:
636
-
637
- 1. Use agent_notices({limit: 100}) to find the most recent wiki-agent compiled/publish/task_complete notice. Treat that as the last compilation boundary.
638
- 2. NOVELTY CHECK by recency, not semantics: call mama_search({limit: 30}) with NO query -- that returns the newest decisions in creation order regardless of language or wording (semantic/lexical search misses cross-language items, which caused missed compilations). Compare created_at against the boundary.
639
- 3. If NO substantive decisions are newer than the boundary -> respond "NO_UPDATE" and stop. Do NOT call obsidian or wiki_publish.
640
- 4. If new items exist, use context_compile first to gather supporting context (limit 30, max_tool_calls 3, strictness "balanced").
641
- Use this exact task text for context_compile: "recent substantive project decisions, task progress, agent alerts, and major changes".
642
- Do not include dashboard_briefing, wiki_compilation, system-audit, or audit-log labels in the context_compile task text; filter those operational summaries after the packet returns.
643
- If the packet misses some of the new items from step 2 (it often will for cross-language content), write from the step-2 items directly -- the recency list is authoritative for WHAT is new; the packet only enriches.
644
- If context_compile is unavailable because there is no active worker envelope, fall back to ONE queried mama_search for enrichment and continue from the step-2 recency list.
645
- 5. Then follow your persona: APPEND today's daily note (daily/YYYY-MM-DD.md, create with the section skeleton on first write of the day), then promote qualifying lesson candidates into lessons/ pages (search before create; update, never duplicate). Do NOT write per-task status pages.
646
- 6. Do NOT call mama_save for the compilation; Obsidian/wiki files plus agent_activity are the durable operational record.
647
-
648
- This saves resources. Only compile when there is genuinely new information to document.`;
649
- const { noUpdate } = await executeValidatedRun('wiki-agent', wikiPrompt, {
650
- requestTimeout: 600_000,
651
- });
652
- if (noUpdate) {
653
- routesLogger.debug('[Wiki Agent] No changes detected, skipped');
654
- }
655
- else {
656
- routesLogger.debug('[Wiki Agent] Compilation complete');
657
- }
359
+ const now = Date.now();
360
+ enqueueWorkOrderOrThrow('wiki', (0, workorder_publishers_js_1.wikiBatchKey)(trigger, now), { batchId: `${now}-${trigger}`, events: [trigger] }, trigger === 'manual' ? 'high' : undefined);
658
361
  }
659
362
  catch (err) {
660
- routesLogger.error('[Wiki Agent] Error:', err instanceof Error ? err.message : err);
363
+ routesLogger.error('[stage2] wiki enqueue failed:', err instanceof Error ? err.message : err);
661
364
  }
662
365
  };
663
366
  // Stage-2 wiki completion hook (plan E4): outcome reading only - the
@@ -668,15 +371,6 @@ This saves resources. Only compile when there is genuinely new information to do
668
371
  after: buildWikiAfterHook((line) => routesLogger.debug(line)),
669
372
  });
670
373
  }
671
- // Serialize ALL wiki runs (boot, event-driven, manual) on one chain -- the
672
- // shared agent process rejects concurrent requests, so the boot run and a
673
- // manual trigger raced into 'Process is busy' (same pattern as the
674
- // dashboard agent's runDashboardAgent chain above).
675
- let wikiRunChain = Promise.resolve();
676
- const runWikiAgent = (trigger) => {
677
- wikiRunChain = wikiRunChain.then(() => doWikiRun(trigger)).catch(() => { });
678
- return wikiRunChain;
679
- };
680
374
  // Event-driven: compile when extraction completes. Trailing-edge debounce so
681
375
  // bursts of extraction:completed events coalesce into one wiki compile run.
682
376
  eventBus.onDebounced('extraction:completed', () => runWikiAgent('extraction:completed'), 30_000);
@@ -698,8 +392,8 @@ This saves resources. Only compile when there is genuinely new information to do
698
392
  });
699
393
  // Manual trigger API
700
394
  apiServer.app.post('/api/wiki/compile', auth_middleware_js_1.requireAuth, async (_req, res) => {
701
- runWikiAgent('manual').catch(() => { });
702
- res.json({ ok: true, message: 'Wiki compilation triggered' });
395
+ runWikiAgent('manual');
396
+ res.json({ ok: true, message: 'Wiki compile workorder enqueued' });
703
397
  });
704
398
  // First run after 15s (let connectors and dashboard agent go first)
705
399
  setTimeout(() => runWikiAgent('boot'), 15_000);
@@ -715,56 +409,16 @@ This saves resources. Only compile when there is genuinely new information to do
715
409
  if (memoryAgentConfigured) {
716
410
  const PROMOTION_INTERVAL_MS = Math.max(1, Number(process.env.MAMA_MEMORY_PROMOTION_HOURS) || 6) * 60 * 60 * 1000;
717
411
  const PROMOTION_INITIAL_DELAY_MS = 10 * 60 * 1000; // let connectors poll first
718
- const buildPromotionPrompt = (nowIso) => 'PROMOTION RUN. You are curating durable business memory from recent data. ' +
719
- `The current time is ${nowIso}.\n` +
720
- '1. agent_notices({limit: 100}): find your latest promotion notice (action "promoted" ' +
721
- 'or "no_update") and treat it as the boundary; default to the last 24h when absent.\n' +
722
- '2. kagemusha_entities({activeOnly: true}) to find the rooms active since the boundary, ' +
723
- 'then kagemusha_messages({channelId, since: <boundary ISO>}) on the busiest 3-4 rooms.\n' +
724
- '3. For each candidate judgment, mama_search first to find the existing topic; reuse it ' +
725
- 'so the evolution chain stays intact.\n' +
726
- '4. Promote at most 5 durable judgments per run via mama_save, following the PROMOTION RUN ' +
727
- 'rules in your persona (pricing/scope agreements, standing client preferences, process ' +
728
- 'rules, recurring risk patterns; NEVER task lifecycle states, greetings, or logistics). ' +
729
- 'Include scopes (the source channel, and the project when identifiable) and event_date.\n' +
730
- '5. Finish with exactly PROMOTED <n> or NO_UPDATE.';
731
- const doPromotionRun = async (opts) => {
732
- // Stage-2 gate (function level, plan F4). Promotion never enters shadow
733
- // - its mama_save writes have no capture seam.
734
- if ((0, workorder_publishers_js_1.resolvePublishAction)(stage2Flag, 'memory-curation') === 'enqueue') {
735
- try {
736
- const now = Date.now();
737
- enqueueWorkOrderOrThrow('memory-curation', opts?.manual ? (0, workorder_publishers_js_1.promotionManualKey)(now) : (0, workorder_publishers_js_1.promotionKey)(now), { scheduledAt: new Date(now).toISOString() }, opts?.manual ? 'high' : undefined);
738
- }
739
- catch (err) {
740
- routesLogger.error('[stage2] promotion enqueue failed:', err instanceof Error ? err.message : err);
741
- }
742
- return;
743
- }
744
- if (!toolExecutor.getAgentProcessManager()) {
745
- routesLogger.warn('[Memory Promotion] AgentProcessManager not available yet');
746
- return;
747
- }
412
+ // Promotion runs are workorders: enqueue-only, the consumer lane runs
413
+ // them under the memory-curation brief. The PROMOTED <n> parse and its
414
+ // event emissions live in the completion hook below.
415
+ const runMemoryPromotion = (opts) => {
748
416
  try {
749
- const { response, noUpdate } = await executeValidatedRun('memory', buildPromotionPrompt(new Date().toISOString()), { requestTimeout: 600_000 });
750
- const promotedMatch = response?.match(/PROMOTED\s+(\d+)/);
751
- const saved = promotedMatch ? Number(promotedMatch[1]) : 0;
752
- eventBus.emit({
753
- type: 'agent:action',
754
- agent: 'Memory Agent',
755
- action: noUpdate || saved === 0 ? 'no_update' : 'promoted',
756
- target: `promotion run: ${saved} saved`,
757
- });
758
- if (saved > 0) {
759
- eventBus.emit({ type: 'memory:promoted', saved });
760
- console.log(`[Memory Promotion] Promoted ${saved} durable judgments`);
761
- }
762
- else {
763
- routesLogger.debug('[Memory Promotion] Nothing qualified for promotion');
764
- }
417
+ const now = Date.now();
418
+ enqueueWorkOrderOrThrow('memory-curation', opts?.manual ? (0, workorder_publishers_js_1.promotionManualKey)(now) : (0, workorder_publishers_js_1.promotionKey)(now), { scheduledAt: new Date(now).toISOString() }, opts?.manual ? 'high' : undefined);
765
419
  }
766
420
  catch (err) {
767
- routesLogger.error('[Memory Promotion] Error:', err instanceof Error ? err.message : err);
421
+ routesLogger.error('[stage2] promotion enqueue failed:', err instanceof Error ? err.message : err);
768
422
  }
769
423
  };
770
424
  // Stage-2 promotion completion hook (plan E4): the PROMOTED <n> parse and
@@ -780,18 +434,11 @@ This saves resources. Only compile when there is genuinely new information to do
780
434
  }),
781
435
  });
782
436
  }
783
- // Serialize all promotion runs on one chain (same 'Process is busy' class
784
- // as the dashboard and wiki agents).
785
- let promotionRunChain = Promise.resolve();
786
- const runMemoryPromotion = (opts) => {
787
- promotionRunChain = promotionRunChain.then(() => doPromotionRun(opts)).catch(() => { });
788
- return promotionRunChain;
789
- };
790
437
  setTimeout(() => runMemoryPromotion(), PROMOTION_INITIAL_DELAY_MS);
791
438
  setInterval(() => runMemoryPromotion(), PROMOTION_INTERVAL_MS);
792
439
  apiServer.app.post('/api/memory/promote', auth_middleware_js_1.requireAuth, (_req, res) => {
793
- runMemoryPromotion({ manual: true }).catch(() => { });
794
- res.json({ ok: true, message: 'Memory promotion run triggered' });
440
+ runMemoryPromotion({ manual: true });
441
+ res.json({ ok: true, message: 'Memory promotion workorder enqueued' });
795
442
  });
796
443
  console.log(`[Memory Promotion] Ready: every ${PROMOTION_INTERVAL_MS / 3_600_000}h, POST /api/memory/promote`);
797
444
  }