glimmervoid 0.29.1 → 0.31.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.
Files changed (67) hide show
  1. package/README.md +8 -10
  2. package/dist/bin/glimmervoid.js +152 -53
  3. package/dist/chunks/{adapters-DIavI9-i.js → adapters-ClA_GbtA.js} +1 -1
  4. package/dist/chunks/{adapters-Dlvu1FVB.js → adapters-gn7ulNO4.js} +49 -20
  5. package/dist/chunks/agent-api-cli-DAA7Jx8N.js +135 -0
  6. package/dist/chunks/{agent-api-core-D6LHrpSL.js → agent-api-core-hVcT26JW.js} +10 -1
  7. package/dist/chunks/{agent-setup-cli-C1CQ8Nkk.js → agent-setup-cli-CiJSDWSo.js} +15 -30
  8. package/dist/chunks/backoff-DjVpfHk7.js +14 -0
  9. package/dist/chunks/child-process-safe-BKPZ-5qQ.js +2 -0
  10. package/dist/chunks/{child-process-safe-Cz22RwlG.js → child-process-safe-Ch-Zgxqs.js} +2 -2
  11. package/dist/chunks/coerce-NSst8oRP.js +32 -0
  12. package/dist/chunks/{contracts-JKChPhZ0.js → config-W1Sv-iPK.js} +380 -1682
  13. package/dist/chunks/{config-store-DtPUFVaO.js → config-store-BCy4FHNG.js} +595 -327
  14. package/dist/chunks/config-store-DbBp7LJZ.js +2 -0
  15. package/dist/chunks/contracts-3LkrilQX.js +2078 -0
  16. package/dist/chunks/{grok-COekzawt.js → grok-CXKeSt2z.js} +69 -36
  17. package/dist/chunks/hook-command-core-DclEoE9e.js +14 -0
  18. package/dist/chunks/{hook-relay-core-BHfYFnYV.js → hook-relay-core-DA_bz2KA.js} +10 -5
  19. package/dist/chunks/hook-tools-UX2fmt81.js +72 -0
  20. package/dist/chunks/{ingest-editor-core-BuI1gMF_.js → ingest-editor-core-vR6Wv6bP.js} +24 -13
  21. package/dist/chunks/json-core-dfa_7Fak.js +23 -0
  22. package/dist/chunks/json-file-oEH9vt-0.js +352 -0
  23. package/dist/chunks/knowledge-graph-cli-D2tt5hPn.js +1354 -0
  24. package/dist/chunks/{loopback-post-C7C22eYi.js → loopback-post-DepQfdcM.js} +40 -26
  25. package/dist/chunks/{main-BVDjy7vH.js → main-DyTCWIu2.js} +28881 -20693
  26. package/dist/chunks/{node-pty-preflight-CK1knGIR.js → node-pty-preflight-C8QIlrtU.js} +4 -6
  27. package/dist/chunks/{pair-cli-BQQnhkkc.js → pair-cli-BbCBfd6l.js} +4 -3
  28. package/dist/chunks/paths-CeJl2Vcg.js +55 -0
  29. package/dist/chunks/{remote-config-BHoDFCpo.js → remote-config-DKIH_VxM.js} +43 -43
  30. package/dist/chunks/rtk-hook-core-BmEz2aq9.js +131 -0
  31. package/dist/chunks/rtk-resolver-BGDzw5XK.js +21 -0
  32. package/dist/chunks/rtk-resolver-D4Nke1Fr.js +2 -0
  33. package/dist/chunks/{runtime-paths-CVvq20A4.js → runtime-paths-BPoxENmK.js} +28 -8
  34. package/dist/chunks/runtime-paths-DU5jBEEO.js +2 -0
  35. package/dist/chunks/{sandbox-deps-BwvbDBDl.js → sandbox-deps-BPKE-ylr.js} +16 -13
  36. package/dist/chunks/sandbox-proxy-core-hnQgKR0T.js +27 -0
  37. package/dist/chunks/{session-CJtOveHl.js → session-fc4pFbon.js} +10 -2
  38. package/dist/chunks/text-DoirglWV.js +21 -0
  39. package/dist/chunks/timer-deps-u2DAQLok.js +15 -0
  40. package/dist/chunks/{visions-cli-DUZnopGY.js → visions-cli-CT5rjiwf.js} +1 -1
  41. package/dist/chunks/{visions-setup-C_kJobsy.js → visions-setup-BmOkI-QE.js} +20 -357
  42. package/dist/client/assets/index-C6KD8lgZ.css +1 -0
  43. package/dist/client/assets/index-CThQEAEy.js +249 -0
  44. package/dist/client/fonts/CommitMono-OFL.txt +92 -0
  45. package/dist/client/fonts/commit-mono-400-italic.woff2 +0 -0
  46. package/dist/client/fonts/commit-mono-400-regular.woff2 +0 -0
  47. package/dist/client/fonts/commit-mono-700-italic.woff2 +0 -0
  48. package/dist/client/fonts/commit-mono-700-regular.woff2 +0 -0
  49. package/dist/client/index.html +7 -24
  50. package/dist/client/octicons-LICENSE.txt +21 -0
  51. package/dist/scripts/postinstall-path-check.js +1 -1
  52. package/dist/server/index.js +3 -3
  53. package/dist/session/command-hook-relay.js +1 -1
  54. package/dist/session/hook-relay.js +3 -3
  55. package/dist/session/{rtk-relay.js → hook-tool-relay.js} +32 -15
  56. package/dist/session/sandbox-open-launcher.js +79 -0
  57. package/dist/session/statusline-relay.js +8 -18
  58. package/dist/session/visions-relay.js +20 -11
  59. package/package.json +5 -1
  60. package/dist/chunks/agent-api-cli-ClOzm28U.js +0 -71
  61. package/dist/chunks/config-store-DtUGwi4-.js +0 -2
  62. package/dist/chunks/ingest-core-DWI1Mbu4.js +0 -342
  63. package/dist/chunks/rtk-hook-core-DRYrKwYP.js +0 -26
  64. package/dist/chunks/rtk-resolver-DDVVNRBT.js +0 -2
  65. package/dist/chunks/rtk-resolver-oGmO-ew7.js +0 -58
  66. package/dist/client/assets/index-BTwYgSAH.js +0 -248
  67. package/dist/client/assets/index-DgA708PE.css +0 -1
package/README.md CHANGED
@@ -17,28 +17,26 @@ Glimmervoid is developed inside Glimmervoid.
17
17
 
18
18
  ## Quickstart
19
19
 
20
- Until the `glimmervoid` npm package is published, run the tarball attached to the latest GitHub release. Windows and macOS:
20
+ Windows and macOS:
21
21
 
22
22
  ```bash
23
- npx --allow-remote=root --allow-scripts=node-pty https://github.com/johncwaters/glimmervoid/releases/latest/download/glimmervoid.tgz
23
+ npx glimmervoid@latest
24
24
  ```
25
25
 
26
- Linux (node-pty ships no Linux prebuilds, so it compiles at install time):
26
+ Linux (node-pty ships no Linux prebuilds, so it compiles at install time, and npm runs that build only when allowed):
27
27
 
28
28
  ```bash
29
29
  sudo apt install build-essential python3 git
30
- npx --allow-remote=root --allow-scripts=node-pty https://github.com/johncwaters/glimmervoid/releases/latest/download/glimmervoid.tgz
30
+ npx --allow-scripts=node-pty glimmervoid@latest
31
31
  ```
32
32
 
33
- To upgrade, run the versioned command the dashboard's update banner shows (or the `glimmervoid-<version>.tgz` URL from the release page): npx caches by the literal command, so rerunning the command above keeps starting the version it first fetched.
34
-
35
- Once the package is published, `npm install -g glimmervoid` (plus `--allow-scripts=node-pty` on Linux) installs it globally; if that fails with `EACCES`, see [docs/troubleshooting.md](docs/troubleshooting.md#eacces-on-a-global-install) and never use `sudo npm`.
33
+ To install it globally instead, run `npm install -g glimmervoid` (add `--allow-scripts=node-pty` on Linux); if that fails with `EACCES`, see [docs/troubleshooting.md](docs/troubleshooting.md#eacces-on-a-global-install) and never use `sudo npm`.
36
34
 
37
35
  Open http://localhost:3000. A fresh install knows no projects, so **+ Session** shows "No projects found" until you say where your repositories live: open **Settings**, then **Repositories**, and add the folder that holds your git checkouts under **Repository roots** (each subfolder becomes a project). For a one-off, **+ Session** then **Advanced options** takes a name and a path directly. That is the whole setup; everything else is optional.
38
36
 
39
37
  To check notifications, allow them when the dashboard asks (or turn on **Desktop notifications** under **Settings**, **Appearance and alerts**) and keep a dashboard tab open. A notification only appears while that tab is not focused, since a focused tab already shows the change. With no dashboard tab open anywhere, only Telegram reaches you (the `telegram` keys in [docs/configuration.md](docs/configuration.md)).
40
38
 
41
- Both flags matter on npm 12, which refuses tarball URLs (`EALLOWREMOTE`) and blocks dependency install scripts by default. npm 10 and 11 allow both by default (npm 11 prints a notice), so there the flags are unnecessary but harmless. If the native module still fails to load, the server refuses to start and prints the repair command; `glimmervoid doctor` runs the same check. See [docs/troubleshooting.md](docs/troubleshooting.md).
39
+ The Linux flag matters on npm 12, which blocks dependency install scripts by default; npm 10 and 11 run them (npm 11 prints a notice), so there it is harmless. Keep `@latest` so npx checks for a new release instead of rerunning the version it cached first. If the native module still fails to load, the server refuses to start and prints the repair command; `glimmervoid doctor` runs the same check. See [docs/troubleshooting.md](docs/troubleshooting.md).
42
40
 
43
41
  ### From source
44
42
 
@@ -59,7 +57,7 @@ Update a clone with `git pull --ff-only && npm ci && npm run build`, then restar
59
57
  - **Claude Code CLI** on PATH, or another supported agent below.
60
58
  - **git** on PATH, for per-session worktrees.
61
59
  - **Linux only:** `build-essential` and `python3` for node-pty.
62
- - **Linux only:** `bubblewrap` (`bwrap`) and `socat` for Claude Code's sandbox, which team review, Keep mergeable, workflow sessions and benchmark runs use (`sudo apt install bubblewrap socat`). `glimmervoid doctor` reports both.
60
+ - **Linux only:** `bubblewrap` (`bwrap`), `socat` and `ripgrep` (`rg`) for the sandbox that sandboxed sessions and factory checks run in (`sudo apt install bubblewrap socat ripgrep`). `glimmervoid doctor` reports all three.
63
61
 
64
62
  ## Supported agents
65
63
 
@@ -144,7 +142,7 @@ Remote access is off by default. It is a second loopback listener meant to sit b
144
142
  ```
145
143
 
146
144
  2. Restart Glimmervoid (stop and start it, or `systemctl --user restart glimmervoid` when it runs as a service). The log line `Glimmervoid remote listener on http://127.0.0.1:3456 (paired devices only)` confirms the listener is up.
147
- 3. Point an HTTPS reverse proxy at that port. With Tailscale, `tailscale serve --bg 3456` serves `https://<machine>.<tailnet>.ts.net`, which is the name `publicHost` should hold.
145
+ 3. Point an HTTPS reverse proxy at that port. With Tailscale, `tailscale serve --bg 3456` serves `https://<machine>.<tailnet>.ts.net`, which is the name `publicHost` should hold. When the port is reached solely through HTTP-mode `tailscale serve`, also set `"ownerLogin": "you@example.com"` in the `remote` block: the listener then refuses every request and WebSocket whose `Tailscale-User-Login` is not that login, including Funnel and tagged-device traffic, which carry none. Only HTTP-mode `tailscale serve` strips a client-sent `Tailscale-User-Login`; behind any other proxy, or `tailscale serve --tcp` or `--tls-terminated-tcp`, a client can forge it, so `ownerLogin` proves nothing there. It works on top of pairing, never instead of it.
148
146
  4. Verify from the device: `https://<publicHost>/` should answer with Glimmervoid's "Pairing required" page.
149
147
  5. On the host, run `glimmervoid pair --name phone`. It prints a single-use URL valid for 10 minutes that sets an auth cookie when opened on the device.
150
148
 
@@ -1,15 +1,93 @@
1
1
  #!/usr/bin/env node
2
- import { o as nodePtyRebuildHint, t as probeNodePty } from "../chunks/node-pty-preflight-CK1knGIR.js";
3
- import { a as packageRoot } from "../chunks/runtime-paths-CVvq20A4.js";
4
- import { t as AGENT_API_VERBS } from "../chunks/session-CJtOveHl.js";
5
- import { i as execSync } from "../chunks/child-process-safe-Cz22RwlG.js";
2
+ import { o as nodePtyRebuildHint, r as firstLine, t as probeNodePty } from "../chunks/node-pty-preflight-C8QIlrtU.js";
3
+ import { i as errorText } from "../chunks/text-DoirglWV.js";
4
+ import { a as packageRoot } from "../chunks/runtime-paths-BPoxENmK.js";
5
+ import { t as AGENT_API_VERBS } from "../chunks/session-fc4pFbon.js";
6
+ import { i as execSync } from "../chunks/child-process-safe-Ch-Zgxqs.js";
6
7
  import { t as renderTable } from "../chunks/ascii-figure-core-pDAwtlKO.js";
7
8
  import { n as glimmervoidHomeDir, t as decideConfigPath } from "../chunks/config-path-core-DFt-TemS.js";
8
- import { i as sandboxDoctorRows, r as probeSandboxDependencies } from "../chunks/sandbox-deps-BwvbDBDl.js";
9
+ import { i as sandboxDoctorRows, r as probeSandboxDependencies } from "../chunks/sandbox-deps-BPKE-ylr.js";
9
10
  import { i as pnpmGlobalBinDir, n as npmGlobalBinDir, r as onPath, t as formatPathNotice } from "../chunks/path-doctor-C23VAqeu.js";
10
11
  import fs from "node:fs";
11
12
  import path from "node:path";
12
13
  import os from "node:os";
14
+ //#region server/core/command-line-core.ts
15
+ var SUBCOMMANDS_HONORING_TRAILING_GLOBAL_OPTIONS = /* @__PURE__ */ new Set([
16
+ "doctor",
17
+ "pair",
18
+ "visions"
19
+ ]);
20
+ var SUBCOMMANDS_HONORING_A_LEADING_HELP_OR_VERSION = new Set(AGENT_API_VERBS);
21
+ var OPTIONS_TAKING_A_VALUE = {
22
+ "--config": "configPath",
23
+ "--port": "port"
24
+ };
25
+ function scanGlobalOption(argv, index, state) {
26
+ const token = argv[index];
27
+ const valueKey = OPTIONS_TAKING_A_VALUE[token];
28
+ if (valueKey) {
29
+ const value = argv[index + 1];
30
+ if (value !== void 0) state[valueKey] = value;
31
+ return index + 2;
32
+ }
33
+ if (token === "--help" || token === "-h") state.wantsHelp = true;
34
+ if (token === "--version") state.wantsVersion = true;
35
+ if (token === "--doctor") state.wantsDoctor = true;
36
+ return index + 1;
37
+ }
38
+ function scanTrailingGlobalOptions(subcommandArgs, state) {
39
+ let index = 0;
40
+ while (index < subcommandArgs.length) {
41
+ if (!subcommandArgs[index].startsWith("-")) {
42
+ index += 1;
43
+ continue;
44
+ }
45
+ index = scanGlobalOption(subcommandArgs, index, state);
46
+ }
47
+ }
48
+ function scanLeadingHelpOrVersion(subcommandArgs, state) {
49
+ const firstArgument = subcommandArgs[0];
50
+ if (firstArgument === "--help" || firstArgument === "-h") state.wantsHelp = true;
51
+ if (firstArgument === "--version") state.wantsVersion = true;
52
+ }
53
+ function parseCommandLine(argv) {
54
+ const state = {
55
+ configPath: null,
56
+ port: null,
57
+ wantsHelp: false,
58
+ wantsVersion: false,
59
+ wantsDoctor: false
60
+ };
61
+ let index = 0;
62
+ while (index < argv.length && argv[index].startsWith("-")) index = scanGlobalOption(argv, index, state);
63
+ const name = argv[index];
64
+ const subcommandArgs = argv.slice(index + 1);
65
+ if (name !== void 0 && SUBCOMMANDS_HONORING_TRAILING_GLOBAL_OPTIONS.has(name)) scanTrailingGlobalOptions(subcommandArgs, state);
66
+ if (name !== void 0 && SUBCOMMANDS_HONORING_A_LEADING_HELP_OR_VERSION.has(name)) scanLeadingHelpOrVersion(subcommandArgs, state);
67
+ if (state.wantsHelp) return { kind: "help" };
68
+ if (state.wantsVersion) return { kind: "version" };
69
+ const globalOptions = {
70
+ configPath: state.configPath,
71
+ port: state.port
72
+ };
73
+ if (state.wantsDoctor) return {
74
+ kind: "subcommand",
75
+ name: "doctor",
76
+ subcommandArgs: [],
77
+ globalOptions
78
+ };
79
+ if (name === void 0) return {
80
+ kind: "server",
81
+ globalOptions
82
+ };
83
+ return {
84
+ kind: "subcommand",
85
+ name,
86
+ subcommandArgs,
87
+ globalOptions
88
+ };
89
+ }
90
+ //#endregion
13
91
  //#region bin/glimmervoid.ts
14
92
  var args = process.argv.slice(2);
15
93
  var pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
@@ -28,6 +106,7 @@ Commands:
28
106
  spawn <prompt> From inside a Glimmervoid session, start a sibling session on that prompt
29
107
  attention <note> From inside a Glimmervoid session, flag it as needing the operator
30
108
  board From inside a Glimmervoid session, list the live sessions
109
+ kg [command] Experimental typed knowledge and task graph (Settings > Lanes > Knowledge graph)
31
110
 
32
111
  Options:
33
112
  --name <label> Label for the device being paired (with: pair)
@@ -35,71 +114,75 @@ Options:
35
114
  --config <path> Path to config file (default: ~/.glimmervoid/config.json)
36
115
  --version Show version number
37
116
  --help, -h Show this help message`;
38
- if (args.includes("--help") || args.includes("-h")) {
117
+ var commandLine = parseCommandLine(args);
118
+ if (commandLine.kind === "help") {
39
119
  console.log(USAGE);
40
120
  process.exit(0);
41
121
  }
42
- if (args.includes("--version")) {
122
+ if (commandLine.kind === "version") {
43
123
  console.log(pkg.version);
44
124
  process.exit(0);
45
125
  }
46
- function getArgValue(flag) {
47
- const idx = args.indexOf(flag);
48
- if (idx !== -1 && idx + 1 < args.length) return args[idx + 1];
49
- return null;
50
- }
51
- var configArg = getArgValue("--config");
52
- if (configArg) process.env.GLIMMERVOID_CONFIG = configArg;
53
- var portArg = getArgValue("--port");
54
- if (portArg) process.env.GLIMMERVOID_PORT = portArg;
126
+ if (commandLine.globalOptions.configPath) process.env.GLIMMERVOID_CONFIG = commandLine.globalOptions.configPath;
127
+ if (commandLine.globalOptions.port) process.env.GLIMMERVOID_PORT = commandLine.globalOptions.port;
55
128
  var SUBCOMMAND_RUNNERS = new Map([
56
129
  ["doctor", async () => {
57
130
  await runDoctor();
58
131
  return 0;
59
132
  }],
60
- ["pair", async (commandArgs) => {
61
- const { runPairCli } = await import("../chunks/pair-cli-BQQnhkkc.js");
62
- return runPairCli(commandArgs.slice(1));
133
+ ["pair", async (subcommandArgs) => {
134
+ const { runPairCli } = await import("../chunks/pair-cli-BbCBfd6l.js");
135
+ return runPairCli(subcommandArgs);
136
+ }],
137
+ ["agent", async (subcommandArgs) => {
138
+ const { runAgentSetupCli } = await import("../chunks/agent-setup-cli-CiJSDWSo.js");
139
+ return runAgentSetupCli(subcommandArgs);
63
140
  }],
64
- ["agent", async (commandArgs) => {
65
- const { runAgentSetupCli } = await import("../chunks/agent-setup-cli-C1CQ8Nkk.js");
66
- return runAgentSetupCli(commandArgs.slice(1));
141
+ ["kg", async (subcommandArgs) => {
142
+ const { runKnowledgeGraphCommand } = await import("../chunks/knowledge-graph-cli-D2tt5hPn.js");
143
+ return runKnowledgeGraphCommand(subcommandArgs);
67
144
  }],
68
- ["visions", async (commandArgs) => {
69
- const { runVisionsCli } = await import("../chunks/visions-cli-DUZnopGY.js");
70
- return runVisionsCli(commandArgs.slice(1));
145
+ ["visions", async (subcommandArgs) => {
146
+ const { runVisionsCli } = await import("../chunks/visions-cli-CT5rjiwf.js");
147
+ return runVisionsCli(subcommandArgs);
71
148
  }],
72
- ...AGENT_API_VERBS.map((verb) => [verb, async (commandArgs) => {
73
- const { runAgentApiCli } = await import("../chunks/agent-api-cli-ClOzm28U.js");
74
- return runAgentApiCli(commandArgs);
149
+ ...AGENT_API_VERBS.map((verb) => [verb, async (subcommandArgs, subcommandName) => {
150
+ const { runAgentApiCli } = await import("../chunks/agent-api-cli-DAA7Jx8N.js");
151
+ return runAgentApiCli([subcommandName, ...subcommandArgs]);
75
152
  }])
76
153
  ]);
154
+ function flushStream(stream) {
155
+ return new Promise((resolve) => {
156
+ stream.once("error", () => resolve());
157
+ stream.write("", () => resolve());
158
+ });
159
+ }
160
+ async function exitAfterOutputDrains(exitCode) {
161
+ await Promise.all([flushStream(process.stdout), flushStream(process.stderr)]);
162
+ process.exit(exitCode);
163
+ }
164
+ async function runSubcommandToExitCode(runSubcommand, subcommandArgs, subcommandName) {
165
+ try {
166
+ return await runSubcommand(subcommandArgs, subcommandName);
167
+ } catch (err) {
168
+ console.error(errorText(err));
169
+ return 1;
170
+ }
171
+ }
77
172
  async function dispatchCommandLine() {
78
- const requestedSubcommand = args.includes("--doctor") ? "doctor" : args[0];
79
- if (!requestedSubcommand || requestedSubcommand.startsWith("-")) {
173
+ if (commandLine.kind !== "subcommand") {
80
174
  await import("../server/index.js");
81
175
  return;
82
176
  }
83
- const runSubcommand = SUBCOMMAND_RUNNERS.get(requestedSubcommand);
177
+ const runSubcommand = SUBCOMMAND_RUNNERS.get(commandLine.name);
84
178
  if (!runSubcommand) {
85
- console.error(`Unknown command: ${requestedSubcommand}\n${USAGE}`);
86
- process.exit(1);
87
- }
88
- try {
89
- process.exit(await runSubcommand(args));
90
- } catch (err) {
91
- console.error(messageOf(err));
92
- process.exit(1);
179
+ console.error(`Unknown command: ${commandLine.name}\n${USAGE}`);
180
+ await exitAfterOutputDrains(1);
181
+ return;
93
182
  }
183
+ await exitAfterOutputDrains(await runSubcommandToExitCode(runSubcommand, commandLine.subcommandArgs, commandLine.name));
94
184
  }
95
185
  await dispatchCommandLine();
96
- function messageOf(error) {
97
- if (error instanceof Error && error.message) return error.message;
98
- return String(error);
99
- }
100
- function firstLineOf(error) {
101
- return messageOf(error).split("\n")[0];
102
- }
103
186
  function decideReadOnlyConfigPath() {
104
187
  return decideConfigPath({
105
188
  env: process.env,
@@ -118,7 +201,7 @@ async function readDeclaredCustomAgents() {
118
201
  declared: [],
119
202
  error: null
120
203
  };
121
- const { loadConfigFile } = await import("../chunks/config-store-DtUGwi4-.js");
204
+ const { loadConfigFile } = await import("../chunks/config-store-DbBp7LJZ.js");
122
205
  try {
123
206
  const loaded = loadConfigFile(decided.path, { exitOnError: false });
124
207
  if (!loaded.config) return {
@@ -132,7 +215,7 @@ async function readDeclaredCustomAgents() {
132
215
  } catch (err) {
133
216
  return {
134
217
  declared: [],
135
- error: `could not read ${decided.path}: ${firstLineOf(err)}`
218
+ error: `could not read ${decided.path}: ${firstLine(errorText(err))}`
136
219
  };
137
220
  }
138
221
  }
@@ -194,7 +277,7 @@ async function runDoctor() {
194
277
  }
195
278
  switchSection("Agents");
196
279
  try {
197
- const { listAgentIds, describeAgentResolvability, setCustomAgents } = await import("../chunks/adapters-DIavI9-i.js");
280
+ const { listAgentIds, describeAgentResolvability, setCustomAgents } = await import("../chunks/adapters-ClA_GbtA.js");
198
281
  const declaredCustomAgents = await readDeclaredCustomAgents();
199
282
  if (declaredCustomAgents.error) line("config", declaredCustomAgents.error);
200
283
  setCustomAgents(declaredCustomAgents.declared);
@@ -202,18 +285,34 @@ async function runDoctor() {
202
285
  const { label, path: resolvedPath } = describeAgentResolvability(id);
203
286
  line(`${id} (${label})`, resolvedPath ?? "not found on PATH");
204
287
  }
205
- const { inspectGrokAgentSetup } = await import("../chunks/agent-setup-cli-C1CQ8Nkk.js");
288
+ const { inspectGrokAgentSetup } = await import("../chunks/agent-setup-cli-CiJSDWSo.js");
206
289
  const grokSetup = inspectGrokAgentSetup();
207
290
  line("grok hook setup", `${grokSetup.classification}: ${grokSetup.filePath}`);
208
291
  } catch (err) {
209
- line("agents", `probe failed: ${firstLineOf(err)}`);
292
+ line("agents", `probe failed: ${firstLine(errorText(err))}`);
210
293
  }
211
294
  switchSection("rtk");
212
295
  try {
213
- const { getRtkPath } = await import("../chunks/rtk-resolver-DDVVNRBT.js");
296
+ const { getRtkPath } = await import("../chunks/rtk-resolver-D4Nke1Fr.js");
214
297
  line("rtk", getRtkPath() || "not installed (Glimmervoid installs it when the rtk setting is on)");
215
298
  } catch (err) {
216
- line("rtk", `probe failed: ${firstLineOf(err)}`);
299
+ line("rtk", `probe failed: ${firstLine(errorText(err))}`);
300
+ }
301
+ switchSection("Sane YOLO");
302
+ try {
303
+ const { resolvePackageBin } = await import("../chunks/runtime-paths-DU5jBEEO.js");
304
+ const { execFileSync } = await import("../chunks/child-process-safe-BKPZ-5qQ.js");
305
+ const { inspectGrokAgentSetup } = await import("../chunks/agent-setup-cli-CiJSDWSo.js");
306
+ const binPath = resolvePackageBin("cc-safety-net", "cc-safety-net");
307
+ line("Sane YOLO", binPath || "not installed");
308
+ if (binPath) line("cc-safety-net version", execFileSync(process.execPath, [binPath, "--version"], {
309
+ encoding: "utf8",
310
+ timeout: 3e3
311
+ }).trim());
312
+ line("Codex hook trust", "Sane YOLO is inactive unless Codex trusts the injected hooks.");
313
+ line("Grok Sane YOLO", inspectGrokAgentSetup().saneYoloReady ? "installed" : "inactive; run glimmervoid agent setup grok");
314
+ } catch (err) {
315
+ line("Sane YOLO", `probe failed: ${firstLine(errorText(err))}`);
217
316
  }
218
317
  switchSection("Native module");
219
318
  const nodePty = await probeNodePty();
@@ -1,2 +1,2 @@
1
- import { a as describeAgentResolvability, d as setCustomAgents, l as listAgentIds } from "./adapters-Dlvu1FVB.js";
1
+ import { a as describeAgentResolvability, d as setCustomAgents, l as listAgentIds } from "./adapters-gn7ulNO4.js";
2
2
  export { describeAgentResolvability, listAgentIds, setCustomAgents };
@@ -1,9 +1,12 @@
1
- import { o as relayPath } from "./runtime-paths-CVvq20A4.js";
2
- import { c as PendingPromptQuestion, g as toolDetailFieldValue, h as firstDetailLine, o as PROMPT_DETAIL_HIDDEN_CHARACTERS } from "./session-CJtOveHl.js";
3
- import { r as execFileSync } from "./child-process-safe-Cz22RwlG.js";
1
+ import { o as relayPath } from "./runtime-paths-BPoxENmK.js";
2
+ import { _ as firstDetailLine, l as PendingPromptQuestion, s as PROMPT_DETAIL_HIDDEN_CHARACTERS, v as toolDetailFieldValue } from "./session-fc4pFbon.js";
3
+ import { r as execFileSync } from "./child-process-safe-Ch-Zgxqs.js";
4
4
  import { n as resolveAgentCommand, t as buildAgentSpawnCommand } from "./spawn-command-Cc40FiZI.js";
5
- import { c as buildAgentEnv, i as isSpinnerChar, n as classifyAgentTitle, r as isBrailleChar, s as buildHookCommand, t as grok } from "./grok-COekzawt.js";
6
- import "./contracts-JKChPhZ0.js";
5
+ import { n as isRecord } from "./coerce-NSst8oRP.js";
6
+ import { c as buildAgentEnv, i as isSpinnerChar, n as classifyAgentTitle, r as isBrailleChar, t as grok } from "./grok-CXKeSt2z.js";
7
+ import { t as buildHookCommand } from "./hook-command-core-DclEoE9e.js";
8
+ import "./contracts-3LkrilQX.js";
9
+ import { n as mergeCodexPreToolUse, t as HOOK_TOOLS } from "./hook-tools-UX2fmt81.js";
7
10
  import fs from "node:fs";
8
11
  //#region session/core/anti-slop-prompt.ts
9
12
  var ANTI_SLOP_NOTE = [
@@ -85,9 +88,6 @@ function mapHookPromptDetail(event, payload) {
85
88
  question: answerableQuestionOf(toolName, payload?.tool_input)
86
89
  };
87
90
  }
88
- function isRecord(value) {
89
- return typeof value === "object" && value !== null && !Array.isArray(value);
90
- }
91
91
  function answerableQuestionOf(toolName, toolInput) {
92
92
  if (toolName !== "AskUserQuestion" || !isRecord(toolInput)) return null;
93
93
  const questions = toolInput.questions;
@@ -121,6 +121,8 @@ function isWholeBashCommandShown(toolName, toolInput, fieldValue, summary) {
121
121
  function mapHookToSignal$1(event, payload) {
122
122
  switch (String(event || "").toLowerCase()) {
123
123
  case "sessionstart": return "session-start";
124
+ case "precompact": return "compaction-start";
125
+ case "postcompact": return "compaction-end";
124
126
  case "sessionend": return "session-end";
125
127
  case "userpromptsubmit": return "resume";
126
128
  case "stop": return "ready";
@@ -176,7 +178,15 @@ var settingsArgs = (settingsPath) => ["--settings", settingsPath];
176
178
  function buildArgs$1({ dangerouslySkipPermissions = false, resumeSessionId = null, extraArgs = [], antiSlopPrompt = false, initialPrompt = null } = {}) {
177
179
  const args = dangerouslySkipPermissions ? ["--dangerously-skip-permissions"] : [];
178
180
  if (resumeSessionId) args.push("--resume", resumeSessionId);
179
- if (extraArgs.length > 0) args.push(...extraArgs);
181
+ for (let index = 0; index < extraArgs.length; index += 1) {
182
+ const argument = extraArgs[index];
183
+ if (resumeSessionId && argument === "--session-id") {
184
+ index += 1;
185
+ continue;
186
+ }
187
+ if (resumeSessionId && argument.startsWith("--session-id=")) continue;
188
+ args.push(argument);
189
+ }
180
190
  args.push(...buildAntiSlopArgs(antiSlopPrompt));
181
191
  if (initialPrompt != null) args.push(initialPrompt);
182
192
  return args;
@@ -201,6 +211,7 @@ var claudeCode = {
201
211
  resume: true,
202
212
  statusLine: true,
203
213
  rtk: true,
214
+ saneYolo: true,
204
215
  antiSlop: true,
205
216
  compactQuiet: true,
206
217
  skipPermissionsFlag: true,
@@ -219,9 +230,7 @@ var claudeCode = {
219
230
  var ID = "codex";
220
231
  var COMMAND_NAME = "codex";
221
232
  var RELAY_PATH = relayPath("hook-relay");
222
- var RTK_RELAY_PATH = relayPath("rtk-relay");
223
- var RTK_HOOK_EVENT = "PreToolUse";
224
- var RTK_TOOL_MATCHER = "Bash";
233
+ var HOOK_TOOL_RELAY_PATH = relayPath("hook-tool-relay");
225
234
  var HOOK_EVENTS = [
226
235
  "SessionStart",
227
236
  "SessionEnd",
@@ -285,17 +294,23 @@ function mayContributeHooks(configText) {
285
294
  if (typeof configText !== "string") return false;
286
295
  return HOOKS_DECLARATION_RE.test(configText);
287
296
  }
288
- function buildHookArgs({ relayPath = RELAY_PATH, events = HOOK_EVENTS, bypassHookTrust = false, rtkRewrites = false, rtkRelayPath = RTK_RELAY_PATH } = {}) {
297
+ function buildHookArgs({ relayPath = RELAY_PATH, events = HOOK_EVENTS, bypassHookTrust = false, hookTools = [], hookToolRelayPath = HOOK_TOOL_RELAY_PATH } = {}) {
289
298
  const args = bypassHookTrust ? [TRUST_BYPASS_FLAG] : [];
299
+ const preToolUseGroups = [];
290
300
  for (const event of events) {
291
301
  const command = buildHookCommand(relayPath, event);
292
302
  if (!command) return null;
303
+ if (event === "PreToolUse") {
304
+ preToolUseGroups.push(`{hooks=[{type='command',command='${command}'}]}`);
305
+ continue;
306
+ }
293
307
  args.push("-c", `hooks.${event}=[{hooks=[{type='command',command='${command}'}]}]`);
294
308
  }
295
- if (!rtkRewrites) return args;
296
- const rtkCommand = buildHookCommand(rtkRelayPath, RTK_HOOK_EVENT);
297
- if (!rtkCommand) return args;
298
- args.push("-c", `hooks.${RTK_HOOK_EVENT}=[{matcher='${RTK_TOOL_MATCHER}',hooks=[{type='command',command='${rtkCommand}'}]}]`);
309
+ const groups = hookTools.flatMap((tool) => {
310
+ const group = HOOK_TOOLS[tool.id].codexGroup(tool, hookToolRelayPath);
311
+ return group ? [group] : [];
312
+ });
313
+ args.push(...mergeCodexPreToolUse([...preToolUseGroups, ...groups]));
299
314
  return args;
300
315
  }
301
316
  var hooks = {
@@ -337,10 +352,22 @@ var SKIP_PERMISSIONS_ARGS = [
337
352
  "-s",
338
353
  "workspace-write"
339
354
  ];
340
- function buildArgs({ dangerouslySkipPermissions = false, resumeSessionId = null, extraArgs = [], initialPrompt = null } = {}) {
355
+ var OUTER_SANDBOX_ARGS = ["-s", "danger-full-access"];
356
+ var OUTER_SANDBOX_SKIP_PERMISSIONS_ARGS = [
357
+ "-a",
358
+ "never",
359
+ ...OUTER_SANDBOX_ARGS
360
+ ];
361
+ function permissionArgs(dangerouslySkipPermissions, hasOuterSandbox) {
362
+ if (hasOuterSandbox && dangerouslySkipPermissions) return OUTER_SANDBOX_SKIP_PERMISSIONS_ARGS;
363
+ if (hasOuterSandbox) return OUTER_SANDBOX_ARGS;
364
+ if (dangerouslySkipPermissions) return SKIP_PERMISSIONS_ARGS;
365
+ return [];
366
+ }
367
+ function buildArgs({ dangerouslySkipPermissions = false, resumeSessionId = null, extraArgs = [], initialPrompt = null, hasOuterSandbox = false } = {}) {
341
368
  const args = [...UPDATE_CHECK_ARGS];
342
369
  if (resumeSessionId) args.push("resume", resumeSessionId);
343
- if (dangerouslySkipPermissions) args.push(...SKIP_PERMISSIONS_ARGS);
370
+ args.push(...permissionArgs(dangerouslySkipPermissions, hasOuterSandbox));
344
371
  if (extraArgs.length > 0) args.push(...extraArgs);
345
372
  if (initialPrompt != null) args.push(initialPrompt);
346
373
  return args;
@@ -368,7 +395,7 @@ var codex = {
368
395
  sessionIdOf,
369
396
  HOOK_EVENTS,
370
397
  RELAY_PATH,
371
- RTK_RELAY_PATH,
398
+ HOOK_TOOL_RELAY_PATH,
372
399
  TRUST_BYPASS_FLAG,
373
400
  SKIP_PERMISSIONS_ARGS,
374
401
  UPDATE_CHECK_ARGS,
@@ -379,6 +406,7 @@ var codex = {
379
406
  resume: true,
380
407
  statusLine: false,
381
408
  rtk: true,
409
+ saneYolo: true,
382
410
  antiSlop: false,
383
411
  compactQuiet: false,
384
412
  skipPermissionsFlag: true,
@@ -398,6 +426,7 @@ var TITLE_ONLY_CAPABILITIES = Object.freeze({
398
426
  resume: false,
399
427
  statusLine: false,
400
428
  rtk: false,
429
+ saneYolo: false,
401
430
  antiSlop: false,
402
431
  compactQuiet: false,
403
432
  skipPermissionsFlag: false,
@@ -0,0 +1,135 @@
1
+ import { t as errorCode } from "./text-DoirglWV.js";
2
+ import { a as AgentDispatchRequest, n as AGENT_URL_ENV } from "./session-fc4pFbon.js";
3
+ import { i as resolveHookTarget } from "./hook-relay-core-DA_bz2KA.js";
4
+ import { n as decideSandboxProxy } from "./sandbox-proxy-core-hnQgKR0T.js";
5
+ import { a as isAgentApiVerb } from "./agent-api-core-hVcT26JW.js";
6
+ import http from "node:http";
7
+ //#region server/agent-api-cli.ts
8
+ var AGENT_PATH_PREFIX = "/agent/";
9
+ var BODY_BUILDERS = {
10
+ spawn: (rest) => {
11
+ const prompt = rest.join(" ").trim();
12
+ return prompt ? { prompt } : null;
13
+ },
14
+ attention: (rest) => {
15
+ const note = rest.join(" ").trim();
16
+ return note ? { note } : null;
17
+ },
18
+ board: () => ({}),
19
+ dispatch: (rest) => {
20
+ const parsed = AgentDispatchRequest.safeParse(rest[0] === "--ready" ? { readyIntent: rest[1] } : { workId: rest[0] });
21
+ return parsed.success ? parsed.data : null;
22
+ }
23
+ };
24
+ function bodySaysOk(text) {
25
+ try {
26
+ const parsed = JSON.parse(text);
27
+ return typeof parsed === "object" && parsed !== null && parsed.ok === true;
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+ function verbEndpoint(rawUrl, verb) {
33
+ const target = resolveHookTarget(rawUrl, verb, AGENT_PATH_PREFIX);
34
+ if (!target.url) return { ok: false };
35
+ const endpoint = new URL(target.url);
36
+ const token = endpoint.searchParams.get("t") || "";
37
+ endpoint.search = "";
38
+ return {
39
+ ok: true,
40
+ endpoint,
41
+ token
42
+ };
43
+ }
44
+ async function postDirectly(endpoint, headers, bodyText) {
45
+ const response = await fetch(endpoint, {
46
+ method: "POST",
47
+ headers,
48
+ body: bodyText
49
+ });
50
+ return {
51
+ hasSuccessStatus: response.ok,
52
+ text: await response.text()
53
+ };
54
+ }
55
+ function postThroughSandboxProxy(sandboxProxy, endpoint, headers, bodyText) {
56
+ const proxyHeaders = sandboxProxy.authorization ? { "proxy-authorization": sandboxProxy.authorization } : {};
57
+ return new Promise((resolve, reject) => {
58
+ const request = http.request({
59
+ host: sandboxProxy.hostname,
60
+ port: sandboxProxy.port,
61
+ method: "POST",
62
+ path: endpoint.href,
63
+ headers: {
64
+ ...headers,
65
+ ...proxyHeaders,
66
+ host: endpoint.host,
67
+ "content-length": String(Buffer.byteLength(bodyText))
68
+ }
69
+ }, (response) => {
70
+ const chunks = [];
71
+ response.on("data", (chunk) => {
72
+ chunks.push(chunk);
73
+ });
74
+ response.on("error", reject);
75
+ response.on("end", () => {
76
+ const statusCode = response.statusCode ?? 0;
77
+ resolve({
78
+ hasSuccessStatus: statusCode >= 200 && statusCode < 300,
79
+ text: Buffer.concat(chunks).toString("utf8")
80
+ });
81
+ });
82
+ });
83
+ request.on("error", reject);
84
+ request.end(bodyText);
85
+ });
86
+ }
87
+ function wasConnectionRefused(error) {
88
+ return errorCode(error?.cause) === "ECONNREFUSED";
89
+ }
90
+ async function postToAgentIngress(endpoint, token, body) {
91
+ const headers = {
92
+ "content-type": "application/json",
93
+ authorization: `Bearer ${token}`
94
+ };
95
+ const bodyText = JSON.stringify(body);
96
+ try {
97
+ return await postDirectly(endpoint, headers, bodyText);
98
+ } catch (error) {
99
+ if (!wasConnectionRefused(error)) throw error;
100
+ const sandboxProxy = decideSandboxProxy(process.env);
101
+ if (!sandboxProxy) throw error;
102
+ return postThroughSandboxProxy(sandboxProxy, endpoint, headers, bodyText);
103
+ }
104
+ }
105
+ async function runAgentApiCli(args) {
106
+ const verb = args[0];
107
+ if (!verb || !isAgentApiVerb(verb)) {
108
+ console.error(`glimmervoid: ${verb || "that"} is not an agent command`);
109
+ return 1;
110
+ }
111
+ const body = BODY_BUILDERS[verb](args.slice(1));
112
+ if (!body) {
113
+ if (verb === "dispatch") {
114
+ console.error("glimmervoid dispatch: expected a work id matching wrk-[0-9a-f]{16}");
115
+ return 1;
116
+ }
117
+ console.error(`glimmervoid ${verb}: this command needs text, for example "glimmervoid ${verb} check the failing test"`);
118
+ return 1;
119
+ }
120
+ const rawUrl = process.env[AGENT_URL_ENV];
121
+ if (!rawUrl) {
122
+ console.error(`glimmervoid: ${AGENT_URL_ENV} is not set, so this is not a Glimmervoid session with the agent API turned on`);
123
+ return 1;
124
+ }
125
+ const target = verbEndpoint(rawUrl, verb);
126
+ if (!target.ok) {
127
+ console.error(`glimmervoid: ${AGENT_URL_ENV} must be a loopback http url under ${AGENT_PATH_PREFIX}, so nothing was sent`);
128
+ return 1;
129
+ }
130
+ const reply = await postToAgentIngress(target.endpoint, target.token, body);
131
+ console.log(reply.text);
132
+ return reply.hasSuccessStatus && bodySaysOk(reply.text) ? 0 : 1;
133
+ }
134
+ //#endregion
135
+ export { runAgentApiCli };
@@ -1,4 +1,4 @@
1
- import { a as AgentSpawnRequest, i as AgentAttentionRequest, t as AGENT_API_VERBS } from "./session-CJtOveHl.js";
1
+ import { a as AgentDispatchRequest, i as AgentAttentionRequest, o as AgentSpawnRequest, t as AGENT_API_VERBS } from "./session-fc4pFbon.js";
2
2
  import crypto from "node:crypto";
3
3
  var MAX_SPAWNS_IN_FLIGHT = 1;
4
4
  var SPAWNABLE_DEPTH = 0;
@@ -67,6 +67,15 @@ function parseAgentVerb(verb, payload) {
67
67
  status: 404,
68
68
  error: REFUSAL_REASON
69
69
  };
70
+ if (verb === "dispatch") {
71
+ const parsed = AgentDispatchRequest.safeParse(payload);
72
+ if (!parsed.success) return refuseInvalidBody(parsed.error.issues);
73
+ return {
74
+ ok: true,
75
+ verb,
76
+ request: parsed.data
77
+ };
78
+ }
70
79
  if (verb === "board") return {
71
80
  ok: true,
72
81
  verb