@jungjaehoon/mama-os 0.21.0 → 0.22.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.
- package/CHANGELOG.md +31 -0
- package/README.md +12 -4
- package/dist/agent/agent-loop.d.ts.map +1 -1
- package/dist/agent/agent-loop.js +76 -11
- package/dist/agent/agent-loop.js.map +1 -1
- package/dist/agent/code-act/host-bridge.d.ts.map +1 -1
- package/dist/agent/code-act/host-bridge.js +74 -0
- package/dist/agent/code-act/host-bridge.js.map +1 -1
- package/dist/agent/codex-mcp-process.d.ts +1 -0
- package/dist/agent/codex-mcp-process.d.ts.map +1 -1
- package/dist/agent/codex-mcp-process.js +3 -2
- package/dist/agent/codex-mcp-process.js.map +1 -1
- package/dist/agent/gateway-tool-executor.d.ts +16 -1
- package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
- package/dist/agent/gateway-tool-executor.js +148 -2
- package/dist/agent/gateway-tool-executor.js.map +1 -1
- package/dist/agent/gateway-tools.md +6 -1
- package/dist/agent/model-runner.d.ts +8 -0
- package/dist/agent/model-runner.d.ts.map +1 -1
- package/dist/agent/model-runner.js.map +1 -1
- package/dist/agent/persistent-cli-adapter.d.ts +6 -0
- package/dist/agent/persistent-cli-adapter.d.ts.map +1 -1
- package/dist/agent/persistent-cli-adapter.js +21 -10
- package/dist/agent/persistent-cli-adapter.js.map +1 -1
- package/dist/agent/tool-registry.d.ts.map +1 -1
- package/dist/agent/tool-registry.js +32 -1
- package/dist/agent/tool-registry.js.map +1 -1
- package/dist/agent/types.d.ts +26 -2
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +15 -13
- package/dist/api/index.js.map +1 -1
- package/dist/api/operator-handler.d.ts +2 -0
- package/dist/api/operator-handler.d.ts.map +1 -1
- package/dist/api/operator-handler.js +23 -5
- package/dist/api/operator-handler.js.map +1 -1
- package/dist/api/operator-summary.d.ts +3 -0
- package/dist/api/operator-summary.d.ts.map +1 -0
- package/dist/api/operator-summary.js +72 -0
- package/dist/api/operator-summary.js.map +1 -0
- package/dist/api/operator-tasks-handler.d.ts +7 -0
- package/dist/api/operator-tasks-handler.d.ts.map +1 -0
- package/dist/api/operator-tasks-handler.js +165 -0
- package/dist/api/operator-tasks-handler.js.map +1 -0
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +62 -12
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/runtime/agent-loop-init.d.ts +2 -1
- package/dist/cli/runtime/agent-loop-init.d.ts.map +1 -1
- package/dist/cli/runtime/agent-loop-init.js +16 -8
- package/dist/cli/runtime/agent-loop-init.js.map +1 -1
- package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
- package/dist/cli/runtime/api-routes-init.js +196 -158
- 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 +3 -1
- 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/cli/runtime/memory-agent-init.d.ts +2 -1
- package/dist/cli/runtime/memory-agent-init.d.ts.map +1 -1
- package/dist/cli/runtime/memory-agent-init.js +15 -3
- package/dist/cli/runtime/memory-agent-init.js.map +1 -1
- package/dist/connectors/framework/types.d.ts +8 -0
- package/dist/connectors/framework/types.d.ts.map +1 -1
- package/dist/connectors/slack/index.d.ts.map +1 -1
- package/dist/connectors/slack/index.js +3 -1
- package/dist/connectors/slack/index.js.map +1 -1
- package/dist/connectors/trello/index.d.ts.map +1 -1
- package/dist/connectors/trello/index.js +6 -2
- package/dist/connectors/trello/index.js.map +1 -1
- package/dist/db/agent-store.d.ts +5 -0
- package/dist/db/agent-store.d.ts.map +1 -1
- package/dist/db/agent-store.js +6 -3
- package/dist/db/agent-store.js.map +1 -1
- package/dist/db/migrations/agent-activity-tool-name.d.ts +8 -0
- package/dist/db/migrations/agent-activity-tool-name.d.ts.map +1 -0
- package/dist/db/migrations/agent-activity-tool-name.js +24 -0
- package/dist/db/migrations/agent-activity-tool-name.js.map +1 -0
- package/dist/envelope/run-guard.d.ts +10 -0
- package/dist/envelope/run-guard.d.ts.map +1 -0
- package/dist/envelope/run-guard.js +19 -0
- package/dist/envelope/run-guard.js.map +1 -0
- package/dist/gateways/telegram.d.ts +1 -0
- package/dist/gateways/telegram.d.ts.map +1 -1
- package/dist/gateways/telegram.js +25 -1
- package/dist/gateways/telegram.js.map +1 -1
- package/dist/memory/history-extractor.d.ts.map +1 -1
- package/dist/memory/history-extractor.js +10 -5
- package/dist/memory/history-extractor.js.map +1 -1
- package/dist/multi-agent/agent-event-bus.d.ts +4 -0
- package/dist/multi-agent/agent-event-bus.d.ts.map +1 -1
- package/dist/multi-agent/agent-event-bus.js.map +1 -1
- package/dist/multi-agent/dashboard-agent-persona.d.ts.map +1 -1
- package/dist/multi-agent/dashboard-agent-persona.js +18 -5
- package/dist/multi-agent/dashboard-agent-persona.js.map +1 -1
- package/dist/multi-agent/runtime-process.d.ts +4 -2
- package/dist/multi-agent/runtime-process.d.ts.map +1 -1
- package/dist/multi-agent/runtime-process.js +7 -4
- package/dist/multi-agent/runtime-process.js.map +1 -1
- package/dist/observability/code-audit.d.ts +83 -0
- package/dist/observability/code-audit.d.ts.map +1 -0
- package/dist/observability/code-audit.js +334 -0
- package/dist/observability/code-audit.js.map +1 -0
- package/dist/operator/action-verifier.d.ts +50 -0
- package/dist/operator/action-verifier.d.ts.map +1 -0
- package/dist/operator/action-verifier.js +68 -0
- package/dist/operator/action-verifier.js.map +1 -0
- package/dist/operator/board-reconcile.d.ts +62 -0
- package/dist/operator/board-reconcile.d.ts.map +1 -0
- package/dist/operator/board-reconcile.js +160 -0
- package/dist/operator/board-reconcile.js.map +1 -0
- package/dist/operator/board-slot-instructions.d.ts +8 -0
- package/dist/operator/board-slot-instructions.d.ts.map +1 -1
- package/dist/operator/board-slot-instructions.js +25 -1
- package/dist/operator/board-slot-instructions.js.map +1 -1
- package/dist/operator/operator-trigger-loop.d.ts +15 -2
- package/dist/operator/operator-trigger-loop.d.ts.map +1 -1
- package/dist/operator/operator-trigger-loop.js +40 -1
- package/dist/operator/operator-trigger-loop.js.map +1 -1
- package/dist/operator/report-scheduler.d.ts +23 -0
- package/dist/operator/report-scheduler.d.ts.map +1 -1
- package/dist/operator/report-scheduler.js +56 -8
- package/dist/operator/report-scheduler.js.map +1 -1
- package/dist/operator/situation-report.d.ts +5 -1
- package/dist/operator/situation-report.d.ts.map +1 -1
- package/dist/operator/situation-report.js +14 -3
- package/dist/operator/situation-report.js.map +1 -1
- package/dist/operator/task-ledger.d.ts +96 -0
- package/dist/operator/task-ledger.d.ts.map +1 -0
- package/dist/operator/task-ledger.js +277 -0
- package/dist/operator/task-ledger.js.map +1 -0
- package/dist/security/security-monitor.d.ts +12 -0
- package/dist/security/security-monitor.d.ts.map +1 -1
- package/dist/security/security-monitor.js +51 -14
- package/dist/security/security-monitor.js.map +1 -1
- package/dist/utils/untrusted-content.d.ts +18 -0
- package/dist/utils/untrusted-content.d.ts.map +1 -0
- package/dist/utils/untrusted-content.js +34 -0
- package/dist/utils/untrusted-content.js.map +1 -0
- package/package.json +1 -1
- package/public/ui/assets/index-CKOJQYXA.css +1 -0
- package/public/ui/assets/index-OSgiwL24.js +62 -0
- package/public/ui/index.html +3 -2
- package/public/ui/theme-init.js +20 -0
- package/public/viewer/viewer.html +12 -28
- package/public/ui/assets/index-BDsaQNGd.js +0 -62
- package/public/ui/assets/index-C4URnm_X.css +0 -1
|
@@ -53,8 +53,11 @@ const session_service_js_1 = require("../../validation/session-service.js");
|
|
|
53
53
|
const agent_store_js_1 = require("../../db/agent-store.js");
|
|
54
54
|
const memory_agent_dashboard_js_1 = require("../../memory/memory-agent-dashboard.js");
|
|
55
55
|
const upload_handler_js_1 = require("../../api/upload-handler.js");
|
|
56
|
+
const operator_tasks_handler_js_1 = require("../../api/operator-tasks-handler.js");
|
|
56
57
|
const auth_middleware_js_1 = require("../../api/auth-middleware.js");
|
|
57
58
|
const utilities_js_1 = require("./utilities.js");
|
|
59
|
+
const code_audit_js_1 = require("../../observability/code-audit.js");
|
|
60
|
+
const security_monitor_js_1 = require("../../security/security-monitor.js");
|
|
58
61
|
const debugLogger = __importStar(require("@jungjaehoon/mama-core/debug-logger"));
|
|
59
62
|
const { DebugLogger } = debugLogger;
|
|
60
63
|
const routesLogger = new DebugLogger('api-routes');
|
|
@@ -200,6 +203,9 @@ async function registerApiRoutes(params) {
|
|
|
200
203
|
}
|
|
201
204
|
// Wire EventBus to tool executor for agent_notices tool
|
|
202
205
|
toolExecutor.setAgentEventBus(eventBus);
|
|
206
|
+
(0, operator_tasks_handler_js_1.registerOperatorTaskRoutes)(apiServer.app, {
|
|
207
|
+
getTaskLedger: () => toolExecutor.getTaskLedger(),
|
|
208
|
+
});
|
|
203
209
|
const hasEnabledAgentConfig = (agentId) => {
|
|
204
210
|
const agentConfig = config.multi_agent?.agents?.[agentId];
|
|
205
211
|
return Boolean(agentConfig && agentConfig.enabled !== false);
|
|
@@ -262,7 +268,9 @@ async function registerApiRoutes(params) {
|
|
|
262
268
|
ensureDashboardPersona();
|
|
263
269
|
routesLogger.debug('[Dashboard Agent] Persona ensured at ~/.mama/personas/dashboard.md');
|
|
264
270
|
// Dashboard cron: 30-min interval via AgentProcessManager
|
|
265
|
-
|
|
271
|
+
// Built PER RUN: the tracker's D-day arithmetic needs today's date, and a
|
|
272
|
+
// module-const prompt would freeze it (#134 lesson).
|
|
273
|
+
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:
|
|
266
274
|
|
|
267
275
|
1. Use agent_notices({limit: 50}) to find the most recent dashboard-agent publish/task_complete notice. Treat that as the last briefing boundary.
|
|
268
276
|
2. Use context_compile first to find recent substantive decisions (limit 20, max_tool_calls 2, strictness "balanced").
|
|
@@ -270,13 +278,13 @@ async function registerApiRoutes(params) {
|
|
|
270
278
|
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.
|
|
271
279
|
If context_compile is unavailable because there is no active worker envelope, fall back to mama_search once (limit 20).
|
|
272
280
|
3. If NO substantive decisions or agent alerts exist since the last dashboard publish → respond "NO_UPDATE" and stop. Do NOT call report_publish.
|
|
273
|
-
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.
|
|
281
|
+
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); compute D-day from today's date above.
|
|
274
282
|
5. Do NOT call mama_save for the briefing; report_publish and agent_activity are the durable operational record.
|
|
275
283
|
|
|
276
284
|
This saves resources. Only publish when there is genuinely new information to report.`;
|
|
277
285
|
// Owner-initiated refresh skips the NO_UPDATE delta gate: an explicit
|
|
278
286
|
// request means "rebuild the board now", not "tell me nothing changed".
|
|
279
|
-
const
|
|
287
|
+
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');
|
|
280
288
|
const doDashboardRun = async (opts) => {
|
|
281
289
|
const pm = toolExecutor.getAgentProcessManager();
|
|
282
290
|
if (!pm) {
|
|
@@ -287,7 +295,7 @@ This saves resources. Only publish when there is genuinely new information to re
|
|
|
287
295
|
// console.log on run outcomes: the DebugLogger only surfaces warn/error in the
|
|
288
296
|
// daemon log, and a silent outcome reads as a hang from the outside.
|
|
289
297
|
console.log(`[Dashboard Agent] run started${opts?.force ? ' (owner-forced, delta gate bypassed)' : ''}`);
|
|
290
|
-
const { noUpdate } = await executeValidatedRun('dashboard-agent', opts?.force ?
|
|
298
|
+
const { noUpdate } = await executeValidatedRun('dashboard-agent', opts?.force ? buildForcedDashboardPrompt() : buildDashboardPrompt());
|
|
291
299
|
console.log(noUpdate
|
|
292
300
|
? '[Dashboard Agent] no changes detected, publish skipped'
|
|
293
301
|
: '[Dashboard Agent] board published');
|
|
@@ -296,16 +304,20 @@ This saves resources. Only publish when there is genuinely new information to re
|
|
|
296
304
|
routesLogger.error('[Dashboard Agent] Error:', err instanceof Error ? err.message : err);
|
|
297
305
|
}
|
|
298
306
|
};
|
|
299
|
-
//
|
|
300
|
-
// agent process rejects concurrent requests
|
|
301
|
-
// 'Process is busy'
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
307
|
+
// ONE board-writer queue: dashboard cron, manual refresh, AND reconcile runs
|
|
308
|
+
// all serialize here -- the shared agent process rejects concurrent requests
|
|
309
|
+
// ('Process is busy' class). Per-job rejection propagates to the caller
|
|
310
|
+
// while the chain itself survives (M8 review: a caller must be able to
|
|
311
|
+
// distinguish its own job's failure).
|
|
312
|
+
let boardWriterChain = Promise.resolve();
|
|
313
|
+
const boardWriterQueue = {
|
|
314
|
+
push(job) {
|
|
315
|
+
const jobPromise = boardWriterChain.then(job);
|
|
316
|
+
boardWriterChain = jobPromise.catch(() => { });
|
|
317
|
+
return jobPromise;
|
|
318
|
+
},
|
|
308
319
|
};
|
|
320
|
+
const runDashboardAgent = (opts) => boardWriterQueue.push(() => doDashboardRun(opts)).catch(() => { });
|
|
309
321
|
// First run after 10s (let connectors poll first), then every 30 min
|
|
310
322
|
setTimeout(runDashboardAgent, 10_000);
|
|
311
323
|
setInterval(runDashboardAgent, 30 * 60 * 1000);
|
|
@@ -314,6 +326,112 @@ This saves resources. Only publish when there is genuinely new information to re
|
|
|
314
326
|
runDashboardAgent({ force: true }).catch(() => { });
|
|
315
327
|
res.json({ ok: true, message: 'Dashboard agent triggered (forced refresh)' });
|
|
316
328
|
});
|
|
329
|
+
// -- M8 board reconcile leg (freshness layer; default OFF, opt-in like
|
|
330
|
+
// MAMA_TRIGGER_LOOP). The trigger loop emits operator:channel-delta after
|
|
331
|
+
// committing its cursor; the 30-min cron above remains the repair pass.
|
|
332
|
+
if (process.env.MAMA_BOARD_RECONCILE === '1') {
|
|
333
|
+
const { buildReconcilePrompt, ReconcileScheduler } = await import('../../operator/board-reconcile.js');
|
|
334
|
+
const { captureSnapshot, verifyAfterRun, OBLIGATED_TOOLS } = await import('../../operator/action-verifier.js');
|
|
335
|
+
const kagemushaContext = (process.env.MAMA_RECONCILE_TASK_CONTEXT ?? 'native') === 'kagemusha';
|
|
336
|
+
// Verifier deps (M8 Phase 2): run-bound signals only -- trace rows from
|
|
337
|
+
// the dashboard agent's gateway_tool_call activity, notes from the
|
|
338
|
+
// operator ledger. Observe, never block.
|
|
339
|
+
const reconcileLedger = toolExecutor.getTaskLedger();
|
|
340
|
+
// gateway_tool_call rows now populate normalized_tool_name at the logging
|
|
341
|
+
// site; the input_summary fallback covers rows written before that fix.
|
|
342
|
+
const traceToolList = OBLIGATED_TOOLS.map((t) => `'${t}'`).join(',');
|
|
343
|
+
const verifierDeps = {
|
|
344
|
+
getSlots: () => apiServer.reportStore.getAllSorted(),
|
|
345
|
+
getLedgerHash: () => reconcileLedger?.payloadHash() ?? '',
|
|
346
|
+
getScopedNoteMaxId: (scope) => reconcileLedger?.maxNoUpdateId(scope) ?? 0,
|
|
347
|
+
getTraceMaxId: () => {
|
|
348
|
+
if (!sessionsDb)
|
|
349
|
+
return 0;
|
|
350
|
+
const row = sessionsDb
|
|
351
|
+
.prepare(`SELECT MAX(id) AS max_id FROM agent_activity
|
|
352
|
+
WHERE type = 'gateway_tool_call' AND agent_id = 'dashboard-agent'`)
|
|
353
|
+
.get();
|
|
354
|
+
return row.max_id ?? 0;
|
|
355
|
+
},
|
|
356
|
+
countObligatedTraceRowsSince: (maxId) => {
|
|
357
|
+
if (!sessionsDb)
|
|
358
|
+
return 0;
|
|
359
|
+
const row = sessionsDb
|
|
360
|
+
.prepare(`SELECT COUNT(*) AS n FROM agent_activity
|
|
361
|
+
WHERE type = 'gateway_tool_call' AND agent_id = 'dashboard-agent'
|
|
362
|
+
AND id > ? AND (normalized_tool_name IN (${traceToolList}) OR input_summary IN (${traceToolList}))`)
|
|
363
|
+
.get(maxId);
|
|
364
|
+
return row.n;
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
const reconcileScheduler = new ReconcileScheduler({
|
|
368
|
+
debounceMs: Number(process.env.MAMA_RECONCILE_DEBOUNCE_MS) || undefined,
|
|
369
|
+
maxWaitMs: Number(process.env.MAMA_RECONCILE_MAX_WAIT_MS) || undefined,
|
|
370
|
+
globalMaxPerHour: Number(process.env.MAMA_RECONCILE_MAX_PER_HOUR) || undefined,
|
|
371
|
+
log: (line) => console.log(line),
|
|
372
|
+
run: (channelKey, deltaLines) => boardWriterQueue.push(async () => {
|
|
373
|
+
const scope = `reconcile:${channelKey}`;
|
|
374
|
+
const before = captureSnapshot(verifierDeps, scope);
|
|
375
|
+
const prompt = buildReconcilePrompt({
|
|
376
|
+
channelKey,
|
|
377
|
+
deltaLines,
|
|
378
|
+
todayIso: new Date().toISOString().slice(0, 10),
|
|
379
|
+
kagemushaContext,
|
|
380
|
+
});
|
|
381
|
+
await executeValidatedRun('dashboard-agent', prompt, {
|
|
382
|
+
requestTimeout: 300_000,
|
|
383
|
+
});
|
|
384
|
+
const verdict = verifyAfterRun(verifierDeps, before, scope);
|
|
385
|
+
const outcome = verdict.verified ? 'reconcile_verified' : 'reconcile_unverified';
|
|
386
|
+
console.log(`[reconcile] ${outcome} channel=${channelKey}${verdict.effects.length > 0 ? ` (${verdict.effects.join('; ')})` : ''}`);
|
|
387
|
+
try {
|
|
388
|
+
if (sessionsDb) {
|
|
389
|
+
(0, agent_store_js_1.logActivity)(sessionsDb, {
|
|
390
|
+
agent_id: 'dashboard-agent',
|
|
391
|
+
agent_version: 0,
|
|
392
|
+
type: outcome,
|
|
393
|
+
input_summary: `reconcile ${channelKey}`,
|
|
394
|
+
output_summary: verdict.effects.join('; '),
|
|
395
|
+
execution_status: 'completed',
|
|
396
|
+
trigger_reason: 'reconcile',
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
/* telemetry only */
|
|
402
|
+
}
|
|
403
|
+
if (!verdict.verified) {
|
|
404
|
+
// Loud, never blocking (observability over restriction).
|
|
405
|
+
eventBus.emit({
|
|
406
|
+
type: 'agent:action',
|
|
407
|
+
agent: 'Dashboard Agent',
|
|
408
|
+
action: 'reconcile_unverified',
|
|
409
|
+
target: channelKey,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}),
|
|
413
|
+
});
|
|
414
|
+
eventBus.on('operator:channel-delta', (event) => {
|
|
415
|
+
if (event.type === 'operator:channel-delta') {
|
|
416
|
+
reconcileScheduler.enqueue(event.channelKey, event.lines);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
// Manual reconcile: lines from the body, or the caller must supply them
|
|
420
|
+
// (no silent alternate data path -- M8 review #17).
|
|
421
|
+
apiServer.app.post('/api/operator/reconcile', auth_middleware_js_1.requireAuth, (req, res) => {
|
|
422
|
+
const { channelKey, lines } = (req.body ?? {});
|
|
423
|
+
if (!channelKey || !Array.isArray(lines) || lines.length === 0) {
|
|
424
|
+
res.status(400).json({
|
|
425
|
+
ok: false,
|
|
426
|
+
error: 'channelKey and non-empty lines[] are required',
|
|
427
|
+
});
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
reconcileScheduler.enqueue(channelKey, lines);
|
|
431
|
+
res.json({ ok: true, message: 'Reconcile queued (async; runs after debounce)' });
|
|
432
|
+
});
|
|
433
|
+
console.log('[reconcile] Board reconcile leg enabled (MAMA_BOARD_RECONCILE=1)');
|
|
434
|
+
}
|
|
317
435
|
}
|
|
318
436
|
else {
|
|
319
437
|
routesLogger.debug('[Dashboard Agent] Skipped; dashboard-agent is not configured');
|
|
@@ -503,164 +621,84 @@ This saves resources. Only compile when there is genuinely new information to do
|
|
|
503
621
|
});
|
|
504
622
|
console.log(`[Memory Promotion] Ready: every ${PROMOTION_INTERVAL_MS / 3_600_000}h, POST /api/memory/promote`);
|
|
505
623
|
}
|
|
506
|
-
//
|
|
624
|
+
// -- System Audit: hourly deterministic code checks ---------------------
|
|
625
|
+
// Owner decision 2026-04-22 (mama_conductor_audit_code_based_read_only),
|
|
626
|
+
// landed 2026-07-17: the audit is fact collection and recording executed by
|
|
627
|
+
// CODE - no LLM invocation, no auto-fix, no broad filesystem access. The
|
|
628
|
+
// prior hourly LLM audit violated that decision and the 2026-05-14
|
|
629
|
+
// no-shell-autofix rule, and lost its tools entirely to the persona
|
|
630
|
+
// lockdown (--tools ""). The 24h alert-dedup contract (owner verdict
|
|
631
|
+
// 2026-07-11) is preserved inside runCodeAudit; MAJOR findings flow through
|
|
632
|
+
// dispatchSecurityAlertDirect to the configured security alert sender.
|
|
507
633
|
const AUDIT_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
|
|
508
634
|
const AUDIT_INITIAL_DELAY_MS = 5 * 60 * 1000; // 5 min after startup
|
|
509
|
-
|
|
510
|
-
// run because each audit was stateless. agent_notices does not carry audit
|
|
511
|
-
// findings (review feedback on #134), so dedup state lives in a JSON file the
|
|
512
|
-
// conductor reads/writes itself, and the current time is interpolated per run
|
|
513
|
-
// so the 24h comparison is grounded.
|
|
514
|
-
const buildAuditPrompt = (nowIso) => 'Perform a system audit. Read ~/.mama/skills/audit-checklist.md and execute each step. ' +
|
|
515
|
-
'Classify findings as MINOR or MAJOR. ' +
|
|
516
|
-
'For MINOR items: execute the auto-fix command immediately (Bash curl). Do NOT just report — fix it. ' +
|
|
517
|
-
'For MAJOR items: report to human via channel alert, subject to the dedup rule below. ' +
|
|
518
|
-
`Alert dedup (MANDATORY): the current time is ${nowIso}. Read ~/.mama/state/audit-findings.json ` +
|
|
519
|
-
'(it may not exist on the first run). Re-alert a MAJOR finding only if it is NEW (no entry ' +
|
|
520
|
-
'with the same id), has ESCALATED, or its last_alerted_at is older than 24 hours compared to ' +
|
|
521
|
-
'the current time; otherwise stay silent about it. Never alert MINOR findings to the owner. ' +
|
|
522
|
-
'After the audit, Write ~/.mama/state/audit-findings.json with an array of every current ' +
|
|
523
|
-
'finding: {id (stable kebab-case slug), severity, first_seen, last_seen, last_alerted_at}. ' +
|
|
524
|
-
'Carry forward first_seen and last_alerted_at from the previous file; set last_alerted_at to ' +
|
|
525
|
-
'the current time only for findings you actually alerted this run. ' +
|
|
526
|
-
// Owner verdict 2026-07-10: hourly-audit self-notes are memory noise -- the two
|
|
527
|
-
// "durable policy" saves it produced were unscoped near-duplicates polluting
|
|
528
|
-
// global recall. The audit record lives in validation sessions + agent_activity;
|
|
529
|
-
// MAJOR findings reach the owner via the channel alert. No memory writes here.
|
|
530
|
-
'Do NOT call mama_save under any circumstances: the validation session and agent_activity ' +
|
|
531
|
-
'are the durable audit record, and repeated audits must not accumulate policy notes in memory.';
|
|
532
|
-
const runConductorAudit = async () => {
|
|
533
|
-
let auditSession = null;
|
|
534
|
-
let auditVersion = 0;
|
|
535
|
-
const auditStart = Date.now();
|
|
635
|
+
const doSystemAudit = async () => {
|
|
536
636
|
try {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
trigger_reason: 'audit',
|
|
637
|
+
const report = await (0, code_audit_js_1.runCodeAudit)({
|
|
638
|
+
config: {
|
|
639
|
+
telegram: config.telegram,
|
|
640
|
+
multi_agent: config.multi_agent,
|
|
641
|
+
},
|
|
642
|
+
// Evaluated at audit time: gateway-wiring registers the sender only
|
|
643
|
+
// for ACTIVE gateways, so a configured target on a dead gateway must
|
|
644
|
+
// not report a false PASS.
|
|
645
|
+
securityAlertConfigured: (0, security_monitor_js_1.hasSecurityAlertSender)(),
|
|
646
|
+
// Direct dispatch, NOT recordSecurityEvent: audit findings are
|
|
647
|
+
// self-generated and must not fabricate incident/denylist/RDAP
|
|
648
|
+
// artifacts for a pseudo client. dispatchSecurityAlertDirect awaits
|
|
649
|
+
// delivery and throws on failure, so runCodeAudit keeps
|
|
650
|
+
// last_alerted_at null and retries on the next run (fail loud).
|
|
651
|
+
alert: async (finding, reason) => {
|
|
652
|
+
await (0, security_monitor_js_1.dispatchSecurityAlertDirect)({
|
|
653
|
+
type: 'system_audit_finding',
|
|
654
|
+
severity: 'critical',
|
|
655
|
+
message: `[Audit/${reason}] ${finding.summary}`,
|
|
656
|
+
// path carries the finding id so the alert-cooldown fingerprint
|
|
657
|
+
// distinguishes findings instead of collapsing them all into one.
|
|
658
|
+
path: finding.id,
|
|
659
|
+
details: { findingId: finding.id, detail: finding.detail ?? null },
|
|
561
660
|
});
|
|
562
|
-
|
|
563
|
-
try {
|
|
564
|
-
validationService?.recordRun(auditSession.id, { activityId: startRow.id });
|
|
565
|
-
}
|
|
566
|
-
catch (telemetryErr) {
|
|
567
|
-
routesLogger.warn('[Conductor Audit] Failed to link startup activity to validation session:', telemetryErr);
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
catch (telemetryErr) {
|
|
572
|
-
routesLogger.warn('[Conductor Audit] Failed to write startup telemetry:', telemetryErr);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
const auditChannelId = `conductor-audit-${Date.now()}`;
|
|
576
|
-
await messageRouter.process({
|
|
577
|
-
source: 'system',
|
|
578
|
-
channelId: auditChannelId,
|
|
579
|
-
userId: 'system',
|
|
580
|
-
text: buildAuditPrompt(new Date().toISOString()),
|
|
661
|
+
},
|
|
581
662
|
});
|
|
582
|
-
const
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
duration_ms: auditDuration,
|
|
591
|
-
run_id: auditSession?.id,
|
|
592
|
-
execution_status: 'completed',
|
|
593
|
-
trigger_reason: 'audit',
|
|
594
|
-
});
|
|
595
|
-
if (auditSession) {
|
|
596
|
-
validationService?.recordRun(auditSession.id, { activityId: activityRow.id });
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
catch (telemetryErr) {
|
|
601
|
-
routesLogger.warn('[Conductor Audit] Failed to write completion telemetry:', telemetryErr);
|
|
602
|
-
}
|
|
603
|
-
try {
|
|
604
|
-
if (auditSession && validationService) {
|
|
605
|
-
validationService.finalizeSession(auditSession.id, {
|
|
606
|
-
execution_status: 'completed',
|
|
607
|
-
metrics: { duration_ms: auditDuration },
|
|
608
|
-
});
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
catch (telemetryErr) {
|
|
612
|
-
routesLogger.warn('[Conductor Audit] Failed to finalize validation session:', telemetryErr);
|
|
613
|
-
}
|
|
614
|
-
routesLogger.debug('[Conductor Audit] Audit complete');
|
|
663
|
+
const majors = report.findings.filter((f) => f.severity === 'MAJOR').length;
|
|
664
|
+
const minors = report.findings.filter((f) => f.severity === 'MINOR').length;
|
|
665
|
+
routesLogger.info(`[System Audit] ${report.pass_items.length} pass, ${majors} MAJOR, ${minors} MINOR, ` +
|
|
666
|
+
`alerted=[${report.alerted.join(',')}] in ${report.duration_ms}ms`);
|
|
667
|
+
if (report.alert_delivery_failures.length > 0) {
|
|
668
|
+
routesLogger.error('[System Audit] Alert delivery failures:', report.alert_delivery_failures.join('; '));
|
|
669
|
+
}
|
|
670
|
+
return report;
|
|
615
671
|
}
|
|
616
672
|
catch (err) {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
if (sessionsDb) {
|
|
620
|
-
const failRow = (0, agent_store_js_1.logActivity)(sessionsDb, {
|
|
621
|
-
agent_id: 'conductor',
|
|
622
|
-
agent_version: auditVersion,
|
|
623
|
-
type: 'audit_failed',
|
|
624
|
-
input_summary: 'Hourly system audit failed',
|
|
625
|
-
error_message: err instanceof Error ? err.message : String(err),
|
|
626
|
-
duration_ms: auditDuration,
|
|
627
|
-
run_id: auditSession?.id,
|
|
628
|
-
execution_status: 'failed',
|
|
629
|
-
trigger_reason: 'audit',
|
|
630
|
-
});
|
|
631
|
-
if (auditSession) {
|
|
632
|
-
validationService?.recordRun(auditSession.id, { activityId: failRow.id });
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
catch (telemetryErr) {
|
|
637
|
-
routesLogger.warn('[Conductor Audit] Failed to write failure telemetry:', telemetryErr);
|
|
638
|
-
}
|
|
639
|
-
try {
|
|
640
|
-
if (auditSession && validationService) {
|
|
641
|
-
validationService.finalizeSession(auditSession.id, {
|
|
642
|
-
execution_status: 'failed',
|
|
643
|
-
error_message: err instanceof Error ? err.message : String(err),
|
|
644
|
-
metrics: { duration_ms: auditDuration },
|
|
645
|
-
});
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
catch (telemetryErr) {
|
|
649
|
-
routesLogger.warn('[Conductor Audit] Failed to finalize failed session:', telemetryErr);
|
|
650
|
-
}
|
|
651
|
-
routesLogger.error('[Conductor Audit] Failed:', err instanceof Error ? err.message : String(err));
|
|
673
|
+
routesLogger.error('[System Audit] Failed:', err instanceof Error ? err.message : String(err));
|
|
674
|
+
return null;
|
|
652
675
|
}
|
|
653
676
|
};
|
|
677
|
+
// Serialize scheduled and manual runs (same pattern as the memory-promotion
|
|
678
|
+
// chain above): overlapping audits would read the same prior state, alert
|
|
679
|
+
// twice, and race the audit-findings.json write. doSystemAudit never
|
|
680
|
+
// rejects, so chaining on the previous run is sufficient.
|
|
681
|
+
let auditRunChain = Promise.resolve(null);
|
|
682
|
+
const runSystemAudit = () => {
|
|
683
|
+
const next = auditRunChain.then(doSystemAudit);
|
|
684
|
+
auditRunChain = next;
|
|
685
|
+
return next;
|
|
686
|
+
};
|
|
654
687
|
setTimeout(() => {
|
|
655
|
-
|
|
656
|
-
setInterval(
|
|
688
|
+
void runSystemAudit();
|
|
689
|
+
setInterval(() => void runSystemAudit(), AUDIT_INTERVAL_MS);
|
|
657
690
|
}, AUDIT_INITIAL_DELAY_MS);
|
|
658
|
-
// Manual trigger
|
|
691
|
+
// Manual trigger returns the full report (read-only, no LLM, fast)
|
|
659
692
|
apiServer.app.post('/api/conductor/audit', auth_middleware_js_1.requireAuth, async (_req, res) => {
|
|
660
|
-
|
|
661
|
-
|
|
693
|
+
const report = await runSystemAudit();
|
|
694
|
+
if (report) {
|
|
695
|
+
res.json({ ok: true, mode: 'code', report });
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
res.status(500).json({ ok: false, error: 'audit failed - see daemon.log' });
|
|
699
|
+
}
|
|
662
700
|
});
|
|
663
|
-
routesLogger.info('[
|
|
701
|
+
routesLogger.info('[System Audit] Ready - deterministic code checks every 60 min, POST /api/conductor/audit for manual run');
|
|
664
702
|
// ── Memory Agent stats API ────────────────────────────────────────────
|
|
665
703
|
apiServer.app.get('/api/memory-agent/stats', auth_middleware_js_1.requireAuth, (_req, res) => {
|
|
666
704
|
const stats = messageRouter.getMemoryAgentStats();
|