@kybernesis/create 0.24.2 → 0.25.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.
@@ -1,13 +1,11 @@
1
1
  import { type HostKind } from "./templates.js";
2
2
  import type { ModelScaffoldConfig } from "./model-reach.js";
3
3
  /**
4
- * The registry items install the newest release of each third-party
5
- * extension, and those are built on a later eve than the certified one, which
6
- * eve then refuses at discovery ("has no compile or runtime usage") and the
7
- * agent never boots. These are the releases proven on the certified eve.
8
- * Move them with the eve certification, never on their own.
4
+ * The third-party extensions at the releases proven on the certified eve (see
5
+ * THIRD_PARTY_PINS): the registry items install the newest release, which eve
6
+ * refuses at discovery when it is built on a later eve.
9
7
  */
10
- export declare const ENGINEER_EXTENSION_PINS: readonly ["@agent-browser/eve@0.34.0", "@github-tools/eve-extension@0.8.0"];
8
+ export declare const ENGINEER_EXTENSION_PINS: string[];
11
9
  /**
12
10
  * Agent-to-agent: the eve channel becomes the governed dispatch door (people
13
11
  * still come in through kybernesisAuth in the same walk) and granted peers
@@ -17,7 +15,7 @@ export declare const ENGINEER_EXTENSION_PINS: readonly ["@agent-browser/eve@0.34
17
15
  export declare function applyDispatch(dir: string): string[];
18
16
  /**
19
17
  * The engineer: a declared `builder` subagent with its own workshop sandbox,
20
- * memory and skills, plus the browser and GitHub tools at the root.
18
+ * memory and skills, plus the GitHub tools at the root.
21
19
  */
22
20
  export declare function applyEngineer(dir: string, host: HostKind, model: ModelScaffoldConfig): {
23
21
  files: string[];
@@ -2,6 +2,7 @@ import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node
2
2
  import { join } from "node:path";
3
3
  import { finalizeGithubToolsRegistryMount, wireArpAuth } from "./init.js";
4
4
  import { DISPATCH_DEP, dispatchEveChannelTs, dispatchPeersTs, engineerPlan } from "./templates.js";
5
+ import { THIRD_PARTY_PINS } from "./line-hop.js";
5
6
  import { run, yellow } from "./util.js";
6
7
  /**
7
8
  * The capabilities every company agent carries, applied the same way by
@@ -11,16 +12,14 @@ import { run, yellow } from "./util.js";
11
12
  // Official eve-registry limbs installed with the engineer subagent.
12
13
  // connection/vercel is Vercel-Connect-backed, so it is VERCEL-HOST ONLY: on a
13
14
  // self-hosted agent it cannot get an OIDC token and the agent fails to boot.
14
- const ENGINEER_ITEMS_ALL = ["extension/agent-browser", "extension/github-tools"];
15
+ const ENGINEER_ITEMS_ALL = ["extension/github-tools"];
15
16
  const ENGINEER_ITEMS_VERCEL = ["connection/vercel"];
16
17
  /**
17
- * The registry items install the newest release of each third-party
18
- * extension, and those are built on a later eve than the certified one, which
19
- * eve then refuses at discovery ("has no compile or runtime usage") and the
20
- * agent never boots. These are the releases proven on the certified eve.
21
- * Move them with the eve certification, never on their own.
18
+ * The third-party extensions at the releases proven on the certified eve (see
19
+ * THIRD_PARTY_PINS): the registry items install the newest release, which eve
20
+ * refuses at discovery when it is built on a later eve.
22
21
  */
23
- export const ENGINEER_EXTENSION_PINS = ["@agent-browser/eve@0.34.0", "@github-tools/eve-extension@0.8.0"];
22
+ export const ENGINEER_EXTENSION_PINS = Object.entries(THIRD_PARTY_PINS).map(([name, version]) => `${name}@${version}`);
24
23
  function write(dir, path, content) {
25
24
  const full = join(dir, path);
26
25
  mkdirSync(join(full, ".."), { recursive: true });
@@ -43,7 +42,7 @@ export function applyDispatch(dir) {
43
42
  }
44
43
  /**
45
44
  * The engineer: a declared `builder` subagent with its own workshop sandbox,
46
- * memory and skills, plus the browser and GitHub tools at the root.
45
+ * memory and skills, plus the GitHub tools at the root.
47
46
  */
48
47
  export function applyEngineer(dir, host, model) {
49
48
  const plan = engineerPlan(host, model);
@@ -56,7 +55,7 @@ export function applyEngineer(dir, host, model) {
56
55
  if (!ok)
57
56
  console.log(yellow(` ! ${item} did not install cleanly — re-run: npx eve add ${item}`));
58
57
  }
59
- // --legacy-peer-deps: both declare peer ranges that trail the certified eve.
58
+ // --legacy-peer-deps: it declares a peer range that trails the certified eve.
60
59
  const pinned = run("npm", ["install", "--save-exact", "--legacy-peer-deps", ...ENGINEER_EXTENSION_PINS, "--no-audit", "--no-fund"], {
61
60
  cwd: dir,
62
61
  allowFail: true,
package/dist/doctor.d.ts CHANGED
@@ -8,6 +8,11 @@ export interface Check {
8
8
  }
9
9
  /** Report the opt-in GitHub capability only when its mount is present. */
10
10
  export declare function githubToolsDoctorCheck(mountExists: boolean, env: Record<string, string | undefined>): Check | null;
11
+ /**
12
+ * What the agent still carries from an older eve line: everything `kyb
13
+ * upgrade` would move (reported, not done), and what it leaves to a person.
14
+ */
15
+ export declare function lineHopDoctorChecks(cwd: string): Check[];
11
16
  /** The shape @kybernesis/exe's inspector returns; declared here so create keeps zero runtime deps. */
12
17
  export interface DockerTemplateInspection {
13
18
  status: "skipped" | "present" | "failed";
package/dist/doctor.js CHANGED
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
2
  import { TEMPLATE_CONTAINER_PROBE, parseTemplateContainers, templateContainerDoctorChecks } from "./sandbox-orphans.js";
3
3
  import { join } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
- import { bold, capture, dim, green, parseEnv, red, yellow } from "./util.js";
5
+ import { EVE_VERSION, REGISTRY_URL, bold, capture, dim, green, parseEnv, red, yellow } from "./util.js";
6
6
  import { diagnoseManageRestart, findMatchingAgentServiceUnit } from "./systemd.js";
7
7
  import { inspectEveAgent } from "./agent-limits.js";
8
8
  import { classifyModelReach } from "./model-reach.js";
@@ -10,6 +10,7 @@ import { countAbandonedRuns } from "./run-store.js";
10
10
  import { describeStaleSidecar, newestPackageChange, staleSidecars } from "./stale-sidecar.js";
11
11
  import { checkSelfVersion } from "./self-version.js";
12
12
  import { envUrlProblems } from "./env-urls.js";
13
+ import { migrateManifest, migrateSources } from "./line-hop.js";
13
14
  const MARK = {
14
15
  pass: green("✓"),
15
16
  warn: yellow("!"),
@@ -25,6 +26,38 @@ export function githubToolsDoctorCheck(mountExists, env) {
25
26
  detail: "set GITHUB_TOKEN in .env.local or the deployment environment, then rebuild and restart",
26
27
  };
27
28
  }
29
+ /**
30
+ * What the agent still carries from an older eve line: everything `kyb
31
+ * upgrade` would move (reported, not done), and what it leaves to a person.
32
+ */
33
+ export function lineHopDoctorChecks(cwd) {
34
+ const checks = [];
35
+ try {
36
+ const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
37
+ const manifest = migrateManifest(pkg, REGISTRY_URL);
38
+ for (const move of manifest.moves) {
39
+ checks.push({
40
+ verdict: "warn",
41
+ label: move.to === null ? `${move.name} is installed` : `${move.name} is ${move.from}, not ${move.to}`,
42
+ detail: `${move.why}; kyb upgrade moves it`,
43
+ });
44
+ }
45
+ if (manifest.registry) {
46
+ checks.push({ verdict: "warn", label: "the @kybernesis registry is not the one for this eve line", detail: `kyb upgrade points it at ${manifest.registry.to}` });
47
+ }
48
+ }
49
+ catch {
50
+ /* no readable package.json: the dependency checks say so */
51
+ }
52
+ const sources = migrateSources(cwd, { write: false });
53
+ for (const file of sources.updated)
54
+ checks.push({ verdict: "warn", label: `${file} is written for an older eve`, detail: `kyb upgrade rewrites it for eve ${EVE_VERSION}` });
55
+ for (const file of sources.removed)
56
+ checks.push({ verdict: "warn", label: `${file} mounts an extension that does not run on eve ${EVE_VERSION}`, detail: "kyb upgrade removes it" });
57
+ for (const { file, action } of sources.manual)
58
+ checks.push({ verdict: "warn", label: `${file} needs a hand`, detail: action });
59
+ return checks;
60
+ }
28
61
  export function dockerTemplateDoctorChecks(result) {
29
62
  if (result.status === "skipped")
30
63
  return [];
@@ -242,6 +275,8 @@ export async function doctor() {
242
275
  const githubCheck = githubToolsDoctorCheck(existsSync(join(cwd, "agent/extensions/github.ts")), env);
243
276
  if (githubCheck)
244
277
  add(githubCheck.verdict, githubCheck.label, githubCheck.detail);
278
+ for (const check of lineHopDoctorChecks(cwd))
279
+ add(check.verdict, check.label, check.detail);
245
280
  // ── arcana keys: validate every key↔workspace pair read-only ───────────
246
281
  const arcanaPairs = [];
247
282
  if (env.ARCANA_API_KEY && env.ARCANA_COMPANY_WORKSPACE)
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Give every `exeModel({ …, createOpenAI })` call in an agent the Anthropic
3
+ * factory too.
4
+ *
5
+ * @remarks
6
+ * Claude on the exe gateway is served on its Messages API, not the OpenAI one.
7
+ * Older scaffolds wrote subagents such as the builder with only `createOpenAI`
8
+ * and a Claude default (`anthropic/claude-sonnet-5`). That never had a working
9
+ * route, and since exe 0.17 `exeModel` refuses it at load, so the agent's whole
10
+ * eval suite stops before the first case and an update is rolled back for a
11
+ * file nobody touched. Passing `createAnthropic` as well is harmless for a
12
+ * non-Claude id (exeModel only uses it for `anthropic/…`), so the repair is
13
+ * unconditional for any call that lacks it.
14
+ *
15
+ * Returns the agent-relative paths it changed.
16
+ */
17
+ export declare function repairExeClaudeRoute(cwd: string): string[];
18
+ /** The source with `createAnthropic` passed to each exeModel call that has only `createOpenAI`, and imported. */
19
+ export declare function withAnthropicFactory(source: string): string;
@@ -0,0 +1,69 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
2
+ import { join, relative } from "node:path";
3
+ /**
4
+ * Give every `exeModel({ …, createOpenAI })` call in an agent the Anthropic
5
+ * factory too.
6
+ *
7
+ * @remarks
8
+ * Claude on the exe gateway is served on its Messages API, not the OpenAI one.
9
+ * Older scaffolds wrote subagents such as the builder with only `createOpenAI`
10
+ * and a Claude default (`anthropic/claude-sonnet-5`). That never had a working
11
+ * route, and since exe 0.17 `exeModel` refuses it at load, so the agent's whole
12
+ * eval suite stops before the first case and an update is rolled back for a
13
+ * file nobody touched. Passing `createAnthropic` as well is harmless for a
14
+ * non-Claude id (exeModel only uses it for `anthropic/…`), so the repair is
15
+ * unconditional for any call that lacks it.
16
+ *
17
+ * Returns the agent-relative paths it changed.
18
+ */
19
+ export function repairExeClaudeRoute(cwd) {
20
+ const root = join(cwd, "agent");
21
+ if (!existsSync(root))
22
+ return [];
23
+ const changed = [];
24
+ for (const file of sourceFiles(root)) {
25
+ const source = readFileSync(file, "utf8");
26
+ const next = withAnthropicFactory(source);
27
+ if (next === source)
28
+ continue;
29
+ writeFileSync(file, next);
30
+ changed.push(relative(cwd, file));
31
+ }
32
+ return changed;
33
+ }
34
+ /** The source with `createAnthropic` passed to each exeModel call that has only `createOpenAI`, and imported. */
35
+ export function withAnthropicFactory(source) {
36
+ if (!/\bexeModel\s*\(/.test(source))
37
+ return source;
38
+ let touched = false;
39
+ const next = source.replace(/\bexeModel\s*\(\s*\{([^{}]*)\}\s*\)/g, (call, body) => {
40
+ if (!/\bcreateOpenAI\b/.test(body) || /\bcreateAnthropic\b/.test(body))
41
+ return call;
42
+ touched = true;
43
+ return call.replace(/\bcreateOpenAI\b/, "createOpenAI, createAnthropic");
44
+ });
45
+ if (!touched)
46
+ return source;
47
+ if (/\bimport\s*\{[^}]*\bcreateAnthropic\b[^}]*\}\s*from\s*["']@ai-sdk\/anthropic["']/.test(next))
48
+ return next;
49
+ const openaiImport = /^import\s*\{[^}]*\bcreateOpenAI\b[^}]*\}\s*from\s*["']@ai-sdk\/openai["'];?[^\n]*\n/m.exec(next);
50
+ const line = 'import { createAnthropic } from "@ai-sdk/anthropic";\n';
51
+ if (openaiImport) {
52
+ const at = openaiImport.index + openaiImport[0].length;
53
+ return next.slice(0, at) + line + next.slice(at);
54
+ }
55
+ return line + next;
56
+ }
57
+ function sourceFiles(dir) {
58
+ const out = [];
59
+ for (const entry of readdirSync(dir)) {
60
+ if (entry === "node_modules" || entry.startsWith("."))
61
+ continue;
62
+ const full = join(dir, entry);
63
+ if (statSync(full).isDirectory())
64
+ out.push(...sourceFiles(full));
65
+ else if (/\.(ts|mts|js|mjs)$/.test(entry))
66
+ out.push(full);
67
+ }
68
+ return out;
69
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * What an agent built on eve 0.68 needs to run on eve 0.81, beyond new package
3
+ * versions. Each step touches only what a Kybernesis scaffold or registry item
4
+ * wrote, in the exact form it wrote it; anything authored by hand is reported
5
+ * and left alone.
6
+ */
7
+ /** Where `kyb init` pointed the @kybernesis registry before the registry served one copy per eve line. */
8
+ export declare const UNVERSIONED_REGISTRY_URL = "https://registry.kybernesis.ai/r/{name}.json";
9
+ /**
10
+ * Third-party extensions, at the releases proven on the certified eve. The
11
+ * registry items install their newest release, which is built on a later eve
12
+ * and refused at discovery. Move them with the eve certification, never on
13
+ * their own.
14
+ */
15
+ export declare const THIRD_PARTY_PINS: Readonly<Record<string, string>>;
16
+ /** Third-party extensions with no release that mounts on the certified eve, and why. */
17
+ export declare const DROPPED_EXTENSIONS: Readonly<Record<string, string>>;
18
+ /** eve 0.81.1 peers on ai ^7.0.137; an agent's own range for ai must allow it. */
19
+ export declare const AI_FLOOR = "7.0.137";
20
+ /** Exactly what `eve add extension/agent-browser` wrote. */
21
+ export declare const AGENT_BROWSER_MOUNT = "import browser from \"@agent-browser/eve\";\n\nexport default browser({});\n";
22
+ export interface DependencyMove {
23
+ name: string;
24
+ from: string;
25
+ /** null when the dependency is removed. */
26
+ to: string | null;
27
+ why: string;
28
+ }
29
+ export interface ManifestMigration {
30
+ moves: DependencyMove[];
31
+ registry: {
32
+ from: string;
33
+ to: string;
34
+ } | null;
35
+ }
36
+ /**
37
+ * The manifest half: third-party pins, dropped extensions, the ai floor and
38
+ * the registry namespace. Mutates `pkg` and says what moved; writing it is the
39
+ * caller's, so a dry run can read the answer without touching disk.
40
+ */
41
+ export declare function migrateManifest(pkg: Record<string, unknown>, registryUrl: string): ManifestMigration;
42
+ export interface SourceMigration {
43
+ /** Files changed, relative to the agent. */
44
+ updated: string[];
45
+ /** Files removed, relative to the agent. */
46
+ removed: string[];
47
+ /** Files that need a hand, with what to do. */
48
+ manual: Array<{
49
+ file: string;
50
+ action: string;
51
+ }>;
52
+ }
53
+ /**
54
+ * The source half: the judge, the browser mount, the ask tools, and old tool
55
+ * names in prose. With `write: false` it only says what it would do.
56
+ */
57
+ export declare function migrateSources(cwd: string, { write }?: {
58
+ write?: boolean;
59
+ }): SourceMigration;
60
+ /** The conversation store. The first boot on eve 0.81 deletes one written by an older line. */
61
+ export declare const WORKFLOW_STORE = ".eve/.workflow-data";
62
+ /** Where the store from line `fromLine` (e.g. "0.68") is kept across a hop. */
63
+ export declare function workflowStoreBackup(fromLine: string): string;
64
+ /** Build caches from the old line that the new one must not reuse. */
65
+ export declare const STALE_EVE_CACHES: string[];
66
+ /**
67
+ * Copy the conversation store aside before anything on the new line boots
68
+ * (the eval gate's dev server shares it with production). An existing backup
69
+ * is never overwritten: it is the copy taken before the first attempt.
70
+ */
71
+ export declare function backUpWorkflowStore(cwd: string, fromLine: string): "copied" | "kept" | "none";
72
+ /** Put the backed-up store back, for an agent that is going back to its old line. */
73
+ export declare function restoreWorkflowStore(cwd: string, fromLine: string): boolean;
74
+ export declare function clearStaleEveCaches(cwd: string): string[];
@@ -0,0 +1,207 @@
1
+ import { cpSync, existsSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join, relative } from "node:path";
3
+ /**
4
+ * What an agent built on eve 0.68 needs to run on eve 0.81, beyond new package
5
+ * versions. Each step touches only what a Kybernesis scaffold or registry item
6
+ * wrote, in the exact form it wrote it; anything authored by hand is reported
7
+ * and left alone.
8
+ */
9
+ /** Where `kyb init` pointed the @kybernesis registry before the registry served one copy per eve line. */
10
+ export const UNVERSIONED_REGISTRY_URL = "https://registry.kybernesis.ai/r/{name}.json";
11
+ /**
12
+ * Third-party extensions, at the releases proven on the certified eve. The
13
+ * registry items install their newest release, which is built on a later eve
14
+ * and refused at discovery. Move them with the eve certification, never on
15
+ * their own.
16
+ */
17
+ export const THIRD_PARTY_PINS = { "@github-tools/eve-extension": "0.8.1" };
18
+ /** Third-party extensions with no release that mounts on the certified eve, and why. */
19
+ export const DROPPED_EXTENSIONS = {
20
+ "@agent-browser/eve": "no release mounts on eve 0.81; the agent reads pages on its own computer (open_browser)",
21
+ };
22
+ /** eve 0.81.1 peers on ai ^7.0.137; an agent's own range for ai must allow it. */
23
+ export const AI_FLOOR = "7.0.137";
24
+ /** Exactly what `eve add extension/agent-browser` wrote. */
25
+ export const AGENT_BROWSER_MOUNT = `import browser from "@agent-browser/eve";\n\nexport default browser({});\n`;
26
+ function floorBelow(range, floor) {
27
+ const match = /^[\^~]?(\d+)\.(\d+)\.(\d+)$/.exec(range.trim());
28
+ if (!match)
29
+ return false;
30
+ const want = floor.split(".").map(Number);
31
+ const have = match.slice(1, 4).map(Number);
32
+ if (have[0] !== want[0])
33
+ return false;
34
+ for (let i = 0; i < 3; i += 1)
35
+ if (have[i] !== want[i])
36
+ return have[i] < want[i];
37
+ return false;
38
+ }
39
+ /**
40
+ * The manifest half: third-party pins, dropped extensions, the ai floor and
41
+ * the registry namespace. Mutates `pkg` and says what moved; writing it is the
42
+ * caller's, so a dry run can read the answer without touching disk.
43
+ */
44
+ export function migrateManifest(pkg, registryUrl) {
45
+ const moves = [];
46
+ for (const section of ["dependencies", "devDependencies"]) {
47
+ const deps = pkg[section];
48
+ if (!deps)
49
+ continue;
50
+ for (const [name, range] of Object.entries(deps)) {
51
+ if (name in DROPPED_EXTENSIONS) {
52
+ delete deps[name];
53
+ moves.push({ name, from: range, to: null, why: DROPPED_EXTENSIONS[name] });
54
+ }
55
+ else if (THIRD_PARTY_PINS[name] && range !== THIRD_PARTY_PINS[name]) {
56
+ deps[name] = THIRD_PARTY_PINS[name];
57
+ moves.push({ name, from: range, to: THIRD_PARTY_PINS[name], why: "the release proven on the certified eve" });
58
+ }
59
+ else if (name === "ai" && floorBelow(range, AI_FLOOR)) {
60
+ deps[name] = `^${AI_FLOOR}`;
61
+ moves.push({ name, from: range, to: `^${AI_FLOOR}`, why: "the certified eve needs it" });
62
+ }
63
+ }
64
+ }
65
+ const overrides = pkg.overrides;
66
+ if (overrides && typeof overrides.ai === "string" && floorBelow(overrides.ai, AI_FLOOR))
67
+ overrides.ai = `^${AI_FLOOR}`;
68
+ let registry = null;
69
+ const registries = pkg.registries;
70
+ if (registries?.["@kybernesis"] === UNVERSIONED_REGISTRY_URL && registryUrl !== UNVERSIONED_REGISTRY_URL) {
71
+ registries["@kybernesis"] = registryUrl;
72
+ registry = { from: UNVERSIONED_REGISTRY_URL, to: registryUrl };
73
+ }
74
+ return { moves, registry };
75
+ }
76
+ /**
77
+ * Registry-written workflow tools whose question must survive the person
78
+ * sending another message. On 0.69+ an `execute` tool's open question is
79
+ * withdrawn by one; a `task` keeps it open and hands the answer back later.
80
+ */
81
+ const ASK_TOOLS = [
82
+ {
83
+ path: "agent/tools/request_vault_item.ts",
84
+ helper: "@kybernesis/vault/ask",
85
+ explain: (source) => source.replace("// Parks durably; ", "// Runs as a task: the question stays open through any other message the person\n" +
86
+ "// sends (an `execute` call's question is withdrawn by one), and the answer\n" +
87
+ "// reaches the model later in the conversation. Parks durably; "),
88
+ },
89
+ {
90
+ path: "agent/tools/approve_spend_request.ts",
91
+ helper: "@kybernesis/payments",
92
+ explain: (source) => source.replace("\nexport default defineWorkflowTool(", "\n// Runs as a task: the question stays open through any other message the person\n" +
93
+ "// sends (an `execute` call's question is withdrawn by one, which would cancel\n" +
94
+ "// the purchase), and the result reaches the model later in the conversation." +
95
+ "\nexport default defineWorkflowTool("),
96
+ },
97
+ ];
98
+ const EXECUTE_WORKFLOW = /( {2})async execute\(input, ctx\) \{(\n\s*"use workflow";)/;
99
+ function walk(dir, keep) {
100
+ if (!existsSync(dir))
101
+ return [];
102
+ return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
103
+ const path = join(dir, entry.name);
104
+ if (entry.isDirectory())
105
+ return entry.name === "node_modules" ? [] : walk(path, keep);
106
+ return entry.isFile() && keep(entry.name) ? [path] : [];
107
+ });
108
+ }
109
+ /**
110
+ * The source half: the judge, the browser mount, the ask tools, and old tool
111
+ * names in prose. With `write: false` it only says what it would do.
112
+ */
113
+ export function migrateSources(cwd, { write = true } = {}) {
114
+ const result = { updated: [], removed: [], manual: [] };
115
+ const rel = (path) => relative(cwd, path);
116
+ // eve 0.72 grades with a decision model; `.evaluationModel(` no longer compiles.
117
+ for (const path of [join(cwd, "evals/evals.config.ts"), join(cwd, "evals.config.ts")]) {
118
+ if (!existsSync(path))
119
+ continue;
120
+ const source = readFileSync(path, "utf8");
121
+ if (!source.includes(".evaluationModel("))
122
+ continue;
123
+ if (write)
124
+ writeFileSync(path, source.replaceAll(".evaluationModel(", ".decisionModel("));
125
+ result.updated.push(rel(path));
126
+ }
127
+ // The browser extension: remove the scaffold's mount, flag any other.
128
+ for (const path of walk(join(cwd, "agent"), (name) => /\.[cm]?[jt]s$/.test(name))) {
129
+ const source = readFileSync(path, "utf8");
130
+ if (!source.includes("@agent-browser/eve"))
131
+ continue;
132
+ if (source === AGENT_BROWSER_MOUNT) {
133
+ if (write)
134
+ rmSync(path);
135
+ result.removed.push(rel(path));
136
+ }
137
+ else {
138
+ result.manual.push({ file: rel(path), action: `remove it: @agent-browser/eve ${DROPPED_EXTENSIONS["@agent-browser/eve"]}` });
139
+ }
140
+ }
141
+ for (const tool of ASK_TOOLS) {
142
+ const path = join(cwd, tool.path);
143
+ if (!existsSync(path))
144
+ continue;
145
+ const source = readFileSync(path, "utf8");
146
+ if (!source.includes(tool.helper) || !EXECUTE_WORKFLOW.test(source))
147
+ continue;
148
+ const swapped = source.replace(EXECUTE_WORKFLOW, "$1async task(input, ctx) {$2");
149
+ if (write)
150
+ writeFileSync(path, swapped.includes("Runs as a task:") ? swapped : tool.explain(swapped));
151
+ result.updated.push(tool.path);
152
+ }
153
+ // Prose that names tools eve 0.81 no longer has: the model would look for them.
154
+ for (const path of walk(join(cwd, "agent"), (name) => /\.(md|ts)$/.test(name))) {
155
+ const source = readFileSync(path, "utf8");
156
+ const names = ["connection_search", "load_skill"].filter((name) => new RegExp(`\\b${name}\\b`).test(source));
157
+ if (names.length === 0)
158
+ continue;
159
+ result.manual.push({
160
+ file: rel(path),
161
+ action: `rewrite ${names.map((n) => `\`${n}\``).join(" and ")}: eve 0.81 finds tools with \`eve__search\`, calls them with \`eve__tool\`, and loads skills with \`eve__skill\``,
162
+ });
163
+ }
164
+ return result;
165
+ }
166
+ /** The conversation store. The first boot on eve 0.81 deletes one written by an older line. */
167
+ export const WORKFLOW_STORE = ".eve/.workflow-data";
168
+ /** Where the store from line `fromLine` (e.g. "0.68") is kept across a hop. */
169
+ export function workflowStoreBackup(fromLine) {
170
+ return `${WORKFLOW_STORE}.eve-${fromLine}`;
171
+ }
172
+ /** Build caches from the old line that the new one must not reuse. */
173
+ export const STALE_EVE_CACHES = [".eve/sandbox-cache", ".eve/dev-runtime"];
174
+ /**
175
+ * Copy the conversation store aside before anything on the new line boots
176
+ * (the eval gate's dev server shares it with production). An existing backup
177
+ * is never overwritten: it is the copy taken before the first attempt.
178
+ */
179
+ export function backUpWorkflowStore(cwd, fromLine) {
180
+ const store = join(cwd, WORKFLOW_STORE);
181
+ const backup = join(cwd, workflowStoreBackup(fromLine));
182
+ if (existsSync(backup))
183
+ return "kept";
184
+ if (!existsSync(store))
185
+ return "none";
186
+ cpSync(store, backup, { recursive: true, preserveTimestamps: true });
187
+ return "copied";
188
+ }
189
+ /** Put the backed-up store back, for an agent that is going back to its old line. */
190
+ export function restoreWorkflowStore(cwd, fromLine) {
191
+ const backup = join(cwd, workflowStoreBackup(fromLine));
192
+ if (!existsSync(backup))
193
+ return false;
194
+ const store = join(cwd, WORKFLOW_STORE);
195
+ rmSync(store, { recursive: true, force: true });
196
+ cpSync(backup, store, { recursive: true, preserveTimestamps: true });
197
+ return true;
198
+ }
199
+ export function clearStaleEveCaches(cwd) {
200
+ return STALE_EVE_CACHES.filter((dir) => {
201
+ const path = join(cwd, dir);
202
+ if (!existsSync(path))
203
+ return false;
204
+ rmSync(path, { recursive: true, force: true });
205
+ return true;
206
+ });
207
+ }
package/dist/templates.js CHANGED
@@ -792,9 +792,10 @@ export { deliver as default } from "@kybernesis/engineer/tools";
792
792
  // that matters is host access, and the old wording blurred exactly the line
793
793
  // people read this comment to understand.
794
794
  //
795
- // The root DOES get a browser and GitHub tools — the engineer layer installs
796
- // extension/agent-browser and extension/github-tools at the root, deliberately,
797
- // because reading a page is not the same blast radius as running a command.
795
+ // The root DOES get GitHub tools — the engineer layer installs
796
+ // extension/github-tools at the root, deliberately, because reading a repo is
797
+ // not the same blast radius as running a command. Pages are read on the
798
+ // agent's own computer (open_browser).
798
799
  export { default } from "@kybernesis/engineer";
799
800
  `,
800
801
  },
@@ -860,8 +861,9 @@ export default defineEvalConfig({
860
861
  // Verified against the exe integration rather than assumed: it validates model
861
862
  // names and refuses one it does not carry, so a stale default here fails the
862
863
  // whole suite at the judge rather than at anything the agent did.
863
- // eve ≥0.62 grades with an EVALUATION model, not a language model instance.
864
- judge: { model: exe.evaluationModel(process.env.EXE_JUDGE_MODEL ?? "claude-sonnet-5") },
864
+ // eve ≥0.72 grades with a DECISION model (an evaluation model from 0.62),
865
+ // not a language model instance.
866
+ judge: { model: exe.decisionModel(process.env.EXE_JUDGE_MODEL ?? "claude-sonnet-5") },
865
867
  // Real model and real memory on every turn: generous timeout, gentle concurrency.
866
868
  timeoutMs: 300_000,
867
869
  maxConcurrency: 1,
package/dist/upgrade.js CHANGED
@@ -11,8 +11,10 @@ import { dockerPruneCronArtifact } from "./docker-prune-cron.js";
11
11
  import { newestOnLine } from "./on-line-version.js";
12
12
  import { findMatchingAgentServiceUnit, repairManageRestart } from "./systemd.js";
13
13
  import { repairTerminalSandboxCleanupHooks } from "./sandbox-cleanup.js";
14
+ import { repairExeClaudeRoute } from "./exe-claude-route.js";
14
15
  import { repairOptInTools, repairRemovedDefaultTools as repairRemovedDefaultTools_ } from "./removed-default-tools.js";
15
- import { EVE_VERSION, bold, capture, dim, green, parseEnv, red, run, yellow } from "./util.js";
16
+ import { EVE_VERSION, REGISTRY_URL, bold, capture, dim, green, parseEnv, red, run, yellow } from "./util.js";
17
+ import { backUpWorkflowStore, clearStaleEveCaches, migrateManifest, migrateSources, restoreWorkflowStore, workflowStoreBackup, } from "./line-hop.js";
16
18
  import { inspectEveAgent } from "./agent-limits.js";
17
19
  import { remoteProjectPath, sshTarget } from "./deploy.js";
18
20
  import { classifyModelReach } from "./model-reach.js";
@@ -75,6 +77,25 @@ function repairLocalQueueTimeouts(cwd, deps) {
75
77
  ` ${dim("slower than 30s was redelivered and its steps re-run — the agent answered twice.")}\n` +
76
78
  ` ${dim("Takes effect on the next server restart.")}\n`);
77
79
  }
80
+ /** Route Claude ids through the gateway's Messages API wherever an agent builds an exe model (see exe-claude-route.ts). */
81
+ function repairExeClaudeRoutes(cwd, deps) {
82
+ if (!deps["@kybernesis/exe"])
83
+ return;
84
+ const changed = repairExeClaudeRoute(cwd);
85
+ if (changed.length === 0)
86
+ return;
87
+ if (!deps["@ai-sdk/anthropic"]) {
88
+ const added = commandResult("npm", ["install", "@ai-sdk/anthropic"], cwd);
89
+ if (added.status !== 0) {
90
+ console.log(red(` ✗ could not install @ai-sdk/anthropic for the Claude route:\n${added.stderr.trim()}`));
91
+ process.exitCode = 1;
92
+ return;
93
+ }
94
+ }
95
+ console.log(` ${green("+")} passed createAnthropic to exeModel in ${changed.join(", ")}\n` +
96
+ ` ${dim("Claude on the exe gateway is served on its Messages API; with only createOpenAI")}\n` +
97
+ ` ${dim("a Claude id has no route, and exe 0.17+ refuses it when the agent loads.")}\n`);
98
+ }
78
99
  /**
79
100
  * Finish a Buzz install that a version bump alone leaves half-done.
80
101
  *
@@ -578,11 +599,14 @@ function exeJudgeUrl(cwd, env) {
578
599
  if (!path)
579
600
  return null;
580
601
  const source = readFileSync(path, "utf8");
581
- const judgeProvider = /judge\s*:\s*\{[\s\S]*?model\s*:\s*([A-Za-z_$][\w$]*)\s*(?:\.evaluationModel\s*)?\(/.exec(source)?.[1];
602
+ const judgeProvider = /judge\s*:\s*\{[\s\S]*?model\s*:\s*([A-Za-z_$][\w$]*)\s*(?:\.(?:decisionModel|evaluationModel)\s*)?\(/.exec(source)?.[1];
582
603
  if (!judgeProvider)
583
604
  return null;
584
605
  const escaped = judgeProvider.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
585
- const providerBody = new RegExp(`(?:const|let)\\s+${escaped}\\s*=\\s*create[A-Za-z]+\\s*\\(\\s*\\{([\\s\\S]*?)\\}\\s*\\)`).exec(source)?.[1];
606
+ // The provider may be made inline (`model: createAnthropic({ … }).decisionModel(…)`)
607
+ // as well as bound to a name first; both carry the base URL in the same object.
608
+ const inline = /judge\s*:\s*\{[\s\S]*?model\s*:\s*create[A-Za-z]+\s*\(\s*\{([\s\S]*?)\}\s*\)/.exec(source)?.[1];
609
+ const providerBody = inline ?? new RegExp(`(?:const|let)\\s+${escaped}\\s*=\\s*create[A-Za-z]+\\s*\\(\\s*\\{([\\s\\S]*?)\\}\\s*\\)`).exec(source)?.[1];
586
610
  if (!providerBody)
587
611
  return /EXE_LLM_URL|llm\.int\.exe\.xyz/.test(source) ? undefined : null;
588
612
  if (/EXE_LLM_URL/.test(providerBody))
@@ -642,7 +666,7 @@ async function runUpgradeEvalGate(cwd, host) {
642
666
  const target = sshTarget(cwd, typeof host === "string" && host.length > 0 ? host : undefined);
643
667
  if (!target) {
644
668
  reportEvalGateUnavailable(cwd, null, ["no ssh target is configured"]);
645
- return;
669
+ return "unavailable";
646
670
  }
647
671
  console.log(bold(`\nRunning the eval gate on ${target} (${remoteProjectPath(cwd)}) — deploy only on green …\n`));
648
672
  const result = spawnSync("ssh", [target, `cd ${remoteProjectPath(cwd)} && npm run eval`], {
@@ -652,27 +676,26 @@ async function runUpgradeEvalGate(cwd, host) {
652
676
  });
653
677
  if (result.status === 0) {
654
678
  console.log(`\n${green("✓ Upgrade green.")} Deploy with: ${bold("npx eve deploy")}\n`);
679
+ return "green";
655
680
  }
656
- else {
657
- console.log(`\n${red("✗ Evals failed after upgrade.")} Do NOT deploy — inspect .eve/evals/ artifacts on the host.\n`);
658
- process.exitCode = 1;
659
- }
660
- return;
681
+ console.log(`\n${red("✗ Evals failed after upgrade.")} Do NOT deploy — inspect .eve/evals/ artifacts on the host.\n`);
682
+ process.exitCode = 1;
683
+ return "failed";
661
684
  }
662
685
  const preflight = await preflightEvalGate(cwd);
663
686
  if (preflight.kind === "unavailable") {
664
687
  reportEvalGateUnavailable(cwd, sshTarget(cwd), preflight.reasons);
665
- return;
688
+ return "unavailable";
666
689
  }
667
690
  console.log(bold("\nRunning the eval gate (npm run eval) — deploy only on green …\n"));
668
691
  const ok = run("npm", ["run", "eval"], { cwd, allowFail: true });
669
692
  if (ok) {
670
693
  console.log(`\n${green("✓ Upgrade green.")} Deploy with: ${bold("npx eve deploy")}\n`);
694
+ return "green";
671
695
  }
672
- else {
673
- console.log(`\n${red("✗ Evals failed after upgrade.")} Do NOT deploy — inspect .eve/evals/ artifacts.\n`);
674
- process.exitCode = 1;
675
- }
696
+ console.log(`\n${red("✗ Evals failed after upgrade.")} Do NOT deploy — inspect .eve/evals/ artifacts.\n`);
697
+ process.exitCode = 1;
698
+ return "failed";
676
699
  }
677
700
  function upgradeEnvironment(cwd) {
678
701
  const envPath = join(cwd, ".env.local");
@@ -867,6 +890,47 @@ function startStoppedBridge(cwd, unit) {
867
890
  ` systemctl status ${unit}`);
868
891
  return false;
869
892
  }
893
+ /**
894
+ * A third-party extension whose declared peer range trails the certified
895
+ * eve (the Vercel-owned @github-tools/eve-extension does) makes npm's
896
+ * default resolver ERESOLVE before it will write a tree at all. That is not
897
+ * a reason to abort the upgrade: retry the same install with
898
+ * --legacy-peer-deps so the tree is written, then let assessPeerTree judge
899
+ * WHO actually objects. A @kybernesis or root conflict still stops
900
+ * everything; only a stale third-party edge is tolerated (same principle as
901
+ * the KYB-548 peer-tree gate). KYB-561.
902
+ */
903
+ function npmInstall(cwd, args) {
904
+ let result = commandResult("npm", args, cwd);
905
+ if (result.status !== 0 && /ERESOLVE/i.test(`${result.stdout}\n${result.stderr}`)) {
906
+ console.log(yellow("\n ! npm reported a peer conflict. Retrying with --legacy-peer-deps so the installed peer tree can be assessed."));
907
+ result = commandResult("npm", [...args, "--legacy-peer-deps"], cwd);
908
+ }
909
+ return result;
910
+ }
911
+ function manifestMoved(migration) {
912
+ return migration.moves.length > 0 || migration.registry !== null;
913
+ }
914
+ function reportManifestMigration(migration) {
915
+ for (const move of migration.moves) {
916
+ console.log(move.to === null
917
+ ? ` ${green("-")} ${move.name} removed ${dim(`(${move.why})`)}`
918
+ : ` ${yellow("↑")} ${move.name}: ${move.from} → ${move.to} ${dim(`(${move.why})`)}`);
919
+ }
920
+ if (migration.registry)
921
+ console.log(` ${green("+")} @kybernesis registry → ${migration.registry.to} ${dim("(the items built for this eve line)")}`);
922
+ }
923
+ function reportSourceMigration(cwd) {
924
+ const result = migrateSources(cwd);
925
+ for (const file of result.updated)
926
+ console.log(` ${green("+")} updated ${file} ${dim(`(for eve ${EVE_VERSION})`)}`);
927
+ for (const file of result.removed)
928
+ console.log(` ${green("-")} removed ${file} ${dim("(mounted an extension that does not run on this eve)")}`);
929
+ for (const { file, action } of result.manual)
930
+ console.log(` ${yellow("!")} ${file}: ${action}`);
931
+ if (result.updated.length || result.removed.length || result.manual.length)
932
+ console.log();
933
+ }
870
934
  /** The one line `kyb upgrade --check` prints for a program to read; everything else it prints is for people. */
871
935
  export const UPGRADE_PLAN_PREFIX = "KYB_UPGRADE_PLAN ";
872
936
  function printPlan(plan) {
@@ -915,6 +979,21 @@ export async function upgrade(options = {}) {
915
979
  console.log(` ${dim(selfVersion.fix)} ${dim("(continuing: --allow-stale)")}\n`);
916
980
  }
917
981
  reportAgentInputLimit(cwd);
982
+ // An agent already on a newer eve line than this kyb was certified on must
983
+ // be upgraded by that line's kyb: from here every @kybernesis package would
984
+ // be "upgraded" to a build for the older eve.
985
+ const eveNow = installedPackageValue(cwd, "eve", ".version");
986
+ const nowLine = eveNow ? majorMinor(eveNow) : null;
987
+ const ownLine = majorMinor(EVE_VERSION);
988
+ if (nowLine && ownLine && (nowLine[0] > ownLine[0] || (nowLine[0] === ownLine[0] && nowLine[1] > ownLine[1]))) {
989
+ if (options.check)
990
+ printPlan({ ok: false, unresolved: ["eve"], changes: [], resetsConversations: false });
991
+ fail(`This agent runs eve ${eveNow}, a newer line than this kyb is certified for (${EVE_VERSION}). ` +
992
+ `Run the kyb for its line:\n npx --yes --package=@kybernesis/create@eve-${nowLine[0]}.${nowLine[1]} kyb upgrade\n` +
993
+ `Nothing was changed.`);
994
+ return;
995
+ }
996
+ const selfInstalled = "installed" in selfVersion ? selfVersion.installed : undefined;
918
997
  const entries = dependencyEntries(pkg);
919
998
  const installedVersions = new Map();
920
999
  const targetVersions = new Map();
@@ -933,11 +1012,16 @@ export async function upgrade(options = {}) {
933
1012
  // that supports the eve this host will be running — NOT npm's `latest`,
934
1013
  // which for a per-eve-line package is routinely a build for a different
935
1014
  // line and installs without complaint.
1015
+ // kyb itself declares no eve peer, so the newest published kyb can belong
1016
+ // to another eve line. The project carries the kyb doing this upgrade (a
1017
+ // stale one renewed itself above), never a newer one.
936
1018
  const target = name === "eve"
937
1019
  ? EVE_VERSION
938
- : name.startsWith("@kybernesis/")
939
- ? newestBuildForEve(name, EVE_VERSION)
940
- : capture("npm", ["view", name, "version"])?.trim();
1020
+ : name === "@kybernesis/create"
1021
+ ? selfInstalled && versionLt(installed, selfInstalled) ? selfInstalled : installed
1022
+ : name.startsWith("@kybernesis/")
1023
+ ? newestBuildForEve(name, EVE_VERSION)
1024
+ : capture("npm", ["view", name, "version"])?.trim();
941
1025
  if (!target) {
942
1026
  console.log(` ${yellow("!")} ${name}: ` +
943
1027
  (name.startsWith("@kybernesis/")
@@ -1002,13 +1086,19 @@ export async function upgrade(options = {}) {
1002
1086
  const changed = [...new Set(targets
1003
1087
  .filter((target) => target.installedVersion !== target.targetVersion && !(target.name === "eve" && versionLt(target.targetVersion, target.installedVersion)))
1004
1088
  .map((target) => `${target.name}@${target.targetVersion}`))];
1089
+ // Third-party pins, dropped extensions and the registry namespace for this
1090
+ // eve line. pkg now carries them; it is written below with the rest.
1091
+ const manifest = migrateManifest(pkg, REGISTRY_URL);
1005
1092
  if (options.check) {
1006
1093
  printPlan({
1007
1094
  ok: true,
1008
1095
  eve: { installed: eveInstalled ?? null, certified: EVE_VERSION },
1009
- changes: [...new Map(targets
1096
+ changes: [
1097
+ ...new Map(targets
1010
1098
  .filter((target) => changed.includes(`${target.name}@${target.targetVersion}`))
1011
- .map((target) => [target.name, { name: target.name, from: target.installedVersion, to: target.targetVersion }])).values()],
1099
+ .map((target) => [target.name, { name: target.name, from: target.installedVersion, to: target.targetVersion }])).values(),
1100
+ ...manifest.moves.map((move) => ({ name: move.name, from: move.from, to: move.to ?? "removed" })),
1101
+ ],
1012
1102
  resetsConversations: eveChanged,
1013
1103
  });
1014
1104
  return;
@@ -1019,6 +1109,15 @@ export async function upgrade(options = {}) {
1019
1109
  }
1020
1110
  repairLocalQueueTimeouts(cwd, deps);
1021
1111
  if (changed.length === 0) {
1112
+ if (manifestMoved(manifest)) {
1113
+ reportManifestMigration(manifest);
1114
+ writeFileSync(packagePath, `${JSON.stringify(pkg, null, 2)}\n`);
1115
+ const reinstalled = npmInstall(cwd, ["install"]);
1116
+ if (reinstalled.status !== 0) {
1117
+ fail("\nnpm install failed after the dependency moves above; package.json carries them, the tree may not.");
1118
+ return;
1119
+ }
1120
+ }
1022
1121
  if (loosePin) {
1023
1122
  const pinned = commandResult("npm", ["install", "--save-exact", `eve@${eveTarget}`], cwd);
1024
1123
  if (pinned.status !== 0) {
@@ -1038,9 +1137,12 @@ export async function upgrade(options = {}) {
1038
1137
  repairNotifySetup(cwd, deps);
1039
1138
  repairReasoningSandboxes(cwd, deps);
1040
1139
  repairManageRestart(cwd, deps);
1140
+ repairExeClaudeRoutes(cwd, deps);
1141
+ reportSourceMigration(cwd);
1041
1142
  return;
1042
1143
  }
1043
1144
  console.log(bold(`\nInstalling: ${changed.join(", ")}\n`));
1145
+ reportManifestMigration(manifest);
1044
1146
  let stoppedBridge = null;
1045
1147
  if (cleanInstall && deps["@kybernesis/buzz"]) {
1046
1148
  const stopped = stopActiveBridge(cwd, buzzBridgeUnit(cwd));
@@ -1048,6 +1150,18 @@ export async function upgrade(options = {}) {
1048
1150
  return;
1049
1151
  stoppedBridge = stopped;
1050
1152
  }
1153
+ // A new eve line: keep the conversations aside before anything on it boots
1154
+ // (its first boot deletes an older line's store, and the eval gate's dev
1155
+ // server shares the store with production), and drop the old line's caches.
1156
+ const storeLine = cleanInstall && installedMajorMinor ? installedMajorMinor.join(".") : null;
1157
+ if (storeLine) {
1158
+ const kept = backUpWorkflowStore(cwd, storeLine);
1159
+ if (kept !== "none") {
1160
+ console.log(` ${green("+")} conversations from eve ${storeLine} kept at ${workflowStoreBackup(storeLine)}${kept === "kept" ? ` ${dim("(from an earlier attempt)")}` : ""}`);
1161
+ }
1162
+ for (const dir of clearStaleEveCaches(cwd))
1163
+ console.log(` ${green("-")} cleared ${dir} ${dim(`(built for eve ${storeLine})`)}`);
1164
+ }
1051
1165
  if (cleanInstall) {
1052
1166
  rewriteManifest(cwd, pkg, targets);
1053
1167
  try {
@@ -1064,28 +1178,12 @@ export async function upgrade(options = {}) {
1064
1178
  // reason rewriteManifest pins it: npm would save a caret by default.
1065
1179
  const eveChange = changed.find((spec) => spec.startsWith("eve@")) ?? (loosePin ? `eve@${eveTarget}` : undefined);
1066
1180
  const others = changed.filter((spec) => spec !== eveChange);
1067
- /**
1068
- * A third-party extension whose declared peer range trails the certified
1069
- * eve (the Vercel-owned @github-tools/eve-extension and @agent-browser/eve
1070
- * both do) makes npm's default resolver ERESOLVE before it will write a
1071
- * tree at all. That is not a reason to abort the upgrade: retry the same
1072
- * install with --legacy-peer-deps so the tree is written, then let
1073
- * assessPeerTree below judge WHO actually objects. A @kybernesis or root
1074
- * conflict still stops everything; only a stale third-party edge is
1075
- * tolerated (same principle as the KYB-548 peer-tree gate). KYB-561.
1076
- */
1077
- const npmInstall = (args) => {
1078
- let result = commandResult("npm", args, cwd);
1079
- if (result.status !== 0 && /ERESOLVE/i.test(`${result.stdout}\n${result.stderr}`)) {
1080
- console.log(yellow("\n ! npm reported a peer conflict. Retrying with --legacy-peer-deps so the installed peer tree can be assessed."));
1081
- result = commandResult("npm", [...args, "--legacy-peer-deps"], cwd);
1082
- }
1083
- return result;
1084
- };
1181
+ if (!cleanInstall && manifestMoved(manifest))
1182
+ writeFileSync(packagePath, `${JSON.stringify(pkg, null, 2)}\n`);
1085
1183
  const installArgs = cleanInstall ? ["install"] : ["install", ...others];
1086
- let installed = cleanInstall || others.length > 0 ? npmInstall(installArgs) : { status: 0, stdout: "", stderr: "" };
1184
+ let installed = cleanInstall || others.length > 0 || manifestMoved(manifest) ? npmInstall(cwd, installArgs) : { status: 0, stdout: "", stderr: "" };
1087
1185
  if (installed.status === 0 && !cleanInstall && eveChange) {
1088
- installed = npmInstall(["install", "--save-exact", eveChange]);
1186
+ installed = npmInstall(cwd, ["install", "--save-exact", eveChange]);
1089
1187
  }
1090
1188
  if (installed.status !== 0) {
1091
1189
  const output = `${installed.stdout}\n${installed.stderr}`;
@@ -1129,6 +1227,8 @@ export async function upgrade(options = {}) {
1129
1227
  repairNotifySetup(cwd, deps);
1130
1228
  repairReasoningSandboxes(cwd, deps);
1131
1229
  repairManageRestart(cwd, deps);
1230
+ repairExeClaudeRoutes(cwd, deps);
1231
+ reportSourceMigration(cwd);
1132
1232
  run("npm", ["run", "typecheck"], { cwd });
1133
1233
  if (eveChanged) {
1134
1234
  const infoOk = run("npx", ["eve", "info"], { cwd, allowFail: true, quiet: true });
@@ -1138,5 +1238,9 @@ export async function upgrade(options = {}) {
1138
1238
  console.log(yellow("\nEval gate SKIPPED (--skip-eval). Run `npm run eval` before deploying.\n"));
1139
1239
  return;
1140
1240
  }
1141
- await runUpgradeEvalGate(cwd, options.host);
1241
+ const outcome = await runUpgradeEvalGate(cwd, options.host);
1242
+ // Going back means the old line, and the old line needs its conversations.
1243
+ if (outcome === "failed" && storeLine && restoreWorkflowStore(cwd, storeLine)) {
1244
+ console.log(` ${green("+")} conversations from eve ${storeLine} put back from ${workflowStoreBackup(storeLine)} ${dim("(for when the agent goes back to it)")}\n`);
1245
+ }
1142
1246
  }
package/dist/util.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- export declare const EVE_VERSION = "0.68.0";
2
- export declare const REGISTRY_URL = "https://registry.kybernesis.ai/r/{name}.json";
1
+ export declare const EVE_VERSION = "0.81.1";
2
+ /** The registry items built for EVE_VERSION's line; every @kybernesis dep in them is tagged for it. */
3
+ export declare const REGISTRY_URL = "https://registry.kybernesis.ai/r/eve-0.81/{name}.json";
3
4
  export declare const DEFAULT_ISSUER = "https://agent.kybernesis.ai";
4
5
  export declare const green: (s: string) => string;
5
6
  export declare const yellow: (s: string) => string;
package/dist/util.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { createInterface } from "node:readline/promises";
3
- export const EVE_VERSION = "0.68.0";
4
- export const REGISTRY_URL = "https://registry.kybernesis.ai/r/{name}.json";
3
+ export const EVE_VERSION = "0.81.1";
4
+ /** The registry items built for EVE_VERSION's line; every @kybernesis dep in them is tagged for it. */
5
+ export const REGISTRY_URL = "https://registry.kybernesis.ai/r/eve-0.81/{name}.json";
5
6
  export const DEFAULT_ISSUER = "https://agent.kybernesis.ai";
6
7
  const TTY = process.stdout.isTTY === true;
7
8
  export const green = (s) => (TTY ? `\x1b[32m${s}\x1b[0m` : s);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kybernesis/create",
3
- "version": "0.24.2",
3
+ "version": "0.25.0",
4
4
  "description": "The Kybernesis agent scaffolder and FDE toolkit: one command to a governed, remembering, multiplayer, self-testing eve agent \u2014 plus doctor and upgrade.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -39,14 +39,15 @@
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "24.x",
42
- "eve": "0.68.0",
42
+ "eve": "0.81.1",
43
43
  "typescript": "7.0.2"
44
44
  },
45
45
  "engines": {
46
46
  "node": ">=20"
47
47
  },
48
48
  "publishConfig": {
49
- "access": "public"
49
+ "access": "public",
50
+ "tag": "eve-0.81"
50
51
  },
51
52
  "peerDependencies": {}
52
53
  }
@@ -72,7 +72,7 @@ Docs: `docs/connections/*`.
72
72
 
73
73
  ## Skills (agent/skills/)
74
74
 
75
- On-demand procedures (model calls `load_skill` when the description matches).
75
+ On-demand procedures (model calls `eve__skill` when the description matches).
76
76
  Forms: flat `.md` (first line = routing description) → packaged dir with
77
77
  `SKILL.md` (+`references/`, description frontmatter required) → `defineSkill`
78
78
  (only for typed/generated content). The description is a ROUTING HINT — write
@@ -112,7 +112,7 @@ Instruct serial delegation ("one draft at a time, wait for each result").
112
112
  ## Test in eve dev
113
113
 
114
114
  `npx eve dev` boots the local runtime + chat TUI. Walk: identity → skill
115
- routing (watch load_skill) → delegation → memory round-trip → (engineer)
115
+ routing (watch eve__skill) → delegation → memory round-trip → (engineer)
116
116
  screenshot turn. Local principal counts as a DM surface. Kill the dev server
117
117
  before `npm run eval`. The TUI is NOT the deployed agent — redeploy after
118
118
  every change.
@@ -77,7 +77,7 @@ exactly §§2, 5, 6, and 7 of this playbook:
77
77
  | --- | --- | --- |
78
78
  | `npm create @kybernesis <name>` | Day 1, once | Same as `kyb init <name>` without installing anything first |
79
79
  | `kyb init <name>` | Day 1, once | eve scaffold (pinned version) + registry + all four packages + generated departments + eval wiring + env template + checklist |
80
- | `kyb init <name> --engineer` | When the pilot includes building software | Everything above PLUS the engineer layer: workshop sandbox (Playwright baked into the template), the vision screenshot tool, build/ship skills, and the official limbs (agent-browser, github-tools, vercel connection) |
80
+ | `kyb init <name> --engineer` | When the pilot includes building software | Everything above PLUS the engineer layer: workshop sandbox (Playwright baked into the template), the vision screenshot tool, build/ship skills, and the official limbs (github-tools, vercel connection) |
81
81
  | `kyb doctor` | **Constantly** — after every human step, before every deploy, whenever anything is weird | Live preflight: every Arcana key↔workspace pair validated against the API (with the specific fix per failure: wrong-key 403 vs missing-workspace 404), issuer JWKS reachability, `KYBERNESIS_AGENT`, Slack connector env, `eve info` discovery, port-2000 conflict. Exit 1 on failure → usable in CI |
82
82
  | `kyb upgrade` | Maintenance visits / after Kybernesis ships a package update | Compares installed `@kybernesis/*` against npm **and eve against the Kybernesis-certified version** (never blindly npm-latest — we certify eve releases in the platform repo first), installs what's behind, typechecks (+ `eve info` after a framework bump), then **runs the eval suite as the gate** — tells you to deploy only on green |
83
83
  | `kyb upgrade --skip-eval` | Never for production changes | Same, without the gate |
@@ -613,8 +613,10 @@ six build/ship skills + engineering-conduct instructions) and
613
613
  `agent/sandbox/sandbox.ts` (the **workshop**: Playwright + Chromium baked into
614
614
  the sandbox template, domain allowlist on deployed sessions). Pair it with the
615
615
  official limbs if not using `kyb init --engineer`:
616
- `npx eve add extension/agent-browser extension/github-tools connection/vercel`
617
- (run individually).
616
+ `npx eve add extension/github-tools connection/vercel` (run individually), then
617
+ pin `@github-tools/eve-extension` to the release in `kyb`'s THIRD_PARTY_PINS.
618
+ (extension/agent-browser does not mount on eve 0.81; the agent reads pages on
619
+ its own computer with `open_browser`.)
618
620
 
619
621
  **Wire the Vercel connection** (preview deploys + a clickable link for every
620
622
  build — proven live 2026-08-06). After `vercel link` in the agent dir:
@@ -866,7 +868,7 @@ reach for the connection at all. Write it like a capability, name the systems.
866
868
  ### 4.3e Skills — teach the client's procedures (and mine skills.sh)
867
869
 
868
870
  A skill is a markdown procedure the model loads **on demand** (eve advertises
869
- each skill's description; the model calls `load_skill` when a turn matches).
871
+ each skill's description; the model calls `eve__skill` when a turn matches).
870
872
  Anything the client's team does "the same way every time" — their release
871
873
  checklist, escalation path, report format — is a skill, not an always-on
872
874
  instruction. Keep always-on for identity and rules; skills for procedures.
@@ -1018,7 +1020,7 @@ this order — each line catches a different class of wiring mistake:
1018
1020
  1. **Identity**: "who are you, what can you do?" — does the persona match
1019
1021
  identity.md, and does it write chat-length answers?
1020
1022
  2. **Skill routing**: ask something a skill covers *without naming the skill*
1021
- — watch for the `load_skill` call. If it doesn't fire, the skill's
1023
+ — watch for the `eve__skill` call. If it doesn't fire, the skill's
1022
1024
  `description` isn't written as a triggering task.
1023
1025
  3. **Delegation**: ask a department question — watch the subagent call and
1024
1026
  check the answer came back briefed (§4.4's "pack everything" rule).