@kybernesis/create 0.9.0 → 0.11.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/dist/cli.js CHANGED
@@ -19,6 +19,7 @@ import { bold, dim, red } from "./util.js";
19
19
  import { init } from "./init.js";
20
20
  import { doctor } from "./doctor.js";
21
21
  import { upgrade } from "./upgrade.js";
22
+ import { tui } from "./tui.js";
22
23
  import { installSkills } from "./skills.js";
23
24
  import { deploy } from "./deploy.js";
24
25
  import { register } from "./register.js";
@@ -70,6 +71,7 @@ const COMMANDS = {
70
71
  register: "Register this agent with the control plane (--name, --url).",
71
72
  deploy: "Deploy this agent to its host (--no-env to leave the env file alone).",
72
73
  upgrade: "Bring @kybernesis packages and eve to the certified versions (--skip-eval).",
74
+ tui: "Talk to your agents in the terminal.",
73
75
  version: "Print the version of this tool.",
74
76
  };
75
77
  /**
@@ -132,6 +134,9 @@ switch (command) {
132
134
  case "deploy":
133
135
  await deploy({ host: flag(rest, "host"), noEnv: rest.includes("--no-env") });
134
136
  break;
137
+ case "tui":
138
+ tui(rest);
139
+ break;
135
140
  case "upgrade":
136
141
  await upgrade(rest.includes("--skip-eval"));
137
142
  break;
package/dist/doctor.js CHANGED
@@ -250,6 +250,31 @@ export async function doctor() {
250
250
  add("warn", "self-hosted: export .env.local into the server process", "eve start does NOT read it; use the supervision script from @kybernesis/exe (scripts/eve-server.sh)");
251
251
  add("warn", "self-hosted: start via `npx eve start`, not `node .output/server/index.mjs`", "sandbox templates are prewarmed by the CLI; starting the server directly skips prewarm and every sandbox tool fails with SandboxTemplateNotProvisionedError");
252
252
  }
253
+ /**
254
+ * A self-hosted agent answering everything twice.
255
+ *
256
+ * The local queue delivers a turn by POSTing it to this same server and
257
+ * holds that connection open for the whole turn, but its client gives up
258
+ * after 30 seconds by default. Every turn slower than that is redelivered,
259
+ * and the workflow re-executes steps that already ran — so the person gets
260
+ * two differently-worded answers to one question, and the log says only
261
+ * that a retry recovered. It is reported as the model being odd, which
262
+ * sends the search nowhere near the transport.
263
+ *
264
+ * Hosted agents never see it; real queue infrastructure runs there. This is
265
+ * a cost of self-hosting that nothing in the environment announces.
266
+ */
267
+ const QUEUE_TIMEOUT_FLOOR_MS = 120_000;
268
+ const shortQueueTimeouts = [
269
+ "WORKFLOW_LOCAL_HEADERS_TIMEOUT_MS",
270
+ "WORKFLOW_LOCAL_BODY_TIMEOUT_MS",
271
+ ].filter((name) => Number(env[name] ?? 30_000) < QUEUE_TIMEOUT_FLOOR_MS);
272
+ if (shortQueueTimeouts.length === 0) {
273
+ add("pass", "local queue delivery survives turns longer than 30s");
274
+ }
275
+ else {
276
+ add("fail", `self-hosted: ${shortQueueTimeouts.join(" and ")} left at the 30s default`, "one queue delivery holds a connection open for the entire turn, so any turn slower than the timeout is redelivered and its steps re-run — the agent answers the same question twice, with two different answers, and nothing reports an error. Set both to 900000 in .env.local and restart the server");
277
+ }
253
278
  // The exe VM sandbox backend needs a credential that cannot be scoped.
254
279
  // Surface the blast radius here, where it is still cheap to change course.
255
280
  const sandboxFile = join(cwd, "agent/sandbox/sandbox.ts");
package/dist/init.js CHANGED
@@ -166,6 +166,21 @@ export async function init(rawName, options = {}) {
166
166
  * curl https://llm.int.exe.xyz/models.json
167
167
  */
168
168
  known.EXE_MODEL = "";
169
+ /**
170
+ * Longer than any turn, because the alternative is answering twice.
171
+ *
172
+ * The local queue delivers a turn by POSTing it to this same server and
173
+ * holds the connection open until the turn finishes, but its client gives
174
+ * up after 30 seconds by default. Any turn slower than that is redelivered
175
+ * and its steps re-run, so the person gets two differently-worded answers
176
+ * to one question with nothing in any log that looks like a fault.
177
+ *
178
+ * Written at scaffold rather than documented: it only affects self-hosted
179
+ * agents, it has one sensible value, and the failure it prevents is one
180
+ * nobody recognises in time.
181
+ */
182
+ known.WORKFLOW_LOCAL_HEADERS_TIMEOUT_MS = "900000";
183
+ known.WORKFLOW_LOCAL_BODY_TIMEOUT_MS = "900000";
169
184
  }
170
185
  upsertEnv(dir, known);
171
186
  if (!options.yes)
package/dist/tui.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function tui(args: string[]): void;
package/dist/tui.js ADDED
@@ -0,0 +1,48 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { delimiter, join } from "node:path";
5
+ import { bold, dim, yellow } from "./util.js";
6
+ /**
7
+ * Hand the terminal to the TUI.
8
+ *
9
+ * @remarks
10
+ * The TUI is a compiled binary rather than part of this package, so this
11
+ * command's whole job is finding it and getting out of the way. `stdio:
12
+ * "inherit"` is the load-bearing part: a full-screen app needs the real
13
+ * terminal, not a pipe, and anything that buffers its output turns it into
14
+ * garbage on the way through.
15
+ *
16
+ * The search order is deliberate. PATH first, so a version someone installed
17
+ * on purpose wins; then the two places it actually lands — cargo's bin, and a
18
+ * checkout's release build — because "command not found" is a useless answer
19
+ * when the binary is sitting in one of two well-known directories.
20
+ */
21
+ const BINARY = "kyb-tui";
22
+ function candidates() {
23
+ const onPath = (process.env.PATH ?? "")
24
+ .split(delimiter)
25
+ .filter(Boolean)
26
+ .map((dir) => join(dir, BINARY));
27
+ return [
28
+ ...onPath,
29
+ join(homedir(), ".cargo", "bin", BINARY),
30
+ join(homedir(), "kyb-tui", "target", "release", BINARY),
31
+ ];
32
+ }
33
+ export function tui(args) {
34
+ const binary = candidates().find((path) => existsSync(path));
35
+ if (!binary) {
36
+ console.log(`\n ${yellow("The terminal app is not installed on this machine.")}\n`);
37
+ console.log(` ${bold("cargo install --path .")} ${dim("from the kyb-tui checkout")}\n`);
38
+ process.exit(1);
39
+ }
40
+ const result = spawnSync(binary, args, { stdio: "inherit" });
41
+ // Exit with what it exited with: a wrapper that always reports success makes
42
+ // the thing it wraps untestable from a script.
43
+ if (result.error) {
44
+ console.error(`\n Could not start the terminal app: ${result.error.message}\n`);
45
+ process.exit(1);
46
+ }
47
+ process.exit(result.status ?? 0);
48
+ }
package/dist/upgrade.js CHANGED
@@ -1,5 +1,6 @@
1
- import { readFileSync } from "node:fs";
1
+ import { existsSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
+ import { upsertEnv } from "./envfile.js";
3
4
  import { EVE_VERSION, bold, capture, dim, green, red, run, yellow } from "./util.js";
4
5
  /**
5
6
  * Which packages to upgrade: every `@kybernesis/*` this agent depends on.
@@ -56,12 +57,43 @@ function warnIfStale() {
56
57
  `compiled into this tool, so an old kyb reports an old pin as current.`);
57
58
  console.log(` ${dim("npm install -g @kybernesis/create@latest")}\n`);
58
59
  }
60
+ /**
61
+ * Raise the local queue's delivery timeouts on an agent that already exists.
62
+ *
63
+ * @remarks
64
+ * Written as a repair rather than a warning because of what the bug looks like
65
+ * from outside: the agent answers the same question twice, in two different
66
+ * wordings, and no error appears in any log. Nobody reports that as a transport
67
+ * problem, so a warning would be read past — and the correct value is not a
68
+ * judgement call, it is "longer than a turn".
69
+ *
70
+ * Only for self-hosted agents. Hosted ones use real queue infrastructure and
71
+ * never touch this transport, so the variables would be noise in their
72
+ * environment.
73
+ */
74
+ function repairLocalQueueTimeouts(cwd, deps) {
75
+ if (!deps["@kybernesis/exe"])
76
+ return;
77
+ const path = join(cwd, ".env.local");
78
+ if (!existsSync(path))
79
+ return;
80
+ const text = readFileSync(path, "utf8");
81
+ const missing = ["WORKFLOW_LOCAL_HEADERS_TIMEOUT_MS", "WORKFLOW_LOCAL_BODY_TIMEOUT_MS"].filter((name) => !new RegExp(`^${name}=`, "m").test(text));
82
+ if (missing.length === 0)
83
+ return;
84
+ upsertEnv(cwd, Object.fromEntries(missing.map((name) => [name, "900000"])));
85
+ console.log(` ${green("+")} raised the local queue delivery timeout in .env.local ${dim("(was 30s)")}\n` +
86
+ ` ${dim("A delivery holds one connection open for the whole turn. Below this, any turn")}\n` +
87
+ ` ${dim("slower than 30s was redelivered and its steps re-run — the agent answered twice.")}\n` +
88
+ ` ${dim("Takes effect on the next server restart.")}\n`);
89
+ }
59
90
  export async function upgrade(skipEval) {
60
91
  const cwd = process.cwd();
61
92
  const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
62
93
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
63
94
  console.log(bold("\nkyb upgrade — checking @kybernesis/* and eve against npm\n"));
64
95
  warnIfStale();
96
+ repairLocalQueueTimeouts(cwd, deps);
65
97
  const toUpgrade = [];
66
98
  const unresolved = [];
67
99
  for (const name of kybernesisPackages(deps)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kybernesis/create",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
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",