@zhixuan92/multi-model-agent 5.0.2 → 5.0.3
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/README.md +8 -18
- package/dist/cli/index.d.ts +62 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +345 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/info.d.ts +22 -0
- package/dist/cli/info.d.ts.map +1 -0
- package/dist/cli/info.js +100 -0
- package/dist/cli/info.js.map +1 -0
- package/dist/cli/logs.d.ts +15 -0
- package/dist/cli/logs.d.ts.map +1 -0
- package/dist/cli/logs.js +102 -0
- package/dist/cli/logs.js.map +1 -0
- package/dist/cli/print-token.d.ts +18 -0
- package/dist/cli/print-token.d.ts.map +1 -0
- package/dist/cli/print-token.js +60 -0
- package/dist/cli/print-token.js.map +1 -0
- package/dist/cli/serve.d.ts +28 -0
- package/dist/cli/serve.d.ts.map +1 -0
- package/dist/cli/serve.js +405 -0
- package/dist/cli/serve.js.map +1 -0
- package/dist/cli/status.d.ts +49 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +155 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/sync-skills.d.ts +58 -0
- package/dist/cli/sync-skills.d.ts.map +1 -0
- package/dist/cli/sync-skills.js +266 -0
- package/dist/cli/sync-skills.js.map +1 -0
- package/dist/cli/telemetry.d.ts +10 -0
- package/dist/cli/telemetry.d.ts.map +1 -0
- package/dist/cli/telemetry.js +161 -0
- package/dist/cli/telemetry.js.map +1 -0
- package/dist/cli/toggle.d.ts +26 -0
- package/dist/cli/toggle.d.ts.map +1 -0
- package/dist/cli/toggle.js +185 -0
- package/dist/cli/toggle.js.map +1 -0
- package/dist/http/async-dispatch.d.ts +44 -0
- package/dist/http/async-dispatch.d.ts.map +1 -0
- package/dist/http/async-dispatch.js +175 -0
- package/dist/http/async-dispatch.js.map +1 -0
- package/dist/http/auth.d.ts +20 -0
- package/dist/http/auth.d.ts.map +1 -0
- package/dist/http/auth.js +56 -0
- package/dist/http/auth.js.map +1 -0
- package/dist/http/canonicalize-file-paths.d.ts +8 -0
- package/dist/http/canonicalize-file-paths.d.ts.map +1 -0
- package/dist/http/canonicalize-file-paths.js +43 -0
- package/dist/http/canonicalize-file-paths.js.map +1 -0
- package/dist/http/cwd-validator.d.ts +11 -0
- package/dist/http/cwd-validator.d.ts.map +1 -0
- package/dist/http/cwd-validator.js +130 -0
- package/dist/http/cwd-validator.js.map +1 -0
- package/dist/http/errors.d.ts +4 -0
- package/dist/http/errors.d.ts.map +1 -0
- package/dist/http/errors.js +9 -0
- package/dist/http/errors.js.map +1 -0
- package/dist/http/execution-context.d.ts +18 -0
- package/dist/http/execution-context.d.ts.map +1 -0
- package/dist/http/execution-context.js +61 -0
- package/dist/http/execution-context.js.map +1 -0
- package/dist/http/handler-deps.d.ts +19 -0
- package/dist/http/handler-deps.d.ts.map +1 -0
- package/dist/http/handler-deps.js +2 -0
- package/dist/http/handler-deps.js.map +1 -0
- package/dist/http/handlers/control/batch-slice.d.ts +4 -0
- package/dist/http/handlers/control/batch-slice.d.ts.map +1 -0
- package/dist/http/handlers/control/batch-slice.js +40 -0
- package/dist/http/handlers/control/batch-slice.js.map +1 -0
- package/dist/http/handlers/control/batch.d.ts +23 -0
- package/dist/http/handlers/control/batch.d.ts.map +1 -0
- package/dist/http/handlers/control/batch.js +332 -0
- package/dist/http/handlers/control/batch.js.map +1 -0
- package/dist/http/handlers/control/context-blocks.d.ts +22 -0
- package/dist/http/handlers/control/context-blocks.d.ts.map +1 -0
- package/dist/http/handlers/control/context-blocks.js +111 -0
- package/dist/http/handlers/control/context-blocks.js.map +1 -0
- package/dist/http/handlers/introspection/health.d.ts +20 -0
- package/dist/http/handlers/introspection/health.d.ts.map +1 -0
- package/dist/http/handlers/introspection/health.js +18 -0
- package/dist/http/handlers/introspection/health.js.map +1 -0
- package/dist/http/handlers/introspection/status.d.ts +26 -0
- package/dist/http/handlers/introspection/status.d.ts.map +1 -0
- package/dist/http/handlers/introspection/status.js +136 -0
- package/dist/http/handlers/introspection/status.js.map +1 -0
- package/dist/http/handlers/tools/audit.d.ts +4 -0
- package/dist/http/handlers/tools/audit.d.ts.map +1 -0
- package/dist/http/handlers/tools/audit.js +43 -0
- package/dist/http/handlers/tools/audit.js.map +1 -0
- package/dist/http/handlers/tools/debug.d.ts +4 -0
- package/dist/http/handlers/tools/debug.d.ts.map +1 -0
- package/dist/http/handlers/tools/debug.js +43 -0
- package/dist/http/handlers/tools/debug.js.map +1 -0
- package/dist/http/handlers/tools/delegate.d.ts +4 -0
- package/dist/http/handlers/tools/delegate.d.ts.map +1 -0
- package/dist/http/handlers/tools/delegate.js +43 -0
- package/dist/http/handlers/tools/delegate.js.map +1 -0
- package/dist/http/handlers/tools/execute-plan.d.ts +4 -0
- package/dist/http/handlers/tools/execute-plan.d.ts.map +1 -0
- package/dist/http/handlers/tools/execute-plan.js +45 -0
- package/dist/http/handlers/tools/execute-plan.js.map +1 -0
- package/dist/http/handlers/tools/investigate.d.ts +4 -0
- package/dist/http/handlers/tools/investigate.d.ts.map +1 -0
- package/dist/http/handlers/tools/investigate.js +64 -0
- package/dist/http/handlers/tools/investigate.js.map +1 -0
- package/dist/http/handlers/tools/journal-recall.d.ts +4 -0
- package/dist/http/handlers/tools/journal-recall.d.ts.map +1 -0
- package/dist/http/handlers/tools/journal-recall.js +40 -0
- package/dist/http/handlers/tools/journal-recall.js.map +1 -0
- package/dist/http/handlers/tools/journal-record.d.ts +8 -0
- package/dist/http/handlers/tools/journal-record.d.ts.map +1 -0
- package/dist/http/handlers/tools/journal-record.js +40 -0
- package/dist/http/handlers/tools/journal-record.js.map +1 -0
- package/dist/http/handlers/tools/research.d.ts +4 -0
- package/dist/http/handlers/tools/research.d.ts.map +1 -0
- package/dist/http/handlers/tools/research.js +64 -0
- package/dist/http/handlers/tools/research.js.map +1 -0
- package/dist/http/handlers/tools/retry.d.ts +4 -0
- package/dist/http/handlers/tools/retry.d.ts.map +1 -0
- package/dist/http/handlers/tools/retry.js +73 -0
- package/dist/http/handlers/tools/retry.js.map +1 -0
- package/dist/http/handlers/tools/review.d.ts +4 -0
- package/dist/http/handlers/tools/review.d.ts.map +1 -0
- package/dist/http/handlers/tools/review.js +43 -0
- package/dist/http/handlers/tools/review.js.map +1 -0
- package/dist/http/journal-lock.d.ts +4 -0
- package/dist/http/journal-lock.d.ts.map +1 -0
- package/dist/http/journal-lock.js +34 -0
- package/dist/http/journal-lock.js.map +1 -0
- package/dist/http/middleware/body-reader.d.ts +16 -0
- package/dist/http/middleware/body-reader.d.ts.map +1 -0
- package/dist/http/middleware/body-reader.js +44 -0
- package/dist/http/middleware/body-reader.js.map +1 -0
- package/dist/http/middleware/caller-identity.d.ts +16 -0
- package/dist/http/middleware/caller-identity.d.ts.map +1 -0
- package/dist/http/middleware/caller-identity.js +16 -0
- package/dist/http/middleware/caller-identity.js.map +1 -0
- package/dist/http/middleware/decompress.d.ts +14 -0
- package/dist/http/middleware/decompress.d.ts.map +1 -0
- package/dist/http/middleware/decompress.js +51 -0
- package/dist/http/middleware/decompress.js.map +1 -0
- package/dist/http/project-registry.d.ts +54 -0
- package/dist/http/project-registry.d.ts.map +1 -0
- package/dist/http/project-registry.js +130 -0
- package/dist/http/project-registry.js.map +1 -0
- package/dist/http/request-observability.d.ts +8 -0
- package/dist/http/request-observability.d.ts.map +1 -0
- package/dist/http/request-observability.js +20 -0
- package/dist/http/request-observability.js.map +1 -0
- package/dist/http/request-pipeline.d.ts +16 -0
- package/dist/http/request-pipeline.d.ts.map +1 -0
- package/dist/http/request-pipeline.js +144 -0
- package/dist/http/request-pipeline.js.map +1 -0
- package/dist/http/server.d.ts +17 -0
- package/dist/http/server.d.ts.map +1 -0
- package/dist/http/server.js +300 -0
- package/dist/http/server.js.map +1 -0
- package/dist/http/types.d.ts +20 -0
- package/dist/http/types.d.ts.map +1 -0
- package/dist/http/types.js +2 -0
- package/dist/http/types.js.map +1 -0
- package/dist/skill-install/disabled-state.d.ts +35 -0
- package/dist/skill-install/disabled-state.d.ts.map +1 -0
- package/dist/skill-install/disabled-state.js +96 -0
- package/dist/skill-install/disabled-state.js.map +1 -0
- package/dist/skill-install/discover.d.ts +29 -0
- package/dist/skill-install/discover.d.ts.map +1 -0
- package/dist/skill-install/discover.js +104 -0
- package/dist/skill-install/discover.js.map +1 -0
- package/dist/skill-install/include-utils.d.ts +27 -0
- package/dist/skill-install/include-utils.d.ts.map +1 -0
- package/dist/skill-install/include-utils.js +90 -0
- package/dist/skill-install/include-utils.js.map +1 -0
- package/dist/skill-install/manifest.d.ts +82 -0
- package/dist/skill-install/manifest.d.ts.map +1 -0
- package/dist/skill-install/manifest.js +215 -0
- package/dist/skill-install/manifest.js.map +1 -0
- package/dist/skill-install/skill-installer-common.d.ts +26 -0
- package/dist/skill-install/skill-installer-common.d.ts.map +1 -0
- package/dist/skill-install/skill-installer-common.js +139 -0
- package/dist/skill-install/skill-installer-common.js.map +1 -0
- package/dist/skill-install/skill-installers/claude-code.d.ts +43 -0
- package/dist/skill-install/skill-installers/claude-code.d.ts.map +1 -0
- package/dist/skill-install/skill-installers/claude-code.js +65 -0
- package/dist/skill-install/skill-installers/claude-code.js.map +1 -0
- package/dist/skill-install/skill-installers/codex-cli.d.ts +27 -0
- package/dist/skill-install/skill-installers/codex-cli.d.ts.map +1 -0
- package/dist/skill-install/skill-installers/codex-cli.js +84 -0
- package/dist/skill-install/skill-installers/codex-cli.js.map +1 -0
- package/dist/skill-install/skill-installers/cursor.d.ts +72 -0
- package/dist/skill-install/skill-installers/cursor.d.ts.map +1 -0
- package/dist/skill-install/skill-installers/cursor.js +81 -0
- package/dist/skill-install/skill-installers/cursor.js.map +1 -0
- package/dist/skill-install/skill-installers/gemini-cli.d.ts +50 -0
- package/dist/skill-install/skill-installers/gemini-cli.d.ts.map +1 -0
- package/dist/skill-install/skill-installers/gemini-cli.js +72 -0
- package/dist/skill-install/skill-installers/gemini-cli.js.map +1 -0
- package/dist/skill-install/skill-manifest-sync.d.ts +11 -0
- package/dist/skill-install/skill-manifest-sync.d.ts.map +1 -0
- package/dist/skill-install/skill-manifest-sync.js +65 -0
- package/dist/skill-install/skill-manifest-sync.js.map +1 -0
- package/dist/skills/_shared/auth.md +41 -0
- package/dist/skills/_shared/error-handling.md +31 -0
- package/dist/skills/_shared/polling.md +88 -0
- package/dist/skills/_shared/response-shape.md +55 -0
- package/dist/skills/_shared/review-policy.md +15 -0
- package/dist/skills/mma-audit/SKILL.md +270 -0
- package/dist/skills/mma-context-blocks/SKILL.md +148 -0
- package/dist/skills/mma-debug/SKILL.md +208 -0
- package/dist/skills/mma-delegate/SKILL.md +216 -0
- package/dist/skills/mma-execute-plan/SKILL.md +214 -0
- package/dist/skills/mma-explore/SKILL.md +190 -0
- package/dist/skills/mma-investigate/SKILL.md +258 -0
- package/dist/skills/mma-journal-recall/SKILL.md +242 -0
- package/dist/skills/mma-journal-record/SKILL.md +202 -0
- package/dist/skills/mma-research/SKILL.md +223 -0
- package/dist/skills/mma-retry/SKILL.md +221 -0
- package/dist/skills/mma-review/SKILL.md +209 -0
- package/dist/skills/multi-model-agent/SKILL.md +206 -0
- package/dist/telemetry/consent.d.ts +4 -0
- package/dist/telemetry/consent.d.ts.map +1 -0
- package/dist/telemetry/consent.js +40 -0
- package/dist/telemetry/consent.js.map +1 -0
- package/dist/telemetry/flusher.d.ts +19 -0
- package/dist/telemetry/flusher.d.ts.map +1 -0
- package/dist/telemetry/flusher.js +277 -0
- package/dist/telemetry/flusher.js.map +1 -0
- package/dist/telemetry/generation.d.ts +9 -0
- package/dist/telemetry/generation.d.ts.map +1 -0
- package/dist/telemetry/generation.js +33 -0
- package/dist/telemetry/generation.js.map +1 -0
- package/dist/telemetry/identity.d.ts +9 -0
- package/dist/telemetry/identity.d.ts.map +1 -0
- package/dist/telemetry/identity.js +35 -0
- package/dist/telemetry/identity.js.map +1 -0
- package/dist/telemetry/install-id.d.ts +13 -0
- package/dist/telemetry/install-id.d.ts.map +1 -0
- package/dist/telemetry/install-id.js +49 -0
- package/dist/telemetry/install-id.js.map +1 -0
- package/dist/telemetry/install-meta.d.ts +10 -0
- package/dist/telemetry/install-meta.d.ts.map +1 -0
- package/dist/telemetry/install-meta.js +15 -0
- package/dist/telemetry/install-meta.js.map +1 -0
- package/dist/telemetry/queue.d.ts +35 -0
- package/dist/telemetry/queue.d.ts.map +1 -0
- package/dist/telemetry/queue.js +287 -0
- package/dist/telemetry/queue.js.map +1 -0
- package/dist/telemetry/recorder.d.ts +39 -0
- package/dist/telemetry/recorder.d.ts.map +1 -0
- package/dist/telemetry/recorder.js +173 -0
- package/dist/telemetry/recorder.js.map +1 -0
- package/package.json +43 -24
- package/scripts/postinstall.js +36 -0
- package/bin/mmagent.mjs +0 -47
- package/postinstall.mjs +0 -8
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MultiModelConfig } from '@zhixuan92/multi-model-agent-core';
|
|
2
|
+
export interface LogsDeps {
|
|
3
|
+
config: MultiModelConfig;
|
|
4
|
+
homeDir?: string;
|
|
5
|
+
follow?: boolean;
|
|
6
|
+
batchId?: string;
|
|
7
|
+
/** Polling interval when --follow; defaults to 300ms. */
|
|
8
|
+
pollMs?: number;
|
|
9
|
+
/** Max time to wait for the log file to appear under --follow; defaults to 30s. */
|
|
10
|
+
waitForLogMs?: number;
|
|
11
|
+
stdout?: (s: string) => boolean;
|
|
12
|
+
stderr?: (s: string) => boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function runLogs(deps: LogsDeps): Promise<number>;
|
|
15
|
+
//# sourceMappingURL=logs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../src/cli/logs.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IAChC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;CACjC;AAeD,wBAAsB,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CA0E7D"}
|
package/dist/cli/logs.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* logs.ts — `mmagent logs` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Tails the diagnostic log file for today (mmagent-YYYY-MM-DD.jsonl). Supports
|
|
5
|
+
* --follow for tail-F semantics and --batch=<id> to filter to a single batch.
|
|
6
|
+
*
|
|
7
|
+
* Exit codes:
|
|
8
|
+
* 0 — success (including "no log file found" cases so scripts don't break)
|
|
9
|
+
* 1 — reserved for future use
|
|
10
|
+
*/
|
|
11
|
+
import * as os from 'node:os';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
import * as fs from 'node:fs';
|
|
14
|
+
function todayUtc() {
|
|
15
|
+
return new Date().toISOString().slice(0, 10);
|
|
16
|
+
}
|
|
17
|
+
function resolveLogPath(config, homeDir) {
|
|
18
|
+
const dir = config.diagnostics?.logDir ?? path.join(homeDir, '.multi-model', 'logs');
|
|
19
|
+
return path.join(dir, `mmagent-${todayUtc()}.jsonl`);
|
|
20
|
+
}
|
|
21
|
+
function matchesBatch(line, batchId) {
|
|
22
|
+
return line.includes(`"batchId":"${batchId}"`);
|
|
23
|
+
}
|
|
24
|
+
export async function runLogs(deps) {
|
|
25
|
+
const stdout = deps.stdout ?? process.stdout.write.bind(process.stdout);
|
|
26
|
+
const stderr = deps.stderr ?? process.stderr.write.bind(process.stderr);
|
|
27
|
+
const homeDir = deps.homeDir ?? os.homedir();
|
|
28
|
+
const follow = deps.follow ?? false;
|
|
29
|
+
const pollMs = deps.pollMs ?? 300;
|
|
30
|
+
const waitForLogMs = deps.waitForLogMs ?? 30_000;
|
|
31
|
+
if (!deps.config.diagnostics?.log) {
|
|
32
|
+
stderr(`mmagent logs: diagnostics.log is false in config; set it to true to capture new events.\n`);
|
|
33
|
+
}
|
|
34
|
+
const logPath = resolveLogPath(deps.config, homeDir);
|
|
35
|
+
if (!fs.existsSync(logPath)) {
|
|
36
|
+
if (!follow) {
|
|
37
|
+
stderr(`mmagent logs: no log file at ${logPath}. Start the server with diagnostics.log: true and try again.\n`);
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
const deadline = Date.now() + waitForLogMs;
|
|
41
|
+
while (!fs.existsSync(logPath) && Date.now() < deadline) {
|
|
42
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
43
|
+
}
|
|
44
|
+
if (!fs.existsSync(logPath)) {
|
|
45
|
+
stderr(`mmagent logs: no log file appeared within ${Math.floor(waitForLogMs / 1000)}s at ${logPath}.\n`);
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Emit existing content (optionally batch-filtered).
|
|
50
|
+
let offset = 0;
|
|
51
|
+
try {
|
|
52
|
+
const existing = fs.readFileSync(logPath, 'utf8');
|
|
53
|
+
for (const line of existing.split('\n')) {
|
|
54
|
+
if (line.length === 0)
|
|
55
|
+
continue;
|
|
56
|
+
if (deps.batchId && !matchesBatch(line, deps.batchId))
|
|
57
|
+
continue;
|
|
58
|
+
stdout(line + '\n');
|
|
59
|
+
}
|
|
60
|
+
offset = existing.length;
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
stderr(`mmagent logs: cannot read ${logPath}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
if (!follow)
|
|
67
|
+
return 0;
|
|
68
|
+
// Tail — poll for new content appended after `offset`.
|
|
69
|
+
let buf = '';
|
|
70
|
+
while (true) {
|
|
71
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
72
|
+
let stat;
|
|
73
|
+
try {
|
|
74
|
+
stat = fs.statSync(logPath);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (stat.size <= offset)
|
|
80
|
+
continue;
|
|
81
|
+
const fd = fs.openSync(logPath, 'r');
|
|
82
|
+
try {
|
|
83
|
+
const chunk = Buffer.alloc(stat.size - offset);
|
|
84
|
+
fs.readSync(fd, chunk, 0, chunk.length, offset);
|
|
85
|
+
buf += chunk.toString('utf8');
|
|
86
|
+
offset = stat.size;
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
fs.closeSync(fd);
|
|
90
|
+
}
|
|
91
|
+
const lines = buf.split('\n');
|
|
92
|
+
buf = lines.pop() ?? ''; // keep any trailing partial line for the next iteration
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
if (line.length === 0)
|
|
95
|
+
continue;
|
|
96
|
+
if (deps.batchId && !matchesBatch(line, deps.batchId))
|
|
97
|
+
continue;
|
|
98
|
+
stdout(line + '\n');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=logs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logs.js","sourceRoot":"","sources":["../../src/cli/logs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAgB9B,SAAS,QAAQ;IACf,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC/C,CAAC;AAED,SAAS,cAAc,CAAC,MAAwB,EAAE,OAAe;IAC/D,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,EAAE,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;IACrF,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACvD,CAAC;AAED,SAAS,YAAY,CAAC,IAAY,EAAE,OAAe;IACjD,OAAO,IAAI,CAAC,QAAQ,CAAC,cAAc,OAAO,GAAG,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAc;IAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACxE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;IAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC;IACpC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC;IAClC,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,MAAM,CAAC;IAEjD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC;QAClC,MAAM,CAAC,2FAA2F,CAAC,CAAC;IACtG,CAAC;IAED,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAErD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,CAAC,gCAAgC,OAAO,gEAAgE,CAAC,CAAC;YAChH,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,CAAC;QAC3C,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;YACxD,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAClD,CAAC;QACD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,MAAM,CAAC,6CAA6C,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,QAAQ,OAAO,KAAK,CAAC,CAAC;YACzG,OAAO,CAAC,CAAC;QACX,CAAC;IACH,CAAC;IAED,qDAAqD;IACrD,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAClD,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACxC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YAChC,IAAI,IAAI,CAAC,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC;gBAAE,SAAS;YAChE,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;QACtB,CAAC;QACD,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,6BAA6B,OAAO,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACtG,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC;IAEtB,uDAAuD;IACvD,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAChD,IAAI,IAAc,CAAC;QACnB,IAAI,CAAC;YACH,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,IAAI,MAAM;YAAE,SAAS;QAClC,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;YAC/C,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAChD,GAAG,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAC9B,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC;QACrB,CAAC;gBAAS,CAAC;YACT,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QACnB,CAAC;QACD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9B,GAAG,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,wDAAwD;QACjF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YAChC,IAAI,IAAI,CAAC,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC;gBAAE,SAAS;YAChE,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface PrintTokenDeps {
|
|
2
|
+
/** Home directory (defaults to os.homedir()). */
|
|
3
|
+
homeDir?: string;
|
|
4
|
+
/** Token file path (already expanded). Overrides config discovery. */
|
|
5
|
+
tokenFile?: string;
|
|
6
|
+
/** Environment variable accessor. Defaults to process.env. */
|
|
7
|
+
env?: Record<string, string | undefined>;
|
|
8
|
+
/** Write to stdout. Defaults to process.stdout.write. */
|
|
9
|
+
stdout?: (s: string) => boolean;
|
|
10
|
+
/** Write to stderr. Defaults to process.stderr.write. */
|
|
11
|
+
stderr?: (s: string) => boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Read the bearer token and print it to stdout.
|
|
15
|
+
* Returns 0 on success, 1 on error.
|
|
16
|
+
*/
|
|
17
|
+
export declare function printToken(deps?: PrintTokenDeps): number;
|
|
18
|
+
//# sourceMappingURL=print-token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"print-token.d.ts","sourceRoot":"","sources":["../../src/cli/print-token.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,cAAc;IAC7B,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,yDAAyD;IACzD,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IAChC,yDAAyD;IACzD,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;CACjC;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,GAAE,cAAmB,GAAG,MAAM,CAsC5D"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* print-token.ts — `mmagent print-token` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Reads the bearer auth token and prints it to stdout.
|
|
5
|
+
* Env override (MMAGENT_AUTH_TOKEN) wins over any file.
|
|
6
|
+
* Missing file → prints an error message to stderr and exits 1.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* mmagent print-token [--config <path>]
|
|
10
|
+
*/
|
|
11
|
+
import * as os from 'node:os';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
import * as fs from 'node:fs';
|
|
14
|
+
/** Expand a leading '~/' to the home directory. */
|
|
15
|
+
function expandHome(p, homeDir) {
|
|
16
|
+
if (p.startsWith('~/'))
|
|
17
|
+
return path.join(homeDir, p.slice(2));
|
|
18
|
+
return p;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Read the bearer token and print it to stdout.
|
|
22
|
+
* Returns 0 on success, 1 on error.
|
|
23
|
+
*/
|
|
24
|
+
export function printToken(deps = {}) {
|
|
25
|
+
const homeDir = deps.homeDir ?? os.homedir();
|
|
26
|
+
const env = deps.env ?? process.env;
|
|
27
|
+
const stdout = deps.stdout ?? process.stdout.write.bind(process.stdout);
|
|
28
|
+
const stderr = deps.stderr ?? process.stderr.write.bind(process.stderr);
|
|
29
|
+
// Env override wins
|
|
30
|
+
const envToken = (env['MMAGENT_AUTH_TOKEN'] ?? '').trim();
|
|
31
|
+
if (envToken.length > 0) {
|
|
32
|
+
stdout(envToken + '\n');
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
// Fall back to token file
|
|
36
|
+
const rawTokenFile = deps.tokenFile ?? path.join(homeDir, '.multi-model', 'auth-token');
|
|
37
|
+
const tokenFile = expandHome(rawTokenFile, homeDir);
|
|
38
|
+
try {
|
|
39
|
+
const token = fs.readFileSync(tokenFile, 'utf-8').trim();
|
|
40
|
+
if (token.length === 0) {
|
|
41
|
+
stderr(`mmagent: token file is empty: ${tokenFile}\n`);
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
stdout(token + '\n');
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
const code = err.code;
|
|
49
|
+
if (code === 'ENOENT') {
|
|
50
|
+
stderr(`mmagent: token file not found: ${tokenFile}\n` +
|
|
51
|
+
`Run 'mmagent serve' once to generate a token, or set MMAGENT_AUTH_TOKEN.\n`);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
55
|
+
stderr(`mmagent: cannot read token file ${tokenFile}: ${msg}\n`);
|
|
56
|
+
}
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=print-token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"print-token.js","sourceRoot":"","sources":["../../src/cli/print-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B,mDAAmD;AACnD,SAAS,UAAU,CAAC,CAAS,EAAE,OAAe;IAC5C,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9D,OAAO,CAAC,CAAC;AACX,CAAC;AAeD;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,OAAuB,EAAE;IAClD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;IAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACpC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAExE,oBAAoB;IACpB,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1D,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;QACxB,OAAO,CAAC,CAAC;IACX,CAAC;IAED,0BAA0B;IAC1B,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,YAAY,CAAC,CAAC;IACxF,MAAM,SAAS,GAAG,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAEpD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QACzD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,CAAC,iCAAiC,SAAS,IAAI,CAAC,CAAC;YACvD,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;QACrB,OAAO,CAAC,CAAC;IACX,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,GAAI,GAA6B,CAAC,IAAI,CAAC;QACjD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtB,MAAM,CACJ,kCAAkC,SAAS,IAAI;gBAC/C,4EAA4E,CAC7E,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,MAAM,CAAC,mCAAmC,SAAS,KAAK,GAAG,IAAI,CAAC,CAAC;QACnE,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { MultiModelConfig } from '@zhixuan92/multi-model-agent-core';
|
|
2
|
+
export declare function maybeAutoUpdateSkills(config: MultiModelConfig, stderr: (s: string) => boolean): Promise<void>;
|
|
3
|
+
/** A running server handle returned by startServe(). */
|
|
4
|
+
export interface ServeHandle {
|
|
5
|
+
/** The port the server is listening on (useful when port=0 for ephemeral). */
|
|
6
|
+
port: number;
|
|
7
|
+
/**
|
|
8
|
+
* Gracefully shut down the server.
|
|
9
|
+
* Removes any registered SIGTERM/SIGINT handlers to prevent leaks.
|
|
10
|
+
* After this resolves, the process is no longer listening and may exit safely.
|
|
11
|
+
*/
|
|
12
|
+
stop(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Start the HTTP server with the given config.
|
|
16
|
+
*
|
|
17
|
+
* Registers SIGTERM and SIGINT handlers that drain in-flight requests and
|
|
18
|
+
* exit the process cleanly. If config includes `server.limits.shutdownDrainMs`,
|
|
19
|
+
* the server will wait up to that duration for in-flight requests to finish.
|
|
20
|
+
*
|
|
21
|
+
* @param config Full MultiModelConfig (includes agents.*, defaults, diagnostics,
|
|
22
|
+
* and server block). startServer() inspects the agents.* field
|
|
23
|
+
* and enables real tool handlers when present.
|
|
24
|
+
* @param exit Process exit function — defaults to process.exit.
|
|
25
|
+
* Exposed so tests can suppress actual exits.
|
|
26
|
+
*/
|
|
27
|
+
export declare function startServe(config: MultiModelConfig, exit?: (code: number) => never): Promise<ServeHandle>;
|
|
28
|
+
//# sourceMappingURL=serve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/cli/serve.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AA2B1E,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,GAC7B,OAAO,CAAC,IAAI,CAAC,CAsCf;AAiBD,wDAAwD;AACxD,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAgBD;;;;;;;;;;;;GAYG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,gBAAgB,EACxB,IAAI,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAkC,GACzD,OAAO,CAAC,WAAW,CAAC,CA2QtB"}
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* serve.ts — starts the HTTP server and manages its signal lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* This module owns the complete serve lifecycle: starting the HTTP server,
|
|
5
|
+
* registering SIGTERM/SIGINT handlers, draining in-flight requests, and
|
|
6
|
+
* cleanly exiting the process. The CLI entry point (cli/index.ts) delegates
|
|
7
|
+
* to this module and does not manage signals directly.
|
|
8
|
+
*
|
|
9
|
+
* Usage (library):
|
|
10
|
+
* const handle = await startServe(config);
|
|
11
|
+
* // server is running on handle.port
|
|
12
|
+
* await handle.stop(); // graceful shutdown; no process.exit
|
|
13
|
+
*
|
|
14
|
+
* Usage (CLI):
|
|
15
|
+
* mmagent serve [--config <path>]
|
|
16
|
+
* // this module owns signal handling and process.exit
|
|
17
|
+
*/
|
|
18
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
19
|
+
import * as path from 'node:path';
|
|
20
|
+
import * as fs from 'node:fs';
|
|
21
|
+
import * as os from 'node:os';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
23
|
+
import { collectInlineApiKeyOffenders, loadAuthToken } from '@zhixuan92/multi-model-agent-core';
|
|
24
|
+
import { startServer } from '../http/server.js';
|
|
25
|
+
import { setDraining } from '../http/request-pipeline.js';
|
|
26
|
+
import { createRecorder } from '../telemetry/recorder.js';
|
|
27
|
+
import { Flusher } from '../telemetry/flusher.js';
|
|
28
|
+
import { Queue } from '../telemetry/queue.js';
|
|
29
|
+
import { runSyncSkills } from './sync-skills.js';
|
|
30
|
+
import { listEntries, FutureManifestError } from '../skill-install/manifest.js';
|
|
31
|
+
import { readSkillContent, SUPPORTED_SKILLS } from '../skill-install/discover.js';
|
|
32
|
+
import { findMissingSkills } from '../skill-install/skill-installer-common.js';
|
|
33
|
+
import matter from 'gray-matter';
|
|
34
|
+
function isSkillBehind(entryName, entrySkillVersion) {
|
|
35
|
+
const src = readSkillContent(entryName);
|
|
36
|
+
if (src === null)
|
|
37
|
+
return false; // skill removed from bundle — sync-skills will drop it
|
|
38
|
+
try {
|
|
39
|
+
const parsed = matter(src);
|
|
40
|
+
const v = parsed.data['version'];
|
|
41
|
+
return typeof v === 'string' && v !== entrySkillVersion;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export async function maybeAutoUpdateSkills(config, stderr) {
|
|
48
|
+
let entries;
|
|
49
|
+
try {
|
|
50
|
+
entries = listEntries();
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
if (err instanceof FutureManifestError) {
|
|
54
|
+
stderr(`[mmagent] warning: ${err.message}; skipping skill auto-sync\n`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
return; // best-effort — never let manifest IO issues block serve
|
|
58
|
+
}
|
|
59
|
+
const behind = entries.filter((e) => isSkillBehind(e.name, e.skillVersion));
|
|
60
|
+
const missing = findMissingSkills(entries, SUPPORTED_SKILLS);
|
|
61
|
+
if (behind.length === 0 && missing.length === 0)
|
|
62
|
+
return;
|
|
63
|
+
if (!config.server.autoUpdateSkills) {
|
|
64
|
+
const drift = [];
|
|
65
|
+
if (behind.length > 0)
|
|
66
|
+
drift.push(`${behind.length} out of date (${behind.map((e) => e.name).join(', ')})`);
|
|
67
|
+
if (missing.length > 0)
|
|
68
|
+
drift.push(`${missing.length} new (${missing.map((m) => m.name).join(', ')})`);
|
|
69
|
+
stderr(`[mmagent] skill drift: ${drift.join('; ')}. ` +
|
|
70
|
+
`Run 'mmagent sync-skills' to reconcile (or set server.autoUpdateSkills=true in config).\n`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const deadlineMs = 5000;
|
|
74
|
+
try {
|
|
75
|
+
await Promise.race([
|
|
76
|
+
runSyncSkills({ silent: true, bestEffort: true, ifExists: true }),
|
|
77
|
+
new Promise((resolve) => setTimeout(() => resolve(), deadlineMs)),
|
|
78
|
+
]);
|
|
79
|
+
if (behind.length > 0)
|
|
80
|
+
process.stdout.write(`[mmagent] auto-synced ${behind.length} updated skill(s)\n`);
|
|
81
|
+
if (missing.length > 0)
|
|
82
|
+
process.stdout.write(`[mmagent] auto-synced ${missing.length} new skill(s): ${missing.map((m) => m.name).join(', ')}\n`);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// bestEffort swallows inside; extra safety here.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function readServerVersion() {
|
|
89
|
+
try {
|
|
90
|
+
const thisDir = path.dirname(fileURLToPath(import.meta.url));
|
|
91
|
+
const pkgPath = path.join(thisDir, '..', '..', 'package.json');
|
|
92
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
93
|
+
return pkg.version ?? '0.0.0';
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return '0.0.0';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function envVarHint(agentName) {
|
|
100
|
+
return `${agentName.toUpperCase().replace(/[^A-Z0-9]/g, '_')}_API_KEY`;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Shared signal-state used to deduplicate shutdown if two signals arrive
|
|
104
|
+
* before stop() resolves.
|
|
105
|
+
*/
|
|
106
|
+
let stopInFlight = false;
|
|
107
|
+
// Stored so they can be removed when stop() is called programmatically
|
|
108
|
+
let onSigterm;
|
|
109
|
+
let onSigint;
|
|
110
|
+
let onStdoutErrorRef;
|
|
111
|
+
let onStderrErrorRef;
|
|
112
|
+
let onUncaughtRef;
|
|
113
|
+
let onUnhandledRejectionRef;
|
|
114
|
+
/**
|
|
115
|
+
* Start the HTTP server with the given config.
|
|
116
|
+
*
|
|
117
|
+
* Registers SIGTERM and SIGINT handlers that drain in-flight requests and
|
|
118
|
+
* exit the process cleanly. If config includes `server.limits.shutdownDrainMs`,
|
|
119
|
+
* the server will wait up to that duration for in-flight requests to finish.
|
|
120
|
+
*
|
|
121
|
+
* @param config Full MultiModelConfig (includes agents.*, defaults, diagnostics,
|
|
122
|
+
* and server block). startServer() inspects the agents.* field
|
|
123
|
+
* and enables real tool handlers when present.
|
|
124
|
+
* @param exit Process exit function — defaults to process.exit.
|
|
125
|
+
* Exposed so tests can suppress actual exits.
|
|
126
|
+
*/
|
|
127
|
+
export async function startServe(config, exit = process.exit.bind(process)) {
|
|
128
|
+
const stderr = process.stderr.write.bind(process.stderr);
|
|
129
|
+
// Auto-update installed skills before bind (bounded 5s; never blocks indefinitely).
|
|
130
|
+
await maybeAutoUpdateSkills(config, stderr);
|
|
131
|
+
// Drift check — warn if installed skills don't match the canonical manifest.
|
|
132
|
+
try {
|
|
133
|
+
const { makeSkillManifestSync } = await import('../skill-install/skill-manifest-sync.js');
|
|
134
|
+
const { discoverPerClientInstallDirs } = await import('../skill-install/discover.js');
|
|
135
|
+
const sync = makeSkillManifestSync(discoverPerClientInstallDirs());
|
|
136
|
+
const drift = sync.driftReport();
|
|
137
|
+
if (drift.length > 0) {
|
|
138
|
+
const summary = drift.map(d => `${d.client}/${d.skill}=${d.issue}`).join(', ');
|
|
139
|
+
stderr(`[mmagent] WARN: skill manifest drift detected: ${summary}. Re-run 'mmagent sync-skills' to reconcile.\n`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// best-effort — never let drift check block serve
|
|
144
|
+
}
|
|
145
|
+
// Create the telemetry recorder BEFORE startServer. The server's bus
|
|
146
|
+
// subscriber (TelemetryUploader) calls getRecorder() during startServer →
|
|
147
|
+
// if recorder is null at that moment, the uploader is wired with
|
|
148
|
+
// recorder=null and silently drops every event for the daemon's lifetime.
|
|
149
|
+
const homeDir = path.join(os.homedir(), '.multi-model');
|
|
150
|
+
const mmagentVersion = readServerVersion();
|
|
151
|
+
createRecorder({ homeDir, mmagentVersion });
|
|
152
|
+
// Pass the full MultiModelConfig (not just the server block) so
|
|
153
|
+
// registerToolHandlers sees `agents` and registers real tool endpoints.
|
|
154
|
+
// Stripping to { server } here caused a 3.1.0 regression where tool
|
|
155
|
+
// endpoints returned 503 'no_agent_config' even when agents were set.
|
|
156
|
+
const running = await startServer(config);
|
|
157
|
+
// ── stdout/stderr error + uncaught/unhandled rejection guards ────────
|
|
158
|
+
const logShutdown = (_cause) => {
|
|
159
|
+
// Option A: no diagnostics surface today. Cause name routed via stderr only.
|
|
160
|
+
// Option B (follow-up): wire running.diagnostics?.shutdown(_cause) here.
|
|
161
|
+
};
|
|
162
|
+
const onStdoutError = (err) => {
|
|
163
|
+
if (err.code === 'EPIPE') {
|
|
164
|
+
logShutdown('stdout_epipe');
|
|
165
|
+
exit(0);
|
|
166
|
+
}
|
|
167
|
+
logShutdown('stdout_other_error');
|
|
168
|
+
try {
|
|
169
|
+
process.stderr.write(`[mmagent] stdout error: ${err.message}\n`);
|
|
170
|
+
}
|
|
171
|
+
catch { /* stderr may also be dead */ }
|
|
172
|
+
exit(1);
|
|
173
|
+
};
|
|
174
|
+
const onStderrError = (err) => {
|
|
175
|
+
if (err.code === 'EPIPE') {
|
|
176
|
+
logShutdown('stdout_epipe');
|
|
177
|
+
exit(0);
|
|
178
|
+
}
|
|
179
|
+
logShutdown('stdout_other_error');
|
|
180
|
+
exit(1);
|
|
181
|
+
};
|
|
182
|
+
const onUncaught = (err) => {
|
|
183
|
+
const errno = err?.code;
|
|
184
|
+
if (errno === 'EPIPE') {
|
|
185
|
+
logShutdown('stdout_epipe');
|
|
186
|
+
exit(0);
|
|
187
|
+
}
|
|
188
|
+
logShutdown('uncaughtException');
|
|
189
|
+
try {
|
|
190
|
+
const msg = err instanceof Error ? (err.stack ?? err.message) : String(err);
|
|
191
|
+
process.stderr.write(`[mmagent] uncaught exception: ${msg}\n`);
|
|
192
|
+
}
|
|
193
|
+
catch { /* best-effort */ }
|
|
194
|
+
exit(1);
|
|
195
|
+
};
|
|
196
|
+
const onUnhandledRejection = (reason) => {
|
|
197
|
+
const errno = reason?.code;
|
|
198
|
+
if (errno === 'EPIPE') {
|
|
199
|
+
logShutdown('stdout_epipe');
|
|
200
|
+
exit(0);
|
|
201
|
+
}
|
|
202
|
+
logShutdown('unhandledRejection');
|
|
203
|
+
try {
|
|
204
|
+
const msg = reason instanceof Error ? (reason.stack ?? reason.message) : String(reason);
|
|
205
|
+
process.stderr.write(`[mmagent] unhandled rejection: ${msg}\n`);
|
|
206
|
+
}
|
|
207
|
+
catch { /* best-effort */ }
|
|
208
|
+
exit(1);
|
|
209
|
+
};
|
|
210
|
+
process.stdout.on('error', onStdoutError);
|
|
211
|
+
process.stderr.on('error', onStderrError);
|
|
212
|
+
process.on('uncaughtException', onUncaught);
|
|
213
|
+
process.on('unhandledRejection', onUnhandledRejection);
|
|
214
|
+
onStdoutErrorRef = onStdoutError;
|
|
215
|
+
onStderrErrorRef = onStderrError;
|
|
216
|
+
onUncaughtRef = onUncaught;
|
|
217
|
+
onUnhandledRejectionRef = onUnhandledRejection;
|
|
218
|
+
// Recorder was created above (BEFORE startServer). homeDir + mmagentVersion
|
|
219
|
+
// are computed there and reused here for the version-pin file + Flusher.
|
|
220
|
+
const lastVersionPath = path.join(homeDir, 'last-version');
|
|
221
|
+
let lastVersion = null;
|
|
222
|
+
try {
|
|
223
|
+
lastVersion = fs.readFileSync(lastVersionPath, 'utf8').trim();
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
// first run — no last-version file yet
|
|
227
|
+
}
|
|
228
|
+
if (lastVersion !== mmagentVersion) {
|
|
229
|
+
try {
|
|
230
|
+
fs.mkdirSync(homeDir, { recursive: true });
|
|
231
|
+
fs.writeFileSync(lastVersionPath, mmagentVersion + '\n', { mode: 0o600 });
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
stderr(`[mmagent] warning: failed to write last-version at ${lastVersionPath}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Telemetry uploader. Default endpoint ships to the project's hosted
|
|
238
|
+
// dashboard. MMAGENT_TELEMETRY_ENDPOINT overrides for self-hosted backends;
|
|
239
|
+
// setting it to an empty string disables shipping entirely (events stay in
|
|
240
|
+
// ~/.multi-model/telemetry-queue.ndjson). The real off-switch for telemetry
|
|
241
|
+
// is the consent flag (MMAGENT_TELEMETRY=0 / config.telemetry.enabled =
|
|
242
|
+
// false) — when consent is off the recorder enqueues nothing, so the
|
|
243
|
+
// flusher's tick is a no-op even with the default endpoint set.
|
|
244
|
+
const DEFAULT_TELEMETRY_ENDPOINT = 'https://mma-telemetry-frontend.x1.lucazhang.work/v1/events';
|
|
245
|
+
const envEndpoint = process.env.MMAGENT_TELEMETRY_ENDPOINT;
|
|
246
|
+
const telemetryEndpoint = envEndpoint === undefined
|
|
247
|
+
? DEFAULT_TELEMETRY_ENDPOINT
|
|
248
|
+
: envEndpoint.trim();
|
|
249
|
+
let flusher = null;
|
|
250
|
+
if (telemetryEndpoint) {
|
|
251
|
+
flusher = new Flusher({
|
|
252
|
+
queue: new Queue(homeDir),
|
|
253
|
+
dir: homeDir,
|
|
254
|
+
endpoint: telemetryEndpoint,
|
|
255
|
+
});
|
|
256
|
+
flusher.start();
|
|
257
|
+
}
|
|
258
|
+
// Fire once at serve startup. Lives here (not in loadConfigFromFile) so
|
|
259
|
+
// print-token / info / status don't re-emit the same warning repeatedly.
|
|
260
|
+
const inlineOffenders = collectInlineApiKeyOffenders(config);
|
|
261
|
+
if (inlineOffenders.length > 0) {
|
|
262
|
+
const firstHint = envVarHint(inlineOffenders[0]);
|
|
263
|
+
stderr(`[mmagent] WARNING: inline apiKey in config for agent(s): ${inlineOffenders.join(', ')}.\n` +
|
|
264
|
+
` Fix:\n` +
|
|
265
|
+
` export ${firstHint}='<your-key>'\n` +
|
|
266
|
+
` # then in config.json, replace\n` +
|
|
267
|
+
` # "apiKey": "..."\n` +
|
|
268
|
+
` # with\n` +
|
|
269
|
+
` # "apiKeyEnv": "${firstHint}"\n`);
|
|
270
|
+
}
|
|
271
|
+
const cleanupSignal = (sig) => {
|
|
272
|
+
if (stopInFlight)
|
|
273
|
+
return;
|
|
274
|
+
stopInFlight = true;
|
|
275
|
+
stderr(`[mmagent] received ${sig}, shutting down gracefully\u2026\n`);
|
|
276
|
+
// 1) Refuse new dispatches immediately so they don't compound the drain.
|
|
277
|
+
setDraining(true);
|
|
278
|
+
// 2) Walk BatchRegistry: close every in-flight task's sessions in parallel
|
|
279
|
+
// with a 5-second wall clock. After the grace window, SIGKILL any
|
|
280
|
+
// subprocess still alive whose session exposes a pid. Without this,
|
|
281
|
+
// SIGTERM-killed daemons leaked codex children as orphans (see 2026-05-16
|
|
282
|
+
// OOM post-mortem).
|
|
283
|
+
const SHUTDOWN_GRACE_MS = 5000;
|
|
284
|
+
const inflight = running.batchRegistry?.allInFlight?.() ?? [];
|
|
285
|
+
const closeAll = Promise.allSettled(inflight.flatMap((entry) => {
|
|
286
|
+
const ctxs = entry.executionContexts ? Array.from(entry.executionContexts.values()) : [];
|
|
287
|
+
return ctxs.map(async (ec) => {
|
|
288
|
+
try {
|
|
289
|
+
await ec.closeSessions();
|
|
290
|
+
}
|
|
291
|
+
catch { /* swallow */ }
|
|
292
|
+
});
|
|
293
|
+
}));
|
|
294
|
+
const drainSessions = Promise.race([
|
|
295
|
+
closeAll,
|
|
296
|
+
new Promise((resolve) => setTimeout(resolve, SHUTDOWN_GRACE_MS).unref()),
|
|
297
|
+
]).then(() => {
|
|
298
|
+
// Belt-and-suspenders: SIGKILL anything still alive whose pid we know.
|
|
299
|
+
for (const entry of inflight) {
|
|
300
|
+
const ctxs = entry.executionContexts ? Array.from(entry.executionContexts.values()) : [];
|
|
301
|
+
for (const ec of ctxs) {
|
|
302
|
+
for (const pid of ec.getActivePids?.() ?? []) {
|
|
303
|
+
try {
|
|
304
|
+
process.kill(pid, 'SIGKILL');
|
|
305
|
+
}
|
|
306
|
+
catch { /* already dead */ }
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
const drainTelemetry = flusher ? flusher.drain() : Promise.resolve();
|
|
312
|
+
drainSessions
|
|
313
|
+
.catch(() => { })
|
|
314
|
+
.then(() => drainTelemetry)
|
|
315
|
+
.catch(() => { })
|
|
316
|
+
.then(() => running.stop())
|
|
317
|
+
.then(() => exit(0))
|
|
318
|
+
.catch((err) => {
|
|
319
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
320
|
+
stderr(`[mmagent] shutdown failed: ${msg}\n`);
|
|
321
|
+
exit(1);
|
|
322
|
+
});
|
|
323
|
+
};
|
|
324
|
+
// Register handlers using named references so they can be removed correctly.
|
|
325
|
+
// Using anonymous wrappers (e.g. `process.once('SIGTERM', () => fn(sig))`)
|
|
326
|
+
// would make process.off(sig, fn) unable to find and remove the listener.
|
|
327
|
+
onSigterm = () => cleanupSignal('SIGTERM');
|
|
328
|
+
onSigint = () => cleanupSignal('SIGINT');
|
|
329
|
+
process.once('SIGTERM', onSigterm);
|
|
330
|
+
process.once('SIGINT', onSigint);
|
|
331
|
+
// Print the actual bound address so operators see what the kernel assigned
|
|
332
|
+
// (useful when port=0 selects an ephemeral port).
|
|
333
|
+
const host = running.serverAddress ?? config.server.bind;
|
|
334
|
+
// Emit a single structured startup line before the "listening" line.
|
|
335
|
+
// Fingerprint the auth token (first 8 hex of sha256) so operators can verify
|
|
336
|
+
// the running instance matches what their clients are using, without ever
|
|
337
|
+
// revealing the token. bootId discriminates successive startups from the same pid.
|
|
338
|
+
try {
|
|
339
|
+
const token = loadAuthToken({ tokenFile: config.server.auth.tokenFile });
|
|
340
|
+
const fp = createHash('sha256').update(token).digest('hex').slice(0, 8);
|
|
341
|
+
const bootId = randomUUID();
|
|
342
|
+
const version = readServerVersion();
|
|
343
|
+
process.stdout.write(`[mmagent] started | version=${version} | bind=${host}:${running.port} | pid=${process.pid} | token=${fp} | boot=${bootId}\n`);
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
// Token load shouldn't fail here (startServer already validated it), but
|
|
347
|
+
// if it does, skip the startup line rather than crash the server.
|
|
348
|
+
}
|
|
349
|
+
// Per-tier model lines so operators can see which provider is wired to
|
|
350
|
+
// each agent slot. The complex tier handles read-only sub-workers + most
|
|
351
|
+
// implementer work; the standard tier handles annotator/reviewer + the
|
|
352
|
+
// explore route's internal half. When a tier is unconfigured, log it as
|
|
353
|
+
// "(not configured)" so a misconfigured slot is visible at boot time.
|
|
354
|
+
const fmtTier = (slot) => {
|
|
355
|
+
const cfg = config.agents[slot];
|
|
356
|
+
if (!cfg || !cfg.model)
|
|
357
|
+
return '(not configured)';
|
|
358
|
+
return `${cfg.model} [${cfg.type ?? 'unknown'}]`;
|
|
359
|
+
};
|
|
360
|
+
process.stdout.write(`[mmagent] tiers | complex=${fmtTier('complex')} | standard=${fmtTier('standard')}\n`);
|
|
361
|
+
// A4a.4 (4.2.2+): warn when stale Claude Code project siblings exist
|
|
362
|
+
// under /tmp/claude/G--*. These come from prior Claude Code test runs
|
|
363
|
+
// and confuse worker cwd resolution if a caller passes one as ?cwd=.
|
|
364
|
+
// The validator already rejects them at request time (A4a.1); this
|
|
365
|
+
// startup scan surfaces the contamination so operators clean it up.
|
|
366
|
+
// Pure log behavior — does NOT block startup.
|
|
367
|
+
for (const root of ['/tmp/claude', '/private/tmp/claude']) {
|
|
368
|
+
try {
|
|
369
|
+
if (!fs.existsSync(root))
|
|
370
|
+
continue;
|
|
371
|
+
const stale = fs.readdirSync(root).filter(e => e.startsWith('G--'));
|
|
372
|
+
if (stale.length > 0) {
|
|
373
|
+
process.stdout.write(`[mmagent] WARNING: ${stale.length} stale Claude Code project sibling(s) under ${root}/G--*. ` +
|
|
374
|
+
`These can confuse cwd resolution; clean up with: rm -rf ${root}/G--*\n`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
catch { /* swallow — log-only */ }
|
|
378
|
+
}
|
|
379
|
+
process.stdout.write(`[mmagent] listening on ${host}:${running.port}\n`);
|
|
380
|
+
return {
|
|
381
|
+
port: running.port,
|
|
382
|
+
stop: async () => {
|
|
383
|
+
// Clean up signal listeners to prevent leaks when stop() is called
|
|
384
|
+
// programmatically (i.e. not via a signal).
|
|
385
|
+
if (onSigterm)
|
|
386
|
+
process.off('SIGTERM', onSigterm);
|
|
387
|
+
if (onSigint)
|
|
388
|
+
process.off('SIGINT', onSigint);
|
|
389
|
+
if (onStdoutErrorRef)
|
|
390
|
+
process.stdout.off('error', onStdoutErrorRef);
|
|
391
|
+
if (onStderrErrorRef)
|
|
392
|
+
process.stderr.off('error', onStderrErrorRef);
|
|
393
|
+
if (onUncaughtRef)
|
|
394
|
+
process.off('uncaughtException', onUncaughtRef);
|
|
395
|
+
if (onUnhandledRejectionRef)
|
|
396
|
+
process.off('unhandledRejection', onUnhandledRejectionRef);
|
|
397
|
+
onStdoutErrorRef = onStderrErrorRef = onUncaughtRef = onUnhandledRejectionRef = undefined;
|
|
398
|
+
if (flusher) {
|
|
399
|
+
await flusher.drain().catch(() => { });
|
|
400
|
+
}
|
|
401
|
+
await running.stop();
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
//# sourceMappingURL=serve.js.map
|