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
|
@@ -48,10 +48,10 @@ export class ClaudeCodeAdapter {
|
|
|
48
48
|
canonicalRoot,
|
|
49
49
|
kind: "instructions",
|
|
50
50
|
});
|
|
51
|
-
const mcpItems = this.planMcp(canonical);
|
|
51
|
+
const mcpItems = await this.planMcp(canonical);
|
|
52
52
|
return [...skillItems, ...instructionsItems, ...mcpItems];
|
|
53
53
|
}
|
|
54
|
-
planMcp(canonical) {
|
|
54
|
+
async planMcp(canonical) {
|
|
55
55
|
const configPath = join(this.homeDir, ".claude.json");
|
|
56
56
|
const parsed = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf-8")) : undefined;
|
|
57
57
|
const ownership = ownedByAgent(loadMcpOwnership(this.homeDir), this.id);
|
package/dist/adapters/codex.js
CHANGED
|
@@ -50,7 +50,7 @@ export class CodexAdapter {
|
|
|
50
50
|
canonicalRoot: join(canonicalRoot, "skills"),
|
|
51
51
|
kind: "skill",
|
|
52
52
|
});
|
|
53
|
-
const mcpItems = this.planMcp(canonical);
|
|
53
|
+
const mcpItems = await this.planMcp(canonical);
|
|
54
54
|
const configuredInstructionsPath = readInstructionsPath(join(this.homeDir, ".codex", "config.toml"));
|
|
55
55
|
if (!configuredInstructionsPath) {
|
|
56
56
|
// Codex has no `instructions` key set — not Trellis's to decide.
|
|
@@ -65,10 +65,10 @@ export class CodexAdapter {
|
|
|
65
65
|
});
|
|
66
66
|
return [...skillItems, ...instructionsItems, ...mcpItems];
|
|
67
67
|
}
|
|
68
|
-
planMcp(canonical) {
|
|
68
|
+
async planMcp(canonical) {
|
|
69
69
|
const configTomlPath = join(this.homeDir, ".codex", "config.toml");
|
|
70
70
|
const content = existsSync(configTomlPath) ? readFileSync(configTomlPath, "utf-8") : "";
|
|
71
|
-
const { desired, conflicts } = resolveMcpPlan(this.id, globalLayerMcp(canonical), canonical.managedAgents, canonical.secretsPolicy);
|
|
71
|
+
const { desired, conflicts } = await resolveMcpPlan(this.id, globalLayerMcp(canonical), canonical.managedAgents, canonical.secretsPolicy);
|
|
72
72
|
const items = [];
|
|
73
73
|
for (const { name, def } of desired) {
|
|
74
74
|
const current = currentServerSectionText(content, name);
|
|
@@ -29,7 +29,7 @@ export declare function planJsonMcp(opts: {
|
|
|
29
29
|
* zero removal candidates. */
|
|
30
30
|
ownership?: Record<string, unknown>;
|
|
31
31
|
render?: JsonMcpEntryRenderer;
|
|
32
|
-
}): AdapterPlanItem[]
|
|
32
|
+
}): Promise<AdapterPlanItem[]>;
|
|
33
33
|
/** Merges every `"create"` MCP item into `parsed` (or a fresh `{}` if the
|
|
34
34
|
* file didn't exist) and deletes every `"remove"` item's key, returning
|
|
35
35
|
* the object to stringify — every sibling top-level key on `parsed` is
|
package/dist/adapters/jsonMcp.js
CHANGED
|
@@ -30,10 +30,10 @@ export function renderJsonServerEntry(def) {
|
|
|
30
30
|
}
|
|
31
31
|
return entry;
|
|
32
32
|
}
|
|
33
|
-
export function planJsonMcp(opts) {
|
|
33
|
+
export async function planJsonMcp(opts) {
|
|
34
34
|
const { configPath, parsed, mcp, agentId, managedAgents, policy, ownership } = opts;
|
|
35
35
|
const render = opts.render ?? ((def, _name) => renderJsonServerEntry(def));
|
|
36
|
-
const { desired, conflicts } = resolveMcpPlan(agentId, mcp, managedAgents, policy);
|
|
36
|
+
const { desired, conflicts } = await resolveMcpPlan(agentId, mcp, managedAgents, policy);
|
|
37
37
|
const existingServers = parsed?.mcpServers ?? {};
|
|
38
38
|
const items = [];
|
|
39
39
|
for (const { name, def } of desired) {
|
|
@@ -63,7 +63,7 @@ export class KimiCodeAdapter {
|
|
|
63
63
|
const configPath = join(this.homeDir, ".kimi-code", "mcp.json");
|
|
64
64
|
const parsed = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf-8")) : undefined;
|
|
65
65
|
const ownership = ownedByAgent(loadMcpOwnership(this.homeDir), this.id);
|
|
66
|
-
items.push(...planJsonMcp({
|
|
66
|
+
items.push(...(await planJsonMcp({
|
|
67
67
|
configPath,
|
|
68
68
|
parsed,
|
|
69
69
|
mcp: globalLayerMcp(canonical),
|
|
@@ -72,7 +72,7 @@ export class KimiCodeAdapter {
|
|
|
72
72
|
policy: canonical.secretsPolicy,
|
|
73
73
|
ownership,
|
|
74
74
|
render: renderKimiServerEntry,
|
|
75
|
-
}));
|
|
75
|
+
})));
|
|
76
76
|
return items;
|
|
77
77
|
}
|
|
78
78
|
async apply(plan, backup) {
|
package/dist/adapters/kiro.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
19
19
|
import { dirname, join } from "node:path";
|
|
20
20
|
import { homedir } from "node:os";
|
|
21
|
+
import { deliveredName } from "../lib/secretEnv.js";
|
|
21
22
|
import { isInScope } from "../core/adapter.js";
|
|
22
23
|
import { usesNativeCapabilityDelivery } from "../core/types.js";
|
|
23
24
|
import * as kiroProbe from "../probes/kiro.js";
|
|
@@ -64,11 +65,11 @@ export class KiroAdapter {
|
|
|
64
65
|
canonicalRoot,
|
|
65
66
|
kind: "instructions",
|
|
66
67
|
});
|
|
67
|
-
const mcpItems = this.planMcp(canonical);
|
|
68
|
-
const approvedEnvVarsItems = this.planApprovedEnvVars(canonical);
|
|
68
|
+
const mcpItems = await this.planMcp(canonical);
|
|
69
|
+
const approvedEnvVarsItems = await this.planApprovedEnvVars(canonical);
|
|
69
70
|
return [...skillItems, ...instructionsItems, ...mcpItems, ...approvedEnvVarsItems];
|
|
70
71
|
}
|
|
71
|
-
planMcp(canonical) {
|
|
72
|
+
async planMcp(canonical) {
|
|
72
73
|
const configPath = join(this.homeDir, ".kiro", "settings", "mcp.json");
|
|
73
74
|
const parsed = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf-8")) : undefined;
|
|
74
75
|
const ownership = ownedByAgent(loadMcpOwnership(this.homeDir), this.id);
|
|
@@ -77,17 +78,18 @@ export class KiroAdapter {
|
|
|
77
78
|
/** Names come from `resolveMcpPlan`, not a raw scan of `canonical.mcp.servers`
|
|
78
79
|
* (design.md D2) — a server scoped away from Kiro, or refused for a
|
|
79
80
|
* known_host_injected collision, never contributes a name here either. */
|
|
80
|
-
desiredApprovedEnvVars(canonical) {
|
|
81
|
-
const { desired } = resolveMcpPlan(this.id, globalLayerMcp(canonical), canonical.managedAgents, canonical.secretsPolicy);
|
|
81
|
+
async desiredApprovedEnvVars(canonical) {
|
|
82
|
+
const { desired } = await resolveMcpPlan(this.id, globalLayerMcp(canonical), canonical.managedAgents, canonical.secretsPolicy);
|
|
82
83
|
const names = new Set();
|
|
83
84
|
for (const { def } of desired) {
|
|
85
|
+
// Approved list holds DELIVERED names (what renderers write), not raw entries.
|
|
84
86
|
for (const name of declaredEnvNames(def))
|
|
85
|
-
names.add(name);
|
|
87
|
+
names.add(deliveredName(name));
|
|
86
88
|
}
|
|
87
89
|
return [...names];
|
|
88
90
|
}
|
|
89
|
-
planApprovedEnvVars(canonical) {
|
|
90
|
-
const desired = this.desiredApprovedEnvVars(canonical);
|
|
91
|
+
async planApprovedEnvVars(canonical) {
|
|
92
|
+
const desired = await this.desiredApprovedEnvVars(canonical);
|
|
91
93
|
if (desired.length === 0) {
|
|
92
94
|
return [];
|
|
93
95
|
}
|
|
@@ -188,7 +190,7 @@ export class KiroAdapter {
|
|
|
188
190
|
return [];
|
|
189
191
|
}
|
|
190
192
|
})();
|
|
191
|
-
for (const name of this.desiredApprovedEnvVars(canonical)) {
|
|
193
|
+
for (const name of await this.desiredApprovedEnvVars(canonical)) {
|
|
192
194
|
if (!approved.includes(name)) {
|
|
193
195
|
mismatches.push(`env var "${name}" is needed by a canonical MCP server for kiro but missing from ${APPROVED_ENV_VARS_KEY}`);
|
|
194
196
|
}
|
|
@@ -91,4 +91,4 @@ export interface LiteralSecretMatch {
|
|
|
91
91
|
* as before this function reported a field at all.
|
|
92
92
|
*/
|
|
93
93
|
export declare function findLiteralSecret(def: McpServerDef): LiteralSecretMatch | undefined;
|
|
94
|
-
export declare function resolveMcpPlan(agentId: AgentId, mcp: McpConfig, managedAgents: readonly AgentId[], policy: SecretsPolicy): McpPlanResult
|
|
94
|
+
export declare function resolveMcpPlan(agentId: AgentId, mcp: McpConfig, managedAgents: readonly AgentId[], policy: SecretsPolicy): Promise<McpPlanResult>;
|
package/dist/adapters/mcpPlan.js
CHANGED
|
@@ -20,7 +20,7 @@ import { isInScope } from "../core/adapter.js";
|
|
|
20
20
|
import { capabilityDeliveryForAgent } from "../core/types.js";
|
|
21
21
|
import { isOAuthAuth, oauthOwner } from "../core/types.js";
|
|
22
22
|
import { codexBearerTokenEnvVar } from "../lib/tomlSection.js";
|
|
23
|
-
import { resolveSecretEnv } from "../lib/secretEnv.js";
|
|
23
|
+
import { parseEnvEntry, resolveSecretEnv } from "../lib/secretEnv.js";
|
|
24
24
|
export const HUB_ENTRY_NAME = "trellis-hub";
|
|
25
25
|
export const GATEWAY_ENTRY_NAME = "trellis";
|
|
26
26
|
export const LEGACY_GATEWAY_ENTRY_NAME = "trellis-gateway";
|
|
@@ -138,14 +138,25 @@ export function findLiteralSecret(def) {
|
|
|
138
138
|
* the target key it would have been written under
|
|
139
139
|
* (trellis-migrate-env-var-alias).
|
|
140
140
|
*/
|
|
141
|
-
function findUnresolvedEnvName(def, policy) {
|
|
141
|
+
async function findUnresolvedEnvName(def, policy) {
|
|
142
142
|
const names = [...(def.env ?? []), ...Object.values(def.envAliases ?? {})];
|
|
143
143
|
if (names.length === 0)
|
|
144
144
|
return undefined;
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
// A malformed `mxenv://` entry conflicts here too — it is rejected input,
|
|
146
|
+
// never a silently-resolved bare name (mx-env-secret-source D6). The
|
|
147
|
+
// returned text is the DECLARED entry; resolution checks use delivered
|
|
148
|
+
// names (the `KEY` segment for a reference).
|
|
149
|
+
const parsed = names.map(parseEnvEntry);
|
|
150
|
+
const invalid = parsed.find((p) => p.kind === "invalid");
|
|
151
|
+
if (invalid)
|
|
152
|
+
return invalid.entry;
|
|
153
|
+
const resolved = await resolveSecretEnv(names, policy);
|
|
154
|
+
return names.find((name) => {
|
|
155
|
+
const p = parseEnvEntry(name);
|
|
156
|
+
return !resolved[p.kind === "invalid" ? name : p.delivered];
|
|
157
|
+
});
|
|
147
158
|
}
|
|
148
|
-
export function resolveMcpPlan(agentId, mcp, managedAgents, policy) {
|
|
159
|
+
export async function resolveMcpPlan(agentId, mcp, managedAgents, policy) {
|
|
149
160
|
const route = routeForAgent(agentId, mcp, managedAgents);
|
|
150
161
|
const runtimeEnabled = capabilityDeliveryForAgent(agentId, mcp) !== "native";
|
|
151
162
|
// Checked before `hub`: only one entry can be "the" MCP entry for an
|
|
@@ -161,7 +172,7 @@ export function resolveMcpPlan(agentId, mcp, managedAgents, policy) {
|
|
|
161
172
|
conflicts: [{ name: entryName, message: collisionMessage(entryName, agentId), remediation: collisionRemediation(entryName) }],
|
|
162
173
|
};
|
|
163
174
|
}
|
|
164
|
-
const oauth = planDirectServers(agentId, mcp, managedAgents, policy, route, isAgentOwnedOAuth);
|
|
175
|
+
const oauth = await planDirectServers(agentId, mcp, managedAgents, policy, route, isAgentOwnedOAuth);
|
|
165
176
|
// Every ordinary per-server check (enabled, scope, literal secrets,
|
|
166
177
|
// unresolved env names, Codex's header shape) still runs at gateway
|
|
167
178
|
// startup. OAuth servers are the deliberate exception: they are written
|
|
@@ -237,7 +248,7 @@ export function resolveMcpPlan(agentId, mcp, managedAgents, policy) {
|
|
|
237
248
|
// Runtime mode owns the agent-facing MCP edge. It resolves the same
|
|
238
249
|
// direct/gateway-compatible upstream route at startup, so native
|
|
239
250
|
// per-server entries are not duplicated here.
|
|
240
|
-
const oauth = planDirectServers(agentId, mcp, managedAgents, policy, route, isAgentOwnedOAuth);
|
|
251
|
+
const oauth = await planDirectServers(agentId, mcp, managedAgents, policy, route, isAgentOwnedOAuth);
|
|
241
252
|
return {
|
|
242
253
|
desired: [{
|
|
243
254
|
name: RUNTIME_ENTRY_NAME,
|
|
@@ -246,14 +257,14 @@ export function resolveMcpPlan(agentId, mcp, managedAgents, policy) {
|
|
|
246
257
|
conflicts: oauth.conflicts,
|
|
247
258
|
};
|
|
248
259
|
}
|
|
249
|
-
const native = planDirectServers(agentId, mcp, managedAgents, policy, route);
|
|
260
|
+
const native = await planDirectServers(agentId, mcp, managedAgents, policy, route);
|
|
250
261
|
const hosted = native.desired.filter(({ def }) => isOAuthMcpServer(def) && oauthOwner(def.auth) === "trellis");
|
|
251
262
|
return {
|
|
252
263
|
desired: native.desired.filter(({ def }) => !isOAuthMcpServer(def) || oauthOwner(def.auth) === "agent"),
|
|
253
264
|
conflicts: [...native.conflicts, ...hosted.map(({ name }) => ({ name, message: `Trellis-owned OAuth server "${name}" requires gateway or runtime delivery for ${agentId}`, remediation: "enable gateway/runtime delivery or set auth.owner to agent" }))],
|
|
254
265
|
};
|
|
255
266
|
}
|
|
256
|
-
function planDirectServers(agentId, mcp, managedAgents, policy, route, filter) {
|
|
267
|
+
async function planDirectServers(agentId, mcp, managedAgents, policy, route, filter) {
|
|
257
268
|
const desired = [];
|
|
258
269
|
const conflicts = [];
|
|
259
270
|
for (const [name, def] of Object.entries(mcp.servers)) {
|
|
@@ -306,13 +317,25 @@ function planDirectServers(agentId, mcp, managedAgents, policy, route, filter) {
|
|
|
306
317
|
});
|
|
307
318
|
continue;
|
|
308
319
|
}
|
|
309
|
-
const unresolvedName = findUnresolvedEnvName(def, policy);
|
|
320
|
+
const unresolvedName = await findUnresolvedEnvName(def, policy);
|
|
310
321
|
if (unresolvedName) {
|
|
311
|
-
const
|
|
322
|
+
const entry = parseEnvEntry(unresolvedName);
|
|
323
|
+
if (entry.kind === "invalid") {
|
|
324
|
+
conflicts.push({
|
|
325
|
+
name,
|
|
326
|
+
message: `refusing to write MCP server "${name}": its declared env entry "${unresolvedName}" is not a valid mxenv://<profile>/<KEY> reference (mx-env-secret-source)`,
|
|
327
|
+
remediation: `fix the entry in servers.yaml to the mxenv://<profile>/<KEY> form or a plain variable name, then re-run mcp sync`,
|
|
328
|
+
});
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
const source = entry.kind === "ref"
|
|
332
|
+
? `the mxenv composition (profile "${entry.profile}")`
|
|
333
|
+
: (policy.envFile ?? "process environment");
|
|
334
|
+
const remediationTarget = entry.kind === "ref" ? entry.key : unresolvedName;
|
|
312
335
|
conflicts.push({
|
|
313
336
|
name,
|
|
314
337
|
message: `refusing to write MCP server "${name}": its declared env var "${unresolvedName}" has no resolvable value in ${source} — writing it now would silently break this server's connection once the agent starts it (trellis-mcp-static-env-and-disabled-servers)`,
|
|
315
|
-
remediation: `set "${
|
|
338
|
+
remediation: `set "${remediationTarget}" in ${source}, or fix the name in servers.yaml if it was a typo, then re-run mcp sync`,
|
|
316
339
|
});
|
|
317
340
|
continue;
|
|
318
341
|
}
|
package/dist/adapters/pi.js
CHANGED
|
@@ -114,7 +114,7 @@ export class PiAdapter {
|
|
|
114
114
|
kind: "instructions",
|
|
115
115
|
});
|
|
116
116
|
if (supportsNativeMcp(this.versionProbe())) {
|
|
117
|
-
return [...skillItems, ...instructionsItems, ...this.planNativeMcp(canonical)];
|
|
117
|
+
return [...skillItems, ...instructionsItems, ...await this.planNativeMcp(canonical)];
|
|
118
118
|
}
|
|
119
119
|
const { file: bridgeFile, symlinkName } = resolveBridgeFile();
|
|
120
120
|
const extensionItems = planSymlinks({
|
|
@@ -139,12 +139,12 @@ export class PiAdapter {
|
|
|
139
139
|
* apply, and a bare skills/instructions sync can neither remove the
|
|
140
140
|
* bridge early nor resurrect it afterwards.
|
|
141
141
|
*/
|
|
142
|
-
planNativeMcp(canonical) {
|
|
142
|
+
async planNativeMcp(canonical) {
|
|
143
143
|
const agentDir = join(this.homeDir, ".pi", "agent");
|
|
144
144
|
const configPath = join(agentDir, "mcp.json");
|
|
145
145
|
const parsed = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf-8")) : undefined;
|
|
146
146
|
const ownership = ownedByAgent(loadMcpOwnership(this.homeDir), this.id);
|
|
147
|
-
const items = planJsonMcp({
|
|
147
|
+
const items = (await planJsonMcp({
|
|
148
148
|
configPath,
|
|
149
149
|
parsed,
|
|
150
150
|
mcp: globalLayerMcp(canonical),
|
|
@@ -153,7 +153,7 @@ export class PiAdapter {
|
|
|
153
153
|
policy: canonical.secretsPolicy,
|
|
154
154
|
ownership,
|
|
155
155
|
render: piRenderer,
|
|
156
|
-
}).map((item) => {
|
|
156
|
+
})).map((item) => {
|
|
157
157
|
// pi speaks stdio and streamable HTTP only — an sse server gets an
|
|
158
158
|
// honest conflict instead of an entry pi could never connect.
|
|
159
159
|
const write = item.mcpWrite;
|
|
@@ -251,7 +251,7 @@ export class PiAdapter {
|
|
|
251
251
|
// definition drift. The plan only ever names Trellis-owned entries
|
|
252
252
|
// (the ownership ledger gates removals), so user-authored servers
|
|
253
253
|
// can never surface here.
|
|
254
|
-
for (const item of this.planNativeMcp(canonical)) {
|
|
254
|
+
for (const item of await this.planNativeMcp(canonical)) {
|
|
255
255
|
if (item.action === "create" && item.mcpWrite) {
|
|
256
256
|
mismatches.push(`mcp.json entry "${item.mcpWrite.name}" is missing or differs from canonical — run trellis mcp sync`);
|
|
257
257
|
}
|
package/dist/adapters/zcode.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export declare function planZcodeMcp(opts: {
|
|
|
16
16
|
managedAgents: readonly import("../core/types.js").AgentId[];
|
|
17
17
|
policy: SecretsPolicy;
|
|
18
18
|
ownership: JsonRecord;
|
|
19
|
-
}): AdapterPlanItem[]
|
|
19
|
+
}): Promise<AdapterPlanItem[]>;
|
|
20
20
|
export declare function applyZcodeMcp(parsed: JsonRecord | undefined, items: readonly AdapterPlanItem[]): JsonRecord;
|
|
21
21
|
export declare class ZcodeAdapter implements TrellisAdapter {
|
|
22
22
|
private readonly homeDir;
|
package/dist/adapters/zcode.js
CHANGED
|
@@ -29,8 +29,8 @@ function record(value) {
|
|
|
29
29
|
function zcodeServers(parsed) {
|
|
30
30
|
return record(record(parsed?.mcp).servers);
|
|
31
31
|
}
|
|
32
|
-
export function planZcodeMcp(opts) {
|
|
33
|
-
const { desired, conflicts } = resolveMcpPlan("zcode", opts.mcp, opts.managedAgents, opts.policy);
|
|
32
|
+
export async function planZcodeMcp(opts) {
|
|
33
|
+
const { desired, conflicts } = await resolveMcpPlan("zcode", opts.mcp, opts.managedAgents, opts.policy);
|
|
34
34
|
const existing = zcodeServers(opts.parsed);
|
|
35
35
|
const items = [];
|
|
36
36
|
for (const { name, def } of desired) {
|
|
@@ -136,14 +136,14 @@ export class ZcodeAdapter {
|
|
|
136
136
|
canonicalRoot,
|
|
137
137
|
kind: "instructions",
|
|
138
138
|
}));
|
|
139
|
-
items.push(...planZcodeMcp({
|
|
139
|
+
items.push(...(await planZcodeMcp({
|
|
140
140
|
configPath: profile.configPath,
|
|
141
141
|
parsed,
|
|
142
142
|
mcp: globalLayerMcp(canonical),
|
|
143
143
|
managedAgents: canonical.managedAgents,
|
|
144
144
|
policy: canonical.secretsPolicy,
|
|
145
145
|
ownership: ownedByAgent(loadMcpOwnership(this.homeDir), this.id),
|
|
146
|
-
}));
|
|
146
|
+
})));
|
|
147
147
|
const current = currentSkillControls(parsed);
|
|
148
148
|
const ownership = loadZcodeSettingsOwnership(this.homeDir);
|
|
149
149
|
if (runtimeOnly && (current.featuresSkill !== false || current.skillsEnabled !== false)) {
|
|
@@ -214,14 +214,14 @@ export class ZcodeAdapter {
|
|
|
214
214
|
for (const name of desiredNativeSkills)
|
|
215
215
|
if (!actualNativeSkills.has(name))
|
|
216
216
|
mismatches.push(`skill "${name}" is missing on ZCode`);
|
|
217
|
-
const expectedMcp = planZcodeMcp({
|
|
217
|
+
const expectedMcp = (await planZcodeMcp({
|
|
218
218
|
configPath: profile.configPath,
|
|
219
219
|
parsed: readConfig(profile.configPath),
|
|
220
220
|
mcp: globalLayerMcp(canonical),
|
|
221
221
|
managedAgents: canonical.managedAgents,
|
|
222
222
|
policy: canonical.secretsPolicy,
|
|
223
223
|
ownership: ownedByAgent(loadMcpOwnership(this.homeDir), this.id),
|
|
224
|
-
}).filter((item) => item.action === "create" || item.action === "conflict");
|
|
224
|
+
})).filter((item) => item.action === "create" || item.action === "conflict");
|
|
225
225
|
mismatches.push(...expectedMcp.map((item) => item.description));
|
|
226
226
|
if (!snapshot.instructionsFile?.isSymlink || snapshot.instructionsFile.target !== canonical.instructionsFile)
|
|
227
227
|
mismatches.push("shared instructions are not linked into ~/.zcode/AGENTS.md");
|
package/dist/cli.js
CHANGED
|
@@ -18,6 +18,10 @@ import { runMcpAuth } from "./commands/mcpAuth.js";
|
|
|
18
18
|
import { runMcpRenew } from "./commands/mcpRenew.js";
|
|
19
19
|
import { runSecretsAudit } from "./commands/secretsAudit.js";
|
|
20
20
|
import { runRollback } from "./commands/rollback.js";
|
|
21
|
+
// consumer-wiring(8.7):`trellis evolve` 管理面挂载。这条 import 是
|
|
22
|
+
// 托管层仅有的消费者接线,登记在 test/unit/hostingIndependence.test.ts
|
|
23
|
+
// 的 CONSUMER_WIRING;物理移出自我进化时与下文 evolve 分支一并删除。
|
|
24
|
+
import { runEvolveCommand } from "./commands/evolve.js";
|
|
21
25
|
import { runSkillList, runSkillAdd, runSkillRemove, runSkillScope, runSkillUpdateBuiltin } from "./commands/skill.js";
|
|
22
26
|
import { runRemoteSkillAdd, runRemoteSkillList, runRemoteSkillUpdate } from "./commands/remoteSkill.js";
|
|
23
27
|
import { collectMemoryList, runMemoryExtraction, runMemorySync } from "./commands/memory.js";
|
|
@@ -26,7 +30,7 @@ import { runKimi } from "./commands/kimi.js";
|
|
|
26
30
|
import { TRELLIS_VERSION } from "./lib/cliMetadata.js";
|
|
27
31
|
import { flagValue } from "./lib/cliArgs.js";
|
|
28
32
|
import { parseSyncArgs } from "./lib/syncArgs.js";
|
|
29
|
-
const KNOWN_COMMANDS = ["onboard", "init", "migrate", "doctor", "sync", "mcp", "mcp-gateway", "mcp-runtime", "skill", "add", "update", "memory", "manage", "kimi", "secrets", "rollback"];
|
|
33
|
+
const KNOWN_COMMANDS = ["onboard", "init", "migrate", "doctor", "sync", "mcp", "mcp-gateway", "mcp-runtime", "skill", "add", "update", "memory", "manage", "kimi", "secrets", "rollback", "evolve"];
|
|
30
34
|
function printUsage() {
|
|
31
35
|
console.log(`trellis - a single source of capability for every coding agent
|
|
32
36
|
|
|
@@ -625,6 +629,13 @@ async function main(argv) {
|
|
|
625
629
|
process.exitCode = exitCode;
|
|
626
630
|
return;
|
|
627
631
|
}
|
|
632
|
+
if (command === "evolve") {
|
|
633
|
+
// 自我进化管理面(trellis-evolve 12.4):批准仅限交互式终端,由
|
|
634
|
+
// runEvolveCommand 内部检测;argv 原样传入,不拼 shell。
|
|
635
|
+
const { exitCode } = await runEvolveCommand(rest);
|
|
636
|
+
process.exitCode = exitCode;
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
628
639
|
console.error(`\`trellis ${command}\` is not implemented yet — this is a pre-alpha scaffold.\nSee docs/roadmap.md for status.`);
|
|
629
640
|
process.exitCode = 1;
|
|
630
641
|
}
|
|
@@ -8,6 +8,7 @@ import { loadCanonicalSource } from "../core/canonical.js";
|
|
|
8
8
|
import type { AgentId, AgentSnapshot, CanonicalSource } from "../core/types.js";
|
|
9
9
|
import { type VersionExecutor } from "../lib/cliEntrypoints.js";
|
|
10
10
|
import { type WorkspaceDoctorSection } from "../lib/workspaceDoctor.js";
|
|
11
|
+
import { type EvolveLevelSection } from "../host/levels.js";
|
|
11
12
|
/**
|
|
12
13
|
* Interim default until P1 wires this from `.trellis/mcp/servers.yaml`'s
|
|
13
14
|
* real `known_host_injected` field (P0 has no canonical source at all —
|
|
@@ -74,6 +75,13 @@ export interface DoctorReport {
|
|
|
74
75
|
/** Per-entry layer attribution when a `.agents/` workspace layer
|
|
75
76
|
* participated (trellis-workspace-scope task 3.5). */
|
|
76
77
|
workspace?: WorkspaceDoctorSection;
|
|
78
|
+
/** Self-evolution levels when `~/.trellis/evolve.yaml` has any explicit
|
|
79
|
+
* setting (trellis-evolve task 10.2). Absent when unconfigured — a
|
|
80
|
+
* machine that never enabled evolution sees byte-identical output. */
|
|
81
|
+
evolve?: EvolveLevelSection;
|
|
82
|
+
/** Pending proposal count (trellis-evolve task 12.4; spec: doctor SHALL
|
|
83
|
+
* list it). Present when > 0 or the evolve section is shown. Read-only. */
|
|
84
|
+
evolvePending?: number;
|
|
77
85
|
}
|
|
78
86
|
/** Exported so tests can assert on this resolution directly, without
|
|
79
87
|
* routing through real per-agent probes `runDoctor` also runs. */
|
|
@@ -129,7 +137,7 @@ export declare function detectMcpUnreachable(snapshots: AgentSnapshot[]): Findin
|
|
|
129
137
|
/** Compares runtime entries against the same canonical MCP plan that sync
|
|
130
138
|
* uses. Only missing Trellis-owned runtime edges are reported; extra
|
|
131
139
|
* user-authored MCP entries remain outside doctor ownership. */
|
|
132
|
-
export declare function detectRuntimeDrift(snapshots: AgentSnapshot[], canonical: Pick<ReturnType<typeof loadCanonicalSource>, "mcp" | "managedAgents" | "secretsPolicy">): Finding[]
|
|
140
|
+
export declare function detectRuntimeDrift(snapshots: AgentSnapshot[], canonical: Pick<ReturnType<typeof loadCanonicalSource>, "mcp" | "managedAgents" | "secretsPolicy">): Promise<Finding[]>;
|
|
133
141
|
/** Exported so `trellis onboard` can reuse it verbatim for its own final
|
|
134
142
|
* stage (trellis-onboard-closed-loop) rather than a second copy of this
|
|
135
143
|
* formatting. */
|
package/dist/commands/doctor.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* current state against each other, the same manual process used
|
|
5
5
|
* throughout docs/research.md's investigation, now formalized.
|
|
6
6
|
*/
|
|
7
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
7
9
|
import { homedir } from "node:os";
|
|
8
10
|
import * as claudeCodeProbe from "../probes/claude-code.js";
|
|
9
11
|
import * as codexProbe from "../probes/codex.js";
|
|
@@ -13,12 +15,43 @@ import * as kimiCodeProbe from "../probes/kimi-code.js";
|
|
|
13
15
|
import * as zcodeProbe from "../probes/zcode.js";
|
|
14
16
|
import { loadCanonicalSource } from "../core/canonical.js";
|
|
15
17
|
import { GATEWAY_ENTRY_NAME, RUNTIME_ENTRY_NAME, resolveMcpPlan } from "../adapters/mcpPlan.js";
|
|
16
|
-
import { ALL_AGENTS, isOAuthAuth, oauthOwner } from "../core/types.js";
|
|
18
|
+
import { ALL_AGENTS, capabilityDeliveryForAgent, isOAuthAuth, oauthOwner } from "../core/types.js";
|
|
17
19
|
import { classifyStoredToken, remediationFor } from "../lib/oauth/renew.js";
|
|
18
20
|
import { readToken } from "../lib/oauth/store.js";
|
|
19
21
|
import { findWorkspaceRoot } from "../lib/workspace.js";
|
|
20
22
|
import { detectCliVersionSplit, findCliEntrypoints } from "../lib/cliEntrypoints.js";
|
|
21
23
|
import { collectWorkspaceDoctorSection } from "../lib/workspaceDoctor.js";
|
|
24
|
+
import { describeEvolveLevels, loadEvolveLevelPolicy, } from "../host/levels.js";
|
|
25
|
+
/**
|
|
26
|
+
* Pending proposal count via the on-disk data contract (8.1 状态目录 /
|
|
27
|
+
* 12.4 pending 存储:`~/.trellis/evolve/pending/<safeId>.json`,记录含
|
|
28
|
+
* `status` 字段)。doctor 属托管层,只读数据、不 import 自我进化代码,
|
|
29
|
+
* 物理移出消费者后 doctor 仍自洽(目录不存在 → 0)。
|
|
30
|
+
*/
|
|
31
|
+
function countPendingProposalRecords(homeDir) {
|
|
32
|
+
const dir = join(homeDir, ".trellis", "evolve", "pending");
|
|
33
|
+
let names;
|
|
34
|
+
try {
|
|
35
|
+
names = readdirSync(dir);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
let count = 0;
|
|
41
|
+
for (const name of names) {
|
|
42
|
+
if (!name.endsWith(".json"))
|
|
43
|
+
continue;
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(readFileSync(join(dir, name), "utf-8"));
|
|
46
|
+
if (parsed.status === "pending")
|
|
47
|
+
count += 1;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// 坏文件不阻塞 doctor 扫描。
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return count;
|
|
54
|
+
}
|
|
22
55
|
/**
|
|
23
56
|
* Interim default until P1 wires this from `.trellis/mcp/servers.yaml`'s
|
|
24
57
|
* real `known_host_injected` field (P0 has no canonical source at all —
|
|
@@ -145,7 +178,7 @@ export async function collectDoctorReport(homeDir = homedir(), knownHostInjected
|
|
|
145
178
|
// `collectWorkspaceDoctorSection`'s per-file inspection is what we want.
|
|
146
179
|
let workspace;
|
|
147
180
|
if (canonical?.workspace) {
|
|
148
|
-
workspace = collectWorkspaceDoctorSection(canonical);
|
|
181
|
+
workspace = await collectWorkspaceDoctorSection(canonical);
|
|
149
182
|
}
|
|
150
183
|
else {
|
|
151
184
|
const workspaceRoot = workspaceDir != null ? findWorkspaceRoot(workspaceDir) : null;
|
|
@@ -165,7 +198,7 @@ export async function collectDoctorReport(homeDir = homedir(), knownHostInjected
|
|
|
165
198
|
diagnostics: [],
|
|
166
199
|
workspace: { root: workspaceRoot, mcpProvenance: {}, mcpGlobalServers: {}, skillProvenance: {}, globalSkills: [], shadowed: [] },
|
|
167
200
|
};
|
|
168
|
-
workspace = collectWorkspaceDoctorSection(minimal);
|
|
201
|
+
workspace = await collectWorkspaceDoctorSection(minimal);
|
|
169
202
|
}
|
|
170
203
|
}
|
|
171
204
|
// Workspace TOML findings become top-level findings so the exit code
|
|
@@ -182,8 +215,40 @@ export async function collectDoctorReport(homeDir = homedir(), knownHostInjected
|
|
|
182
215
|
}
|
|
183
216
|
}
|
|
184
217
|
const oauth = canonical ? collectOAuthHealth(homeDir, canonical) : [];
|
|
185
|
-
findings.push(...detectDuplication(snapshots), ...detectCollisions(snapshots, knownHostInjected), ...detectCrossAgentDrift(snapshots), ...detectCaseMismatches(snapshots), ...detectParseDiagnostics(snapshots), ...(canonical ? detectRuntimeDrift(snapshots, canonical) : []), ...detectMcpUnreachable(snapshots), ...oauth.flatMap((entry) => (entry.status === "valid" ? [] : [oauthFinding(entry)])));
|
|
186
|
-
|
|
218
|
+
findings.push(...detectDuplication(snapshots), ...detectCollisions(snapshots, knownHostInjected), ...detectCrossAgentDrift(snapshots), ...detectCaseMismatches(snapshots), ...detectParseDiagnostics(snapshots), ...(canonical ? await detectRuntimeDrift(snapshots, canonical) : []), ...detectMcpUnreachable(snapshots), ...oauth.flatMap((entry) => (entry.status === "valid" ? [] : [oauthFinding(entry)])));
|
|
219
|
+
// 分级开启(trellis-evolve 10.2):级别策略是显式用户选择,读取零副作用。
|
|
220
|
+
// 未配置(或全空)时整段缺席 —— 从未开启自我进化的机器输出与未安装时
|
|
221
|
+
// 一致;损坏的策略文件不拖垮扫描,降级为段内诊断呈现。
|
|
222
|
+
let evolve;
|
|
223
|
+
try {
|
|
224
|
+
const policy = loadEvolveLevelPolicy(homeDir);
|
|
225
|
+
// 整段只在存在显式设置时出现(与未安装时输出一致);托管 agent 的
|
|
226
|
+
// 列表只是展示面,不构成出现理由。
|
|
227
|
+
const hasExplicitSetting = policy.defaultLevel !== undefined || Object.keys(policy.agents).length > 0 || Object.keys(policy.workspaces).length > 0;
|
|
228
|
+
if (hasExplicitSetting) {
|
|
229
|
+
// 11.4:观测形态按 capability delivery 分类——runtime 交付
|
|
230
|
+
// ("mcp"/"both")的 agent 走 gateway 事件观察;仅原生渲染
|
|
231
|
+
// ("native"/未配置)只有 doctor/sync 信号。
|
|
232
|
+
const observationOf = (agent) => canonical !== undefined && capabilityDeliveryForAgent(agent, canonical.mcp) !== "native"
|
|
233
|
+
? "events"
|
|
234
|
+
: "doctor-only";
|
|
235
|
+
evolve = describeEvolveLevels(policy, (canonical?.managedAgents ?? []).map((agent) => ({ agent, observation: observationOf(agent) })));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch (err) {
|
|
239
|
+
evolve = { agents: [], workspaces: [], diagnostics: [err.message] };
|
|
240
|
+
}
|
|
241
|
+
// 12.4:pending 提案数量(spec "trellis doctor SHALL 同样列出")。
|
|
242
|
+
// 只读状态目录;未开启进化且无存储时为 0,输出保持零噪音。
|
|
243
|
+
const evolvePending = countPendingProposalRecords(homeDir);
|
|
244
|
+
return {
|
|
245
|
+
snapshots,
|
|
246
|
+
findings,
|
|
247
|
+
...(oauth.length > 0 ? { oauth } : {}),
|
|
248
|
+
...(workspace ? { workspace } : {}),
|
|
249
|
+
...(evolve ? { evolve } : {}),
|
|
250
|
+
...(evolve || evolvePending > 0 ? { evolvePending } : {}),
|
|
251
|
+
};
|
|
187
252
|
}
|
|
188
253
|
/**
|
|
189
254
|
* Read-only OAuth credential health (trellis-mcp-oauth-renew design.md
|
|
@@ -362,12 +427,12 @@ export function detectMcpUnreachable(snapshots) {
|
|
|
362
427
|
/** Compares runtime entries against the same canonical MCP plan that sync
|
|
363
428
|
* uses. Only missing Trellis-owned runtime edges are reported; extra
|
|
364
429
|
* user-authored MCP entries remain outside doctor ownership. */
|
|
365
|
-
export function detectRuntimeDrift(snapshots, canonical) {
|
|
430
|
+
export async function detectRuntimeDrift(snapshots, canonical) {
|
|
366
431
|
const findings = [];
|
|
367
432
|
for (const snap of snapshots) {
|
|
368
433
|
if (!snap.present || !canonical.managedAgents.includes(snap.agent) || snap.agent === "pi")
|
|
369
434
|
continue;
|
|
370
|
-
const expected = resolveMcpPlan(snap.agent, canonical.mcp, canonical.managedAgents, canonical.secretsPolicy).desired
|
|
435
|
+
const expected = (await resolveMcpPlan(snap.agent, canonical.mcp, canonical.managedAgents, canonical.secretsPolicy)).desired
|
|
371
436
|
.map((entry) => entry.name)
|
|
372
437
|
.filter((name) => name === RUNTIME_ENTRY_NAME || name === GATEWAY_ENTRY_NAME);
|
|
373
438
|
const actual = new Set(snap.mcpServers.map((server) => server.name));
|
|
@@ -463,4 +528,24 @@ export function printReport(report) {
|
|
|
463
528
|
console.log(` ⚠ ${warning}`);
|
|
464
529
|
}
|
|
465
530
|
}
|
|
531
|
+
if (report.evolve) {
|
|
532
|
+
const ev = report.evolve;
|
|
533
|
+
console.log(`\nSelf-evolution (evolve.yaml):`);
|
|
534
|
+
if (ev.defaultLevel !== undefined)
|
|
535
|
+
console.log(` default level: ${ev.defaultLevel}`);
|
|
536
|
+
for (const entry of ev.agents) {
|
|
537
|
+
console.log(` agent ${entry.agent}: ${entry.level} (${entry.observation})`);
|
|
538
|
+
}
|
|
539
|
+
for (const entry of ev.workspaces) {
|
|
540
|
+
console.log(` workspace ${entry.root}: ${entry.level}`);
|
|
541
|
+
}
|
|
542
|
+
for (const diagnostic of ev.diagnostics) {
|
|
543
|
+
console.log(` ⚠ ${diagnostic}`);
|
|
544
|
+
}
|
|
545
|
+
console.log(` pending proposals: ${report.evolvePending ?? 0}`);
|
|
546
|
+
}
|
|
547
|
+
else if ((report.evolvePending ?? 0) > 0) {
|
|
548
|
+
// 无级别配置但有 pending 存储:同样列出(spec SHALL),单行提示。
|
|
549
|
+
console.log(`\nSelf-evolution: ${report.evolvePending} pending proposal(s) — run \`trellis evolve pending\``);
|
|
550
|
+
}
|
|
466
551
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 自我进化管理面命令(trellis-evolve 任务 12.4)。
|
|
3
|
+
*
|
|
4
|
+
* 消费者归属(8.7):本文件是自我进化子系统的一部分,物理移出消费者时
|
|
5
|
+
* 与 src/evolve/**、CLI 挂载(src/cli.ts 的 CONSUMER_WIRING 接线行)一并
|
|
6
|
+
* 删除;hostingIndependence.test.ts 的 CONSUMER_OWNED/CONSUMER_WIRING
|
|
7
|
+
* 登记了这条移出清单。
|
|
8
|
+
*
|
|
9
|
+
* `trellis evolve <status | report | pending | approve | reject | apply | run | import | enable | disable | rollback>`
|
|
10
|
+
*
|
|
11
|
+
* 批准只走这里,且**仅限交互式终端**(spec"批准只走带外通道"):非交互
|
|
12
|
+
* 环境一律拒绝,提案保持 pending。不存在任何 `--yes` / `--confirm` 之类的
|
|
13
|
+
* 非交互批准捷径 —— agent 传入的"确认"参数不构成批准,加了也只会被当
|
|
14
|
+
* 未知参数忽略。循环副作用边界:除 `run`(唯一命令行触发入口,手动
|
|
15
|
+
* 触发一次运行)外的命令不写信号、不触发观察/提案/probe。approve
|
|
16
|
+
* 决策后**立即应用**(spec"批准即应用与显式应用入口":持单飞作业锁经
|
|
17
|
+
* 备份会话写入,锁忙暂缓并告知);apply 幂等应用全部已批准未应用的编辑
|
|
18
|
+
* (失败重试与滞留消化入口);reject 只做状态迁移 + 档案;enable/disable
|
|
19
|
+
* 写 canonical 级别策略,并按 design 8.1 同步扩展清单条目——级别升至
|
|
20
|
+
* observe 及以上时写入、置回 off 时移除;rollback 转发既有备份会话回滚。
|
|
21
|
+
*/
|
|
22
|
+
import { type ApplyAgent } from "../evolve/apply.js";
|
|
23
|
+
export interface EvolveCommandOptions {
|
|
24
|
+
/** 测试/sandbox 注入缝;缺省用户 HOME。永不作为 CLI 标志暴露。 */
|
|
25
|
+
readonly homeDir?: string;
|
|
26
|
+
/** TTY 检测缝;缺省 stdin+stdout 同时为 TTY。 */
|
|
27
|
+
readonly isTTY?: boolean;
|
|
28
|
+
/** 时间源(毫秒);缺省 Date.now。 */
|
|
29
|
+
readonly now?: () => number;
|
|
30
|
+
/** 应用阶段的 agent 分类(测试/sandbox 注入缝;缺省按 delivery 分类)。 */
|
|
31
|
+
readonly applyAgents?: readonly ApplyAgent[];
|
|
32
|
+
/** 原生渲染同步触发缝(测试注入;缺省 host `triggerRenderSync`)。 */
|
|
33
|
+
readonly triggerSync?: () => Promise<{
|
|
34
|
+
exitCode: number;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 管理面命令入口。`args` 为 `evolve` 之后的 argv(子命令 + 参数),
|
|
39
|
+
* 由 cli 原样传入 —— 不经 shell 拼接。
|
|
40
|
+
*/
|
|
41
|
+
export declare function runEvolveCommand(args: readonly string[], options?: EvolveCommandOptions): Promise<{
|
|
42
|
+
exitCode: number;
|
|
43
|
+
}>;
|