glimmervoid 0.29.1 → 0.30.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 (41) hide show
  1. package/README.md +7 -9
  2. package/dist/bin/glimmervoid.js +28 -12
  3. package/dist/chunks/{adapters-DIavI9-i.js → adapters-Dho1VSY5.js} +1 -1
  4. package/dist/chunks/{adapters-Dlvu1FVB.js → adapters-zPDx1Xox.js} +23 -14
  5. package/dist/chunks/{agent-api-cli-ClOzm28U.js → agent-api-cli-D0MmtEdz.js} +2 -2
  6. package/dist/chunks/{agent-api-core-D6LHrpSL.js → agent-api-core-Dz0r21y9.js} +1 -1
  7. package/dist/chunks/{agent-setup-cli-C1CQ8Nkk.js → agent-setup-cli-B5Atxr3F.js} +5 -3
  8. package/dist/chunks/{child-process-safe-Cz22RwlG.js → child-process-safe-DRGsp0TE.js} +1 -1
  9. package/dist/chunks/child-process-safe-DvQjXsgz.js +2 -0
  10. package/dist/chunks/config-store-CC8T3okC.js +2 -0
  11. package/dist/chunks/{config-store-DtPUFVaO.js → config-store-mm7MgzoL.js} +13 -1
  12. package/dist/chunks/{contracts-JKChPhZ0.js → contracts-DGkKNh9W.js} +25 -3
  13. package/dist/chunks/{grok-COekzawt.js → grok-DuU8xVLZ.js} +54 -25
  14. package/dist/chunks/hook-command-core-DclEoE9e.js +14 -0
  15. package/dist/chunks/hook-tools-pSJia7ks.js +74 -0
  16. package/dist/chunks/{main-BVDjy7vH.js → main-CuIZQPhZ.js} +975 -156
  17. package/dist/chunks/{pair-cli-BQQnhkkc.js → pair-cli-w-gCAF-1.js} +2 -2
  18. package/dist/chunks/{remote-config-BHoDFCpo.js → remote-config-C2ytQwrs.js} +8 -4
  19. package/dist/chunks/rtk-hook-core-Bl6gYHPN.js +133 -0
  20. package/dist/chunks/rtk-resolver-CLo-gZzw.js +21 -0
  21. package/dist/chunks/rtk-resolver-CMQAidlI.js +2 -0
  22. package/dist/chunks/{runtime-paths-CVvq20A4.js → runtime-paths-Ci_ksSkj.js} +26 -2
  23. package/dist/chunks/runtime-paths-JLrZ3wyi.js +2 -0
  24. package/dist/chunks/{sandbox-deps-BwvbDBDl.js → sandbox-deps-Dd5-kZnf.js} +1 -1
  25. package/dist/chunks/{session-CJtOveHl.js → session-Cez8aIWg.js} +3 -1
  26. package/dist/chunks/{visions-cli-DUZnopGY.js → visions-cli-BXQFIBCX.js} +1 -1
  27. package/dist/chunks/{visions-setup-C_kJobsy.js → visions-setup-CvVnOe1r.js} +2 -2
  28. package/dist/client/assets/index-B1MFijjb.css +1 -0
  29. package/dist/client/assets/index-CU1iPPdn.js +249 -0
  30. package/dist/client/index.html +2 -2
  31. package/dist/scripts/postinstall-path-check.js +1 -1
  32. package/dist/server/index.js +2 -2
  33. package/dist/session/{rtk-relay.js → hook-tool-relay.js} +32 -15
  34. package/dist/session/statusline-relay.js +1 -1
  35. package/package.json +2 -1
  36. package/dist/chunks/config-store-DtUGwi4-.js +0 -2
  37. package/dist/chunks/rtk-hook-core-DRYrKwYP.js +0 -26
  38. package/dist/chunks/rtk-resolver-DDVVNRBT.js +0 -2
  39. package/dist/chunks/rtk-resolver-oGmO-ew7.js +0 -58
  40. package/dist/client/assets/index-BTwYgSAH.js +0 -248
  41. 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
 
@@ -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,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
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";
3
+ import { a as packageRoot } from "../chunks/runtime-paths-Ci_ksSkj.js";
4
+ import { t as AGENT_API_VERBS } from "../chunks/session-Cez8aIWg.js";
5
+ import { i as execSync } from "../chunks/child-process-safe-DRGsp0TE.js";
6
6
  import { t as renderTable } from "../chunks/ascii-figure-core-pDAwtlKO.js";
7
7
  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";
8
+ import { i as sandboxDoctorRows, r as probeSandboxDependencies } from "../chunks/sandbox-deps-Dd5-kZnf.js";
9
9
  import { i as pnpmGlobalBinDir, n as npmGlobalBinDir, r as onPath, t as formatPathNotice } from "../chunks/path-doctor-C23VAqeu.js";
10
10
  import fs from "node:fs";
11
11
  import path from "node:path";
@@ -58,19 +58,19 @@ var SUBCOMMAND_RUNNERS = new Map([
58
58
  return 0;
59
59
  }],
60
60
  ["pair", async (commandArgs) => {
61
- const { runPairCli } = await import("../chunks/pair-cli-BQQnhkkc.js");
61
+ const { runPairCli } = await import("../chunks/pair-cli-w-gCAF-1.js");
62
62
  return runPairCli(commandArgs.slice(1));
63
63
  }],
64
64
  ["agent", async (commandArgs) => {
65
- const { runAgentSetupCli } = await import("../chunks/agent-setup-cli-C1CQ8Nkk.js");
65
+ const { runAgentSetupCli } = await import("../chunks/agent-setup-cli-B5Atxr3F.js");
66
66
  return runAgentSetupCli(commandArgs.slice(1));
67
67
  }],
68
68
  ["visions", async (commandArgs) => {
69
- const { runVisionsCli } = await import("../chunks/visions-cli-DUZnopGY.js");
69
+ const { runVisionsCli } = await import("../chunks/visions-cli-BXQFIBCX.js");
70
70
  return runVisionsCli(commandArgs.slice(1));
71
71
  }],
72
72
  ...AGENT_API_VERBS.map((verb) => [verb, async (commandArgs) => {
73
- const { runAgentApiCli } = await import("../chunks/agent-api-cli-ClOzm28U.js");
73
+ const { runAgentApiCli } = await import("../chunks/agent-api-cli-D0MmtEdz.js");
74
74
  return runAgentApiCli(commandArgs);
75
75
  }])
76
76
  ]);
@@ -118,7 +118,7 @@ async function readDeclaredCustomAgents() {
118
118
  declared: [],
119
119
  error: null
120
120
  };
121
- const { loadConfigFile } = await import("../chunks/config-store-DtUGwi4-.js");
121
+ const { loadConfigFile } = await import("../chunks/config-store-CC8T3okC.js");
122
122
  try {
123
123
  const loaded = loadConfigFile(decided.path, { exitOnError: false });
124
124
  if (!loaded.config) return {
@@ -194,7 +194,7 @@ async function runDoctor() {
194
194
  }
195
195
  switchSection("Agents");
196
196
  try {
197
- const { listAgentIds, describeAgentResolvability, setCustomAgents } = await import("../chunks/adapters-DIavI9-i.js");
197
+ const { listAgentIds, describeAgentResolvability, setCustomAgents } = await import("../chunks/adapters-Dho1VSY5.js");
198
198
  const declaredCustomAgents = await readDeclaredCustomAgents();
199
199
  if (declaredCustomAgents.error) line("config", declaredCustomAgents.error);
200
200
  setCustomAgents(declaredCustomAgents.declared);
@@ -202,7 +202,7 @@ async function runDoctor() {
202
202
  const { label, path: resolvedPath } = describeAgentResolvability(id);
203
203
  line(`${id} (${label})`, resolvedPath ?? "not found on PATH");
204
204
  }
205
- const { inspectGrokAgentSetup } = await import("../chunks/agent-setup-cli-C1CQ8Nkk.js");
205
+ const { inspectGrokAgentSetup } = await import("../chunks/agent-setup-cli-B5Atxr3F.js");
206
206
  const grokSetup = inspectGrokAgentSetup();
207
207
  line("grok hook setup", `${grokSetup.classification}: ${grokSetup.filePath}`);
208
208
  } catch (err) {
@@ -210,11 +210,27 @@ async function runDoctor() {
210
210
  }
211
211
  switchSection("rtk");
212
212
  try {
213
- const { getRtkPath } = await import("../chunks/rtk-resolver-DDVVNRBT.js");
213
+ const { getRtkPath } = await import("../chunks/rtk-resolver-CMQAidlI.js");
214
214
  line("rtk", getRtkPath() || "not installed (Glimmervoid installs it when the rtk setting is on)");
215
215
  } catch (err) {
216
216
  line("rtk", `probe failed: ${firstLineOf(err)}`);
217
217
  }
218
+ switchSection("Sane YOLO");
219
+ try {
220
+ const { resolvePackageBin } = await import("../chunks/runtime-paths-JLrZ3wyi.js");
221
+ const { execFileSync } = await import("../chunks/child-process-safe-DvQjXsgz.js");
222
+ const { inspectGrokAgentSetup } = await import("../chunks/agent-setup-cli-B5Atxr3F.js");
223
+ const binPath = resolvePackageBin("cc-safety-net", "cc-safety-net");
224
+ line("Sane YOLO", binPath || "not installed");
225
+ if (binPath) line("cc-safety-net version", execFileSync(process.execPath, [binPath, "--version"], {
226
+ encoding: "utf8",
227
+ timeout: 3e3
228
+ }).trim());
229
+ line("Codex hook trust", "Sane YOLO is inactive unless Codex trusts the injected hooks.");
230
+ line("Grok Sane YOLO", inspectGrokAgentSetup().saneYoloReady ? "installed" : "inactive; run glimmervoid agent setup grok");
231
+ } catch (err) {
232
+ line("Sane YOLO", `probe failed: ${firstLineOf(err)}`);
233
+ }
218
234
  switchSection("Native module");
219
235
  const nodePty = await probeNodePty();
220
236
  if (nodePty.ok) line("node-pty", "loads OK");
@@ -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-zPDx1Xox.js";
2
2
  export { describeAgentResolvability, listAgentIds, setCustomAgents };
@@ -1,9 +1,11 @@
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-Ci_ksSkj.js";
2
+ import { _ as toolDetailFieldValue, c as PendingPromptQuestion, g as firstDetailLine, o as PROMPT_DETAIL_HIDDEN_CHARACTERS } from "./session-Cez8aIWg.js";
3
+ import { r as execFileSync } from "./child-process-safe-DRGsp0TE.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 { i as isSpinnerChar, n as classifyAgentTitle, r as isBrailleChar, s as buildAgentEnv, t as grok } from "./grok-DuU8xVLZ.js";
6
+ import { t as buildHookCommand } from "./hook-command-core-DclEoE9e.js";
7
+ import "./contracts-DGkKNh9W.js";
8
+ import { n as mergeCodexPreToolUse, t as HOOK_TOOLS } from "./hook-tools-pSJia7ks.js";
7
9
  import fs from "node:fs";
8
10
  //#region session/core/anti-slop-prompt.ts
9
11
  var ANTI_SLOP_NOTE = [
@@ -201,6 +203,7 @@ var claudeCode = {
201
203
  resume: true,
202
204
  statusLine: true,
203
205
  rtk: true,
206
+ saneYolo: true,
204
207
  antiSlop: true,
205
208
  compactQuiet: true,
206
209
  skipPermissionsFlag: true,
@@ -219,9 +222,7 @@ var claudeCode = {
219
222
  var ID = "codex";
220
223
  var COMMAND_NAME = "codex";
221
224
  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";
225
+ var HOOK_TOOL_RELAY_PATH = relayPath("hook-tool-relay");
225
226
  var HOOK_EVENTS = [
226
227
  "SessionStart",
227
228
  "SessionEnd",
@@ -285,17 +286,23 @@ function mayContributeHooks(configText) {
285
286
  if (typeof configText !== "string") return false;
286
287
  return HOOKS_DECLARATION_RE.test(configText);
287
288
  }
288
- function buildHookArgs({ relayPath = RELAY_PATH, events = HOOK_EVENTS, bypassHookTrust = false, rtkRewrites = false, rtkRelayPath = RTK_RELAY_PATH } = {}) {
289
+ function buildHookArgs({ relayPath = RELAY_PATH, events = HOOK_EVENTS, bypassHookTrust = false, hookTools = [], hookToolRelayPath = HOOK_TOOL_RELAY_PATH } = {}) {
289
290
  const args = bypassHookTrust ? [TRUST_BYPASS_FLAG] : [];
291
+ const preToolUseGroups = [];
290
292
  for (const event of events) {
291
293
  const command = buildHookCommand(relayPath, event);
292
294
  if (!command) return null;
295
+ if (event === "PreToolUse") {
296
+ preToolUseGroups.push(`{hooks=[{type='command',command='${command}'}]}`);
297
+ continue;
298
+ }
293
299
  args.push("-c", `hooks.${event}=[{hooks=[{type='command',command='${command}'}]}]`);
294
300
  }
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}'}]}]`);
301
+ const groups = hookTools.flatMap((tool) => {
302
+ const group = HOOK_TOOLS[tool.id].codexGroup(tool, hookToolRelayPath);
303
+ return group ? [group] : [];
304
+ });
305
+ args.push(...mergeCodexPreToolUse([...preToolUseGroups, ...groups]));
299
306
  return args;
300
307
  }
301
308
  var hooks = {
@@ -368,7 +375,7 @@ var codex = {
368
375
  sessionIdOf,
369
376
  HOOK_EVENTS,
370
377
  RELAY_PATH,
371
- RTK_RELAY_PATH,
378
+ HOOK_TOOL_RELAY_PATH,
372
379
  TRUST_BYPASS_FLAG,
373
380
  SKIP_PERMISSIONS_ARGS,
374
381
  UPDATE_CHECK_ARGS,
@@ -379,6 +386,7 @@ var codex = {
379
386
  resume: true,
380
387
  statusLine: false,
381
388
  rtk: true,
389
+ saneYolo: true,
382
390
  antiSlop: false,
383
391
  compactQuiet: false,
384
392
  skipPermissionsFlag: true,
@@ -398,6 +406,7 @@ var TITLE_ONLY_CAPABILITIES = Object.freeze({
398
406
  resume: false,
399
407
  statusLine: false,
400
408
  rtk: false,
409
+ saneYolo: false,
401
410
  antiSlop: false,
402
411
  compactQuiet: false,
403
412
  skipPermissionsFlag: false,
@@ -1,6 +1,6 @@
1
- import { n as AGENT_URL_ENV } from "./session-CJtOveHl.js";
1
+ import { n as AGENT_URL_ENV } from "./session-Cez8aIWg.js";
2
2
  import { i as resolveHookTarget } from "./hook-relay-core-BHfYFnYV.js";
3
- import { a as isAgentApiVerb } from "./agent-api-core-D6LHrpSL.js";
3
+ import { a as isAgentApiVerb } from "./agent-api-core-Dz0r21y9.js";
4
4
  //#region server/agent-api-cli.ts
5
5
  var AGENT_PATH_PREFIX = "/agent/";
6
6
  var BODY_BUILDERS = {
@@ -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 AgentSpawnRequest, i as AgentAttentionRequest, t as AGENT_API_VERBS } from "./session-Cez8aIWg.js";
2
2
  import crypto from "node:crypto";
3
3
  var MAX_SPAWNS_IN_FLIGHT = 1;
4
4
  var SPAWNABLE_DEPTH = 0;
@@ -1,4 +1,4 @@
1
- import { a as classifyGrokHooksFile, o as renderGrokHooksFile, t as grok } from "./grok-COekzawt.js";
1
+ import { a as classifyGrokHooksFile, o as renderGrokHooksFile, t as grok } from "./grok-DuU8xVLZ.js";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import crypto from "node:crypto";
@@ -15,6 +15,7 @@ function setupInputs(env = process.env) {
15
15
  return {
16
16
  filePath: grok.hooksFilePath(env),
17
17
  relayPath: grok.RELAY_PATH,
18
+ hookToolRelayPath: grok.HOOK_TOOL_RELAY_PATH,
18
19
  events: grok.HOOK_EVENTS,
19
20
  managedEventSets: grok.MANAGED_HOOK_EVENT_SETS
20
21
  };
@@ -37,7 +38,8 @@ function inspectGrokAgentSetup({ env = process.env, readFileSync = fs.readFileSy
37
38
  }
38
39
  return {
39
40
  filePath: inputs.filePath,
40
- classification: classifyGrokHooksFile(contents, inputs)
41
+ classification: classifyGrokHooksFile(contents, inputs),
42
+ saneYoloReady: contents === renderGrokHooksFile(inputs)
41
43
  };
42
44
  }
43
45
  function assertRealDirectory(directory, fileSystem, requireOwnership = true) {
@@ -96,7 +98,7 @@ function runGrokSetup({ env, fileSystem, log, error }) {
96
98
  env,
97
99
  readFileSync: fileSystem.readFileSync
98
100
  });
99
- if (inspection.classification === "current") {
101
+ if (inspection.classification === "current" && inspection.saneYoloReady) {
100
102
  log(`Grok hooks are already current at ${inputs.filePath}`);
101
103
  return 0;
102
104
  }
@@ -66,4 +66,4 @@ function spawn(file, ...rest) {
66
66
  }
67
67
  var execFileAsync = promisify(execFile);
68
68
  //#endregion
69
- export { spawn as a, execSync as i, execFileAsync as n, execFileSync as r, execFile as t };
69
+ export { hide as a, execSync as i, execFileAsync as n, spawn as o, execFileSync as r, execFile as t };
@@ -0,0 +1,2 @@
1
+ import { r as execFileSync } from "./child-process-safe-DRGsp0TE.js";
2
+ export { execFileSync };
@@ -0,0 +1,2 @@
1
+ import { c as loadConfigFile } from "./config-store-mm7MgzoL.js";
2
+ export { loadConfigFile };
@@ -1,5 +1,5 @@
1
1
  import { n as glimmervoidHomeDir$1, t as decideConfigPath } from "./config-path-core-DFt-TemS.js";
2
- import { I as TELEMETRY_KILL_SWITCH_FLAG, N as FeatureFlagsResponse, R as AGENT_ID_SHAPE_MESSAGE, U as RUNTIME_CONFIG_SCALAR_KEYS, V as Config, W as configIssueMessage, q as BranchGcFileSettings, z as BUILTIN_AGENT_IDS } from "./contracts-JKChPhZ0.js";
2
+ import { I as TELEMETRY_KILL_SWITCH_FLAG, N as FeatureFlagsResponse, R as AGENT_ID_SHAPE_MESSAGE, U as RUNTIME_CONFIG_SCALAR_KEYS, V as Config, W as configIssueMessage, q as BranchGcFileSettings, z as BUILTIN_AGENT_IDS } from "./contracts-DGkKNh9W.js";
3
3
  import { r as SOURCE_NAMES } from "./ingest-core-DWI1Mbu4.js";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
@@ -899,6 +899,12 @@ var DEFAULT_CONFIG = {
899
899
  recordSignals: true,
900
900
  trace: { enabled: true },
901
901
  planReview: { enabled: true },
902
+ taskTitle: { refiner: {
903
+ enabled: true,
904
+ model: "haiku",
905
+ minIntervalSeconds: 60,
906
+ timeoutSeconds: 60
907
+ } },
902
908
  changeMap: { narrator: {
903
909
  enabled: false,
904
910
  engine: "claude",
@@ -913,6 +919,7 @@ var DEFAULT_CONFIG = {
913
919
  skipPermissionsByDefault: false,
914
920
  antiSlopPrompt: false,
915
921
  rtk: false,
922
+ saneYolo: true,
916
923
  telegramNotifications: false,
917
924
  integrationBranch: null,
918
925
  worktreeRoot: "",
@@ -1269,6 +1276,7 @@ function createConfigStore({ settingsDefaults } = {}) {
1269
1276
  telemetryForcedOff: isTelemetryForcedOff(process.env),
1270
1277
  antiSlopPrompt: config.antiSlopPrompt ?? effectiveDefaults.antiSlopPrompt,
1271
1278
  rtk: config.rtk ?? effectiveDefaults.rtk,
1279
+ saneYolo: config.saneYolo ?? effectiveDefaults.saneYolo,
1272
1280
  checkForUpdates: config.checkForUpdates ?? effectiveDefaults.checkForUpdates,
1273
1281
  updateChannel: config.updateChannel ?? effectiveDefaults.updateChannel,
1274
1282
  autoResume: config.autoResume ?? effectiveDefaults.autoResume,
@@ -1278,6 +1286,7 @@ function createConfigStore({ settingsDefaults } = {}) {
1278
1286
  worktreeRoot: config.worktreeRoot ?? effectiveDefaults.worktreeRoot,
1279
1287
  worktreeShare: config.worktreeShare ?? effectiveDefaults.worktreeShare,
1280
1288
  repoRoots: config.repoRoots,
1289
+ taskTitle: config.taskTitle ? { ...config.taskTitle } : { ...DEFAULT_CONFIG.taskTitle },
1281
1290
  changeMap: config.changeMap ? { ...config.changeMap } : null,
1282
1291
  branchGc: { ...config.branchGc },
1283
1292
  postTurnChecks: config.postTurnChecks ? { ...config.postTurnChecks } : { ...DEFAULT_CONFIG.postTurnChecks },
@@ -1318,6 +1327,7 @@ function createConfigStore({ settingsDefaults } = {}) {
1318
1327
  config.repoRoots = newConfig.repoRoots || [];
1319
1328
  if (newConfig.postTurnChecks != null) config.postTurnChecks = newConfig.postTurnChecks;
1320
1329
  if (newConfig.worktreeShare != null) config.worktreeShare = newConfig.worktreeShare;
1330
+ if (newConfig.taskTitle != null) config.taskTitle = newConfig.taskTitle;
1321
1331
  if (newConfig.changeMap != null) config.changeMap = newConfig.changeMap;
1322
1332
  if (newConfig.branchGc != null) config.branchGc = resolveBranchGc(newConfig.branchGc);
1323
1333
  if (newConfig.visions != null) config.visions = newConfig.visions;
@@ -1331,6 +1341,8 @@ function createConfigStore({ settingsDefaults } = {}) {
1331
1341
  if (newConfig.telemetry != null) config.telemetry = newConfig.telemetry;
1332
1342
  if (newConfig.workflows == null) delete config.workflows;
1333
1343
  if (newConfig.workflows != null) config.workflows = newConfig.workflows;
1344
+ if (newConfig.coder == null) delete config.coder;
1345
+ if (newConfig.coder != null) config.coder = newConfig.coder;
1334
1346
  config.customAgents = newConfig.customAgents ?? [];
1335
1347
  config.hooks = Array.isArray(newConfig.hooks) ? newConfig.hooks : [];
1336
1348
  if (newConfig.port != null && newConfig.port !== config.port) console.log(`[settings] Port changed to ${newConfig.port} - restart required to take effect`);
@@ -1,4 +1,4 @@
1
- import { d as SessionState, l as PendingWakeup, p as TaskTitle, s as PendingPromptDetail, u as SessionSnapshot } from "./session-CJtOveHl.js";
1
+ import { d as SessionSnapshot, f as SessionState, l as PendingWakeup, m as TaskTitle, s as PendingPromptDetail } from "./session-Cez8aIWg.js";
2
2
  import path from "node:path";
3
3
  import { z } from "zod";
4
4
  //#region shared/settings-ranges.ts
@@ -360,6 +360,10 @@ var TeamReviewThread = z.object({
360
360
  viewerCanResolve: z.boolean(),
361
361
  isNit: z.boolean(),
362
362
  url: z.string().url(),
363
+ viewerComment: z.string().max(1500).optional(),
364
+ lastReplyBody: z.string().max(1500).optional(),
365
+ viewerCommentTruncated: z.boolean().optional(),
366
+ lastReplyTruncated: z.boolean().optional(),
363
367
  lastReplyAuthor: z.string(),
364
368
  lastReplyAt: z.string().datetime(),
365
369
  judgement: TeamReviewThreadResult.extend({
@@ -1040,6 +1044,16 @@ var ChangeMapSettings = optionalObject("changeMap", { narrator: optionalObject("
1040
1044
  model: optionalString("changeMap.narrator.model", true),
1041
1045
  timeoutSeconds: optionalInteger("changeMap.narrator.timeoutSeconds", CHANGE_MAP_NARRATOR_TIMEOUT_RANGE)
1042
1046
  }) });
1047
+ var TaskTitleSettings = z.object({ refiner: z.object({
1048
+ enabled: optionalBoolean("taskTitle.refiner.enabled"),
1049
+ model: optionalString("taskTitle.refiner.model", true),
1050
+ minIntervalSeconds: optionalNumber("taskTitle.refiner.minIntervalSeconds", { min: 0 }),
1051
+ timeoutSeconds: optionalNumber("taskTitle.refiner.timeoutSeconds", {
1052
+ min: 0,
1053
+ exclusiveMin: true,
1054
+ max: 2147483647 / 1e3
1055
+ })
1056
+ }, { error: "taskTitle.refiner must be an object" }).nullable().optional() }, { error: "taskTitle must be an object" }).nullable().optional();
1043
1057
  var BRANCH_GC_SETTINGS_SHAPE = {
1044
1058
  enabled: optionalBoolean("branchGc.enabled"),
1045
1059
  worktrees: optionalBoolean("branchGc.worktrees"),
@@ -1161,6 +1175,7 @@ var createBrowserConfigShape = (isAbsolutePath) => ({
1161
1175
  recordSignals: optionalBoolean("recordSignals"),
1162
1176
  antiSlopPrompt: optionalBoolean("antiSlopPrompt"),
1163
1177
  rtk: optionalBoolean("rtk"),
1178
+ saneYolo: optionalBoolean("saneYolo"),
1164
1179
  skipPermissionsByDefault: optionalBoolean("skipPermissionsByDefault"),
1165
1180
  checkForUpdates: optionalBoolean("checkForUpdates"),
1166
1181
  updateChannel: z.enum(["release", "main"], { error: "updateChannel must be one of release, main" }).optional(),
@@ -1171,6 +1186,7 @@ var createBrowserConfigShape = (isAbsolutePath) => ({
1171
1186
  worktreeShare: z.array(z.string()).optional(),
1172
1187
  repoRoots: z.array(z.string()).optional(),
1173
1188
  changeMap: ChangeMapSettings,
1189
+ taskTitle: TaskTitleSettings,
1174
1190
  branchGc: BranchGcSettings,
1175
1191
  postTurnChecks: PostTurnChecksSettings,
1176
1192
  visions: VisionsSettings,
@@ -1257,6 +1273,7 @@ var FILE_CONFIG_SHAPE = {
1257
1273
  ...BROWSER_CONFIG_SHAPE,
1258
1274
  changeMap: ChangeMapSettings,
1259
1275
  branchGc: optionalLooseObject("branchGc"),
1276
+ coder: z.object({ appSlug: z.string().optional() }).loose().optional(),
1260
1277
  visions: optionalLooseObject("visions"),
1261
1278
  teamReview: optionalLooseObject("teamReview"),
1262
1279
  benchmarks: optionalLooseObject("benchmarks"),
@@ -1283,7 +1300,8 @@ var Config = z.object({
1283
1300
  enabled: z.boolean().optional(),
1284
1301
  port: z.number().int().min(1).max(65535).nullable().optional(),
1285
1302
  publicHost: z.string().optional(),
1286
- allowedOrigins: z.array(z.string()).optional()
1303
+ allowedOrigins: z.array(z.string()).optional(),
1304
+ ownerLogin: z.string().optional()
1287
1305
  }).passthrough().optional(),
1288
1306
  projects: z.array(ProjectConfig)
1289
1307
  }).passthrough().superRefine((config, ctx) => {
@@ -1308,6 +1326,7 @@ var Config = z.object({
1308
1326
  Object.freeze(Object.keys(BROWSER_CONFIG_SHAPE));
1309
1327
  var CONFIG_BLOCK_KEYS = Object.freeze([
1310
1328
  "changeMap",
1329
+ "taskTitle",
1311
1330
  "branchGc",
1312
1331
  "postTurnChecks",
1313
1332
  "visions",
@@ -1319,7 +1338,8 @@ var CONFIG_BLOCK_KEYS = Object.freeze([
1319
1338
  "ingest",
1320
1339
  "agentApi",
1321
1340
  "telemetry",
1322
- "workflows"
1341
+ "workflows",
1342
+ "coder"
1323
1343
  ]);
1324
1344
  var CONFIG_SCALAR_KEYS = Object.freeze(Object.keys(BROWSER_CONFIG_SHAPE).filter((key) => {
1325
1345
  if (CONFIG_BLOCK_KEYS.includes(key)) return false;
@@ -2143,6 +2163,7 @@ var SessionCardFields = z.object({
2143
2163
  state: SessionState,
2144
2164
  stateSince: timestamp,
2145
2165
  skipPerms: z.boolean(),
2166
+ saneYolo: z.boolean(),
2146
2167
  worktree: z.boolean(),
2147
2168
  workspace: z.boolean().optional(),
2148
2169
  resumeSessionId: nullableString,
@@ -2552,6 +2573,7 @@ var serverVariants = [
2552
2573
  event: z.string(),
2553
2574
  timestamp,
2554
2575
  skipPerms: z.boolean().optional(),
2576
+ saneYolo: z.boolean().optional(),
2555
2577
  hasEndedTurn: z.boolean().optional()
2556
2578
  }),
2557
2579
  loose("session-added", SessionCardFields.shape),
@@ -1,6 +1,8 @@
1
- import { o as relayPath } from "./runtime-paths-CVvq20A4.js";
2
- import { n as AGENT_URL_ENV } from "./session-CJtOveHl.js";
1
+ import { o as relayPath } from "./runtime-paths-Ci_ksSkj.js";
2
+ import { n as AGENT_URL_ENV } from "./session-Cez8aIWg.js";
3
3
  import { t as HOOK_URL_ENV } from "./hook-relay-core-BHfYFnYV.js";
4
+ import { a as SANE_YOLO_PATH_ENV, n as RTK_PATH_ENV, o as saneYoloEnv } from "./rtk-hook-core-Bl6gYHPN.js";
5
+ import { t as buildHookCommand } from "./hook-command-core-DclEoE9e.js";
4
6
  import fs from "node:fs";
5
7
  import path from "node:path";
6
8
  import os from "node:os";
@@ -9,8 +11,17 @@ var GLIMMERVOID_SCRUB_KEYS = [
9
11
  "GLIMMERVOID_PORT",
10
12
  "GLIMMERVOID_CONFIG",
11
13
  HOOK_URL_ENV,
12
- AGENT_URL_ENV
14
+ AGENT_URL_ENV,
15
+ RTK_PATH_ENV,
16
+ SANE_YOLO_PATH_ENV,
17
+ ...[
18
+ "GLIMMERVOID_POSTHOG_API_KEY",
19
+ "GLIMMERVOID_TELEGRAM_BOT_TOKEN",
20
+ "GLIMMERVOID_CLAUDE_OAUTH_TOKEN"
21
+ ]
13
22
  ];
23
+ var SANE_YOLO_GUARD_KEYS = [SANE_YOLO_PATH_ENV, ...Object.keys(saneYoloEnv(""))];
24
+ var SAFETY_NET_ENV_PREFIXES = ["CC_SAFETY_NET_", "SAFETY_NET_"];
14
25
  var LAUNCHING_TERMINAL_IDENTITY_KEYS = ["TERM_PROGRAM", "TERM_PROGRAM_VERSION"];
15
26
  function normalizePathEntry(entry) {
16
27
  return entry.replace(/\\/g, "/").toLowerCase();
@@ -46,29 +57,29 @@ function prependPathDir(env, prependPathDirValue) {
46
57
  }
47
58
  env[pathKey] = `${prependPathDirValue}${path.delimiter}${existingPath}`;
48
59
  }
60
+ function isSafetyNetKey(key) {
61
+ const upperKey = key.toUpperCase();
62
+ return SAFETY_NET_ENV_PREFIXES.some((prefix) => upperKey.startsWith(prefix));
63
+ }
64
+ function isSaneYoloGuarded(extraEnv) {
65
+ if (!extraEnv) return false;
66
+ return SANE_YOLO_GUARD_KEYS.some((key) => extraEnv[key] !== void 0);
67
+ }
68
+ function scrubInheritedSafetyNetEnv(env) {
69
+ for (const key of Object.keys(env)) if (isSafetyNetKey(key)) delete env[key];
70
+ }
49
71
  function buildAgentEnv(baseEnv, extraEnv, profile, { prependPathDir: pathDir = null } = {}) {
50
72
  const env = { ...baseEnv };
51
73
  for (const key of profile.scrub || []) delete env[key];
52
74
  for (const key of GLIMMERVOID_SCRUB_KEYS) delete env[key];
53
75
  for (const key of LAUNCHING_TERMINAL_IDENTITY_KEYS) delete env[key];
76
+ if (isSaneYoloGuarded(extraEnv)) scrubInheritedSafetyNetEnv(env);
54
77
  Object.assign(env, extraEnv || {});
55
78
  Object.assign(env, profile.set || {});
56
79
  prependPathDir(env, pathDir);
57
80
  return env;
58
81
  }
59
82
  //#endregion
60
- //#region session/core/hook-command-core.ts
61
- var SAFE_PATH_RE = /^[A-Za-z0-9_.:/\\ -]+$/;
62
- var SAFE_EVENT_RE = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
63
- function buildHookCommand(relayPath, event) {
64
- const rawPath = String(relayPath);
65
- const rawEvent = String(event);
66
- if (!SAFE_PATH_RE.test(rawPath)) return null;
67
- if (!SAFE_EVENT_RE.test(rawEvent)) return null;
68
- const forwardSlashedPath = rawPath.replace(/\\/g, "/");
69
- return `node ${/\s/.test(forwardSlashedPath) ? `"${forwardSlashedPath}"` : forwardSlashedPath} ${rawEvent}`;
70
- }
71
- //#endregion
72
83
  //#region session/core/grok-hooks-file-core.ts
73
84
  function hookGroup(command) {
74
85
  return [{ hooks: [{
@@ -76,13 +87,18 @@ function hookGroup(command) {
76
87
  command
77
88
  }] }];
78
89
  }
79
- function renderGrokHooksFile({ relayPath, events }) {
90
+ function renderGrokHooksFile({ relayPath, events, hookToolRelayPath }) {
80
91
  const hooks = {};
81
92
  for (const event of events) {
82
93
  const command = buildHookCommand(relayPath, event);
83
94
  if (!command) return null;
84
95
  hooks[event] = hookGroup(command);
85
96
  }
97
+ if (hookToolRelayPath) {
98
+ const command = buildHookCommand(hookToolRelayPath, "saneYolo");
99
+ if (!command) return null;
100
+ hooks.PreToolUse = hookGroup(command);
101
+ }
86
102
  return `${JSON.stringify({ hooks }, null, 2)}\n`;
87
103
  }
88
104
  function hasOnlyKeys(value, expectedKeys) {
@@ -91,15 +107,18 @@ function hasOnlyKeys(value, expectedKeys) {
91
107
  const sortedExpectedKeys = [...expectedKeys].sort();
92
108
  return actualKeys.length === sortedExpectedKeys.length && actualKeys.every((key, index) => key === sortedExpectedKeys[index]);
93
109
  }
94
- var MANAGED_RELAY_RE = /(?:^|\/)hook-relay\.(?:js|ts)$/;
110
+ var MANAGED_RELAY_RE = /(?:^|\/)(hook-relay|hook-tool-relay)\.(?:js|ts)$/;
95
111
  function isManagedCommand(command, event) {
96
112
  if (typeof command !== "string") return false;
97
113
  const match = command.match(/^node (?:(?:"([^"\r\n]+)")|([^\s"']+)) ([A-Za-z][A-Za-z0-9_-]{0,63})$/);
98
114
  if (!match) return false;
99
115
  const relayPath = match[1] || match[2];
100
- if (match[3] !== event) return false;
101
- if (!MANAGED_RELAY_RE.test(relayPath)) return false;
102
- return buildHookCommand(relayPath, event) === command;
116
+ const relayMatch = relayPath.match(MANAGED_RELAY_RE);
117
+ if (!relayMatch) return false;
118
+ const expectedRelay = event === "PreToolUse" ? "hook-tool-relay" : "hook-relay";
119
+ const expectedToken = event === "PreToolUse" ? "saneYolo" : event;
120
+ if (relayMatch[1] !== expectedRelay || match[3] !== expectedToken) return false;
121
+ return buildHookCommand(relayPath, expectedToken) === command;
103
122
  }
104
123
  function isManagedHookGroup(group, event) {
105
124
  if (!Array.isArray(group) || group.length !== 1) return false;
@@ -111,8 +130,13 @@ function isManagedHookGroup(group, event) {
111
130
  if (handler.type !== "command") return false;
112
131
  return isManagedCommand(handler.command, event);
113
132
  }
114
- function classifyGrokHooksFile(contents, { relayPath, events, managedEventSets = [] }) {
133
+ function classifyGrokHooksFile(contents, { relayPath, events, managedEventSets = [], hookToolRelayPath }) {
115
134
  if (contents == null) return "absent";
135
+ if (contents === renderGrokHooksFile({
136
+ relayPath,
137
+ events,
138
+ hookToolRelayPath
139
+ })) return "current";
116
140
  if (contents === renderGrokHooksFile({
117
141
  relayPath,
118
142
  events
@@ -130,7 +154,7 @@ function classifyGrokHooksFile(contents, { relayPath, events, managedEventSets =
130
154
  const actualEvents = Object.keys(hooksValue);
131
155
  if (!hasOnlyKeys(hooksValue, actualEvents)) return "foreign";
132
156
  const hooks = hooksValue;
133
- if (!(hasOnlyKeys(hooks, events) || managedEventSets.some((managedEvents) => hasOnlyKeys(hooks, managedEvents)))) return "foreign";
157
+ if (![events, ...managedEventSets].some((managedEvents) => hasOnlyKeys(hooks, managedEvents) || hasOnlyKeys(hooks, [...managedEvents, "PreToolUse"]))) return "foreign";
134
158
  for (const event of actualEvents) if (!isManagedHookGroup(hooks[event], event)) return "foreign";
135
159
  return "managed-stale";
136
160
  }
@@ -174,6 +198,7 @@ function classifyAgentTitle(title, { cwdBasename = null }, vocabulary) {
174
198
  var ID = "grok";
175
199
  var COMMAND_NAME = "grok";
176
200
  var RELAY_PATH = relayPath("hook-relay");
201
+ var HOOK_TOOL_RELAY_PATH = relayPath("hook-tool-relay");
177
202
  var HOOK_EVENTS = [
178
203
  "UserPromptSubmit",
179
204
  "Stop",
@@ -359,12 +384,14 @@ var grok = {
359
384
  filePath: hooksFilePath,
360
385
  expectedContents: () => renderGrokHooksFile({
361
386
  relayPath: RELAY_PATH,
362
- events: HOOK_EVENTS
387
+ events: HOOK_EVENTS,
388
+ hookToolRelayPath: HOOK_TOOL_RELAY_PATH
363
389
  }),
364
390
  classifyContents: (contents) => classifyGrokHooksFile(contents, {
365
391
  relayPath: RELAY_PATH,
366
392
  events: HOOK_EVENTS,
367
- managedEventSets: MANAGED_HOOK_EVENT_SETS
393
+ managedEventSets: MANAGED_HOOK_EVENT_SETS,
394
+ hookToolRelayPath: HOOK_TOOL_RELAY_PATH
368
395
  }),
369
396
  projectConfigCandidates: PROJECT_CONFIG_CANDIDATES,
370
397
  mayContributeHooks
@@ -386,6 +413,7 @@ var grok = {
386
413
  mayContributeHooks,
387
414
  PROJECT_CONFIG_CANDIDATES,
388
415
  CLAUDE_COMPAT_HOOKS_ENV,
416
+ HOOK_TOOL_RELAY_PATH,
389
417
  HOOK_EVENTS,
390
418
  MANAGED_HOOK_EVENT_SETS,
391
419
  RELAY_PATH,
@@ -397,6 +425,7 @@ var grok = {
397
425
  resume: true,
398
426
  statusLine: false,
399
427
  rtk: false,
428
+ saneYolo: true,
400
429
  antiSlop: false,
401
430
  compactQuiet: false,
402
431
  skipPermissionsFlag: true,
@@ -404,4 +433,4 @@ var grok = {
404
433
  }
405
434
  };
406
435
  //#endregion
407
- export { classifyGrokHooksFile as a, buildAgentEnv as c, isSpinnerChar as i, classifyAgentTitle as n, renderGrokHooksFile as o, isBrailleChar as r, buildHookCommand as s, grok as t };
436
+ export { classifyGrokHooksFile as a, isSpinnerChar as i, classifyAgentTitle as n, renderGrokHooksFile as o, isBrailleChar as r, buildAgentEnv as s, grok as t };