@nowcrew/daemon 0.6.85 → 0.6.87-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/dist/agent-ability/resolver.js +29 -12
- package/dist/cloud-report-upload.js +50 -0
- package/dist/cloud-report-uploader.js +205 -0
- package/dist/local-executor.js +19 -12
- package/dist/main.js +0 -0
- package/dist/storage-worker-executor.js +1 -1
- package/package.json +9 -10
|
@@ -78,6 +78,28 @@ const git = async (args, cwd) => {
|
|
|
78
78
|
});
|
|
79
79
|
return result.stdout.trim();
|
|
80
80
|
};
|
|
81
|
+
export const parseAbilityRefLine = (line) => {
|
|
82
|
+
const [branch, commit, rawTime, ...extra] = line.split("\t");
|
|
83
|
+
const time = Number(rawTime);
|
|
84
|
+
if (!branch || !commit || extra.length > 0 || !Number.isSafeInteger(time)) {
|
|
85
|
+
throw new Error("ability_release_ref_metadata_invalid");
|
|
86
|
+
}
|
|
87
|
+
return { branch, commit, time };
|
|
88
|
+
};
|
|
89
|
+
export const selectAbilityRef = (lines, branchGlob, desiredCommit) => {
|
|
90
|
+
const refs = lines.filter(Boolean).map(parseAbilityRefLine)
|
|
91
|
+
.filter((candidate) => matchesAbilityBranch(branchGlob, candidate.branch));
|
|
92
|
+
if (refs.length === 0)
|
|
93
|
+
throw new Error("ability_release_branch_not_found");
|
|
94
|
+
const selected = refs.filter((candidate) => desiredCommit === undefined || candidate.commit === desiredCommit)
|
|
95
|
+
.sort((left, right) => right.time - left.time || right.branch.localeCompare(left.branch))[0];
|
|
96
|
+
if (!selected)
|
|
97
|
+
throw new Error("ability_desired_commit_not_found");
|
|
98
|
+
const committedDate = new Date(selected.time * 1_000);
|
|
99
|
+
return Number.isNaN(committedDate.getTime())
|
|
100
|
+
? selected
|
|
101
|
+
: { ...selected, committedAt: committedDate.toISOString() };
|
|
102
|
+
};
|
|
81
103
|
export const projectDependencyIds = (files, manifest) => {
|
|
82
104
|
const path = manifest.spec.git?.projectDependencies.path;
|
|
83
105
|
if (path === undefined)
|
|
@@ -209,17 +231,10 @@ const resolveRoot = async (cacheRoot, repositoryUrl, branchGlob, desiredCommit)
|
|
|
209
231
|
else
|
|
210
232
|
await git(["--git-dir", bare, "remote", "set-url", "origin", repositoryUrl]);
|
|
211
233
|
await git(["--git-dir", bare, "fetch", "--no-tags", "--prune", "origin", "+refs/heads/*:refs/remotes/origin/*"]);
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}).filter((candidate) => matchesAbilityBranch(branchGlob, candidate.branch));
|
|
217
|
-
if (refs.length === 0)
|
|
218
|
-
throw new Error("ability_release_branch_not_found");
|
|
219
|
-
const selected = refs.filter((candidate) => desiredCommit === undefined || candidate.commit === desiredCommit)
|
|
220
|
-
.sort((left, right) => right.time - left.time || right.branch.localeCompare(left.branch))[0];
|
|
221
|
-
if (!selected)
|
|
222
|
-
throw new Error("ability_desired_commit_not_found");
|
|
234
|
+
const selected = selectAbilityRef((await git([
|
|
235
|
+
"--git-dir", bare, "for-each-ref",
|
|
236
|
+
"--format=%(refname:strip=3)\t%(objectname)\t%(committerdate:unix)", "refs/remotes/origin",
|
|
237
|
+
])).split("\n"), branchGlob, desiredCommit);
|
|
223
238
|
const root = join(cacheRoot, "artifacts", repositoryKey, selected.commit);
|
|
224
239
|
try {
|
|
225
240
|
await lstat(root);
|
|
@@ -440,7 +455,9 @@ export async function resolveAbilityRelease(agentsRoot, command) {
|
|
|
440
455
|
}
|
|
441
456
|
const projectDependencies = projectDependencyIds(files, manifest);
|
|
442
457
|
return {
|
|
443
|
-
branch: resolved.branch, rootCommit: resolved.commit,
|
|
458
|
+
branch: resolved.branch, rootCommit: resolved.commit,
|
|
459
|
+
...(resolved.committedAt ? { committedAt: resolved.committedAt } : {}),
|
|
460
|
+
treeDigest: resolved.treeDigest,
|
|
444
461
|
artifactDigest, schemaVersion: manifest.apiVersion, manifest, lock, assetSummary,
|
|
445
462
|
scanSummary: { passed: security.findings.length === 0, blockingFindings: security.findings.length, findings: security.findings.slice(0, 100) },
|
|
446
463
|
evalSummary: { passed: assertions.every((assertion) => assertion.passed), assertions },
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir, hostname, platform } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const ALIYUN_MARKER = /alibaba|aliyun|alicloud/iu;
|
|
5
|
+
function readMarker(path) {
|
|
6
|
+
try {
|
|
7
|
+
return readFileSync(path, "utf8");
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return "";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function detectCloudProvider(env = process.env, platformName = platform(), hostName = hostname(), readMarkerValue = readMarker) {
|
|
14
|
+
const explicit = env.CREW_CLOUD_PROVIDER?.trim().toLowerCase();
|
|
15
|
+
if (explicit === "aliyun" || explicit === "alibaba" || explicit === "alicloud")
|
|
16
|
+
return "aliyun";
|
|
17
|
+
if (explicit !== undefined && explicit !== "")
|
|
18
|
+
return "unknown";
|
|
19
|
+
if (ALIYUN_MARKER.test(hostName))
|
|
20
|
+
return "aliyun";
|
|
21
|
+
if (platformName !== "linux")
|
|
22
|
+
return "unknown";
|
|
23
|
+
const markers = [
|
|
24
|
+
"/sys/class/dmi/id/sys_vendor",
|
|
25
|
+
"/sys/class/dmi/id/product_name",
|
|
26
|
+
"/sys/class/dmi/id/board_vendor",
|
|
27
|
+
];
|
|
28
|
+
return markers.some((path) => ALIYUN_MARKER.test(readMarkerValue(path))) ? "aliyun" : "unknown";
|
|
29
|
+
}
|
|
30
|
+
export function findOssUploadScript(env = process.env, exists = existsSync) {
|
|
31
|
+
const explicit = env.CREW_OSS_UPLOAD_SCRIPT?.trim();
|
|
32
|
+
if (explicit)
|
|
33
|
+
return exists(explicit) ? explicit : undefined;
|
|
34
|
+
const home = env.HOME?.trim() || homedir();
|
|
35
|
+
const candidates = [
|
|
36
|
+
join(home, ".claude", "skills", "oss-upload", "scripts", "oss_upload.py"),
|
|
37
|
+
join(home, ".codex", "skills", "oss-upload", "scripts", "oss_upload.py"),
|
|
38
|
+
join(home, ".agents", "skills", "oss-upload", "scripts", "oss_upload.py"),
|
|
39
|
+
];
|
|
40
|
+
return candidates.find(exists);
|
|
41
|
+
}
|
|
42
|
+
export function cloudReportUploadFacts(env = process.env, platformName = platform(), hostName = hostname(), readMarkerValue, exists) {
|
|
43
|
+
const provider = detectCloudProvider(env, platformName, hostName, readMarkerValue);
|
|
44
|
+
const ossUploadScript = provider === "aliyun" ? findOssUploadScript(env, exists) : undefined;
|
|
45
|
+
return Object.freeze({
|
|
46
|
+
provider,
|
|
47
|
+
...(ossUploadScript === undefined ? {} : { ossUploadScript }),
|
|
48
|
+
enabled: provider === "aliyun" && ossUploadScript !== undefined,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { lstat, readdir } from "node:fs/promises";
|
|
3
|
+
import { extname, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { cloudReportUploadFacts } from "./cloud-report-upload.js";
|
|
6
|
+
import { dslog } from "./slog.js";
|
|
7
|
+
const execFileP = promisify(execFile);
|
|
8
|
+
const REPORT_FILENAMES = new Set(["report.md", "result.json"]);
|
|
9
|
+
const MAX_DEPTH = 8;
|
|
10
|
+
const MAX_FILES = 100;
|
|
11
|
+
const OSS_URL_HOST = "ai-claw-in.nowcoder.com";
|
|
12
|
+
const fileFingerprint = async (path) => {
|
|
13
|
+
const stat = await lstat(path);
|
|
14
|
+
return `${stat.size}:${stat.mtimeMs}`;
|
|
15
|
+
};
|
|
16
|
+
function isReportFile(root, path) {
|
|
17
|
+
const parts = relative(root, path).split(sep).filter(Boolean);
|
|
18
|
+
const filename = parts.at(-1) ?? "";
|
|
19
|
+
if (parts[0] === "tasks" && parts.length >= 3) {
|
|
20
|
+
return REPORT_FILENAMES.has(filename) || extname(filename).toLowerCase() === ".html";
|
|
21
|
+
}
|
|
22
|
+
if (parts[0] !== "qa-report-server-v2")
|
|
23
|
+
return false;
|
|
24
|
+
if (parts[1] === "static" && parts.length === 3)
|
|
25
|
+
return extname(filename).toLowerCase() === ".html";
|
|
26
|
+
if (parts[1] === "qa-results" && parts.length >= 4) {
|
|
27
|
+
return REPORT_FILENAMES.has(filename) || extname(filename).toLowerCase() === ".html";
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
async function walkReports(root, directory, depth, output) {
|
|
32
|
+
if (depth > MAX_DEPTH || output.length >= MAX_FILES)
|
|
33
|
+
return;
|
|
34
|
+
const entries = await readdir(directory, { withFileTypes: true }).catch(() => []);
|
|
35
|
+
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
36
|
+
if (output.length >= MAX_FILES)
|
|
37
|
+
return;
|
|
38
|
+
if (entry.name === ".git" || entry.name === "node_modules" || entry.name === ".home")
|
|
39
|
+
continue;
|
|
40
|
+
const path = join(directory, entry.name);
|
|
41
|
+
if (entry.isDirectory()) {
|
|
42
|
+
await walkReports(root, path, depth + 1, output);
|
|
43
|
+
}
|
|
44
|
+
else if (entry.isFile() && isReportFile(root, path)) {
|
|
45
|
+
output.push(resolve(path));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export async function snapshotReportFiles(root) {
|
|
50
|
+
const files = [];
|
|
51
|
+
const workspaceRoot = resolve(root);
|
|
52
|
+
for (const directory of [
|
|
53
|
+
join(workspaceRoot, "tasks"),
|
|
54
|
+
join(workspaceRoot, "qa-report-server-v2", "qa-results"),
|
|
55
|
+
join(workspaceRoot, "qa-report-server-v2", "static"),
|
|
56
|
+
]) {
|
|
57
|
+
await walkReports(workspaceRoot, directory, 0, files);
|
|
58
|
+
}
|
|
59
|
+
const entries = await Promise.all(files.map(async (path) => [path, await fileFingerprint(path)]));
|
|
60
|
+
return new Map(entries);
|
|
61
|
+
}
|
|
62
|
+
export async function discoverReportFiles(root, before) {
|
|
63
|
+
const after = await snapshotReportFiles(root);
|
|
64
|
+
return [...after.entries()]
|
|
65
|
+
.filter(([path, fingerprint]) => before.get(path) !== fingerprint)
|
|
66
|
+
.map(([path]) => path)
|
|
67
|
+
.sort((left, right) => left.localeCompare(right));
|
|
68
|
+
}
|
|
69
|
+
function validOssUrl(value) {
|
|
70
|
+
if (typeof value !== "string")
|
|
71
|
+
return false;
|
|
72
|
+
try {
|
|
73
|
+
const url = new URL(value);
|
|
74
|
+
return url.protocol === "https:"
|
|
75
|
+
&& url.hostname === OSS_URL_HOST
|
|
76
|
+
&& !url.username && !url.password && !url.search && !url.hash
|
|
77
|
+
&& url.pathname.length > 1;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function verifyOssUrl(url) {
|
|
84
|
+
try {
|
|
85
|
+
const head = await fetch(url, { method: "HEAD", redirect: "error" });
|
|
86
|
+
if (head.ok)
|
|
87
|
+
return true;
|
|
88
|
+
const ranged = await fetch(url, {
|
|
89
|
+
method: "GET",
|
|
90
|
+
headers: { range: "bytes=0-0" },
|
|
91
|
+
redirect: "error",
|
|
92
|
+
});
|
|
93
|
+
return ranged.ok;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export async function uploadCloudReports(input) {
|
|
100
|
+
const files = await discoverReportFiles(input.root, input.before);
|
|
101
|
+
if (files.length === 0)
|
|
102
|
+
return { urls: [], failures: [] };
|
|
103
|
+
const run = input.execFile ?? (async (file, args) => {
|
|
104
|
+
const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => (key === "PATH" || key === "HOME" || key.startsWith("CLAUDE_OSS_"))));
|
|
105
|
+
const result = await execFileP(file, [...args], {
|
|
106
|
+
env, maxBuffer: 2 * 1024 * 1024, timeout: 300_000,
|
|
107
|
+
});
|
|
108
|
+
return { stdout: result.stdout, stderr: result.stderr };
|
|
109
|
+
});
|
|
110
|
+
const result = await run("python3", [input.script, ...files, "--prefix", "nowcrew/reports", "--json"]);
|
|
111
|
+
let records;
|
|
112
|
+
try {
|
|
113
|
+
records = JSON.parse(result.stdout);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
throw new Error("cloud_report_upload_invalid_json");
|
|
117
|
+
}
|
|
118
|
+
if (!Array.isArray(records))
|
|
119
|
+
throw new Error("cloud_report_upload_invalid_result");
|
|
120
|
+
const urls = [];
|
|
121
|
+
const failures = [];
|
|
122
|
+
const verify = input.verifyUrl ?? verifyOssUrl;
|
|
123
|
+
for (const file of files) {
|
|
124
|
+
const record = records.find((candidate) => (candidate && typeof candidate === "object" && "file" in candidate && candidate.file === file));
|
|
125
|
+
const url = record && typeof record === "object" && "url" in record ? record.url : undefined;
|
|
126
|
+
const success = record && typeof record === "object" && "success" in record && record.success === true;
|
|
127
|
+
if (success && validOssUrl(url) && await verify(url))
|
|
128
|
+
urls.push(url);
|
|
129
|
+
else
|
|
130
|
+
failures.push(file);
|
|
131
|
+
}
|
|
132
|
+
return { urls, failures };
|
|
133
|
+
}
|
|
134
|
+
export async function prepareCloudReportSnapshot(facts, root) {
|
|
135
|
+
if (!facts.enabled)
|
|
136
|
+
return null;
|
|
137
|
+
return snapshotReportFiles(root);
|
|
138
|
+
}
|
|
139
|
+
export async function prepareCloudReportExecution(input) {
|
|
140
|
+
const facts = cloudReportUploadFacts(input.env);
|
|
141
|
+
if (facts.provider !== "aliyun")
|
|
142
|
+
return { facts, snapshot: null };
|
|
143
|
+
if (facts.ossUploadScript === undefined) {
|
|
144
|
+
dslog("cloud_report_upload.unavailable", "阿里云机器未找到 OSS 上传脚本", {
|
|
145
|
+
level: "WARN", execution_id: input.executionId, agent_handle: input.agentHandle,
|
|
146
|
+
});
|
|
147
|
+
return { facts, snapshot: null };
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
return { facts, snapshot: await prepareCloudReportSnapshot(facts, input.root) };
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
dslog("cloud_report_upload.snapshot_failed", "报告文件快照失败", {
|
|
154
|
+
level: "WARN",
|
|
155
|
+
execution_id: input.executionId,
|
|
156
|
+
agent_handle: input.agentHandle,
|
|
157
|
+
error_message: error instanceof Error ? error.message.slice(0, 500) : String(error).slice(0, 500),
|
|
158
|
+
});
|
|
159
|
+
return { facts, snapshot: null };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
export function appendReportLinks(value, urls) {
|
|
163
|
+
const unique = [...new Set(urls)].filter(Boolean);
|
|
164
|
+
if (unique.length === 0)
|
|
165
|
+
return value;
|
|
166
|
+
const suffix = `\n\n报告链接:\n${unique.map((url) => `- ${url}`).join("\n")}`;
|
|
167
|
+
const closeAt = value.lastIndexOf("</nowwork_external_answer>");
|
|
168
|
+
return closeAt >= 0
|
|
169
|
+
? `${value.slice(0, closeAt)}${suffix}${value.slice(closeAt)}`
|
|
170
|
+
: `${value}${suffix}`;
|
|
171
|
+
}
|
|
172
|
+
export async function deliverCloudReports(input) {
|
|
173
|
+
if (!input.facts.enabled || input.facts.ossUploadScript === undefined || input.before === null) {
|
|
174
|
+
return { finalText: input.finalText, externalAnswer: input.externalAnswer };
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
const result = await (input.upload ?? uploadCloudReports)({
|
|
178
|
+
script: input.facts.ossUploadScript,
|
|
179
|
+
root: input.root,
|
|
180
|
+
before: input.before,
|
|
181
|
+
});
|
|
182
|
+
if (result.failures.length > 0) {
|
|
183
|
+
dslog("cloud_report_upload.partial_failure", "部分报告文件上传失败", {
|
|
184
|
+
level: "WARN", execution_id: input.executionId, agent_handle: input.agentHandle,
|
|
185
|
+
uploaded_count: result.urls.length, failed_count: result.failures.length,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
finalText: input.captureFinal && result.urls.length > 0
|
|
190
|
+
? appendReportLinks(input.finalText ?? "", result.urls)
|
|
191
|
+
: input.finalText,
|
|
192
|
+
externalAnswer: input.captureFinal && result.urls.length > 0
|
|
193
|
+
? appendReportLinks(input.externalAnswer ?? "", result.urls)
|
|
194
|
+
: input.externalAnswer,
|
|
195
|
+
result,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
const message = error instanceof Error ? error.message.slice(0, 500) : String(error).slice(0, 500);
|
|
200
|
+
dslog("cloud_report_upload.failed", "报告上传失败", {
|
|
201
|
+
level: "WARN", execution_id: input.executionId, agent_handle: input.agentHandle, error_message: message,
|
|
202
|
+
});
|
|
203
|
+
return { finalText: input.finalText, externalAnswer: input.externalAnswer };
|
|
204
|
+
}
|
|
205
|
+
}
|
package/dist/local-executor.js
CHANGED
|
@@ -11,6 +11,7 @@ import { materializePrivateCodexHome } from "./codex-home-launch.js";
|
|
|
11
11
|
import { spawnKimi, KIMI_EFFORT_LEVELS } from "./runtimes/kimi.js";
|
|
12
12
|
import { applyDeepSeekHarnessMachineEnv, applyProviderEnv, providerFingerprint, } from "./provider-env.js";
|
|
13
13
|
import { augmentedPath } from "./runtime-path.js";
|
|
14
|
+
import { deliverCloudReports, prepareCloudReportExecution } from "./cloud-report-uploader.js";
|
|
14
15
|
import { extractFinalText, extractRunMeta, normalizeEvent, parseLine, } from "./normalize.js";
|
|
15
16
|
import { readSession, writeSession, pickResumeId, isNearBudget } from "./session.js";
|
|
16
17
|
import { providerErrorFromExit } from "./provider-error.js";
|
|
@@ -252,8 +253,10 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
252
253
|
let executionWorkspace = workspace;
|
|
253
254
|
let localMemoryTelemetry = null;
|
|
254
255
|
const inheritedEnv = { ...process.env };
|
|
256
|
+
const cloudReportPreparation = await prepareCloudReportExecution({ env: inheritedEnv, root: workspace.dir, executionId: input.executionId, agentHandle: input.handle });
|
|
257
|
+
const { facts: cloudReportUpload, snapshot: reportSnapshot } = cloudReportPreparation;
|
|
255
258
|
for (const key of Object.keys(inheritedEnv)) {
|
|
256
|
-
if (key.startsWith("CREW_AGENT_MEMORY_"))
|
|
259
|
+
if (key.startsWith("CREW_AGENT_MEMORY_") || ["CREW_CLOUD_PROVIDER", "CREW_OSS_UPLOAD_SCRIPT", "CREW_OSS_REPORT_UPLOAD"].includes(key))
|
|
257
260
|
delete inheritedEnv[key];
|
|
258
261
|
}
|
|
259
262
|
const sourceCodexHome = inheritedEnv.CODEX_HOME
|
|
@@ -510,6 +513,8 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
510
513
|
inherited_qa_runtime_root_present: inheritedEnv.QA_RUNTIME_ROOT !== undefined,
|
|
511
514
|
...packageManagerEnvFacts(childEnv),
|
|
512
515
|
training_root: join(crewHome, "training"),
|
|
516
|
+
cloud_provider: cloudReportUpload.provider,
|
|
517
|
+
oss_report_upload_enabled: cloudReportUpload.enabled,
|
|
513
518
|
project_context_present: input.projectContext !== undefined,
|
|
514
519
|
project_ids: JSON.stringify(input.projectContext === undefined
|
|
515
520
|
? []
|
|
@@ -983,6 +988,16 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
983
988
|
await writeFile(legacyCwdMarker, "1", "utf8");
|
|
984
989
|
}
|
|
985
990
|
}
|
|
991
|
+
const cloudReportResult = exitCode === 0
|
|
992
|
+
? await deliverCloudReports({
|
|
993
|
+
facts: cloudReportUpload, root: workspace.dir, before: reportSnapshot, finalText,
|
|
994
|
+
externalAnswer: externalOutput.lastClosedBlock
|
|
995
|
+
?? (finalText === null ? null : extractExternalAnswer(finalText)),
|
|
996
|
+
captureFinal: input.captureFinal, executionId: input.executionId, agentHandle: input.handle,
|
|
997
|
+
...(dependencies.uploadCloudReports === undefined ? {} : { upload: dependencies.uploadCloudReports }),
|
|
998
|
+
})
|
|
999
|
+
: { finalText, externalAnswer: externalOutput.lastClosedBlock
|
|
1000
|
+
?? (finalText === null ? null : extractExternalAnswer(finalText)) };
|
|
986
1001
|
memoryPruneExecutorCompleted = true;
|
|
987
1002
|
localMemoryTelemetry?.markExecutorCompleted();
|
|
988
1003
|
return {
|
|
@@ -997,18 +1012,10 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
997
1012
|
sessionId,
|
|
998
1013
|
errorMessage: errorTail || null,
|
|
999
1014
|
...(providerError === undefined ? {} : { providerError }),
|
|
1000
|
-
finalText: input.captureFinal && finalText !== null
|
|
1001
|
-
? stripExternalAnswerMarkers(finalText)
|
|
1002
|
-
: null,
|
|
1003
|
-
// 契约答案 = 整轮最后一个闭合的标签块(六个 runtime 语义一致的"后到者胜",半截块与
|
|
1004
|
-
// 叙述里的开标签不算数);从未闭合过时才回落到对 finalText 的宽容提取,保住"收尾被
|
|
1005
|
-
// 截断"这一老兜底。注意这放宽了"externalAnswer 非空
|
|
1006
|
-
// ⇒ finalText 非空"的旧不变式;是否上报仍由 execution-runner 按 exitCode===0 把关。
|
|
1007
|
-
externalAnswer: input.captureFinal
|
|
1008
|
-
? (externalOutput.lastClosedBlock !== null
|
|
1009
|
-
? externalOutput.lastClosedBlock
|
|
1010
|
-
: finalText !== null ? extractExternalAnswer(finalText) : null)
|
|
1015
|
+
finalText: input.captureFinal && cloudReportResult.finalText !== null
|
|
1016
|
+
? stripExternalAnswerMarkers(cloudReportResult.finalText)
|
|
1011
1017
|
: null,
|
|
1018
|
+
externalAnswer: input.captureFinal ? cloudReportResult.externalAnswer : null,
|
|
1012
1019
|
sentViaCrew,
|
|
1013
1020
|
};
|
|
1014
1021
|
}
|
package/dist/main.js
CHANGED
|
File without changes
|
|
@@ -95,7 +95,7 @@ export function createStorageWorkerServiceLifecycle(operation, injected = {}) {
|
|
|
95
95
|
const targetRegistry = { userHome: operation.service.userHome, localHome: targetLocalHome };
|
|
96
96
|
const sourceRecord = operation.service.sourceRecord;
|
|
97
97
|
const targetRecord = operation.service.targetRecord;
|
|
98
|
-
const sourceSpec = serviceSpec(operation, sourceRecord, operation.migration.sourceRoots["daemon-home"]
|
|
98
|
+
const sourceSpec = serviceSpec(operation, sourceRecord, operation.migration.sourceRoots["daemon-home"]);
|
|
99
99
|
const targetSpec = serviceSpec(operation, targetRecord, operation.installation.configRoot);
|
|
100
100
|
assertRecordMatchesSpec(sourceRecord, sourceSpec);
|
|
101
101
|
assertRecordMatchesSpec(targetRecord, targetSpec);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nowcrew/daemon",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.87-beta.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -16,14 +16,6 @@
|
|
|
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
|
-
},
|
|
27
19
|
"dependencies": {
|
|
28
20
|
"@agentclientprotocol/sdk": "1.2.1",
|
|
29
21
|
"@nowcrew/cli": "0.4.13",
|
|
@@ -44,5 +36,12 @@
|
|
|
44
36
|
"tsx": "^4.19.0",
|
|
45
37
|
"typescript": "^5.6.0",
|
|
46
38
|
"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"
|
|
47
46
|
}
|
|
48
|
-
}
|
|
47
|
+
}
|