@wrongstack/core 0.310.1 → 0.313.1
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/chronicle/index.js +84 -6391
- package/dist/chronicle/project-server.js +84 -4868
- package/dist/chunk-2BUUIMQB.js +225 -0
- package/dist/chunk-2FK4WTFF.js +207 -0
- package/dist/chunk-2FQL4PDC.js +292 -0
- package/dist/chunk-2KKODIQU.js +2256 -0
- package/dist/chunk-2YU4WVBW.js +134 -0
- package/dist/chunk-3E244UDA.js +356 -0
- package/dist/chunk-3XPVB7BA.js +135 -0
- package/dist/chunk-4LJJYBAU.js +388 -0
- package/dist/chunk-4VWO3ZXL.js +342 -0
- package/dist/chunk-4XV6IR37.js +187 -0
- package/dist/chunk-4ZHTKJHM.js +183 -0
- package/dist/chunk-52RTVH4M.js +361 -0
- package/dist/chunk-57GEWK5X.js +26 -0
- package/dist/chunk-5DSMEVA4.js +931 -0
- package/dist/chunk-5RP3QTRL.js +1223 -0
- package/dist/chunk-5S437PRC.js +50 -0
- package/dist/chunk-5XDUGTKN.js +511 -0
- package/dist/chunk-62V2AFVG.js +422 -0
- package/dist/chunk-65RDIOR2.js +2470 -0
- package/dist/chunk-6AKOTGLM.js +597 -0
- package/dist/chunk-7ZHZE5EO.js +28 -0
- package/dist/chunk-A455ATZB.js +140 -0
- package/dist/chunk-AD7ZFZCS.js +46 -0
- package/dist/chunk-AI4LLQ2U.js +856 -0
- package/dist/chunk-ASX6AW36.js +789 -0
- package/dist/chunk-AT4V4XGI.js +3522 -0
- package/dist/chunk-AWNQ6ED6.js +311 -0
- package/dist/chunk-B4AMLZPB.js +9 -0
- package/dist/chunk-BAKVJLEV.js +417 -0
- package/dist/chunk-BJHVULHO.js +55 -0
- package/dist/chunk-CB2HFKTP.js +9 -0
- package/dist/chunk-CE4ZOCOI.js +1640 -0
- package/dist/chunk-CQQL4ZOQ.js +151 -0
- package/dist/chunk-DEIQMBD2.js +65 -0
- package/dist/chunk-E6I5RXVK.js +58 -0
- package/dist/chunk-EFEHDQGY.js +1589 -0
- package/dist/chunk-EODDVJBM.js +82 -0
- package/dist/chunk-EPC3GZD4.js +308 -0
- package/dist/chunk-FAYRX2AD.js +13 -0
- package/dist/chunk-FFSX2QDQ.js +362 -0
- package/dist/chunk-FHAMQS7Q.js +5856 -0
- package/dist/chunk-FIV4IJL7.js +2263 -0
- package/dist/chunk-FNXVT7HF.js +52 -0
- package/dist/chunk-FSWCWSTS.js +395 -0
- package/dist/chunk-FW5LDDMJ.js +16464 -0
- package/dist/chunk-FXQLIX3K.js +211 -0
- package/dist/chunk-G5AAKEEZ.js +228 -0
- package/dist/chunk-G5S3S5ZN.js +38 -0
- package/dist/chunk-GAZEWQEZ.js +231 -0
- package/dist/chunk-GFJOMSHG.js +125 -0
- package/dist/chunk-GFXZTHNI.js +158 -0
- package/dist/chunk-GM54GJBU.js +960 -0
- package/dist/chunk-GPQ3NJPR.js +900 -0
- package/dist/chunk-GTCU6DZ3.js +1 -0
- package/dist/chunk-H5E77KUJ.js +15 -0
- package/dist/chunk-H72H4XDU.js +1023 -0
- package/dist/chunk-HA4ZYA6T.js +178 -0
- package/dist/chunk-I45KAW2N.js +5020 -0
- package/dist/chunk-IQ35HMQL.js +1396 -0
- package/dist/chunk-IYFBFX34.js +86 -0
- package/dist/chunk-JJPMWPB6.js +109 -0
- package/dist/chunk-JTHKOYWU.js +663 -0
- package/dist/chunk-JVGPR3TS.js +365 -0
- package/dist/chunk-JVKB2TBL.js +48 -0
- package/dist/chunk-JX7VBZDK.js +31 -0
- package/dist/chunk-K3G6JBUC.js +5232 -0
- package/dist/chunk-K4YUNKU7.js +589 -0
- package/dist/chunk-KBZGE6LN.js +710 -0
- package/dist/chunk-KDQJCT2J.js +45 -0
- package/dist/chunk-KI4L2DUL.js +560 -0
- package/dist/chunk-KJ2ZTQSL.js +47 -0
- package/dist/chunk-KSJ5UTP7.js +154 -0
- package/dist/chunk-LHOA5DWA.js +28 -0
- package/dist/chunk-LX5QWLBL.js +27 -0
- package/dist/chunk-LZDDLNCZ.js +279 -0
- package/dist/chunk-M2WF6AXG.js +115 -0
- package/dist/chunk-M7BS2K6U.js +1 -0
- package/dist/chunk-NDBIPFNY.js +102 -0
- package/dist/chunk-NEMIWEXC.js +1199 -0
- package/dist/chunk-NFAYHPRH.js +323 -0
- package/dist/chunk-O37BQ65S.js +21 -0
- package/dist/chunk-O74MJHXU.js +187 -0
- package/dist/chunk-OJZEX73F.js +112 -0
- package/dist/chunk-PAAWLLZX.js +74 -0
- package/dist/chunk-PFXFQQRT.js +339 -0
- package/dist/chunk-PFZO3C6H.js +14 -0
- package/dist/chunk-PHH7LD45.js +113 -0
- package/dist/chunk-QRTG4XTI.js +188 -0
- package/dist/chunk-QVOVMXJR.js +1351 -0
- package/dist/chunk-RCQYT2I4.js +757 -0
- package/dist/chunk-RD6DEE36.js +1178 -0
- package/dist/chunk-RJIZEJ42.js +30 -0
- package/dist/chunk-RPOA7IAE.js +150 -0
- package/dist/chunk-SN2DQRL6.js +1172 -0
- package/dist/chunk-SQJVFEBV.js +41 -0
- package/dist/chunk-SQP64RCS.js +5391 -0
- package/dist/chunk-SVT4RNTR.js +49 -0
- package/dist/chunk-SWUFN7XM.js +43 -0
- package/dist/chunk-SXMVYC5H.js +174 -0
- package/dist/chunk-SZ6AYUIZ.js +23 -0
- package/dist/chunk-T3KGZBU5.js +3171 -0
- package/dist/chunk-TBRL2RCC.js +9 -0
- package/dist/chunk-TRT2HRWZ.js +18 -0
- package/dist/chunk-TYW2GI5I.js +1077 -0
- package/dist/chunk-UIWONWVG.js +4959 -0
- package/dist/chunk-VB3H5WWB.js +151 -0
- package/dist/chunk-W4A6QA3D.js +258 -0
- package/dist/chunk-WBPVKEEE.js +227 -0
- package/dist/chunk-WEBMSQP2.js +123 -0
- package/dist/chunk-WOEZV3Q4.js +333 -0
- package/dist/chunk-WQQON3SV.js +473 -0
- package/dist/chunk-WQTOVMGV.js +119 -0
- package/dist/chunk-WZO23AWO.js +2779 -0
- package/dist/chunk-XBDP4QPW.js +7 -0
- package/dist/chunk-XEBHSJLT.js +103 -0
- package/dist/chunk-XQ2ECNK3.js +1 -0
- package/dist/chunk-XQ2XGIBF.js +2086 -0
- package/dist/chunk-Y2GE5TSI.js +355 -0
- package/dist/chunk-YBUJCZ57.js +351 -0
- package/dist/chunk-YNCVSFGF.js +149 -0
- package/dist/chunk-YUPMXOGP.js +282 -0
- package/dist/chunk-YY6APYUM.js +49 -0
- package/dist/chunk-Z2IJUO45.js +691 -0
- package/dist/chunk-ZEHUCJHY.js +129 -0
- package/dist/chunk-ZYU2YOJ6.js +1496 -0
- package/dist/chunk-ZZ4V3WIZ.js +163 -0
- package/dist/coordination/agents/index.js +145 -6260
- package/dist/coordination/index.js +365 -34323
- package/dist/coordination/mailbox-project-server.js +48 -3220
- package/dist/coordination/mailbox-types.d.ts +13 -1
- package/dist/coordination/provider-status-tracker.d.ts +4 -0
- package/dist/coordination/sqlite-mailbox.d.ts +2 -1
- package/dist/core/agent.d.ts +14 -1
- package/dist/core/index.js +267 -12479
- package/dist/core/model-ref.js +5 -23
- package/dist/core/provider-runner.d.ts +36 -1
- package/dist/design/index.js +50 -1087
- package/dist/execution/index.d.ts +1 -1
- package/dist/execution/index.js +1599 -21887
- package/dist/execution/prompt-enhancer.js +16 -535
- package/dist/execution/retry-policy.d.ts +31 -1
- package/dist/extension/index.js +7 -311
- package/dist/goal/index.js +33 -3435
- package/dist/hooks/index.js +14 -1138
- package/dist/hq/index.js +112 -5139
- package/dist/hq/protocol.js +21 -668
- package/dist/index.js +1410 -92655
- package/dist/infrastructure/index.js +64 -4811
- package/dist/kernel/events/provider-events.d.ts +15 -0
- package/dist/kernel/events/wrongtrace-events.d.ts +26 -0
- package/dist/kernel/events.d.ts +16 -7
- package/dist/kernel/index.js +14 -992
- package/dist/models/index.js +40 -1117
- package/dist/notifications/index.js +3 -108
- package/dist/observability/index.js +17 -279
- package/dist/plugin/index.js +584 -14835
- package/dist/prompts/index.js +14 -948
- package/dist/registry/index.js +9 -822
- package/dist/registry-R23UBE7H.js +14 -0
- package/dist/replay/index.js +6 -84
- package/dist/review-finding-store-DKAV3AS6.js +21 -0
- package/dist/review-report-store-OWY3EC3N.js +19 -0
- package/dist/security/index.js +114 -2202
- package/dist/session-catalog/index.js +36 -2103
- package/dist/session-catalog/project-server.js +42 -1435
- package/dist/skills/index.js +35 -1578
- package/dist/storage/index.js +182 -15600
- package/dist/tasking/index.js +10 -676
- package/dist/tools/index.js +52 -9589
- package/dist/types/config/autonomy.d.ts +5 -0
- package/dist/types/config/tools.d.ts +2 -2
- package/dist/types/config/ui.d.ts +1 -1
- package/dist/types/errors.d.ts +18 -0
- package/dist/types/index.js +121 -1341
- package/dist/types/provider-runner.d.ts +8 -0
- package/dist/types/session-markers.js +8 -144
- package/dist/utils/error.js +3 -4
- package/dist/utils/expect-defined.js +3 -9
- package/dist/utils/heap-watchdog.js +6 -706
- package/dist/utils/index.js +269 -5685
- package/dist/utils/sage-output-block.js +5 -42
- package/dist/wiring/proxy-rewrite.d.ts +92 -4
- package/dist/wiring/proxy-rewrite.js +20 -61
- package/dist/worktree/index.js +6 -887
- package/package.json +4 -4
|
@@ -0,0 +1,1496 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_HQ_REDACTION_POLICY,
|
|
3
|
+
HQ_PROTOCOL_VERSION,
|
|
4
|
+
HQ_TRANSCRIPT_TEXT_CAP,
|
|
5
|
+
createHqEventEnvelope
|
|
6
|
+
} from "./chunk-Z2IJUO45.js";
|
|
7
|
+
import {
|
|
8
|
+
SESSION_RECIPIENT_PREFIX
|
|
9
|
+
} from "./chunk-W4A6QA3D.js";
|
|
10
|
+
import {
|
|
11
|
+
SECRET_FILE_MODE,
|
|
12
|
+
restrictFilePermissions
|
|
13
|
+
} from "./chunk-H5E77KUJ.js";
|
|
14
|
+
import {
|
|
15
|
+
DefaultSecretScrubber
|
|
16
|
+
} from "./chunk-3E244UDA.js";
|
|
17
|
+
import {
|
|
18
|
+
readProjectIdentitySync
|
|
19
|
+
} from "./chunk-4XV6IR37.js";
|
|
20
|
+
import {
|
|
21
|
+
isPidAlive
|
|
22
|
+
} from "./chunk-TRT2HRWZ.js";
|
|
23
|
+
import {
|
|
24
|
+
atomicWrite
|
|
25
|
+
} from "./chunk-7ZHZE5EO.js";
|
|
26
|
+
import {
|
|
27
|
+
wstackGlobalRoot
|
|
28
|
+
} from "./chunk-YNCVSFGF.js";
|
|
29
|
+
|
|
30
|
+
// src/hq/auth-audit.ts
|
|
31
|
+
import { appendFileSync, readFileSync, mkdirSync } from "node:fs";
|
|
32
|
+
import * as path from "node:path";
|
|
33
|
+
function hqAuthAuditPath(dataDir) {
|
|
34
|
+
return path.join(dataDir, "auth-audit.jsonl");
|
|
35
|
+
}
|
|
36
|
+
function readHqAuthAuditTail(dataDir, maxEntries = 50) {
|
|
37
|
+
const filePath = hqAuthAuditPath(dataDir);
|
|
38
|
+
let content;
|
|
39
|
+
try {
|
|
40
|
+
content = readFileSync(filePath, "utf8");
|
|
41
|
+
} catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
const lines = content.trimEnd().split("\n");
|
|
45
|
+
const tail = lines.slice(-maxEntries);
|
|
46
|
+
const entries = [];
|
|
47
|
+
for (const line of tail) {
|
|
48
|
+
if (!line) continue;
|
|
49
|
+
try {
|
|
50
|
+
entries.push(JSON.parse(line));
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return entries.reverse();
|
|
55
|
+
}
|
|
56
|
+
function appendHqAuthAudit(dataDir, entry, options) {
|
|
57
|
+
try {
|
|
58
|
+
mkdirSync(dataDir, { recursive: true });
|
|
59
|
+
const line = `${JSON.stringify(entry)}
|
|
60
|
+
`;
|
|
61
|
+
appendFileSync(hqAuthAuditPath(dataDir), line, { encoding: "utf8", flag: "a" });
|
|
62
|
+
} catch (err) {
|
|
63
|
+
options?.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function logHqAuthAudit(dataDir, partial, options) {
|
|
67
|
+
const at = partial.at ?? (options?.now?.() ?? Date.now());
|
|
68
|
+
const { at: _omit, ...rest } = partial;
|
|
69
|
+
void _omit;
|
|
70
|
+
appendHqAuthAudit(dataDir, { at, ...rest }, options);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/hq/auth-store.ts
|
|
74
|
+
import { createHash, randomBytes, randomUUID, scrypt, timingSafeEqual } from "node:crypto";
|
|
75
|
+
import * as syncFs from "node:fs";
|
|
76
|
+
import * as fs from "node:fs/promises";
|
|
77
|
+
import * as path2 from "node:path";
|
|
78
|
+
var HQ_AUTH_FILE_VERSION = 1;
|
|
79
|
+
function defaultHqDataDir() {
|
|
80
|
+
return path2.join(wstackGlobalRoot(), "hq");
|
|
81
|
+
}
|
|
82
|
+
function resolveHqDataDir(override, env = process.env) {
|
|
83
|
+
const raw = override ?? env["WRONGSTACK_HQ_DATA_DIR"]?.trim();
|
|
84
|
+
if (!raw) return defaultHqDataDir();
|
|
85
|
+
return path2.isAbsolute(raw) ? path2.resolve(raw) : path2.resolve(process.cwd(), raw);
|
|
86
|
+
}
|
|
87
|
+
function hqTokenVerifier(secret) {
|
|
88
|
+
return createHash("sha256").update(secret, "utf8").digest("hex");
|
|
89
|
+
}
|
|
90
|
+
function hqTokenKey(token) {
|
|
91
|
+
if (token.verifier !== void 0 && token.verifier.length > 0) return token.verifier;
|
|
92
|
+
return token.token ? hqTokenVerifier(token.token) : "";
|
|
93
|
+
}
|
|
94
|
+
function hashBrowserToken(token) {
|
|
95
|
+
if (token.verifier !== void 0 && token.verifier.length > 0) {
|
|
96
|
+
return token.token ? { ...token, token: "" } : token;
|
|
97
|
+
}
|
|
98
|
+
if (!token.token) return token;
|
|
99
|
+
return { ...token, verifier: hqTokenVerifier(token.token), token: "" };
|
|
100
|
+
}
|
|
101
|
+
function isTokenExpired(token, at = Date.now(), clockSkewMs = 0) {
|
|
102
|
+
if (token === void 0) return false;
|
|
103
|
+
const expiresAt = token.expiresAt;
|
|
104
|
+
if (expiresAt === void 0) return false;
|
|
105
|
+
const parsed = Date.parse(expiresAt);
|
|
106
|
+
if (!Number.isFinite(parsed)) return false;
|
|
107
|
+
return at >= parsed - Math.max(0, clockSkewMs);
|
|
108
|
+
}
|
|
109
|
+
function tokenHasCapability(token, capability, clockSkewMs = 0) {
|
|
110
|
+
if (token === void 0) return false;
|
|
111
|
+
if (isTokenExpired(token, Date.now(), clockSkewMs)) return false;
|
|
112
|
+
if (token.capabilities === void 0) return true;
|
|
113
|
+
return token.capabilities.includes(capability);
|
|
114
|
+
}
|
|
115
|
+
function emptyHqAuthFile() {
|
|
116
|
+
return {
|
|
117
|
+
version: HQ_AUTH_FILE_VERSION,
|
|
118
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
var HQ_AUTH_CONTENT_HASH_REDACTED = "<redacted>";
|
|
122
|
+
function hqAuthContentHash(file) {
|
|
123
|
+
const REDACTED = HQ_AUTH_CONTENT_HASH_REDACTED;
|
|
124
|
+
const redactToken = (t) => ({ ...t, token: REDACTED });
|
|
125
|
+
try {
|
|
126
|
+
const projection = {
|
|
127
|
+
version: file.version,
|
|
128
|
+
updatedAt: file.updatedAt,
|
|
129
|
+
...file.redactionPolicy !== void 0 ? { redactionPolicy: file.redactionPolicy } : {},
|
|
130
|
+
...file.browserTokens !== void 0 ? { browserTokens: file.browserTokens.map(redactToken) } : {},
|
|
131
|
+
...file.clientTokens !== void 0 ? { clientTokens: file.clientTokens.map(redactToken) } : {},
|
|
132
|
+
...file.passwordHash !== void 0 ? { passwordHash: REDACTED } : {},
|
|
133
|
+
...file.cookieSecret !== void 0 ? { cookieSecret: REDACTED } : {},
|
|
134
|
+
...file.totpSecret !== void 0 ? { totpSecret: REDACTED } : {},
|
|
135
|
+
...file.totpPendingSecret !== void 0 ? { totpPendingSecret: REDACTED } : {},
|
|
136
|
+
...file.totpRecoveryCodes !== void 0 ? { totpRecoveryCodes: file.totpRecoveryCodes.map(() => REDACTED) } : {},
|
|
137
|
+
...file.totpLastUsedCounter !== void 0 ? { totpLastUsedCounter: file.totpLastUsedCounter } : {},
|
|
138
|
+
...file.alertRules !== void 0 ? { alertRules: file.alertRules } : {}
|
|
139
|
+
};
|
|
140
|
+
const payload = JSON.stringify(projection);
|
|
141
|
+
return createHash("sha256").update(payload).digest("hex");
|
|
142
|
+
} catch {
|
|
143
|
+
return void 0;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function hqAuthFilePath(dataDir) {
|
|
147
|
+
return path2.join(dataDir, "auth.json");
|
|
148
|
+
}
|
|
149
|
+
function hqRuntimeFilePath(dataDir) {
|
|
150
|
+
return path2.join(dataDir, "runtime.json");
|
|
151
|
+
}
|
|
152
|
+
async function writeHqRuntimeFile(dataDir, file) {
|
|
153
|
+
const payload = {
|
|
154
|
+
...file,
|
|
155
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
156
|
+
};
|
|
157
|
+
const target = hqRuntimeFilePath(dataDir);
|
|
158
|
+
await atomicWrite(target, `${JSON.stringify(payload, null, 2)}
|
|
159
|
+
`, { mode: SECRET_FILE_MODE });
|
|
160
|
+
await restrictFilePermissions(target, { label: "hq-runtime" });
|
|
161
|
+
}
|
|
162
|
+
function readHqRuntimeFileSync(dataDir) {
|
|
163
|
+
try {
|
|
164
|
+
const parsed = JSON.parse(
|
|
165
|
+
syncFs.readFileSync(hqRuntimeFilePath(dataDir), "utf8")
|
|
166
|
+
);
|
|
167
|
+
if (typeof parsed.url !== "string" || parsed.url.trim().length === 0) return void 0;
|
|
168
|
+
if (typeof parsed.pid === "number" && !isPidAlive(parsed.pid)) return void 0;
|
|
169
|
+
return {
|
|
170
|
+
url: parsed.url,
|
|
171
|
+
updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : "",
|
|
172
|
+
...typeof parsed.pid === "number" ? { pid: parsed.pid } : {}
|
|
173
|
+
};
|
|
174
|
+
} catch {
|
|
175
|
+
return void 0;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function readHqAuthFile(dataDir, _opts = {}) {
|
|
179
|
+
const file = hqAuthFilePath(dataDir);
|
|
180
|
+
let raw;
|
|
181
|
+
try {
|
|
182
|
+
raw = await fs.readFile(file, "utf8");
|
|
183
|
+
} catch (err) {
|
|
184
|
+
if (err.code === "ENOENT") return emptyHqAuthFile();
|
|
185
|
+
throw new Error(
|
|
186
|
+
`HQ auth file at ${file} cannot be read: ${err.message}. Fix the file permissions or delete it to start with an empty auth state.`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
let parsed;
|
|
190
|
+
try {
|
|
191
|
+
parsed = JSON.parse(raw);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
`HQ auth file at ${file} is not valid JSON: ${err.message}. Fix the file or delete it to start with an empty auth state.`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
if (parsed.version !== HQ_AUTH_FILE_VERSION) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`HQ auth file at ${file} has unsupported version ${String(parsed.version)} (expected ${String(HQ_AUTH_FILE_VERSION)}). Remove or update the file to match the current schema version.`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
return parsed;
|
|
203
|
+
}
|
|
204
|
+
async function writeHqAuthFile(dataDir, file) {
|
|
205
|
+
const target = hqAuthFilePath(dataDir);
|
|
206
|
+
const payload = {
|
|
207
|
+
...file,
|
|
208
|
+
version: HQ_AUTH_FILE_VERSION,
|
|
209
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
210
|
+
// WS-044: browser secrets never touch the disk. Any legacy cleartext in
|
|
211
|
+
// the incoming file is converted here, so the first write after an
|
|
212
|
+
// upgrade migrates the whole list — no separate migration step to forget
|
|
213
|
+
// to run, and no window where an old file stays cleartext because nothing
|
|
214
|
+
// happened to rewrite it.
|
|
215
|
+
...file.browserTokens !== void 0 ? { browserTokens: file.browserTokens.map(hashBrowserToken) } : {}
|
|
216
|
+
};
|
|
217
|
+
await atomicWrite(target, `${JSON.stringify(payload, null, 2)}
|
|
218
|
+
`, { mode: SECRET_FILE_MODE });
|
|
219
|
+
await restrictFilePermissions(target, { label: "hq-auth" });
|
|
220
|
+
}
|
|
221
|
+
async function ensureHqFirstRunAuthFile(dataDir, opts = {}) {
|
|
222
|
+
const file = hqAuthFilePath(dataDir);
|
|
223
|
+
try {
|
|
224
|
+
await fs.access(file);
|
|
225
|
+
const existing = await readHqAuthFile(dataDir, opts);
|
|
226
|
+
if (opts.password) {
|
|
227
|
+
const passwordUnchanged = existing.passwordHash !== void 0 && await verifyHqPassword(opts.password, existing.passwordHash);
|
|
228
|
+
if (!passwordUnchanged) {
|
|
229
|
+
const authFile2 = {
|
|
230
|
+
...existing,
|
|
231
|
+
passwordHash: await hashHqPassword(opts.password),
|
|
232
|
+
// Rotating the cookie secret invalidates sessions created with the
|
|
233
|
+
// previous password. This matters when --password is used to
|
|
234
|
+
// recover or deliberately change an existing HQ installation.
|
|
235
|
+
cookieSecret: mintHqCookieSecret()
|
|
236
|
+
};
|
|
237
|
+
await writeHqAuthFile(dataDir, authFile2);
|
|
238
|
+
const persisted2 = await readHqAuthFile(dataDir, opts);
|
|
239
|
+
const hash2 = hqAuthContentHash(persisted2);
|
|
240
|
+
const actorField2 = opts.actor ? { actor: opts.actor } : {};
|
|
241
|
+
logHqAuthAudit(
|
|
242
|
+
dataDir,
|
|
243
|
+
{
|
|
244
|
+
kind: "password-rotate",
|
|
245
|
+
scope: "browser",
|
|
246
|
+
tokenId: "(password-rotation)",
|
|
247
|
+
...hash2 !== void 0 ? { contentHash: hash2 } : {},
|
|
248
|
+
...actorField2
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
onError: (err) => opts.warn?.(`HQ password-rotate audit write failed: ${err.message}`)
|
|
252
|
+
}
|
|
253
|
+
);
|
|
254
|
+
return { authFile: persisted2, created: false };
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return { authFile: existing, created: false };
|
|
258
|
+
} catch (err) {
|
|
259
|
+
if (err.code !== "ENOENT") {
|
|
260
|
+
opts.warn?.(`HQ auth file access failed at ${file}: ${err.message}`);
|
|
261
|
+
return { authFile: await readHqAuthFile(dataDir, opts), created: false };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const browserToken = {
|
|
265
|
+
...mintHqToken({ label: "first-run browser", ttlMs: opts.tokenTtlMs }),
|
|
266
|
+
capabilities: ["control.enqueue"]
|
|
267
|
+
};
|
|
268
|
+
const clientToken = {
|
|
269
|
+
...mintHqToken({ label: "first-run client", ttlMs: opts.tokenTtlMs }),
|
|
270
|
+
capabilities: ["telemetry.publish"]
|
|
271
|
+
};
|
|
272
|
+
const authFile = {
|
|
273
|
+
version: HQ_AUTH_FILE_VERSION,
|
|
274
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
275
|
+
browserTokens: [browserToken],
|
|
276
|
+
clientTokens: [clientToken]
|
|
277
|
+
};
|
|
278
|
+
if (opts.password) {
|
|
279
|
+
authFile.passwordHash = await hashHqPassword(opts.password);
|
|
280
|
+
authFile.cookieSecret = mintHqCookieSecret();
|
|
281
|
+
}
|
|
282
|
+
await writeHqAuthFile(dataDir, authFile);
|
|
283
|
+
const persisted = await readHqAuthFile(dataDir, opts);
|
|
284
|
+
const hash = hqAuthContentHash(persisted);
|
|
285
|
+
const hashField = hash !== void 0 ? { contentHash: hash } : {};
|
|
286
|
+
const actorField = opts.actor ? { actor: opts.actor } : {};
|
|
287
|
+
for (const [scope, token] of [
|
|
288
|
+
["browser", browserToken],
|
|
289
|
+
["client", clientToken]
|
|
290
|
+
]) {
|
|
291
|
+
logHqAuthAudit(
|
|
292
|
+
dataDir,
|
|
293
|
+
{
|
|
294
|
+
kind: "first-run",
|
|
295
|
+
scope,
|
|
296
|
+
tokenId: token.id,
|
|
297
|
+
...hashField,
|
|
298
|
+
...token.label ? { label: token.label } : {},
|
|
299
|
+
capabilities: token.capabilities,
|
|
300
|
+
...token.expiresAt ? { expiresAt: token.expiresAt } : {},
|
|
301
|
+
...actorField
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
onError: (err) => opts.warn?.(`HQ first-run audit write failed: ${err.message}`)
|
|
305
|
+
}
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
return { authFile: persisted, created: true, browserToken, clientToken };
|
|
309
|
+
}
|
|
310
|
+
async function mutateHqAuthFile(dataDir, mutator, opts = {}) {
|
|
311
|
+
const current = await readHqAuthFile(dataDir, opts);
|
|
312
|
+
const next = await mutator(current);
|
|
313
|
+
await writeHqAuthFile(dataDir, next);
|
|
314
|
+
return next;
|
|
315
|
+
}
|
|
316
|
+
var HQ_PASSWORD_SALT_BYTES = 16;
|
|
317
|
+
var HQ_PASSWORD_HASH_BYTES = 32;
|
|
318
|
+
var HQ_PASSWORD_SCRYPT_PARAMS = { N: 16384, r: 8, p: 1 };
|
|
319
|
+
function hashHqPassword(password) {
|
|
320
|
+
return new Promise((resolve2, reject) => {
|
|
321
|
+
const salt = randomBytes(HQ_PASSWORD_SALT_BYTES);
|
|
322
|
+
scrypt(password, salt, HQ_PASSWORD_HASH_BYTES, HQ_PASSWORD_SCRYPT_PARAMS, (err, derivedKey) => {
|
|
323
|
+
if (err) return reject(err);
|
|
324
|
+
const payload = `scrypt$${salt.toString("base64url")}$${derivedKey.toString("base64url")}`;
|
|
325
|
+
resolve2(payload);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
async function verifyHqPassword(password, hash) {
|
|
330
|
+
const parts = hash.split("$");
|
|
331
|
+
if (parts.length !== 3 || parts[0] !== "scrypt" || !parts[1] || !parts[2]) return false;
|
|
332
|
+
let salt;
|
|
333
|
+
let expected;
|
|
334
|
+
try {
|
|
335
|
+
salt = Buffer.from(parts[1], "base64url");
|
|
336
|
+
expected = Buffer.from(parts[2], "base64url");
|
|
337
|
+
} catch {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
if (salt.length === 0 || expected.length === 0) return false;
|
|
341
|
+
const derived = await new Promise((resolve2, reject) => {
|
|
342
|
+
scrypt(password, salt, expected.length, HQ_PASSWORD_SCRYPT_PARAMS, (err, key) => {
|
|
343
|
+
if (err) reject(err);
|
|
344
|
+
else resolve2(key);
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
return timingSafeEqual(derived, expected);
|
|
348
|
+
}
|
|
349
|
+
function mintHqCookieSecret() {
|
|
350
|
+
return randomBytes(32).toString("base64url");
|
|
351
|
+
}
|
|
352
|
+
function mintHqToken(labelOrOptions) {
|
|
353
|
+
const opts = typeof labelOrOptions === "string" ? { label: labelOrOptions } : labelOrOptions ?? {};
|
|
354
|
+
const at = opts.now ?? Date.now();
|
|
355
|
+
const createdAtIso = new Date(at).toISOString();
|
|
356
|
+
return {
|
|
357
|
+
id: randomUUID(),
|
|
358
|
+
token: randomUUID().replace(/-/g, "") + randomUUID().replace(/-/g, ""),
|
|
359
|
+
createdAt: createdAtIso,
|
|
360
|
+
...opts.label ? { label: opts.label } : {},
|
|
361
|
+
...opts.ttlMs !== void 0 && Number.isFinite(opts.ttlMs) && opts.ttlMs > 0 ? { expiresAt: new Date(at + opts.ttlMs).toISOString() } : {}
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
var mintHqBrowserToken = mintHqToken;
|
|
365
|
+
function watchHqAuthFile(dataDir, onChange, opts = {}) {
|
|
366
|
+
const file = hqAuthFilePath(dataDir);
|
|
367
|
+
const debounceMs = opts.debounceMs ?? 200;
|
|
368
|
+
let timer;
|
|
369
|
+
let closed = false;
|
|
370
|
+
let watcher;
|
|
371
|
+
try {
|
|
372
|
+
watcher = syncFs.watch(path2.dirname(file), { recursive: false });
|
|
373
|
+
} catch (err) {
|
|
374
|
+
opts.warn?.(`HQ auth watcher could not start: ${err.message}`);
|
|
375
|
+
return { close: () => {
|
|
376
|
+
} };
|
|
377
|
+
}
|
|
378
|
+
const trigger = () => {
|
|
379
|
+
if (closed) return;
|
|
380
|
+
if (timer) clearTimeout(timer);
|
|
381
|
+
timer = setTimeout(() => {
|
|
382
|
+
timer = void 0;
|
|
383
|
+
const readOpts = opts.warn ? { warn: opts.warn } : {};
|
|
384
|
+
void readHqAuthFile(dataDir, readOpts).then(
|
|
385
|
+
(next) => {
|
|
386
|
+
if (!closed) onChange(next);
|
|
387
|
+
},
|
|
388
|
+
() => {
|
|
389
|
+
}
|
|
390
|
+
);
|
|
391
|
+
}, debounceMs);
|
|
392
|
+
};
|
|
393
|
+
watcher.on("error", (err) => {
|
|
394
|
+
opts.warn?.(`HQ auth watcher error: ${err.message}`);
|
|
395
|
+
try {
|
|
396
|
+
watcher.close();
|
|
397
|
+
} catch {
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
watcher.on("change", (eventType, filename) => {
|
|
401
|
+
const name = typeof filename === "string" ? filename : "";
|
|
402
|
+
if (eventType === "rename" || eventType === "change") {
|
|
403
|
+
if (!name || name === "auth.json" || name === path2.basename(file)) {
|
|
404
|
+
trigger();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
return {
|
|
409
|
+
close: () => {
|
|
410
|
+
closed = true;
|
|
411
|
+
if (timer) clearTimeout(timer);
|
|
412
|
+
watcher.close();
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/hq/redaction.ts
|
|
418
|
+
var RAW_CONTENT_REPLACEMENT = "[REDACTED:hq_raw_content]";
|
|
419
|
+
var SENSITIVE_FIELD_REPLACEMENT = "[REDACTED:hq_sensitive_field]";
|
|
420
|
+
var REDACTED_PATH_REPLACEMENT = "[REDACTED:hq_path]";
|
|
421
|
+
var RAW_CONTENT_KEYS = /* @__PURE__ */ new Set([
|
|
422
|
+
"content",
|
|
423
|
+
"contents",
|
|
424
|
+
"raw",
|
|
425
|
+
"rawContent",
|
|
426
|
+
"body",
|
|
427
|
+
"message",
|
|
428
|
+
"messages",
|
|
429
|
+
"prompt",
|
|
430
|
+
"prompts",
|
|
431
|
+
"completion",
|
|
432
|
+
"response",
|
|
433
|
+
"text",
|
|
434
|
+
"partialText",
|
|
435
|
+
"assistantMessage",
|
|
436
|
+
"userMessage",
|
|
437
|
+
"stdout",
|
|
438
|
+
"stderr",
|
|
439
|
+
"output",
|
|
440
|
+
"toolInput",
|
|
441
|
+
"toolOutput",
|
|
442
|
+
"outputSummary",
|
|
443
|
+
"body",
|
|
444
|
+
"mailboxBody",
|
|
445
|
+
"messageBody",
|
|
446
|
+
"outcome",
|
|
447
|
+
"fileContent",
|
|
448
|
+
"diff",
|
|
449
|
+
"patch",
|
|
450
|
+
"transcript",
|
|
451
|
+
"logs"
|
|
452
|
+
]);
|
|
453
|
+
var HQ_PROJECT_STATE_EVENT_TYPES = /* @__PURE__ */ new Set(["kanban.snapshot"]);
|
|
454
|
+
var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
455
|
+
"authorization",
|
|
456
|
+
"cookie",
|
|
457
|
+
"password",
|
|
458
|
+
"passwd",
|
|
459
|
+
"pwd",
|
|
460
|
+
"secret",
|
|
461
|
+
"token",
|
|
462
|
+
"accessToken",
|
|
463
|
+
"refreshToken",
|
|
464
|
+
"apiKey",
|
|
465
|
+
"apikey",
|
|
466
|
+
"clientSecret",
|
|
467
|
+
"privateKey"
|
|
468
|
+
]);
|
|
469
|
+
var PATH_KEYS = /* @__PURE__ */ new Set([
|
|
470
|
+
"path",
|
|
471
|
+
"paths",
|
|
472
|
+
"file",
|
|
473
|
+
"files",
|
|
474
|
+
"filepath",
|
|
475
|
+
"filePath",
|
|
476
|
+
"filename",
|
|
477
|
+
"projectRoot",
|
|
478
|
+
"projectRootDisplay",
|
|
479
|
+
"cwd",
|
|
480
|
+
"workingDir",
|
|
481
|
+
"workingDirectory"
|
|
482
|
+
]);
|
|
483
|
+
var defaultScrubber = new DefaultSecretScrubber();
|
|
484
|
+
function resolveHqRedactionPolicy(policy) {
|
|
485
|
+
return {
|
|
486
|
+
...DEFAULT_HQ_REDACTION_POLICY,
|
|
487
|
+
...policy
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function tightenHqRedactionPolicy(publisherPolicy, operatorPolicy) {
|
|
491
|
+
if (operatorPolicy === void 0) return publisherPolicy;
|
|
492
|
+
const toolRank = {
|
|
493
|
+
none: 0,
|
|
494
|
+
summary: 1,
|
|
495
|
+
redacted: 2,
|
|
496
|
+
full: 3
|
|
497
|
+
};
|
|
498
|
+
const pathRank = {
|
|
499
|
+
none: 0,
|
|
500
|
+
redacted: 0,
|
|
501
|
+
"project-relative": 1,
|
|
502
|
+
full: 2
|
|
503
|
+
};
|
|
504
|
+
const requestedToolArgs = operatorPolicy.toolArgs;
|
|
505
|
+
const requestedPaths = operatorPolicy.paths;
|
|
506
|
+
return {
|
|
507
|
+
rawContent: publisherPolicy.rawContent && (operatorPolicy.rawContent === void 0 || operatorPolicy.rawContent),
|
|
508
|
+
toolArgs: requestedToolArgs !== void 0 && toolRank[requestedToolArgs] < toolRank[publisherPolicy.toolArgs] ? requestedToolArgs : publisherPolicy.toolArgs,
|
|
509
|
+
paths: requestedPaths !== void 0 && pathRank[requestedPaths] < pathRank[publisherPolicy.paths] ? requestedPaths : publisherPolicy.paths
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
function isPlainObject(value) {
|
|
513
|
+
return Object.prototype.toString.call(value) === "[object Object]";
|
|
514
|
+
}
|
|
515
|
+
function isSensitiveKey(key) {
|
|
516
|
+
const lower = key.toLowerCase();
|
|
517
|
+
return SENSITIVE_KEYS.has(key) || lower.includes("authorization") || lower.includes("password") || lower.includes("secret") || lower.endsWith("token") || lower.endsWith("apikey") || lower.endsWith("api_key");
|
|
518
|
+
}
|
|
519
|
+
function isRawContentKey(key) {
|
|
520
|
+
return RAW_CONTENT_KEYS.has(key) || RAW_CONTENT_KEYS.has(key.toLowerCase());
|
|
521
|
+
}
|
|
522
|
+
function isPathKey(key) {
|
|
523
|
+
return PATH_KEYS.has(key) || PATH_KEYS.has(key.toLowerCase());
|
|
524
|
+
}
|
|
525
|
+
function normalizePathForCompare(value) {
|
|
526
|
+
return value.replace(/\\/g, "/").replace(/\/+$/g, "");
|
|
527
|
+
}
|
|
528
|
+
function redactPath(value, projectRoot, policy) {
|
|
529
|
+
if (policy.paths === "full") return value;
|
|
530
|
+
if (policy.paths === "none" || policy.paths === "redacted") return REDACTED_PATH_REPLACEMENT;
|
|
531
|
+
const normalizedValue = normalizePathForCompare(value);
|
|
532
|
+
if (!projectRoot) return normalizedValue.split("/").at(-1) ?? REDACTED_PATH_REPLACEMENT;
|
|
533
|
+
const normalizedRoot = normalizePathForCompare(projectRoot);
|
|
534
|
+
if (normalizedValue === normalizedRoot) return ".";
|
|
535
|
+
if (normalizedValue.startsWith(`${normalizedRoot}/`)) {
|
|
536
|
+
return normalizedValue.slice(normalizedRoot.length + 1) || ".";
|
|
537
|
+
}
|
|
538
|
+
return normalizedValue.split("/").at(-1) ?? REDACTED_PATH_REPLACEMENT;
|
|
539
|
+
}
|
|
540
|
+
function summarizeString(value, maxSummaryLength, scrub) {
|
|
541
|
+
const scrubbed = scrub ? defaultScrubber.scrub(value) : value;
|
|
542
|
+
if (scrubbed.length <= maxSummaryLength) return scrubbed;
|
|
543
|
+
return `${scrubbed.slice(0, maxSummaryLength)}\u2026[truncated:${scrubbed.length - maxSummaryLength}]`;
|
|
544
|
+
}
|
|
545
|
+
function scrubAndTruncateHqPreview(value, maxLength = 280) {
|
|
546
|
+
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
547
|
+
return summarizeString(value, maxLength, true);
|
|
548
|
+
}
|
|
549
|
+
function visitValue(value, options, key) {
|
|
550
|
+
if (key !== void 0 && isSensitiveKey(key)) {
|
|
551
|
+
return { value: SENSITIVE_FIELD_REPLACEMENT, redacted: true };
|
|
552
|
+
}
|
|
553
|
+
if (typeof value === "string") {
|
|
554
|
+
if (key !== void 0 && isPathKey(key)) {
|
|
555
|
+
const redactedPath = redactPath(value, options.projectRoot, options.policy);
|
|
556
|
+
return { value: redactedPath, redacted: redactedPath !== value };
|
|
557
|
+
}
|
|
558
|
+
if (!options.preserveRawContentKeys && !options.policy.rawContent && key !== void 0 && isRawContentKey(key)) {
|
|
559
|
+
return { value: RAW_CONTENT_REPLACEMENT, redacted: true };
|
|
560
|
+
}
|
|
561
|
+
const scrubbed = summarizeString(value, options.maxSummaryLength, true);
|
|
562
|
+
return { value: scrubbed, redacted: scrubbed !== value };
|
|
563
|
+
}
|
|
564
|
+
if (value === null || typeof value !== "object") return { value, redacted: false };
|
|
565
|
+
if (options.seen.has(value)) return { value: "[REDACTED:hq_circular]", redacted: true };
|
|
566
|
+
options.seen.add(value);
|
|
567
|
+
if (Array.isArray(value)) {
|
|
568
|
+
let redacted2 = false;
|
|
569
|
+
const out2 = value.map((item) => {
|
|
570
|
+
const next = visitValue(item, options, key);
|
|
571
|
+
redacted2 ||= next.redacted;
|
|
572
|
+
return next.value;
|
|
573
|
+
});
|
|
574
|
+
return { value: out2, redacted: redacted2 };
|
|
575
|
+
}
|
|
576
|
+
if (!isPlainObject(value)) return { value: String(value), redacted: true };
|
|
577
|
+
let redacted = false;
|
|
578
|
+
const out = {};
|
|
579
|
+
for (const [childKey, childValue] of Object.entries(value)) {
|
|
580
|
+
const next = visitValue(childValue, options, childKey);
|
|
581
|
+
redacted ||= next.redacted;
|
|
582
|
+
out[childKey] = next.value;
|
|
583
|
+
}
|
|
584
|
+
return { value: out, redacted };
|
|
585
|
+
}
|
|
586
|
+
function redactHqValueInternal(value, options, preserveRawContentKeys) {
|
|
587
|
+
const result = visitValue(value, {
|
|
588
|
+
policy: resolveHqRedactionPolicy(options.policy),
|
|
589
|
+
...options.projectRoot !== void 0 ? { projectRoot: options.projectRoot } : {},
|
|
590
|
+
maxSummaryLength: options.maxSummaryLength ?? 500,
|
|
591
|
+
preserveRawContentKeys,
|
|
592
|
+
seen: /* @__PURE__ */ new WeakSet()
|
|
593
|
+
});
|
|
594
|
+
return { value: result.value, redacted: result.redacted };
|
|
595
|
+
}
|
|
596
|
+
function redactHqValue(value, options = {}) {
|
|
597
|
+
return redactHqValueInternal(value, options, false);
|
|
598
|
+
}
|
|
599
|
+
function redactHqEventPayload(type, payload, options = {}) {
|
|
600
|
+
return redactHqValueInternal(payload, options, HQ_PROJECT_STATE_EVENT_TYPES.has(type));
|
|
601
|
+
}
|
|
602
|
+
function redactHqEvent(event, options = {}) {
|
|
603
|
+
const payload = redactHqEventPayload(event.type, event.payload, options);
|
|
604
|
+
const nextEvent = {
|
|
605
|
+
...event,
|
|
606
|
+
payload: payload.value
|
|
607
|
+
};
|
|
608
|
+
return { value: nextEvent, redacted: payload.redacted };
|
|
609
|
+
}
|
|
610
|
+
function summarizeHqToolArgs(value, options = {}) {
|
|
611
|
+
const policy = resolveHqRedactionPolicy(options.policy);
|
|
612
|
+
if (policy.toolArgs === "full") return redactHqValue(value, options).value;
|
|
613
|
+
if (policy.toolArgs === "none") return "[REDACTED:hq_tool_args]";
|
|
614
|
+
if (policy.toolArgs === "redacted") return redactHqValue(value, options).value;
|
|
615
|
+
if (value === null || typeof value !== "object") return redactHqValue(value, options).value;
|
|
616
|
+
if (Array.isArray(value)) return `[array:${value.length}]`;
|
|
617
|
+
const entries = Object.entries(value);
|
|
618
|
+
const summary = {};
|
|
619
|
+
for (const [key, item] of entries.slice(0, 20)) {
|
|
620
|
+
if (isSensitiveKey(key)) {
|
|
621
|
+
summary[key] = SENSITIVE_FIELD_REPLACEMENT;
|
|
622
|
+
} else if (typeof item === "string") {
|
|
623
|
+
summary[key] = isPathKey(key) ? redactPath(item, options.projectRoot, policy) : summarizeString(item, 120, true);
|
|
624
|
+
} else if (Array.isArray(item)) {
|
|
625
|
+
summary[key] = `[array:${item.length}]`;
|
|
626
|
+
} else if (item !== null && typeof item === "object") {
|
|
627
|
+
summary[key] = "[object]";
|
|
628
|
+
} else {
|
|
629
|
+
summary[key] = item;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if (entries.length > 20) summary.__truncatedKeys = entries.length - 20;
|
|
633
|
+
return summary;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// src/hq/mailbox-mapper.ts
|
|
637
|
+
function previewText(value, maxLength, policy) {
|
|
638
|
+
if (value === void 0 || value.length === 0) return void 0;
|
|
639
|
+
if (policy?.rawContent === false) return "[REDACTED:hq_raw_content]";
|
|
640
|
+
if (policy?.rawContent === true) {
|
|
641
|
+
if (value.length <= maxLength) return value;
|
|
642
|
+
return `${value.slice(0, maxLength)}\u2026`;
|
|
643
|
+
}
|
|
644
|
+
return scrubAndTruncateHqPreview(value, maxLength);
|
|
645
|
+
}
|
|
646
|
+
function readCount(message) {
|
|
647
|
+
return Object.keys(message.readBy).length;
|
|
648
|
+
}
|
|
649
|
+
function classifyMailboxRecipient(to) {
|
|
650
|
+
if (to === "*" || to === "all") return { scope: "project" };
|
|
651
|
+
if (to.startsWith(SESSION_RECIPIENT_PREFIX)) {
|
|
652
|
+
const recipientSessionId = to.slice(SESSION_RECIPIENT_PREFIX.length);
|
|
653
|
+
if (recipientSessionId.length === 0) return { scope: "agent" };
|
|
654
|
+
return { scope: "session", recipientSessionId };
|
|
655
|
+
}
|
|
656
|
+
return { scope: "agent" };
|
|
657
|
+
}
|
|
658
|
+
function taskSummary(message) {
|
|
659
|
+
if (message.taskContext === void 0) return void 0;
|
|
660
|
+
const task = {
|
|
661
|
+
...message.taskContext.taskId !== void 0 ? { taskId: message.taskContext.taskId } : {},
|
|
662
|
+
...message.taskContext.agentRole !== void 0 ? { agentRole: message.taskContext.agentRole } : {},
|
|
663
|
+
...message.taskContext.agentName !== void 0 ? { agentName: message.taskContext.agentName } : {},
|
|
664
|
+
...message.taskContext.status !== void 0 ? { status: message.taskContext.status } : {}
|
|
665
|
+
};
|
|
666
|
+
return Object.keys(task).length > 0 ? task : void 0;
|
|
667
|
+
}
|
|
668
|
+
function mapMailboxMessageToHqSummary(message, options = {}) {
|
|
669
|
+
const previewLength = options.previewLength ?? 500;
|
|
670
|
+
const bodyPreview = previewText(message.body, previewLength, options.redactionPolicy);
|
|
671
|
+
const outcomePreview = previewText(message.outcome, previewLength, options.redactionPolicy);
|
|
672
|
+
const task = taskSummary(message);
|
|
673
|
+
const recipient = classifyMailboxRecipient(message.to);
|
|
674
|
+
return {
|
|
675
|
+
mailId: message.id,
|
|
676
|
+
// unique UUID per message record, used for deduplication
|
|
677
|
+
messageId: message.id,
|
|
678
|
+
from: message.from,
|
|
679
|
+
to: message.to,
|
|
680
|
+
scope: recipient.scope,
|
|
681
|
+
...recipient.recipientSessionId !== void 0 ? { recipientSessionId: recipient.recipientSessionId } : {},
|
|
682
|
+
type: message.type,
|
|
683
|
+
...message.audience !== void 0 ? { audience: message.audience } : {},
|
|
684
|
+
subject: previewText(message.subject, previewLength, options.redactionPolicy) ?? "",
|
|
685
|
+
priority: message.priority,
|
|
686
|
+
timestamp: message.timestamp,
|
|
687
|
+
...message.replyTo !== void 0 ? { replyTo: message.replyTo } : {},
|
|
688
|
+
...message.senderSessionId !== void 0 ? { senderSessionId: message.senderSessionId } : {},
|
|
689
|
+
completed: message.completed,
|
|
690
|
+
...message.completedBy !== void 0 ? { completedBy: message.completedBy } : {},
|
|
691
|
+
...message.completedAt !== void 0 ? { completedAt: message.completedAt } : {},
|
|
692
|
+
readCount: readCount(message),
|
|
693
|
+
hasBody: message.body.length > 0,
|
|
694
|
+
...bodyPreview !== void 0 ? { bodyPreview } : {},
|
|
695
|
+
...outcomePreview !== void 0 ? { outcomePreview } : {},
|
|
696
|
+
...task !== void 0 ? { task } : {}
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
function mapMailboxAgentToHqSummary(agent) {
|
|
700
|
+
return {
|
|
701
|
+
agentId: agent.agentId,
|
|
702
|
+
name: agent.name,
|
|
703
|
+
...agent.role !== void 0 ? { role: agent.role } : {},
|
|
704
|
+
sessionId: agent.sessionId,
|
|
705
|
+
status: agent.status,
|
|
706
|
+
...agent.currentTool !== void 0 ? { currentTool: agent.currentTool } : {},
|
|
707
|
+
...agent.currentTask !== void 0 ? { currentTask: agent.currentTask } : {},
|
|
708
|
+
iterations: agent.iterations,
|
|
709
|
+
toolCalls: agent.toolCalls,
|
|
710
|
+
lastActivityAt: agent.lastActivityAt,
|
|
711
|
+
lastSeenAt: agent.lastSeenAt,
|
|
712
|
+
online: agent.online,
|
|
713
|
+
...agent.source !== void 0 ? { source: agent.source } : {}
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
function createMailboxSnapshotPayload(messages, agents, options) {
|
|
717
|
+
const includeCompleted = options.includeCompleted ?? true;
|
|
718
|
+
const filteredMessages = includeCompleted ? messages : messages.filter((message) => !message.completed);
|
|
719
|
+
const limit = options.limit ?? 50;
|
|
720
|
+
const sortedMessages = [...filteredMessages].sort((a, b) => b.timestamp.localeCompare(a.timestamp)).slice(0, limit);
|
|
721
|
+
const summaries = sortedMessages.map((message) => mapMailboxMessageToHqSummary(message, options));
|
|
722
|
+
const agentSummaries = agents.map(mapMailboxAgentToHqSummary);
|
|
723
|
+
return {
|
|
724
|
+
mailboxId: options.mailboxId,
|
|
725
|
+
scope: options.scope ?? "project",
|
|
726
|
+
messages: summaries,
|
|
727
|
+
agents: agentSummaries,
|
|
728
|
+
totals: {
|
|
729
|
+
messages: messages.length,
|
|
730
|
+
unread: messages.filter((message) => !message.completed && readCount(message) === 0).length,
|
|
731
|
+
incomplete: messages.filter((message) => !message.completed).length,
|
|
732
|
+
highPriority: messages.filter((message) => message.priority === "high").length,
|
|
733
|
+
onlineAgents: agents.filter((agent) => agent.online).length
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
async function createMailboxSnapshotPayloadFromMailbox(mailbox, options) {
|
|
738
|
+
const limit = options.limit ?? 50;
|
|
739
|
+
const [messages, agents] = await Promise.all([
|
|
740
|
+
mailbox.query({ limit }),
|
|
741
|
+
mailbox.getAgentStatuses()
|
|
742
|
+
]);
|
|
743
|
+
return createMailboxSnapshotPayload(messages, agents, options);
|
|
744
|
+
}
|
|
745
|
+
function createMailboxEventPayload(input) {
|
|
746
|
+
const summaryOptions = {
|
|
747
|
+
...input.previewLength !== void 0 ? { previewLength: input.previewLength } : {},
|
|
748
|
+
...input.redactionPolicy !== void 0 ? { redactionPolicy: input.redactionPolicy } : {}
|
|
749
|
+
};
|
|
750
|
+
return {
|
|
751
|
+
mailboxId: input.mailboxId,
|
|
752
|
+
action: input.action,
|
|
753
|
+
...input.message !== void 0 ? { message: mapMailboxMessageToHqSummary(input.message, summaryOptions) } : {},
|
|
754
|
+
...input.agent !== void 0 ? { agent: mapMailboxAgentToHqSummary(input.agent) } : {},
|
|
755
|
+
...input.summary !== void 0 ? { summary: previewText(input.summary, input.previewLength ?? 160, input.redactionPolicy) ?? "" } : {}
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// src/hq/publisher.ts
|
|
760
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
761
|
+
import * as v8 from "node:v8";
|
|
762
|
+
var TRANSCRIPT_EVENT_TYPES = /* @__PURE__ */ new Set(["session.transcript", "agent.message"]);
|
|
763
|
+
var OPEN_STATE = 1;
|
|
764
|
+
var CONNECT_WARN_AFTER_FAILURES = 5;
|
|
765
|
+
var DEFAULT_CONNECT_WARN_COOLDOWN_MS = 5 * 6e4;
|
|
766
|
+
var DEFAULT_RECONNECT_BASE_MS = 1e3;
|
|
767
|
+
var DEFAULT_RECONNECT_MAX_MS = 3e4;
|
|
768
|
+
var DEFAULT_DISCOVERY_POLL_MS = 5e3;
|
|
769
|
+
var DEFAULT_MAX_QUEUED_MESSAGES = 2e3;
|
|
770
|
+
var DEFAULT_MAX_QUEUED_BYTES = Math.min(
|
|
771
|
+
16 * 1024 * 1024,
|
|
772
|
+
Math.floor(v8.getHeapStatistics().heap_size_limit * 0.1)
|
|
773
|
+
);
|
|
774
|
+
var DEFAULT_COMMAND_POLL_INTERVAL_MS = 500;
|
|
775
|
+
var DEFAULT_COMMAND_POLL_LIMIT = 25;
|
|
776
|
+
var DEFAULT_HEARTBEAT_INTERVAL_MS = 25e3;
|
|
777
|
+
var lastConnectWarningAtMs = 0;
|
|
778
|
+
function queuedFrameCoalesceKey(frame) {
|
|
779
|
+
if (frame.type !== "client.event" || !frame.event.type.endsWith(".snapshot")) {
|
|
780
|
+
return void 0;
|
|
781
|
+
}
|
|
782
|
+
const payload = frame.event.payload;
|
|
783
|
+
const chunk = typeof payload?.chunkIndex === "number" && Number.isFinite(payload.chunkIndex) ? String(payload.chunkIndex) : "";
|
|
784
|
+
return [frame.event.type, frame.event.sessionId ?? "", frame.event.runId ?? "", chunk].join("|");
|
|
785
|
+
}
|
|
786
|
+
function defaultSocketFactory(url) {
|
|
787
|
+
const WebSocketCtor = globalThis.WebSocket;
|
|
788
|
+
if (WebSocketCtor === void 0) {
|
|
789
|
+
throw new Error(
|
|
790
|
+
"No global WebSocket implementation is available; provide HqPublisherOptions.socketFactory."
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
return new WebSocketCtor(url);
|
|
794
|
+
}
|
|
795
|
+
function toClientUrl(baseUrl, token) {
|
|
796
|
+
const url = new URL(baseUrl);
|
|
797
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
798
|
+
if (url.pathname === "/" || url.pathname === "") url.pathname = "/ws/client";
|
|
799
|
+
if (token !== void 0 && token.length > 0) url.searchParams.set("token", token);
|
|
800
|
+
return url.toString();
|
|
801
|
+
}
|
|
802
|
+
function addSocketListener(socket, type, listener) {
|
|
803
|
+
if (socket.addEventListener !== void 0) {
|
|
804
|
+
socket.addEventListener(type, listener);
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
socket.on?.(type, listener);
|
|
808
|
+
}
|
|
809
|
+
function removeSocketListener(socket, type, listener) {
|
|
810
|
+
if (socket.removeEventListener !== void 0) {
|
|
811
|
+
socket.removeEventListener(type, listener);
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
socket.off?.(type, listener);
|
|
815
|
+
}
|
|
816
|
+
var HqPublisher = class {
|
|
817
|
+
constructor(options) {
|
|
818
|
+
this.options = options;
|
|
819
|
+
this.socketFactory = options.socketFactory ?? defaultSocketFactory;
|
|
820
|
+
this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
821
|
+
this.idFactory = options.idFactory ?? randomUUID2;
|
|
822
|
+
this.capabilities = options.capabilities ?? [
|
|
823
|
+
"telemetry.publish",
|
|
824
|
+
"mailbox.summary",
|
|
825
|
+
"fleet.summary",
|
|
826
|
+
"session.summary"
|
|
827
|
+
];
|
|
828
|
+
this.reconnect = options.reconnect ?? true;
|
|
829
|
+
this.reconnectBaseMs = options.reconnectBaseMs ?? DEFAULT_RECONNECT_BASE_MS;
|
|
830
|
+
this.reconnectMaxMs = options.reconnectMaxMs ?? DEFAULT_RECONNECT_MAX_MS;
|
|
831
|
+
this.connectWarnAfterFailures = options.connectWarnAfterFailures ?? CONNECT_WARN_AFTER_FAILURES;
|
|
832
|
+
this.connectWarnCooldownMs = options.connectWarnCooldownMs ?? DEFAULT_CONNECT_WARN_COOLDOWN_MS;
|
|
833
|
+
this.maxQueuedMessages = options.maxQueuedMessages ?? DEFAULT_MAX_QUEUED_MESSAGES;
|
|
834
|
+
this.maxQueuedBytes = options.maxQueuedBytes ?? DEFAULT_MAX_QUEUED_BYTES;
|
|
835
|
+
this.resolvedRedactionPolicy = resolveHqRedactionPolicy(options.redactionPolicy);
|
|
836
|
+
this.logger = options.logger;
|
|
837
|
+
}
|
|
838
|
+
options;
|
|
839
|
+
socketFactory;
|
|
840
|
+
now;
|
|
841
|
+
idFactory;
|
|
842
|
+
capabilities;
|
|
843
|
+
reconnect;
|
|
844
|
+
reconnectBaseMs;
|
|
845
|
+
reconnectMaxMs;
|
|
846
|
+
connectWarnAfterFailures;
|
|
847
|
+
connectWarnCooldownMs;
|
|
848
|
+
maxQueuedMessages;
|
|
849
|
+
maxQueuedBytes;
|
|
850
|
+
resolvedRedactionPolicy;
|
|
851
|
+
logger;
|
|
852
|
+
socket = null;
|
|
853
|
+
seq = 0;
|
|
854
|
+
queue = [];
|
|
855
|
+
queueBytes = 0;
|
|
856
|
+
/**
|
|
857
|
+
* Cumulative count of frames dropped by the bounded outbound queue when the
|
|
858
|
+
* count/byte cap is exceeded while HQ is unreachable. Lifetime statistic —
|
|
859
|
+
* intentionally NOT reset on close()/reconnect, so it reflects total
|
|
860
|
+
* telemetry loss to overflow for this publisher instance.
|
|
861
|
+
*/
|
|
862
|
+
droppedFrames = 0;
|
|
863
|
+
/** Cumulative UTF-8 bytes of frames dropped on overflow (see droppedFrames). */
|
|
864
|
+
droppedBytes = 0;
|
|
865
|
+
/** Obsolete state snapshots replaced in-place while HQ was unreachable. */
|
|
866
|
+
coalescedFrames = 0;
|
|
867
|
+
/** UTF-8 bytes released by snapshot coalescing. */
|
|
868
|
+
coalescedBytes = 0;
|
|
869
|
+
stopped = false;
|
|
870
|
+
reconnectAttempt = 0;
|
|
871
|
+
connectWarningEmitted = false;
|
|
872
|
+
lastAttempt = null;
|
|
873
|
+
reconnectTimer = null;
|
|
874
|
+
commandPollTimer = null;
|
|
875
|
+
heartbeatTimer = null;
|
|
876
|
+
lastCommandId;
|
|
877
|
+
connect() {
|
|
878
|
+
if (this.socket !== null || this.stopped) return;
|
|
879
|
+
if (this.reconnectTimer !== null) return;
|
|
880
|
+
let url = this.options.url;
|
|
881
|
+
let token = this.options.token;
|
|
882
|
+
if (this.options.resolveEndpoint !== void 0) {
|
|
883
|
+
const endpoint = this.options.resolveEndpoint();
|
|
884
|
+
if (endpoint === void 0) {
|
|
885
|
+
this.scheduleDiscoveryPoll();
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
url = endpoint.url;
|
|
889
|
+
token = endpoint.token ?? token;
|
|
890
|
+
}
|
|
891
|
+
this.lastAttempt = { url, hadToken: token !== void 0 };
|
|
892
|
+
let socket;
|
|
893
|
+
try {
|
|
894
|
+
socket = this.socketFactory(toClientUrl(url, token), {
|
|
895
|
+
...token !== void 0 ? { token } : {}
|
|
896
|
+
});
|
|
897
|
+
} catch {
|
|
898
|
+
this.scheduleReconnect();
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
this.socket = socket;
|
|
902
|
+
const onOpen = () => {
|
|
903
|
+
this.reconnectAttempt = 0;
|
|
904
|
+
this.sendHelloNow();
|
|
905
|
+
this.flushQueue();
|
|
906
|
+
this.startHeartbeat();
|
|
907
|
+
if (this.options.onCommand !== void 0) {
|
|
908
|
+
this.startCommandPolling();
|
|
909
|
+
this.pollCommands();
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
const onMessage = (event) => {
|
|
913
|
+
this.handleServerMessage(event).catch((error) => {
|
|
914
|
+
const message = `WrongStack HQ publisher: server message handling failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
915
|
+
if (this.logger) {
|
|
916
|
+
this.logger.warn(message, { event: "hq.publisher.message_failed" });
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
process.emitWarning(message, { code: "WRONGSTACK_HQ_MESSAGE_FAILED" });
|
|
920
|
+
});
|
|
921
|
+
};
|
|
922
|
+
const onCloseOrError = () => {
|
|
923
|
+
removeSocketListener(socket, "open", onOpen);
|
|
924
|
+
removeSocketListener(socket, "message", onMessage);
|
|
925
|
+
removeSocketListener(socket, "close", onCloseOrError);
|
|
926
|
+
removeSocketListener(socket, "error", onCloseOrError);
|
|
927
|
+
this.stopCommandPolling();
|
|
928
|
+
this.stopHeartbeat();
|
|
929
|
+
if (this.socket === socket) this.socket = null;
|
|
930
|
+
this.scheduleReconnect();
|
|
931
|
+
};
|
|
932
|
+
addSocketListener(socket, "open", onOpen);
|
|
933
|
+
addSocketListener(socket, "message", onMessage);
|
|
934
|
+
addSocketListener(socket, "close", onCloseOrError);
|
|
935
|
+
addSocketListener(socket, "error", onCloseOrError);
|
|
936
|
+
if (socket.readyState === OPEN_STATE) onOpen();
|
|
937
|
+
}
|
|
938
|
+
close() {
|
|
939
|
+
this.stopped = true;
|
|
940
|
+
if (this.reconnectTimer !== null) {
|
|
941
|
+
clearTimeout(this.reconnectTimer);
|
|
942
|
+
this.reconnectTimer = null;
|
|
943
|
+
}
|
|
944
|
+
this.stopCommandPolling();
|
|
945
|
+
this.stopHeartbeat();
|
|
946
|
+
this.queue = [];
|
|
947
|
+
this.queueBytes = 0;
|
|
948
|
+
const socket = this.socket;
|
|
949
|
+
this.socket = null;
|
|
950
|
+
socket?.close(1e3, "hq publisher closed");
|
|
951
|
+
}
|
|
952
|
+
publishEvent(options) {
|
|
953
|
+
const event = createHqEventEnvelope({
|
|
954
|
+
id: this.idFactory(),
|
|
955
|
+
type: options.type,
|
|
956
|
+
timestamp: options.timestamp ?? this.now(),
|
|
957
|
+
clientId: this.options.client.clientId,
|
|
958
|
+
projectId: this.options.project.projectId,
|
|
959
|
+
seq: ++this.seq,
|
|
960
|
+
payload: options.payload,
|
|
961
|
+
...options.sessionId !== void 0 ? { sessionId: options.sessionId } : {},
|
|
962
|
+
...options.runId !== void 0 ? { runId: options.runId } : {}
|
|
963
|
+
});
|
|
964
|
+
const maxSummaryLength = options.maxSummaryLength ?? (TRANSCRIPT_EVENT_TYPES.has(options.type) ? HQ_TRANSCRIPT_TEXT_CAP : void 0);
|
|
965
|
+
const redacted = redactHqEvent(event, {
|
|
966
|
+
policy: this.resolvedRedactionPolicy,
|
|
967
|
+
projectRoot: this.options.project.projectRoot,
|
|
968
|
+
...maxSummaryLength !== void 0 ? { maxSummaryLength } : {}
|
|
969
|
+
}).value;
|
|
970
|
+
this.sendFrame({ type: "client.event", event: redacted });
|
|
971
|
+
return redacted;
|
|
972
|
+
}
|
|
973
|
+
async publishMailboxSnapshot(mailbox, options) {
|
|
974
|
+
const payload = await createMailboxSnapshotPayloadFromMailbox(mailbox, {
|
|
975
|
+
...options,
|
|
976
|
+
redactionPolicy: this.resolvedRedactionPolicy
|
|
977
|
+
});
|
|
978
|
+
return this.publishEvent({
|
|
979
|
+
type: "mailbox.snapshot",
|
|
980
|
+
payload,
|
|
981
|
+
...options.sessionId !== void 0 ? { sessionId: options.sessionId } : {},
|
|
982
|
+
...options.timestamp !== void 0 ? { timestamp: options.timestamp } : {}
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
publishMailboxEvent(input) {
|
|
986
|
+
const payload = createMailboxEventPayload({
|
|
987
|
+
mailboxId: input.mailboxId,
|
|
988
|
+
action: input.action,
|
|
989
|
+
...input.message !== void 0 ? { message: input.message } : {},
|
|
990
|
+
...input.agent !== void 0 ? { agent: input.agent } : {},
|
|
991
|
+
...input.summary !== void 0 ? { summary: input.summary } : {},
|
|
992
|
+
...input.previewLength !== void 0 ? { previewLength: input.previewLength } : {},
|
|
993
|
+
redactionPolicy: this.resolvedRedactionPolicy
|
|
994
|
+
});
|
|
995
|
+
return this.publishEvent({
|
|
996
|
+
type: "mailbox.event",
|
|
997
|
+
payload,
|
|
998
|
+
...input.sessionId !== void 0 ? { sessionId: input.sessionId } : {},
|
|
999
|
+
...input.timestamp !== void 0 ? { timestamp: input.timestamp } : {}
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
/** The client identity this publisher announced (clientId, kind, machineId, …). */
|
|
1003
|
+
get identity() {
|
|
1004
|
+
return this.options.client;
|
|
1005
|
+
}
|
|
1006
|
+
/** The project identity this publisher is bound to. */
|
|
1007
|
+
get project() {
|
|
1008
|
+
return this.options.project;
|
|
1009
|
+
}
|
|
1010
|
+
/** Effective publisher-side policy applied before any event leaves this process. */
|
|
1011
|
+
get redactionPolicy() {
|
|
1012
|
+
return this.resolvedRedactionPolicy;
|
|
1013
|
+
}
|
|
1014
|
+
/** Current outbound queue pressure — useful for flow control in telemetry bridges. */
|
|
1015
|
+
getQueueStats() {
|
|
1016
|
+
return {
|
|
1017
|
+
entries: this.queue.length,
|
|
1018
|
+
bytes: this.queueBytes,
|
|
1019
|
+
maxBytes: this.maxQueuedBytes,
|
|
1020
|
+
droppedFrames: this.droppedFrames,
|
|
1021
|
+
droppedBytes: this.droppedBytes,
|
|
1022
|
+
coalescedFrames: this.coalescedFrames,
|
|
1023
|
+
coalescedBytes: this.coalescedBytes
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
/** Publish a live session/terminal snapshot (state + agents). */
|
|
1027
|
+
publishSessionSnapshot(payload, opts) {
|
|
1028
|
+
return this.publishEvent({
|
|
1029
|
+
type: "session.snapshot",
|
|
1030
|
+
payload,
|
|
1031
|
+
sessionId: payload.sessionId,
|
|
1032
|
+
...opts?.timestamp !== void 0 ? { timestamp: opts.timestamp } : {}
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
/** Publish an incremental batch of transcript turns for a session. */
|
|
1036
|
+
publishTranscriptAppend(payload, opts) {
|
|
1037
|
+
return this.publishEvent({
|
|
1038
|
+
type: "session.transcript",
|
|
1039
|
+
payload,
|
|
1040
|
+
sessionId: payload.sessionId,
|
|
1041
|
+
...opts?.timestamp !== void 0 ? { timestamp: opts.timestamp } : {}
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
/** Mark a session/terminal as ended. */
|
|
1045
|
+
publishSessionEnded(payload, opts) {
|
|
1046
|
+
return this.publishEvent({
|
|
1047
|
+
type: "session.ended",
|
|
1048
|
+
payload,
|
|
1049
|
+
sessionId: payload.sessionId,
|
|
1050
|
+
...opts?.timestamp !== void 0 ? { timestamp: opts.timestamp } : {}
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
/** Publish a fleet (multi-agent coordinator) snapshot. */
|
|
1054
|
+
publishFleetSnapshot(payload, opts) {
|
|
1055
|
+
return this.publishEvent({
|
|
1056
|
+
type: "fleet.snapshot",
|
|
1057
|
+
payload,
|
|
1058
|
+
runId: payload.runId,
|
|
1059
|
+
...opts?.sessionId !== void 0 ? { sessionId: opts.sessionId } : {},
|
|
1060
|
+
...opts?.timestamp !== void 0 ? { timestamp: opts.timestamp } : {}
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
pollCommands() {
|
|
1064
|
+
this.sendFrame({
|
|
1065
|
+
type: "client.command_poll",
|
|
1066
|
+
clientId: this.options.client.clientId,
|
|
1067
|
+
projectId: this.options.project.projectId,
|
|
1068
|
+
...this.lastCommandId !== void 0 ? { afterCommandId: this.lastCommandId } : {},
|
|
1069
|
+
limit: this.options.commandPollLimit ?? DEFAULT_COMMAND_POLL_LIMIT
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
ackCommand(result) {
|
|
1073
|
+
this.sendFrame({
|
|
1074
|
+
type: "client.command_ack",
|
|
1075
|
+
clientId: this.options.client.clientId,
|
|
1076
|
+
projectId: this.options.project.projectId,
|
|
1077
|
+
commandId: result.commandId,
|
|
1078
|
+
status: result.status,
|
|
1079
|
+
...result.message !== void 0 ? { message: result.message } : {}
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
createHelloFrame() {
|
|
1083
|
+
return {
|
|
1084
|
+
type: "client.hello",
|
|
1085
|
+
payload: {
|
|
1086
|
+
protocolVersion: HQ_PROTOCOL_VERSION,
|
|
1087
|
+
client: this.options.client,
|
|
1088
|
+
project: this.options.project,
|
|
1089
|
+
capabilities: this.capabilities,
|
|
1090
|
+
redactionPolicy: this.resolvedRedactionPolicy
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
sendHelloNow() {
|
|
1095
|
+
const socket = this.socket;
|
|
1096
|
+
if (socket?.readyState !== OPEN_STATE) {
|
|
1097
|
+
this.sendFrame(this.createHelloFrame());
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
socket.send(JSON.stringify(this.createHelloFrame()));
|
|
1101
|
+
}
|
|
1102
|
+
sendFrame(frame) {
|
|
1103
|
+
const serialized = JSON.stringify(frame);
|
|
1104
|
+
const socket = this.socket;
|
|
1105
|
+
if (socket?.readyState === OPEN_STATE && this.queue.length === 0) {
|
|
1106
|
+
socket.send(serialized);
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
this.enqueue(serialized, queuedFrameCoalesceKey(frame));
|
|
1110
|
+
this.connect();
|
|
1111
|
+
}
|
|
1112
|
+
enqueue(serialized, coalesceKey) {
|
|
1113
|
+
const bytes = Buffer.byteLength(serialized, "utf8");
|
|
1114
|
+
if (bytes > this.maxQueuedBytes) {
|
|
1115
|
+
this.droppedFrames += 1;
|
|
1116
|
+
this.droppedBytes += bytes;
|
|
1117
|
+
process.emitWarning(
|
|
1118
|
+
`HQ telemetry frame of ${bytes} bytes exceeds the ${this.maxQueuedBytes}-byte offline queue cap and was dropped.`,
|
|
1119
|
+
{ code: "WRONGSTACK_HQ_FRAME_TOO_LARGE" }
|
|
1120
|
+
);
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
if (coalesceKey !== void 0) {
|
|
1124
|
+
let existingIndex = -1;
|
|
1125
|
+
for (let index = this.queue.length - 1; index >= 0; index -= 1) {
|
|
1126
|
+
if (this.queue[index]?.coalesceKey === coalesceKey) {
|
|
1127
|
+
existingIndex = index;
|
|
1128
|
+
break;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
if (existingIndex !== -1) {
|
|
1132
|
+
const [obsolete] = this.queue.splice(existingIndex, 1);
|
|
1133
|
+
if (obsolete !== void 0) {
|
|
1134
|
+
this.queueBytes -= obsolete.bytes;
|
|
1135
|
+
this.coalescedFrames += 1;
|
|
1136
|
+
this.coalescedBytes += obsolete.bytes;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
while ((this.queue.length >= this.maxQueuedMessages || this.queueBytes + bytes > this.maxQueuedBytes) && this.queue.length > 0) {
|
|
1141
|
+
const dropped = this.queue.shift();
|
|
1142
|
+
if (dropped !== void 0) {
|
|
1143
|
+
this.queueBytes -= dropped.bytes;
|
|
1144
|
+
this.droppedFrames += 1;
|
|
1145
|
+
this.droppedBytes += dropped.bytes;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
this.queue.push({ serialized, bytes, coalesceKey });
|
|
1149
|
+
this.queueBytes += bytes;
|
|
1150
|
+
}
|
|
1151
|
+
/** Batch-dequeue up to 50 frames at a time, yielding to the microtask
|
|
1152
|
+
* queue between batches so we don't starve the event loop on reconnect. */
|
|
1153
|
+
flushQueue() {
|
|
1154
|
+
const socket = this.socket;
|
|
1155
|
+
if (socket?.readyState !== OPEN_STATE || this.queue.length === 0) return;
|
|
1156
|
+
const batch = this.queue.splice(0, 50);
|
|
1157
|
+
for (const frame of batch) {
|
|
1158
|
+
socket.send(frame.serialized);
|
|
1159
|
+
this.queueBytes -= frame.bytes;
|
|
1160
|
+
}
|
|
1161
|
+
this.queueBytes = Math.max(0, this.queueBytes);
|
|
1162
|
+
if (this.queue.length > 0) setImmediate(() => this.flushQueue());
|
|
1163
|
+
}
|
|
1164
|
+
startCommandPolling() {
|
|
1165
|
+
if (this.options.onCommand === void 0 || this.commandPollTimer !== null) return;
|
|
1166
|
+
this.commandPollTimer = setInterval(
|
|
1167
|
+
() => this.pollCommands(),
|
|
1168
|
+
this.options.commandPollIntervalMs ?? DEFAULT_COMMAND_POLL_INTERVAL_MS
|
|
1169
|
+
);
|
|
1170
|
+
this.commandPollTimer.unref?.();
|
|
1171
|
+
}
|
|
1172
|
+
stopCommandPolling() {
|
|
1173
|
+
if (this.commandPollTimer === null) return;
|
|
1174
|
+
clearInterval(this.commandPollTimer);
|
|
1175
|
+
this.commandPollTimer = null;
|
|
1176
|
+
}
|
|
1177
|
+
startHeartbeat() {
|
|
1178
|
+
if (this.heartbeatTimer !== null) return;
|
|
1179
|
+
this.heartbeatTimer = setInterval(
|
|
1180
|
+
() => this.publishHeartbeat(),
|
|
1181
|
+
this.options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS
|
|
1182
|
+
);
|
|
1183
|
+
this.heartbeatTimer.unref?.();
|
|
1184
|
+
}
|
|
1185
|
+
stopHeartbeat() {
|
|
1186
|
+
if (this.heartbeatTimer === null) return;
|
|
1187
|
+
clearInterval(this.heartbeatTimer);
|
|
1188
|
+
this.heartbeatTimer = null;
|
|
1189
|
+
}
|
|
1190
|
+
publishHeartbeat() {
|
|
1191
|
+
const startedAt = Date.parse(this.options.client.startedAt);
|
|
1192
|
+
const now = Date.parse(this.now());
|
|
1193
|
+
this.publishEvent({
|
|
1194
|
+
type: "client.heartbeat",
|
|
1195
|
+
payload: {
|
|
1196
|
+
uptimeMs: Number.isFinite(startedAt) && Number.isFinite(now) ? Math.max(0, now - startedAt) : 0,
|
|
1197
|
+
status: "idle"
|
|
1198
|
+
}
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
async handleServerMessage(event) {
|
|
1202
|
+
const message = this.parseServerMessage(event);
|
|
1203
|
+
if (message?.type === "hq.command_batch") {
|
|
1204
|
+
await this.handleCommandBatch(message);
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
if (message?.type === "hq.kanban_snapshot") {
|
|
1208
|
+
await this.options.onKanbanSnapshot?.(message.payload);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
parseServerMessage(event) {
|
|
1212
|
+
const data = this.extractMessageData(event);
|
|
1213
|
+
if (data === null) return null;
|
|
1214
|
+
try {
|
|
1215
|
+
const parsed = JSON.parse(data);
|
|
1216
|
+
if (parsed.type === "hq.command_batch" && Array.isArray(parsed.commands)) {
|
|
1217
|
+
return parsed;
|
|
1218
|
+
}
|
|
1219
|
+
if (parsed.type === "hq.kanban_snapshot") {
|
|
1220
|
+
const payload = parsed.payload;
|
|
1221
|
+
if (payload !== void 0 && typeof payload === "object" && typeof payload.projectId === "string" && payload.projectId === this.options.project.projectId && Array.isArray(payload.boards) && Array.isArray(payload.tombstones)) {
|
|
1222
|
+
return parsed;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
return null;
|
|
1226
|
+
} catch {
|
|
1227
|
+
return null;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
extractMessageData(event) {
|
|
1231
|
+
const value = typeof event === "object" && event !== null && "data" in event ? event.data : event;
|
|
1232
|
+
if (typeof value === "string") return value;
|
|
1233
|
+
if (value instanceof ArrayBuffer) return new TextDecoder().decode(value);
|
|
1234
|
+
if (ArrayBuffer.isView(value)) {
|
|
1235
|
+
const bytes = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
1236
|
+
return new TextDecoder().decode(bytes);
|
|
1237
|
+
}
|
|
1238
|
+
return null;
|
|
1239
|
+
}
|
|
1240
|
+
async handleCommandBatch(message) {
|
|
1241
|
+
const handler = this.options.onCommand;
|
|
1242
|
+
if (handler === void 0) return;
|
|
1243
|
+
for (const command of message.commands) {
|
|
1244
|
+
try {
|
|
1245
|
+
const result = await handler(command);
|
|
1246
|
+
if (result !== void 0) this.ackCommand(result);
|
|
1247
|
+
else if (command.requiresAck)
|
|
1248
|
+
this.ackCommand({ commandId: command.commandId, status: "accepted" });
|
|
1249
|
+
} catch (err) {
|
|
1250
|
+
this.ackCommand({
|
|
1251
|
+
commandId: command.commandId,
|
|
1252
|
+
status: "failed",
|
|
1253
|
+
message: err instanceof Error ? err.message : String(err)
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
this.lastCommandId = command.commandId;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
scheduleReconnect() {
|
|
1260
|
+
if (this.stopped || !this.reconnect || this.reconnectTimer !== null) return;
|
|
1261
|
+
const delay = Math.min(this.reconnectMaxMs, this.reconnectBaseMs * 2 ** this.reconnectAttempt);
|
|
1262
|
+
this.reconnectAttempt += 1;
|
|
1263
|
+
if (!this.connectWarningEmitted && this.reconnectAttempt >= this.connectWarnAfterFailures) {
|
|
1264
|
+
if (this.emitConnectWarning()) this.connectWarningEmitted = true;
|
|
1265
|
+
}
|
|
1266
|
+
this.reconnectTimer = setTimeout(() => {
|
|
1267
|
+
this.reconnectTimer = null;
|
|
1268
|
+
this.connect();
|
|
1269
|
+
}, delay);
|
|
1270
|
+
this.reconnectTimer.unref?.();
|
|
1271
|
+
}
|
|
1272
|
+
emitConnectWarning() {
|
|
1273
|
+
const nowMs = Date.now();
|
|
1274
|
+
if (nowMs - lastConnectWarningAtMs < this.connectWarnCooldownMs) {
|
|
1275
|
+
return false;
|
|
1276
|
+
}
|
|
1277
|
+
lastConnectWarningAtMs = nowMs;
|
|
1278
|
+
const attempt = this.lastAttempt;
|
|
1279
|
+
const message = `WrongStack HQ publisher: ${this.reconnectAttempt} consecutive connection failures${attempt !== null ? ` to ${attempt.url} (client token ${attempt.hadToken ? "present" : "absent"})` : ""}. Either the HQ server is unreachable or it rejected the token (401). If HQ runs in client-token mode, verify WRONGSTACK_HQ_TOKEN / auth.json. Retries continue with backoff.`;
|
|
1280
|
+
if (this.logger) {
|
|
1281
|
+
this.logger.warn(message, { event: "hq.publisher.connect_failed" });
|
|
1282
|
+
return true;
|
|
1283
|
+
}
|
|
1284
|
+
const warn = this.options.warn ?? ((msg) => console.warn(
|
|
1285
|
+
JSON.stringify({
|
|
1286
|
+
level: "warn",
|
|
1287
|
+
event: "hq.publisher.connect_failed",
|
|
1288
|
+
message: msg,
|
|
1289
|
+
timestamp: this.now()
|
|
1290
|
+
})
|
|
1291
|
+
));
|
|
1292
|
+
try {
|
|
1293
|
+
warn(message);
|
|
1294
|
+
} catch {
|
|
1295
|
+
}
|
|
1296
|
+
return true;
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Dormant re-check while no HQ endpoint is discoverable. Uses a FIXED
|
|
1300
|
+
* interval (not the exponential reconnect backoff): the check is a cheap
|
|
1301
|
+
* local file read, and backing off would delay attaching to an HQ the
|
|
1302
|
+
* user just started — the whole point of auto-discovery.
|
|
1303
|
+
*/
|
|
1304
|
+
scheduleDiscoveryPoll() {
|
|
1305
|
+
if (this.stopped || !this.reconnect || this.reconnectTimer !== null) return;
|
|
1306
|
+
this.reconnectAttempt = 0;
|
|
1307
|
+
this.reconnectTimer = setTimeout(() => {
|
|
1308
|
+
this.reconnectTimer = null;
|
|
1309
|
+
this.connect();
|
|
1310
|
+
}, this.options.discoveryPollMs ?? DEFAULT_DISCOVERY_POLL_MS);
|
|
1311
|
+
this.reconnectTimer.unref?.();
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1314
|
+
|
|
1315
|
+
// src/hq/factory.ts
|
|
1316
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
|
|
1317
|
+
import * as fs2 from "node:fs";
|
|
1318
|
+
import { hostname } from "node:os";
|
|
1319
|
+
import { basename as basename2 } from "node:path";
|
|
1320
|
+
function discoverLocalHqEndpoint(options = {}) {
|
|
1321
|
+
const dataDir = resolveHqDataDir(options.dataDir, options.env ?? process.env);
|
|
1322
|
+
const runtime = readHqRuntimeFileSync(dataDir);
|
|
1323
|
+
if (runtime === void 0) return void 0;
|
|
1324
|
+
if (!isLoopbackHqUrl(runtime.url)) return void 0;
|
|
1325
|
+
const token = readFirstClientTokenFromAuthFile(dataDir);
|
|
1326
|
+
return { url: runtime.url, ...token ? { token } : {} };
|
|
1327
|
+
}
|
|
1328
|
+
function readFirstClientTokenFromAuthFile(dataDir) {
|
|
1329
|
+
try {
|
|
1330
|
+
const raw = fs2.readFileSync(hqAuthFilePath(dataDir), "utf8");
|
|
1331
|
+
const parsed = JSON.parse(raw);
|
|
1332
|
+
return parsed.clientTokens?.find((t) => t.token.trim().length > 0)?.token;
|
|
1333
|
+
} catch {
|
|
1334
|
+
return void 0;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
function isLoopbackHqUrl(url) {
|
|
1338
|
+
try {
|
|
1339
|
+
const host = new URL(url).hostname.toLowerCase();
|
|
1340
|
+
return host === "127.0.0.1" || host === "localhost" || host === "::1" || host === "[::1]" || host === "0.0.0.0";
|
|
1341
|
+
} catch {
|
|
1342
|
+
return false;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
function resolveHqConfigFromEnv(env = process.env) {
|
|
1346
|
+
return resolveHqConfig({ env });
|
|
1347
|
+
}
|
|
1348
|
+
function resolveHqConfig(options = {}) {
|
|
1349
|
+
const env = options.env ?? process.env;
|
|
1350
|
+
const fileConfig = options.config;
|
|
1351
|
+
const envUrl = env["WRONGSTACK_HQ_URL"]?.trim();
|
|
1352
|
+
const envToken = env["WRONGSTACK_HQ_TOKEN"]?.trim();
|
|
1353
|
+
const configUrl = fileConfig?.url?.trim();
|
|
1354
|
+
const configToken = fileConfig?.token?.trim();
|
|
1355
|
+
const envEnabledRaw = env["WRONGSTACK_HQ_ENABLED"]?.trim();
|
|
1356
|
+
const enabled = envEnabledRaw !== void 0 && envEnabledRaw.length > 0 ? envEnabledRaw !== "0" : fileConfig?.enabled;
|
|
1357
|
+
const dataDir = resolveHqDataDir(fileConfig?.dataDir, env);
|
|
1358
|
+
const explicitToken = envToken || configToken;
|
|
1359
|
+
const runtimeUrl = readHqRuntimeFileSync(dataDir)?.url.trim();
|
|
1360
|
+
const url = envUrl || configUrl;
|
|
1361
|
+
const localFallbackToken = !url || isLoopbackHqUrl(url) ? readFirstClientTokenFromAuthFile(dataDir) : void 0;
|
|
1362
|
+
const token = explicitToken || localFallbackToken;
|
|
1363
|
+
const rawContentEnv = env["WRONGSTACK_HQ_RAW_CONTENT"]?.trim();
|
|
1364
|
+
const explicitRawContent = rawContentEnv ? rawContentEnv === "1" : fileConfig?.rawContent;
|
|
1365
|
+
const projectAliasEnv = env["WRONGSTACK_HQ_PROJECT_ALIAS"]?.trim();
|
|
1366
|
+
const projectAlias = projectAliasEnv || fileConfig?.projectAlias;
|
|
1367
|
+
if (!url) {
|
|
1368
|
+
if (enabled === false) return void 0;
|
|
1369
|
+
return {
|
|
1370
|
+
url: runtimeUrl || "http://127.0.0.1:3499",
|
|
1371
|
+
enabled: true,
|
|
1372
|
+
discover: true,
|
|
1373
|
+
dataDir,
|
|
1374
|
+
rawContent: explicitRawContent ?? true,
|
|
1375
|
+
...token ? { token } : {},
|
|
1376
|
+
...projectAlias ? { projectAlias } : {}
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
const rawContent = explicitRawContent ?? true;
|
|
1380
|
+
return {
|
|
1381
|
+
url,
|
|
1382
|
+
...token ? { token } : {},
|
|
1383
|
+
...enabled !== void 0 ? { enabled } : {},
|
|
1384
|
+
...rawContent !== void 0 ? { rawContent } : {},
|
|
1385
|
+
...projectAlias ? { projectAlias } : {}
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
function stableMachineId() {
|
|
1389
|
+
return createHash2("sha256").update(hostname()).digest("hex").slice(0, 12);
|
|
1390
|
+
}
|
|
1391
|
+
function deriveHqProjectId(projectRoot, projectAlias) {
|
|
1392
|
+
const projectIdentity = readProjectIdentitySync(projectRoot);
|
|
1393
|
+
if (projectIdentity) return projectIdentity.projectId;
|
|
1394
|
+
const alias = projectAlias?.trim();
|
|
1395
|
+
if (!alias && projectRoot.startsWith("alias:")) {
|
|
1396
|
+
throw new Error('projectRoot must not use the reserved "alias:" HQ identity prefix');
|
|
1397
|
+
}
|
|
1398
|
+
const identity = alias ? `alias:${alias}` : projectRoot;
|
|
1399
|
+
return createHash2("sha256").update(identity).digest("hex").slice(0, 12);
|
|
1400
|
+
}
|
|
1401
|
+
function createHqPublisherFromEnv(options) {
|
|
1402
|
+
const config = options.config ?? resolveHqConfig({ config: options.appConfig?.hq });
|
|
1403
|
+
if (!config || config.enabled === false) return void 0;
|
|
1404
|
+
const machineId = options.machineId ?? stableMachineId();
|
|
1405
|
+
const host = options.hostnameOverride ?? hostname();
|
|
1406
|
+
const projectAlias = config.projectAlias?.trim() || void 0;
|
|
1407
|
+
const projectName = projectAlias ?? options.projectName ?? (basename2(options.projectRoot) || "unknown");
|
|
1408
|
+
const client = {
|
|
1409
|
+
clientId: `${machineId}:${options.clientKind}:${process.pid}:${randomUUID3().slice(0, 8)}`,
|
|
1410
|
+
kind: options.clientKind,
|
|
1411
|
+
machineId,
|
|
1412
|
+
...host ? { hostname: host } : {},
|
|
1413
|
+
pid: process.pid,
|
|
1414
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1415
|
+
};
|
|
1416
|
+
const project = {
|
|
1417
|
+
projectId: deriveHqProjectId(options.projectRoot, projectAlias),
|
|
1418
|
+
projectRoot: options.projectRoot,
|
|
1419
|
+
projectName,
|
|
1420
|
+
machineId,
|
|
1421
|
+
workspaceKind: "git"
|
|
1422
|
+
};
|
|
1423
|
+
const redactionPolicy = options.redactionPolicy || config.rawContent !== void 0 ? {
|
|
1424
|
+
...config.rawContent !== void 0 ? { rawContent: config.rawContent } : {},
|
|
1425
|
+
...options.redactionPolicy ?? {}
|
|
1426
|
+
} : void 0;
|
|
1427
|
+
const discoveryDataDir = config.dataDir;
|
|
1428
|
+
return new HqPublisher({
|
|
1429
|
+
url: config.url,
|
|
1430
|
+
...config.token ? { token: config.token } : {},
|
|
1431
|
+
client,
|
|
1432
|
+
project,
|
|
1433
|
+
...options.socketFactory ? { socketFactory: options.socketFactory } : {},
|
|
1434
|
+
...redactionPolicy !== void 0 ? { redactionPolicy } : {},
|
|
1435
|
+
...options.capabilities !== void 0 ? { capabilities: options.capabilities } : {},
|
|
1436
|
+
...options.maxQueuedMessages !== void 0 ? { maxQueuedMessages: options.maxQueuedMessages } : {},
|
|
1437
|
+
...options.onCommand !== void 0 ? { onCommand: options.onCommand } : {},
|
|
1438
|
+
...options.onKanbanSnapshot !== void 0 ? { onKanbanSnapshot: options.onKanbanSnapshot } : {},
|
|
1439
|
+
// Auto-discovery: re-read the local HQ runtime marker + client token on
|
|
1440
|
+
// every connect attempt so late-started/restarted HQs are picked up.
|
|
1441
|
+
...config.discover ? { resolveEndpoint: () => discoverLocalHqEndpoint({ dataDir: discoveryDataDir }) } : {},
|
|
1442
|
+
...options.discoveryPollMs !== void 0 ? { discoveryPollMs: options.discoveryPollMs } : {},
|
|
1443
|
+
...options.logger !== void 0 ? { logger: options.logger } : {}
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
export {
|
|
1448
|
+
hqAuthAuditPath,
|
|
1449
|
+
readHqAuthAuditTail,
|
|
1450
|
+
appendHqAuthAudit,
|
|
1451
|
+
logHqAuthAudit,
|
|
1452
|
+
HQ_AUTH_FILE_VERSION,
|
|
1453
|
+
defaultHqDataDir,
|
|
1454
|
+
resolveHqDataDir,
|
|
1455
|
+
hqTokenVerifier,
|
|
1456
|
+
hqTokenKey,
|
|
1457
|
+
isTokenExpired,
|
|
1458
|
+
tokenHasCapability,
|
|
1459
|
+
emptyHqAuthFile,
|
|
1460
|
+
HQ_AUTH_CONTENT_HASH_REDACTED,
|
|
1461
|
+
hqAuthContentHash,
|
|
1462
|
+
hqAuthFilePath,
|
|
1463
|
+
hqRuntimeFilePath,
|
|
1464
|
+
writeHqRuntimeFile,
|
|
1465
|
+
readHqRuntimeFileSync,
|
|
1466
|
+
readHqAuthFile,
|
|
1467
|
+
writeHqAuthFile,
|
|
1468
|
+
ensureHqFirstRunAuthFile,
|
|
1469
|
+
mutateHqAuthFile,
|
|
1470
|
+
hashHqPassword,
|
|
1471
|
+
verifyHqPassword,
|
|
1472
|
+
mintHqCookieSecret,
|
|
1473
|
+
mintHqToken,
|
|
1474
|
+
mintHqBrowserToken,
|
|
1475
|
+
watchHqAuthFile,
|
|
1476
|
+
resolveHqRedactionPolicy,
|
|
1477
|
+
tightenHqRedactionPolicy,
|
|
1478
|
+
scrubAndTruncateHqPreview,
|
|
1479
|
+
redactHqValue,
|
|
1480
|
+
redactHqEventPayload,
|
|
1481
|
+
redactHqEvent,
|
|
1482
|
+
summarizeHqToolArgs,
|
|
1483
|
+
classifyMailboxRecipient,
|
|
1484
|
+
mapMailboxMessageToHqSummary,
|
|
1485
|
+
mapMailboxAgentToHqSummary,
|
|
1486
|
+
createMailboxSnapshotPayload,
|
|
1487
|
+
createMailboxSnapshotPayloadFromMailbox,
|
|
1488
|
+
createMailboxEventPayload,
|
|
1489
|
+
HqPublisher,
|
|
1490
|
+
discoverLocalHqEndpoint,
|
|
1491
|
+
resolveHqConfigFromEnv,
|
|
1492
|
+
resolveHqConfig,
|
|
1493
|
+
deriveHqProjectId,
|
|
1494
|
+
createHqPublisherFromEnv
|
|
1495
|
+
};
|
|
1496
|
+
//# sourceMappingURL=chunk-ZYU2YOJ6.js.map
|