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,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 分级开启的级别策略(trellis-evolve 任务 10.2)。
|
|
3
|
+
*
|
|
4
|
+
* 级别配置是用户的显式选择,写入 canonical 的 `~/.trellis/evolve.yaml`
|
|
5
|
+
* (与 scope.yaml / secrets.policy.yaml 同级的根级策略文件;与 `evolve/`
|
|
6
|
+
* 运行状态目录刻意分开 —— 后者是只追加的运行记录,见任务 8.1)。格式、
|
|
7
|
+
* 校验与解析归 host:canonical 配置的解析是托管的职责,且 doctor(托管
|
|
8
|
+
* 命令)与自我进化消费者(`src/evolve/`,只允许 import `src/host/*`)必须
|
|
9
|
+
* 对同一份格式与解析规则达成一致 —— `src/host/` 是二者之间唯一合法的
|
|
10
|
+
* 共享缝(10.3 的边界)。卸载自我进化后本模块仍然自洽:文件不存在即
|
|
11
|
+
* 全部为 `off`,doctor 不出现该段。
|
|
12
|
+
*
|
|
13
|
+
* 文件形态(snake_case,沿用 servers.yaml 惯例):
|
|
14
|
+
*
|
|
15
|
+
* ```yaml
|
|
16
|
+
* default_level: observe
|
|
17
|
+
* agents:
|
|
18
|
+
* claude-code: propose
|
|
19
|
+
* workspaces:
|
|
20
|
+
* /abs/path/ws-a: off
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* 解析优先级(更具体覆盖更宽泛):workspace > agent > default_level > `off`。
|
|
24
|
+
* 值白名单即 `LEVELS` 四级;present-but-invalid 一律硬错误,绝不静默误读。
|
|
25
|
+
* 读取零副作用:本模块永不创建文件或目录;写入只经 `writeEvolveLevelPolicy`
|
|
26
|
+
* (12.4 的 enable/disable 管理命令调用),从不静默开启。
|
|
27
|
+
*/
|
|
28
|
+
/** 分级开启的四级,从关闭到最激进,有序(开启判定按此序)。 */
|
|
29
|
+
export declare const LEVELS: readonly ["off", "observe", "propose", "auto-A"];
|
|
30
|
+
export type Level = (typeof LEVELS)[number];
|
|
31
|
+
/** 显式级别策略。缺省的键即"未设置",向更宽泛的粒度回退。 */
|
|
32
|
+
export interface EvolveLevelPolicy {
|
|
33
|
+
/** 全局默认级别;未设置时回退为 `off`。 */
|
|
34
|
+
readonly defaultLevel?: Level;
|
|
35
|
+
/** per-agent 覆盖(agent 标识 → 级别)。 */
|
|
36
|
+
readonly agents: Readonly<Record<string, Level>>;
|
|
37
|
+
/** per-workspace 覆盖(绝对 workspace root 路径 → 级别)。 */
|
|
38
|
+
readonly workspaces: Readonly<Record<string, Level>>;
|
|
39
|
+
/** 触发/预算上限(trellis-evolve 任务 11.5,spec"可配置");缺省键
|
|
40
|
+
* 由触发器回退到内置缺省值。全部为非负整数。 */
|
|
41
|
+
readonly trigger?: EvolveTriggerLimits;
|
|
42
|
+
}
|
|
43
|
+
/** `evolve.yaml` 的 `trigger:` 段(单位见字段注释;缺省值见 evolve 侧
|
|
44
|
+
* `DEFAULT_TRIGGER_LIMITS`)。 */
|
|
45
|
+
export interface EvolveTriggerLimits {
|
|
46
|
+
/** 累计未消费信号数阈值,达到即考虑拉起作业。 */
|
|
47
|
+
readonly signalThreshold?: number;
|
|
48
|
+
/** 冷却期(分钟):上一作业结束后此时间内不再拉起。 */
|
|
49
|
+
readonly cooldownMinutes?: number;
|
|
50
|
+
/** 每日提案上限:当日提案数达到即跳过并记录原因。 */
|
|
51
|
+
readonly dailyProposalCap?: number;
|
|
52
|
+
/** 委派 editor 每日预算上限(调用次数)。 */
|
|
53
|
+
readonly editorBudget?: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 观测形态(trellis-evolve 任务 11.4):该 agent 的进化信号来源。
|
|
57
|
+
* `events` = gateway 事件观察(runtime 交付);`doctor-only` = 只有
|
|
58
|
+
* doctor/sync 信号(仅原生渲染,无事件面)。由调用方按 capability
|
|
59
|
+
* delivery 计算,本模块不读 MCP 配置。
|
|
60
|
+
*/
|
|
61
|
+
export type EvolveObservation = "events" | "doctor-only";
|
|
62
|
+
/** doctor 展示形态:各 agent 的生效级别与观测形态 + 显式 workspace 覆盖。 */
|
|
63
|
+
export interface EvolveLevelSection {
|
|
64
|
+
readonly defaultLevel?: Level;
|
|
65
|
+
readonly agents: readonly {
|
|
66
|
+
readonly agent: string;
|
|
67
|
+
readonly level: Level;
|
|
68
|
+
readonly observation: EvolveObservation;
|
|
69
|
+
}[];
|
|
70
|
+
readonly workspaces: readonly {
|
|
71
|
+
readonly root: string;
|
|
72
|
+
readonly level: Level;
|
|
73
|
+
}[];
|
|
74
|
+
readonly diagnostics: readonly string[];
|
|
75
|
+
}
|
|
76
|
+
export declare function evolveLevelPolicyPath(homeDir: string): string;
|
|
77
|
+
/**
|
|
78
|
+
* 读取级别策略。文件不存在 = 从未开启,返回空策略(全 `off`),且不
|
|
79
|
+
* 创建任何文件。present-but-invalid(坏 YAML、未知键、非法级别)抛错
|
|
80
|
+
* 带原因 —— 不静默降级成 `off`,否则用户的显式开启会被伪装成关闭。
|
|
81
|
+
*/
|
|
82
|
+
export declare function loadEvolveLevelPolicy(homeDir: string): EvolveLevelPolicy;
|
|
83
|
+
/**
|
|
84
|
+
* 显式写入级别策略(用户选择的唯一入库通道;12.4 的 enable/disable
|
|
85
|
+
* 命令调用)。幂等:相同策略重写产生相同文件。
|
|
86
|
+
*/
|
|
87
|
+
export declare function writeEvolveLevelPolicy(homeDir: string, policy: EvolveLevelPolicy): void;
|
|
88
|
+
/**
|
|
89
|
+
* 生效级别:更具体覆盖更宽泛 —— workspace 覆盖 > agent 覆盖 > 全局
|
|
90
|
+
* 默认 > `off`。会话在固定 (agent, workspace) 上下文中取值;任一粒度
|
|
91
|
+
* 未设置即向更宽泛粒度回退。
|
|
92
|
+
*/
|
|
93
|
+
export declare function resolveEvolveLevel(policy: EvolveLevelPolicy, context: {
|
|
94
|
+
agent?: string;
|
|
95
|
+
workspaceRoot?: string | null;
|
|
96
|
+
}): Level;
|
|
97
|
+
/**
|
|
98
|
+
* doctor 展示数据:托管的每个 agent 一行生效级别与观测形态(取 agent
|
|
99
|
+
* 覆盖或全局默认;workspace 覆盖单独成行)。观测形态由调用方按 capability
|
|
100
|
+
* delivery 计算后随条目传入;`agents` 之外的显式 agent 覆盖也列出(用户
|
|
101
|
+
* 设置了就可见),它们没有 runtime 交付配置,观测形态按 `doctor-only`。
|
|
102
|
+
*/
|
|
103
|
+
export declare function describeEvolveLevels(policy: EvolveLevelPolicy, managedAgents: readonly {
|
|
104
|
+
readonly agent: string;
|
|
105
|
+
readonly observation: EvolveObservation;
|
|
106
|
+
}[]): EvolveLevelSection;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 分级开启的级别策略(trellis-evolve 任务 10.2)。
|
|
3
|
+
*
|
|
4
|
+
* 级别配置是用户的显式选择,写入 canonical 的 `~/.trellis/evolve.yaml`
|
|
5
|
+
* (与 scope.yaml / secrets.policy.yaml 同级的根级策略文件;与 `evolve/`
|
|
6
|
+
* 运行状态目录刻意分开 —— 后者是只追加的运行记录,见任务 8.1)。格式、
|
|
7
|
+
* 校验与解析归 host:canonical 配置的解析是托管的职责,且 doctor(托管
|
|
8
|
+
* 命令)与自我进化消费者(`src/evolve/`,只允许 import `src/host/*`)必须
|
|
9
|
+
* 对同一份格式与解析规则达成一致 —— `src/host/` 是二者之间唯一合法的
|
|
10
|
+
* 共享缝(10.3 的边界)。卸载自我进化后本模块仍然自洽:文件不存在即
|
|
11
|
+
* 全部为 `off`,doctor 不出现该段。
|
|
12
|
+
*
|
|
13
|
+
* 文件形态(snake_case,沿用 servers.yaml 惯例):
|
|
14
|
+
*
|
|
15
|
+
* ```yaml
|
|
16
|
+
* default_level: observe
|
|
17
|
+
* agents:
|
|
18
|
+
* claude-code: propose
|
|
19
|
+
* workspaces:
|
|
20
|
+
* /abs/path/ws-a: off
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* 解析优先级(更具体覆盖更宽泛):workspace > agent > default_level > `off`。
|
|
24
|
+
* 值白名单即 `LEVELS` 四级;present-but-invalid 一律硬错误,绝不静默误读。
|
|
25
|
+
* 读取零副作用:本模块永不创建文件或目录;写入只经 `writeEvolveLevelPolicy`
|
|
26
|
+
* (12.4 的 enable/disable 管理命令调用),从不静默开启。
|
|
27
|
+
*/
|
|
28
|
+
import { mkdirSync, readFileSync } from "node:fs";
|
|
29
|
+
import { dirname, join } from "node:path";
|
|
30
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
31
|
+
import { writeFileAtomicSync } from "../lib/fsAtomic.js";
|
|
32
|
+
/** 分级开启的四级,从关闭到最激进,有序(开启判定按此序)。 */
|
|
33
|
+
export const LEVELS = ["off", "observe", "propose", "auto-A"];
|
|
34
|
+
const LABEL = "evolve.yaml";
|
|
35
|
+
function isLevel(value) {
|
|
36
|
+
return typeof value === "string" && LEVELS.includes(value);
|
|
37
|
+
}
|
|
38
|
+
export function evolveLevelPolicyPath(homeDir) {
|
|
39
|
+
return join(homeDir, ".trellis", LABEL);
|
|
40
|
+
}
|
|
41
|
+
const EMPTY_POLICY = { agents: {}, workspaces: {} };
|
|
42
|
+
/**
|
|
43
|
+
* 读取级别策略。文件不存在 = 从未开启,返回空策略(全 `off`),且不
|
|
44
|
+
* 创建任何文件。present-but-invalid(坏 YAML、未知键、非法级别)抛错
|
|
45
|
+
* 带原因 —— 不静默降级成 `off`,否则用户的显式开启会被伪装成关闭。
|
|
46
|
+
*/
|
|
47
|
+
export function loadEvolveLevelPolicy(homeDir) {
|
|
48
|
+
let text;
|
|
49
|
+
try {
|
|
50
|
+
text = readFileSync(evolveLevelPolicyPath(homeDir), "utf-8");
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return EMPTY_POLICY;
|
|
54
|
+
}
|
|
55
|
+
let parsed;
|
|
56
|
+
try {
|
|
57
|
+
parsed = parseYaml(text);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
throw new Error(`${LABEL}: invalid YAML — ${err.message}`);
|
|
61
|
+
}
|
|
62
|
+
if (parsed === null || parsed === undefined)
|
|
63
|
+
return EMPTY_POLICY;
|
|
64
|
+
if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
65
|
+
throw new Error(`${LABEL}: top level must be a mapping { default_level?, agents?, workspaces? }`);
|
|
66
|
+
}
|
|
67
|
+
const raw = parsed;
|
|
68
|
+
for (const key of Object.keys(raw)) {
|
|
69
|
+
if (key !== "default_level" && key !== "agents" && key !== "workspaces" && key !== "trigger") {
|
|
70
|
+
throw new Error(`${LABEL}: unrecognized top-level key "${key}"`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
let defaultLevel;
|
|
74
|
+
if (raw.default_level !== undefined) {
|
|
75
|
+
if (!isLevel(raw.default_level)) {
|
|
76
|
+
throw new Error(`${LABEL}: "default_level" must be one of ${LEVELS.join(" | ")} — got ${JSON.stringify(raw.default_level)}`);
|
|
77
|
+
}
|
|
78
|
+
defaultLevel = raw.default_level;
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
...(defaultLevel !== undefined ? { defaultLevel } : {}),
|
|
82
|
+
agents: parseLevelMap(raw.agents, "agents"),
|
|
83
|
+
workspaces: parseLevelMap(raw.workspaces, "workspaces"),
|
|
84
|
+
...(raw.trigger !== undefined ? { trigger: parseTriggerLimits(raw.trigger) } : {}),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const TRIGGER_KEYS = {
|
|
88
|
+
signal_threshold: "signalThreshold",
|
|
89
|
+
cooldown_minutes: "cooldownMinutes",
|
|
90
|
+
daily_proposal_cap: "dailyProposalCap",
|
|
91
|
+
editor_budget: "editorBudget",
|
|
92
|
+
};
|
|
93
|
+
function parseTriggerLimits(value) {
|
|
94
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
95
|
+
throw new Error(`${LABEL}: "trigger" must be a mapping of ${Object.keys(TRIGGER_KEYS).join(" | ")}`);
|
|
96
|
+
}
|
|
97
|
+
const limits = {};
|
|
98
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
99
|
+
const camel = TRIGGER_KEYS[key];
|
|
100
|
+
if (camel === undefined) {
|
|
101
|
+
throw new Error(`${LABEL}: "trigger" has unrecognized key "${key}" — expected one of ${Object.keys(TRIGGER_KEYS).join(" | ")}`);
|
|
102
|
+
}
|
|
103
|
+
if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 0) {
|
|
104
|
+
throw new Error(`${LABEL}: "trigger.${key}" must be a non-negative integer — got ${JSON.stringify(raw)}`);
|
|
105
|
+
}
|
|
106
|
+
limits[camel] = raw;
|
|
107
|
+
}
|
|
108
|
+
// 缺省键不出现(由触发器回退到内置缺省值)。
|
|
109
|
+
return limits;
|
|
110
|
+
}
|
|
111
|
+
function parseLevelMap(value, field) {
|
|
112
|
+
if (value === undefined)
|
|
113
|
+
return {};
|
|
114
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
115
|
+
throw new Error(`${LABEL}: "${field}" must be a mapping of key → level`);
|
|
116
|
+
}
|
|
117
|
+
const out = {};
|
|
118
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
119
|
+
if (key.length === 0) {
|
|
120
|
+
throw new Error(`${LABEL}: "${field}" keys must be non-empty strings`);
|
|
121
|
+
}
|
|
122
|
+
if (!isLevel(raw)) {
|
|
123
|
+
throw new Error(`${LABEL}: "${field}.${key}" must be one of ${LEVELS.join(" | ")} — got ${JSON.stringify(raw)}`);
|
|
124
|
+
}
|
|
125
|
+
out[key] = raw;
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 显式写入级别策略(用户选择的唯一入库通道;12.4 的 enable/disable
|
|
131
|
+
* 命令调用)。幂等:相同策略重写产生相同文件。
|
|
132
|
+
*/
|
|
133
|
+
export function writeEvolveLevelPolicy(homeDir, policy) {
|
|
134
|
+
const doc = {};
|
|
135
|
+
if (policy.defaultLevel !== undefined)
|
|
136
|
+
doc.default_level = policy.defaultLevel;
|
|
137
|
+
if (Object.keys(policy.agents).length > 0)
|
|
138
|
+
doc.agents = policy.agents;
|
|
139
|
+
if (Object.keys(policy.workspaces).length > 0)
|
|
140
|
+
doc.workspaces = policy.workspaces;
|
|
141
|
+
if (policy.trigger !== undefined && Object.keys(policy.trigger).length > 0) {
|
|
142
|
+
const trigger = {};
|
|
143
|
+
if (policy.trigger.signalThreshold !== undefined)
|
|
144
|
+
trigger.signal_threshold = policy.trigger.signalThreshold;
|
|
145
|
+
if (policy.trigger.cooldownMinutes !== undefined)
|
|
146
|
+
trigger.cooldown_minutes = policy.trigger.cooldownMinutes;
|
|
147
|
+
if (policy.trigger.dailyProposalCap !== undefined)
|
|
148
|
+
trigger.daily_proposal_cap = policy.trigger.dailyProposalCap;
|
|
149
|
+
if (policy.trigger.editorBudget !== undefined)
|
|
150
|
+
trigger.editor_budget = policy.trigger.editorBudget;
|
|
151
|
+
doc.trigger = trigger;
|
|
152
|
+
}
|
|
153
|
+
const path = evolveLevelPolicyPath(homeDir);
|
|
154
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
155
|
+
// 原子写:崩溃不留半截 YAML——present-but-invalid 会硬抛,半写文件
|
|
156
|
+
// 会让 doctor/recorder 等全部读取面报错。
|
|
157
|
+
writeFileAtomicSync(path, stringifyYaml(doc));
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* 生效级别:更具体覆盖更宽泛 —— workspace 覆盖 > agent 覆盖 > 全局
|
|
161
|
+
* 默认 > `off`。会话在固定 (agent, workspace) 上下文中取值;任一粒度
|
|
162
|
+
* 未设置即向更宽泛粒度回退。
|
|
163
|
+
*/
|
|
164
|
+
export function resolveEvolveLevel(policy, context) {
|
|
165
|
+
const root = context.workspaceRoot;
|
|
166
|
+
if (root != null) {
|
|
167
|
+
const workspaceLevel = policy.workspaces[root];
|
|
168
|
+
if (workspaceLevel !== undefined)
|
|
169
|
+
return workspaceLevel;
|
|
170
|
+
}
|
|
171
|
+
if (context.agent !== undefined) {
|
|
172
|
+
const agentLevel = policy.agents[context.agent];
|
|
173
|
+
if (agentLevel !== undefined)
|
|
174
|
+
return agentLevel;
|
|
175
|
+
}
|
|
176
|
+
return policy.defaultLevel ?? "off";
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* doctor 展示数据:托管的每个 agent 一行生效级别与观测形态(取 agent
|
|
180
|
+
* 覆盖或全局默认;workspace 覆盖单独成行)。观测形态由调用方按 capability
|
|
181
|
+
* delivery 计算后随条目传入;`agents` 之外的显式 agent 覆盖也列出(用户
|
|
182
|
+
* 设置了就可见),它们没有 runtime 交付配置,观测形态按 `doctor-only`。
|
|
183
|
+
*/
|
|
184
|
+
export function describeEvolveLevels(policy, managedAgents) {
|
|
185
|
+
const seen = new Set();
|
|
186
|
+
const agents = [];
|
|
187
|
+
for (const entry of managedAgents) {
|
|
188
|
+
if (seen.has(entry.agent))
|
|
189
|
+
continue;
|
|
190
|
+
seen.add(entry.agent);
|
|
191
|
+
agents.push({ agent: entry.agent, level: resolveEvolveLevel(policy, { agent: entry.agent }), observation: entry.observation });
|
|
192
|
+
}
|
|
193
|
+
for (const agent of Object.keys(policy.agents)) {
|
|
194
|
+
if (seen.has(agent))
|
|
195
|
+
continue;
|
|
196
|
+
seen.add(agent);
|
|
197
|
+
agents.push({ agent, level: resolveEvolveLevel(policy, { agent }), observation: "doctor-only" });
|
|
198
|
+
}
|
|
199
|
+
const workspaces = Object.entries(policy.workspaces).map(([root, level]) => ({ root, level }));
|
|
200
|
+
return {
|
|
201
|
+
...(policy.defaultLevel !== undefined ? { defaultLevel: policy.defaultLevel } : {}),
|
|
202
|
+
agents,
|
|
203
|
+
workspaces,
|
|
204
|
+
diagnostics: [],
|
|
205
|
+
};
|
|
206
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `host` facade — scratch staging (host-extension-points spec).
|
|
3
|
+
*
|
|
4
|
+
* Candidate edits must be evaluated against a COPY of the canonical
|
|
5
|
+
* surface, never the live one: `staging 后真实 canonical 与 agent 原生
|
|
6
|
+
* 配置逐字节不变` is a hard scenario of the spec. `createStaging` builds
|
|
7
|
+
* a disposable scratch home (and optional scratch workspace) that the
|
|
8
|
+
* check/apply/rollback half of the facade can read and write freely.
|
|
9
|
+
*
|
|
10
|
+
* What gets copied is deliberately bounded by `NATIVE_CHECK_SURFACE`:
|
|
11
|
+
* only the configuration and skill surfaces the machine-readable checks
|
|
12
|
+
* actually read. Account caches and credential stores — `.codex/auth.json`,
|
|
13
|
+
* `.claude/projects`, OAuth token files — are NOT part of that surface and
|
|
14
|
+
* never leave the real home (same boundary as the morphix-env rule: never
|
|
15
|
+
* read or replicate agent account caches).
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Home-relative paths the machine-readable checks read — presence probes,
|
|
19
|
+
* native config files, and skill roots for each agent. The staging copy
|
|
20
|
+
* is exactly this set plus `~/.trellis/`, nothing more. Paths here are
|
|
21
|
+
* the ONLY agent-native content that may be replicated into a scratch
|
|
22
|
+
* home; anything credential-shaped stays out by construction.
|
|
23
|
+
*/
|
|
24
|
+
export declare const NATIVE_CHECK_SURFACE: readonly string[];
|
|
25
|
+
export interface CreateStagingOptions {
|
|
26
|
+
homeDir: string;
|
|
27
|
+
/** Real workspace root to replicate, or omit/null for global-only. */
|
|
28
|
+
workspaceRoot?: string | null;
|
|
29
|
+
/** Where to place the scratch root. Defaults to the OS temp dir. */
|
|
30
|
+
tmpRoot?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface Staging {
|
|
33
|
+
/** Scratch root: everything lives under here and dies with `dispose()`. */
|
|
34
|
+
root: string;
|
|
35
|
+
/** Scratch home — pass this where the facade wants `homeDir`. */
|
|
36
|
+
homeDir: string;
|
|
37
|
+
/** Scratch workspace root (`.agents/` layer + workspace MCP configs),
|
|
38
|
+
* or null when staging is global-only. Pass this where the facade wants
|
|
39
|
+
* `workspaceRoot`. */
|
|
40
|
+
workspaceRoot: string | null;
|
|
41
|
+
/** The REAL workspace root this staging replicates — the stable label
|
|
42
|
+
* for layer attribution and id normalization. Physical scratch paths
|
|
43
|
+
* must never appear in comparable identities. */
|
|
44
|
+
logicalWorkspaceRoot: string | null;
|
|
45
|
+
/** Delete the entire scratch tree. Safe to call twice. */
|
|
46
|
+
dispose(): void;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Build a disposable scratch home (and optional scratch workspace).
|
|
50
|
+
* Copies `~/.trellis/` minus credential/state subtrees (`mcp/oauth`,
|
|
51
|
+
* `backups`, `evolve` — see TRELLIS_STAGING_EXCLUDES) plus exactly
|
|
52
|
+
* `NATIVE_CHECK_SURFACE`, then the workspace tree minus `.git`. The real
|
|
53
|
+
* home and workspace are never written to, and credential material never
|
|
54
|
+
* leaves it.
|
|
55
|
+
*/
|
|
56
|
+
export declare function createStaging(options: CreateStagingOptions): Staging;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `host` facade — scratch staging (host-extension-points spec).
|
|
3
|
+
*
|
|
4
|
+
* Candidate edits must be evaluated against a COPY of the canonical
|
|
5
|
+
* surface, never the live one: `staging 后真实 canonical 与 agent 原生
|
|
6
|
+
* 配置逐字节不变` is a hard scenario of the spec. `createStaging` builds
|
|
7
|
+
* a disposable scratch home (and optional scratch workspace) that the
|
|
8
|
+
* check/apply/rollback half of the facade can read and write freely.
|
|
9
|
+
*
|
|
10
|
+
* What gets copied is deliberately bounded by `NATIVE_CHECK_SURFACE`:
|
|
11
|
+
* only the configuration and skill surfaces the machine-readable checks
|
|
12
|
+
* actually read. Account caches and credential stores — `.codex/auth.json`,
|
|
13
|
+
* `.claude/projects`, OAuth token files — are NOT part of that surface and
|
|
14
|
+
* never leave the real home (same boundary as the morphix-env rule: never
|
|
15
|
+
* read or replicate agent account caches).
|
|
16
|
+
*/
|
|
17
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
|
|
18
|
+
import { basename, dirname, join, relative, sep } from "node:path";
|
|
19
|
+
import { tmpdir } from "node:os";
|
|
20
|
+
/**
|
|
21
|
+
* Home-relative paths the machine-readable checks read — presence probes,
|
|
22
|
+
* native config files, and skill roots for each agent. The staging copy
|
|
23
|
+
* is exactly this set plus `~/.trellis/`, nothing more. Paths here are
|
|
24
|
+
* the ONLY agent-native content that may be replicated into a scratch
|
|
25
|
+
* home; anything credential-shaped stays out by construction.
|
|
26
|
+
*/
|
|
27
|
+
export const NATIVE_CHECK_SURFACE = [
|
|
28
|
+
// claude-code (probe: `.claude.json` exists)
|
|
29
|
+
".claude.json",
|
|
30
|
+
".claude/CLAUDE.md",
|
|
31
|
+
".claude/skills",
|
|
32
|
+
".claude/agents",
|
|
33
|
+
// codex (probe: `.codex/config.toml` exists)
|
|
34
|
+
".codex/config.toml",
|
|
35
|
+
".codex/skills",
|
|
36
|
+
// kiro
|
|
37
|
+
".kiro/settings",
|
|
38
|
+
".kiro/skills",
|
|
39
|
+
".kiro/steering",
|
|
40
|
+
// kimi-code
|
|
41
|
+
".kimi-code/mcp.json",
|
|
42
|
+
// zcode
|
|
43
|
+
".zcode/cli",
|
|
44
|
+
".zcode/skills",
|
|
45
|
+
".zcode/AGENTS.md",
|
|
46
|
+
// pi
|
|
47
|
+
".pi/agent",
|
|
48
|
+
// home-level shared skills root (codex plan reads `.agents/skills`)
|
|
49
|
+
".agents/skills",
|
|
50
|
+
// kiro adapter's user-level settings file (macOS layout)
|
|
51
|
+
"Library/Application Support/Kiro/User/settings.json",
|
|
52
|
+
];
|
|
53
|
+
/** True unless `src` is a `.git` directory — VCS metadata is neither a
|
|
54
|
+
* check surface nor something a staging copy needs. */
|
|
55
|
+
function notGitDir(src) {
|
|
56
|
+
return basename(src) !== ".git";
|
|
57
|
+
}
|
|
58
|
+
/** `.trellis` subtrees that NEVER leave the real home: credential stores
|
|
59
|
+
* (mcp/oauth token material), backup sessions, and evolve run state
|
|
60
|
+
* (pending records carry config file contents). A job killed mid-gate
|
|
61
|
+
* must not leave tokens behind in /tmp. */
|
|
62
|
+
const TRELLIS_STAGING_EXCLUDES = ["mcp/oauth", "backups", "evolve"];
|
|
63
|
+
function trellisStagingFilter(sourceRoot) {
|
|
64
|
+
return (src) => {
|
|
65
|
+
const rel = relative(sourceRoot, src).split(sep).join("/");
|
|
66
|
+
return !TRELLIS_STAGING_EXCLUDES.some((excluded) => rel === excluded || rel.startsWith(excluded + "/"));
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function copyIfExists(from, to, filter) {
|
|
70
|
+
if (!existsSync(from))
|
|
71
|
+
return;
|
|
72
|
+
mkdirSync(dirname(to), { recursive: true });
|
|
73
|
+
cpSync(from, to, { recursive: true, ...(filter ? { filter } : {}) });
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Build a disposable scratch home (and optional scratch workspace).
|
|
77
|
+
* Copies `~/.trellis/` minus credential/state subtrees (`mcp/oauth`,
|
|
78
|
+
* `backups`, `evolve` — see TRELLIS_STAGING_EXCLUDES) plus exactly
|
|
79
|
+
* `NATIVE_CHECK_SURFACE`, then the workspace tree minus `.git`. The real
|
|
80
|
+
* home and workspace are never written to, and credential material never
|
|
81
|
+
* leaves it.
|
|
82
|
+
*/
|
|
83
|
+
export function createStaging(options) {
|
|
84
|
+
const logicalWorkspaceRoot = options.workspaceRoot ?? null;
|
|
85
|
+
const root = mkdtempSync(join(options.tmpRoot ?? tmpdir(), "trellis-staging-"));
|
|
86
|
+
const scratchHome = join(root, "home");
|
|
87
|
+
mkdirSync(scratchHome, { recursive: true });
|
|
88
|
+
copyIfExists(join(options.homeDir, ".trellis"), join(scratchHome, ".trellis"), trellisStagingFilter(join(options.homeDir, ".trellis")));
|
|
89
|
+
for (const rel of NATIVE_CHECK_SURFACE) {
|
|
90
|
+
copyIfExists(join(options.homeDir, rel), join(scratchHome, rel), notGitDir);
|
|
91
|
+
}
|
|
92
|
+
let scratchWorkspace = null;
|
|
93
|
+
if (logicalWorkspaceRoot) {
|
|
94
|
+
scratchWorkspace = join(root, "workspace");
|
|
95
|
+
copyIfExists(logicalWorkspaceRoot, scratchWorkspace, notGitDir);
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
root,
|
|
99
|
+
homeDir: scratchHome,
|
|
100
|
+
workspaceRoot: scratchWorkspace,
|
|
101
|
+
logicalWorkspaceRoot,
|
|
102
|
+
dispose() {
|
|
103
|
+
rmSync(root, { recursive: true, force: true });
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `host` facade — surface read (host-extension-points spec).
|
|
3
|
+
*
|
|
4
|
+
* The narrow host boundary the self-evolution consumer codes against:
|
|
5
|
+
* every function here is part of the published `./host` contract, and
|
|
6
|
+
* nothing from `src/adapters/*` or `src/commands/*` is re-exported
|
|
7
|
+
* through it. This module is the read half: hand the facade a home
|
|
8
|
+
* directory (and optionally a workspace root) and get back the canonical
|
|
9
|
+
* surface for that exact layer — read-only, no mutation, no rendering.
|
|
10
|
+
*/
|
|
11
|
+
import type { CanonicalSource } from "../core/types.js";
|
|
12
|
+
/** Which layer a surface read (or a structured check entry) belongs to.
|
|
13
|
+
* A workspace layer is always pinned to a concrete root so two different
|
|
14
|
+
* workspaces never share an identity. */
|
|
15
|
+
export type SurfaceLayer = {
|
|
16
|
+
kind: "global";
|
|
17
|
+
} | {
|
|
18
|
+
kind: "workspace";
|
|
19
|
+
root: string;
|
|
20
|
+
};
|
|
21
|
+
export interface ReadSurfaceOptions {
|
|
22
|
+
homeDir: string;
|
|
23
|
+
/** Explicit workspace root (as `findWorkspaceRoot` would return).
|
|
24
|
+
* Omit or pass null for the global layer only. Never inferred from
|
|
25
|
+
* `process.cwd()` — the host facade never guesses which project the
|
|
26
|
+
* caller means. */
|
|
27
|
+
workspaceRoot?: string | null;
|
|
28
|
+
}
|
|
29
|
+
export interface SurfaceRead {
|
|
30
|
+
source: CanonicalSource;
|
|
31
|
+
layer: SurfaceLayer;
|
|
32
|
+
}
|
|
33
|
+
/** Read the canonical surface at the requested layer. Read-only: the
|
|
34
|
+
* returned `CanonicalSource` is the same structure every other reader
|
|
35
|
+
* sees; this function writes nothing. */
|
|
36
|
+
export declare function readSurface(options: ReadSurfaceOptions): SurfaceRead;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `host` facade — surface read (host-extension-points spec).
|
|
3
|
+
*
|
|
4
|
+
* The narrow host boundary the self-evolution consumer codes against:
|
|
5
|
+
* every function here is part of the published `./host` contract, and
|
|
6
|
+
* nothing from `src/adapters/*` or `src/commands/*` is re-exported
|
|
7
|
+
* through it. This module is the read half: hand the facade a home
|
|
8
|
+
* directory (and optionally a workspace root) and get back the canonical
|
|
9
|
+
* surface for that exact layer — read-only, no mutation, no rendering.
|
|
10
|
+
*/
|
|
11
|
+
import { loadCanonicalSource } from "../core/canonical.js";
|
|
12
|
+
/** Read the canonical surface at the requested layer. Read-only: the
|
|
13
|
+
* returned `CanonicalSource` is the same structure every other reader
|
|
14
|
+
* sees; this function writes nothing. */
|
|
15
|
+
export function readSurface(options) {
|
|
16
|
+
const workspaceRoot = options.workspaceRoot ?? null;
|
|
17
|
+
const source = loadCanonicalSource({ homeDir: options.homeDir, workspaceDir: workspaceRoot });
|
|
18
|
+
const layer = workspaceRoot ? { kind: "workspace", root: workspaceRoot } : { kind: "global" };
|
|
19
|
+
return { source, layer };
|
|
20
|
+
}
|
package/dist/lib/backup.js
CHANGED
|
@@ -73,21 +73,29 @@ export function openBackupSession(homeDir, command) {
|
|
|
73
73
|
writeFile(path, content) {
|
|
74
74
|
ensureDir();
|
|
75
75
|
const afterHash = sha256(content);
|
|
76
|
+
// Record AFTER the write lands: an operation logged before its write
|
|
77
|
+
// is a phantom if the write throws (e.g. missing parent dir) — the
|
|
78
|
+
// manifest would claim a file-create for a path that never existed,
|
|
79
|
+
// and rollback would report a spurious conflict instead of "nothing
|
|
80
|
+
// to undo". The overwrite branch's before-snapshot must still be
|
|
81
|
+
// captured first, but it is an unreferenced file in the run dir
|
|
82
|
+
// until the push below references it — harmless on failure.
|
|
83
|
+
let operation;
|
|
76
84
|
if (existsSync(path)) {
|
|
77
85
|
const before = readFileSync(path, "utf-8");
|
|
78
86
|
const relSnapshot = join("files", `${fileIndex}-${basename(path)}`);
|
|
79
87
|
fileIndex += 1;
|
|
80
88
|
writeFileSync(join(runDir, relSnapshot), before);
|
|
81
|
-
|
|
89
|
+
operation = {
|
|
82
90
|
kind: "file-overwrite",
|
|
83
91
|
path,
|
|
84
92
|
beforeFile: relSnapshot,
|
|
85
93
|
beforeHash: sha256(before),
|
|
86
94
|
afterHash,
|
|
87
|
-
}
|
|
95
|
+
};
|
|
88
96
|
}
|
|
89
97
|
else {
|
|
90
|
-
|
|
98
|
+
operation = { kind: "file-create", path, afterHash };
|
|
91
99
|
}
|
|
92
100
|
// A lock/provenance update must not leave a partially-written JSON file
|
|
93
101
|
// if the process stops mid-write. This also strengthens the existing
|
|
@@ -95,6 +103,7 @@ export function openBackupSession(homeDir, command) {
|
|
|
95
103
|
const temporary = join(dirname(path), `.${basename(path)}.${process.pid}.${fileIndex}.tmp`);
|
|
96
104
|
writeFileSync(temporary, content);
|
|
97
105
|
renameSync(temporary, path);
|
|
106
|
+
operations.push(operation);
|
|
98
107
|
},
|
|
99
108
|
async createSymlink(path, linkTarget) {
|
|
100
109
|
ensureDir();
|
package/dist/lib/envVarNames.js
CHANGED
|
@@ -22,7 +22,14 @@ export function extractTemplateVarNames(value) {
|
|
|
22
22
|
* same answer to "what names does this def reference," not two
|
|
23
23
|
* separately-maintained collections. */
|
|
24
24
|
export function declaredEnvNames(def) {
|
|
25
|
+
// RAW entries, including `envAliases` sources (mx-env-secret-source
|
|
26
|
+
// secrets-audit delta): consumers that RESOLVE must see the original
|
|
27
|
+
// `mxenv://` text so references go through composition, then look the
|
|
28
|
+
// result up by delivered name; consumers that want the delivered NAMES
|
|
29
|
+
// map through `deliveredName` at their own use site (e.g. Kiro).
|
|
25
30
|
const names = new Set(def.env ?? []);
|
|
31
|
+
for (const source of Object.values(def.envAliases ?? {}))
|
|
32
|
+
names.add(source);
|
|
26
33
|
for (const value of Object.values(def.headers ?? {})) {
|
|
27
34
|
for (const name of extractTemplateVarNames(value))
|
|
28
35
|
names.add(name);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 原子文件写(tmp + rename):进程崩溃不留半截文件,读取方要么看到
|
|
3
|
+
* 旧内容要么看到新内容。canonical 配置与 pending 记录的写路径统一走
|
|
4
|
+
* 这里(对照:运行档案是只追加 JSONL,语义不同,不用本助手)。
|
|
5
|
+
*/
|
|
6
|
+
import { randomBytes } from "node:crypto";
|
|
7
|
+
import { renameSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
/** 原子写入整个文件(调用方负责确保目录存在)。 */
|
|
10
|
+
export function writeFileAtomicSync(path, content) {
|
|
11
|
+
const temporary = join(dirname(path), `.atomic-${process.pid}-${randomBytes(6).toString("hex")}.tmp`);
|
|
12
|
+
writeFileSync(temporary, content, "utf-8");
|
|
13
|
+
renameSync(temporary, path);
|
|
14
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-process host event bus (host-extension-points spec).
|
|
3
|
+
*
|
|
4
|
+
* Dispatch is synchronous and ordered: `emit` walks listeners at the event
|
|
5
|
+
* point so a session-end event is observably delivered before upstream
|
|
6
|
+
* release continues. Failures are contained per listener — a synchronous
|
|
7
|
+
* throw or a rejected promise is swallowed and warned, never propagated to
|
|
8
|
+
* the tool call or to sibling listeners. With no subscribers `emit` is a
|
|
9
|
+
* single length check (zero overhead; runtime behavior stays byte-identical
|
|
10
|
+
* to the pre-event world). Listeners are expected to return promptly; slow
|
|
11
|
+
* work belongs behind the returned promise, which the caller never awaits.
|
|
12
|
+
*/
|
|
13
|
+
import { type HostEvent, type ToolCallInfo } from "../host/hostEvents.js";
|
|
14
|
+
export type HostEventListener = (event: HostEvent) => void | Promise<void>;
|
|
15
|
+
export declare class HostEventBus {
|
|
16
|
+
private readonly warn;
|
|
17
|
+
private readonly listeners;
|
|
18
|
+
constructor(warn?: (message: string) => void);
|
|
19
|
+
/** Register a listener; returns its unsubscribe function. Registration
|
|
20
|
+
* failure isolation for extensions is the loader's job (8.6) — this method
|
|
21
|
+
* only ever pushes. */
|
|
22
|
+
subscribe(listener: HostEventListener): () => void;
|
|
23
|
+
emit(event: HostEvent): void;
|
|
24
|
+
get subscriberCount(): number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Stamps events with process-level identity. One instance per gateway
|
|
28
|
+
* process: `sessionId` is generated here and carried unchanged on every
|
|
29
|
+
* event the process emits ("会话开始"/tool calls/"会话结束").
|
|
30
|
+
*/
|
|
31
|
+
export declare class HostEventEmitter {
|
|
32
|
+
private readonly opts;
|
|
33
|
+
readonly sessionId: string;
|
|
34
|
+
constructor(opts: {
|
|
35
|
+
bus: HostEventBus;
|
|
36
|
+
agent: string;
|
|
37
|
+
/** Workspace root observed by the process; null = no workspace
|
|
38
|
+
* attribution (consumers derive layer from this). */
|
|
39
|
+
workspaceRoot: string | null;
|
|
40
|
+
now?: () => number;
|
|
41
|
+
sessionId?: string;
|
|
42
|
+
});
|
|
43
|
+
private ts;
|
|
44
|
+
sessionStart(): void;
|
|
45
|
+
toolCall(info: ToolCallInfo): void;
|
|
46
|
+
sessionEnd(): void;
|
|
47
|
+
}
|