micro-models-agent 0.51.1 → 0.52.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/dist/cli/commands.js +162 -38
- package/dist/cli/completer.js +5 -5
- package/dist/cli/main.js +42 -54
- package/dist/cli/repl-commands.js +138 -38
- package/dist/cli/repl.js +175 -89
- package/dist/cli/run-result.js +11 -0
- package/dist/cli/security-commands.js +6 -6
- package/dist/cli/setup.js +21 -15
- package/dist/config/config.js +54 -27
- package/dist/config/defaults.js +17 -0
- package/dist/config/domains.js +179 -0
- package/dist/config/index.js +2 -1
- package/dist/config/security.js +28 -8
- package/dist/core/agent.js +162 -30
- package/dist/core/bootstrap.js +94 -17
- package/dist/core/crash-handler.js +51 -0
- package/dist/core/environment.js +199 -0
- package/dist/core/session-logger.js +60 -6
- package/dist/core/version.js +2 -0
- package/dist/i18n/en.json +120 -39
- package/dist/i18n/ru.json +91 -10
- package/dist/llm/openai-compat.js +191 -53
- package/dist/llm/orchestrator.js +5 -3
- package/dist/logger/app-logger.js +50 -4
- package/dist/main.js +1288 -635
- package/dist/modules/browser/session.js +4 -0
- package/dist/modules/certification/cli.js +58 -19
- package/dist/modules/certification/loader.js +2 -1
- package/dist/modules/certification/manifest.js +22 -14
- package/dist/modules/certification/runner.js +91 -5
- package/dist/modules/certification/scenarios.js +290 -7
- package/dist/modules/context/fact-extractor.js +6 -0
- package/dist/modules/context/manager.js +19 -2
- package/dist/modules/execution/audit-runners.js +61 -7
- package/dist/modules/execution/execution-plugin.js +219 -60
- package/dist/modules/execution/module.js +207 -18
- package/dist/modules/execution/moe-executor.js +33 -20
- package/dist/modules/execution/plan-store.js +39 -0
- package/dist/modules/execution/plan-tool.js +188 -19
- package/dist/modules/execution/planner.js +27 -23
- package/dist/modules/execution/stuck-detector.js +244 -8
- package/dist/modules/execution/tracker.js +8 -6
- package/dist/modules/execution/verifier.js +15 -2
- package/dist/modules/hallucination/detector.js +4 -0
- package/dist/modules/hallucination/factual.js +45 -5
- package/dist/modules/indexer/module.js +1 -0
- package/dist/modules/lsp/client.js +123 -12
- package/dist/modules/lsp/index.js +1 -1
- package/dist/modules/lsp/module.js +30 -2
- package/dist/modules/lsp/probe.js +11 -1
- package/dist/modules/lsp/startup-check.js +5 -2
- package/dist/modules/plugins/builtin/lint-on-write.js +144 -41
- package/dist/modules/plugins/manager.js +57 -13
- package/dist/modules/pricing/index.js +61 -0
- package/dist/modules/pricing/prices.js +129 -0
- package/dist/modules/providers/create.js +22 -0
- package/dist/modules/providers/fallback.js +79 -0
- package/dist/modules/providers/health.js +46 -0
- package/dist/modules/providers/index.js +5 -0
- package/dist/modules/providers/manager.js +161 -0
- package/dist/modules/providers/presets.js +128 -0
- package/dist/modules/providers/registry.js +22 -0
- package/dist/modules/providers/types.js +1 -0
- package/dist/modules/registry.js +1 -0
- package/dist/modules/security/command-validator.js +14 -0
- package/dist/modules/security/encryption.js +6 -6
- package/dist/modules/security/network-validator.js +17 -0
- package/dist/modules/security/path-validator.js +22 -26
- package/dist/modules/session/store.js +10 -10
- package/dist/tools/approve.js +1 -0
- package/dist/tools/attach-image.js +12 -0
- package/dist/tools/bash.js +27 -4
- package/dist/tools/browser.js +1 -0
- package/dist/tools/chunk-query.js +1 -0
- package/dist/tools/create-dir.js +1 -0
- package/dist/tools/delete-file.js +1 -0
- package/dist/tools/download-file.js +1 -0
- package/dist/tools/edit-file.js +2 -1
- package/dist/tools/enable-tools.js +1 -0
- package/dist/tools/executor.js +17 -7
- package/dist/tools/file-info.js +1 -0
- package/dist/tools/glob-tool.js +1 -0
- package/dist/tools/grep-tool.js +54 -13
- package/dist/tools/list-dir.js +1 -0
- package/dist/tools/load-skill.js +1 -0
- package/dist/tools/mcp-call.js +1 -0
- package/dist/tools/move-file.js +1 -0
- package/dist/tools/path-utils.js +51 -1
- package/dist/tools/pipeline-run.js +1 -0
- package/dist/tools/process-kill.js +11 -0
- package/dist/tools/process-list.js +1 -0
- package/dist/tools/process-log.js +9 -0
- package/dist/tools/question.js +1 -0
- package/dist/tools/read-file.js +94 -6
- package/dist/tools/recall.js +1 -0
- package/dist/tools/remember.js +1 -0
- package/dist/tools/scope-check.js +7 -5
- package/dist/tools/search-history.js +1 -0
- package/dist/tools/subagent.js +4 -4
- package/dist/tools/web-browse.js +1 -0
- package/dist/tools/web-fetch.js +27 -6
- package/dist/tools/web-search.js +70 -43
- package/dist/tools/write-file.js +1 -0
- package/dist/ui/line-editor.js +142 -23
- package/dist/ui/line-math.js +8 -4
- package/dist/ui/renderer.js +57 -7
- package/package.json +50 -48
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
2
|
+
import { spawnSync } from "child_process";
|
|
3
|
+
import { createRequire } from "module";
|
|
4
|
+
import { join, dirname } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { arch, homedir, hostname, platform, release } from "os";
|
|
7
|
+
import { env } from "process";
|
|
8
|
+
import { readMmaVersion } from "./version";
|
|
9
|
+
import { sanitizeUrl } from "../modules/security/network-validator";
|
|
10
|
+
import { t } from "../i18n/index";
|
|
11
|
+
/** Tools probed for availability on PATH (real executables only — no .cmd shims). */
|
|
12
|
+
const TOOL_CHECKS = ["bun", "node", "git", "python"];
|
|
13
|
+
function readEngineRequirement() {
|
|
14
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const candidates = [join(here, "..", "..", "package.json"), join(here, "..", "package.json")];
|
|
16
|
+
for (const p of candidates) {
|
|
17
|
+
if (!existsSync(p))
|
|
18
|
+
continue;
|
|
19
|
+
try {
|
|
20
|
+
const raw = JSON.parse(readFileSync(p, "utf8"));
|
|
21
|
+
if (raw.engines?.node)
|
|
22
|
+
return String(raw.engines.node);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Broken package.json — fall through
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return ">=20";
|
|
29
|
+
}
|
|
30
|
+
/** Numeric tuple comparison: does `version` meet the minimum in `requirement` (e.g. `>=20`)? */
|
|
31
|
+
export function satisfiesMinimum(version, requirement) {
|
|
32
|
+
const minMatch = requirement.match(/(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
|
|
33
|
+
if (!minMatch)
|
|
34
|
+
return true;
|
|
35
|
+
const min = [
|
|
36
|
+
parseInt(minMatch[1], 10),
|
|
37
|
+
minMatch[2] ? parseInt(minMatch[2], 10) : 0,
|
|
38
|
+
minMatch[3] ? parseInt(minMatch[3], 10) : 0,
|
|
39
|
+
];
|
|
40
|
+
const parts = version.replace(/^v/i, "").split(".");
|
|
41
|
+
const got = [
|
|
42
|
+
parts[0] ? parseInt(parts[0], 10) : 0,
|
|
43
|
+
parts[1] ? parseInt(parts[1], 10) : 0,
|
|
44
|
+
parts[2] ? parseInt(parts[2], 10) : 0,
|
|
45
|
+
];
|
|
46
|
+
for (let i = 0; i < 3; i++) {
|
|
47
|
+
if (got[i] > min[i])
|
|
48
|
+
return true;
|
|
49
|
+
if (got[i] < min[i])
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
export function detectRuntime() {
|
|
55
|
+
const bun = globalThis.Bun;
|
|
56
|
+
const isBun = typeof bun !== "undefined" && typeof bun?.version !== "undefined";
|
|
57
|
+
const engine = readEngineRequirement();
|
|
58
|
+
const runtime = isBun ? "bun" : "node";
|
|
59
|
+
const runtimeVersion = isBun ? String(bun.version) : process.version;
|
|
60
|
+
return {
|
|
61
|
+
runtime,
|
|
62
|
+
runtimeVersion,
|
|
63
|
+
nodeVersion: process.version,
|
|
64
|
+
engine,
|
|
65
|
+
// engines.node applies to the Node runtime. Bun satisfies it through its
|
|
66
|
+
// embedded Node (process.version) — never compare the Bun version itself
|
|
67
|
+
// against a Node requirement (Bun 1.x would always look "too old").
|
|
68
|
+
engineOk: satisfiesMinimum(process.version, engine),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function toolVersion(cmd) {
|
|
72
|
+
try {
|
|
73
|
+
const res = spawnSync(cmd, ["--version"], {
|
|
74
|
+
encoding: "utf8",
|
|
75
|
+
timeout: 3000,
|
|
76
|
+
windowsHide: true,
|
|
77
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
78
|
+
});
|
|
79
|
+
if (res.error || res.status !== 0)
|
|
80
|
+
return "missing";
|
|
81
|
+
const out = ((res.stdout || "") + (res.stderr || "")).trim();
|
|
82
|
+
return out.split(/\r?\n/)[0].slice(0, 40) || "ok";
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return "missing";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Where Playwright keeps downloaded browser binaries (env override wins). */
|
|
89
|
+
export function playwrightBrowsersDir() {
|
|
90
|
+
if (process.env.PLAYWRIGHT_BROWSERS_PATH)
|
|
91
|
+
return process.env.PLAYWRIGHT_BROWSERS_PATH;
|
|
92
|
+
return process.platform === "win32"
|
|
93
|
+
? join(homedir(), "AppData", "Local", "ms-playwright")
|
|
94
|
+
: join(homedir(), ".cache", "ms-playwright");
|
|
95
|
+
}
|
|
96
|
+
/** Playwright package presence/version + whether browser binaries are downloaded. */
|
|
97
|
+
export function playwrightInfo() {
|
|
98
|
+
let installed = false;
|
|
99
|
+
let version = "";
|
|
100
|
+
const browsersDir = playwrightBrowsersDir();
|
|
101
|
+
try {
|
|
102
|
+
// Rule #12: resolve from disk at runtime — works in both src/ and dist/
|
|
103
|
+
// layouts, and under both Bun and Node (the bridge uses createRequire too).
|
|
104
|
+
const require = createRequire(import.meta.url);
|
|
105
|
+
const pkgPath = require.resolve("playwright/package.json");
|
|
106
|
+
installed = existsSync(pkgPath);
|
|
107
|
+
version = JSON.parse(readFileSync(pkgPath, "utf8")).version || "";
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
installed = false;
|
|
111
|
+
}
|
|
112
|
+
let browsersInstalled = false;
|
|
113
|
+
try {
|
|
114
|
+
if (existsSync(browsersDir)) {
|
|
115
|
+
browsersInstalled = readdirSync(browsersDir).some((d) => /chrom/i.test(d));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
browsersInstalled = false;
|
|
120
|
+
}
|
|
121
|
+
return { installed, version, browsersDir, browsersInstalled };
|
|
122
|
+
}
|
|
123
|
+
/** Pure requirements check — produces warnings from a report (no side effects). */
|
|
124
|
+
export function checkEnvironmentRequirements(report) {
|
|
125
|
+
const warnings = [];
|
|
126
|
+
if (report.runtime.runtime === "node") {
|
|
127
|
+
warnings.push(t("env.runtime_node", { version: report.runtime.nodeVersion }));
|
|
128
|
+
}
|
|
129
|
+
if (!report.runtime.engineOk) {
|
|
130
|
+
warnings.push(t("env.runtime_old", {
|
|
131
|
+
version: report.runtime.nodeVersion,
|
|
132
|
+
engine: report.runtime.engine,
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
if (report.tools.bun === "missing") {
|
|
136
|
+
warnings.push(t("env.tool_missing", { tool: "bun" }));
|
|
137
|
+
}
|
|
138
|
+
if (report.tools.git === "missing") {
|
|
139
|
+
warnings.push(t("env.tool_missing", { tool: "git" }));
|
|
140
|
+
}
|
|
141
|
+
if (report.features.browser && report.playwright) {
|
|
142
|
+
if (!report.playwright.installed) {
|
|
143
|
+
warnings.push(t("env.playwright_missing"));
|
|
144
|
+
}
|
|
145
|
+
else if (!report.playwright.browsersInstalled) {
|
|
146
|
+
warnings.push(t("env.playwright_browsers_missing", { dir: report.playwright.browsersDir }));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return warnings;
|
|
150
|
+
}
|
|
151
|
+
export function collectEnvironment(opts) {
|
|
152
|
+
const runtime = detectRuntime();
|
|
153
|
+
const tools = {};
|
|
154
|
+
if (opts.scanTools) {
|
|
155
|
+
for (const tool of TOOL_CHECKS)
|
|
156
|
+
tools[tool] = toolVersion(tool);
|
|
157
|
+
}
|
|
158
|
+
const provider = opts.config
|
|
159
|
+
? {
|
|
160
|
+
model: opts.config.model,
|
|
161
|
+
baseUrl: sanitizeUrl(opts.config.provider?.baseUrl || ""),
|
|
162
|
+
contextWindow: opts.config.contextWindow,
|
|
163
|
+
retry: {
|
|
164
|
+
maxRetries: opts.config.retry?.maxRetries ?? 0,
|
|
165
|
+
baseDelay: opts.config.retry?.baseDelay ?? 0,
|
|
166
|
+
maxDelay: opts.config.retry?.maxDelay ?? 0,
|
|
167
|
+
maxStreamRetries: opts.config.retry?.maxStreamRetries ?? 0,
|
|
168
|
+
noDataTimeoutMs: opts.config.retry?.noDataTimeoutMs ?? 0,
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
: null;
|
|
172
|
+
const report = {
|
|
173
|
+
ts: new Date().toISOString(),
|
|
174
|
+
mmaVersion: readMmaVersion(),
|
|
175
|
+
runtime,
|
|
176
|
+
os: {
|
|
177
|
+
platform: platform(),
|
|
178
|
+
arch: arch(),
|
|
179
|
+
release: release(),
|
|
180
|
+
hostname: hostname(),
|
|
181
|
+
shell: env.SHELL || env.ComSpec || "unknown",
|
|
182
|
+
home: homedir(),
|
|
183
|
+
cwd: process.cwd(),
|
|
184
|
+
},
|
|
185
|
+
paths: { configDir: opts.configDir, baseDir: opts.baseDir || process.cwd() },
|
|
186
|
+
tools,
|
|
187
|
+
provider,
|
|
188
|
+
features: { browser: Boolean(opts.config?.browser?.enabled) },
|
|
189
|
+
playwright: playwrightInfo(),
|
|
190
|
+
warnings: [],
|
|
191
|
+
};
|
|
192
|
+
report.warnings = checkEnvironmentRequirements(report);
|
|
193
|
+
return report;
|
|
194
|
+
}
|
|
195
|
+
/** Compact human line + full structured report into the JSONL log. */
|
|
196
|
+
export function logEnvironment(report, logger) {
|
|
197
|
+
logger.info(`Environment: ${report.os.platform} ${report.os.arch} | ${report.runtime.runtime} ${report.runtime.runtimeVersion} (node ${report.runtime.nodeVersion}) | mma ${report.mmaVersion} | ${report.provider?.model ?? "no provider"}`);
|
|
198
|
+
logger.logStructured("environment", report);
|
|
199
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { setAuditSessionDir } from "../modules/security/audit-log";
|
|
2
|
+
import { sanitizeLogMessage } from "../modules/security/data-sanitizer";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { readFileSync, existsSync } from "fs";
|
|
2
5
|
/**
|
|
3
6
|
* Thin wrapper around SessionManager that eliminates repetitive
|
|
4
7
|
* `if (sessionManager)` + `new Date().toISOString()` boilerplate
|
|
@@ -9,9 +12,11 @@ import { setAuditSessionDir } from "../modules/security/audit-log";
|
|
|
9
12
|
*/
|
|
10
13
|
export class SessionLogger {
|
|
11
14
|
session;
|
|
15
|
+
getCaller;
|
|
12
16
|
logger;
|
|
13
|
-
constructor(session, logger) {
|
|
17
|
+
constructor(session, logger, getCaller) {
|
|
14
18
|
this.session = session;
|
|
19
|
+
this.getCaller = getCaller;
|
|
15
20
|
this.logger = logger;
|
|
16
21
|
this.bindSessionDir();
|
|
17
22
|
}
|
|
@@ -37,24 +42,28 @@ export class SessionLogger {
|
|
|
37
42
|
});
|
|
38
43
|
}
|
|
39
44
|
logUser(content) {
|
|
45
|
+
const sanitized = sanitizeLogMessage(content);
|
|
40
46
|
this.session?.appendMessage({
|
|
41
47
|
role: "user",
|
|
42
|
-
content,
|
|
48
|
+
content: sanitized,
|
|
43
49
|
timestamp: new Date().toISOString(),
|
|
44
50
|
});
|
|
45
51
|
this.session?.appendLog({
|
|
46
52
|
ts: new Date().toISOString(),
|
|
47
53
|
type: "user",
|
|
48
|
-
content,
|
|
54
|
+
content: sanitized,
|
|
49
55
|
});
|
|
50
56
|
}
|
|
51
57
|
logAssistant(content, reasoning, toolCalls, iteration) {
|
|
58
|
+
const caller = this.getCaller?.();
|
|
52
59
|
if (reasoning) {
|
|
53
60
|
this.session?.appendLog({
|
|
54
61
|
ts: new Date().toISOString(),
|
|
55
62
|
type: "reasoning",
|
|
56
63
|
content: reasoning,
|
|
57
64
|
iteration,
|
|
65
|
+
provider: caller?.provider,
|
|
66
|
+
model: caller?.model,
|
|
58
67
|
});
|
|
59
68
|
}
|
|
60
69
|
this.session?.appendLog({
|
|
@@ -71,13 +80,18 @@ export class SessionLogger {
|
|
|
71
80
|
}
|
|
72
81
|
: {}),
|
|
73
82
|
iteration,
|
|
83
|
+
provider: caller?.provider,
|
|
84
|
+
model: caller?.model,
|
|
74
85
|
});
|
|
75
86
|
}
|
|
76
87
|
saveAssistantMessage(content) {
|
|
88
|
+
const caller = this.getCaller?.();
|
|
77
89
|
this.session?.appendMessage({
|
|
78
90
|
role: "assistant",
|
|
79
91
|
content: content.slice(0, 500),
|
|
80
92
|
timestamp: new Date().toISOString(),
|
|
93
|
+
provider: caller?.provider,
|
|
94
|
+
model: caller?.model,
|
|
81
95
|
});
|
|
82
96
|
}
|
|
83
97
|
logToolDefs(toolCount, toolNames, iteration) {
|
|
@@ -95,16 +109,19 @@ export class SessionLogger {
|
|
|
95
109
|
type: "tool_call",
|
|
96
110
|
tool: call.name,
|
|
97
111
|
tool_call_id: call.id,
|
|
98
|
-
args: call.arguments,
|
|
112
|
+
args: JSON.parse(sanitizeLogMessage(JSON.stringify(call.arguments))),
|
|
99
113
|
iteration,
|
|
100
114
|
});
|
|
101
115
|
}
|
|
102
116
|
logToolResult(call, result, duration, iteration) {
|
|
117
|
+
const caller = this.getCaller?.();
|
|
103
118
|
this.session?.appendMessage({
|
|
104
119
|
role: "tool",
|
|
105
|
-
content: result.output.slice(0, 500),
|
|
120
|
+
content: sanitizeLogMessage(result.output.slice(0, 500)),
|
|
106
121
|
name: call.name,
|
|
107
122
|
timestamp: new Date().toISOString(),
|
|
123
|
+
provider: caller?.provider,
|
|
124
|
+
model: caller?.model,
|
|
108
125
|
});
|
|
109
126
|
this.session?.appendLog({
|
|
110
127
|
ts: new Date().toISOString(),
|
|
@@ -112,10 +129,12 @@ export class SessionLogger {
|
|
|
112
129
|
tool: call.name,
|
|
113
130
|
tool_call_id: call.id,
|
|
114
131
|
success: result.success,
|
|
115
|
-
content: result.output.slice(0, 1000),
|
|
132
|
+
content: sanitizeLogMessage(result.output.slice(0, 1000)),
|
|
116
133
|
diff: result.diff,
|
|
117
134
|
duration,
|
|
118
135
|
iteration,
|
|
136
|
+
provider: caller?.provider,
|
|
137
|
+
model: caller?.model,
|
|
119
138
|
});
|
|
120
139
|
}
|
|
121
140
|
logCompaction(info) {
|
|
@@ -144,6 +163,8 @@ export class SessionLogger {
|
|
|
144
163
|
? "context snapshot at session start"
|
|
145
164
|
: `context at iteration ${info.iteration}`,
|
|
146
165
|
iteration: info.iteration,
|
|
166
|
+
provider: info.provider,
|
|
167
|
+
model: info.model,
|
|
147
168
|
window: info.window,
|
|
148
169
|
systemBudget: info.systemBudget,
|
|
149
170
|
reserveBudget: info.reserveBudget,
|
|
@@ -163,6 +184,8 @@ export class SessionLogger {
|
|
|
163
184
|
ts: new Date().toISOString(),
|
|
164
185
|
type: "llm_usage",
|
|
165
186
|
iteration,
|
|
187
|
+
provider: usage.provider,
|
|
188
|
+
model: usage.model,
|
|
166
189
|
promptTokens: usage.promptTokens,
|
|
167
190
|
completionTokens: usage.completionTokens,
|
|
168
191
|
totalTokens: usage.totalTokens,
|
|
@@ -194,4 +217,35 @@ export class SessionLogger {
|
|
|
194
217
|
...(iteration !== undefined ? { iteration } : {}),
|
|
195
218
|
});
|
|
196
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Log session startup diagnostics: environment, LSP probe, plugins, skills.
|
|
222
|
+
* Should be the FIRST entry in session.jsonl so the file is self-describing.
|
|
223
|
+
* Skips if a session_start entry already exists (e.g. logged by REPL).
|
|
224
|
+
*/
|
|
225
|
+
logSessionStart(data) {
|
|
226
|
+
// Skip if session_start already logged (REPL may have logged it with LSP probe)
|
|
227
|
+
const meta = this.session?.getActiveMeta();
|
|
228
|
+
if (meta) {
|
|
229
|
+
const logPath = join(this.session.getSessionDirectory(meta.id), "session.jsonl");
|
|
230
|
+
try {
|
|
231
|
+
if (existsSync(logPath)) {
|
|
232
|
+
const content = readFileSync(logPath, "utf-8");
|
|
233
|
+
if (content.includes('"type":"session_start"'))
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
// File read error — proceed to write
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
this.session?.appendLog({
|
|
242
|
+
ts: new Date().toISOString(),
|
|
243
|
+
type: "session_start",
|
|
244
|
+
content: "session started",
|
|
245
|
+
environment: data.environment,
|
|
246
|
+
lspProbe: data.lspProbe,
|
|
247
|
+
plugins: data.plugins,
|
|
248
|
+
skills: data.skills,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
197
251
|
}
|