@openlimiter/core 1.3.5 → 2.0.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/acquire/cadence.d.ts +5 -3
- package/dist/acquire/cadence.d.ts.map +1 -1
- package/dist/acquire/cadence.js +14 -9
- package/dist/acquire/cadence.js.map +1 -1
- package/dist/acquire/coordination.d.ts.map +1 -1
- package/dist/acquire/coordination.js +3 -4
- package/dist/acquire/coordination.js.map +1 -1
- package/dist/acquire/credentials.d.ts +1 -1
- package/dist/acquire/credentials.d.ts.map +1 -1
- package/dist/acquire/credentials.js +8 -0
- package/dist/acquire/credentials.js.map +1 -1
- package/dist/acquire/cursor.d.ts +8 -0
- package/dist/acquire/cursor.d.ts.map +1 -0
- package/dist/acquire/cursor.js +60 -0
- package/dist/acquire/cursor.js.map +1 -0
- package/dist/acquire/identity.d.ts +1 -1
- package/dist/acquire/identity.js +1 -1
- package/dist/acquire/index.d.ts +1 -0
- package/dist/acquire/index.d.ts.map +1 -1
- package/dist/acquire/index.js +1 -0
- package/dist/acquire/index.js.map +1 -1
- package/dist/acquire/providers.d.ts +1 -0
- package/dist/acquire/providers.d.ts.map +1 -1
- package/dist/acquire/providers.js +10 -1
- package/dist/acquire/providers.js.map +1 -1
- package/dist/acquire/runner.d.ts +7 -14
- package/dist/acquire/runner.d.ts.map +1 -1
- package/dist/acquire/runner.js +144 -6
- package/dist/acquire/runner.js.map +1 -1
- package/dist/acquire/transport.d.ts +7 -1
- package/dist/acquire/transport.d.ts.map +1 -1
- package/dist/acquire/transport.js +30 -4
- package/dist/acquire/transport.js.map +1 -1
- package/dist/activity/contract.d.ts +108 -0
- package/dist/activity/contract.d.ts.map +1 -0
- package/dist/activity/contract.js +281 -0
- package/dist/activity/contract.js.map +1 -0
- package/dist/activity/spool.d.ts +20 -0
- package/dist/activity/spool.d.ts.map +1 -0
- package/dist/activity/spool.js +305 -0
- package/dist/activity/spool.js.map +1 -0
- package/dist/cache.d.ts +46 -1
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +117 -1
- package/dist/cache.js.map +1 -1
- package/dist/connection-state.d.ts +15 -0
- package/dist/connection-state.d.ts.map +1 -1
- package/dist/connection-state.js +18 -0
- package/dist/connection-state.js.map +1 -1
- package/dist/contracts/notify.d.ts +47 -0
- package/dist/contracts/notify.d.ts.map +1 -0
- package/dist/contracts/notify.js +83 -0
- package/dist/contracts/notify.js.map +1 -0
- package/dist/contracts/surfaces.d.ts +61 -0
- package/dist/contracts/surfaces.d.ts.map +1 -0
- package/dist/contracts/surfaces.js +73 -0
- package/dist/contracts/surfaces.js.map +1 -0
- package/dist/contracts/wire.d.ts +108 -0
- package/dist/contracts/wire.d.ts.map +1 -0
- package/dist/contracts/wire.js +202 -0
- package/dist/contracts/wire.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/normalizer.d.ts.map +1 -1
- package/dist/normalizer.js +42 -7
- package/dist/normalizer.js.map +1 -1
- package/dist/types.d.ts +44 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -1
- package/package.json +16 -1
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/** Local activity protocol. No collector, clock, storage or notification delivery. */
|
|
2
|
+
export const ACTIVITY_EVENT_VERSION = 1;
|
|
3
|
+
export const MAX_ACTIVITY_EVENT_BYTES = 8 * 1024;
|
|
4
|
+
export const MAX_ACTIVITY_SPOOL_BYTES = 16 * 1024 * 1024;
|
|
5
|
+
export const MAX_ACTIVITY_EVENT_AGE_MS = 24 * 60 * 60 * 1000;
|
|
6
|
+
/** Proposed live cap, frozen for consumers of version 1. */
|
|
7
|
+
export const MAX_LIVE_ACTIVITY_SESSIONS = 64;
|
|
8
|
+
export const ACTIVITY_SESSION_RETIREMENT_MS = 24 * 60 * 60 * 1000;
|
|
9
|
+
export const ACTIVITY_AGENTS = [
|
|
10
|
+
"claude_code", "codex", "muse", "gemini_cli", "cursor", "kimi", "grok", "antigravity"
|
|
11
|
+
];
|
|
12
|
+
export const ACTIVITY_STATES = ["busy", "waiting", "done", "idle", "unknown"];
|
|
13
|
+
export const ACTIVITY_SOURCES = ["hook", "registry", "transcript", "rollout", "editor_state"];
|
|
14
|
+
export const ACTIVITY_SIGNALS = [
|
|
15
|
+
"observation", "user_question", "claude_stop", "process_vanished", "session_ended"
|
|
16
|
+
];
|
|
17
|
+
/** Shared boundary primitives, deliberately independent of Node and browser state. */
|
|
18
|
+
export function isContractObject(value) {
|
|
19
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
20
|
+
}
|
|
21
|
+
export function isContractInstant(value) {
|
|
22
|
+
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(value))
|
|
23
|
+
return false;
|
|
24
|
+
const ms = Date.parse(value);
|
|
25
|
+
return Number.isFinite(ms) && new Date(ms).toISOString() === value;
|
|
26
|
+
}
|
|
27
|
+
export function hasOnlyContractKeys(value, keys) {
|
|
28
|
+
return Object.keys(value).every((key) => keys.includes(key));
|
|
29
|
+
}
|
|
30
|
+
const identifier = (value) => typeof value === "string" && value["length"] > 0 && value["length"] <= 256 && !/[\u0000-\u001f\u007f]/u.test(value);
|
|
31
|
+
const pid = (value) => Number.isSafeInteger(value) && value > 0;
|
|
32
|
+
const member = (values, value) => typeof value === "string" && values.includes(value);
|
|
33
|
+
/** Strict spool boundary. now is epoch milliseconds supplied by the consumer. */
|
|
34
|
+
export function isActivityEvent(value, now, origin = "hook") {
|
|
35
|
+
if (!Number.isFinite(now) || !isContractObject(value))
|
|
36
|
+
return false;
|
|
37
|
+
if (!hasOnlyContractKeys(value, ["version", "eventId", "sessionId", "sequence", "agent", "accountAlias",
|
|
38
|
+
"observedAt", "state", "outcome", "source", "confidence", "process", "signal", "parentSessionId", "isSidechain"]))
|
|
39
|
+
return false;
|
|
40
|
+
try {
|
|
41
|
+
if (new TextEncoder().encode(JSON.stringify(value)).byteLength > MAX_ACTIVITY_EVENT_BYTES)
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (value["version"] !== ACTIVITY_EVENT_VERSION || !identifier(value["eventId"]) || !identifier(value["sessionId"]) ||
|
|
48
|
+
!Number.isSafeInteger(value["sequence"]) || value["sequence"] < 0 ||
|
|
49
|
+
!member(ACTIVITY_AGENTS, value["agent"]) || !member(ACTIVITY_STATES, value["state"]) ||
|
|
50
|
+
!member(ACTIVITY_SOURCES, value["source"]) || !member(["explicit", "inferred"], value["confidence"]) ||
|
|
51
|
+
!isContractInstant(value["observedAt"]))
|
|
52
|
+
return false;
|
|
53
|
+
const age = now - Date.parse(value["observedAt"]);
|
|
54
|
+
if (age < 0 || age > MAX_ACTIVITY_EVENT_AGE_MS)
|
|
55
|
+
return false;
|
|
56
|
+
if (origin === "hook" && value["source"] !== "hook")
|
|
57
|
+
return false;
|
|
58
|
+
if (value["accountAlias"] !== undefined && !identifier(value["accountAlias"]))
|
|
59
|
+
return false;
|
|
60
|
+
if (value["parentSessionId"] !== undefined && (!identifier(value["parentSessionId"]) || value["parentSessionId"] === value["sessionId"]))
|
|
61
|
+
return false;
|
|
62
|
+
if (value["isSidechain"] !== undefined && typeof value["isSidechain"] !== "boolean")
|
|
63
|
+
return false;
|
|
64
|
+
if (value["signal"] !== undefined && !member(ACTIVITY_SIGNALS, value["signal"]))
|
|
65
|
+
return false;
|
|
66
|
+
if (value["signal"] === "claude_stop" && value["agent"] !== "claude_code")
|
|
67
|
+
return false;
|
|
68
|
+
if (value["outcome"] !== undefined && !member(["cancelled", "failed"], value["outcome"]))
|
|
69
|
+
return false;
|
|
70
|
+
if (value["outcome"] !== undefined && value["state"] === "done")
|
|
71
|
+
return false;
|
|
72
|
+
if ((value["state"] === "done" || value["signal"] === "user_question" || value["signal"] === "claude_stop") && value["confidence"] !== "explicit")
|
|
73
|
+
return false;
|
|
74
|
+
if (value["process"] !== undefined) {
|
|
75
|
+
const process = value["process"];
|
|
76
|
+
if (!isContractObject(process) || !hasOnlyContractKeys(process, ["ppid", "pid", "startedAt"]))
|
|
77
|
+
return false;
|
|
78
|
+
if (process["ppid"] !== undefined && !pid(process["ppid"]))
|
|
79
|
+
return false;
|
|
80
|
+
if (origin === "hook" && (process["ppid"] === undefined || process["pid"] !== undefined || process["startedAt"] !== undefined))
|
|
81
|
+
return false;
|
|
82
|
+
if ((process["pid"] === undefined) !== (process["startedAt"] === undefined))
|
|
83
|
+
return false;
|
|
84
|
+
if (process["pid"] !== undefined && (!pid(process["pid"]) || !isContractInstant(process["startedAt"])))
|
|
85
|
+
return false;
|
|
86
|
+
if (isContractInstant(process["startedAt"]) && process["startedAt"] > value["observedAt"])
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
/** Validate original byte size before parsing; reserialization alone misses whitespace padding. */
|
|
92
|
+
export function readActivityEvent(serialized, now, origin = "hook") {
|
|
93
|
+
if (new TextEncoder().encode(serialized).byteLength > MAX_ACTIVITY_EVENT_BYTES)
|
|
94
|
+
throw new TypeError("activity: event too large");
|
|
95
|
+
let value;
|
|
96
|
+
try {
|
|
97
|
+
value = JSON.parse(serialized);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
throw new TypeError("activity: invalid JSON");
|
|
101
|
+
}
|
|
102
|
+
if (!isActivityEvent(value, now, origin))
|
|
103
|
+
throw new TypeError("activity: invalid event");
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
/** Batch dedupe across sessions before applying per session sequence precedence. */
|
|
107
|
+
export function dedupeActivityEvents(events) {
|
|
108
|
+
const ids = new Set();
|
|
109
|
+
const sequences = new Set();
|
|
110
|
+
return events.filter((event) => {
|
|
111
|
+
const key = JSON.stringify([event.sessionId, event.sequence]);
|
|
112
|
+
if (ids.has(event.eventId) || sequences.has(key))
|
|
113
|
+
return false;
|
|
114
|
+
ids.add(event.eventId);
|
|
115
|
+
sequences.add(key);
|
|
116
|
+
return true;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Call only with validated events. Sequence is monotonic for a session across sources.
|
|
121
|
+
* Suppressed observations still advance dedupe, but never weaken the winning evidence.
|
|
122
|
+
*/
|
|
123
|
+
export function nextSessionState(previous, event) {
|
|
124
|
+
if (previous !== null) {
|
|
125
|
+
if (previous.sessionId !== event.sessionId || previous.agent !== event.agent)
|
|
126
|
+
return { session: previous, notification: null, reason: "other_session" };
|
|
127
|
+
if (previous.seenEventIds.includes(event.eventId) || previous.sequence === event.sequence)
|
|
128
|
+
return { session: previous, notification: null, reason: "duplicate" };
|
|
129
|
+
if (event.sequence < previous.sequence || event.observedAt < previous.observedAt)
|
|
130
|
+
return { session: previous, notification: null, reason: "out_of_order" };
|
|
131
|
+
}
|
|
132
|
+
const base = previous ?? {
|
|
133
|
+
sessionId: event.sessionId, agent: event.agent, state: "unknown", confidence: "inferred",
|
|
134
|
+
firstObservedAt: event.observedAt, observedAt: event.observedAt, stateChangedAt: event.observedAt,
|
|
135
|
+
sequence: event.sequence, seenEventIds: [], awaitingInput: false,
|
|
136
|
+
...(event.accountAlias === undefined ? {} : { accountAlias: event.accountAlias })
|
|
137
|
+
};
|
|
138
|
+
const session = {
|
|
139
|
+
...base, sequence: event.sequence, seenEventIds: [...base.seenEventIds, event.eventId], observedAt: event.observedAt
|
|
140
|
+
};
|
|
141
|
+
// A sidechain observation attributed to a parent cannot change that parent's state.
|
|
142
|
+
if (event.isSidechain)
|
|
143
|
+
return { session, notification: null, reason: previous === null ? "baseline" : "child" };
|
|
144
|
+
const identityChanged = base.process?.pid !== undefined && event.process?.pid !== undefined &&
|
|
145
|
+
(base.process["pid"] !== event.process["pid"] || base.process["startedAt"] !== event.process["startedAt"]);
|
|
146
|
+
const vanished = event.signal === "process_vanished" || event.signal === "session_ended" || identityChanged;
|
|
147
|
+
if (!vanished && previous !== null && base.confidence === "explicit" && event.confidence === "inferred")
|
|
148
|
+
return { session, notification: null, reason: "lower_confidence" };
|
|
149
|
+
let state = event.state;
|
|
150
|
+
let awaitingInput = base.awaitingInput;
|
|
151
|
+
if (event.signal === "user_question") {
|
|
152
|
+
state = "waiting";
|
|
153
|
+
awaitingInput = true;
|
|
154
|
+
}
|
|
155
|
+
if (event.signal === "claude_stop" && awaitingInput)
|
|
156
|
+
state = "waiting";
|
|
157
|
+
if (state === "busy" && event.confidence === "explicit")
|
|
158
|
+
awaitingInput = false;
|
|
159
|
+
if (vanished) {
|
|
160
|
+
state = "unknown";
|
|
161
|
+
awaitingInput = false;
|
|
162
|
+
}
|
|
163
|
+
// A failure or cancellation is never a success, including defensive typed calls.
|
|
164
|
+
if (event.outcome !== undefined && state === "done")
|
|
165
|
+
state = "unknown";
|
|
166
|
+
if (state === "done" && event.confidence !== "explicit")
|
|
167
|
+
state = "unknown";
|
|
168
|
+
if (state !== "waiting")
|
|
169
|
+
awaitingInput = false;
|
|
170
|
+
session.state = state;
|
|
171
|
+
session.confidence = vanished ? "inferred" : event.confidence;
|
|
172
|
+
session.awaitingInput = awaitingInput;
|
|
173
|
+
if (state !== base.state)
|
|
174
|
+
session.stateChangedAt = event.observedAt;
|
|
175
|
+
delete session.outcome;
|
|
176
|
+
if (event.outcome !== undefined)
|
|
177
|
+
session.outcome = event.outcome;
|
|
178
|
+
delete session.endedAt;
|
|
179
|
+
if (vanished || state === "done" || event.outcome !== undefined)
|
|
180
|
+
session.endedAt = base.state === state && base.outcome === event.outcome ? base.endedAt ?? event.observedAt : event.observedAt;
|
|
181
|
+
if (event.process !== undefined && !identityChanged)
|
|
182
|
+
session.process = {
|
|
183
|
+
...(base.process?.pid === undefined ? {} : { pid: base.process.pid, startedAt: base.process.startedAt }),
|
|
184
|
+
...(event.process["ppid"] === undefined ? {} : { ppid: event.process["ppid"] }),
|
|
185
|
+
...(event.process["pid"] === undefined ? {} : { pid: event.process["pid"], startedAt: event.process["startedAt"] })
|
|
186
|
+
};
|
|
187
|
+
if (identityChanged)
|
|
188
|
+
delete session.process;
|
|
189
|
+
const notification = previous !== null && base.state !== state && event.outcome === undefined &&
|
|
190
|
+
session.confidence === "explicit" ? state === "waiting" ? "agent_waiting" : state === "done" ? "agent_done" : null : null;
|
|
191
|
+
return { session, notification, reason: previous === null ? "baseline" : "applied" };
|
|
192
|
+
}
|
|
193
|
+
/** Retirement uses the state/ending time, so repeated idle polls cannot keep it alive. */
|
|
194
|
+
export function shouldRetireActivitySession(session, now) {
|
|
195
|
+
const since = session.endedAt ?? (session.state === "idle" ? session.stateChangedAt : undefined);
|
|
196
|
+
return since !== undefined && Number.isFinite(now) && now - Date.parse(since) >= ACTIVITY_SESSION_RETIREMENT_MS;
|
|
197
|
+
}
|
|
198
|
+
// SHA-256 over a domain separated, length delimited identity. Pure and usable in every lane.
|
|
199
|
+
// This is a stable pseudonym, not a secret or an authorization token.
|
|
200
|
+
function opaqueSessionId(agent, sessionId) {
|
|
201
|
+
const bytes = new TextEncoder().encode(JSON.stringify(["openlimiter.activity.v1", agent, sessionId]));
|
|
202
|
+
const words = new Uint32Array(Math.ceil((bytes.length + 9) / 64) * 16);
|
|
203
|
+
bytes.forEach((byte, index) => { words[index >>> 2] = words[index >>> 2] | byte << (24 - (index % 4) * 8); });
|
|
204
|
+
words[bytes.length >>> 2] = words[bytes.length >>> 2] | 0x80 << (24 - (bytes.length % 4) * 8);
|
|
205
|
+
words[words.length - 1] = bytes.length * 8;
|
|
206
|
+
const k = [
|
|
207
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
208
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
209
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
210
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
211
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
212
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
213
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
214
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
|
215
|
+
];
|
|
216
|
+
const h = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
|
|
217
|
+
const rotate = (n, shift) => n >>> shift | n << (32 - shift);
|
|
218
|
+
for (let offset = 0; offset < words.length; offset += 16) {
|
|
219
|
+
const w = new Uint32Array(64);
|
|
220
|
+
for (let i = 0; i < 64; i++) {
|
|
221
|
+
const x = w[i - 15] ?? 0, y = w[i - 2] ?? 0;
|
|
222
|
+
w[i] = i < 16 ? words[offset + i] : w[i - 16] + (rotate(x, 7) ^ rotate(x, 18) ^ x >>> 3) + w[i - 7] + (rotate(y, 17) ^ rotate(y, 19) ^ y >>> 10);
|
|
223
|
+
}
|
|
224
|
+
let [a, b, c, d, e, f, g, j] = h;
|
|
225
|
+
for (let i = 0; i < 64; i++) {
|
|
226
|
+
const t1 = (j + (rotate(e, 6) ^ rotate(e, 11) ^ rotate(e, 25)) + ((e & f) ^ (~e & g)) + k[i] + w[i]) | 0;
|
|
227
|
+
const t2 = ((rotate(a, 2) ^ rotate(a, 13) ^ rotate(a, 22)) + ((a & b) ^ (a & c) ^ (b & c))) | 0;
|
|
228
|
+
j = g;
|
|
229
|
+
g = f;
|
|
230
|
+
f = e;
|
|
231
|
+
e = (d + t1) | 0;
|
|
232
|
+
d = c;
|
|
233
|
+
c = b;
|
|
234
|
+
b = a;
|
|
235
|
+
a = (t1 + t2) | 0;
|
|
236
|
+
}
|
|
237
|
+
[a, b, c, d, e, f, g, j].forEach((value, i) => { h[i] = (h[i] + value) >>> 0; });
|
|
238
|
+
}
|
|
239
|
+
return h.map((value) => value["toString"](16).padStart(8, "0")).join("");
|
|
240
|
+
}
|
|
241
|
+
function isProjectedActivityRecord(value, display) {
|
|
242
|
+
if (!isContractObject(value) || !hasOnlyContractKeys(value, ["sessionId", "agent", "state", "confidence", "firstObservedAt",
|
|
243
|
+
"observedAt", "stateChangedAt", "userProjectLabel", ...(display ? ["outcome"] : [])]))
|
|
244
|
+
return false;
|
|
245
|
+
if (typeof value["sessionId"] !== "string" || !/^[a-f0-9]{64}$/u.test(value["sessionId"]) ||
|
|
246
|
+
!member(ACTIVITY_AGENTS, value["agent"]) || !member(ACTIVITY_STATES, value["state"]) ||
|
|
247
|
+
!member(["explicit", "inferred"], value["confidence"]) || !isContractInstant(value["firstObservedAt"]) ||
|
|
248
|
+
!isContractInstant(value["observedAt"]) || !isContractInstant(value["stateChangedAt"]))
|
|
249
|
+
return false;
|
|
250
|
+
if (value["firstObservedAt"] > value["stateChangedAt"] || value["stateChangedAt"] > value["observedAt"])
|
|
251
|
+
return false;
|
|
252
|
+
if (value["state"] === "done" && value["confidence"] !== "explicit")
|
|
253
|
+
return false;
|
|
254
|
+
if (value["userProjectLabel"] !== undefined && !identifier(value["userProjectLabel"]))
|
|
255
|
+
return false;
|
|
256
|
+
return value["outcome"] === undefined || (display && member(["cancelled", "failed"], value["outcome"]) && value["state"] !== "done");
|
|
257
|
+
}
|
|
258
|
+
export function isActivityUploadRecord(value) {
|
|
259
|
+
return isProjectedActivityRecord(value, false);
|
|
260
|
+
}
|
|
261
|
+
export function isActivityDisplayRecord(value) {
|
|
262
|
+
return isProjectedActivityRecord(value, true);
|
|
263
|
+
}
|
|
264
|
+
/** An explicit allowlist. Never spread a session, event, alias or process into either record. */
|
|
265
|
+
export function toUploadRecord(session) {
|
|
266
|
+
const record = {
|
|
267
|
+
sessionId: opaqueSessionId(session.agent, session.sessionId), agent: session.agent,
|
|
268
|
+
state: session.state, confidence: session.confidence, firstObservedAt: session.firstObservedAt,
|
|
269
|
+
observedAt: session.observedAt, stateChangedAt: session.stateChangedAt
|
|
270
|
+
};
|
|
271
|
+
if (session.userProjectLabel !== undefined) {
|
|
272
|
+
if (!identifier(session.userProjectLabel))
|
|
273
|
+
throw new TypeError("activity: invalid user project label");
|
|
274
|
+
record.userProjectLabel = session.userProjectLabel;
|
|
275
|
+
}
|
|
276
|
+
return record;
|
|
277
|
+
}
|
|
278
|
+
export function toDisplayRecord(session) {
|
|
279
|
+
return { ...toUploadRecord(session), ...(session.outcome === undefined ? {} : { outcome: session.outcome }) };
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/activity/contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ActivityEvent } from "./contract.js";
|
|
2
|
+
export interface ActivitySpoolOptions {
|
|
3
|
+
/** The existing OpenLimiter state root, never a value from a hook payload. */
|
|
4
|
+
directory: string;
|
|
5
|
+
now?: number;
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
/** Monotonic deadline for synchronous filesystem work; timers cannot interrupt it. */
|
|
8
|
+
deadline?: number;
|
|
9
|
+
/** Windows must establish and verify an inheritable owner ACL before writing. */
|
|
10
|
+
protectWindowsDirectory?: (directory: string) => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare function prepareActivitySpool(options: ActivitySpoolOptions): Promise<string>;
|
|
13
|
+
/** A global spool lock serializes size accounting and sequence allocation. */
|
|
14
|
+
export declare function writeActivityEvent(event: ActivityEvent, options: ActivitySpoolOptions): Promise<ActivityEvent>;
|
|
15
|
+
/** Read only. Invalid, stale, partial and hostile records are counted and skipped. */
|
|
16
|
+
export declare function readActivitySpool(directory: string, now?: number): {
|
|
17
|
+
events: ActivityEvent[];
|
|
18
|
+
skipped: number;
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=spool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spool.d.ts","sourceRoot":"","sources":["../../src/activity/spool.ts"],"names":[],"mappings":"AAOA,OAAO,EAEwC,KAAK,aAAa,EAChE,MAAM,eAAe,CAAC;AASvB,MAAM,WAAW,oBAAoB;IACnC,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,uBAAuB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChE;AAyED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwCzF;AAED,8EAA8E;AAC9E,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC,CA0DpH;AAED,sFAAsF;AACtF,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG;IAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAiCnH"}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { chmodSync, closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync, readdirSync, readSync, realpathSync, renameSync, rmdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { isActivityEvent, MAX_ACTIVITY_EVENT_AGE_MS, MAX_ACTIVITY_EVENT_BYTES, MAX_ACTIVITY_SPOOL_BYTES, readActivityEvent } from "./contract.js";
|
|
5
|
+
const EVENT_NAME = /^(\d{16})-([a-f0-9-]{36})\.json$/u;
|
|
6
|
+
const TEMP_NAME = /^\.[a-f0-9-]{36}\.tmp$/u;
|
|
7
|
+
const MAX_ENTRIES = 65_536;
|
|
8
|
+
/* A healthy writer holds the lock for milliseconds; a hook killed mid write
|
|
9
|
+
(Ctrl+C in the agent) strands it, so recover well before the event age. */
|
|
10
|
+
const STALE_WRITER_LOCK_MS = 10_000;
|
|
11
|
+
function code(error) {
|
|
12
|
+
return error.code;
|
|
13
|
+
}
|
|
14
|
+
function checkpoint(options) {
|
|
15
|
+
options.signal?.throwIfAborted();
|
|
16
|
+
if (options.deadline !== undefined && performance.now() >= options.deadline)
|
|
17
|
+
throw new Error("activity: deadline");
|
|
18
|
+
}
|
|
19
|
+
/** The root is already canonical. Refuse links in the owned subtree. */
|
|
20
|
+
function directories(target, create) {
|
|
21
|
+
if (!path.isAbsolute(target))
|
|
22
|
+
throw new Error("activity: absolute state root required");
|
|
23
|
+
let current = path.dirname(target);
|
|
24
|
+
const root = lstatSync(current);
|
|
25
|
+
if (!root.isDirectory() || root.isSymbolicLink())
|
|
26
|
+
throw new Error("activity: unsafe directory");
|
|
27
|
+
let created = false;
|
|
28
|
+
for (const segment of target.slice(current.length).split(path.sep).filter(Boolean)) {
|
|
29
|
+
if (segment === "." || segment === "..")
|
|
30
|
+
throw new Error("activity: path escape");
|
|
31
|
+
current = path.join(current, segment);
|
|
32
|
+
let info;
|
|
33
|
+
try {
|
|
34
|
+
info = lstatSync(current);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (!create || code(error) !== "ENOENT")
|
|
38
|
+
throw error;
|
|
39
|
+
try {
|
|
40
|
+
mkdirSync(current, { mode: 0o700 });
|
|
41
|
+
if (current === target)
|
|
42
|
+
created = true;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (code(error) !== "EEXIST")
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
info = lstatSync(current);
|
|
49
|
+
}
|
|
50
|
+
if (!info.isDirectory() || info.isSymbolicLink())
|
|
51
|
+
throw new Error("activity: unsafe directory");
|
|
52
|
+
}
|
|
53
|
+
return created;
|
|
54
|
+
}
|
|
55
|
+
function spoolPath(root, create = false) {
|
|
56
|
+
if (!path.isAbsolute(root) || root.split(/[\\/]/u).includes(".."))
|
|
57
|
+
throw new Error("activity: path escape");
|
|
58
|
+
if (create)
|
|
59
|
+
mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
60
|
+
// System roots may contain aliases (macOS /var, Windows junctions). Resolve
|
|
61
|
+
// only the supplied state root, never activity or any of its entries.
|
|
62
|
+
return path.join(realpathSync(root), "activity");
|
|
63
|
+
}
|
|
64
|
+
function same(left, right) {
|
|
65
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
66
|
+
}
|
|
67
|
+
function regular(info) {
|
|
68
|
+
return info.isFile() && !info.isSymbolicLink() && info.nlink === 1 &&
|
|
69
|
+
(process.platform === "win32" ||
|
|
70
|
+
(info.uid === process.getuid?.() && (info.mode & 0o077) === 0));
|
|
71
|
+
}
|
|
72
|
+
function readFile(file, info, now) {
|
|
73
|
+
if (!regular(info) || info.size > MAX_ACTIVITY_EVENT_BYTES)
|
|
74
|
+
throw new Error("activity: unsafe file");
|
|
75
|
+
const fd = openSync(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
|
|
76
|
+
try {
|
|
77
|
+
const opened = fstatSync(fd);
|
|
78
|
+
if (!same(info, opened) || !regular(opened) || opened.size > MAX_ACTIVITY_EVENT_BYTES)
|
|
79
|
+
throw new Error("activity: changed file");
|
|
80
|
+
const bytes = Buffer.alloc(MAX_ACTIVITY_EVENT_BYTES + 1);
|
|
81
|
+
let length = 0;
|
|
82
|
+
while (length < bytes.length) {
|
|
83
|
+
const count = readSync(fd, bytes, length, bytes.length - length, null);
|
|
84
|
+
if (count === 0)
|
|
85
|
+
break;
|
|
86
|
+
length += count;
|
|
87
|
+
}
|
|
88
|
+
if (length > MAX_ACTIVITY_EVENT_BYTES)
|
|
89
|
+
throw new Error("activity: oversized file");
|
|
90
|
+
const serialized = new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, length));
|
|
91
|
+
const event = readActivityEvent(serialized, now, "desktop");
|
|
92
|
+
if (event.source === "hook" && !isActivityEvent(event, now, "hook"))
|
|
93
|
+
throw new Error("activity: invalid hook process");
|
|
94
|
+
return event;
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
closeSync(fd);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export async function prepareActivitySpool(options) {
|
|
101
|
+
checkpoint(options);
|
|
102
|
+
const directory = spoolPath(options.directory, true);
|
|
103
|
+
const created = directories(directory, true);
|
|
104
|
+
const before = lstatSync(directory);
|
|
105
|
+
if (process.platform === "win32") {
|
|
106
|
+
const stamp = path.join(directory, ".acl-verified");
|
|
107
|
+
let verified = false;
|
|
108
|
+
try {
|
|
109
|
+
const info = lstatSync(stamp);
|
|
110
|
+
verified = regular(info) && info.size <= MAX_ACTIVITY_EVENT_BYTES &&
|
|
111
|
+
(options.now ?? Date.now()) - info.mtimeMs <= MAX_ACTIVITY_EVENT_AGE_MS;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (code(error) !== "ENOENT")
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
if (created || !verified) {
|
|
118
|
+
if (!options.protectWindowsDirectory)
|
|
119
|
+
throw new Error("activity: Windows ACL helper required");
|
|
120
|
+
await options.protectWindowsDirectory(directory);
|
|
121
|
+
checkpoint(options);
|
|
122
|
+
directories(directory, false);
|
|
123
|
+
if (!same(before, lstatSync(directory)))
|
|
124
|
+
throw new Error("activity: changed directory");
|
|
125
|
+
const temporary = path.join(directory, "." + randomUUID() + ".tmp");
|
|
126
|
+
try {
|
|
127
|
+
const fd = openSync(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 0o600);
|
|
128
|
+
try {
|
|
129
|
+
writeFileSync(fd, "");
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
closeSync(fd);
|
|
133
|
+
}
|
|
134
|
+
// Replace only the stamp itself. Never traverse a planted directory or link.
|
|
135
|
+
try {
|
|
136
|
+
const info = lstatSync(stamp);
|
|
137
|
+
if (info.isDirectory() && !info.isSymbolicLink())
|
|
138
|
+
rmdirSync(stamp);
|
|
139
|
+
else if (!regular(info))
|
|
140
|
+
unlinkSync(stamp);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
if (code(error) !== "ENOENT")
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
renameSync(temporary, stamp);
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
try {
|
|
150
|
+
unlinkSync(temporary);
|
|
151
|
+
}
|
|
152
|
+
catch { /* Published or failed stamp. */ }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
if (before.uid !== process.getuid?.())
|
|
158
|
+
throw new Error("activity: wrong directory owner");
|
|
159
|
+
chmodSync(directory, 0o700);
|
|
160
|
+
}
|
|
161
|
+
checkpoint(options);
|
|
162
|
+
directories(directory, false);
|
|
163
|
+
if (!same(before, lstatSync(directory)))
|
|
164
|
+
throw new Error("activity: changed directory");
|
|
165
|
+
return directory;
|
|
166
|
+
}
|
|
167
|
+
/** A global spool lock serializes size accounting and sequence allocation. */
|
|
168
|
+
export async function writeActivityEvent(event, options) {
|
|
169
|
+
const now = options.now ?? Date.now();
|
|
170
|
+
if (!isActivityEvent(event, now, event.source === "hook" ? "hook" : "desktop"))
|
|
171
|
+
throw new Error("activity: invalid event");
|
|
172
|
+
const directory = await prepareActivitySpool(options);
|
|
173
|
+
const lock = path.join(directory, ".writer");
|
|
174
|
+
const deadline = performance.now() + 40;
|
|
175
|
+
while (true) {
|
|
176
|
+
checkpoint(options);
|
|
177
|
+
try {
|
|
178
|
+
mkdirSync(lock, { mode: 0o700 });
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
if (code(error) !== "EEXIST" || performance.now() >= deadline)
|
|
183
|
+
throw error;
|
|
184
|
+
const info = lstatSync(lock);
|
|
185
|
+
if (!info.isDirectory() || info.isSymbolicLink())
|
|
186
|
+
throw new Error("activity: unsafe lock");
|
|
187
|
+
// Only an empty abandoned lock can be recovered. Never recurse into it.
|
|
188
|
+
if (now - info.mtimeMs > STALE_WRITER_LOCK_MS) {
|
|
189
|
+
rmdirSync(lock);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
await new Promise((resolve) => setTimeout(resolve, 2));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
let temporary;
|
|
196
|
+
try {
|
|
197
|
+
directories(directory, false);
|
|
198
|
+
let total = 0;
|
|
199
|
+
let sequence = Math.max(event.sequence, now * 1_000);
|
|
200
|
+
const entries = readdirSync(directory);
|
|
201
|
+
if (entries.length > MAX_ENTRIES)
|
|
202
|
+
throw new Error("activity: too many entries");
|
|
203
|
+
for (const name of entries) {
|
|
204
|
+
checkpoint(options);
|
|
205
|
+
if (name === ".writer")
|
|
206
|
+
continue;
|
|
207
|
+
const file = path.join(directory, name);
|
|
208
|
+
const info = lstatSync(file);
|
|
209
|
+
if (!regular(info))
|
|
210
|
+
throw new Error("activity: unsafe spool entry");
|
|
211
|
+
const match = EVENT_NAME.exec(name);
|
|
212
|
+
const owned = match !== null || TEMP_NAME.test(name);
|
|
213
|
+
if (owned && now - info.mtimeMs > MAX_ACTIVITY_EVENT_AGE_MS) {
|
|
214
|
+
unlinkSync(file);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (info.size > MAX_ACTIVITY_EVENT_BYTES)
|
|
218
|
+
throw new Error("activity: oversized spool entry");
|
|
219
|
+
/* Names and times only: parsing every record here made each write O(n)
|
|
220
|
+
reads and dropped hooks once a busy day filled the spool. A record
|
|
221
|
+
whose observedAt is stale is refused by the reader and removed by
|
|
222
|
+
the age cleanup above. */
|
|
223
|
+
total += info.size;
|
|
224
|
+
if (match !== null)
|
|
225
|
+
sequence = Math.max(sequence, Number(match[1]) + 1);
|
|
226
|
+
}
|
|
227
|
+
const stored = { ...event, sequence };
|
|
228
|
+
if (!isActivityEvent(stored, now, event.source === "hook" ? "hook" : "desktop"))
|
|
229
|
+
throw new Error("activity: invalid sequence");
|
|
230
|
+
const bytes = Buffer.from(JSON.stringify(stored));
|
|
231
|
+
if (bytes.length > MAX_ACTIVITY_EVENT_BYTES || total + bytes.length > MAX_ACTIVITY_SPOOL_BYTES)
|
|
232
|
+
throw new Error("activity: spool full");
|
|
233
|
+
const id = randomUUID();
|
|
234
|
+
temporary = path.join(directory, "." + id + ".tmp");
|
|
235
|
+
const fd = openSync(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 0o600);
|
|
236
|
+
try {
|
|
237
|
+
writeFileSync(fd, bytes);
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
closeSync(fd);
|
|
241
|
+
}
|
|
242
|
+
checkpoint(options);
|
|
243
|
+
directories(directory, false);
|
|
244
|
+
renameSync(temporary, path.join(directory, String(sequence).padStart(16, "0") + "-" + id + ".json"));
|
|
245
|
+
temporary = undefined;
|
|
246
|
+
return stored;
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
if (temporary !== undefined) {
|
|
250
|
+
try {
|
|
251
|
+
unlinkSync(temporary);
|
|
252
|
+
}
|
|
253
|
+
catch { /* Failed writes never publish. */ }
|
|
254
|
+
}
|
|
255
|
+
rmdirSync(lock);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/** Read only. Invalid, stale, partial and hostile records are counted and skipped. */
|
|
259
|
+
export function readActivitySpool(directory, now = Date.now()) {
|
|
260
|
+
const events = [];
|
|
261
|
+
let skipped = 0;
|
|
262
|
+
try {
|
|
263
|
+
const spool = spoolPath(directory);
|
|
264
|
+
directories(spool, false);
|
|
265
|
+
const info = lstatSync(spool);
|
|
266
|
+
if (process.platform !== "win32" && (info.uid !== process.getuid?.() || (info.mode & 0o077) !== 0))
|
|
267
|
+
throw new Error("activity: unsafe directory");
|
|
268
|
+
const names = readdirSync(spool);
|
|
269
|
+
let total = 0;
|
|
270
|
+
for (const [index, name] of names.entries()) {
|
|
271
|
+
if (name === ".acl-verified" && process.platform === "win32") {
|
|
272
|
+
const stamp = lstatSync(path.join(spool, name));
|
|
273
|
+
if (!regular(stamp) || stamp.size > MAX_ACTIVITY_EVENT_BYTES)
|
|
274
|
+
skipped += 1;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (name === ".writer") {
|
|
278
|
+
const lock = lstatSync(path.join(spool, name));
|
|
279
|
+
if (!lock.isDirectory() || lock.isSymbolicLink())
|
|
280
|
+
skipped += 1;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
try {
|
|
284
|
+
if (index >= MAX_ENTRIES || !EVENT_NAME.test(name))
|
|
285
|
+
throw new Error("activity: unexpected name");
|
|
286
|
+
const file = path.join(spool, name);
|
|
287
|
+
const info = lstatSync(file);
|
|
288
|
+
total += info.size;
|
|
289
|
+
if (total > MAX_ACTIVITY_SPOOL_BYTES)
|
|
290
|
+
throw new Error("activity: read cap");
|
|
291
|
+
events.push(readFile(file, info, now));
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
skipped += 1;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
if (code(error) !== "ENOENT")
|
|
300
|
+
skipped += 1;
|
|
301
|
+
}
|
|
302
|
+
events.sort((left, right) => left.sequence - right.sequence || left.observedAt.localeCompare(right.observedAt) || left.eventId.localeCompare(right.eventId));
|
|
303
|
+
return { events, skipped };
|
|
304
|
+
}
|
|
305
|
+
//# sourceMappingURL=spool.js.map
|