@wrongstack/cli 1.1.5 → 1.1.7

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 (36) hide show
  1. package/dist/{acp-NKUEYJDZ.js → acp-DJWUTU5J.js} +6 -6
  2. package/dist/acp-leader/acp-leader-host.d.ts +7 -0
  3. package/dist/acp-leader/acp-leader-provider.d.ts +10 -0
  4. package/dist/acp-leader/acp-leader-skills.d.ts +5 -0
  5. package/dist/acp-leader/acp-leader-stream.d.ts +8 -0
  6. package/dist/{auth-2ZG5DBHS.js → auth-XOJIPFFX.js} +2 -2
  7. package/dist/{chunk-UROB7MK5.js → chunk-BQ5OMSZG.js} +3 -2
  8. package/dist/{chunk-M7PSENGF.js → chunk-CL3BU45P.js} +5 -4
  9. package/dist/{chunk-M7FNHCE3.js → chunk-EAATL6FL.js} +110 -30
  10. package/dist/{chunk-JFYQGMMM.js → chunk-FWUIKCCO.js} +54 -49
  11. package/dist/{chunk-MEBXWI7U.js → chunk-OCB4I4GQ.js} +4 -4
  12. package/dist/{chunk-VEESMPTX.js → chunk-PGC44T7N.js} +2 -1
  13. package/dist/{chunk-5SGMUDP5.js → chunk-QRWXHGLB.js} +2 -2
  14. package/dist/{chunk-7BM4LGKQ.js → chunk-V3AKAH25.js} +6 -6
  15. package/dist/{chunk-LUEO5HLZ.js → chunk-X4DEQLAZ.js} +1 -1
  16. package/dist/{cli-context-CQYGIWOU.js → cli-context-F3B5TRZX.js} +40 -15
  17. package/dist/{cli-main-VRUJNUBX.js → cli-main-BPYX7AC6.js} +45 -12
  18. package/dist/{execution-IV72RUSM.js → execution-W3OWWXY7.js} +23 -9
  19. package/dist/{hq-54PQYR5O.js → hq-V2M4IYCI.js} +13 -3
  20. package/dist/hq-server/login-attempt-store.d.ts +2 -0
  21. package/dist/{hq-server-YK7FBLL6.js → hq-server-QM7EIVGJ.js} +2 -2
  22. package/dist/index.js +6 -6
  23. package/dist/live-settings-input.d.ts +1 -0
  24. package/dist/short-circuit-flags-6LWKP4KP.js +11 -0
  25. package/dist/{subcommands-OCJNO57E.js → subcommands-SR4K73WA.js} +2 -2
  26. package/dist/{version-help-X6CC4CFY.js → version-help-MITOB3YK.js} +2 -2
  27. package/dist/vibeui-dist-OEPU6BOM.js +27 -0
  28. package/dist/vibeui-dist.d.ts +6 -0
  29. package/dist/webui-server/host-settings.d.ts +1 -1
  30. package/dist/webui-server/http-bridge.d.ts +1 -1
  31. package/dist/webui-server/instance-registration.d.ts +1 -1
  32. package/dist/{webui-server-V2R5WQUK.js → webui-server-KREX7GYA.js} +11 -9
  33. package/dist/webui-server-options.d.ts +1 -1
  34. package/dist/wrongforge-leader/wrongforge-leader-provider.d.ts +8 -1
  35. package/package.json +35 -34
  36. package/dist/short-circuit-flags-AGHURHMQ.js +0 -11
@@ -4,13 +4,13 @@ import {
4
4
  loadCachedAcpRegistry,
5
5
  refreshAcpRegistry,
6
6
  setupProvider
7
- } from "./chunk-M7FNHCE3.js";
7
+ } from "./chunk-EAATL6FL.js";
8
8
  import {
9
9
  createHqCommandDispatcher,
10
10
  wrongstackBridgeSetup
11
- } from "./chunk-5SGMUDP5.js";
11
+ } from "./chunk-QRWXHGLB.js";
12
12
  import "./chunk-3LEIFO5D.js";
13
- import "./chunk-VEESMPTX.js";
13
+ import "./chunk-PGC44T7N.js";
14
14
  import {
15
15
  startCliHqConnection
16
16
  } from "./chunk-76JE54WD.js";
@@ -18,7 +18,7 @@ import {
18
18
  WebSocketServer
19
19
  } from "./chunk-OZCYZYN2.js";
20
20
  import "./chunk-UFRVTBV6.js";
21
- import "./chunk-LUEO5HLZ.js";
21
+ import "./chunk-X4DEQLAZ.js";
22
22
  import "./chunk-HKMOIIPQ.js";
23
23
  import "./chunk-W23PY4IJ.js";
24
24
 
@@ -1124,7 +1124,7 @@ var acpCmd = async (args, deps) => {
1124
1124
  const sub = args[0];
1125
1125
  const loginIndex = sub === "auth" ? 0 : (sub === "server" || sub === "serve") && args[1] === "auth" ? 1 : -1;
1126
1126
  if (loginIndex >= 0) {
1127
- const { authCmd } = await import("./auth-2ZG5DBHS.js");
1127
+ const { authCmd } = await import("./auth-XOJIPFFX.js");
1128
1128
  return authCmd(args.slice(loginIndex + 1), deps);
1129
1129
  }
1130
1130
  if (!sub || sub === "server" || sub === "serve") {
@@ -1205,4 +1205,4 @@ parallel:
1205
1205
  export {
1206
1206
  acpCmd
1207
1207
  };
1208
- //# sourceMappingURL=acp-NKUEYJDZ.js.map
1208
+ //# sourceMappingURL=acp-DJWUTU5J.js.map
@@ -40,6 +40,13 @@ export interface AcpLeaderHost {
40
40
  runTool(name: string, input: Record<string, unknown>, run?: {
41
41
  sessionId?: string | undefined;
42
42
  approve?: ((tool: Tool, input: Record<string, unknown>) => Promise<boolean>) | undefined;
43
+ /**
44
+ * Settle the call's tool card here: no tool_result reaches the
45
+ * conversation, and the caller's own stream does not draw one (the
46
+ * WrongForge leader's `wstack_*` calls). An ACP agent reports its fleet
47
+ * calls itself, so its cards come from its stream instead.
48
+ */
49
+ announce?: boolean | undefined;
43
50
  }): Promise<{
44
51
  content: string;
45
52
  isError: boolean;
@@ -1,6 +1,15 @@
1
1
  import type { AgentContextUsage, Capabilities, Config, Provider, ProviderFactory, Request, Response, StreamEvent } from '@wrongstack/core/types';
2
2
  import { ACP_LEADER_PROVIDER_ID } from './acp-leader-host.js';
3
3
  export { ACP_LEADER_PROVIDER_ID };
4
+ /**
5
+ * The side model the user set for an agent leader (`acp.leader.sideModel`),
6
+ * without falling back to the fallback chain: a leader that can answer side
7
+ * requests itself (WrongForge) must not hand them to `fallbackModels[0]`.
8
+ */
9
+ export declare function explicitLeaderSideModel(config: Config): {
10
+ provider: string;
11
+ model: string;
12
+ } | undefined;
4
13
  export declare function leaderSideModel(config: Config): {
5
14
  provider: string;
6
15
  model: string;
@@ -58,6 +67,7 @@ export declare class AcpLeaderProvider implements Provider {
58
67
  private observeContext;
59
68
  private publishContext;
60
69
  private loadAcp;
70
+ private dropFleetGrants;
61
71
  private drop;
62
72
  /** The persistent agent session for this conversation, started on first use. */
63
73
  private sessionFor;
@@ -0,0 +1,5 @@
1
+ /** The WrongStack `skill` tool as an agent sees it on this route, if offered. */
2
+ export declare function externalSkillLoader(offered: readonly string[] | undefined, prefix: string): string | undefined;
3
+ /** `text` with its required-skills marker rewritten for an agent leader. */
4
+ export declare function externalSkillDirective(text: string, loader: string | undefined): string;
5
+ //# sourceMappingURL=acp-leader-skills.d.ts.map
@@ -34,6 +34,13 @@ export declare class TextBlocks {
34
34
  text(chunk: string): void;
35
35
  close(): void;
36
36
  }
37
+ interface CardOutput {
38
+ output: string;
39
+ fullOutput?: string | undefined;
40
+ outputBytes: number;
41
+ }
42
+ /** A card's output fields for a result text that no tool_result will carry. */
43
+ export declare function cardOutputForText(text: string): CardOutput;
37
44
  /**
38
45
  * The agent's own tool calls as the host's tool cards: `tool.started` once a
39
46
  * call is announced, `tool.executed` once it settles. Named by the agent's
@@ -80,4 +87,5 @@ export declare function agentTurnError(err: unknown, agentId: string, classify:
80
87
  error: ProviderError;
81
88
  dead: boolean;
82
89
  };
90
+ export {};
83
91
  //# sourceMappingURL=acp-leader-stream.d.ts.map
@@ -44,7 +44,7 @@ import {
44
44
  import "./chunk-BCKVNORU.js";
45
45
  import {
46
46
  parseAuthFlags
47
- } from "./chunk-M7PSENGF.js";
47
+ } from "./chunk-CL3BU45P.js";
48
48
  import "./chunk-W23PY4IJ.js";
49
49
 
50
50
  // src/subcommands/handlers/auth.ts
@@ -745,4 +745,4 @@ async function runAuthRemove(deps, providerId) {
745
745
  export {
746
746
  authCmd
747
747
  };
748
- //# sourceMappingURL=auth-2ZG5DBHS.js.map
748
+ //# sourceMappingURL=auth-XOJIPFFX.js.map
@@ -33,7 +33,7 @@ var helpCmd = async (_args, deps) => {
33
33
  ["wstack", "Interactive session (REPL or TUI, picked at launch)"],
34
34
  ['wstack "<task>"', 'Run one task and exit (also: --prompt "<task>")'],
35
35
  ["wstack quick", "Straight into the TUI with saved defaults"],
36
- ["wstack webui | simpleui", "Browser UI / minimal chat UI for this project"],
36
+ ["wstack webui | simpleui | vibeui", "Browser UI / minimal chat / focused coding workspace"],
37
37
  ["wstack desktop", "WrongStack Desktop (requires @wrongstack/desktop)"],
38
38
  ["wstack hq", "HQ command center across projects and machines"],
39
39
  ["wstack remote <user@host:/path>", "Run on a remote machine over SSH; WebUI tunneled here"],
@@ -129,6 +129,7 @@ var helpCmd = async (_args, deps) => {
129
129
  "--simpleui [same flags] [--full-auto]",
130
130
  "Minimal chat UI; --full-auto = YOLO + autonomy for this run only"
131
131
  ],
132
+ ["--vibeui [same browser flags]", "Focused coding workspace (default port 3476)"],
132
133
  [
133
134
  "--hq [--host <h>] [--port <n>] [--password <p>] [--tunnel] [--open]",
134
135
  "HQ server (+ --hq-allowlist, --hq-public-url)"
@@ -158,4 +159,4 @@ export {
158
159
  versionCmd,
159
160
  helpCmd
160
161
  };
161
- //# sourceMappingURL=chunk-UROB7MK5.js.map
162
+ //# sourceMappingURL=chunk-BQ5OMSZG.js.map
@@ -54,6 +54,7 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
54
54
  "webui",
55
55
  "webui-session-child",
56
56
  "simpleui",
57
+ "vibeui",
57
58
  "full-auto",
58
59
  "desktop",
59
60
  "open",
@@ -205,11 +206,11 @@ function parseArgs(argv) {
205
206
  return { flags, positional };
206
207
  }
207
208
  function normalizeSurfaceAliases(flags, positional) {
208
- if (flags["simpleui"]) flags["webui"] = true;
209
+ if (flags["simpleui"] || flags["vibeui"]) flags["webui"] = true;
209
210
  if (flags["webui-session-child"]) flags["webui"] = true;
210
211
  const first = positional[0];
211
- if (first === "simpleui") {
212
- flags["simpleui"] = true;
212
+ if (first === "simpleui" || first === "vibeui") {
213
+ flags[first] = true;
213
214
  flags["webui"] = true;
214
215
  positional.splice(0, 1);
215
216
  return;
@@ -313,4 +314,4 @@ export {
313
314
  parseAuthFlags,
314
315
  parseSpawnFlags
315
316
  };
316
- //# sourceMappingURL=chunk-M7PSENGF.js.map
317
+ //# sourceMappingURL=chunk-CL3BU45P.js.map
@@ -3,7 +3,7 @@ import {
3
3
  reportWrongForgeQuota,
4
4
  writeWrongForgeInfoCache,
5
5
  wrongstackBridgeSetup
6
- } from "./chunk-5SGMUDP5.js";
6
+ } from "./chunk-QRWXHGLB.js";
7
7
  import {
8
8
  isRestrictedMode
9
9
  } from "./chunk-3LEIFO5D.js";
@@ -21,7 +21,7 @@ import {
21
21
  toProgress,
22
22
  wfTokens,
23
23
  wrongForgeCall
24
- } from "./chunk-VEESMPTX.js";
24
+ } from "./chunk-PGC44T7N.js";
25
25
  import {
26
26
  buildProviderForId,
27
27
  resolveProviderCfgWithProxy
@@ -30,7 +30,7 @@ import {
30
30
  ACP_LEADER_PROVIDER_ID,
31
31
  acpLeaderHost,
32
32
  trackAcpLeader
33
- } from "./chunk-LUEO5HLZ.js";
33
+ } from "./chunk-X4DEQLAZ.js";
34
34
 
35
35
  // src/acp-agent-list.ts
36
36
  import { REGISTRY_ID_ALIASES } from "@wrongstack/acp";
@@ -569,6 +569,7 @@ function publishLeaderContext(events, sessionId, reading, fallbackMaxContext) {
569
569
 
570
570
  // src/acp-leader/acp-leader-fleet.ts
571
571
  import { randomBytes } from "node:crypto";
572
+ import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
572
573
  import { MCPServer, serveHttp } from "@wrongstack/mcp";
573
574
  var FLEET_SERVER_NAME = "wrongstack-fleet";
574
575
  var FLEET_TOOLS = [
@@ -579,7 +580,10 @@ var FLEET_TOOLS = [
579
580
  "roll_up",
580
581
  "fleet_status",
581
582
  "session_note",
582
- "terminate_subagent"
583
+ "terminate_subagent",
584
+ // Not a fleet tool, but it too must run in this process: WrongStack's
585
+ // skills (a /bughunt playbook) reach the agent only through it.
586
+ "skill"
583
587
  ];
584
588
  var MCP_PREFIX = `mcp__${FLEET_SERVER_NAME}__`;
585
589
  function fleetToolName(mcpName) {
@@ -605,7 +609,11 @@ async function startFleetEndpoint(host, run) {
605
609
  if (!offered.has(name)) {
606
610
  return { content: `Tool "${name}" is not offered here`, isError: true };
607
611
  }
608
- return run ? host.runTool(name, args, run) : host.runTool(name, args);
612
+ try {
613
+ return await (run ? host.runTool(name, args, run) : host.runTool(name, args));
614
+ } catch (err) {
615
+ return { content: `Failed to execute ${name}: ${toErrorMessage2(err)}`, isError: true };
616
+ }
609
617
  }
610
618
  }
611
619
  });
@@ -631,7 +639,11 @@ function fleetNote(tools) {
631
639
  ...has("fleet_status") ? ["- fleet_status: what is running and its progress."] : [],
632
640
  ...has("roll_up") ? ["- roll_up: the full result of finished tasks."] : [],
633
641
  ...has("session_note") ? ["- session_note: steer a running worker."] : [],
634
- ...has("terminate_subagent") ? ["- terminate_subagent: stop a worker."] : []
642
+ ...has("terminate_subagent") ? ["- terminate_subagent: stop a worker."] : [],
643
+ ...has("skill") ? [
644
+ "- skill: load one of WrongStack's skills by name (follow nextOffset to the last page);",
645
+ " a task that names WrongStack skills means these."
646
+ ] : []
635
647
  ].join("\n");
636
648
  }
637
649
 
@@ -674,9 +686,28 @@ function writeLeaderSession(stateDir, sessionId, record) {
674
686
  return writing.catch(() => void 0);
675
687
  }
676
688
 
689
+ // src/acp-leader/acp-leader-skills.ts
690
+ import { parseRequiredSkillsMarker } from "@wrongstack/core/skills";
691
+ var MARKERS = /<!--\s*wrongstack:required-skills\s+[\s\S]*?-->/g;
692
+ function externalSkillLoader(offered, prefix) {
693
+ return offered?.includes("skill") ? `${prefix}skill` : void 0;
694
+ }
695
+ function externalSkillDirective(text, loader) {
696
+ const skills = parseRequiredSkillsMarker(text);
697
+ if (!skills) return text;
698
+ const list = skills.join(", ");
699
+ const line = loader ? `[WrongStack] This task's playbook skills: ${list}. They are WrongStack skills: load each with the \`${loader}\` tool, following nextOffset to the last page, before changing code. Where the text below names the \`skill\` tool or a $name, it means that tool and these skills.` : `[WrongStack] This task's playbook skills: ${list}. This route offers no WrongStack skill tool: load them with your own skill tool if you have them, otherwise follow the protocol below without them.`;
700
+ let first = true;
701
+ return text.replace(MARKERS, () => {
702
+ if (!first) return "";
703
+ first = false;
704
+ return line;
705
+ });
706
+ }
707
+
677
708
  // src/acp-leader/acp-leader-stream.ts
678
709
  import { ProviderError } from "@wrongstack/core/types";
679
- import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
710
+ import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
680
711
  var StreamQueue = class {
681
712
  items = [];
682
713
  wake;
@@ -742,10 +773,7 @@ function clip(text, max) {
742
773
  if (last >= 55296 && last <= 56319) end -= 1;
743
774
  return `${text.slice(0, end)}\u2026`;
744
775
  }
745
- function cardOutput(call) {
746
- const raw = call.rawOutput;
747
- const text = call.output ?? (raw ? typeof raw.output === "string" ? raw.output : JSON.stringify(raw) : void 0);
748
- if (text === void 0) return void 0;
776
+ function cardOutputForText(text) {
749
777
  const output = clip(text, OUTPUT_PREVIEW_CHARS);
750
778
  return {
751
779
  output,
@@ -754,6 +782,12 @@ function cardOutput(call) {
754
782
  outputBytes: Buffer.byteLength(text)
755
783
  };
756
784
  }
785
+ function cardOutput(call) {
786
+ const raw = call.rawOutput;
787
+ const text = call.output ?? (raw ? typeof raw.output === "string" ? raw.output : JSON.stringify(raw) : void 0);
788
+ if (text === void 0) return void 0;
789
+ return cardOutputForText(text);
790
+ }
757
791
  var ToolCardRelay = class {
758
792
  constructor(events, meta) {
759
793
  this.events = events;
@@ -886,7 +920,7 @@ function agentTurnError(err, agentId, classify) {
886
920
  };
887
921
  }
888
922
  const kind = err?.kind;
889
- const message = `${agentId}: ${toErrorMessage2(err)}`;
923
+ const message = `${agentId}: ${toErrorMessage3(err)}`;
890
924
  if (kind === "stalled") {
891
925
  return {
892
926
  error: new ProviderError(message, 408, false, "acp", { kind: "timeout", cause: err }),
@@ -941,8 +975,11 @@ function parseSideModel(ref) {
941
975
  if (provider === ACP_LEADER_PROVIDER_ID) return void 0;
942
976
  return { provider, model: ref.slice(slash + 1) };
943
977
  }
978
+ function explicitLeaderSideModel(config) {
979
+ return parseSideModel(config.acp?.leader?.sideModel);
980
+ }
944
981
  function leaderSideModel(config) {
945
- return parseSideModel(config.acp?.leader?.sideModel) ?? parseSideModel(config.fallbackBridge) ?? parseSideModel(config.fallbackModels?.[0]);
982
+ return explicitLeaderSideModel(config) ?? parseSideModel(config.fallbackBridge) ?? parseSideModel(config.fallbackModels?.[0]);
946
983
  }
947
984
  function isLeaderTurn(req) {
948
985
  const { sessionId, threadId } = req.cache ?? {};
@@ -1060,7 +1097,10 @@ var AcpLeaderProvider = class {
1060
1097
  }
1061
1098
  /** The turn's prompt; an agent joining a conversation under way gets the turns before it. */
1062
1099
  promptText(leader, req) {
1063
- const latest = latestUserText(req.messages);
1100
+ const latest = externalSkillDirective(
1101
+ latestUserText(req.messages),
1102
+ externalSkillLoader(leader.fleet?.tools, `mcp__${FLEET_SERVER_NAME}__`)
1103
+ );
1064
1104
  const earlier = leader.joined ? void 0 : earlierConversation(req.messages);
1065
1105
  leader.joined = true;
1066
1106
  const prompt = earlier ? `${earlier}
@@ -1136,8 +1176,20 @@ ${withNotes}` : withNotes;
1136
1176
  this.acp ??= await import("@wrongstack/acp");
1137
1177
  return this.acp;
1138
1178
  }
1179
+ dropFleetGrants(sessionId) {
1180
+ for (const grantKey of this.fleetGrants.keys()) {
1181
+ try {
1182
+ const parsed = JSON.parse(grantKey);
1183
+ if (Array.isArray(parsed) && parsed[0] === sessionId) {
1184
+ this.fleetGrants.delete(grantKey);
1185
+ }
1186
+ } catch {
1187
+ }
1188
+ }
1189
+ }
1139
1190
  drop(key, leader) {
1140
1191
  this.sessions.delete(key);
1192
+ this.dropFleetGrants(key);
1141
1193
  void leader.session.close().catch(() => {
1142
1194
  });
1143
1195
  void leader.fleet?.close().catch(() => {
@@ -1312,6 +1364,7 @@ ${fleetNote(fleet.tools)}` : "")
1312
1364
  async closeAll() {
1313
1365
  const all = [...this.sessions.values()];
1314
1366
  this.sessions.clear();
1367
+ this.fleetGrants.clear();
1315
1368
  await Promise.all(
1316
1369
  all.map(
1317
1370
  (p) => p.then((leader) => {
@@ -1371,7 +1424,7 @@ import { registerBuiltinProviderAuthStrategies } from "@wrongstack/providers/oau
1371
1424
  // src/wrongforge-leader/wrongforge-leader-provider.ts
1372
1425
  import { consumeBtwNotes as consumeBtwNotes3 } from "@wrongstack/core/agent";
1373
1426
  import { ProviderError as ProviderError3 } from "@wrongstack/core/types";
1374
- import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
1427
+ import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
1375
1428
 
1376
1429
  // src/wrongforge-leader/wrongforge-leader-gateway.ts
1377
1430
  import { randomBytes as randomBytes2 } from "node:crypto";
@@ -1379,7 +1432,7 @@ import http from "node:http";
1379
1432
  import {
1380
1433
  compactToolDefinitionForWire,
1381
1434
  subjectForToolInput as subjectForToolInput2,
1382
- toErrorMessage as toErrorMessage3
1435
+ toErrorMessage as toErrorMessage4
1383
1436
  } from "@wrongstack/core/utils";
1384
1437
  import { timingSafeTokenEqual } from "@wrongstack/primitives";
1385
1438
  var NOT_OFFERED = /* @__PURE__ */ new Set([
@@ -1475,14 +1528,17 @@ async function startLeaderGateway(host, sessionId) {
1475
1528
  }
1476
1529
  host.runTool(name, input, {
1477
1530
  sessionId,
1478
- approve: (t, i) => askUser(host, sessionId, t, i)
1531
+ approve: (t, i) => askUser(host, sessionId, t, i),
1532
+ // The provider draws no card for a `wstack_*` call (its events are
1533
+ // marked external), so the session's card is settled here.
1534
+ announce: true
1479
1535
  }).then(
1480
1536
  (r) => send(res, 200, { ok: !r.isError, tool: name, content: r.content }),
1481
1537
  (err) => send(res, 200, {
1482
1538
  ok: false,
1483
1539
  tool: name,
1484
1540
  error: "tool_failed",
1485
- message: toErrorMessage3(err)
1541
+ message: toErrorMessage4(err)
1486
1542
  })
1487
1543
  );
1488
1544
  });
@@ -1582,7 +1638,7 @@ function providerError(err, id) {
1582
1638
  if (err.status >= 500) return make(err.message, err.status, true, "server");
1583
1639
  return make(`${err.code}: ${err.message}`, err.status, false, "invalid_request");
1584
1640
  }
1585
- return make(toErrorMessage4(err), 0, false, "unknown");
1641
+ return make(toErrorMessage5(err), 0, false, "unknown");
1586
1642
  }
1587
1643
  function taskError(record, id) {
1588
1644
  const make = (message, status, retryable, kind) => new ProviderError3(`WrongForge: ${message}`, status, retryable, id, { kind });
@@ -1657,19 +1713,27 @@ var WrongForgeLeaderProvider = class {
1657
1713
  }
1658
1714
  return choice.id;
1659
1715
  }
1660
- /** A configured side model, when the user named one. */
1661
- sideProvider() {
1662
- const ref = leaderSideModel(this.config());
1716
+ /**
1717
+ * A side model for a turn WrongForge does not lead. `explicitOnly` (a side
1718
+ * request through `complete()`) takes only the model the user named in
1719
+ * `acp.leader.sideModel`: WrongForge answers those itself (`oneShot`), and
1720
+ * handing them to `fallbackModels[0]` let that model take over the session.
1721
+ * A worker's turn may still fall back to the fallback chain, since `oneShot`
1722
+ * would leave it without tools.
1723
+ */
1724
+ sideProvider(explicitOnly = false) {
1725
+ const config = this.config();
1726
+ const ref = explicitOnly ? explicitLeaderSideModel(config) : leaderSideModel(config);
1663
1727
  if (!ref || ref.provider === this.id) return void 0;
1664
1728
  let provider = this.sideProviders.get(ref.provider);
1665
1729
  if (!provider) {
1666
- provider = this.opts.buildProvider(ref.provider, this.config());
1730
+ provider = this.opts.buildProvider(ref.provider, config);
1667
1731
  if (provider) this.sideProviders.set(ref.provider, provider);
1668
1732
  }
1669
1733
  return provider ? { provider, model: ref.model } : void 0;
1670
1734
  }
1671
1735
  async complete(req, opts) {
1672
- const side = this.sideProvider();
1736
+ const side = this.sideProvider(true);
1673
1737
  if (side) return side.provider.complete({ ...req, model: side.model }, opts);
1674
1738
  return this.oneShot(req, opts.signal);
1675
1739
  }
@@ -1739,8 +1803,16 @@ var WrongForgeLeaderProvider = class {
1739
1803
  try {
1740
1804
  info = await this.info(call, opts.signal);
1741
1805
  model = this.model(req.model, info);
1742
- if (host) leader.gateway ??= startLeaderGateway(host, key).catch(() => void 0);
1806
+ if (host && !leader.gateway) {
1807
+ leader.gateway = startLeaderGateway(host, key).catch(() => {
1808
+ leader.gateway = void 0;
1809
+ return void 0;
1810
+ });
1811
+ }
1743
1812
  gateway = await leader.gateway;
1813
+ if (!gateway) {
1814
+ leader.gateway = void 0;
1815
+ }
1744
1816
  joinedBefore = leader.joined ?? await this.wfSessionExists(call, leader, opts.signal);
1745
1817
  } catch (err) {
1746
1818
  throw providerError(err, this.id);
@@ -1764,7 +1836,7 @@ var WrongForgeLeaderProvider = class {
1764
1836
  "POST",
1765
1837
  "/v1/tasks",
1766
1838
  {
1767
- prompt: this.promptText(req, joinedBefore),
1839
+ prompt: this.promptText(req, joinedBefore, gateway),
1768
1840
  session_id: leader.wfSessionId,
1769
1841
  workdir: host?.projectRoot ?? process.cwd(),
1770
1842
  ...model ? { model } : {},
@@ -1780,6 +1852,10 @@ var WrongForgeLeaderProvider = class {
1780
1852
  );
1781
1853
  taskId = task.id;
1782
1854
  leader.joined = true;
1855
+ if (opts.signal.aborted) {
1856
+ cancelTask(call, taskId, "interrupted in WrongStack");
1857
+ throw new WfAborted();
1858
+ }
1783
1859
  for await (const ev of followTask(call, task.id, opts.signal)) {
1784
1860
  if (ev.type === "task.heartbeat" || ev.type === "step.finished") {
1785
1861
  void reportWrongForgeQuota({ providerId: this.id, fetchImpl: this.opts.fetchImpl });
@@ -1831,8 +1907,11 @@ var WrongForgeLeaderProvider = class {
1831
1907
  }
1832
1908
  }
1833
1909
  /** The turn's prompt; WrongForge joining a conversation under way gets the turns before it. */
1834
- promptText(req, joined) {
1835
- const latest = latestUserText(req.messages);
1910
+ promptText(req, joined, gateway) {
1911
+ const latest = externalSkillDirective(
1912
+ latestUserText(req.messages),
1913
+ gateway ? externalSkillLoader(gateway.tools, gateway.spec.name_prefix ?? "") : void 0
1914
+ );
1836
1915
  const earlier = joined ? void 0 : earlierConversation(req.messages);
1837
1916
  const prompt = earlier ? `${earlier}
1838
1917
 
@@ -2043,7 +2122,7 @@ Try \`wstack models refresh\` once you have network access.`,
2043
2122
  cause: err
2044
2123
  });
2045
2124
  }
2046
- if (config.features.modelsRegistry && !acpLeader) {
2125
+ if (config.features.modelsRegistry && !acpLeader && !wrongForgeLeader) {
2047
2126
  provider = await withCatalogCapabilities(
2048
2127
  modelsRegistry,
2049
2128
  config.provider,
@@ -2062,9 +2141,10 @@ export {
2062
2141
  wrongstackAcpPermissionPolicy,
2063
2142
  recordAcpRateLimit,
2064
2143
  startAcpTurnReminders,
2144
+ cardOutputForText,
2065
2145
  WRONGFORGE_REASONING,
2066
2146
  wrongForgeRuntime,
2067
2147
  setupProvider,
2068
2148
  createGracefulShutdown
2069
2149
  };
2070
- //# sourceMappingURL=chunk-M7FNHCE3.js.map
2150
+ //# sourceMappingURL=chunk-EAATL6FL.js.map
@@ -201,6 +201,7 @@ var LoginAttemptStore = class _LoginAttemptStore {
201
201
  store = /* @__PURE__ */ new Map();
202
202
  writeTimer;
203
203
  dirty = false;
204
+ writeTail = Promise.resolve();
204
205
  filePath;
205
206
  debounceMs;
206
207
  retentionMs;
@@ -339,9 +340,7 @@ var LoginAttemptStore = class _LoginAttemptStore {
339
340
  clearTimeout(this.writeTimer);
340
341
  this.writeTimer = void 0;
341
342
  }
342
- if (this.dirty) {
343
- await this.writeToDisk();
344
- }
343
+ await this.writeToDisk();
345
344
  }
346
345
  scheduleWrite() {
347
346
  this.dirty = true;
@@ -352,7 +351,14 @@ var LoginAttemptStore = class _LoginAttemptStore {
352
351
  }, this.debounceMs);
353
352
  this.writeTimer.unref?.();
354
353
  }
355
- async writeToDisk() {
354
+ writeToDisk() {
355
+ const pending = this.writeTail.then(() => {
356
+ return this.dirty ? this.writeSnapshotToDisk() : void 0;
357
+ });
358
+ this.writeTail = pending.catch(() => void 0);
359
+ return pending;
360
+ }
361
+ async writeSnapshotToDisk() {
356
362
  this.dirty = false;
357
363
  const now = Date.now();
358
364
  const cutoff = now - this.retentionMs;
@@ -3733,52 +3739,51 @@ ${terminalText("Reachable from other machines on your network:", "blue", { bold:
3733
3739
 
3734
3740
  // src/hq-server/server-lifecycle.ts
3735
3741
  function createHqServerShutdown(params) {
3736
- let closed = false;
3737
- return () => new Promise((res) => {
3738
- if (closed) {
3739
- res();
3740
- return;
3741
- }
3742
- closed = true;
3743
- clearInterval(params.cleanupTimer);
3744
- clearInterval(params.browserHeartbeatTimer);
3745
- clearInterval(params.timeseriesFlushTimer);
3746
- clearInterval(params.sessionCleanupTimer);
3747
- params.stopAlertEngine();
3748
- params.authWatcher.close();
3749
- params.snapshotBroadcaster.close();
3750
- params.bootstrapStore.clear();
3751
- params.persistence.timeseries.flush();
3752
- for (const ws of [...params.browsers, ...params.clients.keys()]) {
3753
- try {
3754
- ws.close(1001, "HQ shutting down");
3755
- ws.terminate();
3756
- } catch {
3742
+ let closing;
3743
+ return () => {
3744
+ if (closing) return closing;
3745
+ closing = new Promise((res) => {
3746
+ clearInterval(params.cleanupTimer);
3747
+ clearInterval(params.browserHeartbeatTimer);
3748
+ clearInterval(params.timeseriesFlushTimer);
3749
+ clearInterval(params.sessionCleanupTimer);
3750
+ params.stopAlertEngine();
3751
+ params.authWatcher.close();
3752
+ params.snapshotBroadcaster.close();
3753
+ params.bootstrapStore.clear();
3754
+ params.persistence.timeseries.flush();
3755
+ for (const ws of [...params.browsers, ...params.clients.keys()]) {
3756
+ try {
3757
+ ws.close(1001, "HQ shutting down");
3758
+ ws.terminate();
3759
+ } catch {
3760
+ }
3757
3761
  }
3758
- }
3759
- params.wss.close();
3760
- params.mailboxManager.close();
3761
- params.httpServer.close(() => {
3762
- const mailboxCloses = [...params.mailboxManager.mailboxGateways.values()].map(
3763
- ({ mailbox }) => mailbox.close().catch(() => void 0)
3764
- );
3765
- params.mailboxManager.mailboxGateways.clear();
3766
- params.mailboxManager.mailboxGatewayLastUsed.clear();
3767
- void Promise.all([
3768
- ...mailboxCloses,
3769
- // Chimera/race: flush the login-attempt store INSIDE the awaited
3770
- // shutdown set — the old fire-and-forget `void flush()` let close()
3771
- // resolve before the debounced write landed, so persistence tests
3772
- // had to sleep past it (and restarts could miss lockout state).
3773
- params.loginAttempts.flush(),
3774
- params.persistence.eventLog.drain(),
3775
- params.persistence.snapshotStore.drain(),
3776
- params.persistence.timeseries.drain(),
3777
- params.persistence.kanban.drain(),
3778
- params.persistence.sage?.drain()
3779
- ]).then(() => clearHqRuntimeMarker(params.dataDir, params.hqUrl)).catch(() => void 0).finally(() => res());
3762
+ params.wss.close();
3763
+ params.mailboxManager.close();
3764
+ params.httpServer.close(() => {
3765
+ const mailboxCloses = [...params.mailboxManager.mailboxGateways.values()].map(
3766
+ ({ mailbox }) => mailbox.close().catch(() => void 0)
3767
+ );
3768
+ params.mailboxManager.mailboxGateways.clear();
3769
+ params.mailboxManager.mailboxGatewayLastUsed.clear();
3770
+ void Promise.allSettled([
3771
+ ...mailboxCloses,
3772
+ // Chimera/race: flush the login-attempt store INSIDE the awaited
3773
+ // shutdown set — the old fire-and-forget `void flush()` let close()
3774
+ // resolve before the debounced write landed, so persistence tests
3775
+ // had to sleep past it (and restarts could miss lockout state).
3776
+ params.loginAttempts.flush(),
3777
+ params.persistence.eventLog.drain(),
3778
+ params.persistence.snapshotStore.drain(),
3779
+ params.persistence.timeseries.drain(),
3780
+ params.persistence.kanban.drain(),
3781
+ params.persistence.sage?.drain()
3782
+ ]).then(() => clearHqRuntimeMarker(params.dataDir, params.hqUrl)).catch(() => void 0).finally(() => res());
3783
+ });
3780
3784
  });
3781
- });
3785
+ return closing;
3786
+ };
3782
3787
  }
3783
3788
 
3784
3789
  // src/hq-server/socket-credentials.ts
@@ -5385,4 +5390,4 @@ export {
5385
5390
  startHqServer,
5386
5391
  HqInsecureExposureError2 as HqInsecureExposureError
5387
5392
  };
5388
- //# sourceMappingURL=chunk-JFYQGMMM.js.map
5393
+ //# sourceMappingURL=chunk-FWUIKCCO.js.map