odai-dsh-agent 0.2.5 → 0.2.6
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 +3 -3
- package/build/bin/odai-dsh-agent.d.mts +3 -0
- package/build/bin/odai-dsh-agent.d.mts.map +1 -0
- package/build/bin/odai-dsh-agent.mjs +114 -0
- package/build/bin/odai-dsh-agent.mjs.map +1 -0
- package/build/src/dsh-version.d.mts +16 -0
- package/build/src/dsh-version.d.mts.map +1 -0
- package/build/src/dsh-version.mjs +20 -0
- package/build/src/dsh-version.mjs.map +1 -0
- package/build/src/installer.d.mts +46 -0
- package/build/src/installer.d.mts.map +1 -0
- package/build/src/installer.mjs +397 -0
- package/build/src/installer.mjs.map +1 -0
- package/package.json +13 -12
- package/preset/odai/runtime/compaction-config.d.mts +66 -0
- package/preset/odai/runtime/compaction-config.d.mts.map +1 -0
- package/preset/odai/runtime/compaction-config.mjs +320 -342
- package/preset/odai/runtime/compaction-config.mjs.map +1 -0
- package/preset/odai/runtime/compaction-runtime.d.mts +68 -0
- package/preset/odai/runtime/compaction-runtime.d.mts.map +1 -0
- package/preset/odai/runtime/compaction-runtime.mjs +157 -0
- package/preset/odai/runtime/compaction-runtime.mjs.map +1 -0
- package/preset/odai/runtime/context-activation.d.mts +31 -0
- package/preset/odai/runtime/context-activation.d.mts.map +1 -0
- package/preset/odai/runtime/context-activation.mjs +55 -49
- package/preset/odai/runtime/context-activation.mjs.map +1 -0
- package/preset/odai/runtime/context-capability.d.mts +16 -0
- package/preset/odai/runtime/context-capability.d.mts.map +1 -0
- package/preset/odai/runtime/context-capability.mjs +79 -74
- package/preset/odai/runtime/context-capability.mjs.map +1 -0
- package/preset/odai/runtime/governance.d.mts +30 -0
- package/preset/odai/runtime/governance.d.mts.map +1 -0
- package/preset/odai/runtime/governance.mjs +126 -113
- package/preset/odai/runtime/governance.mjs.map +1 -0
- package/preset/odai/runtime/human-care.d.mts +13 -0
- package/preset/odai/runtime/human-care.d.mts.map +1 -0
- package/preset/odai/runtime/human-care.mjs +45 -42
- package/preset/odai/runtime/human-care.mjs.map +1 -0
- package/preset/odai/runtime/human-safety-continuity-store.d.mts +43 -0
- package/preset/odai/runtime/human-safety-continuity-store.d.mts.map +1 -0
- package/preset/odai/runtime/human-safety-continuity-store.mjs +193 -215
- package/preset/odai/runtime/human-safety-continuity-store.mjs.map +1 -0
- package/preset/odai/runtime/human-safety-continuity.d.mts +27 -0
- package/preset/odai/runtime/human-safety-continuity.d.mts.map +1 -0
- package/preset/odai/runtime/human-safety-continuity.mjs +205 -216
- package/preset/odai/runtime/human-safety-continuity.mjs.map +1 -0
- package/preset/odai/runtime/human-safety.d.mts +14 -0
- package/preset/odai/runtime/human-safety.d.mts.map +1 -0
- package/preset/odai/runtime/human-safety.mjs +50 -47
- package/preset/odai/runtime/human-safety.mjs.map +1 -0
- package/preset/odai/runtime/index.d.mts +8 -0
- package/preset/odai/runtime/index.d.mts.map +1 -0
- package/preset/odai/runtime/index.mjs +161 -2473
- package/preset/odai/runtime/index.mjs.map +1 -0
- package/preset/odai/runtime/lifecycle-runtime.d.mts +83 -0
- package/preset/odai/runtime/lifecycle-runtime.d.mts.map +1 -0
- package/preset/odai/runtime/lifecycle-runtime.mjs +1081 -0
- package/preset/odai/runtime/lifecycle-runtime.mjs.map +1 -0
- package/preset/odai/runtime/model-route.d.mts +22 -0
- package/preset/odai/runtime/model-route.d.mts.map +1 -0
- package/preset/odai/runtime/model-route.mjs +68 -60
- package/preset/odai/runtime/model-route.mjs.map +1 -0
- package/preset/odai/runtime/output-config.d.mts +54 -0
- package/preset/odai/runtime/output-config.d.mts.map +1 -0
- package/preset/odai/runtime/output-config.mjs +289 -315
- package/preset/odai/runtime/output-config.mjs.map +1 -0
- package/preset/odai/runtime/output-policy-state.d.mts +3 -0
- package/preset/odai/runtime/output-policy-state.d.mts.map +1 -0
- package/preset/odai/runtime/output-policy-state.mjs +41 -29
- package/preset/odai/runtime/output-policy-state.mjs.map +1 -0
- package/preset/odai/runtime/prompt-runtime.d.mts +55 -0
- package/preset/odai/runtime/prompt-runtime.d.mts.map +1 -0
- package/preset/odai/runtime/prompt-runtime.mjs +303 -0
- package/preset/odai/runtime/prompt-runtime.mjs.map +1 -0
- package/preset/odai/runtime/research-packet.d.mts +30 -0
- package/preset/odai/runtime/research-packet.d.mts.map +1 -0
- package/preset/odai/runtime/research-packet.mjs +116 -117
- package/preset/odai/runtime/research-packet.mjs.map +1 -0
- package/preset/odai/runtime/responsibility-gap.d.mts +26 -0
- package/preset/odai/runtime/responsibility-gap.d.mts.map +1 -0
- package/preset/odai/runtime/responsibility-gap.mjs +91 -85
- package/preset/odai/runtime/responsibility-gap.mjs.map +1 -0
- package/preset/odai/runtime/responsibility-scope.d.mts +69 -0
- package/preset/odai/runtime/responsibility-scope.d.mts.map +1 -0
- package/preset/odai/runtime/responsibility-scope.mjs +199 -171
- package/preset/odai/runtime/responsibility-scope.mjs.map +1 -0
- package/preset/odai/runtime/role-overlays.d.mts +5 -0
- package/preset/odai/runtime/role-overlays.d.mts.map +1 -0
- package/preset/odai/runtime/role-overlays.mjs +14 -13
- package/preset/odai/runtime/role-overlays.mjs.map +1 -0
- package/preset/odai/runtime/route-card.d.mts +42 -0
- package/preset/odai/runtime/route-card.d.mts.map +1 -0
- package/preset/odai/runtime/route-card.mjs +158 -146
- package/preset/odai/runtime/route-card.mjs.map +1 -0
- package/preset/odai/runtime/router.d.mts +79 -0
- package/preset/odai/runtime/router.d.mts.map +1 -0
- package/preset/odai/runtime/router.mjs +491 -559
- package/preset/odai/runtime/router.mjs.map +1 -0
- package/preset/odai/runtime/routing-config.d.mts +76 -0
- package/preset/odai/runtime/routing-config.d.mts.map +1 -0
- package/preset/odai/runtime/routing-config.mjs +373 -438
- package/preset/odai/runtime/routing-config.mjs.map +1 -0
- package/preset/odai/runtime/routing-context.d.mts +47 -0
- package/preset/odai/runtime/routing-context.d.mts.map +1 -0
- package/preset/odai/runtime/routing-context.mjs +238 -273
- package/preset/odai/runtime/routing-context.mjs.map +1 -0
- package/preset/odai/runtime/runtime-config.d.mts +24 -0
- package/preset/odai/runtime/runtime-config.d.mts.map +1 -0
- package/preset/odai/runtime/runtime-config.mjs +181 -0
- package/preset/odai/runtime/runtime-config.mjs.map +1 -0
- package/preset/odai/runtime/runtime-support.d.mts +91 -0
- package/preset/odai/runtime/runtime-support.d.mts.map +1 -0
- package/preset/odai/runtime/runtime-support.mjs +371 -0
- package/preset/odai/runtime/runtime-support.mjs.map +1 -0
- package/preset/odai/runtime/runtime-types.d.mts +235 -0
- package/preset/odai/runtime/runtime-types.d.mts.map +1 -0
- package/preset/odai/runtime/runtime-types.mjs +4 -0
- package/preset/odai/runtime/runtime-types.mjs.map +1 -0
- package/preset/odai/runtime/semantic-memory-store.d.mts +110 -0
- package/preset/odai/runtime/semantic-memory-store.d.mts.map +1 -0
- package/preset/odai/runtime/semantic-memory-store.mjs +271 -385
- package/preset/odai/runtime/semantic-memory-store.mjs.map +1 -0
- package/preset/odai/runtime/semantic-memory.d.mts +88 -0
- package/preset/odai/runtime/semantic-memory.d.mts.map +1 -0
- package/preset/odai/runtime/semantic-memory.mjs +790 -750
- package/preset/odai/runtime/semantic-memory.mjs.map +1 -0
- package/preset/odai/runtime/session-compat.d.mts +31 -0
- package/preset/odai/runtime/session-compat.d.mts.map +1 -0
- package/preset/odai/runtime/session-compat.mjs +301 -328
- package/preset/odai/runtime/session-compat.mjs.map +1 -0
- package/preset/odai/runtime/session-evidence.d.mts +25 -0
- package/preset/odai/runtime/session-evidence.d.mts.map +1 -0
- package/preset/odai/runtime/session-evidence.mjs +309 -287
- package/preset/odai/runtime/session-evidence.mjs.map +1 -0
- package/preset/odai/runtime/skill-bundle.d.mts +53 -0
- package/preset/odai/runtime/skill-bundle.d.mts.map +1 -0
- package/preset/odai/runtime/skill-bundle.mjs +286 -276
- package/preset/odai/runtime/skill-bundle.mjs.map +1 -0
- package/preset/odai/runtime/skill-evolution.d.mts +112 -0
- package/preset/odai/runtime/skill-evolution.d.mts.map +1 -0
- package/preset/odai/runtime/skill-evolution.mjs +1287 -1261
- package/preset/odai/runtime/skill-evolution.mjs.map +1 -0
- package/preset/odai/runtime/skill-selection-state.d.mts +5 -0
- package/preset/odai/runtime/skill-selection-state.d.mts.map +1 -0
- package/preset/odai/runtime/skill-selection-state.mjs +59 -43
- package/preset/odai/runtime/skill-selection-state.mjs.map +1 -0
- package/preset/odai/runtime/skill-selector.d.mts +28 -0
- package/preset/odai/runtime/skill-selector.d.mts.map +1 -0
- package/preset/odai/runtime/skill-selector.mjs +100 -109
- package/preset/odai/runtime/skill-selector.mjs.map +1 -0
- package/preset/odai/runtime/skill-source-config.d.mts +32 -0
- package/preset/odai/runtime/skill-source-config.d.mts.map +1 -0
- package/preset/odai/runtime/skill-source-config.mjs +200 -210
- package/preset/odai/runtime/skill-source-config.mjs.map +1 -0
- package/preset/odai/runtime/store-lock.d.mts +2 -0
- package/preset/odai/runtime/store-lock.d.mts.map +1 -0
- package/preset/odai/runtime/store-lock.mjs +92 -87
- package/preset/odai/runtime/store-lock.mjs.map +1 -0
- package/preset/odai/runtime/tool-runtime.d.mts +43 -0
- package/preset/odai/runtime/tool-runtime.d.mts.map +1 -0
- package/preset/odai/runtime/tool-runtime.mjs +221 -0
- package/preset/odai/runtime/tool-runtime.mjs.map +1 -0
- package/bin/odai-dsh-agent.mjs +0 -108
- package/scripts/verify-agent-load.mjs +0 -260
- package/src/dsh-version.mjs +0 -24
- package/src/installer.mjs +0 -385
|
@@ -1,389 +1,362 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
-
import {
|
|
4
|
-
closeSync,
|
|
5
|
-
constants as fsConstants,
|
|
6
|
-
copyFileSync,
|
|
7
|
-
existsSync,
|
|
8
|
-
fsyncSync,
|
|
9
|
-
openSync,
|
|
10
|
-
readFileSync,
|
|
11
|
-
readdirSync,
|
|
12
|
-
renameSync,
|
|
13
|
-
rmSync,
|
|
14
|
-
statSync,
|
|
15
|
-
writeSync,
|
|
16
|
-
} from "node:fs";
|
|
3
|
+
import { closeSync, constants as fsConstants, copyFileSync, existsSync, fsyncSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeSync, } from "node:fs";
|
|
17
4
|
import { homedir } from "node:os";
|
|
18
5
|
import { dirname, resolve, sep } from "node:path";
|
|
19
6
|
import * as zlib from "node:zlib";
|
|
20
|
-
|
|
7
|
+
import { isUnknownRecord } from "./runtime-types.mjs";
|
|
21
8
|
const ZSTD_MAGIC = 4247762216;
|
|
22
9
|
const ODAI_EVENT_PREFIX = "odai/";
|
|
23
10
|
const LEGACY_ODAI_AUDIT_EVENT_TYPES = new Set([
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"odai/route-config-missing",
|
|
28
|
-
"odai/route-decided",
|
|
29
|
-
"odai/route-protection",
|
|
30
|
-
"odai/route-result",
|
|
31
|
-
"odai/route-upgrade",
|
|
32
|
-
"odai/routing-configured",
|
|
33
|
-
"odai/tool-observed",
|
|
11
|
+
"odai/governance-denied", "odai/research-decided", "odai/research-result", "odai/route-config-missing",
|
|
12
|
+
"odai/route-decided", "odai/route-protection", "odai/route-result", "odai/route-upgrade",
|
|
13
|
+
"odai/routing-configured", "odai/tool-observed",
|
|
34
14
|
]);
|
|
35
|
-
|
|
36
15
|
export function resolveDshHome(configured, env = process.env) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
16
|
+
const value = configured ?? env.DSH_HOME ?? resolve(homedir(), ".dsh");
|
|
17
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
18
|
+
throw new TypeError("DSH home must be a non-empty path");
|
|
19
|
+
const trimmed = value.trim();
|
|
20
|
+
if (trimmed === "~")
|
|
21
|
+
return homedir();
|
|
22
|
+
if (trimmed.startsWith(`~${sep}`))
|
|
23
|
+
return resolve(homedir(), trimmed.slice(2));
|
|
24
|
+
return resolve(trimmed);
|
|
45
25
|
}
|
|
46
|
-
|
|
47
26
|
function isDshCommand(commandLine) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
27
|
+
if (typeof commandLine !== "string")
|
|
28
|
+
return false;
|
|
29
|
+
return /@deepseek-ai[\\/]dsh(?:[\\/]|$)/iu.test(commandLine)
|
|
30
|
+
|| /(?:^|[\s"'])[^\s"']*[\\/]dsh(?:\.cmd|\.mjs|\.js)?(?=$|[\s"'])/iu.test(commandLine)
|
|
31
|
+
|| /(?:^|[\s"'])dsh(?:\.cmd)?(?=$|[\s"'])/iu.test(commandLine);
|
|
52
32
|
}
|
|
53
|
-
|
|
54
33
|
function listWindowsProcesses() {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
34
|
+
const command = "Get-CimInstance Win32_Process | Select-Object ProcessId,Name,CommandLine | ConvertTo-Json -Compress";
|
|
35
|
+
const output = execFileSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", command], {
|
|
36
|
+
encoding: "utf8", windowsHide: true,
|
|
37
|
+
}).trim();
|
|
38
|
+
if (output === "")
|
|
39
|
+
return [];
|
|
40
|
+
const parsed = JSON.parse(output);
|
|
41
|
+
return (Array.isArray(parsed) ? parsed : [parsed]).flatMap((entry) => {
|
|
42
|
+
if (!isUnknownRecord(entry))
|
|
43
|
+
return [];
|
|
44
|
+
return [{
|
|
45
|
+
pid: Number(entry.ProcessId),
|
|
46
|
+
name: typeof entry.Name === "string" ? entry.Name : "unknown",
|
|
47
|
+
commandLine: entry.CommandLine,
|
|
48
|
+
}];
|
|
49
|
+
});
|
|
67
50
|
}
|
|
68
|
-
|
|
69
51
|
function listPosixProcesses() {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
52
|
+
const output = execFileSync("ps", ["-axo", "pid=,comm=,args="], { encoding: "utf8" });
|
|
53
|
+
return output.split(/\r?\n/u).flatMap((line) => {
|
|
54
|
+
const matched = /^\s*(\d+)\s+(\S+)\s+(.*)$/u.exec(line);
|
|
55
|
+
return matched ? [{ pid: Number(matched[1]), name: matched[2], commandLine: matched[3] }] : [];
|
|
56
|
+
});
|
|
75
57
|
}
|
|
76
|
-
|
|
77
58
|
export function listActiveDshProcesses(platform = process.platform) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
59
|
+
const processes = platform === "win32" ? listWindowsProcesses() : listPosixProcesses();
|
|
60
|
+
return processes
|
|
61
|
+
.filter((entry) => Number.isSafeInteger(entry.pid) && entry.pid > 0 && entry.pid !== process.pid)
|
|
62
|
+
.filter((entry) => isDshCommand(entry.commandLine))
|
|
63
|
+
.map(({ pid, name }) => Object.freeze({ pid, name }));
|
|
83
64
|
}
|
|
84
|
-
|
|
85
65
|
function assertDshStopped(options) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
66
|
+
let active;
|
|
67
|
+
try {
|
|
68
|
+
const scanner = options.processScanner ?? listActiveDshProcesses;
|
|
69
|
+
active = scanner();
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
throw new Error("cannot verify that DSH is stopped; run the repair from a normal terminal with permission to inspect local process command lines");
|
|
73
|
+
}
|
|
74
|
+
if (!Array.isArray(active))
|
|
75
|
+
throw new Error("DSH process scanner returned an invalid result");
|
|
76
|
+
if (active.length > 0) {
|
|
77
|
+
const owners = active.map((entry) => `${entry.name ?? "dsh"} (pid ${entry.pid ?? "unknown"})`).join(", ");
|
|
78
|
+
throw new Error(`refusing to rewrite session artifacts while DSH is running: ${owners}`);
|
|
79
|
+
}
|
|
98
80
|
}
|
|
99
|
-
|
|
100
81
|
function listSessionArtifacts(root) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
82
|
+
if (!existsSync(root))
|
|
83
|
+
return [];
|
|
84
|
+
const found = [];
|
|
85
|
+
const pending = [root];
|
|
86
|
+
while (pending.length > 0) {
|
|
87
|
+
const current = pending.pop();
|
|
88
|
+
if (!current)
|
|
89
|
+
break;
|
|
90
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
91
|
+
const path = resolve(current, entry.name);
|
|
92
|
+
if (entry.isDirectory())
|
|
93
|
+
pending.push(path);
|
|
94
|
+
else if (entry.isFile() && (entry.name === "session.jsonl" || entry.name === "session.jsonl.zstd"))
|
|
95
|
+
found.push(path);
|
|
96
|
+
}
|
|
112
97
|
}
|
|
113
|
-
|
|
114
|
-
return found.sort();
|
|
98
|
+
return found.sort();
|
|
115
99
|
}
|
|
116
|
-
|
|
117
100
|
function transformLine(line) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
101
|
+
if (line === "")
|
|
102
|
+
return { line, repaired: 0 };
|
|
103
|
+
const parsed = JSON.parse(line);
|
|
104
|
+
if (!isUnknownRecord(parsed)
|
|
105
|
+
|| typeof parsed.type !== "string"
|
|
106
|
+
|| !parsed.type.startsWith(ODAI_EVENT_PREFIX)
|
|
107
|
+
|| parsed.ignorable === true)
|
|
108
|
+
return { line, repaired: 0 };
|
|
109
|
+
if (!LEGACY_ODAI_AUDIT_EVENT_TYPES.has(parsed.type)) {
|
|
110
|
+
throw new Error(`refusing unknown Odai session event type ${JSON.stringify(parsed.type)}; use a compatible Odai repair version`);
|
|
111
|
+
}
|
|
112
|
+
return { line: JSON.stringify({ ...parsed, ignorable: true }), repaired: 1 };
|
|
127
113
|
}
|
|
128
|
-
|
|
129
114
|
function transformCompleteJsonl(text) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
for (;;) {
|
|
134
|
-
const newline = text.indexOf("\n", offset);
|
|
135
|
-
if (newline < 0) break;
|
|
136
|
-
const raw = text.slice(offset, newline);
|
|
137
|
-
const carriageReturn = raw.endsWith("\r");
|
|
138
|
-
const line = carriageReturn ? raw.slice(0, -1) : raw;
|
|
139
|
-
const transformed = transformLine(line);
|
|
140
|
-
output.push(transformed.line, carriageReturn ? "\r\n" : "\n");
|
|
141
|
-
repaired += transformed.repaired;
|
|
142
|
-
offset = newline + 1;
|
|
143
|
-
}
|
|
144
|
-
const tornTail = text.slice(offset);
|
|
145
|
-
output.push(tornTail);
|
|
146
|
-
return { text: output.join(""), repaired, torn: tornTail.length > 0 };
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function scanZstdFrames(buffer) {
|
|
150
|
-
const frames = [];
|
|
151
|
-
let offset = 0;
|
|
152
|
-
while (offset < buffer.length) {
|
|
153
|
-
const start = offset;
|
|
154
|
-
if (buffer.length - offset < 4) return { frames, tornStart: start };
|
|
155
|
-
if (buffer.readUInt32LE(offset) !== ZSTD_MAGIC) {
|
|
156
|
-
throw new Error(`invalid Zstandard frame magic at byte ${offset}`);
|
|
157
|
-
}
|
|
158
|
-
offset += 4;
|
|
159
|
-
if (offset === buffer.length) return { frames, tornStart: start };
|
|
160
|
-
const descriptor = buffer.readUInt8(offset);
|
|
161
|
-
offset += 1;
|
|
162
|
-
if ((descriptor & 24) !== 0) throw new Error(`reserved Zstandard frame-header bit at byte ${offset - 1}`);
|
|
163
|
-
const contentSizeFlag = descriptor >>> 6;
|
|
164
|
-
const singleSegment = (descriptor & 32) !== 0;
|
|
165
|
-
const checksum = (descriptor & 4) !== 0;
|
|
166
|
-
const dictionaryFlag = descriptor & 3;
|
|
167
|
-
const dictionaryBytes = dictionaryFlag === 3 ? 4 : dictionaryFlag;
|
|
168
|
-
const contentSizeBytes = contentSizeFlag === 0 ? (singleSegment ? 1 : 0) : 1 << contentSizeFlag;
|
|
169
|
-
const remainingHeaderBytes = (singleSegment ? 0 : 1) + dictionaryBytes + contentSizeBytes;
|
|
170
|
-
if (buffer.length - offset < remainingHeaderBytes) return { frames, tornStart: start };
|
|
171
|
-
offset += remainingHeaderBytes;
|
|
115
|
+
const output = [];
|
|
116
|
+
let repaired = 0;
|
|
117
|
+
let offset = 0;
|
|
172
118
|
for (;;) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (lastBlock) break;
|
|
119
|
+
const newline = text.indexOf("\n", offset);
|
|
120
|
+
if (newline < 0)
|
|
121
|
+
break;
|
|
122
|
+
const raw = text.slice(offset, newline);
|
|
123
|
+
const carriageReturn = raw.endsWith("\r");
|
|
124
|
+
const line = carriageReturn ? raw.slice(0, -1) : raw;
|
|
125
|
+
const transformed = transformLine(line);
|
|
126
|
+
output.push(transformed.line, carriageReturn ? "\r\n" : "\n");
|
|
127
|
+
repaired += transformed.repaired;
|
|
128
|
+
offset = newline + 1;
|
|
184
129
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
130
|
+
const tornTail = text.slice(offset);
|
|
131
|
+
output.push(tornTail);
|
|
132
|
+
return { text: output.join(""), repaired, torn: tornTail.length > 0 };
|
|
133
|
+
}
|
|
134
|
+
function scanZstdFrames(buffer) {
|
|
135
|
+
const frames = [];
|
|
136
|
+
let offset = 0;
|
|
137
|
+
while (offset < buffer.length) {
|
|
138
|
+
const start = offset;
|
|
139
|
+
if (buffer.length - offset < 4)
|
|
140
|
+
return { frames, tornStart: start };
|
|
141
|
+
if (buffer.readUInt32LE(offset) !== ZSTD_MAGIC)
|
|
142
|
+
throw new Error(`invalid Zstandard frame magic at byte ${offset}`);
|
|
143
|
+
offset += 4;
|
|
144
|
+
if (offset === buffer.length)
|
|
145
|
+
return { frames, tornStart: start };
|
|
146
|
+
const descriptor = buffer.readUInt8(offset);
|
|
147
|
+
offset += 1;
|
|
148
|
+
if ((descriptor & 24) !== 0)
|
|
149
|
+
throw new Error(`reserved Zstandard frame-header bit at byte ${offset - 1}`);
|
|
150
|
+
const contentSizeFlag = descriptor >>> 6;
|
|
151
|
+
const singleSegment = (descriptor & 32) !== 0;
|
|
152
|
+
const checksum = (descriptor & 4) !== 0;
|
|
153
|
+
const dictionaryFlag = descriptor & 3;
|
|
154
|
+
const dictionaryBytes = dictionaryFlag === 3 ? 4 : dictionaryFlag;
|
|
155
|
+
const contentSizeBytes = contentSizeFlag === 0 ? (singleSegment ? 1 : 0) : 1 << contentSizeFlag;
|
|
156
|
+
const remainingHeaderBytes = (singleSegment ? 0 : 1) + dictionaryBytes + contentSizeBytes;
|
|
157
|
+
if (buffer.length - offset < remainingHeaderBytes)
|
|
158
|
+
return { frames, tornStart: start };
|
|
159
|
+
offset += remainingHeaderBytes;
|
|
160
|
+
for (;;) {
|
|
161
|
+
if (buffer.length - offset < 3)
|
|
162
|
+
return { frames, tornStart: start };
|
|
163
|
+
const blockHeader = buffer.readUIntLE(offset, 3);
|
|
164
|
+
offset += 3;
|
|
165
|
+
const lastBlock = (blockHeader & 1) !== 0;
|
|
166
|
+
const blockType = (blockHeader >>> 1) & 3;
|
|
167
|
+
const blockSize = blockHeader >>> 3;
|
|
168
|
+
if (blockType === 3)
|
|
169
|
+
throw new Error(`reserved Zstandard block type at byte ${offset - 3}`);
|
|
170
|
+
const payloadBytes = blockType === 1 ? 1 : blockSize;
|
|
171
|
+
if (buffer.length - offset < payloadBytes)
|
|
172
|
+
return { frames, tornStart: start };
|
|
173
|
+
offset += payloadBytes;
|
|
174
|
+
if (lastBlock)
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
if (checksum) {
|
|
178
|
+
if (buffer.length - offset < 4)
|
|
179
|
+
return { frames, tornStart: start };
|
|
180
|
+
offset += 4;
|
|
181
|
+
}
|
|
182
|
+
frames.push({ start, end: offset });
|
|
188
183
|
}
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
return { frames };
|
|
184
|
+
return { frames };
|
|
192
185
|
}
|
|
193
|
-
|
|
194
186
|
function assertZstdAvailable() {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
187
|
+
if (typeof zlib.zstdCompressSync !== "function" || typeof zlib.zstdDecompressSync !== "function") {
|
|
188
|
+
throw new Error("this Node.js build cannot migrate Zstandard session logs");
|
|
189
|
+
}
|
|
198
190
|
}
|
|
199
|
-
|
|
200
191
|
function compressZstdFrame(plaintext) {
|
|
201
|
-
|
|
202
|
-
params: { [zlib.constants.ZSTD_c_checksumFlag]: 1 },
|
|
203
|
-
});
|
|
192
|
+
return zlib.zstdCompressSync(plaintext, { params: { [zlib.constants.ZSTD_c_checksumFlag]: 1 } });
|
|
204
193
|
}
|
|
205
|
-
|
|
206
194
|
function transformZstd(source) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
195
|
+
assertZstdAvailable();
|
|
196
|
+
const { frames, tornStart } = scanZstdFrames(source);
|
|
197
|
+
if (frames.length === 0)
|
|
198
|
+
throw new Error("Zstandard session log has no complete header frame");
|
|
199
|
+
const output = [];
|
|
200
|
+
let repaired = 0;
|
|
201
|
+
for (const [index, frame] of frames.entries()) {
|
|
202
|
+
const encoded = source.subarray(frame.start, frame.end);
|
|
203
|
+
if (index === 0) {
|
|
204
|
+
const header = zlib.zstdDecompressSync(encoded).toString("utf8");
|
|
205
|
+
const headerValue = JSON.parse(header.trimEnd());
|
|
206
|
+
if (!header.endsWith("\n") || !isUnknownRecord(headerValue) || headerValue.type !== "session") {
|
|
207
|
+
throw new Error("Zstandard session log has an invalid header frame");
|
|
208
|
+
}
|
|
209
|
+
output.push(encoded);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const plaintext = zlib.zstdDecompressSync(encoded);
|
|
213
|
+
const transformed = transformCompleteJsonl(plaintext.toString("utf8"));
|
|
214
|
+
if (transformed.torn)
|
|
215
|
+
throw new Error("complete Zstandard event frame has an unterminated JSONL record");
|
|
216
|
+
repaired += transformed.repaired;
|
|
217
|
+
if (transformed.repaired === 0) {
|
|
218
|
+
output.push(encoded);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const replacementPlaintext = Buffer.from(transformed.text, "utf8");
|
|
222
|
+
const replacement = compressZstdFrame(replacementPlaintext);
|
|
223
|
+
if (!zlib.zstdDecompressSync(replacement).equals(replacementPlaintext))
|
|
224
|
+
throw new Error("Zstandard migration verification failed");
|
|
225
|
+
output.push(replacement);
|
|
229
226
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
throw new Error("Zstandard migration verification failed");
|
|
234
|
-
}
|
|
235
|
-
output.push(replacement);
|
|
236
|
-
}
|
|
237
|
-
if (tornStart !== undefined) output.push(source.subarray(tornStart));
|
|
238
|
-
return {
|
|
239
|
-
buffer: repaired > 0 ? Buffer.concat(output) : source,
|
|
240
|
-
repaired,
|
|
241
|
-
torn: tornStart !== undefined,
|
|
242
|
-
};
|
|
227
|
+
if (tornStart !== undefined)
|
|
228
|
+
output.push(source.subarray(tornStart));
|
|
229
|
+
return { buffer: repaired > 0 ? Buffer.concat(output) : source, repaired, torn: tornStart !== undefined };
|
|
243
230
|
}
|
|
244
|
-
|
|
245
231
|
function transformArtifact(path) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
source,
|
|
251
|
-
buffer: transformed.repaired > 0 ? Buffer.from(transformed.text, "utf8") : source,
|
|
252
|
-
repaired: transformed.repaired,
|
|
253
|
-
torn: transformed.torn,
|
|
254
|
-
};
|
|
232
|
+
const source = readFileSync(path);
|
|
233
|
+
if (path.endsWith(".zstd"))
|
|
234
|
+
return { source, ...transformZstd(source) };
|
|
235
|
+
const transformed = transformCompleteJsonl(source.toString("utf8"));
|
|
236
|
+
return { source, buffer: transformed.repaired > 0 ? Buffer.from(transformed.text, "utf8") : source, repaired: transformed.repaired, torn: transformed.torn };
|
|
255
237
|
}
|
|
256
|
-
|
|
257
238
|
function writeDurably(path, content, mode) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
239
|
+
const handle = openSync(path, "wx", mode);
|
|
240
|
+
try {
|
|
241
|
+
let offset = 0;
|
|
242
|
+
while (offset < content.length) {
|
|
243
|
+
const written = writeSync(handle, content, offset, content.length - offset);
|
|
244
|
+
if (written <= 0)
|
|
245
|
+
throw new Error(`short write while preparing session artifact: ${path}`);
|
|
246
|
+
offset += written;
|
|
247
|
+
}
|
|
248
|
+
fsyncSync(handle);
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
closeSync(handle);
|
|
265
252
|
}
|
|
266
|
-
fsyncSync(handle);
|
|
267
|
-
} finally {
|
|
268
|
-
closeSync(handle);
|
|
269
|
-
}
|
|
270
253
|
}
|
|
271
|
-
|
|
272
254
|
function syncDirectory(path) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
255
|
+
let handle;
|
|
256
|
+
try {
|
|
257
|
+
handle = openSync(path, "r");
|
|
258
|
+
fsyncSync(handle);
|
|
259
|
+
}
|
|
260
|
+
catch { }
|
|
261
|
+
finally {
|
|
262
|
+
if (handle !== undefined)
|
|
263
|
+
closeSync(handle);
|
|
264
|
+
}
|
|
282
265
|
}
|
|
283
|
-
|
|
284
266
|
function backupPathFor(path, source) {
|
|
285
|
-
|
|
286
|
-
|
|
267
|
+
const digest = createHash("sha256").update(source).digest("hex").slice(0, 16);
|
|
268
|
+
return `${path}.odai-compat-${digest}.bak`;
|
|
287
269
|
}
|
|
288
|
-
|
|
289
270
|
function createVerifiedBackup(path, source) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
271
|
+
const backupPath = backupPathFor(path, source);
|
|
272
|
+
if (!existsSync(backupPath)) {
|
|
273
|
+
copyFileSync(path, backupPath, fsConstants.COPYFILE_EXCL);
|
|
274
|
+
const handle = openSync(backupPath, "r+");
|
|
275
|
+
try {
|
|
276
|
+
fsyncSync(handle);
|
|
277
|
+
}
|
|
278
|
+
finally {
|
|
279
|
+
closeSync(handle);
|
|
280
|
+
}
|
|
281
|
+
syncDirectory(dirname(path));
|
|
298
282
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
throw new Error(`session backup does not match the migration source: ${backupPath}`);
|
|
303
|
-
}
|
|
304
|
-
return backupPath;
|
|
283
|
+
if (!readFileSync(backupPath).equals(source))
|
|
284
|
+
throw new Error(`session backup does not match the migration source: ${backupPath}`);
|
|
285
|
+
return backupPath;
|
|
305
286
|
}
|
|
306
|
-
|
|
307
287
|
function replaceAtomically(path, source, replacement, backupPath) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
published = true;
|
|
318
|
-
syncDirectory(dirname(path));
|
|
319
|
-
if (!readFileSync(path).equals(replacement)) throw new Error("published session artifact failed byte verification");
|
|
320
|
-
const verified = transformArtifact(path);
|
|
321
|
-
if (verified.repaired !== 0) throw new Error("published session artifact still contains required Odai events");
|
|
322
|
-
} catch (error) {
|
|
323
|
-
if (published) {
|
|
324
|
-
const restore = `${path}.odai-restore-${process.pid}-${randomUUID()}.tmp`;
|
|
325
|
-
try {
|
|
326
|
-
writeDurably(restore, readFileSync(backupPath), mode);
|
|
327
|
-
renameSync(restore, path);
|
|
288
|
+
if (!readFileSync(path).equals(source))
|
|
289
|
+
throw new Error("session artifact changed while compatibility migration was preparing it; stop every DSH process and retry");
|
|
290
|
+
const temporary = `${path}.odai-compat-${process.pid}-${randomUUID()}.tmp`;
|
|
291
|
+
const mode = statSync(path).mode & 0o777;
|
|
292
|
+
let published = false;
|
|
293
|
+
try {
|
|
294
|
+
writeDurably(temporary, replacement, mode);
|
|
295
|
+
renameSync(temporary, path);
|
|
296
|
+
published = true;
|
|
328
297
|
syncDirectory(dirname(path));
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
298
|
+
if (!readFileSync(path).equals(replacement))
|
|
299
|
+
throw new Error("published session artifact failed byte verification");
|
|
300
|
+
const verified = transformArtifact(path);
|
|
301
|
+
if (verified.repaired !== 0)
|
|
302
|
+
throw new Error("published session artifact still contains required Odai events");
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
if (published) {
|
|
306
|
+
const restore = `${path}.odai-restore-${process.pid}-${randomUUID()}.tmp`;
|
|
307
|
+
try {
|
|
308
|
+
writeDurably(restore, readFileSync(backupPath), mode);
|
|
309
|
+
renameSync(restore, path);
|
|
310
|
+
syncDirectory(dirname(path));
|
|
311
|
+
}
|
|
312
|
+
finally {
|
|
313
|
+
rmSync(restore, { force: true });
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
finally {
|
|
319
|
+
rmSync(temporary, { force: true });
|
|
332
320
|
}
|
|
333
|
-
throw error;
|
|
334
|
-
} finally {
|
|
335
|
-
rmSync(temporary, { force: true });
|
|
336
|
-
}
|
|
337
321
|
}
|
|
338
|
-
|
|
339
322
|
function processLegacySessionLogs(options, writeChanges) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
result.repairedArtifacts += 1;
|
|
368
|
-
result.repairedEvents += transformed.repaired;
|
|
369
|
-
} catch (error) {
|
|
370
|
-
result.failures.push({
|
|
371
|
-
path,
|
|
372
|
-
error: error instanceof Error ? error.message : String(error),
|
|
373
|
-
});
|
|
323
|
+
const dshHome = resolveDshHome(options.dshHome);
|
|
324
|
+
const sessionRoot = resolve(options.sessionRoot ?? resolve(dshHome, "sessions"));
|
|
325
|
+
const artifacts = listSessionArtifacts(sessionRoot);
|
|
326
|
+
const result = {
|
|
327
|
+
dshHome, sessionRoot, scannedArtifacts: artifacts.length, matchedArtifacts: 0, matchedEvents: 0,
|
|
328
|
+
repairedArtifacts: 0, repairedEvents: 0, backupPaths: [], tornArtifacts: [], failures: [],
|
|
329
|
+
};
|
|
330
|
+
for (const path of artifacts) {
|
|
331
|
+
try {
|
|
332
|
+
const transformed = transformArtifact(path);
|
|
333
|
+
if (transformed.torn)
|
|
334
|
+
result.tornArtifacts.push(path);
|
|
335
|
+
if (transformed.repaired === 0)
|
|
336
|
+
continue;
|
|
337
|
+
result.matchedArtifacts += 1;
|
|
338
|
+
result.matchedEvents += transformed.repaired;
|
|
339
|
+
if (!writeChanges)
|
|
340
|
+
continue;
|
|
341
|
+
const backupPath = createVerifiedBackup(path, transformed.source);
|
|
342
|
+
replaceAtomically(path, transformed.source, transformed.buffer, backupPath);
|
|
343
|
+
result.backupPaths.push(backupPath);
|
|
344
|
+
result.repairedArtifacts += 1;
|
|
345
|
+
result.repairedEvents += transformed.repaired;
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
result.failures.push({ path, error: error instanceof Error ? error.message : String(error) });
|
|
349
|
+
}
|
|
374
350
|
}
|
|
375
|
-
|
|
376
|
-
return result;
|
|
351
|
+
return result;
|
|
377
352
|
}
|
|
378
|
-
|
|
379
353
|
export function inspectLegacySessionLogs(options = {}) {
|
|
380
|
-
|
|
354
|
+
return processLegacySessionLogs(options, false);
|
|
381
355
|
}
|
|
382
|
-
|
|
383
356
|
export function repairLegacySessionLogs(options = {}) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
return processLegacySessionLogs(options, true);
|
|
357
|
+
if (options.confirmDshStopped !== true)
|
|
358
|
+
throw new Error("refusing to rewrite session artifacts until confirmDshStopped is true; stop every DSH process first");
|
|
359
|
+
assertDshStopped(options);
|
|
360
|
+
return processLegacySessionLogs(options, true);
|
|
389
361
|
}
|
|
362
|
+
//# sourceMappingURL=session-compat.mjs.map
|