@kybernesis/create 0.15.0 → 0.15.2

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.
@@ -80,3 +80,19 @@ export declare function arcanaMemorySlotTs(options: {
80
80
  * registry item, so the three can never disagree about what the file says.
81
81
  */
82
82
  export declare function notifyMountTs(): string;
83
+ /**
84
+ * The sandbox source for a reasoning-only specialist. One source for the
85
+ * `kyb upgrade` repair and any future scaffold, so they cannot disagree.
86
+ *
87
+ * Every eve agent has exactly one sandbox, and a declared subagent that
88
+ * authors none gets the framework default — on a self-hosted host, a Docker
89
+ * container from an 840MB image. A specialist that reasons and remembers with
90
+ * its file tools disabled never touches a filesystem, yet still booted a
91
+ * container per call; on a small host those boots blow eve's command-hook
92
+ * wait, which reads as the specialist being unavailable. `justbash` is eve's
93
+ * pure-JS bash: a virtual filesystem, no daemon, no VM, no container, no
94
+ * image, available instantly.
95
+ */
96
+ export declare function reasoningSandboxLibTs(): string;
97
+ /** What each reasoning-only subagent's `sandbox.ts` re-exports. */
98
+ export declare function reasoningSandboxReexportTs(): string;
package/dist/templates.js CHANGED
@@ -733,3 +733,34 @@ export function notifyMountTs() {
733
733
  export { default } from "@kybernesis/notify/extension";
734
734
  `;
735
735
  }
736
+ /**
737
+ * The sandbox source for a reasoning-only specialist. One source for the
738
+ * `kyb upgrade` repair and any future scaffold, so they cannot disagree.
739
+ *
740
+ * Every eve agent has exactly one sandbox, and a declared subagent that
741
+ * authors none gets the framework default — on a self-hosted host, a Docker
742
+ * container from an 840MB image. A specialist that reasons and remembers with
743
+ * its file tools disabled never touches a filesystem, yet still booted a
744
+ * container per call; on a small host those boots blow eve's command-hook
745
+ * wait, which reads as the specialist being unavailable. `justbash` is eve's
746
+ * pure-JS bash: a virtual filesystem, no daemon, no VM, no container, no
747
+ * image, available instantly.
748
+ */
749
+ export function reasoningSandboxLibTs() {
750
+ return `import { defineSandbox } from "eve/sandbox";
751
+ import { justbash } from "eve/sandbox/just-bash";
752
+
753
+ // The sandbox for a reasoning-only specialist: an instant virtual filesystem,
754
+ // 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()
756
+ // sandbox of their own.
757
+ export default defineSandbox({ backend: justbash() });
758
+ `;
759
+ }
760
+ /** What each reasoning-only subagent's `sandbox.ts` re-exports. */
761
+ export function reasoningSandboxReexportTs() {
762
+ return `// Reasoning-only specialist: no real filesystem, so no Docker container.
763
+ // See agent/lib/reasoning-sandbox for why this exists and what it replaces.
764
+ export { default } from "../../lib/reasoning-sandbox";
765
+ `;
766
+ }
package/dist/upgrade.d.ts CHANGED
@@ -15,6 +15,10 @@ export declare function agentInputLimitUpgradeMessage(limit: AgentInputLimit): s
15
15
  export declare function needsNotify(deps: Record<string, string>): boolean;
16
16
  /** Write the notify mount if it is missing. Never touches a file that exists. */
17
17
  export declare function repairNotifyMount(cwd: string): "mounted" | "present";
18
+ /** Reasoning-only subagents (disable all file tools) that have no sandbox of their own. */
19
+ export declare function reasoningSubagentsNeedingSandbox(cwd: string): string[];
20
+ /** Write the shared lib source and each specialist's re-export. Never touches a file that exists. */
21
+ export declare function writeReasoningSandboxes(cwd: string, subagents: string[]): string[];
18
22
  export type GithubToolsMountRepair = {
19
23
  kind: "missing" | "current" | "updated" | "customized";
20
24
  } | {
package/dist/upgrade.js CHANGED
@@ -1,4 +1,4 @@
1
- import { chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
1
+ import { chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
2
2
  import { spawnSync } from "node:child_process";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
@@ -15,7 +15,7 @@ import { inspectEveAgent } from "./agent-limits.js";
15
15
  import { remoteProjectPath, sshTarget } from "./deploy.js";
16
16
  import { classifyModelReach } from "./model-reach.js";
17
17
  import { checkSelfVersion, versionLt } from "./self-version.js";
18
- import { LEGACY_GITHUB_TOOLS_MOUNT, githubToolsMountTs, notifyMountTs } from "./templates.js";
18
+ import { LEGACY_GITHUB_TOOLS_MOUNT, githubToolsMountTs, notifyMountTs, reasoningSandboxLibTs, reasoningSandboxReexportTs } from "./templates.js";
19
19
  import { confirmEveUpgrade, inspectBuzzSessions, inspectDurableRuns, reconcileEvalScript, } from "./upgrade-sessions.js";
20
20
  export function agentInputLimitUpgradeMessage(limit) {
21
21
  switch (limit.kind) {
@@ -210,6 +210,109 @@ function repairNotifySetup(cwd, deps) {
210
210
  console.log(` ${dim("Takes effect after the next build and restart.")}\n`);
211
211
  }
212
212
  }
213
+ /**
214
+ * A reasoning-only specialist that still boots a Docker container.
215
+ *
216
+ * Every eve agent has exactly one sandbox, and a declared subagent that
217
+ * authors none gets the framework default — on a self-hosted host, a Docker
218
+ * container from an 840MB image. A specialist whose `bash`, `read_file`, and
219
+ * `write_file` tools are all disabled reasons and remembers and never touches
220
+ * a filesystem, yet each still booted its own container per call. On a small
221
+ * host those boots contend and blow eve's ~120s command-hook wait, which reads
222
+ * as "the specialist is unavailable" and, under a scheduled fan-out, thrashes
223
+ * the host. Disabling the file tools never stopped the sandbox: it is a slot
224
+ * eve always fills, not a consequence of the tools.
225
+ *
226
+ * The signal is deliberately narrow and static: a subagent that disables all
227
+ * three file tools has declared it does no filesystem work, so `just-bash` —
228
+ * eve's containerless virtual filesystem — is safe and correct for it. A
229
+ * subagent that leaves those tools enabled is left alone; whether it needs a
230
+ * real sandbox is a judgment only a person can make.
231
+ */
232
+ function disablesAllFileTools(dir) {
233
+ const tools = join(dir, "tools");
234
+ if (!existsSync(tools))
235
+ return false;
236
+ return ["bash", "read_file", "write_file"].every((name) => {
237
+ const file = join(tools, `${name}.ts`);
238
+ if (!existsSync(file))
239
+ return false;
240
+ try {
241
+ return /disableTool\s*\(/.test(readFileSync(file, "utf8"));
242
+ }
243
+ catch {
244
+ return false;
245
+ }
246
+ });
247
+ }
248
+ function hasAuthoredSandbox(dir) {
249
+ return existsSync(join(dir, "sandbox.ts")) || existsSync(join(dir, "sandbox"));
250
+ }
251
+ /** Reasoning-only subagents (disable all file tools) that have no sandbox of their own. */
252
+ export function reasoningSubagentsNeedingSandbox(cwd) {
253
+ const root = join(cwd, "agent/subagents");
254
+ if (!existsSync(root))
255
+ return [];
256
+ const found = [];
257
+ for (const entry of readdirSync(root)) {
258
+ const dir = join(root, entry);
259
+ try {
260
+ if (!statSync(dir).isDirectory())
261
+ continue;
262
+ }
263
+ catch {
264
+ continue;
265
+ }
266
+ if (!existsSync(join(dir, "agent.ts")))
267
+ continue;
268
+ if (hasAuthoredSandbox(dir))
269
+ continue;
270
+ if (disablesAllFileTools(dir))
271
+ found.push(entry);
272
+ }
273
+ return found.sort();
274
+ }
275
+ /** Write the shared lib source and each specialist's re-export. Never touches a file that exists. */
276
+ export function writeReasoningSandboxes(cwd, subagents) {
277
+ const wrote = [];
278
+ const lib = join(cwd, "agent/lib/reasoning-sandbox.ts");
279
+ if (!existsSync(lib)) {
280
+ mkdirSync(join(cwd, "agent/lib"), { recursive: true });
281
+ writeFileSync(lib, reasoningSandboxLibTs());
282
+ wrote.push("agent/lib/reasoning-sandbox.ts");
283
+ }
284
+ for (const name of subagents) {
285
+ const file = join(cwd, "agent/subagents", name, "sandbox.ts");
286
+ if (existsSync(file))
287
+ continue;
288
+ writeFileSync(file, reasoningSandboxReexportTs());
289
+ wrote.push(`agent/subagents/${name}/sandbox.ts`);
290
+ }
291
+ return wrote;
292
+ }
293
+ function repairReasoningSandboxes(cwd, deps) {
294
+ const needing = reasoningSubagentsNeedingSandbox(cwd);
295
+ if (needing.length === 0)
296
+ return;
297
+ const done = [];
298
+ if (!deps["just-bash"]) {
299
+ const ok = run("npm", ["install", "just-bash@^3.1.0", "--no-audit", "--no-fund"], { cwd, allowFail: true, quiet: true });
300
+ if (!ok) {
301
+ console.log(` ${yellow("!")} ${needing.length} reasoning specialist(s) still boot a Docker container per call, but just-bash could not be installed.\n` +
302
+ ` Run: npm install just-bash@^3.1.0 && npx kyb upgrade`);
303
+ return;
304
+ }
305
+ deps["just-bash"] = "^3.1.0";
306
+ done.push("installed just-bash");
307
+ }
308
+ const wrote = writeReasoningSandboxes(cwd, needing);
309
+ if (wrote.length > 0)
310
+ done.push(`gave ${needing.join(", ")} an instant sandbox (${wrote.length} file${wrote.length === 1 ? "" : "s"})`);
311
+ if (done.length > 0) {
312
+ console.log(` ${green("+")} Sandboxes: ${done.join("; ")} ${dim("— reasoning specialists no longer boot a Docker container")}`);
313
+ console.log(` ${dim("Takes effect after the next build and restart.")}\n`);
314
+ }
315
+ }
213
316
  /** Quote one argument for the manual shell commands printed after a failed repair. */
214
317
  function shellQuote(value) {
215
318
  return `'${value.replaceAll("'", `'"'"'`)}'`;
@@ -721,15 +824,6 @@ function startStoppedBridge(cwd, unit) {
721
824
  }
722
825
  export async function upgrade(options = {}) {
723
826
  console.log(bold("\nkyb upgrade — checking @kybernesis/* and eve against npm\n"));
724
- const selfVersion = checkSelfVersion();
725
- if (selfVersion.kind === "stale") {
726
- console.log(` ${yellow("!")} ${selfVersion.message}`);
727
- console.log(` ${dim(selfVersion.fix)}\n`);
728
- if (!options.allowStale) {
729
- process.exitCode = 1;
730
- return;
731
- }
732
- }
733
827
  const cwd = process.cwd();
734
828
  const packagePath = join(cwd, "package.json");
735
829
  const pkg = JSON.parse(readFileSync(packagePath, "utf8"));
@@ -737,6 +831,39 @@ export async function upgrade(options = {}) {
737
831
  ...(pkg.dependencies ?? {}),
738
832
  ...(pkg.devDependencies ?? {}),
739
833
  };
834
+ const selfVersion = checkSelfVersion();
835
+ if (selfVersion.kind === "stale" && !options.allowStale) {
836
+ /**
837
+ * A stale kyb that is the PROJECT's own dependency renews itself.
838
+ *
839
+ * On every self-hosted agent, kyb is in package.json and `npx kyb` runs
840
+ * that copy; a global install — the printed fix until now — changes
841
+ * nothing there, and the person who followed it was back at the same
842
+ * message. So when the project owns kyb, install the latest into the
843
+ * project and run the upgrade again from that copy. The re-run is marked
844
+ * so that a registry that has not caught up cannot make this loop.
845
+ */
846
+ if (deps["@kybernesis/create"] && process.env.KYB_UPGRADE_RENEWED !== "1") {
847
+ console.log(` ${yellow("↑")} kyb ${selfVersion.installed} → ${selfVersion.latest} ${dim("(this project's own copy; renewing it first)")}`);
848
+ const renewed = run("npm", ["install", `@kybernesis/create@${selfVersion.latest}`, "--no-audit", "--no-fund"], { cwd, allowFail: true, quiet: true });
849
+ const cli = join(cwd, "node_modules/@kybernesis/create/dist/cli.js");
850
+ if (renewed && existsSync(cli)) {
851
+ const again = spawnSync(process.execPath, [cli, "upgrade", ...process.argv.slice(3)], {
852
+ cwd, stdio: "inherit", env: { ...process.env, KYB_UPGRADE_RENEWED: "1" },
853
+ });
854
+ process.exitCode = again.status ?? 1;
855
+ return;
856
+ }
857
+ }
858
+ console.log(` ${yellow("!")} ${selfVersion.message}`);
859
+ console.log(` ${dim(deps["@kybernesis/create"] ? `npm install @kybernesis/create@latest && npx kyb upgrade` : selfVersion.fix)}\n`);
860
+ process.exitCode = 1;
861
+ return;
862
+ }
863
+ if (selfVersion.kind === "stale") {
864
+ console.log(` ${yellow("!")} ${selfVersion.message}`);
865
+ console.log(` ${dim(selfVersion.fix)} ${dim("(continuing: --allow-stale)")}\n`);
866
+ }
740
867
  reportAgentInputLimit(cwd);
741
868
  const entries = dependencyEntries(pkg);
742
869
  const installedVersions = new Map();
@@ -822,6 +949,7 @@ export async function upgrade(options = {}) {
822
949
  repairHostArtifacts(cwd, deps);
823
950
  repairEvalCommand(cwd);
824
951
  repairNotifySetup(cwd, deps);
952
+ repairReasoningSandboxes(cwd, deps);
825
953
  repairManageRestart(cwd, deps);
826
954
  return;
827
955
  }
@@ -894,6 +1022,7 @@ export async function upgrade(options = {}) {
894
1022
  repairHostArtifacts(cwd, deps);
895
1023
  repairEvalCommand(cwd);
896
1024
  repairNotifySetup(cwd, deps);
1025
+ repairReasoningSandboxes(cwd, deps);
897
1026
  repairManageRestart(cwd, deps);
898
1027
  run("npm", ["run", "typecheck"], { cwd });
899
1028
  if (eveChanged) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kybernesis/create",
3
- "version": "0.15.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.",
3
+ "version": "0.15.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.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "homepage": "https://kybernesis.ai",