@nowcrew/daemon 0.6.97-beta.9 → 0.6.98-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/automatic-service-migration.js +7 -6
- package/dist/computer-main.js +0 -0
- package/dist/computer-service.js +40 -42
- package/dist/computer-setup-client.js +5 -2
- package/dist/main.js +0 -0
- package/dist/npm-command.js +5 -5
- package/dist/powershell-command.js +12 -0
- package/dist/provider-env.js +8 -3
- package/dist/runtimes/codex-app-server-runner.js +2 -0
- package/dist/runtimes/codex-execution-policy.js +12 -0
- package/dist/runtimes/codex.js +2 -0
- package/dist/windows-scheduled-task.js +3 -3
- package/dist/windows-task-principal.js +26 -0
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -413,6 +413,13 @@ discovery is unchanged: Claude may still load `~/.claude/skills`, and Codex may
|
|
|
413
413
|
and `~/.agents/skills`. The DeepSeek Codex variant keeps its Agent-private `.codex-deepseek` Home for
|
|
414
414
|
provider credentials. Project bindings do not hide or rewrite any of those sources.
|
|
415
415
|
|
|
416
|
+
Both Codex launchers disable host automatic memories and inherit the execution's prepared environment,
|
|
417
|
+
overriding host `core`, default exclusions, `exclude` and `include_only` filters. These are process-local
|
|
418
|
+
settings: authentication, providers and Skills retain the configured Home, and no other environment
|
|
419
|
+
scope is merged. The policy version in Codex's session fingerprint cold-starts older native sessions
|
|
420
|
+
once, then subsequent runs can resume normally. Explicit `shell_environment_policy.set` entries, shell
|
|
421
|
+
startup scripts and Agent notes remain separate configuration; this change does not correct them.
|
|
422
|
+
|
|
416
423
|
Binding changes reconcile immediately when the Computer is online. On reconnect the server sends the
|
|
417
424
|
complete binding set for every Agent assigned to that Computer, including empty sets that clear stale
|
|
418
425
|
links left by offline unbinds. Every non-empty execution snapshot is also reconciled immediately before
|
|
@@ -3,6 +3,8 @@ 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";
|
|
6
8
|
function encoded(value) {
|
|
7
9
|
return Buffer.from(value, "utf16le").toString("base64");
|
|
8
10
|
}
|
|
@@ -142,12 +144,11 @@ async function discoverWindows(input, identity) {
|
|
|
142
144
|
"$n=[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($args[0]));",
|
|
143
145
|
"$tasks=@(Get-ScheduledTask -ErrorAction Stop|Where-Object{$_.TaskPath -eq '\\' -and $_.TaskName -ceq $n});",
|
|
144
146
|
"if($tasks.Count -ne 1){[pscustomobject]@{count=$tasks.Count}|ConvertTo-Json -Compress;exit 0};",
|
|
145
|
-
"$task=$tasks[0];$actions=@($task.Actions)
|
|
146
|
-
|
|
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;`,
|
|
147
150
|
].join("");
|
|
148
|
-
const result = await input.runner("powershell.exe", [
|
|
149
|
-
"-NoProfile", "-NonInteractive", "-Command", script, encoded(identity.id),
|
|
150
|
-
]);
|
|
151
|
+
const result = await input.runner("powershell.exe", powershellCommandArgs(script, [encoded(identity.id)]));
|
|
151
152
|
if (result.exitCode !== 0) {
|
|
152
153
|
throw new Error(`Unable to inspect legacy scheduled task: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
|
|
153
154
|
}
|
|
@@ -165,7 +166,7 @@ async function discoverWindows(input, identity) {
|
|
|
165
166
|
: null;
|
|
166
167
|
if (definition.count !== 1
|
|
167
168
|
|| definition.actionCount !== 1
|
|
168
|
-
|| definition
|
|
169
|
+
|| !windowsTaskPrincipalMatches(definition)
|
|
169
170
|
|| !win32.isAbsolute(definition.execute ?? "")
|
|
170
171
|
|| match === null
|
|
171
172
|
|| match[2] !== input.desired.profile
|
package/dist/computer-main.js
CHANGED
|
File without changes
|
package/dist/computer-service.js
CHANGED
|
@@ -6,7 +6,9 @@ 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";
|
|
9
10
|
import { npmInvocation } from "./npm-command.js";
|
|
11
|
+
import { powershellCommandArgs } from "./powershell-command.js";
|
|
10
12
|
const execFileAsync = promisify(execFile);
|
|
11
13
|
const WINDOWS_STOP_TIMEOUT_MS = 5_000;
|
|
12
14
|
const WINDOWS_STOP_POLL_MS = 100;
|
|
@@ -195,12 +197,11 @@ async function windowsTaskDefinitionMatches(spec, runner) {
|
|
|
195
197
|
"$n=[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($args[0]));",
|
|
196
198
|
"$tasks=@(Get-ScheduledTask -ErrorAction Stop|Where-Object{$_.TaskPath -eq '\\' -and $_.TaskName -ceq $n});",
|
|
197
199
|
"if($tasks.Count -ne 1){[pscustomobject]@{count=$tasks.Count}|ConvertTo-Json -Compress;exit 0};",
|
|
198
|
-
"$task=$tasks[0];$actions=@($task.Actions)
|
|
199
|
-
|
|
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;`,
|
|
200
203
|
].join("");
|
|
201
|
-
const result = await runner("powershell.exe", [
|
|
202
|
-
"-NoProfile", "-NonInteractive", "-Command", script, encodedName,
|
|
203
|
-
]);
|
|
204
|
+
const result = await runner("powershell.exe", powershellCommandArgs(script, [encodedName]));
|
|
204
205
|
if (result.exitCode !== 0) {
|
|
205
206
|
throw new Error(`Unable to inspect scheduled task definition: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
|
|
206
207
|
}
|
|
@@ -214,7 +215,7 @@ async function windowsTaskDefinitionMatches(spec, runner) {
|
|
|
214
215
|
const expectedArguments = spec.daemonCommand.slice(1).map(windowsCommandArg).join(" ");
|
|
215
216
|
return definition.count === 1
|
|
216
217
|
&& definition.actionCount === 1
|
|
217
|
-
&& definition
|
|
218
|
+
&& windowsTaskPrincipalMatches(definition)
|
|
218
219
|
&& sameWindowsPath(definition.execute, spec.daemonCommand[0])
|
|
219
220
|
&& definition.arguments === expectedArguments;
|
|
220
221
|
}
|
|
@@ -226,9 +227,7 @@ async function windowsTaskState(spec, runner) {
|
|
|
226
227
|
"$tasks=@(Get-ScheduledTask -ErrorAction Stop|Where-Object{$_.TaskPath -eq '\\' -and $_.TaskName -ceq $n});",
|
|
227
228
|
"if($tasks.Count -eq 0){'missing'}elseif($tasks.Count -eq 1){'state:'+([int]$tasks[0].State)}else{throw 'duplicate scheduled tasks'}",
|
|
228
229
|
].join("");
|
|
229
|
-
const result = await runner("powershell.exe", [
|
|
230
|
-
"-NoProfile", "-NonInteractive", "-Command", script, encodedName,
|
|
231
|
-
]);
|
|
230
|
+
const result = await runner("powershell.exe", powershellCommandArgs(script, [encodedName]));
|
|
232
231
|
if (result.exitCode !== 0) {
|
|
233
232
|
throw new Error(`Unable to query scheduled task: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
|
|
234
233
|
}
|
|
@@ -279,10 +278,9 @@ export async function installService(spec, runner = systemCommandRunner) {
|
|
|
279
278
|
"$principal=New-ScheduledTaskPrincipal -UserId $identity -LogonType Interactive -RunLevel Limited;",
|
|
280
279
|
"Register-ScheduledTask -TaskName $name -TaskPath '\\' -Action $action -Trigger $trigger -Settings $settings -Principal $principal -ErrorAction Stop|Out-Null;",
|
|
281
280
|
].join("");
|
|
282
|
-
await checked(runner, "powershell.exe", [
|
|
283
|
-
"-NoProfile", "-NonInteractive", "-Command", script,
|
|
281
|
+
await checked(runner, "powershell.exe", powershellCommandArgs(script, [
|
|
284
282
|
encodedName, encodedExecutable, encodedArguments,
|
|
285
|
-
], "Scheduled task installation");
|
|
283
|
+
]), "Scheduled task installation");
|
|
286
284
|
return;
|
|
287
285
|
}
|
|
288
286
|
if (spec.descriptorPath === null || spec.descriptor === null)
|
|
@@ -418,6 +416,33 @@ export async function serviceAction(spec, action, runner = systemCommandRunner)
|
|
|
418
416
|
}
|
|
419
417
|
}
|
|
420
418
|
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
|
+
}
|
|
421
446
|
const RESTART_HELPER_SOURCE = String.raw `
|
|
422
447
|
const { spawnSync } = require("node:child_process");
|
|
423
448
|
const spec = JSON.parse(process.argv[1]);
|
|
@@ -428,27 +453,7 @@ setTimeout(() => {
|
|
|
428
453
|
} else if (spec.platform === "linux") {
|
|
429
454
|
restarted = spawnSync("systemctl", ["--user", "--no-block", "restart", spec.id], { stdio: "ignore", shell: false });
|
|
430
455
|
} 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 });
|
|
456
|
+
restarted = spawnSync("powershell.exe", spec.powershellArgs, { stdio: "ignore", shell: false });
|
|
452
457
|
}
|
|
453
458
|
if (restarted.status !== 0) process.send?.({ type: "restart-failed", status: restarted.status });
|
|
454
459
|
setTimeout(() => {
|
|
@@ -470,15 +475,8 @@ export function scheduleServiceRestart(spec, spawnDetached = systemDetachedSpawn
|
|
|
470
475
|
? { platform: spec.platform, id: spec.id }
|
|
471
476
|
: {
|
|
472
477
|
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,
|
|
478
|
+
powershellArgs: windowsRestartCommandArgs(spec),
|
|
478
479
|
};
|
|
479
|
-
if (spec.platform === "win32" && !payload.processIdentity) {
|
|
480
|
-
throw new Error("Windows daemon process identity is required for restart");
|
|
481
|
-
}
|
|
482
480
|
const child = spawnDetached(process.execPath, ["-e", RESTART_HELPER_SOURCE, JSON.stringify(payload)], { detached: true, stdio: ["ignore", "ignore", "ignore", "ipc"], shell: false });
|
|
483
481
|
child.unref();
|
|
484
482
|
let settleFailure;
|
|
@@ -627,7 +625,7 @@ export async function windowsProfileIsPrivate(path, runner = systemCommandRunner
|
|
|
627
625
|
"$unsafe=$rules|Where-Object { $_.AccessControlType -eq 'Allow' -and $safe -notcontains $_.IdentityReference.Translate([Security.Principal.SecurityIdentifier]).Value };",
|
|
628
626
|
"if($unsafe){'false'}else{'true'}",
|
|
629
627
|
].join("");
|
|
630
|
-
const result = await runner("powershell.exe",
|
|
628
|
+
const result = await runner("powershell.exe", powershellCommandArgs(script, [encodedPath]));
|
|
631
629
|
if (result.exitCode !== 0)
|
|
632
630
|
throw new Error(`Profile ACL inspection failed: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
|
|
633
631
|
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
|
package/dist/npm-command.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { posix, win32 } from "node:path";
|
|
2
|
-
/** Execute
|
|
2
|
+
/** Execute npm through its owning Node binary so PATH and cmd.exe quoting are irrelevant. */
|
|
3
3
|
export function npmInvocation(platform, nodePath = process.execPath) {
|
|
4
4
|
const paths = platform === "win32" ? win32 : posix;
|
|
5
|
-
const npmPath =
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
const npmPath = platform === "win32"
|
|
6
|
+
? paths.join(paths.dirname(nodePath), "node_modules", "npm", "bin", "npm-cli.js")
|
|
7
|
+
: paths.join(paths.dirname(nodePath), "npm");
|
|
8
|
+
return { command: nodePath, argsPrefix: [npmPath] };
|
|
9
9
|
}
|
|
@@ -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
|
+
}
|
package/dist/provider-env.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { DEEPSEEK_CODEX_KEY_ENV, deepSeekCodexHome, } from "./runtimes/codex-deepseek-config.js";
|
|
13
13
|
import { mergeRuntimeEnvironments } from "./runtime-environment-merge.js";
|
|
14
|
+
import { CODEX_EXECUTION_POLICY_VERSION } from "./runtimes/codex-execution-policy.js";
|
|
14
15
|
const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
15
16
|
const RESERVED_ENV = new Set([
|
|
16
17
|
"PATH",
|
|
@@ -125,11 +126,15 @@ export function applyDeepSeekHarnessMachineEnv(base, machineEnv, platform = proc
|
|
|
125
126
|
}
|
|
126
127
|
/**
|
|
127
128
|
* provider 关键配置指纹:Claude Custom 路由和 Codex DeepSeek key 有无都会改变会话边界,
|
|
128
|
-
* 供 pickResumeId 判定冷启动。
|
|
129
|
+
* 供 pickResumeId 判定冷启动。Codex 还包含执行策略版本,避免续用带宿主记忆的旧会话。
|
|
130
|
+
* 不含 key 明文(同 Provider 换 key 可续);其他 runtime 的 default 仍为 null。
|
|
129
131
|
*/
|
|
130
132
|
export function providerFingerprint(runtime, cfg) {
|
|
131
|
-
if (runtime === "codex"
|
|
132
|
-
|
|
133
|
+
if (runtime === "codex") {
|
|
134
|
+
const provider = cfg.provider === "deepseek"
|
|
135
|
+
? `deepseek|${cfg.providerBaseUrl ?? "default"}|${cfg.providerApiKey ? "key" : "nokey"}`
|
|
136
|
+
: "default";
|
|
137
|
+
return `${CODEX_EXECUTION_POLICY_VERSION}|${provider}`;
|
|
133
138
|
}
|
|
134
139
|
if (runtime !== "claude" || cfg.provider !== "custom")
|
|
135
140
|
return null;
|
|
@@ -10,6 +10,7 @@ import { assertCodexCollaborationVersion, assertCodexNativeWorkspaceVersion, ass
|
|
|
10
10
|
import { decideTurnRetry, itemMakesTurnUnsafe } from "./codex-turn-retry.js";
|
|
11
11
|
import { startFirstProgressWatchdog } from "./progress-watchdog.js";
|
|
12
12
|
import { prepareThreadDelivery, ThreadDeliveryColdStartRequired } from "../thread-input-delivery.js";
|
|
13
|
+
import { codexExecutionPolicyArgs } from "./codex-execution-policy.js";
|
|
13
14
|
const RunnerInputSchema = z.object({
|
|
14
15
|
systemPrompt: z.string().min(1),
|
|
15
16
|
wakePrompt: z.string().min(1),
|
|
@@ -57,6 +58,7 @@ export function codexAppServerArgs(input) {
|
|
|
57
58
|
// multi_agent 按开关;multi_agent_v2 无条件关,钉死已验证的 v1 契约(spec 2b)。
|
|
58
59
|
"-c", `features.multi_agent=${input.collaborationTools === true}`,
|
|
59
60
|
"-c", "features.multi_agent_v2=false",
|
|
61
|
+
...codexExecutionPolicyArgs(),
|
|
60
62
|
"app-server", "--listen", "stdio://",
|
|
61
63
|
];
|
|
62
64
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Bump when old native transcripts must not cross a changed execution boundary. */
|
|
2
|
+
export const CODEX_EXECUTION_POLICY_VERSION = "nowwork-codex-context-v1";
|
|
3
|
+
/** Process-local overrides: keep host auth/Skills without importing host automatic memory. */
|
|
4
|
+
export function codexExecutionPolicyArgs() {
|
|
5
|
+
return [
|
|
6
|
+
"features.memories=false",
|
|
7
|
+
'shell_environment_policy.inherit="all"',
|
|
8
|
+
"shell_environment_policy.ignore_default_excludes=true",
|
|
9
|
+
"shell_environment_policy.exclude=[]",
|
|
10
|
+
"shell_environment_policy.include_only=[]",
|
|
11
|
+
].flatMap(value => ["-c", value]);
|
|
12
|
+
}
|
package/dist/runtimes/codex.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
// cross-spawn:win32 上 npm CLI 是 .cmd shim,node 原生 spawn 不带 shell 无法执行(ENOENT/EINVAL)
|
|
5
5
|
import spawn from "cross-spawn";
|
|
6
|
+
import { codexExecutionPolicyArgs } from "./codex-execution-policy.js";
|
|
6
7
|
/** Minimum CLI whose real app-server contract supports NowCrew native project workspaces. */
|
|
7
8
|
export const CODEX_NATIVE_WORKSPACE_MIN_VERSION = "0.148.0";
|
|
8
9
|
/** Minimum CLI whose `multi_agent` collaboration tool contract was empirically verified (2026-08-28). */
|
|
@@ -26,6 +27,7 @@ export function buildCodexArgs(input) {
|
|
|
26
27
|
args.push("-c", `model_reasoning_effort=${effort}`);
|
|
27
28
|
// 纵深防御:exec 启动器没有 initialize 握手、无法断言版本,协作工具恒关(spec 2b)。
|
|
28
29
|
args.push("-c", "features.multi_agent=false", "-c", "features.multi_agent_v2=false");
|
|
30
|
+
args.push(...codexExecutionPolicyArgs());
|
|
29
31
|
if (input.projectRootMarkers) {
|
|
30
32
|
args.push("-c", `project_root_markers=${JSON.stringify(input.projectRootMarkers)}`);
|
|
31
33
|
}
|
|
@@ -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;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Read only: resolve the task's account with Windows, without guessing or stripping its domain. */
|
|
2
|
+
export const windowsTaskPrincipalScript = [
|
|
3
|
+
"$currentUserSid=[Security.Principal.WindowsIdentity]::GetCurrent().User.Value;",
|
|
4
|
+
"$principalSid=$null;",
|
|
5
|
+
"try{",
|
|
6
|
+
"$account=[string]$task.Principal.UserId;",
|
|
7
|
+
"if($account -match '^S-\\d-'){",
|
|
8
|
+
"$principalSid=([Security.Principal.SecurityIdentifier]::new($account)).Value;",
|
|
9
|
+
"}else{",
|
|
10
|
+
"$principalSid=([Security.Principal.NTAccount]::new($account)).Translate([Security.Principal.SecurityIdentifier]).Value;",
|
|
11
|
+
"}",
|
|
12
|
+
"}catch{$principalSid=$null};",
|
|
13
|
+
].join("");
|
|
14
|
+
export const windowsTaskPrincipalFields = [
|
|
15
|
+
"currentUserSid=$currentUserSid;",
|
|
16
|
+
"principalSid=$principalSid;",
|
|
17
|
+
"logonType=[int]$task.Principal.LogonType;",
|
|
18
|
+
"runLevel=[int]$task.Principal.RunLevel;",
|
|
19
|
+
].join("");
|
|
20
|
+
export function windowsTaskPrincipalMatches(principal) {
|
|
21
|
+
return typeof principal.currentUserSid === "string"
|
|
22
|
+
&& /^S-1-(?:\d+-)*\d+$/.test(principal.currentUserSid)
|
|
23
|
+
&& principal.principalSid === principal.currentUserSid
|
|
24
|
+
&& principal.logonType === 3 // Interactive
|
|
25
|
+
&& principal.runLevel === 0; // Limited
|
|
26
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nowcrew/daemon",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.98-beta.1",
|
|
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
|
+
}
|