@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.
Files changed (116) hide show
  1. package/README.md +64 -3
  2. package/dist/agent-ability/materializer.js +21 -10
  3. package/dist/agent-environment-contract.js +55 -0
  4. package/dist/agent-environment-control.js +24 -0
  5. package/dist/agent-environment-execution.js +98 -0
  6. package/dist/agent-environments.js +275 -0
  7. package/dist/automatic-daemon-installer.js +49 -1
  8. package/dist/computer-main.js +18 -0
  9. package/dist/computer-profile.js +17 -4
  10. package/dist/computer-setup-client.js +318 -0
  11. package/dist/computer-setup-orchestration.js +203 -0
  12. package/dist/computer-setup.js +244 -0
  13. package/dist/config.js +30 -12
  14. package/dist/console-formatter.js +3 -3
  15. package/dist/console.js +15 -2
  16. package/dist/daemon-update-eligibility.js +1 -1
  17. package/dist/daemon-updater.js +2 -12
  18. package/dist/execution-journal.js +9 -6
  19. package/dist/execution-protocol.js +1 -0
  20. package/dist/execution-runner.js +13 -8
  21. package/dist/i18n.js +19 -0
  22. package/dist/list-models.js +12 -12
  23. package/dist/local-execution-lease.js +22 -0
  24. package/dist/local-executor.js +57 -84
  25. package/dist/local-memory/candidate.js +43 -0
  26. package/dist/local-memory/index-budget.js +8 -0
  27. package/dist/local-memory/inventory-cli.js +61 -0
  28. package/dist/local-memory/inventory-preview.js +159 -0
  29. package/dist/local-memory/inventory-source.js +159 -0
  30. package/dist/local-memory/pending-match.js +29 -0
  31. package/dist/local-memory/pending-query.js +100 -0
  32. package/dist/local-memory/pending-schema.js +29 -0
  33. package/dist/local-memory/pending-source.js +126 -0
  34. package/dist/local-memory/pending.js +34 -84
  35. package/dist/local-memory/prompt.js +19 -12
  36. package/dist/local-memory/read-cli.js +97 -12
  37. package/dist/local-memory/rollout.js +82 -0
  38. package/dist/local-memory/runtime.js +23 -17
  39. package/dist/local-memory/work-log-execution.js +31 -0
  40. package/dist/local-memory/work-log.js +58 -0
  41. package/dist/local-memory/write.js +27 -58
  42. package/dist/local-runtime-environment.js +50 -0
  43. package/dist/machine-environment-cache.js +147 -0
  44. package/dist/machine-environment-contract.js +10 -0
  45. package/dist/machine-environment-control.js +27 -0
  46. package/dist/machine-environment-policy.js +75 -0
  47. package/dist/machine-environment-process.js +110 -0
  48. package/dist/machine-info.js +70 -25
  49. package/dist/machine-runtime-environment.js +189 -0
  50. package/dist/machine-shell-environment-capture.js +135 -0
  51. package/dist/machine-windows-environment-capture.js +215 -0
  52. package/dist/machine-windows-environment-script.js +158 -0
  53. package/dist/main.js +4 -69
  54. package/dist/marketplace-command.js +21 -0
  55. package/dist/marketplace-controller.js +80 -0
  56. package/dist/marketplace-protocol.js +38 -0
  57. package/dist/marketplace-remote.js +48 -0
  58. package/dist/marketplace-runner.js +235 -0
  59. package/dist/marketplace-serve.js +41 -0
  60. package/dist/marketplace-source.js +103 -0
  61. package/dist/message-file-preview.js +246 -0
  62. package/dist/pre-admission-rejection.js +64 -0
  63. package/dist/project-skills/execution-adapter.js +3 -2
  64. package/dist/project-skills/runtime-launch.js +14 -0
  65. package/dist/provider-env.js +26 -15
  66. package/dist/runner.js +17 -4
  67. package/dist/runtime-environment-merge.js +16 -0
  68. package/dist/runtime-health-handler.js +10 -0
  69. package/dist/runtime-health.js +3 -1
  70. package/dist/runtime-path.js +22 -12
  71. package/dist/runtime-probe.js +35 -6
  72. package/dist/runtimes/acp-probe-transport.js +69 -0
  73. package/dist/runtimes/claude-models.js +20 -9
  74. package/dist/runtimes/deepseek-harness-launch.js +10 -0
  75. package/dist/runtimes/deepseek-harness-profile.js +92 -0
  76. package/dist/runtimes/deepseek-harness-runner.js +226 -0
  77. package/dist/runtimes/deepseek-harness.js +76 -15
  78. package/dist/runtimes/harness-probe-retry.js +42 -0
  79. package/dist/runtimes/hermes-models.js +2 -1
  80. package/dist/runtimes/kimi-acp-runner.js +78 -13
  81. package/dist/runtimes/opencode.js +2 -2
  82. package/dist/serve.js +88 -178
  83. package/dist/storage-identity.js +0 -3
  84. package/dist/storage-serve-adapter.js +6 -72
  85. package/dist/supervised-runtime.js +17 -2
  86. package/dist/token.js +0 -1
  87. package/dist/workspace-control-handler.js +27 -0
  88. package/dist/workspace-fs.js +66 -15
  89. package/dist/workspace.js +1 -1
  90. package/package.json +13 -12
  91. package/dist/codex-home-launch.js +0 -7
  92. package/dist/codex-home-mode-controller.js +0 -68
  93. package/dist/daemon-migration-controller.js +0 -211
  94. package/dist/daemon-migration-wiring.js +0 -22
  95. package/dist/runtimes/codex-home-migration-cli.js +0 -26
  96. package/dist/runtimes/codex-home-migration.js +0 -112
  97. package/dist/runtimes/codex-home.js +0 -236
  98. package/dist/storage-control.js +0 -212
  99. package/dist/storage-copy.js +0 -628
  100. package/dist/storage-credential-guard.js +0 -21
  101. package/dist/storage-doctor.js +0 -228
  102. package/dist/storage-manifest.js +0 -1027
  103. package/dist/storage-migration-controller.js +0 -440
  104. package/dist/storage-migration-manifest.js +0 -105
  105. package/dist/storage-migration-policy.js +0 -100
  106. package/dist/storage-runtime-controller.js +0 -381
  107. package/dist/storage-runtime-readiness.js +0 -241
  108. package/dist/storage-safe-fs.js +0 -302
  109. package/dist/storage-status-delivery.js +0 -224
  110. package/dist/storage-status-replay.js +0 -140
  111. package/dist/storage-target-confirmation.js +0 -129
  112. package/dist/storage-worker-command.js +0 -37
  113. package/dist/storage-worker-executor.js +0 -447
  114. package/dist/storage-worker-handoff.js +0 -151
  115. package/dist/storage-worker-operation.js +0 -294
  116. 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 migrationRuntime = values.profile === undefined || activeDaemonHome === undefined || storageRuntime !== null
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
- ...(storageMigration === undefined ? {} : { storageMigration }),
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";