@wrongstack/cli 1.0.3 → 1.0.4

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 (74) hide show
  1. package/dist/{acp-JK4ZQO3R.js → acp-AB54FWM2.js} +120 -38
  2. package/dist/acp-agent-list.d.ts +14 -0
  3. package/dist/acp-hq-telemetry.d.ts +9 -2
  4. package/dist/acp-mcp-servers.d.ts +49 -0
  5. package/dist/acp-server-agent.d.ts +18 -6
  6. package/dist/{auth-XD3GN5RJ.js → auth-WDMSNZ3S.js} +2 -2
  7. package/dist/boot/short-circuit-hq.d.ts +2 -0
  8. package/dist/boot/tool-registry.d.ts +18 -1
  9. package/dist/{chunk-C2E24IXZ.js → chunk-3I5ZJXM5.js} +147 -241
  10. package/dist/{chunk-JHPXJRRI.js → chunk-4XCBEUBX.js} +13 -3
  11. package/dist/{chunk-FPIWTJ2G.js → chunk-6WRKAACA.js} +4 -4
  12. package/dist/chunk-AUQDGU5D.js +32 -0
  13. package/dist/{chunk-IUEKNHPT.js → chunk-BIWE22O5.js} +10 -10
  14. package/dist/{chunk-VOTTENR5.js → chunk-CDY4D5QK.js} +2 -2
  15. package/dist/{chunk-E6RFXHWD.js → chunk-EK2P53GL.js} +3 -2
  16. package/dist/chunk-I7V2OYVH.js +242 -0
  17. package/dist/{chunk-MXEJEYT7.js → chunk-JX6KL7OF.js} +15 -5
  18. package/dist/{chunk-GMY45LO6.js → chunk-KEPABQYT.js} +101 -2
  19. package/dist/{chunk-IMWXXF4J.js → chunk-KNMIBKLV.js} +79 -1
  20. package/dist/chunk-Q7E3BPDU.js +62 -0
  21. package/dist/{chunk-4NHD5OXJ.js → chunk-U6PGWDMZ.js} +5 -5
  22. package/dist/{chunk-U6KCWRMC.js → chunk-XJ7K67PP.js} +5 -1
  23. package/dist/{cli-context-Q6QP534K.js → cli-context-WFC3BNGM.js} +116 -21
  24. package/dist/{cli-main-JQXCDZEA.js → cli-main-GU3YZLMO.js} +1221 -694
  25. package/dist/{diag-doctor-7LYUYU35.js → diag-doctor-PCER6KHH.js} +2 -2
  26. package/dist/{execution-KH7WJNAX.js → execution-RAZDDQFC.js} +107 -15
  27. package/dist/execution-specialist-trigger.d.ts +28 -0
  28. package/dist/fleet/acp-live-progress.d.ts +19 -0
  29. package/dist/fleet/host-acp-runner-cache.d.ts +3 -1
  30. package/dist/fleet/host-acp.d.ts +14 -2
  31. package/dist/fleet/host.d.ts +1 -0
  32. package/dist/{hq-IG3YRGCO.js → hq-5XHEIIH6.js} +127 -8
  33. package/dist/hq-command-controller.d.ts +31 -2
  34. package/dist/hq-server/client-address.d.ts +2 -0
  35. package/dist/hq-server/ip-allowlist.d.ts +16 -0
  36. package/dist/hq-server/preflight.d.ts +1 -0
  37. package/dist/hq-server/routes/auth/password-routes.d.ts +1 -1
  38. package/dist/hq-server/routes.d.ts +5 -4
  39. package/dist/hq-server/secret-input.d.ts +7 -0
  40. package/dist/hq-server/types.d.ts +3 -2
  41. package/dist/hq-server/upgrade-handler.d.ts +2 -0
  42. package/dist/hq-server/utils.d.ts +6 -0
  43. package/dist/{hq-server-OILU3NPI.js → hq-server-CNUTBWTK.js} +4 -3
  44. package/dist/hq-server.d.ts +4 -0
  45. package/dist/hq-service-L5INQFM4.js +507 -0
  46. package/dist/hq-service.d.ts +22 -0
  47. package/dist/index.js +4 -4
  48. package/dist/ip-allowlist-NHXO6AKJ.js +9 -0
  49. package/dist/live-settings-input.d.ts +2 -0
  50. package/dist/{per-subcommand-help-BWBHYVJO.js → per-subcommand-help-DHTYU524.js} +2 -2
  51. package/dist/{plugins-U7KGQAUG.js → plugins-WJLW6ULV.js} +2 -2
  52. package/dist/projects-LMNAGGEU.js +36 -0
  53. package/dist/{proxy-wiring-MGRSKZZB.js → proxy-wiring-K2DORFFL.js} +3 -3
  54. package/dist/short-circuit-flags-YDMLAQ5R.js +10 -0
  55. package/dist/slash-commands/acp.d.ts +3 -0
  56. package/dist/slash-commands/command-context.d.ts +22 -0
  57. package/dist/slash-commands/ensemble.d.ts +1 -1
  58. package/dist/slash-commands/profile.d.ts +7 -0
  59. package/dist/subcommands/handlers/hq.d.ts +1 -0
  60. package/dist/subcommands/handlers/projects.d.ts +14 -0
  61. package/dist/{subcommands-CUTGBDKP.js → subcommands-7N4RIK6H.js} +2 -2
  62. package/dist/{version-help-MYKF724O.js → version-help-WXI42PZ3.js} +2 -2
  63. package/dist/{webui-server-QWBUT7BO.js → webui-server-OBV5JYJV.js} +9 -3
  64. package/dist/wiring/cli-slash-commands-setup.d.ts +7 -0
  65. package/dist/wiring/codebase-embeddings.d.ts +2 -2
  66. package/dist/wiring/command-host-state.d.ts +7 -0
  67. package/dist/wiring/pipeline.d.ts +2 -0
  68. package/dist/wiring/provider-runtime-setup.d.ts +1 -1
  69. package/dist/wiring/session-command-handlers.d.ts +2 -1
  70. package/dist/wiring/slash-commands.d.ts +1 -0
  71. package/package.json +28 -28
  72. package/dist/chunk-Q5GTM25S.js +0 -17
  73. package/dist/projects-L6C6XLDY.js +0 -37
  74. package/dist/short-circuit-flags-ZNZX44MC.js +0 -10
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  startProxyProbe
3
- } from "./chunk-JHPXJRRI.js";
3
+ } from "./chunk-4XCBEUBX.js";
4
4
  import {
5
5
  formatGateCounterReport,
6
6
  loadWrongTraceGateCounters
@@ -382,4 +382,4 @@ export {
382
382
  doctorCmd,
383
383
  proxyCmd
384
384
  };
385
- //# sourceMappingURL=diag-doctor-7LYUYU35.js.map
385
+ //# sourceMappingURL=diag-doctor-PCER6KHH.js.map
@@ -11,17 +11,6 @@ import {
11
11
  resolveActualTarget,
12
12
  resolvePersistPath
13
13
  } from "./chunk-JACOMTIH.js";
14
- import {
15
- theme
16
- } from "./chunk-QD544J2B.js";
17
- import {
18
- WEBUI_SESSION_CHILD_CAPABILITIES,
19
- writeWebuiSessionChildError,
20
- writeWebuiSessionChildReady
21
- } from "./chunk-GPPRNAKW.js";
22
- import {
23
- fmtTok
24
- } from "./chunk-EWKESL4L.js";
25
14
  import "./chunk-WHOIR577.js";
26
15
  import {
27
16
  advanceToNextTask,
@@ -36,6 +25,17 @@ import {
36
25
  trySaveSpecFromAIOutput,
37
26
  trySaveTasksFromAIOutput
38
27
  } from "./chunk-JHUJUIOH.js";
28
+ import {
29
+ theme
30
+ } from "./chunk-QD544J2B.js";
31
+ import {
32
+ fmtTok
33
+ } from "./chunk-EWKESL4L.js";
34
+ import {
35
+ WEBUI_SESSION_CHILD_CAPABILITIES,
36
+ writeWebuiSessionChildError,
37
+ writeWebuiSessionChildReady
38
+ } from "./chunk-GPPRNAKW.js";
39
39
  import {
40
40
  terminalLink,
41
41
  terminalText
@@ -212,7 +212,7 @@ async function runWebUIDispatch(ctx) {
212
212
  const isSimpleUi = !isSessionChild && flags["simpleui"] === true;
213
213
  agent.disableInteractiveConfirmation();
214
214
  renderer.setSilent(true);
215
- const { runWebUI } = await import("./webui-server-QWBUT7BO.js");
215
+ const { runWebUI } = await import("./webui-server-OBV5JYJV.js");
216
216
  const flagValue = (names) => {
217
217
  for (const name of names) {
218
218
  if (!Object.hasOwn(flags, name)) continue;
@@ -416,7 +416,7 @@ async function runWebUIDispatch(ctx) {
416
416
  applyLiveSettings?.({ yolo: enabled });
417
417
  },
418
418
  onWrongProxyPrefsChange: (payload) => {
419
- void import("./proxy-wiring-MGRSKZZB.js").then(
419
+ void import("./proxy-wiring-K2DORFFL.js").then(
420
420
  ({ applyWrongProxyPrefs }) => applyWrongProxyPrefs(payload)
421
421
  );
422
422
  }
@@ -1937,6 +1937,7 @@ function createSettingsAdapter(ctx) {
1937
1937
  breakerEnabled: cfg.circuitBreaker?.enabled === true,
1938
1938
  breakerAutoKillResetMs: cfg.circuitBreaker?.autoKillResetMs ?? 6e4,
1939
1939
  showModelReasoning: autonomy?.showModelReasoning ?? false,
1940
+ toolResultViewMode: autonomy?.toolResultViewMode === "minimal" || autonomy?.toolResultViewMode === "full" ? autonomy.toolResultViewMode : "normal",
1940
1941
  showAgentSwarmPanel: coerceAgentSwarmMode(autonomy?.showAgentSwarmPanel),
1941
1942
  showSidebar: autonomy?.showSidebar ?? true,
1942
1943
  // Migrate the legacy `autonomy.showAgentSwarmPanel: 'sidebar'` into
@@ -1963,7 +1964,7 @@ function createSettingsAdapter(ctx) {
1963
1964
  }
1964
1965
  async function saveSettings(s) {
1965
1966
  try {
1966
- if (s.mode !== void 0 || s.delayMs !== void 0 || s.titleAnimation !== void 0 || s.yolo !== void 0 || s.fleetChatVerbosity !== void 0 || s.chime !== void 0 || s.confirmExit !== void 0 || s.mouseMode !== void 0 || s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureTokenSaving !== void 0 || s.allowOutsideProjectRoot !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.contextMode !== void 0 || s.maxConcurrent !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.multiDiffSummaryThreshold !== void 0 || s.nextStepsTool !== void 0 || s.restrictFsToRoot !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.shellBangWarningDontShowAgain !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0 || s.enhanceEnabled !== void 0 || s.preRefineSeconds !== void 0 || s.enhanceLanguage !== void 0 || s.midRunSendPicker !== void 0 || s.statuslineMode !== void 0 || s.thinkingWord !== void 0 || s.animationStyle !== void 0 || s.autonomyNextPrompt !== void 0 || s.autoProceedMaxIterations !== void 0 || s.reasoningMode !== void 0 || s.reasoningEffort !== void 0 || s.reasoningPreserve !== void 0 || s.cacheTtl !== void 0 || s.breakerEnabled !== void 0 || s.breakerAutoKillResetMs !== void 0 || s.showModelReasoning !== void 0 || s.showAgentSwarmPanel !== void 0 || s.showSidebar !== void 0 || s.panelPositions !== void 0 || s.showSageMemoryInject !== void 0 || s.sageMemoryInjectThreshold !== void 0 || s.readSymbols !== void 0 || // WrongProxy / WrongTrace: gate the persisted-section write on
1967
+ if (s.mode !== void 0 || s.delayMs !== void 0 || s.titleAnimation !== void 0 || s.yolo !== void 0 || s.fleetChatVerbosity !== void 0 || s.chime !== void 0 || s.confirmExit !== void 0 || s.mouseMode !== void 0 || s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureTokenSaving !== void 0 || s.allowOutsideProjectRoot !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.contextMode !== void 0 || s.maxConcurrent !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.multiDiffSummaryThreshold !== void 0 || s.nextStepsTool !== void 0 || s.restrictFsToRoot !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.shellBangWarningDontShowAgain !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0 || s.enhanceEnabled !== void 0 || s.preRefineSeconds !== void 0 || s.enhanceLanguage !== void 0 || s.midRunSendPicker !== void 0 || s.statuslineMode !== void 0 || s.thinkingWord !== void 0 || s.animationStyle !== void 0 || s.autonomyNextPrompt !== void 0 || s.autoProceedMaxIterations !== void 0 || s.reasoningMode !== void 0 || s.reasoningEffort !== void 0 || s.reasoningPreserve !== void 0 || s.cacheTtl !== void 0 || s.breakerEnabled !== void 0 || s.breakerAutoKillResetMs !== void 0 || s.showModelReasoning !== void 0 || s.toolResultViewMode !== void 0 || s.showAgentSwarmPanel !== void 0 || s.showSidebar !== void 0 || s.panelPositions !== void 0 || s.showSageMemoryInject !== void 0 || s.sageMemoryInjectThreshold !== void 0 || s.readSymbols !== void 0 || // WrongProxy / WrongTrace: gate the persisted-section write on
1967
1968
  // either key being present in the live patch. Without this, a
1968
1969
  // picker toggle round-trip would silently no-op the persistence
1969
1970
  // layer (the runtime probe would never read the change).
@@ -2018,6 +2019,7 @@ function createSettingsAdapter(ctx) {
2018
2019
  autonomy.thinkingWord = normalizeTuiThinkingWord(s.thinkingWord);
2019
2020
  if (s.animationStyle !== void 0) autonomy.animationStyle = s.animationStyle;
2020
2021
  if (s.showModelReasoning !== void 0) autonomy.showModelReasoning = s.showModelReasoning;
2022
+ if (s.toolResultViewMode !== void 0) autonomy.toolResultViewMode = s.toolResultViewMode;
2021
2023
  if (s.showAgentSwarmPanel !== void 0)
2022
2024
  autonomy.showAgentSwarmPanel = s.showAgentSwarmPanel;
2023
2025
  if (s.showSidebar !== void 0) autonomy.showSidebar = s.showSidebar;
@@ -4369,6 +4371,87 @@ async function persistChimeraReview(payload, projectDir) {
4369
4371
  }
4370
4372
  }
4371
4373
 
4374
+ // src/execution-specialist-trigger.ts
4375
+ import { randomUUID as randomUUID4 } from "node:crypto";
4376
+ var SPECIALIST_MAX_ITERATIONS = 30;
4377
+ var SPECIALIST_MAX_TOOL_CALLS = 120;
4378
+ var SPECIALIST_TIMEOUT_MS = 6 * 60 * 1e3;
4379
+ function installSpecialistTriggerHandler({
4380
+ events,
4381
+ director,
4382
+ session,
4383
+ teardownHandlers,
4384
+ trackWork
4385
+ }) {
4386
+ const off = events.onPattern(
4387
+ "fleet.specialist_needed",
4388
+ (_event, payload) => {
4389
+ const request = payload;
4390
+ const dir = director;
4391
+ if (!dir) return;
4392
+ if (!request?.role || !request.task) return;
4393
+ const work = (async () => {
4394
+ try {
4395
+ const subagentId = await dir.spawn({
4396
+ name: `trigger-${request.role}`,
4397
+ role: request.role,
4398
+ tier: request.tier,
4399
+ maxIterations: SPECIALIST_MAX_ITERATIONS,
4400
+ maxToolCalls: SPECIALIST_MAX_TOOL_CALLS,
4401
+ timeoutMs: SPECIALIST_TIMEOUT_MS,
4402
+ // Triggered specialists are background infrastructure, like
4403
+ // reviewers: they must not eat the leader's lifetime spawn budget,
4404
+ // which the user reserved for work they asked for.
4405
+ spawnBudgetExempt: true
4406
+ });
4407
+ const taskId = await dir.assign({
4408
+ id: randomUUID4(),
4409
+ description: request.task,
4410
+ subagentId,
4411
+ timeoutMs: SPECIALIST_TIMEOUT_MS
4412
+ });
4413
+ const [result] = await dir.awaitTasks([taskId]);
4414
+ if (result?.status === "success") {
4415
+ const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
4416
+ await session.append({
4417
+ type: "llm_response",
4418
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
4419
+ content: [
4420
+ {
4421
+ type: "text",
4422
+ text: `\u{1F514} ${request.role} (triggered by ${request.paths.length} changed file(s)) \u2014 ${text}`
4423
+ }
4424
+ ],
4425
+ stopReason: "end_turn",
4426
+ usage: { input: 0, output: 0 }
4427
+ });
4428
+ return;
4429
+ }
4430
+ await session.append({
4431
+ type: "error",
4432
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
4433
+ message: `\u{1F514} ${request.role} trigger ${result?.status ?? "unknown"}: ${result?.error?.message ?? "no result"}`,
4434
+ phase: "agent"
4435
+ });
4436
+ } catch (err) {
4437
+ try {
4438
+ await session.append({
4439
+ type: "error",
4440
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
4441
+ message: `\u{1F514} ${request.role} trigger failed to run: ${err instanceof Error ? err.message : String(err)}`,
4442
+ phase: "agent"
4443
+ });
4444
+ } catch {
4445
+ }
4446
+ }
4447
+ })();
4448
+ trackWork?.(work);
4449
+ },
4450
+ "specialist-trigger"
4451
+ );
4452
+ if (off) teardownHandlers.push(off);
4453
+ }
4454
+
4372
4455
  // src/execution-storage-observability.ts
4373
4456
  function installStorageObservability(events, rootTraceId) {
4374
4457
  const isStorageNamespace = (event) => event === "storage.read" || event === "storage.write" || event === "storage.error" || event.startsWith("storage.");
@@ -6217,6 +6300,15 @@ async function execute(deps) {
6217
6300
  chimeraWork.track(work);
6218
6301
  }
6219
6302
  });
6303
+ installSpecialistTriggerHandler({
6304
+ events,
6305
+ director,
6306
+ session,
6307
+ teardownHandlers: chimeraTeardowns,
6308
+ trackWork: (work) => {
6309
+ chimeraWork.track(work);
6310
+ }
6311
+ });
6220
6312
  installChimeraCascadeHandler({
6221
6313
  events,
6222
6314
  director,
@@ -6748,4 +6840,4 @@ export {
6748
6840
  execute,
6749
6841
  resolveReviewerFallbackModels
6750
6842
  };
6751
- //# sourceMappingURL=execution-KH7WJNAX.js.map
6843
+ //# sourceMappingURL=execution-RAZDDQFC.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Spawns the roster specialist a file-pattern trigger asked for.
3
+ *
4
+ * The detection half lives in core (`specialist-trigger-plugin.ts`) and knows
5
+ * nothing about the fleet; this is the other side of that boundary, mirroring
6
+ * how `chimera.review_needed` is handled. A host with no Director simply never
7
+ * spawns, and the plugin keeps working as a pure detector.
8
+ *
9
+ * What it deliberately does not do: retry ladders, model pinning, cascades, or
10
+ * report persistence. A specialist woken by a glob is a background opinion,
11
+ * not a gate — if the model is down, the right outcome is one line in the
12
+ * transcript saying so, not a chain of failovers spending more money on an
13
+ * opinion nobody asked for.
14
+ */
15
+ import type { Director } from '@wrongstack/core/coordination';
16
+ import type { EventBus } from '@wrongstack/core/kernel';
17
+ import type { SessionWriter } from '@wrongstack/core/types';
18
+ export interface InstallSpecialistTriggerHandlerOptions {
19
+ events: EventBus;
20
+ director: Director | null | undefined;
21
+ session: SessionWriter;
22
+ /** Session-scoped disposers, drained at session end. */
23
+ teardownHandlers: Array<() => void>;
24
+ /** Registers background work so the session waits for it before closing. */
25
+ trackWork?: ((work: Promise<void>) => void) | undefined;
26
+ }
27
+ export declare function installSpecialistTriggerHandler({ events, director, session, teardownHandlers, trackWork, }: InstallSpecialistTriggerHandlerOptions): void;
28
+ //# sourceMappingURL=execution-specialist-trigger.d.ts.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Bridge ACP session/update progress onto the same buses in-process
3
+ * subagents use, so TUI fleet chat, WebUI Agents panel, and HQ timeline
4
+ * can watch Claude Code / Gemini / Kimi the same way they watch a native worker.
5
+ */
6
+ import type { ACPProgressEvent } from '@wrongstack/acp';
7
+ import type { FleetBus } from '@wrongstack/core/coordination';
8
+ import type { EventBus } from '@wrongstack/core/kernel';
9
+ export interface PublishAcpLiveProgressOpts {
10
+ event: ACPProgressEvent;
11
+ subagentId: string;
12
+ agentName: string;
13
+ sessionId?: string | undefined;
14
+ taskId?: string | undefined;
15
+ fleet?: Pick<FleetBus, 'emit'> | undefined;
16
+ hostEvents?: Pick<EventBus, 'emit'> | undefined;
17
+ }
18
+ export declare function publishAcpLiveProgress(opts: PublishAcpLiveProgressOpts): void;
19
+ //# sourceMappingURL=acp-live-progress.d.ts.map
@@ -1,9 +1,11 @@
1
1
  import type { SubagentRunner } from '@wrongstack/core/types';
2
+ import { type BuildAcpSubagentRunnerOptions } from './host-acp.js';
2
3
  export declare class HostAcpRunnerCache {
3
4
  private readonly maxEntries;
5
+ private readonly resolveOpts?;
4
6
  private readonly runners;
5
7
  private readonly accessOrder;
6
- constructor(maxEntries?: number);
8
+ constructor(maxEntries?: number, resolveOpts?: (() => BuildAcpSubagentRunnerOptions | undefined) | undefined);
7
9
  get(subagentId: string): Promise<SubagentRunner>;
8
10
  }
9
11
  //# sourceMappingURL=host-acp-runner-cache.d.ts.map
@@ -1,3 +1,15 @@
1
- import type { SubagentRunner } from '@wrongstack/core/types';
2
- export declare function buildAcpSubagentRunner(subagentId: string): Promise<SubagentRunner>;
1
+ import { type ACPProgressEvent, type AcpAgentCommandOverrides, type AcpLiveCatalog } from '@wrongstack/acp';
2
+ import type { SubagentRunContext, SubagentRunner, TaskSpec } from '@wrongstack/core/types';
3
+ export type AcpLivePublisher = (ctx: SubagentRunContext, task: TaskSpec, event: ACPProgressEvent) => void;
4
+ export interface BuildAcpSubagentRunnerOptions {
5
+ overrides?: AcpAgentCommandOverrides | undefined;
6
+ live?: AcpLiveCatalog | undefined;
7
+ publishLive?: AcpLivePublisher | undefined;
8
+ }
9
+ /**
10
+ * Same command resolution as `wstack acp spawn` / `/acp` — user override,
11
+ * bundled catalog, synced registry, then the legacy map. Director / `--bg`
12
+ * must not use a different argv than the CLI spawn path.
13
+ */
14
+ export declare function buildAcpSubagentRunner(subagentId: string, options?: BuildAcpSubagentRunnerOptions): Promise<SubagentRunner>;
3
15
  //# sourceMappingURL=host-acp.d.ts.map
@@ -120,6 +120,7 @@ export declare class MultiAgentHost {
120
120
  */
121
121
  buildSubagentRunner(config: Config): Promise<SubagentRunner>;
122
122
  buildACPRunner(subagentId: string): Promise<SubagentRunner>;
123
+ private acpCommandOpts;
123
124
  spawnACP(subagentId: string, task: string, config: Config): Promise<string>;
124
125
  private filterTools;
125
126
  private mailboxProjectDir;
@@ -2,8 +2,15 @@ import {
2
2
  parseTokenTtlValue
3
3
  } from "./chunk-UZ2TMHNH.js";
4
4
  import {
5
+ resolveHqPasswordInput
6
+ } from "./chunk-AUQDGU5D.js";
7
+ import {
8
+ normalizeHqPublicOrigin,
5
9
  resolveAuditActor
6
- } from "./chunk-Q5GTM25S.js";
10
+ } from "./chunk-I7V2OYVH.js";
11
+ import {
12
+ parseHqIpAllowlist
13
+ } from "./chunk-Q7E3BPDU.js";
7
14
 
8
15
  // src/subcommands/handlers/hq.ts
9
16
  import * as fs from "node:fs/promises";
@@ -14,6 +21,7 @@ import {
14
21
  hqAuthAuditPath,
15
22
  hqAuthContentHash,
16
23
  hqAuthFilePath,
24
+ isLoopbackHost,
17
25
  logHqAuthAudit,
18
26
  mintHqToken,
19
27
  mutateHqAuthFile,
@@ -40,6 +48,10 @@ var hqCmd = async (args, deps) => {
40
48
  if (sub === "audit") {
41
49
  return hqAuditCmd(args.slice(1), deps);
42
50
  }
51
+ if (sub === "service") {
52
+ const { hqServiceCmd } = await import("./hq-service-L5INQFM4.js");
53
+ return hqServiceCmd(args.slice(1), deps);
54
+ }
43
55
  if (sub === "help" || sub === "--help") {
44
56
  printHelp(deps);
45
57
  return 0;
@@ -50,15 +62,53 @@ var hqCmd = async (args, deps) => {
50
62
  return 1;
51
63
  };
52
64
  async function startServer(deps) {
53
- const { startHqServer } = await import("./hq-server-OILU3NPI.js");
65
+ const { startHqServer } = await import("./hq-server-CNUTBWTK.js");
54
66
  const dataDir = resolveDataDir(deps);
55
67
  const flags = deps.flags ?? {};
56
- const host = typeof flags["host"] === "string" ? flags["host"] : HQ_CLI_DEFAULT_HOST;
68
+ const rawPublicUrl = typeof flags["hq-public-url"] === "string" ? flags["hq-public-url"] : process.env.WRONGSTACK_HQ_PUBLIC_URL;
69
+ let publicOrigin;
70
+ if (rawPublicUrl !== void 0 && rawPublicUrl.trim() !== "") {
71
+ try {
72
+ publicOrigin = normalizeHqPublicOrigin(rawPublicUrl);
73
+ } catch (cause) {
74
+ deps.renderer.writeError(`${cause instanceof Error ? cause.message : String(cause)}
75
+ `);
76
+ return 1;
77
+ }
78
+ }
79
+ const host = typeof flags["host"] === "string" ? flags["host"] : publicOrigin !== void 0 ? "127.0.0.1" : HQ_CLI_DEFAULT_HOST;
57
80
  const port = typeof flags["port"] === "string" ? Number.parseInt(flags["port"], 10) : 3499;
81
+ if (publicOrigin !== void 0 && !isLoopbackHost(host)) {
82
+ deps.renderer.writeError(
83
+ "Persistent public HQ requires a loopback bind. Remove --host or use --host 127.0.0.1.\n"
84
+ );
85
+ return 1;
86
+ }
58
87
  const strictPort = flags["strict-port"] === true;
59
88
  const open = flags["open"] === true;
60
- const password = typeof flags["password"] === "string" ? flags["password"] : process.env.WRONGSTACK_HQ_PASSWORD;
89
+ let password;
90
+ try {
91
+ password = await resolveHqPasswordInput({
92
+ ...typeof flags["password"] === "string" ? { explicitPassword: flags["password"] } : {}
93
+ });
94
+ } catch (cause) {
95
+ deps.renderer.writeError(`${cause instanceof Error ? cause.message : String(cause)}
96
+ `);
97
+ return 1;
98
+ }
99
+ const bootstrapPasswordOnly = /^(?:1|true)$/i.test(
100
+ process.env.WRONGSTACK_HQ_BOOTSTRAP_PASSWORD_ONLY?.trim() ?? ""
101
+ );
61
102
  const allowInsecureOpen = flags["insecure-open"] === true;
103
+ const rawAllowlist = typeof flags["hq-allowlist"] === "string" ? flags["hq-allowlist"] : process.env.WRONGSTACK_HQ_ALLOWLIST;
104
+ let ipAllowlist;
105
+ try {
106
+ ipAllowlist = parseHqIpAllowlist(rawAllowlist);
107
+ } catch (cause) {
108
+ deps.renderer.writeError(`${cause instanceof Error ? cause.message : String(cause)}
109
+ `);
110
+ return 1;
111
+ }
62
112
  const rawTtl = typeof flags["hq-token-ttl"] === "string" ? flags["hq-token-ttl"] : void 0;
63
113
  let tokenTtlMs;
64
114
  if (rawTtl !== void 0 && rawTtl.length > 0) {
@@ -94,9 +144,13 @@ async function startServer(deps) {
94
144
  strictPort,
95
145
  dataDir,
96
146
  allowInsecureOpen,
147
+ secureCookies: publicOrigin !== void 0,
148
+ requireBrowserAuth: publicOrigin !== void 0,
97
149
  ...password !== void 0 ? { password } : {},
150
+ ...bootstrapPasswordOnly ? { bootstrapPasswordOnly: true } : {},
98
151
  ...tokenTtlMs !== void 0 ? { tokenTtlMs } : {},
99
- ...trustedProxyHops !== void 0 ? { trustedProxyHops } : {}
152
+ ...trustedProxyHops !== void 0 ? { trustedProxyHops } : {},
153
+ ...ipAllowlist !== void 0 ? { ipAllowlist } : {}
100
154
  });
101
155
  } catch (err) {
102
156
  if (err instanceof HqInsecureExposureError) {
@@ -106,14 +160,34 @@ async function startServer(deps) {
106
160
  }
107
161
  throw err;
108
162
  }
163
+ if (publicOrigin !== void 0) {
164
+ if (handle.firstRunSetup?.passwordMode !== true) {
165
+ await handle.close();
166
+ deps.renderer.writeError(
167
+ "Persistent public HQ requires password mode. Set WRONGSTACK_HQ_PASSWORD and retry.\n"
168
+ );
169
+ return 1;
170
+ }
171
+ handle.trustPublicOrigin(publicOrigin);
172
+ deps.renderer.write(`Public endpoint: ${publicOrigin}/
173
+ `);
174
+ deps.renderer.write(`Public mobile: ${publicOrigin}/mobile
175
+ `);
176
+ }
109
177
  if (open) {
110
178
  try {
111
179
  const { openBrowser } = await import("@wrongstack/webui-server");
112
- openBrowser(handle.firstRunSetup?.browserUrl ?? `http://${handle.host}:${handle.port}`);
180
+ openBrowser(
181
+ publicOrigin ?? handle.firstRunSetup?.browserUrl ?? `http://${handle.host}:${handle.port}`
182
+ );
113
183
  } catch {
114
184
  }
115
185
  }
116
186
  writeStartupInfo(deps, handle);
187
+ deps.renderer.write(
188
+ ipAllowlist === void 0 ? "Network allowlist: disabled (authentication remains required when configured).\n" : `Network allowlist: active (${ipAllowlist.length} configured rule${ipAllowlist.length === 1 ? "" : "s"} + loopback).
189
+ `
190
+ );
117
191
  await new Promise((resolve) => {
118
192
  const shutdown = async () => {
119
193
  try {
@@ -137,9 +211,34 @@ function writeStartupInfo(deps, handle) {
137
211
  `);
138
212
  deps.renderer.write(`Browser endpoint: http://${handle.host}:${handle.port}
139
213
  `);
214
+ deps.renderer.write(
215
+ `Mobile endpoint: http://${handle.host}:${handle.port}/mobile (password required)
216
+ `
217
+ );
218
+ return;
219
+ }
220
+ if (process.env.WRONGSTACK_HQ_SUPPRESS_STARTUP_SECRETS === "1") {
221
+ deps.renderer.write(`Browser endpoint: http://${handle.host}:${handle.port}
222
+ `);
223
+ deps.renderer.write(
224
+ `Mobile endpoint: http://${handle.host}:${handle.port}/mobile (password required)
225
+ `
226
+ );
227
+ deps.renderer.write(`Client endpoint: ws://${handle.host}:${handle.port}/ws/client
228
+ `);
229
+ deps.renderer.write(
230
+ `Startup credentials suppressed; auth state is stored in ${handle.firstRunSetup.dataDir}.
231
+ `
232
+ );
140
233
  return;
141
234
  }
235
+ const mobileUrl = new URL(handle.firstRunSetup.browserUrl);
236
+ mobileUrl.pathname = "/mobile";
237
+ mobileUrl.search = "";
238
+ mobileUrl.hash = "";
142
239
  deps.renderer.write(`Browser endpoint: ${handle.firstRunSetup.browserUrl}
240
+ `);
241
+ deps.renderer.write(`Mobile endpoint: ${mobileUrl.toString()} (password required)
143
242
  `);
144
243
  deps.renderer.write(`Client endpoint: ${handle.firstRunSetup.clientUrl}
145
244
  `);
@@ -537,7 +636,7 @@ async function tokenRevoke(args, deps) {
537
636
  return 0;
538
637
  }
539
638
  function printHelp(deps) {
540
- deps.renderer.write(`Usage: wstack hq <serve | token | audit>
639
+ deps.renderer.write(`Usage: wstack hq <serve | token | audit | service>
541
640
  `);
542
641
  deps.renderer.write("\n");
543
642
  deps.renderer.write(` wstack hq Start the HQ command center server.
@@ -564,6 +663,18 @@ function printHelp(deps) {
564
663
  `);
565
664
  deps.renderer.write(
566
665
  ` wstack hq audit verify Re-derive auth.json contentHash for forensic comparison.
666
+ `
667
+ );
668
+ deps.renderer.write(
669
+ ` wstack hq service install Install persistent Ubuntu/systemd service + updater timer.
670
+ `
671
+ );
672
+ deps.renderer.write(` wstack hq service status Show service and updater status.
673
+ `);
674
+ deps.renderer.write(` wstack hq service update Run the guarded update job now.
675
+ `);
676
+ deps.renderer.write(
677
+ ` wstack hq service uninstall Remove units but preserve HQ data and secret file.
567
678
  `
568
679
  );
569
680
  deps.renderer.write("\n");
@@ -579,6 +690,10 @@ function printHelp(deps) {
579
690
  );
580
691
  deps.renderer.write(
581
692
  ` --tunnel Publish a temporary *.trycloudflare.com URL (requires cloudflared).
693
+ `
694
+ );
695
+ deps.renderer.write(
696
+ ` --hq-public-url <https-origin> Trust a persistent TLS reverse-proxy origin; binds loopback and requires password mode.
582
697
  `
583
698
  );
584
699
  deps.renderer.write(` --password <value> Set or rotate the browser login password.
@@ -599,6 +714,10 @@ function printHelp(deps) {
599
714
  ` --hq-trusted-proxy-hops <n> Trust the rightmost n X-Forwarded-For entries when rate-limiting logins.
600
715
  Default 0 (ignore the header). Set to the real hop count behind a tunnel,
601
716
  otherwise every user shares one backoff bucket. Never guess high.
717
+ `
718
+ );
719
+ deps.renderer.write(
720
+ ` --hq-allowlist <csv> Optional source IP/CIDR allowlist (for example 203.0.113.4,10.0.0.0/8).
602
721
  `
603
722
  );
604
723
  deps.renderer.write(` --open Open the dashboard in the default browser.
@@ -708,4 +827,4 @@ export {
708
827
  hqCmd,
709
828
  resolveAuditActor
710
829
  };
711
- //# sourceMappingURL=hq-IG3YRGCO.js.map
830
+ //# sourceMappingURL=hq-5XHEIIH6.js.map
@@ -6,8 +6,9 @@
6
6
  * (`director`, the rebound `interruptLeader`) are not yet defined at HQ-connect
7
7
  * time.
8
8
  *
9
- * The five command types route through the agent's own decision loop or the
10
- * project mailbox, so they inherit existing guardrails. `run-command` (raw
9
+ * Conversational commands route through the agent's own decision loop or the
10
+ * project mailbox; Kanban commands route through its project IPC owner, so
11
+ * both inherit existing guardrails. `run-command` (raw
11
12
  * shell) is deliberately NOT wired here — it requires a separately-audited
12
13
  * escalation policy (operator flag + per-token `control.execute` + allowlist)
13
14
  * and is left as a stub that rejects until that policy exists.
@@ -67,7 +68,35 @@ export interface HqCommandController {
67
68
  ownsSession?: ((sessionId: string) => boolean) | undefined;
68
69
  /** Whether raw shell execution is explicitly opted-in by the operator. */
69
70
  allowRunCommand: () => boolean;
71
+ /** Apply a lifecycle transition through the project's Kanban IPC client. */
72
+ kanbanTransition?: ((input: {
73
+ boardId: string;
74
+ taskId: string;
75
+ to: 'backlog' | 'todo' | 'running' | 'review' | 'done';
76
+ comment: string;
77
+ sessionId?: string | undefined;
78
+ }) => Promise<string>) | undefined;
79
+ /** Assign a card through the project owner while preserving active leases. */
80
+ kanbanAssign?: ((input: {
81
+ boardId: string;
82
+ taskId: string;
83
+ agentId: string;
84
+ assignee: string;
85
+ comment: string;
86
+ sessionId?: string | undefined;
87
+ }) => Promise<string>) | undefined;
88
+ /** Claim and dispatch one ready card through this host's canonical Director path. */
89
+ kanbanDispatch?: ((input: {
90
+ boardId: string;
91
+ taskId: string;
92
+ comment: string;
93
+ sessionId?: string | undefined;
94
+ }) => Promise<string>) | undefined;
70
95
  }
96
+ /** Bind HQ transitions to the same project-scoped IPC path every other client uses. */
97
+ export declare function createProjectKanbanTransitionHandler(projectRoot: string): NonNullable<HqCommandController['kanbanTransition']>;
98
+ /** Bind guarded HQ assignment to the canonical project-scoped Kanban owner. */
99
+ export declare function createProjectKanbanAssignHandler(projectRoot: string): NonNullable<HqCommandController['kanbanAssign']>;
71
100
  /**
72
101
  * Build the `onCommand` handler for an HQ publisher. The handler validates
73
102
  * the command, dispatches through the controller, and returns an ack result
@@ -35,6 +35,8 @@
35
35
  * @module hq-server/client-address
36
36
  */
37
37
  import type * as http from 'node:http';
38
+ /** The actual TCP peer, with IPv4-mapped IPv6 normalized. Never reads headers. */
39
+ export declare function resolveSocketAddress(req: http.IncomingMessage): string | undefined;
38
40
  /**
39
41
  * Parse one `X-Forwarded-For` element into a bare address, or `undefined` when
40
42
  * it is not one. Handles `1.2.3.4`, `1.2.3.4:5678`, `[::1]`, and `[::1]:5678`.
@@ -0,0 +1,16 @@
1
+ export interface HqIpAllowlist {
2
+ readonly entries: readonly string[];
3
+ allows(address: string | undefined): boolean;
4
+ }
5
+ /**
6
+ * Build the optional HQ admission allowlist.
7
+ *
8
+ * Matching is intentionally performed against the TCP peer, never a forwarded
9
+ * header. When HQ is directly reachable on 0.0.0.0, trusting a client-supplied
10
+ * X-Forwarded-For value here would turn the allowlist into a bypass. Loopback
11
+ * remains implicit so local administration and health checks cannot be locked
12
+ * out by a remote-only list.
13
+ */
14
+ export declare function createHqIpAllowlist(rules: readonly string[] | undefined): HqIpAllowlist | undefined;
15
+ export declare function parseHqIpAllowlist(value: string | undefined): string[] | undefined;
16
+ //# sourceMappingURL=ip-allowlist.d.ts.map
@@ -4,6 +4,7 @@ interface HqPreflightOptions {
4
4
  port?: number;
5
5
  dataDir?: string;
6
6
  password?: string;
7
+ bootstrapPasswordOnly?: boolean;
7
8
  tokenTtlMs?: number;
8
9
  requireBrowserAuth?: boolean;
9
10
  allowInsecureOpen?: boolean;
@@ -3,7 +3,7 @@ import type { LoginAttemptStore } from '../../login-attempt-store.js';
3
3
  import type { HqRouterMutableAuth, HqSessionEntry } from '../../types.js';
4
4
  import { type ApplyHqAuthFile } from './common.js';
5
5
  export declare function handleApiAuthStatus(req: http.IncomingMessage, res: http.ServerResponse, url: URL, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, requireBrowserAuth: boolean | undefined, trustedPublicOrigins: Set<string>, secureCookies: boolean | undefined): Promise<void>;
6
- export declare function handleApiLogin(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, loginAttempts: LoginAttemptStore, secureCookies: boolean | undefined, trustedProxyHops: number): Promise<void>;
6
+ export declare function handleApiLogin(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, loginAttempts: LoginAttemptStore, secureCookies: boolean | undefined, trustedProxyHops: number, sessionCapabilities?: string[]): Promise<void>;
7
7
  export declare function handleApiLogout(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, secureCookies: boolean | undefined): Promise<void>;
8
8
  export declare function handleApiPassword(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, dataDir: string, secureCookies: boolean | undefined, requireBrowserAuth: boolean | undefined, applyAuthFile: ApplyHqAuthFile): Promise<void>;
9
9
  //# sourceMappingURL=password-routes.d.ts.map
@@ -8,10 +8,8 @@
8
8
  * @module hq-server/routes
9
9
  */
10
10
  import type * as http from 'node:http';
11
- import type { createMailboxHttpRouter } from '@wrongstack/core/coordination';
12
- import type { Mailbox, MailboxHttpAccessDecision, MailboxHttpRateLimiter } from '@wrongstack/core/coordination';
13
- import type { HqAlertEngine, HqCommandAuditLog, HqEventEnvelope, HqSnapshot, HqTranscriptEntry } from '@wrongstack/core/hq';
14
- import type { createHqPersistence } from '@wrongstack/core/hq';
11
+ import type { createMailboxHttpRouter, Mailbox, MailboxHttpAccessDecision, MailboxHttpRateLimiter } from '@wrongstack/core/coordination';
12
+ import type { createHqPersistence, HqAlertEngine, HqCommandAuditLog, HqEventEnvelope, HqSnapshot, HqTranscriptEntry } from '@wrongstack/core/hq';
15
13
  import type { TrustBoundary } from '@wrongstack/core/security';
16
14
  import type { WebSocket } from 'ws';
17
15
  import * as HqServerAuth from './auth.js';
@@ -41,6 +39,7 @@ export declare const agentMessageToEntry: typeof HqServerUtils.agentMessageToEnt
41
39
  export declare const readLocalSubagentTranscript: typeof HqServerUtils.readLocalSubagentTranscript;
42
40
  export declare const lanIPv4Addresses: typeof HqServerUtils.lanIPv4Addresses;
43
41
  export declare const hqRuntimeMarkerPath: typeof HqServerUtils.hqRuntimeMarkerPath;
42
+ import type { HqIpAllowlist } from './ip-allowlist.js';
44
43
  import type { LoginAttemptStore } from './login-attempt-store.js';
45
44
  import type { ConnectedClient, HqRouterMutableAuth, HqSessionEntry, HqSnapshotBroadcaster, ProjectDetail, TranscriptRing } from './types.js';
46
45
  export type { ConnectedClient, HqSnapshotBroadcaster, ProjectDetail, TranscriptRing };
@@ -99,6 +98,8 @@ export interface HqRouterDeps {
99
98
  * Defaults to 0, which ignores `X-Forwarded-For` entirely.
100
99
  */
101
100
  trustedProxyHops?: number | undefined;
101
+ /** TCP-peer allowlist shared by HTTP and WebSocket admission. */
102
+ ipAllowlist?: HqIpAllowlist | undefined;
102
103
  }
103
104
  /**
104
105
  * Create the HTTP request listener that handles all `/api/*` routes as well
@@ -0,0 +1,7 @@
1
+ export interface ResolveHqPasswordOptions {
2
+ explicitPassword?: string | undefined;
3
+ environment?: NodeJS.ProcessEnv | undefined;
4
+ }
5
+ /** Resolve HQ password input without putting Docker/Kubernetes secrets in argv or env values. */
6
+ export declare function resolveHqPasswordInput(options?: ResolveHqPasswordOptions): Promise<string | undefined>;
7
+ //# sourceMappingURL=secret-input.d.ts.map