@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,48 @@
1
+ import { mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { z } from "zod";
5
+ import { MARKETPLACE, PLUGIN } from "./marketplace-protocol.js";
6
+ import { MarketplaceFailure } from "./marketplace-command.js";
7
+ import { validateMarketplaceSource } from "./marketplace-source.js";
8
+ const Manifest = z.object({ name: z.literal(MARKETPLACE), plugins: z.array(z.object({ name: z.string() }).passthrough()) });
9
+ const Target = z.object({
10
+ version: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._+-]{0,79}$/),
11
+ source: z.object({ source: z.literal("git-subdir"),
12
+ url: z.literal("git@codeup.aliyun.com:6128d2e757e7cd986dfae5ab/pm/AIinterview-agent-skill.git"),
13
+ path: z.literal("plugins/AIinterview-plugin"), sha: z.string().regex(/^[a-f0-9]{40}$/) }),
14
+ });
15
+ export async function readRemoteMarketplace(runtime, registered, env, command) {
16
+ const source = validateMarketplaceSource(registered.url, registered.ref);
17
+ const temporary = await mkdtemp(join(tmpdir(), "nowwork-marketplace-query-"));
18
+ const options = { env, cwd: temporary };
19
+ try {
20
+ await command("git", ["init", "--bare", "--template=", "."], options);
21
+ await command("git", ["fetch", "--depth=1", "--no-tags", source.url, source.ref], options);
22
+ const revision = (await command("git", ["rev-parse", "--verify", "FETCH_HEAD"], options)).trim();
23
+ if (!/^[a-f0-9]{40}$/.test(revision))
24
+ throw new MarketplaceFailure("verification_failed");
25
+ const path = runtime === "claude" ? ".claude-plugin/marketplace.json" : ".agents/plugins/marketplace.json";
26
+ const raw = await command("git", ["show", `${revision}:${path}`], options);
27
+ let value;
28
+ try {
29
+ value = JSON.parse(raw);
30
+ }
31
+ catch {
32
+ throw new MarketplaceFailure("invalid_inventory");
33
+ }
34
+ const manifest = Manifest.safeParse(value);
35
+ if (!manifest.success)
36
+ throw new MarketplaceFailure("invalid_inventory");
37
+ const matches = manifest.data.plugins.filter(plugin => plugin.name === PLUGIN);
38
+ if (matches.length !== 1)
39
+ throw new MarketplaceFailure("invalid_inventory");
40
+ const target = Target.safeParse(matches[0]);
41
+ if (!target.success)
42
+ throw new MarketplaceFailure("unsupported_installation");
43
+ return { marketRevision: revision, targetVersion: target.data.version };
44
+ }
45
+ finally {
46
+ await rm(temporary, { recursive: true, force: true });
47
+ }
48
+ }
@@ -0,0 +1,235 @@
1
+ /** 固定市场操作的短命 CLI 适配;原始命令输出不离开设备。 */
2
+ import { createHash } from "node:crypto";
3
+ import { readFile, readdir, realpath, lstat, mkdtemp, rm } from "node:fs/promises";
4
+ import { homedir, tmpdir } from "node:os";
5
+ import { join, resolve, isAbsolute, relative, sep } from "node:path";
6
+ import { applyProviderEnv } from "./provider-env.js";
7
+ import { augmentedPath } from "./runtime-path.js";
8
+ import { MARKETPLACE, PLUGIN } from "./marketplace-protocol.js";
9
+ import { MarketplaceFailure, runMarketplaceCommand } from "./marketplace-command.js";
10
+ import { readRemoteMarketplace } from "./marketplace-remote.js";
11
+ import { readMarketplaceSource } from "./marketplace-source.js";
12
+ export { MarketplaceFailure, runMarketplaceCommand } from "./marketplace-command.js";
13
+ const SELECTOR = `${PLUGIN}@${MARKETPLACE}`;
14
+ const SOURCE = "git@codeup.aliyun.com:6128d2e757e7cd986dfae5ab/pm/AIinterview-agent-skill.git";
15
+ const SOURCE_PATH = "plugins/AIinterview-plugin";
16
+ const obj = (x) => x !== null && typeof x === "object" && !Array.isArray(x) ? x : {};
17
+ const parse = (text) => { try {
18
+ return JSON.parse(text);
19
+ }
20
+ catch {
21
+ throw new MarketplaceFailure("invalid_inventory");
22
+ } };
23
+ const version = (x) => typeof x === "string" && /^[A-Za-z0-9][A-Za-z0-9._+-]{0,79}$/.test(x) ? x : null;
24
+ const sha = (x) => typeof x === "string" && /^[a-f0-9]{40}$/.test(x) ? x : null;
25
+ const within = (root, candidate) => {
26
+ const child = relative(root, candidate);
27
+ return child === "" || (child !== ".." && !child.startsWith(`..${sep}`) && !isAbsolute(child));
28
+ };
29
+ const canonical = async (path) => realpath(path).catch(() => resolve(path));
30
+ export async function resolveMarketplaceScopes(targets, agentsRoot, inherited = process.env) {
31
+ const scopes = new Map();
32
+ const trustedHome = await canonical(inherited.HOME ?? homedir());
33
+ for (const target of targets) {
34
+ const home = target.handle ? join(agentsRoot, target.handle, ".home") : homedir();
35
+ const agentHome = target.handle ? await canonical(home) : null;
36
+ const allowedHomeRoots = [trustedHome, ...(agentHome === null ? [] : [agentHome])];
37
+ const sharedConfigRoot = join(trustedHome, target.runtime === "codex" ? ".codex" : ".claude");
38
+ const agentConfigRoot = agentHome === null ? null : join(agentHome, target.runtime === "codex" ? ".codex" : ".claude");
39
+ const allowedConfigRoots = [sharedConfigRoot, ...(agentConfigRoot === null ? [] : [agentConfigRoot])];
40
+ const suppliedHome = target.paths.HOME;
41
+ if (suppliedHome !== undefined && !within(allowedHomeRoots[0], await canonical(suppliedHome))
42
+ && (agentHome === null || !within(agentHome, await canonical(suppliedHome))))
43
+ throw new MarketplaceFailure("scope_unavailable");
44
+ const suppliedConfig = target.runtime === "codex" ? target.paths.CODEX_HOME : target.paths.CLAUDE_CONFIG_DIR;
45
+ const suppliedConfigPath = suppliedConfig === undefined ? null : await canonical(suppliedConfig);
46
+ if (suppliedConfigPath !== null && !allowedConfigRoots.some(root => within(root, suppliedConfigPath)))
47
+ throw new MarketplaceFailure("scope_unavailable");
48
+ // 与 local-executor 使用同一 provider 解析;凭据存在性只用于选择已配置的 provider,不传递凭据内容。
49
+ const env = applyProviderEnv({ ...inherited, ...target.paths }, target.runtime, target.handle ? {
50
+ provider: target.provider, providerApiKey: target.hasProviderKey ? "configured" : null,
51
+ } : {}, home);
52
+ for (const key of Object.keys(env))
53
+ if (/^(CREW_|ANTHROPIC_|OPENAI_|DEEPSEEK_)/.test(key))
54
+ delete env[key];
55
+ for (const key of ["GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_COMMON_DIR"])
56
+ delete env[key];
57
+ env.PATH = augmentedPath(inherited);
58
+ env.GIT_TERMINAL_PROMPT = "0";
59
+ env.CI = "1";
60
+ const raw = target.runtime === "codex" ? env.CODEX_HOME ?? join(env.HOME ?? homedir(), ".codex")
61
+ : env.CLAUDE_CONFIG_DIR ?? join(env.HOME ?? homedir(), ".claude");
62
+ if (!isAbsolute(raw))
63
+ throw new MarketplaceFailure("scope_unavailable");
64
+ const configHome = await realpath(raw).catch(() => resolve(raw));
65
+ if (!allowedConfigRoots.some(root => within(root, configHome)))
66
+ throw new MarketplaceFailure("scope_unavailable");
67
+ if (target.runtime === "codex")
68
+ env.CODEX_HOME = configHome;
69
+ else
70
+ env.CLAUDE_CONFIG_DIR = configHome;
71
+ const key = `${target.runtime}:${configHome}`;
72
+ const prior = scopes.get(key);
73
+ if (prior) {
74
+ if (target.handle && !prior.agents.includes(target.handle))
75
+ prior.agents.push(target.handle);
76
+ prior.shared ||= !target.handle;
77
+ }
78
+ else
79
+ scopes.set(key, { runtime: target.runtime, configHome, env, agents: target.handle ? [target.handle] : [], shared: !target.handle });
80
+ }
81
+ return [...scopes.values()];
82
+ }
83
+ export function parsePluginInventory(runtime, value, configHome) {
84
+ const rows = Array.isArray(value) ? value : obj(value).installed;
85
+ if (!Array.isArray(rows))
86
+ throw new MarketplaceFailure("invalid_inventory");
87
+ const found = rows.map(obj).filter(x => (x.id ?? x.pluginId) === SELECTOR);
88
+ if (found.length === 0)
89
+ return null;
90
+ if (found.length !== 1)
91
+ throw new MarketplaceFailure("unsupported_installation");
92
+ const item = found[0];
93
+ const v = version(item.version);
94
+ if (!v || typeof item.enabled !== "boolean")
95
+ throw new MarketplaceFailure("invalid_inventory");
96
+ if (runtime === "claude" && item.scope !== "user")
97
+ throw new MarketplaceFailure("unsupported_installation");
98
+ const path = runtime === "claude" ? item.installPath : join(configHome, "plugins/cache", MARKETPLACE, PLUGIN, v);
99
+ if (typeof path !== "string" || !isAbsolute(path))
100
+ throw new MarketplaceFailure("invalid_inventory");
101
+ return { version: v, enabled: item.enabled, path, revision: sha(obj(item.source).sha) };
102
+ }
103
+ /** 查询与任务并行;外部更新改变安装清单时重读一次,避免混合两代安装事实。 */
104
+ async function readInstalledSnapshot(runtime, inventory) {
105
+ for (let attempt = 0; attempt < 2; attempt += 1) {
106
+ const before = await inventory();
107
+ let valid = before === null;
108
+ if (before !== null) {
109
+ try {
110
+ const manifest = obj(parse(await readFile(join(before.path, runtime === "claude" ? ".claude-plugin" : ".codex-plugin", "plugin.json"), "utf8")));
111
+ valid = manifest.name === PLUGIN && version(manifest.version) === before.version;
112
+ }
113
+ catch { /* 清单变化则重读;稳定但不可读的安装仍报告核验失败。 */ }
114
+ }
115
+ if (JSON.stringify(before) !== JSON.stringify(await inventory()))
116
+ continue;
117
+ if (!valid)
118
+ throw new MarketplaceFailure("verification_failed");
119
+ return before;
120
+ }
121
+ throw new MarketplaceFailure("configuration_changed");
122
+ }
123
+ /** 逐字节比对固定 Git 源子目录,不能只凭 CLI 退出码或版本字符串证明安装完成。 */
124
+ export async function verifyInstalledContent(path, revision, env, command) {
125
+ const temporary = await mkdtemp(join(tmpdir(), "nowwork-plugin-verify-"));
126
+ const options = { env, cwd: temporary };
127
+ try {
128
+ await command("git", ["init", "--bare", "."], options);
129
+ await command("git", ["fetch", "--depth=1", SOURCE, revision], options);
130
+ const tree = await command("git", ["ls-tree", "-rz", `${revision}:${SOURCE_PATH}`], options);
131
+ const expected = new Map();
132
+ for (const entry of tree.split("\0").filter(Boolean)) {
133
+ const match = /^(100644|100755) blob ([a-f0-9]{40})\t(.+)$/.exec(entry);
134
+ if (!match || match[3].split("/").some(x => x === ".." || x === "."))
135
+ throw new MarketplaceFailure("verification_failed");
136
+ expected.set(match[3], match[2]);
137
+ }
138
+ if (!expected.size || expected.size > 2000)
139
+ throw new MarketplaceFailure("verification_failed");
140
+ const actual = new Map();
141
+ let bytes = 0;
142
+ const walk = async (dir, prefix = "") => {
143
+ for (const name of await readdir(dir)) {
144
+ if (name === "__pycache__" || name === ".DS_Store")
145
+ continue;
146
+ const entry = join(dir, name);
147
+ const key = prefix + name;
148
+ const info = await lstat(entry);
149
+ if (info.isSymbolicLink())
150
+ throw new MarketplaceFailure("verification_failed");
151
+ if (info.isDirectory())
152
+ await walk(entry, key + "/");
153
+ else if (info.isFile()) {
154
+ bytes += info.size;
155
+ if (bytes > 100 * 1024 * 1024 || actual.size > 2000)
156
+ throw new MarketplaceFailure("verification_failed");
157
+ const data = await readFile(entry);
158
+ actual.set(key, createHash("sha1").update(`blob ${data.length}\0`).update(data).digest("hex"));
159
+ }
160
+ else
161
+ throw new MarketplaceFailure("verification_failed");
162
+ }
163
+ };
164
+ await walk(path);
165
+ if (actual.size !== expected.size || [...expected].some(([name, hash]) => actual.get(name) !== hash))
166
+ throw new MarketplaceFailure("verification_failed");
167
+ }
168
+ finally {
169
+ await rm(temporary, { recursive: true, force: true });
170
+ }
171
+ }
172
+ export async function runMarketplaceScope(scope, mode, command = runMarketplaceCommand, verify = verifyInstalledContent) {
173
+ let result = { runtime: scope.runtime, scopeId: createHash("sha256").update(`${scope.runtime}:${scope.configHome}`).digest("hex").slice(0, 16),
174
+ agents: scope.agents, shared: scope.shared, status: "failed", beforeVersion: null, afterVersion: null, targetVersion: null,
175
+ marketRevision: null, installedRevision: null, enabled: null, activation: "unverified", errorCode: null };
176
+ const options = { env: scope.env, cwd: scope.configHome };
177
+ const run = (args) => command(scope.runtime, args, options);
178
+ const inventory = async () => parsePluginInventory(scope.runtime, parse(await run(["plugin", "list", "--json"])), scope.configHome);
179
+ try {
180
+ if (!(await lstat(scope.configHome)).isDirectory())
181
+ throw new MarketplaceFailure("scope_unavailable");
182
+ const markets = parse(await run(["plugin", "marketplace", "list", "--json"]));
183
+ const rows = Array.isArray(markets) ? markets : obj(markets).marketplaces;
184
+ if (!Array.isArray(rows))
185
+ throw new MarketplaceFailure("invalid_inventory");
186
+ const market = rows.map(obj).filter(x => x.name === MARKETPLACE);
187
+ if (market.length !== 1)
188
+ throw new MarketplaceFailure("market_not_registered");
189
+ const root = market[0].root ?? market[0].installLocation;
190
+ if (typeof root !== "string" || !isAbsolute(root))
191
+ throw new MarketplaceFailure("invalid_inventory");
192
+ const registered = await readMarketplaceSource(scope.runtime, scope.configHome, market[0]);
193
+ const before = mode === "update" ? await inventory() : await readInstalledSnapshot(scope.runtime, inventory);
194
+ result = { ...result, beforeVersion: before?.version ?? null, afterVersion: before?.version ?? null, enabled: before?.enabled ?? null };
195
+ if (mode === "inspect")
196
+ return { ...result, status: before ? "checked" : "not_installed", activation: "unverified" };
197
+ if (mode === "refresh") {
198
+ const latest = await readRemoteMarketplace(scope.runtime, registered, scope.env, command);
199
+ const current = await readMarketplaceSource(scope.runtime, scope.configHome, market[0]);
200
+ if (current.url !== registered.url || current.ref !== registered.ref)
201
+ throw new MarketplaceFailure("configuration_changed");
202
+ return { ...result, ...latest, status: "refreshed", activation: "not_changed" };
203
+ }
204
+ await run(["plugin", "marketplace", scope.runtime === "claude" ? "update" : "upgrade", MARKETPLACE]);
205
+ const revision = sha((await command("git", ["-C", root, "rev-parse", "HEAD"], options)).trim());
206
+ if (!revision)
207
+ throw new MarketplaceFailure("verification_failed");
208
+ const manifest = obj(parse(await readFile(join(root, scope.runtime === "claude" ? ".claude-plugin" : ".agents/plugins", "marketplace.json"), "utf8")));
209
+ const plugins = manifest.plugins;
210
+ const target = Array.isArray(plugins) ? plugins.map(obj).find(x => x.name === PLUGIN) : undefined;
211
+ const source = obj(target?.source);
212
+ const targetVersion = version(target?.version);
213
+ const targetSha = sha(source.sha);
214
+ result = { ...result, marketRevision: revision, targetVersion };
215
+ if (!before)
216
+ return { ...result, status: "not_installed", activation: "not_changed" };
217
+ if (!targetVersion || !targetSha || source.url !== SOURCE || source.path !== SOURCE_PATH || source.source !== "git-subdir")
218
+ throw new MarketplaceFailure("unsupported_installation");
219
+ // Codex add 会启用插件;不偷偷改变用户已禁用的状态。
220
+ if (!before.enabled)
221
+ throw new MarketplaceFailure("unsupported_installation");
222
+ await run(scope.runtime === "claude" ? ["plugin", "update", SELECTOR, "--scope", "user", "--json"] : ["plugin", "add", SELECTOR, "--json"]);
223
+ const after = await inventory();
224
+ result = { ...result, afterVersion: after?.version ?? null, enabled: after?.enabled ?? null };
225
+ if (!after || after.version !== targetVersion || after.enabled !== before.enabled)
226
+ throw new MarketplaceFailure("verification_failed");
227
+ await verify(after.path, targetSha, scope.env, command);
228
+ return { ...result, status: before.version === after.version && before.revision === targetSha ? "unchanged" : "updated",
229
+ installedRevision: targetSha, activation: "reload_required" };
230
+ }
231
+ catch (error) {
232
+ const code = error instanceof MarketplaceFailure ? error.code : "scope_unavailable";
233
+ return { ...result, status: code === "cli_missing" ? "missing_cli" : "failed", errorCode: code };
234
+ }
235
+ }
@@ -0,0 +1,41 @@
1
+ import { createMarketplaceController } from "./marketplace-controller.js";
2
+ import { prepareDaemonRestart } from "./daemon-updater.js";
3
+ export function createServeMarketplace(config, localSlots, hostCoordinator) {
4
+ const controller = createMarketplaceController({ agentsRoot: config.agentsRoot,
5
+ acquire: async () => {
6
+ // Marketplace updates can mutate shared Codex/Claude homes across profiles.
7
+ const local = await prepareDaemonRestart({ localSlots });
8
+ if (!local.ok)
9
+ return null;
10
+ try {
11
+ const host = await hostCoordinator.tryAcquireExclusiveExecution();
12
+ if (host === null) {
13
+ await local.release();
14
+ return null;
15
+ }
16
+ let released = false;
17
+ return { release: async () => {
18
+ if (released)
19
+ return;
20
+ released = true;
21
+ await host.release();
22
+ await local.release();
23
+ } };
24
+ }
25
+ catch (error) {
26
+ await local.release();
27
+ throw error;
28
+ }
29
+ } });
30
+ return {
31
+ handle(raw, send) {
32
+ if (!raw || typeof raw !== "object" || !("type" in raw) || raw.type !== "marketplace:operation")
33
+ return false;
34
+ const { reqId, ...request } = raw;
35
+ if (typeof reqId === "string")
36
+ send({ type: "fs:result", reqId, ...controller.handle(request) });
37
+ return true;
38
+ },
39
+ stop: controller.stop,
40
+ };
41
+ }
@@ -0,0 +1,103 @@
1
+ import { readFile, realpath, stat } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { parse as parseToml } from "smol-toml";
4
+ import { MARKETPLACE } from "./marketplace-protocol.js";
5
+ import { MarketplaceFailure } from "./marketplace-command.js";
6
+ const REPOSITORY_PATH = "6128d2e757e7cd986dfae5ab/pm/nowwork-pm-skills-marketplace";
7
+ const obj = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
8
+ /** 固定仓库身份,但保留用户登记的 SSH/HTTPS 传输方式;不允许 URL 内嵌凭据。 */
9
+ function allowedRepository(value) {
10
+ if (typeof value !== "string" || value.length > 4096 || /[\s\u0000-\u001f\u007f\\]/u.test(value))
11
+ return false;
12
+ let host;
13
+ let path;
14
+ const scp = /^git@([^/:]+):(.+)$/u.exec(value);
15
+ if (scp) {
16
+ host = scp[1];
17
+ path = scp[2];
18
+ }
19
+ else {
20
+ let url;
21
+ try {
22
+ url = new URL(value);
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ if (!["https:", "ssh:"].includes(url.protocol) || url.search || url.hash || url.password)
28
+ return false;
29
+ if (url.protocol === "https:" && (url.username || url.port))
30
+ return false;
31
+ if (url.protocol === "ssh:" && (url.username !== "git" || (url.port !== "" && url.port !== "22")))
32
+ return false;
33
+ host = url.hostname;
34
+ path = url.pathname.replace(/^\//u, "");
35
+ }
36
+ return host.toLowerCase() === "codeup.aliyun.com" && path.replace(/\/$/u, "").replace(/\.git$/u, "") === REPOSITORY_PATH;
37
+ }
38
+ export function validateMarketplaceSource(url, ref) {
39
+ const selectedRef = ref === undefined ? "HEAD" : ref;
40
+ if (!allowedRepository(url) || typeof selectedRef !== "string" || !selectedRef || selectedRef.length > 1024
41
+ || /^[+-]/u.test(selectedRef) || selectedRef === "@" || selectedRef.includes("..") || selectedRef.includes("@{")
42
+ || /[\s\u0000-\u001f\u007f~^:?*\[\\]/u.test(selectedRef)
43
+ || selectedRef.split("/").some(part => !part || part.startsWith(".") || part.endsWith(".") || part.endsWith(".lock"))) {
44
+ throw new MarketplaceFailure("unsupported_installation");
45
+ }
46
+ return { url, ref: selectedRef };
47
+ }
48
+ async function registrationFile(path) {
49
+ try {
50
+ if ((await stat(path)).size > 1024 * 1024)
51
+ throw new MarketplaceFailure("invalid_inventory");
52
+ return await readFile(path, "utf8");
53
+ }
54
+ catch (error) {
55
+ if (error instanceof MarketplaceFailure)
56
+ throw error;
57
+ throw new MarketplaceFailure("configuration_changed");
58
+ }
59
+ }
60
+ /** 列表会省略 ref;以同一配置目录内的原生登记文件为准,不从已安装缓存猜分支。 */
61
+ export async function readMarketplaceSource(runtime, configHome, listed) {
62
+ let source;
63
+ let listedUrl;
64
+ if (runtime === "claude") {
65
+ let registered;
66
+ const text = await registrationFile(join(configHome, "plugins/known_marketplaces.json"));
67
+ try {
68
+ registered = obj(obj(JSON.parse(text))[MARKETPLACE]);
69
+ }
70
+ catch {
71
+ throw new MarketplaceFailure("invalid_inventory");
72
+ }
73
+ const entry = obj(registered.source);
74
+ if (entry.source !== "git" || listed.source !== "git")
75
+ throw new MarketplaceFailure("unsupported_installation");
76
+ source = validateMarketplaceSource(entry.url, entry.ref);
77
+ listedUrl = listed.url ?? listed.repo;
78
+ if (typeof registered.installLocation !== "string" || typeof listed.installLocation !== "string")
79
+ throw new MarketplaceFailure("configuration_changed");
80
+ const canonical = (path) => realpath(path).catch(() => resolve(path));
81
+ if (await canonical(registered.installLocation) !== await canonical(listed.installLocation))
82
+ throw new MarketplaceFailure("configuration_changed");
83
+ }
84
+ else {
85
+ let entry;
86
+ const text = await registrationFile(join(configHome, "config.toml"));
87
+ try {
88
+ entry = obj(obj(parseToml(text).marketplaces)[MARKETPLACE]);
89
+ }
90
+ catch {
91
+ throw new MarketplaceFailure("invalid_inventory");
92
+ }
93
+ const origin = obj(listed.marketplaceSource);
94
+ if (entry.source_type !== "git" || origin.sourceType !== "git")
95
+ throw new MarketplaceFailure("unsupported_installation");
96
+ source = validateMarketplaceSource(entry.source, entry.ref);
97
+ listedUrl = origin.source;
98
+ }
99
+ // 同一轮清单与登记文件来源不一致,可能正被重新登记;不擅自改查另一来源。
100
+ if (listedUrl !== source.url)
101
+ throw new MarketplaceFailure("configuration_changed");
102
+ return source;
103
+ }