@parall/daemon 1.44.0 → 1.45.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.
@@ -0,0 +1,226 @@
1
+ import * as path from 'node:path';
2
+ /**
3
+ * Windows service (Task Scheduler) artifact generation. Everything here is a
4
+ * pure function from paths to strings so the whole surface unit-tests on any
5
+ * platform; cli.ts owns filesystem writes and schtasks invocations.
6
+ *
7
+ * The chain at run time:
8
+ * Task Scheduler → wscript.exe //B launcher.vbs
9
+ * → vbs Run("node.exe launcher.js", 0, True) — window hidden, waits,
10
+ * propagates the exit code via WScript.Quit
11
+ * → launcher.js sets PRLL_DAEMON_MANAGED=1, writes the pidfile,
12
+ * redirects stdio to the log file, then imports the daemon bundle
13
+ * IN-PROCESS (overlay-first) — so the pid in the pidfile IS the
14
+ * daemon and a self-update exit(42) is the task's own exit code,
15
+ * which RestartOnFailure turns into a restart on the new overlay.
16
+ */
17
+ export const WIN_TASK_NAME = 'ParallDaemon';
18
+ export function winServicePaths(home) {
19
+ const root = path.join(home, '.parall-daemon');
20
+ const serviceDir = path.join(root, 'service');
21
+ const logDir = path.join(root, 'logs');
22
+ return {
23
+ serviceDir,
24
+ launcherCjs: path.join(serviceDir, 'parall-daemon-launcher.cjs'),
25
+ legacyLauncherJs: path.join(serviceDir, 'parall-daemon-launcher.js'),
26
+ launcherVbs: path.join(serviceDir, 'parall-daemon-launcher.vbs'),
27
+ taskXml: path.join(serviceDir, 'task.xml'),
28
+ pidFile: path.join(root, 'daemon.pid'),
29
+ logDir,
30
+ logFile: path.join(logDir, 'parall-daemon.log'),
31
+ overlayEntry: path.join(root, 'bundle', 'current', 'parall-daemon.js'),
32
+ runningMarker: path.join(root, 'bundle', 'daemon-running'),
33
+ };
34
+ }
35
+ /**
36
+ * VBScript string literal: wrap in quotes, escape embedded quotes by
37
+ * doubling. NTFS forbids `"` in file names, so real paths never exercise
38
+ * the escape — it exists as defense-in-depth for arbitrary strings.
39
+ */
40
+ export function vbsQuote(value) {
41
+ return `"${value.replaceAll('"', '""')}"`;
42
+ }
43
+ /**
44
+ * Encode a generated artifact as UTF-16LE with a BOM. wscript reads BOM-less
45
+ * files in the ANSI codepage (mangling non-ASCII profile paths) and schtasks'
46
+ * canonical XML form is UTF-16 — both .vbs and task.xml go through this.
47
+ */
48
+ export function encodeUtf16LeBom(content) {
49
+ return Buffer.from('\ufeff' + content, 'utf16le');
50
+ }
51
+ export function xmlEscape(value) {
52
+ return value
53
+ .replaceAll('&', '&')
54
+ .replaceAll('<', '&lt;')
55
+ .replaceAll('>', '&gt;')
56
+ .replaceAll('"', '&quot;')
57
+ .replaceAll("'", '&apos;');
58
+ }
59
+ /**
60
+ * The hidden-window launcher. `Run(cmd, 0, True)`: 0 hides the console
61
+ * window (a console app started by an interactive scheduled task would
62
+ * otherwise flash/persist one), True waits and yields the exit code, and
63
+ * WScript.Quit hands that code to Task Scheduler so RestartOnFailure sees
64
+ * daemon crashes and self-update exit(42)s.
65
+ *
66
+ * Caller must write this file as UTF-16LE with a BOM: wscript reads BOM-less
67
+ * files in the ANSI codepage, which mangles non-ASCII (e.g. Chinese
68
+ * user-profile) paths.
69
+ */
70
+ export function buildLauncherVbs(p) {
71
+ const runCommand = `"${p.nodeExe}" "${p.launcherCjs}"`;
72
+ return [
73
+ "' generated by `parall-daemon service install` - do not edit",
74
+ 'Set sh = CreateObject("WScript.Shell")',
75
+ `code = sh.Run(${vbsQuote(runCommand)}, 0, True)`,
76
+ 'WScript.Quit code',
77
+ '',
78
+ ].join('\r\n');
79
+ }
80
+ /**
81
+ * The in-process daemon launcher. Embedded paths go through JSON.stringify,
82
+ * which handles backslashes and quotes; the file itself is plain UTF-8 (Node
83
+ * always reads JS as UTF-8, so non-ASCII paths round-trip).
84
+ *
85
+ * The content is CommonJS and the artifact is written as `.cjs` so an
86
+ * ancestor package.json with "type": "module" (users do npm-init their home
87
+ * directories) can never flip it into ESM scope.
88
+ */
89
+ export function buildLauncherCjs(p) {
90
+ return `// generated by \`parall-daemon service install\` - do not edit
91
+ 'use strict';
92
+ const fs = require('node:fs');
93
+ const path = require('node:path');
94
+ const { pathToFileURL } = require('node:url');
95
+
96
+ // Must be set before the daemon loads: the bootstrap reads it at import time
97
+ // to enable supervised self-update.
98
+ process.env.PRLL_DAEMON_MANAGED = '1';
99
+
100
+ const OVERLAY_ENTRY = ${JSON.stringify(p.overlayEntry)};
101
+ const NPM_ENTRY = ${JSON.stringify(p.npmEntry)};
102
+ const LOG_FILE = ${JSON.stringify(p.logFile)};
103
+ const PID_FILE = ${JSON.stringify(p.pidFile)};
104
+
105
+ fs.mkdirSync(path.dirname(LOG_FILE), { recursive: true });
106
+ try {
107
+ if (fs.statSync(LOG_FILE).size > 20 * 1024 * 1024) {
108
+ fs.renameSync(LOG_FILE, LOG_FILE + '.old');
109
+ }
110
+ } catch {}
111
+
112
+ // Synchronous writes so an abrupt exit (self-update exit(42), crash) cannot
113
+ // lose the log tail. The daemon is not chatty enough for this to matter.
114
+ const logFd = fs.openSync(LOG_FILE, 'a');
115
+ for (const stream of [process.stdout, process.stderr]) {
116
+ stream.write = (chunk, encoding, callback) => {
117
+ try {
118
+ fs.writeSync(
119
+ logFd,
120
+ Buffer.isBuffer(chunk)
121
+ ? chunk
122
+ : Buffer.from(chunk, typeof encoding === 'string' ? encoding : 'utf8'),
123
+ );
124
+ } catch {}
125
+ const done = typeof encoding === 'function' ? encoding : callback;
126
+ if (done) done();
127
+ return true;
128
+ };
129
+ }
130
+
131
+ fs.writeFileSync(PID_FILE, String(process.pid));
132
+ process.on('exit', () => {
133
+ try {
134
+ if (fs.readFileSync(PID_FILE, 'utf8').trim() === String(process.pid)) {
135
+ fs.unlinkSync(PID_FILE);
136
+ }
137
+ } catch {}
138
+ });
139
+
140
+ // Overlay-first, mirroring the launchd/systemd wrapper semantics.
141
+ const entry = fs.existsSync(OVERLAY_ENTRY) ? OVERLAY_ENTRY : NPM_ENTRY;
142
+ // The daemon derives bridge-bundle siblings and self-update mode from its
143
+ // entry path - argv[1] must point at the real bundle entry, not this file.
144
+ process.argv[1] = entry;
145
+ import(pathToFileURL(entry).href).catch((err) => {
146
+ process.stderr.write('launcher: failed to load ' + entry + ': ' + ((err && err.stack) || err) + '\\n');
147
+ process.exit(1);
148
+ });
149
+ `;
150
+ }
151
+ /**
152
+ * Task Scheduler task definition.
153
+ *
154
+ * Element-order ground rules (Microsoft Task Scheduler schema docs):
155
+ * - trigger children (triggerBaseType) are an xs:sequence and MUST appear as
156
+ * StartBoundary → EndBoundary → Enabled → Repetition → ExecutionTimeLimit
157
+ * (learn.microsoft.com .../taskschedulerschema-timetrigger-triggergroup-element);
158
+ * - Settings children (settingsType) are an xs:all — order-insensitive;
159
+ * - RestartOnFailure Count is xs:unsignedByte (1–255)
160
+ * (.../taskschedulerschema-count-restarttype-element).
161
+ *
162
+ * - LogonTrigger: start at user logon (no admin, no stored password:
163
+ * InteractiveToken + omitted UserId registers for the calling user).
164
+ * - TimeTrigger PT1H: revival tick (StartBoundary is a fixed date safely in
165
+ * the past — a boundary in the future would silently disarm the trigger on
166
+ * a machine whose clock is behind) — if RestartOnFailure's count is ever
167
+ * exhausted the daemon still comes back within the hour; IgnoreNew makes
168
+ * it a no-op while an instance is running. `stop` disables the whole task,
169
+ * so this trigger never resurrects an intentionally stopped daemon.
170
+ * - ExecutionTimeLimit PT0S: no 72h default kill.
171
+ * - RestartOnFailure PT1M/99: crash or self-update exit(42) restarts in
172
+ * ~1 minute (PT1M is the schema minimum interval; 99 stays well inside the
173
+ * unsignedByte cap). Note this makes Windows self-update restarts ~1min,
174
+ * vs ~5s under launchd.
175
+ *
176
+ * Caller must write this as UTF-16LE with BOM to match the declared
177
+ * encoding — the canonical form schtasks itself produces on export.
178
+ */
179
+ export function buildTaskXml(p) {
180
+ const args = `//B //Nologo "${p.launcherVbs}"`;
181
+ return `<?xml version="1.0" encoding="UTF-16"?>
182
+ <Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
183
+ <RegistrationInfo>
184
+ <Description>Parall Daemon (BYOC machine supervisor)</Description>
185
+ </RegistrationInfo>
186
+ <Triggers>
187
+ <LogonTrigger>
188
+ <Enabled>true</Enabled>
189
+ </LogonTrigger>
190
+ <TimeTrigger>
191
+ <StartBoundary>2020-01-01T00:00:00</StartBoundary>
192
+ <Enabled>true</Enabled>
193
+ <Repetition>
194
+ <Interval>PT1H</Interval>
195
+ </Repetition>
196
+ </TimeTrigger>
197
+ </Triggers>
198
+ <Principals>
199
+ <Principal id="Author">
200
+ <LogonType>InteractiveToken</LogonType>
201
+ <RunLevel>LeastPrivilege</RunLevel>
202
+ </Principal>
203
+ </Principals>
204
+ <Settings>
205
+ <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
206
+ <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
207
+ <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
208
+ <AllowHardTerminate>true</AllowHardTerminate>
209
+ <StartWhenAvailable>true</StartWhenAvailable>
210
+ <AllowStartOnDemand>true</AllowStartOnDemand>
211
+ <Enabled>true</Enabled>
212
+ <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
213
+ <RestartOnFailure>
214
+ <Interval>PT1M</Interval>
215
+ <Count>99</Count>
216
+ </RestartOnFailure>
217
+ </Settings>
218
+ <Actions Context="Author">
219
+ <Exec>
220
+ <Command>${xmlEscape(p.wscriptExe)}</Command>
221
+ <Arguments>${xmlEscape(args)}</Arguments>
222
+ </Exec>
223
+ </Actions>
224
+ </Task>
225
+ `;
226
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/daemon",
3
- "version": "1.44.0",
3
+ "version": "1.45.0",
4
4
  "description": "Parall local agent runtime — daemon supervisor + bridge runtimes, bundled as standalone JS files",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -32,11 +32,11 @@
32
32
  "dependencies": {
33
33
  "@aws-sdk/client-s3": "3.984.0",
34
34
  "@pinixai/bb-browser-pro": "0.15.0",
35
- "@parall/agent-core": "1.44.0",
36
- "@parall/claude-agent": "1.44.0",
37
- "@parall/sdk": "1.44.0",
38
- "@parall/codex-agent": "1.44.0",
39
- "@parall/openclaw-agent": "1.44.0"
35
+ "@parall/agent-core": "1.45.0",
36
+ "@parall/sdk": "1.45.0",
37
+ "@parall/codex-agent": "1.45.0",
38
+ "@parall/claude-agent": "1.45.0",
39
+ "@parall/openclaw-agent": "1.45.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/node": "^22.0.0",