@kybernesis/create 0.14.3 → 0.14.5
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/README.md +10 -1
- package/dist/cli.js +7 -2
- package/dist/templates.js +1 -1
- package/dist/upgrade-sessions.d.ts +43 -0
- package/dist/upgrade-sessions.js +123 -0
- package/dist/upgrade.d.ts +5 -1
- package/dist/upgrade.js +284 -95
- package/package.json +2 -2
- package/skills/fde-engagement/references/playbook.md +1 -1
package/README.md
CHANGED
|
@@ -47,13 +47,22 @@ Run inside an agent project. Checks, with pass/warn/fail per line:
|
|
|
47
47
|
|
|
48
48
|
Exit code 1 on any failure — usable as a CI preflight.
|
|
49
49
|
|
|
50
|
-
## `kyb upgrade [--skip-eval]`
|
|
50
|
+
## `kyb upgrade [--skip-eval] [--yes]`
|
|
51
51
|
|
|
52
52
|
Compares installed `@kybernesis/*` versions against npm, installs what's
|
|
53
53
|
behind, typechecks, then **runs the eval suite as the gate** — prints
|
|
54
54
|
"deploy" only on green. This is the maintenance-retainer loop as a command:
|
|
55
55
|
package improvement → `kyb upgrade` per client → green → `npx eve deploy`.
|
|
56
56
|
|
|
57
|
+
Before an eve version change, upgrade reads `.eve/.workflow-data/runs`, prints
|
|
58
|
+
`This will reset N open conversations`, lists matching persisted Buzz channel
|
|
59
|
+
sessions when available, and requires explicit confirmation. Noninteractive
|
|
60
|
+
runs fail closed unless `--yes` (or `-y`) is supplied. The warning is still printed with
|
|
61
|
+
`--yes` and is not bypassed by `--skip-eval`. Existing direct `eve eval` scripts
|
|
62
|
+
are migrated to `kyb-eval`; custom scripts are left untouched with a manual fix.
|
|
63
|
+
The wrapper counts corruption diagnostics beside the eval result and turns a
|
|
64
|
+
nominally green eve exit into a failure when condemned durable state was seen.
|
|
65
|
+
|
|
57
66
|
On self-hosted agents, upgrade also reconciles package-owned host artifacts. It
|
|
58
67
|
refreshes a stale `/etc/cron.daily/kyb-docker-prune` and an existing generated
|
|
59
68
|
systemd unit, warning before replacement and printing an exact manual repair
|
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* kyb deploy put this repo on its host and restart it, with proof
|
|
12
12
|
* kyb upgrade bump @kybernesis/* to latest, gated on the eval suite
|
|
13
13
|
* --skip-eval skip the eval gate (not for production changes)
|
|
14
|
+
* --yes, -y confirm an eve upgrade noninteractively
|
|
14
15
|
*/
|
|
15
16
|
import { existsSync, readFileSync } from "node:fs";
|
|
16
17
|
import { basename, dirname, join } from "node:path";
|
|
@@ -73,7 +74,7 @@ const COMMANDS = {
|
|
|
73
74
|
credential: "Write the agent credential onto a host (--local to stay here).",
|
|
74
75
|
register: "Register this agent with the control plane (--name, --url).",
|
|
75
76
|
deploy: "Deploy this agent to its host (--no-env to leave the env file alone).",
|
|
76
|
-
upgrade: "Bring @kybernesis packages and eve to
|
|
77
|
+
upgrade: "Bring @kybernesis packages and eve to certified versions (--skip-eval, --yes).",
|
|
77
78
|
tui: "Talk to your agents in the terminal.",
|
|
78
79
|
version: "Print the version of this tool.",
|
|
79
80
|
};
|
|
@@ -144,7 +145,10 @@ switch (command) {
|
|
|
144
145
|
tui(rest);
|
|
145
146
|
break;
|
|
146
147
|
case "upgrade":
|
|
147
|
-
await upgrade(
|
|
148
|
+
await upgrade({
|
|
149
|
+
skipEval: rest.includes("--skip-eval"),
|
|
150
|
+
yes: rest.includes("--yes") || rest.includes("-y"),
|
|
151
|
+
});
|
|
148
152
|
break;
|
|
149
153
|
case "--version":
|
|
150
154
|
case "-v":
|
|
@@ -209,6 +213,7 @@ ${dim(" npm i -g @kybernesis/create@latest")}
|
|
|
209
213
|
--no-env ${dim("do not send .env.local (host manages its own secrets)")}
|
|
210
214
|
${bold("kyb upgrade")} bump @kybernesis/* packages, gated on evals
|
|
211
215
|
--skip-eval ${dim("skip the eval gate")}
|
|
216
|
+
--yes, -y ${dim("confirm an eve version change noninteractively")}
|
|
212
217
|
|
|
213
218
|
Registry: https://registry.kybernesis.ai · Docs: the FDE playbook
|
|
214
219
|
`);
|
package/dist/templates.js
CHANGED
|
@@ -207,7 +207,7 @@ export function evalScript(name, depts) {
|
|
|
207
207
|
`ARCANA_DM_WORKSPACE=${name}-eval`,
|
|
208
208
|
...depts.map((d) => `ARCANA_${d.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_WORKSPACE=${name}-eval`),
|
|
209
209
|
];
|
|
210
|
-
return `${overrides.join(" ")}
|
|
210
|
+
return `${overrides.join(" ")} kyb-eval`;
|
|
211
211
|
}
|
|
212
212
|
export const CHANNEL_KINDS = [
|
|
213
213
|
"none",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
export interface DurableRunInspection {
|
|
3
|
+
runningRunIds: string[];
|
|
4
|
+
inspectedRecords: number;
|
|
5
|
+
issues: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface BuzzSessionMatch {
|
|
8
|
+
runId: string;
|
|
9
|
+
community: string;
|
|
10
|
+
channel: string;
|
|
11
|
+
}
|
|
12
|
+
export interface BuzzSessionInspection {
|
|
13
|
+
path?: string;
|
|
14
|
+
matches: BuzzSessionMatch[];
|
|
15
|
+
issue?: string;
|
|
16
|
+
}
|
|
17
|
+
export type EvalScriptReconciliation = {
|
|
18
|
+
kind: "updated";
|
|
19
|
+
script: string;
|
|
20
|
+
} | {
|
|
21
|
+
kind: "current";
|
|
22
|
+
script: string;
|
|
23
|
+
} | {
|
|
24
|
+
kind: "custom";
|
|
25
|
+
script: string;
|
|
26
|
+
} | {
|
|
27
|
+
kind: "missing";
|
|
28
|
+
};
|
|
29
|
+
/** Read the local durable records without loading eve or mutating its store. */
|
|
30
|
+
export declare function inspectDurableRuns(cwd: string): DurableRunInspection;
|
|
31
|
+
/** Follow the same precedence as buzzBridge when enough environment is available. */
|
|
32
|
+
export declare function buzzSessionsPath(cwd: string, env: Record<string, string | undefined>): string | undefined;
|
|
33
|
+
/** Best-effort join from durable run ids to Buzz's persisted community|channel map. */
|
|
34
|
+
export declare function inspectBuzzSessions(cwd: string, env: Record<string, string | undefined>, runningRunIds: readonly string[]): BuzzSessionInspection;
|
|
35
|
+
/** Replace only a simple direct eve eval invocation, preserving assignments and arguments. */
|
|
36
|
+
export declare function reconcileEvalScript(script: unknown): EvalScriptReconciliation;
|
|
37
|
+
export declare function confirmEveUpgrade(options: {
|
|
38
|
+
yes: boolean;
|
|
39
|
+
input?: Readable & {
|
|
40
|
+
isTTY?: boolean;
|
|
41
|
+
};
|
|
42
|
+
output?: Writable;
|
|
43
|
+
}): Promise<boolean>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
4
|
+
/** Read the local durable records without loading eve or mutating its store. */
|
|
5
|
+
export function inspectDurableRuns(cwd) {
|
|
6
|
+
const runsDir = join(cwd, ".eve", ".workflow-data", "runs");
|
|
7
|
+
if (!existsSync(runsDir))
|
|
8
|
+
return { runningRunIds: [], inspectedRecords: 0, issues: [] };
|
|
9
|
+
let entries;
|
|
10
|
+
try {
|
|
11
|
+
entries = readdirSync(runsDir, { withFileTypes: true });
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
return {
|
|
15
|
+
runningRunIds: [],
|
|
16
|
+
inspectedRecords: 0,
|
|
17
|
+
issues: [`could not read ${runsDir}: ${error.message}`],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const runningRunIds = [];
|
|
21
|
+
const issues = [];
|
|
22
|
+
let inspectedRecords = 0;
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
if (!entry.isFile() || !entry.name.endsWith(".json"))
|
|
25
|
+
continue;
|
|
26
|
+
const path = join(runsDir, entry.name);
|
|
27
|
+
try {
|
|
28
|
+
const record = JSON.parse(readFileSync(path, "utf8"));
|
|
29
|
+
inspectedRecords += 1;
|
|
30
|
+
if (record.status !== "running")
|
|
31
|
+
continue;
|
|
32
|
+
if (typeof record.runId !== "string" || record.runId.length === 0) {
|
|
33
|
+
issues.push(`${path}: running record has no string runId`);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
runningRunIds.push(record.runId);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
issues.push(`${path}: ${error.message}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { runningRunIds, inspectedRecords, issues };
|
|
43
|
+
}
|
|
44
|
+
function resolveEnvPath(cwd, value) {
|
|
45
|
+
return isAbsolute(value) ? value : resolve(cwd, value);
|
|
46
|
+
}
|
|
47
|
+
/** Follow the same precedence as buzzBridge when enough environment is available. */
|
|
48
|
+
export function buzzSessionsPath(cwd, env) {
|
|
49
|
+
if (env.BUZZ_SESSIONS_FILE)
|
|
50
|
+
return resolveEnvPath(cwd, env.BUZZ_SESSIONS_FILE);
|
|
51
|
+
const legacy = join(cwd, ".buzz-sessions.json");
|
|
52
|
+
if (existsSync(legacy))
|
|
53
|
+
return legacy;
|
|
54
|
+
if (env.BUZZ_KEYFILE)
|
|
55
|
+
return join(dirname(resolveEnvPath(cwd, env.BUZZ_KEYFILE)), "buzz-sessions.json");
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
/** Best-effort join from durable run ids to Buzz's persisted community|channel map. */
|
|
59
|
+
export function inspectBuzzSessions(cwd, env, runningRunIds) {
|
|
60
|
+
const path = buzzSessionsPath(cwd, env);
|
|
61
|
+
if (!path) {
|
|
62
|
+
return { matches: [], issue: "no BUZZ_SESSIONS_FILE, legacy .buzz-sessions.json, or BUZZ_KEYFILE was found" };
|
|
63
|
+
}
|
|
64
|
+
if (!existsSync(path))
|
|
65
|
+
return { path, matches: [], issue: `${path} does not exist` };
|
|
66
|
+
try {
|
|
67
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
68
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
69
|
+
throw new Error("expected an object map");
|
|
70
|
+
const running = new Set(runningRunIds);
|
|
71
|
+
const matches = [];
|
|
72
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
73
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
74
|
+
continue;
|
|
75
|
+
const runId = value.id;
|
|
76
|
+
if (typeof runId !== "string" || !running.has(runId))
|
|
77
|
+
continue;
|
|
78
|
+
const separator = key.lastIndexOf("|");
|
|
79
|
+
matches.push({
|
|
80
|
+
runId,
|
|
81
|
+
community: separator < 0 ? "unknown community" : key.slice(0, separator),
|
|
82
|
+
channel: separator < 0 ? key : key.slice(separator + 1),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return { path, matches };
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
return { path, matches: [], issue: `could not read Buzz session metadata at ${path}: ${error.message}` };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** Replace only a simple direct eve eval invocation, preserving assignments and arguments. */
|
|
92
|
+
export function reconcileEvalScript(script) {
|
|
93
|
+
if (typeof script !== "string" || script.trim().length === 0)
|
|
94
|
+
return { kind: "missing" };
|
|
95
|
+
if (/^\s*(?:(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|[^\s]+))\s+)*kyb-eval(?:\s|$)/.test(script)) {
|
|
96
|
+
return { kind: "current", script };
|
|
97
|
+
}
|
|
98
|
+
if (/[;&|<>`]/.test(script))
|
|
99
|
+
return { kind: "custom", script };
|
|
100
|
+
const direct = /^(\s*(?:(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|[^\s]+))\s+)*)eve\s+eval(?=\s|$)(.*)$/.exec(script);
|
|
101
|
+
if (!direct)
|
|
102
|
+
return { kind: "custom", script };
|
|
103
|
+
return { kind: "updated", script: `${direct[1]}kyb-eval${direct[2]}` };
|
|
104
|
+
}
|
|
105
|
+
export async function confirmEveUpgrade(options) {
|
|
106
|
+
if (options.yes)
|
|
107
|
+
return true;
|
|
108
|
+
const input = options.input ?? process.stdin;
|
|
109
|
+
const output = options.output ?? process.stdout;
|
|
110
|
+
if (input.isTTY !== true)
|
|
111
|
+
return false;
|
|
112
|
+
const rl = createInterface({ input, output });
|
|
113
|
+
try {
|
|
114
|
+
const answer = (await rl.question("Continue with the eve upgrade? Type yes to continue: ")).trim().toLowerCase();
|
|
115
|
+
return answer === "yes" || answer === "y";
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
rl.close();
|
|
122
|
+
}
|
|
123
|
+
}
|
package/dist/upgrade.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import { type AgentInputLimit } from "./agent-limits.js";
|
|
2
2
|
export declare function agentInputLimitUpgradeMessage(limit: AgentInputLimit): string;
|
|
3
|
-
export
|
|
3
|
+
export interface UpgradeOptions {
|
|
4
|
+
skipEval?: boolean;
|
|
5
|
+
yes?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function upgrade(options?: UpgradeOptions): Promise<void>;
|
package/dist/upgrade.js
CHANGED
|
@@ -1,31 +1,14 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { upsertEnv } from "./envfile.js";
|
|
4
5
|
import { reconcileHostArtifact } from "./host-artifacts.js";
|
|
5
|
-
import { repairManageRestart } from "./systemd.js";
|
|
6
|
+
import { findMatchingAgentServiceUnit, repairManageRestart } from "./systemd.js";
|
|
6
7
|
import { repairTerminalSandboxCleanupHooks } from "./sandbox-cleanup.js";
|
|
7
8
|
import { repairRemovedDefaultTools as repairRemovedDefaultTools_ } from "./removed-default-tools.js";
|
|
8
9
|
import { EVE_VERSION, bold, capture, dim, green, parseEnv, red, run, yellow } from "./util.js";
|
|
9
10
|
import { inspectEveAgent } from "./agent-limits.js";
|
|
10
|
-
|
|
11
|
-
* Which packages to upgrade: every `@kybernesis/*` this agent depends on.
|
|
12
|
-
*
|
|
13
|
-
* @remarks
|
|
14
|
-
* This was a fixed list of six, written when there were six. Four more shipped
|
|
15
|
-
* afterwards — connectors, local, manage and exe — and an agent using them was
|
|
16
|
-
* told "everything is at latest certified versions" while holding versions from
|
|
17
|
-
* months earlier. A hardcoded list does not fail loudly when it falls behind;
|
|
18
|
-
* it just quietly stops covering things, and the command that reports it is the
|
|
19
|
-
* same one that is wrong.
|
|
20
|
-
*
|
|
21
|
-
* Reading the manifest cannot fall behind. A package added tomorrow is covered
|
|
22
|
-
* by an upgrade run today.
|
|
23
|
-
*/
|
|
24
|
-
function kybernesisPackages(deps) {
|
|
25
|
-
return Object.keys(deps)
|
|
26
|
-
.filter((name) => name.startsWith("@kybernesis/"))
|
|
27
|
-
.sort();
|
|
28
|
-
}
|
|
11
|
+
import { confirmEveUpgrade, inspectBuzzSessions, inspectDurableRuns, reconcileEvalScript, } from "./upgrade-sessions.js";
|
|
29
12
|
function versionLt(a, b) {
|
|
30
13
|
const pa = a.split(".").map(Number);
|
|
31
14
|
const pb = b.split(".").map(Number);
|
|
@@ -330,113 +313,319 @@ function repairHostArtifacts(cwd, deps) {
|
|
|
330
313
|
manualCommand: `bash ${shellQuote(serviceScript)} --refresh-unit`,
|
|
331
314
|
});
|
|
332
315
|
}
|
|
333
|
-
|
|
316
|
+
function repairEvalCommand(cwd) {
|
|
317
|
+
const packagePath = join(cwd, "package.json");
|
|
318
|
+
const pkg = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
319
|
+
const result = reconcileEvalScript(pkg.scripts?.eval);
|
|
320
|
+
if (result.kind === "updated") {
|
|
321
|
+
pkg.scripts = { ...pkg.scripts, eval: result.script };
|
|
322
|
+
writeFileSync(packagePath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
323
|
+
console.log(` ${green("+")} updated scripts.eval to run through kyb-eval`);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (result.kind === "current")
|
|
327
|
+
return;
|
|
328
|
+
const current = result.kind === "custom" ? result.script : "(missing)";
|
|
329
|
+
console.log(` ${yellow("!")} scripts.eval is not a recognized direct eve eval command, so it was left unchanged.\n` +
|
|
330
|
+
` Manual remediation: make this command invoke kyb-eval instead of eve eval, preserving its setup and arguments.\n` +
|
|
331
|
+
` Current scripts.eval: ${dim(JSON.stringify(current))}`);
|
|
332
|
+
}
|
|
333
|
+
function upgradeEnvironment(cwd) {
|
|
334
|
+
const envPath = join(cwd, ".env.local");
|
|
335
|
+
const env = {
|
|
336
|
+
...(existsSync(envPath) ? parseEnv(readFileSync(envPath, "utf8")) : {}),
|
|
337
|
+
...process.env,
|
|
338
|
+
};
|
|
339
|
+
if (!env.BUZZ_SESSIONS_FILE && !env.BUZZ_KEYFILE) {
|
|
340
|
+
const unit = capture("sh", [
|
|
341
|
+
"-c",
|
|
342
|
+
"grep -ohE 'BUZZ_(SESSIONS_FILE|KEYFILE)=[^ \" ]+' /etc/systemd/system/*buzz-bridge.service 2>/dev/null | head -1",
|
|
343
|
+
]);
|
|
344
|
+
const [name, ...value] = (unit ?? "").trim().split("=");
|
|
345
|
+
if (name && value.length > 0)
|
|
346
|
+
env[name] = value.join("=").replace(/^\"|\"$/g, "");
|
|
347
|
+
}
|
|
348
|
+
return env;
|
|
349
|
+
}
|
|
350
|
+
async function approveEveChange(cwd, yes, hasBuzz) {
|
|
351
|
+
const durable = inspectDurableRuns(cwd);
|
|
352
|
+
console.log(bold(`\nThis will reset ${durable.runningRunIds.length} open conversations`));
|
|
353
|
+
if (durable.issues.length > 0) {
|
|
354
|
+
console.log(` ${yellow("!")} Durable-store inspection was incomplete; the count includes only readable records ` +
|
|
355
|
+
`whose status is exactly "running".`);
|
|
356
|
+
for (const issue of durable.issues)
|
|
357
|
+
console.log(` ${dim(issue)}`);
|
|
358
|
+
}
|
|
359
|
+
if (hasBuzz) {
|
|
360
|
+
const buzz = inspectBuzzSessions(cwd, upgradeEnvironment(cwd), durable.runningRunIds);
|
|
361
|
+
if (buzz.matches.length > 0) {
|
|
362
|
+
console.log(" Buzz-bound open conversations:");
|
|
363
|
+
for (const match of buzz.matches) {
|
|
364
|
+
console.log(` ${match.runId} ${dim(`(${match.community} | ${match.channel})`)}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
else if (buzz.issue) {
|
|
368
|
+
console.log(` ${yellow("!")} Buzz session metadata unavailable: ${buzz.issue}`);
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
console.log(` ${dim("No open durable conversations were matched to Buzz session metadata.")}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const approved = await confirmEveUpgrade({ yes });
|
|
375
|
+
if (!approved) {
|
|
376
|
+
console.log(`\n${red("Eve upgrade cancelled before installation.")} ` +
|
|
377
|
+
`${dim("Pass --yes for deliberate noninteractive use.")}\n`);
|
|
378
|
+
}
|
|
379
|
+
return approved;
|
|
380
|
+
}
|
|
381
|
+
function commandResult(command, args, cwd, print = true) {
|
|
382
|
+
console.log(dim(` $ ${command} ${args.join(" ")}`));
|
|
383
|
+
const result = spawnSync(command, args, { cwd, encoding: "utf8", env: process.env });
|
|
384
|
+
const stdout = result.stdout ?? "";
|
|
385
|
+
const stderr = result.stderr ?? "";
|
|
386
|
+
if (print) {
|
|
387
|
+
if (stdout)
|
|
388
|
+
process.stdout.write(stdout);
|
|
389
|
+
if (stderr)
|
|
390
|
+
process.stderr.write(stderr);
|
|
391
|
+
}
|
|
392
|
+
return { status: result.status ?? 1, stdout, stderr };
|
|
393
|
+
}
|
|
394
|
+
function fail(message) {
|
|
395
|
+
console.error(red(message));
|
|
396
|
+
process.exitCode = 1;
|
|
397
|
+
}
|
|
398
|
+
function majorMinor(version) {
|
|
399
|
+
const match = /^(\d+)\.(\d+)(?:\.|$)/.exec(version);
|
|
400
|
+
if (!match)
|
|
401
|
+
return null;
|
|
402
|
+
const major = Number(match[1]);
|
|
403
|
+
const minor = Number(match[2]);
|
|
404
|
+
return Number.isSafeInteger(major) && Number.isSafeInteger(minor) ? [major, minor] : null;
|
|
405
|
+
}
|
|
406
|
+
function installedPackageValue(cwd, name, expression) {
|
|
407
|
+
const packageName = JSON.stringify(`${name}/package.json`);
|
|
408
|
+
return capture("node", ["-p", `require(${packageName})${expression}`], cwd)?.trim() || null;
|
|
409
|
+
}
|
|
410
|
+
function targetEvePeer(name, version) {
|
|
411
|
+
return capture("npm", ["view", `${name}@${version}`, "peerDependencies.eve"])?.trim() || undefined;
|
|
412
|
+
}
|
|
413
|
+
function dependencyEntries(pkg) {
|
|
414
|
+
const entries = [];
|
|
415
|
+
for (const section of ["dependencies", "devDependencies"]) {
|
|
416
|
+
const values = pkg[section];
|
|
417
|
+
for (const [name, range] of Object.entries(values ?? {})) {
|
|
418
|
+
if (name === "eve" || name.startsWith("@kybernesis/"))
|
|
419
|
+
entries.push({ name, section, range });
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return entries.sort((a, b) => a.name.localeCompare(b.name) || a.section.localeCompare(b.section));
|
|
423
|
+
}
|
|
424
|
+
function rewriteManifest(cwd, pkg, targets) {
|
|
425
|
+
for (const target of targets) {
|
|
426
|
+
const section = pkg[target.section];
|
|
427
|
+
section[target.name] = `^${target.targetVersion}`;
|
|
428
|
+
}
|
|
429
|
+
writeFileSync(join(cwd, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`);
|
|
430
|
+
}
|
|
431
|
+
function recoveryGuidance(targets, bridgeUnit) {
|
|
432
|
+
console.log(`\n${yellow("The dependency tree was not verified. Complete the clean install before using it:")}\n` +
|
|
433
|
+
` 1. Ensure every root @kybernesis/* and eve range in package.json is the resolved ^version.\n` +
|
|
434
|
+
` 2. ${dim("rm -rf node_modules")}\n` +
|
|
435
|
+
` 3. ${dim("rm -f package-lock.json")}\n` +
|
|
436
|
+
` 4. ${dim("npm install")}\n` +
|
|
437
|
+
` 5. ${dim("npm ls eve")}`);
|
|
438
|
+
const peerChanges = targets.filter((target) => target.name.startsWith("@kybernesis/") &&
|
|
439
|
+
target.installedEvePeer !== target.targetEvePeer &&
|
|
440
|
+
(target.installedEvePeer !== undefined || target.targetEvePeer !== undefined));
|
|
441
|
+
if (peerChanges.length > 0) {
|
|
442
|
+
console.log("\n Published Eve peer metadata changed for these root packages:");
|
|
443
|
+
for (const target of peerChanges) {
|
|
444
|
+
console.log(` ${target.name}: ${target.installedVersion} peers on ${target.installedEvePeer ?? "(none)"}; ` +
|
|
445
|
+
`${target.targetVersion} peers on ${target.targetEvePeer ?? "(none)"}`);
|
|
446
|
+
}
|
|
447
|
+
console.log(dim(" These are metadata facts, not an attribution of npm's reported conflict."));
|
|
448
|
+
}
|
|
449
|
+
if (bridgeUnit) {
|
|
450
|
+
console.log(`\n The Buzz bridge remains stopped until the tree is verified. Then run:\n ${dim(`sudo -n systemctl start ${bridgeUnit}`)}`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function buzzBridgeUnit(cwd) {
|
|
454
|
+
const systemdDir = process.env.KYB_SYSTEMD_DIR;
|
|
455
|
+
const match = findMatchingAgentServiceUnit(cwd, systemdDir || undefined);
|
|
456
|
+
return match ? `${match.values.name}-buzz-bridge.service` : null;
|
|
457
|
+
}
|
|
458
|
+
function stopActiveBridge(cwd, unit) {
|
|
459
|
+
if (!unit)
|
|
460
|
+
return null;
|
|
461
|
+
const active = commandResult("systemctl", ["is-active", "--quiet", unit], cwd, false);
|
|
462
|
+
if (active.status !== 0)
|
|
463
|
+
return null;
|
|
464
|
+
const stopped = commandResult("sudo", ["-n", "systemctl", "stop", unit], cwd);
|
|
465
|
+
if (stopped.status === 0)
|
|
466
|
+
return unit;
|
|
467
|
+
fail(`Could not stop the active Buzz bridge. Run exactly:\n sudo -n systemctl stop ${unit}`);
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
function startStoppedBridge(cwd, unit) {
|
|
471
|
+
const started = commandResult("sudo", ["-n", "systemctl", "start", unit], cwd);
|
|
472
|
+
if (started.status === 0)
|
|
473
|
+
return true;
|
|
474
|
+
fail(`Could not restart the Buzz bridge. Run exactly:\n` +
|
|
475
|
+
` sudo -n systemctl start ${unit}\n` +
|
|
476
|
+
` systemctl status ${unit}`);
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
export async function upgrade(options = {}) {
|
|
334
480
|
const cwd = process.cwd();
|
|
335
|
-
const
|
|
336
|
-
const
|
|
481
|
+
const packagePath = join(cwd, "package.json");
|
|
482
|
+
const pkg = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
483
|
+
const deps = {
|
|
484
|
+
...(pkg.dependencies ?? {}),
|
|
485
|
+
...(pkg.devDependencies ?? {}),
|
|
486
|
+
};
|
|
337
487
|
console.log(bold("\nkyb upgrade — checking @kybernesis/* and eve against npm\n"));
|
|
338
488
|
warnIfStale();
|
|
339
|
-
// Read-only: report the compiled effective policy, never rewrite agent/agent.ts.
|
|
340
489
|
reportAgentInputLimit(cwd);
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
490
|
+
const entries = dependencyEntries(pkg);
|
|
491
|
+
const installedVersions = new Map();
|
|
492
|
+
const targetVersions = new Map();
|
|
493
|
+
const installedPeers = new Map();
|
|
494
|
+
const targetPeers = new Map();
|
|
495
|
+
const unresolved = new Set();
|
|
496
|
+
for (const name of [...new Set(entries.map((entry) => entry.name))]) {
|
|
497
|
+
const installed = installedPackageValue(cwd, name, ".version");
|
|
498
|
+
if (!installed) {
|
|
499
|
+
console.log(` ${yellow("!")} ${name}: could not resolve installed version`);
|
|
500
|
+
unresolved.add(name);
|
|
347
501
|
continue;
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
502
|
+
}
|
|
503
|
+
installedVersions.set(name, installed);
|
|
504
|
+
const target = name === "eve" ? EVE_VERSION : capture("npm", ["view", name, "version"])?.trim();
|
|
505
|
+
if (!target) {
|
|
506
|
+
console.log(` ${yellow("!")} ${name}: could not resolve target version`);
|
|
507
|
+
unresolved.add(name);
|
|
353
508
|
continue;
|
|
354
509
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
toUpgrade.push(`${name}@${latest}`);
|
|
510
|
+
targetVersions.set(name, target);
|
|
511
|
+
if (name.startsWith("@kybernesis/")) {
|
|
512
|
+
installedPeers.set(name, installedPackageValue(cwd, name, ".peerDependencies?.eve || ''") ?? undefined);
|
|
513
|
+
targetPeers.set(name, targetEvePeer(name, target));
|
|
360
514
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
// npm latest. Certification happens in the platform repo — packages are
|
|
364
|
-
// tested against a new eve, then the pin in @kybernesis/create advances,
|
|
365
|
-
// then this command carries client agents there behind their eval gate.
|
|
366
|
-
let eveChanged = false;
|
|
367
|
-
const eveInstalled = capture("node", ["-p", "require('eve/package.json').version"], cwd)?.trim();
|
|
368
|
-
const eveLatest = capture("npm", ["view", "eve", "version"])?.trim();
|
|
369
|
-
if (eveInstalled) {
|
|
370
|
-
if (versionLt(eveInstalled, EVE_VERSION)) {
|
|
371
|
-
console.log(` ${yellow("↑")} eve: ${eveInstalled} → ${EVE_VERSION} ${dim("(Kybernesis-certified)")}`);
|
|
372
|
-
toUpgrade.push(`eve@${EVE_VERSION}`);
|
|
373
|
-
eveChanged = true;
|
|
515
|
+
if (installed === target) {
|
|
516
|
+
console.log(` ${green("✓")} ${name}@${installed} ${dim(name === "eve" ? "(certified)" : "(latest)")}`);
|
|
374
517
|
}
|
|
375
|
-
else if (versionLt(EVE_VERSION,
|
|
376
|
-
console.log(` ${yellow("!")} eve@${
|
|
518
|
+
else if (name === "eve" && versionLt(EVE_VERSION, installed)) {
|
|
519
|
+
console.log(` ${yellow("!")} eve@${installed} is AHEAD of the certified ${EVE_VERSION} ${dim("— unsupported territory")}`);
|
|
377
520
|
}
|
|
378
521
|
else {
|
|
379
|
-
console.log(` ${
|
|
380
|
-
|
|
381
|
-
if (eveLatest && versionLt(EVE_VERSION, eveLatest)) {
|
|
382
|
-
console.log(dim(` note: eve@${eveLatest} exists upstream; ${EVE_VERSION} is the newest Kybernesis-certified version.`));
|
|
522
|
+
console.log(` ${yellow("↑")} ${name}: ${installed} → ${target}` +
|
|
523
|
+
(name === "eve" ? ` ${dim("(Kybernesis-certified)")}` : ""));
|
|
383
524
|
}
|
|
384
525
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
`
|
|
393
|
-
repairManageRestart(cwd, deps);
|
|
526
|
+
const eveInstalled = installedVersions.get("eve");
|
|
527
|
+
const eveLatest = capture("npm", ["view", "eve", "version"])?.trim();
|
|
528
|
+
if (eveLatest && versionLt(EVE_VERSION, eveLatest)) {
|
|
529
|
+
console.log(dim(` note: eve@${eveLatest} exists upstream; ${EVE_VERSION} is the newest Kybernesis-certified version.`));
|
|
530
|
+
}
|
|
531
|
+
if (unresolved.size > 0) {
|
|
532
|
+
fail(`\nUpgrade planning failed for ${[...unresolved].sort().join(", ")}. ` +
|
|
533
|
+
`No manifest, dependency tree, or service state was changed.`);
|
|
394
534
|
return;
|
|
395
535
|
}
|
|
396
|
-
|
|
536
|
+
const targets = entries.map((entry) => ({
|
|
537
|
+
name: entry.name,
|
|
538
|
+
section: entry.section,
|
|
539
|
+
currentRange: entry.range,
|
|
540
|
+
installedVersion: installedVersions.get(entry.name),
|
|
541
|
+
targetVersion: targetVersions.get(entry.name),
|
|
542
|
+
installedEvePeer: installedPeers.get(entry.name),
|
|
543
|
+
targetEvePeer: targetPeers.get(entry.name),
|
|
544
|
+
}));
|
|
545
|
+
const eveTarget = targetVersions.get("eve");
|
|
546
|
+
const installedMajorMinor = eveInstalled ? majorMinor(eveInstalled) : null;
|
|
547
|
+
const targetMajorMinor = eveTarget ? majorMinor(eveTarget) : null;
|
|
548
|
+
if ((eveInstalled && !installedMajorMinor) || (eveTarget && !targetMajorMinor)) {
|
|
549
|
+
fail(`Cannot safely compare Eve versions ${JSON.stringify(eveInstalled)} and ${JSON.stringify(eveTarget)}.`);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
const eveChanged = Boolean(eveInstalled && eveTarget && versionLt(eveInstalled, eveTarget));
|
|
553
|
+
const cleanInstall = Boolean(eveChanged &&
|
|
554
|
+
installedMajorMinor &&
|
|
555
|
+
targetMajorMinor &&
|
|
556
|
+
(installedMajorMinor[0] !== targetMajorMinor[0] || installedMajorMinor[1] !== targetMajorMinor[1]));
|
|
557
|
+
const changed = [...new Set(targets
|
|
558
|
+
.filter((target) => target.installedVersion !== target.targetVersion && !(target.name === "eve" && versionLt(target.targetVersion, target.installedVersion)))
|
|
559
|
+
.map((target) => `${target.name}@${target.targetVersion}`))];
|
|
560
|
+
if (eveChanged && !(await approveEveChange(cwd, options.yes === true, Boolean(deps["@kybernesis/buzz"])))) {
|
|
561
|
+
process.exitCode = 1;
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
repairLocalQueueTimeouts(cwd, deps);
|
|
565
|
+
if (changed.length === 0) {
|
|
397
566
|
console.log(`\n${green("Everything is at latest certified versions.")}\n`);
|
|
398
|
-
// Still repair: being on the right versions is not the same as being set
|
|
399
|
-
// up. An agent can sit at latest for weeks with a capability switched off.
|
|
400
567
|
repairBuzzSetup(cwd, deps);
|
|
401
568
|
repairRemovedDefaultTools(cwd);
|
|
402
569
|
repairSandboxCleanupHooks(cwd, deps);
|
|
403
570
|
repairHostArtifacts(cwd, deps);
|
|
571
|
+
repairEvalCommand(cwd);
|
|
404
572
|
repairManageRestart(cwd, deps);
|
|
405
573
|
return;
|
|
406
574
|
}
|
|
407
|
-
console.log(bold(`\nInstalling: ${
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
575
|
+
console.log(bold(`\nInstalling: ${changed.join(", ")}\n`));
|
|
576
|
+
let stoppedBridge = null;
|
|
577
|
+
if (cleanInstall && deps["@kybernesis/buzz"]) {
|
|
578
|
+
const stopped = stopActiveBridge(cwd, buzzBridgeUnit(cwd));
|
|
579
|
+
if (stopped === false)
|
|
580
|
+
return;
|
|
581
|
+
stoppedBridge = stopped;
|
|
582
|
+
}
|
|
583
|
+
if (cleanInstall) {
|
|
584
|
+
rewriteManifest(cwd, pkg, targets);
|
|
585
|
+
try {
|
|
586
|
+
rmSync(join(cwd, "node_modules"), { recursive: true, force: true });
|
|
587
|
+
rmSync(join(cwd, "package-lock.json"), { force: true });
|
|
588
|
+
}
|
|
589
|
+
catch (error) {
|
|
590
|
+
fail(`Could not remove npm resolver state: ${error.message}`);
|
|
591
|
+
recoveryGuidance(targets, stoppedBridge ?? undefined);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
const installArgs = cleanInstall ? ["install"] : ["install", ...changed];
|
|
596
|
+
const installed = commandResult("npm", installArgs, cwd);
|
|
597
|
+
if (installed.status !== 0) {
|
|
598
|
+
const output = `${installed.stdout}\n${installed.stderr}`;
|
|
599
|
+
if (/ERESOLVE/i.test(output)) {
|
|
600
|
+
console.log(red("\nnpm reported ERESOLVE while installing the planned dependency set."));
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
console.log(red("\nnpm install failed before the dependency tree could be verified."));
|
|
604
|
+
}
|
|
605
|
+
recoveryGuidance(targets, stoppedBridge ?? undefined);
|
|
606
|
+
process.exitCode = 1;
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const validation = commandResult("npm", ["ls", "eve"], cwd);
|
|
610
|
+
if (validation.status !== 0) {
|
|
611
|
+
fail("npm ls eve failed. The installed peer tree is not valid.");
|
|
612
|
+
recoveryGuidance(targets, stoppedBridge ?? undefined);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
if (stoppedBridge && !startStoppedBridge(cwd, stoppedBridge))
|
|
616
|
+
return;
|
|
428
617
|
repairBuzzSetup(cwd, deps);
|
|
429
618
|
repairRemovedDefaultTools(cwd);
|
|
430
619
|
repairSandboxCleanupHooks(cwd, deps);
|
|
431
620
|
repairHostArtifacts(cwd, deps);
|
|
621
|
+
repairEvalCommand(cwd);
|
|
432
622
|
repairManageRestart(cwd, deps);
|
|
433
623
|
run("npm", ["run", "typecheck"], { cwd });
|
|
434
624
|
if (eveChanged) {
|
|
435
|
-
// A framework bump must also pass discovery/compile, not just types.
|
|
436
625
|
const infoOk = run("npx", ["eve", "info"], { cwd, allowFail: true, quiet: true });
|
|
437
626
|
console.log(infoOk ? green(" ✓ eve discovery clean after framework upgrade") : red(" ✗ eve info failed after framework upgrade — inspect before going further"));
|
|
438
627
|
}
|
|
439
|
-
if (skipEval) {
|
|
628
|
+
if (options.skipEval) {
|
|
440
629
|
console.log(yellow("\nEval gate SKIPPED (--skip-eval). Run `npm run eval` before deploying.\n"));
|
|
441
630
|
return;
|
|
442
631
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kybernesis/create",
|
|
3
|
-
"version": "0.14.
|
|
4
|
-
"description": "The Kybernesis agent scaffolder and FDE toolkit: one command to a governed, remembering, multiplayer, self-testing eve agent
|
|
3
|
+
"version": "0.14.5",
|
|
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",
|
|
7
7
|
"homepage": "https://kybernesis.ai",
|
|
@@ -1229,7 +1229,7 @@ to `acme-eval` and evals never write into a real brain:
|
|
|
1229
1229
|
|
|
1230
1230
|
```jsonc
|
|
1231
1231
|
"scripts": {
|
|
1232
|
-
"eval": "ARCANA_COMPANY_WORKSPACE=acme-eval ARCANA_DM_WORKSPACE=acme-eval ARCANA_FINANCE_WORKSPACE=acme-eval ARCANA_ENGINEERING_WORKSPACE=acme-eval
|
|
1232
|
+
"eval": "ARCANA_COMPANY_WORKSPACE=acme-eval ARCANA_DM_WORKSPACE=acme-eval ARCANA_FINANCE_WORKSPACE=acme-eval ARCANA_ENGINEERING_WORKSPACE=acme-eval kyb-eval",
|
|
1233
1233
|
"typecheck": "tsc"
|
|
1234
1234
|
}
|
|
1235
1235
|
```
|