@wrongstack/cli 1.1.2 → 1.1.5
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.
- package/dist/{acp-HCGUTFNE.js → acp-NKUEYJDZ.js} +14 -5
- package/dist/acp-leader/acp-leader-confirm.d.ts +20 -0
- package/dist/acp-leader/acp-leader-context.d.ts +29 -0
- package/dist/acp-leader/acp-leader-fleet.d.ts +26 -0
- package/dist/acp-leader/acp-leader-host-binding.d.ts +17 -0
- package/dist/acp-leader/acp-leader-host.d.ts +82 -0
- package/dist/acp-leader/acp-leader-picker.d.ts +24 -0
- package/dist/acp-leader/acp-leader-provider.d.ts +85 -0
- package/dist/acp-leader/acp-leader-resume.d.ts +10 -0
- package/dist/acp-leader/acp-leader-stream.d.ts +83 -0
- package/dist/{auth-ANOVZVHE.js → auth-2ZG5DBHS.js} +62 -66
- package/dist/auth-menu/index.d.ts +1 -2
- package/dist/boot/dispatch-webui.d.ts +16 -0
- package/dist/boot/launch-menu-arrow.d.ts +20 -0
- package/dist/boot/launch-menu-types.d.ts +36 -0
- package/dist/boot/launch-menu.d.ts +1 -57
- package/dist/boot/tui-session-resume-rollback.d.ts +31 -0
- package/dist/{boot-auto-thin-7D4OVEWA.js → boot-auto-thin-N2CIGTXE.js} +3 -3
- package/dist/{chunk-TZPKOS5G.js → chunk-2XQ6SRKG.js} +2 -2
- package/dist/chunk-3LEIFO5D.js +65 -0
- package/dist/chunk-5SGMUDP5.js +752 -0
- package/dist/{chunk-3EXRPTAU.js → chunk-5TQ65PXQ.js} +17 -17
- package/dist/{chunk-GRAGMA4B.js → chunk-7BM4LGKQ.js} +18 -18
- package/dist/{chunk-VAQXOZKP.js → chunk-AJM4EFCM.js} +139 -136
- package/dist/{chunk-C2BBH4S4.js → chunk-E3MJFAFR.js} +2 -2
- package/dist/{chunk-5TRZZHMN.js → chunk-JCZ2D3VV.js} +4 -65
- package/dist/{chunk-AWRZWPJ3.js → chunk-JFYQGMMM.js} +99 -75
- package/dist/{chunk-BIGSVBPI.js → chunk-KNYIEQAY.js} +64 -61
- package/dist/chunk-LUEO5HLZ.js +50 -0
- package/dist/chunk-LYWOV4OX.js +252 -0
- package/dist/chunk-M7FNHCE3.js +2070 -0
- package/dist/{chunk-75OLD563.js → chunk-M7PSENGF.js} +3 -1
- package/dist/{chunk-NMXQFQ26.js → chunk-MEBXWI7U.js} +5 -5
- package/dist/{chunk-RAWHRRUJ.js → chunk-MKMM5RIW.js} +209 -201
- package/dist/{chunk-3R3OADC5.js → chunk-P7MEEUGT.js} +281 -252
- package/dist/chunk-QLYPIJ7X.js +258 -0
- package/dist/chunk-RN5CFAVQ.js +292 -0
- package/dist/{chunk-WDXNBYGD.js → chunk-TDP7CZGE.js} +6 -1
- package/dist/{chunk-6VPWQ4TG.js → chunk-UROB7MK5.js} +2 -1
- package/dist/chunk-VEESMPTX.js +254 -0
- package/dist/{chunk-IJLIAPCW.js → chunk-VPSAKGLK.js} +47 -47
- package/dist/{cli-context-YIT4FEDM.js → cli-context-CQYGIWOU.js} +254 -237
- package/dist/{cli-main-JIADJ5OL.js → cli-main-VRUJNUBX.js} +2081 -1594
- package/dist/cli-main-boot.d.ts +1 -1
- package/dist/config-doctor-known-keys.d.ts +33 -0
- package/dist/config-doctor.d.ts +2 -47
- package/dist/config-history-support.d.ts +36 -0
- package/dist/config-history.d.ts +1 -1
- package/dist/{config-transfer-Y7XJMJRN.js → config-transfer-B4GOZTFC.js} +2 -2
- package/dist/{execution-BAP566QC.js → execution-IV72RUSM.js} +5354 -5258
- package/dist/execution-tui.d.ts +30 -0
- package/dist/fleet/acp-live-steer.d.ts +28 -0
- package/dist/fleet/acp-permission-bridge.d.ts +49 -0
- package/dist/fleet/acp-quota-hold.d.ts +3 -0
- package/dist/fleet/acp-quota-reading.d.ts +3 -0
- package/dist/fleet/acp-turn-reminders.d.ts +15 -0
- package/dist/fleet/acp-wrongstack-bridge.d.ts +50 -0
- package/dist/fleet/host-acp.d.ts +23 -2
- package/dist/fleet/wrongforge-client.d.ts +116 -0
- package/dist/fleet/wrongforge-gateway.d.ts +20 -0
- package/dist/fleet/wrongforge-info.d.ts +32 -0
- package/dist/fleet/wrongforge-quota.d.ts +10 -0
- package/dist/fleet/wrongforge-runner.d.ts +37 -0
- package/dist/fleet/wrongforge-runtime.d.ts +10 -0
- package/dist/{hq-JD5AZVMG.js → hq-54PQYR5O.js} +311 -307
- package/dist/hq-server/sweeps.d.ts +28 -0
- package/dist/{hq-server-BI23EPM7.js → hq-server-YK7FBLL6.js} +3 -3
- package/dist/index.js +8 -5
- package/dist/mcp-22CFBX4H.js +236 -0
- package/dist/mcp-serve-project.d.ts +18 -0
- package/dist/mcp-serve.d.ts +0 -63
- package/dist/{modeldiag-HDIKG37X.js → modeldiag-5PBB777J.js} +4 -4
- package/dist/{per-subcommand-help-D6J4ZXW2.js → per-subcommand-help-MQGERD4E.js} +2 -2
- package/dist/plugin-llm-command.d.ts +9 -0
- package/dist/{plugin-usage-TYF5HY4K.js → plugin-usage-UWKYMNQU.js} +3 -3
- package/dist/{plugins-XGAXDLPT.js → plugins-VP7JQH47.js} +4 -4
- package/dist/{sdd-runtime-6MFUXWJK.js → sdd-runtime-HIJ4Z5TF.js} +2 -2
- package/dist/services/sdd/state.d.ts +2 -0
- package/dist/session-event-wiring-types.d.ts +62 -0
- package/dist/session-event-wiring.d.ts +2 -52
- package/dist/{sessions-config-SJVQXCIE.js → sessions-config-7MK742OW.js} +2 -2
- package/dist/short-circuit-flags-AGHURHMQ.js +11 -0
- package/dist/slash-commands/command-context-fleet.d.ts +187 -0
- package/dist/slash-commands/command-context-sdd-goal.d.ts +102 -0
- package/dist/slash-commands/command-context.d.ts +3 -282
- package/dist/slash-commands/project-switch-interactive.d.ts +11 -0
- package/dist/slash-commands/setmodel-helpers.d.ts +36 -0
- package/dist/subcommands/handlers/hq-token.d.ts +3 -0
- package/dist/subcommands/handlers/hq.d.ts +0 -21
- package/dist/subcommands/handlers/per-subcommand-help-operations.d.ts +8 -0
- package/dist/subcommands/handlers/tools-cli-args.d.ts +57 -0
- package/dist/subcommands/handlers/tools-cli.d.ts +5 -0
- package/dist/subcommands/handlers/tools-serve.d.ts +4 -0
- package/dist/{subcommands-VTMIZQBP.js → subcommands-OCJNO57E.js} +2 -2
- package/dist/tool-gateway-http.d.ts +30 -0
- package/dist/tool-gateway.d.ts +185 -0
- package/dist/tools-cli-44MLIUQO.js +509 -0
- package/dist/tools-serve-TRCET2M7.js +102 -0
- package/dist/{tools-skills-55QVTMSK.js → tools-skills-WDTMCE46.js} +7 -2
- package/dist/tuneup-runtime-checks.d.ts +7 -0
- package/dist/tuneup-types.d.ts +118 -0
- package/dist/tuneup.d.ts +3 -122
- package/dist/{typesafe-YBEADNAH.js → typesafe-QLSMFU7J.js} +1 -1
- package/dist/{version-help-RBZC5GZZ.js → version-help-X6CC4CFY.js} +2 -2
- package/dist/{webui-server-HIUY3RHD.js → webui-server-V2R5WQUK.js} +79 -3
- package/dist/wiring/provider-runtime-setup.d.ts +3 -1
- package/dist/wiring/session-runtime.d.ts +1 -1
- package/dist/wrongforge-gateway-UOFBK56J.js +92 -0
- package/dist/wrongforge-leader/wrongforge-leader-gateway.d.ts +9 -0
- package/dist/wrongforge-leader/wrongforge-leader-picker.d.ts +12 -0
- package/dist/wrongforge-leader/wrongforge-leader-provider.d.ts +49 -0
- package/package.json +34 -30
- package/dist/auth-menu/anthropic-oauth.d.ts +0 -43
- package/dist/chunk-XT3NAHRX.js +0 -337
- package/dist/chunk-ZNNFEKDZ.js +0 -319
- package/dist/mcp-2XWBJEQN.js +0 -464
- package/dist/short-circuit-flags-CPCT5MWB.js +0 -11
|
@@ -0,0 +1,752 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveWrongForgeEndpoint,
|
|
3
|
+
wrongForgeCall
|
|
4
|
+
} from "./chunk-VEESMPTX.js";
|
|
5
|
+
|
|
6
|
+
// src/fleet/acp-wrongstack-bridge.ts
|
|
7
|
+
import { statSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { findAgentDescriptor, REGISTRY_ID_ALIASES } from "@wrongstack/acp";
|
|
10
|
+
import { cliSpawnArgs, isStandaloneBinary } from "@wrongstack/core/utils";
|
|
11
|
+
var DEFAULT_BRIDGE_AGENTS = ["claude-code"];
|
|
12
|
+
var DEFAULT_BRIDGE_SERVERS = [
|
|
13
|
+
"codebase-index",
|
|
14
|
+
"sage",
|
|
15
|
+
"mailbox",
|
|
16
|
+
"kanban"
|
|
17
|
+
];
|
|
18
|
+
function wstackLaunch(args, entry = process.argv[1]) {
|
|
19
|
+
if (isStandaloneBinary() || entry && /wstack|wrongstack|index\.(js|ts|mjs|cjs)$/.test(entry)) {
|
|
20
|
+
return { command: process.execPath, args: cliSpawnArgs(entry ?? "", args) };
|
|
21
|
+
}
|
|
22
|
+
return { command: "wstack", args };
|
|
23
|
+
}
|
|
24
|
+
function wstackToolsCommand(entry = process.argv[1], platform = process.platform) {
|
|
25
|
+
const launch = wstackLaunch(["tools"], entry);
|
|
26
|
+
return [launch.command, ...launch.args].map((word) => shellWord(word, platform)).join(" ");
|
|
27
|
+
}
|
|
28
|
+
function shellWord(word, platform) {
|
|
29
|
+
const w = platform === "win32" ? word.replace(/\\/g, "/") : word;
|
|
30
|
+
return /^[A-Za-z0-9_./:@=+-]+$/.test(w) ? w : `"${w.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
31
|
+
}
|
|
32
|
+
function cliIndexNote(cmd) {
|
|
33
|
+
return [
|
|
34
|
+
`- \`${cmd} run <tool> -- --<field> <value>\` (run through your shell): a prebuilt, ranked`,
|
|
35
|
+
" symbol index of this repository, caught up with the disk on every call. Prefer it over",
|
|
36
|
+
' grep/glob and whole-file reads for code navigation: codebase-context --query "<task>"',
|
|
37
|
+
" (files relevant to a task), codebase-search --query <name> (where something is defined),",
|
|
38
|
+
" codebase-incoming-calls / codebase-outgoing-calls --symbol <name> (callers, callees),",
|
|
39
|
+
" codebase-read-symbol --file <path> --symbol <name> (one symbol's source),",
|
|
40
|
+
" codebase-skeleton --path <file-or-dir> (an outline), codebase-impact-analysis --symbol",
|
|
41
|
+
` <name> (what a change affects). \`${cmd} describe <tool>\` lists a tool's fields;`,
|
|
42
|
+
` \`${cmd} batch\` runs several JSONL calls ({"tool":\u2026,"input":\u2026}) in one process.`,
|
|
43
|
+
" Use grep for literal text such as strings, comments and config values."
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
function serviceNotes(servers, delegated, indexCli) {
|
|
47
|
+
const viaCli = indexCli !== void 0 && servers.includes("codebase-index");
|
|
48
|
+
const mcp = viaCli ? servers.filter((s) => s !== "codebase-index") : servers;
|
|
49
|
+
return [
|
|
50
|
+
...mcp.length > 0 ? [
|
|
51
|
+
"WrongStack's project services are available as MCP servers:",
|
|
52
|
+
...mcp.map((s) => SERVER_NOTES[s](delegated))
|
|
53
|
+
] : [],
|
|
54
|
+
...viaCli ? [
|
|
55
|
+
...mcp.length > 0 ? [""] : [],
|
|
56
|
+
"WrongStack's codebase index is available through its CLI:",
|
|
57
|
+
...cliIndexNote(indexCli)
|
|
58
|
+
] : []
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
var SERVER_NAMES = {
|
|
62
|
+
sage: "wrongstack-sage",
|
|
63
|
+
mailbox: "wrongstack-mailbox",
|
|
64
|
+
kanban: "wrongstack-kanban",
|
|
65
|
+
"codebase-index": "wrongstack-index",
|
|
66
|
+
"requirement-intake": "wrongstack-intake"
|
|
67
|
+
};
|
|
68
|
+
var SERVER_NOTES = {
|
|
69
|
+
sage: () => "- wrongstack-sage: the project's shared memory. Recall what is relevant before you start; when you learn something durable (a decision, a gotcha, a convention), propose it with memory_candidates (WrongStack reviews proposals).",
|
|
70
|
+
mailbox: (delegated) => "- wrongstack-mailbox: the project's agent mailbox, already signed in as you. Send progress, blockers and questions to " + (delegated ? '"@session" (the WrongStack session that delegated this task), ' : '"all" (the agents working in this project), ') + '(a send needs a type, e.g. "note"), check unread mail during long work, and acknowledge what you handle.',
|
|
71
|
+
kanban: () => "- wrongstack-kanban: the project's board. If the task names a card, keep its status current. Lists return only the most recently updated (pass limit for more); use the stats action for totals and workbench for what is in flight.",
|
|
72
|
+
"codebase-index": () => "- wrongstack-index: a prebuilt, ranked symbol index of this repository, kept fresh by WrongStack. Prefer it over grep/glob and whole-file reads for code navigation: codebase_search (where something is defined), codebase_incoming_calls / codebase_outgoing_calls (callers, callees), codebase_read_symbol (one symbol's source), codebase_skeleton (a file or folder outline), codebase_impact_analysis (what a change affects), codebase_context (files relevant to a task), codebase_repo_map (overview). Use grep for literal text such as strings, comments and config values.",
|
|
73
|
+
"requirement-intake": () => "- wrongstack-intake: the project's requirements intake. List open requests; file a new one when you are handed an unrecorded software request."
|
|
74
|
+
};
|
|
75
|
+
function serviceWorkflow(servers, indexCli) {
|
|
76
|
+
return [
|
|
77
|
+
...servers.includes("codebase-index") ? indexCli !== void 0 ? [
|
|
78
|
+
"Use the WrongStack index first for code navigation; use your file tools for edits,",
|
|
79
|
+
"exact literal searches, and verification. Start a code task with",
|
|
80
|
+
`${indexCli} run codebase-context -- --query "<the task in a few words>"; for a named`,
|
|
81
|
+
`symbol use ${indexCli} run codebase-search -- --query <name>. If the index lacks a`,
|
|
82
|
+
"result, fall back to your tools."
|
|
83
|
+
] : [
|
|
84
|
+
"Use wrongstack-index first for code navigation; use your file tools for edits, exact",
|
|
85
|
+
"literal searches, and verification. Start a code task with",
|
|
86
|
+
"mcp__wrongstack-index__codebase_context (task description); for a named symbol use",
|
|
87
|
+
"mcp__wrongstack-index__codebase_search. If the index lacks a result, fall back to your tools."
|
|
88
|
+
] : [],
|
|
89
|
+
...servers.includes("sage") ? [
|
|
90
|
+
"Recall relevant decisions from wrongstack-sage before substantial work; propose durable",
|
|
91
|
+
"findings to memory_candidates after verification. Start with",
|
|
92
|
+
"mcp__wrongstack-sage__memory_search using the task topic."
|
|
93
|
+
] : [],
|
|
94
|
+
...servers.includes("mailbox") ? [
|
|
95
|
+
"Check wrongstack-mailbox for unread messages when starting and during long work;",
|
|
96
|
+
'use mcp__wrongstack-mailbox__mailbox_read with action "inbox" to read your',
|
|
97
|
+
"messages. Send meaningful",
|
|
98
|
+
"progress or blockers and acknowledge messages you handle."
|
|
99
|
+
] : [],
|
|
100
|
+
...servers.includes("kanban") ? [
|
|
101
|
+
"Use wrongstack-kanban when work is tied to a board task; inspect its current state",
|
|
102
|
+
"with mcp__wrongstack-kanban__kanban_read and update it as work progresses."
|
|
103
|
+
] : [],
|
|
104
|
+
"If a project service is unavailable, continue with available tools and report the gap."
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
function bridgeTaskKickoff(servers, indexCli) {
|
|
108
|
+
return [
|
|
109
|
+
"Project-service kickoff for this task:",
|
|
110
|
+
...servers.includes("codebase-index") ? [
|
|
111
|
+
indexCli !== void 0 ? `First run ${indexCli} run codebase-context -- --query "<the task>" to locate relevant code.` : "First use mcp__wrongstack-index__codebase_context to locate relevant code."
|
|
112
|
+
] : [],
|
|
113
|
+
...servers.includes("sage") ? ["Use mcp__wrongstack-sage__memory_search for prior project guidance."] : [],
|
|
114
|
+
...servers.includes("mailbox") ? ['Check mcp__wrongstack-mailbox__mailbox_read with action "inbox".'] : [],
|
|
115
|
+
...servers.includes("kanban") ? ["If the task names a board card, inspect it with mcp__wrongstack-kanban__kanban_read."] : [],
|
|
116
|
+
"Then use file and other tools as the task requires."
|
|
117
|
+
].join("\n");
|
|
118
|
+
}
|
|
119
|
+
function serverArgs(server, projectRoot, ctx) {
|
|
120
|
+
const actor = ctx.subagentId;
|
|
121
|
+
const base = ["mcp", "serve", server, `--project-root=${projectRoot}`];
|
|
122
|
+
switch (server) {
|
|
123
|
+
case "sage":
|
|
124
|
+
return [...base, `--origin=${actor}`];
|
|
125
|
+
case "mailbox": {
|
|
126
|
+
const name = ctx.config.name ?? actor;
|
|
127
|
+
return [
|
|
128
|
+
...base,
|
|
129
|
+
`--actor=${actor}`,
|
|
130
|
+
`--name=${name}`,
|
|
131
|
+
...ctx.config.role ? [`--role=${ctx.config.role}`] : [],
|
|
132
|
+
...ctx.sessionId ? [`--session-id=${ctx.sessionId}`] : [],
|
|
133
|
+
// Switch last: the global parser would read a following word as its value.
|
|
134
|
+
"--writable"
|
|
135
|
+
];
|
|
136
|
+
}
|
|
137
|
+
case "kanban":
|
|
138
|
+
return [...base, `--actor=${actor}`, "--writable"];
|
|
139
|
+
case "codebase-index":
|
|
140
|
+
return [...base, "--watch"];
|
|
141
|
+
case "requirement-intake":
|
|
142
|
+
return [...base, "--writable"];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function bridgeNote(servers, delegated = true, indexCli) {
|
|
146
|
+
return [
|
|
147
|
+
"You are working as a delegated worker inside a WrongStack project.",
|
|
148
|
+
"WrongStack is the orchestrator the user runs: it sent you this task and receives your",
|
|
149
|
+
"final reply, so end with a clear summary of what you did and what is left.",
|
|
150
|
+
...delegated ? [
|
|
151
|
+
"While you work, the WrongStack session can steer you: such a message arrives as a",
|
|
152
|
+
'user message starting with "[WrongStack <kind> from <agent>]". Treat it as coming',
|
|
153
|
+
"from the user who delegated the task, and answer what it asks."
|
|
154
|
+
] : [],
|
|
155
|
+
"",
|
|
156
|
+
...serviceNotes(servers, delegated, indexCli),
|
|
157
|
+
"",
|
|
158
|
+
...serviceWorkflow(servers, indexCli),
|
|
159
|
+
"",
|
|
160
|
+
"WrongStack applies the user's rules to your edits and commands. A refused permission",
|
|
161
|
+
"comes from them (a deny rule the user wrote, a history-rewriting git command, a",
|
|
162
|
+
"credential read): do not retry it or work around it; finish what you can and name what",
|
|
163
|
+
"was refused in your final reply."
|
|
164
|
+
].join("\n");
|
|
165
|
+
}
|
|
166
|
+
function leaderBridgeNote(servers, indexCli) {
|
|
167
|
+
return [
|
|
168
|
+
"You are the leader of a WrongStack session. The user talks to you through WrongStack",
|
|
169
|
+
"(its terminal UI or web UI), which shows your replies, your tool calls and their results.",
|
|
170
|
+
"",
|
|
171
|
+
...serviceNotes(servers, false, indexCli),
|
|
172
|
+
"",
|
|
173
|
+
...serviceWorkflow(servers, indexCli),
|
|
174
|
+
"",
|
|
175
|
+
"WrongStack applies the user's rules to your edits and commands and asks the user when one",
|
|
176
|
+
"needs approval. A refused permission is the user's decision or rule: do not retry it or",
|
|
177
|
+
"work around it.",
|
|
178
|
+
"",
|
|
179
|
+
"When you finish a turn and a concrete follow-on action exists, end your reply with a",
|
|
180
|
+
"<nextsteps> block of 1-4 exact prompts the user can send back to you, one per numbered",
|
|
181
|
+
"line, for example:",
|
|
182
|
+
"<nextsteps>",
|
|
183
|
+
"1. Run the package tests and fix any failure",
|
|
184
|
+
"</nextsteps>",
|
|
185
|
+
"When the work is done, end with <nextsteps-complete/> instead."
|
|
186
|
+
].join("\n");
|
|
187
|
+
}
|
|
188
|
+
function onPath(command, env = process.env, platform = process.platform) {
|
|
189
|
+
const pathVar = env.PATH ?? env.Path ?? "";
|
|
190
|
+
const exts = platform === "win32" ? ["", ...(env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";")] : [""];
|
|
191
|
+
const sep = platform === "win32" ? ";" : ":";
|
|
192
|
+
for (const dir of pathVar.split(sep)) {
|
|
193
|
+
if (!dir) continue;
|
|
194
|
+
for (const ext of exts) {
|
|
195
|
+
try {
|
|
196
|
+
if (statSync(join(dir, command + ext)).isFile()) return true;
|
|
197
|
+
} catch {
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
function installedBridgeAgents(acp, isOnPath = (c) => onPath(c)) {
|
|
204
|
+
if ((acp?.bridge?.servers ?? DEFAULT_BRIDGE_SERVERS).length === 0) return [];
|
|
205
|
+
const toCatalogId = new Map(Object.entries(REGISTRY_ID_ALIASES).map(([c, r]) => [r, c]));
|
|
206
|
+
const out = [];
|
|
207
|
+
for (const listed of acp?.bridge?.agents ?? DEFAULT_BRIDGE_AGENTS) {
|
|
208
|
+
const id = findAgentDescriptor(listed) ? listed : toCatalogId.get(listed) ?? listed;
|
|
209
|
+
if (out.includes(id)) continue;
|
|
210
|
+
const probe = findAgentDescriptor(id)?.probe.command;
|
|
211
|
+
if (acp?.agents?.[id] || acp?.agents?.[listed] || probe && isOnPath(probe)) out.push(id);
|
|
212
|
+
}
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
function wrongstackBridgeSetup(agentId, projectRoot, config, role = "worker") {
|
|
216
|
+
const agents = config?.agents ?? DEFAULT_BRIDGE_AGENTS;
|
|
217
|
+
const listed = (a) => a === agentId || REGISTRY_ID_ALIASES[a] === agentId || REGISTRY_ID_ALIASES[agentId] === a;
|
|
218
|
+
if (!agents.some(listed)) return void 0;
|
|
219
|
+
const servers = [...new Set(config?.servers ?? DEFAULT_BRIDGE_SERVERS)].filter(
|
|
220
|
+
(s) => Object.hasOwn(SERVER_NAMES, s)
|
|
221
|
+
);
|
|
222
|
+
if (servers.length === 0) return void 0;
|
|
223
|
+
const indexCli = config?.indexSurface === "cli" && servers.includes("codebase-index") ? wstackToolsCommand() : void 0;
|
|
224
|
+
const attached = indexCli ? servers.filter((s) => s !== "codebase-index") : servers;
|
|
225
|
+
return (_task, ctx) => ({
|
|
226
|
+
promptPrefix: bridgeTaskKickoff(servers, indexCli),
|
|
227
|
+
mcpServers: attached.map((server) => {
|
|
228
|
+
const launch = wstackLaunch(serverArgs(server, projectRoot, ctx));
|
|
229
|
+
return { name: SERVER_NAMES[server], ...launch, env: [] };
|
|
230
|
+
}),
|
|
231
|
+
meta: {
|
|
232
|
+
systemPrompt: {
|
|
233
|
+
// `@session` only reaches someone when a WrongStack session delegated the task.
|
|
234
|
+
append: role === "leader" ? leaderBridgeNote(servers, indexCli) : bridgeNote(servers, ctx.sessionId !== void 0, indexCli)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/hq-command-controller.ts
|
|
241
|
+
function createProjectKanbanTransitionHandler(projectRoot) {
|
|
242
|
+
return async (input) => {
|
|
243
|
+
const { transitionTask } = await import("@wrongstack/kanban");
|
|
244
|
+
const result = await transitionTask(projectRoot, input.boardId, input.taskId, {
|
|
245
|
+
to: input.to,
|
|
246
|
+
actor: "hq-operator",
|
|
247
|
+
comment: input.comment,
|
|
248
|
+
sessionId: input.sessionId ?? "hq-mobile"
|
|
249
|
+
});
|
|
250
|
+
if (result === null) throw new Error(`kanban task not found: ${input.taskId}`);
|
|
251
|
+
return `task ${input.taskId} transitioned to ${input.to}`;
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function createProjectKanbanAssignHandler(projectRoot) {
|
|
255
|
+
return async (input) => {
|
|
256
|
+
const { assignTask } = await import("@wrongstack/kanban");
|
|
257
|
+
const result = await assignTask(
|
|
258
|
+
projectRoot,
|
|
259
|
+
input.boardId,
|
|
260
|
+
input.taskId,
|
|
261
|
+
{
|
|
262
|
+
agentId: input.agentId,
|
|
263
|
+
name: input.assignee,
|
|
264
|
+
assignee: input.assignee,
|
|
265
|
+
status: "assigned",
|
|
266
|
+
protectActiveAssignment: true
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
actor: "hq-operator",
|
|
270
|
+
note: input.comment,
|
|
271
|
+
sessionId: input.sessionId ?? "hq-mobile"
|
|
272
|
+
}
|
|
273
|
+
);
|
|
274
|
+
if (result === null) throw new Error(`kanban task not found: ${input.taskId}`);
|
|
275
|
+
return `task ${input.taskId} assigned to ${input.assignee}`;
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function createHqCommandDispatcher(controller) {
|
|
279
|
+
return async (command) => {
|
|
280
|
+
const cmd = command;
|
|
281
|
+
try {
|
|
282
|
+
const result = await dispatch(
|
|
283
|
+
controller,
|
|
284
|
+
cmd.commandId,
|
|
285
|
+
cmd.type,
|
|
286
|
+
cmd.payload
|
|
287
|
+
);
|
|
288
|
+
return result;
|
|
289
|
+
} catch (err) {
|
|
290
|
+
return {
|
|
291
|
+
commandId: command.commandId,
|
|
292
|
+
status: "failed",
|
|
293
|
+
message: err instanceof Error ? err.message : String(err)
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function resolveCommandSessionId(controller, payload) {
|
|
299
|
+
const named = payload["sessionId"];
|
|
300
|
+
if (typeof named === "string" && named.length > 0) return named;
|
|
301
|
+
return controller.sessionId?.();
|
|
302
|
+
}
|
|
303
|
+
function rejectUnknownSession(controller, commandId, sessionId) {
|
|
304
|
+
const named = sessionId;
|
|
305
|
+
if (named === void 0) return null;
|
|
306
|
+
const owns = controller.ownsSession?.(named) ?? (controller.sessionId === void 0 ? true : controller.sessionId() === named);
|
|
307
|
+
if (owns) return null;
|
|
308
|
+
return {
|
|
309
|
+
commandId,
|
|
310
|
+
status: "rejected",
|
|
311
|
+
message: `session ${named} is not active on this client`
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
function leaderSessionAffinity(to, sessionId) {
|
|
315
|
+
if (to.trim().toLowerCase() !== "leader") return {};
|
|
316
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) return {};
|
|
317
|
+
return { sessionAffinity: { sessionId } };
|
|
318
|
+
}
|
|
319
|
+
async function dispatch(controller, commandId, type, payload) {
|
|
320
|
+
const sessionId = resolveCommandSessionId(controller, payload);
|
|
321
|
+
const namedSession = typeof payload["sessionId"] === "string" ? payload["sessionId"] : void 0;
|
|
322
|
+
if (type !== "broadcast") {
|
|
323
|
+
const rejected = rejectUnknownSession(controller, commandId, namedSession);
|
|
324
|
+
if (rejected !== null) return rejected;
|
|
325
|
+
}
|
|
326
|
+
switch (type) {
|
|
327
|
+
case "steer": {
|
|
328
|
+
const mailbox = controller.steerMailbox;
|
|
329
|
+
if (mailbox === void 0) {
|
|
330
|
+
return { commandId, status: "rejected", message: "no mailbox available" };
|
|
331
|
+
}
|
|
332
|
+
const to = typeof payload["to"] === "string" ? payload["to"] : "leader";
|
|
333
|
+
const subject = typeof payload["subject"] === "string" ? payload["subject"] : "HQ steer";
|
|
334
|
+
const body = typeof payload["body"] === "string" ? payload["body"] : "";
|
|
335
|
+
const priority = payload["priority"] === "high" ? "high" : payload["priority"] === "low" ? "low" : "normal";
|
|
336
|
+
const from = `hq@${controller.sessionTag()}`;
|
|
337
|
+
await mailbox.send({
|
|
338
|
+
from,
|
|
339
|
+
to,
|
|
340
|
+
type: "steer",
|
|
341
|
+
subject,
|
|
342
|
+
body,
|
|
343
|
+
priority,
|
|
344
|
+
...leaderSessionAffinity(to, sessionId)
|
|
345
|
+
});
|
|
346
|
+
return { commandId, status: "accepted", message: `steered ${to}` };
|
|
347
|
+
}
|
|
348
|
+
case "btw": {
|
|
349
|
+
const mailbox = controller.steerMailbox;
|
|
350
|
+
if (mailbox === void 0) {
|
|
351
|
+
return { commandId, status: "rejected", message: "no mailbox available" };
|
|
352
|
+
}
|
|
353
|
+
const to = typeof payload["to"] === "string" ? payload["to"] : "leader";
|
|
354
|
+
const subject = typeof payload["subject"] === "string" ? payload["subject"] : "HQ note";
|
|
355
|
+
const body = typeof payload["body"] === "string" ? payload["body"] : "";
|
|
356
|
+
const priority = payload["priority"] === "high" ? "high" : payload["priority"] === "low" ? "low" : "normal";
|
|
357
|
+
const from = `hq@${controller.sessionTag()}`;
|
|
358
|
+
await mailbox.send({
|
|
359
|
+
from,
|
|
360
|
+
to,
|
|
361
|
+
type: "btw",
|
|
362
|
+
subject,
|
|
363
|
+
body,
|
|
364
|
+
priority,
|
|
365
|
+
...leaderSessionAffinity(to, sessionId)
|
|
366
|
+
});
|
|
367
|
+
return { commandId, status: "accepted", message: `noted ${to}` };
|
|
368
|
+
}
|
|
369
|
+
case "queue": {
|
|
370
|
+
const mailbox = controller.steerMailbox;
|
|
371
|
+
if (mailbox === void 0) {
|
|
372
|
+
return { commandId, status: "rejected", message: "no mailbox available" };
|
|
373
|
+
}
|
|
374
|
+
const to = typeof payload["to"] === "string" ? payload["to"] : "leader";
|
|
375
|
+
const subject = typeof payload["subject"] === "string" ? payload["subject"] : "HQ prompt";
|
|
376
|
+
const body = typeof payload["body"] === "string" ? payload["body"] : "";
|
|
377
|
+
const priority = payload["priority"] === "high" ? "high" : payload["priority"] === "low" ? "low" : "normal";
|
|
378
|
+
const from = `hq@${controller.sessionTag()}`;
|
|
379
|
+
await mailbox.send({
|
|
380
|
+
from,
|
|
381
|
+
to,
|
|
382
|
+
type: "note",
|
|
383
|
+
subject,
|
|
384
|
+
body,
|
|
385
|
+
priority,
|
|
386
|
+
...leaderSessionAffinity(to, sessionId)
|
|
387
|
+
});
|
|
388
|
+
return { commandId, status: "accepted", message: `queued for ${to}` };
|
|
389
|
+
}
|
|
390
|
+
case "broadcast": {
|
|
391
|
+
const mailbox = controller.steerMailbox;
|
|
392
|
+
if (mailbox === void 0) {
|
|
393
|
+
return { commandId, status: "rejected", message: "no mailbox available" };
|
|
394
|
+
}
|
|
395
|
+
const subject = typeof payload["subject"] === "string" ? payload["subject"] : "HQ broadcast";
|
|
396
|
+
const body = typeof payload["body"] === "string" ? payload["body"] : "";
|
|
397
|
+
const priority = payload["priority"] === "high" ? "high" : payload["priority"] === "low" ? "low" : "normal";
|
|
398
|
+
const from = `hq@${controller.sessionTag()}`;
|
|
399
|
+
await mailbox.send({ from, to: "all", type: "broadcast", subject, body, priority });
|
|
400
|
+
return { commandId, status: "accepted", message: "broadcast sent" };
|
|
401
|
+
}
|
|
402
|
+
case "abort": {
|
|
403
|
+
const target = typeof payload["target"] === "string" ? payload["target"] : "leader";
|
|
404
|
+
if (target === "leader") {
|
|
405
|
+
const aborted = controller.interruptLeader(sessionId);
|
|
406
|
+
return {
|
|
407
|
+
commandId,
|
|
408
|
+
status: aborted ? "completed" : "accepted",
|
|
409
|
+
message: aborted ? "leader aborted" : "no active leader run"
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
if (target === "fleet") {
|
|
413
|
+
if (controller.killFleet === void 0) {
|
|
414
|
+
return {
|
|
415
|
+
commandId,
|
|
416
|
+
status: "rejected",
|
|
417
|
+
message: "this client does not manage a fleet"
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
const killed = await controller.killFleet(sessionId);
|
|
421
|
+
return {
|
|
422
|
+
commandId,
|
|
423
|
+
status: "completed",
|
|
424
|
+
message: `fleet stopped (${killed} agents)`
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
if (controller.terminateAgent === void 0) {
|
|
428
|
+
return {
|
|
429
|
+
commandId,
|
|
430
|
+
status: "rejected",
|
|
431
|
+
message: "this client does not manage subagents"
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
const terminated = await controller.terminateAgent(target, sessionId);
|
|
435
|
+
return {
|
|
436
|
+
commandId,
|
|
437
|
+
status: terminated ? "completed" : "rejected",
|
|
438
|
+
message: terminated ? `agent ${target} terminated` : `agent ${target} not found`
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
case "spawn": {
|
|
442
|
+
const role = typeof payload["role"] === "string" ? payload["role"] : "";
|
|
443
|
+
const task = typeof payload["task"] === "string" ? payload["task"] : void 0;
|
|
444
|
+
const maxIterations = typeof payload["maxIterations"] === "number" ? payload["maxIterations"] : void 0;
|
|
445
|
+
if (controller.spawnAgent === void 0) {
|
|
446
|
+
return { commandId, status: "rejected", message: "no director active" };
|
|
447
|
+
}
|
|
448
|
+
const subagentId = await controller.spawnAgent(role, task, maxIterations, sessionId);
|
|
449
|
+
return { commandId, status: "completed", message: `spawned ${subagentId}` };
|
|
450
|
+
}
|
|
451
|
+
case "kanban-transition": {
|
|
452
|
+
if (controller.kanbanTransition === void 0) {
|
|
453
|
+
return { commandId, status: "rejected", message: "kanban control is unavailable" };
|
|
454
|
+
}
|
|
455
|
+
const message = await controller.kanbanTransition({
|
|
456
|
+
boardId: payload["boardId"],
|
|
457
|
+
taskId: payload["taskId"],
|
|
458
|
+
to: payload["to"],
|
|
459
|
+
comment: payload["comment"],
|
|
460
|
+
...sessionId !== void 0 ? { sessionId } : {}
|
|
461
|
+
});
|
|
462
|
+
return { commandId, status: "completed", message };
|
|
463
|
+
}
|
|
464
|
+
case "kanban-assign": {
|
|
465
|
+
if (controller.kanbanAssign === void 0) {
|
|
466
|
+
return { commandId, status: "rejected", message: "kanban assignment is unavailable" };
|
|
467
|
+
}
|
|
468
|
+
const message = await controller.kanbanAssign({
|
|
469
|
+
boardId: payload["boardId"],
|
|
470
|
+
taskId: payload["taskId"],
|
|
471
|
+
agentId: payload["agentId"],
|
|
472
|
+
assignee: payload["assignee"],
|
|
473
|
+
comment: payload["comment"],
|
|
474
|
+
...sessionId !== void 0 ? { sessionId } : {}
|
|
475
|
+
});
|
|
476
|
+
return { commandId, status: "completed", message };
|
|
477
|
+
}
|
|
478
|
+
case "kanban-dispatch": {
|
|
479
|
+
if (controller.kanbanDispatch === void 0) {
|
|
480
|
+
return { commandId, status: "rejected", message: "kanban dispatch is unavailable" };
|
|
481
|
+
}
|
|
482
|
+
const message = await controller.kanbanDispatch({
|
|
483
|
+
boardId: payload["boardId"],
|
|
484
|
+
taskId: payload["taskId"],
|
|
485
|
+
comment: payload["comment"],
|
|
486
|
+
...sessionId !== void 0 ? { sessionId } : {}
|
|
487
|
+
});
|
|
488
|
+
return { commandId, status: "completed", message };
|
|
489
|
+
}
|
|
490
|
+
case "approve": {
|
|
491
|
+
const resolveApproval = controller.resolveApproval;
|
|
492
|
+
if (resolveApproval === void 0) {
|
|
493
|
+
return {
|
|
494
|
+
commandId,
|
|
495
|
+
status: "rejected",
|
|
496
|
+
message: "this client does not mirror approvals"
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
const toolUseId = typeof payload["toolUseId"] === "string" ? payload["toolUseId"] : "";
|
|
500
|
+
const decision = payload["decision"];
|
|
501
|
+
if (toolUseId.length === 0 || decision !== "yes" && decision !== "no" && decision !== "always" && decision !== "always-exact" && decision !== "always-command" && decision !== "always-tool" && decision !== "deny") {
|
|
502
|
+
return { commandId, status: "rejected", message: "malformed approve command" };
|
|
503
|
+
}
|
|
504
|
+
const applied = resolveApproval(toolUseId, decision, sessionId);
|
|
505
|
+
if (!applied) {
|
|
506
|
+
return {
|
|
507
|
+
commandId,
|
|
508
|
+
status: "rejected",
|
|
509
|
+
// Says WHY, because "rejected" alone reads as a failure the operator
|
|
510
|
+
// should retry — and retrying a settled prompt does nothing.
|
|
511
|
+
message: `approval ${toolUseId} is no longer pending (already answered, aborted or timed out)`
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
return { commandId, status: "completed", message: `approval ${decision}` };
|
|
515
|
+
}
|
|
516
|
+
case "answer-input": {
|
|
517
|
+
const resolver = controller.resolveUserInput;
|
|
518
|
+
if (!resolver)
|
|
519
|
+
return {
|
|
520
|
+
commandId,
|
|
521
|
+
status: "rejected",
|
|
522
|
+
message: "this client does not mirror user input forms"
|
|
523
|
+
};
|
|
524
|
+
const requestId = typeof payload["requestId"] === "string" ? payload["requestId"] : "";
|
|
525
|
+
const response = payload["response"];
|
|
526
|
+
if (!requestId || !response || !Array.isArray(response.answers))
|
|
527
|
+
return { commandId, status: "rejected", message: "malformed answer-input command" };
|
|
528
|
+
if (!resolver(requestId, response, sessionId))
|
|
529
|
+
return {
|
|
530
|
+
commandId,
|
|
531
|
+
status: "rejected",
|
|
532
|
+
message: `user input ${requestId} is no longer pending`
|
|
533
|
+
};
|
|
534
|
+
return { commandId, status: "completed", message: "user input submitted" };
|
|
535
|
+
}
|
|
536
|
+
case "run-command": {
|
|
537
|
+
if (!controller.allowRunCommand()) {
|
|
538
|
+
return {
|
|
539
|
+
commandId,
|
|
540
|
+
status: "rejected",
|
|
541
|
+
message: "run-command requires operator opt-in (--hq-allow-exec)"
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
const mailbox = controller.steerMailbox;
|
|
545
|
+
const command = typeof payload["command"] === "string" ? payload["command"] : "";
|
|
546
|
+
if (mailbox !== void 0 && command.length > 0) {
|
|
547
|
+
const from = `hq@${controller.sessionTag()}`;
|
|
548
|
+
await mailbox.send({
|
|
549
|
+
from,
|
|
550
|
+
to: "leader",
|
|
551
|
+
type: "steer",
|
|
552
|
+
subject: "Run command (HQ)",
|
|
553
|
+
body: `Run the following shell command:
|
|
554
|
+
|
|
555
|
+
\`\`\`
|
|
556
|
+
${command}
|
|
557
|
+
\`\`\``,
|
|
558
|
+
priority: "high",
|
|
559
|
+
...leaderSessionAffinity("leader", sessionId)
|
|
560
|
+
});
|
|
561
|
+
return { commandId, status: "accepted", message: "run-command routed to leader as steer" };
|
|
562
|
+
}
|
|
563
|
+
return { commandId, status: "rejected", message: "cannot route run-command (no mailbox)" };
|
|
564
|
+
}
|
|
565
|
+
default:
|
|
566
|
+
return { commandId, status: "rejected", message: `unknown command type: ${type}` };
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// src/fleet/wrongforge-info.ts
|
|
571
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
572
|
+
import path from "node:path";
|
|
573
|
+
import { wstackGlobalRoot } from "@wrongstack/core/utils";
|
|
574
|
+
function wrongForgeInfoCacheFile() {
|
|
575
|
+
return path.join(wstackGlobalRoot(), "cache", "wrongforge-info.json");
|
|
576
|
+
}
|
|
577
|
+
function parseWrongForgeInfo(body) {
|
|
578
|
+
const o = body && typeof body === "object" ? body : {};
|
|
579
|
+
const strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.length > 0) : [];
|
|
580
|
+
const def = o["default_model"] ?? o["defaultModel"];
|
|
581
|
+
const details = o["model_details"] ?? o["modelDetails"];
|
|
582
|
+
const effort = o["effort"];
|
|
583
|
+
const levels = effort?.levels ?? o["effortLevels"];
|
|
584
|
+
return {
|
|
585
|
+
models: strings(o["models"]),
|
|
586
|
+
...typeof def === "string" && def ? { defaultModel: def } : {},
|
|
587
|
+
features: strings(o["features"]),
|
|
588
|
+
...Array.isArray(levels) ? { effortLevels: strings(levels) } : {},
|
|
589
|
+
...Array.isArray(details) ? {
|
|
590
|
+
modelDetails: details.flatMap((value) => {
|
|
591
|
+
if (!value || typeof value !== "object") return [];
|
|
592
|
+
const d = value;
|
|
593
|
+
if (typeof d["id"] !== "string" || !d["id"]) return [];
|
|
594
|
+
return [
|
|
595
|
+
{
|
|
596
|
+
id: d["id"],
|
|
597
|
+
description: typeof d["description"] === "string" ? d["description"] : "",
|
|
598
|
+
effortLevels: strings(d["effort_levels"] ?? d["effortLevels"])
|
|
599
|
+
}
|
|
600
|
+
];
|
|
601
|
+
})
|
|
602
|
+
} : {}
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
function readCachedWrongForgeInfo(file = wrongForgeInfoCacheFile()) {
|
|
606
|
+
try {
|
|
607
|
+
const info = parseWrongForgeInfo(JSON.parse(readFileSync(file, "utf8").replace(/^\uFEFF/, "")));
|
|
608
|
+
return info.models.length > 0 ? info : void 0;
|
|
609
|
+
} catch {
|
|
610
|
+
return void 0;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
function writeWrongForgeInfoCache(info, file = wrongForgeInfoCacheFile()) {
|
|
614
|
+
if (info.models.length === 0) return;
|
|
615
|
+
try {
|
|
616
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
617
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
618
|
+
writeFileSync(tmp, `${JSON.stringify(info)}
|
|
619
|
+
`);
|
|
620
|
+
renameSync(tmp, file);
|
|
621
|
+
} catch {
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
async function fetchWrongForgeInfo(ep, opts = {}) {
|
|
625
|
+
const res = await (opts.fetchImpl ?? fetch)(`${ep.url}/v1/info`, {
|
|
626
|
+
headers: { authorization: `Bearer ${ep.apiKey}` },
|
|
627
|
+
...opts.signal ? { signal: opts.signal } : {}
|
|
628
|
+
});
|
|
629
|
+
if (!res.ok) throw new Error(`WrongForge /v1/info answered ${res.status}`);
|
|
630
|
+
return parseWrongForgeInfo(await res.json());
|
|
631
|
+
}
|
|
632
|
+
function refreshWrongForgeInfoCache(ep) {
|
|
633
|
+
if (!ep) return;
|
|
634
|
+
void fetchWrongForgeInfo(ep, { signal: AbortSignal.timeout(3e3) }).then((info) => writeWrongForgeInfoCache(info)).catch(() => void 0);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// src/fleet/wrongforge-quota.ts
|
|
638
|
+
import {
|
|
639
|
+
DEFAULT_QUOTA_METER,
|
|
640
|
+
recordProviderQuota
|
|
641
|
+
} from "@wrongstack/core/quota";
|
|
642
|
+
var reads = /* @__PURE__ */ new Map();
|
|
643
|
+
var REFRESH_MS = 2e4;
|
|
644
|
+
function object(value) {
|
|
645
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
646
|
+
}
|
|
647
|
+
function percent(value) {
|
|
648
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 100;
|
|
649
|
+
}
|
|
650
|
+
async function reportWrongForgeQuota(opts = {}) {
|
|
651
|
+
const endpoint = opts.endpoint ?? resolveWrongForgeEndpoint();
|
|
652
|
+
if (!endpoint) return false;
|
|
653
|
+
const providerId = opts.providerId ?? "wrongforge";
|
|
654
|
+
const key = `${providerId}\0${endpoint.url}\0${endpoint.apiKey}`;
|
|
655
|
+
const prior = reads.get(key);
|
|
656
|
+
if (prior?.pending) {
|
|
657
|
+
if (opts.force && !prior.forced) {
|
|
658
|
+
await prior.pending;
|
|
659
|
+
return reportWrongForgeQuota(opts);
|
|
660
|
+
}
|
|
661
|
+
return prior.pending;
|
|
662
|
+
}
|
|
663
|
+
if (!opts.force && prior && Date.now() - prior.at < REFRESH_MS) return prior.ok;
|
|
664
|
+
const state = { at: Date.now(), ok: false, forced: opts.force === true };
|
|
665
|
+
reads.set(key, state);
|
|
666
|
+
if (reads.size > 128)
|
|
667
|
+
for (const [id, entry] of reads) {
|
|
668
|
+
if (!entry.pending && id !== key) {
|
|
669
|
+
reads.delete(id);
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
state.pending = (async () => {
|
|
674
|
+
try {
|
|
675
|
+
const signal = AbortSignal.timeout(opts.timeoutMs ?? 1e4);
|
|
676
|
+
const body = object(
|
|
677
|
+
await wrongForgeCall(endpoint, opts.fetchImpl)(
|
|
678
|
+
"GET",
|
|
679
|
+
`/v1/usage${opts.force ? "?force=1" : ""}`,
|
|
680
|
+
void 0,
|
|
681
|
+
signal
|
|
682
|
+
)
|
|
683
|
+
);
|
|
684
|
+
if (signal.aborted) return false;
|
|
685
|
+
const windows = [];
|
|
686
|
+
for (const spec of [
|
|
687
|
+
{ key: "five_hour", id: "primary", label: "5h", windowMinutes: 300 },
|
|
688
|
+
{ key: "weekly", id: "secondary", label: "weekly", windowMinutes: 10080 }
|
|
689
|
+
]) {
|
|
690
|
+
const raw = object(body[spec.key]);
|
|
691
|
+
if (!percent(raw.percent)) continue;
|
|
692
|
+
const reset = typeof raw.resetsAt === "string" ? Date.parse(raw.resetsAt) : Number.NaN;
|
|
693
|
+
windows.push({
|
|
694
|
+
id: spec.id,
|
|
695
|
+
label: spec.label,
|
|
696
|
+
windowMinutes: spec.windowMinutes,
|
|
697
|
+
usedPercent: raw.percent,
|
|
698
|
+
...Number.isFinite(reset) ? { resetsAt: Math.floor(reset / 1e3) } : {}
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
if (windows.length === 0) return false;
|
|
702
|
+
const extra = object(body.extra_usage);
|
|
703
|
+
const snapshot = {
|
|
704
|
+
providerId,
|
|
705
|
+
meterId: DEFAULT_QUOTA_METER,
|
|
706
|
+
meterLabel: "WrongForge \xB7 Claude subscription",
|
|
707
|
+
windows,
|
|
708
|
+
capturedAt: Date.now()
|
|
709
|
+
};
|
|
710
|
+
if (extra.enabled === false) snapshot.credits = { hasCredits: false, unlimited: false };
|
|
711
|
+
if (extra.enabled === true) {
|
|
712
|
+
snapshot.note = percent(extra.utilization) ? `Extra usage enabled \xB7 ${extra.utilization}% used` : "Extra usage enabled";
|
|
713
|
+
if (typeof extra.usedCredits === "number" && Number.isFinite(extra.usedCredits) && extra.usedCredits >= 0 && typeof extra.monthlyLimit === "number" && Number.isFinite(extra.monthlyLimit) && extra.monthlyLimit >= 0) {
|
|
714
|
+
const remaining = Math.max(0, extra.monthlyLimit - extra.usedCredits);
|
|
715
|
+
snapshot.credits = {
|
|
716
|
+
hasCredits: remaining > 0,
|
|
717
|
+
unlimited: false,
|
|
718
|
+
balance: `${remaining} credits remaining (${extra.usedCredits}/${extra.monthlyLimit})`
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
} else if (extra.enabled === false) snapshot.note = "Extra usage disabled";
|
|
722
|
+
const exhausted = windows.filter(
|
|
723
|
+
(window) => window.usedPercent >= 100 && (window.resetsAt === void 0 || window.resetsAt * 1e3 > snapshot.capturedAt)
|
|
724
|
+
);
|
|
725
|
+
exhausted.sort((a, b) => (b.resetsAt ?? Infinity) - (a.resetsAt ?? Infinity));
|
|
726
|
+
if (exhausted[0]) snapshot.reachedWindowId = exhausted[0].id;
|
|
727
|
+
recordProviderQuota(providerId, [snapshot]);
|
|
728
|
+
state.ok = true;
|
|
729
|
+
return true;
|
|
730
|
+
} catch {
|
|
731
|
+
return false;
|
|
732
|
+
} finally {
|
|
733
|
+
delete state.pending;
|
|
734
|
+
}
|
|
735
|
+
})();
|
|
736
|
+
return state.pending;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
export {
|
|
740
|
+
DEFAULT_BRIDGE_SERVERS,
|
|
741
|
+
installedBridgeAgents,
|
|
742
|
+
wrongstackBridgeSetup,
|
|
743
|
+
createProjectKanbanTransitionHandler,
|
|
744
|
+
createProjectKanbanAssignHandler,
|
|
745
|
+
createHqCommandDispatcher,
|
|
746
|
+
parseWrongForgeInfo,
|
|
747
|
+
readCachedWrongForgeInfo,
|
|
748
|
+
writeWrongForgeInfoCache,
|
|
749
|
+
refreshWrongForgeInfoCache,
|
|
750
|
+
reportWrongForgeQuota
|
|
751
|
+
};
|
|
752
|
+
//# sourceMappingURL=chunk-5SGMUDP5.js.map
|