micro-models-agent 2.10.0 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/dist/assets/app.js +22 -4
- package/dist/assets/index.html +29 -0
- package/dist/assets/js/dom.js +2 -1
- package/dist/assets/js/feed.js +38 -26
- package/dist/assets/js/format.js +1 -1
- package/dist/assets/js/rail.js +114 -0
- package/dist/assets/js/settings-controls.js +2 -2
- package/dist/assets/js/settings.js +246 -51
- package/dist/assets/js/store.js +3 -0
- package/dist/assets/js/tabs.js +104 -2
- package/dist/assets/styles.css +215 -66
- package/dist/cli/cache-line.js +1 -0
- package/dist/cli/command-suggest.js +5 -4
- package/dist/cli/commands.js +231 -122
- package/dist/cli/completer.js +132 -31
- package/dist/cli/index.js +1 -0
- package/dist/cli/json-payload.js +4 -4
- package/dist/cli/main.js +166 -61
- package/dist/cli/onboarding-commands.js +126 -0
- package/dist/cli/plugin-commands.js +2 -1
- package/dist/cli/relaunch.js +16 -13
- package/dist/cli/repl-commands-lsp.js +75 -0
- package/dist/cli/repl-commands-skills.js +105 -0
- package/dist/cli/repl-commands.js +589 -266
- package/dist/cli/repl.js +436 -177
- package/dist/cli/run-result.js +5 -6
- package/dist/cli/security-commands.js +37 -28
- package/dist/cli/setup-order.js +4 -55
- package/dist/cli/setup-prompt.js +3 -2
- package/dist/cli/setup.js +27 -107
- package/dist/cli/web-command.js +98 -0
- package/dist/config/budget.js +5 -5
- package/dist/config/config.js +103 -96
- package/dist/config/defaults.js +38 -8
- package/dist/config/domains.js +24 -27
- package/dist/config/env.js +46 -0
- package/dist/config/experts.js +2 -0
- package/dist/config/index.js +1 -0
- package/dist/config/paths.js +56 -0
- package/dist/config/redact.js +44 -0
- package/dist/config/registry-extra.js +334 -0
- package/dist/config/registry.js +584 -0
- package/dist/config/security.js +35 -36
- package/dist/config/timeouts.js +74 -0
- package/dist/config/utils.js +16 -15
- package/dist/core/agent/artifact-progress.js +59 -0
- package/dist/core/agent/artifact-resolve.js +79 -0
- package/dist/core/agent/audit-gate.js +7 -3
- package/dist/core/agent/compaction.js +2 -2
- package/dist/core/agent/constants.js +33 -4
- package/dist/core/agent/context-log.js +454 -0
- package/dist/core/agent/context-renderer.js +5 -0
- package/dist/core/agent/critic-gate.js +132 -0
- package/dist/core/agent/hallucination-gate.js +148 -38
- package/dist/core/agent/loop-state.js +15 -0
- package/dist/core/agent/prefix-monitor.js +1 -1
- package/dist/core/agent/reasoning-resolver.js +10 -1
- package/dist/core/agent/token-tracker.js +7 -4
- package/dist/core/agent/tool-batch.js +93 -18
- package/dist/core/agent/tool-images.js +18 -0
- package/dist/core/agent/tool-output.js +65 -11
- package/dist/core/agent/tool-pairing.js +60 -0
- package/dist/core/agent-moe.js +25 -24
- package/dist/core/agent.js +429 -108
- package/dist/core/bootstrap/agent-phase.js +66 -0
- package/dist/core/bootstrap/config-phase.js +86 -0
- package/dist/core/bootstrap/modules-phase.js +269 -0
- package/dist/core/bootstrap/provider-phase.js +183 -0
- package/dist/core/bootstrap/session-phase.js +204 -0
- package/dist/core/bootstrap/state.js +94 -0
- package/dist/core/bootstrap/workspace-phase.js +115 -0
- package/dist/core/bootstrap.js +92 -660
- package/dist/core/crash-handler.js +17 -7
- package/dist/core/environment.js +37 -12
- package/dist/core/host-plan.js +43 -0
- package/dist/core/host.js +23 -0
- package/dist/core/index.js +1 -0
- package/dist/core/prompt-builder.js +17 -1
- package/dist/core/prompt-overflow.js +42 -43
- package/dist/core/session-logger.js +122 -30
- package/dist/core/system-prompt.js +117 -0
- package/dist/core/version.js +6 -4
- package/dist/core/web-host.js +34 -0
- package/dist/core/workspace.js +19 -15
- package/dist/i18n/en.json +106 -21
- package/dist/i18n/index.js +37 -3
- package/dist/i18n/ru.json +109 -21
- package/dist/index.js +7 -3
- package/dist/llm/image-utils.js +55 -64
- package/dist/llm/llm-errors.js +9 -0
- package/dist/llm/model-loader.js +10 -32
- package/dist/llm/models-dev.js +121 -0
- package/dist/llm/openai-compat.js +146 -73
- package/dist/llm/orchestrator.js +33 -11
- package/dist/llm/provider-budget.js +2 -2
- package/dist/llm/provider-context.js +233 -0
- package/dist/llm/provider.js +1 -1
- package/dist/llm/response.js +5 -0
- package/dist/llm/token-counter.js +27 -11
- package/dist/logger/app-logger.js +48 -22
- package/dist/logger/file-log.js +28 -19
- package/dist/logger/index.js +1 -0
- package/dist/main.js +24285 -22447
- package/dist/migration/backup.js +14 -3
- package/dist/migration/detect.js +12 -3
- package/dist/migration/index.js +1 -0
- package/dist/modules/artifacts/store.js +9 -8
- package/dist/modules/browser/actions.js +6 -0
- package/dist/modules/browser/bridge-client.js +14 -12
- package/dist/modules/browser/bridge-path.js +4 -4
- package/dist/modules/browser/console-buffer.js +37 -0
- package/dist/modules/browser/cookie-store.js +5 -0
- package/dist/modules/browser/driver-types.js +1 -0
- package/dist/modules/browser/driver.js +19 -11
- package/dist/modules/browser/index.js +3 -1
- package/dist/modules/browser/module.js +2 -1
- package/dist/modules/browser/session.js +10 -30
- package/dist/modules/browser/snapshot.js +19 -13
- package/dist/modules/browser/types.js +1 -0
- package/dist/modules/certification/cli.js +28 -20
- package/dist/modules/certification/fact-checker.js +9 -1
- package/dist/modules/certification/loader.js +9 -2
- package/dist/modules/certification/manifest.js +83 -21
- package/dist/modules/certification/runner.js +23 -13
- package/dist/modules/certification/scenarios.js +20 -20
- package/dist/modules/certification/syntax-scenarios.js +1 -2
- package/dist/modules/context/chunk-query.js +11 -9
- package/dist/modules/context/fact-extractor.js +37 -39
- package/dist/modules/context/index.js +1 -0
- package/dist/modules/context/manager.js +106 -80
- package/dist/modules/critic/client.js +176 -0
- package/dist/modules/critic/evidence.js +81 -0
- package/dist/modules/critic/provider.js +45 -0
- package/dist/modules/critic/tools.js +78 -0
- package/dist/modules/critic/types.js +1 -0
- package/dist/modules/execution/audit-runners.js +28 -34
- package/dist/modules/execution/auditor.js +53 -49
- package/dist/modules/execution/execution-plugin.js +142 -102
- package/dist/modules/execution/index.js +1 -1
- package/dist/modules/execution/input-from.js +37 -13
- package/dist/modules/execution/module.js +368 -191
- package/dist/modules/execution/moe-executor.js +53 -26
- package/dist/modules/execution/plan-actions.js +320 -202
- package/dist/modules/execution/plan-checks.js +35 -0
- package/dist/modules/execution/plan-coverage.js +36 -33
- package/dist/modules/execution/plan-steps.js +11 -3
- package/dist/modules/execution/plan-store.js +83 -17
- package/dist/modules/execution/plan-tool.js +89 -35
- package/dist/modules/execution/plan-validator.js +30 -10
- package/dist/modules/execution/planner.js +22 -13
- package/dist/modules/execution/stuck-detector.js +196 -93
- package/dist/modules/execution/tracker.js +22 -8
- package/dist/modules/execution/verifier.js +69 -48
- package/dist/modules/execution/windows-commands.js +12 -12
- package/dist/modules/hallucination/artifact-claim.js +93 -0
- package/dist/modules/hallucination/confidence.js +13 -11
- package/dist/modules/hallucination/consistency.js +10 -2
- package/dist/modules/hallucination/detector.js +10 -5
- package/dist/modules/hallucination/factual.js +43 -26
- package/dist/modules/hallucination/index.js +1 -0
- package/dist/modules/hallucination/js-identifiers.js +44 -45
- package/dist/modules/hallucination/llm-judge.js +17 -12
- package/dist/modules/hallucination/success-claim.js +59 -0
- package/dist/modules/indexer/cache.js +52 -4
- package/dist/modules/indexer/index.js +1 -0
- package/dist/modules/indexer/map-command.js +2 -4
- package/dist/modules/indexer/map-select.js +16 -16
- package/dist/modules/indexer/module.js +47 -12
- package/dist/modules/indexer/project-profile.js +8 -7
- package/dist/modules/indexer/symbols.js +15 -15
- package/dist/modules/indexer/walker.js +159 -29
- package/dist/modules/lsp/check-tool.js +7 -5
- package/dist/modules/lsp/client.js +39 -39
- package/dist/modules/lsp/command.js +17 -24
- package/dist/modules/lsp/config.js +12 -11
- package/dist/modules/lsp/index.js +1 -0
- package/dist/modules/lsp/module.js +48 -46
- package/dist/modules/lsp/probe.js +13 -14
- package/dist/modules/lsp/project-root.js +14 -17
- package/dist/modules/lsp/startup-check.js +18 -16
- package/dist/modules/mcp/client.js +59 -62
- package/dist/modules/mcp/index.js +1 -0
- package/dist/modules/mcp/module.js +66 -37
- package/dist/modules/mcp/registry.js +5 -0
- package/dist/modules/memory/index.js +1 -0
- package/dist/modules/memory/module.js +12 -8
- package/dist/modules/memory/search.js +4 -1
- package/dist/modules/memory/store.js +40 -2
- package/dist/modules/onboarding/init.js +47 -0
- package/dist/modules/onboarding/meet.js +30 -0
- package/dist/modules/onboarding/mode.js +30 -0
- package/dist/modules/onboarding/prompts.js +43 -0
- package/dist/modules/pipelines/engine.js +10 -0
- package/dist/modules/pipelines/index.js +1 -0
- package/dist/modules/pipelines/parser.js +4 -1
- package/dist/modules/pipelines/template.js +4 -0
- package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
- package/dist/modules/plugins/builtin/notify.js +2 -0
- package/dist/modules/plugins/index.js +1 -0
- package/dist/modules/plugins/loader.js +18 -3
- package/dist/modules/plugins/manager.js +37 -17
- package/dist/modules/pricing/index.js +10 -11
- package/dist/modules/pricing/prices.js +12 -12
- package/dist/modules/processes/index.js +1 -0
- package/dist/modules/processes/kill-tree.js +27 -31
- package/dist/modules/processes/registry.js +44 -25
- package/dist/modules/processes/runner.js +4 -4
- package/dist/modules/providers/capabilities.js +28 -0
- package/dist/modules/providers/create.js +3 -4
- package/dist/modules/providers/factory.js +7 -7
- package/dist/modules/providers/fallback.js +12 -12
- package/dist/modules/providers/health.js +4 -4
- package/dist/modules/providers/manager.js +50 -19
- package/dist/modules/providers/naming.js +15 -0
- package/dist/modules/providers/preflight.js +71 -0
- package/dist/modules/providers/presets.js +39 -8
- package/dist/modules/providers/registry.js +7 -2
- package/dist/modules/reasoning/policy.js +8 -6
- package/dist/modules/reasoning/probe.js +30 -25
- package/dist/modules/security/audit-log.js +19 -36
- package/dist/modules/security/audit-notifier.js +35 -75
- package/dist/modules/security/command-validator.js +99 -51
- package/dist/modules/security/content-scanner.js +8 -8
- package/dist/modules/security/data-sanitizer.js +9 -16
- package/dist/modules/security/encryption.js +32 -52
- package/dist/modules/security/index.js +2 -1
- package/dist/modules/security/network-validator.js +120 -43
- package/dist/modules/security/path-validator.js +61 -46
- package/dist/modules/security/protected-paths.js +73 -0
- package/dist/modules/security/rate-limiter.js +17 -49
- package/dist/modules/security/security-policies.js +25 -57
- package/dist/modules/security/session-encryption.js +32 -71
- package/dist/modules/security/session-isolation.js +15 -28
- package/dist/modules/session/auto-name.js +63 -0
- package/dist/modules/session/export.js +98 -0
- package/dist/modules/session/index.js +1 -0
- package/dist/modules/session/manager.js +79 -28
- package/dist/modules/session/module.js +4 -3
- package/dist/modules/session/store.js +57 -20
- package/dist/modules/setup/answers.js +35 -0
- package/dist/modules/setup/apply.js +79 -0
- package/dist/modules/setup/discovery.js +72 -0
- package/dist/modules/skills/index.js +1 -0
- package/dist/modules/skills/loader.js +9 -1
- package/dist/modules/skills/module.js +11 -0
- package/dist/modules/updater/changelog-reader.js +15 -16
- package/dist/modules/updater/checker.js +9 -4
- package/dist/modules/updater/dev-detect.js +7 -6
- package/dist/modules/updater/index.js +1 -0
- package/dist/modules/updater/module.js +17 -15
- package/dist/modules/user-profile/compressor.js +2 -0
- package/dist/modules/user-profile/index.js +1 -0
- package/dist/modules/user-profile/profile.js +10 -1
- package/dist/modules/webui/answer-api.js +29 -0
- package/dist/modules/webui/assets.js +50 -0
- package/dist/modules/webui/body.js +21 -0
- package/dist/modules/webui/commands-settings.js +209 -0
- package/dist/modules/webui/commands-shared.js +60 -0
- package/dist/modules/webui/commands.js +174 -0
- package/dist/modules/webui/config-api.js +134 -0
- package/dist/modules/webui/file-api.js +202 -0
- package/dist/modules/webui/forward.js +159 -0
- package/dist/modules/webui/history-api.js +112 -0
- package/dist/modules/webui/host-state.js +60 -0
- package/dist/modules/webui/i18n-api.js +9 -0
- package/dist/modules/webui/limits.js +6 -0
- package/dist/modules/webui/log-forward.js +20 -0
- package/dist/modules/webui/memory-commands.js +64 -0
- package/dist/modules/webui/model-cache.js +18 -0
- package/dist/modules/webui/module.js +148 -0
- package/dist/modules/webui/persist.js +24 -0
- package/dist/modules/webui/prompt-io.js +97 -0
- package/dist/modules/webui/providers-api.js +337 -0
- package/dist/modules/webui/providers-view.js +56 -0
- package/dist/modules/webui/reasoning-view.js +15 -0
- package/dist/modules/webui/run-agent.js +81 -0
- package/dist/modules/webui/server.js +284 -0
- package/dist/modules/webui/sessions-api.js +178 -0
- package/dist/modules/webui/settings-api.js +561 -0
- package/dist/modules/webui/setup-api.js +201 -0
- package/dist/modules/webui/state.js +74 -0
- package/dist/modules/webui/util.js +261 -0
- package/dist/output/bus.js +7 -2
- package/dist/output/channel.js +80 -28
- package/dist/output/format.js +2 -1
- package/dist/output/index.js +1 -0
- package/dist/output/json-sink.js +5 -3
- package/dist/output/machine.js +4 -3
- package/dist/output/session-sink.js +4 -4
- package/dist/tools/approve.js +4 -0
- package/dist/tools/attach-image.js +5 -4
- package/dist/tools/auto-fixer.js +26 -44
- package/dist/tools/auto-tags.js +83 -0
- package/dist/tools/bash.js +193 -116
- package/dist/tools/browser.js +76 -31
- package/dist/tools/chunk-query.js +14 -6
- package/dist/tools/create-dir.js +9 -5
- package/dist/tools/delete-file.js +9 -5
- package/dist/tools/download-file.js +32 -7
- package/dist/tools/edit-file.js +52 -14
- package/dist/tools/enable-tools.js +6 -6
- package/dist/tools/executor.js +132 -27
- package/dist/tools/file-info.js +3 -1
- package/dist/tools/filter-tools.js +3 -3
- package/dist/tools/glob-tool.js +4 -1
- package/dist/tools/grep-tool.js +13 -10
- package/dist/tools/hidden-tools-block.js +6 -6
- package/dist/tools/index.js +16 -8
- package/dist/tools/list-dir.js +4 -2
- package/dist/tools/load-skill.js +7 -0
- package/dist/tools/mcp-call.js +15 -4
- package/dist/tools/meet-done.js +14 -0
- package/dist/tools/move-file.js +14 -7
- package/dist/tools/path-utils.js +29 -29
- package/dist/tools/pipeline-run.js +29 -7
- package/dist/tools/preview.js +1 -1
- package/dist/tools/process-ids.js +31 -0
- package/dist/tools/process-kill.js +9 -16
- package/dist/tools/process-list.js +1 -0
- package/dist/tools/process-log.js +11 -16
- package/dist/tools/process-wait.js +63 -0
- package/dist/tools/prompt-io.js +4 -3
- package/dist/tools/question.js +172 -25
- package/dist/tools/read-file.js +55 -34
- package/dist/tools/recall.js +14 -8
- package/dist/tools/registry.js +6 -5
- package/dist/tools/remember.js +8 -4
- package/dist/tools/scope-request.js +11 -6
- package/dist/tools/search-history.js +5 -2
- package/dist/tools/session-info.js +2 -0
- package/dist/tools/set-thinking.js +8 -4
- package/dist/tools/subagent.js +59 -39
- package/dist/tools/syntax-validator.js +292 -90
- package/dist/tools/user-input.js +22 -18
- package/dist/tools/web-browse.js +9 -3
- package/dist/tools/web-fetch.js +12 -23
- package/dist/tools/web-search.js +7 -5
- package/dist/tools/write-file.js +45 -15
- package/dist/ui/action-log.js +13 -0
- package/dist/ui/box.js +4 -3
- package/dist/ui/colors.js +2 -0
- package/dist/ui/diff.js +6 -5
- package/dist/ui/index.js +1 -0
- package/dist/ui/line-editor.js +230 -122
- package/dist/ui/line-math.js +10 -2
- package/dist/ui/md-formatter.js +57 -37
- package/dist/ui/output.js +8 -3
- package/dist/ui/plan-view.js +21 -10
- package/dist/ui/renderer.js +393 -67
- package/dist/ui/spinner.js +16 -5
- package/dist/ui/table.js +15 -17
- package/dist/ui/theme.js +143 -0
- package/dist/utils/child-run.js +20 -0
- package/dist/utils/error.js +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/retry.js +2 -2
- package/dist/utils/truncate.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Автоимена сессий: детерминированное из первого сообщения + LLM-уточнение (fail-open).
|
|
2
|
+
import { t } from "../../i18n/index";
|
|
3
|
+
const MAX_NAME_LEN = 60;
|
|
4
|
+
const MIN_CUT = 20;
|
|
5
|
+
/** Детерминированное мгновенное имя сессии из первого сообщения пользователя. */
|
|
6
|
+
export function deriveSessionName(input, date) {
|
|
7
|
+
const firstLine = input.split(/\r?\n/).find((l) => l.trim().length > 0) ?? "";
|
|
8
|
+
let s = firstLine
|
|
9
|
+
.replace(/```[\s\S]*?```/g, " ")
|
|
10
|
+
.replace(/`[^`]*`/g, " ")
|
|
11
|
+
.replace(/https?:\/\/\S+/gi, " ")
|
|
12
|
+
.replace(/^[#>\-*+\s]+/, "")
|
|
13
|
+
.replace(/\s+/g, " ")
|
|
14
|
+
.trim()
|
|
15
|
+
.replace(/[.,;:!?…]+$/u, "")
|
|
16
|
+
.trim();
|
|
17
|
+
if (!s) {
|
|
18
|
+
return t("session.default_name", { date: date ?? new Date().toISOString().slice(0, 10) });
|
|
19
|
+
}
|
|
20
|
+
if (s.length > MAX_NAME_LEN) {
|
|
21
|
+
const cut = s.slice(0, MAX_NAME_LEN);
|
|
22
|
+
const lastSpace = cut.lastIndexOf(" ");
|
|
23
|
+
s = `${(lastSpace > MIN_CUT ? cut.slice(0, lastSpace) : cut).trim()}…`;
|
|
24
|
+
}
|
|
25
|
+
return s;
|
|
26
|
+
}
|
|
27
|
+
/** Нормализовать заголовок от модели; мусор — null. */
|
|
28
|
+
export function sanitizeTitle(raw) {
|
|
29
|
+
const first = raw
|
|
30
|
+
.split(/\r?\n/)
|
|
31
|
+
.map((l) => l.trim())
|
|
32
|
+
.find((l) => l.length > 0) ?? "";
|
|
33
|
+
const cleaned = first.replace(/^["'«»`]+|["'«»`.!,;:]+$/gu, "").trim();
|
|
34
|
+
if (!cleaned)
|
|
35
|
+
return null;
|
|
36
|
+
return cleaned.length > MAX_NAME_LEN ? `${cleaned.slice(0, MAX_NAME_LEN).trim()}…` : cleaned;
|
|
37
|
+
}
|
|
38
|
+
/** Собрать промпт заголовка из отрывка сообщения (первые 1000 символов). */
|
|
39
|
+
export function buildTitlePrompt(input) {
|
|
40
|
+
const excerpt = input.slice(0, 1000);
|
|
41
|
+
return [
|
|
42
|
+
"Give a short session title for the message below.",
|
|
43
|
+
"Rules: 3-6 words, same language as the message, no quotes, no emoji, no trailing punctuation.",
|
|
44
|
+
"Reply with the title only.",
|
|
45
|
+
"",
|
|
46
|
+
excerpt,
|
|
47
|
+
].join("\n");
|
|
48
|
+
}
|
|
49
|
+
/** Разовый LLM-заголовок с таймаутом. Fail-open — любая ошибка даёт null. */
|
|
50
|
+
export async function generateSessionTitle(input, chat, timeoutMs = 8000) {
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
53
|
+
try {
|
|
54
|
+
const raw = await chat(buildTitlePrompt(input), controller.signal);
|
|
55
|
+
return sanitizeTitle(raw);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { getMessageText } from "../../llm/provider";
|
|
2
|
+
import { redactContextSecrets } from "../../core/agent/context-log";
|
|
3
|
+
function roleHeading(role) {
|
|
4
|
+
switch (role) {
|
|
5
|
+
case "user":
|
|
6
|
+
return "User";
|
|
7
|
+
case "assistant":
|
|
8
|
+
return "Assistant";
|
|
9
|
+
case "tool":
|
|
10
|
+
return "Tool";
|
|
11
|
+
case "system":
|
|
12
|
+
return "System";
|
|
13
|
+
default:
|
|
14
|
+
return role;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Отрендерить сессию в Markdown. */
|
|
18
|
+
export function renderSessionMarkdown(meta, messages, opts) {
|
|
19
|
+
const lines = [];
|
|
20
|
+
const name = meta?.name ?? "session";
|
|
21
|
+
const id = meta?.id ?? "unknown";
|
|
22
|
+
lines.push(`# ${name} (${id})`);
|
|
23
|
+
lines.push("");
|
|
24
|
+
if (meta) {
|
|
25
|
+
lines.push(`- Model: ${meta.model}`);
|
|
26
|
+
lines.push(`- Created: ${meta.createdAt}`);
|
|
27
|
+
lines.push(`- Updated: ${meta.updatedAt}`);
|
|
28
|
+
lines.push(`- Messages: ${meta.messageCount}`);
|
|
29
|
+
if (meta.projectDir)
|
|
30
|
+
lines.push(`- Project: ${meta.projectDir}`);
|
|
31
|
+
}
|
|
32
|
+
lines.push("");
|
|
33
|
+
lines.push("---");
|
|
34
|
+
lines.push("");
|
|
35
|
+
for (const msg of messages) {
|
|
36
|
+
appendMessageLines(lines, msg, opts);
|
|
37
|
+
}
|
|
38
|
+
return lines.join("\n");
|
|
39
|
+
}
|
|
40
|
+
function appendMessageLines(lines, msg, opts) {
|
|
41
|
+
// Compact режет тела tool-результатов (основной объём длинных сессий);
|
|
42
|
+
// однострочники `- tool_call:` на ходах ассистента показывают, что запускалось.
|
|
43
|
+
if (opts?.compact && msg.role === "tool")
|
|
44
|
+
return;
|
|
45
|
+
const heading = roleHeading(msg.role);
|
|
46
|
+
const nameSuffix = msg.name ? ` (${msg.name})` : "";
|
|
47
|
+
lines.push(`## ${heading}${nameSuffix}`);
|
|
48
|
+
lines.push("");
|
|
49
|
+
const text = getMessageText(msg.content);
|
|
50
|
+
lines.push(text.length > 0 ? text : "_(empty)_");
|
|
51
|
+
lines.push("");
|
|
52
|
+
if (Array.isArray(msg.tool_calls)) {
|
|
53
|
+
for (const tc of msg.tool_calls) {
|
|
54
|
+
lines.push(`- tool_call: \`${tc.function.name}\` ${tc.function.arguments}`);
|
|
55
|
+
}
|
|
56
|
+
lines.push("");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Собрать JSON-экспорт сессии. */
|
|
60
|
+
export function buildSessionJson(meta, messages, opts) {
|
|
61
|
+
const list = opts?.compact ? messages.filter((m) => m.role !== "tool") : messages;
|
|
62
|
+
return JSON.stringify({ meta, messages: list }, null, 2);
|
|
63
|
+
}
|
|
64
|
+
/** Объёмные снапшот-записи, выкидываемые --compact (для чтения человеком). */
|
|
65
|
+
const COMPACT_JSONL_DROP = new Set(["context", "tool_defs", "tool_result", "llm_usage"]);
|
|
66
|
+
/** Собрать JSONL-экспорт из журнала. */
|
|
67
|
+
export function buildSessionJsonl(log, opts) {
|
|
68
|
+
const list = opts?.compact ? log.filter((e) => !COMPACT_JSONL_DROP.has(e.type)) : log;
|
|
69
|
+
return list.map((e) => JSON.stringify(e)).join("\n");
|
|
70
|
+
}
|
|
71
|
+
/** Отформатировать пакет в заданном формате. */
|
|
72
|
+
export function formatExport(bundle, format, opts) {
|
|
73
|
+
switch (format) {
|
|
74
|
+
case "md":
|
|
75
|
+
return renderSessionMarkdown(bundle.meta, bundle.messages, opts);
|
|
76
|
+
case "json":
|
|
77
|
+
return buildSessionJson(bundle.meta, bundle.messages, opts);
|
|
78
|
+
case "jsonl":
|
|
79
|
+
return buildSessionJsonl(bundle.log, opts);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Вычистить секреты из экспорта, если не отключено. */
|
|
83
|
+
export function redactExport(text, redact) {
|
|
84
|
+
return redact ? redactContextSecrets(text) : text;
|
|
85
|
+
}
|
|
86
|
+
/** Санитизировать имя сессии под имя файла. */
|
|
87
|
+
export function sanitizeFileName(name) {
|
|
88
|
+
const cleaned = name
|
|
89
|
+
.replace(/[<>:"/\\|?*\u0000-\u001f]/g, "-")
|
|
90
|
+
.replace(/\s+/g, "-")
|
|
91
|
+
.replace(/-+/g, "-")
|
|
92
|
+
.replace(/^[-.]+|[-.]+$/g, "");
|
|
93
|
+
return cleaned.length > 0 ? cleaned.slice(0, 60) : "session";
|
|
94
|
+
}
|
|
95
|
+
/** Расширение файла под формат. */
|
|
96
|
+
export function extensionFor(format) {
|
|
97
|
+
return format === "jsonl" ? "jsonl" : format;
|
|
98
|
+
}
|
|
@@ -1,34 +1,31 @@
|
|
|
1
1
|
import { t } from "../../i18n/index";
|
|
2
|
+
import { getMessageText } from "../../llm/provider";
|
|
2
3
|
import { createSessionContext, DEFAULT_SESSION_ISOLATION, } from "../../modules/security/session-isolation";
|
|
4
|
+
/** Менеджер: активная сессия, контексты изоляции, maxSessions, prune пустых. */
|
|
3
5
|
export class SessionManager {
|
|
4
6
|
store;
|
|
5
7
|
activeId = null;
|
|
6
8
|
options;
|
|
7
|
-
/**
|
|
8
|
-
* Map of session ID to session context (for isolation)
|
|
9
|
-
*/
|
|
9
|
+
/** Контексты сессий (для изоляции) по id */
|
|
10
10
|
sessionContexts = new Map();
|
|
11
11
|
constructor(store, options) {
|
|
12
12
|
this.store = store;
|
|
13
13
|
this.options = options;
|
|
14
|
-
//
|
|
14
|
+
// Шифрование — если настроено
|
|
15
15
|
if (options.encryption) {
|
|
16
16
|
this.store.updateEncryption(options.encryption);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Check if session file encryption is enabled
|
|
21
|
-
*/
|
|
19
|
+
/** Включено ли шифрование файлов сессий. */
|
|
22
20
|
isEncryptionEnabled() {
|
|
23
21
|
return this.store.isEncryptionEnabled();
|
|
24
22
|
}
|
|
25
|
-
/**
|
|
26
|
-
* Update encryption configuration
|
|
27
|
-
*/
|
|
23
|
+
/** Обновить конфиг шифрования. */
|
|
28
24
|
updateEncryption(config) {
|
|
29
25
|
this.store.updateEncryption(config);
|
|
30
26
|
this.options.encryption = config;
|
|
31
27
|
}
|
|
28
|
+
/** Создать сессию (имя/оверрайды опциональны), сделать активной. */
|
|
32
29
|
create(name, securityOverrides) {
|
|
33
30
|
const now = new Date().toISOString();
|
|
34
31
|
const id = `ses_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -36,6 +33,7 @@ export class SessionManager {
|
|
|
36
33
|
const meta = {
|
|
37
34
|
id,
|
|
38
35
|
name: sessionName,
|
|
36
|
+
nameSource: name ? "manual" : "default",
|
|
39
37
|
createdAt: now,
|
|
40
38
|
updatedAt: now,
|
|
41
39
|
messageCount: 0,
|
|
@@ -47,22 +45,31 @@ export class SessionManager {
|
|
|
47
45
|
this.store.saveMeta(id, meta);
|
|
48
46
|
this.activeId = id;
|
|
49
47
|
this.enforceMaxSessions();
|
|
50
|
-
|
|
48
|
+
try {
|
|
49
|
+
this.pruneEmpty(id);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// fail-open: автопрунинг не должен ломать создание сессии
|
|
53
|
+
}
|
|
54
|
+
// Контекст сессии
|
|
51
55
|
const context = createSessionContext(id, this.options.projectDir, this.options.isolation, this.normalizeSecurityOverrides(securityOverrides));
|
|
52
56
|
this.sessionContexts.set(id, context);
|
|
53
57
|
return meta;
|
|
54
58
|
}
|
|
59
|
+
/** Список сессий. */
|
|
55
60
|
list() {
|
|
56
61
|
return this.store.listSessions();
|
|
57
62
|
}
|
|
63
|
+
/** Мета по id (null — нет). */
|
|
58
64
|
get(id) {
|
|
59
65
|
return this.store.loadMeta(id);
|
|
60
66
|
}
|
|
67
|
+
/** Удалить сессию (+ контекст, сброс активной). */
|
|
61
68
|
delete(id) {
|
|
62
|
-
//
|
|
69
|
+
// Чистим контекст сессии
|
|
63
70
|
const context = this.sessionContexts.get(id);
|
|
64
71
|
if (context) {
|
|
65
|
-
//
|
|
72
|
+
// Реально temp-файлы тут не чистятся (нет реализации)
|
|
66
73
|
this.sessionContexts.delete(id);
|
|
67
74
|
}
|
|
68
75
|
this.store.deleteSession(id);
|
|
@@ -70,38 +77,79 @@ export class SessionManager {
|
|
|
70
77
|
this.activeId = null;
|
|
71
78
|
}
|
|
72
79
|
}
|
|
73
|
-
|
|
80
|
+
/** Переименовать сессию (источник имени: auto/manual). */
|
|
81
|
+
rename(id, name, source = "manual") {
|
|
74
82
|
const meta = this.store.loadMeta(id);
|
|
75
83
|
if (!meta)
|
|
76
84
|
throw new Error(t("session.not_found", { id }));
|
|
77
85
|
meta.name = name;
|
|
86
|
+
meta.nameSource = source;
|
|
78
87
|
this.store.saveMeta(id, meta);
|
|
79
88
|
}
|
|
89
|
+
/** Пустая ли сессия (нет user-сообщения с текстом). Fail-open: ошибки чтения — непустая. */
|
|
90
|
+
isEmptySession(id) {
|
|
91
|
+
const meta = this.store.loadMeta(id);
|
|
92
|
+
if (!meta)
|
|
93
|
+
return false;
|
|
94
|
+
if ((meta.messageCount ?? 0) === 0)
|
|
95
|
+
return true;
|
|
96
|
+
let history;
|
|
97
|
+
try {
|
|
98
|
+
history = this.getHistory(id);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
return !history.some((m) => m.role === "user" && getMessageText(m.content).trim().length > 0);
|
|
104
|
+
}
|
|
105
|
+
/** Удалить все пустые сессии, кроме `excludeId`. Вернуть удалённые id. Не бросает. */
|
|
106
|
+
pruneEmpty(excludeId) {
|
|
107
|
+
const deleted = [];
|
|
108
|
+
for (const s of this.store.listSessions()) {
|
|
109
|
+
if (s.id === excludeId)
|
|
110
|
+
continue;
|
|
111
|
+
try {
|
|
112
|
+
if (this.isEmptySession(s.id)) {
|
|
113
|
+
this.delete(s.id);
|
|
114
|
+
deleted.push(s.id);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// fail-open: нечитаемые пропускаем
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return deleted;
|
|
122
|
+
}
|
|
123
|
+
/** Id активной сессии (null — нет). */
|
|
80
124
|
getActive() {
|
|
81
125
|
return this.activeId;
|
|
82
126
|
}
|
|
127
|
+
/** Мета активной сессии (null — нет). */
|
|
83
128
|
getActiveMeta() {
|
|
84
129
|
if (!this.activeId)
|
|
85
130
|
return null;
|
|
86
131
|
return this.store.loadMeta(this.activeId);
|
|
87
132
|
}
|
|
133
|
+
/** Сделать сессию активной (нет такой — ошибка). */
|
|
88
134
|
setActive(id) {
|
|
89
135
|
if (!this.store.sessionExists(id)) {
|
|
90
136
|
throw new Error(t("session.not_found", { id }));
|
|
91
137
|
}
|
|
92
138
|
this.activeId = id;
|
|
93
139
|
}
|
|
140
|
+
/** Добавить сообщение в активную сессию (нет активной — ошибка). */
|
|
94
141
|
appendMessage(msg) {
|
|
95
142
|
if (!this.activeId)
|
|
96
143
|
throw new Error(t("session.no_active"));
|
|
97
144
|
this.store.appendMessage(this.activeId, msg);
|
|
98
145
|
}
|
|
146
|
+
/** Добавить запись в лог активной сессии (без активной — молча пропуск). */
|
|
99
147
|
appendLog(entry) {
|
|
100
148
|
if (!this.activeId)
|
|
101
149
|
return;
|
|
102
150
|
this.store.appendSessionLog(this.activeId, entry);
|
|
103
151
|
}
|
|
104
|
-
/**
|
|
152
|
+
/** Прочитать полный session.jsonl любой сессии (для `session show`). */
|
|
105
153
|
loadSessionLog(id) {
|
|
106
154
|
try {
|
|
107
155
|
return this.store.loadSessionLog(id);
|
|
@@ -110,25 +158,34 @@ export class SessionManager {
|
|
|
110
158
|
return [];
|
|
111
159
|
}
|
|
112
160
|
}
|
|
161
|
+
/** История активной сессии ([] — нет активной). */
|
|
113
162
|
loadHistory() {
|
|
114
163
|
if (!this.activeId)
|
|
115
164
|
return [];
|
|
116
165
|
return this.store.loadHistory(this.activeId);
|
|
117
166
|
}
|
|
118
167
|
/**
|
|
119
|
-
*
|
|
168
|
+
* История любой сессии, включая ротированные (фолбэк на архив
|
|
169
|
+
* `<id>.jsonl.gz`, если живого history-файла нет). Для
|
|
170
|
+
* `session export` / `/export`.
|
|
120
171
|
*/
|
|
172
|
+
getHistory(id) {
|
|
173
|
+
const live = this.store.loadHistory(id);
|
|
174
|
+
if (live.length > 0)
|
|
175
|
+
return live;
|
|
176
|
+
return this.store.loadRotatedHistory(id);
|
|
177
|
+
}
|
|
178
|
+
/** Контекст активной сессии (для изоляции). */
|
|
121
179
|
getSessionContext() {
|
|
122
180
|
if (!this.activeId)
|
|
123
181
|
return undefined;
|
|
124
182
|
return this.getSessionContextById(this.activeId);
|
|
125
183
|
}
|
|
126
|
-
/**
|
|
127
|
-
* Get the session context for a specific session
|
|
128
|
-
*/
|
|
184
|
+
/** Каталог указанной сессии. */
|
|
129
185
|
getSessionDirectory(id) {
|
|
130
186
|
return this.store.getSessionDir(id);
|
|
131
187
|
}
|
|
188
|
+
/** Контекст сессии по id (лениво создаётся из меты). */
|
|
132
189
|
getSessionContextById(sessionId) {
|
|
133
190
|
if (!this.sessionContexts.has(sessionId)) {
|
|
134
191
|
const meta = this.store.loadMeta(sessionId);
|
|
@@ -139,21 +196,15 @@ export class SessionManager {
|
|
|
139
196
|
}
|
|
140
197
|
return this.sessionContexts.get(sessionId);
|
|
141
198
|
}
|
|
142
|
-
/**
|
|
143
|
-
* Get session isolation configuration
|
|
144
|
-
*/
|
|
199
|
+
/** Конфиг изоляции сессий. */
|
|
145
200
|
getIsolationConfig() {
|
|
146
201
|
return this.options.isolation ?? DEFAULT_SESSION_ISOLATION;
|
|
147
202
|
}
|
|
148
|
-
/**
|
|
149
|
-
* Check if session isolation is enabled
|
|
150
|
-
*/
|
|
203
|
+
/** Включена ли изоляция сессий. */
|
|
151
204
|
isIsolationEnabled() {
|
|
152
205
|
return this.getIsolationConfig().enabled;
|
|
153
206
|
}
|
|
154
|
-
/**
|
|
155
|
-
* Normalize security overrides to match SecurityConfig structure
|
|
156
|
-
*/
|
|
207
|
+
/** Нормализовать оверрайды под структуру SecurityConfig. */
|
|
157
208
|
normalizeSecurityOverrides(overrides) {
|
|
158
209
|
if (!overrides)
|
|
159
210
|
return undefined;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { t } from "../../i18n/index";
|
|
2
|
+
/** Модуль сессии: системный блок со стабильными полями + пустой плагин. */
|
|
2
3
|
export class SessionModule {
|
|
3
4
|
name = "session";
|
|
4
5
|
manager;
|
|
@@ -9,9 +10,9 @@ export class SessionModule {
|
|
|
9
10
|
const meta = this.manager.getActiveMeta();
|
|
10
11
|
if (!meta)
|
|
11
12
|
return null;
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
13
|
+
// Стабильно на сессию: id/имя/модель/окно между ходами не меняются,
|
|
14
|
+
// так что системный промпт не мутирует (KV-кеш). Живое (счётчик сообщений)
|
|
15
|
+
// отдаётся через тул session_info.
|
|
15
16
|
return {
|
|
16
17
|
content: t("session.info_full", {
|
|
17
18
|
name: meta.name,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
// Хранилище сессий: meta/history/session-лог на диске, шифрование, ротация в .gz.
|
|
1
2
|
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync, appendFileSync, } from "fs";
|
|
2
3
|
import { join } from "path";
|
|
3
|
-
import { gzipSync } from "zlib";
|
|
4
|
+
import { gzipSync, gunzipSync } from "zlib";
|
|
4
5
|
import { SessionFileEncryptor } from "../security/session-encryption";
|
|
6
|
+
/** Файловое хранилище сессий: meta.json, history.jsonl, session.jsonl. */
|
|
5
7
|
export class SessionStore {
|
|
6
8
|
baseDir;
|
|
7
9
|
encryptor = null;
|
|
8
|
-
// In-memory
|
|
10
|
+
// In-memory кеш меты — иначе O(N²) чтений диска на appendMessage.
|
|
9
11
|
_metaCache = new Map();
|
|
10
12
|
constructor(baseDir, encryptionConfig) {
|
|
11
13
|
this.baseDir = baseDir;
|
|
@@ -13,12 +15,11 @@ export class SessionStore {
|
|
|
13
15
|
this.encryptor = new SessionFileEncryptor(encryptionConfig);
|
|
14
16
|
}
|
|
15
17
|
}
|
|
18
|
+
/** Каталог сессии по id. */
|
|
16
19
|
getSessionDir(id) {
|
|
17
20
|
return join(this.baseDir, id);
|
|
18
21
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Update encryption configuration
|
|
21
|
-
*/
|
|
22
|
+
/** Обновить конфиг шифрования. */
|
|
22
23
|
updateEncryption(config) {
|
|
23
24
|
if (config?.enabled) {
|
|
24
25
|
this.encryptor = new SessionFileEncryptor(config);
|
|
@@ -27,12 +28,11 @@ export class SessionStore {
|
|
|
27
28
|
this.encryptor = null;
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
|
-
/**
|
|
31
|
-
* Check if encryption is enabled
|
|
32
|
-
*/
|
|
31
|
+
/** Включено ли шифрование. */
|
|
33
32
|
isEncryptionEnabled() {
|
|
34
33
|
return this.encryptor?.isEnabled() ?? false;
|
|
35
34
|
}
|
|
35
|
+
/** Создать базовый каталог (0700). */
|
|
36
36
|
init() {
|
|
37
37
|
mkdirSync(this.baseDir, { recursive: true, mode: 0o700 });
|
|
38
38
|
}
|
|
@@ -48,9 +48,11 @@ export class SessionStore {
|
|
|
48
48
|
sessionLogPath(id) {
|
|
49
49
|
return join(this.sessionDir(id), "session.jsonl");
|
|
50
50
|
}
|
|
51
|
+
/** Есть ли сессия (по meta.json). */
|
|
51
52
|
sessionExists(id) {
|
|
52
53
|
return existsSync(this.metaPath(id));
|
|
53
54
|
}
|
|
55
|
+
/** Сохранить мету (кеш + диск 0600, опционально шифр). */
|
|
54
56
|
saveMeta(id, meta) {
|
|
55
57
|
this._metaCache.set(id, meta);
|
|
56
58
|
const dir = this.sessionDir(id);
|
|
@@ -66,6 +68,7 @@ export class SessionStore {
|
|
|
66
68
|
writeFileSync(this.metaPath(id), content, { encoding: "utf-8", mode: 0o600 });
|
|
67
69
|
}
|
|
68
70
|
}
|
|
71
|
+
/** Загрузить мету (кеш, иначе диск); нет/битая — null. */
|
|
69
72
|
loadMeta(id) {
|
|
70
73
|
const cached = this._metaCache.get(id);
|
|
71
74
|
if (cached)
|
|
@@ -84,8 +87,8 @@ export class SessionStore {
|
|
|
84
87
|
return null;
|
|
85
88
|
}
|
|
86
89
|
}
|
|
87
|
-
/**
|
|
88
|
-
*
|
|
90
|
+
/** Мета строго с диска, мимо _metaCache — там, где точность счётчика
|
|
91
|
+
* важнее скорости чтения (см. appendMessage). */
|
|
89
92
|
readMetaFromDisk(id) {
|
|
90
93
|
const path = this.metaPath(id);
|
|
91
94
|
if (!existsSync(path))
|
|
@@ -99,23 +102,24 @@ export class SessionStore {
|
|
|
99
102
|
return null;
|
|
100
103
|
}
|
|
101
104
|
}
|
|
105
|
+
/** Добавить сообщение в историю; мету инкрементить с диска (защита от гонки процессов). */
|
|
102
106
|
appendMessage(id, msg) {
|
|
103
107
|
const dir = this.sessionDir(id);
|
|
104
108
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
105
109
|
const line = JSON.stringify(msg);
|
|
106
110
|
if (this.encryptor?.isEnabled()) {
|
|
107
|
-
appendFileSync(this.historyPath(id), this.encryptor.encryptFileContent(line)
|
|
111
|
+
appendFileSync(this.historyPath(id), `${this.encryptor.encryptFileContent(line)}\n`, {
|
|
108
112
|
encoding: "utf-8",
|
|
109
113
|
mode: 0o600,
|
|
110
114
|
});
|
|
111
115
|
}
|
|
112
116
|
else {
|
|
113
|
-
appendFileSync(this.historyPath(id), line
|
|
117
|
+
appendFileSync(this.historyPath(id), `${line}\n`, { encoding: "utf-8", mode: 0o600 });
|
|
114
118
|
}
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
+
// Мету перечитываем с диска прямо перед инкрементом, чтобы счётчики
|
|
120
|
+
// ДРУГОГО процесса не затирались протухшим in-memory снапшотом
|
|
121
|
+
// (last-write-wins). Полноценные кросс-процессные локи вне скоупа;
|
|
122
|
+
// это сужает окно гонки.
|
|
119
123
|
const meta = this.readMetaFromDisk(id) ?? this._metaCache.get(id) ?? null;
|
|
120
124
|
if (meta) {
|
|
121
125
|
meta.messageCount = (meta.messageCount || 0) + 1;
|
|
@@ -123,6 +127,7 @@ export class SessionStore {
|
|
|
123
127
|
this.saveMeta(id, meta);
|
|
124
128
|
}
|
|
125
129
|
}
|
|
130
|
+
/** Загрузить историю (битые строки пропускаются); нет файла — []. */
|
|
126
131
|
loadHistory(id) {
|
|
127
132
|
const path = this.historyPath(id);
|
|
128
133
|
if (!existsSync(path))
|
|
@@ -158,20 +163,49 @@ export class SessionStore {
|
|
|
158
163
|
return [];
|
|
159
164
|
}
|
|
160
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* История ротированной сессии из `<baseDir>/<id>.jsonl.gz`.
|
|
168
|
+
* Нет gz или не читается — [].
|
|
169
|
+
*/
|
|
170
|
+
loadRotatedHistory(id) {
|
|
171
|
+
const gzPath = join(this.baseDir, `${id}.jsonl.gz`);
|
|
172
|
+
if (!existsSync(gzPath))
|
|
173
|
+
return [];
|
|
174
|
+
try {
|
|
175
|
+
const content = gunzipSync(readFileSync(gzPath)).toString("utf-8");
|
|
176
|
+
return content
|
|
177
|
+
.split("\n")
|
|
178
|
+
.filter(Boolean)
|
|
179
|
+
.map((line) => {
|
|
180
|
+
try {
|
|
181
|
+
return JSON.parse(line);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
.filter((m) => m !== null);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** Добавить запись в session-лог. */
|
|
161
194
|
appendSessionLog(id, entry) {
|
|
162
195
|
const dir = this.sessionDir(id);
|
|
163
196
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
164
197
|
const line = JSON.stringify(entry);
|
|
165
198
|
if (this.encryptor?.isEnabled()) {
|
|
166
|
-
appendFileSync(this.sessionLogPath(id), this.encryptor.encryptFileContent(line)
|
|
199
|
+
appendFileSync(this.sessionLogPath(id), `${this.encryptor.encryptFileContent(line)}\n`, {
|
|
167
200
|
encoding: "utf-8",
|
|
168
201
|
mode: 0o600,
|
|
169
202
|
});
|
|
170
203
|
}
|
|
171
204
|
else {
|
|
172
|
-
appendFileSync(this.sessionLogPath(id), line
|
|
205
|
+
appendFileSync(this.sessionLogPath(id), `${line}\n`, { encoding: "utf-8", mode: 0o600 });
|
|
173
206
|
}
|
|
174
207
|
}
|
|
208
|
+
/** Загрузить session-лог (битые строки пропускаются); нет файла — []. */
|
|
175
209
|
loadSessionLog(id) {
|
|
176
210
|
const path = this.sessionLogPath(id);
|
|
177
211
|
if (!existsSync(path))
|
|
@@ -207,6 +241,7 @@ export class SessionStore {
|
|
|
207
241
|
return [];
|
|
208
242
|
}
|
|
209
243
|
}
|
|
244
|
+
/** Список сессий по убыванию updatedAt. */
|
|
210
245
|
listSessions() {
|
|
211
246
|
if (!existsSync(this.baseDir))
|
|
212
247
|
return [];
|
|
@@ -222,6 +257,7 @@ export class SessionStore {
|
|
|
222
257
|
sessions.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
223
258
|
return sessions;
|
|
224
259
|
}
|
|
260
|
+
/** Удалить сессию (кеш + каталог). */
|
|
225
261
|
deleteSession(id) {
|
|
226
262
|
this._metaCache.delete(id);
|
|
227
263
|
const dir = this.sessionDir(id);
|
|
@@ -229,6 +265,7 @@ export class SessionStore {
|
|
|
229
265
|
rmSync(dir, { recursive: true, force: true });
|
|
230
266
|
}
|
|
231
267
|
}
|
|
268
|
+
/** Ротация: истории старше 30 дней — в .jsonl.gz, мета помечается rotated. */
|
|
232
269
|
rotateOldSessions() {
|
|
233
270
|
const thirtyDaysAgo = new Date();
|
|
234
271
|
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
@@ -243,8 +280,8 @@ export class SessionStore {
|
|
|
243
280
|
const gzPath = join(this.baseDir, `${session.id}.jsonl.gz`);
|
|
244
281
|
writeFileSync(gzPath, compressed);
|
|
245
282
|
rmSync(historyPath);
|
|
246
|
-
//
|
|
247
|
-
//
|
|
283
|
+
// Пометить мету, чтобы сессия не выглядела «живой» в списках —
|
|
284
|
+
// иначе gz-сессия рекламирует messageCount > 0.
|
|
248
285
|
const meta = this.loadMeta(session.id);
|
|
249
286
|
if (meta) {
|
|
250
287
|
meta.messageCount = 0;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Модель данных first-run визарда: ответы, языки, дефолты, провайдеры для UI.
|
|
2
|
+
/**
|
|
3
|
+
* Общая модель данных first-run визарда.
|
|
4
|
+
* Едят оба фронтенда (терминальный `src/cli/setup.ts` и браузерный визард
|
|
5
|
+
* `mma web`), чтобы форма и дефолты не расходились. Без терминальных/UI
|
|
6
|
+
* зависимостей — безопасно импортить с веб-сервера.
|
|
7
|
+
*/
|
|
8
|
+
import { BUILTIN_PROVIDERS } from "../providers/presets";
|
|
9
|
+
/** Языки UI визарда (подписи — на своём языке). */
|
|
10
|
+
export const SETUP_LANGUAGES = [
|
|
11
|
+
{ value: "en", label: "English" },
|
|
12
|
+
{ value: "ru", label: "Русский" },
|
|
13
|
+
];
|
|
14
|
+
/** Числовые дефолты шага настроек агента. */
|
|
15
|
+
export const SETUP_DEFAULTS = {
|
|
16
|
+
contextWindow: 32768,
|
|
17
|
+
maxToolIterations: 1000,
|
|
18
|
+
};
|
|
19
|
+
/** Статические display-данные, общие для веб-визарда и его тестов. */
|
|
20
|
+
export function buildSetupDisplay() {
|
|
21
|
+
const providers = BUILTIN_PROVIDERS.map((spec) => {
|
|
22
|
+
const option = { type: spec.type, label: spec.label };
|
|
23
|
+
if (spec.baseUrl)
|
|
24
|
+
option.baseUrl = spec.baseUrl;
|
|
25
|
+
return option;
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
languages: SETUP_LANGUAGES.map((l) => ({ value: l.value, label: l.label })),
|
|
29
|
+
providers,
|
|
30
|
+
defaults: {
|
|
31
|
+
contextWindow: SETUP_DEFAULTS.contextWindow,
|
|
32
|
+
maxToolIterations: SETUP_DEFAULTS.maxToolIterations,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|