@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 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 the certified versions (--skip-eval).",
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(rest.includes("--skip-eval"));
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 = capture("npx", ["eve", "info"], cwd, env);
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 = /Diagnostics\s+(\d+) errors?, (\d+) warnings?/.exec(info);
490
- if (diag && diag[1] === "0")
491
- add("pass", `eve discovery clean (${diag[2]} warnings)`);
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[1]} errors` : "unparsed"}`, "npx eve info");
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(" ")} eve eval`;
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
- export declare function upgrade(skipEval: boolean): Promise<void>;
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
- * Which packages to upgrade: every `@kybernesis/*` this agent depends on.
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
- export async function upgrade(skipEval) {
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 pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
309
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
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
- // Env-only, so it is safe before anything is installed.
313
- repairLocalQueueTimeouts(cwd, deps);
314
- const toUpgrade = [];
315
- const unresolved = [];
316
- for (const name of kybernesisPackages(deps)) {
317
- if (!deps[name])
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
- const installed = capture("node", ["-p", `require('${name}/package.json').version`], cwd)?.trim();
320
- const latest = capture("npm", ["view", name, "version"])?.trim();
321
- if (!installed || !latest) {
322
- console.log(` ${yellow("!")} ${name}: could not resolve versions`);
323
- unresolved.push(name);
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
- if (installed === latest)
327
- console.log(` ${green("✓")} ${name}@${installed} ${dim("(latest)")}`);
328
- else {
329
- console.log(` ${yellow("↑")} ${name}: ${installed} → ${latest}`);
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
- // eve itself: upgrade to the KYBERNESIS-CERTIFIED version, never blindly to
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, eveInstalled)) {
347
- console.log(` ${yellow("!")} eve@${eveInstalled} is AHEAD of the certified ${EVE_VERSION} ${dim("— unsupported territory")}`);
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(` ${green("✓")} eve@${eveInstalled} ${dim("(certified)")}`);
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
- if (toUpgrade.length === 0 && unresolved.length > 0) {
357
- // Saying everything is current, having just failed to check several
358
- // packages, is the worst available answer: it is the sentence someone
359
- // repeats to a client. Usually the dependencies are simply not installed
360
- // here, which is worth naming rather than hiding behind a green tick.
361
- console.log(`\n${yellow(`Checked what could be read. ${unresolved.length} package(s) could not be ` +
362
- `resolved, so this is not a clean bill of health.`)}\n` +
363
- ` ${dim("Usually: dependencies are not installed here. Run npm install, then kyb upgrade.")}\n`);
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
- if (toUpgrade.length === 0) {
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: ${toUpgrade.join(", ")}\n`));
379
- /**
380
- * One install, with peers relaxed. Every @kybernesis package peers on a
381
- * narrow eve range, and eve moves with them: the installed set demands the
382
- * old eve, the new set demands the new one, and npm's resolver refuses
383
- * either order — eve first, packages first, or all at once — under strict
384
- * peers (seen on Kyber moving 0.38 → 0.49: ERESOLVE all three ways). The
385
- * set being installed is exactly the certified combination, so relaxing
386
- * peer resolution for this one command changes nothing about what ends up
387
- * on disk; the version check above is what guards correctness.
388
- */
389
- run("npm", ["install", ...toUpgrade, "--legacy-peer-deps"], { cwd });
390
- /**
391
- * Repairs run AFTER the install, not before.
392
- *
393
- * Both of these copy files out of packages that the install has just put
394
- * there — a proxy script, a cron job, a CLI. Running them first meant looking
395
- * for a file the older installed version did not ship: the repair found
396
- * nothing, said nothing, and only worked on the NEXT upgrade. Which is a
397
- * bug that hides itself, because by then it looks like it always worked.
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.2",
4
- "description": "The Kybernesis agent scaffolder and FDE toolkit: one command to a governed, remembering, multiplayer, self-testing eve agent — plus doctor and upgrade.",
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 eve 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
  ```