@kybernesis/create 0.15.8 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -97,7 +97,7 @@ Before an eve version change, upgrade reads `.eve/.workflow-data/runs`, prints
97
97
  sessions when available, and requires explicit confirmation. Noninteractive
98
98
  runs fail closed unless `--yes` (or `-y`) is supplied. The warning is still printed with
99
99
  `--yes` and is not bypassed by `--skip-eval`. Existing direct `eve eval` scripts
100
- are migrated to `kyb-eval`; custom scripts are left untouched with a manual fix.
100
+ are migrated to `kyb-eval` (shipped as a binary of this package from 0.17); custom scripts are left untouched with a manual fix.
101
101
  The wrapper counts corruption diagnostics beside the eval result and turns a
102
102
  nominally green eve exit into a failure when condemned durable state was seen.
103
103
  It also identifies failures from `judge.autoevals.*` assertions as judge
@@ -10,7 +10,10 @@ export const CERTIFIED_INHERITED_MAX_INPUT_TOKENS_PER_SESSION = 40_000_000;
10
10
  export function parseEveInfoInspection(output, readText = (path) => readFileSync(path, "utf8")) {
11
11
  let info;
12
12
  try {
13
- info = JSON.parse(output);
13
+ // eve ≥0.62 prints its terminal header ("☰eve v0.68.0 …") ahead of the
14
+ // JSON even with --json, so parse from the first object literal.
15
+ const start = output.indexOf("{");
16
+ info = JSON.parse(start > 0 ? output.slice(start) : output);
14
17
  }
15
18
  catch (error) {
16
19
  return unresolved(`eve info --json was not valid JSON: ${error.message}`);
package/dist/doctor.js CHANGED
@@ -313,7 +313,7 @@ export async function doctor() {
313
313
  const vercelConn = join(cwd, "agent/connections/vercel.ts");
314
314
  const selfHostedAgent = Boolean(deps["@kybernesis/exe"]) ||
315
315
  (existsSync(join(cwd, "agent/subagents/builder/sandbox/sandbox.ts")) &&
316
- readFileSync(join(cwd, "agent/subagents/builder/sandbox/sandbox.ts"), "utf8").includes("docker("));
316
+ /docker\(|DockerSandbox/.test(readFileSync(join(cwd, "agent/subagents/builder/sandbox/sandbox.ts"), "utf8")));
317
317
  if (selfHostedAgent && !existsSync(vercelConn)) {
318
318
  add("pass", "no Vercel MCP connection (self-hosted)", "public deploys need the CLIENT's own Vercel token — Vercel Connect does not work off-Vercel");
319
319
  }
@@ -380,12 +380,26 @@ export async function doctor() {
380
380
  add("warn", "@kybernesis/dispatch installed but no edges or dispatch channel found", "see the connect-agents skill");
381
381
  }
382
382
  }
383
+ // ── .agent identity (ARP) ──────────────────────────────────────────────
384
+ // The owner connects the agent from the ARP console; the only thing that
385
+ // has to be true in this repo is that deliveries get in.
386
+ if (deps["@kybernesis/identity"]) {
387
+ if (!existsSync(join(cwd, "agent/channels/arp.ts")))
388
+ add("warn", "identity: agent/channels/arp.ts missing", "npx eve add @kybernesis/identity (serves /eve/v1/arp for Connect your agent)");
389
+ if (eveChannelSrc && !eveChannelSrc.includes("arpAuth("))
390
+ add("fail", "identity: eve channel has no arpAuth()", "add arpAuth() to the channel's auth list — without it every delivery from a paired agent is a 401");
391
+ else if (eveChannelSrc)
392
+ add("pass", "identity: eve channel accepts ARP deliveries (arpAuth)");
393
+ }
394
+ else {
395
+ add("warn", "no .agent identity add-on", "npx eve add @kybernesis/identity — lets the owner connect this agent to a .agent name from the console");
396
+ }
383
397
  // ── self-hosted agents (host !== Vercel) ───────────────────────────────
384
398
  // Every check here cost a real debugging session on the first exe.dev
385
399
  // deployment. None of them are theoretical.
386
400
  const selfHosted = Boolean(deps["@kybernesis/exe"]) ||
387
401
  existsSync(join(cwd, "agent/sandbox/sandbox.ts")) &&
388
- readFileSync(join(cwd, "agent/sandbox/sandbox.ts"), "utf8").includes("docker(");
402
+ /docker\(|DockerSandbox/.test(readFileSync(join(cwd, "agent/sandbox/sandbox.ts"), "utf8"));
389
403
  const templateInspection = await inspectDockerTemplatesViaExe(cwd);
390
404
  for (const check of templateInspection ? dockerTemplateDoctorChecks(templateInspection) : []) {
391
405
  add(check.verdict, check.label, check.detail);
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `kyb-eval`: `eve eval` with the two checks eve's own exit code does not make.
4
+ *
5
+ * `kyb upgrade` migrates every agent's `scripts.eval` to this command and runs
6
+ * it as the deploy gate, and the README has described it since 0.14 — but no
7
+ * binary ever shipped, so a migrated agent's `npm run eval` failed with
8
+ * "command not found" and the gate reported red for the wrong reason.
9
+ *
10
+ * What it adds on top of `eve eval <args>`:
11
+ * - a nominally green run that logged condemned durable state
12
+ * (`CORRUPTED_EVENT_LOG`, `REPLAY_DIVERGENCE`) exits 1 — those runs are
13
+ * terminal and a suite that passed beside them proves less than it says;
14
+ * - a run whose failures are all judge errors (evaluation model unreachable,
15
+ * HTTP failures from the judge provider) says so, rather than reading as the
16
+ * agent under test failing.
17
+ */
18
+ import { spawn } from "node:child_process";
19
+ const CONDEMNED = /CORRUPTED_EVENT_LOG|REPLAY_DIVERGENCE/;
20
+ const JUDGE_FAILURE = /judge[^\n]*(unreachable|ECONNREFUSED|fetch failed|HTTP \d{3}|Unauthenticated|no credentials|not valid)/i;
21
+ function main() {
22
+ const args = process.argv.slice(2);
23
+ const child = spawn("npx", ["eve", "eval", ...args], { stdio: ["inherit", "pipe", "pipe"], env: process.env });
24
+ let condemned = 0;
25
+ let judgeFailures = 0;
26
+ const scan = (chunk, out) => {
27
+ const text = chunk.toString();
28
+ out.write(text);
29
+ for (const line of text.split("\n")) {
30
+ if (CONDEMNED.test(line))
31
+ condemned += 1;
32
+ if (JUDGE_FAILURE.test(line))
33
+ judgeFailures += 1;
34
+ }
35
+ };
36
+ child.stdout.on("data", (chunk) => scan(chunk, process.stdout));
37
+ child.stderr.on("data", (chunk) => scan(chunk, process.stderr));
38
+ child.on("error", (error) => {
39
+ console.error(`kyb-eval: could not start eve eval: ${error.message}`);
40
+ process.exit(2);
41
+ });
42
+ child.on("close", (code) => {
43
+ if (condemned > 0) {
44
+ console.error(`\nkyb-eval: ${condemned} condemned durable-state diagnostic(s) seen (CORRUPTED_EVENT_LOG / REPLAY_DIVERGENCE). ` +
45
+ "Those conversations are terminal; treating this run as red.");
46
+ process.exit(1);
47
+ }
48
+ if (code !== 0 && judgeFailures > 0) {
49
+ console.error(`\nkyb-eval: ${judgeFailures} judge failure(s) seen — the evaluation model was unreachable or refused, ` +
50
+ "which is a judge-reachability problem, not evidence that the agent under test failed. Fix the judge, then rerun.");
51
+ }
52
+ process.exit(code ?? 1);
53
+ });
54
+ }
55
+ main();
package/dist/init.d.ts CHANGED
@@ -39,3 +39,9 @@ export interface InitOptions {
39
39
  yes?: boolean;
40
40
  }
41
41
  export declare function init(rawName: string | undefined, options?: InitOptions): Promise<void>;
42
+ /**
43
+ * Put `arpAuth()` first in the eve channel's auth walk so ARP Cloud deliveries
44
+ * are accepted. Returns the new source, the same source when already wired,
45
+ * or null when the file has no recognisable auth list.
46
+ */
47
+ export declare function wireArpAuth(src: string): string | null;
package/dist/init.js CHANGED
@@ -13,7 +13,9 @@ import { MODEL_REACH_ENV, resolveModelScaffold } from "./model-reach.js";
13
13
  * host bindings — is opt-in, because assuming them means the FDE deletes files
14
14
  * AND undoes real setup work (an Arcana workspace + scoped key per subagent).
15
15
  */
16
- const CORE_ITEMS = ["enterprise", "arcana", "evals"];
16
+ // `identity` gives the agent its .agent name surface: the owner connects it
17
+ // from the ARP console with one click and never touches this repo again.
18
+ const CORE_ITEMS = ["enterprise", "arcana", "evals", "identity"];
17
19
  // Official eve-registry limbs installed with the engineer subagent.
18
20
  // connection/vercel is Vercel-Connect-backed, so it is VERCEL-HOST ONLY: on a
19
21
  // self-hosted agent it cannot get an OIDC token and the agent fails to boot.
@@ -194,6 +196,20 @@ export async function init(rawName, options = {}) {
194
196
  }
195
197
  console.log(bold("\n3/6 Writing identity, model config, memory mount, and evals …"));
196
198
  mkdirSync(join(dir, "agent/instructions"), { recursive: true });
199
+ // .agent identity: the channel + peers tool came from the registry item;
200
+ // deliveries need arpAuth() on the eve channel, and the model needs to know
201
+ // what the ask_<name>_agent tools are. Both are idempotent.
202
+ writeFileSync(join(dir, "agent/instructions/arp.md"), arpInstructionsMd());
203
+ {
204
+ const eveChannelPath = join(dir, "agent/channels/eve.ts");
205
+ if (existsSync(eveChannelPath)) {
206
+ const wired = wireArpAuth(readFileSync(eveChannelPath, "utf8"));
207
+ if (wired)
208
+ writeFileSync(eveChannelPath, wired);
209
+ else
210
+ console.log(yellow(" ! agent/channels/eve.ts: could not add arpAuth() — add it to the channel's auth list by hand"));
211
+ }
212
+ }
197
213
  writeFileSync(join(dir, "agent/instructions/identity.md"), identityMd(displayName, depts));
198
214
  try {
199
215
  unlinkSync(join(dir, "agent/instructions.md"));
@@ -398,3 +414,35 @@ ${steps.map((s, i) => ` ${i + 1}. ${s}`).join("\n")}
398
414
  ${dim(" Add more later: kyb add channel <kind> · kyb add subagent <name>")}
399
415
  `);
400
416
  }
417
+ /**
418
+ * Put `arpAuth()` first in the eve channel's auth walk so ARP Cloud deliveries
419
+ * are accepted. Returns the new source, the same source when already wired,
420
+ * or null when the file has no recognisable auth list.
421
+ */
422
+ export function wireArpAuth(src) {
423
+ if (src.includes("arpAuth("))
424
+ return src;
425
+ const lines = src.split("\n");
426
+ const lastImport = lines.reduce((acc, l, i) => (l.startsWith("import ") ? i : acc), -1);
427
+ lines.splice(lastImport + 1, 0, 'import { arpAuth } from "@kybernesis/identity";');
428
+ let out = lines.join("\n");
429
+ const comment = "// Deliveries from paired .agent peers: a per-message token ARP Cloud signs,\n // verified offline against its JWKS; skips to the next entry otherwise.";
430
+ if (/extraAuth:\s*\[/.test(out))
431
+ return out.replace(/extraAuth:\s*\[/, `extraAuth: [\n ${comment}\n arpAuth(),`);
432
+ if (/\bauth:\s*\[/.test(out))
433
+ return out.replace(/\bauth:\s*\[/, `auth: [\n ${comment}\n arpAuth(),`);
434
+ return null;
435
+ }
436
+ function arpInstructionsMd() {
437
+ return `## Your .agent identity
438
+
439
+ You have a registered name on the agent network. Other agents that are paired
440
+ with you appear as \`ask_<name>_agent\` tools (the name is their .agent name; a
441
+ plain \`ask_<name>\` tool, if present, is a different, non-network peer); use
442
+ them when a request belongs to that agent rather than to you. When a message
443
+ arrives from a paired agent, the caller's identity has already been verified
444
+ and the message has already been checked against what its owner allowed —
445
+ answer it as you would a trusted colleague, and honour any obligations
446
+ attached (for example, do not include fields you were told to redact).
447
+ `;
448
+ }
@@ -27,3 +27,15 @@ export interface RemovedDefaultToolsRepair {
27
27
  optedIn: string[];
28
28
  }
29
29
  export declare function repairRemovedDefaultTools(cwd: string): RemovedDefaultToolsRepair;
30
+ /**
31
+ * eve 0.65 took `ask_question` out of the default tool set and made it an
32
+ * opt-in workflow tool (`eve add tool/ask_question`). An agent certified on an
33
+ * earlier line had it everywhere, and the notify hooks, the Buzz approval
34
+ * bridge and the Studio question cards all key on that tool name — so every
35
+ * scope that could ask keeps the tool, and a stale `disableTool()` file (a
36
+ * build error from 0.65) is removed.
37
+ */
38
+ export declare const OPT_IN_TOOLS_SINCE: {
39
+ readonly ask_question: "0.65.0";
40
+ };
41
+ export declare function repairOptInTools(cwd: string, eveVersion: string): RemovedDefaultToolsRepair;
@@ -84,3 +84,52 @@ export function repairRemovedDefaultTools(cwd) {
84
84
  }
85
85
  return { removed, optedIn };
86
86
  }
87
+ /**
88
+ * eve 0.65 took `ask_question` out of the default tool set and made it an
89
+ * opt-in workflow tool (`eve add tool/ask_question`). An agent certified on an
90
+ * earlier line had it everywhere, and the notify hooks, the Buzz approval
91
+ * bridge and the Studio question cards all key on that tool name — so every
92
+ * scope that could ask keeps the tool, and a stale `disableTool()` file (a
93
+ * build error from 0.65) is removed.
94
+ */
95
+ export const OPT_IN_TOOLS_SINCE = { ask_question: "0.65.0" };
96
+ function askQuestionSource() {
97
+ return [
98
+ "// eve 0.65 made ask_question an opt-in tool. This agent asked people questions",
99
+ "// on the certified earlier line, so it keeps the tool (kyb upgrade wrote this).",
100
+ 'import { askQuestion } from "eve/tools/ask_question";',
101
+ "",
102
+ "export default askQuestion();",
103
+ "",
104
+ ].join("\n");
105
+ }
106
+ export function repairOptInTools(cwd, eveVersion) {
107
+ const removed = [];
108
+ const optedIn = [];
109
+ if (versionAtLeast(eveVersion, OPT_IN_TOOLS_SINCE.ask_question)) {
110
+ for (const scope of agentScopes(cwd)) {
111
+ const tools = join(scope, "tools");
112
+ const file = join(tools, "ask_question.ts");
113
+ if (existsSync(file)) {
114
+ if (isDisableToolFile(file)) {
115
+ unlinkSync(file);
116
+ removed.push(relative(cwd, file));
117
+ }
118
+ continue;
119
+ }
120
+ mkdirSync(tools, { recursive: true });
121
+ writeFileSync(file, askQuestionSource());
122
+ optedIn.push(relative(cwd, file));
123
+ }
124
+ }
125
+ return { removed, optedIn };
126
+ }
127
+ function versionAtLeast(version, floor) {
128
+ const parse = (v) => v.replace(/^[^\d]*/, "").split("-")[0].split(".").map((n) => Number(n) || 0);
129
+ const a = parse(version), b = parse(floor);
130
+ for (let i = 0; i < 3; i++) {
131
+ if ((a[i] ?? 0) !== (b[i] ?? 0))
132
+ return (a[i] ?? 0) > (b[i] ?? 0);
133
+ }
134
+ return true;
135
+ }
package/dist/templates.js CHANGED
@@ -456,28 +456,26 @@ export function hostSteps(host, name, model) {
456
456
  function workshopSandbox(host) {
457
457
  if (host === "exe") {
458
458
  return `import { defineSandbox } from "eve/sandbox";
459
- import { docker } from "eve/sandbox/docker";
459
+ import { DockerSandbox } from "eve/sandbox/docker";
460
460
 
461
461
  /**
462
462
  * The engineer workshop, self-hosted: pnpm + Playwright + Chromium baked into
463
- * the TEMPLATE so warm sessions run render→screenshot→vision in seconds.
463
+ * the ENVIRONMENT (prepared once at build, inherited by every session) so warm
464
+ * sessions run render→screenshot→vision in seconds.
464
465
  *
465
- * Docker rather than vercel(): the hosted backend needs Vercel OIDC, which does
466
- * not exist off-Vercel.
466
+ * Docker rather than Vercel Sandbox: the hosted provider needs Vercel OIDC,
467
+ * which does not exist off-Vercel.
467
468
  *
468
469
  * HOST PREREQUISITE: some images ship Docker disabled (exe.dev's exeuntu runs
469
470
  * \`systemctl disable docker.service\`). Run \`sudo systemctl enable --now docker\`
470
471
  * or every build fails with SandboxTemplateNotProvisionedError.
471
472
  *
472
473
  * NOTE: Docker sessions do not enforce a domain allowlist the way the hosted
473
- * backend does. Egress control is the HOST's responsibility here — a deliberate
474
+ * provider does. Egress control is the HOST's responsibility here — a deliberate
474
475
  * difference from the Vercel deployment, not an oversight.
475
476
  */
476
- export default defineSandbox({
477
- backend: docker(),
478
- revalidationKey: () => "kybernesis-workshop-v5-docker",
479
- async bootstrap({ use }) {
480
- const sandbox = await use();
477
+ export const environment = DockerSandbox.environment({
478
+ prepare: async (sandbox) => {
481
479
  // Base image is Debian-family; refresh indexes before installing browser deps.
482
480
  await sandbox.run({ command: "apt-get update" });
483
481
  await sandbox.run({ command: "npm install -g pnpm" });
@@ -492,28 +490,49 @@ export default defineSandbox({
492
490
  });
493
491
  },
494
492
  });
493
+
494
+ export default defineSandbox(() => environment.open());
495
495
  `;
496
496
  }
497
497
  return `import { defineSandbox } from "eve/sandbox";
498
- import { vercel } from "eve/sandbox/vercel";
498
+ import { VercelSandbox } from "eve/sandbox/vercel";
499
499
 
500
500
  /**
501
501
  * The engineer workshop: a warm, safe cloud dev machine.
502
502
  *
503
- * TEMPLATE bootstrap (once, inherited by every session): pnpm + Playwright +
504
- * Chromium. Prewarm runs at deploy time, so a broken bootstrap fails the build
505
- * loudly and warm sessions run the full render→screenshot→vision loop in
506
- * seconds. Backend PINNED to Vercel Sandbox — hosted sandboxes even from local
507
- * dev (run \`vercel link\` + \`vercel env pull\` first), so evals exercise the
508
- * exact production backend. No Docker anywhere.
503
+ * ENVIRONMENT preparation (once at build, inherited by every session): pnpm +
504
+ * Playwright + Chromium. Preparation runs at deploy time, so a broken recipe
505
+ * fails the build loudly and warm sessions run the full render→screenshot→
506
+ * vision loop in seconds. Provider PINNED to Vercel Sandbox — hosted sandboxes
507
+ * even from local dev (run \`vercel link\` + \`vercel env pull\` first), so evals
508
+ * exercise the exact production provider. No Docker anywhere.
509
509
  *
510
510
  * All sessions run under a domain ALLOWLIST: an agent that installs arbitrary
511
511
  * npm packages must not have open egress. A blocked domain fails loudly;
512
512
  * treat every addition as a security decision.
513
513
  */
514
- export default defineSandbox({
515
- backend: vercel({
516
- resources: { vcpus: 4 },
514
+ export const environment = VercelSandbox.environment({
515
+ resources: { vcpus: 4 },
516
+ prepare: async (sandbox) => {
517
+ // The egress proxy carries HTTPS only; apt defaults to http:// mirrors, so
518
+ // every index fetch silently fails. Rewrite to https first.
519
+ await sandbox.run({
520
+ command:
521
+ "find /etc/apt -type f \\\\( -name '*.list' -o -name '*.sources' \\\\) -exec sed -i 's|http://|https://|g' {} + && apt-get update",
522
+ });
523
+ await sandbox.run({ command: "npm install -g pnpm" });
524
+ await sandbox.run({
525
+ command:
526
+ "mkdir -p /workspace/.shot && cd /workspace/.shot && echo '{\\"name\\":\\"kyb-shot\\",\\"private\\":true}' > package.json && npm install playwright",
527
+ });
528
+ await sandbox.run({
529
+ command: "cd /workspace/.shot && npx playwright install --with-deps chromium",
530
+ });
531
+ },
532
+ });
533
+
534
+ export default defineSandbox(() =>
535
+ environment.open({
517
536
  networkPolicy: {
518
537
  allow: [
519
538
  "registry.npmjs.org",
@@ -541,25 +560,7 @@ export default defineSandbox({
541
560
  ],
542
561
  },
543
562
  }),
544
- revalidationKey: () => "kybernesis-workshop-v5",
545
- async bootstrap({ use }) {
546
- const sandbox = await use();
547
- // The egress proxy carries HTTPS only; apt defaults to http:// mirrors, so
548
- // every index fetch silently fails. Rewrite to https first.
549
- await sandbox.run({
550
- command:
551
- "find /etc/apt -type f \\\\( -name '*.list' -o -name '*.sources' \\\\) -exec sed -i 's|http://|https://|g' {} + && apt-get update",
552
- });
553
- await sandbox.run({ command: "npm install -g pnpm" });
554
- await sandbox.run({
555
- command:
556
- "mkdir -p /workspace/.shot && cd /workspace/.shot && echo '{\\"name\\":\\"kyb-shot\\",\\"private\\":true}' > package.json && npm install playwright",
557
- });
558
- await sandbox.run({
559
- command: "cd /workspace/.shot && npx playwright install --with-deps chromium",
560
- });
561
- },
562
- });
563
+ );
563
564
  `;
564
565
  }
565
566
  export function engineerPlan(host, model) {
@@ -671,7 +672,8 @@ export default defineEvalConfig({
671
672
  // Verified against the exe integration rather than assumed: it validates model
672
673
  // names and refuses one it does not carry, so a stale default here fails the
673
674
  // whole suite at the judge rather than at anything the agent did.
674
- judge: { model: exe(process.env.EXE_JUDGE_MODEL ?? "claude-sonnet-5") },
675
+ // eve ≥0.62 grades with an EVALUATION model, not a language model instance.
676
+ judge: { model: exe.evaluationModel(process.env.EXE_JUDGE_MODEL ?? "claude-sonnet-5") },
675
677
  // Real model and real memory on every turn: generous timeout, gentle concurrency.
676
678
  timeoutMs: 300_000,
677
679
  maxConcurrency: 1,
@@ -748,13 +750,15 @@ export { default } from "@kybernesis/notify/extension";
748
750
  */
749
751
  export function reasoningSandboxLibTs() {
750
752
  return `import { defineSandbox } from "eve/sandbox";
751
- import { justbash } from "eve/sandbox/just-bash";
753
+ import { JustBashSandbox } from "eve/sandbox/just-bash";
752
754
 
753
755
  // The sandbox for a reasoning-only specialist: an instant virtual filesystem,
754
756
  // no Docker container. See agent/subagents/<id>/sandbox.ts, which re-export
755
- // this. Only subagents that do real build/git/network work keep a docker()
757
+ // this. Only subagents that do real build/git/network work keep a Docker
756
758
  // sandbox of their own.
757
- export default defineSandbox({ backend: justbash() });
759
+ export const environment = JustBashSandbox.environment();
760
+
761
+ export default defineSandbox(() => environment.open());
758
762
  `;
759
763
  }
760
764
  /** What each reasoning-only subagent's `sandbox.ts` re-exports. */
package/dist/upgrade.js CHANGED
@@ -10,7 +10,7 @@ import { dockerPruneCronArtifact } from "./docker-prune-cron.js";
10
10
  import { newestOnLine } from "./on-line-version.js";
11
11
  import { findMatchingAgentServiceUnit, repairManageRestart } from "./systemd.js";
12
12
  import { repairTerminalSandboxCleanupHooks } from "./sandbox-cleanup.js";
13
- import { repairRemovedDefaultTools as repairRemovedDefaultTools_ } from "./removed-default-tools.js";
13
+ import { repairOptInTools, repairRemovedDefaultTools as repairRemovedDefaultTools_ } from "./removed-default-tools.js";
14
14
  import { EVE_VERSION, bold, capture, dim, green, parseEnv, red, run, yellow } from "./util.js";
15
15
  import { inspectEveAgent } from "./agent-limits.js";
16
16
  import { remoteProjectPath, sshTarget } from "./deploy.js";
@@ -332,7 +332,14 @@ function repairRemovedDefaultTools(cwd) {
332
332
  for (const file of optedIn) {
333
333
  console.log(` ${green("+")} wrote ${file} ${dim("(keeps the tool this scope had on eve 0.38)")}`);
334
334
  }
335
- if (removed.length || optedIn.length)
335
+ const optIn = repairOptInTools(cwd, EVE_VERSION);
336
+ for (const file of optIn.removed) {
337
+ console.log(` ${green("-")} removed ${file} ${dim("(disabled a tool eve no longer provides by default; a compile error from eve 0.65)")}`);
338
+ }
339
+ for (const file of optIn.optedIn) {
340
+ console.log(` ${green("+")} wrote ${file} ${dim("(ask_question is opt-in from eve 0.65; this scope keeps it)")}`);
341
+ }
342
+ if (removed.length || optedIn.length || optIn.removed.length || optIn.optedIn.length)
336
343
  console.log();
337
344
  }
338
345
  function repairSandboxCleanupHooks(cwd, deps) {
@@ -578,7 +585,7 @@ function exeJudgeUrl(cwd, env) {
578
585
  if (!path)
579
586
  return null;
580
587
  const source = readFileSync(path, "utf8");
581
- const judgeProvider = /judge\s*:\s*\{[\s\S]*?model\s*:\s*([A-Za-z_$][\w$]*)\s*\(/.exec(source)?.[1];
588
+ const judgeProvider = /judge\s*:\s*\{[\s\S]*?model\s*:\s*([A-Za-z_$][\w$]*)\s*(?:\.evaluationModel\s*)?\(/.exec(source)?.[1];
582
589
  if (!judgeProvider)
583
590
  return null;
584
591
  const escaped = judgeProvider.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
package/dist/util.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const EVE_VERSION = "0.51.1";
1
+ export declare const EVE_VERSION = "0.68.0";
2
2
  export declare const REGISTRY_URL = "https://registry.kybernesis.ai/r/{name}.json";
3
3
  export declare const DEFAULT_ISSUER = "https://agent.kybernesis.ai";
4
4
  export declare const green: (s: string) => string;
package/dist/util.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { createInterface } from "node:readline/promises";
3
- export const EVE_VERSION = "0.51.1";
3
+ export const EVE_VERSION = "0.68.0";
4
4
  export const REGISTRY_URL = "https://registry.kybernesis.ai/r/{name}.json";
5
5
  export const DEFAULT_ISSUER = "https://agent.kybernesis.ai";
6
6
  const TTY = process.stdout.isTTY === true;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kybernesis/create",
3
- "version": "0.15.8",
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.17.0",
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",
@@ -24,7 +24,8 @@
24
24
  "NOTICE"
25
25
  ],
26
26
  "bin": {
27
- "kyb": "./dist/cli.js"
27
+ "kyb": "./dist/cli.js",
28
+ "kyb-eval": "./dist/eval-cli.js"
28
29
  },
29
30
  "exports": {
30
31
  "./package.json": "./package.json"
@@ -38,7 +39,7 @@
38
39
  },
39
40
  "devDependencies": {
40
41
  "@types/node": "24.x",
41
- "eve": "^0.51.1",
42
+ "eve": "0.68.0",
42
43
  "typescript": "7.0.2"
43
44
  },
44
45
  "engines": {
@@ -1989,7 +1989,7 @@ export default defineAgent({
1989
1989
  });
1990
1990
  ```
1991
1991
 
1992
- Four things to know before you promise it:
1992
+ Five things to know before you promise it:
1993
1993
 
1994
1994
  - **The exe LLM integration also serves `anthropic/*` ids, and that is NOT this.**
1995
1995
  It reaches Anthropic through a gateway, which bills metered usage. Same model,
@@ -2000,9 +2000,18 @@ Four things to know before you promise it:
2000
2000
  provider-defined tools, which Anthropic validates by name, and the failure
2001
2001
  (`tools.N.web_search_20250305.name: Input should be 'web_search'`) reads like
2002
2002
  a bug in the agent's own tool definitions.
2003
- - **The sign-in is per host and interactive** — a browser step, once, in the
2004
- container. It survives restarts because it lives in a named volume, but a new
2005
- VM needs its own.
2003
+ - **The sign-in is per host and interactive** — a browser step in the container.
2004
+ It survives restarts because it lives in a named volume, but a new VM needs
2005
+ its own.
2006
+ - **And it is not a one-off: the sign-in expires about every 28 days.** The
2007
+ access token refreshes hourly and does so happily the whole time, which tells
2008
+ you nothing — the refresh token underneath carries a fixed expiry that using
2009
+ it does not extend. On expiry the proxy blanks the credential to empty strings
2010
+ and every turn 401s; only `login` recovers it, never `reload` or a restart.
2011
+ **Say this to the client before you promise the arrangement, and put the
2012
+ re-login in someone's calendar** — it is the operational debt you take on in
2013
+ exchange for not billing metered usage. Run `scripts/claude-subscription.sh
2014
+ status` on a schedule: it now reports days remaining and escalates at 7 and 3.
2006
2015
  - **It is a process to keep alive.** If the container stops, every turn fails
2007
2016
  with a connection error from inside the model SDK, which reads like the model
2008
2017
  being down. `hostPreflight({ claudeProxyUrl })` asks about it at boot; the
@@ -86,6 +86,31 @@ What this arrangement costs you, and it is worth saying to the client:
86
86
  presents as the agent "breaking".
87
87
  - **Ask the vendor's terms question before a client demo**, not after.
88
88
 
89
+ ### A Claude subscription expires on a ~28-day clock, and lies on the way out
90
+
91
+ Claude is the third provider and behaves differently enough to plan around.
92
+ Grok and ChatGPT are a file a CLI keeps fresh; Claude is a **proxy process**
93
+ holding Claude Code's own OAuth (`scripts/claude-subscription.sh` in
94
+ `@kybernesis/exe`). Two things follow, both learned on Sid:
95
+
96
+ - **The sign-in has a hard expiry of about 28 days.** The access token refreshes
97
+ hourly and does so happily the whole time, which tells you nothing — the
98
+ *refresh* token underneath carries its own fixed expiry, and using it does not
99
+ extend it. On expiry the proxy blanks the credential file to empty strings and
100
+ every turn 401s. Only `login` recovers it; `reload` and restarts cannot.
101
+ **Diary the re-login for every subscription-backed agent you deploy.**
102
+ - **`/ready` cannot see a dead credential.** It reports that a credential was
103
+ *loaded*, not that it is *valid*, so it answers 200 over a blanked file while
104
+ the API 401s — `status` read "signed in, answering" straight through an
105
+ outage. `status` now probes `/v1/models` as well and warns from the refresh
106
+ expiry (`✓ sign-in valid for 27d`, escalating at 7 and 3 days). Run it on a
107
+ schedule; the failure is silent and looks like a model outage, so people go
108
+ and read Anthropic's status page instead of opening a browser.
109
+
110
+ Say the re-login cadence out loud to a client before it strands their agent at
111
+ a weekend. It is the one piece of operational debt this arrangement creates in
112
+ exchange for not billing metered API usage.
113
+
89
114
  ## The failure modes, each of which cost a real session
90
115
 
91
116
  - **`EXE_MODEL` is deliberately empty after `kyb init`.** Set it from the