@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.
- package/dist/{acp-JK4ZQO3R.js → acp-AB54FWM2.js} +120 -38
- package/dist/acp-agent-list.d.ts +14 -0
- package/dist/acp-hq-telemetry.d.ts +9 -2
- package/dist/acp-mcp-servers.d.ts +49 -0
- package/dist/acp-server-agent.d.ts +18 -6
- package/dist/{auth-XD3GN5RJ.js → auth-WDMSNZ3S.js} +2 -2
- package/dist/boot/short-circuit-hq.d.ts +2 -0
- package/dist/boot/tool-registry.d.ts +18 -1
- package/dist/{chunk-C2E24IXZ.js → chunk-3I5ZJXM5.js} +147 -241
- package/dist/{chunk-JHPXJRRI.js → chunk-4XCBEUBX.js} +13 -3
- package/dist/{chunk-FPIWTJ2G.js → chunk-6WRKAACA.js} +4 -4
- package/dist/chunk-AUQDGU5D.js +32 -0
- package/dist/{chunk-IUEKNHPT.js → chunk-BIWE22O5.js} +10 -10
- package/dist/{chunk-VOTTENR5.js → chunk-CDY4D5QK.js} +2 -2
- package/dist/{chunk-E6RFXHWD.js → chunk-EK2P53GL.js} +3 -2
- package/dist/chunk-I7V2OYVH.js +242 -0
- package/dist/{chunk-MXEJEYT7.js → chunk-JX6KL7OF.js} +15 -5
- package/dist/{chunk-GMY45LO6.js → chunk-KEPABQYT.js} +101 -2
- package/dist/{chunk-IMWXXF4J.js → chunk-KNMIBKLV.js} +79 -1
- package/dist/chunk-Q7E3BPDU.js +62 -0
- package/dist/{chunk-4NHD5OXJ.js → chunk-U6PGWDMZ.js} +5 -5
- package/dist/{chunk-U6KCWRMC.js → chunk-XJ7K67PP.js} +5 -1
- package/dist/{cli-context-Q6QP534K.js → cli-context-WFC3BNGM.js} +116 -21
- package/dist/{cli-main-JQXCDZEA.js → cli-main-GU3YZLMO.js} +1221 -694
- package/dist/{diag-doctor-7LYUYU35.js → diag-doctor-PCER6KHH.js} +2 -2
- package/dist/{execution-KH7WJNAX.js → execution-RAZDDQFC.js} +107 -15
- package/dist/execution-specialist-trigger.d.ts +28 -0
- package/dist/fleet/acp-live-progress.d.ts +19 -0
- package/dist/fleet/host-acp-runner-cache.d.ts +3 -1
- package/dist/fleet/host-acp.d.ts +14 -2
- package/dist/fleet/host.d.ts +1 -0
- package/dist/{hq-IG3YRGCO.js → hq-5XHEIIH6.js} +127 -8
- package/dist/hq-command-controller.d.ts +31 -2
- package/dist/hq-server/client-address.d.ts +2 -0
- package/dist/hq-server/ip-allowlist.d.ts +16 -0
- package/dist/hq-server/preflight.d.ts +1 -0
- package/dist/hq-server/routes/auth/password-routes.d.ts +1 -1
- package/dist/hq-server/routes.d.ts +5 -4
- package/dist/hq-server/secret-input.d.ts +7 -0
- package/dist/hq-server/types.d.ts +3 -2
- package/dist/hq-server/upgrade-handler.d.ts +2 -0
- package/dist/hq-server/utils.d.ts +6 -0
- package/dist/{hq-server-OILU3NPI.js → hq-server-CNUTBWTK.js} +4 -3
- package/dist/hq-server.d.ts +4 -0
- package/dist/hq-service-L5INQFM4.js +507 -0
- package/dist/hq-service.d.ts +22 -0
- package/dist/index.js +4 -4
- package/dist/ip-allowlist-NHXO6AKJ.js +9 -0
- package/dist/live-settings-input.d.ts +2 -0
- package/dist/{per-subcommand-help-BWBHYVJO.js → per-subcommand-help-DHTYU524.js} +2 -2
- package/dist/{plugins-U7KGQAUG.js → plugins-WJLW6ULV.js} +2 -2
- package/dist/projects-LMNAGGEU.js +36 -0
- package/dist/{proxy-wiring-MGRSKZZB.js → proxy-wiring-K2DORFFL.js} +3 -3
- package/dist/short-circuit-flags-YDMLAQ5R.js +10 -0
- package/dist/slash-commands/acp.d.ts +3 -0
- package/dist/slash-commands/command-context.d.ts +22 -0
- package/dist/slash-commands/ensemble.d.ts +1 -1
- package/dist/slash-commands/profile.d.ts +7 -0
- package/dist/subcommands/handlers/hq.d.ts +1 -0
- package/dist/subcommands/handlers/projects.d.ts +14 -0
- package/dist/{subcommands-CUTGBDKP.js → subcommands-7N4RIK6H.js} +2 -2
- package/dist/{version-help-MYKF724O.js → version-help-WXI42PZ3.js} +2 -2
- package/dist/{webui-server-QWBUT7BO.js → webui-server-OBV5JYJV.js} +9 -3
- package/dist/wiring/cli-slash-commands-setup.d.ts +7 -0
- package/dist/wiring/codebase-embeddings.d.ts +2 -2
- package/dist/wiring/command-host-state.d.ts +7 -0
- package/dist/wiring/pipeline.d.ts +2 -0
- package/dist/wiring/provider-runtime-setup.d.ts +1 -1
- package/dist/wiring/session-command-handlers.d.ts +2 -1
- package/dist/wiring/slash-commands.d.ts +1 -0
- package/package.json +28 -28
- package/dist/chunk-Q5GTM25S.js +0 -17
- package/dist/projects-L6C6XLDY.js +0 -37
- package/dist/short-circuit-flags-ZNZX44MC.js +0 -10
|
@@ -129,11 +129,21 @@ function scheduleImmediateProbe(runner) {
|
|
|
129
129
|
void runner.poke();
|
|
130
130
|
}
|
|
131
131
|
function buildHealthUrl(rawBase) {
|
|
132
|
-
const trimmed = rawBase.trim()
|
|
133
|
-
|
|
132
|
+
const trimmed = rawBase.trim();
|
|
133
|
+
try {
|
|
134
|
+
const base = new URL(trimmed);
|
|
135
|
+
base.search = "";
|
|
136
|
+
base.hash = "";
|
|
137
|
+
base.pathname = `${base.pathname.replace(/\/+$/, "")}${HEALTH_PATH}`;
|
|
138
|
+
return base.toString();
|
|
139
|
+
} catch {
|
|
140
|
+
const boundary = trimmed.search(/[?#]/);
|
|
141
|
+
const pathBase = boundary === -1 ? trimmed : trimmed.slice(0, boundary);
|
|
142
|
+
return `${pathBase.replace(/\/+$/, "")}${HEALTH_PATH}`;
|
|
143
|
+
}
|
|
134
144
|
}
|
|
135
145
|
|
|
136
146
|
export {
|
|
137
147
|
startProxyProbe
|
|
138
148
|
};
|
|
139
|
-
//# sourceMappingURL=chunk-
|
|
149
|
+
//# sourceMappingURL=chunk-4XCBEUBX.js.map
|
|
@@ -67,12 +67,12 @@ var helpCmd = async (_args, deps) => {
|
|
|
67
67
|
color.bold("Common flags"),
|
|
68
68
|
" --yolo / --no-yolo Force auto-approval on or off at startup",
|
|
69
69
|
" --confirm-destructive Deprecated \u2014 YOLO no longer prompts by destructiveness",
|
|
70
|
-
" --yolo-destructive
|
|
70
|
+
" --yolo-destructive Let YOLO run every destructive kind it is allowed to (see /yolo confirm)",
|
|
71
71
|
" --tui / --no-tui Force or disable TUI mode",
|
|
72
72
|
" --mouse Full mouse mode in the TUI (in-app scroll + clickable UI)",
|
|
73
73
|
" --desktop Open WrongStack Desktop (requires @wrongstack/desktop)",
|
|
74
|
-
" --hq [--host <h>] [--port <n>] [--password <secret>] [--tunnel] [--open]",
|
|
75
|
-
" Start HQ;
|
|
74
|
+
" --hq [--host <h>] [--port <n>] [--password <secret>] [--hq-allowlist <ip,cidr,...>] [--tunnel] [--hq-public-url <https-origin>] [--open]",
|
|
75
|
+
" Start HQ; publish through a temporary tunnel or persistent TLS proxy",
|
|
76
76
|
" --webui [--host <h>] [--port <n>] [--webui-token <t>] [--open]",
|
|
77
77
|
" [--webui-public-url <url>] [--webui-public-ws-url <url>] [--webui-require-token]",
|
|
78
78
|
" Serve the browser UI + WS bridge (prints a token URL,",
|
|
@@ -93,4 +93,4 @@ export {
|
|
|
93
93
|
versionCmd,
|
|
94
94
|
helpCmd
|
|
95
95
|
};
|
|
96
|
-
//# sourceMappingURL=chunk-
|
|
96
|
+
//# sourceMappingURL=chunk-6WRKAACA.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
|
|
2
|
+
// src/hq-server/secret-input.ts
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
var MAX_PASSWORD_FILE_BYTES = 4096;
|
|
6
|
+
async function resolveHqPasswordInput(options = {}) {
|
|
7
|
+
const environment = options.environment ?? process.env;
|
|
8
|
+
const direct = options.explicitPassword ?? environment.WRONGSTACK_HQ_PASSWORD;
|
|
9
|
+
const file = environment.WRONGSTACK_HQ_PASSWORD_FILE;
|
|
10
|
+
if (direct !== void 0 && file !== void 0) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
"Set only one of --password/WRONGSTACK_HQ_PASSWORD or WRONGSTACK_HQ_PASSWORD_FILE."
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
if (file === void 0) return direct;
|
|
16
|
+
if (!path.isAbsolute(file)) {
|
|
17
|
+
throw new Error("WRONGSTACK_HQ_PASSWORD_FILE must be an absolute path.");
|
|
18
|
+
}
|
|
19
|
+
const stat2 = await fs.stat(file);
|
|
20
|
+
if (!stat2.isFile()) throw new Error("WRONGSTACK_HQ_PASSWORD_FILE must point to a regular file.");
|
|
21
|
+
if (stat2.size > MAX_PASSWORD_FILE_BYTES) {
|
|
22
|
+
throw new Error(`WRONGSTACK_HQ_PASSWORD_FILE exceeds ${MAX_PASSWORD_FILE_BYTES} bytes.`);
|
|
23
|
+
}
|
|
24
|
+
const raw = await fs.readFile(file, "utf8");
|
|
25
|
+
if (raw.includes("\0")) throw new Error("WRONGSTACK_HQ_PASSWORD_FILE contains a NUL byte.");
|
|
26
|
+
return raw.replace(/\r?\n$/, "");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
resolveHqPasswordInput
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=chunk-AUQDGU5D.js.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
2
|
// src/subcommands/index.ts
|
|
3
3
|
var loaders = {
|
|
4
|
-
acp: async () => (await import("./acp-
|
|
4
|
+
acp: async () => (await import("./acp-AB54FWM2.js")).acpCmd,
|
|
5
5
|
init: async () => (await import("./init-DRMMFG2G.js")).initCmd,
|
|
6
|
-
auth: async () => (await import("./auth-
|
|
6
|
+
auth: async () => (await import("./auth-WDMSNZ3S.js")).authCmd,
|
|
7
7
|
update: async () => (await import("./update-5DPB4YD4.js")).updateCmd,
|
|
8
8
|
sessions: async () => (await import("./sessions-config-UL45XKX7.js")).sessionsCmd,
|
|
9
9
|
config: async () => (await import("./sessions-config-UL45XKX7.js")).configCmd,
|
|
@@ -19,19 +19,19 @@ var loaders = {
|
|
|
19
19
|
mcp: async () => (await import("./mcp-HOMLC4WE.js")).mcpCmd,
|
|
20
20
|
plugin: async () => (await import("./plugin-usage-M6KET4OZ.js")).pluginCmd,
|
|
21
21
|
plugins: async () => (await import("./plugin-usage-M6KET4OZ.js")).pluginCmd,
|
|
22
|
-
diag: async () => (await import("./diag-doctor-
|
|
23
|
-
doctor: async () => (await import("./diag-doctor-
|
|
24
|
-
"proxy-status": async () => (await import("./diag-doctor-
|
|
22
|
+
diag: async () => (await import("./diag-doctor-PCER6KHH.js")).diagCmd,
|
|
23
|
+
doctor: async () => (await import("./diag-doctor-PCER6KHH.js")).doctorCmd,
|
|
24
|
+
"proxy-status": async () => (await import("./diag-doctor-PCER6KHH.js")).proxyCmd,
|
|
25
25
|
export: async () => (await import("./export-VVRIOWFV.js")).exportCmd,
|
|
26
26
|
usage: async () => (await import("./plugin-usage-M6KET4OZ.js")).usageCmd,
|
|
27
|
-
version: async () => (await import("./version-help-
|
|
28
|
-
help: async () => (await import("./version-help-
|
|
29
|
-
projects: async () => (await import("./projects-
|
|
27
|
+
version: async () => (await import("./version-help-WXI42PZ3.js")).versionCmd,
|
|
28
|
+
help: async () => (await import("./version-help-WXI42PZ3.js")).helpCmd,
|
|
29
|
+
projects: async () => (await import("./projects-LMNAGGEU.js")).projectsCmd,
|
|
30
30
|
modeldiag: async () => (await import("./modeldiag-46CMWUNM.js")).modeldiagCmd,
|
|
31
31
|
quick: async () => (await import("./quick-CNB4Z3HS.js")).quickCmd,
|
|
32
32
|
bench: async () => (await import("./bench-YBU6BVQP.js")).benchCmd,
|
|
33
33
|
chronicle: async () => (await import("./chronicle-UTFETHP6.js")).chronicleCmd,
|
|
34
|
-
hq: async () => (await import("./hq-
|
|
34
|
+
hq: async () => (await import("./hq-5XHEIIH6.js")).hqCmd,
|
|
35
35
|
mailbox: async () => (await import("./mailbox-serve-X3CR6VFW.js")).mailboxServeCmd,
|
|
36
36
|
permissions: async () => (await import("./permissions-CFDLXETM.js")).permissionsCmd,
|
|
37
37
|
project: async () => (await import("./project-AOQI5XTK.js")).projectCmd,
|
|
@@ -51,4 +51,4 @@ export {
|
|
|
51
51
|
subcommands,
|
|
52
52
|
subcommandNames
|
|
53
53
|
};
|
|
54
|
-
//# sourceMappingURL=chunk-
|
|
54
|
+
//# sourceMappingURL=chunk-BIWE22O5.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
startProxyProbe
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4XCBEUBX.js";
|
|
4
4
|
|
|
5
5
|
// src/wiring/proxy-wiring.ts
|
|
6
6
|
import { applyProxyConfig } from "@wrongstack/core/wiring/proxy-rewrite";
|
|
@@ -53,4 +53,4 @@ export {
|
|
|
53
53
|
bootstrapWrongProxy,
|
|
54
54
|
shutdownWrongProxy
|
|
55
55
|
};
|
|
56
|
-
//# sourceMappingURL=chunk-
|
|
56
|
+
//# sourceMappingURL=chunk-CDY4D5QK.js.map
|
|
@@ -12,7 +12,6 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
12
12
|
"version",
|
|
13
13
|
"yes",
|
|
14
14
|
"no-banner",
|
|
15
|
-
"no-features",
|
|
16
15
|
"tui",
|
|
17
16
|
"no-tui",
|
|
18
17
|
// Recovery switches. `--recover` reopens the most recent session with no
|
|
@@ -64,6 +63,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
64
63
|
"pnpm",
|
|
65
64
|
"yarn",
|
|
66
65
|
"bun",
|
|
66
|
+
// `wstack hq service install` toggles.
|
|
67
|
+
"no-auto-update",
|
|
67
68
|
// `wstack acp` booleans. Without these, parseArgs treats the value after
|
|
68
69
|
// `--fs`/`--echo` as the flag's value and eats a following positional
|
|
69
70
|
// (e.g. the agent list in `acp bench --fs gemini-cli,codex-cli`).
|
|
@@ -237,4 +238,4 @@ export {
|
|
|
237
238
|
parseAuthFlags,
|
|
238
239
|
parseSpawnFlags
|
|
239
240
|
};
|
|
240
|
-
//# sourceMappingURL=chunk-
|
|
241
|
+
//# sourceMappingURL=chunk-EK2P53GL.js.map
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
|
|
2
|
+
// src/hq-server/audit-actor.ts
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
function resolveAuditActor() {
|
|
5
|
+
const user = (process.env.USERNAME ?? process.env.USER ?? "").toString();
|
|
6
|
+
try {
|
|
7
|
+
const host = os.hostname();
|
|
8
|
+
return user ? `${user}@${host}` : `cli@${host}`;
|
|
9
|
+
} catch {
|
|
10
|
+
return user ? `${user}@unknown` : "cli";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/hq-server/utils.ts
|
|
15
|
+
import * as fs from "node:fs/promises";
|
|
16
|
+
import * as os2 from "node:os";
|
|
17
|
+
import * as path from "node:path";
|
|
18
|
+
import { resolveHqDataDir } from "@wrongstack/core/hq";
|
|
19
|
+
import { sanitizeApiError, scrubErrorDetail } from "@wrongstack/core/security";
|
|
20
|
+
var TRANSCRIPT_RING_MAX = 4e3;
|
|
21
|
+
var MAX_TRANSCRIPT_SESSIONS = 400;
|
|
22
|
+
var MAX_AGENT_RINGS = 800;
|
|
23
|
+
function evictOldest(map, max) {
|
|
24
|
+
while (map.size > max) {
|
|
25
|
+
const oldest = map.keys().next().value;
|
|
26
|
+
if (oldest === void 0) break;
|
|
27
|
+
map.delete(oldest);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function agentRingKey(sessionId, subId) {
|
|
31
|
+
return sessionId ? `${sessionId}::${subId}` : subId;
|
|
32
|
+
}
|
|
33
|
+
function agentMessageToEntry(p) {
|
|
34
|
+
const kind = typeof p["kind"] === "string" ? p["kind"] : "text";
|
|
35
|
+
const role = kind === "thinking" ? "thinking" : kind === "tool_use" || kind === "tool_result" ? "tool" : kind === "error" ? "error" : kind === "status" || kind === "system" ? "system" : "assistant";
|
|
36
|
+
return {
|
|
37
|
+
ts: typeof p["ts"] === "string" ? p["ts"] : (/* @__PURE__ */ new Date()).toISOString(),
|
|
38
|
+
role,
|
|
39
|
+
text: typeof p["content"] === "string" ? p["content"] : "",
|
|
40
|
+
...typeof p["toolName"] === "string" ? { tool: p["toolName"] } : {},
|
|
41
|
+
...kind === "error" ? { isError: true } : {}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async function readLocalSubagentTranscript(sessionId, subagentId) {
|
|
45
|
+
try {
|
|
46
|
+
const { getSessionRegistry } = await import("@wrongstack/core/storage");
|
|
47
|
+
const { resolveWstackPaths, sessionScopedPath } = await import("@wrongstack/core/utils");
|
|
48
|
+
const globalRoot = path.dirname(resolveHqDataDir());
|
|
49
|
+
const registry = getSessionRegistry(globalRoot);
|
|
50
|
+
const entry = await registry.get(sessionId).catch(() => null);
|
|
51
|
+
if (!entry) return null;
|
|
52
|
+
const paths = resolveWstackPaths({ projectRoot: entry.projectRoot, globalRoot });
|
|
53
|
+
const sessionDir = sessionScopedPath(paths.projectSessions, sessionId, "");
|
|
54
|
+
if (!isSafePathSegment(subagentId)) return null;
|
|
55
|
+
const transcriptsRoot = path.resolve(sessionDir, "subagents", "transcripts");
|
|
56
|
+
const file = path.resolve(transcriptsRoot, subagentId, "transcript.jsonl");
|
|
57
|
+
if (!file.startsWith(transcriptsRoot + path.sep)) return null;
|
|
58
|
+
const raw = await fs.readFile(file, "utf8").catch(() => null);
|
|
59
|
+
if (raw === null) return null;
|
|
60
|
+
const out = [];
|
|
61
|
+
for (const line of raw.split("\n")) {
|
|
62
|
+
const trimmed = line.trim();
|
|
63
|
+
if (trimmed.length === 0) continue;
|
|
64
|
+
try {
|
|
65
|
+
out.push(agentMessageToEntry(JSON.parse(trimmed)));
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function decodePathSegment(value) {
|
|
75
|
+
let decoded;
|
|
76
|
+
try {
|
|
77
|
+
decoded = decodeURIComponent(value);
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return isSafePathSegment(decoded) ? decoded : null;
|
|
82
|
+
}
|
|
83
|
+
function decodeSessionId(value) {
|
|
84
|
+
let decoded;
|
|
85
|
+
try {
|
|
86
|
+
decoded = decodeURIComponent(value);
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
return isSafeSessionId(decoded) ? decoded : null;
|
|
91
|
+
}
|
|
92
|
+
var MAX_PATH_SEGMENT_LENGTH = 256;
|
|
93
|
+
function isSafePathSegment(value) {
|
|
94
|
+
if (value.length === 0 || value.length > MAX_PATH_SEGMENT_LENGTH) return false;
|
|
95
|
+
if (value === "." || value === "..") return false;
|
|
96
|
+
if (value.includes("/") || value.includes("\\")) return false;
|
|
97
|
+
if (value.includes("\0")) return false;
|
|
98
|
+
if (value.includes(":")) return false;
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
function isSafeSessionId(value) {
|
|
102
|
+
if (value.length === 0 || value.length > MAX_PATH_SEGMENT_LENGTH) return false;
|
|
103
|
+
if (value.includes("\\") || value.includes("\0") || value.includes(":")) return false;
|
|
104
|
+
const parts = value.split("/");
|
|
105
|
+
if (parts.length > 2) return false;
|
|
106
|
+
return parts.every((p) => p.length > 0 && p !== "." && p !== "..");
|
|
107
|
+
}
|
|
108
|
+
function displayHost(host) {
|
|
109
|
+
return host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
110
|
+
}
|
|
111
|
+
var RequestBodyTooLargeError = class extends Error {
|
|
112
|
+
};
|
|
113
|
+
function readRequestBody(req, maxBytes = 1e6) {
|
|
114
|
+
return new Promise((resolve2, reject) => {
|
|
115
|
+
const chunks = [];
|
|
116
|
+
let size = 0;
|
|
117
|
+
let settled = false;
|
|
118
|
+
req.on("data", (chunk) => {
|
|
119
|
+
if (settled) return;
|
|
120
|
+
size += chunk.length;
|
|
121
|
+
if (size > maxBytes) {
|
|
122
|
+
settled = true;
|
|
123
|
+
chunks.length = 0;
|
|
124
|
+
reject(new RequestBodyTooLargeError("request body too large"));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
chunks.push(chunk);
|
|
128
|
+
});
|
|
129
|
+
req.on("end", () => {
|
|
130
|
+
if (!settled) resolve2(Buffer.concat(chunks).toString("utf8"));
|
|
131
|
+
});
|
|
132
|
+
req.on("error", (error) => {
|
|
133
|
+
if (!settled) reject(error);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function writeInvalidBody(res, error) {
|
|
138
|
+
const tooLarge = error instanceof RequestBodyTooLargeError;
|
|
139
|
+
res.writeHead(tooLarge ? 413 : 400, { "Content-Type": "application/json" });
|
|
140
|
+
res.end(JSON.stringify({ error: tooLarge ? "request body too large" : "invalid json body" }));
|
|
141
|
+
}
|
|
142
|
+
function buildHttpUrl(host, port, token) {
|
|
143
|
+
const url = new URL(`http://${displayHost(host)}:${port}/`);
|
|
144
|
+
if (token) url.searchParams.set("token", token);
|
|
145
|
+
return url.toString();
|
|
146
|
+
}
|
|
147
|
+
function buildBootstrapHttpUrl(host, port, bootstrapCode) {
|
|
148
|
+
const url = new URL(`http://${displayHost(host)}:${port}/`);
|
|
149
|
+
url.hash = `bootstrap=${bootstrapCode}`;
|
|
150
|
+
return url.toString();
|
|
151
|
+
}
|
|
152
|
+
function buildClientWsUrl(host, port, token) {
|
|
153
|
+
const url = new URL(`ws://${displayHost(host)}:${port}/ws/client`);
|
|
154
|
+
if (token) url.searchParams.set("token", token);
|
|
155
|
+
return url.toString();
|
|
156
|
+
}
|
|
157
|
+
function normalizeHqPublicOrigin(value) {
|
|
158
|
+
const parsed = new URL(value.trim());
|
|
159
|
+
if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "" || parsed.pathname !== "" && parsed.pathname !== "/" || parsed.search !== "" || parsed.hash !== "") {
|
|
160
|
+
throw new TypeError(
|
|
161
|
+
"HQ public URL must be an exact HTTPS origin without a path or credentials."
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
return parsed.origin;
|
|
165
|
+
}
|
|
166
|
+
function hqRuntimeMarkerPath(dataDir) {
|
|
167
|
+
return path.join(dataDir, "runtime.json");
|
|
168
|
+
}
|
|
169
|
+
function lanIPv4Addresses() {
|
|
170
|
+
const out = [];
|
|
171
|
+
try {
|
|
172
|
+
const ifaces = os2.networkInterfaces();
|
|
173
|
+
for (const name of Object.keys(ifaces)) {
|
|
174
|
+
for (const ni of ifaces[name] ?? []) {
|
|
175
|
+
if (ni.family === "IPv4" && !ni.internal) out.push(ni.address);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
function truncateHqSummary(value, maxLength) {
|
|
183
|
+
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
184
|
+
if (value.length <= maxLength) return value;
|
|
185
|
+
const suffix = `\u2026[truncated:${value.length - maxLength}]`;
|
|
186
|
+
if (maxLength <= suffix.length) return "\u2026";
|
|
187
|
+
const prefixLen = maxLength - suffix.length;
|
|
188
|
+
return `${value.slice(0, prefixLen)}${suffix}`;
|
|
189
|
+
}
|
|
190
|
+
function hqMachineKey(hostname2, machineId) {
|
|
191
|
+
const hn = hostname2?.trim();
|
|
192
|
+
return hn ? `host:${hn.toLowerCase()}` : `mid:${machineId || "local"}`;
|
|
193
|
+
}
|
|
194
|
+
function recordTimeseriesSignal(persistence, event) {
|
|
195
|
+
try {
|
|
196
|
+
if (event.type === "session.usage") {
|
|
197
|
+
const p = event.payload;
|
|
198
|
+
persistence.timeseries.record({
|
|
199
|
+
ts: Date.parse(event.timestamp) || Date.now(),
|
|
200
|
+
...typeof p.costUsd === "number" ? { costUsd: p.costUsd } : {},
|
|
201
|
+
...typeof p.inputTokens === "number" ? { inputTokens: p.inputTokens } : {},
|
|
202
|
+
...typeof p.outputTokens === "number" ? { outputTokens: p.outputTokens } : {},
|
|
203
|
+
...typeof p.model === "string" && p.model.length > 0 ? { model: p.model } : {},
|
|
204
|
+
...typeof p.provider === "string" && p.provider.length > 0 ? { provider: p.provider } : {},
|
|
205
|
+
...typeof p.cacheRead === "number" ? { cacheRead: p.cacheRead } : {},
|
|
206
|
+
...typeof p.cacheWrite === "number" ? { cacheWrite: p.cacheWrite } : {}
|
|
207
|
+
});
|
|
208
|
+
} else if (event.type === "tool.completed") {
|
|
209
|
+
persistence.timeseries.record({
|
|
210
|
+
ts: Date.parse(event.timestamp) || Date.now(),
|
|
211
|
+
toolCalls: 1
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
} catch {
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export {
|
|
219
|
+
resolveAuditActor,
|
|
220
|
+
TRANSCRIPT_RING_MAX,
|
|
221
|
+
MAX_TRANSCRIPT_SESSIONS,
|
|
222
|
+
MAX_AGENT_RINGS,
|
|
223
|
+
evictOldest,
|
|
224
|
+
agentRingKey,
|
|
225
|
+
agentMessageToEntry,
|
|
226
|
+
readLocalSubagentTranscript,
|
|
227
|
+
decodePathSegment,
|
|
228
|
+
decodeSessionId,
|
|
229
|
+
readRequestBody,
|
|
230
|
+
writeInvalidBody,
|
|
231
|
+
buildHttpUrl,
|
|
232
|
+
buildBootstrapHttpUrl,
|
|
233
|
+
buildClientWsUrl,
|
|
234
|
+
normalizeHqPublicOrigin,
|
|
235
|
+
hqRuntimeMarkerPath,
|
|
236
|
+
lanIPv4Addresses,
|
|
237
|
+
truncateHqSummary,
|
|
238
|
+
hqMachineKey,
|
|
239
|
+
recordTimeseriesSignal,
|
|
240
|
+
sanitizeApiError
|
|
241
|
+
};
|
|
242
|
+
//# sourceMappingURL=chunk-I7V2OYVH.js.map
|
|
@@ -1043,9 +1043,9 @@ var helpTable = {
|
|
|
1043
1043
|
// -- HQ (dashboard server) ──────────────────────────────────────────
|
|
1044
1044
|
hq: {
|
|
1045
1045
|
name: "hq",
|
|
1046
|
-
title: "wstack hq \u2014
|
|
1047
|
-
description: "Start the HQ server \u2014 a web dashboard for monitoring sessions, fleet status, and agent activity across projects
|
|
1048
|
-
usage: "wstack hq [serve] [
|
|
1046
|
+
title: "wstack hq \u2014 serve HQ, manage authentication, or install its system service",
|
|
1047
|
+
description: "Start the HQ server \u2014 a web dashboard for monitoring sessions, fleet status, and agent activity across projects; manage browser/client authentication, or install the persistent Ubuntu systemd service.",
|
|
1048
|
+
usage: "wstack hq [serve] [...] | wstack hq token [create|list|revoke] | wstack hq service [install|status|update|uninstall]",
|
|
1049
1049
|
subcommands: [
|
|
1050
1050
|
{ name: "serve", description: "Start the HQ dashboard server (default)." },
|
|
1051
1051
|
{
|
|
@@ -1053,7 +1053,17 @@ var helpTable = {
|
|
|
1053
1053
|
description: "Mint a browser or client authentication token."
|
|
1054
1054
|
},
|
|
1055
1055
|
{ name: "token list", description: "List active HQ authentication tokens." },
|
|
1056
|
-
{ name: "token revoke <id>", description: "Revoke an issued HQ authentication token." }
|
|
1056
|
+
{ name: "token revoke <id>", description: "Revoke an issued HQ authentication token." },
|
|
1057
|
+
{
|
|
1058
|
+
name: "service install",
|
|
1059
|
+
description: "Install an always-on systemd service and guarded automatic updater."
|
|
1060
|
+
},
|
|
1061
|
+
{ name: "service status", description: "Inspect the HQ service and update timer." },
|
|
1062
|
+
{ name: "service update", description: "Run the guarded update job immediately." },
|
|
1063
|
+
{
|
|
1064
|
+
name: "service uninstall",
|
|
1065
|
+
description: "Remove systemd units while preserving HQ data and secrets."
|
|
1066
|
+
}
|
|
1057
1067
|
],
|
|
1058
1068
|
seeAlso: "wstack sessions fleet (CLI equivalent for fleet status)"
|
|
1059
1069
|
},
|
|
@@ -1202,4 +1212,4 @@ export {
|
|
|
1202
1212
|
subcommandsWithFocusedHelp,
|
|
1203
1213
|
renderGenericHelp
|
|
1204
1214
|
};
|
|
1205
|
-
//# sourceMappingURL=chunk-
|
|
1215
|
+
//# sourceMappingURL=chunk-JX6KL7OF.js.map
|
|
@@ -4,6 +4,104 @@ import {
|
|
|
4
4
|
isCodexCatalogModel
|
|
5
5
|
} from "./chunk-SIGUWXAA.js";
|
|
6
6
|
|
|
7
|
+
// src/acp-agent-list.ts
|
|
8
|
+
import { REGISTRY_ID_ALIASES } from "@wrongstack/acp";
|
|
9
|
+
function reverseAliases(aliases = REGISTRY_ID_ALIASES) {
|
|
10
|
+
const out = {};
|
|
11
|
+
for (const [ours, theirs] of Object.entries(aliases)) {
|
|
12
|
+
if (ours !== theirs) out[theirs] = ours;
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
function distKind(command) {
|
|
17
|
+
if (command === "npx") return "npx";
|
|
18
|
+
if (command === "uvx") return "uvx";
|
|
19
|
+
return "local";
|
|
20
|
+
}
|
|
21
|
+
function localProbe(registryId, alias, detectedById) {
|
|
22
|
+
return detectedById.get(registryId) ?? (alias ? detectedById.get(alias) : void 0);
|
|
23
|
+
}
|
|
24
|
+
function formatRegistryRow(agent, alias, local, idWidth) {
|
|
25
|
+
const kind = distKind(agent.acp.command);
|
|
26
|
+
const aliasBit = alias ? ` (also ${alias})` : "";
|
|
27
|
+
if (kind === "local") {
|
|
28
|
+
if (local?.installed) {
|
|
29
|
+
const ver = local.version?.split("\n")[0]?.trim();
|
|
30
|
+
const note2 = ver ? ` ${ver}` : " on PATH";
|
|
31
|
+
return ` \u2713 ${agent.id.padEnd(idWidth)} ${agent.displayName}${aliasBit}${note2}`;
|
|
32
|
+
}
|
|
33
|
+
const reason = local?.reason ?? "not on PATH";
|
|
34
|
+
return ` \u2717 ${agent.id.padEnd(idWidth)} ${agent.displayName}${aliasBit} (${reason})`;
|
|
35
|
+
}
|
|
36
|
+
const pkg = (agent.acp.args ?? []).find((a) => a !== "-y" && !a.startsWith("--"));
|
|
37
|
+
const note = pkg ? `${kind} ${pkg}` : kind;
|
|
38
|
+
return ` \u25B8 ${agent.id.padEnd(idWidth)} ${agent.displayName}${aliasBit} ${note}`;
|
|
39
|
+
}
|
|
40
|
+
function formatAcpAgentList(opts) {
|
|
41
|
+
const { live, detected } = opts;
|
|
42
|
+
const detectedById = new Map(detected.map((d) => [d.id, d]));
|
|
43
|
+
if (live && live.agents.length > 0) {
|
|
44
|
+
const aliasOf = reverseAliases();
|
|
45
|
+
const when = live.fetchedAt ? live.fetchedAt.slice(0, 19).replace("T", " ") : "locally";
|
|
46
|
+
const rows = [...live.agents].sort((a, b) => {
|
|
47
|
+
const rank = (command) => {
|
|
48
|
+
const k = distKind(command);
|
|
49
|
+
return k === "local" ? 0 : k === "uvx" ? 1 : 2;
|
|
50
|
+
};
|
|
51
|
+
const r = rank(a.acp.command) - rank(b.acp.command);
|
|
52
|
+
return r !== 0 ? r : a.id.localeCompare(b.id);
|
|
53
|
+
});
|
|
54
|
+
const idWidth = Math.max(16, ...rows.map((a) => a.id.length));
|
|
55
|
+
const lines2 = [
|
|
56
|
+
`Official ACP registry \u2014 ${live.agents.length} agents (synced ${when}).`,
|
|
57
|
+
"Spawn ids below are what `/acp <id> <task>` and `/acp <id> --bg` accept.",
|
|
58
|
+
""
|
|
59
|
+
];
|
|
60
|
+
for (const agent of rows) {
|
|
61
|
+
const alias = aliasOf[agent.id];
|
|
62
|
+
lines2.push(
|
|
63
|
+
formatRegistryRow(agent, alias, localProbe(agent.id, alias, detectedById), idWidth)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const leftover = detected.filter((d) => {
|
|
67
|
+
if (live.byId[d.id]) return false;
|
|
68
|
+
const mapped = REGISTRY_ID_ALIASES[d.id];
|
|
69
|
+
return !(mapped && live.byId[mapped]);
|
|
70
|
+
});
|
|
71
|
+
if (leftover.length > 0) {
|
|
72
|
+
lines2.push("");
|
|
73
|
+
lines2.push("Bundled extras (not in the official registry \u2014 experimental):");
|
|
74
|
+
for (const d of leftover) {
|
|
75
|
+
const mark = d.installed ? "\u2713" : "\u2717";
|
|
76
|
+
const extra = d.installed ? d.version?.split("\n")[0] ?? "" : d.reason ?? "not installed";
|
|
77
|
+
lines2.push(` ${mark} ${d.id.padEnd(16)} ${d.displayName} (${extra})`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
lines2.push("");
|
|
81
|
+
lines2.push(
|
|
82
|
+
"Spawn: /acp <id> <task> Background: /acp <id> --bg <task> Probe: /acp probe <id>"
|
|
83
|
+
);
|
|
84
|
+
return lines2.join("\n");
|
|
85
|
+
}
|
|
86
|
+
const installed = detected.filter((a) => a.installed);
|
|
87
|
+
const missing = detected.filter((a) => !a.installed);
|
|
88
|
+
const lines = [
|
|
89
|
+
"Bundled offline catalog (not the official registry).",
|
|
90
|
+
"Run `/acp sync` to replace this list with agentclientprotocol/registry.",
|
|
91
|
+
""
|
|
92
|
+
];
|
|
93
|
+
for (const a of installed) {
|
|
94
|
+
const ver = a.version ? ` (${a.version.split("\n")[0]})` : "";
|
|
95
|
+
lines.push(` \u2713 ${a.id.padEnd(16)} ${a.displayName}${ver}`);
|
|
96
|
+
}
|
|
97
|
+
for (const a of missing) {
|
|
98
|
+
lines.push(` \u2717 ${a.id.padEnd(16)} ${a.displayName} (${a.reason ?? "not installed"})`);
|
|
99
|
+
}
|
|
100
|
+
lines.push("");
|
|
101
|
+
lines.push(`${installed.length} of ${detected.length} bundled agents installed locally.`);
|
|
102
|
+
return lines.join("\n");
|
|
103
|
+
}
|
|
104
|
+
|
|
7
105
|
// src/acp-registry-cache.ts
|
|
8
106
|
import * as fs from "node:fs/promises";
|
|
9
107
|
import * as path from "node:path";
|
|
@@ -261,7 +359,7 @@ async function setupProvider(params) {
|
|
|
261
359
|
} catch (err) {
|
|
262
360
|
throw new ConfigError({
|
|
263
361
|
message: `Failed to load models.dev registry: ${err instanceof Error ? err.message : err}
|
|
264
|
-
Try \`wstack models refresh\` once you have network access
|
|
362
|
+
Try \`wstack models refresh\` once you have network access.`,
|
|
265
363
|
code: "CONFIG_INVALID",
|
|
266
364
|
context: { phase: "registry-build", provider: config.provider },
|
|
267
365
|
cause: err
|
|
@@ -304,6 +402,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
304
402
|
}
|
|
305
403
|
|
|
306
404
|
export {
|
|
405
|
+
formatAcpAgentList,
|
|
307
406
|
loadCachedAcpRegistry,
|
|
308
407
|
refreshAcpRegistry,
|
|
309
408
|
resolveProviderCfg,
|
|
@@ -311,4 +410,4 @@ export {
|
|
|
311
410
|
setupProvider,
|
|
312
411
|
createGracefulShutdown
|
|
313
412
|
};
|
|
314
|
-
//# sourceMappingURL=chunk-
|
|
413
|
+
//# sourceMappingURL=chunk-KEPABQYT.js.map
|
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
|
|
2
2
|
// src/hq-command-controller.ts
|
|
3
|
+
function createProjectKanbanTransitionHandler(projectRoot) {
|
|
4
|
+
return async (input) => {
|
|
5
|
+
const { transitionTask } = await import("@wrongstack/kanban");
|
|
6
|
+
const result = await transitionTask(projectRoot, input.boardId, input.taskId, {
|
|
7
|
+
to: input.to,
|
|
8
|
+
actor: "hq-operator",
|
|
9
|
+
comment: input.comment,
|
|
10
|
+
sessionId: input.sessionId ?? "hq-mobile"
|
|
11
|
+
});
|
|
12
|
+
if (result === null) throw new Error(`kanban task not found: ${input.taskId}`);
|
|
13
|
+
return `task ${input.taskId} transitioned to ${input.to}`;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function createProjectKanbanAssignHandler(projectRoot) {
|
|
17
|
+
return async (input) => {
|
|
18
|
+
const { assignTask } = await import("@wrongstack/kanban");
|
|
19
|
+
const result = await assignTask(
|
|
20
|
+
projectRoot,
|
|
21
|
+
input.boardId,
|
|
22
|
+
input.taskId,
|
|
23
|
+
{
|
|
24
|
+
agentId: input.agentId,
|
|
25
|
+
name: input.assignee,
|
|
26
|
+
assignee: input.assignee,
|
|
27
|
+
status: "assigned",
|
|
28
|
+
protectActiveAssignment: true
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
actor: "hq-operator",
|
|
32
|
+
note: input.comment,
|
|
33
|
+
sessionId: input.sessionId ?? "hq-mobile"
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
if (result === null) throw new Error(`kanban task not found: ${input.taskId}`);
|
|
37
|
+
return `task ${input.taskId} assigned to ${input.assignee}`;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
3
40
|
function createHqCommandDispatcher(controller) {
|
|
4
41
|
return async (command) => {
|
|
5
42
|
const cmd = command;
|
|
@@ -159,6 +196,45 @@ async function dispatch(controller, commandId, type, payload) {
|
|
|
159
196
|
const subagentId = await controller.spawnAgent(role, task, maxIterations, sessionId);
|
|
160
197
|
return { commandId, status: "completed", message: `spawned ${subagentId}` };
|
|
161
198
|
}
|
|
199
|
+
case "kanban-transition": {
|
|
200
|
+
if (controller.kanbanTransition === void 0) {
|
|
201
|
+
return { commandId, status: "rejected", message: "kanban control is unavailable" };
|
|
202
|
+
}
|
|
203
|
+
const message = await controller.kanbanTransition({
|
|
204
|
+
boardId: payload["boardId"],
|
|
205
|
+
taskId: payload["taskId"],
|
|
206
|
+
to: payload["to"],
|
|
207
|
+
comment: payload["comment"],
|
|
208
|
+
...sessionId !== void 0 ? { sessionId } : {}
|
|
209
|
+
});
|
|
210
|
+
return { commandId, status: "completed", message };
|
|
211
|
+
}
|
|
212
|
+
case "kanban-assign": {
|
|
213
|
+
if (controller.kanbanAssign === void 0) {
|
|
214
|
+
return { commandId, status: "rejected", message: "kanban assignment is unavailable" };
|
|
215
|
+
}
|
|
216
|
+
const message = await controller.kanbanAssign({
|
|
217
|
+
boardId: payload["boardId"],
|
|
218
|
+
taskId: payload["taskId"],
|
|
219
|
+
agentId: payload["agentId"],
|
|
220
|
+
assignee: payload["assignee"],
|
|
221
|
+
comment: payload["comment"],
|
|
222
|
+
...sessionId !== void 0 ? { sessionId } : {}
|
|
223
|
+
});
|
|
224
|
+
return { commandId, status: "completed", message };
|
|
225
|
+
}
|
|
226
|
+
case "kanban-dispatch": {
|
|
227
|
+
if (controller.kanbanDispatch === void 0) {
|
|
228
|
+
return { commandId, status: "rejected", message: "kanban dispatch is unavailable" };
|
|
229
|
+
}
|
|
230
|
+
const message = await controller.kanbanDispatch({
|
|
231
|
+
boardId: payload["boardId"],
|
|
232
|
+
taskId: payload["taskId"],
|
|
233
|
+
comment: payload["comment"],
|
|
234
|
+
...sessionId !== void 0 ? { sessionId } : {}
|
|
235
|
+
});
|
|
236
|
+
return { commandId, status: "completed", message };
|
|
237
|
+
}
|
|
162
238
|
case "run-command": {
|
|
163
239
|
if (!controller.allowRunCommand()) {
|
|
164
240
|
return {
|
|
@@ -194,6 +270,8 @@ ${command}
|
|
|
194
270
|
}
|
|
195
271
|
|
|
196
272
|
export {
|
|
273
|
+
createProjectKanbanTransitionHandler,
|
|
274
|
+
createProjectKanbanAssignHandler,
|
|
197
275
|
createHqCommandDispatcher
|
|
198
276
|
};
|
|
199
|
-
//# sourceMappingURL=chunk-
|
|
277
|
+
//# sourceMappingURL=chunk-KNMIBKLV.js.map
|