@framers/agentos-ext-browser-automation 0.1.0 → 0.2.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/attach/AttachController.d.ts +183 -0
- package/dist/attach/AttachController.d.ts.map +1 -0
- package/dist/attach/AttachController.js +283 -0
- package/dist/attach/AttachController.js.map +1 -0
- package/dist/attach/__tests__/AttachController.test.d.ts +2 -0
- package/dist/attach/__tests__/AttachController.test.d.ts.map +1 -0
- package/dist/attach/__tests__/AttachController.test.js +278 -0
- package/dist/attach/__tests__/AttachController.test.js.map +1 -0
- package/dist/attach/__tests__/attach-primitives.test.d.ts +2 -0
- package/dist/attach/__tests__/attach-primitives.test.d.ts.map +1 -0
- package/dist/attach/__tests__/attach-primitives.test.js +158 -0
- package/dist/attach/__tests__/attach-primitives.test.js.map +1 -0
- package/dist/attach/__tests__/jxa-args.test.d.ts +2 -0
- package/dist/attach/__tests__/jxa-args.test.d.ts.map +1 -0
- package/dist/attach/__tests__/jxa-args.test.js +37 -0
- package/dist/attach/__tests__/jxa-args.test.js.map +1 -0
- package/dist/attach/__tests__/pack-registration.test.d.ts +2 -0
- package/dist/attach/__tests__/pack-registration.test.d.ts.map +1 -0
- package/dist/attach/__tests__/pack-registration.test.js +34 -0
- package/dist/attach/__tests__/pack-registration.test.js.map +1 -0
- package/dist/attach/__tests__/tools.test.d.ts +2 -0
- package/dist/attach/__tests__/tools.test.d.ts.map +1 -0
- package/dist/attach/__tests__/tools.test.js +155 -0
- package/dist/attach/__tests__/tools.test.js.map +1 -0
- package/dist/attach/backends/__tests__/raw-cdp.test.d.ts +2 -0
- package/dist/attach/backends/__tests__/raw-cdp.test.d.ts.map +1 -0
- package/dist/attach/backends/__tests__/raw-cdp.test.js +133 -0
- package/dist/attach/backends/__tests__/raw-cdp.test.js.map +1 -0
- package/dist/attach/backends/jxa.d.ts +39 -0
- package/dist/attach/backends/jxa.d.ts.map +1 -0
- package/dist/attach/backends/jxa.js +188 -0
- package/dist/attach/backends/jxa.js.map +1 -0
- package/dist/attach/backends/raw-cdp.d.ts +53 -0
- package/dist/attach/backends/raw-cdp.d.ts.map +1 -0
- package/dist/attach/backends/raw-cdp.js +216 -0
- package/dist/attach/backends/raw-cdp.js.map +1 -0
- package/dist/attach/daemon/__tests__/client.test.d.ts +2 -0
- package/dist/attach/daemon/__tests__/client.test.d.ts.map +1 -0
- package/dist/attach/daemon/__tests__/client.test.js +58 -0
- package/dist/attach/daemon/__tests__/client.test.js.map +1 -0
- package/dist/attach/daemon/__tests__/daemon-lock.test.d.ts +2 -0
- package/dist/attach/daemon/__tests__/daemon-lock.test.d.ts.map +1 -0
- package/dist/attach/daemon/__tests__/daemon-lock.test.js +72 -0
- package/dist/attach/daemon/__tests__/daemon-lock.test.js.map +1 -0
- package/dist/attach/daemon/__tests__/daemon-loop.integration.test.d.ts +2 -0
- package/dist/attach/daemon/__tests__/daemon-loop.integration.test.d.ts.map +1 -0
- package/dist/attach/daemon/__tests__/daemon-loop.integration.test.js +102 -0
- package/dist/attach/daemon/__tests__/daemon-loop.integration.test.js.map +1 -0
- package/dist/attach/daemon/__tests__/harness.d.ts +44 -0
- package/dist/attach/daemon/__tests__/harness.d.ts.map +1 -0
- package/dist/attach/daemon/__tests__/harness.js +96 -0
- package/dist/attach/daemon/__tests__/harness.js.map +1 -0
- package/dist/attach/daemon/__tests__/protocol.test.d.ts +2 -0
- package/dist/attach/daemon/__tests__/protocol.test.d.ts.map +1 -0
- package/dist/attach/daemon/__tests__/protocol.test.js +71 -0
- package/dist/attach/daemon/__tests__/protocol.test.js.map +1 -0
- package/dist/attach/daemon/__tests__/runner.test.d.ts +2 -0
- package/dist/attach/daemon/__tests__/runner.test.d.ts.map +1 -0
- package/dist/attach/daemon/__tests__/runner.test.js +67 -0
- package/dist/attach/daemon/__tests__/runner.test.js.map +1 -0
- package/dist/attach/daemon/client.d.ts +64 -0
- package/dist/attach/daemon/client.d.ts.map +1 -0
- package/dist/attach/daemon/client.js +120 -0
- package/dist/attach/daemon/client.js.map +1 -0
- package/dist/attach/daemon/daemon-lock.d.ts +19 -0
- package/dist/attach/daemon/daemon-lock.d.ts.map +1 -0
- package/dist/attach/daemon/daemon-lock.js +87 -0
- package/dist/attach/daemon/daemon-lock.js.map +1 -0
- package/dist/attach/daemon/daemon-main.d.ts +6 -0
- package/dist/attach/daemon/daemon-main.d.ts.map +1 -0
- package/dist/attach/daemon/daemon-main.js +80 -0
- package/dist/attach/daemon/daemon-main.js.map +1 -0
- package/dist/attach/daemon/daemon.d.ts +52 -0
- package/dist/attach/daemon/daemon.d.ts.map +1 -0
- package/dist/attach/daemon/daemon.js +264 -0
- package/dist/attach/daemon/daemon.js.map +1 -0
- package/dist/attach/daemon/index.d.ts +12 -0
- package/dist/attach/daemon/index.d.ts.map +1 -0
- package/dist/attach/daemon/index.js +30 -0
- package/dist/attach/daemon/index.js.map +1 -0
- package/dist/attach/daemon/protocol.d.ts +69 -0
- package/dist/attach/daemon/protocol.d.ts.map +1 -0
- package/dist/attach/daemon/protocol.js +108 -0
- package/dist/attach/daemon/protocol.js.map +1 -0
- package/dist/attach/daemon/runner.d.ts +64 -0
- package/dist/attach/daemon/runner.d.ts.map +1 -0
- package/dist/attach/daemon/runner.js +60 -0
- package/dist/attach/daemon/runner.js.map +1 -0
- package/dist/attach/daemon/surface.d.ts +31 -0
- package/dist/attach/daemon/surface.d.ts.map +1 -0
- package/dist/attach/daemon/surface.js +89 -0
- package/dist/attach/daemon/surface.js.map +1 -0
- package/dist/attach/devtools-port.d.ts +37 -0
- package/dist/attach/devtools-port.d.ts.map +1 -0
- package/dist/attach/devtools-port.js +50 -0
- package/dist/attach/devtools-port.js.map +1 -0
- package/dist/attach/errors.d.ts +38 -0
- package/dist/attach/errors.d.ts.map +1 -0
- package/dist/attach/errors.js +72 -0
- package/dist/attach/errors.js.map +1 -0
- package/dist/attach/lease.d.ts +43 -0
- package/dist/attach/lease.d.ts.map +1 -0
- package/dist/attach/lease.js +92 -0
- package/dist/attach/lease.js.map +1 -0
- package/dist/attach/singleton-lock.d.ts +21 -0
- package/dist/attach/singleton-lock.d.ts.map +1 -0
- package/dist/attach/singleton-lock.js +30 -0
- package/dist/attach/singleton-lock.js.map +1 -0
- package/dist/attach/state.d.ts +31 -0
- package/dist/attach/state.d.ts.map +1 -0
- package/dist/attach/state.js +78 -0
- package/dist/attach/state.js.map +1 -0
- package/dist/attach/tools.d.ts +208 -0
- package/dist/attach/tools.d.ts.map +1 -0
- package/dist/attach/tools.js +195 -0
- package/dist/attach/tools.js.map +1 -0
- package/dist/attach/url-policy.d.ts +41 -0
- package/dist/attach/url-policy.d.ts.map +1 -0
- package/dist/attach/url-policy.js +59 -0
- package/dist/attach/url-policy.js.map +1 -0
- package/dist/attach.d.ts +18 -0
- package/dist/attach.d.ts.map +1 -0
- package/dist/attach.js +34 -0
- package/dist/attach.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -1
- package/dist/index.js.map +1 -1
- package/package.json +22 -7
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fakeBackend = fakeBackend;
|
|
4
|
+
exports.daemonUnderTest = daemonUnderTest;
|
|
5
|
+
exports.op = op;
|
|
6
|
+
/**
|
|
7
|
+
* @fileoverview Shared test harness for daemon-lane suites: a recording fake
|
|
8
|
+
* backend, an in-process daemon over real queue files, and a raw file-level op
|
|
9
|
+
* driver mirroring the client contract (responses are deleted after reading).
|
|
10
|
+
*/
|
|
11
|
+
const node_fs_1 = require("node:fs");
|
|
12
|
+
const node_fs_2 = require("node:fs");
|
|
13
|
+
const node_os_1 = require("node:os");
|
|
14
|
+
const node_path_1 = require("node:path");
|
|
15
|
+
const AttachController_js_1 = require("../../AttachController.js");
|
|
16
|
+
const daemon_js_1 = require("../daemon.js");
|
|
17
|
+
const protocol_js_1 = require("../protocol.js");
|
|
18
|
+
/** Recording fake backend satisfying AttachBackend + evalInTab. */
|
|
19
|
+
function fakeBackend() {
|
|
20
|
+
const calls = [];
|
|
21
|
+
const backend = {
|
|
22
|
+
calls,
|
|
23
|
+
kind: 'cdp',
|
|
24
|
+
onUnexpectedClose: null,
|
|
25
|
+
async connect() {
|
|
26
|
+
calls.push('connect');
|
|
27
|
+
},
|
|
28
|
+
async disconnectTransport() {
|
|
29
|
+
calls.push('disconnect');
|
|
30
|
+
},
|
|
31
|
+
async claimAgentTab() {
|
|
32
|
+
calls.push('claim');
|
|
33
|
+
return 'tab-1';
|
|
34
|
+
},
|
|
35
|
+
async probeIdentity() {
|
|
36
|
+
calls.push('probe');
|
|
37
|
+
return 'Inbox - user@gmail.com - Gmail';
|
|
38
|
+
},
|
|
39
|
+
async gotoTab(_t, url) {
|
|
40
|
+
calls.push(`goto:${url}`);
|
|
41
|
+
return url;
|
|
42
|
+
},
|
|
43
|
+
async readTab() {
|
|
44
|
+
calls.push('read');
|
|
45
|
+
return 'PAGE TEXT';
|
|
46
|
+
},
|
|
47
|
+
async evalInTab(_t, expression) {
|
|
48
|
+
calls.push('eval');
|
|
49
|
+
return expression.includes('querySelectorAll') || expression.includes('location.href')
|
|
50
|
+
? { extracted: true }
|
|
51
|
+
: 'evaluated';
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
return backend;
|
|
55
|
+
}
|
|
56
|
+
/** An in-process daemon over a fresh temp dir; caller owns cleanup + `await run`. */
|
|
57
|
+
function daemonUnderTest(opts = {}) {
|
|
58
|
+
const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'attach-daemon-'));
|
|
59
|
+
const backend = fakeBackend();
|
|
60
|
+
const controller = new AttachController_js_1.AttachController({
|
|
61
|
+
backend,
|
|
62
|
+
leaseFile: (0, node_path_1.join)(dir, 'lease'),
|
|
63
|
+
expectedIdentity: 'user@gmail.com',
|
|
64
|
+
});
|
|
65
|
+
const daemon = new daemon_js_1.AttachDaemon({
|
|
66
|
+
controller,
|
|
67
|
+
backend,
|
|
68
|
+
ipcDir: (0, node_path_1.join)(dir, 'ipc'),
|
|
69
|
+
pidFile: (0, node_path_1.join)(dir, 'daemon.pid'),
|
|
70
|
+
pollMs: 25,
|
|
71
|
+
claimTtlMs: opts.claimTtlMs ?? 60000,
|
|
72
|
+
});
|
|
73
|
+
const run = daemon.run();
|
|
74
|
+
return { dir, backend, controller, daemon, run };
|
|
75
|
+
}
|
|
76
|
+
/** Raw file-level op driver; deletes the response after reading (client contract). */
|
|
77
|
+
async function op(dir, client, opName, args) {
|
|
78
|
+
const ipc = (0, node_path_1.join)(dir, 'ipc');
|
|
79
|
+
const id = (0, protocol_js_1.newCmdId)(client);
|
|
80
|
+
(0, protocol_js_1.writeAtomicJson)((0, protocol_js_1.cmdPath)(ipc, id), { id, client, op: opName, args });
|
|
81
|
+
for (let i = 0; i < 400; i++) {
|
|
82
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
83
|
+
const resp = (0, protocol_js_1.readJson)((0, protocol_js_1.respPath)(ipc, id));
|
|
84
|
+
if (resp) {
|
|
85
|
+
try {
|
|
86
|
+
(0, node_fs_2.unlinkSync)((0, protocol_js_1.respPath)(ipc, id));
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
/* raced */
|
|
90
|
+
}
|
|
91
|
+
return resp;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
throw new Error(`no response to ${opName}`);
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=harness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness.js","sourceRoot":"","sources":["../../../../src/attach/daemon/__tests__/harness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.test.d.ts","sourceRoot":"","sources":["../../../../src/attach/daemon/__tests__/protocol.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_os_1 = require("node:os");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
const protocol_js_1 = require("../protocol.js");
|
|
8
|
+
const dirs = [];
|
|
9
|
+
const tdir = () => {
|
|
10
|
+
const d = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'attach-ipc-'));
|
|
11
|
+
dirs.push(d);
|
|
12
|
+
return d;
|
|
13
|
+
};
|
|
14
|
+
(0, vitest_1.afterEach)(() => {
|
|
15
|
+
for (const d of dirs.splice(0))
|
|
16
|
+
(0, node_fs_1.rmSync)(d, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
(0, vitest_1.describe)('protocol', () => {
|
|
19
|
+
(0, vitest_1.it)('ids are strings and survive a JSON round trip at time_ns scale', () => {
|
|
20
|
+
const id = (0, protocol_js_1.newCmdId)('cli');
|
|
21
|
+
(0, vitest_1.expect)(typeof id).toBe('string');
|
|
22
|
+
(0, vitest_1.expect)(id.startsWith('cli-')).toBe(true);
|
|
23
|
+
// the historical bug: 1753112793123456789 > 2^53 rounds as a JS number
|
|
24
|
+
const echoed = JSON.parse(JSON.stringify({ id: 'cli-1753112793123456789-x' }));
|
|
25
|
+
(0, vitest_1.expect)(echoed.id).toBe('cli-1753112793123456789-x');
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.it)('writeAtomicJson leaves no .tmp behind and readJson round-trips', () => {
|
|
28
|
+
const d = tdir();
|
|
29
|
+
const f = (0, node_path_1.join)(d, 'x.json');
|
|
30
|
+
(0, protocol_js_1.writeAtomicJson)(f, { a: 1 });
|
|
31
|
+
(0, vitest_1.expect)((0, protocol_js_1.readJson)(f)).toEqual({ a: 1 });
|
|
32
|
+
(0, vitest_1.expect)((0, node_fs_1.readdirSync)(d).filter((n) => n.includes('.tmp'))).toEqual([]);
|
|
33
|
+
});
|
|
34
|
+
(0, vitest_1.it)('readJson returns undefined for missing or torn files', () => {
|
|
35
|
+
const d = tdir();
|
|
36
|
+
(0, vitest_1.expect)((0, protocol_js_1.readJson)((0, node_path_1.join)(d, 'missing.json'))).toBeUndefined();
|
|
37
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(d, 'torn.json'), '{not json');
|
|
38
|
+
(0, vitest_1.expect)((0, protocol_js_1.readJson)((0, node_path_1.join)(d, 'torn.json'))).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
(0, vitest_1.it)('listPendingCmds returns cmd-* files oldest-first and ignores responses and temp files', () => {
|
|
41
|
+
const d = tdir();
|
|
42
|
+
(0, protocol_js_1.writeAtomicJson)((0, protocol_js_1.cmdPath)(d, 'b'), { id: 'b' });
|
|
43
|
+
(0, protocol_js_1.writeAtomicJson)((0, protocol_js_1.cmdPath)(d, 'a'), { id: 'a' });
|
|
44
|
+
(0, protocol_js_1.writeAtomicJson)((0, protocol_js_1.respPath)(d, 'zzz'), { id: 'zzz' });
|
|
45
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(d, 'cmd-torn.json.tmp'), '{');
|
|
46
|
+
const past = new Date(Date.now() - 5000);
|
|
47
|
+
(0, node_fs_1.utimesSync)((0, protocol_js_1.cmdPath)(d, 'b'), past, past);
|
|
48
|
+
(0, vitest_1.expect)((0, protocol_js_1.listPendingCmds)(d).map((c) => c.id)).toEqual(['b', 'a']);
|
|
49
|
+
});
|
|
50
|
+
(0, vitest_1.it)('gcStale removes cmd/resp files older than STALE_FILE_MS and keeps fresh ones', () => {
|
|
51
|
+
const d = tdir();
|
|
52
|
+
(0, protocol_js_1.writeAtomicJson)((0, protocol_js_1.cmdPath)(d, 'old'), { id: 'old' });
|
|
53
|
+
(0, protocol_js_1.writeAtomicJson)((0, protocol_js_1.respPath)(d, 'old'), { id: 'old' });
|
|
54
|
+
(0, protocol_js_1.writeAtomicJson)((0, protocol_js_1.cmdPath)(d, 'new'), { id: 'new' });
|
|
55
|
+
const past = new Date(Date.now() - protocol_js_1.STALE_FILE_MS - 1000);
|
|
56
|
+
(0, node_fs_1.utimesSync)((0, protocol_js_1.cmdPath)(d, 'old'), past, past);
|
|
57
|
+
(0, node_fs_1.utimesSync)((0, protocol_js_1.respPath)(d, 'old'), past, past);
|
|
58
|
+
(0, protocol_js_1.gcStale)(d);
|
|
59
|
+
(0, vitest_1.expect)((0, node_fs_1.existsSync)((0, protocol_js_1.cmdPath)(d, 'old'))).toBe(false);
|
|
60
|
+
(0, vitest_1.expect)((0, node_fs_1.existsSync)((0, protocol_js_1.respPath)(d, 'old'))).toBe(false);
|
|
61
|
+
(0, vitest_1.expect)((0, node_fs_1.existsSync)((0, protocol_js_1.cmdPath)(d, 'new'))).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
(0, vitest_1.it)('gcStale with now=Infinity sweeps every queue file regardless of age', () => {
|
|
64
|
+
const d = tdir();
|
|
65
|
+
(0, protocol_js_1.writeAtomicJson)((0, protocol_js_1.cmdPath)(d, 'x'), { id: 'x' });
|
|
66
|
+
(0, protocol_js_1.writeAtomicJson)((0, protocol_js_1.respPath)(d, 'y'), { id: 'y' });
|
|
67
|
+
(0, protocol_js_1.gcStale)(d, Number.POSITIVE_INFINITY);
|
|
68
|
+
(0, vitest_1.expect)((0, node_fs_1.readdirSync)(d).filter((n) => n.startsWith('cmd-') || n.startsWith('resp-'))).toEqual([]);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=protocol.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.test.js","sourceRoot":"","sources":["../../../../src/attach/daemon/__tests__/protocol.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.test.d.ts","sourceRoot":"","sources":["../../../../src/attach/daemon/__tests__/runner.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const runner_js_1 = require("../runner.js");
|
|
7
|
+
const client_js_1 = require("../client.js");
|
|
8
|
+
const harness_js_1 = require("./harness.js");
|
|
9
|
+
const cleanups = [];
|
|
10
|
+
(0, vitest_1.afterEach)(() => {
|
|
11
|
+
for (const f of cleanups.splice(0))
|
|
12
|
+
f();
|
|
13
|
+
});
|
|
14
|
+
(0, vitest_1.describe)('runAttachScript', () => {
|
|
15
|
+
(0, vitest_1.it)('runs goto/read/extract/wait steps, captures out keys untrusted-labeled, releases the claim', async () => {
|
|
16
|
+
const { dir, run } = (0, harness_js_1.daemonUnderTest)();
|
|
17
|
+
cleanups.push(() => (0, node_fs_1.rmSync)(dir, { recursive: true, force: true }));
|
|
18
|
+
const client = new client_js_1.AttachDaemonClient({ ipcDir: (0, node_path_1.join)(dir, 'ipc'), client: 'runner', pollMs: 25 });
|
|
19
|
+
const result = await (0, runner_js_1.runAttachScript)({
|
|
20
|
+
name: 'demo',
|
|
21
|
+
steps: [
|
|
22
|
+
{ op: 'goto', url: 'https://example.com/', settle: 0 },
|
|
23
|
+
{ op: 'read', out: 'page' },
|
|
24
|
+
{ op: 'extract', fields: { perks: 'li' }, out: 'perks' },
|
|
25
|
+
{ op: 'wait', ms: 10 },
|
|
26
|
+
],
|
|
27
|
+
}, client);
|
|
28
|
+
(0, vitest_1.expect)(result.ok).toBe(true);
|
|
29
|
+
(0, vitest_1.expect)(result.results.page).toMatchObject({ untrusted: true });
|
|
30
|
+
(0, vitest_1.expect)(result.results.perks).toMatchObject({ untrusted: true });
|
|
31
|
+
(0, vitest_1.expect)(result.steps).toHaveLength(4);
|
|
32
|
+
(0, vitest_1.expect)(result.steps.every((s) => s.ok)).toBe(true);
|
|
33
|
+
// The claim was released: a different client can claim immediately.
|
|
34
|
+
const other = new client_js_1.AttachDaemonClient({ ipcDir: (0, node_path_1.join)(dir, 'ipc'), client: 'other', pollMs: 25 });
|
|
35
|
+
await (0, vitest_1.expect)(other.claim()).resolves.toBeTruthy();
|
|
36
|
+
await other.quit();
|
|
37
|
+
await run;
|
|
38
|
+
}, 60000);
|
|
39
|
+
(0, vitest_1.it)('rejects eval steps up front', async () => {
|
|
40
|
+
const { dir, run } = (0, harness_js_1.daemonUnderTest)();
|
|
41
|
+
cleanups.push(() => (0, node_fs_1.rmSync)(dir, { recursive: true, force: true }));
|
|
42
|
+
const client = new client_js_1.AttachDaemonClient({ ipcDir: (0, node_path_1.join)(dir, 'ipc'), client: 'runner', pollMs: 25 });
|
|
43
|
+
await (0, vitest_1.expect)((0, runner_js_1.runAttachScript)({ name: 'bad', steps: [{ op: 'eval', expression: '1' }] }, client)).rejects.toThrowError(/eval is not a declarative op/);
|
|
44
|
+
await client.quit();
|
|
45
|
+
await run;
|
|
46
|
+
}, 60000);
|
|
47
|
+
(0, vitest_1.it)('aborts on the first failing step with the step index and skips later steps', async () => {
|
|
48
|
+
const { dir, run } = (0, harness_js_1.daemonUnderTest)();
|
|
49
|
+
cleanups.push(() => (0, node_fs_1.rmSync)(dir, { recursive: true, force: true }));
|
|
50
|
+
const client = new client_js_1.AttachDaemonClient({ ipcDir: (0, node_path_1.join)(dir, 'ipc'), client: 'runner', pollMs: 25 });
|
|
51
|
+
const failed = await (0, runner_js_1.runAttachScript)({
|
|
52
|
+
name: 'abort',
|
|
53
|
+
steps: [
|
|
54
|
+
{ op: 'goto', url: 'http://insecure.example/' },
|
|
55
|
+
{ op: 'read', out: 'never' },
|
|
56
|
+
],
|
|
57
|
+
}, client);
|
|
58
|
+
(0, vitest_1.expect)(failed.ok).toBe(false);
|
|
59
|
+
(0, vitest_1.expect)(failed.failedStep).toBe(0);
|
|
60
|
+
(0, vitest_1.expect)(failed.error?.code).toBe('POLICY_BLOCKED');
|
|
61
|
+
(0, vitest_1.expect)(failed.results.never).toBeUndefined();
|
|
62
|
+
(0, vitest_1.expect)(failed.steps).toHaveLength(1);
|
|
63
|
+
await client.quit();
|
|
64
|
+
await run;
|
|
65
|
+
}, 60000);
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=runner.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.test.js","sourceRoot":"","sources":["../../../../src/attach/daemon/__tests__/runner.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type DaemonStatusFile } from './protocol.js';
|
|
2
|
+
/** Client options. */
|
|
3
|
+
export interface AttachDaemonClientOptions {
|
|
4
|
+
ipcDir?: string;
|
|
5
|
+
/** Client name prefix used in ids and claimant identity (default `client`). */
|
|
6
|
+
client?: string;
|
|
7
|
+
/** Response poll cadence ms (default 400). */
|
|
8
|
+
pollMs?: number;
|
|
9
|
+
/** Base per-op timeout ms before per-op margins (default 45000). */
|
|
10
|
+
opTimeoutMs?: number;
|
|
11
|
+
}
|
|
12
|
+
/** See fileoverview. All failures throw {@link AttachError} with a structured code. */
|
|
13
|
+
export declare class AttachDaemonClient {
|
|
14
|
+
readonly ipcDir: string;
|
|
15
|
+
readonly client: string;
|
|
16
|
+
private readonly pollMs;
|
|
17
|
+
private readonly opTimeoutMs;
|
|
18
|
+
constructor(opts?: AttachDaemonClientOptions);
|
|
19
|
+
/** Fast, round-trip-free snapshot of status.json (undefined when absent). */
|
|
20
|
+
statusFile(): DaemonStatusFile | undefined;
|
|
21
|
+
/** True when status.json shows a fresh heartbeat or a live recorded pid. */
|
|
22
|
+
daemonLooksAlive(): boolean;
|
|
23
|
+
/** Liveness echo through the queue. */
|
|
24
|
+
ping(): Promise<{
|
|
25
|
+
pong: boolean;
|
|
26
|
+
pid: number;
|
|
27
|
+
}>;
|
|
28
|
+
/** Controller + daemon status snapshot via the queue. */
|
|
29
|
+
status(): Promise<Record<string, unknown>>;
|
|
30
|
+
/** Acquire driving rights (and trigger the controller claim on first use). */
|
|
31
|
+
claim(ttlMs?: number): Promise<Record<string, unknown>>;
|
|
32
|
+
/** Navigate the agent tab (daemon settles for `settle` ms, default 1800). */
|
|
33
|
+
goto(url: string, o?: {
|
|
34
|
+
settle?: number;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
url: string;
|
|
37
|
+
}>;
|
|
38
|
+
/** Read visible text (untrusted page content). */
|
|
39
|
+
read(o?: {
|
|
40
|
+
selector?: string;
|
|
41
|
+
maxChars?: number;
|
|
42
|
+
}): Promise<{
|
|
43
|
+
untrusted: true;
|
|
44
|
+
text: string;
|
|
45
|
+
}>;
|
|
46
|
+
/** Structured extraction via a selector map (untrusted page content). */
|
|
47
|
+
extract(fields?: Record<string, string>): Promise<{
|
|
48
|
+
untrusted: true;
|
|
49
|
+
data: unknown;
|
|
50
|
+
}>;
|
|
51
|
+
/** USER LANE ONLY: arbitrary page evaluation for user-authored scripts. */
|
|
52
|
+
evaluate(expression: string, timeoutMs?: number): Promise<{
|
|
53
|
+
untrusted: true;
|
|
54
|
+
value: unknown;
|
|
55
|
+
}>;
|
|
56
|
+
/** Give up driving rights (daemon parks the tab; its session persists). */
|
|
57
|
+
release(): Promise<void>;
|
|
58
|
+
/** Runtime session controls. */
|
|
59
|
+
control(action: 'pause' | 'resume' | 'dry_run_on' | 'dry_run_off' | 'status'): Promise<Record<string, unknown>>;
|
|
60
|
+
/** Full daemon shutdown (parks tab, releases lease; Chrome untouched). */
|
|
61
|
+
quit(): Promise<void>;
|
|
62
|
+
private request;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/attach/daemon/client.ts"],"names":[],"mappings":"AAgBA,OAAO,EAWL,KAAK,gBAAgB,EACtB,MAAM,eAAe,CAAC;AAEvB,sBAAsB;AACtB,MAAM,WAAW,yBAAyB;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAID,uFAAuF;AACvF,qBAAa,kBAAkB;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAEzB,IAAI,GAAE,yBAA8B;IAOhD,6EAA6E;IAC7E,UAAU,IAAI,gBAAgB,GAAG,SAAS;IAI1C,4EAA4E;IAC5E,gBAAgB,IAAI,OAAO;IAY3B,uCAAuC;IACjC,IAAI,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAIrD,yDAAyD;IACnD,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAIhD,8EAA8E;IACxE,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAI7D,6EAA6E;IACvE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAI9E,kDAAkD;IAC5C,IAAI,CAAC,CAAC,GAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAIxG,yEAAyE;IACnE,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC;IAI3F,2EAA2E;IACrE,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,IAAI,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IAOpG,2EAA2E;IACrE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B,gCAAgC;IAC1B,OAAO,CAAC,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,YAAY,GAAG,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAIrH,0EAA0E;IACpE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAIb,OAAO;CA6BtB"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AttachDaemonClient = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* @fileoverview Typed client for the attach daemon's file queue.
|
|
6
|
+
*
|
|
7
|
+
* No network, no child processes: writes `cmd-<id>.json`, polls for
|
|
8
|
+
* `resp-<id>.json`, deletes the response after reading. Used by user scripts
|
|
9
|
+
* (`wunderland attach run`), the declarative runner, and the tools'
|
|
10
|
+
* DaemonAttachSurface. `evaluate` is the user lane; no agent tool reaches it.
|
|
11
|
+
*
|
|
12
|
+
* Liveness: a daemon mid-op can miss heartbeats (the loop is serial), so
|
|
13
|
+
* {@link AttachDaemonClient.daemonLooksAlive} accepts a fresh heartbeat OR a
|
|
14
|
+
* live recorded pid.
|
|
15
|
+
*
|
|
16
|
+
* @module browser-automation/attach/daemon/client
|
|
17
|
+
*/
|
|
18
|
+
const node_fs_1 = require("node:fs");
|
|
19
|
+
const errors_js_1 = require("../errors.js");
|
|
20
|
+
const protocol_js_1 = require("./protocol.js");
|
|
21
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
22
|
+
/** See fileoverview. All failures throw {@link AttachError} with a structured code. */
|
|
23
|
+
class AttachDaemonClient {
|
|
24
|
+
constructor(opts = {}) {
|
|
25
|
+
this.ipcDir = (0, protocol_js_1.resolveIpcDir)(opts.ipcDir);
|
|
26
|
+
this.client = `${opts.client ?? 'client'}-${process.pid}`;
|
|
27
|
+
this.pollMs = opts.pollMs ?? 400;
|
|
28
|
+
this.opTimeoutMs = opts.opTimeoutMs ?? 45000;
|
|
29
|
+
}
|
|
30
|
+
/** Fast, round-trip-free snapshot of status.json (undefined when absent). */
|
|
31
|
+
statusFile() {
|
|
32
|
+
return (0, protocol_js_1.readJson)((0, protocol_js_1.statusPath)(this.ipcDir));
|
|
33
|
+
}
|
|
34
|
+
/** True when status.json shows a fresh heartbeat or a live recorded pid. */
|
|
35
|
+
daemonLooksAlive() {
|
|
36
|
+
const s = this.statusFile();
|
|
37
|
+
if (!s || s.state === 'stopped' || s.state === 'failed')
|
|
38
|
+
return false;
|
|
39
|
+
if (Date.now() - s.t <= protocol_js_1.HEARTBEAT_STALE_MS)
|
|
40
|
+
return true;
|
|
41
|
+
try {
|
|
42
|
+
process.kill(s.pid, 0);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Liveness echo through the queue. */
|
|
50
|
+
async ping() {
|
|
51
|
+
return (await this.request('ping'));
|
|
52
|
+
}
|
|
53
|
+
/** Controller + daemon status snapshot via the queue. */
|
|
54
|
+
async status() {
|
|
55
|
+
return (await this.request('status'));
|
|
56
|
+
}
|
|
57
|
+
/** Acquire driving rights (and trigger the controller claim on first use). */
|
|
58
|
+
async claim(ttlMs) {
|
|
59
|
+
return (await this.request('claim', ttlMs ? { ttlMs } : {}));
|
|
60
|
+
}
|
|
61
|
+
/** Navigate the agent tab (daemon settles for `settle` ms, default 1800). */
|
|
62
|
+
async goto(url, o = {}) {
|
|
63
|
+
return (await this.request('goto', { url, ...o }, this.opTimeoutMs + (o.settle ?? 1800)));
|
|
64
|
+
}
|
|
65
|
+
/** Read visible text (untrusted page content). */
|
|
66
|
+
async read(o = {}) {
|
|
67
|
+
return (await this.request('read', o));
|
|
68
|
+
}
|
|
69
|
+
/** Structured extraction via a selector map (untrusted page content). */
|
|
70
|
+
async extract(fields) {
|
|
71
|
+
return (await this.request('extract', fields ? { fields } : {}));
|
|
72
|
+
}
|
|
73
|
+
/** USER LANE ONLY: arbitrary page evaluation for user-authored scripts. */
|
|
74
|
+
async evaluate(expression, timeoutMs) {
|
|
75
|
+
return (await this.request('eval', { expression, timeoutMs }, this.opTimeoutMs + (timeoutMs ?? 30000)));
|
|
76
|
+
}
|
|
77
|
+
/** Give up driving rights (daemon parks the tab; its session persists). */
|
|
78
|
+
async release() {
|
|
79
|
+
await this.request('release');
|
|
80
|
+
}
|
|
81
|
+
/** Runtime session controls. */
|
|
82
|
+
async control(action) {
|
|
83
|
+
return (await this.request('control', { action }));
|
|
84
|
+
}
|
|
85
|
+
/** Full daemon shutdown (parks tab, releases lease; Chrome untouched). */
|
|
86
|
+
async quit() {
|
|
87
|
+
await this.request('quit');
|
|
88
|
+
}
|
|
89
|
+
async request(op, args = {}, timeoutMs) {
|
|
90
|
+
if (!this.daemonLooksAlive()) {
|
|
91
|
+
throw new errors_js_1.AttachError('DAEMON_UNAVAILABLE', 'no live attach daemon behind this IPC dir; run `wunderland attach start`');
|
|
92
|
+
}
|
|
93
|
+
const id = (0, protocol_js_1.newCmdId)(this.client);
|
|
94
|
+
const cmdFile = (0, protocol_js_1.cmdPath)(this.ipcDir, id);
|
|
95
|
+
const respFile = (0, protocol_js_1.respPath)(this.ipcDir, id);
|
|
96
|
+
(0, protocol_js_1.writeAtomicJson)(cmdFile, { id, client: this.client, op, args });
|
|
97
|
+
const deadline = Date.now() + (timeoutMs ?? this.opTimeoutMs);
|
|
98
|
+
while (Date.now() < deadline) {
|
|
99
|
+
await sleep(this.pollMs);
|
|
100
|
+
const resp = (0, protocol_js_1.readJson)(respFile);
|
|
101
|
+
if (!resp)
|
|
102
|
+
continue;
|
|
103
|
+
try {
|
|
104
|
+
(0, node_fs_1.unlinkSync)(respFile);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
/* raced with GC */
|
|
108
|
+
}
|
|
109
|
+
if (resp.ok)
|
|
110
|
+
return resp.data;
|
|
111
|
+
throw new errors_js_1.AttachError(resp.error.code, resp.error.message);
|
|
112
|
+
}
|
|
113
|
+
const consumed = !(0, node_fs_1.existsSync)(cmdFile);
|
|
114
|
+
throw new errors_js_1.AttachError(consumed ? 'CDP_TIMEOUT' : 'DAEMON_UNAVAILABLE', consumed
|
|
115
|
+
? `${op} timed out awaiting the daemon's response`
|
|
116
|
+
: `${op} was never consumed — daemon gone; run \`wunderland attach start\``);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
exports.AttachDaemonClient = AttachDaemonClient;
|
|
120
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/attach/daemon/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** A held daemon lock. */
|
|
2
|
+
export interface DaemonLock {
|
|
3
|
+
/** Absolute pidfile path backing the lock. */
|
|
4
|
+
readonly pidFile: string;
|
|
5
|
+
/** Remove the pidfile iff this process still owns it. Never throws. */
|
|
6
|
+
release(): void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Acquire the daemon singleton atomically.
|
|
10
|
+
*
|
|
11
|
+
* Creates `pidFile` with the `wx` flag (fails when it exists). On EEXIST the
|
|
12
|
+
* holder is inspected: a live pid refuses acquisition with `DAEMON_RUNNING`;
|
|
13
|
+
* a dead pid or an unreadable body reclaims the file once. Concurrent
|
|
14
|
+
* acquirers race safely on the atomic create — exactly one wins.
|
|
15
|
+
*
|
|
16
|
+
* @throws {AttachError} `DAEMON_RUNNING` when another live daemon holds the lock.
|
|
17
|
+
*/
|
|
18
|
+
export declare function acquireDaemonLock(pidFile: string): DaemonLock;
|
|
19
|
+
//# sourceMappingURL=daemon-lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-lock.d.ts","sourceRoot":"","sources":["../../../src/attach/daemon/daemon-lock.ts"],"names":[],"mappings":"AAmBA,0BAA0B;AAC1B,MAAM,WAAW,UAAU;IACzB,8CAA8C;IAC9C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,OAAO,IAAI,IAAI,CAAC;CACjB;AAkBD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAsC7D"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.acquireDaemonLock = acquireDaemonLock;
|
|
4
|
+
/**
|
|
5
|
+
* @fileoverview Atomic daemon singleton via an O_EXCL pidfile.
|
|
6
|
+
*
|
|
7
|
+
* Mutual exclusion for the attach daemon: exactly one daemon may hold one
|
|
8
|
+
* Chrome session, or concurrent `attach start`s would open two CDP sockets
|
|
9
|
+
* and raise two macOS Local Network prompts. NOTE: `../singleton-lock.ts` is,
|
|
10
|
+
* despite its name, a parser for Chrome's own SingletonLock symlink (JXA pid
|
|
11
|
+
* addressing) and provides no locking — this module is the daemon's lock.
|
|
12
|
+
*
|
|
13
|
+
* Reclaim policy is pid-liveness, not age: a lock whose recorded pid is dead
|
|
14
|
+
* (or whose body is unreadable/torn) is reclaimed; a live pid refuses the
|
|
15
|
+
* acquisition no matter how old the file is.
|
|
16
|
+
*
|
|
17
|
+
* @module browser-automation/attach/daemon/daemon-lock
|
|
18
|
+
*/
|
|
19
|
+
const node_fs_1 = require("node:fs");
|
|
20
|
+
const node_path_1 = require("node:path");
|
|
21
|
+
const errors_js_1 = require("../errors.js");
|
|
22
|
+
/** True when `pid` refers to a live process we may signal (EPERM counts as alive). */
|
|
23
|
+
function pidAlive(pid) {
|
|
24
|
+
try {
|
|
25
|
+
process.kill(pid, 0);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
return err.code === 'EPERM';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Acquire the daemon singleton atomically.
|
|
34
|
+
*
|
|
35
|
+
* Creates `pidFile` with the `wx` flag (fails when it exists). On EEXIST the
|
|
36
|
+
* holder is inspected: a live pid refuses acquisition with `DAEMON_RUNNING`;
|
|
37
|
+
* a dead pid or an unreadable body reclaims the file once. Concurrent
|
|
38
|
+
* acquirers race safely on the atomic create — exactly one wins.
|
|
39
|
+
*
|
|
40
|
+
* @throws {AttachError} `DAEMON_RUNNING` when another live daemon holds the lock.
|
|
41
|
+
*/
|
|
42
|
+
function acquireDaemonLock(pidFile) {
|
|
43
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(pidFile), { recursive: true, mode: 0o700 });
|
|
44
|
+
const body = { pid: process.pid, startedAt: Date.now() };
|
|
45
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
46
|
+
try {
|
|
47
|
+
const fd = (0, node_fs_1.openSync)(pidFile, 'wx', 0o600);
|
|
48
|
+
(0, node_fs_1.writeSync)(fd, JSON.stringify(body));
|
|
49
|
+
(0, node_fs_1.closeSync)(fd);
|
|
50
|
+
return {
|
|
51
|
+
pidFile,
|
|
52
|
+
release() {
|
|
53
|
+
try {
|
|
54
|
+
const cur = JSON.parse((0, node_fs_1.readFileSync)(pidFile, 'utf8'));
|
|
55
|
+
if (cur.pid === process.pid)
|
|
56
|
+
(0, node_fs_1.unlinkSync)(pidFile);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
/* released best-effort: file gone or owned by a newer daemon */
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
if (err.code !== 'EEXIST')
|
|
66
|
+
throw err;
|
|
67
|
+
let holder;
|
|
68
|
+
try {
|
|
69
|
+
holder = JSON.parse((0, node_fs_1.readFileSync)(pidFile, 'utf8'));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
/* torn or unreadable body: treat as reclaimable */
|
|
73
|
+
}
|
|
74
|
+
if (holder && pidAlive(holder.pid)) {
|
|
75
|
+
throw new errors_js_1.AttachError('DAEMON_RUNNING', `daemon already running (pid ${holder.pid})`);
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
(0, node_fs_1.unlinkSync)(pidFile);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* raced with another reclaimer; the retry create decides the winner */
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
throw new errors_js_1.AttachError('DAEMON_RUNNING', 'daemon lock contention: another process reclaimed the pidfile first');
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=daemon-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-lock.js","sourceRoot":"","sources":["../../../src/attach/daemon/daemon-lock.ts"],"names":[],"mappings":";;AAqDA,8CAsCC;AA3FD;;;;;;;;;;;;;;GAcG;AACH,qCAA8F;AAC9F,yCAAoC;AACpC,4CAA2C;AAgB3C,sFAAsF;AACtF,SAAS,QAAQ,CAAC,GAAW;IAC3B,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAQ,GAA6B,CAAC,IAAI,KAAK,OAAO,CAAC;IACzD,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,iBAAiB,CAAC,OAAe;IAC/C,IAAA,mBAAS,EAAC,IAAA,mBAAO,EAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9D,MAAM,IAAI,GAAa,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IACnE,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;QAC7C,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAA,kBAAQ,EAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YAC1C,IAAA,mBAAS,EAAC,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;YACpC,IAAA,mBAAS,EAAC,EAAE,CAAC,CAAC;YACd,OAAO;gBACL,OAAO;gBACP,OAAO;oBACL,IAAI,CAAC;wBACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAA,sBAAY,EAAC,OAAO,EAAE,MAAM,CAAC,CAAa,CAAC;wBAClE,IAAI,GAAG,CAAC,GAAG,KAAK,OAAO,CAAC,GAAG;4BAAE,IAAA,oBAAU,EAAC,OAAO,CAAC,CAAC;oBACnD,CAAC;oBAAC,MAAM,CAAC;wBACP,gEAAgE;oBAClE,CAAC;gBACH,CAAC;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,GAAG,CAAC;YAChE,IAAI,MAA4B,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAA,sBAAY,EAAC,OAAO,EAAE,MAAM,CAAC,CAAa,CAAC;YACjE,CAAC;YAAC,MAAM,CAAC;gBACP,mDAAmD;YACrD,CAAC;YACD,IAAI,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;gBACnC,MAAM,IAAI,uBAAW,CAAC,gBAAgB,EAAE,+BAA+B,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC;YACxF,CAAC;YACD,IAAI,CAAC;gBACH,IAAA,oBAAU,EAAC,OAAO,CAAC,CAAC;YACtB,CAAC;YAAC,MAAM,CAAC;gBACP,uEAAuE;YACzE,CAAC;QACH,CAAC;IACH,CAAC;IACD,MAAM,IAAI,uBAAW,CAAC,gBAAgB,EAAE,qEAAqE,CAAC,CAAC;AACjH,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build and run the daemon from an environment map. Returns the process exit
|
|
3
|
+
* code (0 clean stop, 2 failure). Exported for tests and for the CLI.
|
|
4
|
+
*/
|
|
5
|
+
export declare function main(env?: Record<string, string | undefined>): Promise<number>;
|
|
6
|
+
//# sourceMappingURL=daemon-main.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-main.d.ts","sourceRoot":"","sources":["../../../src/attach/daemon/daemon-main.ts"],"names":[],"mappings":"AAiBA;;;GAGG;AACH,wBAAsB,IAAI,CAAC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAoDjG"}
|