@byokit/openclaw 0.5.0 → 0.6.1
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/CHANGELOG.md +39 -0
- package/README.md +174 -2
- package/dist/auth-store.js +1 -1
- package/dist/bridge.d.ts +10 -2
- package/dist/bridge.js +61 -14
- package/dist/browser/broker.d.ts +63 -0
- package/dist/browser/broker.js +990 -0
- package/dist/browser/host.d.ts +98 -0
- package/dist/browser/host.js +945 -0
- package/dist/browser/resume.d.ts +10 -0
- package/dist/browser/resume.js +23 -0
- package/dist/browser/store.d.ts +25 -0
- package/dist/browser/store.js +151 -0
- package/dist/browser/verify.d.ts +17 -0
- package/dist/browser/verify.js +38 -0
- package/dist/browser.d.ts +212 -0
- package/dist/browser.js +4 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.js +130 -0
- package/dist/day-usage.d.ts +76 -0
- package/dist/day-usage.js +104 -0
- package/dist/device.d.ts +32 -2
- package/dist/device.js +224 -4
- package/dist/engine-patches.d.ts +36 -0
- package/dist/engine-patches.js +320 -0
- package/dist/engine-status.d.ts +13 -0
- package/dist/engine-status.js +31 -0
- package/dist/engine.d.ts +13 -1
- package/dist/engine.js +99 -52
- package/dist/index.d.ts +6 -1
- package/dist/index.js +2 -0
- package/dist/kit.d.ts +29 -3
- package/dist/kit.js +324 -13
- package/dist/link.d.ts +5 -2
- package/dist/link.js +333 -4
- package/dist/routes.d.ts +19 -8
- package/dist/routes.js +44 -11
- package/dist/routes.json +3775 -356
- package/dist/runs.js +8 -2
- package/dist/signin.d.ts +2 -1
- package/dist/signin.js +103 -7
- package/dist/testing/browser.d.ts +50 -0
- package/dist/testing/browser.js +166 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +1 -0
- package/dist/testing/model-stub.d.ts +1 -0
- package/dist/testing/model-stub.js +3 -1
- package/dist/types.d.ts +50 -3
- package/dist/usage-boots.d.ts +8 -0
- package/dist/usage-boots.js +26 -0
- package/dist/usage.d.ts +51 -0
- package/dist/usage.js +61 -0
- package/dist/words.json +42 -0
- package/engine/OPENCLAW-LICENSE +24 -0
- package/engine/patches.json +59 -0
- package/engine/patches.schema.json +41 -0
- package/package.json +12 -4
- package/plugin/index.js +78 -5
- package/plugin/usage.js +125 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type PatchFile = {
|
|
2
|
+
path: string;
|
|
3
|
+
before: string;
|
|
4
|
+
after: string;
|
|
5
|
+
edits: {
|
|
6
|
+
find: string;
|
|
7
|
+
replace: string;
|
|
8
|
+
}[];
|
|
9
|
+
};
|
|
10
|
+
export type PatchSet = {
|
|
11
|
+
v: 1;
|
|
12
|
+
id: string;
|
|
13
|
+
upstream: {
|
|
14
|
+
name: string;
|
|
15
|
+
version: string;
|
|
16
|
+
integrity: string;
|
|
17
|
+
commit: string;
|
|
18
|
+
license: string;
|
|
19
|
+
};
|
|
20
|
+
files: PatchFile[];
|
|
21
|
+
};
|
|
22
|
+
export declare class EnginePatchError extends Error {
|
|
23
|
+
readonly code = "engine-patch";
|
|
24
|
+
readonly cause: 'spec' | 'drift' | 'drift-after-build' | 'write';
|
|
25
|
+
readonly file?: string;
|
|
26
|
+
constructor(cause: EnginePatchError['cause'], file?: string);
|
|
27
|
+
}
|
|
28
|
+
export declare const sha256: (text: string | Buffer) => string;
|
|
29
|
+
export declare const patchId: (files: PatchFile[]) => string;
|
|
30
|
+
export declare function readPatchSet(path: string, version: string, integrity: string): PatchSet;
|
|
31
|
+
export declare function editText(text: string, file: PatchFile): string;
|
|
32
|
+
export declare function atomic(path: string, text: string): void;
|
|
33
|
+
export declare function processStartTime(pid: number): string;
|
|
34
|
+
export declare function verifyEngineSet(dir: string, set: PatchSet, installMatches: (dir: string) => boolean): void;
|
|
35
|
+
export declare function engineSetName(set: PatchSet): string;
|
|
36
|
+
export declare function prepareEngineSet(engineDir: string, set: PatchSet, install: (dir: string) => void | Promise<void>, installMatches: (dir: string) => boolean): Promise<string>;
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
// Internal artifact seam. Published patch semantics live only in engine/patches.json.
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { closeSync, constants, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { chmod, cp, open, readdir, rm } from 'node:fs/promises';
|
|
5
|
+
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
6
|
+
export class EnginePatchError extends Error {
|
|
7
|
+
code = 'engine-patch';
|
|
8
|
+
cause;
|
|
9
|
+
file;
|
|
10
|
+
constructor(cause, file) {
|
|
11
|
+
super(`engine-patch: ${cause}${file ? ` (${file})` : ''}`);
|
|
12
|
+
this.name = 'EnginePatchError';
|
|
13
|
+
this.cause = cause;
|
|
14
|
+
this.file = file;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export const sha256 = (text) => createHash('sha256').update(text).digest('hex');
|
|
18
|
+
function canonical(value) {
|
|
19
|
+
if (Array.isArray(value))
|
|
20
|
+
return value.map(canonical);
|
|
21
|
+
if (value && typeof value === 'object')
|
|
22
|
+
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => [key, canonical(item)]));
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
export const patchId = (files) => sha256(JSON.stringify(canonical(files))).slice(0, 16);
|
|
26
|
+
const object = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
27
|
+
const keys = (v, expected) => Object.keys(v).sort().join(',') === expected.sort().join(',');
|
|
28
|
+
function validateFiles(id, files) {
|
|
29
|
+
if (typeof id !== 'string' || !Array.isArray(files))
|
|
30
|
+
throw new EnginePatchError('spec');
|
|
31
|
+
const paths = new Set();
|
|
32
|
+
for (const f of files) {
|
|
33
|
+
if (!object(f) || !keys(f, ['path', 'before', 'after', 'edits']) || typeof f.path !== 'string' ||
|
|
34
|
+
!/^dist\/(?:[A-Za-z0-9_-]+\/)*[A-Za-z0-9_.-]+\.m?js$/.test(f.path) || f.path.split('/').some(p => p === '.' || p === '..') || paths.has(f.path) ||
|
|
35
|
+
typeof f.before !== 'string' || !/^[a-f0-9]{64}$/.test(f.before) || typeof f.after !== 'string' || !/^[a-f0-9]{64}$/.test(f.after) || f.before === f.after ||
|
|
36
|
+
!Array.isArray(f.edits) || !f.edits.length)
|
|
37
|
+
throw new EnginePatchError('spec');
|
|
38
|
+
paths.add(f.path);
|
|
39
|
+
for (const e of f.edits)
|
|
40
|
+
if (!object(e) || !keys(e, ['find', 'replace']) || typeof e.find !== 'string' || !e.find || typeof e.replace !== 'string' || !e.replace || e.find === e.replace)
|
|
41
|
+
throw new EnginePatchError('spec');
|
|
42
|
+
}
|
|
43
|
+
if (id !== patchId(files))
|
|
44
|
+
throw new EnginePatchError('spec');
|
|
45
|
+
}
|
|
46
|
+
export function readPatchSet(path, version, integrity) {
|
|
47
|
+
try {
|
|
48
|
+
const value = JSON.parse(readFileSync(path, 'utf8'));
|
|
49
|
+
if (!object(value) || !keys(value, ['v', 'id', 'upstream', 'files']) || value.v !== 1 || !object(value.upstream) ||
|
|
50
|
+
!keys(value.upstream, ['name', 'version', 'integrity', 'commit', 'license']) || value.upstream.name !== 'openclaw' || value.upstream.version !== version ||
|
|
51
|
+
value.upstream.integrity !== integrity || value.upstream.license !== 'MIT' || typeof value.upstream.commit !== 'string' || !/^[a-f0-9]{40}$/.test(value.upstream.commit))
|
|
52
|
+
throw new Error();
|
|
53
|
+
validateFiles(value.id, value.files);
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
throw new EnginePatchError('spec', path);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export function editText(text, file) {
|
|
61
|
+
for (const edit of file.edits) {
|
|
62
|
+
const at = text.indexOf(edit.find);
|
|
63
|
+
if (at < 0 || text.indexOf(edit.find, at + 1) >= 0)
|
|
64
|
+
throw new EnginePatchError('spec', file.path);
|
|
65
|
+
text = text.slice(0, at) + edit.replace + text.slice(at + edit.find.length);
|
|
66
|
+
}
|
|
67
|
+
return text;
|
|
68
|
+
}
|
|
69
|
+
export function atomic(path, text) {
|
|
70
|
+
const temp = `${path}.${randomUUID()}.tmp`;
|
|
71
|
+
let fd;
|
|
72
|
+
try {
|
|
73
|
+
fd = openSync(temp, 'wx', 0o600);
|
|
74
|
+
writeFileSync(fd, text);
|
|
75
|
+
fsyncSync(fd);
|
|
76
|
+
closeSync(fd);
|
|
77
|
+
fd = undefined;
|
|
78
|
+
renameSync(temp, path);
|
|
79
|
+
syncDir(dirname(path));
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
throw new EnginePatchError('write', path);
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
if (fd !== undefined)
|
|
86
|
+
closeSync(fd);
|
|
87
|
+
rmSync(temp, { force: true });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const syncDir = (path) => { const fd = openSync(path, 'r'); try {
|
|
91
|
+
fsyncSync(fd);
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
closeSync(fd);
|
|
95
|
+
} };
|
|
96
|
+
export function processStartTime(pid) {
|
|
97
|
+
const stat = readFileSync(`/proc/${pid}/stat`, 'utf8');
|
|
98
|
+
const startTime = stat.slice(stat.lastIndexOf(')') + 2).split(' ')[19];
|
|
99
|
+
if (!startTime || !/^\d+$/.test(startTime))
|
|
100
|
+
throw new EnginePatchError('write');
|
|
101
|
+
return startTime;
|
|
102
|
+
}
|
|
103
|
+
const noLink = (path) => { if (lstatSync(path, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
104
|
+
throw new EnginePatchError('spec', path); };
|
|
105
|
+
const inside = (root, path) => { const rel = relative(root, path); return rel !== '..' && !rel.startsWith(`..${sep}`) && !rel.startsWith(sep); };
|
|
106
|
+
function tree(dir) {
|
|
107
|
+
const result = [];
|
|
108
|
+
const root = realpathSync(dir);
|
|
109
|
+
const walk = (path, rel) => {
|
|
110
|
+
const info = lstatSync(path);
|
|
111
|
+
if (info.isSymbolicLink()) {
|
|
112
|
+
const link = readlinkSync(path);
|
|
113
|
+
if (resolve(link) === link || !inside(root, realpathSync(path)))
|
|
114
|
+
throw new EnginePatchError('spec', rel);
|
|
115
|
+
result.push({ path: rel, kind: 'link', mode: info.mode & 0o777, size: info.size, hash: sha256(link) });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (info.isDirectory()) {
|
|
119
|
+
for (const name of readdirSync(path).sort()) {
|
|
120
|
+
if (!rel && ['.byokit-tree', '.byokit-patches'].includes(name))
|
|
121
|
+
continue;
|
|
122
|
+
walk(join(path, name), rel ? `${rel}/${name}` : name);
|
|
123
|
+
}
|
|
124
|
+
result.push({ path: rel, kind: 'dir', mode: info.mode & 0o777, size: 0, hash: '' });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (!info.isFile())
|
|
128
|
+
throw new EnginePatchError('spec', rel);
|
|
129
|
+
const hash = sha256(readFileSync(path));
|
|
130
|
+
result.push({ path: rel, kind: 'file', mode: info.mode & 0o777, size: info.size, hash });
|
|
131
|
+
};
|
|
132
|
+
walk(dir, '');
|
|
133
|
+
return result.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
134
|
+
}
|
|
135
|
+
function metadata(dir, set) {
|
|
136
|
+
const root = join(dir, 'node_modules/openclaw');
|
|
137
|
+
const build = JSON.parse(readFileSync(join(root, 'dist/build-info.json'), 'utf8'));
|
|
138
|
+
if (build.version !== set.upstream.version || build.commit !== set.upstream.commit || JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version !== set.upstream.version)
|
|
139
|
+
throw new EnginePatchError('drift', 'dist/build-info.json');
|
|
140
|
+
for (const file of set.files) {
|
|
141
|
+
const path = join(root, file.path);
|
|
142
|
+
if (!inside(realpathSync(root), realpathSync(path)) || lstatSync(path).isSymbolicLink() || sha256(readFileSync(path)) !== file.after)
|
|
143
|
+
throw new EnginePatchError('drift', file.path);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Full verification on EVERY prepare/adoption, not just listed patched files or the directory mode.
|
|
147
|
+
export function verifyEngineSet(dir, set, installMatches) {
|
|
148
|
+
noLink(dir);
|
|
149
|
+
try {
|
|
150
|
+
for (const name of ['.byokit-tree', '.byokit-patches']) {
|
|
151
|
+
const info = lstatSync(join(dir, name));
|
|
152
|
+
if (!info.isFile() || (info.mode & 0o777) !== 0o444)
|
|
153
|
+
throw new Error();
|
|
154
|
+
}
|
|
155
|
+
const bytes = readFileSync(join(dir, '.byokit-tree'), 'utf8');
|
|
156
|
+
const marker = JSON.parse(readFileSync(join(dir, '.byokit-patches'), 'utf8'));
|
|
157
|
+
if (!object(marker) || !keys(marker, ['id', 'files', 'tree']) || marker.id !== set.id || JSON.stringify(marker.files) !== JSON.stringify(set.files) || marker.tree !== sha256(bytes))
|
|
158
|
+
throw new Error();
|
|
159
|
+
metadata(dir, set);
|
|
160
|
+
if (!installMatches(dir) || bytes !== JSON.stringify(tree(dir)))
|
|
161
|
+
throw new Error();
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (error instanceof EnginePatchError && error.cause === 'spec')
|
|
165
|
+
throw error;
|
|
166
|
+
throw new EnginePatchError('drift', dir);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// chmod/remove only this invocation's unlaunched private temp, never a final set or the base install.
|
|
170
|
+
async function removeTemp(dir) {
|
|
171
|
+
if (!existsSync(dir))
|
|
172
|
+
return;
|
|
173
|
+
const walk = async (path) => {
|
|
174
|
+
if (!lstatSync(path).isDirectory())
|
|
175
|
+
return;
|
|
176
|
+
await chmod(path, 0o700);
|
|
177
|
+
for (const name of await readdir(path))
|
|
178
|
+
await walk(join(path, name));
|
|
179
|
+
};
|
|
180
|
+
await walk(dir);
|
|
181
|
+
await rm(dir, { recursive: true, force: true });
|
|
182
|
+
}
|
|
183
|
+
async function freeze(dir) {
|
|
184
|
+
const entries = tree(dir);
|
|
185
|
+
// Bound descriptors/I/O while yielding the host event loop. Every regular file and directory is fsynced.
|
|
186
|
+
const paths = [...entries.filter(e => e.kind === 'file'), ...entries.filter(e => e.kind === 'dir').reverse()];
|
|
187
|
+
for (let i = 0; i < paths.length; i += 16)
|
|
188
|
+
await Promise.all(paths.slice(i, i + 16).map(async (entry) => {
|
|
189
|
+
const path = join(dir, entry.path);
|
|
190
|
+
await chmod(path, entry.kind === 'dir' ? 0o555 : 0o444);
|
|
191
|
+
const fd = await open(path, 'r');
|
|
192
|
+
try {
|
|
193
|
+
await fd.sync();
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
await fd.close();
|
|
197
|
+
}
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
export function engineSetName(set) { return `${sha256(set.upstream.integrity).slice(0, 16)}-${set.id}`; }
|
|
201
|
+
export async function prepareEngineSet(engineDir, set, install, installMatches) {
|
|
202
|
+
validateFiles(set.id, set.files);
|
|
203
|
+
const sets = `${engineDir}.sets`;
|
|
204
|
+
noLink(sets);
|
|
205
|
+
mkdirSync(sets, { recursive: true, mode: 0o700 });
|
|
206
|
+
const name = engineSetName(set);
|
|
207
|
+
for (const candidate of readdirSync(sets).filter(n => n === name || n.startsWith(`${name}.`)).sort()) {
|
|
208
|
+
const dir = join(sets, candidate);
|
|
209
|
+
try {
|
|
210
|
+
verifyEngineSet(dir, set, installMatches);
|
|
211
|
+
return dir;
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
if (!(error instanceof EnginePatchError) || error.cause !== 'drift')
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const stock = { ...set, id: patchId([]), files: [] };
|
|
219
|
+
const stockDir = set.files.length ? await prepareEngineSet(engineDir, stock, install, installMatches) : undefined;
|
|
220
|
+
const tmp = join(sets, `.tmp-${process.pid}-${process.platform === 'linux' ? processStartTime(process.pid) : 'unknown'}-${randomUUID()}`);
|
|
221
|
+
const final = join(sets, existsSync(join(sets, name)) ? `${name}.${randomUUID()}` : name);
|
|
222
|
+
try {
|
|
223
|
+
if (stockDir) {
|
|
224
|
+
// ponytail: reflink or plain copy, never hardlinks; no GC without per-launch leases (~889 MB/set).
|
|
225
|
+
await cp(stockDir, tmp, { recursive: true, verbatimSymlinks: true, mode: constants.COPYFILE_FICLONE });
|
|
226
|
+
// The copy is private and unlaunched. Make only it writable, and drop inherited provenance.
|
|
227
|
+
const writable = async (path) => {
|
|
228
|
+
const info = lstatSync(path);
|
|
229
|
+
if (info.isSymbolicLink())
|
|
230
|
+
return;
|
|
231
|
+
await chmod(path, info.isDirectory() ? 0o700 : 0o600);
|
|
232
|
+
if (info.isDirectory())
|
|
233
|
+
for (const name of await readdir(path))
|
|
234
|
+
await writable(join(path, name));
|
|
235
|
+
};
|
|
236
|
+
await writable(tmp);
|
|
237
|
+
rmSync(join(tmp, '.byokit-tree'));
|
|
238
|
+
rmSync(join(tmp, '.byokit-patches'));
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
mkdirSync(tmp, { mode: 0o700 });
|
|
242
|
+
await install(tmp);
|
|
243
|
+
}
|
|
244
|
+
if (!installMatches(tmp))
|
|
245
|
+
throw new EnginePatchError('drift-after-build', tmp);
|
|
246
|
+
const root = join(tmp, 'node_modules/openclaw');
|
|
247
|
+
for (const file of set.files) {
|
|
248
|
+
const path = join(root, file.path);
|
|
249
|
+
if (!inside(realpathSync(root), realpathSync(path)) || lstatSync(path).isSymbolicLink())
|
|
250
|
+
throw new EnginePatchError('spec', file.path);
|
|
251
|
+
const before = readFileSync(path, 'utf8');
|
|
252
|
+
if (sha256(before) !== file.before)
|
|
253
|
+
throw new EnginePatchError('drift-after-build', file.path);
|
|
254
|
+
const after = editText(before, file);
|
|
255
|
+
if (sha256(after) !== file.after)
|
|
256
|
+
throw new EnginePatchError('spec', file.path);
|
|
257
|
+
atomic(path, after);
|
|
258
|
+
}
|
|
259
|
+
metadata(tmp, set);
|
|
260
|
+
await freeze(tmp);
|
|
261
|
+
const bytes = JSON.stringify(tree(tmp));
|
|
262
|
+
// Root alone is writable while publishing metadata, then made read-only too.
|
|
263
|
+
await chmod(tmp, 0o700);
|
|
264
|
+
atomic(join(tmp, '.byokit-tree'), bytes);
|
|
265
|
+
atomic(join(tmp, '.byokit-patches'), JSON.stringify({ id: set.id, files: set.files, tree: sha256(bytes) }));
|
|
266
|
+
for (const file of ['.byokit-tree', '.byokit-patches']) {
|
|
267
|
+
const path = join(tmp, file);
|
|
268
|
+
await chmod(path, 0o444);
|
|
269
|
+
const fd = await open(path, 'r');
|
|
270
|
+
try {
|
|
271
|
+
await fd.sync();
|
|
272
|
+
}
|
|
273
|
+
finally {
|
|
274
|
+
await fd.close();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
await chmod(tmp, 0o555);
|
|
278
|
+
const directory = await open(tmp, 'r');
|
|
279
|
+
try {
|
|
280
|
+
await directory.sync();
|
|
281
|
+
}
|
|
282
|
+
finally {
|
|
283
|
+
await directory.close();
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
verifyEngineSet(tmp, set, installMatches);
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
if (error instanceof EnginePatchError && error.cause === 'drift')
|
|
290
|
+
throw new EnginePatchError('drift-after-build', error.file);
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
renameSync(tmp, final);
|
|
295
|
+
syncDir(sets);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
if (!['ENOTEMPTY', 'EEXIST', 'EPERM'].includes(error.code ?? ''))
|
|
299
|
+
throw error;
|
|
300
|
+
try {
|
|
301
|
+
verifyEngineSet(final, set, installMatches);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
throw new EnginePatchError('drift-after-build', final);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return final;
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
if (error instanceof EnginePatchError) {
|
|
311
|
+
if (error.cause === 'drift')
|
|
312
|
+
throw new EnginePatchError('drift-after-build', error.file);
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
315
|
+
throw new EnginePatchError('write', tmp);
|
|
316
|
+
}
|
|
317
|
+
finally {
|
|
318
|
+
await removeTemp(tmp);
|
|
319
|
+
}
|
|
320
|
+
}
|
package/dist/engine-status.d.ts
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
import type { ChildProcess } from 'node:child_process';
|
|
2
|
+
/** Every process the kit itself spawned, held for shutdown by pid. */
|
|
3
|
+
export declare class StartedProcesses {
|
|
4
|
+
private readonly started;
|
|
5
|
+
/** Record a spawn. Its pid is the kit's to signal; nothing else about it is. */
|
|
6
|
+
add(child: ChildProcess): void;
|
|
7
|
+
forget(child: ChildProcess | undefined): void;
|
|
8
|
+
/** SIGTERM each recorded pid, wait, SIGKILL the ones still running, wait. Never signals a process group. */
|
|
9
|
+
terminate(wait: (ms: number) => Promise<void>, graceMs?: number): Promise<void>;
|
|
10
|
+
private live;
|
|
11
|
+
private signal;
|
|
12
|
+
private settle;
|
|
13
|
+
}
|
|
1
14
|
/** A live or unverifiable writer still owns the isolated engine state. Retry after it stops. */
|
|
2
15
|
export declare class EngineAlreadyRunningError extends Error {
|
|
3
16
|
readonly code = "engine-already-running";
|
package/dist/engine-status.js
CHANGED
|
@@ -1,4 +1,35 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
+
/** Every process the kit itself spawned, held for shutdown by pid. */
|
|
3
|
+
export class StartedProcesses {
|
|
4
|
+
started = new Set();
|
|
5
|
+
/** Record a spawn. Its pid is the kit's to signal; nothing else about it is. */
|
|
6
|
+
add(child) { this.started.add(child); }
|
|
7
|
+
forget(child) { if (child)
|
|
8
|
+
this.started.delete(child); }
|
|
9
|
+
/** SIGTERM each recorded pid, wait, SIGKILL the ones still running, wait. Never signals a process group. */
|
|
10
|
+
async terminate(wait, graceMs = 3000) {
|
|
11
|
+
for (const child of this.live())
|
|
12
|
+
this.signal(child, 'SIGTERM');
|
|
13
|
+
await this.settle(wait, graceMs);
|
|
14
|
+
for (const child of this.live())
|
|
15
|
+
this.signal(child, 'SIGKILL');
|
|
16
|
+
await this.settle(wait, graceMs);
|
|
17
|
+
}
|
|
18
|
+
live() { return [...this.started].filter(child => child.exitCode === null && child.signalCode === null); }
|
|
19
|
+
signal(child, sig) {
|
|
20
|
+
const { pid } = child;
|
|
21
|
+
if (pid === undefined)
|
|
22
|
+
return;
|
|
23
|
+
try {
|
|
24
|
+
process.kill(pid, sig);
|
|
25
|
+
}
|
|
26
|
+
catch { /* already gone */ }
|
|
27
|
+
}
|
|
28
|
+
async settle(wait, graceMs) {
|
|
29
|
+
for (let waited = 200; waited <= graceMs && this.live().length; waited += 200)
|
|
30
|
+
await wait(200);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
2
33
|
/** A live or unverifiable writer still owns the isolated engine state. Retry after it stops. */
|
|
3
34
|
export class EngineAlreadyRunningError extends Error {
|
|
4
35
|
code = 'engine-already-running';
|
package/dist/engine.d.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import type { KitOptions } from './kit.ts';
|
|
2
2
|
import type { KitState, ToolSpec } from './types.ts';
|
|
3
|
-
export type EngineOptions = Pick<KitOptions, 'stateDir' | 'engineDir' | 'npmPath' | 'enginePath' | 'config' | 'installPolicy' | 'log' | 'bridge' | 'authSeal'> & {
|
|
3
|
+
export type EngineOptions = Pick<KitOptions, 'stateDir' | 'engineDir' | 'npmPath' | 'enginePath' | 'config' | 'appOwnedSessions' | 'installPolicy' | 'log' | 'bridge' | 'authSeal'> & {
|
|
4
4
|
pluginId: string;
|
|
5
5
|
tools: ToolSpec[];
|
|
6
6
|
gateBuiltins?: boolean;
|
|
7
7
|
spawnEngine: boolean;
|
|
8
|
+
browserConfig?: () => {
|
|
9
|
+
profiles: Record<string, {
|
|
10
|
+
cdpUrl: string;
|
|
11
|
+
attachOnly: true;
|
|
12
|
+
}>;
|
|
13
|
+
tools: string[];
|
|
14
|
+
};
|
|
8
15
|
onState(s: KitState): void;
|
|
9
16
|
onExit(code: number | null): void;
|
|
10
17
|
};
|
|
@@ -12,17 +19,22 @@ export declare class Engine {
|
|
|
12
19
|
readonly root: string;
|
|
13
20
|
readonly bridgeSock: string;
|
|
14
21
|
private readonly dir;
|
|
22
|
+
private setDir?;
|
|
23
|
+
private wantedSet?;
|
|
15
24
|
private child?;
|
|
25
|
+
private readonly started;
|
|
16
26
|
private starting?;
|
|
17
27
|
private readonly authStore;
|
|
18
28
|
private stopping;
|
|
19
29
|
private credentialsLocked;
|
|
20
30
|
private repaired;
|
|
21
31
|
private prepared?;
|
|
32
|
+
patchSet?: string | null;
|
|
22
33
|
private port;
|
|
23
34
|
private token;
|
|
24
35
|
private readonly o;
|
|
25
36
|
private readonly paramPrefix;
|
|
37
|
+
private readonly appOwnedSessionPrefixes;
|
|
26
38
|
constructor(o: EngineOptions);
|
|
27
39
|
private state;
|
|
28
40
|
private get entry();
|