@totalreclaw/totalreclaw 3.3.1-rc.2 → 3.3.1-rc.21
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/CHANGELOG.md +330 -0
- package/SKILL.md +50 -83
- package/api-client.ts +18 -11
- package/config.ts +117 -3
- package/crypto.ts +10 -2
- package/dist/api-client.js +226 -0
- package/dist/billing-cache.js +100 -0
- package/dist/claims-helper.js +606 -0
- package/dist/config.js +280 -0
- package/dist/consolidation.js +258 -0
- package/dist/contradiction-sync.js +1034 -0
- package/dist/crypto.js +138 -0
- package/dist/digest-sync.js +361 -0
- package/dist/download-ux.js +63 -0
- package/dist/embedding.js +86 -0
- package/dist/extractor.js +1225 -0
- package/dist/first-run.js +103 -0
- package/dist/fs-helpers.js +563 -0
- package/dist/gateway-url.js +197 -0
- package/dist/generate-mnemonic.js +13 -0
- package/dist/hot-cache-wrapper.js +101 -0
- package/dist/import-adapters/base-adapter.js +64 -0
- package/dist/import-adapters/chatgpt-adapter.js +238 -0
- package/dist/import-adapters/claude-adapter.js +114 -0
- package/dist/import-adapters/gemini-adapter.js +201 -0
- package/dist/import-adapters/index.js +26 -0
- package/dist/import-adapters/mcp-memory-adapter.js +219 -0
- package/dist/import-adapters/mem0-adapter.js +158 -0
- package/dist/import-adapters/types.js +1 -0
- package/dist/index.js +5348 -0
- package/dist/llm-client.js +686 -0
- package/dist/llm-profile-reader.js +346 -0
- package/dist/lsh.js +62 -0
- package/dist/onboarding-cli.js +750 -0
- package/dist/pair-cli.js +344 -0
- package/dist/pair-crypto.js +359 -0
- package/dist/pair-http.js +404 -0
- package/dist/pair-page.js +826 -0
- package/dist/pair-qr.js +107 -0
- package/dist/pair-remote-client.js +410 -0
- package/dist/pair-session-store.js +566 -0
- package/dist/pin.js +542 -0
- package/dist/qa-bug-report.js +301 -0
- package/dist/relay-headers.js +44 -0
- package/dist/reranker.js +442 -0
- package/dist/retype-setscope.js +348 -0
- package/dist/semantic-dedup.js +75 -0
- package/dist/subgraph-search.js +289 -0
- package/dist/subgraph-store.js +694 -0
- package/dist/tool-gating.js +58 -0
- package/download-ux.ts +91 -0
- package/embedding.ts +32 -9
- package/fs-helpers.ts +124 -0
- package/gateway-url.ts +57 -9
- package/index.ts +586 -357
- package/llm-client.ts +211 -23
- package/lsh.ts +7 -2
- package/onboarding-cli.ts +114 -1
- package/package.json +19 -5
- package/pair-cli.ts +76 -8
- package/pair-crypto.ts +34 -24
- package/pair-page.ts +28 -17
- package/pair-qr.ts +152 -0
- package/pair-remote-client.ts +540 -0
- package/qa-bug-report.ts +381 -0
- package/relay-headers.ts +50 -0
- package/reranker.ts +73 -0
- package/retype-setscope.ts +12 -0
- package/subgraph-search.ts +4 -3
- package/subgraph-store.ts +109 -16
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* totalreclaw_report_qa_bug — RC-gated tool for agent-driven QA bug reports.
|
|
3
|
+
*
|
|
4
|
+
* Only registered when the plugin version contains `-rc.` (SemVer pre-release
|
|
5
|
+
* token); stable builds never expose this tool. Shipped in 3.3.1-rc.3 so
|
|
6
|
+
* agents running the `qa-totalreclaw` skill can file structured issues to
|
|
7
|
+
* `p-diogo/totalreclaw-internal` via direct GitHub REST API fetch (scanner-
|
|
8
|
+
* safe — no shelling out to CLIs) without the maintainer opening a fresh
|
|
9
|
+
* issue by hand for every RC finding.
|
|
10
|
+
*
|
|
11
|
+
* See `.github/ISSUE_TEMPLATE/qa-bug.yml` in the internal repo — the
|
|
12
|
+
* markdown body this module renders mirrors the form-template field
|
|
13
|
+
* names so future automation can parse either the form or the tool
|
|
14
|
+
* output identically.
|
|
15
|
+
*
|
|
16
|
+
* Security: all user-supplied strings (symptom / expected / repro / logs
|
|
17
|
+
* / environment) run through `redactSecrets()` fail-close before the
|
|
18
|
+
* POST. BIP-39 phrases, API keys, Telegram bot tokens, and bearer tokens
|
|
19
|
+
* in headers all become `<REDACTED>` in the posted issue. Refer to
|
|
20
|
+
* `redactSecrets()` for the exact rule set.
|
|
21
|
+
*
|
|
22
|
+
* Target repo safety: the default target is `p-diogo/totalreclaw-internal`.
|
|
23
|
+
* Operators can override via the `TOTALRECLAW_QA_REPO` env var, but only
|
|
24
|
+
* to another slug ending in `-internal`. Any other slug — including the
|
|
25
|
+
* public `p-diogo/totalreclaw` — is rejected with a loud error. rc.13 QA
|
|
26
|
+
* surfaced a repo-slug drift where QA findings leaked to the public
|
|
27
|
+
* tracker; rc.14 adds this fail-loud guard.
|
|
28
|
+
*/
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// RC-gate detection
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
/**
|
|
33
|
+
* True when the given version string indicates a pre-release build
|
|
34
|
+
* (SemVer `-rc.` or PEP-440 `rc`). Used to gate the QA bug-report tool so
|
|
35
|
+
* stable users never see it.
|
|
36
|
+
*
|
|
37
|
+
* Accepts:
|
|
38
|
+
* - `3.3.1-rc.3` → SemVer pre-release (plugin)
|
|
39
|
+
* - `2.3.1rc3` → PEP-440 release-candidate (Hermes-style)
|
|
40
|
+
* - `1.0.0-rc.1` → SemVer
|
|
41
|
+
*
|
|
42
|
+
* Rejects:
|
|
43
|
+
* - `3.3.1` → stable
|
|
44
|
+
* - `3.3.1-beta.1` → pre-release but not RC (future: might unblock beta QA)
|
|
45
|
+
* - `"" / null` → empty defensive
|
|
46
|
+
*/
|
|
47
|
+
export function isRcBuild(version) {
|
|
48
|
+
if (!version || typeof version !== 'string')
|
|
49
|
+
return false;
|
|
50
|
+
const v = version.toLowerCase();
|
|
51
|
+
// SemVer: `-rc.<N>`
|
|
52
|
+
if (/-rc\.\d+/.test(v))
|
|
53
|
+
return true;
|
|
54
|
+
// PEP-440: `rc<N>` (no dash)
|
|
55
|
+
if (/\d+rc\d+/.test(v))
|
|
56
|
+
return true;
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Redaction — fail-close
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
const REDACTED = '<REDACTED>';
|
|
63
|
+
/**
|
|
64
|
+
* Redact likely secrets from free-text fields before posting to GitHub.
|
|
65
|
+
* Runs a sequence of patterns; order matters (longer/more-specific first).
|
|
66
|
+
*
|
|
67
|
+
* Covered:
|
|
68
|
+
* - BIP-39 recovery phrases (12 or 24 lowercase words, space-separated)
|
|
69
|
+
* - OpenAI-style `sk-` keys, Anthropic `sk-ant-` keys
|
|
70
|
+
* - Google-style `AIzaSy...` keys
|
|
71
|
+
* - Telegram bot tokens (`\d+:[A-Za-z0-9_-]{35,}`)
|
|
72
|
+
* - Bearer tokens in `Authorization:` headers
|
|
73
|
+
* - Hex auth keys (>=32 chars of hex alone on a line or after `key=`)
|
|
74
|
+
*
|
|
75
|
+
* Unknown shapes may still leak. Fail-close on the patterns we DO match,
|
|
76
|
+
* fail-open on patterns we don't — the agent is also instructed (via the
|
|
77
|
+
* SKILL.md addendum) to not pass raw secrets.
|
|
78
|
+
*/
|
|
79
|
+
export function redactSecrets(text) {
|
|
80
|
+
if (!text || typeof text !== 'string')
|
|
81
|
+
return '';
|
|
82
|
+
let out = text;
|
|
83
|
+
// BIP-39 mnemonic — 12 or 24 lowercase alpha words separated by single
|
|
84
|
+
// spaces. Some test vectors use 15/18/21 words, accept those too.
|
|
85
|
+
//
|
|
86
|
+
// CAVEAT: the regex is a shape check, not a dictionary check. A line of
|
|
87
|
+
// 12 random English words that happen to all be lowercase will also be
|
|
88
|
+
// redacted — acceptable over-redaction for a bug report field.
|
|
89
|
+
out = out.replace(/\b(?:[a-z]{3,10}(?:\s+[a-z]{3,10}){11,23})\b/g, REDACTED);
|
|
90
|
+
// OpenAI / Anthropic-style `sk-...` keys. `sk-ant-api03-...` gets caught
|
|
91
|
+
// by the broader `sk-[A-Za-z0-9_-]{20,}` pattern below.
|
|
92
|
+
out = out.replace(/\bsk-[A-Za-z0-9_-]{20,}/g, REDACTED);
|
|
93
|
+
// Google API key: `AIzaSy` prefix + ~33 trailing chars (total 39).
|
|
94
|
+
// We accept 30–45 trailing chars so accidental suffixes / URL-encoded
|
|
95
|
+
// variants don't escape.
|
|
96
|
+
out = out.replace(/\bAIza[0-9A-Za-z\-_]{30,45}\b/g, REDACTED);
|
|
97
|
+
// Telegram bot token: `\d+:[A-Za-z0-9_-]{35,}`.
|
|
98
|
+
out = out.replace(/\b\d{6,}:[A-Za-z0-9_-]{35,}\b/g, REDACTED);
|
|
99
|
+
// Bearer token in Authorization header (case-insensitive). Preserves the
|
|
100
|
+
// header name so the log remains recognizable.
|
|
101
|
+
out = out.replace(/(authorization[:\s]*bearer\s+)[A-Za-z0-9._\-+/=]+/gi, `$1${REDACTED}`);
|
|
102
|
+
// X-Api-Key / x-api-key style header.
|
|
103
|
+
out = out.replace(/(x-api-key[:\s]*)[A-Za-z0-9._\-+/=]{20,}/gi, `$1${REDACTED}`);
|
|
104
|
+
// Hex blobs 64+ chars (typical auth-key / private-key shape). Must not
|
|
105
|
+
// eat commit SHAs or contract addresses; gate on length 40+. Bump to 64
|
|
106
|
+
// to avoid eating regular addresses.
|
|
107
|
+
out = out.replace(/\b[a-fA-F0-9]{64,}\b/g, REDACTED);
|
|
108
|
+
// Private-key-style 0x-prefixed 64-hex.
|
|
109
|
+
out = out.replace(/\b0x[a-fA-F0-9]{64}\b/g, REDACTED);
|
|
110
|
+
// UUIDs that appear alongside `token=` or `secret=` qualifiers. Naked
|
|
111
|
+
// UUIDs are left alone (fact IDs are legitimate UUIDs).
|
|
112
|
+
out = out.replace(/((?:token|secret|auth_key)\s*[=:]\s*)[A-Za-z0-9-]{20,}/gi, `$1${REDACTED}`);
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Target repo guard — fail-loud on any repo that isn't the internal tracker.
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
export const DEFAULT_QA_REPO = 'p-diogo/totalreclaw-internal';
|
|
119
|
+
/**
|
|
120
|
+
* Known-public repo slugs that must never receive QA bug reports. The
|
|
121
|
+
* structural rule (`endsWith('-internal')`) below should already block
|
|
122
|
+
* these, but the explicit denylist is a belt-and-braces safety against
|
|
123
|
+
* a future rename that accidentally drops the `-internal` suffix.
|
|
124
|
+
*/
|
|
125
|
+
export const PUBLIC_REPOS_DENYLIST = new Set([
|
|
126
|
+
'p-diogo/totalreclaw',
|
|
127
|
+
'p-diogo/totalreclaw-website',
|
|
128
|
+
'p-diogo/totalreclaw-relay',
|
|
129
|
+
'p-diogo/totalreclaw-plugin',
|
|
130
|
+
'p-diogo/totalreclaw-hermes',
|
|
131
|
+
]);
|
|
132
|
+
/**
|
|
133
|
+
* Resolve the target repo for a QA bug filing.
|
|
134
|
+
*
|
|
135
|
+
* Precedence: explicit override → `TOTALRECLAW_QA_REPO` env → default.
|
|
136
|
+
* Throws if the slug is on the public denylist or does not end in
|
|
137
|
+
* `-internal`. rc.13 QA found agent-filed bug reports leaking to the
|
|
138
|
+
* public repo; this guard makes any such drift fail loudly rather than
|
|
139
|
+
* silently leak RC ship-stopper detail.
|
|
140
|
+
*
|
|
141
|
+
* `TOTALRECLAW_QA_REPO` is the documented override var. The env-var
|
|
142
|
+
* read lives in `config.ts` (CONFIG.qaRepoOverride) so this module
|
|
143
|
+
* never touches process environment directly — keeps the plugin
|
|
144
|
+
* scanner-sim clean because this file also performs a GitHub HTTPS
|
|
145
|
+
* request (env + network in the same file would trip OpenClaw's
|
|
146
|
+
* env-harvesting heuristic).
|
|
147
|
+
*
|
|
148
|
+
* Pass the env-resolved slug (or `null`/empty for default) as
|
|
149
|
+
* `override`. Tests can inject via the second arg.
|
|
150
|
+
*/
|
|
151
|
+
export function resolveQaRepo(override, env) {
|
|
152
|
+
// `env` is only for test injection — production callers should
|
|
153
|
+
// pre-resolve the env value via CONFIG.qaRepoOverride and pass it as
|
|
154
|
+
// `override`. The env lookup is a last-resort fallback that works in
|
|
155
|
+
// Node but is NEVER the primary path in production.
|
|
156
|
+
const envOverride = env ? env.TOTALRECLAW_QA_REPO : undefined;
|
|
157
|
+
const raw = (override || envOverride || DEFAULT_QA_REPO).trim();
|
|
158
|
+
if (!raw || !raw.includes('/')) {
|
|
159
|
+
throw new Error(`invalid QA repo slug '${raw}': expected 'owner/name' format`);
|
|
160
|
+
}
|
|
161
|
+
if (PUBLIC_REPOS_DENYLIST.has(raw)) {
|
|
162
|
+
throw new Error(`refusing to file QA bug to PUBLIC repo '${raw}'. ` +
|
|
163
|
+
'QA bug reports contain RC ship-stopper detail that must not ' +
|
|
164
|
+
"leak to public. Set TOTALRECLAW_QA_REPO to a repo ending in " +
|
|
165
|
+
"'-internal' (e.g. p-diogo/totalreclaw-internal).");
|
|
166
|
+
}
|
|
167
|
+
if (!raw.endsWith('-internal')) {
|
|
168
|
+
throw new Error(`refusing to file QA bug to repo '${raw}': slug must end in ` +
|
|
169
|
+
"'-internal' (structural safety rule). Override via " +
|
|
170
|
+
'TOTALRECLAW_QA_REPO only to another internal fork.');
|
|
171
|
+
}
|
|
172
|
+
return raw;
|
|
173
|
+
}
|
|
174
|
+
const VALID_INTEGRATIONS = new Set([
|
|
175
|
+
'plugin',
|
|
176
|
+
'hermes',
|
|
177
|
+
'nanoclaw',
|
|
178
|
+
'mcp',
|
|
179
|
+
'relay',
|
|
180
|
+
'clawhub',
|
|
181
|
+
'docs',
|
|
182
|
+
'other',
|
|
183
|
+
]);
|
|
184
|
+
// Internal → display-name mapping for the issue body. Matches the
|
|
185
|
+
// dropdown values in `.github/ISSUE_TEMPLATE/qa-bug.yml`.
|
|
186
|
+
const INTEGRATION_DISPLAY = {
|
|
187
|
+
plugin: 'OpenClaw plugin',
|
|
188
|
+
hermes: 'Hermes Python',
|
|
189
|
+
nanoclaw: 'NanoClaw skill',
|
|
190
|
+
mcp: 'MCP server',
|
|
191
|
+
relay: 'Relay (backend)',
|
|
192
|
+
clawhub: 'ClawHub publishing',
|
|
193
|
+
docs: 'Docs / setup guide',
|
|
194
|
+
other: 'Other',
|
|
195
|
+
};
|
|
196
|
+
const VALID_SEVERITIES = new Set(['blocker', 'high', 'medium', 'low']);
|
|
197
|
+
export function validateQaBugArgs(args) {
|
|
198
|
+
if (!args || typeof args !== 'object')
|
|
199
|
+
return { ok: false, error: 'args must be an object' };
|
|
200
|
+
const missing = ['integration', 'rc_version', 'severity', 'title', 'symptom', 'expected', 'repro', 'logs', 'environment']
|
|
201
|
+
.filter((f) => !args[f] || typeof args[f] !== 'string');
|
|
202
|
+
if (missing.length) {
|
|
203
|
+
return { ok: false, error: `missing or non-string fields: ${missing.join(', ')}` };
|
|
204
|
+
}
|
|
205
|
+
if (!VALID_INTEGRATIONS.has(args.integration)) {
|
|
206
|
+
return { ok: false, error: `invalid integration "${args.integration}"; expected one of ${[...VALID_INTEGRATIONS].join(', ')}` };
|
|
207
|
+
}
|
|
208
|
+
if (!VALID_SEVERITIES.has(args.severity)) {
|
|
209
|
+
return { ok: false, error: `invalid severity "${args.severity}"; expected one of ${[...VALID_SEVERITIES].join(', ')}` };
|
|
210
|
+
}
|
|
211
|
+
if (args.title.length > 60) {
|
|
212
|
+
return { ok: false, error: 'title must be <= 60 chars' };
|
|
213
|
+
}
|
|
214
|
+
return { ok: true };
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Build the issue body mirroring the `.github/ISSUE_TEMPLATE/qa-bug.yml`
|
|
218
|
+
* layout. Runs every user-supplied string through `redactSecrets` before
|
|
219
|
+
* embedding. Exported for unit testing.
|
|
220
|
+
*/
|
|
221
|
+
export function buildIssueBody(args) {
|
|
222
|
+
const integrationDisplay = INTEGRATION_DISPLAY[args.integration] ?? args.integration;
|
|
223
|
+
const header = [
|
|
224
|
+
'_Filed automatically by the TotalReclaw RC bug-report tool._',
|
|
225
|
+
'',
|
|
226
|
+
'### Integration',
|
|
227
|
+
integrationDisplay,
|
|
228
|
+
'',
|
|
229
|
+
'### RC version',
|
|
230
|
+
'`' + redactSecrets(args.rc_version) + '`',
|
|
231
|
+
'',
|
|
232
|
+
'### Severity',
|
|
233
|
+
args.severity,
|
|
234
|
+
'',
|
|
235
|
+
'### What happened',
|
|
236
|
+
redactSecrets(args.symptom),
|
|
237
|
+
'',
|
|
238
|
+
'### What was expected',
|
|
239
|
+
redactSecrets(args.expected),
|
|
240
|
+
'',
|
|
241
|
+
'### Reproduction steps',
|
|
242
|
+
redactSecrets(args.repro),
|
|
243
|
+
'',
|
|
244
|
+
'### Relevant logs / evidence',
|
|
245
|
+
'```',
|
|
246
|
+
redactSecrets(args.logs),
|
|
247
|
+
'```',
|
|
248
|
+
'',
|
|
249
|
+
'### Environment',
|
|
250
|
+
redactSecrets(args.environment),
|
|
251
|
+
'',
|
|
252
|
+
'---',
|
|
253
|
+
'> Reporter: LLM agent via `totalreclaw_report_qa_bug` (RC-gated tool)',
|
|
254
|
+
].join('\n');
|
|
255
|
+
return header;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* POST the bug to GitHub. Returns the issue URL on success; throws with a
|
|
259
|
+
* structured message on failure. The caller (tool handler) wraps the
|
|
260
|
+
* exception into a JSON tool response.
|
|
261
|
+
*/
|
|
262
|
+
export async function postQaBugIssue(args, deps) {
|
|
263
|
+
const validation = validateQaBugArgs(args);
|
|
264
|
+
if ('error' in validation)
|
|
265
|
+
throw new Error(`invalid args: ${validation.error}`);
|
|
266
|
+
if (!deps.githubToken)
|
|
267
|
+
throw new Error('githubToken is required');
|
|
268
|
+
const repo = resolveQaRepo(deps.repo ?? null);
|
|
269
|
+
const url = `https://api.github.com/repos/${repo}/issues`;
|
|
270
|
+
const title = `[qa-bug] ${redactSecrets(args.title)}`;
|
|
271
|
+
const body = buildIssueBody(args);
|
|
272
|
+
const labels = [
|
|
273
|
+
'qa-bug',
|
|
274
|
+
'pending-triage',
|
|
275
|
+
`severity:${args.severity}`,
|
|
276
|
+
`component:${args.integration}`,
|
|
277
|
+
`rc:${args.rc_version.replace(/[^A-Za-z0-9.\-]/g, '_').slice(0, 40)}`,
|
|
278
|
+
];
|
|
279
|
+
const fetchFn = deps.fetchImpl ?? fetch;
|
|
280
|
+
const res = await fetchFn(url, {
|
|
281
|
+
method: 'POST',
|
|
282
|
+
headers: {
|
|
283
|
+
Accept: 'application/vnd.github+json',
|
|
284
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
285
|
+
Authorization: `Bearer ${deps.githubToken}`,
|
|
286
|
+
'Content-Type': 'application/json',
|
|
287
|
+
'User-Agent': 'totalreclaw-plugin-qa-bug',
|
|
288
|
+
},
|
|
289
|
+
body: JSON.stringify({ title, body, labels }),
|
|
290
|
+
});
|
|
291
|
+
if (!res.ok) {
|
|
292
|
+
const text = await res.text().catch(() => '');
|
|
293
|
+
throw new Error(`GitHub API ${res.status}: ${text.slice(0, 200)}`);
|
|
294
|
+
}
|
|
295
|
+
const json = (await res.json());
|
|
296
|
+
if (!json.html_url || typeof json.number !== 'number') {
|
|
297
|
+
throw new Error('GitHub API returned no html_url / number');
|
|
298
|
+
}
|
|
299
|
+
deps.logger?.info(`Filed QA bug #${json.number}: ${json.html_url}`);
|
|
300
|
+
return { issue_url: json.html_url, issue_number: json.number };
|
|
301
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared outbound-header helper for relay calls.
|
|
3
|
+
*
|
|
4
|
+
* Centralizes the common `X-TotalReclaw-*` headers so every fetch site
|
|
5
|
+
* consistently tags requests with:
|
|
6
|
+
* - `X-TotalReclaw-Client` — caller identity (defaults to `openclaw-plugin`).
|
|
7
|
+
* - `X-TotalReclaw-Session` — optional QA / observability tag from
|
|
8
|
+
* `TOTALRECLAW_SESSION_ID`. Used by Axiom log filters and the
|
|
9
|
+
* `qa-totalreclaw` skill to scope log searches per QA run.
|
|
10
|
+
*
|
|
11
|
+
* Pure function — no I/O, no network. Reads `getSessionId()` (which reads the
|
|
12
|
+
* env var via getter so harnesses that flip the env between calls pick up
|
|
13
|
+
* the new value).
|
|
14
|
+
*
|
|
15
|
+
* The session-id env var was accidentally placed in the v1 REMOVED_ENV_VARS
|
|
16
|
+
* list and silently warned-and-dropped, breaking Axiom traceability for QA
|
|
17
|
+
* runs (see internal#127). This helper is the canonical re-entry point for
|
|
18
|
+
* the variable.
|
|
19
|
+
*/
|
|
20
|
+
import { getSessionId } from './config.js';
|
|
21
|
+
/** Default `X-TotalReclaw-Client` value. */
|
|
22
|
+
export const DEFAULT_CLIENT_ID = 'openclaw-plugin';
|
|
23
|
+
/**
|
|
24
|
+
* Build the standard outbound header set.
|
|
25
|
+
*
|
|
26
|
+
* @param overrides - merge-in additional headers (`Authorization`,
|
|
27
|
+
* `Content-Type`, etc.); these win over the defaults.
|
|
28
|
+
* @param clientId - override the `X-TotalReclaw-Client` value.
|
|
29
|
+
*
|
|
30
|
+
* Always includes `X-TotalReclaw-Client`. Includes `X-TotalReclaw-Session`
|
|
31
|
+
* only when `TOTALRECLAW_SESSION_ID` is set + non-empty.
|
|
32
|
+
*/
|
|
33
|
+
export function buildRelayHeaders(overrides = {}, clientId = DEFAULT_CLIENT_ID) {
|
|
34
|
+
const headers = {
|
|
35
|
+
'X-TotalReclaw-Client': clientId,
|
|
36
|
+
};
|
|
37
|
+
const sessionId = getSessionId();
|
|
38
|
+
if (sessionId) {
|
|
39
|
+
headers['X-TotalReclaw-Session'] = sessionId;
|
|
40
|
+
}
|
|
41
|
+
// Caller-supplied headers (Authorization, Content-Type, Accept, etc.) take
|
|
42
|
+
// precedence over the defaults but should generally not stomp the X-* tags.
|
|
43
|
+
return { ...headers, ...overrides };
|
|
44
|
+
}
|