@yanlinglabs/winter-agent-sdk 0.0.1 → 0.0.3
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 +30 -0
- package/dist/index-51ysrfm8.js +614 -0
- package/dist/index-9e98bg1r.js +707 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +292 -741
- package/dist/messaging/adapter.d.ts +119 -0
- package/dist/messaging/addressing.d.ts +13 -0
- package/dist/messaging/attribution.d.ts +43 -0
- package/dist/messaging/idle.d.ts +54 -0
- package/dist/messaging/inbound.d.ts +39 -0
- package/dist/messaging/index.d.ts +15 -0
- package/dist/messaging/index.js +118 -0
- package/dist/messaging/outcomes.d.ts +25 -0
- package/dist/messaging/resolution.d.ts +39 -0
- package/dist/messaging/router.d.ts +100 -0
- package/dist/paths/project-key.d.ts +2 -0
- package/dist/protocol/messaging.d.ts +111 -0
- package/dist/query.d.ts +91 -0
- package/dist/tools/accept.d.ts +39 -0
- package/dist/tools/advisor.d.ts +106 -0
- package/dist/tools/definitions.d.ts +31 -0
- package/dist/tools/index.d.ts +14 -0
- package/dist/tools/index.js +438 -0
- package/dist/tools/messaging-handlers.d.ts +52 -0
- package/dist/tools/port.d.ts +39 -0
- package/dist/tools/schemas.d.ts +48 -0
- package/dist/version.d.ts +1 -0
- package/package.json +13 -5
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
// src/brand.ts
|
|
2
|
+
var BRAND_TOKEN_RE2 = /^[a-z][a-z0-9-]{0,31}$/;
|
|
3
|
+
var DOT_DIR_RE = /^\.[a-z][a-z0-9-]{0,31}$/;
|
|
4
|
+
var INSTRUCTIONS_FILE_RE = /^[A-Z][A-Z0-9_]{0,31}\.md$/;
|
|
5
|
+
var ENV_PREFIX_RE = /^[A-Z][A-Z0-9]{0,15}_$/;
|
|
6
|
+
var KEYCHAIN_SERVICE_RE = /^[a-z][a-z0-9.-]{0,63}$/;
|
|
7
|
+
var MAX_PRODUCT_NAME = 64;
|
|
8
|
+
var CONTROL_BYTE_RE = /[\u0000-\u001f\u007f]/;
|
|
9
|
+
function isHttpsUrl(value) {
|
|
10
|
+
let url;
|
|
11
|
+
try {
|
|
12
|
+
url = new URL(value);
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
if (CONTROL_BYTE_RE.test(value))
|
|
17
|
+
return false;
|
|
18
|
+
return url.protocol === "https:";
|
|
19
|
+
}
|
|
20
|
+
var FIRST_PARTY_ORIGINATORS2 = ["codex", "codex_cli_rs", "openai", "anthropic", "claude", "claude-code"];
|
|
21
|
+
var WINTER_BRAND2 = Object.freeze({
|
|
22
|
+
productName: "Winter",
|
|
23
|
+
packageName: "winter-agent-sdk",
|
|
24
|
+
homeDirName: ".winter",
|
|
25
|
+
projectDirName: ".winter",
|
|
26
|
+
instructionsFile: "WINTER.md",
|
|
27
|
+
envPrefix: "WINTER_",
|
|
28
|
+
keychainService: "com.winter.core",
|
|
29
|
+
mcpServerName: "winter",
|
|
30
|
+
presetName: "winter_code",
|
|
31
|
+
processLabel: "winter",
|
|
32
|
+
codexOriginator: "winter",
|
|
33
|
+
tempRootName: "winter",
|
|
34
|
+
pluginManifestDir: ".winter-plugin",
|
|
35
|
+
contactUrl: "https://github.com/yanlingLabs/winter-agent-sdk"
|
|
36
|
+
});
|
|
37
|
+
var FIELD_RULES = [
|
|
38
|
+
{ field: "packageName", re: BRAND_TOKEN_RE2, shape: "a lowercase token of 1-32 chars: a letter, then letters/digits/hyphens" },
|
|
39
|
+
{ field: "mcpServerName", re: BRAND_TOKEN_RE2, shape: "a lowercase token of 1-32 chars: a letter, then letters/digits/hyphens" },
|
|
40
|
+
{ field: "processLabel", re: BRAND_TOKEN_RE2, shape: "a lowercase token of 1-32 chars: a letter, then letters/digits/hyphens" },
|
|
41
|
+
{ field: "tempRootName", re: BRAND_TOKEN_RE2, shape: "a lowercase token of 1-32 chars: a letter, then letters/digits/hyphens" },
|
|
42
|
+
{ field: "codexOriginator", re: BRAND_TOKEN_RE2, shape: "a lowercase token of 1-32 chars: a letter, then letters/digits/hyphens" },
|
|
43
|
+
{ field: "homeDirName", re: DOT_DIR_RE, shape: "a LEADING DOT then a lowercase token (it names a hidden directory)" },
|
|
44
|
+
{ field: "projectDirName", re: DOT_DIR_RE, shape: "a LEADING DOT then a lowercase token (it names a hidden directory)" },
|
|
45
|
+
{ field: "pluginManifestDir", re: DOT_DIR_RE, shape: "a LEADING DOT then a lowercase token (it names a hidden directory)" },
|
|
46
|
+
{ field: "instructionsFile", re: INSTRUCTIONS_FILE_RE, shape: 'an UPPERCASE name with a `.md` extension, e.g. "ACME.md"' },
|
|
47
|
+
{ field: "envPrefix", re: ENV_PREFIX_RE, shape: 'an UPPERCASE prefix ENDING IN AN UNDERSCORE, e.g. "ACME_"' },
|
|
48
|
+
{ field: "keychainService", re: KEYCHAIN_SERVICE_RE, shape: 'a lowercase reverse-DNS-style service name, e.g. "com.acme.core"' }
|
|
49
|
+
];
|
|
50
|
+
function resolveBrand2(partial) {
|
|
51
|
+
const p = partial ?? {};
|
|
52
|
+
const pick = (key) => p[key] === undefined ? WINTER_BRAND2[key] : p[key];
|
|
53
|
+
const brand = {
|
|
54
|
+
productName: pick("productName"),
|
|
55
|
+
packageName: pick("packageName"),
|
|
56
|
+
homeDirName: pick("homeDirName"),
|
|
57
|
+
projectDirName: pick("projectDirName"),
|
|
58
|
+
instructionsFile: pick("instructionsFile"),
|
|
59
|
+
envPrefix: pick("envPrefix"),
|
|
60
|
+
keychainService: pick("keychainService"),
|
|
61
|
+
mcpServerName: pick("mcpServerName"),
|
|
62
|
+
presetName: pick("presetName"),
|
|
63
|
+
processLabel: pick("processLabel"),
|
|
64
|
+
codexOriginator: pick("codexOriginator"),
|
|
65
|
+
tempRootName: pick("tempRootName"),
|
|
66
|
+
pluginManifestDir: pick("pluginManifestDir"),
|
|
67
|
+
contactUrl: pick("contactUrl")
|
|
68
|
+
};
|
|
69
|
+
for (const key of Object.keys(brand)) {
|
|
70
|
+
if (typeof brand[key] !== "string")
|
|
71
|
+
return { ok: false, reason: `brand.${key}: expected a string, got ${brand[key] === null ? "null" : typeof brand[key]}` };
|
|
72
|
+
}
|
|
73
|
+
if (brand.productName.length === 0 || brand.productName.length > MAX_PRODUCT_NAME) {
|
|
74
|
+
return { ok: false, reason: `brand.productName: expected 1-${MAX_PRODUCT_NAME} characters, got ${brand.productName.length}` };
|
|
75
|
+
}
|
|
76
|
+
if (brand.presetName.length === 0)
|
|
77
|
+
return { ok: false, reason: "brand.presetName: expected a non-empty preset name" };
|
|
78
|
+
for (const rule of FIELD_RULES) {
|
|
79
|
+
const value = brand[rule.field];
|
|
80
|
+
if (!rule.re.test(value))
|
|
81
|
+
return { ok: false, reason: `brand.${rule.field}: ${JSON.stringify(value)} is not ${rule.shape}` };
|
|
82
|
+
}
|
|
83
|
+
if (!isHttpsUrl(brand.contactUrl)) {
|
|
84
|
+
return {
|
|
85
|
+
ok: false,
|
|
86
|
+
reason: `brand.contactUrl: ${JSON.stringify(brand.contactUrl)} is not an https:// URL — it is published to vendors in identity headers as the way to reach whoever runs this client`
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (FIRST_PARTY_ORIGINATORS2.includes(brand.codexOriginator)) {
|
|
90
|
+
return {
|
|
91
|
+
ok: false,
|
|
92
|
+
reason: `brand.codexOriginator: ${JSON.stringify(brand.codexOriginator)} is a first-party value. ` + `The originator field names the CLIENT, and sending a vendor's own name presents this software as that vendor's tool — ` + `supply your own token instead (one of: ${FIRST_PARTY_ORIGINATORS2.join(", ")} is never it).`
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return { ok: true, brand };
|
|
96
|
+
}
|
|
97
|
+
function envName2(brand, suffix) {
|
|
98
|
+
return `${brand.envPrefix}${suffix}`;
|
|
99
|
+
}
|
|
100
|
+
function mcpToolName2(brand, tool) {
|
|
101
|
+
return `mcp__${brand.mcpServerName}__${tool}`;
|
|
102
|
+
}
|
|
103
|
+
function userAgent2(brand, version) {
|
|
104
|
+
return `${brand.packageName}/${version}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/paths/home.ts
|
|
108
|
+
import { homedir } from "node:os";
|
|
109
|
+
import { join } from "node:path";
|
|
110
|
+
function isUnset2(value) {
|
|
111
|
+
return value === undefined || value.trim() === "";
|
|
112
|
+
}
|
|
113
|
+
function resolveWinterHome2(env, brand) {
|
|
114
|
+
const b = brand ?? WINTER_BRAND2;
|
|
115
|
+
const e = env ?? process.env;
|
|
116
|
+
const override = e[envName2(b, "HOME")];
|
|
117
|
+
if (!isUnset2(override))
|
|
118
|
+
return override;
|
|
119
|
+
const profile = e[envName2(b, "PROFILE")];
|
|
120
|
+
const dirName = profile !== undefined && profile.trim() === "dev" ? `${b.homeDirName}-dev` : b.homeDirName;
|
|
121
|
+
return join(homedir(), dirName);
|
|
122
|
+
}
|
|
123
|
+
function resolveKeychainServiceForProfile2(brand, env, hostSetKeychainService) {
|
|
124
|
+
if (hostSetKeychainService)
|
|
125
|
+
return brand.keychainService;
|
|
126
|
+
const profile = (env ?? process.env)[envName2(brand, "PROFILE")];
|
|
127
|
+
if (profile === undefined || profile.trim() !== "dev")
|
|
128
|
+
return brand.keychainService;
|
|
129
|
+
const suffixed = `${brand.keychainService}.dev`;
|
|
130
|
+
return suffixed.length <= 64 ? suffixed : brand.keychainService;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/store/leases.ts
|
|
134
|
+
import { openSync, readFileSync, writeSync, fsyncSync, closeSync, renameSync, linkSync, unlinkSync } from "node:fs";
|
|
135
|
+
|
|
136
|
+
class WinterStoreError2 extends Error {
|
|
137
|
+
constructor(message) {
|
|
138
|
+
super(message);
|
|
139
|
+
this.name = "WinterStoreError";
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
class WinterStoreLeaseError2 extends WinterStoreError2 {
|
|
144
|
+
heldByPid;
|
|
145
|
+
constructor(message, heldByPid) {
|
|
146
|
+
super(message);
|
|
147
|
+
this.heldByPid = heldByPid;
|
|
148
|
+
this.name = "WinterStoreLeaseError";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function isPidAlive(pid) {
|
|
152
|
+
try {
|
|
153
|
+
process.kill(pid, 0);
|
|
154
|
+
return true;
|
|
155
|
+
} catch (err) {
|
|
156
|
+
const code = err.code;
|
|
157
|
+
if (code === "ESRCH")
|
|
158
|
+
return false;
|
|
159
|
+
if (code === "EPERM")
|
|
160
|
+
return true;
|
|
161
|
+
throw err;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function writeAllSync(fd, buf) {
|
|
165
|
+
let written = 0;
|
|
166
|
+
while (written < buf.length) {
|
|
167
|
+
written += writeSync(fd, buf, written, buf.length - written);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function readLeaseInfo(lockPath) {
|
|
171
|
+
let raw;
|
|
172
|
+
try {
|
|
173
|
+
raw = readFileSync(lockPath, "utf8");
|
|
174
|
+
} catch (err) {
|
|
175
|
+
if (err.code === "ENOENT")
|
|
176
|
+
return null;
|
|
177
|
+
throw err;
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const parsed = JSON.parse(raw);
|
|
181
|
+
if (typeof parsed.pid === "number" && typeof parsed.startTimeMs === "number") {
|
|
182
|
+
return { pid: parsed.pid, startTimeMs: parsed.startTimeMs };
|
|
183
|
+
}
|
|
184
|
+
} catch {}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
function createExclusive(lockPath, info) {
|
|
188
|
+
const data = Buffer.from(JSON.stringify(info), "utf8");
|
|
189
|
+
const tmpPath = `${lockPath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
190
|
+
const fd = openSync(tmpPath, "wx", 384);
|
|
191
|
+
try {
|
|
192
|
+
writeAllSync(fd, data);
|
|
193
|
+
fsyncSync(fd);
|
|
194
|
+
} finally {
|
|
195
|
+
closeSync(fd);
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
linkSync(tmpPath, lockPath);
|
|
199
|
+
} finally {
|
|
200
|
+
try {
|
|
201
|
+
unlinkSync(tmpPath);
|
|
202
|
+
} catch (err) {
|
|
203
|
+
if (err.code !== "ENOENT")
|
|
204
|
+
throw err;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function writeLeaseInfoReplacing(lockPath, info) {
|
|
209
|
+
const data = Buffer.from(JSON.stringify(info), "utf8");
|
|
210
|
+
const tmpPath = `${lockPath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
211
|
+
const fd = openSync(tmpPath, "wx", 384);
|
|
212
|
+
try {
|
|
213
|
+
writeAllSync(fd, data);
|
|
214
|
+
fsyncSync(fd);
|
|
215
|
+
} finally {
|
|
216
|
+
closeSync(fd);
|
|
217
|
+
}
|
|
218
|
+
renameSync(tmpPath, lockPath);
|
|
219
|
+
}
|
|
220
|
+
function acquireLease(lockPath) {
|
|
221
|
+
const fresh = { pid: process.pid, startTimeMs: Date.now() };
|
|
222
|
+
try {
|
|
223
|
+
createExclusive(lockPath, fresh);
|
|
224
|
+
return fresh;
|
|
225
|
+
} catch (err) {
|
|
226
|
+
if (err.code !== "EEXIST")
|
|
227
|
+
throw err;
|
|
228
|
+
}
|
|
229
|
+
const existing = readLeaseInfo(lockPath);
|
|
230
|
+
if (existing !== null && existing.pid === process.pid) {
|
|
231
|
+
return existing;
|
|
232
|
+
}
|
|
233
|
+
if (existing !== null && isPidAlive(existing.pid)) {
|
|
234
|
+
throw new WinterStoreLeaseError2(`session lease is held by another live process (pid ${existing.pid}); refusing a concurrent writer`, existing.pid);
|
|
235
|
+
}
|
|
236
|
+
writeLeaseInfoReplacing(lockPath, fresh);
|
|
237
|
+
return fresh;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/store/session-store.ts
|
|
241
|
+
import {
|
|
242
|
+
mkdirSync,
|
|
243
|
+
lstatSync,
|
|
244
|
+
chmodSync,
|
|
245
|
+
readdirSync,
|
|
246
|
+
readFileSync as readFileSync2,
|
|
247
|
+
statSync,
|
|
248
|
+
openSync as openSync2,
|
|
249
|
+
fsyncSync as fsyncSync2,
|
|
250
|
+
closeSync as closeSync2,
|
|
251
|
+
renameSync as renameSync2,
|
|
252
|
+
rmSync,
|
|
253
|
+
ftruncateSync,
|
|
254
|
+
constants as fsConstants
|
|
255
|
+
} from "node:fs";
|
|
256
|
+
import { randomUUID } from "node:crypto";
|
|
257
|
+
import { join as join2 } from "node:path";
|
|
258
|
+
var DIALECT_RECORD_ENTRY_TYPE2 = "winter_dialect_record";
|
|
259
|
+
var PROVIDER_STATE_FILE_SUFFIX2 = ".provider-state.jsonl";
|
|
260
|
+
function assertSafeSingleSegment(value, label) {
|
|
261
|
+
if (value === "")
|
|
262
|
+
throw new WinterStoreError2(`${label} must not be empty`);
|
|
263
|
+
if (value.includes("/"))
|
|
264
|
+
throw new WinterStoreError2(`${label} must not contain a path separator: ${JSON.stringify(value)}`);
|
|
265
|
+
if (value === "." || value === "..")
|
|
266
|
+
throw new WinterStoreError2(`${label} must not be a traversal segment: ${JSON.stringify(value)}`);
|
|
267
|
+
}
|
|
268
|
+
function assertSafeSubpath(value) {
|
|
269
|
+
if (value === "")
|
|
270
|
+
throw new WinterStoreError2("subpath must not be empty");
|
|
271
|
+
if (value.startsWith("/"))
|
|
272
|
+
throw new WinterStoreError2(`subpath must not be absolute: ${JSON.stringify(value)}`);
|
|
273
|
+
const segments = value.split("/");
|
|
274
|
+
for (const segment of segments) {
|
|
275
|
+
if (segment === "") {
|
|
276
|
+
throw new WinterStoreError2(`subpath must not contain empty segments (double/trailing separators): ${JSON.stringify(value)}`);
|
|
277
|
+
}
|
|
278
|
+
if (segment === "." || segment === "..") {
|
|
279
|
+
throw new WinterStoreError2(`subpath must not contain a traversal segment: ${JSON.stringify(value)}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return segments;
|
|
283
|
+
}
|
|
284
|
+
function projectDir(winterHome, projectKey) {
|
|
285
|
+
return join2(winterHome, "projects", projectKey);
|
|
286
|
+
}
|
|
287
|
+
function sessionStem(winterHome, projectKey, sessionId) {
|
|
288
|
+
assertSafeSingleSegment(projectKey, "projectKey");
|
|
289
|
+
assertSafeSingleSegment(sessionId, "sessionId");
|
|
290
|
+
return join2(projectDir(winterHome, projectKey), sessionId);
|
|
291
|
+
}
|
|
292
|
+
function locateResource(winterHome, key) {
|
|
293
|
+
assertSafeSingleSegment(key.projectKey, "projectKey");
|
|
294
|
+
assertSafeSingleSegment(key.sessionId, "sessionId");
|
|
295
|
+
const projDir = projectDir(winterHome, key.projectKey);
|
|
296
|
+
const dirLevels = [winterHome, join2(winterHome, "projects"), projDir];
|
|
297
|
+
if (key.subpath === undefined) {
|
|
298
|
+
return { dirLevels, stem: join2(projDir, key.sessionId) };
|
|
299
|
+
}
|
|
300
|
+
const segments = assertSafeSubpath(key.subpath);
|
|
301
|
+
let current = join2(projDir, key.sessionId);
|
|
302
|
+
dirLevels.push(current);
|
|
303
|
+
for (let i = 0;i < segments.length - 1; i++) {
|
|
304
|
+
current = join2(current, segments[i]);
|
|
305
|
+
dirLevels.push(current);
|
|
306
|
+
}
|
|
307
|
+
const stem = join2(current, segments[segments.length - 1]);
|
|
308
|
+
return { dirLevels, stem };
|
|
309
|
+
}
|
|
310
|
+
function realUid() {
|
|
311
|
+
return process.getuid();
|
|
312
|
+
}
|
|
313
|
+
function ensureSecureDir(path) {
|
|
314
|
+
try {
|
|
315
|
+
mkdirSync(path, { mode: 448 });
|
|
316
|
+
} catch (err) {
|
|
317
|
+
if (err.code !== "EEXIST")
|
|
318
|
+
throw err;
|
|
319
|
+
}
|
|
320
|
+
const stat = lstatSync(path);
|
|
321
|
+
if (stat.isSymbolicLink())
|
|
322
|
+
throw new WinterStoreError2(`refusing a symlink at a level the store must own: ${path}`);
|
|
323
|
+
if (!stat.isDirectory())
|
|
324
|
+
throw new WinterStoreError2(`expected a directory, found something else at: ${path}`);
|
|
325
|
+
if (stat.uid !== realUid())
|
|
326
|
+
throw new WinterStoreError2(`refusing a directory owned by a different uid: ${path}`);
|
|
327
|
+
chmodSync(path, 448);
|
|
328
|
+
}
|
|
329
|
+
var APPEND_FLAGS = fsConstants.O_WRONLY | fsConstants.O_APPEND | fsConstants.O_CREAT | fsConstants.O_NOFOLLOW;
|
|
330
|
+
var RW_EXISTING_FLAGS = fsConstants.O_RDWR | fsConstants.O_NOFOLLOW;
|
|
331
|
+
function appendLinesAtomically(path, lines) {
|
|
332
|
+
const data = Buffer.from(lines.map((l) => l + `
|
|
333
|
+
`).join(""), "utf8");
|
|
334
|
+
const fd = openSync2(path, APPEND_FLAGS, 384);
|
|
335
|
+
try {
|
|
336
|
+
writeAllSync(fd, data);
|
|
337
|
+
fsyncSync2(fd);
|
|
338
|
+
} finally {
|
|
339
|
+
closeSync2(fd);
|
|
340
|
+
}
|
|
341
|
+
chmodSync(path, 384);
|
|
342
|
+
}
|
|
343
|
+
function quarantineTornTail(jsonlPath, tornRaw) {
|
|
344
|
+
const quarantinePath = `${jsonlPath}.tail-quarantine`;
|
|
345
|
+
const fd = openSync2(quarantinePath, APPEND_FLAGS, 384);
|
|
346
|
+
try {
|
|
347
|
+
writeAllSync(fd, tornRaw);
|
|
348
|
+
fsyncSync2(fd);
|
|
349
|
+
} finally {
|
|
350
|
+
closeSync2(fd);
|
|
351
|
+
}
|
|
352
|
+
chmodSync(quarantinePath, 384);
|
|
353
|
+
}
|
|
354
|
+
function repairTruncate(jsonlPath, keepBytes) {
|
|
355
|
+
const fd = openSync2(jsonlPath, RW_EXISTING_FLAGS);
|
|
356
|
+
try {
|
|
357
|
+
ftruncateSync(fd, keepBytes);
|
|
358
|
+
fsyncSync2(fd);
|
|
359
|
+
} finally {
|
|
360
|
+
closeSync2(fd);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function writeJsonAtomically(path, value) {
|
|
364
|
+
const data = Buffer.from(JSON.stringify(value), "utf8");
|
|
365
|
+
const tmpPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
366
|
+
const fd = openSync2(tmpPath, "wx", 384);
|
|
367
|
+
try {
|
|
368
|
+
writeAllSync(fd, data);
|
|
369
|
+
fsyncSync2(fd);
|
|
370
|
+
} finally {
|
|
371
|
+
closeSync2(fd);
|
|
372
|
+
}
|
|
373
|
+
renameSync2(tmpPath, path);
|
|
374
|
+
chmodSync(path, 384);
|
|
375
|
+
}
|
|
376
|
+
function readJsonIfExists(path) {
|
|
377
|
+
let raw;
|
|
378
|
+
try {
|
|
379
|
+
raw = readFileSync2(path, "utf8");
|
|
380
|
+
} catch (err) {
|
|
381
|
+
if (err.code === "ENOENT")
|
|
382
|
+
return null;
|
|
383
|
+
throw err;
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
return JSON.parse(raw);
|
|
387
|
+
} catch {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function rmIfExists(path) {
|
|
392
|
+
try {
|
|
393
|
+
rmSync(path);
|
|
394
|
+
} catch (err) {
|
|
395
|
+
if (err.code !== "ENOENT")
|
|
396
|
+
throw err;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
var NEWLINE = 10;
|
|
400
|
+
function isParseableJson(text) {
|
|
401
|
+
try {
|
|
402
|
+
JSON.parse(text);
|
|
403
|
+
return true;
|
|
404
|
+
} catch {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
function decodeCompleteLines(buf) {
|
|
409
|
+
if (buf.length === 0)
|
|
410
|
+
return [];
|
|
411
|
+
const lines = buf.toString("utf8").split(`
|
|
412
|
+
`);
|
|
413
|
+
lines.pop();
|
|
414
|
+
return lines.map((line) => JSON.parse(line));
|
|
415
|
+
}
|
|
416
|
+
function parseWithTailRepair(buf) {
|
|
417
|
+
if (buf.length === 0)
|
|
418
|
+
return { entries: [], torn: null };
|
|
419
|
+
const endsWithNewline = buf[buf.length - 1] === NEWLINE;
|
|
420
|
+
if (endsWithNewline) {
|
|
421
|
+
const searchEnd = buf.length - 2;
|
|
422
|
+
const prevNL = searchEnd < 0 ? -1 : buf.lastIndexOf(NEWLINE, searchEnd);
|
|
423
|
+
const lastLineStart = prevNL + 1;
|
|
424
|
+
const lastLine = buf.subarray(lastLineStart, buf.length - 1).toString("utf8");
|
|
425
|
+
if (isParseableJson(lastLine)) {
|
|
426
|
+
return { entries: decodeCompleteLines(buf), torn: null };
|
|
427
|
+
}
|
|
428
|
+
const keepBytes = lastLineStart;
|
|
429
|
+
const tornRaw = Buffer.from(buf.subarray(lastLineStart));
|
|
430
|
+
return { entries: decodeCompleteLines(buf.subarray(0, keepBytes)), torn: { raw: tornRaw, keepBytes } };
|
|
431
|
+
}
|
|
432
|
+
const lastNL = buf.lastIndexOf(NEWLINE);
|
|
433
|
+
const keepBytes = lastNL + 1;
|
|
434
|
+
const tornRaw = Buffer.from(buf.subarray(keepBytes));
|
|
435
|
+
return { entries: decodeCompleteLines(buf.subarray(0, keepBytes)), torn: { raw: tornRaw, keepBytes } };
|
|
436
|
+
}
|
|
437
|
+
function hasLiveForeignLeaseHolder(lockPath) {
|
|
438
|
+
const lease = readLeaseInfo(lockPath);
|
|
439
|
+
return lease !== null && lease.pid !== process.pid && isPidAlive(lease.pid);
|
|
440
|
+
}
|
|
441
|
+
function walkResourceStems(dir, prefix, out) {
|
|
442
|
+
let entries;
|
|
443
|
+
try {
|
|
444
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
445
|
+
} catch (err) {
|
|
446
|
+
if (err.code === "ENOENT")
|
|
447
|
+
return;
|
|
448
|
+
throw err;
|
|
449
|
+
}
|
|
450
|
+
for (const dirent of entries) {
|
|
451
|
+
const relPath = prefix === "" ? dirent.name : `${prefix}/${dirent.name}`;
|
|
452
|
+
if (dirent.isDirectory()) {
|
|
453
|
+
walkResourceStems(join2(dir, dirent.name), relPath, out);
|
|
454
|
+
} else if (dirent.isFile() && dirent.name.endsWith(".jsonl")) {
|
|
455
|
+
out.add(relPath.slice(0, -".jsonl".length));
|
|
456
|
+
} else if (dirent.isFile() && dirent.name.endsWith(".meta.json")) {
|
|
457
|
+
out.add(relPath.slice(0, -".meta.json".length));
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function foldSummary(summaryPath, sessionId, newEntries, dialectExtra) {
|
|
462
|
+
const previous = readJsonIfExists(summaryPath) ?? {};
|
|
463
|
+
const mechanical = {};
|
|
464
|
+
if (newEntries.length > 0) {
|
|
465
|
+
const lastEntry = newEntries[newEntries.length - 1];
|
|
466
|
+
mechanical.entryCount = (previous.entryCount ?? 0) + newEntries.length;
|
|
467
|
+
mechanical.lastEntryType = lastEntry.type;
|
|
468
|
+
if (lastEntry.timestamp !== undefined)
|
|
469
|
+
mechanical.lastTimestamp = lastEntry.timestamp;
|
|
470
|
+
}
|
|
471
|
+
const updated = {
|
|
472
|
+
...previous,
|
|
473
|
+
...dialectExtra,
|
|
474
|
+
sessionId,
|
|
475
|
+
...mechanical,
|
|
476
|
+
mtime: Date.now()
|
|
477
|
+
};
|
|
478
|
+
writeJsonAtomically(summaryPath, updated);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
class WinterCompatibilitySessionStore2 {
|
|
482
|
+
winterHome;
|
|
483
|
+
constructor(opts) {
|
|
484
|
+
this.winterHome = opts.winterHome;
|
|
485
|
+
}
|
|
486
|
+
async append(key, entries) {
|
|
487
|
+
if (entries.length === 0)
|
|
488
|
+
return;
|
|
489
|
+
const { dirLevels, stem } = locateResource(this.winterHome, key);
|
|
490
|
+
for (const level of dirLevels)
|
|
491
|
+
ensureSecureDir(level);
|
|
492
|
+
const lockPath = `${sessionStem(this.winterHome, key.projectKey, key.sessionId)}.lock`;
|
|
493
|
+
acquireLease(lockPath);
|
|
494
|
+
chmodSync(lockPath, 384);
|
|
495
|
+
const jsonlPath = `${stem}.jsonl`;
|
|
496
|
+
const metaPath = `${stem}.meta.json`;
|
|
497
|
+
const nativeEntries = [];
|
|
498
|
+
let latestMetadata;
|
|
499
|
+
let dialectExtra;
|
|
500
|
+
for (const e of entries) {
|
|
501
|
+
if (e.type === "agent_metadata") {
|
|
502
|
+
latestMetadata = e;
|
|
503
|
+
} else if (e.type === DIALECT_RECORD_ENTRY_TYPE2) {
|
|
504
|
+
const { type: _type, ...fields } = e;
|
|
505
|
+
dialectExtra = fields;
|
|
506
|
+
} else {
|
|
507
|
+
nativeEntries.push(e);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (nativeEntries.length > 0) {
|
|
511
|
+
appendLinesAtomically(jsonlPath, nativeEntries.map((e) => JSON.stringify(e)));
|
|
512
|
+
}
|
|
513
|
+
if (latestMetadata !== undefined) {
|
|
514
|
+
writeJsonAtomically(metaPath, latestMetadata);
|
|
515
|
+
}
|
|
516
|
+
if (key.subpath === undefined && (nativeEntries.length > 0 || dialectExtra !== undefined)) {
|
|
517
|
+
foldSummary(`${sessionStem(this.winterHome, key.projectKey, key.sessionId)}.summary.json`, key.sessionId, nativeEntries, dialectExtra);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
async load(key) {
|
|
521
|
+
const { stem } = locateResource(this.winterHome, key);
|
|
522
|
+
const jsonlPath = `${stem}.jsonl`;
|
|
523
|
+
const metaPath = `${stem}.meta.json`;
|
|
524
|
+
let raw;
|
|
525
|
+
try {
|
|
526
|
+
raw = readFileSync2(jsonlPath);
|
|
527
|
+
} catch (err) {
|
|
528
|
+
if (err.code !== "ENOENT")
|
|
529
|
+
throw err;
|
|
530
|
+
raw = null;
|
|
531
|
+
}
|
|
532
|
+
if (raw === null) {
|
|
533
|
+
const meta = readJsonIfExists(metaPath);
|
|
534
|
+
return meta === null ? null : [meta];
|
|
535
|
+
}
|
|
536
|
+
const { entries, torn } = parseWithTailRepair(raw);
|
|
537
|
+
if (torn !== null) {
|
|
538
|
+
const lockPath = `${sessionStem(this.winterHome, key.projectKey, key.sessionId)}.lock`;
|
|
539
|
+
if (!hasLiveForeignLeaseHolder(lockPath)) {
|
|
540
|
+
quarantineTornTail(jsonlPath, torn.raw);
|
|
541
|
+
repairTruncate(jsonlPath, torn.keepBytes);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const meta = readJsonIfExists(metaPath);
|
|
545
|
+
if (meta !== null)
|
|
546
|
+
entries.push(meta);
|
|
547
|
+
return entries;
|
|
548
|
+
}
|
|
549
|
+
async listSessions(projectKey) {
|
|
550
|
+
assertSafeSingleSegment(projectKey, "projectKey");
|
|
551
|
+
const dir = projectDir(this.winterHome, projectKey);
|
|
552
|
+
let names;
|
|
553
|
+
try {
|
|
554
|
+
names = readdirSync(dir);
|
|
555
|
+
} catch (err) {
|
|
556
|
+
if (err.code === "ENOENT")
|
|
557
|
+
return [];
|
|
558
|
+
throw err;
|
|
559
|
+
}
|
|
560
|
+
const result = [];
|
|
561
|
+
const seenSessionIds = new Set;
|
|
562
|
+
for (const name of names) {
|
|
563
|
+
if (!name.endsWith(".jsonl"))
|
|
564
|
+
continue;
|
|
565
|
+
const full = join2(dir, name);
|
|
566
|
+
const stat = statSync(full);
|
|
567
|
+
if (!stat.isFile())
|
|
568
|
+
continue;
|
|
569
|
+
const sessionId = name.slice(0, -".jsonl".length);
|
|
570
|
+
seenSessionIds.add(sessionId);
|
|
571
|
+
result.push({ sessionId, mtime: stat.mtimeMs });
|
|
572
|
+
}
|
|
573
|
+
for (const name of names) {
|
|
574
|
+
if (!name.endsWith(".meta.json"))
|
|
575
|
+
continue;
|
|
576
|
+
const sessionId = name.slice(0, -".meta.json".length);
|
|
577
|
+
if (seenSessionIds.has(sessionId))
|
|
578
|
+
continue;
|
|
579
|
+
const full = join2(dir, name);
|
|
580
|
+
const stat = statSync(full);
|
|
581
|
+
if (!stat.isFile())
|
|
582
|
+
continue;
|
|
583
|
+
seenSessionIds.add(sessionId);
|
|
584
|
+
result.push({ sessionId, mtime: stat.mtimeMs });
|
|
585
|
+
}
|
|
586
|
+
return result;
|
|
587
|
+
}
|
|
588
|
+
async listSessionSummaries(projectKey) {
|
|
589
|
+
assertSafeSingleSegment(projectKey, "projectKey");
|
|
590
|
+
const dir = projectDir(this.winterHome, projectKey);
|
|
591
|
+
let names;
|
|
592
|
+
try {
|
|
593
|
+
names = readdirSync(dir);
|
|
594
|
+
} catch (err) {
|
|
595
|
+
if (err.code === "ENOENT")
|
|
596
|
+
return [];
|
|
597
|
+
throw err;
|
|
598
|
+
}
|
|
599
|
+
const result = [];
|
|
600
|
+
for (const name of names) {
|
|
601
|
+
if (!name.endsWith(".summary.json"))
|
|
602
|
+
continue;
|
|
603
|
+
const parsed = readJsonIfExists(join2(dir, name));
|
|
604
|
+
if (parsed !== null)
|
|
605
|
+
result.push(parsed);
|
|
606
|
+
}
|
|
607
|
+
return result;
|
|
608
|
+
}
|
|
609
|
+
async delete(key) {
|
|
610
|
+
const { stem } = locateResource(this.winterHome, key);
|
|
611
|
+
if (key.subpath === undefined) {
|
|
612
|
+
rmIfExists(`${stem}.jsonl`);
|
|
613
|
+
rmIfExists(`${stem}.jsonl.tail-quarantine`);
|
|
614
|
+
rmIfExists(`${stem}.lock`);
|
|
615
|
+
rmIfExists(`${stem}.summary.json`);
|
|
616
|
+
rmIfExists(`${stem}.meta.json`);
|
|
617
|
+
rmIfExists(`${stem}${PROVIDER_STATE_FILE_SUFFIX2}`);
|
|
618
|
+
rmSync(stem, { recursive: true, force: true });
|
|
619
|
+
} else {
|
|
620
|
+
rmIfExists(`${stem}.jsonl`);
|
|
621
|
+
rmIfExists(`${stem}.jsonl.tail-quarantine`);
|
|
622
|
+
rmIfExists(`${stem}.meta.json`);
|
|
623
|
+
rmIfExists(`${stem}${PROVIDER_STATE_FILE_SUFFIX2}`);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
async listSubkeys(key) {
|
|
627
|
+
const sessionDir = sessionStem(this.winterHome, key.projectKey, key.sessionId);
|
|
628
|
+
const results = new Set;
|
|
629
|
+
walkResourceStems(sessionDir, "", results);
|
|
630
|
+
return [...results];
|
|
631
|
+
}
|
|
632
|
+
async listProjectKeys() {
|
|
633
|
+
const dir = join2(this.winterHome, "projects");
|
|
634
|
+
let entries;
|
|
635
|
+
try {
|
|
636
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
637
|
+
} catch (err) {
|
|
638
|
+
if (err.code === "ENOENT")
|
|
639
|
+
return [];
|
|
640
|
+
throw err;
|
|
641
|
+
}
|
|
642
|
+
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
643
|
+
}
|
|
644
|
+
async copyProviderStateForFork(src, dest) {
|
|
645
|
+
const { stem: srcStem } = locateResource(this.winterHome, src);
|
|
646
|
+
const { stem: destStem, dirLevels } = locateResource(this.winterHome, dest);
|
|
647
|
+
let raw;
|
|
648
|
+
try {
|
|
649
|
+
raw = readFileSync2(`${srcStem}${PROVIDER_STATE_FILE_SUFFIX2}`, "utf8");
|
|
650
|
+
} catch (err) {
|
|
651
|
+
if (err.code === "ENOENT")
|
|
652
|
+
return 0;
|
|
653
|
+
throw err;
|
|
654
|
+
}
|
|
655
|
+
const lines = [];
|
|
656
|
+
for (const line of raw.split(`
|
|
657
|
+
`)) {
|
|
658
|
+
if (line.length === 0)
|
|
659
|
+
continue;
|
|
660
|
+
let record;
|
|
661
|
+
try {
|
|
662
|
+
record = JSON.parse(line);
|
|
663
|
+
} catch {
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
if (typeof record !== "object" || record === null || Array.isArray(record))
|
|
667
|
+
continue;
|
|
668
|
+
lines.push(JSON.stringify({ ...record, uuid: randomUUID(), sessionId: dest.sessionId }));
|
|
669
|
+
}
|
|
670
|
+
if (lines.length === 0)
|
|
671
|
+
return 0;
|
|
672
|
+
for (const level of dirLevels)
|
|
673
|
+
ensureSecureDir(level);
|
|
674
|
+
appendLinesAtomically(`${destStem}${PROVIDER_STATE_FILE_SUFFIX2}`, lines);
|
|
675
|
+
return lines.length;
|
|
676
|
+
}
|
|
677
|
+
async readSessionSummary(key) {
|
|
678
|
+
assertSafeSingleSegment(key.projectKey, "projectKey");
|
|
679
|
+
assertSafeSingleSegment(key.sessionId, "sessionId");
|
|
680
|
+
return readJsonIfExists(`${sessionStem(this.winterHome, key.projectKey, key.sessionId)}.summary.json`);
|
|
681
|
+
}
|
|
682
|
+
async mergeSessionMetadata(key, patch) {
|
|
683
|
+
const stem = sessionStem(this.winterHome, key.projectKey, key.sessionId);
|
|
684
|
+
const lockPath = `${stem}.lock`;
|
|
685
|
+
acquireLease(lockPath);
|
|
686
|
+
chmodSync(lockPath, 384);
|
|
687
|
+
const summaryPath = `${stem}.summary.json`;
|
|
688
|
+
const previous = readJsonIfExists(summaryPath) ?? {};
|
|
689
|
+
const updated = {
|
|
690
|
+
...previous,
|
|
691
|
+
...patch,
|
|
692
|
+
sessionId: key.sessionId,
|
|
693
|
+
mtime: Date.now()
|
|
694
|
+
};
|
|
695
|
+
writeJsonAtomically(summaryPath, updated);
|
|
696
|
+
}
|
|
697
|
+
async acquireSessionLease(key) {
|
|
698
|
+
const { dirLevels } = locateResource(this.winterHome, key);
|
|
699
|
+
for (const level of dirLevels)
|
|
700
|
+
ensureSecureDir(level);
|
|
701
|
+
const lockPath = `${sessionStem(this.winterHome, key.projectKey, key.sessionId)}.lock`;
|
|
702
|
+
acquireLease(lockPath);
|
|
703
|
+
chmodSync(lockPath, 384);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
export { BRAND_TOKEN_RE2, FIRST_PARTY_ORIGINATORS2, WINTER_BRAND2, resolveBrand2, envName2, mcpToolName2, userAgent2, isUnset2, resolveWinterHome2, resolveKeychainServiceForProfile2, WinterStoreError2, WinterStoreLeaseError2, DIALECT_RECORD_ENTRY_TYPE2, PROVIDER_STATE_FILE_SUFFIX2, WinterCompatibilitySessionStore2 };
|