@nowcrew/daemon 0.6.89 → 0.6.91-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 +64 -3
- package/dist/agent-ability/materializer.js +21 -10
- package/dist/agent-environment-contract.js +55 -0
- package/dist/agent-environment-control.js +24 -0
- package/dist/agent-environment-execution.js +98 -0
- package/dist/agent-environments.js +275 -0
- package/dist/automatic-daemon-installer.js +49 -1
- package/dist/computer-main.js +18 -0
- package/dist/computer-profile.js +17 -4
- package/dist/computer-setup-client.js +318 -0
- package/dist/computer-setup-orchestration.js +203 -0
- package/dist/computer-setup.js +244 -0
- package/dist/config.js +30 -12
- package/dist/console-formatter.js +3 -3
- package/dist/console.js +15 -2
- package/dist/daemon-update-eligibility.js +1 -1
- package/dist/daemon-updater.js +2 -12
- package/dist/execution-journal.js +9 -6
- package/dist/execution-protocol.js +1 -0
- package/dist/execution-runner.js +13 -8
- package/dist/i18n.js +19 -0
- package/dist/list-models.js +12 -12
- package/dist/local-execution-lease.js +22 -0
- package/dist/local-executor.js +57 -84
- package/dist/local-memory/candidate.js +43 -0
- package/dist/local-memory/index-budget.js +8 -0
- package/dist/local-memory/inventory-cli.js +61 -0
- package/dist/local-memory/inventory-preview.js +159 -0
- package/dist/local-memory/inventory-source.js +159 -0
- package/dist/local-memory/pending-match.js +29 -0
- package/dist/local-memory/pending-query.js +100 -0
- package/dist/local-memory/pending-schema.js +29 -0
- package/dist/local-memory/pending-source.js +126 -0
- package/dist/local-memory/pending.js +34 -84
- package/dist/local-memory/prompt.js +19 -12
- package/dist/local-memory/read-cli.js +97 -12
- package/dist/local-memory/rollout.js +82 -0
- package/dist/local-memory/runtime.js +23 -17
- package/dist/local-memory/work-log-execution.js +31 -0
- package/dist/local-memory/work-log.js +58 -0
- package/dist/local-memory/write.js +27 -58
- package/dist/local-runtime-environment.js +50 -0
- package/dist/machine-environment-cache.js +147 -0
- package/dist/machine-environment-contract.js +10 -0
- package/dist/machine-environment-control.js +27 -0
- package/dist/machine-environment-policy.js +75 -0
- package/dist/machine-environment-process.js +110 -0
- package/dist/machine-info.js +70 -25
- package/dist/machine-runtime-environment.js +189 -0
- package/dist/machine-shell-environment-capture.js +135 -0
- package/dist/machine-windows-environment-capture.js +215 -0
- package/dist/machine-windows-environment-script.js +158 -0
- package/dist/main.js +4 -69
- package/dist/marketplace-command.js +21 -0
- package/dist/marketplace-controller.js +80 -0
- package/dist/marketplace-protocol.js +38 -0
- package/dist/marketplace-remote.js +48 -0
- package/dist/marketplace-runner.js +235 -0
- package/dist/marketplace-serve.js +41 -0
- package/dist/marketplace-source.js +103 -0
- package/dist/message-file-preview.js +246 -0
- package/dist/pre-admission-rejection.js +64 -0
- package/dist/project-skills/execution-adapter.js +3 -2
- package/dist/project-skills/runtime-launch.js +14 -0
- package/dist/provider-env.js +26 -15
- package/dist/runner.js +17 -4
- package/dist/runtime-environment-merge.js +16 -0
- package/dist/runtime-health-handler.js +10 -0
- package/dist/runtime-health.js +3 -1
- package/dist/runtime-path.js +22 -12
- package/dist/runtime-probe.js +35 -6
- package/dist/runtimes/acp-probe-transport.js +69 -0
- package/dist/runtimes/claude-models.js +20 -9
- package/dist/runtimes/deepseek-harness-launch.js +10 -0
- package/dist/runtimes/deepseek-harness-profile.js +92 -0
- package/dist/runtimes/deepseek-harness-runner.js +226 -0
- package/dist/runtimes/deepseek-harness.js +76 -15
- package/dist/runtimes/harness-probe-retry.js +42 -0
- package/dist/runtimes/hermes-models.js +2 -1
- package/dist/runtimes/kimi-acp-runner.js +78 -13
- package/dist/runtimes/opencode.js +2 -2
- package/dist/serve.js +88 -178
- package/dist/storage-identity.js +0 -3
- package/dist/storage-serve-adapter.js +6 -72
- package/dist/supervised-runtime.js +17 -2
- package/dist/token.js +0 -1
- package/dist/workspace-control-handler.js +27 -0
- package/dist/workspace-fs.js +66 -15
- package/dist/workspace.js +1 -1
- package/package.json +13 -12
- package/dist/codex-home-launch.js +0 -7
- package/dist/codex-home-mode-controller.js +0 -68
- package/dist/daemon-migration-controller.js +0 -211
- package/dist/daemon-migration-wiring.js +0 -22
- package/dist/runtimes/codex-home-migration-cli.js +0 -26
- package/dist/runtimes/codex-home-migration.js +0 -112
- package/dist/runtimes/codex-home.js +0 -236
- package/dist/storage-control.js +0 -212
- package/dist/storage-copy.js +0 -628
- package/dist/storage-credential-guard.js +0 -21
- package/dist/storage-doctor.js +0 -228
- package/dist/storage-manifest.js +0 -1027
- package/dist/storage-migration-controller.js +0 -440
- package/dist/storage-migration-manifest.js +0 -105
- package/dist/storage-migration-policy.js +0 -100
- package/dist/storage-runtime-controller.js +0 -381
- package/dist/storage-runtime-readiness.js +0 -241
- package/dist/storage-safe-fs.js +0 -302
- package/dist/storage-status-delivery.js +0 -224
- package/dist/storage-status-replay.js +0 -140
- package/dist/storage-target-confirmation.js +0 -129
- package/dist/storage-worker-command.js +0 -37
- package/dist/storage-worker-executor.js +0 -447
- package/dist/storage-worker-handoff.js +0 -151
- package/dist/storage-worker-operation.js +0 -294
- package/dist/storage-worker-status.js +0 -259
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { win32 } from "node:path";
|
|
3
|
+
import { MACHINE_ENVIRONMENT_MAX_BYTES, MACHINE_ENVIRONMENT_MAX_ENTRIES, MachineEnvironmentCaptureError, } from "./machine-environment-contract.js";
|
|
4
|
+
import { WINDOWS_DAEMON_SID_SCRIPT, WINDOWS_ENVIRONMENT_CAPTURE_SCRIPT } from "./machine-windows-environment-script.js";
|
|
5
|
+
const CAPTURE_TIMEOUT_MS = 8_000;
|
|
6
|
+
const CLEANUP_TIMEOUT_MS = 2_000;
|
|
7
|
+
const validSid = (value) => typeof value === "string" && value.length <= 184 && /^S-1-(?:\d+-)+\d+$/.test(value);
|
|
8
|
+
const unavailable = () => new MachineEnvironmentCaptureError("windows_environment_unavailable");
|
|
9
|
+
const validKey = (key) => key.length > 0 && !/[=\0]/.test(key);
|
|
10
|
+
function systemExecutable(startupEnv, relative) {
|
|
11
|
+
const roots = Object.entries(startupEnv).filter(([key]) => key.toUpperCase() === "SYSTEMROOT");
|
|
12
|
+
const root = roots[0]?.[1];
|
|
13
|
+
if (roots.length !== 1 || !root || !/^[A-Za-z]:[\\/]/.test(root) || /[\0\r\n]/.test(root)
|
|
14
|
+
|| root.slice(2).includes(":") || root.split(/[\\/]/).some((part) => part === "." || part === ".."))
|
|
15
|
+
throw unavailable();
|
|
16
|
+
return win32.join(root, "System32", relative);
|
|
17
|
+
}
|
|
18
|
+
/** 仅终止本调用仍持有的活 helper PID;根进程已退出时不按旧 PID 杀进程。 */
|
|
19
|
+
export const runWindowsEnvironmentProcess = (request) => new Promise((resolve, reject) => {
|
|
20
|
+
if (request.signal?.aborted || request.deadline <= Date.now()) {
|
|
21
|
+
reject(new MachineEnvironmentCaptureError("shell_capture_timeout"));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
let child;
|
|
25
|
+
try {
|
|
26
|
+
child = spawn(request.file, [...request.args], {
|
|
27
|
+
env: { ...request.env }, stdio: ["ignore", "pipe", "pipe"], shell: false, windowsHide: true,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
reject(unavailable());
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
let settled = false;
|
|
35
|
+
let failure;
|
|
36
|
+
let cleanupTimer;
|
|
37
|
+
let killer;
|
|
38
|
+
let killerClosed = true;
|
|
39
|
+
let helperClosed = false;
|
|
40
|
+
const chunks = [];
|
|
41
|
+
let bytes = 0;
|
|
42
|
+
function finish(error) {
|
|
43
|
+
if (settled)
|
|
44
|
+
return;
|
|
45
|
+
settled = true;
|
|
46
|
+
clearTimeout(captureTimer);
|
|
47
|
+
if (cleanupTimer !== undefined)
|
|
48
|
+
clearTimeout(cleanupTimer);
|
|
49
|
+
request.signal?.removeEventListener("abort", abort);
|
|
50
|
+
if (error) {
|
|
51
|
+
chunks.length = 0;
|
|
52
|
+
reject(new MachineEnvironmentCaptureError(error));
|
|
53
|
+
}
|
|
54
|
+
else
|
|
55
|
+
resolve(Buffer.concat(chunks));
|
|
56
|
+
}
|
|
57
|
+
function finishCleanup() {
|
|
58
|
+
if (helperClosed && killerClosed)
|
|
59
|
+
finish(failure);
|
|
60
|
+
}
|
|
61
|
+
function stop(code) {
|
|
62
|
+
if (failure || settled)
|
|
63
|
+
return;
|
|
64
|
+
failure = code;
|
|
65
|
+
chunks.length = 0;
|
|
66
|
+
clearTimeout(captureTimer);
|
|
67
|
+
cleanupTimer = setTimeout(() => {
|
|
68
|
+
// Node 的 kill 使用本 ChildProcess 的句柄;这里不使用已退出 helper 的陈旧 PID。
|
|
69
|
+
if (killer && killer.exitCode === null && killer.signalCode === null)
|
|
70
|
+
killer.kill();
|
|
71
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
72
|
+
child.kill();
|
|
73
|
+
child.stdout?.destroy();
|
|
74
|
+
child.stderr?.destroy();
|
|
75
|
+
child.unref();
|
|
76
|
+
finish("shell_cleanup_failed");
|
|
77
|
+
}, CLEANUP_TIMEOUT_MS);
|
|
78
|
+
if (child.pid && child.exitCode === null && child.signalCode === null) {
|
|
79
|
+
try {
|
|
80
|
+
killerClosed = false;
|
|
81
|
+
killer = spawn(systemExecutable(request.env, "taskkill.exe"), ["/PID", String(child.pid), "/T", "/F"], {
|
|
82
|
+
env: { ...request.env }, stdio: "ignore", shell: false, windowsHide: true,
|
|
83
|
+
});
|
|
84
|
+
killer.once("error", () => { killerClosed = true; failure = "shell_cleanup_failed"; child.kill(); finishCleanup(); });
|
|
85
|
+
killer.once("close", (exitCode) => {
|
|
86
|
+
killerClosed = true;
|
|
87
|
+
if (exitCode !== 0)
|
|
88
|
+
failure = "shell_cleanup_failed";
|
|
89
|
+
finishCleanup();
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
killerClosed = true;
|
|
94
|
+
failure = "shell_cleanup_failed";
|
|
95
|
+
child.kill();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
finishCleanup();
|
|
99
|
+
}
|
|
100
|
+
const abort = () => stop("shell_capture_timeout");
|
|
101
|
+
const captureTimer = setTimeout(abort, Math.max(1, request.deadline - Date.now()));
|
|
102
|
+
request.signal?.addEventListener("abort", abort, { once: true });
|
|
103
|
+
child.stdout?.on("data", (data) => {
|
|
104
|
+
if (settled || failure)
|
|
105
|
+
return;
|
|
106
|
+
bytes += data.length;
|
|
107
|
+
if (bytes > request.maxBytes)
|
|
108
|
+
stop("shell_capture_too_large");
|
|
109
|
+
else
|
|
110
|
+
chunks.push(Buffer.from(data));
|
|
111
|
+
});
|
|
112
|
+
child.stderr?.on("data", (data) => {
|
|
113
|
+
if (settled || failure)
|
|
114
|
+
return;
|
|
115
|
+
bytes += data.length;
|
|
116
|
+
if (bytes > request.maxBytes)
|
|
117
|
+
stop("shell_capture_too_large");
|
|
118
|
+
});
|
|
119
|
+
child.once("error", () => {
|
|
120
|
+
if (failure)
|
|
121
|
+
return;
|
|
122
|
+
finish("windows_environment_unavailable");
|
|
123
|
+
});
|
|
124
|
+
child.once("close", (code) => {
|
|
125
|
+
helperClosed = true;
|
|
126
|
+
if (failure)
|
|
127
|
+
finishCleanup();
|
|
128
|
+
else if (code === 0)
|
|
129
|
+
finish();
|
|
130
|
+
else
|
|
131
|
+
finish(code === 12 ? "shell_capture_too_large" : "windows_environment_unavailable");
|
|
132
|
+
});
|
|
133
|
+
if (request.signal?.aborted)
|
|
134
|
+
abort();
|
|
135
|
+
});
|
|
136
|
+
async function runScript(options, script) {
|
|
137
|
+
const deadline = Math.min(options.deadline ?? Date.now() + CAPTURE_TIMEOUT_MS, Date.now() + CAPTURE_TIMEOUT_MS);
|
|
138
|
+
if (options.signal?.aborted || deadline <= Date.now())
|
|
139
|
+
throw new MachineEnvironmentCaptureError("shell_capture_timeout");
|
|
140
|
+
const maxBytes = Math.min(options.maxBytes ?? MACHINE_ENVIRONMENT_MAX_BYTES, MACHINE_ENVIRONMENT_MAX_BYTES);
|
|
141
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1)
|
|
142
|
+
throw unavailable();
|
|
143
|
+
const file = systemExecutable(options.startupEnv, "WindowsPowerShell\\v1.0\\powershell.exe");
|
|
144
|
+
let output;
|
|
145
|
+
try {
|
|
146
|
+
output = await (options.runner ?? runWindowsEnvironmentProcess)({
|
|
147
|
+
file, args: ["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")],
|
|
148
|
+
env: options.startupEnv, deadline, maxBytes, ...(options.signal ? { signal: options.signal } : {}),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (error instanceof MachineEnvironmentCaptureError)
|
|
153
|
+
throw new MachineEnvironmentCaptureError(error.code);
|
|
154
|
+
throw unavailable();
|
|
155
|
+
}
|
|
156
|
+
if (output.length > maxBytes)
|
|
157
|
+
throw new MachineEnvironmentCaptureError("shell_capture_too_large");
|
|
158
|
+
try {
|
|
159
|
+
const parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(output));
|
|
160
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
161
|
+
throw unavailable();
|
|
162
|
+
return parsed;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
throw unavailable();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
export async function captureWindowsEnvironment(options) {
|
|
169
|
+
if (!validSid(options.expectedSid))
|
|
170
|
+
throw unavailable();
|
|
171
|
+
const payload = await runScript(options, WINDOWS_ENVIRONMENT_CAPTURE_SCRIPT);
|
|
172
|
+
if (!validSid(payload.accountSid))
|
|
173
|
+
throw unavailable();
|
|
174
|
+
if (payload.accountSid !== options.expectedSid)
|
|
175
|
+
throw new MachineEnvironmentCaptureError("windows_environment_identity_mismatch");
|
|
176
|
+
if (!payload.env || typeof payload.env !== "object" || Array.isArray(payload.env) || !Array.isArray(payload.sourceOwnedKeys))
|
|
177
|
+
throw unavailable();
|
|
178
|
+
const entries = Object.entries(payload.env);
|
|
179
|
+
if (entries.length > MACHINE_ENVIRONMENT_MAX_ENTRIES || payload.sourceOwnedKeys.length > MACHINE_ENVIRONMENT_MAX_ENTRIES) {
|
|
180
|
+
throw new MachineEnvironmentCaptureError("shell_capture_too_large");
|
|
181
|
+
}
|
|
182
|
+
const env = Object.create(null);
|
|
183
|
+
const seen = new Set();
|
|
184
|
+
for (const [key, value] of entries) {
|
|
185
|
+
const folded = key.toUpperCase();
|
|
186
|
+
if (!validKey(key) || seen.has(folded) || typeof value !== "string" || value.includes("\0"))
|
|
187
|
+
throw unavailable();
|
|
188
|
+
seen.add(folded);
|
|
189
|
+
env[key] = value;
|
|
190
|
+
}
|
|
191
|
+
seen.clear();
|
|
192
|
+
const sourceOwnedKeys = [];
|
|
193
|
+
for (const key of payload.sourceOwnedKeys) {
|
|
194
|
+
if (typeof key !== "string" || !validKey(key) || seen.has(key.toUpperCase()))
|
|
195
|
+
throw unavailable();
|
|
196
|
+
seen.add(key.toUpperCase());
|
|
197
|
+
sourceOwnedKeys.push(key);
|
|
198
|
+
}
|
|
199
|
+
return Object.freeze({ env: Object.freeze(env), sourceOwnedKeys: Object.freeze(sourceOwnedKeys), accountSid: payload.accountSid });
|
|
200
|
+
}
|
|
201
|
+
export async function resolveWindowsDaemonSid(options) {
|
|
202
|
+
const payload = await runScript(options, WINDOWS_DAEMON_SID_SCRIPT);
|
|
203
|
+
if (!validSid(payload.accountSid))
|
|
204
|
+
throw unavailable();
|
|
205
|
+
return payload.accountSid;
|
|
206
|
+
}
|
|
207
|
+
export const captureWindowsMachineEnvironment = async (request) => {
|
|
208
|
+
const options = { startupEnv: request.startupEnv, signal: request.signal, deadline: request.deadline };
|
|
209
|
+
const expectedSid = request.account.sid ?? await resolveWindowsDaemonSid(options);
|
|
210
|
+
const result = await captureWindowsEnvironment({ ...options, expectedSid });
|
|
211
|
+
return {
|
|
212
|
+
...result,
|
|
213
|
+
sourceLocations: ["HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", "HKCU\\Environment"],
|
|
214
|
+
};
|
|
215
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/** 固定可信脚本;无 profile、动态命令、注册表写入或桌面用户切换。 */
|
|
2
|
+
export const WINDOWS_ENVIRONMENT_CAPTURE_SCRIPT = String.raw `
|
|
3
|
+
$ErrorActionPreference = 'Stop'
|
|
4
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
5
|
+
[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)
|
|
6
|
+
try {
|
|
7
|
+
Add-Type -TypeDefinition @'
|
|
8
|
+
using System;
|
|
9
|
+
using System.Collections.Generic;
|
|
10
|
+
using System.IO;
|
|
11
|
+
using System.Runtime.InteropServices;
|
|
12
|
+
using System.Security.Cryptography;
|
|
13
|
+
using System.Security.Principal;
|
|
14
|
+
using System.Text;
|
|
15
|
+
using Microsoft.Win32;
|
|
16
|
+
|
|
17
|
+
public sealed class NowWorkEnvironmentSnapshot {
|
|
18
|
+
public Dictionary<string, string> env;
|
|
19
|
+
public string[] sourceOwnedKeys;
|
|
20
|
+
public string accountSid;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public static class NowWorkNativeEnvironment {
|
|
24
|
+
private const int MaxBytes = 1048576;
|
|
25
|
+
private const int MaxKeys = 4096;
|
|
26
|
+
[DllImport("userenv.dll", SetLastError = true)]
|
|
27
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
28
|
+
private static extern bool CreateEnvironmentBlock(out IntPtr block, IntPtr token, [MarshalAs(UnmanagedType.Bool)] bool inherit);
|
|
29
|
+
[DllImport("userenv.dll", SetLastError = true)]
|
|
30
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
31
|
+
private static extern bool DestroyEnvironmentBlock(IntPtr block);
|
|
32
|
+
|
|
33
|
+
private static void CheckLength(long length) {
|
|
34
|
+
if (length > MaxBytes) throw new InvalidOperationException("capture_too_large");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private static void WriteRaw(BinaryWriter writer, object value) {
|
|
38
|
+
if (value == null) { writer.Write((byte)0); return; }
|
|
39
|
+
if (value is string) { writer.Write((byte)1); writer.Write((string)value); }
|
|
40
|
+
else if (value is int) { writer.Write((byte)2); writer.Write((int)value); }
|
|
41
|
+
else if (value is long) { writer.Write((byte)3); writer.Write((long)value); }
|
|
42
|
+
else if (value is byte[]) {
|
|
43
|
+
byte[] bytes = (byte[])value;
|
|
44
|
+
CheckLength(bytes.Length);
|
|
45
|
+
writer.Write((byte)4); writer.Write(bytes.Length); writer.Write(bytes);
|
|
46
|
+
} else if (value is string[]) {
|
|
47
|
+
string[] values = (string[])value;
|
|
48
|
+
writer.Write((byte)5); writer.Write(values.Length);
|
|
49
|
+
foreach (string item in values) { writer.Write(item); CheckLength(writer.BaseStream.Length); }
|
|
50
|
+
} else { throw new InvalidOperationException("capture_unavailable"); }
|
|
51
|
+
CheckLength(writer.BaseStream.Length);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private static void ReadRegistry(RegistryKey root, string path, BinaryWriter writer, HashSet<string> owned) {
|
|
55
|
+
using (RegistryKey key = root.OpenSubKey(path, false)) {
|
|
56
|
+
writer.Write(path);
|
|
57
|
+
writer.Write(key != null);
|
|
58
|
+
if (key == null) return;
|
|
59
|
+
string[] names = key.GetValueNames();
|
|
60
|
+
if (names.Length > MaxKeys) throw new InvalidOperationException("capture_too_large");
|
|
61
|
+
Array.Sort(names, StringComparer.Ordinal);
|
|
62
|
+
writer.Write(names.Length);
|
|
63
|
+
foreach (string name in names) {
|
|
64
|
+
writer.Write(name);
|
|
65
|
+
writer.Write((int)key.GetValueKind(name));
|
|
66
|
+
WriteRaw(writer, key.GetValue(name, null, RegistryValueOptions.DoNotExpandEnvironmentNames));
|
|
67
|
+
if (name.Length > 0 && name.IndexOf('=') < 0 && name.IndexOf('\0') < 0) owned.Add(name);
|
|
68
|
+
if (owned.Count > MaxKeys) throw new InvalidOperationException("capture_too_large");
|
|
69
|
+
CheckLength(writer.BaseStream.Length);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 只在本地比较 key/type/raw-value 指纹,不输出原始值或指纹。
|
|
75
|
+
private static string Fingerprint(out string[] sourceOwnedKeys) {
|
|
76
|
+
HashSet<string> owned = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
77
|
+
using (MemoryStream memory = new MemoryStream()) {
|
|
78
|
+
using (BinaryWriter writer = new BinaryWriter(memory, Encoding.UTF8, true)) {
|
|
79
|
+
ReadRegistry(Registry.LocalMachine, @"SYSTEM\CurrentControlSet\Control\Session Manager\Environment", writer, owned);
|
|
80
|
+
ReadRegistry(Registry.CurrentUser, "Environment", writer, owned);
|
|
81
|
+
}
|
|
82
|
+
sourceOwnedKeys = new string[owned.Count];
|
|
83
|
+
owned.CopyTo(sourceOwnedKeys);
|
|
84
|
+
Array.Sort(sourceOwnedKeys, StringComparer.OrdinalIgnoreCase);
|
|
85
|
+
using (SHA256 hash = SHA256.Create()) { return Convert.ToBase64String(hash.ComputeHash(memory.ToArray())); }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private static Dictionary<string, string> DecodeBlock(IntPtr block) {
|
|
90
|
+
Dictionary<string, string> values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
91
|
+
StringBuilder entry = new StringBuilder();
|
|
92
|
+
for (int offset = 0; offset < MaxBytes; offset += 2) {
|
|
93
|
+
char character = (char)Marshal.ReadInt16(block, offset);
|
|
94
|
+
if (character != '\0') { entry.Append(character); continue; }
|
|
95
|
+
if (entry.Length == 0) return values;
|
|
96
|
+
string text = entry.ToString();
|
|
97
|
+
entry.Length = 0;
|
|
98
|
+
int equals = text.IndexOf('=');
|
|
99
|
+
if (equals == 0) continue; // =C: 等驱动器工作目录条目不是普通环境变量。
|
|
100
|
+
if (equals < 1) throw new InvalidOperationException("capture_unavailable");
|
|
101
|
+
string name = text.Substring(0, equals);
|
|
102
|
+
if (values.ContainsKey(name)) throw new InvalidOperationException("capture_unavailable");
|
|
103
|
+
values.Add(name, text.Substring(equals + 1));
|
|
104
|
+
if (values.Count > MaxKeys) throw new InvalidOperationException("capture_too_large");
|
|
105
|
+
}
|
|
106
|
+
throw new InvalidOperationException("capture_too_large");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public static NowWorkEnvironmentSnapshot Capture() {
|
|
110
|
+
using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) {
|
|
111
|
+
if (identity.User == null) throw new InvalidOperationException("capture_unavailable");
|
|
112
|
+
for (int attempt = 0; attempt < 4; attempt++) {
|
|
113
|
+
string[] owned;
|
|
114
|
+
string before = Fingerprint(out owned);
|
|
115
|
+
IntPtr block = IntPtr.Zero;
|
|
116
|
+
Dictionary<string, string> values;
|
|
117
|
+
try {
|
|
118
|
+
// bInherit=false:让 userenv 原生重建最新系统 + 当前账号环境,含 Path / REG_EXPAND_SZ。
|
|
119
|
+
if (!CreateEnvironmentBlock(out block, identity.Token, false)) throw new InvalidOperationException("capture_unavailable");
|
|
120
|
+
values = DecodeBlock(block);
|
|
121
|
+
} finally {
|
|
122
|
+
if (block != IntPtr.Zero && !DestroyEnvironmentBlock(block)) throw new InvalidOperationException("capture_unavailable");
|
|
123
|
+
}
|
|
124
|
+
string[] afterOwned;
|
|
125
|
+
string after = Fingerprint(out afterOwned);
|
|
126
|
+
if (before.Equals(after, StringComparison.Ordinal)) {
|
|
127
|
+
return new NowWorkEnvironmentSnapshot { env = values, sourceOwnedKeys = owned, accountSid = identity.User.Value };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
throw new InvalidOperationException("capture_unavailable");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
'@ -ErrorAction Stop | Out-Null
|
|
135
|
+
$snapshot = [NowWorkNativeEnvironment]::Capture()
|
|
136
|
+
$json = ConvertTo-Json -InputObject $snapshot -Depth 5 -Compress
|
|
137
|
+
if ([Text.Encoding]::UTF8.GetByteCount($json) -gt 1048576) { exit 12 }
|
|
138
|
+
[Console]::Write($json)
|
|
139
|
+
exit 0
|
|
140
|
+
} catch {
|
|
141
|
+
# 不输出异常:它可能携带注册表值、编译器命令或用户路径。
|
|
142
|
+
if ($_.Exception.ToString().Contains('capture_too_large')) { exit 12 }
|
|
143
|
+
exit 11
|
|
144
|
+
}
|
|
145
|
+
`;
|
|
146
|
+
export const WINDOWS_DAEMON_SID_SCRIPT = String.raw `
|
|
147
|
+
$ErrorActionPreference = 'Stop'
|
|
148
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
149
|
+
[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)
|
|
150
|
+
$identity = $null
|
|
151
|
+
try {
|
|
152
|
+
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
|
|
153
|
+
$json = ConvertTo-Json -InputObject @{ accountSid = $identity.User.Value } -Compress
|
|
154
|
+
[Console]::Write($json)
|
|
155
|
+
} catch { exit 11 } finally {
|
|
156
|
+
if ($null -ne $identity) { $identity.Dispose() }
|
|
157
|
+
}
|
|
158
|
+
`;
|
package/dist/main.js
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { parseArgs } from "node:util";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
|
-
import { join } from "node:path";
|
|
11
10
|
import { loadConfig, ConfigError } from "./config.js";
|
|
12
11
|
import { detectDaemonLang, formatDaemonText, translateDaemon } from "./i18n.js";
|
|
13
12
|
import { cliVersion, daemonVersion } from "./machine-info.js";
|
|
@@ -25,19 +24,9 @@ import { assertManagedServiceStartup } from "./managed-service-startup.js";
|
|
|
25
24
|
import { daemonGlobalInstallation } from "./daemon-installation.js";
|
|
26
25
|
import { acquireDaemonInstallationLease, runWithDaemonInstallationLease, } from "./daemon-installation-lease.js";
|
|
27
26
|
import { createStorageRuntimeOwnershipBinding, loadStorageRuntime } from "./storage-runtime.js";
|
|
28
|
-
import { readStorageMigrationReceipt } from "./storage-migration-controller.js";
|
|
29
|
-
import { storageMigrationStatusReplay } from "./storage-status-replay.js";
|
|
30
|
-
import { runStorageWorkerCommand } from "./storage-worker-command.js";
|
|
31
|
-
import { probeStorageMigrationRuntime } from "./storage-runtime-readiness.js";
|
|
32
|
-
import { createStorageTargetConfirmationHandler } from "./storage-target-confirmation.js";
|
|
33
27
|
import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
34
28
|
import { storageServiceEnvironment } from "./computer-service.js";
|
|
35
29
|
async function main() {
|
|
36
|
-
const workerResult = await runStorageWorkerCommand(process.argv.slice(2));
|
|
37
|
-
if (workerResult !== null) {
|
|
38
|
-
process.exitCode = workerResult;
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
30
|
const computerResult = await runComputerCommand(process.argv.slice(2));
|
|
42
31
|
if (computerResult !== null) {
|
|
43
32
|
process.exitCode = computerResult;
|
|
@@ -74,7 +63,6 @@ async function main() {
|
|
|
74
63
|
// 不把 machine token 放进 argv / plist / systemd unit / scheduled task。
|
|
75
64
|
if (values["daemon-home"])
|
|
76
65
|
process.env.CREW_DAEMON_HOME = values["daemon-home"];
|
|
77
|
-
let activeDaemonHome;
|
|
78
66
|
const localHome = resolveDaemonLocalStateRoot({
|
|
79
67
|
userHome: homedir(),
|
|
80
68
|
platform: process.platform,
|
|
@@ -85,7 +73,6 @@ async function main() {
|
|
|
85
73
|
});
|
|
86
74
|
if (values.profile) {
|
|
87
75
|
const home = daemonHome(process.env, values.profile);
|
|
88
|
-
activeDaemonHome = home;
|
|
89
76
|
const profile = await loadProfile(values.profile, home);
|
|
90
77
|
if (cmd === "serve") {
|
|
91
78
|
await assertProfileAgentsRootUnique(profile, home, homedir());
|
|
@@ -148,68 +135,16 @@ async function main() {
|
|
|
148
135
|
const installationLease = installation === null
|
|
149
136
|
? null
|
|
150
137
|
: await acquireDaemonInstallationLease(installation.npmPrefix, { localHome });
|
|
151
|
-
const
|
|
152
|
-
? { ready: false, reason: "not_legacy_source" }
|
|
153
|
-
: await probeStorageMigrationRuntime({
|
|
154
|
-
profile: values.profile,
|
|
155
|
-
serverUrl: config.serverUrl,
|
|
156
|
-
currentDaemonHome: activeDaemonHome,
|
|
157
|
-
currentAgentsRoot: config.agentsRoot,
|
|
158
|
-
userHome: homedir(),
|
|
159
|
-
platform: process.platform,
|
|
160
|
-
...(process.getuid === undefined ? {} : { uid: process.getuid() }),
|
|
161
|
-
environment: process.env,
|
|
162
|
-
});
|
|
163
|
-
const targetStorageBinding = storageRuntime === null
|
|
138
|
+
const storageBinding = storageRuntime === null
|
|
164
139
|
? null
|
|
165
140
|
: createStorageRuntimeOwnershipBinding(storageRuntime);
|
|
166
|
-
const targetConfirmation = storageRuntime?.identity.storageMigrationId === undefined
|
|
167
|
-
? null
|
|
168
|
-
: createStorageTargetConfirmationHandler({
|
|
169
|
-
backupsRoot: storageRuntime.local.backupsRoot,
|
|
170
|
-
identity: storageRuntime.identity,
|
|
171
|
-
profile: storageRuntime.installation.profile,
|
|
172
|
-
completionTimeoutMs: 180_000,
|
|
173
|
-
});
|
|
174
|
-
const storageMigration = storageRuntime !== null
|
|
175
|
-
? {
|
|
176
|
-
ready: false,
|
|
177
|
-
statusOutboxPath: join(storageRuntime.local.stateRoot, "storage-status-outbox.json"),
|
|
178
|
-
identity: targetStorageBinding.identity,
|
|
179
|
-
bindServerIdentity: targetStorageBinding.bind,
|
|
180
|
-
...(targetConfirmation === null ? {} : { confirm: targetConfirmation.handle }),
|
|
181
|
-
...(storageRuntime.identity.storageMigrationId === undefined ? {} : {
|
|
182
|
-
replayStatus: async (send) => {
|
|
183
|
-
const migrationId = storageRuntime.identity.storageMigrationId;
|
|
184
|
-
const receipt = await readStorageMigrationReceipt(storageRuntime.local.backupsRoot, migrationId);
|
|
185
|
-
if (receipt === null)
|
|
186
|
-
throw new Error(`storage migration receipt not found: ${migrationId}`);
|
|
187
|
-
for (const frame of storageMigrationStatusReplay(receipt, {
|
|
188
|
-
migrationId,
|
|
189
|
-
environment: storageRuntime.identity.storageEnvironment,
|
|
190
|
-
workspaceId: storageRuntime.identity.storageWorkspaceId,
|
|
191
|
-
profile: storageRuntime.installation.profile,
|
|
192
|
-
}))
|
|
193
|
-
send(frame);
|
|
194
|
-
},
|
|
195
|
-
}),
|
|
196
|
-
}
|
|
197
|
-
: migrationRuntime.ready
|
|
198
|
-
? {
|
|
199
|
-
ready: true,
|
|
200
|
-
statusOutboxPath: migrationRuntime.runtime.statusOutboxPath,
|
|
201
|
-
identity: migrationRuntime.runtime.identity,
|
|
202
|
-
controller: migrationRuntime.runtime.controller,
|
|
203
|
-
attach: migrationRuntime.runtime.attach,
|
|
204
|
-
bindServerIdentity: migrationRuntime.runtime.bindServerIdentity,
|
|
205
|
-
replayStatus: migrationRuntime.runtime.replayStatus,
|
|
206
|
-
}
|
|
207
|
-
: undefined;
|
|
208
141
|
await runWithDaemonInstallationLease(installationLease, async () => {
|
|
209
142
|
const service = serve(runtimeConfig, {
|
|
210
143
|
...(values.profile === undefined ? {} : { profileName: values.profile }),
|
|
211
144
|
localHome,
|
|
212
|
-
...(
|
|
145
|
+
...(storageBinding === null ? {} : {
|
|
146
|
+
storageRuntime: { identity: storageBinding.identity, bindServerIdentity: storageBinding.bind },
|
|
147
|
+
}),
|
|
213
148
|
});
|
|
214
149
|
await runServeLifecycle(service);
|
|
215
150
|
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
export class MarketplaceFailure extends Error {
|
|
4
|
+
code;
|
|
5
|
+
constructor(code) {
|
|
6
|
+
super(code);
|
|
7
|
+
this.code = code;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
const exec = promisify(execFile);
|
|
11
|
+
export const runMarketplaceCommand = async (bin, args, options) => {
|
|
12
|
+
try {
|
|
13
|
+
const result = await exec(bin, [...args], { ...options, timeout: 120_000, maxBuffer: 4 * 1024 * 1024,
|
|
14
|
+
killSignal: "SIGKILL", encoding: "utf8" });
|
|
15
|
+
return result.stdout;
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
const value = error;
|
|
19
|
+
throw new MarketplaceFailure(value.code === "ENOENT" ? "cli_missing" : value.killed ? "command_timeout" : "command_failed");
|
|
20
|
+
}
|
|
21
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { MarketplaceRequestSchema, marketplaceActive } from "./marketplace-protocol.js";
|
|
2
|
+
import { resolveMarketplaceScopes, runMarketplaceScope } from "./marketplace-runner.js";
|
|
3
|
+
export function createMarketplaceController(deps) {
|
|
4
|
+
const operations = new Map();
|
|
5
|
+
let stopped = false;
|
|
6
|
+
let current = null;
|
|
7
|
+
const now = deps.now ?? Date.now;
|
|
8
|
+
const wait = deps.wait ?? (() => new Promise(resolve => { setTimeout(resolve, 1000); }));
|
|
9
|
+
const execute = async (request) => {
|
|
10
|
+
const id = request.operationId;
|
|
11
|
+
const deadline = now() + 10 * 60_000;
|
|
12
|
+
let lease = null;
|
|
13
|
+
try {
|
|
14
|
+
const scopes = await (deps.resolve ?? resolveMarketplaceScopes)(request.targets, deps.agentsRoot);
|
|
15
|
+
// 查询不使用任务执行槽;只有写入运行时共用市场/插件目录时才等待独占保护。
|
|
16
|
+
if (request.mode === "update") {
|
|
17
|
+
while (!stopped && now() < deadline && lease === null) {
|
|
18
|
+
lease = await deps.acquire();
|
|
19
|
+
if (!lease)
|
|
20
|
+
await wait();
|
|
21
|
+
}
|
|
22
|
+
if (!lease || stopped) {
|
|
23
|
+
operations.set(id, { operationId: id, status: "failed", results: [], errorCode: "runtime_busy" });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (stopped || now() >= deadline)
|
|
28
|
+
throw new Error("operation_deadline");
|
|
29
|
+
operations.set(id, { operationId: id, status: "running", results: [], errorCode: null });
|
|
30
|
+
const results = [];
|
|
31
|
+
for (const scope of scopes) {
|
|
32
|
+
if (stopped || now() >= deadline)
|
|
33
|
+
throw new Error("operation_deadline");
|
|
34
|
+
results.push(await (deps.run ?? runMarketplaceScope)(scope, request.mode));
|
|
35
|
+
operations.set(id, { operationId: id, status: "running", results: [...results], errorCode: null });
|
|
36
|
+
if (now() >= deadline)
|
|
37
|
+
throw new Error("operation_deadline");
|
|
38
|
+
}
|
|
39
|
+
const successes = results.filter(x => !["failed", "missing_cli"].includes(x.status)).length;
|
|
40
|
+
operations.set(id, { operationId: id, results, status: successes === results.length ? "completed" : successes ? "partial" : "failed", errorCode: null });
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
const previous = operations.get(id);
|
|
44
|
+
operations.set(id, { ...previous, status: previous.results.some(x => !x.errorCode) ? "partial" : "failed", errorCode: "execution_failed" });
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
try {
|
|
48
|
+
await lease?.release();
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
const previous = operations.get(id);
|
|
52
|
+
operations.set(id, { ...previous, status: "partial", errorCode: "execution_failed" });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
handle(raw) {
|
|
58
|
+
const parsed = MarketplaceRequestSchema.safeParse(raw);
|
|
59
|
+
if (!parsed.success || stopped)
|
|
60
|
+
return { ok: false, error: "invalid_request" };
|
|
61
|
+
const request = parsed.data;
|
|
62
|
+
const existing = operations.get(request.operationId);
|
|
63
|
+
if (existing)
|
|
64
|
+
return { ok: true, data: existing };
|
|
65
|
+
if (request.action === "status")
|
|
66
|
+
return { ok: true, data: { operationId: request.operationId, status: "unknown", results: [], errorCode: "operation_unknown" } };
|
|
67
|
+
if (process.platform !== "darwin" && process.platform !== "linux")
|
|
68
|
+
return { ok: false, error: "unsupported_platform" };
|
|
69
|
+
if (current !== null || [...operations.values()].some(x => marketplaceActive(x.status)))
|
|
70
|
+
return { ok: false, error: "runtime_busy" };
|
|
71
|
+
if (operations.size >= 100)
|
|
72
|
+
operations.delete(operations.keys().next().value);
|
|
73
|
+
const accepted = { operationId: request.operationId, status: "queued", results: [], errorCode: null };
|
|
74
|
+
operations.set(request.operationId, accepted);
|
|
75
|
+
current = execute(request).finally(() => { current = null; });
|
|
76
|
+
return { ok: true, data: accepted };
|
|
77
|
+
},
|
|
78
|
+
async stop() { stopped = true; await current; },
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// 与另一个进程中的 marketplace-protocol.ts 保持一致;跨进程契约测试校验摘要。
|
|
3
|
+
export const MARKETPLACE_CAPABILITY = "marketplace_update_v1";
|
|
4
|
+
export const MARKETPLACE = "nowwork-pm";
|
|
5
|
+
export const PLUGIN = "AIinterview-plugin";
|
|
6
|
+
export const MarketplaceModeSchema = z.enum(["inspect", "refresh", "update"]);
|
|
7
|
+
export const MarketplaceStatusSchema = z.enum(["queued", "running", "completed", "partial", "failed", "unknown"]);
|
|
8
|
+
export const MarketplaceTargetSchema = z.object({
|
|
9
|
+
handle: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/).optional(),
|
|
10
|
+
runtime: z.enum(["claude", "codex"]),
|
|
11
|
+
provider: z.enum(["default", "custom", "deepseek"]).default("default"),
|
|
12
|
+
hasProviderKey: z.boolean().default(false),
|
|
13
|
+
paths: z.object({ HOME: z.string().min(1).max(4096).optional(), CODEX_HOME: z.string().min(1).max(4096).optional(),
|
|
14
|
+
CLAUDE_CONFIG_DIR: z.string().min(1).max(4096).optional() }).strict().default({}),
|
|
15
|
+
}).strict();
|
|
16
|
+
export const MarketplaceRequestSchema = z.object({
|
|
17
|
+
type: z.literal("marketplace:operation"), action: z.enum(["start", "status"]),
|
|
18
|
+
operationId: z.string().uuid(), mode: MarketplaceModeSchema.optional(),
|
|
19
|
+
targets: z.array(MarketplaceTargetSchema).min(1).max(202).optional(),
|
|
20
|
+
}).strict().refine(x => x.action !== "start" || (x.mode !== undefined && x.targets !== undefined));
|
|
21
|
+
const Version = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._+-]{0,79}$/).nullable();
|
|
22
|
+
const Revision = z.string().regex(/^[a-f0-9]{40}$/).nullable();
|
|
23
|
+
export const MarketplaceResultSchema = z.object({
|
|
24
|
+
runtime: z.enum(["claude", "codex"]), scopeId: z.string().regex(/^[a-f0-9]{16}$/),
|
|
25
|
+
agents: z.array(z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/)).max(200),
|
|
26
|
+
shared: z.boolean(), status: z.enum(["checked", "refreshed", "updated", "unchanged", "not_installed", "missing_cli", "failed"]),
|
|
27
|
+
beforeVersion: Version, afterVersion: Version, targetVersion: Version,
|
|
28
|
+
marketRevision: Revision, installedRevision: Revision, enabled: z.boolean().nullable(),
|
|
29
|
+
activation: z.enum(["not_changed", "reload_required", "unverified"]),
|
|
30
|
+
errorCode: z.enum(["scope_unavailable", "market_not_registered", "command_failed", "command_timeout", "invalid_inventory",
|
|
31
|
+
"verification_failed", "unsupported_installation", "cli_missing", "configuration_changed"]).nullable(),
|
|
32
|
+
}).strict();
|
|
33
|
+
export const MarketplaceReportSchema = z.object({
|
|
34
|
+
operationId: z.string().uuid(), status: MarketplaceStatusSchema,
|
|
35
|
+
results: z.array(MarketplaceResultSchema).max(202),
|
|
36
|
+
errorCode: z.enum(["runtime_busy", "execution_failed", "operation_unknown", "unsupported_platform", "too_many_scopes"]).nullable(),
|
|
37
|
+
}).strict();
|
|
38
|
+
export const marketplaceActive = (status) => status === "queued" || status === "running";
|