@nowcrew/daemon 0.6.97-beta.11 → 0.6.97-beta.12
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/automatic-service-migration.js +2 -3
- package/dist/computer-main.js +0 -0
- package/dist/computer-service.js +35 -39
- package/dist/computer-setup-client.js +5 -2
- package/dist/main.js +0 -0
- package/dist/powershell-command.js +12 -0
- package/dist/windows-scheduled-task.js +3 -3
- package/package.json +9 -8
|
@@ -3,6 +3,7 @@ import { posix, win32 } from "node:path";
|
|
|
3
3
|
import { buildServiceSpec, serviceStatus, windowsCommandArg, } from "./computer-service.js";
|
|
4
4
|
import { daemonGlobalInstallation } from "./daemon-installation.js";
|
|
5
5
|
import { inspectLegacyServiceInstallationDetails } from "./legacy-service-installation.js";
|
|
6
|
+
import { powershellCommandArgs } from "./powershell-command.js";
|
|
6
7
|
function encoded(value) {
|
|
7
8
|
return Buffer.from(value, "utf16le").toString("base64");
|
|
8
9
|
}
|
|
@@ -145,9 +146,7 @@ async function discoverWindows(input, identity) {
|
|
|
145
146
|
"$task=$tasks[0];$actions=@($task.Actions);$me=[Security.Principal.WindowsIdentity]::GetCurrent().Name;",
|
|
146
147
|
"[pscustomobject]@{count=1;state=[int]$task.State;actionCount=$actions.Count;execute=if($actions.Count -eq 1){$actions[0].Execute}else{$null};arguments=if($actions.Count -eq 1){$actions[0].Arguments}else{$null};principalMatches=([String]::Equals($task.Principal.UserId,$me,[StringComparison]::OrdinalIgnoreCase)-and [int]$task.Principal.LogonType -eq 3 -and [int]$task.Principal.RunLevel -eq 0)}|ConvertTo-Json -Compress;",
|
|
147
148
|
].join("");
|
|
148
|
-
const result = await input.runner("powershell.exe", [
|
|
149
|
-
"-NoProfile", "-NonInteractive", "-Command", script, encoded(identity.id),
|
|
150
|
-
]);
|
|
149
|
+
const result = await input.runner("powershell.exe", powershellCommandArgs(script, [encoded(identity.id)]));
|
|
151
150
|
if (result.exitCode !== 0) {
|
|
152
151
|
throw new Error(`Unable to inspect legacy scheduled task: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
|
|
153
152
|
}
|
package/dist/computer-main.js
CHANGED
|
File without changes
|
package/dist/computer-service.js
CHANGED
|
@@ -7,6 +7,7 @@ import { dirname, resolve } from "node:path";
|
|
|
7
7
|
import { normalizeDaemonNpmRegistry } from "./npm-registry.js";
|
|
8
8
|
import { daemonRegistryCandidates } from "./daemon-registry.js";
|
|
9
9
|
import { npmInvocation } from "./npm-command.js";
|
|
10
|
+
import { powershellCommandArgs } from "./powershell-command.js";
|
|
10
11
|
const execFileAsync = promisify(execFile);
|
|
11
12
|
const WINDOWS_STOP_TIMEOUT_MS = 5_000;
|
|
12
13
|
const WINDOWS_STOP_POLL_MS = 100;
|
|
@@ -198,9 +199,7 @@ async function windowsTaskDefinitionMatches(spec, runner) {
|
|
|
198
199
|
"$task=$tasks[0];$actions=@($task.Actions);$me=[Security.Principal.WindowsIdentity]::GetCurrent().Name;",
|
|
199
200
|
"[pscustomobject]@{count=1;actionCount=$actions.Count;execute=if($actions.Count -eq 1){$actions[0].Execute}else{$null};arguments=if($actions.Count -eq 1){$actions[0].Arguments}else{$null};principalMatches=([String]::Equals($task.Principal.UserId,$me,[StringComparison]::OrdinalIgnoreCase)-and [int]$task.Principal.LogonType -eq 3 -and [int]$task.Principal.RunLevel -eq 0)}|ConvertTo-Json -Compress;",
|
|
200
201
|
].join("");
|
|
201
|
-
const result = await runner("powershell.exe", [
|
|
202
|
-
"-NoProfile", "-NonInteractive", "-Command", script, encodedName,
|
|
203
|
-
]);
|
|
202
|
+
const result = await runner("powershell.exe", powershellCommandArgs(script, [encodedName]));
|
|
204
203
|
if (result.exitCode !== 0) {
|
|
205
204
|
throw new Error(`Unable to inspect scheduled task definition: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
|
|
206
205
|
}
|
|
@@ -226,9 +225,7 @@ async function windowsTaskState(spec, runner) {
|
|
|
226
225
|
"$tasks=@(Get-ScheduledTask -ErrorAction Stop|Where-Object{$_.TaskPath -eq '\\' -and $_.TaskName -ceq $n});",
|
|
227
226
|
"if($tasks.Count -eq 0){'missing'}elseif($tasks.Count -eq 1){'state:'+([int]$tasks[0].State)}else{throw 'duplicate scheduled tasks'}",
|
|
228
227
|
].join("");
|
|
229
|
-
const result = await runner("powershell.exe", [
|
|
230
|
-
"-NoProfile", "-NonInteractive", "-Command", script, encodedName,
|
|
231
|
-
]);
|
|
228
|
+
const result = await runner("powershell.exe", powershellCommandArgs(script, [encodedName]));
|
|
232
229
|
if (result.exitCode !== 0) {
|
|
233
230
|
throw new Error(`Unable to query scheduled task: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
|
|
234
231
|
}
|
|
@@ -279,10 +276,9 @@ export async function installService(spec, runner = systemCommandRunner) {
|
|
|
279
276
|
"$principal=New-ScheduledTaskPrincipal -UserId $identity -LogonType Interactive -RunLevel Limited;",
|
|
280
277
|
"Register-ScheduledTask -TaskName $name -TaskPath '\\' -Action $action -Trigger $trigger -Settings $settings -Principal $principal -ErrorAction Stop|Out-Null;",
|
|
281
278
|
].join("");
|
|
282
|
-
await checked(runner, "powershell.exe", [
|
|
283
|
-
"-NoProfile", "-NonInteractive", "-Command", script,
|
|
279
|
+
await checked(runner, "powershell.exe", powershellCommandArgs(script, [
|
|
284
280
|
encodedName, encodedExecutable, encodedArguments,
|
|
285
|
-
], "Scheduled task installation");
|
|
281
|
+
]), "Scheduled task installation");
|
|
286
282
|
return;
|
|
287
283
|
}
|
|
288
284
|
if (spec.descriptorPath === null || spec.descriptor === null)
|
|
@@ -418,6 +414,33 @@ export async function serviceAction(spec, action, runner = systemCommandRunner)
|
|
|
418
414
|
}
|
|
419
415
|
}
|
|
420
416
|
const systemDetachedSpawn = (command, args, options) => spawn(command, [...args], options);
|
|
417
|
+
function windowsRestartCommandArgs(spec) {
|
|
418
|
+
if (!spec.runtimeProcessIdentity)
|
|
419
|
+
throw new Error("Windows daemon process identity is required for restart");
|
|
420
|
+
const encodedName = Buffer.from(spec.id, "utf16le").toString("base64");
|
|
421
|
+
const encodedExecutable = Buffer.from(spec.daemonCommand[0], "utf16le").toString("base64");
|
|
422
|
+
const encodedArguments = Buffer.from(spec.daemonCommand.slice(1).map(windowsCommandArg).join(" "), "utf16le").toString("base64");
|
|
423
|
+
const script = [
|
|
424
|
+
"$ErrorActionPreference='Stop';",
|
|
425
|
+
"$n=[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($args[1]));",
|
|
426
|
+
"$exe=[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($args[2]));",
|
|
427
|
+
"$argv=[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($args[3]));",
|
|
428
|
+
"$task=@(Get-ScheduledTask -ErrorAction Stop|Where-Object{$_.TaskPath -eq '\\' -and $_.TaskName -ceq $n});if($task.Count -ne 1){throw 'scheduled task identity changed'};",
|
|
429
|
+
"$actions=@($task[0].Actions);if($actions.Count -ne 1 -or -not [String]::Equals($actions[0].Execute,$exe,[StringComparison]::OrdinalIgnoreCase) -or $actions[0].Arguments -cne $argv){throw 'scheduled task action changed'};",
|
|
430
|
+
"$p=Get-Process -Id ([int]$args[0]) -ErrorAction Stop;",
|
|
431
|
+
"if($p.StartTime.ToUniversalTime().ToFileTimeUtc().ToString() -cne $args[4]){throw 'daemon process identity changed'};",
|
|
432
|
+
"$p.Kill();",
|
|
433
|
+
"if(-not $p.WaitForExit(10000)){throw 'daemon exit timeout'};",
|
|
434
|
+
"$deadline=[DateTime]::UtcNow.AddSeconds(10);",
|
|
435
|
+
"do{$task=@(Get-ScheduledTask -ErrorAction Stop|Where-Object{$_.TaskPath -eq '\\' -and $_.TaskName -ceq $n});if($task.Count -ne 1){throw 'scheduled task identity changed'};if([int]$task[0].State -ne 4){break};Start-Sleep -Milliseconds 100}while([DateTime]::UtcNow -lt $deadline);",
|
|
436
|
+
"if([int]$task[0].State -eq 4){throw 'scheduled task stop timeout'};",
|
|
437
|
+
"$actions=@($task[0].Actions);if($actions.Count -ne 1 -or -not [String]::Equals($actions[0].Execute,$exe,[StringComparison]::OrdinalIgnoreCase) -or $actions[0].Arguments -cne $argv){throw 'scheduled task action changed'};",
|
|
438
|
+
"& schtasks.exe /Run /TN $n | Out-Null;if($LASTEXITCODE -ne 0){throw 'scheduled task start failed'};",
|
|
439
|
+
].join("");
|
|
440
|
+
return powershellCommandArgs(script, [
|
|
441
|
+
String(process.pid), encodedName, encodedExecutable, encodedArguments, spec.runtimeProcessIdentity,
|
|
442
|
+
]);
|
|
443
|
+
}
|
|
421
444
|
const RESTART_HELPER_SOURCE = String.raw `
|
|
422
445
|
const { spawnSync } = require("node:child_process");
|
|
423
446
|
const spec = JSON.parse(process.argv[1]);
|
|
@@ -428,27 +451,7 @@ setTimeout(() => {
|
|
|
428
451
|
} else if (spec.platform === "linux") {
|
|
429
452
|
restarted = spawnSync("systemctl", ["--user", "--no-block", "restart", spec.id], { stdio: "ignore", shell: false });
|
|
430
453
|
} else {
|
|
431
|
-
|
|
432
|
-
const encodedExecutable = Buffer.from(spec.executable, "utf16le").toString("base64");
|
|
433
|
-
const encodedArguments = Buffer.from(spec.arguments, "utf16le").toString("base64");
|
|
434
|
-
const script = [
|
|
435
|
-
"$ErrorActionPreference='Stop';",
|
|
436
|
-
"$n=[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($args[1]));",
|
|
437
|
-
"$exe=[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($args[2]));",
|
|
438
|
-
"$argv=[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($args[3]));",
|
|
439
|
-
"$task=@(Get-ScheduledTask -ErrorAction Stop|Where-Object{$_.TaskPath -eq '\\' -and $_.TaskName -ceq $n});if($task.Count -ne 1){throw 'scheduled task identity changed'};",
|
|
440
|
-
"$actions=@($task[0].Actions);if($actions.Count -ne 1 -or -not [String]::Equals($actions[0].Execute,$exe,[StringComparison]::OrdinalIgnoreCase) -or $actions[0].Arguments -cne $argv){throw 'scheduled task action changed'};",
|
|
441
|
-
"$p=Get-Process -Id ([int]$args[0]) -ErrorAction Stop;",
|
|
442
|
-
"if($p.StartTime.ToUniversalTime().ToFileTimeUtc().ToString() -cne $args[4]){throw 'daemon process identity changed'};",
|
|
443
|
-
"$p.Kill();",
|
|
444
|
-
"if(-not $p.WaitForExit(10000)){throw 'daemon exit timeout'};",
|
|
445
|
-
"$deadline=[DateTime]::UtcNow.AddSeconds(10);",
|
|
446
|
-
"do{$task=@(Get-ScheduledTask -ErrorAction Stop|Where-Object{$_.TaskPath -eq '\\' -and $_.TaskName -ceq $n});if($task.Count -ne 1){throw 'scheduled task identity changed'};if([int]$task[0].State -ne 4){break};Start-Sleep -Milliseconds 100}while([DateTime]::UtcNow -lt $deadline);",
|
|
447
|
-
"if([int]$task[0].State -eq 4){throw 'scheduled task stop timeout'};",
|
|
448
|
-
"$actions=@($task[0].Actions);if($actions.Count -ne 1 -or -not [String]::Equals($actions[0].Execute,$exe,[StringComparison]::OrdinalIgnoreCase) -or $actions[0].Arguments -cne $argv){throw 'scheduled task action changed'};",
|
|
449
|
-
"& schtasks.exe /Run /TN $n | Out-Null;if($LASTEXITCODE -ne 0){throw 'scheduled task start failed'};",
|
|
450
|
-
].join("");
|
|
451
|
-
restarted = spawnSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script, String(spec.daemonPid), encodedName, encodedExecutable, encodedArguments, spec.processIdentity], { stdio: "ignore", shell: false });
|
|
454
|
+
restarted = spawnSync("powershell.exe", spec.powershellArgs, { stdio: "ignore", shell: false });
|
|
452
455
|
}
|
|
453
456
|
if (restarted.status !== 0) process.send?.({ type: "restart-failed", status: restarted.status });
|
|
454
457
|
setTimeout(() => {
|
|
@@ -470,15 +473,8 @@ export function scheduleServiceRestart(spec, spawnDetached = systemDetachedSpawn
|
|
|
470
473
|
? { platform: spec.platform, id: spec.id }
|
|
471
474
|
: {
|
|
472
475
|
platform: spec.platform,
|
|
473
|
-
|
|
474
|
-
daemonPid: process.pid,
|
|
475
|
-
executable: spec.daemonCommand[0],
|
|
476
|
-
arguments: spec.daemonCommand.slice(1).map(windowsCommandArg).join(" "),
|
|
477
|
-
processIdentity: spec.runtimeProcessIdentity,
|
|
476
|
+
powershellArgs: windowsRestartCommandArgs(spec),
|
|
478
477
|
};
|
|
479
|
-
if (spec.platform === "win32" && !payload.processIdentity) {
|
|
480
|
-
throw new Error("Windows daemon process identity is required for restart");
|
|
481
|
-
}
|
|
482
478
|
const child = spawnDetached(process.execPath, ["-e", RESTART_HELPER_SOURCE, JSON.stringify(payload)], { detached: true, stdio: ["ignore", "ignore", "ignore", "ipc"], shell: false });
|
|
483
479
|
child.unref();
|
|
484
480
|
let settleFailure;
|
|
@@ -627,7 +623,7 @@ export async function windowsProfileIsPrivate(path, runner = systemCommandRunner
|
|
|
627
623
|
"$unsafe=$rules|Where-Object { $_.AccessControlType -eq 'Allow' -and $safe -notcontains $_.IdentityReference.Translate([Security.Principal.SecurityIdentifier]).Value };",
|
|
628
624
|
"if($unsafe){'false'}else{'true'}",
|
|
629
625
|
].join("");
|
|
630
|
-
const result = await runner("powershell.exe",
|
|
626
|
+
const result = await runner("powershell.exe", powershellCommandArgs(script, [encodedPath]));
|
|
631
627
|
if (result.exitCode !== 0)
|
|
632
628
|
throw new Error(`Profile ACL inspection failed: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
|
|
633
629
|
if (result.stdout.trim() === "true")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { hostname } from "node:os";
|
|
3
3
|
import { MachineTokenSchema } from "./computer-profile.js";
|
|
4
|
+
import { powershellCommandArgs } from "./powershell-command.js";
|
|
4
5
|
import { systemCommandRunner, } from "./computer-service.js";
|
|
5
6
|
const HttpUrlSchema = z.string().url().refine((value) => {
|
|
6
7
|
const url = new URL(value);
|
|
@@ -309,8 +310,10 @@ export function createComputerSetupClient(options) {
|
|
|
309
310
|
};
|
|
310
311
|
}
|
|
311
312
|
export async function openSetupBrowser(url, platform = process.platform, runner = systemCommandRunner) {
|
|
312
|
-
const command = platform === "win32" ? "
|
|
313
|
-
const args =
|
|
313
|
+
const command = platform === "win32" ? "powershell.exe" : platform === "darwin" ? "open" : "xdg-open";
|
|
314
|
+
const args = platform === "win32"
|
|
315
|
+
? powershellCommandArgs("Start-Process -FilePath $args[0] -ErrorAction Stop | Out-Null", [url])
|
|
316
|
+
: [url];
|
|
314
317
|
const result = await runner(command, args);
|
|
315
318
|
if (result.exitCode !== 0) {
|
|
316
319
|
throw new Error(`Unable to open the computer setup browser: ${redactSetupSecrets(result.stderr.trim() || `exit ${result.exitCode}`)}`);
|
package/dist/main.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* powershell.exe -Command joins trailing argv into command text, rather than
|
|
3
|
+
* binding it to $args. Invoke a scriptblock with literal arguments inside one
|
|
4
|
+
* UTF-16LE encoded command. Encoding is transport, not encryption.
|
|
5
|
+
*/
|
|
6
|
+
export function powershellCommandArgs(script, args = []) {
|
|
7
|
+
// PowerShell also treats typographic single quotes as string delimiters.
|
|
8
|
+
const literals = args.map((arg) => `'${arg.replace(/['\u2018-\u201b]/gu, (quote) => quote + quote)}'`).join(" ");
|
|
9
|
+
const command = "[Console]::OutputEncoding=[Text.UTF8Encoding]::new($false);\n"
|
|
10
|
+
+ `& {\n${script}\n} ${literals}`;
|
|
11
|
+
return ["-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(command, "utf16le").toString("base64")];
|
|
12
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { systemCommandRunner, windowsCommandArg, } from "./computer-service.js";
|
|
2
2
|
import { win32 } from "node:path";
|
|
3
|
+
import { powershellCommandArgs } from "./powershell-command.js";
|
|
3
4
|
function encoded(value) {
|
|
4
5
|
return Buffer.from(value, "utf16le").toString("base64");
|
|
5
6
|
}
|
|
@@ -33,10 +34,9 @@ export async function inspectWindowsServiceUpdateScope(spec, npmPrefix, daemonPi
|
|
|
33
34
|
"$enginePid=if($instances.Count -eq 1){[int64]$instances[0].EnginePID}else{0};",
|
|
34
35
|
"[pscustomobject]@{count=1;state=[int]$task.State;execute=if($actions.Count -eq 1){$actions[0].Execute}else{$null};arguments=if($actions.Count -eq 1){$actions[0].Arguments}else{$null};instanceCount=$instances.Count;enginePid=$enginePid;engineOwned=($enginePid -eq $pidValue -or $ancestors -contains $enginePid);processIdentity=$processIdentity;conflictingPrefixes=$conflicts.Count}|ConvertTo-Json -Compress;",
|
|
35
36
|
].join("");
|
|
36
|
-
const result = await runner("powershell.exe", [
|
|
37
|
-
"-NoProfile", "-NonInteractive", "-Command", script,
|
|
37
|
+
const result = await runner("powershell.exe", powershellCommandArgs(script, [
|
|
38
38
|
encoded(spec.id), encoded(entryMarker), String(daemonPid),
|
|
39
|
-
]);
|
|
39
|
+
]));
|
|
40
40
|
if (result.exitCode !== 0)
|
|
41
41
|
return { valid: false };
|
|
42
42
|
let inspection;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nowcrew/daemon",
|
|
3
|
-
"version": "0.6.97-beta.
|
|
3
|
+
"version": "0.6.97-beta.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -18,6 +18,13 @@
|
|
|
18
18
|
"publishConfig": {
|
|
19
19
|
"access": "public"
|
|
20
20
|
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
|
|
23
|
+
"build": "tsc -p tsconfig.json",
|
|
24
|
+
"prepublishOnly": "pnpm build && node ../scripts/daemon-release-artifact.mjs --strict-registry",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"typecheck": "tsc --noEmit"
|
|
27
|
+
},
|
|
21
28
|
"dependencies": {
|
|
22
29
|
"@agentclientprotocol/sdk": "1.2.1",
|
|
23
30
|
"@nowcrew/cli": "0.4.17",
|
|
@@ -39,11 +46,5 @@
|
|
|
39
46
|
"tsx": "^4.19.0",
|
|
40
47
|
"typescript": "^5.6.0",
|
|
41
48
|
"vitest": "^2.1.0"
|
|
42
|
-
},
|
|
43
|
-
"scripts": {
|
|
44
|
-
"daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
|
|
45
|
-
"build": "tsc -p tsconfig.json",
|
|
46
|
-
"test": "vitest run",
|
|
47
|
-
"typecheck": "tsc --noEmit"
|
|
48
49
|
}
|
|
49
|
-
}
|
|
50
|
+
}
|