@nowcrew/daemon 0.6.77-beta.4 → 0.6.77

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.
@@ -185,7 +185,7 @@ const materializeGit = async (cacheRoot, repositoryUrl, commit, target) => {
185
185
  await git(["--git-dir", bare, "fetch", "--no-tags", "--prune", "origin", commit]);
186
186
  const temp = `${target}.tmp-${randomUUID()}`;
187
187
  await rm(temp, { recursive: true, force: true });
188
- await git(["clone", "--no-checkout", "--shared", bare, temp]);
188
+ await git(["clone", "--shared", bare, temp]);
189
189
  await git(["checkout", "--detach", commit], temp);
190
190
  await rm(join(temp, ".git"), { recursive: true, force: true });
191
191
  await mkdir(dirname(target), { recursive: true });
package/dist/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /** daemon 配置:从 env 读取,解析 crew CLI 路径 (注入给 agent 的 wrapper 用)。 */
2
2
  import { fileURLToPath } from "node:url";
3
- import { dirname, resolve } from "node:path";
3
+ import { dirname, join, resolve } from "node:path";
4
4
  import { homedir } from "node:os";
5
5
  import { createRequire } from "node:module";
6
6
  import { detectDaemonLang, translateDaemon } from "./i18n.js";
@@ -134,10 +134,18 @@ export function loadConfig(env = process.env) {
134
134
  maxFirstOutputWaitMs: positiveIntegerEnv(env, "CREW_EXECUTION_MAX_FIRST_OUTPUT_WAIT_MS", DEFAULT_EXECUTION_LIMITS.maxFirstOutputWaitMs),
135
135
  firstOutputGraceMs: positiveIntegerEnv(env, "CREW_EXECUTION_FIRST_OUTPUT_GRACE_MS", DEFAULT_EXECUTION_LIMITS.firstOutputGraceMs),
136
136
  });
137
+ const agentsRoot = resolveAgentsRoot(env.CREW_AGENTS_ROOT, homedir());
138
+ const configuredReposRoot = env.NOWWORK_REPOS_ROOT?.trim();
139
+ const reposRoot = configuredReposRoot ? resolve(configuredReposRoot) : undefined;
140
+ const configuredQaReportRoot = env.QA_REPORT_HUB?.trim();
137
141
  return {
138
142
  serverUrl,
139
143
  machineToken,
140
- agentsRoot: resolveAgentsRoot(env.CREW_AGENTS_ROOT, homedir()),
144
+ agentsRoot,
145
+ ...(reposRoot === undefined ? {} : { reposRoot }),
146
+ qaReportRoot: configuredQaReportRoot
147
+ ? resolve(configuredQaReportRoot)
148
+ : join(dirname(reposRoot ?? agentsRoot), "qa-report-server-v2"),
141
149
  cliPath: env.CREW_CLI_PATH ?? defaultCliPath(),
142
150
  runtimeBin: env.CREW_RUNTIME ?? "claude",
143
151
  codexHomeMode: codexHomeModeEnv(env),
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { join } from "node:path";
2
+ import { dirname, join, resolve } from "node:path";
3
3
  import { DaemonToServerExecutionFrameSchema, ExecutionCompletedSchema, ExecutionRejectedSchema, ExecutionStartSchema, } from "./execution-protocol.js";
4
4
  import { JournalConflictError } from "./execution-journal.js";
5
5
  import { boundExecutionFrame } from "./execution-event-limit.js";
@@ -524,10 +524,11 @@ export async function runExecution(config, input, dependencies) {
524
524
  if (dependencies.abilityRelease !== undefined) {
525
525
  await cancellable(dependencies.abilityRelease.apply(config.agentsRoot, spec.agent.handle, spec.agent.abilityRelease), dependencies.cancellation);
526
526
  }
527
+ const reposRoot = config.reposRoot ?? join(config.agentsRoot, "repos");
527
528
  const preparedProjectDependencies = await cancellable(dependencies.projectDependencies.prepare({
528
529
  trainingRoot: join(config.agentsRoot, spec.agent.handle, "training"),
529
- reposRoot: join(config.agentsRoot, "repos"),
530
- lockRoot: join(config.agentsRoot, ".crew", "project-dependencies"),
530
+ reposRoot,
531
+ lockRoot: join(dirname(resolve(reposRoot)), ".crew", "project-dependencies"),
531
532
  }), dependencies.cancellation);
532
533
  trainingProjectContext = Object.freeze({
533
534
  projects: Object.freeze(preparedProjectDependencies.map((project) => Object.freeze({
@@ -862,6 +863,8 @@ export async function runExecution(config, input, dependencies) {
862
863
  token: credential.token,
863
864
  agentId: credential.agentId,
864
865
  agentsRoot: config.agentsRoot,
866
+ ...(config.reposRoot === undefined ? {} : { reposRoot: config.reposRoot }),
867
+ ...(config.qaReportRoot === undefined ? {} : { qaReportRoot: config.qaReportRoot }),
865
868
  cliPath: config.cliPath,
866
869
  providerConfig,
867
870
  codexHomeMode: config.codexHomeMode,
@@ -466,6 +466,8 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
466
466
  CREW_TOKEN: input.launch.token,
467
467
  CREW_CHANNEL: input.channelId,
468
468
  CREW_HOME: workspace.dir,
469
+ ...(input.launch.reposRoot === undefined ? {} : { NOWWORK_REPOS_ROOT: input.launch.reposRoot }),
470
+ ...(input.launch.qaReportRoot === undefined ? {} : { QA_REPORT_HUB: input.launch.qaReportRoot }),
469
471
  // Expose only the training root; ability-specific workspace layout stays
470
472
  // owned by the training package rather than the NowWork daemon.
471
473
  QA_RUNTIME_ROOT: join(workspace.dir, "training"),
package/dist/main.js CHANGED
@@ -117,6 +117,23 @@ async function main() {
117
117
  }
118
118
  const provenance = daemonReleaseProvenance(daemonVersion());
119
119
  assertReleaseTagForServerUrl(config.serverUrl, provenance.releaseTag);
120
+ const storageRuntime = values.profile === undefined
121
+ ? null
122
+ : await loadStorageRuntime({
123
+ profile: values.profile,
124
+ serverUrl: config.serverUrl,
125
+ userHome: homedir(),
126
+ platform: process.platform,
127
+ environment: process.env,
128
+ });
129
+ const runtimeConfig = storageRuntime === null
130
+ ? config
131
+ : Object.freeze({
132
+ ...config,
133
+ agentsRoot: storageRuntime.layout.agentItemsRoot,
134
+ reposRoot: storageRuntime.layout.repoRoot,
135
+ qaReportRoot: storageRuntime.layout.qaReportRoot,
136
+ });
120
137
  if (cmd === "serve") {
121
138
  process.stdout.write(formatDaemonLogLine(`🛰️ crew-daemon v${daemonVersion()} (cli v${cliVersion()}, ${provenance.releaseChannel}/${provenance.releaseTag}) ${td("resident, connecting to")} ${config.serverUrl} ${td("control plane")}...`) + "\n");
122
139
  const installation = values.profile === undefined
@@ -125,15 +142,6 @@ async function main() {
125
142
  const installationLease = installation === null
126
143
  ? null
127
144
  : await acquireDaemonInstallationLease(installation.npmPrefix);
128
- const storageRuntime = values.profile === undefined
129
- ? null
130
- : await loadStorageRuntime({
131
- profile: values.profile,
132
- serverUrl: config.serverUrl,
133
- userHome: homedir(),
134
- platform: process.platform,
135
- environment: process.env,
136
- });
137
145
  const migrationRuntime = values.profile === undefined || activeDaemonHome === undefined || storageRuntime !== null
138
146
  ? { ready: false, reason: "not_legacy_source" }
139
147
  : await probeStorageMigrationRuntime({
@@ -188,7 +196,7 @@ async function main() {
188
196
  }
189
197
  : undefined;
190
198
  await runWithDaemonInstallationLease(installationLease, async () => {
191
- const service = serve(config, {
199
+ const service = serve(runtimeConfig, {
192
200
  ...(values.profile === undefined ? {} : { profileName: values.profile }),
193
201
  ...(storageMigration === undefined ? {} : { storageMigration }),
194
202
  });
@@ -209,9 +217,9 @@ async function main() {
209
217
  gitSha: provenance.gitSha,
210
218
  artifactDigest: provenance.artifactDigest,
211
219
  ...(values.profile === undefined ? {} : { profileName: values.profile }),
212
- agentsRoot: config.agentsRoot,
220
+ agentsRoot: runtimeConfig.agentsRoot,
213
221
  }); // 一次性 run 模式也上报 SLS(runner 里的埋点生效)
214
- const result = await runAgent(config, {
222
+ const result = await runAgent(runtimeConfig, {
215
223
  handle: values.agent,
216
224
  channelId: values.channel,
217
225
  ...(values.wake ? { wake: values.wake } : {}),
@@ -111,8 +111,8 @@ async function ensureWorktreeExclude(mainRoot) {
111
111
  const suffix = current.length === 0 || current.endsWith("\n") ? "" : "\n";
112
112
  await writeFile(excludePath, current + suffix + ".worktrees\n", "utf8");
113
113
  }
114
- async function ensureMainClone(git, repo, repositoryKey, reposRoot) {
115
- const mainRoot = resolve(reposRoot, repositoryKey);
114
+ async function ensureMainClone(git, repo, repositoryKey, directoryName, reposRoot) {
115
+ const mainRoot = resolve(reposRoot, directoryName);
116
116
  if (!isWithin(reposRoot, mainRoot) || await existingAncestorIsSymlink(reposRoot, mainRoot)) {
117
117
  throw new ProjectDependencyError("project_dependency_checkout_conflict", "repository path is unsafe", repositoryKey);
118
118
  }
@@ -123,7 +123,7 @@ async function ensureMainClone(git, repo, repositoryKey, reposRoot) {
123
123
  if (info === null) {
124
124
  await mkdir(dirname(mainRoot), { recursive: true });
125
125
  try {
126
- await git.run(["clone", "--no-checkout", repo, mainRoot]);
126
+ await git.run(["clone", repo, mainRoot]);
127
127
  }
128
128
  catch {
129
129
  await rm(mainRoot, { recursive: true, force: true }).catch(() => { });
@@ -243,7 +243,7 @@ export function createProjectDependencyProvisioner(options = {}) {
243
243
  for (const { identity, projects } of grouped.values()) {
244
244
  try {
245
245
  await withProjectDependencyLock(lockRoot, identity.repositoryKey, async () => {
246
- const mainRoot = await ensureMainClone(git, projects[0].repo, identity.repositoryKey, input.reposRoot);
246
+ const mainRoot = await ensureMainClone(git, projects[0].repo, identity.repositoryKey, identity.directoryName, input.reposRoot);
247
247
  const seenVersions = new Set();
248
248
  for (const project of projects) {
249
249
  const branchSource = project.branch === undefined ? "remote-default" : "manifest";
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { AbilityRepositoryUrlSchema } from "./agent-ability/types.js";
3
3
  const SCP_REPOSITORY = /^git@([^:]+):(.+)$/u;
4
4
  const COMMIT = /^[a-f0-9]{40}$/u;
5
+ const DIRECTORY_HASH_LENGTH = 12;
5
6
  function normalizePath(value) {
6
7
  const path = value.replace(/^\/+|\/+$/gu, "").replace(/\.git$/iu, "");
7
8
  if (!path || path.split("/").some((part) => part.length === 0 || part === "." || part === ".." || part.includes("\\"))) {
@@ -31,6 +32,7 @@ export function normalizeRepositoryIdentity(repository) {
31
32
  canonical,
32
33
  repositoryKey: `repo-${hash}`,
33
34
  displayName,
35
+ directoryName: `${displayName}--${hash.slice(0, DIRECTORY_HASH_LENGTH)}`,
34
36
  });
35
37
  }
36
38
  export function worktreeDirectoryName(branch, resolvedCommit) {
package/dist/runner.js CHANGED
@@ -84,6 +84,8 @@ export async function runAgent(config, input, onActivity = defaultPrint, onConso
84
84
  token: credential.token,
85
85
  agentId: credential.agentId,
86
86
  agentsRoot: config.agentsRoot,
87
+ ...(config.reposRoot === undefined ? {} : { reposRoot: config.reposRoot }),
88
+ ...(config.qaReportRoot === undefined ? {} : { qaReportRoot: config.qaReportRoot }),
87
89
  cliPath: config.cliPath,
88
90
  providerConfig,
89
91
  codexHomeMode: config.codexHomeMode,
@@ -121,6 +121,7 @@ export function storageLayout(input) {
121
121
  agentItemsRoot: paths.join(workspaceRoot, "agent-items"),
122
122
  daemonRoot: paths.join(workspaceRoot, "daemon"),
123
123
  repoRoot: paths.join(workspaceRoot, "repos"),
124
+ qaReportRoot: paths.join(workspaceRoot, "qa-report-server-v2"),
124
125
  suiteToolsRoot: paths.join(workspaceRoot, "suite-tools"),
125
126
  migrationsRoot: paths.join(persistentRoot, "migrations"),
126
127
  runtimeRoot,
@@ -132,7 +133,7 @@ export function agentStorageRoot(layout, agentId) {
132
133
  }
133
134
  const COMPARABLE_FIELDS = [
134
135
  "layoutVersion", "environment", "workspaceId", "persistentRoot", "environmentRoot", "workspaceRoot",
135
- "agentItemsRoot", "daemonRoot", "repoRoot", "suiteToolsRoot", "migrationsRoot", "runtimeRoot",
136
+ "agentItemsRoot", "daemonRoot", "repoRoot", "qaReportRoot", "suiteToolsRoot", "migrationsRoot", "runtimeRoot",
136
137
  ];
137
138
  export function compareStorageLayout(current, expected) {
138
139
  if (current === null || current.layoutVersion !== STORAGE_LAYOUT_VERSION)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowcrew/daemon",
3
- "version": "0.6.77-beta.4",
3
+ "version": "0.6.77",
4
4
  "type": "module",
5
5
  "description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
6
6
  "license": "Apache-2.0",