negotium 0.1.7 → 0.1.9
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 +26 -2
- package/dist/canonical-mcp-bridge.js +14 -0
- package/dist/canonical-mcp-bridge.js.map +9 -0
- package/dist/chunk-10ct8814.js +429 -0
- package/dist/chunk-10ct8814.js.map +11 -0
- package/dist/chunk-18fzftjf.js +214 -0
- package/dist/chunk-18fzftjf.js.map +10 -0
- package/dist/chunk-1k7t7bnx.js +97 -0
- package/dist/chunk-1k7t7bnx.js.map +10 -0
- package/dist/chunk-1msje2zf.js +23 -0
- package/dist/chunk-1msje2zf.js.map +10 -0
- package/dist/chunk-2dgkah1z.js +706 -0
- package/dist/chunk-2dgkah1z.js.map +14 -0
- package/dist/chunk-3t4dtm26.js +61 -0
- package/dist/chunk-3t4dtm26.js.map +10 -0
- package/dist/chunk-3vn2vnj1.js +51 -0
- package/dist/chunk-3vn2vnj1.js.map +10 -0
- package/dist/chunk-6hknphtc.js +2054 -0
- package/dist/chunk-6hknphtc.js.map +29 -0
- package/dist/chunk-6tfzpcvk.js +2149 -0
- package/dist/chunk-6tfzpcvk.js.map +15 -0
- package/dist/chunk-8g0c4as0.js +23 -0
- package/dist/chunk-8g0c4as0.js.map +10 -0
- package/dist/chunk-9spx3h9n.js +279 -0
- package/dist/chunk-9spx3h9n.js.map +10 -0
- package/dist/chunk-c500xe9z.js +33 -0
- package/dist/chunk-c500xe9z.js.map +10 -0
- package/dist/chunk-e9b2xpff.js +147 -0
- package/dist/chunk-e9b2xpff.js.map +10 -0
- package/dist/chunk-edb07sjp.js +567 -0
- package/dist/chunk-edb07sjp.js.map +14 -0
- package/dist/chunk-eexqsee7.js +368 -0
- package/dist/chunk-eexqsee7.js.map +11 -0
- package/dist/chunk-efyxk97v.js +500 -0
- package/dist/chunk-efyxk97v.js.map +13 -0
- package/dist/chunk-epqxwkvh.js +3534 -0
- package/dist/chunk-epqxwkvh.js.map +20 -0
- package/dist/chunk-f1pzh3xh.js +115 -0
- package/dist/chunk-f1pzh3xh.js.map +10 -0
- package/dist/chunk-f5xgprg1.js +59 -0
- package/dist/chunk-f5xgprg1.js.map +10 -0
- package/dist/chunk-f97qybwk.js +584 -0
- package/dist/chunk-f97qybwk.js.map +11 -0
- package/dist/chunk-fvc7ncxd.js +252 -0
- package/dist/chunk-fvc7ncxd.js.map +10 -0
- package/dist/chunk-g7pvfj5e.js +574 -0
- package/dist/chunk-g7pvfj5e.js.map +14 -0
- package/dist/chunk-h8vmb9gf.js +315 -0
- package/dist/chunk-h8vmb9gf.js.map +10 -0
- package/dist/chunk-hgw7n8cm.js +43 -0
- package/dist/chunk-hgw7n8cm.js.map +10 -0
- package/dist/chunk-hsq7j9jk.js +126 -0
- package/dist/chunk-hsq7j9jk.js.map +10 -0
- package/dist/chunk-hwy3n31w.js +21 -0
- package/dist/chunk-hwy3n31w.js.map +9 -0
- package/dist/chunk-j0kjscb7.js +53 -0
- package/dist/chunk-j0kjscb7.js.map +10 -0
- package/dist/chunk-jzz91jwh.js +70 -0
- package/dist/chunk-jzz91jwh.js.map +10 -0
- package/dist/chunk-k9z47mab.js +247 -0
- package/dist/chunk-k9z47mab.js.map +12 -0
- package/dist/chunk-kbm35t7w.js +1331 -0
- package/dist/chunk-kbm35t7w.js.map +16 -0
- package/dist/chunk-m4veth0c.js +1649 -0
- package/dist/chunk-m4veth0c.js.map +17 -0
- package/dist/chunk-mhty3nna.js +150 -0
- package/dist/chunk-mhty3nna.js.map +11 -0
- package/dist/chunk-mmgh670x.js +248 -0
- package/dist/chunk-mmgh670x.js.map +11 -0
- package/dist/chunk-n0kk5xbq.js +2358 -0
- package/dist/chunk-n0kk5xbq.js.map +26 -0
- package/dist/chunk-nmv41qx9.js +2847 -0
- package/dist/chunk-nmv41qx9.js.map +23 -0
- package/dist/chunk-nqdp52aj.js +113 -0
- package/dist/chunk-nqdp52aj.js.map +12 -0
- package/dist/chunk-ns2m25dz.js +139 -0
- package/dist/chunk-ns2m25dz.js.map +10 -0
- package/dist/chunk-pkdmypdd.js +147 -0
- package/dist/chunk-pkdmypdd.js.map +11 -0
- package/dist/chunk-rmyfmrrr.js +1634 -0
- package/dist/chunk-rmyfmrrr.js.map +15 -0
- package/dist/chunk-sa874j5c.js +9 -0
- package/dist/chunk-sa874j5c.js.map +10 -0
- package/dist/chunk-sgsz2fpq.js +289 -0
- package/dist/chunk-sgsz2fpq.js.map +10 -0
- package/dist/chunk-sn51wnf1.js +3020 -0
- package/dist/chunk-sn51wnf1.js.map +32 -0
- package/dist/chunk-sqhr1a7e.js +587 -0
- package/dist/chunk-sqhr1a7e.js.map +11 -0
- package/dist/chunk-tmj5r2zv.js +2933 -0
- package/dist/chunk-tmj5r2zv.js.map +20 -0
- package/dist/chunk-vefj3880.js +56 -0
- package/dist/chunk-vefj3880.js.map +10 -0
- package/dist/chunk-vp2db42d.js +121 -0
- package/dist/chunk-vp2db42d.js.map +10 -0
- package/dist/chunk-wry79q55.js +108 -0
- package/dist/chunk-wry79q55.js.map +10 -0
- package/dist/chunk-x1arr7ze.js +63 -0
- package/dist/chunk-x1arr7ze.js.map +10 -0
- package/dist/chunk-z2xy5n2q.js +103 -0
- package/dist/chunk-z2xy5n2q.js.map +10 -0
- package/dist/hosted-agent.js +53 -0
- package/dist/hosted-agent.js.map +10 -0
- package/dist/main.js +132 -20
- package/dist/main.js.map +4 -4
- package/dist/runtime/scripts/faster-whisper-wrapper.py +87 -0
- package/dist/runtime/scripts/mcp-patchright-http.mjs +349 -0
- package/dist/runtime/src/agents/api-topic-agent-switch.ts +178 -0
- package/dist/runtime/src/agents/archiver.ts +396 -0
- package/dist/runtime/src/agents/auth-check.ts +64 -0
- package/dist/runtime/src/agents/claude-provider.ts +737 -0
- package/dist/runtime/src/agents/claude-registry.ts +115 -0
- package/dist/runtime/src/agents/codex-native-multi-agent.ts +177 -0
- package/dist/runtime/src/agents/codex-provider.ts +785 -0
- package/dist/runtime/src/agents/codex-registry.ts +115 -0
- package/dist/runtime/src/agents/codex-tree-kill.ts +365 -0
- package/dist/runtime/src/agents/contracts.ts +69 -0
- package/dist/runtime/src/agents/deep-map.ts +13 -0
- package/dist/runtime/src/agents/execution-host.ts +91 -0
- package/dist/runtime/src/agents/fixtures/claude-attachments.jsonl +2 -0
- package/dist/runtime/src/agents/fixtures/codex-shell.jsonl +5 -0
- package/dist/runtime/src/agents/fork.ts +65 -0
- package/dist/runtime/src/agents/hosted-agent.ts +49 -0
- package/dist/runtime/src/agents/idle-archiver.ts +128 -0
- package/dist/runtime/src/agents/index.ts +205 -0
- package/dist/runtime/src/agents/maestro-provider.ts +206 -0
- package/dist/runtime/src/agents/maestro-registry.ts +31 -0
- package/dist/runtime/src/agents/mcp-tools/ask-user.ts +280 -0
- package/dist/runtime/src/agents/mcp-tools/common.ts +21 -0
- package/dist/runtime/src/agents/mcp-tools/self-config.ts +220 -0
- package/dist/runtime/src/agents/mcp-tools/spawn-subagent.ts +525 -0
- package/dist/runtime/src/agents/mcp-tools/visuals.ts +88 -0
- package/dist/runtime/src/agents/model-catalog.ts +151 -0
- package/dist/runtime/src/agents/registry.ts +24 -0
- package/dist/runtime/src/agents/rollout/claude.ts +331 -0
- package/dist/runtime/src/agents/rollout/codex.ts +569 -0
- package/dist/runtime/src/agents/rollout/shared.ts +205 -0
- package/dist/runtime/src/agents/self-config-core.ts +415 -0
- package/dist/runtime/src/agents/task-events.ts +44 -0
- package/dist/runtime/src/agents/tool-format.ts +352 -0
- package/dist/runtime/src/agents/topic-agent-switch.ts +193 -0
- package/dist/runtime/src/agents/topic-cleanup.ts +231 -0
- package/dist/runtime/src/agents/vault-tool-policy.ts +33 -0
- package/dist/runtime/src/application/execute-external-user-turn.ts +23 -0
- package/dist/runtime/src/application/submit-user-message.ts +50 -0
- package/dist/runtime/src/application/switch-topic-effort.ts +48 -0
- package/dist/runtime/src/application/switch-topic-model.ts +79 -0
- package/dist/runtime/src/application/topic-service.ts +139 -0
- package/dist/runtime/src/application/vault-command.ts +170 -0
- package/dist/runtime/src/bus.ts +319 -0
- package/dist/runtime/src/index.ts +333 -0
- package/dist/runtime/src/mcp/agent-health-server.ts +309 -0
- package/dist/runtime/src/mcp/background-bash-server.ts +346 -0
- package/dist/runtime/src/mcp/canonical-bridge-config.ts +84 -0
- package/dist/runtime/src/mcp/canonical-proxy-server.ts +171 -0
- package/dist/runtime/src/mcp/dm-ipc.ts +60 -0
- package/dist/runtime/src/mcp/mcp-helpers.ts +40 -0
- package/dist/runtime/src/mcp/paddleocr-server.ts +105 -0
- package/dist/runtime/src/mcp/peer-bridge.ts +114 -0
- package/dist/runtime/src/mcp/runtime-spec.ts +137 -0
- package/dist/runtime/src/mcp/session-comm/bridge-ipc-config.ts +27 -0
- package/dist/runtime/src/mcp/session-comm/peer-forward.ts +165 -0
- package/dist/runtime/src/mcp/session-comm/runtime.ts +115 -0
- package/dist/runtime/src/mcp/session-comm/server.ts +649 -0
- package/dist/runtime/src/mcp/session-comm/topic-config.ts +131 -0
- package/dist/runtime/src/mcp/session-comm/topics.ts +229 -0
- package/dist/runtime/src/mcp/stdio-protect.ts +84 -0
- package/dist/runtime/src/mcp/system-health-server.ts +130 -0
- package/dist/runtime/src/mcp/task-server.ts +173 -0
- package/dist/runtime/src/mcp/token-stats-server.ts +98 -0
- package/dist/runtime/src/mcp/vault-http.ts +219 -0
- package/dist/runtime/src/mcp/vault-run.ts +153 -0
- package/dist/runtime/src/mcp/vault-server.ts +104 -0
- package/dist/runtime/src/mcp/wiki-server.ts +755 -0
- package/dist/runtime/src/media/file-events.ts +33 -0
- package/dist/runtime/src/media/text-extractor.ts +312 -0
- package/dist/runtime/src/media/video.ts +92 -0
- package/dist/runtime/src/outbox/file-ops.ts +210 -0
- package/dist/runtime/src/outbox/index.ts +8 -0
- package/dist/runtime/src/outbox/utils.ts +51 -0
- package/dist/runtime/src/platform/background-bash/manager.ts +279 -0
- package/dist/runtime/src/platform/config.ts +371 -0
- package/dist/runtime/src/platform/constants.ts +18 -0
- package/dist/runtime/src/platform/debug-users.ts +14 -0
- package/dist/runtime/src/platform/delay.ts +1 -0
- package/dist/runtime/src/platform/error.ts +9 -0
- package/dist/runtime/src/platform/file-utils.ts +16 -0
- package/dist/runtime/src/platform/jsonl.ts +202 -0
- package/dist/runtime/src/platform/lifecycle.ts +174 -0
- package/dist/runtime/src/platform/logger.ts +31 -0
- package/dist/runtime/src/platform/maestro-bootstrap-env.ts +3 -0
- package/dist/runtime/src/platform/mcp-config.ts +878 -0
- package/dist/runtime/src/platform/modules.ts +145 -0
- package/dist/runtime/src/platform/node-plugins.ts +46 -0
- package/dist/runtime/src/platform/playwright/manager.ts +1233 -0
- package/dist/runtime/src/prompts/agents/wiki-archiver.md +261 -0
- package/dist/runtime/src/prompts/builders.ts +368 -0
- package/dist/runtime/src/prompts/sessions/channel-system.md +48 -0
- package/dist/runtime/src/prompts/sessions/manager-system.md +37 -0
- package/dist/runtime/src/prompts/sessions/topic-system.md +47 -0
- package/dist/runtime/src/prompts/sessions/visual-design.md +86 -0
- package/dist/runtime/src/query/active-rooms.ts +534 -0
- package/dist/runtime/src/query/control.ts +313 -0
- package/dist/runtime/src/query/index.ts +24 -0
- package/dist/runtime/src/query/session-inbox-cleanup.ts +44 -0
- package/dist/runtime/src/query/session-inbox-path.ts +42 -0
- package/dist/runtime/src/query/state.ts +94 -0
- package/dist/runtime/src/query/types.ts +83 -0
- package/dist/runtime/src/runtime/ask-callbacks.ts +153 -0
- package/dist/runtime/src/runtime/attachments.ts +151 -0
- package/dist/runtime/src/runtime/background-sessions.ts +83 -0
- package/dist/runtime/src/runtime/channel-context.ts +107 -0
- package/dist/runtime/src/runtime/errors.ts +69 -0
- package/dist/runtime/src/runtime/event-heartbeat.ts +77 -0
- package/dist/runtime/src/runtime/file-hooks.ts +81 -0
- package/dist/runtime/src/runtime/footer.ts +23 -0
- package/dist/runtime/src/runtime/inbox.ts +946 -0
- package/dist/runtime/src/runtime/tasks.ts +56 -0
- package/dist/runtime/src/runtime/topic-config.ts +29 -0
- package/dist/runtime/src/runtime/turn-runner.ts +2298 -0
- package/dist/runtime/src/runtime/usage-alert.ts +84 -0
- package/dist/runtime/src/runtime/visual-html.ts +109 -0
- package/dist/runtime/src/runtime/visual-store.ts +430 -0
- package/dist/runtime/src/runtime/visuals.ts +171 -0
- package/dist/runtime/src/security/sanitize.ts +27 -0
- package/dist/runtime/src/security/sensitive-path.ts +34 -0
- package/dist/runtime/src/storage/activity-log.ts +152 -0
- package/dist/runtime/src/storage/api-messages.ts +617 -0
- package/dist/runtime/src/storage/api-topic-brief.ts +127 -0
- package/dist/runtime/src/storage/api-topic-config.ts +192 -0
- package/dist/runtime/src/storage/api-topics.ts +844 -0
- package/dist/runtime/src/storage/app-settings.ts +44 -0
- package/dist/runtime/src/storage/browser-profiles.ts +133 -0
- package/dist/runtime/src/storage/conversations.ts +268 -0
- package/dist/runtime/src/storage/forum/agent-settings.ts +20 -0
- package/dist/runtime/src/storage/forum/index.ts +4 -0
- package/dist/runtime/src/storage/forum/repository.ts +283 -0
- package/dist/runtime/src/storage/forum/schema.ts +300 -0
- package/dist/runtime/src/storage/forum-db.ts +23 -0
- package/dist/runtime/src/storage/runtime-events.ts +130 -0
- package/dist/runtime/src/storage/runtime-leases.ts +189 -0
- package/dist/runtime/src/storage/runtime-process-leases.ts +216 -0
- package/dist/runtime/src/storage/runtime-topic-state.ts +148 -0
- package/dist/runtime/src/storage/runtime-turn-requests.ts +301 -0
- package/dist/runtime/src/storage/self-schedules.ts +298 -0
- package/dist/runtime/src/storage/session-asks.ts +347 -0
- package/dist/runtime/src/storage/sqlite.ts +129 -0
- package/dist/runtime/src/storage/tasks.ts +179 -0
- package/dist/runtime/src/storage/token-stats.ts +144 -0
- package/dist/runtime/src/storage/topic-archive-state.ts +59 -0
- package/dist/runtime/src/storage/topic-archive.ts +74 -0
- package/dist/runtime/src/storage/topic-settings.ts +67 -0
- package/dist/runtime/src/storage/topic-transcript.ts +98 -0
- package/dist/runtime/src/storage/vault-crypto.ts +65 -0
- package/dist/runtime/src/storage/vault.ts +225 -0
- package/dist/runtime/src/storage/wiki-summary-names.ts +33 -0
- package/dist/runtime/src/storage/wiki.ts +85 -0
- package/dist/runtime/src/topics/create.ts +123 -0
- package/dist/runtime/src/topics/derive.ts +398 -0
- package/dist/runtime/src/topics/lifecycle.ts +311 -0
- package/dist/runtime/src/topics/links.ts +7 -0
- package/dist/runtime/src/topics/personal-general.ts +65 -0
- package/dist/runtime/src/topics/session.ts +381 -0
- package/dist/runtime/src/types/api.ts +497 -0
- package/dist/runtime/src/types.ts +257 -0
- package/dist/runtime/src/version.ts +1 -0
- package/dist/runtime/tsconfig.json +13 -0
- package/package.json +28 -3
- package/dist/main.d.ts +0 -3
- package/dist/main.d.ts.map +0 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutations and reads scoped to the *current* topic (the one this MCP server
|
|
3
|
+
* was spawned for). Splitting these out from `topics.ts` keeps read-only,
|
|
4
|
+
* cross-topic queries separate from writes that only make sense for
|
|
5
|
+
* `currentTopic`.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { OPTIONAL_FORUM_MCP_SERVERS, REQUIRED_FORUM_MCP_SERVERS } from "#platform/mcp-config";
|
|
9
|
+
import { getApiTopicConfig, setApiTopicConfig } from "#storage/api-topic-config";
|
|
10
|
+
import { getTopic, getTopicByNameForUser } from "#storage/api-topics";
|
|
11
|
+
import {
|
|
12
|
+
currentTopic,
|
|
13
|
+
currentTopicId,
|
|
14
|
+
parseJsonField,
|
|
15
|
+
SESSIONS_DB,
|
|
16
|
+
userId,
|
|
17
|
+
withDb,
|
|
18
|
+
} from "./runtime";
|
|
19
|
+
|
|
20
|
+
export function currentApiTopicId(): string | null {
|
|
21
|
+
if (!userId) return null;
|
|
22
|
+
if (currentTopicId) {
|
|
23
|
+
try {
|
|
24
|
+
const topic = getTopic(currentTopicId);
|
|
25
|
+
if (!topic) {
|
|
26
|
+
process.stderr.write(`warn: session-comm: api topic id not found: ${currentTopicId}\n`);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
if (!topic.participants.some((p) => p.userId === userId)) {
|
|
30
|
+
process.stderr.write(
|
|
31
|
+
`warn: session-comm: user ${userId} is not a participant of topic id ${currentTopicId}\n`,
|
|
32
|
+
);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return topic.id;
|
|
36
|
+
} catch (e) {
|
|
37
|
+
process.stderr.write(`warn: session-comm: api topic id lookup failed: ${e}\n`);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (!currentTopic) return null;
|
|
42
|
+
try {
|
|
43
|
+
return getTopicByNameForUser(currentTopic, userId)?.id ?? null;
|
|
44
|
+
} catch (e) {
|
|
45
|
+
process.stderr.write(`warn: session-comm: api topic lookup failed: ${e}\n`);
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeMcpEnabled(enabled: string[] | null): string[] | undefined {
|
|
51
|
+
if (enabled === null) return undefined;
|
|
52
|
+
const requested = [...new Set(enabled.map((n) => n.trim()).filter(Boolean))];
|
|
53
|
+
const invalid = requested.filter(
|
|
54
|
+
(n) => !OPTIONAL_FORUM_MCP_SERVERS.includes(n) && !REQUIRED_FORUM_MCP_SERVERS.includes(n),
|
|
55
|
+
);
|
|
56
|
+
if (invalid.length > 0) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Unknown MCP server(s): ${invalid.join(", ")}. Optional servers are: ${OPTIONAL_FORUM_MCP_SERVERS.join(", ")}.`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return requested.filter((n) => OPTIONAL_FORUM_MCP_SERVERS.includes(n));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getMcpConfig(): { enabled: string[] | null; extra: Record<string, unknown> } {
|
|
65
|
+
const apiTopicId = currentApiTopicId();
|
|
66
|
+
if (apiTopicId) {
|
|
67
|
+
return { enabled: getApiTopicConfig(apiTopicId)?.mcp ?? [], extra: {} };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!existsSync(SESSIONS_DB)) return { enabled: null, extra: {} };
|
|
71
|
+
try {
|
|
72
|
+
return withDb((db) => {
|
|
73
|
+
const row = db
|
|
74
|
+
.query<{ mcp_enabled: string | null; mcp_extra: string | null }, [string, string]>(
|
|
75
|
+
"SELECT mcp_enabled, mcp_extra FROM topics WHERE user_id = ? AND name = ?",
|
|
76
|
+
)
|
|
77
|
+
.get(userId, currentTopic);
|
|
78
|
+
const enabled =
|
|
79
|
+
parseJsonField<string[] | null>(
|
|
80
|
+
row?.mcp_enabled,
|
|
81
|
+
`mcp_enabled for topic "${currentTopic}"`,
|
|
82
|
+
) ?? null;
|
|
83
|
+
const extra =
|
|
84
|
+
parseJsonField<Record<string, unknown>>(
|
|
85
|
+
row?.mcp_extra,
|
|
86
|
+
`mcp_extra for topic "${currentTopic}"`,
|
|
87
|
+
) ?? {};
|
|
88
|
+
return { enabled, extra };
|
|
89
|
+
});
|
|
90
|
+
} catch (e) {
|
|
91
|
+
process.stderr.write(`warn: session-comm: getMcpConfig DB query failed: ${e}\n`);
|
|
92
|
+
return { enabled: null, extra: {} };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function setCurrentTopicDescription(description: string) {
|
|
97
|
+
withDb(
|
|
98
|
+
(db) => {
|
|
99
|
+
db.query("UPDATE topics SET description = ? WHERE user_id = ? AND name = ?").run(
|
|
100
|
+
description,
|
|
101
|
+
userId,
|
|
102
|
+
currentTopic,
|
|
103
|
+
);
|
|
104
|
+
},
|
|
105
|
+
{ write: true },
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function setMcpConfig(enabled?: string[] | null) {
|
|
110
|
+
const apiTopicId = currentApiTopicId();
|
|
111
|
+
if (apiTopicId) {
|
|
112
|
+
if (enabled !== undefined) {
|
|
113
|
+
const existing = getApiTopicConfig(apiTopicId) ?? {};
|
|
114
|
+
setApiTopicConfig(apiTopicId, { ...existing, mcp: normalizeMcpEnabled(enabled) });
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
withDb(
|
|
120
|
+
(db) => {
|
|
121
|
+
if (enabled !== undefined) {
|
|
122
|
+
db.query("UPDATE topics SET mcp_enabled = ? WHERE user_id = ? AND name = ?").run(
|
|
123
|
+
enabled !== null ? JSON.stringify(enabled) : null,
|
|
124
|
+
userId,
|
|
125
|
+
currentTopic,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{ write: true },
|
|
130
|
+
);
|
|
131
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Topic-level data access for the session-comm MCP server.
|
|
3
|
+
*
|
|
4
|
+
* All read-only DB queries that return topic entities live here. Mutations
|
|
5
|
+
* against the *current* topic belong in `topic-config.ts`; runtime/CLI
|
|
6
|
+
* concerns belong in `runtime.ts`.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { sessionInboxPath } from "#query/session-inbox-path";
|
|
11
|
+
import { sanitizeTopicName } from "#security/sanitize";
|
|
12
|
+
// NOTE: see `runtime.ts` — import these from `@/types` directly to keep
|
|
13
|
+
// `maestro-agent-sdk` (whose `bootstrapHostPath()` prints to stdout) out of
|
|
14
|
+
// this stdio MCP server's import graph.
|
|
15
|
+
import { type AgentKind, isAgentKind, type QueryState } from "#types";
|
|
16
|
+
import {
|
|
17
|
+
currentTopic,
|
|
18
|
+
currentTopicId,
|
|
19
|
+
PLAYWRIGHT_PORTS_DIR,
|
|
20
|
+
parseJsonField,
|
|
21
|
+
SESSIONS_DB,
|
|
22
|
+
userId,
|
|
23
|
+
withDb,
|
|
24
|
+
} from "./runtime";
|
|
25
|
+
|
|
26
|
+
export type { QueryState };
|
|
27
|
+
|
|
28
|
+
// --- Topic entry types ---
|
|
29
|
+
|
|
30
|
+
export interface TopicEntry {
|
|
31
|
+
sessionId: string;
|
|
32
|
+
messageThreadId: number;
|
|
33
|
+
name: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
/** api_topics id (UUID) — delivery target for tell/ask. */
|
|
36
|
+
topicId?: string;
|
|
37
|
+
/** REST topics can be human-only. ask_session/tell_session require an AI target. */
|
|
38
|
+
agent?: AgentKind;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface McpTopicEntry {
|
|
42
|
+
sessionId: string;
|
|
43
|
+
messageThreadId: number;
|
|
44
|
+
name: string;
|
|
45
|
+
createdAt: string;
|
|
46
|
+
description?: string;
|
|
47
|
+
agent: AgentKind;
|
|
48
|
+
mcpEnabled?: string[] | null;
|
|
49
|
+
mcpExtra?: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface McpUserConfig {
|
|
53
|
+
dmSessionId?: string;
|
|
54
|
+
topics: { [name: string]: McpTopicEntry };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type ValidateTargetResult =
|
|
58
|
+
| { ok: true; target: TopicEntry }
|
|
59
|
+
| { ok: false; error: { content: Array<{ type: "text"; text: string }>; isError: true } };
|
|
60
|
+
|
|
61
|
+
// --- Queries ---
|
|
62
|
+
|
|
63
|
+
export function validateTarget(to: string): ValidateTargetResult {
|
|
64
|
+
const topics = getTopicsForUser();
|
|
65
|
+
const target = topics[to];
|
|
66
|
+
if (!target) {
|
|
67
|
+
const available = Object.entries(topics)
|
|
68
|
+
.filter(([name, topic]) => name !== currentTopic && Boolean(topic.agent))
|
|
69
|
+
.map(([name]) => name);
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
error: {
|
|
73
|
+
content: [
|
|
74
|
+
{
|
|
75
|
+
type: "text" as const,
|
|
76
|
+
text: `Error: Session "${to}" not found.\nAvailable: ${available.join(", ") || "none"}`,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
isError: true,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// Topics without an active session (sessionId === "") are still valid
|
|
84
|
+
// targets — ask_session / tell_session deliveries will wake them with a
|
|
85
|
+
// fresh session via the session-inbox worker. See `session-inbox.ts`.
|
|
86
|
+
return { ok: true, target };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getTopicsForUser(): { [name: string]: TopicEntry } {
|
|
90
|
+
if (!existsSync(SESSIONS_DB)) return {};
|
|
91
|
+
try {
|
|
92
|
+
return withDb((db) => {
|
|
93
|
+
// Otium topics live in `api_topics`; membership is normalized in
|
|
94
|
+
// `topic_members`. Expose the current user's rooms keyed by title.
|
|
95
|
+
const rows = db
|
|
96
|
+
.query<
|
|
97
|
+
{
|
|
98
|
+
id: string;
|
|
99
|
+
title: string;
|
|
100
|
+
kind: string | null;
|
|
101
|
+
agent: string | null;
|
|
102
|
+
session_id: string | null;
|
|
103
|
+
description: string | null;
|
|
104
|
+
},
|
|
105
|
+
string
|
|
106
|
+
>(
|
|
107
|
+
`SELECT t.id, t.title, t.kind, t.agent, t.session_id, t.description
|
|
108
|
+
FROM api_topics t
|
|
109
|
+
INNER JOIN topic_members m ON m.topic_id = t.id
|
|
110
|
+
WHERE m.user_id = ?`,
|
|
111
|
+
)
|
|
112
|
+
.all(userId);
|
|
113
|
+
const entries: Array<{ key: string; entry: TopicEntry }> = [];
|
|
114
|
+
const titleCounts = new Map<string, number>();
|
|
115
|
+
for (const row of rows) {
|
|
116
|
+
if (row.kind === "manager") continue;
|
|
117
|
+
if (currentTopicId && row.id === currentTopicId) continue;
|
|
118
|
+
if (row.title === currentTopic) continue;
|
|
119
|
+
const agent = isAgentKind(row.agent) ? row.agent : undefined;
|
|
120
|
+
const kind = row.kind === "agent" ? "agent" : "channel";
|
|
121
|
+
const entry = {
|
|
122
|
+
sessionId: row.session_id ?? "",
|
|
123
|
+
messageThreadId: 0,
|
|
124
|
+
name: row.title,
|
|
125
|
+
topicId: row.id,
|
|
126
|
+
...(agent && { agent }),
|
|
127
|
+
...(row.description && { description: row.description }),
|
|
128
|
+
};
|
|
129
|
+
entries.push({ key: `${kind}:${row.title}`, entry });
|
|
130
|
+
titleCounts.set(row.title, (titleCounts.get(row.title) ?? 0) + 1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const result: { [name: string]: TopicEntry } = {};
|
|
134
|
+
for (const { key, entry } of entries) {
|
|
135
|
+
result[key] = entry;
|
|
136
|
+
if ((titleCounts.get(entry.name) ?? 0) === 1) result[entry.name] = entry;
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
});
|
|
140
|
+
} catch (e) {
|
|
141
|
+
process.stderr.write(`warn: session-comm: failed to load topics from DB: ${e}\n`);
|
|
142
|
+
return {};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function getMcpUserConfig(): McpUserConfig | null {
|
|
147
|
+
if (!existsSync(SESSIONS_DB)) return null;
|
|
148
|
+
try {
|
|
149
|
+
return withDb((db) => {
|
|
150
|
+
const user = db
|
|
151
|
+
.query<{ dm_session_id: string | null }, string>(
|
|
152
|
+
"SELECT dm_session_id FROM users WHERE id = ?",
|
|
153
|
+
)
|
|
154
|
+
.get(userId);
|
|
155
|
+
if (!user) return null;
|
|
156
|
+
const dmSessionId = user.dm_session_id;
|
|
157
|
+
|
|
158
|
+
const topicRows = db
|
|
159
|
+
.query<
|
|
160
|
+
{
|
|
161
|
+
name: string;
|
|
162
|
+
message_thread_id: number;
|
|
163
|
+
session_id: string | null;
|
|
164
|
+
created_at: string;
|
|
165
|
+
description: string | null;
|
|
166
|
+
agent: string | null;
|
|
167
|
+
mcp_enabled: string | null;
|
|
168
|
+
mcp_extra: string | null;
|
|
169
|
+
},
|
|
170
|
+
string
|
|
171
|
+
>(
|
|
172
|
+
"SELECT name, message_thread_id, session_id, created_at, description, agent, mcp_enabled, mcp_extra FROM topics WHERE user_id = ?",
|
|
173
|
+
)
|
|
174
|
+
.all(userId);
|
|
175
|
+
|
|
176
|
+
const topics: { [name: string]: McpTopicEntry } = {};
|
|
177
|
+
for (const row of topicRows) {
|
|
178
|
+
const mcpEnabled = parseJsonField<string[] | null>(
|
|
179
|
+
row.mcp_enabled,
|
|
180
|
+
`mcp_enabled for "${row.name}"`,
|
|
181
|
+
);
|
|
182
|
+
const mcpExtra = parseJsonField<Record<string, unknown>>(
|
|
183
|
+
row.mcp_extra,
|
|
184
|
+
`mcp_extra for "${row.name}"`,
|
|
185
|
+
);
|
|
186
|
+
if (!isAgentKind(row.agent)) throw new Error(`Invalid agent in DB: ${row.agent}`);
|
|
187
|
+
const agent: AgentKind = row.agent;
|
|
188
|
+
topics[row.name] = {
|
|
189
|
+
name: row.name,
|
|
190
|
+
messageThreadId: row.message_thread_id,
|
|
191
|
+
sessionId: row.session_id ?? "",
|
|
192
|
+
createdAt: row.created_at,
|
|
193
|
+
agent,
|
|
194
|
+
...(row.description && { description: row.description }),
|
|
195
|
+
...(mcpEnabled !== undefined && { mcpEnabled }),
|
|
196
|
+
...(mcpExtra !== undefined && { mcpExtra }),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
...(dmSessionId && { dmSessionId }),
|
|
202
|
+
topics,
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
} catch (e) {
|
|
206
|
+
process.stderr.write(`warn: session-comm: getMcpUserConfig failed: ${e}\n`);
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Build the session-inbox jsonl path.
|
|
213
|
+
*/
|
|
214
|
+
export function buildInboxPath(targetTopicId: string): string {
|
|
215
|
+
return sessionInboxPath(userId, targetTopicId);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Look up the playwright SSE port for the target topic from the port file */
|
|
219
|
+
export function getPlaywrightPort(topic: string): number | null {
|
|
220
|
+
if (!userId || !topic) return null;
|
|
221
|
+
const safeTopicName = sanitizeTopicName(topic);
|
|
222
|
+
const portFile = join(PLAYWRIGHT_PORTS_DIR, `${userId}_${safeTopicName}`);
|
|
223
|
+
try {
|
|
224
|
+
const port = parseInt(readFileSync(portFile, "utf-8").trim(), 10);
|
|
225
|
+
return Number.isNaN(port) ? null : port;
|
|
226
|
+
} catch {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Side-effect module: protect the MCP JSON-RPC stdio channel from rogue
|
|
3
|
+
* stdout writes by transitively-imported dependencies.
|
|
4
|
+
*
|
|
5
|
+
* Problem
|
|
6
|
+
* -------
|
|
7
|
+
* MCP servers in stdio mode use `process.stdout` as a JSON-RPC framing
|
|
8
|
+
* channel — every line must parse as JSON. If any imported module logs to
|
|
9
|
+
* stdout (e.g. via `console.log`) during module evaluation or at any later
|
|
10
|
+
* point, the receiving client's MCP parser sees a non-JSON line and aborts:
|
|
11
|
+
*
|
|
12
|
+
* ERROR rmcp::transport::async_rw: Error reading from stream:
|
|
13
|
+
* serde error expected value at line 1 column 2
|
|
14
|
+
*
|
|
15
|
+
* The host then drops every tool exposed by that server.
|
|
16
|
+
*
|
|
17
|
+
* Status (maestro-agent-sdk ≥ 0.1.14)
|
|
18
|
+
* -----------------------------------
|
|
19
|
+
* The original motivating offender — `bootstrapHostPath()` in
|
|
20
|
+
* `maestro-agent-sdk` — is fixed at the source as of v0.1.13: its default
|
|
21
|
+
* console logger now routes every level to stderr, and v0.1.14 ships a
|
|
22
|
+
* `MAESTRO_SDK_SILENT_BOOTSTRAP=1` env to suppress the line entirely.
|
|
23
|
+
* This module sets that env defensively (before any SDK import resolves)
|
|
24
|
+
* and keeps the broad `console.*` redirect in place as a safety net for
|
|
25
|
+
* any *other* transitive dep that might log to stdout in the future. The
|
|
26
|
+
* cost is essentially zero and the failure mode is silent tool loss, so
|
|
27
|
+
* the belt-and-suspenders posture stays.
|
|
28
|
+
*
|
|
29
|
+
* Fix
|
|
30
|
+
* ---
|
|
31
|
+
* Re-route `console.log` / `console.info` (and `process.stdout.write` for
|
|
32
|
+
* a final belt-and-suspenders catch) to stderr. The real MCP transport
|
|
33
|
+
* still uses `process.stdout.write` directly via the @modelcontextprotocol
|
|
34
|
+
* SDK, so JSON-RPC frames are unaffected — only stray writes are diverted.
|
|
35
|
+
*
|
|
36
|
+
* Wait — doesn't redirecting `process.stdout.write` break the transport?
|
|
37
|
+
* No: the @modelcontextprotocol/sdk stdio transport holds a bound
|
|
38
|
+
* reference to the *original* `stdout.write` captured when the
|
|
39
|
+
* StdioServerTransport is instantiated. We override the property *before*
|
|
40
|
+
* any of those imports run, but the transport binds its own ref at
|
|
41
|
+
* construction time — which happens via `connectStdio()` *after* this
|
|
42
|
+
* module has applied the redirect. To guarantee transport writes still
|
|
43
|
+
* land on stdout, we expose `realStdoutWrite` so any code that needs a
|
|
44
|
+
* raw stdout channel can use it explicitly. The default transport works
|
|
45
|
+
* because we only redirect `console.*`, not `process.stdout.write`.
|
|
46
|
+
*
|
|
47
|
+
* Usage
|
|
48
|
+
* -----
|
|
49
|
+
* Import this **as the very first import** in every stdio MCP server's
|
|
50
|
+
* entry-point file:
|
|
51
|
+
*
|
|
52
|
+
* #!/usr/bin/env node
|
|
53
|
+
* import "./stdio-protect";
|
|
54
|
+
* // … rest of the imports
|
|
55
|
+
*
|
|
56
|
+
* Being first matters: ES modules evaluate dependencies in DFS post-order,
|
|
57
|
+
* so a side-effect module placed at the top of the import list runs before
|
|
58
|
+
* any sibling `import` that might transitively pull in a stdout-noisy dep.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
// Suppress maestro-agent-sdk's bootstrap log line at source (v0.1.14+).
|
|
62
|
+
// Must run before any `import` of the SDK resolves — placing this in the
|
|
63
|
+
// module body of stdio-protect (always the first import) achieves that
|
|
64
|
+
// because ES module evaluation is DFS post-order: side-effect statements
|
|
65
|
+
// at the top of this file run before sibling imports finish hydrating.
|
|
66
|
+
process.env.MAESTRO_SDK_SILENT_BOOTSTRAP ??= "1";
|
|
67
|
+
|
|
68
|
+
const origLog = console.log.bind(console);
|
|
69
|
+
const origInfo = console.info.bind(console);
|
|
70
|
+
|
|
71
|
+
console.log = (...args: unknown[]) => {
|
|
72
|
+
// Route to stderr; preserve the original signature so callers that pass
|
|
73
|
+
// format strings + interpolation args still work.
|
|
74
|
+
console.error(...args);
|
|
75
|
+
};
|
|
76
|
+
console.info = (...args: unknown[]) => {
|
|
77
|
+
console.error(...args);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Expose originals for tests / explicit re-entry, even though we don't
|
|
81
|
+
// currently rely on them. Marked `void` so unused-import lints don't fire
|
|
82
|
+
// when this file is imported only for its side-effect.
|
|
83
|
+
export const __origConsoleLog: (...args: unknown[]) => void = origLog;
|
|
84
|
+
export const __origConsoleInfo: (...args: unknown[]) => void = origInfo;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { cpus, freemem, loadavg, totalmem } from "node:os";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { errMsg } from "#platform/error";
|
|
7
|
+
import { connectStdio, mcpOk } from "./mcp-helpers";
|
|
8
|
+
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
|
|
11
|
+
function fmtBytes(bytes: number): string {
|
|
12
|
+
const gb = bytes / 1024 ** 3;
|
|
13
|
+
return gb >= 1 ? `${gb.toFixed(1)}GB` : `${(bytes / 1024 ** 2).toFixed(0)}MB`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function shell(cmd: string, args: string[]): Promise<string> {
|
|
17
|
+
const { stdout } = await execFileAsync(cmd, args, { timeout: 5000 });
|
|
18
|
+
return stdout.trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function getMemoryPressure(): Promise<string> {
|
|
22
|
+
try {
|
|
23
|
+
const level = await shell("sysctl", ["-n", "kern.memorystatus_vm_pressure_level"]);
|
|
24
|
+
return level === "4" ? "critical" : level === "2" ? "warning" : "normal";
|
|
25
|
+
} catch {
|
|
26
|
+
return "unknown";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function getSwapUsage(): Promise<string> {
|
|
31
|
+
try {
|
|
32
|
+
const out = await shell("sysctl", ["vm.swapusage"]);
|
|
33
|
+
// vm.swapusage: total = 2048.00M used = 606.00M free = 1442.00M
|
|
34
|
+
const m = out.match(
|
|
35
|
+
/total\s*=\s*([\d.]+\w+)\s+used\s*=\s*([\d.]+\w+)\s+free\s*=\s*([\d.]+\w+)/,
|
|
36
|
+
);
|
|
37
|
+
return m ? `used ${m[2]} / total ${m[1]} 여유: ${m[3]}` : out;
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return errMsg(e);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function getThermal(): Promise<string> {
|
|
44
|
+
try {
|
|
45
|
+
const out = await shell("pmset", ["-g", "therm"]);
|
|
46
|
+
// Look for "System Thermal Level" or "CPU_Scheduler_Limit"
|
|
47
|
+
const cpuLimit = out.match(/CPU_Scheduler_Limit\s*=\s*(\d+)/)?.[1];
|
|
48
|
+
const cpuAvail = out.match(/CPU_Available_CPUs\s*=\s*(\d+)/)?.[1];
|
|
49
|
+
const thermalLevel = out.match(/System Thermal Level\s*=\s*(\d+)/)?.[1];
|
|
50
|
+
const parts: string[] = [];
|
|
51
|
+
if (thermalLevel !== undefined) parts.push(`level ${thermalLevel}`);
|
|
52
|
+
if (cpuLimit !== undefined) parts.push(`CPU scheduler limit ${cpuLimit}%`);
|
|
53
|
+
if (cpuAvail !== undefined) parts.push(`available CPUs ${cpuAvail}`);
|
|
54
|
+
return parts.length > 0 ? parts.join(", ") : "nominal";
|
|
55
|
+
} catch (e) {
|
|
56
|
+
return errMsg(e);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function getProcessCount(): Promise<number> {
|
|
61
|
+
try {
|
|
62
|
+
const out = await shell("ps", ["-A", "-o", "pid="]);
|
|
63
|
+
return out.split("\n").filter(Boolean).length;
|
|
64
|
+
} catch {
|
|
65
|
+
return -1;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function getDisk(): Promise<string> {
|
|
70
|
+
try {
|
|
71
|
+
const out = await shell("df", ["-k", "-P", "/"]);
|
|
72
|
+
const parts = out.split("\n")[1]?.split(/\s+/);
|
|
73
|
+
if (!parts || parts.length < 4) return "조회 실패";
|
|
74
|
+
const diskTotal = Number(parts[1]) * 1024;
|
|
75
|
+
const diskUsed = Number(parts[2]) * 1024;
|
|
76
|
+
const diskAvail = Number(parts[3]) * 1024;
|
|
77
|
+
const diskPct = ((diskUsed / diskTotal) * 100).toFixed(1);
|
|
78
|
+
return `${fmtBytes(diskUsed)} / ${fmtBytes(diskTotal)} (${diskPct}%) 여유: ${fmtBytes(diskAvail)}`;
|
|
79
|
+
} catch (e) {
|
|
80
|
+
return errMsg(e);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const server = new McpServer({ name: "system-health", version: "1.0.0" });
|
|
85
|
+
|
|
86
|
+
server.tool(
|
|
87
|
+
"get_system_health",
|
|
88
|
+
[
|
|
89
|
+
"시스템 상태를 조회합니다.",
|
|
90
|
+
"- CPU 부하 (load average 1/5/15분)",
|
|
91
|
+
"- 메모리 사용량 + 압력 (normal/warning/critical)",
|
|
92
|
+
"- 스왑 사용량",
|
|
93
|
+
"- 디스크 사용률 (/)",
|
|
94
|
+
"- 열 상태 (thermal)",
|
|
95
|
+
"- 프로세스 수",
|
|
96
|
+
].join("\n"),
|
|
97
|
+
{},
|
|
98
|
+
async () => {
|
|
99
|
+
const [memPressure, swap, thermal, disk, procCount] = await Promise.all([
|
|
100
|
+
getMemoryPressure(),
|
|
101
|
+
getSwapUsage(),
|
|
102
|
+
getThermal(),
|
|
103
|
+
getDisk(),
|
|
104
|
+
getProcessCount(),
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
const total = totalmem();
|
|
108
|
+
const free = freemem();
|
|
109
|
+
const used = total - free;
|
|
110
|
+
const memPct = ((used / total) * 100).toFixed(1);
|
|
111
|
+
const [l1, l5, l15] = loadavg();
|
|
112
|
+
const numCpus = cpus().length;
|
|
113
|
+
|
|
114
|
+
return mcpOk(
|
|
115
|
+
[
|
|
116
|
+
"시스템 상태",
|
|
117
|
+
"",
|
|
118
|
+
`CPU 부하: ${l1.toFixed(2)} / ${l5.toFixed(2)} / ${l15.toFixed(2)} (1/5/15분, 코어 ${numCpus}개)`,
|
|
119
|
+
`메모리: ${fmtBytes(used)} / ${fmtBytes(total)} (${memPct}%) 여유: ${fmtBytes(free)}`,
|
|
120
|
+
`메모리 압력: ${memPressure}`,
|
|
121
|
+
`스왑: ${swap}`,
|
|
122
|
+
`디스크(/): ${disk}`,
|
|
123
|
+
`열 상태: ${thermal}`,
|
|
124
|
+
`프로세스 수: ${procCount > 0 ? `${procCount}개` : "조회 실패"}`,
|
|
125
|
+
].join("\n"),
|
|
126
|
+
);
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
await connectStdio(server);
|