@jungjaehoon/mama-os 0.22.0 → 0.23.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/CHANGELOG.md +22 -0
- package/README.md +3 -1
- package/dist/agent/agent-loop.d.ts +0 -3
- package/dist/agent/agent-loop.d.ts.map +1 -1
- package/dist/agent/agent-loop.js +49 -34
- package/dist/agent/agent-loop.js.map +1 -1
- package/dist/agent/gateway-tool-executor.d.ts +18 -0
- package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
- package/dist/agent/gateway-tool-executor.js +185 -6
- package/dist/agent/gateway-tool-executor.js.map +1 -1
- package/dist/agent/gateway-tools.md +10 -5
- package/dist/agent/role-manager.d.ts +22 -1
- package/dist/agent/role-manager.d.ts.map +1 -1
- package/dist/agent/role-manager.js +39 -2
- package/dist/agent/role-manager.js.map +1 -1
- package/dist/agent/tool-registry.d.ts.map +1 -1
- package/dist/agent/tool-registry.js +35 -5
- package/dist/agent/tool-registry.js.map +1 -1
- package/dist/agent/types.d.ts +9 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/api/graph-api.d.ts.map +1 -1
- package/dist/api/graph-api.js +43 -33
- package/dist/api/graph-api.js.map +1 -1
- package/dist/api/operator-tasks-handler.d.ts.map +1 -1
- package/dist/api/operator-tasks-handler.js +6 -2
- package/dist/api/operator-tasks-handler.js.map +1 -1
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +185 -2
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/config/config-manager.d.ts +0 -5
- package/dist/cli/config/config-manager.d.ts.map +1 -1
- package/dist/cli/config/config-manager.js +57 -2
- package/dist/cli/config/config-manager.js.map +1 -1
- package/dist/cli/config/types.d.ts.map +1 -1
- package/dist/cli/config/types.js +35 -0
- package/dist/cli/config/types.js.map +1 -1
- package/dist/cli/runtime/agent-loop-init.d.ts.map +1 -1
- package/dist/cli/runtime/agent-loop-init.js +96 -51
- package/dist/cli/runtime/agent-loop-init.js.map +1 -1
- package/dist/cli/runtime/api-routes-init.d.ts +3 -0
- package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
- package/dist/cli/runtime/api-routes-init.js +293 -196
- package/dist/cli/runtime/api-routes-init.js.map +1 -1
- package/dist/cli/runtime/gateway-init.d.ts.map +1 -1
- package/dist/cli/runtime/gateway-init.js +5 -0
- package/dist/cli/runtime/gateway-init.js.map +1 -1
- package/dist/cli/runtime/gateway-wiring.d.ts.map +1 -1
- package/dist/cli/runtime/gateway-wiring.js +6 -0
- package/dist/cli/runtime/gateway-wiring.js.map +1 -1
- package/dist/envelope/reactive-config.d.ts.map +1 -1
- package/dist/envelope/reactive-config.js +26 -1
- package/dist/envelope/reactive-config.js.map +1 -1
- package/dist/gateways/message-router.d.ts +18 -1
- package/dist/gateways/message-router.d.ts.map +1 -1
- package/dist/gateways/message-router.js +155 -22
- package/dist/gateways/message-router.js.map +1 -1
- package/dist/gateways/telegram.d.ts +1 -0
- package/dist/gateways/telegram.d.ts.map +1 -1
- package/dist/gateways/telegram.js +38 -1
- package/dist/gateways/telegram.js.map +1 -1
- package/dist/gateways/types.d.ts +3 -0
- package/dist/gateways/types.d.ts.map +1 -1
- package/dist/memory/history-extractor.d.ts +0 -24
- package/dist/memory/history-extractor.d.ts.map +1 -1
- package/dist/memory/history-extractor.js +0 -127
- package/dist/memory/history-extractor.js.map +1 -1
- package/dist/memory/save-candidate-extractor.d.ts +3 -0
- package/dist/memory/save-candidate-extractor.d.ts.map +1 -1
- package/dist/memory/save-candidate-extractor.js +12 -1
- package/dist/memory/save-candidate-extractor.js.map +1 -1
- package/dist/memory/secret-filter.d.ts +22 -0
- package/dist/memory/secret-filter.d.ts.map +1 -0
- package/dist/memory/secret-filter.js +77 -0
- package/dist/memory/secret-filter.js.map +1 -0
- package/dist/observability/code-audit.d.ts +87 -0
- package/dist/observability/code-audit.d.ts.map +1 -0
- package/dist/observability/code-audit.js +403 -0
- package/dist/observability/code-audit.js.map +1 -0
- package/dist/operator/briefs.d.ts +26 -0
- package/dist/operator/briefs.d.ts.map +1 -0
- package/dist/operator/briefs.js +128 -0
- package/dist/operator/briefs.js.map +1 -0
- package/dist/operator/operator-trigger-loop.d.ts +24 -0
- package/dist/operator/operator-trigger-loop.d.ts.map +1 -1
- package/dist/operator/operator-trigger-loop.js +71 -12
- package/dist/operator/operator-trigger-loop.js.map +1 -1
- package/dist/operator/report-carry.d.ts +29 -0
- package/dist/operator/report-carry.d.ts.map +1 -0
- package/dist/operator/report-carry.js +81 -0
- package/dist/operator/report-carry.js.map +1 -0
- package/dist/operator/shadow-capture.d.ts +20 -0
- package/dist/operator/shadow-capture.d.ts.map +1 -0
- package/dist/operator/shadow-capture.js +34 -0
- package/dist/operator/shadow-capture.js.map +1 -0
- package/dist/operator/situation-report.d.ts +6 -0
- package/dist/operator/situation-report.d.ts.map +1 -1
- package/dist/operator/situation-report.js +15 -1
- package/dist/operator/situation-report.js.map +1 -1
- package/dist/operator/task-ledger.d.ts +90 -2
- package/dist/operator/task-ledger.d.ts.map +1 -1
- package/dist/operator/task-ledger.js +359 -23
- package/dist/operator/task-ledger.js.map +1 -1
- package/dist/operator/worker-run.d.ts +59 -0
- package/dist/operator/worker-run.d.ts.map +1 -0
- package/dist/operator/worker-run.js +64 -0
- package/dist/operator/worker-run.js.map +1 -0
- package/dist/operator/workorder-consumer.d.ts +118 -0
- package/dist/operator/workorder-consumer.d.ts.map +1 -0
- package/dist/operator/workorder-consumer.js +248 -0
- package/dist/operator/workorder-consumer.js.map +1 -0
- package/dist/operator/workorder-hooks.d.ts +30 -0
- package/dist/operator/workorder-hooks.d.ts.map +1 -0
- package/dist/operator/workorder-hooks.js +70 -0
- package/dist/operator/workorder-hooks.js.map +1 -0
- package/dist/operator/workorder-publishers.d.ts +68 -0
- package/dist/operator/workorder-publishers.d.ts.map +1 -0
- package/dist/operator/workorder-publishers.js +150 -0
- package/dist/operator/workorder-publishers.js.map +1 -0
- package/dist/security/security-monitor.d.ts +19 -0
- package/dist/security/security-monitor.d.ts.map +1 -1
- package/dist/security/security-monitor.js +68 -14
- package/dist/security/security-monitor.js.map +1 -1
- package/dist/utils/untrusted-content.d.ts +31 -0
- package/dist/utils/untrusted-content.d.ts.map +1 -0
- package/dist/utils/untrusted-content.js +77 -0
- package/dist/utils/untrusted-content.js.map +1 -0
- package/package.json +2 -2
|
@@ -56,11 +56,30 @@ const upload_handler_js_1 = require("../../api/upload-handler.js");
|
|
|
56
56
|
const operator_tasks_handler_js_1 = require("../../api/operator-tasks-handler.js");
|
|
57
57
|
const auth_middleware_js_1 = require("../../api/auth-middleware.js");
|
|
58
58
|
const utilities_js_1 = require("./utilities.js");
|
|
59
|
+
const code_audit_js_1 = require("../../observability/code-audit.js");
|
|
60
|
+
const workorder_publishers_js_1 = require("../../operator/workorder-publishers.js");
|
|
61
|
+
const security_monitor_js_1 = require("../../security/security-monitor.js");
|
|
59
62
|
const debugLogger = __importStar(require("@jungjaehoon/mama-core/debug-logger"));
|
|
60
63
|
const { DebugLogger } = debugLogger;
|
|
61
64
|
const routesLogger = new DebugLogger('api-routes');
|
|
62
65
|
async function registerApiRoutes(params) {
|
|
63
|
-
const { config, apiServer, eventBus, oauthManager: _oauthManager, mamaApi: _mamaApi, messageRouter, agentLoop, toolExecutor, discordGateway, slackGateway, graphHandler, getAdapter, sessionsDb, } = params;
|
|
66
|
+
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
|
+
}
|
|
74
|
+
const enqueueWorkOrderOrThrow = (workKind, idempotencyKey, payload, priority) => {
|
|
75
|
+
const ledger = toolExecutor.getTaskLedger();
|
|
76
|
+
if (!ledger) {
|
|
77
|
+
throw new Error(`[stage2] TaskLedger unavailable - cannot enqueue ${workKind} workorder`);
|
|
78
|
+
}
|
|
79
|
+
(0, workorder_publishers_js_1.validateWorkOrderPayload)(workKind, payload);
|
|
80
|
+
const wo = ledger.enqueueWorkOrder({ workKind, idempotencyKey, input: payload, priority });
|
|
81
|
+
console.log(`[stage2] enqueued workorder ${workKind}#${wo.id} key=${idempotencyKey}`);
|
|
82
|
+
};
|
|
64
83
|
// ── Validation Session Service ────────────────────────────────────────
|
|
65
84
|
const validationService = sessionsDb ? new session_service_js_1.ValidationSessionService(sessionsDb) : null;
|
|
66
85
|
/**
|
|
@@ -259,6 +278,22 @@ async function registerApiRoutes(params) {
|
|
|
259
278
|
{
|
|
260
279
|
const { createReportPublisher } = await import('../../api/report-handler.js');
|
|
261
280
|
toolExecutor.setReportPublisher(createReportPublisher(apiServer.reportStore, apiServer.reportSseClients));
|
|
281
|
+
// S1-T4 artifact hub: the console READS the board it directs. Same store
|
|
282
|
+
// the publisher writes - the conductor's status answers cite these slots
|
|
283
|
+
// instead of re-deriving state from memory copies.
|
|
284
|
+
toolExecutor.setReportReader(() => {
|
|
285
|
+
const slots = apiServer.reportStore.getAll();
|
|
286
|
+
const projected = {};
|
|
287
|
+
for (const [name, slot] of Object.entries(slots)) {
|
|
288
|
+
projected[name] = {
|
|
289
|
+
html: slot.html,
|
|
290
|
+
updatedAt: Number.isFinite(slot.updatedAt)
|
|
291
|
+
? new Date(slot.updatedAt).toISOString()
|
|
292
|
+
: null,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return projected;
|
|
296
|
+
});
|
|
262
297
|
}
|
|
263
298
|
if (dashboardAgentConfigured) {
|
|
264
299
|
// ── Dashboard Agent ───────────────────────────────────────────────
|
|
@@ -284,6 +319,23 @@ This saves resources. Only publish when there is genuinely new information to re
|
|
|
284
319
|
// request means "rebuild the board now", not "tell me nothing changed".
|
|
285
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');
|
|
286
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
|
+
}
|
|
287
339
|
const pm = toolExecutor.getAgentProcessManager();
|
|
288
340
|
if (!pm) {
|
|
289
341
|
routesLogger.warn('[Dashboard Agent] AgentProcessManager not available yet');
|
|
@@ -362,52 +414,130 @@ This saves resources. Only publish when there is genuinely new information to re
|
|
|
362
414
|
return row.n;
|
|
363
415
|
},
|
|
364
416
|
};
|
|
417
|
+
// Stage-2 board completion hook (plan D1/E3/G1): the consumer wraps
|
|
418
|
+
// reconcile workorders with the SAME bracket the legacy closure owns,
|
|
419
|
+
// but trace rows from worker runs carry the worker's identity, not
|
|
420
|
+
// agent_id='dashboard-agent' - the re-key goes through details JSON
|
|
421
|
+
// (agent_activity has NO channel_id column; json_extract is the only
|
|
422
|
+
// schema-supported key; agent_id re-keying is a green-test trap).
|
|
423
|
+
if (workOrderConsumer) {
|
|
424
|
+
const { buildWorkerTraceQueries } = await import('../../operator/workorder-hooks.js');
|
|
425
|
+
const workerVerifierDeps = {
|
|
426
|
+
...verifierDeps,
|
|
427
|
+
...buildWorkerTraceQueries(sessionsDb, 'worker:board'),
|
|
428
|
+
};
|
|
429
|
+
workOrderConsumer.registerHook('board', {
|
|
430
|
+
before: (wo) => {
|
|
431
|
+
if (wo.payload.mode !== 'reconcile')
|
|
432
|
+
return null;
|
|
433
|
+
return captureSnapshot(workerVerifierDeps, `reconcile:${String(wo.payload.channelKey)}`);
|
|
434
|
+
},
|
|
435
|
+
after: (wo, response, beforeState) => {
|
|
436
|
+
if (wo.payload.mode !== 'reconcile' || !beforeState) {
|
|
437
|
+
// FULL runs are not bracket-verified (plan: reconcile mode only).
|
|
438
|
+
if (response.includes('NO_UPDATE')) {
|
|
439
|
+
console.log('[stage2] board worker: no changes detected, publish skipped');
|
|
440
|
+
}
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const scope = `reconcile:${String(wo.payload.channelKey)}`;
|
|
444
|
+
const verdict = verifyAfterRun(workerVerifierDeps, beforeState, scope);
|
|
445
|
+
const outcome = verdict.verified ? 'reconcile_verified' : 'reconcile_unverified';
|
|
446
|
+
console.log(`[stage2] ${outcome} channel=${String(wo.payload.channelKey)}${verdict.effects.length > 0 ? ` (${verdict.effects.join('; ')})` : ''}`);
|
|
447
|
+
try {
|
|
448
|
+
if (sessionsDb) {
|
|
449
|
+
(0, agent_store_js_1.logActivity)(sessionsDb, {
|
|
450
|
+
agent_id: 'workorder-board',
|
|
451
|
+
agent_version: 0,
|
|
452
|
+
type: outcome,
|
|
453
|
+
input_summary: `reconcile ${String(wo.payload.channelKey)}`,
|
|
454
|
+
output_summary: verdict.effects.join('; '),
|
|
455
|
+
execution_status: 'completed',
|
|
456
|
+
trigger_reason: 'workorder',
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
/* telemetry only */
|
|
462
|
+
}
|
|
463
|
+
if (!verdict.verified) {
|
|
464
|
+
eventBus.emit({
|
|
465
|
+
type: 'agent:action',
|
|
466
|
+
agent: 'Dashboard Agent',
|
|
467
|
+
action: 'reconcile_unverified',
|
|
468
|
+
target: String(wo.payload.channelKey),
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
}
|
|
365
474
|
const reconcileScheduler = new ReconcileScheduler({
|
|
366
475
|
debounceMs: Number(process.env.MAMA_RECONCILE_DEBOUNCE_MS) || undefined,
|
|
367
476
|
maxWaitMs: Number(process.env.MAMA_RECONCILE_MAX_WAIT_MS) || undefined,
|
|
368
477
|
globalMaxPerHour: Number(process.env.MAMA_RECONCILE_MAX_PER_HOUR) || undefined,
|
|
369
478
|
log: (line) => console.log(line),
|
|
370
|
-
run: (channelKey, deltaLines) =>
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
requestTimeout: 300_000,
|
|
381
|
-
});
|
|
382
|
-
const verdict = verifyAfterRun(verifierDeps, before, scope);
|
|
383
|
-
const outcome = verdict.verified ? 'reconcile_verified' : 'reconcile_unverified';
|
|
384
|
-
console.log(`[reconcile] ${outcome} channel=${channelKey}${verdict.effects.length > 0 ? ` (${verdict.effects.join('; ')})` : ''}`);
|
|
385
|
-
try {
|
|
386
|
-
if (sessionsDb) {
|
|
387
|
-
(0, agent_store_js_1.logActivity)(sessionsDb, {
|
|
388
|
-
agent_id: 'dashboard-agent',
|
|
389
|
-
agent_version: 0,
|
|
390
|
-
type: outcome,
|
|
391
|
-
input_summary: `reconcile ${channelKey}`,
|
|
392
|
-
output_summary: verdict.effects.join('; '),
|
|
393
|
-
execution_status: 'completed',
|
|
394
|
-
trigger_reason: 'reconcile',
|
|
479
|
+
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,
|
|
395
489
|
});
|
|
396
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();
|
|
397
498
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
action: 'reconcile_unverified',
|
|
407
|
-
target: channelKey,
|
|
499
|
+
return boardWriterQueue.push(async () => {
|
|
500
|
+
const scope = `reconcile:${channelKey}`;
|
|
501
|
+
const before = captureSnapshot(verifierDeps, scope);
|
|
502
|
+
const prompt = buildReconcilePrompt({
|
|
503
|
+
channelKey,
|
|
504
|
+
deltaLines,
|
|
505
|
+
todayIso: new Date().toISOString().slice(0, 10),
|
|
506
|
+
kagemushaContext,
|
|
408
507
|
});
|
|
409
|
-
|
|
410
|
-
|
|
508
|
+
await executeValidatedRun('dashboard-agent', prompt, {
|
|
509
|
+
requestTimeout: 300_000,
|
|
510
|
+
});
|
|
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
|
+
});
|
|
540
|
+
},
|
|
411
541
|
});
|
|
412
542
|
eventBus.on('operator:channel-delta', (event) => {
|
|
413
543
|
if (event.type === 'operator:channel-delta') {
|
|
@@ -482,7 +612,20 @@ This saves resources. Only publish when there is genuinely new information to re
|
|
|
482
612
|
});
|
|
483
613
|
});
|
|
484
614
|
// Wiki trigger via executeValidatedRun
|
|
485
|
-
const doWikiRun = async () => {
|
|
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
|
+
}
|
|
486
629
|
if (!toolExecutor.getAgentProcessManager()) {
|
|
487
630
|
routesLogger.warn('[Wiki Agent] AgentProcessManager not available yet');
|
|
488
631
|
return;
|
|
@@ -517,21 +660,29 @@ This saves resources. Only compile when there is genuinely new information to do
|
|
|
517
660
|
routesLogger.error('[Wiki Agent] Error:', err instanceof Error ? err.message : err);
|
|
518
661
|
}
|
|
519
662
|
};
|
|
663
|
+
// Stage-2 wiki completion hook (plan E4): outcome reading only - the
|
|
664
|
+
// wiki:compiled events flow through the wikiPublisher independently.
|
|
665
|
+
if (workOrderConsumer) {
|
|
666
|
+
const { buildWikiAfterHook } = await import('../../operator/workorder-hooks.js');
|
|
667
|
+
workOrderConsumer.registerHook('wiki', {
|
|
668
|
+
after: buildWikiAfterHook((line) => routesLogger.debug(line)),
|
|
669
|
+
});
|
|
670
|
+
}
|
|
520
671
|
// Serialize ALL wiki runs (boot, event-driven, manual) on one chain -- the
|
|
521
672
|
// shared agent process rejects concurrent requests, so the boot run and a
|
|
522
673
|
// manual trigger raced into 'Process is busy' (same pattern as the
|
|
523
674
|
// dashboard agent's runDashboardAgent chain above).
|
|
524
675
|
let wikiRunChain = Promise.resolve();
|
|
525
|
-
const runWikiAgent = () => {
|
|
526
|
-
wikiRunChain = wikiRunChain.then(doWikiRun).catch(() => { });
|
|
676
|
+
const runWikiAgent = (trigger) => {
|
|
677
|
+
wikiRunChain = wikiRunChain.then(() => doWikiRun(trigger)).catch(() => { });
|
|
527
678
|
return wikiRunChain;
|
|
528
679
|
};
|
|
529
680
|
// Event-driven: compile when extraction completes. Trailing-edge debounce so
|
|
530
681
|
// bursts of extraction:completed events coalesce into one wiki compile run.
|
|
531
|
-
eventBus.onDebounced('extraction:completed', () => runWikiAgent(), 30_000);
|
|
682
|
+
eventBus.onDebounced('extraction:completed', () => runWikiAgent('extraction:completed'), 30_000);
|
|
532
683
|
// Promotion feeds the wiki: freshly promoted decisions are exactly the
|
|
533
684
|
// material daily notes and lessons compile from.
|
|
534
|
-
eventBus.onDebounced('memory:promoted', () => runWikiAgent(), 30_000);
|
|
685
|
+
eventBus.onDebounced('memory:promoted', () => runWikiAgent('memory:promoted'), 30_000);
|
|
535
686
|
// Emit agent:action notices when wiki pages are compiled
|
|
536
687
|
eventBus.on('wiki:compiled', (event) => {
|
|
537
688
|
if (event.type === 'wiki:compiled') {
|
|
@@ -547,11 +698,11 @@ This saves resources. Only compile when there is genuinely new information to do
|
|
|
547
698
|
});
|
|
548
699
|
// Manual trigger API
|
|
549
700
|
apiServer.app.post('/api/wiki/compile', auth_middleware_js_1.requireAuth, async (_req, res) => {
|
|
550
|
-
runWikiAgent().catch(() => { });
|
|
701
|
+
runWikiAgent('manual').catch(() => { });
|
|
551
702
|
res.json({ ok: true, message: 'Wiki compilation triggered' });
|
|
552
703
|
});
|
|
553
704
|
// First run after 15s (let connectors and dashboard agent go first)
|
|
554
|
-
setTimeout(runWikiAgent, 15_000);
|
|
705
|
+
setTimeout(() => runWikiAgent('boot'), 15_000);
|
|
555
706
|
routesLogger.info('[Wiki Agent] Ready — triggers: extraction:completed event, POST /api/wiki/compile');
|
|
556
707
|
}
|
|
557
708
|
// -- Memory Promotion: scheduled observation->decision curation --
|
|
@@ -577,7 +728,19 @@ This saves resources. Only compile when there is genuinely new information to do
|
|
|
577
728
|
'rules, recurring risk patterns; NEVER task lifecycle states, greetings, or logistics). ' +
|
|
578
729
|
'Include scopes (the source channel, and the project when identifiable) and event_date.\n' +
|
|
579
730
|
'5. Finish with exactly PROMOTED <n> or NO_UPDATE.';
|
|
580
|
-
const doPromotionRun = async () => {
|
|
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
|
+
}
|
|
581
744
|
if (!toolExecutor.getAgentProcessManager()) {
|
|
582
745
|
routesLogger.warn('[Memory Promotion] AgentProcessManager not available yet');
|
|
583
746
|
return;
|
|
@@ -604,179 +767,113 @@ This saves resources. Only compile when there is genuinely new information to do
|
|
|
604
767
|
routesLogger.error('[Memory Promotion] Error:', err instanceof Error ? err.message : err);
|
|
605
768
|
}
|
|
606
769
|
};
|
|
770
|
+
// Stage-2 promotion completion hook (plan E4): the PROMOTED <n> parse and
|
|
771
|
+
// its event emissions are the wiki ingress chain's second link - losing
|
|
772
|
+
// them in the workorder conversion would silently sever memory:promoted.
|
|
773
|
+
if (workOrderConsumer) {
|
|
774
|
+
const { buildPromotionAfterHook } = await import('../../operator/workorder-hooks.js');
|
|
775
|
+
workOrderConsumer.registerHook('memory-curation', {
|
|
776
|
+
after: buildPromotionAfterHook({
|
|
777
|
+
emitAgentAction: (action, target) => eventBus.emit({ type: 'agent:action', agent: 'Memory Agent', action, target }),
|
|
778
|
+
emitMemoryPromoted: (saved) => eventBus.emit({ type: 'memory:promoted', saved }),
|
|
779
|
+
log: (line) => console.log(line),
|
|
780
|
+
}),
|
|
781
|
+
});
|
|
782
|
+
}
|
|
607
783
|
// Serialize all promotion runs on one chain (same 'Process is busy' class
|
|
608
784
|
// as the dashboard and wiki agents).
|
|
609
785
|
let promotionRunChain = Promise.resolve();
|
|
610
|
-
const runMemoryPromotion = () => {
|
|
611
|
-
promotionRunChain = promotionRunChain.then(doPromotionRun).catch(() => { });
|
|
786
|
+
const runMemoryPromotion = (opts) => {
|
|
787
|
+
promotionRunChain = promotionRunChain.then(() => doPromotionRun(opts)).catch(() => { });
|
|
612
788
|
return promotionRunChain;
|
|
613
789
|
};
|
|
614
|
-
setTimeout(runMemoryPromotion, PROMOTION_INITIAL_DELAY_MS);
|
|
615
|
-
setInterval(runMemoryPromotion, PROMOTION_INTERVAL_MS);
|
|
790
|
+
setTimeout(() => runMemoryPromotion(), PROMOTION_INITIAL_DELAY_MS);
|
|
791
|
+
setInterval(() => runMemoryPromotion(), PROMOTION_INTERVAL_MS);
|
|
616
792
|
apiServer.app.post('/api/memory/promote', auth_middleware_js_1.requireAuth, (_req, res) => {
|
|
617
|
-
runMemoryPromotion().catch(() => { });
|
|
793
|
+
runMemoryPromotion({ manual: true }).catch(() => { });
|
|
618
794
|
res.json({ ok: true, message: 'Memory promotion run triggered' });
|
|
619
795
|
});
|
|
620
796
|
console.log(`[Memory Promotion] Ready: every ${PROMOTION_INTERVAL_MS / 3_600_000}h, POST /api/memory/promote`);
|
|
621
797
|
}
|
|
622
|
-
//
|
|
798
|
+
// -- System Audit: hourly deterministic code checks ---------------------
|
|
799
|
+
// Owner decision 2026-04-22 (mama_conductor_audit_code_based_read_only),
|
|
800
|
+
// landed 2026-07-17: the audit is fact collection and recording executed by
|
|
801
|
+
// CODE - no LLM invocation, no auto-fix, no broad filesystem access. The
|
|
802
|
+
// prior hourly LLM audit violated that decision and the 2026-05-14
|
|
803
|
+
// no-shell-autofix rule, and lost its tools entirely to the persona
|
|
804
|
+
// lockdown (--tools ""). The 24h alert-dedup contract (owner verdict
|
|
805
|
+
// 2026-07-11) is preserved inside runCodeAudit; MAJOR findings flow through
|
|
806
|
+
// dispatchSecurityAlertDirect to the configured security alert sender.
|
|
623
807
|
const AUDIT_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
|
|
624
808
|
const AUDIT_INITIAL_DELAY_MS = 5 * 60 * 1000; // 5 min after startup
|
|
625
|
-
|
|
626
|
-
// run because each audit was stateless. agent_notices does not carry audit
|
|
627
|
-
// findings (review feedback on #134), so dedup state lives in a JSON file the
|
|
628
|
-
// conductor reads/writes itself, and the current time is interpolated per run
|
|
629
|
-
// so the 24h comparison is grounded.
|
|
630
|
-
const buildAuditPrompt = (nowIso) => 'Perform a system audit. Read ~/.mama/skills/audit-checklist.md and execute each step. ' +
|
|
631
|
-
'Classify findings as MINOR or MAJOR. ' +
|
|
632
|
-
'For MINOR items: execute the auto-fix command immediately (Bash curl). Do NOT just report — fix it. ' +
|
|
633
|
-
'For MAJOR items: report to human via channel alert, subject to the dedup rule below. ' +
|
|
634
|
-
`Alert dedup (MANDATORY): the current time is ${nowIso}. Read ~/.mama/state/audit-findings.json ` +
|
|
635
|
-
'(it may not exist on the first run). Re-alert a MAJOR finding only if it is NEW (no entry ' +
|
|
636
|
-
'with the same id), has ESCALATED, or its last_alerted_at is older than 24 hours compared to ' +
|
|
637
|
-
'the current time; otherwise stay silent about it. Never alert MINOR findings to the owner. ' +
|
|
638
|
-
'After the audit, Write ~/.mama/state/audit-findings.json with an array of every current ' +
|
|
639
|
-
'finding: {id (stable kebab-case slug), severity, first_seen, last_seen, last_alerted_at}. ' +
|
|
640
|
-
'Carry forward first_seen and last_alerted_at from the previous file; set last_alerted_at to ' +
|
|
641
|
-
'the current time only for findings you actually alerted this run. ' +
|
|
642
|
-
// Owner verdict 2026-07-10: hourly-audit self-notes are memory noise -- the two
|
|
643
|
-
// "durable policy" saves it produced were unscoped near-duplicates polluting
|
|
644
|
-
// global recall. The audit record lives in validation sessions + agent_activity;
|
|
645
|
-
// MAJOR findings reach the owner via the channel alert. No memory writes here.
|
|
646
|
-
'Do NOT call mama_save under any circumstances: the validation session and agent_activity ' +
|
|
647
|
-
'are the durable audit record, and repeated audits must not accumulate policy notes in memory.';
|
|
648
|
-
const runConductorAudit = async () => {
|
|
649
|
-
let auditSession = null;
|
|
650
|
-
let auditVersion = 0;
|
|
651
|
-
const auditStart = Date.now();
|
|
809
|
+
const doSystemAudit = async () => {
|
|
652
810
|
try {
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
811
|
+
const report = await (0, code_audit_js_1.runCodeAudit)({
|
|
812
|
+
config: {
|
|
813
|
+
telegram: config.telegram,
|
|
814
|
+
multi_agent: config.multi_agent,
|
|
815
|
+
roles: config.roles,
|
|
816
|
+
},
|
|
817
|
+
// Evaluated at audit time: gateway-wiring registers the sender only
|
|
818
|
+
// for ACTIVE gateways, so a configured target on a dead gateway must
|
|
819
|
+
// not report a false PASS.
|
|
820
|
+
securityAlertConfigured: (0, security_monitor_js_1.hasSecurityAlertSender)(),
|
|
821
|
+
// Direct dispatch, NOT recordSecurityEvent: audit findings are
|
|
822
|
+
// self-generated and must not fabricate incident/denylist/RDAP
|
|
823
|
+
// artifacts for a pseudo client. dispatchSecurityAlertDirect awaits
|
|
824
|
+
// delivery and throws on failure, so runCodeAudit keeps
|
|
825
|
+
// last_alerted_at null and retries on the next run (fail loud).
|
|
826
|
+
alert: async (finding, reason) => {
|
|
827
|
+
await (0, security_monitor_js_1.dispatchSecurityAlertDirect)({
|
|
828
|
+
type: 'system_audit_finding',
|
|
829
|
+
severity: 'critical',
|
|
830
|
+
message: `[Audit/${reason}] ${finding.summary}`,
|
|
831
|
+
// path carries the finding id so the alert-cooldown fingerprint
|
|
832
|
+
// distinguishes findings instead of collapsing them all into one.
|
|
833
|
+
path: finding.id,
|
|
834
|
+
details: { findingId: finding.id, detail: finding.detail ?? null },
|
|
677
835
|
});
|
|
678
|
-
|
|
679
|
-
try {
|
|
680
|
-
validationService?.recordRun(auditSession.id, { activityId: startRow.id });
|
|
681
|
-
}
|
|
682
|
-
catch (telemetryErr) {
|
|
683
|
-
routesLogger.warn('[Conductor Audit] Failed to link startup activity to validation session:', telemetryErr);
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
catch (telemetryErr) {
|
|
688
|
-
routesLogger.warn('[Conductor Audit] Failed to write startup telemetry:', telemetryErr);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
const auditChannelId = `conductor-audit-${Date.now()}`;
|
|
692
|
-
await messageRouter.process({
|
|
693
|
-
source: 'system',
|
|
694
|
-
channelId: auditChannelId,
|
|
695
|
-
userId: 'system',
|
|
696
|
-
text: buildAuditPrompt(new Date().toISOString()),
|
|
836
|
+
},
|
|
697
837
|
});
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
duration_ms: auditDuration,
|
|
707
|
-
run_id: auditSession?.id,
|
|
708
|
-
execution_status: 'completed',
|
|
709
|
-
trigger_reason: 'audit',
|
|
710
|
-
});
|
|
711
|
-
if (auditSession) {
|
|
712
|
-
validationService?.recordRun(auditSession.id, { activityId: activityRow.id });
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
catch (telemetryErr) {
|
|
717
|
-
routesLogger.warn('[Conductor Audit] Failed to write completion telemetry:', telemetryErr);
|
|
718
|
-
}
|
|
719
|
-
try {
|
|
720
|
-
if (auditSession && validationService) {
|
|
721
|
-
validationService.finalizeSession(auditSession.id, {
|
|
722
|
-
execution_status: 'completed',
|
|
723
|
-
metrics: { duration_ms: auditDuration },
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
catch (telemetryErr) {
|
|
728
|
-
routesLogger.warn('[Conductor Audit] Failed to finalize validation session:', telemetryErr);
|
|
729
|
-
}
|
|
730
|
-
routesLogger.debug('[Conductor Audit] Audit complete');
|
|
838
|
+
const majors = report.findings.filter((f) => f.severity === 'MAJOR').length;
|
|
839
|
+
const minors = report.findings.filter((f) => f.severity === 'MINOR').length;
|
|
840
|
+
routesLogger.info(`[System Audit] ${report.pass_items.length} pass, ${majors} MAJOR, ${minors} MINOR, ` +
|
|
841
|
+
`alerted=[${report.alerted.join(',')}] in ${report.duration_ms}ms`);
|
|
842
|
+
if (report.alert_delivery_failures.length > 0) {
|
|
843
|
+
routesLogger.error('[System Audit] Alert delivery failures:', report.alert_delivery_failures.join('; '));
|
|
844
|
+
}
|
|
845
|
+
return report;
|
|
731
846
|
}
|
|
732
847
|
catch (err) {
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
if (sessionsDb) {
|
|
736
|
-
const failRow = (0, agent_store_js_1.logActivity)(sessionsDb, {
|
|
737
|
-
agent_id: 'conductor',
|
|
738
|
-
agent_version: auditVersion,
|
|
739
|
-
type: 'audit_failed',
|
|
740
|
-
input_summary: 'Hourly system audit failed',
|
|
741
|
-
error_message: err instanceof Error ? err.message : String(err),
|
|
742
|
-
duration_ms: auditDuration,
|
|
743
|
-
run_id: auditSession?.id,
|
|
744
|
-
execution_status: 'failed',
|
|
745
|
-
trigger_reason: 'audit',
|
|
746
|
-
});
|
|
747
|
-
if (auditSession) {
|
|
748
|
-
validationService?.recordRun(auditSession.id, { activityId: failRow.id });
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
catch (telemetryErr) {
|
|
753
|
-
routesLogger.warn('[Conductor Audit] Failed to write failure telemetry:', telemetryErr);
|
|
754
|
-
}
|
|
755
|
-
try {
|
|
756
|
-
if (auditSession && validationService) {
|
|
757
|
-
validationService.finalizeSession(auditSession.id, {
|
|
758
|
-
execution_status: 'failed',
|
|
759
|
-
error_message: err instanceof Error ? err.message : String(err),
|
|
760
|
-
metrics: { duration_ms: auditDuration },
|
|
761
|
-
});
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
catch (telemetryErr) {
|
|
765
|
-
routesLogger.warn('[Conductor Audit] Failed to finalize failed session:', telemetryErr);
|
|
766
|
-
}
|
|
767
|
-
routesLogger.error('[Conductor Audit] Failed:', err instanceof Error ? err.message : String(err));
|
|
848
|
+
routesLogger.error('[System Audit] Failed:', err instanceof Error ? err.message : String(err));
|
|
849
|
+
return null;
|
|
768
850
|
}
|
|
769
851
|
};
|
|
852
|
+
// Serialize scheduled and manual runs (same pattern as the memory-promotion
|
|
853
|
+
// chain above): overlapping audits would read the same prior state, alert
|
|
854
|
+
// twice, and race the audit-findings.json write. doSystemAudit never
|
|
855
|
+
// rejects, so chaining on the previous run is sufficient.
|
|
856
|
+
let auditRunChain = Promise.resolve(null);
|
|
857
|
+
const runSystemAudit = () => {
|
|
858
|
+
const next = auditRunChain.then(doSystemAudit);
|
|
859
|
+
auditRunChain = next;
|
|
860
|
+
return next;
|
|
861
|
+
};
|
|
770
862
|
setTimeout(() => {
|
|
771
|
-
|
|
772
|
-
setInterval(
|
|
863
|
+
void runSystemAudit();
|
|
864
|
+
setInterval(() => void runSystemAudit(), AUDIT_INTERVAL_MS);
|
|
773
865
|
}, AUDIT_INITIAL_DELAY_MS);
|
|
774
|
-
// Manual trigger
|
|
866
|
+
// Manual trigger returns the full report (read-only, no LLM, fast)
|
|
775
867
|
apiServer.app.post('/api/conductor/audit', auth_middleware_js_1.requireAuth, async (_req, res) => {
|
|
776
|
-
|
|
777
|
-
|
|
868
|
+
const report = await runSystemAudit();
|
|
869
|
+
if (report) {
|
|
870
|
+
res.json({ ok: true, mode: 'code', report });
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
res.status(500).json({ ok: false, error: 'audit failed - see daemon.log' });
|
|
874
|
+
}
|
|
778
875
|
});
|
|
779
|
-
routesLogger.info('[
|
|
876
|
+
routesLogger.info('[System Audit] Ready - deterministic code checks every 60 min, POST /api/conductor/audit for manual run');
|
|
780
877
|
// ── Memory Agent stats API ────────────────────────────────────────────
|
|
781
878
|
apiServer.app.get('/api/memory-agent/stats', auth_middleware_js_1.requireAuth, (_req, res) => {
|
|
782
879
|
const stats = messageRouter.getMemoryAgentStats();
|