@nowcrew/daemon 0.6.88 → 0.6.89
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 +2 -1
- package/dist/agent-ability/controller.js +11 -1
- package/dist/agent-ability/git-errors.js +22 -0
- package/dist/agent-ability/resolver.js +12 -4
- package/dist/automatic-daemon-installer.js +2 -1
- package/dist/automatic-install-target.js +8 -0
- package/dist/main.js +0 -0
- package/dist/server-url.js +25 -0
- package/dist/slog.js +2 -1
- package/dist/storage-runtime-readiness.js +2 -1
- package/dist/storage-runtime.js +2 -2
- package/dist/storage-worker-executor.js +2 -4
- package/package.json +10 -9
package/README.md
CHANGED
|
@@ -50,7 +50,8 @@ npx -y --registry https://registry.npmjs.org @nowcrew/daemon@beta install \
|
|
|
50
50
|
--release-tag beta --registry https://registry.npmjs.org
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
The installer selects the fixed
|
|
53
|
+
The installer selects the fixed production layout for both `nowwork.nowcoder.com` (canonical) and
|
|
54
|
+
`nowwork-in.nowcoder.com` (legacy compatibility), and the fixed `dev` layout for
|
|
54
55
|
`nowwork-dev.nowcoder.com`. It compares the parsed hostname exactly; lookalike suffixes are not accepted.
|
|
55
56
|
An unknown hostname is rejected before any write and reports how to retry with an explicit custom profile:
|
|
56
57
|
|
|
@@ -3,7 +3,17 @@ import { resolveAbilityRelease } from "./resolver.js";
|
|
|
3
3
|
export const normalizeAbilityCommandError = (error, fallback) => {
|
|
4
4
|
const message = error instanceof Error ? error.message : fallback;
|
|
5
5
|
const structured = message.match(/^(ability_[a-z0-9_]+)(?::|$)/u)?.[1];
|
|
6
|
-
|
|
6
|
+
if (structured)
|
|
7
|
+
return structured;
|
|
8
|
+
if (error instanceof Error && "code" in error) {
|
|
9
|
+
if (error.code === "ENOSPC")
|
|
10
|
+
return "ability_disk_full";
|
|
11
|
+
if (error.code === "EACCES" || error.code === "EPERM")
|
|
12
|
+
return "ability_filesystem_permission_denied";
|
|
13
|
+
if (error.code === "ENOENT")
|
|
14
|
+
return "ability_local_file_missing";
|
|
15
|
+
}
|
|
16
|
+
return fallback;
|
|
7
17
|
};
|
|
8
18
|
export function createAgentAbilityController(deps) {
|
|
9
19
|
const resolver = deps.resolve ?? resolveAbilityRelease;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Classify Git failures without returning command arguments or raw stderr. */
|
|
2
|
+
export function abilityGitErrorCode(error) {
|
|
3
|
+
if (!(error instanceof Error))
|
|
4
|
+
return "ability_git_failed";
|
|
5
|
+
if ("code" in error && error.code === "ENOENT")
|
|
6
|
+
return "ability_git_unavailable";
|
|
7
|
+
if ("killed" in error && error.killed === true)
|
|
8
|
+
return "ability_git_timeout";
|
|
9
|
+
const stderr = "stderr" in error && typeof error.stderr === "string" ? error.stderr : "";
|
|
10
|
+
if (/no space left on device/i.test(stderr))
|
|
11
|
+
return "ability_disk_full";
|
|
12
|
+
if (/host key verification failed|remote host identification has changed/i.test(stderr)) {
|
|
13
|
+
return "ability_git_host_verification_failed";
|
|
14
|
+
}
|
|
15
|
+
if (/permission denied \(publickey|authentication failed|could not read username|repository not found|access denied/i.test(stderr)) {
|
|
16
|
+
return "ability_git_access_denied";
|
|
17
|
+
}
|
|
18
|
+
if (/could not resolve host|could not resolve hostname|connection timed out|connection refused|network is unreachable|could not connect to server/i.test(stderr)) {
|
|
19
|
+
return "ability_git_network_failed";
|
|
20
|
+
}
|
|
21
|
+
return "ability_git_failed";
|
|
22
|
+
}
|
|
@@ -7,6 +7,7 @@ import { parse } from "yaml";
|
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { abilityWorkspaceProjectionPath, DaemonAbilityLockSchema, DaemonAbilityManifestSchema, matchesAbilityBranch, } from "./types.js";
|
|
9
9
|
import { PROJECT_MANIFEST_SOURCE_PATH, ProjectDependencyManifestSchema, } from "../project-dependency-manifest.js";
|
|
10
|
+
import { abilityGitErrorCode } from "./git-errors.js";
|
|
10
11
|
import { normalizeRepositoryIdentity } from "../repository-identity.js";
|
|
11
12
|
const runFile = promisify(execFile);
|
|
12
13
|
const MAX_FILES = 5_000;
|
|
@@ -42,6 +43,7 @@ const gitEnv = () => ({
|
|
|
42
43
|
GIT_CONFIG_NOSYSTEM: "1",
|
|
43
44
|
GIT_CONFIG_GLOBAL: process.platform === "win32" ? "NUL" : "/dev/null",
|
|
44
45
|
GIT_TERMINAL_PROMPT: "0",
|
|
46
|
+
LC_ALL: "C",
|
|
45
47
|
});
|
|
46
48
|
export const abilitySha256 = (content) => `sha256:${createHash("sha256").update(content).digest("hex")}`;
|
|
47
49
|
export const safeAbilityName = (value) => value.replace(/[^A-Za-z0-9._-]/gu, "_").slice(0, 128);
|
|
@@ -73,10 +75,16 @@ const comparePosixPaths = (left, right) => {
|
|
|
73
75
|
return leftParts.length - rightParts.length;
|
|
74
76
|
};
|
|
75
77
|
const git = async (args, cwd) => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
try {
|
|
79
|
+
const result = await runFile("git", [...GIT_ARGS, ...args], {
|
|
80
|
+
...(cwd ? { cwd } : {}), env: gitEnv(), timeout: 60_000, maxBuffer: 16 * 1024 * 1024,
|
|
81
|
+
});
|
|
82
|
+
return result.stdout.trim();
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
// 仅回传固定错误分类,不把 Git stderr 中的凭据、仓库地址或本地路径发到界面。
|
|
86
|
+
throw new Error(abilityGitErrorCode(error));
|
|
87
|
+
}
|
|
80
88
|
};
|
|
81
89
|
export const parseAbilityRefLine = (line) => {
|
|
82
90
|
const [branch, commit, rawTime, ...extra] = line.split("\t");
|
|
@@ -14,6 +14,7 @@ import { daemonRegistryCandidates } from "./daemon-registry.js";
|
|
|
14
14
|
import { npmInvocation } from "./npm-command.js";
|
|
15
15
|
import { discoverExactLegacyService, } from "./automatic-service-migration.js";
|
|
16
16
|
import { resolveDaemonLocalStateRootFromEnv } from "./daemon-local-home.js";
|
|
17
|
+
import { serverHostIdentity } from "./server-url.js";
|
|
17
18
|
function profileStorage(platform, userHome, runner) {
|
|
18
19
|
return {
|
|
19
20
|
platform,
|
|
@@ -27,7 +28,7 @@ function profileStorage(platform, userHome, runner) {
|
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
30
|
function sameHostname(left, right) {
|
|
30
|
-
return
|
|
31
|
+
return serverHostIdentity(left) === serverHostIdentity(right);
|
|
31
32
|
}
|
|
32
33
|
function localHomeForTarget(target, platform) {
|
|
33
34
|
const paths = platform === "win32" ? win32 : posix;
|
|
@@ -4,6 +4,14 @@ import { normalizeDaemonNpmRegistry } from "./npm-registry.js";
|
|
|
4
4
|
import { resolveDaemonLocalHome } from "./daemon-local-home.js";
|
|
5
5
|
const PROFILE_NAME = /^[a-z0-9][a-z0-9_-]{0,47}$/;
|
|
6
6
|
const KNOWN_TARGETS = {
|
|
7
|
+
// Canonical production hostname. Keep the internal hostname below as a
|
|
8
|
+
// compatibility alias so existing profiles and service identities continue
|
|
9
|
+
// to work when users move between the two URLs.
|
|
10
|
+
"nowwork.nowcoder.com": {
|
|
11
|
+
environment: "production",
|
|
12
|
+
daemonDirectory: "daemon",
|
|
13
|
+
agentsDirectory: "agents",
|
|
14
|
+
},
|
|
7
15
|
"nowwork-in.nowcoder.com": {
|
|
8
16
|
environment: "production",
|
|
9
17
|
daemonDirectory: "daemon",
|
package/dist/main.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const CANONICAL_PRODUCTION_HOST = "nowwork.nowcoder.com";
|
|
2
|
+
const PRODUCTION_HOST_ALIASES = new Set([
|
|
3
|
+
CANONICAL_PRODUCTION_HOST,
|
|
4
|
+
"nowwork-in.nowcoder.com",
|
|
5
|
+
]);
|
|
6
|
+
function canonicalHostname(hostname) {
|
|
7
|
+
const normalized = hostname.toLowerCase();
|
|
8
|
+
return isProductionServerHostname(normalized) ? CANONICAL_PRODUCTION_HOST : normalized;
|
|
9
|
+
}
|
|
10
|
+
export function isProductionServerHostname(hostname) {
|
|
11
|
+
return PRODUCTION_HOST_ALIASES.has(hostname.toLowerCase());
|
|
12
|
+
}
|
|
13
|
+
/** Host identity used by profile/service guards; the public and internal production names are aliases. */
|
|
14
|
+
export function serverHostIdentity(serverUrl) {
|
|
15
|
+
return canonicalHostname(new URL(serverUrl).hostname);
|
|
16
|
+
}
|
|
17
|
+
/** URL identity used by storage records; preserve path/port/query while collapsing production host aliases. */
|
|
18
|
+
export function serverUrlIdentity(serverUrl) {
|
|
19
|
+
const parsed = new URL(serverUrl);
|
|
20
|
+
parsed.hostname = canonicalHostname(parsed.hostname);
|
|
21
|
+
return parsed.toString().replace(/\/+$/u, "");
|
|
22
|
+
}
|
|
23
|
+
export function serverUrlsMatch(left, right) {
|
|
24
|
+
return serverUrlIdentity(left) === serverUrlIdentity(right);
|
|
25
|
+
}
|
package/dist/slog.js
CHANGED
|
@@ -16,6 +16,7 @@ import { join, resolve } from "node:path";
|
|
|
16
16
|
import { createHash } from "node:crypto";
|
|
17
17
|
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync } from "node:fs";
|
|
18
18
|
import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
19
|
+
import { isProductionServerHostname } from "./server-url.js";
|
|
19
20
|
const FLUSH_INTERVAL_MS = 1500;
|
|
20
21
|
const BATCH_SIZE = 50;
|
|
21
22
|
const SPOOL_MAX_FILES = 50; // spool 目录文件数上限,超出丢最旧(日志非关键数据,防无限膨胀)
|
|
@@ -30,7 +31,7 @@ let lastErrorAt = 0;
|
|
|
30
31
|
let exitHookInstalled = false;
|
|
31
32
|
let localStateRoot;
|
|
32
33
|
function deploymentEnvironment(serverHost) {
|
|
33
|
-
if (serverHost
|
|
34
|
+
if (serverHost !== undefined && isProductionServerHostname(serverHost))
|
|
34
35
|
return "prod";
|
|
35
36
|
if (serverHost === "nowwork-dev.nowcoder.com")
|
|
36
37
|
return "dev";
|
|
@@ -12,6 +12,7 @@ import { createStorageMigrationRuntimeController, } from "./storage-runtime-cont
|
|
|
12
12
|
import { resolveEnvTag, resolveNowworkHome } from "./storage-layout.js";
|
|
13
13
|
import { readStorageOwnershipClaim } from "./storage-ownership.js";
|
|
14
14
|
import { parseDaemonLocalHomeMode, resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
15
|
+
import { isProductionServerHostname } from "./server-url.js";
|
|
15
16
|
const notReady = (reason) => Object.freeze({ ready: false, reason });
|
|
16
17
|
function canonicalPath(value) {
|
|
17
18
|
const absolute = resolve(value);
|
|
@@ -50,7 +51,7 @@ function environmentMatchesServer(serverUrl, environment) {
|
|
|
50
51
|
const hostname = new URL(serverUrl).hostname.toLowerCase();
|
|
51
52
|
if (hostname === "nowwork-dev.nowcoder.com")
|
|
52
53
|
return environment === "dev";
|
|
53
|
-
if (hostname
|
|
54
|
+
if (isProductionServerHostname(hostname))
|
|
54
55
|
return environment === "production";
|
|
55
56
|
return true;
|
|
56
57
|
}
|
package/dist/storage-runtime.js
CHANGED
|
@@ -2,12 +2,12 @@ import { findInstallationForProfile, readInstallations, resolveLocalStorageRoots
|
|
|
2
2
|
import { storageHelloIdentity } from "./storage-identity.js";
|
|
3
3
|
import { storageLayout } from "./storage-layout.js";
|
|
4
4
|
import { readStorageOwnershipClaim, resolveWorkspaceStorageOwnershipClaim, } from "./storage-ownership.js";
|
|
5
|
-
|
|
5
|
+
import { serverUrlsMatch } from "./server-url.js";
|
|
6
6
|
export function resolveStorageRuntime(input) {
|
|
7
7
|
const installation = findInstallationForProfile(input.records, input.profile);
|
|
8
8
|
if (installation === null)
|
|
9
9
|
return null;
|
|
10
|
-
if (
|
|
10
|
+
if (!serverUrlsMatch(installation.serverUrl, input.serverUrl)) {
|
|
11
11
|
throw new Error("installation server identity does not match the active profile");
|
|
12
12
|
}
|
|
13
13
|
const local = resolveLocalStorageRoots({
|
|
@@ -13,6 +13,7 @@ import { readStorageWorkerOperation } from "./storage-worker-operation.js";
|
|
|
13
13
|
import { storageIdFingerprint } from "./storage-identity.js";
|
|
14
14
|
import { waitForStorageMigrationAcknowledgement } from "./storage-target-confirmation.js";
|
|
15
15
|
import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
16
|
+
import { serverUrlsMatch } from "./server-url.js";
|
|
16
17
|
const defaultServiceDependencies = () => ({
|
|
17
18
|
assertManagedServiceIdentity,
|
|
18
19
|
serviceAction,
|
|
@@ -70,9 +71,6 @@ function sameInstallation(left, right) {
|
|
|
70
71
|
function sameProfile(left, right) {
|
|
71
72
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
72
73
|
}
|
|
73
|
-
function canonicalServerUrl(value) {
|
|
74
|
-
return value.replace(/\/+$/u, "");
|
|
75
|
-
}
|
|
76
74
|
export function createStorageWorkerServiceLifecycle(operation, injected = {}) {
|
|
77
75
|
const dependencies = { ...defaultServiceDependencies(), ...injected };
|
|
78
76
|
const sourceLocalHome = resolveDaemonLocalStateRoot({
|
|
@@ -137,7 +135,7 @@ export function createStorageWorkerServiceLifecycle(operation, injected = {}) {
|
|
|
137
135
|
throw new Error(`Target service '${targetSpec.id}' already exists`);
|
|
138
136
|
sourceProfile = await dependencies.loadProfile(operation.profile, sourceRecord.daemonHome);
|
|
139
137
|
if (sourceProfile.name !== operation.profile
|
|
140
|
-
||
|
|
138
|
+
|| !serverUrlsMatch(sourceProfile.serverUrl, operation.installation.target.serverUrl)) {
|
|
141
139
|
throw new Error("Source profile does not match target installation identity");
|
|
142
140
|
}
|
|
143
141
|
baselineInstallations = await assertTargetMetadataUnchanged();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nowcrew/daemon",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.89",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -16,6 +16,14 @@
|
|
|
16
16
|
"publishConfig": {
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
|
|
21
|
+
"build": "tsc -p tsconfig.json",
|
|
22
|
+
"codex-home:migrate": "tsx src/runtimes/codex-home-migration-cli.ts",
|
|
23
|
+
"prepublishOnly": "pnpm build && node ../scripts/daemon-release-artifact.mjs --strict-registry",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"typecheck": "tsc --noEmit"
|
|
26
|
+
},
|
|
19
27
|
"dependencies": {
|
|
20
28
|
"@agentclientprotocol/sdk": "1.2.1",
|
|
21
29
|
"@nowcrew/cli": "0.4.16",
|
|
@@ -36,12 +44,5 @@
|
|
|
36
44
|
"tsx": "^4.19.0",
|
|
37
45
|
"typescript": "^5.6.0",
|
|
38
46
|
"vitest": "^2.1.0"
|
|
39
|
-
},
|
|
40
|
-
"scripts": {
|
|
41
|
-
"daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
|
|
42
|
-
"build": "tsc -p tsconfig.json",
|
|
43
|
-
"codex-home:migrate": "tsx src/runtimes/codex-home-migration-cli.ts",
|
|
44
|
-
"test": "vitest run",
|
|
45
|
-
"typecheck": "tsc --noEmit"
|
|
46
47
|
}
|
|
47
|
-
}
|
|
48
|
+
}
|