@nowcrew/daemon 0.6.67 → 0.6.69
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/materializer.js +10 -0
- package/dist/agent-ability/resolver.js +20 -6
- package/dist/agent-ability/types.js +6 -11
- package/dist/background-job.js +50 -0
- package/dist/console-formatter.js +76 -1
- package/dist/console.js +23 -4
- package/dist/execution-runner.js +55 -3
- package/dist/i18n.js +2 -0
- package/dist/local-execution-prompt.js +17 -2
- package/dist/local-executor.js +14 -2
- package/dist/project-dependencies.js +139 -124
- package/dist/project-dependency-lock.js +87 -0
- package/dist/project-dependency-manifest.js +2 -12
- package/dist/project-registry-wiring.js +1 -4
- package/dist/project-skills/runtime-launch.js +10 -7
- package/dist/repository-identity.js +45 -0
- package/dist/storage-copy.js +27 -8
- package/dist/storage-migration-controller.js +4 -1
- package/dist/storage-worker-command.js +11 -1
- package/dist/storage-worker-executor.js +48 -2
- package/dist/storage-worker-status.js +39 -12
- package/package.json +1 -1
|
@@ -470,6 +470,7 @@ const materializeWorkspaceTraining = async (agentsRoot, handle, ability) => {
|
|
|
470
470
|
const projectionDigest = abilitySha256(JSON.stringify({
|
|
471
471
|
artifactDigest: ability.artifactDigest,
|
|
472
472
|
workspace: ability.workspace,
|
|
473
|
+
git: verified.manifest.spec.git ?? null,
|
|
473
474
|
}));
|
|
474
475
|
let previousCatalog = null;
|
|
475
476
|
if (await exists(trainingRoot)) {
|
|
@@ -531,6 +532,12 @@ const materializeWorkspaceTraining = async (agentsRoot, handle, ability) => {
|
|
|
531
532
|
: join(sourceRoot, asset.source);
|
|
532
533
|
await cp(source, target, { recursive: true, preserveTimestamps: true });
|
|
533
534
|
}
|
|
535
|
+
const gitManifestPath = verified.manifest.spec.git?.projectDependencies.path;
|
|
536
|
+
if (gitManifestPath !== undefined) {
|
|
537
|
+
const target = join(staging, "git", layerRelativePath(gitManifestPath, "git"));
|
|
538
|
+
await mkdir(dirname(target), { recursive: true });
|
|
539
|
+
await cp(join(sourceRoot, gitManifestPath), target, { preserveTimestamps: true });
|
|
540
|
+
}
|
|
534
541
|
const evalPaths = [
|
|
535
542
|
...(verified.manifest.spec.evals.policy === undefined ? [] : [verified.manifest.spec.evals.policy]),
|
|
536
543
|
...verified.manifest.spec.evals.cases,
|
|
@@ -553,6 +560,9 @@ const materializeWorkspaceTraining = async (agentsRoot, handle, ability) => {
|
|
|
553
560
|
...asset,
|
|
554
561
|
projectedPath: `training/workspace/${abilityWorkspaceProjectionPath(asset.target)}`,
|
|
555
562
|
})),
|
|
563
|
+
git: gitManifestPath === undefined ? null : {
|
|
564
|
+
projectDependenciesPath: `training/git/${layerRelativePath(gitManifestPath, "git")}`,
|
|
565
|
+
},
|
|
556
566
|
evals: evalPaths.map((path) => `training/evals/${layerRelativePath(path, "evals")}`),
|
|
557
567
|
}, null, 2), { encoding: "utf8", mode: 0o600 });
|
|
558
568
|
await readonlyTree(staging, { rootWritable: true });
|
|
@@ -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 { normalizeRepositoryIdentity } from "../repository-identity.js";
|
|
10
11
|
const runFile = promisify(execFile);
|
|
11
12
|
const MAX_FILES = 5_000;
|
|
12
13
|
const MAX_TOTAL_BYTES = 25 * 1024 * 1024;
|
|
@@ -78,20 +79,30 @@ const git = async (args, cwd) => {
|
|
|
78
79
|
return result.stdout.trim();
|
|
79
80
|
};
|
|
80
81
|
export const projectDependencyIds = (files, manifest) => {
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
82
|
+
const path = manifest.spec.git?.projectDependencies.path;
|
|
83
|
+
if (path === undefined)
|
|
83
84
|
return Object.freeze([]);
|
|
84
|
-
if (
|
|
85
|
-
throw new Error("
|
|
86
|
-
const file = files.find((candidate) => candidate.path ===
|
|
85
|
+
if (path !== PROJECT_MANIFEST_SOURCE_PATH)
|
|
86
|
+
throw new Error("ability_project_dependency_manifest_path_invalid");
|
|
87
|
+
const file = files.find((candidate) => candidate.path === path);
|
|
87
88
|
if (file === undefined)
|
|
88
89
|
throw new Error("ability_project_dependency_manifest_missing");
|
|
90
|
+
let projects;
|
|
89
91
|
try {
|
|
90
|
-
|
|
92
|
+
projects = ProjectDependencyManifestSchema.parse(parse(file.content.toString("utf8"))).projects;
|
|
91
93
|
}
|
|
92
94
|
catch {
|
|
93
95
|
throw new Error("ability_project_dependency_manifest_invalid");
|
|
94
96
|
}
|
|
97
|
+
return Object.freeze(projects.map((project) => {
|
|
98
|
+
try {
|
|
99
|
+
validateAbilityGitHost(project.repo);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
throw new Error("ability_project_dependency_host_not_allowed");
|
|
103
|
+
}
|
|
104
|
+
return normalizeRepositoryIdentity(project.repo).repositoryKey;
|
|
105
|
+
}));
|
|
95
106
|
};
|
|
96
107
|
export const collectAbilityFiles = async (root) => {
|
|
97
108
|
const files = [];
|
|
@@ -285,6 +296,8 @@ const scan = (files, manifest) => {
|
|
|
285
296
|
required.add("memory-seed");
|
|
286
297
|
if (manifest.spec.workspace.assets.length > 0)
|
|
287
298
|
required.add("workspace");
|
|
299
|
+
if (manifest.spec.git !== undefined)
|
|
300
|
+
required.add("git");
|
|
288
301
|
if (manifest.spec.evals.policy !== undefined)
|
|
289
302
|
required.add("eval-policy");
|
|
290
303
|
if (manifest.spec.evals.cases.length > 0)
|
|
@@ -297,6 +310,7 @@ const lockedManifestPaths = (manifest) => [
|
|
|
297
310
|
...manifest.spec.memory.policy.map((entry) => entry.path),
|
|
298
311
|
...manifest.spec.memory.seeds.map((entry) => entry.path),
|
|
299
312
|
...manifest.spec.workspace.assets.map((entry) => entry.source),
|
|
313
|
+
...(manifest.spec.git === undefined ? [] : [manifest.spec.git.projectDependencies.path]),
|
|
300
314
|
...(manifest.spec.evals.policy === undefined ? [] : [manifest.spec.evals.policy]),
|
|
301
315
|
...manifest.spec.evals.cases,
|
|
302
316
|
];
|
|
@@ -19,7 +19,7 @@ export const AbilityRepositoryUrlSchema = z.string().trim().min(1).max(2_048).su
|
|
|
19
19
|
try {
|
|
20
20
|
const url = new URL(value);
|
|
21
21
|
allowedUrl = (url.protocol === "https:" || url.protocol === "ssh:")
|
|
22
|
-
&& !url.username && !url.password && Boolean(url.hostname);
|
|
22
|
+
&& !url.username && !url.password && !url.search && !url.hash && Boolean(url.hostname);
|
|
23
23
|
}
|
|
24
24
|
catch { /* handled below */ }
|
|
25
25
|
if (!scpLike && !allowedUrl)
|
|
@@ -53,6 +53,10 @@ const LocalSkillSchema = z.object({ type: z.literal("local"), path: SafeAbilityP
|
|
|
53
53
|
const GitSkillSchema = z.object({
|
|
54
54
|
type: z.literal("git"), url: AbilityRepositoryUrlSchema, ref: z.string().min(1).max(256), path: SafeAbilityPathSchema,
|
|
55
55
|
}).strict();
|
|
56
|
+
const GitProjectDependencyPathSchema = z.literal("git/project-dependencies.yaml");
|
|
57
|
+
const GitLayerSchema = z.object({
|
|
58
|
+
projectDependencies: z.object({ path: GitProjectDependencyPathSchema }).strict(),
|
|
59
|
+
}).strict();
|
|
56
60
|
export const DaemonAbilityManifestSchema = z.object({
|
|
57
61
|
apiVersion: z.literal("nowcrew.ai/v1alpha1"), kind: z.literal("AgentTraining"),
|
|
58
62
|
metadata: z.object({ name: z.string().min(1).max(128), version: z.string().min(1).max(64), description: z.string().max(1_000).optional() }).strict(),
|
|
@@ -71,6 +75,7 @@ export const DaemonAbilityManifestSchema = z.object({
|
|
|
71
75
|
workspace: z.object({ assets: z.array(z.object({
|
|
72
76
|
source: SafeAbilityPathSchema, target: WorkspaceAbilityTargetSchema, mode: ModeSchema,
|
|
73
77
|
}).strict()).max(1_000).default([]) }).strict().default({ assets: [] }),
|
|
78
|
+
git: GitLayerSchema.optional(),
|
|
74
79
|
evals: z.object({
|
|
75
80
|
policy: SafeAbilityPathSchema.optional(),
|
|
76
81
|
cases: z.array(SafeAbilityPathSchema).max(1_000).default([]),
|
|
@@ -95,16 +100,6 @@ export const DaemonAbilityManifestSchema = z.object({
|
|
|
95
100
|
unique(manifest.spec.skills.map((skill) => skill.name.replace(/[^A-Za-z0-9._-]/gu, "_")), ["spec", "skills"], "Skill projection");
|
|
96
101
|
unique(manifest.spec.skills.map((skill) => skill.mount), ["spec", "skills"], "Skill mount");
|
|
97
102
|
unique(manifest.spec.workspace.assets.map((asset) => asset.target), ["spec", "workspace", "assets"], "Workspace target");
|
|
98
|
-
for (const asset of manifest.spec.workspace.assets) {
|
|
99
|
-
if (asset.source === "workspace/project-dependencies.yaml"
|
|
100
|
-
&& asset.target !== "training/workspace/project-dependencies.yaml") {
|
|
101
|
-
ctx.addIssue({
|
|
102
|
-
code: z.ZodIssueCode.custom,
|
|
103
|
-
path: ["spec", "workspace", "assets"],
|
|
104
|
-
message: "project dependency manifest target must be training/workspace/project-dependencies.yaml",
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
103
|
unique(manifest.spec.evals.cases, ["spec", "evals", "cases"], "Eval case");
|
|
109
104
|
});
|
|
110
105
|
export const DaemonAbilityLockSchema = z.object({
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude 后台任务(Bash run_in_background + TaskOutput 轮询)的结果解析。纯函数,零 IO。
|
|
3
|
+
*
|
|
4
|
+
* TaskOutput 的 tool_result 是 XML 式文本,实测形状见
|
|
5
|
+
* docs/superpowers/evidence/2026-08-22-claude-background-task-stream-json.json。
|
|
6
|
+
* 注意轮询工具是 TaskOutput 而非 BashOutput——claude 2.1.238 实测确认。
|
|
7
|
+
*/
|
|
8
|
+
/** 输出正文的字符上限,与 console 其它工具返回保持同量级。 */
|
|
9
|
+
const OUTPUT_CAP = 4000;
|
|
10
|
+
const tag = (text, name) => {
|
|
11
|
+
const match = new RegExp(`<${name}>([\\s\\S]*?)</${name}>`, "u").exec(text);
|
|
12
|
+
return match ? match[1].trim() : undefined;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* <output> 是协议里的最后一段,正文本身(命令的 stdout)可能包含字面量的 "</output>"
|
|
16
|
+
* (例如打印了一份 XML/日志)。非贪婪匹配会在第一个 "</output>" 处截断,把后面的真实内容
|
|
17
|
+
* 静默丢掉,产出一个"看起来对但其实错"的结果。这里改为贪婪匹配,吞到文本中*最后一个*
|
|
18
|
+
* "</output>",与协议里 output 恒为末段的事实对齐。
|
|
19
|
+
*/
|
|
20
|
+
const extractOutputTag = (text) => {
|
|
21
|
+
const match = /<output>([\s\S]*)<\/output>/u.exec(text);
|
|
22
|
+
return match ? match[1].trim() : undefined;
|
|
23
|
+
};
|
|
24
|
+
export function parseTaskOutput(text) {
|
|
25
|
+
const jobId = tag(text, "task_id");
|
|
26
|
+
if (!jobId)
|
|
27
|
+
return null;
|
|
28
|
+
const status = tag(text, "status");
|
|
29
|
+
if (!status)
|
|
30
|
+
return null;
|
|
31
|
+
const rawExit = tag(text, "exit_code");
|
|
32
|
+
const exitCode = rawExit !== undefined && /^-?\d+$/u.test(rawExit) ? Number(rawExit) : undefined;
|
|
33
|
+
// status 只要不是 running,就按已结束处理(completed/failed/killed 等都归入 exited)。
|
|
34
|
+
const state = status === "running" ? "running" : "exited";
|
|
35
|
+
const rawOutput = extractOutputTag(text);
|
|
36
|
+
return {
|
|
37
|
+
jobId,
|
|
38
|
+
state,
|
|
39
|
+
...(exitCode !== undefined ? { exitCode } : {}),
|
|
40
|
+
// lineCount 必须基于截断前的完整正文——否则前端拿到的"总行数"其实是预览的行数,
|
|
41
|
+
// 无法用来判断是否还有更多内容可展开(参照 console.ts 里 buildScriptResult 的做法)。
|
|
42
|
+
...(rawOutput
|
|
43
|
+
? {
|
|
44
|
+
output: rawOutput.slice(0, OUTPUT_CAP),
|
|
45
|
+
lineCount: rawOutput.split("\n").length,
|
|
46
|
+
...(rawOutput.length > OUTPUT_CAP ? { truncated: true } : {}),
|
|
47
|
+
}
|
|
48
|
+
: {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
* 用 tool_use id 关联 Read 结果,避免将整文件作为普通 tool_result 倾泻到终端。
|
|
4
4
|
*/
|
|
5
5
|
import { buildFilePreview } from "./console-payload.js";
|
|
6
|
+
import { detectDaemonLang, formatDaemonText, translateDaemon } from "./i18n.js";
|
|
6
7
|
import { buildScriptResult, toConsoleLines, TOOL_RESULT_CAP } from "./console.js";
|
|
7
8
|
import { parseUnifiedDiff } from "./unified-diff.js";
|
|
8
9
|
import { buildJsonResult } from "./json-result.js";
|
|
9
10
|
import { buildCollapsedResult } from "./console-collapse.js";
|
|
10
11
|
import { buildSkillPreview } from "./skill-preview.js";
|
|
12
|
+
import { parseTaskOutput } from "./background-job.js";
|
|
11
13
|
const extract = (content) => {
|
|
12
14
|
if (typeof content === "string")
|
|
13
15
|
return content;
|
|
@@ -22,16 +24,71 @@ const clip = (text) => text.length > TOOL_RESULT_CAP
|
|
|
22
24
|
const SCRIPT_TOOLS = new Set(["Bash", "PowerShell", "Shell"]);
|
|
23
25
|
export function createConsoleFormatter() {
|
|
24
26
|
const pending = new Map();
|
|
27
|
+
// listId → 上一条 todos 快照的指纹;仅用于丢弃「连续两次完全相同」的重复事件(无损去重)。
|
|
28
|
+
// 只比对相邻快照,不做跨快照合并——进度推进的中间态必须逐条下发,否则前端进度条会停在旧值。
|
|
29
|
+
const lastTodoFingerprint = new Map();
|
|
30
|
+
// jobId → 已下发次数(去重后);后台任务在滚动流里折叠成一个单元,次数供前端展示。
|
|
31
|
+
// 与去重表联动:被丢弃的重复轮询不会走到这里递增,所以计的是"不同状态数"而非"原始轮询数"。
|
|
32
|
+
const jobPollCount = new Map();
|
|
33
|
+
// jobId → 上一条轮询结果的指纹,用于丢弃「连续两次完全相同」的轮询(无损去重)。
|
|
34
|
+
const lastJobFingerprint = new Map();
|
|
35
|
+
/** 丢弃与该 listId 上一条完全相同的 todos 行;无 listId 的不参与去重。 */
|
|
36
|
+
const dedupeTodos = (chunks) => chunks.filter((chunk) => {
|
|
37
|
+
const payload = chunk.payload;
|
|
38
|
+
if (!payload || payload.kind !== "todos" || !payload.listId)
|
|
39
|
+
return true;
|
|
40
|
+
const fingerprint = JSON.stringify(payload.todos);
|
|
41
|
+
if (lastTodoFingerprint.get(payload.listId) === fingerprint)
|
|
42
|
+
return false;
|
|
43
|
+
lastTodoFingerprint.set(payload.listId, fingerprint);
|
|
44
|
+
return true;
|
|
45
|
+
});
|
|
46
|
+
// claude redacted-thinking(redact-thinking-2026-02-12):流式阶段 thinking_delta.thinking 为空串,
|
|
47
|
+
// 只携带 estimated_tokens 估算(数值会被修正,取最新值);完整 assistant 消息里 thinking 也为空。
|
|
48
|
+
// 这里累积估算值,在 assistant 消息落地时把空 thinking 块补成占位文本;
|
|
49
|
+
// 若未来 CLI 恢复明文思考,累积到的正文优先生效,占位逻辑自动失效。
|
|
50
|
+
let pendingThinking = null;
|
|
51
|
+
const consumePendingThinking = () => {
|
|
52
|
+
if (!pendingThinking)
|
|
53
|
+
return null;
|
|
54
|
+
const { text, tokens } = pendingThinking;
|
|
55
|
+
pendingThinking = null;
|
|
56
|
+
if (text.trim())
|
|
57
|
+
return text.trim();
|
|
58
|
+
const lang = detectDaemonLang();
|
|
59
|
+
return tokens > 0
|
|
60
|
+
? formatDaemonText(lang, "Thinking (~{{tokens}} tokens, content not streamed by Claude Code)", { tokens })
|
|
61
|
+
: translateDaemon(lang, "Thinking (content not streamed by Claude Code)");
|
|
62
|
+
};
|
|
25
63
|
return {
|
|
26
64
|
format(event) {
|
|
27
65
|
const rec = event && typeof event === "object" ? event : {};
|
|
28
66
|
const message = rec.message && typeof rec.message === "object" ? rec.message : undefined;
|
|
67
|
+
if (rec.type === "stream_event" && rec.event && typeof rec.event === "object") {
|
|
68
|
+
const streamEvent = rec.event;
|
|
69
|
+
if (streamEvent.type === "content_block_delta" && streamEvent.delta?.type === "thinking_delta") {
|
|
70
|
+
const prev = pendingThinking ?? { text: "", tokens: 0 };
|
|
71
|
+
pendingThinking = {
|
|
72
|
+
text: prev.text + (typeof streamEvent.delta.thinking === "string" ? streamEvent.delta.thinking : ""),
|
|
73
|
+
tokens: typeof streamEvent.delta.estimated_tokens === "number" ? streamEvent.delta.estimated_tokens : prev.tokens,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
29
78
|
if (rec.type === "assistant" && Array.isArray(message?.content)) {
|
|
30
79
|
for (const block of message.content) {
|
|
31
80
|
if (block.type === "tool_use" && block.id && block.name) {
|
|
32
81
|
pending.set(block.id, { name: block.name, ...(block.input ? { input: block.input } : {}) });
|
|
33
82
|
}
|
|
34
83
|
}
|
|
84
|
+
const hasEmptyThinking = message.content.some((block) => block.type === "thinking" && !block.thinking?.trim());
|
|
85
|
+
if (hasEmptyThinking) {
|
|
86
|
+
const filler = consumePendingThinking();
|
|
87
|
+
if (filler) {
|
|
88
|
+
const content = message.content.map((block) => block.type === "thinking" && !block.thinking?.trim() ? { ...block, thinking: filler } : block);
|
|
89
|
+
return dedupeTodos(toConsoleLines({ ...rec, message: { ...message, content } }));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
35
92
|
}
|
|
36
93
|
if (rec.type === "user" && Array.isArray(message?.content)) {
|
|
37
94
|
const out = [];
|
|
@@ -44,6 +101,24 @@ export function createConsoleFormatter() {
|
|
|
44
101
|
const tool = block.tool_use_id ? pending.get(block.tool_use_id) : undefined;
|
|
45
102
|
if (block.tool_use_id)
|
|
46
103
|
pending.delete(block.tool_use_id);
|
|
104
|
+
if (!block.is_error && tool?.name === "TaskOutput") {
|
|
105
|
+
const parsed = parseTaskOutput(text);
|
|
106
|
+
if (parsed) {
|
|
107
|
+
const fingerprint = JSON.stringify(parsed);
|
|
108
|
+
if (lastJobFingerprint.get(parsed.jobId) === fingerprint)
|
|
109
|
+
continue;
|
|
110
|
+
lastJobFingerprint.set(parsed.jobId, fingerprint);
|
|
111
|
+
const pollCount = (jobPollCount.get(parsed.jobId) ?? 0) + 1;
|
|
112
|
+
jobPollCount.set(parsed.jobId, pollCount);
|
|
113
|
+
out.push({
|
|
114
|
+
stream: "tool_result",
|
|
115
|
+
text: `⏺ Background job ${parsed.jobId} · ${parsed.state}`,
|
|
116
|
+
payload: { ...parsed, kind: "background_job", pollCount },
|
|
117
|
+
});
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
// 解析不出来就走下面的通用分支,降级成纯文本行,不猜。
|
|
121
|
+
}
|
|
47
122
|
if (!block.is_error && tool?.name === "Read" && typeof tool.input?.file_path === "string") {
|
|
48
123
|
const start = typeof tool.input.offset === "number" && Number.isSafeInteger(tool.input.offset)
|
|
49
124
|
? Math.max(1, tool.input.offset) : 1;
|
|
@@ -71,7 +146,7 @@ export function createConsoleFormatter() {
|
|
|
71
146
|
}
|
|
72
147
|
return out;
|
|
73
148
|
}
|
|
74
|
-
return toConsoleLines(event);
|
|
149
|
+
return dedupeTodos(toConsoleLines(event));
|
|
75
150
|
},
|
|
76
151
|
};
|
|
77
152
|
}
|
package/dist/console.js
CHANGED
|
@@ -17,7 +17,7 @@ import { buildFilePreview, buildSnippetDiff } from "./console-payload.js";
|
|
|
17
17
|
import { parseUnifiedDiff } from "./unified-diff.js";
|
|
18
18
|
import { buildJsonResult } from "./json-result.js";
|
|
19
19
|
import { buildCollapsedResult } from "./console-collapse.js";
|
|
20
|
-
import { boundedCodexPlan
|
|
20
|
+
import { boundedCodexPlan } from "./runtimes/codex-contract.js";
|
|
21
21
|
/** 单条工具返回正文上限(超出截断并标注),避免单条把终端/DB 撑爆。 */
|
|
22
22
|
export const TOOL_RESULT_CAP = 4000;
|
|
23
23
|
/** 工具输入摘要上限(标题行那一段)。 */
|
|
@@ -69,6 +69,12 @@ function summarizeToolInput(name, input) {
|
|
|
69
69
|
.join(", ");
|
|
70
70
|
return `⏺ ${name}(${clip(head.replace(/\s+/g, " "), TOOL_INPUT_CAP)})`;
|
|
71
71
|
}
|
|
72
|
+
/** codex 计划步骤状态(pending/inProgress/completed) → ConsoleTodo 状态(pending/in_progress/completed)。 */
|
|
73
|
+
const CODEX_PLAN_STATUS_MAP = {
|
|
74
|
+
pending: "pending",
|
|
75
|
+
inProgress: "in_progress",
|
|
76
|
+
completed: "completed",
|
|
77
|
+
};
|
|
72
78
|
const TODO_STATUSES = new Set(["pending", "in_progress", "completed"]);
|
|
73
79
|
/** claude TodoWrite 的 todos 数组归一为 ConsoleTodo[](脏数据条目丢弃)。 */
|
|
74
80
|
function normalizeTodos(raw) {
|
|
@@ -236,7 +242,13 @@ function codexItemChunks(item, td) {
|
|
|
236
242
|
if (!todos.length)
|
|
237
243
|
return [];
|
|
238
244
|
const done = todos.filter((t) => t.status === "completed").length;
|
|
239
|
-
|
|
245
|
+
// codex 对同一份 plan 的多次更新复用同一个 item.id,透传给下游做合并键;无 id 时不伪造。
|
|
246
|
+
const listId = typeof item.id === "string" && item.id ? item.id : undefined;
|
|
247
|
+
return [{
|
|
248
|
+
stream: "tool",
|
|
249
|
+
text: `⏺ Todos(${done}/${todos.length})`,
|
|
250
|
+
payload: { kind: "todos", todos, ...(listId ? { listId } : {}) },
|
|
251
|
+
}];
|
|
240
252
|
}
|
|
241
253
|
if (item.type === "web_search" && item.query?.trim()) {
|
|
242
254
|
return [{ stream: "tool", text: `⏺ WebSearch(${clip(item.query.trim(), TOOL_INPUT_CAP)})` }];
|
|
@@ -276,10 +288,17 @@ export function toConsoleLines(event) {
|
|
|
276
288
|
}
|
|
277
289
|
if (e.type === "turn.plan.updated") {
|
|
278
290
|
const plan = boundedCodexPlan(e.plan);
|
|
279
|
-
|
|
291
|
+
if (plan === null)
|
|
292
|
+
return [];
|
|
293
|
+
const todos = plan.map(({ step, status }) => ({
|
|
294
|
+
text: step,
|
|
295
|
+
status: CODEX_PLAN_STATUS_MAP[status],
|
|
296
|
+
}));
|
|
297
|
+
const listId = typeof e.turn_id === "string" && e.turn_id ? e.turn_id : "codex-plan";
|
|
298
|
+
return [{
|
|
280
299
|
stream: "tool",
|
|
281
300
|
text: `⏺ ${td("Plan updated")} (${plan.length})`,
|
|
282
|
-
payload: { kind: "
|
|
301
|
+
payload: { kind: "todos", todos, listId },
|
|
283
302
|
}];
|
|
284
303
|
}
|
|
285
304
|
if (e.type === "item.completed" && e.item) {
|
package/dist/execution-runner.js
CHANGED
|
@@ -19,6 +19,7 @@ import { ProjectSkillRuntimeOwnershipUnverifiedError, } from "./project-skills/r
|
|
|
19
19
|
import { projectSkillExecutionProjection, projectSkillProjectionErrorCode } from "./project-skills/execution-adapter.js";
|
|
20
20
|
import { redactProjectSkillRuntimeRootError, } from "./project-skills/runtime-launch.js";
|
|
21
21
|
import { createProjectRegistry } from "./project-skills/registry.js";
|
|
22
|
+
import { ProjectDependencyError, } from "./project-dependencies.js";
|
|
22
23
|
import { ProjectContextUnavailableError, resolveProjectContext } from "./project-workspaces/resolver.js";
|
|
23
24
|
import { PROJECT_WORKSPACES_CAPABILITY } from "./machine-info.js";
|
|
24
25
|
import { PROJECT_SKILL_PROJECTION_V2_CAPABILITY } from "./project-skills/types.js";
|
|
@@ -315,7 +316,9 @@ function failedCompletion(spec, error, startedAt, finishedAt) {
|
|
|
315
316
|
? "queue_timeout"
|
|
316
317
|
: error instanceof ProjectContextUnavailableError
|
|
317
318
|
? error.code
|
|
318
|
-
:
|
|
319
|
+
: error instanceof ProjectDependencyError
|
|
320
|
+
? error.code
|
|
321
|
+
: projectSkillProjectionErrorCode(error) ?? "local_execution_failed";
|
|
319
322
|
return ExecutionCompletedSchema.parse({
|
|
320
323
|
type: "execution:completed",
|
|
321
324
|
protocolVersion: 1,
|
|
@@ -503,16 +506,64 @@ export async function runExecution(config, input, dependencies) {
|
|
|
503
506
|
await cancellable(dependencies.slot.ready, dependencies.cancellation);
|
|
504
507
|
}
|
|
505
508
|
let projectContext;
|
|
509
|
+
let trainingProjectContext;
|
|
506
510
|
let sessionContextFingerprint;
|
|
507
511
|
const logicalProjectContext = spec.workspace.projectContext;
|
|
508
|
-
|
|
512
|
+
dslog("project_dependencies.preflight", "项目依赖准备前置状态", {
|
|
513
|
+
execution_id: spec.executionId,
|
|
514
|
+
agent_handle: spec.agent.handle,
|
|
515
|
+
has_ability_release: spec.agent.abilityRelease !== undefined,
|
|
516
|
+
ability_release_id: spec.agent.abilityRelease?.releaseId,
|
|
517
|
+
ability_root_commit: spec.agent.abilityRelease?.rootCommit,
|
|
518
|
+
has_project_context: logicalProjectContext !== undefined,
|
|
519
|
+
project_ids: JSON.stringify(logicalProjectContext?.projectIds ?? []),
|
|
520
|
+
project_context_primary: logicalProjectContext?.primaryProjectId,
|
|
521
|
+
dependency_prepare_expected: spec.agent.abilityRelease !== undefined,
|
|
522
|
+
});
|
|
523
|
+
if (spec.agent.abilityRelease !== undefined && dependencies.projectDependencies !== undefined) {
|
|
524
|
+
if (dependencies.abilityRelease !== undefined) {
|
|
525
|
+
await cancellable(dependencies.abilityRelease.apply(config.agentsRoot, spec.agent.handle, spec.agent.abilityRelease), dependencies.cancellation);
|
|
526
|
+
}
|
|
527
|
+
const preparedProjectDependencies = await cancellable(dependencies.projectDependencies.prepare({
|
|
528
|
+
trainingRoot: join(config.agentsRoot, spec.agent.handle, "training"),
|
|
529
|
+
reposRoot: join(config.agentsRoot, "repos"),
|
|
530
|
+
lockRoot: join(config.agentsRoot, ".crew", "project-dependencies"),
|
|
531
|
+
}), dependencies.cancellation);
|
|
532
|
+
trainingProjectContext = Object.freeze({
|
|
533
|
+
projects: Object.freeze(preparedProjectDependencies.map((project) => Object.freeze({
|
|
534
|
+
repositoryKey: project.repositoryKey,
|
|
535
|
+
displayName: project.displayName,
|
|
536
|
+
repo: project.repo,
|
|
537
|
+
root: project.localRoot,
|
|
538
|
+
branch: project.branch,
|
|
539
|
+
resolvedCommit: project.resolvedCommit,
|
|
540
|
+
}))),
|
|
541
|
+
});
|
|
542
|
+
for (const project of preparedProjectDependencies) {
|
|
543
|
+
dslog("project_dependencies.prepared", "Training project dependency prepared", {
|
|
544
|
+
execution_id: spec.executionId,
|
|
545
|
+
agent_handle: spec.agent.handle,
|
|
546
|
+
repository_key: project.repositoryKey,
|
|
547
|
+
project_branch: project.branch,
|
|
548
|
+
project_branch_source: project.branchSource,
|
|
549
|
+
project_commit: project.resolvedCommit,
|
|
550
|
+
project_checkout_mode: project.checkoutMode,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
else if (spec.agent.abilityRelease === undefined
|
|
555
|
+
&& logicalProjectContext !== undefined
|
|
556
|
+
&& logicalProjectContext.projectIds.length > 0) {
|
|
509
557
|
const projectSnapshot = {
|
|
510
558
|
projectIds: logicalProjectContext.projectIds,
|
|
511
559
|
...(logicalProjectContext.primaryProjectId === undefined
|
|
512
560
|
? {}
|
|
513
561
|
: { primaryProjectId: logicalProjectContext.primaryProjectId }),
|
|
514
562
|
};
|
|
515
|
-
|
|
563
|
+
const projectWorkspaceRegistry = dependencies.projectWorkspaceRegistry
|
|
564
|
+
?? dependencies.projectRegistry
|
|
565
|
+
?? createProjectRegistry(config.agentsRoot);
|
|
566
|
+
projectContext = await cancellable(resolveProjectContext(projectSnapshot, projectWorkspaceRegistry), dependencies.cancellation);
|
|
516
567
|
sessionContextFingerprint = projectSessionContextFingerprint(spec.runtime.name, projectSnapshot);
|
|
517
568
|
}
|
|
518
569
|
let recalledMemory = "";
|
|
@@ -788,6 +839,7 @@ export async function runExecution(config, input, dependencies) {
|
|
|
788
839
|
...(spec.workspace.resumeKey === undefined ? {} : { resumeKey: spec.workspace.resumeKey }),
|
|
789
840
|
...(sessionContextFingerprint === undefined ? {} : { sessionContextFingerprint }),
|
|
790
841
|
...(projectContext === undefined ? {} : { projectContext }),
|
|
842
|
+
...(trainingProjectContext === undefined ? {} : { trainingProjectContext }),
|
|
791
843
|
...(spec.context.wakeMessageId === undefined ? {} : { wakeMessageId: spec.context.wakeMessageId }),
|
|
792
844
|
...(spec.context.attachments === undefined ? {} : { attachments: spec.context.attachments }),
|
|
793
845
|
...(spec.agent.projectSkills === undefined
|
package/dist/i18n.js
CHANGED
|
@@ -19,6 +19,8 @@ const zh = {
|
|
|
19
19
|
"Claude session started": "Claude 会话启动",
|
|
20
20
|
"Codex session started": "Codex 会话启动",
|
|
21
21
|
"Plan updated": "计划更新",
|
|
22
|
+
"Thinking (~{{tokens}} tokens, content not streamed by Claude Code)": "思考(约 {{tokens}} tokens,Claude Code 未输出思考正文)",
|
|
23
|
+
"Thinking (content not streamed by Claude Code)": "思考(Claude Code 未输出思考正文)",
|
|
22
24
|
"Files changed": "文件变更",
|
|
23
25
|
"Run failed": "运行出错",
|
|
24
26
|
"Run finished": "本轮结束",
|
|
@@ -5,6 +5,21 @@
|
|
|
5
5
|
* 只有本机事实(CREW_HOME / work-log / MEMORY 注入)是可选增强,按整段丢弃或按字节截断。
|
|
6
6
|
*/
|
|
7
7
|
import { buildCollaborationToolsPrompt, buildRuntimeWorkspacePrompt, capMemoryForInject, capWorkLogForInject, } from "./prompt.js";
|
|
8
|
+
const trainingProjectJson = (project) => JSON.stringify({
|
|
9
|
+
repositoryKey: project.repositoryKey,
|
|
10
|
+
displayName: project.displayName,
|
|
11
|
+
repo: project.repo,
|
|
12
|
+
branch: project.branch,
|
|
13
|
+
resolvedCommit: project.resolvedCommit,
|
|
14
|
+
root: project.root,
|
|
15
|
+
});
|
|
16
|
+
export function buildTrainingGitProjectPrompt(context) {
|
|
17
|
+
if (context === undefined || context.projects.length === 0)
|
|
18
|
+
return "";
|
|
19
|
+
return "\n\n## Training Git projects\n" + context.projects
|
|
20
|
+
.map((project) => "- " + trainingProjectJson(project) + "\n Run project commands with: cd <root> && <command>")
|
|
21
|
+
.join("\n");
|
|
22
|
+
}
|
|
8
23
|
export function truncateUtf8(value, maxBytes) {
|
|
9
24
|
if (maxBytes <= 0)
|
|
10
25
|
return "";
|
|
@@ -31,11 +46,11 @@ function takeCompletePromptSections(sections, maxBytes) {
|
|
|
31
46
|
}
|
|
32
47
|
/** v1 server instructions opt in to daemon-owned paths and bounded local workspace context. */
|
|
33
48
|
export function withLocalExecutionFacts(serverPrompt, maxBytes, options = {}) {
|
|
34
|
-
return ({ workspace, resuming, projectContext }) => {
|
|
49
|
+
return ({ workspace, resuming, projectContext, trainingProjectContext }) => {
|
|
35
50
|
const memory = capMemoryForInject(workspace.memory);
|
|
36
51
|
const workLog = resuming ? "" : capWorkLogForInject(workspace.workLog);
|
|
37
52
|
const collaborationPrompt = options.collaborationTools === true ? buildCollaborationToolsPrompt() : "";
|
|
38
|
-
const mandatoryWorkspacePrompt = buildRuntimeWorkspacePrompt(projectContext)
|
|
53
|
+
const mandatoryWorkspacePrompt = `${buildRuntimeWorkspacePrompt(projectContext)}${buildTrainingGitProjectPrompt(trainingProjectContext)}`;
|
|
39
54
|
const localFactSections = [`\n\n## Local execution facts
|
|
40
55
|
- $CREW_HOME: ${workspace.dir}
|
|
41
56
|
- $CREW_TASK_LOG: ${workspace.workLogPath}`,
|
package/dist/local-executor.js
CHANGED
|
@@ -374,6 +374,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
374
374
|
rotatedForBudget,
|
|
375
375
|
nearBudget,
|
|
376
376
|
...(input.projectContext === undefined ? {} : { projectContext: input.projectContext }),
|
|
377
|
+
...(input.trainingProjectContext === undefined ? {} : { trainingProjectContext: input.trainingProjectContext }),
|
|
377
378
|
};
|
|
378
379
|
if (input.attachments && input.attachments.length > 0) {
|
|
379
380
|
if (dependencies.cancellation?.isRequested())
|
|
@@ -590,19 +591,23 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
590
591
|
skill_count: abilityContext.skillCount,
|
|
591
592
|
});
|
|
592
593
|
}
|
|
594
|
+
const trainingRoots = input.trainingProjectContext?.projects.map((project) => project.root) ?? [];
|
|
593
595
|
const { codexSkillRoots, claudeAdditionalDirectories } = await projectSkillRuntimeDirectories({
|
|
594
596
|
runtime: runtime.name,
|
|
595
597
|
...(runtimeRoot === undefined ? {} : { runtimeRoot }),
|
|
596
598
|
agentRoot: workspace.dir,
|
|
597
599
|
projectSkillsPresent: input.projectSkills !== undefined,
|
|
598
600
|
...(input.projectContext === undefined ? {} : { projectContext: input.projectContext }),
|
|
601
|
+
...(input.trainingProjectContext === undefined ? {} : {
|
|
602
|
+
trainingProjectRoots: input.trainingProjectContext.projects.map((project) => project.root),
|
|
603
|
+
}),
|
|
599
604
|
...(abilityContext?.skillRoot === undefined
|
|
600
605
|
? {}
|
|
601
606
|
: { abilitySkillRoot: abilityContext.skillRoot }),
|
|
602
607
|
});
|
|
603
608
|
let runtimeEnv = childEnv;
|
|
604
609
|
if (runtime.name === "claude"
|
|
605
|
-
&& input.projectContext !== undefined
|
|
610
|
+
&& (input.projectContext !== undefined || trainingRoots.length > 0)
|
|
606
611
|
&& claudeAdditionalDirectories.length > 0) {
|
|
607
612
|
const version = await (dependencies.probeClaudeVersion ?? probeClaudeVersion)(runtime.name, childEnv)
|
|
608
613
|
.catch(() => null);
|
|
@@ -621,6 +626,8 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
621
626
|
});
|
|
622
627
|
}
|
|
623
628
|
}
|
|
629
|
+
const legacyRoots = input.projectContext?.secondary.map((project) => project.root) ?? [];
|
|
630
|
+
const workspaceRoots = [...legacyRoots, ...trainingRoots];
|
|
624
631
|
const launchRequest = {
|
|
625
632
|
runtime: runtime.name,
|
|
626
633
|
bin: runtime.name === "deepseek-harness" ? "dsh-acp-demo" : runtime.name,
|
|
@@ -651,9 +658,14 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
651
658
|
? {}
|
|
652
659
|
: { claudeAdditionalDirectories }),
|
|
653
660
|
...(input.projectContext === undefined ? {} : {
|
|
654
|
-
workspaceRoots: Object.freeze(input.projectContext.secondary.map((project) => project.root)),
|
|
655
661
|
projectContext: input.projectContext,
|
|
656
662
|
}),
|
|
663
|
+
...(input.projectContext === undefined && workspaceRoots.length === 0
|
|
664
|
+
? {}
|
|
665
|
+
: { workspaceRoots: Object.freeze(workspaceRoots) }),
|
|
666
|
+
...(input.trainingProjectContext === undefined ? {} : {
|
|
667
|
+
trainingProjectContext: input.trainingProjectContext,
|
|
668
|
+
}),
|
|
657
669
|
};
|
|
658
670
|
return launchRequest;
|
|
659
671
|
};
|