@nowcrew/daemon 0.6.98-beta.4 → 0.6.98

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.
Files changed (75) hide show
  1. package/README.md +1 -34
  2. package/dist/automatic-service-migration.js +6 -7
  3. package/dist/cloud-report-upload.js +10 -1
  4. package/dist/computer-service.js +42 -40
  5. package/dist/computer-setup-client.js +8 -22
  6. package/dist/computer-setup-orchestration.js +2 -13
  7. package/dist/computer-setup.js +6 -36
  8. package/dist/execution-journal.js +1 -3
  9. package/dist/execution-protocol.js +1 -1
  10. package/dist/execution-runner.js +29 -20
  11. package/dist/external-output.js +12 -1
  12. package/dist/i18n.js +0 -10
  13. package/dist/local-execution-prompt.js +3 -8
  14. package/dist/local-executor.js +52 -69
  15. package/dist/local-memory/index-budget.js +3 -9
  16. package/dist/local-memory/inventory-cli.js +8 -52
  17. package/dist/local-memory/inventory-source.js +8 -15
  18. package/dist/local-memory/pending-query.js +2 -5
  19. package/dist/local-memory/pending-schema.js +1 -1
  20. package/dist/local-memory/pending.js +2 -16
  21. package/dist/local-memory/prompt.js +5 -21
  22. package/dist/local-memory/read-cli.js +76 -98
  23. package/dist/local-memory/rollout.js +11 -79
  24. package/dist/local-memory/runtime.js +13 -181
  25. package/dist/local-memory/write.js +21 -189
  26. package/dist/local-memory-diagnostics.js +13 -94
  27. package/dist/local-memory-telemetry.js +2 -19
  28. package/dist/machine-info.js +3 -6
  29. package/dist/marketplace-controller.js +21 -8
  30. package/dist/marketplace-maintenance-bridge.js +79 -0
  31. package/dist/marketplace-maintenance-protocol.js +40 -0
  32. package/dist/marketplace-protocol.js +1 -1
  33. package/dist/marketplace-remote.js +1 -1
  34. package/dist/marketplace-runner.js +1 -1
  35. package/dist/marketplace-serve.js +4 -2
  36. package/dist/memory-prune-diagnostics.js +4 -60
  37. package/dist/message-file-preview.js +1 -11
  38. package/dist/npm-command.js +5 -5
  39. package/dist/project-skills/machine-hello-publisher.js +6 -95
  40. package/dist/prompt.js +2 -6
  41. package/dist/runner.js +1 -1
  42. package/dist/runtimes/claude-thread-runner.js +4 -25
  43. package/dist/runtimes/pi.js +85 -17
  44. package/dist/serve.js +10 -92
  45. package/dist/token.js +0 -1
  46. package/dist/windows-scheduled-task.js +3 -3
  47. package/dist/workspace-control-handler.js +3 -16
  48. package/dist/workspace.js +2 -13
  49. package/package.json +1 -1
  50. package/dist/computer-token-setup.js +0 -46
  51. package/dist/local-memory/commit-telemetry.js +0 -30
  52. package/dist/local-memory/execution-workspace.js +0 -62
  53. package/dist/local-memory/full-index-search.js +0 -47
  54. package/dist/local-memory/initialization-identity.js +0 -36
  55. package/dist/local-memory/inventory-v2.js +0 -362
  56. package/dist/local-memory/legacy-adapter.js +0 -233
  57. package/dist/local-memory/migration-access.js +0 -242
  58. package/dist/local-memory/migration-cli.js +0 -54
  59. package/dist/local-memory/migration-io.js +0 -199
  60. package/dist/local-memory/migration-pending.js +0 -9
  61. package/dist/local-memory/migration-plan.js +0 -432
  62. package/dist/local-memory/migration-schema.js +0 -55
  63. package/dist/local-memory/migration-state-invariants.js +0 -65
  64. package/dist/local-memory/migration-store.js +0 -414
  65. package/dist/local-memory/migration-tool-entry.js +0 -21
  66. package/dist/local-memory/source-path.js +0 -13
  67. package/dist/local-memory/storage-format.js +0 -152
  68. package/dist/local-memory/writer-mode.js +0 -13
  69. package/dist/memory-migration-controller.js +0 -89
  70. package/dist/memory-migration-inspect.js +0 -108
  71. package/dist/memory-migration-journal.js +0 -14
  72. package/dist/memory-migration-protocol.js +0 -14
  73. package/dist/memory-migration-runtime.js +0 -305
  74. package/dist/powershell-command.js +0 -12
  75. package/dist/windows-task-principal.js +0 -26
package/README.md CHANGED
@@ -40,40 +40,7 @@ This compatibility form stays resident in the current terminal and reconnects wi
40
40
  `pnpm --filter @nowcrew/daemon daemon`. `crew-daemon run --agent <handle> --channel <id>` is the legacy
41
41
  manual one-shot entry.
42
42
 
43
- ## Single-Command Computer Setup
44
-
45
- After the deployment enables Token setup, an owner/admin opens **Add Computer**, copies the complete
46
- command, and pastes it into the target computer's terminal. The same steps apply on macOS, Linux/SSH,
47
- and Windows. Node.js 22+ and npm must already be installed. The command launches the installer with npx;
48
- no separate global installation, browser confirmation, or verification code is needed during execution.
49
-
50
- The generated command pins an exact published installer version and includes a `--setup-token`.
51
- This is an already-approved one-time connection request, valid for ten minutes. The program exchanges
52
- it for the machine credential only when executed, then reuses native service installation, new-hello
53
- verification, credential finalization and rollback. Merely generating or copying a command does not
54
- rotate the existing credential. An expired or consumed command must be regenerated in the Web UI.
55
-
56
- For a machine credential you already hold, the same setup entry accepts `--api-key` or
57
- `CREW_MACHINE_TOKEN` without browser pairing:
58
-
59
- ```bash
60
- nowwork-computer setup --server-url https://nowwork.nowcoder.com /nowcoder-app \
61
- --machine <machine-uuid> --api-key <existing-machine-token>
62
- ```
63
-
64
- The machine Token must match the specified Workspace and Computer. This path never rotates the
65
- credential or creates an approval request; it confirms a new control-plane connection with the expected
66
- profile and paths before reporting success. Token sources cannot be combined. JSON output puts progress
67
- on stderr and the final result on stdout. On Windows shells that block npm.ps1/npx.ps1, use npx.cmd as
68
- the package entry; the setup arguments and workflow are unchanged.
69
-
70
- Deployment requires `CREW_PUBLIC_URL` (or `CREW_SERVER_URL`) and
71
- `CREW_COMPUTER_TOKEN_SETUP_VERSION` set to an exact, published version known to support the Token flags.
72
- The server checks its published release catalog and channel before issuing a Token command. Leave the
73
- version unset until that installer has been released; it returns `computer_token_setup_unavailable`
74
- instead of generating a command for an unsupported package. Existing clients retain browser setup.
75
-
76
- ## Browser-Approved Computer Setup (Compatibility)
43
+ ## Browser-Approved Computer Setup
77
44
 
78
45
  The Web UI generates a token-free command for a specific Workspace and Machine:
79
46
 
@@ -3,8 +3,6 @@ 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";
7
- import { windowsTaskPrincipalFields, windowsTaskPrincipalMatches, windowsTaskPrincipalScript, } from "./windows-task-principal.js";
8
6
  function encoded(value) {
9
7
  return Buffer.from(value, "utf16le").toString("base64");
10
8
  }
@@ -144,11 +142,12 @@ async function discoverWindows(input, identity) {
144
142
  "$n=[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($args[0]));",
145
143
  "$tasks=@(Get-ScheduledTask -ErrorAction Stop|Where-Object{$_.TaskPath -eq '\\' -and $_.TaskName -ceq $n});",
146
144
  "if($tasks.Count -ne 1){[pscustomobject]@{count=$tasks.Count}|ConvertTo-Json -Compress;exit 0};",
147
- "$task=$tasks[0];$actions=@($task.Actions);",
148
- windowsTaskPrincipalScript,
149
- `[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};${windowsTaskPrincipalFields}}|ConvertTo-Json -Compress;`,
145
+ "$task=$tasks[0];$actions=@($task.Actions);$me=[Security.Principal.WindowsIdentity]::GetCurrent().Name;",
146
+ "[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;",
150
147
  ].join("");
151
- const result = await input.runner("powershell.exe", powershellCommandArgs(script, [encoded(identity.id)]));
148
+ const result = await input.runner("powershell.exe", [
149
+ "-NoProfile", "-NonInteractive", "-Command", script, encoded(identity.id),
150
+ ]);
152
151
  if (result.exitCode !== 0) {
153
152
  throw new Error(`Unable to inspect legacy scheduled task: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
154
153
  }
@@ -166,7 +165,7 @@ async function discoverWindows(input, identity) {
166
165
  : null;
167
166
  if (definition.count !== 1
168
167
  || definition.actionCount !== 1
169
- || !windowsTaskPrincipalMatches(definition)
168
+ || definition.principalMatches !== true
170
169
  || !win32.isAbsolute(definition.execute ?? "")
171
170
  || match === null
172
171
  || match[2] !== input.desired.profile
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { homedir, hostname, platform } from "node:os";
3
+ import { join } from "node:path";
3
4
  const ALIYUN_MARKER = /alibaba|aliyun|alicloud/iu;
4
5
  function readMarker(path) {
5
6
  try {
@@ -28,7 +29,15 @@ export function detectCloudProvider(env = process.env, platformName = platform()
28
29
  }
29
30
  export function findOssUploadScript(env = process.env, exists = existsSync) {
30
31
  const explicit = env.CREW_OSS_UPLOAD_SCRIPT?.trim();
31
- return explicit && exists(explicit) ? explicit : undefined;
32
+ if (explicit)
33
+ return exists(explicit) ? explicit : undefined;
34
+ const home = env.HOME?.trim() || homedir();
35
+ const candidates = [
36
+ join(home, ".claude", "skills", "oss-upload", "scripts", "oss_upload.py"),
37
+ join(home, ".codex", "skills", "oss-upload", "scripts", "oss_upload.py"),
38
+ join(home, ".agents", "skills", "oss-upload", "scripts", "oss_upload.py"),
39
+ ];
40
+ return candidates.find(exists);
32
41
  }
33
42
  export function cloudReportUploadFacts(env = process.env, platformName = platform(), hostName = hostname(), readMarkerValue, exists) {
34
43
  const provider = detectCloudProvider(env, platformName, hostName, readMarkerValue);
@@ -6,9 +6,7 @@ import { promisify } from "node:util";
6
6
  import { dirname, resolve } from "node:path";
7
7
  import { normalizeDaemonNpmRegistry } from "./npm-registry.js";
8
8
  import { daemonRegistryCandidates } from "./daemon-registry.js";
9
- import { windowsTaskPrincipalFields, windowsTaskPrincipalMatches, windowsTaskPrincipalScript, } from "./windows-task-principal.js";
10
9
  import { npmInvocation } from "./npm-command.js";
11
- import { powershellCommandArgs } from "./powershell-command.js";
12
10
  const execFileAsync = promisify(execFile);
13
11
  const WINDOWS_STOP_TIMEOUT_MS = 5_000;
14
12
  const WINDOWS_STOP_POLL_MS = 100;
@@ -197,11 +195,12 @@ async function windowsTaskDefinitionMatches(spec, runner) {
197
195
  "$n=[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($args[0]));",
198
196
  "$tasks=@(Get-ScheduledTask -ErrorAction Stop|Where-Object{$_.TaskPath -eq '\\' -and $_.TaskName -ceq $n});",
199
197
  "if($tasks.Count -ne 1){[pscustomobject]@{count=$tasks.Count}|ConvertTo-Json -Compress;exit 0};",
200
- "$task=$tasks[0];$actions=@($task.Actions);",
201
- windowsTaskPrincipalScript,
202
- `[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};${windowsTaskPrincipalFields}}|ConvertTo-Json -Compress;`,
198
+ "$task=$tasks[0];$actions=@($task.Actions);$me=[Security.Principal.WindowsIdentity]::GetCurrent().Name;",
199
+ "[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;",
203
200
  ].join("");
204
- const result = await runner("powershell.exe", powershellCommandArgs(script, [encodedName]));
201
+ const result = await runner("powershell.exe", [
202
+ "-NoProfile", "-NonInteractive", "-Command", script, encodedName,
203
+ ]);
205
204
  if (result.exitCode !== 0) {
206
205
  throw new Error(`Unable to inspect scheduled task definition: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
207
206
  }
@@ -215,7 +214,7 @@ async function windowsTaskDefinitionMatches(spec, runner) {
215
214
  const expectedArguments = spec.daemonCommand.slice(1).map(windowsCommandArg).join(" ");
216
215
  return definition.count === 1
217
216
  && definition.actionCount === 1
218
- && windowsTaskPrincipalMatches(definition)
217
+ && definition.principalMatches === true
219
218
  && sameWindowsPath(definition.execute, spec.daemonCommand[0])
220
219
  && definition.arguments === expectedArguments;
221
220
  }
@@ -227,7 +226,9 @@ async function windowsTaskState(spec, runner) {
227
226
  "$tasks=@(Get-ScheduledTask -ErrorAction Stop|Where-Object{$_.TaskPath -eq '\\' -and $_.TaskName -ceq $n});",
228
227
  "if($tasks.Count -eq 0){'missing'}elseif($tasks.Count -eq 1){'state:'+([int]$tasks[0].State)}else{throw 'duplicate scheduled tasks'}",
229
228
  ].join("");
230
- const result = await runner("powershell.exe", powershellCommandArgs(script, [encodedName]));
229
+ const result = await runner("powershell.exe", [
230
+ "-NoProfile", "-NonInteractive", "-Command", script, encodedName,
231
+ ]);
231
232
  if (result.exitCode !== 0) {
232
233
  throw new Error(`Unable to query scheduled task: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
233
234
  }
@@ -278,9 +279,10 @@ export async function installService(spec, runner = systemCommandRunner) {
278
279
  "$principal=New-ScheduledTaskPrincipal -UserId $identity -LogonType Interactive -RunLevel Limited;",
279
280
  "Register-ScheduledTask -TaskName $name -TaskPath '\\' -Action $action -Trigger $trigger -Settings $settings -Principal $principal -ErrorAction Stop|Out-Null;",
280
281
  ].join("");
281
- await checked(runner, "powershell.exe", powershellCommandArgs(script, [
282
+ await checked(runner, "powershell.exe", [
283
+ "-NoProfile", "-NonInteractive", "-Command", script,
282
284
  encodedName, encodedExecutable, encodedArguments,
283
- ]), "Scheduled task installation");
285
+ ], "Scheduled task installation");
284
286
  return;
285
287
  }
286
288
  if (spec.descriptorPath === null || spec.descriptor === null)
@@ -416,33 +418,6 @@ export async function serviceAction(spec, action, runner = systemCommandRunner)
416
418
  }
417
419
  }
418
420
  const systemDetachedSpawn = (command, args, options) => spawn(command, [...args], options);
419
- function windowsRestartCommandArgs(spec) {
420
- if (!spec.runtimeProcessIdentity)
421
- throw new Error("Windows daemon process identity is required for restart");
422
- const encodedName = Buffer.from(spec.id, "utf16le").toString("base64");
423
- const encodedExecutable = Buffer.from(spec.daemonCommand[0], "utf16le").toString("base64");
424
- const encodedArguments = Buffer.from(spec.daemonCommand.slice(1).map(windowsCommandArg).join(" "), "utf16le").toString("base64");
425
- const script = [
426
- "$ErrorActionPreference='Stop';",
427
- "$n=[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($args[1]));",
428
- "$exe=[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($args[2]));",
429
- "$argv=[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($args[3]));",
430
- "$task=@(Get-ScheduledTask -ErrorAction Stop|Where-Object{$_.TaskPath -eq '\\' -and $_.TaskName -ceq $n});if($task.Count -ne 1){throw 'scheduled task identity changed'};",
431
- "$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'};",
432
- "$p=Get-Process -Id ([int]$args[0]) -ErrorAction Stop;",
433
- "if($p.StartTime.ToUniversalTime().ToFileTimeUtc().ToString() -cne $args[4]){throw 'daemon process identity changed'};",
434
- "$p.Kill();",
435
- "if(-not $p.WaitForExit(10000)){throw 'daemon exit timeout'};",
436
- "$deadline=[DateTime]::UtcNow.AddSeconds(10);",
437
- "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);",
438
- "if([int]$task[0].State -eq 4){throw 'scheduled task stop timeout'};",
439
- "$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'};",
440
- "& schtasks.exe /Run /TN $n | Out-Null;if($LASTEXITCODE -ne 0){throw 'scheduled task start failed'};",
441
- ].join("");
442
- return powershellCommandArgs(script, [
443
- String(process.pid), encodedName, encodedExecutable, encodedArguments, spec.runtimeProcessIdentity,
444
- ]);
445
- }
446
421
  const RESTART_HELPER_SOURCE = String.raw `
447
422
  const { spawnSync } = require("node:child_process");
448
423
  const spec = JSON.parse(process.argv[1]);
@@ -453,7 +428,27 @@ setTimeout(() => {
453
428
  } else if (spec.platform === "linux") {
454
429
  restarted = spawnSync("systemctl", ["--user", "--no-block", "restart", spec.id], { stdio: "ignore", shell: false });
455
430
  } else {
456
- restarted = spawnSync("powershell.exe", spec.powershellArgs, { stdio: "ignore", shell: false });
431
+ const encodedName = Buffer.from(spec.id, "utf16le").toString("base64");
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 });
457
452
  }
458
453
  if (restarted.status !== 0) process.send?.({ type: "restart-failed", status: restarted.status });
459
454
  setTimeout(() => {
@@ -475,8 +470,15 @@ export function scheduleServiceRestart(spec, spawnDetached = systemDetachedSpawn
475
470
  ? { platform: spec.platform, id: spec.id }
476
471
  : {
477
472
  platform: spec.platform,
478
- powershellArgs: windowsRestartCommandArgs(spec),
473
+ id: spec.id,
474
+ daemonPid: process.pid,
475
+ executable: spec.daemonCommand[0],
476
+ arguments: spec.daemonCommand.slice(1).map(windowsCommandArg).join(" "),
477
+ processIdentity: spec.runtimeProcessIdentity,
479
478
  };
479
+ if (spec.platform === "win32" && !payload.processIdentity) {
480
+ throw new Error("Windows daemon process identity is required for restart");
481
+ }
480
482
  const child = spawnDetached(process.execPath, ["-e", RESTART_HELPER_SOURCE, JSON.stringify(payload)], { detached: true, stdio: ["ignore", "ignore", "ignore", "ipc"], shell: false });
481
483
  child.unref();
482
484
  let settleFailure;
@@ -625,7 +627,7 @@ export async function windowsProfileIsPrivate(path, runner = systemCommandRunner
625
627
  "$unsafe=$rules|Where-Object { $_.AccessControlType -eq 'Allow' -and $safe -notcontains $_.IdentityReference.Translate([Security.Principal.SecurityIdentifier]).Value };",
626
628
  "if($unsafe){'false'}else{'true'}",
627
629
  ].join("");
628
- const result = await runner("powershell.exe", powershellCommandArgs(script, [encodedPath]));
630
+ const result = await runner("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script, encodedPath]);
629
631
  if (result.exitCode !== 0)
630
632
  throw new Error(`Profile ACL inspection failed: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
631
633
  if (result.stdout.trim() === "true")
@@ -1,7 +1,6 @@
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";
5
4
  import { systemCommandRunner, } from "./computer-service.js";
6
5
  const HttpUrlSchema = z.string().url().refine((value) => {
7
6
  const url = new URL(value);
@@ -39,19 +38,6 @@ const SetupBootstrapSchema = z.object({
39
38
  expiresAt: z.string().datetime({ offset: true }).optional(),
40
39
  machineToken: MachineTokenSchema,
41
40
  }).strict();
42
- const SetupTokenTargetSchema = SetupBootstrapSchema.omit({ setupId: true, machineToken: true }).extend({
43
- workspaceSlug: z.string().min(1).max(80),
44
- releaseTag: z.enum(["latest", "beta"]),
45
- connection: z.object({
46
- status: z.enum(["online", "offline"]),
47
- epoch: z.number().int().nonnegative(),
48
- profile: z.string().nullable(),
49
- daemonHome: z.string().nullable(),
50
- agentsRoot: z.string().nullable(),
51
- storageId: z.string().optional(),
52
- credentialMatchesConnection: z.boolean(),
53
- }).strict(),
54
- }).strict();
55
41
  const SetupPendingSchema = z.object({
56
42
  status: z.enum(["authorization_pending", "pending"]),
57
43
  intervalSeconds: z.number().int().positive().max(300).optional(),
@@ -250,8 +236,8 @@ export function createComputerSetupClient(options) {
250
236
  setTimeout(resolve, milliseconds);
251
237
  }));
252
238
  const now = options.now ?? Date.now;
239
+ let activeMachineId;
253
240
  const request = async (method, path, body, bearer) => {
254
- const secretInputs = [bearer ?? "", body?.deviceCode ?? ""];
255
241
  const controller = new AbortController();
256
242
  const timeoutId = setTimeout(() => controller.abort(), requestTimeoutMs);
257
243
  try {
@@ -266,7 +252,7 @@ export function createComputerSetupClient(options) {
266
252
  });
267
253
  const payload = await response.json();
268
254
  if (!response.ok || payload?.success === false) {
269
- const message = redactSetupSecrets(payload?.error?.message ?? `computer setup request failed (HTTP ${response.status})`, secretInputs);
255
+ const message = redactSetupSecrets(payload?.error?.message ?? `computer setup request failed (HTTP ${response.status})`);
270
256
  throw new ComputerSetupHttpError(message, response.status, payload?.error?.code ?? `HTTP_${response.status}`);
271
257
  }
272
258
  return (payload?.success === true ? payload.data : payload);
@@ -276,15 +262,15 @@ export function createComputerSetupClient(options) {
276
262
  throw new Error("computer setup request timed out");
277
263
  if (error instanceof ComputerSetupHttpError)
278
264
  throw error;
279
- throw new Error(redactSetupSecrets(error instanceof Error ? error.message : String(error), secretInputs));
265
+ throw new Error(redactSetupSecrets(error instanceof Error ? error.message : String(error)));
280
266
  }
281
267
  finally {
282
268
  clearTimeout(timeoutId);
283
269
  }
284
270
  };
285
271
  return {
286
- tokenTarget: async (machineToken) => SetupTokenTargetSchema.parse(await request("GET", "/daemon/computer-setup/target", undefined, machineToken)),
287
272
  start: async (input) => {
273
+ activeMachineId = input.machineId;
288
274
  return parseStart(await request("POST", "/api/auth/computer-setup/start", {
289
275
  ...(input.workspace.kind === "id"
290
276
  ? { workspaceId: input.workspace.value }
@@ -295,6 +281,8 @@ export function createComputerSetupClient(options) {
295
281
  }));
296
282
  },
297
283
  poll: async (deviceCode) => {
284
+ if (activeMachineId === undefined)
285
+ throw new Error("computer setup polling has no active machine");
298
286
  return parsePoll(await request("POST", "/api/auth/computer-setup/poll", { deviceCode }));
299
287
  },
300
288
  waitForHello: async (input) => {
@@ -321,10 +309,8 @@ export function createComputerSetupClient(options) {
321
309
  };
322
310
  }
323
311
  export async function openSetupBrowser(url, platform = process.platform, runner = systemCommandRunner) {
324
- const command = platform === "win32" ? "powershell.exe" : platform === "darwin" ? "open" : "xdg-open";
325
- const args = platform === "win32"
326
- ? powershellCommandArgs("Start-Process -FilePath $args[0] -ErrorAction Stop | Out-Null", [url])
327
- : [url];
312
+ const command = platform === "win32" ? "explorer.exe" : platform === "darwin" ? "open" : "xdg-open";
313
+ const args = [url];
328
314
  const result = await runner(command, args);
329
315
  if (result.exitCode !== 0) {
330
316
  throw new Error(`Unable to open the computer setup browser: ${redactSetupSecrets(result.stderr.trim() || `exit ${result.exitCode}`)}`);
@@ -6,7 +6,7 @@ function safeError(error, machineToken) {
6
6
  const message = redactSetupSecrets(error instanceof Error ? error.message : String(error), machineToken === undefined ? [] : [machineToken]);
7
7
  return new Error(message, error instanceof Error ? { cause: error } : undefined);
8
8
  }
9
- export function assertBootstrapMatches(bootstrap, input) {
9
+ function assertBootstrapMatches(bootstrap, input) {
10
10
  if (bootstrap.machineId !== input.args.machineId) {
11
11
  throw new Error("setup bootstrap machine does not match --machine");
12
12
  }
@@ -40,7 +40,7 @@ function secondsUntil(expiresAt, now) {
40
40
  const remaining = Date.parse(expiresAt) - now();
41
41
  return Math.max(0, Math.ceil(remaining / 1_000));
42
42
  }
43
- export function localPathsMatch(left, right, platform) {
43
+ function localPathsMatch(left, right, platform) {
44
44
  const paths = platform === "win32" ? win32 : posix;
45
45
  if (!paths.isAbsolute(left) || !paths.isAbsolute(right))
46
46
  return false;
@@ -51,14 +51,6 @@ export function localPathsMatch(left, right, platform) {
51
51
  return normalize(left) === normalize(right);
52
52
  }
53
53
  async function obtainBootstrap(input, deps) {
54
- if (input.args.setupToken !== undefined) {
55
- deps.reportPairing("Checking computer connection information...");
56
- const result = await deps.client.poll(input.args.setupToken);
57
- if (result.status !== "authorized") {
58
- throw new Error("Setup command expired or unavailable; regenerate it in NowWork and run the new command");
59
- }
60
- return result.bootstrap;
61
- }
62
54
  const pairing = await deps.client.start({
63
55
  workspace: input.args.workspace,
64
56
  machineId: input.args.machineId,
@@ -124,7 +116,6 @@ export async function runComputerSetupOrchestration(input, deps) {
124
116
  bootstrap = authorized;
125
117
  machineToken = token;
126
118
  assertBootstrapMatches(authorized, input);
127
- deps.reportPairing("Downloading, installing, and starting the Daemon service...");
128
119
  const installation = await deps.installAutomatically({
129
120
  serverUrl: authorized.serverUrl,
130
121
  machineToken: token,
@@ -137,7 +128,6 @@ export async function runComputerSetupOrchestration(input, deps) {
137
128
  ...(authorized.registryUrl === undefined ? {} : { requestedRegistryUrl: authorized.registryUrl }),
138
129
  beforeRollback: confirmRollback,
139
130
  verifyAfterStart: async (candidate) => {
140
- deps.reportPairing("Confirming the computer connection...");
141
131
  const hello = await deps.client.waitForHello({
142
132
  machineToken: token,
143
133
  machineId: authorized.machineId,
@@ -184,7 +174,6 @@ export async function runComputerSetupOrchestration(input, deps) {
184
174
  });
185
175
  return {
186
176
  workspaceId: authorized.workspaceId,
187
- ...(authorized.workspaceSlug === undefined ? {} : { workspaceSlug: authorized.workspaceSlug }),
188
177
  machineId: authorized.machineId,
189
178
  environment: authorized.environment,
190
179
  profile: installation.profile,
@@ -1,13 +1,11 @@
1
1
  import { parseArgs } from "node:util";
2
2
  import { z } from "zod";
3
3
  import { DEFAULT_SERVER_URL } from "./config.js";
4
- import { MachineTokenSchema } from "./computer-profile.js";
5
4
  import { installDaemonAutomatically, } from "./automatic-daemon-installer.js";
6
5
  import { createComputerSetupClient, openSetupBrowser, redactSetupSecrets, } from "./computer-setup-client.js";
7
6
  import { runComputerSetupOrchestration, } from "./computer-setup-orchestration.js";
8
7
  import { detectDaemonLang, formatDaemonText } from "./i18n.js";
9
8
  import { systemCommandRunner } from "./computer-service.js";
10
- import { runMachineTokenSetup } from "./computer-token-setup.js";
11
9
  const WorkspaceSlugSchema = z.string()
12
10
  .min(2)
13
11
  .max(40)
@@ -72,8 +70,6 @@ export function parseSetupArgs(argv) {
72
70
  assertRepeatedOptionValuesAgree(argv, "machine", (value) => MachineIdSchema.parse(value).toLowerCase());
73
71
  assertRepeatedOptionValuesAgree(argv, "machine-id", (value) => MachineIdSchema.parse(value).toLowerCase());
74
72
  assertRepeatedOptionValuesAgree(argv, "server-url", normalizedServerOrigin);
75
- assertRepeatedOptionValuesAgree(argv, "setup-token", (value) => value);
76
- assertRepeatedOptionValuesAgree(argv, "api-key", (value) => value);
77
73
  const parsed = parseArgs({
78
74
  args: [...argv],
79
75
  allowPositionals: true,
@@ -86,8 +82,6 @@ export function parseSetupArgs(argv) {
86
82
  "server-url": { type: "string" },
87
83
  json: { type: "boolean", default: false },
88
84
  "no-browser": { type: "boolean", default: false },
89
- "setup-token": { type: "string" },
90
- "api-key": { type: "string" },
91
85
  help: { type: "boolean", short: "h", default: false },
92
86
  },
93
87
  });
@@ -130,14 +124,6 @@ export function parseSetupArgs(argv) {
130
124
  }
131
125
  const machineId = parsedMachine;
132
126
  const serverUrl = stringValue(values, "server-url");
133
- const setupToken = stringValue(values, "setup-token");
134
- const machineToken = stringValue(values, "api-key");
135
- if (setupToken !== undefined && machineToken !== undefined)
136
- throw new Error("Choose one setup credential source");
137
- if (setupToken !== undefined && !/^[A-Za-z0-9_-]{32,512}$/u.test(setupToken))
138
- throw new Error("Invalid setup Token");
139
- if (machineToken !== undefined)
140
- MachineTokenSchema.parse(machineToken);
141
127
  return Object.freeze({
142
128
  command: "setup",
143
129
  workspace,
@@ -145,8 +131,6 @@ export function parseSetupArgs(argv) {
145
131
  ...(serverUrl === undefined ? {} : { serverUrl: ServerUrlSchema.parse(serverUrl) }),
146
132
  ...(booleanValue(values, "json") ? { json: true } : {}),
147
133
  ...(booleanValue(values, "no-browser") ? { noBrowser: true } : {}),
148
- ...(setupToken === undefined ? {} : { setupToken }),
149
- ...(machineToken === undefined ? {} : { machineToken }),
150
134
  });
151
135
  }
152
136
  function resolveSetupServerUrl(args, env) {
@@ -164,8 +148,7 @@ export function setupUsage(td = (message) => message) {
164
148
  " nowwork-computer setup --workspace /workspace --machine <uuid>",
165
149
  " nowwork-computer setup --workspace-id <uuid> --machine-id <uuid>",
166
150
  "",
167
- "Options: --server-url <url> --setup-token <token> --api-key <sk_machine_*> --json --no-browser",
168
- td("Token setup does not require browser authorization. CREW_MACHINE_TOKEN is also supported."),
151
+ "Options: --server-url <url> --json --no-browser",
169
152
  ].join("\n") + "\n";
170
153
  }
171
154
  function defaults() {
@@ -184,7 +167,6 @@ function defaults() {
184
167
  }
185
168
  function humanSummary(result, td) {
186
169
  return [
187
- td("Connected to {{workspace}}.", { workspace: result.workspaceSlug ?? result.workspaceId }),
188
170
  td("Workspace: {{value}}", { value: result.workspaceId }),
189
171
  td("Machine: {{value}}", { value: result.machineId }),
190
172
  td("Environment: {{value}}", { value: result.environment }),
@@ -223,7 +205,6 @@ export async function runComputerSetupCommand(argv, overrides = {}) {
223
205
  return null;
224
206
  const deps = { ...defaults(), ...overrides };
225
207
  const lang = detectDaemonLang(deps.env);
226
- const secrets = [...optionOccurrences(argv, "setup-token"), ...optionOccurrences(argv, "api-key"), deps.env.CREW_MACHINE_TOKEN ?? ""];
227
208
  try {
228
209
  if (argv.includes("--help") || argv.includes("-h")) {
229
210
  deps.stdout(setupUsage((message, values) => formatDaemonText(lang, message, values)));
@@ -232,43 +213,32 @@ export async function runComputerSetupCommand(argv, overrides = {}) {
232
213
  let args;
233
214
  try {
234
215
  args = parseSetupArgs(argv);
235
- const envToken = deps.env.CREW_MACHINE_TOKEN?.trim();
236
- if (envToken) {
237
- if (args.setupToken !== undefined || (args.machineToken !== undefined && args.machineToken !== envToken)) {
238
- throw new Error("Choose one setup credential source; clear CREW_MACHINE_TOKEN when using another Token");
239
- }
240
- args = Object.freeze({ ...args, machineToken: MachineTokenSchema.parse(envToken) });
241
- }
242
216
  }
243
217
  catch (error) {
244
- deps.stderr(`nowwork-computer: ${formatDaemonText(lang, redactSetupSecrets(error.message, secrets))}\n`);
218
+ deps.stderr(`nowwork-computer: ${redactSetupSecrets(error.message)}\n`);
245
219
  return 2;
246
220
  }
247
- if (args.json === true && args.setupToken === undefined && args.machineToken === undefined) {
221
+ if (args.json === true) {
248
222
  deps.stderr(`${formatDaemonText(lang, "Starting computer setup pairing...")}\n`);
249
223
  }
250
224
  const serverUrl = resolveSetupServerUrl(args, deps.env);
251
225
  const createClient = overrides.createClient
252
226
  ?? ((value) => createComputerSetupClient({ serverUrl: value, fetch: deps.fetch }));
253
- const orchestrationDeps = {
227
+ const result = await runComputerSetupOrchestration({ args, serverUrl }, {
254
228
  client: createClient(serverUrl),
255
229
  installAutomatically: deps.installAutomatically,
256
230
  openBrowser: args.noBrowser === true ? async () => { } : deps.openBrowser,
257
231
  sleep: deps.sleep,
258
232
  reportPairing: (message) => deps[args.json === true ? "stderr" : "stdout"](`${localizedPairingMessage(lang, message)}\n`),
259
233
  now: deps.now,
260
- platform: deps.platform,
261
- };
262
- const result = args.machineToken === undefined
263
- ? await runComputerSetupOrchestration({ args, serverUrl }, orchestrationDeps)
264
- : await runMachineTokenSetup({ args, serverUrl }, orchestrationDeps, args.machineToken);
234
+ });
265
235
  deps.stdout(args.json === true
266
236
  ? `${JSON.stringify(result)}\n`
267
237
  : humanSummary(result, (message, values) => formatDaemonText(lang, message, values)));
268
238
  return 0;
269
239
  }
270
240
  catch (error) {
271
- deps.stderr(`nowwork-computer: ${formatDaemonText(lang, redactSetupSecrets(error.message, secrets))}\n`);
241
+ deps.stderr(`nowwork-computer: ${redactSetupSecrets(error.message)}\n`);
272
242
  return 1;
273
243
  }
274
244
  }
@@ -8,7 +8,7 @@ import { provePosixProcessGroupStopped } from "./execution-posix-stop-proof.js";
8
8
  export { JournalLockedError, JournalLockCorruptionError } from "./execution-journal-lock.js";
9
9
  const ExecutionIdSchema = z.string().uuid();
10
10
  const TimestampSchema = z.string().datetime({ offset: true });
11
- const RuntimeSchema = z.enum(["claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness", "memory_migration", "pi"]);
11
+ const RuntimeSchema = z.enum(["claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness", "pi"]);
12
12
  const RUNTIME_READY_SUFFIX = ".runtime-ready";
13
13
  const RawJournalEntrySchema = z.object({
14
14
  executionId: ExecutionIdSchema,
@@ -523,8 +523,6 @@ export function createExecutionJournal(agentsRoot, options = {}) {
523
523
  }
524
524
  };
525
525
  const pruneInternal = async () => {
526
- if (options.retainCompleted)
527
- return;
528
526
  const protectedExecutionIds = new Set(options.protectedExecutionIds?.() ?? []);
529
527
  const entries = await readAll();
530
528
  const acknowledged = entries
@@ -319,7 +319,7 @@ const RawExecutionCompletedSchema = z.object({
319
319
  providerErrorCategory: ProviderErrorCategorySchema.optional(),
320
320
  providerHttpStatus: ProviderHttpStatusSchema.optional(),
321
321
  providerErrorCode: ProviderErrorCodeSchema.optional(),
322
- runtime: z.union([RuntimeSchema, z.literal("memory_migration")]),
322
+ runtime: RuntimeSchema,
323
323
  model: z.string().optional(),
324
324
  resumed: z.boolean(),
325
325
  finalText: z.string().optional(),
@@ -1,7 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { dirname, join, resolve } from "node:path";
3
3
  import { isLocalMemoryV2Enabled, localMemoryV2Environment, resolveReposRoot } from "./config.js";
4
- import { detectLocalMemoryStorageVersion } from "./local-memory/storage-format.js";
5
4
  import { DaemonToServerExecutionFrameSchema, ExecutionCompletedSchema, ExecutionRejectedSchema, ExecutionStartSchema, } from "./execution-protocol.js";
6
5
  import { JournalConflictError } from "./execution-journal.js";
7
6
  import { boundExecutionFrame } from "./execution-event-limit.js";
@@ -588,6 +587,26 @@ export async function runExecution(config, input, dependencies) {
588
587
  let activitySequence = 0;
589
588
  let consoleSequence = 0;
590
589
  let externalOutputSequence = 0;
590
+ const bufferedPiExternalOutput = [];
591
+ const reportExternalOutput = (text) => {
592
+ if (firstOutputTimer !== undefined) {
593
+ clearTimeout(firstOutputTimer);
594
+ firstOutputTimer = undefined;
595
+ }
596
+ const frame = DaemonToServerExecutionFrameSchema.parse({
597
+ type: "execution:output",
598
+ protocolVersion: 1,
599
+ executionId: spec.executionId,
600
+ channel: "external_answer",
601
+ text,
602
+ seq: externalOutputSequence++,
603
+ at: now().toISOString(),
604
+ });
605
+ try {
606
+ telemetry.enqueue(boundExecutionFrame(frame, config.executionLimits.maxEventBytes));
607
+ }
608
+ catch { /* final completion remains the authoritative repair */ }
609
+ };
591
610
  const callbacks = {
592
611
  onRuntimeStarting: () => {
593
612
  dependencies.onRuntimePhase?.("starting", spec.runtime.name);
@@ -712,23 +731,11 @@ export async function runExecution(config, input, dependencies) {
712
731
  } : {}),
713
732
  ...(spec.context.externalResponseSessionId || spec.context.answerStream ? {
714
733
  onExternalOutput: (text) => {
715
- if (firstOutputTimer !== undefined) {
716
- clearTimeout(firstOutputTimer);
717
- firstOutputTimer = undefined;
718
- }
719
- const frame = DaemonToServerExecutionFrameSchema.parse({
720
- type: "execution:output",
721
- protocolVersion: 1,
722
- executionId: spec.executionId,
723
- channel: "external_answer",
724
- text,
725
- seq: externalOutputSequence++,
726
- at: now().toISOString(),
727
- });
728
- try {
729
- telemetry.enqueue(boundExecutionFrame(frame, config.executionLimits.maxEventBytes));
734
+ if (spec.runtime.name === "pi") {
735
+ bufferedPiExternalOutput.push(text);
736
+ return;
730
737
  }
731
- catch { /* final completion remains the authoritative repair */ }
738
+ reportExternalOutput(text);
732
739
  },
733
740
  } : {}),
734
741
  };
@@ -839,9 +846,8 @@ export async function runExecution(config, input, dependencies) {
839
846
  const localMemoryGuardInBase = localMemoryV2Environment(config.serverUrl) === "custom";
840
847
  const boundedSystemPrompt = (context) => {
841
848
  const enabled = context.workspace.localMemoryV2Enabled ?? localMemoryV2Enabled;
842
- const systemPromptWithLocalFacts = withLocalExecutionFacts(`${spec.instructions.systemPrompt}${enabled && localMemoryGuardInBase && detectLocalMemoryStorageVersion(context.workspace.memory) !== 2 ? renderLocalMemoryV2WriteGuard() : ""}`, baseSystemPromptBudget, {
849
+ const systemPromptWithLocalFacts = withLocalExecutionFacts(`${spec.instructions.systemPrompt}${enabled && localMemoryGuardInBase ? renderLocalMemoryV2WriteGuard() : ""}`, baseSystemPromptBudget, {
843
850
  ...(selfTraining ? { isolateLocalMemory: true } : {}),
844
- memoryQuery: spec.instructions.wakePrompt,
845
851
  collaborationTools: config.codexCollaborationTools && spec.runtime.name === "codex",
846
852
  });
847
853
  const base = typeof systemPromptWithLocalFacts === "string"
@@ -881,7 +887,6 @@ export async function runExecution(config, input, dependencies) {
881
887
  serverUrl: config.serverUrl,
882
888
  token: credential.token,
883
889
  agentId: credential.agentId,
884
- ...(dependencies.controlIdentity === undefined ? {} : dependencies.controlIdentity),
885
890
  agentsRoot: config.agentsRoot,
886
891
  reposRoot: resolveReposRoot(config.agentsRoot, config.reposRoot),
887
892
  ...(config.qaReportRoot === undefined ? {} : { qaReportRoot: config.qaReportRoot }),
@@ -912,6 +917,10 @@ export async function runExecution(config, input, dependencies) {
912
917
  clearTimeout(timeout);
913
918
  if (firstOutputTimer !== undefined)
914
919
  clearTimeout(firstOutputTimer);
920
+ if (spec.runtime.name === "pi" && result.exitCode === 0 && result.externalAnswer !== null) {
921
+ for (const text of bufferedPiExternalOutput)
922
+ reportExternalOutput(text);
923
+ }
915
924
  const finishedAt = now().toISOString();
916
925
  if (result.exitCode === 0 && result.finalText?.trim())
917
926
  memoryCaptureFinalText = result.finalText;