@kybernesis/create 0.14.2 → 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/agent-limits.d.ts +30 -0
- package/dist/agent-limits.js +86 -0
- package/dist/cli.js +7 -2
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +30 -6
- package/dist/templates.js +3 -1
- package/dist/upgrade-sessions.d.ts +43 -0
- package/dist/upgrade-sessions.js +123 -0
- package/dist/upgrade.d.ts +7 -1
- package/dist/upgrade.js +313 -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
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Eve 0.49's root default when the compiled config omits the policy. */
|
|
2
|
+
export declare const CERTIFIED_INHERITED_MAX_INPUT_TOKENS_PER_SESSION = 40000000;
|
|
3
|
+
export type AgentInputLimit = {
|
|
4
|
+
kind: "explicit-numeric";
|
|
5
|
+
value: number;
|
|
6
|
+
} | {
|
|
7
|
+
kind: "explicit-uncapped";
|
|
8
|
+
} | {
|
|
9
|
+
kind: "inherited";
|
|
10
|
+
value: typeof CERTIFIED_INHERITED_MAX_INPUT_TOKENS_PER_SESSION;
|
|
11
|
+
} | {
|
|
12
|
+
kind: "unresolved";
|
|
13
|
+
reason: string;
|
|
14
|
+
};
|
|
15
|
+
export interface EveInfoInspection {
|
|
16
|
+
diagnostics: {
|
|
17
|
+
errors: number;
|
|
18
|
+
warnings: number;
|
|
19
|
+
} | null;
|
|
20
|
+
status: string | null;
|
|
21
|
+
limit: AgentInputLimit;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Read the effective root policy from Eve's compiled artifact, never from
|
|
25
|
+
* authored TypeScript. The 40M fallback is certified only when that artifact
|
|
26
|
+
* truly omits maxInputTokensPerSession.
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseEveInfoInspection(output: string, readText?: (path: string) => string): EveInfoInspection;
|
|
29
|
+
/** Compile once through Eve's structured info contract and inspect its artifact. */
|
|
30
|
+
export declare function inspectEveAgent(cwd: string, env?: Record<string, string>): EveInfoInspection | null;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { capture } from "./util.js";
|
|
3
|
+
/** Eve 0.49's root default when the compiled config omits the policy. */
|
|
4
|
+
export const CERTIFIED_INHERITED_MAX_INPUT_TOKENS_PER_SESSION = 40_000_000;
|
|
5
|
+
/**
|
|
6
|
+
* Read the effective root policy from Eve's compiled artifact, never from
|
|
7
|
+
* authored TypeScript. The 40M fallback is certified only when that artifact
|
|
8
|
+
* truly omits maxInputTokensPerSession.
|
|
9
|
+
*/
|
|
10
|
+
export function parseEveInfoInspection(output, readText = (path) => readFileSync(path, "utf8")) {
|
|
11
|
+
let info;
|
|
12
|
+
try {
|
|
13
|
+
info = JSON.parse(output);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
return unresolved(`eve info --json was not valid JSON: ${error.message}`);
|
|
17
|
+
}
|
|
18
|
+
const diagnostics = parseDiagnostics(info.diagnostics);
|
|
19
|
+
const status = typeof info.status === "string" ? info.status : null;
|
|
20
|
+
const manifestPath = info.artifacts && typeof info.artifacts.compiledManifest === "string"
|
|
21
|
+
? info.artifacts.compiledManifest
|
|
22
|
+
: null;
|
|
23
|
+
if (!manifestPath) {
|
|
24
|
+
return { diagnostics, status, limit: { kind: "unresolved", reason: "compiled manifest path was unavailable" } };
|
|
25
|
+
}
|
|
26
|
+
let manifest;
|
|
27
|
+
try {
|
|
28
|
+
manifest = JSON.parse(readText(manifestPath));
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
return {
|
|
32
|
+
diagnostics,
|
|
33
|
+
status,
|
|
34
|
+
limit: { kind: "unresolved", reason: `compiled manifest could not be read: ${error.message}` },
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (!isRecord(manifest) || !isRecord(manifest.config)) {
|
|
38
|
+
return { diagnostics, status, limit: { kind: "unresolved", reason: "compiled root config was unavailable" } };
|
|
39
|
+
}
|
|
40
|
+
const limits = manifest.config.limits;
|
|
41
|
+
if (limits === undefined) {
|
|
42
|
+
return inherited(diagnostics, status);
|
|
43
|
+
}
|
|
44
|
+
if (!isRecord(limits)) {
|
|
45
|
+
return { diagnostics, status, limit: { kind: "unresolved", reason: "compiled root limits were malformed" } };
|
|
46
|
+
}
|
|
47
|
+
if (!Object.hasOwn(limits, "maxInputTokensPerSession")) {
|
|
48
|
+
return inherited(diagnostics, status);
|
|
49
|
+
}
|
|
50
|
+
const value = limits.maxInputTokensPerSession;
|
|
51
|
+
if (value === false)
|
|
52
|
+
return { diagnostics, status, limit: { kind: "explicit-uncapped" } };
|
|
53
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
54
|
+
return { diagnostics, status, limit: { kind: "explicit-numeric", value } };
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
diagnostics,
|
|
58
|
+
status,
|
|
59
|
+
limit: { kind: "unresolved", reason: "compiled maxInputTokensPerSession was neither a positive number nor false" },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** Compile once through Eve's structured info contract and inspect its artifact. */
|
|
63
|
+
export function inspectEveAgent(cwd, env) {
|
|
64
|
+
const output = capture("npx", ["eve", "info", "--json"], cwd, env);
|
|
65
|
+
return output === null ? null : parseEveInfoInspection(output);
|
|
66
|
+
}
|
|
67
|
+
function parseDiagnostics(value) {
|
|
68
|
+
if (!isRecord(value))
|
|
69
|
+
return null;
|
|
70
|
+
return typeof value.errors === "number" && typeof value.warnings === "number"
|
|
71
|
+
? { errors: value.errors, warnings: value.warnings }
|
|
72
|
+
: null;
|
|
73
|
+
}
|
|
74
|
+
function inherited(diagnostics, status) {
|
|
75
|
+
return {
|
|
76
|
+
diagnostics,
|
|
77
|
+
status,
|
|
78
|
+
limit: { kind: "inherited", value: CERTIFIED_INHERITED_MAX_INPUT_TOKENS_PER_SESSION },
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function unresolved(reason) {
|
|
82
|
+
return { diagnostics: null, status: null, limit: { kind: "unresolved", reason } };
|
|
83
|
+
}
|
|
84
|
+
function isRecord(value) {
|
|
85
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
86
|
+
}
|
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/doctor.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AgentInputLimit } from "./agent-limits.js";
|
|
1
2
|
type Verdict = "pass" | "warn" | "fail";
|
|
2
3
|
export interface Check {
|
|
3
4
|
verdict: Verdict;
|
|
@@ -16,6 +17,7 @@ export interface DockerTemplateInspection {
|
|
|
16
17
|
}[];
|
|
17
18
|
}
|
|
18
19
|
export declare function dockerTemplateDoctorChecks(result: DockerTemplateInspection): Check[];
|
|
20
|
+
export declare function agentInputLimitDoctorCheck(limit: AgentInputLimit): Check;
|
|
19
21
|
/**
|
|
20
22
|
* The inspector lives in @kybernesis/exe, next to the runtime that owns the
|
|
21
23
|
* templates; create already resolves that package's scripts out of
|
package/dist/doctor.js
CHANGED
|
@@ -3,6 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
import { bold, capture, dim, green, parseEnv, red, yellow } from "./util.js";
|
|
5
5
|
import { diagnoseManageRestart, findMatchingAgentServiceUnit } from "./systemd.js";
|
|
6
|
+
import { inspectEveAgent } from "./agent-limits.js";
|
|
6
7
|
const MARK = {
|
|
7
8
|
pass: green("✓"),
|
|
8
9
|
warn: yellow("!"),
|
|
@@ -30,6 +31,26 @@ export function dockerTemplateDoctorChecks(result) {
|
|
|
30
31
|
detail: issue.detail,
|
|
31
32
|
}));
|
|
32
33
|
}
|
|
34
|
+
export function agentInputLimitDoctorCheck(limit) {
|
|
35
|
+
switch (limit.kind) {
|
|
36
|
+
case "explicit-numeric":
|
|
37
|
+
return { verdict: "pass", label: `Eve max input tokens/session: ${limit.value.toLocaleString("en-US")} (explicit)` };
|
|
38
|
+
case "explicit-uncapped":
|
|
39
|
+
return { verdict: "pass", label: "Eve max input tokens/session: uncapped (explicit)" };
|
|
40
|
+
case "inherited":
|
|
41
|
+
return {
|
|
42
|
+
verdict: "warn",
|
|
43
|
+
label: `Eve max input tokens/session: ${limit.value.toLocaleString("en-US")} (inherited default)`,
|
|
44
|
+
detail: "set limits.maxInputTokensPerSession explicitly in the root agent; use false only when the host policy intentionally permits an uncapped session",
|
|
45
|
+
};
|
|
46
|
+
case "unresolved":
|
|
47
|
+
return {
|
|
48
|
+
verdict: "warn",
|
|
49
|
+
label: "Eve max input tokens/session: unresolved",
|
|
50
|
+
detail: limit.reason,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
33
54
|
/**
|
|
34
55
|
* The inspector lives in @kybernesis/exe, next to the runtime that owns the
|
|
35
56
|
* templates; create already resolves that package's scripts out of
|
|
@@ -481,16 +502,19 @@ export async function doctor() {
|
|
|
481
502
|
* `env` here is the same merged view every other check reads: .env.local
|
|
482
503
|
* underneath, the real environment on top.
|
|
483
504
|
*/
|
|
484
|
-
const info =
|
|
505
|
+
const info = inspectEveAgent(cwd, env);
|
|
485
506
|
if (info === null) {
|
|
486
|
-
add("fail", "eve info failed", "run: set -a && . ./.env.local && set +a && npx eve info — the agent's own error is in that output");
|
|
507
|
+
add("fail", "eve info failed", "run: set -a && . ./.env.local && set +a && npx eve info --json — the agent's own error is in that output");
|
|
508
|
+
add("warn", "Eve max input tokens/session: unresolved", "eve info failed before the compiled root policy could be inspected");
|
|
487
509
|
}
|
|
488
510
|
else {
|
|
489
|
-
const diag =
|
|
490
|
-
if (diag && diag
|
|
491
|
-
add("pass", `eve discovery clean (${diag
|
|
511
|
+
const diag = info.diagnostics;
|
|
512
|
+
if (diag && diag.errors === 0)
|
|
513
|
+
add("pass", `eve discovery clean (${diag.warnings} warnings)`);
|
|
492
514
|
else
|
|
493
|
-
add("fail", `eve discovery: ${diag ? `${diag
|
|
515
|
+
add("fail", `eve discovery: ${diag ? `${diag.errors} errors` : "unparsed"}`, "npx eve info --json");
|
|
516
|
+
const limitCheck = agentInputLimitDoctorCheck(info.limit);
|
|
517
|
+
add(limitCheck.verdict, limitCheck.label, limitCheck.detail);
|
|
494
518
|
}
|
|
495
519
|
const portBusy = capture("lsof", ["-ti", ":2000"]);
|
|
496
520
|
if (portBusy && portBusy.trim())
|
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",
|
|
@@ -366,6 +366,8 @@ import { exeModel } from "@kybernesis/exe";
|
|
|
366
366
|
export default defineAgent({
|
|
367
367
|
model: exeModel({ model: process.env.EXE_MODEL ?? ${JSON.stringify(model)}, createOpenAI }),
|
|
368
368
|
modelContextWindowTokens: 200_000,
|
|
369
|
+
// This long-lived self-hosted process has no metered platform session ceiling.
|
|
370
|
+
limits: { maxInputTokensPerSession: false },
|
|
369
371
|
});
|
|
370
372
|
`;
|
|
371
373
|
}
|
|
@@ -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 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import { type AgentInputLimit } from "./agent-limits.js";
|
|
2
|
+
export declare function agentInputLimitUpgradeMessage(limit: AgentInputLimit): string;
|
|
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,30 +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
|
-
import { EVE_VERSION, bold, capture, dim, green, red, run, yellow } from "./util.js";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*
|
|
12
|
-
* @remarks
|
|
13
|
-
* This was a fixed list of six, written when there were six. Four more shipped
|
|
14
|
-
* afterwards — connectors, local, manage and exe — and an agent using them was
|
|
15
|
-
* told "everything is at latest certified versions" while holding versions from
|
|
16
|
-
* months earlier. A hardcoded list does not fail loudly when it falls behind;
|
|
17
|
-
* it just quietly stops covering things, and the command that reports it is the
|
|
18
|
-
* same one that is wrong.
|
|
19
|
-
*
|
|
20
|
-
* Reading the manifest cannot fall behind. A package added tomorrow is covered
|
|
21
|
-
* by an upgrade run today.
|
|
22
|
-
*/
|
|
23
|
-
function kybernesisPackages(deps) {
|
|
24
|
-
return Object.keys(deps)
|
|
25
|
-
.filter((name) => name.startsWith("@kybernesis/"))
|
|
26
|
-
.sort();
|
|
27
|
-
}
|
|
9
|
+
import { EVE_VERSION, bold, capture, dim, green, parseEnv, red, run, yellow } from "./util.js";
|
|
10
|
+
import { inspectEveAgent } from "./agent-limits.js";
|
|
11
|
+
import { confirmEveUpgrade, inspectBuzzSessions, inspectDurableRuns, reconcileEvalScript, } from "./upgrade-sessions.js";
|
|
28
12
|
function versionLt(a, b) {
|
|
29
13
|
const pa = a.split(".").map(Number);
|
|
30
14
|
const pb = b.split(".").map(Number);
|
|
@@ -61,6 +45,32 @@ function warnIfStale() {
|
|
|
61
45
|
`compiled into this tool, so an old kyb reports an old pin as current.`);
|
|
62
46
|
console.log(` ${dim("npm install -g @kybernesis/create@latest")}\n`);
|
|
63
47
|
}
|
|
48
|
+
export function agentInputLimitUpgradeMessage(limit) {
|
|
49
|
+
switch (limit.kind) {
|
|
50
|
+
case "explicit-numeric":
|
|
51
|
+
return `Eve max input tokens/session: ${limit.value.toLocaleString("en-US")} (explicit; unchanged)`;
|
|
52
|
+
case "explicit-uncapped":
|
|
53
|
+
return "Eve max input tokens/session: uncapped (explicit; unchanged)";
|
|
54
|
+
case "inherited":
|
|
55
|
+
return `Eve max input tokens/session: ${limit.value.toLocaleString("en-US")} inherited — set limits.maxInputTokensPerSession explicitly in agent/agent.ts; upgrade will not rewrite authored source`;
|
|
56
|
+
case "unresolved":
|
|
57
|
+
return `Eve max input tokens/session: unresolved (${limit.reason}); authored source was not changed`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function reportAgentInputLimit(cwd) {
|
|
61
|
+
const envPath = join(cwd, ".env.local");
|
|
62
|
+
const env = {
|
|
63
|
+
...(existsSync(envPath) ? parseEnv(readFileSync(envPath, "utf8")) : {}),
|
|
64
|
+
...process.env,
|
|
65
|
+
};
|
|
66
|
+
const inspection = inspectEveAgent(cwd, env);
|
|
67
|
+
const limit = inspection?.limit ?? {
|
|
68
|
+
kind: "unresolved",
|
|
69
|
+
reason: "eve info --json failed",
|
|
70
|
+
};
|
|
71
|
+
const marker = limit.kind === "inherited" || limit.kind === "unresolved" ? yellow("!") : green("✓");
|
|
72
|
+
console.log(` ${marker} ${agentInputLimitUpgradeMessage(limit)}`);
|
|
73
|
+
}
|
|
64
74
|
/**
|
|
65
75
|
* Raise the local queue's delivery timeouts on an agent that already exists.
|
|
66
76
|
*
|
|
@@ -303,111 +313,319 @@ function repairHostArtifacts(cwd, deps) {
|
|
|
303
313
|
manualCommand: `bash ${shellQuote(serviceScript)} --refresh-unit`,
|
|
304
314
|
});
|
|
305
315
|
}
|
|
306
|
-
|
|
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 = {}) {
|
|
307
480
|
const cwd = process.cwd();
|
|
308
|
-
const
|
|
309
|
-
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
|
+
};
|
|
310
487
|
console.log(bold("\nkyb upgrade — checking @kybernesis/* and eve against npm\n"));
|
|
311
488
|
warnIfStale();
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
489
|
+
reportAgentInputLimit(cwd);
|
|
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);
|
|
318
501
|
continue;
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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);
|
|
324
508
|
continue;
|
|
325
509
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
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));
|
|
331
514
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
// npm latest. Certification happens in the platform repo — packages are
|
|
335
|
-
// tested against a new eve, then the pin in @kybernesis/create advances,
|
|
336
|
-
// then this command carries client agents there behind their eval gate.
|
|
337
|
-
let eveChanged = false;
|
|
338
|
-
const eveInstalled = capture("node", ["-p", "require('eve/package.json').version"], cwd)?.trim();
|
|
339
|
-
const eveLatest = capture("npm", ["view", "eve", "version"])?.trim();
|
|
340
|
-
if (eveInstalled) {
|
|
341
|
-
if (versionLt(eveInstalled, EVE_VERSION)) {
|
|
342
|
-
console.log(` ${yellow("↑")} eve: ${eveInstalled} → ${EVE_VERSION} ${dim("(Kybernesis-certified)")}`);
|
|
343
|
-
toUpgrade.push(`eve@${EVE_VERSION}`);
|
|
344
|
-
eveChanged = true;
|
|
515
|
+
if (installed === target) {
|
|
516
|
+
console.log(` ${green("✓")} ${name}@${installed} ${dim(name === "eve" ? "(certified)" : "(latest)")}`);
|
|
345
517
|
}
|
|
346
|
-
else if (versionLt(EVE_VERSION,
|
|
347
|
-
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")}`);
|
|
348
520
|
}
|
|
349
521
|
else {
|
|
350
|
-
console.log(` ${
|
|
351
|
-
|
|
352
|
-
if (eveLatest && versionLt(EVE_VERSION, eveLatest)) {
|
|
353
|
-
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)")}` : ""));
|
|
354
524
|
}
|
|
355
525
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
`
|
|
364
|
-
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.`);
|
|
365
534
|
return;
|
|
366
535
|
}
|
|
367
|
-
|
|
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) {
|
|
368
566
|
console.log(`\n${green("Everything is at latest certified versions.")}\n`);
|
|
369
|
-
// Still repair: being on the right versions is not the same as being set
|
|
370
|
-
// up. An agent can sit at latest for weeks with a capability switched off.
|
|
371
567
|
repairBuzzSetup(cwd, deps);
|
|
372
568
|
repairRemovedDefaultTools(cwd);
|
|
373
569
|
repairSandboxCleanupHooks(cwd, deps);
|
|
374
570
|
repairHostArtifacts(cwd, deps);
|
|
571
|
+
repairEvalCommand(cwd);
|
|
375
572
|
repairManageRestart(cwd, deps);
|
|
376
573
|
return;
|
|
377
574
|
}
|
|
378
|
-
console.log(bold(`\nInstalling: ${
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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;
|
|
399
617
|
repairBuzzSetup(cwd, deps);
|
|
400
618
|
repairRemovedDefaultTools(cwd);
|
|
401
619
|
repairSandboxCleanupHooks(cwd, deps);
|
|
402
620
|
repairHostArtifacts(cwd, deps);
|
|
621
|
+
repairEvalCommand(cwd);
|
|
403
622
|
repairManageRestart(cwd, deps);
|
|
404
623
|
run("npm", ["run", "typecheck"], { cwd });
|
|
405
624
|
if (eveChanged) {
|
|
406
|
-
// A framework bump must also pass discovery/compile, not just types.
|
|
407
625
|
const infoOk = run("npx", ["eve", "info"], { cwd, allowFail: true, quiet: true });
|
|
408
626
|
console.log(infoOk ? green(" ✓ eve discovery clean after framework upgrade") : red(" ✗ eve info failed after framework upgrade — inspect before going further"));
|
|
409
627
|
}
|
|
410
|
-
if (skipEval) {
|
|
628
|
+
if (options.skipEval) {
|
|
411
629
|
console.log(yellow("\nEval gate SKIPPED (--skip-eval). Run `npm run eval` before deploying.\n"));
|
|
412
630
|
return;
|
|
413
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
|
```
|