odai-dsh-agent 0.2.5 → 0.2.7
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 +398 -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 +190 -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 +1140 -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 +93 -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 +15 -14
- 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 +80 -0
- package/preset/odai/runtime/router.d.mts.map +1 -0
- package/preset/odai/runtime/router.mjs +509 -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 +64 -0
- package/preset/odai/runtime/routing-context.d.mts.map +1 -0
- package/preset/odai/runtime/routing-context.mjs +398 -277
- 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 +237 -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,344 +1,366 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
closeSync,
|
|
4
|
-
existsSync,
|
|
5
|
-
fsyncSync,
|
|
6
|
-
ftruncateSync,
|
|
7
|
-
mkdirSync,
|
|
8
|
-
openSync,
|
|
9
|
-
readFileSync,
|
|
10
|
-
rmSync,
|
|
11
|
-
statSync,
|
|
12
|
-
writeSync,
|
|
13
|
-
} from "node:fs";
|
|
2
|
+
import { closeSync, existsSync, fsyncSync, ftruncateSync, mkdirSync, openSync, readFileSync, rmSync, statSync, writeSync, } from "node:fs";
|
|
14
3
|
import { dirname, resolve } from "node:path";
|
|
15
|
-
|
|
4
|
+
import { isUnknownRecord } from "./runtime-types.mjs";
|
|
16
5
|
const STORE_SCHEMA_VERSION = 1;
|
|
17
6
|
const GLOBAL_STATE_KEY = Symbol.for("odai.dsh.session-evidence.v2");
|
|
18
|
-
|
|
7
|
+
function isSessionEvidenceSharedState(value) {
|
|
8
|
+
return isUnknownRecord(value) && value.bySession instanceof WeakMap;
|
|
9
|
+
}
|
|
19
10
|
function sharedState() {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
11
|
+
const root = globalThis;
|
|
12
|
+
const existing = root[GLOBAL_STATE_KEY];
|
|
13
|
+
if (isSessionEvidenceSharedState(existing))
|
|
14
|
+
return existing;
|
|
15
|
+
const created = { bySession: new WeakMap() };
|
|
16
|
+
Object.defineProperty(root, GLOBAL_STATE_KEY, {
|
|
17
|
+
configurable: false,
|
|
18
|
+
enumerable: false,
|
|
19
|
+
writable: false,
|
|
20
|
+
value: created,
|
|
26
21
|
});
|
|
27
|
-
|
|
28
|
-
return globalThis[GLOBAL_STATE_KEY];
|
|
22
|
+
return created;
|
|
29
23
|
}
|
|
30
|
-
|
|
31
24
|
function isOdaiEvent(event) {
|
|
32
|
-
|
|
25
|
+
return isUnknownRecord(event)
|
|
26
|
+
&& typeof event.type === "string"
|
|
27
|
+
&& event.type.startsWith("odai/")
|
|
28
|
+
&& isUnknownRecord(event.data);
|
|
33
29
|
}
|
|
34
|
-
|
|
35
30
|
function sessionIdOf(agent) {
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
const id = agent?.session?.header?.id;
|
|
32
|
+
return typeof id === "string" && id !== "" ? id : undefined;
|
|
38
33
|
}
|
|
39
|
-
|
|
40
34
|
function evidencePath(root, sessionId) {
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
const key = createHash("sha256").update(sessionId).digest("hex");
|
|
36
|
+
return resolve(root, `${key}.jsonl`);
|
|
43
37
|
}
|
|
44
|
-
|
|
45
38
|
function stableJson(value) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
if (Array.isArray(value))
|
|
40
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
41
|
+
if (isUnknownRecord(value)) {
|
|
42
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(",")}}`;
|
|
43
|
+
}
|
|
44
|
+
return JSON.stringify(value);
|
|
51
45
|
}
|
|
52
|
-
|
|
53
46
|
function evidenceId(type, data) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
47
|
+
let identity;
|
|
48
|
+
if (Number.isSafeInteger(data?.turn) && Number.isSafeInteger(data?.step)) {
|
|
49
|
+
identity = `${type}:${data.turn}:${data.step}`;
|
|
50
|
+
}
|
|
51
|
+
else if (typeof data?.callId === "string" && data.callId !== "") {
|
|
52
|
+
identity = `${type}:${data.callId}`;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
identity = `${type}:${stableJson(data)}`;
|
|
56
|
+
}
|
|
57
|
+
return createHash("sha256").update(identity).digest("hex");
|
|
63
58
|
}
|
|
64
|
-
|
|
65
59
|
function snapshotData(data, type) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
60
|
+
let encoded;
|
|
61
|
+
try {
|
|
62
|
+
encoded = JSON.stringify(data);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
throw new Error(`odai evidence ${type} is not JSON-serializable`, { cause: error });
|
|
66
|
+
}
|
|
67
|
+
if (encoded === undefined)
|
|
68
|
+
throw new Error(`odai evidence ${type} is not JSON-serializable`);
|
|
69
|
+
const snapshot = JSON.parse(encoded);
|
|
70
|
+
if (!isUnknownRecord(snapshot))
|
|
71
|
+
throw new Error(`odai evidence ${type} must be an object`);
|
|
72
|
+
return snapshot;
|
|
74
73
|
}
|
|
75
|
-
|
|
76
74
|
function readCompleteLines(path, strict) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
if (!existsSync(path))
|
|
76
|
+
return [];
|
|
77
|
+
const text = readFileSync(path, "utf8");
|
|
78
|
+
if (strict && text !== "" && !text.endsWith("\n")) {
|
|
79
|
+
throw new Error(`odai evidence log has an unterminated final record: ${path}`);
|
|
80
|
+
}
|
|
81
|
+
const completeLength = text.endsWith("\n") ? text.length : text.lastIndexOf("\n") + 1;
|
|
82
|
+
if (completeLength <= 0)
|
|
83
|
+
return [];
|
|
84
|
+
return text.slice(0, completeLength).split("\n").filter(Boolean);
|
|
85
|
+
}
|
|
86
|
+
function parseStoredRecord(value, sessionId) {
|
|
87
|
+
if (!isUnknownRecord(value)
|
|
88
|
+
|| value.schemaVersion !== STORE_SCHEMA_VERSION
|
|
89
|
+
|| value.sessionId !== sessionId
|
|
90
|
+
|| !isOdaiEvent(value)
|
|
91
|
+
|| typeof value.time !== "number"
|
|
92
|
+
|| !Number.isSafeInteger(value.time)
|
|
93
|
+
|| value.time < 0)
|
|
94
|
+
return undefined;
|
|
95
|
+
const id = typeof value.id === "string" && value.id !== "" ? value.id : evidenceId(value.type, value.data);
|
|
96
|
+
return {
|
|
97
|
+
schemaVersion: STORE_SCHEMA_VERSION,
|
|
98
|
+
id,
|
|
99
|
+
sessionId,
|
|
100
|
+
type: value.type,
|
|
101
|
+
time: value.time,
|
|
102
|
+
data: value.data,
|
|
103
|
+
};
|
|
85
104
|
}
|
|
86
|
-
|
|
87
105
|
function readStoredState(root, sessionId, logger, strict = false) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
events.push(Object.freeze({ id, type: record.type, time: record.time, data: record.data }));
|
|
108
|
-
} catch (error) {
|
|
109
|
-
if (strict) throw new Error(`invalid odai evidence at ${path}:${index + 1}`, { cause: error });
|
|
110
|
-
logger.warn(`ignored invalid odai evidence at ${path}:${index + 1}: ${error instanceof Error ? error.message : String(error)}`);
|
|
106
|
+
const path = evidencePath(root, sessionId);
|
|
107
|
+
const events = [];
|
|
108
|
+
const ids = new Set();
|
|
109
|
+
for (const [index, line] of readCompleteLines(path, strict).entries()) {
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(line);
|
|
112
|
+
const record = parseStoredRecord(parsed, sessionId);
|
|
113
|
+
if (!record)
|
|
114
|
+
throw new Error("invalid evidence record");
|
|
115
|
+
if (ids.has(record.id))
|
|
116
|
+
continue;
|
|
117
|
+
ids.add(record.id);
|
|
118
|
+
events.push(Object.freeze({ id: record.id, type: record.type, time: record.time, data: record.data }));
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (strict)
|
|
122
|
+
throw new Error(`invalid odai evidence at ${path}:${index + 1}`, { cause: error });
|
|
123
|
+
logger.warn(`ignored invalid odai evidence at ${path}:${index + 1}: ${error instanceof Error ? error.message : String(error)}`);
|
|
124
|
+
}
|
|
111
125
|
}
|
|
112
|
-
|
|
113
|
-
return { events, ids };
|
|
126
|
+
return { events, ids, sessionId };
|
|
114
127
|
}
|
|
115
|
-
|
|
116
128
|
function mergeUnique(target, events) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
for (const event of events) {
|
|
130
|
+
const id = event.id ?? evidenceId(event.type, event.data);
|
|
131
|
+
if (target.ids.has(id))
|
|
132
|
+
continue;
|
|
133
|
+
target.ids.add(id);
|
|
134
|
+
target.events.push(Object.freeze({ id, type: event.type, time: event.time, data: event.data }));
|
|
135
|
+
}
|
|
123
136
|
}
|
|
124
|
-
|
|
125
137
|
function stateFor(agent, root, logger) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
138
|
+
const session = agent?.session;
|
|
139
|
+
const owner = session && typeof session === "object"
|
|
140
|
+
? session
|
|
141
|
+
: agent && typeof agent === "object"
|
|
142
|
+
? agent
|
|
143
|
+
: undefined;
|
|
144
|
+
if (!owner)
|
|
145
|
+
return { events: [], ids: new Set(), sessionId: undefined };
|
|
146
|
+
const shared = sharedState();
|
|
147
|
+
let roots = shared.bySession.get(owner);
|
|
148
|
+
if (!roots) {
|
|
149
|
+
roots = new Map();
|
|
150
|
+
shared.bySession.set(owner, roots);
|
|
151
|
+
}
|
|
152
|
+
const existing = roots.get(root);
|
|
153
|
+
if (existing)
|
|
154
|
+
return existing;
|
|
155
|
+
const sessionId = sessionIdOf(agent);
|
|
156
|
+
const state = { events: [], ids: new Set(), sessionId };
|
|
157
|
+
const legacy = Array.isArray(session?.events) ? session.events.filter(isOdaiEvent) : [];
|
|
158
|
+
mergeUnique(state, legacy);
|
|
159
|
+
if (sessionId)
|
|
160
|
+
mergeUnique(state, readStoredState(root, sessionId, logger).events);
|
|
161
|
+
roots.set(root, state);
|
|
162
|
+
return state;
|
|
163
|
+
}
|
|
164
|
+
function errorCode(error) {
|
|
165
|
+
if (error === null || typeof error !== "object" || !("code" in error))
|
|
166
|
+
return undefined;
|
|
167
|
+
return typeof error.code === "string" ? error.code : undefined;
|
|
150
168
|
}
|
|
151
|
-
|
|
152
169
|
function readLockOwner(lockPath) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
170
|
+
try {
|
|
171
|
+
const value = readFileSync(lockPath, "utf8").trim();
|
|
172
|
+
const separator = value.indexOf(":");
|
|
173
|
+
const pid = Number(separator < 0 ? "" : value.slice(0, separator));
|
|
174
|
+
return Number.isSafeInteger(pid) && pid > 0 && separator < value.length - 1 ? { value, pid } : undefined;
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
if (errorCode(error) === "ENOENT")
|
|
178
|
+
return undefined;
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
164
181
|
}
|
|
165
|
-
|
|
166
182
|
function processIsAlive(pid) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
183
|
+
try {
|
|
184
|
+
process.kill(pid, 0);
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
return errorCode(error) !== "ESRCH";
|
|
189
|
+
}
|
|
173
190
|
}
|
|
174
|
-
|
|
175
191
|
function createLock(lockPath, owner) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
192
|
+
const handle = openSync(lockPath, "wx", 0o600);
|
|
193
|
+
try {
|
|
194
|
+
const content = Buffer.from(`${owner}\n`, "utf8");
|
|
195
|
+
if (writeSync(handle, content, 0, content.length) !== content.length) {
|
|
196
|
+
throw new Error(`short write while acquiring odai evidence lock: ${lockPath}`);
|
|
197
|
+
}
|
|
198
|
+
fsyncSync(handle);
|
|
199
|
+
return handle;
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
closeSync(handle);
|
|
203
|
+
rmSync(lockPath, { force: true });
|
|
204
|
+
throw error;
|
|
181
205
|
}
|
|
182
|
-
fsyncSync(handle);
|
|
183
|
-
return handle;
|
|
184
|
-
} catch (error) {
|
|
185
|
-
closeSync(handle);
|
|
186
|
-
rmSync(lockPath, { force: true });
|
|
187
|
-
throw error;
|
|
188
|
-
}
|
|
189
206
|
}
|
|
190
|
-
|
|
191
207
|
function releaseLock(handle, lockPath, owner) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
208
|
+
closeSync(handle);
|
|
209
|
+
const current = readLockOwner(lockPath);
|
|
210
|
+
if (current === undefined)
|
|
211
|
+
throw new Error(`odai evidence lock disappeared before release: ${lockPath}`);
|
|
212
|
+
if (current.value !== owner)
|
|
213
|
+
throw new Error(`odai evidence lock ownership changed before release: ${lockPath}`);
|
|
214
|
+
rmSync(lockPath);
|
|
197
215
|
}
|
|
198
|
-
|
|
199
216
|
function acquireLock(path) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
try {
|
|
206
|
-
claimHandle = createLock(claimPath, claimOwner);
|
|
207
|
-
} catch (error) {
|
|
208
|
-
if (error?.code === "EEXIST") {
|
|
209
|
-
throw new Error(`odai evidence lock acquisition is already in progress: ${path}`);
|
|
210
|
-
}
|
|
211
|
-
throw error;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
let handle;
|
|
215
|
-
try {
|
|
217
|
+
const lockPath = `${path}.lock`;
|
|
218
|
+
const claimPath = `${lockPath}.claim`;
|
|
219
|
+
const owner = `${process.pid}:${randomUUID()}`;
|
|
220
|
+
const claimOwner = `${process.pid}:${randomUUID()}`;
|
|
221
|
+
let claimHandle;
|
|
216
222
|
try {
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
throw
|
|
223
|
-
}
|
|
224
|
-
rmSync(lockPath);
|
|
225
|
-
handle = createLock(lockPath, owner);
|
|
223
|
+
claimHandle = createLock(claimPath, claimOwner);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
if (errorCode(error) === "EEXIST")
|
|
227
|
+
throw new Error(`odai evidence lock acquisition is already in progress: ${path}`);
|
|
228
|
+
throw error;
|
|
226
229
|
}
|
|
227
|
-
|
|
230
|
+
let handle;
|
|
228
231
|
try {
|
|
229
|
-
releaseLock(claimHandle, claimPath, claimOwner);
|
|
230
|
-
} catch (error) {
|
|
231
|
-
if (handle !== undefined) {
|
|
232
232
|
try {
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
|
|
233
|
+
handle = createLock(lockPath, owner);
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
if (errorCode(error) !== "EEXIST")
|
|
237
|
+
throw error;
|
|
238
|
+
const current = readLockOwner(lockPath);
|
|
239
|
+
if (current === undefined || processIsAlive(current.pid))
|
|
240
|
+
throw new Error(`odai evidence is being updated by another runtime: ${path}`);
|
|
241
|
+
rmSync(lockPath);
|
|
242
|
+
handle = createLock(lockPath, owner);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
try {
|
|
247
|
+
releaseLock(claimHandle, claimPath, claimOwner);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
if (handle !== undefined) {
|
|
251
|
+
try {
|
|
252
|
+
releaseLock(handle, lockPath, owner);
|
|
253
|
+
}
|
|
254
|
+
catch { }
|
|
255
|
+
handle = undefined;
|
|
256
|
+
}
|
|
257
|
+
throw error;
|
|
236
258
|
}
|
|
237
|
-
handle = undefined;
|
|
238
|
-
}
|
|
239
|
-
throw error;
|
|
240
259
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
260
|
+
if (handle === undefined)
|
|
261
|
+
throw new Error(`could not lock odai evidence: ${path}`);
|
|
262
|
+
const ownedHandle = handle;
|
|
263
|
+
return () => releaseLock(ownedHandle, lockPath, owner);
|
|
245
264
|
}
|
|
246
|
-
|
|
247
265
|
function syncDirectory(path) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
266
|
+
let handle;
|
|
267
|
+
try {
|
|
268
|
+
handle = openSync(path, "r");
|
|
269
|
+
fsyncSync(handle);
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
// Windows does not expose directory fsync; the evidence file itself was synced.
|
|
273
|
+
}
|
|
274
|
+
finally {
|
|
275
|
+
if (handle !== undefined)
|
|
276
|
+
closeSync(handle);
|
|
277
|
+
}
|
|
257
278
|
}
|
|
258
|
-
|
|
259
279
|
function persistRecord(root, sessionId, record, logger) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
try {
|
|
264
|
-
const stored = readStoredState(root, sessionId, logger, true);
|
|
265
|
-
if (stored.ids.has(record.id)) return false;
|
|
266
|
-
const encoded = Buffer.from(`${JSON.stringify(record)}\n`, "utf8");
|
|
267
|
-
const originalSize = existsSync(path) ? statSync(path).size : 0;
|
|
268
|
-
const handle = openSync(path, "a", 0o600);
|
|
280
|
+
mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
281
|
+
const path = evidencePath(root, sessionId);
|
|
282
|
+
const release = acquireLock(path);
|
|
269
283
|
try {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
284
|
+
const stored = readStoredState(root, sessionId, logger, true);
|
|
285
|
+
if (stored.ids.has(record.id))
|
|
286
|
+
return false;
|
|
287
|
+
const encoded = Buffer.from(`${JSON.stringify(record)}\n`, "utf8");
|
|
288
|
+
const originalSize = existsSync(path) ? statSync(path).size : 0;
|
|
289
|
+
const handle = openSync(path, "a", 0o600);
|
|
290
|
+
try {
|
|
291
|
+
let offset = 0;
|
|
292
|
+
while (offset < encoded.length) {
|
|
293
|
+
const written = writeSync(handle, encoded, offset, encoded.length - offset);
|
|
294
|
+
if (written <= 0)
|
|
295
|
+
throw new Error(`short write while persisting odai evidence: ${path}`);
|
|
296
|
+
offset += written;
|
|
297
|
+
}
|
|
298
|
+
fsyncSync(handle);
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
ftruncateSync(handle, originalSize);
|
|
302
|
+
fsyncSync(handle);
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
305
|
+
finally {
|
|
306
|
+
closeSync(handle);
|
|
307
|
+
}
|
|
308
|
+
syncDirectory(root);
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
release();
|
|
283
313
|
}
|
|
284
|
-
syncDirectory(root);
|
|
285
|
-
return true;
|
|
286
|
-
} finally {
|
|
287
|
-
release();
|
|
288
|
-
}
|
|
289
314
|
}
|
|
290
|
-
|
|
291
315
|
export function resolveSessionEvidenceRoot(routingConfigPath) {
|
|
292
|
-
|
|
316
|
+
return resolve(dirname(routingConfigPath), "session-evidence");
|
|
293
317
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
318
|
+
export function readStoredSessionEvidence(root, sessionId, logger = { warn() { } }) {
|
|
319
|
+
if (typeof sessionId !== "string" || sessionId === "")
|
|
320
|
+
return [];
|
|
321
|
+
return readStoredState(resolve(root), sessionId, logger).events;
|
|
298
322
|
}
|
|
299
|
-
|
|
300
323
|
export function createSessionEvidence(options) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
324
|
+
const root = resolve(options.root);
|
|
325
|
+
const logger = options.logger ?? { warn() { } };
|
|
326
|
+
const events = (agent) => stateFor(agent, root, logger).events.slice();
|
|
327
|
+
const has = (agent, type, predicate = () => true) => stateFor(agent, root, logger)
|
|
328
|
+
.events
|
|
329
|
+
.some((event) => event.type === type && predicate(event.data));
|
|
330
|
+
const append = (agent, type, data) => {
|
|
331
|
+
if (typeof type !== "string" || !type.startsWith("odai/")) {
|
|
332
|
+
throw new TypeError("odai evidence type must start with odai/");
|
|
333
|
+
}
|
|
334
|
+
const state = stateFor(agent, root, logger);
|
|
335
|
+
const snapshot = snapshotData(data, type);
|
|
336
|
+
const id = evidenceId(type, snapshot);
|
|
337
|
+
const event = Object.freeze({ id, type, time: Date.now(), data: snapshot });
|
|
338
|
+
if (state.ids.has(id))
|
|
339
|
+
return state.events.find((candidate) => candidate.id === id);
|
|
340
|
+
if (!state.sessionId) {
|
|
341
|
+
state.ids.add(id);
|
|
342
|
+
state.events.push(event);
|
|
343
|
+
try {
|
|
344
|
+
agent?.session?.append?.(type, event.data);
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
logger.warn(`failed to append transient ${type}: ${error instanceof Error ? error.message : String(error)}`);
|
|
348
|
+
}
|
|
349
|
+
return event;
|
|
350
|
+
}
|
|
351
|
+
const record = {
|
|
352
|
+
schemaVersion: STORE_SCHEMA_VERSION,
|
|
353
|
+
id,
|
|
354
|
+
sessionId: state.sessionId,
|
|
355
|
+
type,
|
|
356
|
+
time: event.time,
|
|
357
|
+
data: event.data,
|
|
358
|
+
};
|
|
359
|
+
persistRecord(root, state.sessionId, record, logger);
|
|
360
|
+
state.ids.add(id);
|
|
361
|
+
state.events.push(event);
|
|
362
|
+
return event;
|
|
336
363
|
};
|
|
337
|
-
|
|
338
|
-
state.ids.add(id);
|
|
339
|
-
state.events.push(event);
|
|
340
|
-
return event;
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
return Object.freeze({ append, events, has, root });
|
|
364
|
+
return Object.freeze({ append, events, has, root });
|
|
344
365
|
}
|
|
366
|
+
//# sourceMappingURL=session-evidence.mjs.map
|