agent-trellis 0.19.0 → 0.21.0
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/adapters/claude-code.js +2 -2
- package/dist/adapters/codex.js +3 -3
- package/dist/adapters/jsonMcp.d.ts +1 -1
- package/dist/adapters/jsonMcp.js +2 -2
- package/dist/adapters/kimi-code.js +2 -2
- package/dist/adapters/kiro.js +11 -9
- package/dist/adapters/mcpPlan.d.ts +1 -1
- package/dist/adapters/mcpPlan.js +35 -12
- package/dist/adapters/pi.js +5 -5
- package/dist/adapters/zcode.d.ts +1 -1
- package/dist/adapters/zcode.js +6 -6
- package/dist/cli.js +12 -1
- package/dist/commands/doctor.d.ts +9 -1
- package/dist/commands/doctor.js +92 -7
- package/dist/commands/evolve.d.ts +43 -0
- package/dist/commands/evolve.js +479 -0
- package/dist/commands/mcp.js +1 -1
- package/dist/commands/mcpAuth.js +1 -1
- package/dist/commands/mcpGateway.d.ts +30 -2
- package/dist/commands/mcpGateway.js +44 -10
- package/dist/commands/mcpImport.js +2 -1
- package/dist/commands/secretsAudit.d.ts +1 -1
- package/dist/commands/secretsAudit.js +36 -13
- package/dist/evolve/agentView.d.ts +32 -0
- package/dist/evolve/agentView.js +34 -0
- package/dist/evolve/apply.d.ts +71 -0
- package/dist/evolve/apply.js +162 -0
- package/dist/evolve/archive.d.ts +23 -0
- package/dist/evolve/archive.js +58 -0
- package/dist/evolve/erase.d.ts +17 -0
- package/dist/evolve/erase.js +65 -0
- package/dist/evolve/extension.d.ts +37 -0
- package/dist/evolve/extension.js +63 -0
- package/dist/evolve/gate.d.ts +95 -0
- package/dist/evolve/gate.js +295 -0
- package/dist/evolve/importHistory.d.ts +91 -0
- package/dist/evolve/importHistory.js +239 -0
- package/dist/evolve/jobMain.d.ts +22 -0
- package/dist/evolve/jobMain.js +52 -0
- package/dist/evolve/jobRun.d.ts +94 -0
- package/dist/evolve/jobRun.js +273 -0
- package/dist/evolve/jobShell.d.ts +46 -0
- package/dist/evolve/jobShell.js +75 -0
- package/dist/evolve/l2.d.ts +93 -0
- package/dist/evolve/l2.js +81 -0
- package/dist/evolve/lesson.d.ts +57 -0
- package/dist/evolve/lesson.js +97 -0
- package/dist/evolve/pending.d.ts +69 -0
- package/dist/evolve/pending.js +116 -0
- package/dist/evolve/probeWorkspace.d.ts +57 -0
- package/dist/evolve/probeWorkspace.js +69 -0
- package/dist/evolve/propose.d.ts +73 -0
- package/dist/evolve/propose.js +136 -0
- package/dist/evolve/recorder.d.ts +68 -0
- package/dist/evolve/recorder.js +169 -0
- package/dist/evolve/report.d.ts +26 -0
- package/dist/evolve/report.js +37 -0
- package/dist/evolve/reportView.d.ts +96 -0
- package/dist/evolve/reportView.js +311 -0
- package/dist/evolve/risk.d.ts +106 -0
- package/dist/evolve/risk.js +224 -0
- package/dist/evolve/sources.d.ts +19 -0
- package/dist/evolve/sources.js +29 -0
- package/dist/evolve/stateDir.d.ts +21 -0
- package/dist/evolve/stateDir.js +56 -0
- package/dist/evolve/surface.d.ts +34 -0
- package/dist/evolve/surface.js +72 -0
- package/dist/evolve/trajectory.d.ts +25 -0
- package/dist/evolve/trajectory.js +88 -0
- package/dist/evolve/treeToCanonical.d.ts +28 -0
- package/dist/evolve/treeToCanonical.js +79 -0
- package/dist/evolve/trellisSubstrate.d.ts +65 -0
- package/dist/evolve/trellisSubstrate.js +154 -0
- package/dist/evolve/trigger.d.ts +177 -0
- package/dist/evolve/trigger.js +400 -0
- package/dist/evolve/types.d.ts +126 -0
- package/dist/evolve/types.js +15 -0
- package/dist/evolve/validate.d.ts +139 -0
- package/dist/evolve/validate.js +315 -0
- package/dist/host/checks.d.ts +57 -0
- package/dist/host/checks.js +126 -0
- package/dist/host/delivery.d.ts +42 -0
- package/dist/host/delivery.js +59 -0
- package/dist/host/edits.d.ts +87 -0
- package/dist/host/edits.js +102 -0
- package/dist/host/hostEvents.d.ts +74 -0
- package/dist/host/hostEvents.js +45 -0
- package/dist/host/index.d.ts +22 -0
- package/dist/host/index.js +17 -0
- package/dist/host/levels.d.ts +106 -0
- package/dist/host/levels.js +206 -0
- package/dist/host/staging.d.ts +56 -0
- package/dist/host/staging.js +106 -0
- package/dist/host/surface.d.ts +36 -0
- package/dist/host/surface.js +20 -0
- package/dist/lib/backup.js +12 -3
- package/dist/lib/envVarNames.js +7 -0
- package/dist/lib/fsAtomic.d.ts +7 -0
- package/dist/lib/fsAtomic.js +14 -0
- package/dist/lib/hostEventBus.d.ts +47 -0
- package/dist/lib/hostEventBus.js +94 -0
- package/dist/lib/hostExtensions.d.ts +75 -0
- package/dist/lib/hostExtensions.js +196 -0
- package/dist/lib/mcpConnect.d.ts +1 -1
- package/dist/lib/mcpConnect.js +19 -8
- package/dist/lib/mcpMigrateRead.js +14 -2
- package/dist/lib/mcpRuntime.d.ts +12 -1
- package/dist/lib/mcpRuntime.js +34 -2
- package/dist/lib/mcpToolRegistry.d.ts +18 -2
- package/dist/lib/mcpToolRegistry.js +29 -9
- package/dist/lib/secretEnv.d.ts +53 -8
- package/dist/lib/secretEnv.js +78 -15
- package/dist/lib/tomlSection.js +3 -2
- package/dist/lib/workspaceDoctor.d.ts +1 -1
- package/dist/lib/workspaceDoctor.js +8 -4
- package/dist/lib/workspaceMcp.d.ts +1 -1
- package/dist/lib/workspaceMcp.js +5 -5
- package/dist/pi-bridge/bundle.js +42236 -2701
- package/dist/pi-bridge/index.js +2 -2
- package/docs/magpie-gateway-research.md +422 -0
- package/docs/research.md +4 -1
- package/docs/roadmap.md +22 -12
- package/docs/runtime-infra.md +41 -22
- package/docs/self-evolution.md +138 -44
- package/package.json +8 -2
- package/schema/secrets.policy.example.yaml +5 -0
- package/schema/servers.example.yaml +5 -1
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 历史数据导入与幂等回填(trellis-evolve-run-backfill 任务 3.1/3.2)。
|
|
3
|
+
*
|
|
4
|
+
* 把用户**显式选定**的既有 session 数据回填为观察信号,供后续作业按
|
|
5
|
+
* 既有水位线消费。设计(D4):
|
|
6
|
+
* - 适配器契约 `{ name, sniff, extract }`,首个适配器 `claude-code-jsonl`
|
|
7
|
+
* (Claude Code 会话 transcript JSONL);
|
|
8
|
+
* - 白名单元数据:timestamp / agent / 会话 / 工具名 / 耗时 / 结果类别 /
|
|
9
|
+
* 层归属 —— 入参、结果载体与自由文本正文一律不入库(与 recorder 的
|
|
10
|
+
* 字段白名单同一条线);所有字符串字段写入前经 `eraseSecrets`;
|
|
11
|
+
* - 台账幂等:`evolve/imports/<sha256(内容)>.json` 记源摘要与已导条目
|
|
12
|
+
* 数,同一源重复导入为空操作;stableId `import:<digest 前 12 位>:<行号>`
|
|
13
|
+
* 确定性(重复导入/重复消费不产生重复信号或重复提案);
|
|
14
|
+
* - 写入复用 recorder 的批量 append(0600/0700、并发安全);整批先
|
|
15
|
+
* extract 校验、后写入,任何源不可解析即非零退出且零写入。
|
|
16
|
+
*
|
|
17
|
+
* 字段缺省语义(格式缺字段时):ImportedEntry 对应字段记 null —— 耗时
|
|
18
|
+
* 缺省 0(白名单无 unknown 态),结果类别缺省 "ok"(无错误指示),agent
|
|
19
|
+
* 缺省适配器常量 "claude-code",会话缺省源文件名(D4"会话 id=文件名")。
|
|
20
|
+
* 无工具名的记录(文本轮次等)不写为 tool-call 信号,计入 skippedNoTool。
|
|
21
|
+
* 结果类别按记录自带 is_error 归类(每条记录独立映射);tool_use 与其
|
|
22
|
+
* tool_result 在 transcript 中是不同记录,跨记录关联留待适配器后续细化。
|
|
23
|
+
*/
|
|
24
|
+
import { createHash } from "node:crypto";
|
|
25
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
26
|
+
import { basename, dirname, join } from "node:path";
|
|
27
|
+
import { eraseSecrets } from "./erase.js";
|
|
28
|
+
import { appendSignalLines, signalFilePath } from "./recorder.js";
|
|
29
|
+
import { evolveStateDir } from "./stateDir.js";
|
|
30
|
+
/** 源不可解析(不存在/不可读/不匹配任何适配器):整批零写入收口。 */
|
|
31
|
+
export class ImportSourceError extends Error {
|
|
32
|
+
path;
|
|
33
|
+
constructor(path, reason) {
|
|
34
|
+
super(`历史源 ${path} 无法导入:${reason}`);
|
|
35
|
+
this.name = "ImportSourceError";
|
|
36
|
+
this.path = path;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function parseTimestamp(raw) {
|
|
40
|
+
if (typeof raw !== "string")
|
|
41
|
+
return null;
|
|
42
|
+
const ms = Date.parse(raw);
|
|
43
|
+
return Number.isNaN(ms) ? null : ms;
|
|
44
|
+
}
|
|
45
|
+
/** 首个 tool_use 块的工具名(一条记录一元数据;多工具块取首个)。 */
|
|
46
|
+
function firstToolName(record) {
|
|
47
|
+
const message = record.message;
|
|
48
|
+
const content = (message?.content ?? record.content);
|
|
49
|
+
if (Array.isArray(content)) {
|
|
50
|
+
for (const block of content) {
|
|
51
|
+
const b = block;
|
|
52
|
+
if (b.type === "tool_use" && typeof b.name === "string")
|
|
53
|
+
return b.name;
|
|
54
|
+
if (b.type === "tool_result" && typeof b.name === "string")
|
|
55
|
+
return b.name;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (typeof record.tool === "string")
|
|
59
|
+
return record.tool;
|
|
60
|
+
if (typeof record.tool_name === "string")
|
|
61
|
+
return record.tool_name;
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
/** 记录自带的错误指示(顶层或内容块的 is_error);无指示为 null。 */
|
|
65
|
+
function errorIndicator(record) {
|
|
66
|
+
let seen = typeof record.is_error === "boolean" ? record.is_error : null;
|
|
67
|
+
const message = record.message;
|
|
68
|
+
const content = (message?.content ?? record.content);
|
|
69
|
+
if (Array.isArray(content)) {
|
|
70
|
+
for (const block of content) {
|
|
71
|
+
const b = block;
|
|
72
|
+
if (typeof b.is_error === "boolean")
|
|
73
|
+
seen = seen === true || b.is_error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return seen;
|
|
77
|
+
}
|
|
78
|
+
/** 首个适配器:Claude Code 会话 transcript(JSONL)。 */
|
|
79
|
+
export const claudeCodeJsonlAdapter = {
|
|
80
|
+
name: "claude-code-jsonl",
|
|
81
|
+
sniff(path) {
|
|
82
|
+
if (!path.endsWith(".jsonl"))
|
|
83
|
+
return false;
|
|
84
|
+
try {
|
|
85
|
+
const first = readFileSync(path, "utf-8")
|
|
86
|
+
.split("\n")
|
|
87
|
+
.find((line) => line.trim() !== "");
|
|
88
|
+
if (first === undefined)
|
|
89
|
+
return false;
|
|
90
|
+
const record = JSON.parse(first);
|
|
91
|
+
return (typeof record === "object" &&
|
|
92
|
+
record !== null &&
|
|
93
|
+
("type" in record || "sessionId" in record || "timestamp" in record || "uuid" in record));
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
extract(path) {
|
|
100
|
+
const text = readFileSync(path, "utf-8");
|
|
101
|
+
const stem = basename(path).replace(/\.jsonl$/, "");
|
|
102
|
+
const entries = [];
|
|
103
|
+
let badLines = 0;
|
|
104
|
+
let skippedNoTool = 0;
|
|
105
|
+
text.split("\n").forEach((line, index) => {
|
|
106
|
+
const trimmed = line.trim();
|
|
107
|
+
if (trimmed === "")
|
|
108
|
+
return;
|
|
109
|
+
let record;
|
|
110
|
+
try {
|
|
111
|
+
record = JSON.parse(trimmed);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
badLines += 1;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (typeof record !== "object" || record === null) {
|
|
118
|
+
badLines += 1;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const rec = record;
|
|
122
|
+
const tool = firstToolName(rec);
|
|
123
|
+
if (tool === null) {
|
|
124
|
+
skippedNoTool += 1;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const isError = errorIndicator(rec);
|
|
128
|
+
entries.push({
|
|
129
|
+
lineNo: index + 1,
|
|
130
|
+
entry: {
|
|
131
|
+
timestampMs: parseTimestamp(rec.timestamp),
|
|
132
|
+
agent: null,
|
|
133
|
+
session: stem,
|
|
134
|
+
tool,
|
|
135
|
+
durationMs: null,
|
|
136
|
+
result: isError === null ? null : isError ? "error" : "ok",
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
return { entries, badLines, skippedNoTool };
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
/** 已注册适配器(按序 sniff;契约先行,更多源后置)。 */
|
|
144
|
+
export const HISTORY_ADAPTERS = [claudeCodeJsonlAdapter];
|
|
145
|
+
/** 台账文件路径(按源内容摘要命名;内容变 = 新源)。 */
|
|
146
|
+
export function importLedgerPath(homeDir, digest) {
|
|
147
|
+
return join(evolveStateDir(homeDir), "imports", `${digest}.json`);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 整批导入:先对全部源 sniff + extract 校验(任何源不可解析即抛
|
|
151
|
+
* `ImportSourceError`,零写入),后一次性写信号与台账(台账最后记账,
|
|
152
|
+
* 中断重跑安全:未记账则重导,提案去重兜底)。不触发任何作业。
|
|
153
|
+
*/
|
|
154
|
+
export function importHistory(options) {
|
|
155
|
+
const now = options.now ?? Date.now;
|
|
156
|
+
const layer = options.workspaceRoot === undefined ? { kind: "global" } : { kind: "workspace", root: options.workspaceRoot };
|
|
157
|
+
const prepared = [];
|
|
158
|
+
const seenDigests = new Set();
|
|
159
|
+
const duplicates = new Map();
|
|
160
|
+
for (const path of options.paths) {
|
|
161
|
+
let content;
|
|
162
|
+
try {
|
|
163
|
+
content = readFileSync(path, "utf-8");
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
throw new ImportSourceError(path, `不可读(${err instanceof Error ? err.message : String(err)})`);
|
|
167
|
+
}
|
|
168
|
+
const digest = createHash("sha256").update(content).digest("hex");
|
|
169
|
+
if (seenDigests.has(digest) || existsSync(importLedgerPath(options.homeDir, digest))) {
|
|
170
|
+
duplicates.set(path, {
|
|
171
|
+
path,
|
|
172
|
+
sourceDigest: digest,
|
|
173
|
+
entries: 0,
|
|
174
|
+
badLines: 0,
|
|
175
|
+
skippedNoTool: 0,
|
|
176
|
+
duplicate: true,
|
|
177
|
+
});
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const adapter = HISTORY_ADAPTERS.find((candidate) => candidate.sniff(path));
|
|
181
|
+
if (adapter === undefined) {
|
|
182
|
+
throw new ImportSourceError(path, "不匹配任何已注册适配器(当前支持 Claude Code 会话 JSONL)");
|
|
183
|
+
}
|
|
184
|
+
seenDigests.add(digest);
|
|
185
|
+
prepared.push({ path, digest, extracted: adapter.extract(path) });
|
|
186
|
+
}
|
|
187
|
+
// ---- 阶段 2:写入(信号批量 append,台账最后记账) ----
|
|
188
|
+
const nowMs = now();
|
|
189
|
+
const allSignals = [];
|
|
190
|
+
const sources = [];
|
|
191
|
+
for (const item of prepared) {
|
|
192
|
+
const signals = [];
|
|
193
|
+
for (const { lineNo, entry } of item.extracted.entries) {
|
|
194
|
+
signals.push({
|
|
195
|
+
kind: "tool-call",
|
|
196
|
+
source: "history-import",
|
|
197
|
+
stableId: `import:${item.digest.slice(0, 12)}:${lineNo}`,
|
|
198
|
+
collectedAt: nowMs,
|
|
199
|
+
layer,
|
|
200
|
+
ts: entry.timestampMs ?? nowMs,
|
|
201
|
+
agent: eraseSecrets(entry.agent ?? "claude-code"),
|
|
202
|
+
session: eraseSecrets(entry.session ?? basename(item.path).replace(/\.jsonl$/, "")),
|
|
203
|
+
provider: eraseSecrets("claude-code"),
|
|
204
|
+
tool: eraseSecrets(entry.tool ?? "unknown"),
|
|
205
|
+
durationMs: entry.durationMs ?? 0,
|
|
206
|
+
result: entry.result ?? "ok",
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
allSignals.push(...signals);
|
|
210
|
+
sources.push({
|
|
211
|
+
path: item.path,
|
|
212
|
+
sourceDigest: item.digest,
|
|
213
|
+
entries: signals.length,
|
|
214
|
+
badLines: item.extracted.badLines,
|
|
215
|
+
skippedNoTool: item.extracted.skippedNoTool,
|
|
216
|
+
duplicate: false,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
let signalFile = null;
|
|
220
|
+
if (allSignals.length > 0) {
|
|
221
|
+
signalFile = signalFilePath(options.homeDir, nowMs);
|
|
222
|
+
appendSignalLines(signalFile, allSignals);
|
|
223
|
+
}
|
|
224
|
+
for (const source of sources) {
|
|
225
|
+
const ledgerPath = importLedgerPath(options.homeDir, source.sourceDigest);
|
|
226
|
+
mkdirSync(dirname(ledgerPath), { recursive: true, mode: 0o700 });
|
|
227
|
+
writeFileSync(ledgerPath, `${JSON.stringify({
|
|
228
|
+
sourceDigest: source.sourceDigest,
|
|
229
|
+
entries: source.entries,
|
|
230
|
+
importedAtMs: nowMs,
|
|
231
|
+
layer,
|
|
232
|
+
}, null, 2)}\n`, { mode: 0o600 });
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
sources: [...sources, ...duplicates.values()],
|
|
236
|
+
written: allSignals.length,
|
|
237
|
+
signalFile,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一次性 detached 作业入口(trellis-evolve 任务 11.5)。
|
|
3
|
+
*
|
|
4
|
+
* 契约:argv = [--hold-ms N] [--trigger event|manual|chain] <homeDir>。
|
|
5
|
+
* `--hold-ms`(持有锁 N 毫秒后再收尾,供集成测试观察"作业运行中"的锁
|
|
6
|
+
* 状态;生产不传,作业即时完成)。`--trigger` 记录触发来源入 job-run
|
|
7
|
+
* 档案(缺省 event;chain 为补跑链起,run-backfill D3)。锁由作业自身
|
|
8
|
+
* 持有(拉起方 spawn 前只做活锁检查,本入口再权威抢一次以防并发双拉起
|
|
9
|
+
* 竞态,抢不到立即退出、不做任何工作、不排队)。壳语义(抢锁/释放、
|
|
10
|
+
* job-run 落档含 trigger 字段、错误收口)在 `jobShell.js`——`trellis
|
|
11
|
+
* evolve run` 前台手动触发走同一壳(D1 不另写执行路径)。作业体是
|
|
12
|
+
* rule-based 治理流水的一次运行:`runEvolveJob`(信号 → rule-based
|
|
13
|
+
* 提案 → 机械校验 → L1 前闸 → 风险/采纳门 → 应用 → lesson,14.1 的
|
|
14
|
+
* fixture E2E 同链)。引擎内核(packages/evolve-engine 的
|
|
15
|
+
* schedule/recursion/proposer/selection/judge)golden 锚定但生产未经
|
|
16
|
+
* 此消费,待 L2/探针任务集接入。级别 off/observe 时作业体零副作用早退。
|
|
17
|
+
*
|
|
18
|
+
* 注意:本文件是入口脚本,被 import 时顶层即执行;测试只应 spawn 它,
|
|
19
|
+
* 不应 import(需要锁原语请从 `trigger.js` 取,作业壳从 `jobShell.js`
|
|
20
|
+
* 取,作业体从 `jobRun.js` 取)。
|
|
21
|
+
*/
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一次性 detached 作业入口(trellis-evolve 任务 11.5)。
|
|
3
|
+
*
|
|
4
|
+
* 契约:argv = [--hold-ms N] [--trigger event|manual|chain] <homeDir>。
|
|
5
|
+
* `--hold-ms`(持有锁 N 毫秒后再收尾,供集成测试观察"作业运行中"的锁
|
|
6
|
+
* 状态;生产不传,作业即时完成)。`--trigger` 记录触发来源入 job-run
|
|
7
|
+
* 档案(缺省 event;chain 为补跑链起,run-backfill D3)。锁由作业自身
|
|
8
|
+
* 持有(拉起方 spawn 前只做活锁检查,本入口再权威抢一次以防并发双拉起
|
|
9
|
+
* 竞态,抢不到立即退出、不做任何工作、不排队)。壳语义(抢锁/释放、
|
|
10
|
+
* job-run 落档含 trigger 字段、错误收口)在 `jobShell.js`——`trellis
|
|
11
|
+
* evolve run` 前台手动触发走同一壳(D1 不另写执行路径)。作业体是
|
|
12
|
+
* rule-based 治理流水的一次运行:`runEvolveJob`(信号 → rule-based
|
|
13
|
+
* 提案 → 机械校验 → L1 前闸 → 风险/采纳门 → 应用 → lesson,14.1 的
|
|
14
|
+
* fixture E2E 同链)。引擎内核(packages/evolve-engine 的
|
|
15
|
+
* schedule/recursion/proposer/selection/judge)golden 锚定但生产未经
|
|
16
|
+
* 此消费,待 L2/探针任务集接入。级别 off/observe 时作业体零副作用早退。
|
|
17
|
+
*
|
|
18
|
+
* 注意:本文件是入口脚本,被 import 时顶层即执行;测试只应 spawn 它,
|
|
19
|
+
* 不应 import(需要锁原语请从 `trigger.js` 取,作业壳从 `jobShell.js`
|
|
20
|
+
* 取,作业体从 `jobRun.js` 取)。
|
|
21
|
+
*/
|
|
22
|
+
import { runJobShell } from "./jobShell.js";
|
|
23
|
+
const TRIGGER_KINDS = ["event", "manual", "chain"];
|
|
24
|
+
const argv = process.argv.slice(2);
|
|
25
|
+
let holdMs = 0;
|
|
26
|
+
const holdIdx = argv.indexOf("--hold-ms");
|
|
27
|
+
if (holdIdx !== -1) {
|
|
28
|
+
holdMs = Number(argv[holdIdx + 1] ?? 0);
|
|
29
|
+
argv.splice(holdIdx, 2);
|
|
30
|
+
}
|
|
31
|
+
let trigger = "event";
|
|
32
|
+
const triggerIdx = argv.indexOf("--trigger");
|
|
33
|
+
if (triggerIdx !== -1) {
|
|
34
|
+
const raw = argv[triggerIdx + 1];
|
|
35
|
+
if (raw === undefined || !TRIGGER_KINDS.includes(raw)) {
|
|
36
|
+
console.error(`jobMain: --trigger 必须是 ${TRIGGER_KINDS.join(" | ")}`);
|
|
37
|
+
process.exit(2);
|
|
38
|
+
}
|
|
39
|
+
trigger = raw;
|
|
40
|
+
argv.splice(triggerIdx, 2);
|
|
41
|
+
}
|
|
42
|
+
const homeDir = argv[argv.length - 1];
|
|
43
|
+
if (homeDir === undefined || homeDir === "") {
|
|
44
|
+
console.error("jobMain: 用法 node jobMain.js [--hold-ms N] [--trigger event|manual|chain] <homeDir>");
|
|
45
|
+
process.exit(2);
|
|
46
|
+
}
|
|
47
|
+
const result = await runJobShell(homeDir, { trigger, holdMs });
|
|
48
|
+
if (result.status === "error") {
|
|
49
|
+
console.error(`jobMain: 作业体失败:${result.message}`);
|
|
50
|
+
process.exitCode = 1;
|
|
51
|
+
}
|
|
52
|
+
// lock-busy:静默退出 0(竞态窗口已有作业在跑,不排队)。
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一次性作业的作业体(trellis-evolve 任务 11.5;14.1 fixture E2E 的流水
|
|
3
|
+
* 在此落地为生产入口)。作业体是下述 rule-based 治理流水;引擎内核
|
|
4
|
+
* (packages/evolve-engine 的 schedule/recursion/proposer/selection/judge)
|
|
5
|
+
* golden 锚定但生产未经此消费,待 L2/探针任务集接入(review-fixes 纠偏
|
|
6
|
+
* 旧注"作业即运行移植引擎"的名不副实)。
|
|
7
|
+
*
|
|
8
|
+
* 流水(顺序即依赖,与 fixtureE2e 同链):
|
|
9
|
+
* 0. 级别闸:没有任何 propose/auto-A 设置 → 零副作用返回(触发器只在
|
|
10
|
+
* propose+ 拉起;这里是直接 spawn 的防御)。
|
|
11
|
+
* 1. 可选源探测(11.3):agent-bridge 缺席记 unavailable,不失败。
|
|
12
|
+
* 2. 信号合并(自上次作业的信号合并时刻以来;首次为全部,11.5"未
|
|
13
|
+
* 消费"语义)。
|
|
14
|
+
* 3. 检查发现:全局 + 信号层归属出现过/级别策略声明过的 workspace,
|
|
15
|
+
* 各自 `runChecks`(L1 的 doctor oracle)。观察面即发现面——只有
|
|
16
|
+
* 进过信号/策略的 workspace 会被扫描(与"信号是观察记录"一致)。
|
|
17
|
+
* 4. 提案草稿:rule-based 通道——`proposeFromSignals` 的信号对齐路径
|
|
18
|
+
* ∪ 当前发现直接 `ruleBasedProposal`(同一内核、提案 id 同源
|
|
19
|
+
* `rule-<findingId>` 天然去重;已入 pending 的跳过)。editor/spawn
|
|
20
|
+
* 通道(design D7)在 `runDelegatedCall` 就绪前不在作业里拉真实
|
|
21
|
+
* CLI 会话,见 `ProposalChannelAvailability` 注释。
|
|
22
|
+
* 5. 逐草稿治理:域分派(配置类才进通道)→ 机械校验(无 editor 不
|
|
23
|
+
* 重试)→ L1 前闸(verdict 差集,唯一 verdict)→ 风险分类(不采信
|
|
24
|
+
* 自报)→ 采纳门(A 仅 auto-A 且闸通过自动采纳并通知;B 永远待批)。
|
|
25
|
+
* 草稿的提案/采纳级别按其目标层解析(workspace > default;agent
|
|
26
|
+
* 粒度只闸观察与触发,不进提案产出——提案的来源 agent 不可考,
|
|
27
|
+
* 发现是 doctor 检查面而非 agent 行为)。
|
|
28
|
+
* 6. 应用阶段(12.5):全部已批准未应用的编辑在同一备份会话写入;
|
|
29
|
+
* 按 delivery 分类对原生渲染 agent 触发同步(host 门面 `triggerRenderSync`)。
|
|
30
|
+
* 7. lesson(12.6):本次应用的每条提案蒸馏一条 lesson;记忆启用走
|
|
31
|
+
* 记忆管道,否则只留档案(skipped 不是 failed)。
|
|
32
|
+
*
|
|
33
|
+
* 每步都只经 `src/host/*` 门面与本目录模块(边界 10.3);无模型调用、
|
|
34
|
+
* 不 spawn 真实 agent CLI。摘要返回给 jobMain 并入 job-run 档案。
|
|
35
|
+
*/
|
|
36
|
+
import type { CheckEntry } from "../host/index.js";
|
|
37
|
+
import { type EvolveLevelPolicy } from "../host/index.js";
|
|
38
|
+
import { type AgentEffectiveness, type ApplyAgent } from "./apply.js";
|
|
39
|
+
import { type EvaluateL1Options, type L1GateResult } from "./gate.js";
|
|
40
|
+
import { type OptionalSourceProbe } from "./sources.js";
|
|
41
|
+
import type { Proposal, Signal } from "./types.js";
|
|
42
|
+
/** 草稿来源的输入面(proposeFn 注入缝的契约)。 */
|
|
43
|
+
export interface ProposeInput {
|
|
44
|
+
readonly homeDir: string;
|
|
45
|
+
readonly signals: readonly Signal[];
|
|
46
|
+
readonly findings: readonly CheckEntry[];
|
|
47
|
+
readonly now: () => number;
|
|
48
|
+
}
|
|
49
|
+
export interface RunEvolveJobOptions {
|
|
50
|
+
/** 时间源(毫秒);缺省 Date.now(测试注入固定时钟)。 */
|
|
51
|
+
readonly now?: () => number;
|
|
52
|
+
/** 草稿来源注入缝(测试直达治理层);缺省 rule-based 通道组装。 */
|
|
53
|
+
readonly proposeFn?: (input: ProposeInput) => readonly Proposal[];
|
|
54
|
+
/** L1 前闸注入缝(与 `EvaluateL1Options` 自带缝同风格);缺省真实闸。 */
|
|
55
|
+
readonly evaluateL1Fn?: (options: EvaluateL1Options) => Promise<L1GateResult>;
|
|
56
|
+
/** 应用阶段的 agent 分类(测试传空避开真实同步);缺省按 delivery 分类。 */
|
|
57
|
+
readonly applyAgents?: readonly ApplyAgent[];
|
|
58
|
+
/** 原生渲染同步触发缝;缺省 host `triggerRenderSync`。 */
|
|
59
|
+
readonly triggerSync?: () => Promise<{
|
|
60
|
+
exitCode: number;
|
|
61
|
+
}>;
|
|
62
|
+
}
|
|
63
|
+
export interface EvolveJobSummary {
|
|
64
|
+
readonly signalsConsidered: number;
|
|
65
|
+
readonly findingsConsidered: number;
|
|
66
|
+
readonly draftCount: number;
|
|
67
|
+
readonly enqueuedIds: string[];
|
|
68
|
+
readonly autoAdoptedIds: string[];
|
|
69
|
+
readonly discarded: readonly {
|
|
70
|
+
readonly id: string;
|
|
71
|
+
readonly code: string;
|
|
72
|
+
readonly reason: string;
|
|
73
|
+
}[];
|
|
74
|
+
readonly skippedNotes: readonly string[];
|
|
75
|
+
readonly applied: {
|
|
76
|
+
readonly runId: string | null;
|
|
77
|
+
readonly appliedProposalIds: readonly string[];
|
|
78
|
+
readonly syncExitCode: number | null;
|
|
79
|
+
/** 同步触发失败文本(收口为效果文本);无为 null。 */
|
|
80
|
+
readonly syncError: string | null;
|
|
81
|
+
/** 逐 agent 生效时点(review-fixes ❌3:随 job-run 档案入档)。 */
|
|
82
|
+
readonly effects: readonly AgentEffectiveness[];
|
|
83
|
+
};
|
|
84
|
+
readonly lessonIds: string[];
|
|
85
|
+
readonly notices: string[];
|
|
86
|
+
readonly optionalSources: readonly OptionalSourceProbe[];
|
|
87
|
+
/** 信号合并时刻(步骤 2 的 now());job-run 档案据此推进消费水位线,
|
|
88
|
+
* 作业运行期间到达的信号下次仍被消费。级别闸早退为 null(未合并)。 */
|
|
89
|
+
readonly signalsMergedAtMs: number | null;
|
|
90
|
+
}
|
|
91
|
+
/** 作业级闸:任一粒度出现 propose/auto-A 才值得运行(observe 只采集)。
|
|
92
|
+
* `trellis evolve run` 的早退判定与作业体同源(1.3)。 */
|
|
93
|
+
export declare function anyProposalLevel(policy: EvolveLevelPolicy): boolean;
|
|
94
|
+
export declare function runEvolveJob(homeDir: string, options?: RunEvolveJobOptions): Promise<EvolveJobSummary>;
|