@wrongstack/cli 1.0.6 → 1.0.8

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 (34) hide show
  1. package/dist/{acp-GOQURNC2.js → acp-A5EQEXQO.js} +61 -6
  2. package/dist/{auth-WBXXIQUO.js → auth-3K6EN5JP.js} +3 -3
  3. package/dist/boot/container-wiring.d.ts +2 -0
  4. package/dist/{chunk-VUVOMXWP.js → chunk-3VXYY5JC.js} +8 -1
  5. package/dist/{chunk-NR7BD73C.js → chunk-4OLAZIIN.js} +2 -2
  6. package/dist/{chunk-5A5GCSJC.js → chunk-FSZ5QU3M.js} +2 -2
  7. package/dist/{chunk-KNMIBKLV.js → chunk-JY52HWL6.js} +47 -1
  8. package/dist/{chunk-QDQPF4PB.js → chunk-RLCKKLJA.js} +7 -7
  9. package/dist/{chunk-VPS2SJW4.js → chunk-RMQNCHGO.js} +5 -3
  10. package/dist/{chunk-PQZPHGGD.js → chunk-XIZZ2EXG.js} +34 -2
  11. package/dist/{cli-context-BYKJDAFF.js → cli-context-TIAP6YQK.js} +83 -12
  12. package/dist/cli-context.d.ts +2 -0
  13. package/dist/{cli-main-5WZBH3WA.js → cli-main-AKJHDSHX.js} +1006 -634
  14. package/dist/config-doctor.d.ts +1 -1
  15. package/dist/execute-deps.d.ts +3 -1
  16. package/dist/{execution-Z6A26EG6.js → execution-FJE27VQ6.js} +80 -3
  17. package/dist/{hq-V4PHEETW.js → hq-G5PCP325.js} +7 -5
  18. package/dist/hq-command-controller.d.ts +15 -0
  19. package/dist/{hq-server-MZ7DV6X7.js → hq-server-G3VVVMIG.js} +2 -2
  20. package/dist/index.js +3 -3
  21. package/dist/input-reader.d.ts +2 -2
  22. package/dist/{modeldiag-46CMWUNM.js → modeldiag-CJSFHZ3J.js} +55 -31
  23. package/dist/permission-prompt-mirror.d.ts +70 -0
  24. package/dist/permission-prompt.d.ts +10 -1
  25. package/dist/{providers-models-6MOGAOVK.js → providers-models-LFNZ5WI7.js} +2 -2
  26. package/dist/{short-circuit-flags-KBTYSJTW.js → short-circuit-flags-RAT6UZVI.js} +2 -2
  27. package/dist/slash-commands/subagent-models.d.ts +4 -0
  28. package/dist/subagent-models/panel-service.d.ts +28 -0
  29. package/dist/subcommands/handlers/modeldiag-test.d.ts +7 -0
  30. package/dist/{subcommands-DEYSYHIM.js → subcommands-VFVUTYAV.js} +2 -2
  31. package/dist/webui-server/prefs-seeding.d.ts +8 -0
  32. package/dist/{webui-server-OBV5JYJV.js → webui-server-3NYXBZHX.js} +36 -12
  33. package/dist/wiring/hq-telemetry.d.ts +7 -0
  34. package/package.json +28 -28
@@ -5,6 +5,9 @@ import {
5
5
  refreshAcpRegistry,
6
6
  setupProvider
7
7
  } from "./chunk-KEPABQYT.js";
8
+ import {
9
+ createHqCommandDispatcher
10
+ } from "./chunk-JY52HWL6.js";
8
11
  import {
9
12
  startCliHqConnection
10
13
  } from "./chunk-TE3Q32JP.js";
@@ -36,18 +39,50 @@ import { WebSocketServer } from "ws";
36
39
 
37
40
  // src/acp-hq-telemetry.ts
38
41
  import { AgentStatusTracker } from "@wrongstack/core/coordination";
39
- import { startSessionTelemetryBridge } from "@wrongstack/core/hq";
42
+ import {
43
+ createApprovalRegistry,
44
+ startApprovalTelemetryBridge,
45
+ startSessionTelemetryBridge
46
+ } from "@wrongstack/core/hq";
40
47
  var NO_REGISTRY = { updateAgents: async () => void 0 };
41
48
  function startAcpHqTelemetry(options) {
49
+ const entries = /* @__PURE__ */ new Map();
50
+ let stopped = false;
51
+ const resolveApproval = (toolUseId, decision, sessionId) => {
52
+ if (sessionId !== void 0) {
53
+ return entries.get(sessionId)?.approvals?.resolve(toolUseId, decision, sessionId) ?? false;
54
+ }
55
+ for (const entry of entries.values()) {
56
+ if (entry.approvals?.resolve(toolUseId, decision) === true) return true;
57
+ }
58
+ return false;
59
+ };
42
60
  const connection = startCliHqConnection({
43
61
  clientKind: "acp",
44
62
  projectRoot: options.projectRoot,
45
63
  ...options.projectName !== void 0 ? { projectName: options.projectName } : {},
46
64
  ...options.appConfig !== void 0 ? { appConfig: options.appConfig } : {},
47
- capabilities: ["telemetry.publish", "mailbox.summary", "fleet.summary", "session.summary"]
65
+ capabilities: [
66
+ "telemetry.publish",
67
+ "mailbox.summary",
68
+ "fleet.summary",
69
+ "session.summary",
70
+ // Approvals are the ONLY control this host accepts. `control.receive` is
71
+ // the server's gate for any command at all, so it has to be declared;
72
+ // everything except `approve` falls through the dispatcher and is
73
+ // refused, because an ACP session has no mailbox to steer and no leader
74
+ // to abort.
75
+ "control.receive",
76
+ "control.approve"
77
+ ],
78
+ onCommand: createHqCommandDispatcher({
79
+ interruptLeader: () => false,
80
+ sessionTag: () => "acp",
81
+ ownsSession: (sessionId) => entries.has(sessionId),
82
+ allowRunCommand: () => false,
83
+ resolveApproval
84
+ })
48
85
  });
49
- const entries = /* @__PURE__ */ new Map();
50
- let stopped = false;
51
86
  const attach = (sessionId, agent, cwd) => {
52
87
  if (stopped || entries.has(sessionId)) return;
53
88
  const publisher = connection.getPublisher();
@@ -73,7 +108,19 @@ function startAcpHqTelemetry(options) {
73
108
  tracker.stop();
74
109
  return;
75
110
  }
76
- entries.set(sessionId, { tracker, stopBridge });
111
+ const entry = { tracker, stopBridge };
112
+ try {
113
+ const approvals = createApprovalRegistry(events);
114
+ entry.approvals = approvals;
115
+ entry.stopApprovalBridge = startApprovalTelemetryBridge({
116
+ registry: approvals,
117
+ publisher,
118
+ projectRoot: cwd,
119
+ sessionId
120
+ });
121
+ } catch {
122
+ }
123
+ entries.set(sessionId, entry);
77
124
  };
78
125
  const detach = (sessionId) => {
79
126
  const entry = entries.get(sessionId);
@@ -83,6 +130,14 @@ function startAcpHqTelemetry(options) {
83
130
  entry.stopBridge();
84
131
  } catch {
85
132
  }
133
+ try {
134
+ entry.stopApprovalBridge?.();
135
+ } catch {
136
+ }
137
+ try {
138
+ entry.approvals?.dispose();
139
+ } catch {
140
+ }
86
141
  try {
87
142
  entry.tracker.stop();
88
143
  } catch {
@@ -1102,4 +1157,4 @@ ${renderAcpBenchText(result)}
1102
1157
  export {
1103
1158
  acpCmd
1104
1159
  };
1105
- //# sourceMappingURL=acp-GOQURNC2.js.map
1160
+ //# sourceMappingURL=acp-A5EQEXQO.js.map
@@ -14,7 +14,7 @@ import {
14
14
  runOAuthLoginKind,
15
15
  runOAuthLoginMenu,
16
16
  validateFamily
17
- } from "./chunk-5A5GCSJC.js";
17
+ } from "./chunk-FSZ5QU3M.js";
18
18
  import {
19
19
  renderAuthLocalHelpToString,
20
20
  wantsLocalHelp
@@ -23,7 +23,7 @@ import "./chunk-B4JLSXZB.js";
23
23
  import {
24
24
  restoreFlags
25
25
  } from "./chunk-CSAPOCBP.js";
26
- import "./chunk-VUVOMXWP.js";
26
+ import "./chunk-3VXYY5JC.js";
27
27
  import {
28
28
  activeProfileConfigPath
29
29
  } from "./chunk-YMXXOOFN.js";
@@ -619,4 +619,4 @@ async function runAuthRemove(deps, providerId) {
619
619
  export {
620
620
  authCmd
621
621
  };
622
- //# sourceMappingURL=auth-WBXXIQUO.js.map
622
+ //# sourceMappingURL=auth-3K6EN5JP.js.map
@@ -4,6 +4,7 @@ import type { Config, Logger, ModelsRegistry, Renderer } from '@wrongstack/core/
4
4
  import type { WstackPaths } from '@wrongstack/core/utils';
5
5
  import { createDefaultContainer } from '@wrongstack/runtime';
6
6
  import { makePromptDelegate } from '../permission-prompt.js';
7
+ import { type ApprovalMirrorRef } from '../permission-prompt-mirror.js';
7
8
  interface WireContainerDeps {
8
9
  config: Config;
9
10
  wpaths: WstackPaths;
@@ -30,6 +31,7 @@ export declare function wireContainer(deps: WireContainerDeps): {
30
31
  pathResolver: InstanceType<typeof DefaultPathResolver>;
31
32
  events: EventBus;
32
33
  container: ReturnType<typeof createDefaultContainer>;
34
+ approvalMirror: ApprovalMirrorRef;
33
35
  };
34
36
  export {};
35
37
  //# sourceMappingURL=container-wiring.d.ts.map
@@ -102,6 +102,13 @@ function modelDetails(models) {
102
102
  tools: model.tool_call,
103
103
  vision: model.modalities?.input?.includes("image"),
104
104
  reasoning: model.reasoning ?? model.reasoningConfig !== void 0,
105
+ // Documented effort vocabulary, mirroring the gate
106
+ // `getActiveModelReasoningEffortLevels` applies for the settings
107
+ // panel: `effortSupported === false` is a documented "no effort
108
+ // control", and the picker must not offer a strip for it. An absent
109
+ // field stays absent, which the strip reads as "undocumented →
110
+ // canonical set".
111
+ ...model.reasoningConfig?.effortSupported && model.reasoningConfig.effortLevels?.length ? { effortLevels: [...model.reasoningConfig.effortLevels] } : {},
105
112
  maxContext: model.limit?.context,
106
113
  maxOutput: model.limit?.output,
107
114
  inputCost: model.cost?.input,
@@ -120,4 +127,4 @@ export {
120
127
  hasApiKey,
121
128
  buildPickableProviders
122
129
  };
123
- //# sourceMappingURL=chunk-VUVOMXWP.js.map
130
+ //# sourceMappingURL=chunk-3VXYY5JC.js.map
@@ -22,7 +22,7 @@ async function handleHelpVersionShortCircuit(argv) {
22
22
  }
23
23
  if (earlyFlags["help"] === true) {
24
24
  if (positional.length > 0) {
25
- const { subcommandNames } = await import("./subcommands-DEYSYHIM.js");
25
+ const { subcommandNames } = await import("./subcommands-VFVUTYAV.js");
26
26
  if (subcommandNames.includes(positional[0])) {
27
27
  const first = positional[0];
28
28
  const deep = positional[1];
@@ -43,4 +43,4 @@ async function handleHelpVersionShortCircuit(argv) {
43
43
  export {
44
44
  handleHelpVersionShortCircuit
45
45
  };
46
- //# sourceMappingURL=chunk-NR7BD73C.js.map
46
+ //# sourceMappingURL=chunk-4OLAZIIN.js.map
@@ -6,7 +6,7 @@ import {
6
6
  hasApiKey,
7
7
  isKeylessLocalProvider,
8
8
  visibleModelIds
9
- } from "./chunk-VUVOMXWP.js";
9
+ } from "./chunk-3VXYY5JC.js";
10
10
  import {
11
11
  openBrowser
12
12
  } from "./chunk-DAKVBMD4.js";
@@ -1813,4 +1813,4 @@ export {
1813
1813
  addCustomProvider,
1814
1814
  addKeyForProvider
1815
1815
  };
1816
- //# sourceMappingURL=chunk-5A5GCSJC.js.map
1816
+ //# sourceMappingURL=chunk-FSZ5QU3M.js.map
@@ -235,6 +235,52 @@ async function dispatch(controller, commandId, type, payload) {
235
235
  });
236
236
  return { commandId, status: "completed", message };
237
237
  }
238
+ case "approve": {
239
+ const resolveApproval = controller.resolveApproval;
240
+ if (resolveApproval === void 0) {
241
+ return {
242
+ commandId,
243
+ status: "rejected",
244
+ message: "this client does not mirror approvals"
245
+ };
246
+ }
247
+ const toolUseId = typeof payload["toolUseId"] === "string" ? payload["toolUseId"] : "";
248
+ const decision = payload["decision"];
249
+ if (toolUseId.length === 0 || decision !== "yes" && decision !== "no" && decision !== "always" && decision !== "deny") {
250
+ return { commandId, status: "rejected", message: "malformed approve command" };
251
+ }
252
+ const applied = resolveApproval(toolUseId, decision, sessionId);
253
+ if (!applied) {
254
+ return {
255
+ commandId,
256
+ status: "rejected",
257
+ // Says WHY, because "rejected" alone reads as a failure the operator
258
+ // should retry — and retrying a settled prompt does nothing.
259
+ message: `approval ${toolUseId} is no longer pending (already answered, aborted or timed out)`
260
+ };
261
+ }
262
+ return { commandId, status: "completed", message: `approval ${decision}` };
263
+ }
264
+ case "answer-input": {
265
+ const resolver = controller.resolveUserInput;
266
+ if (!resolver)
267
+ return {
268
+ commandId,
269
+ status: "rejected",
270
+ message: "this client does not mirror user input forms"
271
+ };
272
+ const requestId = typeof payload["requestId"] === "string" ? payload["requestId"] : "";
273
+ const response = payload["response"];
274
+ if (!requestId || !response || !Array.isArray(response.answers))
275
+ return { commandId, status: "rejected", message: "malformed answer-input command" };
276
+ if (!resolver(requestId, response, sessionId))
277
+ return {
278
+ commandId,
279
+ status: "rejected",
280
+ message: `user input ${requestId} is no longer pending`
281
+ };
282
+ return { commandId, status: "completed", message: "user input submitted" };
283
+ }
238
284
  case "run-command": {
239
285
  if (!controller.allowRunCommand()) {
240
286
  return {
@@ -274,4 +320,4 @@ export {
274
320
  createProjectKanbanAssignHandler,
275
321
  createHqCommandDispatcher
276
322
  };
277
- //# sourceMappingURL=chunk-KNMIBKLV.js.map
323
+ //# sourceMappingURL=chunk-JY52HWL6.js.map
@@ -1,9 +1,9 @@
1
1
 
2
2
  // src/subcommands/index.ts
3
3
  var loaders = {
4
- acp: async () => (await import("./acp-GOQURNC2.js")).acpCmd,
4
+ acp: async () => (await import("./acp-A5EQEXQO.js")).acpCmd,
5
5
  init: async () => (await import("./init-DRMMFG2G.js")).initCmd,
6
- auth: async () => (await import("./auth-WBXXIQUO.js")).authCmd,
6
+ auth: async () => (await import("./auth-3K6EN5JP.js")).authCmd,
7
7
  update: async () => (await import("./update-5DPB4YD4.js")).updateCmd,
8
8
  sessions: async () => (await import("./sessions-config-UL45XKX7.js")).sessionsCmd,
9
9
  config: async () => (await import("./sessions-config-UL45XKX7.js")).configCmd,
@@ -14,8 +14,8 @@ var loaders = {
14
14
  audit: async () => (await import("./audit-46YK5DSC.js")).auditCmd,
15
15
  tools: async () => (await import("./tools-skills-GH24UCBY.js")).toolsCmd,
16
16
  skills: async () => (await import("./tools-skills-GH24UCBY.js")).skillsCmd,
17
- providers: async () => (await import("./providers-models-6MOGAOVK.js")).providersCmd,
18
- models: async () => (await import("./providers-models-6MOGAOVK.js")).modelsCmd,
17
+ providers: async () => (await import("./providers-models-LFNZ5WI7.js")).providersCmd,
18
+ models: async () => (await import("./providers-models-LFNZ5WI7.js")).modelsCmd,
19
19
  mcp: async () => (await import("./mcp-FX7TKPTV.js")).mcpCmd,
20
20
  plugin: async () => (await import("./plugin-usage-M6KET4OZ.js")).pluginCmd,
21
21
  plugins: async () => (await import("./plugin-usage-M6KET4OZ.js")).pluginCmd,
@@ -27,11 +27,11 @@ var loaders = {
27
27
  version: async () => (await import("./version-help-WXI42PZ3.js")).versionCmd,
28
28
  help: async () => (await import("./version-help-WXI42PZ3.js")).helpCmd,
29
29
  projects: async () => (await import("./projects-LMNAGGEU.js")).projectsCmd,
30
- modeldiag: async () => (await import("./modeldiag-46CMWUNM.js")).modeldiagCmd,
30
+ modeldiag: async () => (await import("./modeldiag-CJSFHZ3J.js")).modeldiagCmd,
31
31
  quick: async () => (await import("./quick-CNB4Z3HS.js")).quickCmd,
32
32
  bench: async () => (await import("./bench-YBU6BVQP.js")).benchCmd,
33
33
  chronicle: async () => (await import("./chronicle-UTFETHP6.js")).chronicleCmd,
34
- hq: async () => (await import("./hq-V4PHEETW.js")).hqCmd,
34
+ hq: async () => (await import("./hq-G5PCP325.js")).hqCmd,
35
35
  mailbox: async () => (await import("./mailbox-serve-X3CR6VFW.js")).mailboxServeCmd,
36
36
  permissions: async () => (await import("./permissions-CFDLXETM.js")).permissionsCmd,
37
37
  project: async () => (await import("./project-AOQI5XTK.js")).projectCmd,
@@ -51,4 +51,4 @@ export {
51
51
  subcommands,
52
52
  subcommandNames
53
53
  };
54
- //# sourceMappingURL=chunk-QDQPF4PB.js.map
54
+ //# sourceMappingURL=chunk-RLCKKLJA.js.map
@@ -27,7 +27,8 @@ import {
27
27
  writeOut
28
28
  } from "@wrongstack/core/utils";
29
29
  function makePromptDelegate(reader) {
30
- return async (tool, input, suggestedPattern) => {
30
+ return async (tool, input, suggestedPattern, signal) => {
31
+ if (signal?.aborted) return "no";
31
32
  writeOut("\x07");
32
33
  writeOut(
33
34
  `
@@ -54,7 +55,8 @@ ${theme.warn("\u26A0 APPROVAL REQUIRED")} ${theme.primary("\u2502")} ${theme.bol
54
55
  const alwaysHint = noAlwaysReason ? "" : ` ${theme.bold("[a]")}lways allow (${sanitizeTerminalText2(suggestedPattern)})`;
55
56
  const answer = await reader.readKey(
56
57
  `${theme.bold("[y]")}es ${theme.bold("[n]")}o${alwaysHint} ${theme.bold("[d]")}eny: `,
57
- options
58
+ options,
59
+ ...signal ? [{ signal }] : []
58
60
  );
59
61
  return answer;
60
62
  };
@@ -480,4 +482,4 @@ export {
480
482
  detectProjectFacts,
481
483
  renderAgentsTemplate
482
484
  };
483
- //# sourceMappingURL=chunk-VPS2SJW4.js.map
485
+ //# sourceMappingURL=chunk-RMQNCHGO.js.map
@@ -2118,7 +2118,11 @@ async function authorizeHqCommand(input) {
2118
2118
  // bootstrap exchange, which is the same thing the dashboard does.
2119
2119
  actor: { kind: authMethod === "session" ? "user" : "remote-client", id: enqueuedBy },
2120
2120
  surface: "hq",
2121
- capability: command.type === "run-command" ? "hq.control.execute" : "hq.control.enqueue",
2121
+ capability: command.type === "run-command" ? "hq.control.execute" : (
2122
+ // Approvals get their own capability string so a policy can allow
2123
+ // steering while still refusing remote approval of tool calls.
2124
+ command.type === "approve" || command.type === "answer-input" ? "hq.control.approve" : "hq.control.enqueue"
2125
+ ),
2122
2126
  subject,
2123
2127
  risk: command.type === "run-command" ? "critical" : "high",
2124
2128
  scope: { projectId: target.projectId },
@@ -2481,6 +2485,34 @@ async function handleApiCommand(req, res, url, mutableAuth, sessions, clients, b
2481
2485
  res.end(JSON.stringify({ error: "unrecognized or malformed command", type: body.type }));
2482
2486
  return;
2483
2487
  }
2488
+ if ((validated.type === "approve" || validated.type === "answer-input") && isCookieAuth(auth) && auth.capabilities !== void 0 && !auth.capabilities.includes("control.approve")) {
2489
+ res.writeHead(403, { "Content-Type": "application/json" });
2490
+ res.end(
2491
+ JSON.stringify({
2492
+ error: "forbidden: browser session lacks control.approve capability"
2493
+ })
2494
+ );
2495
+ return;
2496
+ }
2497
+ if ((validated.type === "approve" || validated.type === "answer-input") && isTokenAuth(auth) && auth.capabilities !== void 0 && !auth.capabilities.includes("control.approve")) {
2498
+ res.writeHead(403, { "Content-Type": "application/json" });
2499
+ res.end(
2500
+ JSON.stringify({
2501
+ error: "forbidden: token lacks control.approve capability"
2502
+ })
2503
+ );
2504
+ return;
2505
+ }
2506
+ if ((validated.type === "approve" || validated.type === "answer-input") && !target.capabilities.includes("control.approve")) {
2507
+ res.writeHead(409, { "Content-Type": "application/json" });
2508
+ res.end(
2509
+ JSON.stringify({
2510
+ error: "client does not mirror approvals",
2511
+ clientId: body.clientId
2512
+ })
2513
+ );
2514
+ return;
2515
+ }
2484
2516
  if (validated.type === "run-command" && isCookieAuth(auth) && auth.capabilities !== void 0 && !auth.capabilities.includes("control.execute")) {
2485
2517
  res.writeHead(403, { "Content-Type": "application/json" });
2486
2518
  res.end(
@@ -4899,4 +4931,4 @@ export {
4899
4931
  startHqServer,
4900
4932
  HqInsecureExposureError2 as HqInsecureExposureError
4901
4933
  };
4902
- //# sourceMappingURL=chunk-PQZPHGGD.js.map
4934
+ //# sourceMappingURL=chunk-XIZZ2EXG.js.map
@@ -5,7 +5,7 @@ import {
5
5
  makePromptDelegate,
6
6
  renderAgentsTemplate,
7
7
  renderDiff
8
- } from "./chunk-VPS2SJW4.js";
8
+ } from "./chunk-RMQNCHGO.js";
9
9
  import {
10
10
  theme as theme2
11
11
  } from "./chunk-QD544J2B.js";
@@ -19,17 +19,17 @@ import {
19
19
  } from "./chunk-GPPRNAKW.js";
20
20
  import {
21
21
  subcommands
22
- } from "./chunk-QDQPF4PB.js";
22
+ } from "./chunk-RLCKKLJA.js";
23
23
  import {
24
24
  renderDeepHelp,
25
25
  renderFocusedHelp
26
26
  } from "./chunk-JX6KL7OF.js";
27
27
  import {
28
28
  handleHelpVersionShortCircuit
29
- } from "./chunk-NR7BD73C.js";
29
+ } from "./chunk-4OLAZIIN.js";
30
30
  import {
31
31
  DEFAULT_PORT
32
- } from "./chunk-PQZPHGGD.js";
32
+ } from "./chunk-XIZZ2EXG.js";
33
33
  import "./chunk-HXJYHAR4.js";
34
34
  import {
35
35
  resolveHqPasswordInput
@@ -63,13 +63,13 @@ import {
63
63
  runPicker,
64
64
  saveToGlobalConfig,
65
65
  theme
66
- } from "./chunk-5A5GCSJC.js";
66
+ } from "./chunk-FSZ5QU3M.js";
67
67
  import "./chunk-2VMJ3E5Z.js";
68
68
  import "./chunk-B4JLSXZB.js";
69
69
  import {
70
70
  isKeylessLocalProvider,
71
71
  visibleModelIds
72
- } from "./chunk-VUVOMXWP.js";
72
+ } from "./chunk-3VXYY5JC.js";
73
73
  import {
74
74
  activeProfileConfigPath
75
75
  } from "./chunk-YMXXOOFN.js";
@@ -125,11 +125,64 @@ function resolveBundledSkillsDir() {
125
125
  }
126
126
  }
127
127
 
128
+ // src/permission-prompt-mirror.ts
129
+ import { randomUUID } from "node:crypto";
130
+ import { describeWriteTargets } from "@wrongstack/core/security";
131
+ var REPL_MIRROR_TTL_MS = 6e4;
132
+ var REPL_MIRROR_HEARTBEAT_MS = 2e4;
133
+ function makeMirroredPromptDelegate(deps) {
134
+ const now = deps.now ?? Date.now;
135
+ return async (tool, input, suggestedPattern) => {
136
+ const registry = deps.getRegistry();
137
+ const controller = new AbortController();
138
+ if (registry === void 0) {
139
+ return deps.inner(tool, input, suggestedPattern, controller.signal);
140
+ }
141
+ const toolUseId = `repl:${randomUUID()}`;
142
+ let settled = false;
143
+ let remoteDecision;
144
+ const finish = registry.register({
145
+ toolUseId,
146
+ toolName: tool.name,
147
+ sessionId: deps.getSessionId?.(),
148
+ deadlineAt: now() + REPL_MIRROR_TTL_MS,
149
+ input,
150
+ suggestedPattern,
151
+ ...tool.riskTier ? { riskTier: tool.riskTier } : {},
152
+ decisionSource: "default",
153
+ writeTargets: describeWriteTargets(tool, input),
154
+ destructive: tool.riskTier === "destructive",
155
+ resolve: (decision) => {
156
+ if (settled) return;
157
+ settled = true;
158
+ remoteDecision = decision;
159
+ controller.abort();
160
+ }
161
+ });
162
+ const heartbeat = setInterval(() => {
163
+ registry.renew(toolUseId, now() + REPL_MIRROR_TTL_MS);
164
+ }, REPL_MIRROR_HEARTBEAT_MS);
165
+ heartbeat.unref?.();
166
+ try {
167
+ const typed = await deps.inner(tool, input, suggestedPattern, controller.signal);
168
+ if (remoteDecision !== void 0) return remoteDecision;
169
+ settled = true;
170
+ const decision = typed === "yes" || typed === "always" || typed === "deny" ? typed : "no";
171
+ finish(decision);
172
+ return decision;
173
+ } finally {
174
+ clearInterval(heartbeat);
175
+ finish("no");
176
+ }
177
+ };
178
+ }
179
+
128
180
  // src/boot/container-wiring.ts
129
181
  function wireContainer(deps) {
130
182
  const pathResolver = new DefaultPathResolver(deps.cwd);
131
183
  const events = new CoreEventBus();
132
184
  events.setLogger(deps.logger);
185
+ const approvalMirror = { current: void 0 };
133
186
  const container = createDefaultContainer({
134
187
  config: deps.config,
135
188
  wpaths: deps.wpaths,
@@ -138,7 +191,15 @@ function wireContainer(deps) {
138
191
  events,
139
192
  permission: {
140
193
  yolo: deps.config.yolo,
141
- promptDelegate: makePromptDelegate(deps.reader)
194
+ // Wrapped so the REPL's terminal prompt also appears on the HQ
195
+ // dashboard and can be answered from there. The wrapper is a no-op
196
+ // until `approvalMirror.current` is populated (HQ telemetry boot), and
197
+ // the terminal prompt itself behaves exactly as it always has.
198
+ promptDelegate: makeMirroredPromptDelegate({
199
+ inner: makePromptDelegate(deps.reader),
200
+ getRegistry: () => approvalMirror.current,
201
+ getSessionId: () => approvalMirror.sessionId?.()
202
+ })
142
203
  },
143
204
  compactor: {
144
205
  preserveK: deps.config.context.preserveK,
@@ -150,7 +211,7 @@ function wireContainer(deps) {
150
211
  container.bind(TOKENS.PathResolver, () => pathResolver);
151
212
  container.bind(TOKENS.Renderer, () => deps.renderer);
152
213
  container.bind(TOKENS.InputReader, () => deps.reader);
153
- return { pathResolver, events, container };
214
+ return { pathResolver, events, container, approvalMirror };
154
215
  }
155
216
 
156
217
  // src/boot/launch-menu.ts
@@ -805,7 +866,7 @@ async function isPortInUse(host, port) {
805
866
  }
806
867
  async function handleHqShortCircuit(flags) {
807
868
  if (flags["hq"] !== true) return null;
808
- const { startHqServer } = await import("./hq-server-MZ7DV6X7.js");
869
+ const { startHqServer } = await import("./hq-server-G3VVVMIG.js");
809
870
  const tunnelRequested = flags["tunnel"] === true;
810
871
  const rawPublicUrl = typeof flags["hq-public-url"] === "string" ? flags["hq-public-url"] : process.env.WRONGSTACK_HQ_PUBLIC_URL;
811
872
  let publicOrigin;
@@ -1827,8 +1888,10 @@ var ReadlineInputReader = class {
1827
1888
  }
1828
1889
  });
1829
1890
  }
1830
- async readKey(prompt, options) {
1891
+ async readKey(prompt, options, opts = {}) {
1831
1892
  setOutputLineGuard(null);
1893
+ const signal = opts.signal;
1894
+ if (signal?.aborted) return "";
1832
1895
  writeOut(prompt);
1833
1896
  return new Promise((resolve2) => {
1834
1897
  const stdin = process.stdin;
@@ -1858,14 +1921,21 @@ var ReadlineInputReader = class {
1858
1921
  cleanup();
1859
1922
  resolve2("");
1860
1923
  };
1924
+ const onAbort = () => {
1925
+ cleanup();
1926
+ writeOut("\n");
1927
+ resolve2("");
1928
+ };
1861
1929
  const cleanup = () => {
1862
1930
  stdin.off("data", onData);
1863
1931
  stdin.off("close", onClose);
1932
+ signal?.removeEventListener("abort", onAbort);
1864
1933
  setRawMode2(stdin, wasRaw);
1865
1934
  if (wasPaused) stdin.pause();
1866
1935
  };
1867
1936
  stdin.on("data", onData);
1868
1937
  stdin.on("close", onClose);
1938
+ signal?.addEventListener("abort", onAbort, { once: true });
1869
1939
  });
1870
1940
  }
1871
1941
  /**
@@ -3210,7 +3280,7 @@ async function initializeCli(argv) {
3210
3280
  }).catch(() => {
3211
3281
  });
3212
3282
  });
3213
- const { events, container } = wireContainer({
3283
+ const { events, container, approvalMirror } = wireContainer({
3214
3284
  config,
3215
3285
  wpaths,
3216
3286
  cwd,
@@ -3225,6 +3295,7 @@ async function initializeCli(argv) {
3225
3295
  updateInfo: refreshedUpdateInfo,
3226
3296
  events,
3227
3297
  container,
3298
+ approvalMirror,
3228
3299
  configStore,
3229
3300
  webuiSessionChild
3230
3301
  };
@@ -3232,4 +3303,4 @@ async function initializeCli(argv) {
3232
3303
  export {
3233
3304
  initializeCli
3234
3305
  };
3235
- //# sourceMappingURL=cli-context-BYKJDAFF.js.map
3306
+ //# sourceMappingURL=cli-context-TIAP6YQK.js.map
@@ -22,6 +22,8 @@ import type { BootContext } from './boot.js';
22
22
  export interface CliContext extends BootContext {
23
23
  events: EventBus;
24
24
  container: ReturnType<typeof wireContainer>['container'];
25
+ /** Late-bound handle the REPL prompt uses to mirror approvals to HQ. */
26
+ approvalMirror: ReturnType<typeof wireContainer>['approvalMirror'];
25
27
  configStore: ConfigStore;
26
28
  webuiSessionChild?: WebuiSessionChildOptions | undefined;
27
29
  }