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,171 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, resolve } from "node:path";
|
|
3
|
+
import { workspaceCwdFor } from "#runtime/attachments";
|
|
4
|
+
import {
|
|
5
|
+
isUploadFileId,
|
|
6
|
+
resolveAttachmentByFileId,
|
|
7
|
+
resolveUploadedFilePathByFileId,
|
|
8
|
+
storeLocalFileAsUpload,
|
|
9
|
+
} from "#runtime/file-hooks";
|
|
10
|
+
import { topicHasVisualFileId, VISUAL_MEDIA_URL_PLACEHOLDER } from "#runtime/visual-store";
|
|
11
|
+
import { isSensitivePath } from "#security/sensitive-path";
|
|
12
|
+
import { topicHasAttachmentFileId } from "#storage/api-messages";
|
|
13
|
+
|
|
14
|
+
export { buildMermaidHtml, normalizeMermaidTheme } from "#runtime/visual-html";
|
|
15
|
+
|
|
16
|
+
const ACTIVE_VISUAL_PROMPT_MAX_CHARS = 24_000;
|
|
17
|
+
const ACTIVE_VISUAL_PROMPT_TAIL_CHARS = 6_000;
|
|
18
|
+
|
|
19
|
+
export function activeVisualHtmlForPrompt(html: string): { html: string; omittedChars: number } {
|
|
20
|
+
if (html.length <= ACTIVE_VISUAL_PROMPT_MAX_CHARS) {
|
|
21
|
+
return { html, omittedChars: 0 };
|
|
22
|
+
}
|
|
23
|
+
const headChars = ACTIVE_VISUAL_PROMPT_MAX_CHARS - ACTIVE_VISUAL_PROMPT_TAIL_CHARS;
|
|
24
|
+
const omittedChars = html.length - headChars - ACTIVE_VISUAL_PROMPT_TAIL_CHARS;
|
|
25
|
+
return {
|
|
26
|
+
html: [
|
|
27
|
+
html.slice(0, headChars),
|
|
28
|
+
`\n<!-- Active visual HTML truncated: ${omittedChars} chars omitted. -->\n`,
|
|
29
|
+
html.slice(-ACTIVE_VISUAL_PROMPT_TAIL_CHARS),
|
|
30
|
+
].join(""),
|
|
31
|
+
omittedChars,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function isRuntimeTool(name: string, toolName: string): boolean {
|
|
36
|
+
return name === toolName || name.endsWith(`__${toolName}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function isVisualsShowHtmlTool(name: string): boolean {
|
|
40
|
+
return isRuntimeTool(name, "show_html");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isVisualsShowMermaidTool(name: string): boolean {
|
|
44
|
+
return isRuntimeTool(name, "show_mermaid");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function isVisualsShowImageTool(name: string): boolean {
|
|
48
|
+
return isRuntimeTool(name, "show_image");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function isVisualsShowVideoTool(name: string): boolean {
|
|
52
|
+
return isRuntimeTool(name, "show_video");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function normalizeToolUseId(value: string | undefined): string | undefined {
|
|
56
|
+
const normalized = value?.trim();
|
|
57
|
+
return normalized ? normalized : undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function escapeHtml(value: string): string {
|
|
61
|
+
return value
|
|
62
|
+
.replace(/&/g, "&")
|
|
63
|
+
.replace(/</g, "<")
|
|
64
|
+
.replace(/>/g, ">")
|
|
65
|
+
.replace(/"/g, """)
|
|
66
|
+
.replace(/'/g, "'");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function stripMermaidFence(value: string): string {
|
|
70
|
+
const trimmed = value.trim();
|
|
71
|
+
const fenced = trimmed.match(/^```(?:mermaid)?\s*\n([\s\S]*?)\n```$/i);
|
|
72
|
+
return (fenced ? fenced[1] : trimmed).trim();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function topicAllowsVisualFileId(topicId: string, fileId: string): boolean {
|
|
76
|
+
if (!isUploadFileId(fileId)) return false;
|
|
77
|
+
return topicHasAttachmentFileId(topicId, fileId) || topicHasVisualFileId(topicId, fileId);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function isPathInside(baseDir: string, filePath: string): boolean {
|
|
81
|
+
const base = resolve(baseDir);
|
|
82
|
+
const normalized = resolve(filePath);
|
|
83
|
+
try {
|
|
84
|
+
const realBase = realpathSync(base);
|
|
85
|
+
const real = realpathSync(normalized);
|
|
86
|
+
return real === realBase || real.startsWith(`${realBase}/`);
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function buildImageHtml(alt: string): string {
|
|
93
|
+
return `<!doctype html>
|
|
94
|
+
<html>
|
|
95
|
+
<head>
|
|
96
|
+
<meta charset="utf-8">
|
|
97
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
98
|
+
<style>
|
|
99
|
+
html,body{margin:0;width:100%;min-height:100%;background:#111827}
|
|
100
|
+
body{display:grid;place-items:center;padding:16px;box-sizing:border-box}
|
|
101
|
+
img{max-width:100%;max-height:calc(100vh - 32px);object-fit:contain;background:#fff}
|
|
102
|
+
</style>
|
|
103
|
+
</head>
|
|
104
|
+
<body><img src="${VISUAL_MEDIA_URL_PLACEHOLDER}" alt="${escapeHtml(alt)}"></body>
|
|
105
|
+
</html>`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function buildVideoHtml(mimeType: string): string {
|
|
109
|
+
return `<!doctype html>
|
|
110
|
+
<html>
|
|
111
|
+
<head>
|
|
112
|
+
<meta charset="utf-8">
|
|
113
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
114
|
+
<style>
|
|
115
|
+
html,body{margin:0;width:100%;min-height:100%;background:#0b1020}
|
|
116
|
+
body{display:grid;place-items:center;padding:16px;box-sizing:border-box}
|
|
117
|
+
video{width:100%;max-width:1200px;max-height:calc(100vh - 32px);background:#000}
|
|
118
|
+
</style>
|
|
119
|
+
</head>
|
|
120
|
+
<body><video controls playsinline preload="metadata" src="${VISUAL_MEDIA_URL_PLACEHOLDER}" data-mime="${escapeHtml(mimeType)}"></video></body>
|
|
121
|
+
</html>`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function resolveVisualMediaInput(
|
|
125
|
+
topicId: string,
|
|
126
|
+
input: { file_path?: unknown; file_id?: unknown },
|
|
127
|
+
): { fileId: string; mimeType: string; source: string } | { error: string } {
|
|
128
|
+
if (typeof input.file_id === "string" && input.file_id.trim()) {
|
|
129
|
+
const fileId = input.file_id.trim().toLowerCase();
|
|
130
|
+
if (!isUploadFileId(fileId)) return { error: "file_id must be a full uploaded-file UUID" };
|
|
131
|
+
if (!topicAllowsVisualFileId(topicId, fileId)) {
|
|
132
|
+
return { error: "file_id is not attached to this topic" };
|
|
133
|
+
}
|
|
134
|
+
const attachment = resolveAttachmentByFileId(fileId);
|
|
135
|
+
const filePath = resolveUploadedFilePathByFileId(fileId);
|
|
136
|
+
if (!attachment || !filePath) return { error: `uploaded file not found: ${fileId}` };
|
|
137
|
+
return {
|
|
138
|
+
fileId: attachment.id,
|
|
139
|
+
mimeType: attachment.mimeType,
|
|
140
|
+
source: `file_id:${attachment.id}`,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (typeof input.file_path !== "string" || !input.file_path.trim()) {
|
|
145
|
+
return { error: "file_path or file_id is required" };
|
|
146
|
+
}
|
|
147
|
+
const rawPath = input.file_path.trim();
|
|
148
|
+
const cwd = workspaceCwdFor(topicId);
|
|
149
|
+
const candidate = isAbsolute(rawPath) ? rawPath : resolve(cwd, rawPath);
|
|
150
|
+
if (!isPathInside(cwd, candidate)) {
|
|
151
|
+
return { error: "file_path must be inside the topic workspace" };
|
|
152
|
+
}
|
|
153
|
+
let realCandidate: string;
|
|
154
|
+
try {
|
|
155
|
+
realCandidate = realpathSync(candidate);
|
|
156
|
+
} catch {
|
|
157
|
+
return { error: `file not found: ${rawPath}` };
|
|
158
|
+
}
|
|
159
|
+
if (isSensitivePath(realCandidate)) {
|
|
160
|
+
return { error: "file_path matches the sensitive-file blacklist" };
|
|
161
|
+
}
|
|
162
|
+
const attachment = storeLocalFileAsUpload(realCandidate, { topicId });
|
|
163
|
+
if (!attachment) return { error: `failed to store media file: ${rawPath}` };
|
|
164
|
+
const filePath = resolveUploadedFilePathByFileId(attachment.id);
|
|
165
|
+
if (!filePath) return { error: `stored media file disappeared: ${attachment.id}` };
|
|
166
|
+
return {
|
|
167
|
+
fileId: attachment.id,
|
|
168
|
+
mimeType: attachment.mimeType,
|
|
169
|
+
source: rawPath,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Sanitize a topic/session name for use in file paths.
|
|
2
|
+
* Replaces non-alphanumeric characters (excluding Korean) with underscores.
|
|
3
|
+
* Empty input (or input consisting only of disallowed chars) returns "_" so
|
|
4
|
+
* callers always get a non-empty single path component.
|
|
5
|
+
* @param name The raw topic or session name.
|
|
6
|
+
* @param lowercase Whether to lowercase the result (default: false).
|
|
7
|
+
* Use true for wiki paths; leave false for log file paths.
|
|
8
|
+
*/
|
|
9
|
+
export function sanitizeTopicName(name: string, lowercase = false): string {
|
|
10
|
+
const safe = name.replace(/[^a-zA-Z0-9가-힣_-]/g, "_") || "_";
|
|
11
|
+
return lowercase ? safe.toLowerCase() : safe;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Sanitize a file name for use in file paths. Allows dots.
|
|
15
|
+
* Pure-dot results (".", "..") are replaced with "_" to block path-traversal
|
|
16
|
+
* when the sanitized value is used as a standalone path component.
|
|
17
|
+
*/
|
|
18
|
+
export function sanitizeFileName(name: string): string {
|
|
19
|
+
const safe = name.replace(/[^a-zA-Z0-9._-]/g, "_") || "_";
|
|
20
|
+
if (safe === "." || safe === "..") return "_";
|
|
21
|
+
return safe;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Sanitize an arbitrary ID (no Korean, no dots) for use in file paths. */
|
|
25
|
+
export function sanitizeId(id: string): string {
|
|
26
|
+
return id.replace(/[^a-zA-Z0-9_-]/g, "_") || "_";
|
|
27
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
// --- Sensitive path blacklist ---
|
|
5
|
+
|
|
6
|
+
const SENSITIVE_PATH_PATTERNS = [
|
|
7
|
+
/\/\.env(\.|$)/i,
|
|
8
|
+
/\/\.ssh\//i,
|
|
9
|
+
/\/\.aws\//i,
|
|
10
|
+
/\/\.gnupg\//i,
|
|
11
|
+
/\/\.netrc$/i,
|
|
12
|
+
/\/\.npmrc$/i,
|
|
13
|
+
/\/(id_rsa|id_ed25519|id_ecdsa|id_dsa)(\.pub)?$/i,
|
|
14
|
+
/\.(pem|key|p12|pfx|cer|crt)$/i,
|
|
15
|
+
/\/Library\/Keychains\//i,
|
|
16
|
+
// Runtime-owned stores and key material. Vault values are encrypted, but
|
|
17
|
+
// exposing ciphertext or its master key to an agent defeats that boundary.
|
|
18
|
+
/\/vault\.db(-wal|-shm|-journal)?$/i,
|
|
19
|
+
/\/vault-master-key$/i,
|
|
20
|
+
/\/runtime-mcp-secret$/i,
|
|
21
|
+
/\/sessions\.db(-wal|-shm|-journal)?$/i,
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export function isSensitivePath(filePath: string): boolean {
|
|
25
|
+
const normalized = resolve(filePath);
|
|
26
|
+
if (SENSITIVE_PATH_PATTERNS.some((p) => p.test(normalized))) return true;
|
|
27
|
+
try {
|
|
28
|
+
const real = realpathSync(normalized);
|
|
29
|
+
if (real !== normalized) return SENSITIVE_PATH_PATTERNS.some((p) => p.test(real));
|
|
30
|
+
} catch {
|
|
31
|
+
// File doesn't exist — no symlink concern
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readdirSync,
|
|
5
|
+
renameSync,
|
|
6
|
+
statSync,
|
|
7
|
+
unlinkSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import { basename, join } from "node:path";
|
|
11
|
+
import { LOG_DIR, USERS_LOG_DIR } from "#platform/config";
|
|
12
|
+
import { appendJsonlEntry, readJsonlLines } from "#platform/jsonl";
|
|
13
|
+
import { logger } from "#platform/logger";
|
|
14
|
+
import { sanitizeTopicName } from "#security/sanitize";
|
|
15
|
+
import type { TokenUsage } from "#types";
|
|
16
|
+
|
|
17
|
+
// --- Log rotation config ---
|
|
18
|
+
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB per log file
|
|
19
|
+
const MAX_TOTAL_SIZE = 200 * 1024 * 1024; // 200MB total log budget
|
|
20
|
+
const ROTATION_CHECK_INTERVAL = 60 * 60 * 1000; // check every hour
|
|
21
|
+
|
|
22
|
+
export interface LogEntry {
|
|
23
|
+
timestamp: string;
|
|
24
|
+
userId: number;
|
|
25
|
+
sessionId: string | null;
|
|
26
|
+
session: string;
|
|
27
|
+
prompt: string;
|
|
28
|
+
response: string;
|
|
29
|
+
usage?: TokenUsage;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function writeLog(entry: LogEntry) {
|
|
33
|
+
const safeSession = sanitizeTopicName(entry.session);
|
|
34
|
+
const sidShort = entry.sessionId ? entry.sessionId.slice(0, 8) : "new";
|
|
35
|
+
const file = join(LOG_DIR, `${entry.userId}_${safeSession}_${sidShort}.jsonl`);
|
|
36
|
+
|
|
37
|
+
// Rotate if file is too large
|
|
38
|
+
try {
|
|
39
|
+
const stat = statSync(file);
|
|
40
|
+
if (stat.size >= MAX_FILE_SIZE) {
|
|
41
|
+
const rotated = file.replace(/\.jsonl$/, `.${Date.now()}.jsonl`);
|
|
42
|
+
renameSync(file, rotated);
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
// File doesn't exist yet, fine
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
appendJsonlEntry(file, entry);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Remove oldest log files when total size exceeds budget */
|
|
52
|
+
export function rotateOldLogs() {
|
|
53
|
+
try {
|
|
54
|
+
const files = readdirSync(LOG_DIR)
|
|
55
|
+
.filter((f) => f.endsWith(".jsonl"))
|
|
56
|
+
.map((f) => {
|
|
57
|
+
try {
|
|
58
|
+
const stat = statSync(join(LOG_DIR, f));
|
|
59
|
+
return { name: f, size: stat.size, mtimeMs: stat.mtimeMs };
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
.filter((f): f is NonNullable<typeof f> => f !== null);
|
|
65
|
+
|
|
66
|
+
const totalSize = files.reduce((sum, f) => sum + f.size, 0);
|
|
67
|
+
if (totalSize <= MAX_TOTAL_SIZE) return;
|
|
68
|
+
|
|
69
|
+
// Delete oldest files first until under budget
|
|
70
|
+
files.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
71
|
+
let freed = 0;
|
|
72
|
+
const toFree = totalSize - MAX_TOTAL_SIZE;
|
|
73
|
+
for (const file of files) {
|
|
74
|
+
if (freed >= toFree) break;
|
|
75
|
+
try {
|
|
76
|
+
unlinkSync(join(LOG_DIR, file.name));
|
|
77
|
+
freed += file.size;
|
|
78
|
+
logger.info(
|
|
79
|
+
{ file: file.name, sizeKB: (file.size / 1024).toFixed(0) },
|
|
80
|
+
"Rotated out old log",
|
|
81
|
+
);
|
|
82
|
+
} catch (e) {
|
|
83
|
+
logger.warn({ err: e, file: file.name }, "Log rotation: failed to delete old log file");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} catch (e) {
|
|
87
|
+
logger.warn({ err: e }, "Log rotation failed");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Run rotation check periodically
|
|
92
|
+
rotateOldLogs();
|
|
93
|
+
setInterval(rotateOldLogs, ROTATION_CHECK_INTERVAL);
|
|
94
|
+
|
|
95
|
+
export interface SentFileEntry {
|
|
96
|
+
timestamp: string;
|
|
97
|
+
userId: number;
|
|
98
|
+
topicName: string;
|
|
99
|
+
sessionId: string | null;
|
|
100
|
+
filePath: string;
|
|
101
|
+
fileName: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function writeSentFileLog(entry: Omit<SentFileEntry, "fileName">) {
|
|
105
|
+
const dir = join(USERS_LOG_DIR, String(entry.userId));
|
|
106
|
+
mkdirSync(dir, { recursive: true });
|
|
107
|
+
const file = join(dir, "sent-files.jsonl");
|
|
108
|
+
try {
|
|
109
|
+
appendJsonlEntry(file, { ...entry, fileName: basename(entry.filePath) });
|
|
110
|
+
} catch (e) {
|
|
111
|
+
logger.warn({ err: e, entry }, "Failed to write sent-file log");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function entryMatchesTopic(e: SentFileEntry, topicName: string): boolean {
|
|
116
|
+
return e.topicName === topicName;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function readSentFilesForTopic(userId: number, topicName: string): SentFileEntry[] {
|
|
120
|
+
const file = join(USERS_LOG_DIR, String(userId), "sent-files.jsonl");
|
|
121
|
+
if (!existsSync(file)) return [];
|
|
122
|
+
try {
|
|
123
|
+
return readJsonlLines(file)
|
|
124
|
+
.map((line) => JSON.parse(line) as SentFileEntry)
|
|
125
|
+
.filter((e) => entryMatchesTopic(e, topicName));
|
|
126
|
+
} catch (e) {
|
|
127
|
+
logger.warn({ err: e, userId, topicName }, "Failed to read sent-files log");
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** 토픽 삭제 후 JSONL에서 해당 토픽 항목 제거 */
|
|
133
|
+
export function removeSentFilesForTopic(userId: number, topicName: string): void {
|
|
134
|
+
const file = join(USERS_LOG_DIR, String(userId), "sent-files.jsonl");
|
|
135
|
+
if (!existsSync(file)) return;
|
|
136
|
+
try {
|
|
137
|
+
const remaining = readJsonlLines(file).filter((line) => {
|
|
138
|
+
try {
|
|
139
|
+
return !entryMatchesTopic(JSON.parse(line) as SentFileEntry, topicName);
|
|
140
|
+
} catch {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
if (remaining.length > 0) {
|
|
145
|
+
writeFileSync(file, `${remaining.join("\n")}\n`);
|
|
146
|
+
} else {
|
|
147
|
+
unlinkSync(file);
|
|
148
|
+
}
|
|
149
|
+
} catch (e) {
|
|
150
|
+
logger.warn({ err: e, userId, topicName }, "Failed to remove sent-files for topic");
|
|
151
|
+
}
|
|
152
|
+
}
|