@scalequality/cli 0.4.9-rc.1 → 0.4.9-rc.3
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/connect.build.json +2 -2
- package/dist/connect.cjs +892 -870
- package/package.json +1 -1
package/dist/connect.cjs
CHANGED
|
@@ -33,123 +33,11 @@ var import_os4 = require("os");
|
|
|
33
33
|
var import_path16 = require("path");
|
|
34
34
|
var import_url = require("url");
|
|
35
35
|
|
|
36
|
-
// src/application/services/workspaceSandbox/
|
|
37
|
-
var
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
function parseReasoningCapability(raw) {
|
|
42
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
43
|
-
const r = raw;
|
|
44
|
-
if (!Array.isArray(r.levels)) return null;
|
|
45
|
-
const listed = [...r.levels, ...r.off === true ? ["off"] : []];
|
|
46
|
-
const levels = REASONING_LEVELS.filter((l) => listed.includes(l));
|
|
47
|
-
if (!levels.length) return null;
|
|
48
|
-
const def = isReasoningLevel(r.default) && levels.includes(r.default) ? r.default : levels[0];
|
|
49
|
-
return { levels, default: def };
|
|
50
|
-
}
|
|
51
|
-
function effectiveReasoning(requested, capability) {
|
|
52
|
-
if (!capability) return null;
|
|
53
|
-
if (requested && capability.levels.includes(requested)) return requested;
|
|
54
|
-
return capability.default;
|
|
55
|
-
}
|
|
56
|
-
function isMaxMode(level, capability) {
|
|
57
|
-
if (!level || level === "off" || !capability) return false;
|
|
58
|
-
return capability.levels[capability.levels.length - 1] === level;
|
|
59
|
-
}
|
|
60
|
-
function turnReasoning(level, capability) {
|
|
61
|
-
if (!capability || !level) return { options: {}, forceNoThinking: true, outputCeiling: false };
|
|
62
|
-
if (level === "off") return { options: { thinking: { type: "disabled" } }, forceNoThinking: true, outputCeiling: false };
|
|
63
|
-
return { options: { effort: level, thinking: { type: "adaptive" } }, forceNoThinking: false, outputCeiling: isMaxMode(level, capability) };
|
|
64
|
-
}
|
|
65
|
-
function engineModelCapabilities(aliases) {
|
|
66
|
-
const entries = ["-mid_conv_system"];
|
|
67
|
-
const seen = /* @__PURE__ */ new Set();
|
|
68
|
-
for (const { alias, reasoning } of aliases) {
|
|
69
|
-
const name = alias.trim();
|
|
70
|
-
if (!name || seen.has(name) || /[;=,]/.test(name)) continue;
|
|
71
|
-
seen.add(name);
|
|
72
|
-
entries.push(reasoning ? `${name}=effort,${reasoning.levels.includes("max") ? "" : "-"}max_effort,${reasoning.levels.includes("xhigh") ? "" : "-"}xhigh_effort` : `${name}=-effort,-max_effort,-xhigh_effort`);
|
|
73
|
-
}
|
|
74
|
-
return entries.join(";");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// src/application/services/workspaceSandbox/autoRouting.ts
|
|
78
|
-
var LONG_INSTRUCTION_CHARS = 3e3;
|
|
79
|
-
var LONG_CODE_LINES = 60;
|
|
80
|
-
var MANY_FILES = 5;
|
|
81
|
-
var HARD_WORK = [
|
|
82
|
-
[/\b(refactor|refator|refactoriz)\w*\b[\s\S]{0,80}\b(across|between|all|every|entre|todos|todas|varios|varias|modul|servic|packages?|pacotes?|layers?|camadas?)/, "refactor across modules"],
|
|
83
|
-
[/\b(architect|arquitet|arquitect|redesign|re-architect)\w*/, "architecture"],
|
|
84
|
-
[/\b(root cause|causa raiz|causa-raiz|debug|depur|investigat|investig|stack ?trace|flaky|race condition|condicao de corrida|memory leak|vazamento de memoria|deadlock|intermittent|intermitente)\w*/, "debugging, root cause"],
|
|
85
|
-
[/\b(migrat|migra(c|t)a?o|migrar|migre|upgrade\b[\s\S]{0,40}\b(from|to|de|para)\b|port (this|the|it)\b[\s\S]{0,40}\bto\b)/, "migration"],
|
|
86
|
-
[/\b(vulnerab|cve-\d|security (fix|issue|flaw|hole|bug)|injection|injecao|xss|csrf|ssrf|rce\b|seguranca|seguridad|exploit)\w*/, "security fix"],
|
|
87
|
-
[/\b(every|all|each|todos os|todas as|cada|todos los|todas las) (the )?(files?|modules?|services?|endpoints?|packages?|arquivos?|modulos?|servicos?|archivos?)\b|\b(across|throughout) (the )?(whole )?(codebase|repo|repository|project|modules|services)\b|\b(codebase|base de codigo)[- ](wide|inteira|toda)\b/, "many files"],
|
|
88
|
-
[/\b(refactor|refator|refactoriz)\w*/, "refactor"],
|
|
89
|
-
[/\b(step[- ]by[- ]step|passo a passo|paso a paso|multi[- ]step|end[- ]to[- ]end|threat model|performance (issue|regression|problem)|regressao de performance|rewrite|reescrev|reescrib)\w*/, "multi-step work"]
|
|
90
|
-
];
|
|
91
|
-
var normalize = (text3) => text3.toLowerCase().normalize("NFKD").replace(new RegExp("\\p{M}", "gu"), "");
|
|
92
|
-
function namedFiles(text3) {
|
|
93
|
-
const found = /* @__PURE__ */ new Set();
|
|
94
|
-
for (const m of text3.matchAll(/(?:^|[\s`'"(\[])((?:\.{0,2}\/)?[\w.-]+(?:\/[\w.-]+)+\/?|[\w-]+\.(?:ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|kt|rb|php|cs|cpp|c|h|swift|scala|sql|yml|yaml|json|tf|vue|svelte))(?=$|[\s`'"),.:;\]])/g)) {
|
|
95
|
-
found.add(m[1].replace(/^\.\//, ""));
|
|
96
|
-
}
|
|
97
|
-
return found.size;
|
|
98
|
-
}
|
|
99
|
-
function classifyCodeTurn(s) {
|
|
100
|
-
if (s.maxMode) return { hard: true, reason: "Max Mode" };
|
|
101
|
-
if (s.previousTrouble) return { hard: true, reason: s.previousTrouble };
|
|
102
|
-
const text3 = s.content;
|
|
103
|
-
const codeLines = [...text3.matchAll(/```[\s\S]*?```/g)].reduce((n, block) => n + block[0].split("\n").length, 0);
|
|
104
|
-
if (codeLines > LONG_CODE_LINES) return { hard: true, reason: `${codeLines} lines of code in the request` };
|
|
105
|
-
if (text3.length > LONG_INSTRUCTION_CHARS) return { hard: true, reason: `long instruction (${text3.length} characters)` };
|
|
106
|
-
const plain = normalize(text3);
|
|
107
|
-
for (const [re, reason] of HARD_WORK) if (re.test(plain)) return { hard: true, reason };
|
|
108
|
-
const files = namedFiles(text3);
|
|
109
|
-
if (files >= MANY_FILES) return { hard: true, reason: `${files} files named` };
|
|
110
|
-
return { hard: false, reason: text3.length > 600 || codeLines > 0 ? "bounded task" : "short, direct request" };
|
|
111
|
-
}
|
|
112
|
-
var STREAM_REASONS = ["ORG_OUTPUT_TEXT_RULES", "SEMANTIC_INSPECTOR", "APPLICATION_OUTPUT_TEXT_RULES", "DESTINATION_NO_STREAM"];
|
|
113
|
-
function routeAutoTurn(boot, signals) {
|
|
114
|
-
if (boot.model !== "sq-auto") return null;
|
|
115
|
-
const aliases = boot.runtime.aliases ?? [];
|
|
116
|
-
if (!aliases.some((a) => a.tier)) return null;
|
|
117
|
-
const primary = boot.runtime.primaryModel || aliases.find((a) => a.tier === "MEDIUM")?.alias || null;
|
|
118
|
-
if (!primary) return null;
|
|
119
|
-
const decision = classifyCodeTurn(signals);
|
|
120
|
-
const max = decision.hard ? aliases.find((a) => a.tier === "COMPLEX") ?? aliases.find((a) => a.alias === "sq-auto-max") : void 0;
|
|
121
|
-
const chosen = max ?? aliases.find((a) => a.alias === primary) ?? { alias: primary, reasoning: null, tier: "MEDIUM" };
|
|
122
|
-
const fallback = decision.hard && !max;
|
|
123
|
-
const kind = decision.hard ? "hard task" : "everyday task";
|
|
124
|
-
return {
|
|
125
|
-
alias: chosen.alias,
|
|
126
|
-
tier: chosen.tier ?? null,
|
|
127
|
-
hard: decision.hard,
|
|
128
|
-
reason: decision.reason,
|
|
129
|
-
label: `SQ Auto \xB7 ${kind} \xB7 ${decision.reason}${fallback ? " (no hard-task model available, main model used)" : ""}`,
|
|
130
|
-
capability: chosen.reasoning ?? null,
|
|
131
|
-
maxOutputTokens: typeof chosen.maxOutputTokens === "number" && chosen.maxOutputTokens > 0 ? chosen.maxOutputTokens : null,
|
|
132
|
-
fallback
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
function credentialAlias(raw) {
|
|
136
|
-
const tier = raw.tier === "LIGHT" || raw.tier === "MEDIUM" || raw.tier === "COMPLEX" ? raw.tier : null;
|
|
137
|
-
const positive = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? Math.trunc(v) : null;
|
|
138
|
-
const max = positive(raw.maxOutputTokens), organization = positive(raw.organizationMaxOutputTokens);
|
|
139
|
-
const source = raw.outputLimitSource === "ORGANIZATION" || raw.outputLimitSource === "MODEL" || raw.outputLimitSource === "PLATFORM" ? raw.outputLimitSource : null;
|
|
140
|
-
const s = raw.stream && typeof raw.stream === "object" ? raw.stream : null;
|
|
141
|
-
const mode = s && (s.mode === "LIVE" || s.mode === "BUFFERED" || s.mode === "BUFFERED_POLICY") ? s.mode : null;
|
|
142
|
-
const reason = s && STREAM_REASONS.includes(s.reason) ? s.reason : null;
|
|
143
|
-
return {
|
|
144
|
-
alias: String(raw.alias),
|
|
145
|
-
reasoning: parseReasoningCapability(raw.reasoning),
|
|
146
|
-
...tier ? { tier } : {},
|
|
147
|
-
...max ? { maxOutputTokens: max } : {},
|
|
148
|
-
...source ? { outputLimitSource: source } : {},
|
|
149
|
-
...organization ? { organizationMaxOutputTokens: organization } : {},
|
|
150
|
-
...mode ? { stream: { mode, ...reason ? { reason } : {} } } : {}
|
|
151
|
-
};
|
|
152
|
-
}
|
|
36
|
+
// src/application/services/workspaceSandbox/importers.ts
|
|
37
|
+
var import_fs = require("fs");
|
|
38
|
+
var import_promises = require("fs/promises");
|
|
39
|
+
var import_path = require("path");
|
|
40
|
+
var import_readline = require("readline");
|
|
153
41
|
|
|
154
42
|
// src/application/services/workspaceSandbox/machineShared.ts
|
|
155
43
|
var USER_CODE_ALPHABET = "ABCDEFGHJKMNPQRSTUVWXYZ23456789";
|
|
@@ -229,790 +117,902 @@ function localRepoId(rel) {
|
|
|
229
117
|
return `local:${rel}`;
|
|
230
118
|
}
|
|
231
119
|
|
|
232
|
-
// src/application/services/workspaceSandbox/
|
|
233
|
-
var
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
120
|
+
// src/application/services/workspaceSandbox/secretScrubber.ts
|
|
121
|
+
var R = (kind) => `[REDACTED:${kind}]`;
|
|
122
|
+
function looksLikeSecretValue(v) {
|
|
123
|
+
if (v.length < 8) return false;
|
|
124
|
+
if (/^\[REDACTED:/.test(v)) return false;
|
|
125
|
+
if (/^(true|false|null|undefined|none|nil|string|number|boolean|required|optional|redacted|changeme|example|placeholder|xxx+|\*+)$/i.test(v)) return false;
|
|
126
|
+
if (/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)+(\(\))?$/.test(v) || /^[A-Za-z_$][\w$]*\(\)?$/.test(v)) return false;
|
|
127
|
+
if (/^(\$\{?[A-Za-z_][\w]*\}?|\{\{.*\}\}|<[^>]*>|%[A-Za-z_]+%)$/.test(v)) return false;
|
|
128
|
+
if (/^[A-Za-z_]+$/.test(v) && v.length < 24) return false;
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
var RULES = [
|
|
132
|
+
{ kind: "PRIVATE_KEY", re: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----[\s\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----|$)/g },
|
|
133
|
+
{ kind: "CONNECTION_STRING", re: /\b([a-z][a-z0-9+.-]{1,30}:\/\/)([^\s:@/'"]{1,200}):([^\s@/'"]{1,300})@/gi, replace: (_m, scheme, user) => `${scheme}${user}:${R("PASSWORD")}@` },
|
|
134
|
+
{ kind: "AWS_ACCESS_KEY", re: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|ANPA|ANVA|AIPA|ABIA|ACCA)[A-Z0-9]{16}\b/g },
|
|
135
|
+
{ kind: "AWS_SECRET_KEY", re: /\b(aws_?secret_?access_?key|aws_?secret|secretAccessKey)(["']?\s*[:=]\s*["']?)([A-Za-z0-9/+=]{40})\b/gi, replace: (_m, k, sep11) => `${k}${sep11}${R("AWS_SECRET_KEY")}` },
|
|
136
|
+
{ kind: "ANTHROPIC_KEY", re: /\bsk-ant-[a-z]{2,10}\d{0,3}-[A-Za-z0-9_-]{20,}/g },
|
|
137
|
+
{ kind: "OPENAI_KEY", re: /\bsk-(?:proj-|svcacct-|admin-|None-)?[A-Za-z0-9_-]{20,}/g },
|
|
138
|
+
{ kind: "GITHUB_TOKEN", re: /\b(?:gh[pousr]_[A-Za-z0-9]{30,255}|github_pat_[A-Za-z0-9_]{22,255})\b/g },
|
|
139
|
+
{ kind: "GITLAB_TOKEN", re: /\bgl(?:pat|dt|rt|ptt|ft|cbt|imt|oas|soat|agent)-[A-Za-z0-9_-]{20,}/g },
|
|
140
|
+
{ kind: "SLACK_TOKEN", re: /\bxox[abposre]-[A-Za-z0-9-]{10,}/g },
|
|
141
|
+
{ kind: "SLACK_WEBHOOK", re: /https:\/\/hooks\.slack\.com\/(?:services|workflows|triggers)\/[A-Za-z0-9_/-]{20,}/g },
|
|
142
|
+
{ kind: "STRIPE_KEY", re: /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,}\b/g },
|
|
143
|
+
{ kind: "STRIPE_WEBHOOK_SECRET", re: /\bwhsec_[A-Za-z0-9]{24,}\b/g },
|
|
144
|
+
{ kind: "GOOGLE_API_KEY", re: /\bAIza[0-9A-Za-z_-]{35}\b/g },
|
|
145
|
+
{ kind: "JWT", re: /\beyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g },
|
|
146
|
+
{ kind: "BEARER_TOKEN", re: /\b(Bearer\s+)([A-Za-z0-9._~+/=-]{20,})/g, replace: (_m, p) => `${p}${R("BEARER_TOKEN")}` },
|
|
147
|
+
{
|
|
148
|
+
kind: "ASSIGNED_SECRET",
|
|
149
|
+
// password=..., "api_key": "...", SECRET_TOKEN: ..., --token ... (key names that say "secret").
|
|
150
|
+
re: /\b([A-Za-z0-9_.-]*(?:passw(?:or)?d|pwd|secret|token|api[_-]?key|apikey|access[_-]?key|private[_-]?key|client[_-]?secret|credential|auth[_-]?key)[A-Za-z0-9_-]*)(["']?\s*(?:=|:|\s)\s*)(["']?)([^\s"'`,;)}\]]{8,500})\3/gi,
|
|
151
|
+
replace: (_m, key, sep11, quote, value) => looksLikeSecretValue(value) ? `${key}${sep11}${quote}${R("SECRET")}${quote}` : null
|
|
238
152
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
153
|
+
];
|
|
154
|
+
function scrubSecrets(input) {
|
|
155
|
+
if (!input) return { text: input, count: 0 };
|
|
156
|
+
let text3 = input;
|
|
157
|
+
let count = 0;
|
|
158
|
+
for (const rule of RULES) {
|
|
159
|
+
text3 = text3.replace(rule.re, (...args) => {
|
|
160
|
+
const match = args[0];
|
|
161
|
+
const groups = args.slice(1, -2).map((g) => typeof g === "string" ? g : "");
|
|
162
|
+
if (!rule.replace) {
|
|
163
|
+
count++;
|
|
164
|
+
return R(rule.kind);
|
|
165
|
+
}
|
|
166
|
+
const out2 = rule.replace(match, ...groups);
|
|
167
|
+
if (out2 === null) return match;
|
|
168
|
+
count++;
|
|
169
|
+
return out2;
|
|
170
|
+
});
|
|
247
171
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
172
|
+
return { text: text3, count };
|
|
173
|
+
}
|
|
174
|
+
function countSecrets(input) {
|
|
175
|
+
return scrubSecrets(input).count;
|
|
176
|
+
}
|
|
253
177
|
|
|
254
|
-
// src/application/services/workspaceSandbox/
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const wait = Math.max(0, Math.min(25, Math.floor(waitSeconds)));
|
|
274
|
-
const body = await this.get(`/commands?wait=${wait}`, (wait + 15) * 1e3, signal);
|
|
275
|
-
return Array.isArray(body?.commands) ? body.commands : [];
|
|
276
|
-
}
|
|
277
|
-
async postEvents(events) {
|
|
278
|
-
if (events.length === 0) return;
|
|
279
|
-
await this.post("/events", { events }, this.opts.requestTimeoutMs ?? 3e4);
|
|
280
|
-
}
|
|
281
|
-
callTool(name, args, approvalId) {
|
|
282
|
-
if (!/^[a-z][a-z0-9_]{0,63}$/.test(name)) throw new TransportError("invalid tool name", null, false);
|
|
283
|
-
return this.post(`/tools/${name}`, approvalId ? { args, approvalId } : { args }, this.opts.toolTimeoutMs ?? 12e4);
|
|
178
|
+
// src/application/services/workspaceSandbox/importers.ts
|
|
179
|
+
function defaultImportSources(home, env = process.env) {
|
|
180
|
+
return { claudeDir: env.CLAUDE_CONFIG_DIR || (0, import_path.join)(home, ".claude"), codexDir: env.CODEX_HOME || (0, import_path.join)(home, ".codex") };
|
|
181
|
+
}
|
|
182
|
+
var MAX_LINE = 8 * 1024 * 1024;
|
|
183
|
+
var MAX_FILES = IMPORT_LIMITS.maxScanItems;
|
|
184
|
+
var READ_WINDOW_BYTES = 4 * IMPORT_LIMITS.maxBytes;
|
|
185
|
+
var WRAPPER = /^\s*<(command-[a-z-]+|local-command-[a-z-]+|bash-(?:input|stdout|stderr)|task-notification|system-reminder|user-prompt-submit-hook|environment_context|user_instructions|permissions instructions|recommended_plugins|app-context|skills_instructions|collaboration_mode|multi_agent_[a-z_]+|turn_aborted|user_shell_command)[^>]*>/i;
|
|
186
|
+
var oneLine = (s, max) => {
|
|
187
|
+
const t = s.replace(/\s+/g, " ").trim();
|
|
188
|
+
return t.length > max ? `${t.slice(0, max - 1)}\u2026` : t;
|
|
189
|
+
};
|
|
190
|
+
function summarizeToolInput(name, input) {
|
|
191
|
+
if (typeof input === "string") {
|
|
192
|
+
try {
|
|
193
|
+
return summarizeToolInput(name, JSON.parse(input));
|
|
194
|
+
} catch {
|
|
195
|
+
return oneLine(input, IMPORT_LIMITS.maxToolSummary);
|
|
196
|
+
}
|
|
284
197
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
198
|
+
if (!input || typeof input !== "object") return "";
|
|
199
|
+
const o = input;
|
|
200
|
+
for (const key of ["command", "cmd", "file_path", "path", "notebook_path", "pattern", "query", "url", "description", "prompt"]) {
|
|
201
|
+
const v = o[key];
|
|
202
|
+
if (typeof v === "string" && v.trim()) return oneLine(v, IMPORT_LIMITS.maxToolSummary);
|
|
203
|
+
if (Array.isArray(v) && v.every((x) => typeof x === "string")) return oneLine(v.join(" "), IMPORT_LIMITS.maxToolSummary);
|
|
288
204
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
token: text3(raw?.token),
|
|
306
|
-
provider: text3(raw?.provider),
|
|
307
|
-
repoFullName: text3(raw?.repoFullName, repoFullName),
|
|
308
|
-
defaultBranch,
|
|
309
|
-
branch: typeof raw?.branch === "string" ? raw.branch : defaultBranch
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
async importedHistory() {
|
|
313
|
-
const raw = await this.get("/imported", this.opts.requestTimeoutMs ?? 6e4);
|
|
314
|
-
const messages = Array.isArray(raw?.messages) ? raw.messages : [];
|
|
315
|
-
return {
|
|
316
|
-
messages: messages.filter((m) => !!m && typeof m === "object" && (m.role === "user" || m.role === "assistant")).map((m) => ({
|
|
317
|
-
role: m.role,
|
|
318
|
-
text: typeof m.text === "string" ? m.text : "",
|
|
319
|
-
at: typeof m.at === "string" ? m.at : null,
|
|
320
|
-
...Array.isArray(m.tools) ? { tools: m.tools.filter((t) => !!t && typeof t.name === "string" && typeof t.summary === "string") } : {}
|
|
321
|
-
}))
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
async saveTranscript(t) {
|
|
325
|
-
await this.post("/transcript", t, this.opts.requestTimeoutMs ?? 6e4);
|
|
205
|
+
const first = Object.values(o).find((v) => typeof v === "string" && v.trim());
|
|
206
|
+
return first ? oneLine(first, IMPORT_LIMITS.maxToolSummary) : "";
|
|
207
|
+
}
|
|
208
|
+
async function* lines(file) {
|
|
209
|
+
const rl = (0, import_readline.createInterface)({ input: (0, import_fs.createReadStream)(file, { encoding: "utf8" }), crlfDelay: Infinity });
|
|
210
|
+
try {
|
|
211
|
+
for await (const line of rl) {
|
|
212
|
+
if (!line || line.length > MAX_LINE || line[0] !== "{") continue;
|
|
213
|
+
try {
|
|
214
|
+
const rec = JSON.parse(line);
|
|
215
|
+
if (rec && typeof rec === "object") yield rec;
|
|
216
|
+
} catch {
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} finally {
|
|
220
|
+
rl.close();
|
|
326
221
|
}
|
|
327
|
-
|
|
328
|
-
|
|
222
|
+
}
|
|
223
|
+
var MessageWindow = class {
|
|
224
|
+
messages = [];
|
|
225
|
+
bytes = 0;
|
|
226
|
+
total = 0;
|
|
227
|
+
push(m) {
|
|
228
|
+
this.messages.push(m);
|
|
229
|
+
this.total++;
|
|
230
|
+
this.bytes += importBytes([m]);
|
|
231
|
+
while (this.bytes > READ_WINDOW_BYTES && this.messages.length > 1) this.bytes -= importBytes([this.messages.shift()]);
|
|
329
232
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
await this.post(`/replies/${commandId}`, reply, this.opts.requestTimeoutMs ?? 3e4);
|
|
233
|
+
get last() {
|
|
234
|
+
return this.messages[this.messages.length - 1];
|
|
333
235
|
}
|
|
334
|
-
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
236
|
+
grow(m, text3, tools) {
|
|
237
|
+
const before = importBytes([m]);
|
|
238
|
+
if (text3) m.text = m.text ? `${m.text}
|
|
239
|
+
|
|
240
|
+
${text3}` : text3;
|
|
241
|
+
if (tools.length) m.tools = [...m.tools ?? [], ...tools].slice(0, IMPORT_LIMITS.maxToolsPerMessage);
|
|
242
|
+
this.bytes += importBytes([m]) - before;
|
|
338
243
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
244
|
+
};
|
|
245
|
+
function claudeText(content, role) {
|
|
246
|
+
if (typeof content === "string") return { text: role === "user" && WRAPPER.test(content) ? "" : content, tools: [] };
|
|
247
|
+
if (!Array.isArray(content)) return { text: "", tools: [] };
|
|
248
|
+
const texts = [];
|
|
249
|
+
const tools = [];
|
|
250
|
+
for (const b of content) {
|
|
251
|
+
if (b?.type === "text" && typeof b.text === "string" && !(role === "user" && WRAPPER.test(b.text))) texts.push(b.text);
|
|
252
|
+
else if (b?.type === "tool_use" && typeof b.name === "string") tools.push({ name: b.name.slice(0, 200), summary: summarizeToolInput(b.name, b.input) });
|
|
253
|
+
else if (b?.type === "image") texts.push("[image]");
|
|
346
254
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
255
|
+
return { text: texts.join("\n\n").trim(), tools };
|
|
256
|
+
}
|
|
257
|
+
async function parseClaudeCodeFile(file, opts = {}) {
|
|
258
|
+
const externalId = (0, import_path.basename)(file, ".jsonl");
|
|
259
|
+
if (!isExternalId(externalId)) return null;
|
|
260
|
+
const win = new MessageWindow();
|
|
261
|
+
let folder = null;
|
|
262
|
+
let customTitle = null;
|
|
263
|
+
let aiTitle = null;
|
|
264
|
+
let summary = null;
|
|
265
|
+
let firstUser = null;
|
|
266
|
+
let current = null;
|
|
267
|
+
for await (const r of lines(file)) {
|
|
268
|
+
if (opts.probe && win.total) break;
|
|
269
|
+
if (!folder && typeof r.cwd === "string") folder = r.cwd;
|
|
270
|
+
if (r.type === "custom-title" && typeof r.customTitle === "string") customTitle = r.customTitle;
|
|
271
|
+
else if (r.type === "ai-title" && typeof r.aiTitle === "string") aiTitle = r.aiTitle;
|
|
272
|
+
else if (r.type === "summary" && typeof r.summary === "string") summary = r.summary;
|
|
273
|
+
if (r.type !== "user" && r.type !== "assistant" || r.isSidechain === true || r.isMeta === true) continue;
|
|
274
|
+
const role = r.type;
|
|
275
|
+
const { text: text3, tools } = claudeText(r.message?.content, role);
|
|
276
|
+
const at = typeof r.timestamp === "string" ? r.timestamp : null;
|
|
277
|
+
if (role === "assistant") {
|
|
278
|
+
const id = typeof r.message?.id === "string" ? r.message.id : null;
|
|
279
|
+
if (!current || !id || current.id !== id) current = { id, msg: null, at };
|
|
280
|
+
if (!text3 && !tools.length) continue;
|
|
281
|
+
if (current.msg && win.last === current.msg) win.grow(current.msg, text3, tools);
|
|
282
|
+
else {
|
|
283
|
+
current.msg = { role, text: text3, at: current.at ?? at, ...tools.length ? { tools } : {} };
|
|
284
|
+
win.push(current.msg);
|
|
360
285
|
}
|
|
286
|
+
continue;
|
|
361
287
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
288
|
+
const onlyToolResults = Array.isArray(r.message?.content) && r.message.content.every((b) => b?.type === "tool_result");
|
|
289
|
+
if (!onlyToolResults) current = null;
|
|
290
|
+
if (!text3) continue;
|
|
291
|
+
const shown = r.isCompactSummary === true ? `[Summary of the earlier conversation]
|
|
292
|
+
${text3}` : text3;
|
|
293
|
+
if (!firstUser && r.isCompactSummary !== true) firstUser = text3;
|
|
294
|
+
win.push({ role, text: shown, at });
|
|
366
295
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
throw new TransportError(`${method} ${routeLabel(path)} failed: ${e.name}`, null, true);
|
|
380
|
-
}
|
|
381
|
-
if (this.gone.has(res.status)) {
|
|
382
|
-
await res.body?.cancel().catch(() => void 0);
|
|
383
|
-
throw new SessionGoneError(res.status);
|
|
384
|
-
}
|
|
385
|
-
if (!res.ok) {
|
|
386
|
-
const code = await res.text().then((t) => JSON.parse(t)?.code, () => void 0).catch(() => void 0);
|
|
387
|
-
const retryable = res.status === 429 || res.status >= 500;
|
|
388
|
-
const error = new TransportError(`${method} ${routeLabel(path)} answered ${res.status}`, res.status, retryable);
|
|
389
|
-
if (typeof code === "string" && /^[A-Z_]{3,80}$/.test(code)) error.code = code;
|
|
390
|
-
throw error;
|
|
391
|
-
}
|
|
392
|
-
if (res.status === 204) return void 0;
|
|
393
|
-
const text3 = await res.text();
|
|
394
|
-
if (!text3) return void 0;
|
|
395
|
-
try {
|
|
396
|
-
return JSON.parse(text3);
|
|
397
|
-
} catch {
|
|
398
|
-
throw new TransportError(`${method} ${routeLabel(path)} returned invalid JSON`, res.status, false);
|
|
399
|
-
}
|
|
296
|
+
if (!win.total) return null;
|
|
297
|
+
const title = oneLine(customTitle || aiTitle || summary || firstUser || "Untitled conversation", IMPORT_LIMITS.maxTitle);
|
|
298
|
+
const st = await (0, import_promises.lstat)(file).catch(() => null);
|
|
299
|
+
return { source: "CLAUDE_CODE", externalId, title, folder, messages: win.messages, updatedAt: st ? st.mtime.toISOString() : null };
|
|
300
|
+
}
|
|
301
|
+
async function regularFiles(dir, match) {
|
|
302
|
+
const out2 = [];
|
|
303
|
+
const names = await (0, import_promises.readdir)(dir).catch(() => []);
|
|
304
|
+
for (const name of names) {
|
|
305
|
+
if (!match(name)) continue;
|
|
306
|
+
const st = await (0, import_promises.lstat)((0, import_path.join)(dir, name)).catch(() => null);
|
|
307
|
+
if (st?.isFile()) out2.push({ path: (0, import_path.join)(dir, name), mtime: st.mtimeMs });
|
|
400
308
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
if (
|
|
408
|
-
ac.abort(s.reason);
|
|
409
|
-
return ac.signal;
|
|
410
|
-
}
|
|
411
|
-
s.addEventListener("abort", () => ac.abort(s.reason), { once: true });
|
|
309
|
+
return out2;
|
|
310
|
+
}
|
|
311
|
+
async function subdirs(dir) {
|
|
312
|
+
const out2 = [];
|
|
313
|
+
for (const name of await (0, import_promises.readdir)(dir).catch(() => [])) {
|
|
314
|
+
const st = await (0, import_promises.lstat)((0, import_path.join)(dir, name)).catch(() => null);
|
|
315
|
+
if (st?.isDirectory()) out2.push((0, import_path.join)(dir, name));
|
|
412
316
|
}
|
|
413
|
-
return
|
|
317
|
+
return out2;
|
|
414
318
|
}
|
|
415
|
-
function
|
|
416
|
-
|
|
319
|
+
async function claudeCodeFiles(claudeDir) {
|
|
320
|
+
const files = [];
|
|
321
|
+
for (const project of await subdirs((0, import_path.join)(claudeDir, "projects"))) files.push(...await regularFiles(project, (n) => n.endsWith(".jsonl")));
|
|
322
|
+
return files;
|
|
417
323
|
}
|
|
418
|
-
function
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
resolve8();
|
|
424
|
-
}, { once: true });
|
|
425
|
-
});
|
|
324
|
+
function codexText(content, role) {
|
|
325
|
+
if (typeof content === "string") return content;
|
|
326
|
+
if (!Array.isArray(content)) return "";
|
|
327
|
+
const want = role === "user" ? "input_text" : "output_text";
|
|
328
|
+
return content.filter((b) => b?.type === want && typeof b.text === "string" && !(role === "user" && WRAPPER.test(b.text))).map((b) => b.text).join("\n\n").trim();
|
|
426
329
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
imported: importedInfo(session.imported),
|
|
471
|
-
checkpointPatch: typeof raw?.checkpointPatch === "string" ? raw.checkpointPatch : null,
|
|
472
|
-
sdkSessionId: typeof session.sdkSessionId === "string" ? session.sdkSessionId : null,
|
|
473
|
-
workspaceKind: session.workspaceKind === "LOCAL" ? "LOCAL" : "CLOUD",
|
|
474
|
-
projectName: typeof session.projectName === "string" ? session.projectName : typeof raw?.project?.name === "string" ? raw.project.name : null,
|
|
475
|
-
folderLink: typeof session.folderLink?.projectId === "string" && session.folderLink.projectId ? { projectId: session.folderLink.projectId } : null,
|
|
476
|
-
transcript: transcriptOf(raw?.transcript),
|
|
477
|
-
baseMeasurements: raw?.baseMeasurements && typeof raw.baseMeasurements === "object" && !Array.isArray(raw.baseMeasurements) ? raw.baseMeasurements : {},
|
|
478
|
-
pullRequests: pullRequestsOf(raw?.pullRequests),
|
|
479
|
-
skills: skillsOf(raw?.skills),
|
|
480
|
-
plugins: pluginsOf(raw?.plugins),
|
|
481
|
-
environments: environmentsOf(raw?.environments)
|
|
330
|
+
async function parseCodexFile(file, opts = {}) {
|
|
331
|
+
const fromName = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i.exec(file)?.[1] ?? null;
|
|
332
|
+
let externalId = null;
|
|
333
|
+
let folder = null;
|
|
334
|
+
let firstUser = null;
|
|
335
|
+
const win = new MessageWindow();
|
|
336
|
+
for await (const r of lines(file)) {
|
|
337
|
+
if (opts.probe && win.total && externalId) break;
|
|
338
|
+
const p = r.payload;
|
|
339
|
+
if (!p || typeof p !== "object") continue;
|
|
340
|
+
if (r.type === "session_meta") {
|
|
341
|
+
if (p.source && typeof p.source === "object" && p.source.subagent) return null;
|
|
342
|
+
if (!externalId && typeof p.id === "string") externalId = p.id;
|
|
343
|
+
if (!folder && typeof p.cwd === "string") folder = p.cwd;
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (r.type !== "response_item") continue;
|
|
347
|
+
const at = typeof r.timestamp === "string" ? r.timestamp : null;
|
|
348
|
+
if (p.type === "message" && (p.role === "user" || p.role === "assistant")) {
|
|
349
|
+
const text3 = codexText(p.content, p.role);
|
|
350
|
+
if (!text3) continue;
|
|
351
|
+
if (p.role === "user" && !firstUser) firstUser = text3;
|
|
352
|
+
const last = win.last;
|
|
353
|
+
if (p.role === "assistant" && last?.role === "assistant" && !last.text) win.grow(last, text3, []);
|
|
354
|
+
else win.push({ role: p.role, text: text3, at });
|
|
355
|
+
} else if ((p.type === "function_call" || p.type === "custom_tool_call" || p.type === "local_shell_call") && (typeof p.name === "string" || p.type === "local_shell_call")) {
|
|
356
|
+
const name = typeof p.name === "string" ? p.name : "shell";
|
|
357
|
+
const tool = { name: name.slice(0, 200), summary: summarizeToolInput(name, p.arguments ?? p.input ?? p.action) };
|
|
358
|
+
const last = win.last;
|
|
359
|
+
if (last?.role === "assistant") win.grow(last, "", [tool]);
|
|
360
|
+
else win.push({ role: "assistant", text: "", at, tools: [tool] });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const id = externalId ?? fromName;
|
|
364
|
+
if (!id || !isExternalId(id) || !win.total) return null;
|
|
365
|
+
const st = await (0, import_promises.lstat)(file).catch(() => null);
|
|
366
|
+
return {
|
|
367
|
+
source: "CODEX",
|
|
368
|
+
externalId: id,
|
|
369
|
+
title: oneLine(firstUser || "Untitled conversation", IMPORT_LIMITS.maxTitle),
|
|
370
|
+
folder,
|
|
371
|
+
messages: win.messages,
|
|
372
|
+
updatedAt: st ? st.mtime.toISOString() : null
|
|
482
373
|
};
|
|
483
374
|
}
|
|
484
|
-
function
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
375
|
+
async function codexFiles(codexDir) {
|
|
376
|
+
const files = [];
|
|
377
|
+
const walk = async (dir, depth) => {
|
|
378
|
+
files.push(...await regularFiles(dir, (n) => n.startsWith("rollout-") && n.endsWith(".jsonl")));
|
|
379
|
+
if (depth < 4) for (const sub of await subdirs(dir)) await walk(sub, depth + 1);
|
|
380
|
+
};
|
|
381
|
+
await walk((0, import_path.join)(codexDir, "sessions"), 0);
|
|
382
|
+
return files;
|
|
488
383
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
384
|
+
function secretsIn(c) {
|
|
385
|
+
let n = 0;
|
|
386
|
+
for (const m of c.messages) {
|
|
387
|
+
n += countSecrets(m.text);
|
|
388
|
+
for (const t of m.tools ?? []) n += countSecrets(t.summary);
|
|
389
|
+
}
|
|
390
|
+
return n;
|
|
493
391
|
}
|
|
494
|
-
function
|
|
495
|
-
|
|
496
|
-
|
|
392
|
+
async function importFiles(sources) {
|
|
393
|
+
return [
|
|
394
|
+
...(await claudeCodeFiles(sources.claudeDir)).map((f) => ({ ...f, source: "CLAUDE_CODE" })),
|
|
395
|
+
...(await codexFiles(sources.codexDir)).map((f) => ({ ...f, source: "CODEX" }))
|
|
396
|
+
].sort((a, b) => b.mtime - a.mtime).slice(0, MAX_FILES);
|
|
497
397
|
}
|
|
498
|
-
function
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
398
|
+
async function scanImports(sources) {
|
|
399
|
+
const files = await importFiles(sources);
|
|
400
|
+
const items = [];
|
|
401
|
+
const seen = /* @__PURE__ */ new Set();
|
|
402
|
+
for (const f of files) {
|
|
403
|
+
const c = await (f.source === "CLAUDE_CODE" ? parseClaudeCodeFile(f.path) : parseCodexFile(f.path)).catch(() => null);
|
|
404
|
+
if (!c || seen.has(`${c.source}:${c.externalId}`)) continue;
|
|
405
|
+
seen.add(`${c.source}:${c.externalId}`);
|
|
406
|
+
items.push({
|
|
407
|
+
source: c.source,
|
|
408
|
+
externalId: c.externalId,
|
|
409
|
+
title: oneLine(scrubSecrets(c.title).text, IMPORT_LIMITS.maxTitle),
|
|
410
|
+
folder: c.folder,
|
|
411
|
+
messageCount: Math.min(c.messages.length, IMPORT_LIMITS.maxMessages),
|
|
412
|
+
updatedAt: c.updatedAt,
|
|
413
|
+
secretsFound: secretsIn(c)
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
return items;
|
|
506
417
|
}
|
|
507
|
-
function
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
for (const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
418
|
+
async function countImports(sources) {
|
|
419
|
+
const counts = { claudeCode: 0, codex: 0 };
|
|
420
|
+
const seen = /* @__PURE__ */ new Set();
|
|
421
|
+
for (const f of await importFiles(sources)) {
|
|
422
|
+
const c = await (f.source === "CLAUDE_CODE" ? parseClaudeCodeFile(f.path, { probe: true }) : parseCodexFile(f.path, { probe: true })).catch(() => null);
|
|
423
|
+
if (!c || seen.has(`${c.source}:${c.externalId}`)) continue;
|
|
424
|
+
seen.add(`${c.source}:${c.externalId}`);
|
|
425
|
+
if (c.source === "CLAUDE_CODE") counts.claudeCode++;
|
|
426
|
+
else counts.codex++;
|
|
427
|
+
}
|
|
428
|
+
return counts;
|
|
429
|
+
}
|
|
430
|
+
async function conversationFolders(sources, deadline) {
|
|
431
|
+
const claude = [];
|
|
432
|
+
for (const project of await subdirs((0, import_path.join)(sources.claudeDir, "projects"))) {
|
|
433
|
+
if (Date.now() > deadline) break;
|
|
434
|
+
const newest = (await regularFiles(project, (n) => n.endsWith(".jsonl"))).sort((a, b) => b.mtime - a.mtime)[0];
|
|
435
|
+
if (newest) claude.push({ ...newest, source: "CLAUDE_CODE" });
|
|
436
|
+
}
|
|
437
|
+
const codex = Date.now() > deadline ? [] : (await codexFiles(sources.codexDir)).map((f) => ({ ...f, source: "CODEX" }));
|
|
438
|
+
const files = [...claude, ...codex].sort((a, b) => b.mtime - a.mtime).slice(0, MAX_FILES);
|
|
439
|
+
const out2 = [];
|
|
440
|
+
const seen = /* @__PURE__ */ new Set();
|
|
441
|
+
for (const f of files) {
|
|
442
|
+
if (Date.now() > deadline) break;
|
|
443
|
+
let n = 0;
|
|
444
|
+
try {
|
|
445
|
+
for await (const r of lines(f.path)) {
|
|
446
|
+
if (++n > 50) break;
|
|
447
|
+
const cwd = f.source === "CLAUDE_CODE" ? r.cwd : r.type === "session_meta" && r.payload && typeof r.payload === "object" && !(r.payload.source && typeof r.payload.source === "object" && r.payload.source.subagent) ? r.payload.cwd : void 0;
|
|
448
|
+
if (typeof cwd === "string" && cwd) {
|
|
449
|
+
if (!seen.has(cwd)) {
|
|
450
|
+
seen.add(cwd);
|
|
451
|
+
out2.push(cwd);
|
|
452
|
+
}
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
} catch {
|
|
457
|
+
}
|
|
518
458
|
}
|
|
519
459
|
return out2;
|
|
520
460
|
}
|
|
521
|
-
function
|
|
522
|
-
if (!
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
461
|
+
async function findConversation(sources, source, externalId) {
|
|
462
|
+
if (!isExternalId(externalId)) return null;
|
|
463
|
+
if (source === "CLAUDE_CODE") {
|
|
464
|
+
const file = (await claudeCodeFiles(sources.claudeDir)).find((f) => (0, import_path.basename)(f.path, ".jsonl") === externalId);
|
|
465
|
+
return file ? parseClaudeCodeFile(file.path) : null;
|
|
466
|
+
}
|
|
467
|
+
const files = (await codexFiles(sources.codexDir)).filter((f) => f.path.includes(externalId)).sort((a, b) => b.mtime - a.mtime);
|
|
468
|
+
for (const f of files) {
|
|
469
|
+
const c = await parseCodexFile(f.path).catch(() => null);
|
|
470
|
+
if (c?.externalId === externalId) return c;
|
|
471
|
+
}
|
|
472
|
+
return null;
|
|
533
473
|
}
|
|
534
|
-
function
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
474
|
+
function prepareImport(c) {
|
|
475
|
+
let removed = 0;
|
|
476
|
+
const scrub = (s) => {
|
|
477
|
+
const r = scrubSecrets(s);
|
|
478
|
+
removed += r.count;
|
|
479
|
+
return r.text;
|
|
480
|
+
};
|
|
481
|
+
const cap = (s, max) => Buffer.byteLength(s) > max ? `${Buffer.from(s).subarray(0, max - 32).toString("utf8").replace(/�+$/, "")}
|
|
482
|
+
[... cut ...]` : s;
|
|
483
|
+
const messages = c.messages.map((m) => ({
|
|
484
|
+
role: m.role,
|
|
485
|
+
text: cap(scrub(m.text), IMPORT_LIMITS.maxMessageBytes),
|
|
486
|
+
at: m.at,
|
|
487
|
+
...m.tools?.length ? { tools: m.tools.slice(0, IMPORT_LIMITS.maxToolsPerMessage).map((t) => ({ name: t.name, summary: oneLine(scrub(t.summary), IMPORT_LIMITS.maxToolSummary) })) } : {}
|
|
541
488
|
}));
|
|
489
|
+
const title = oneLine(scrubSecrets(c.title).text, IMPORT_LIMITS.maxTitle) || "Untitled conversation";
|
|
490
|
+
let start = Math.max(0, messages.length - IMPORT_LIMITS.maxMessages);
|
|
491
|
+
let bytes = importBytes(messages.slice(start));
|
|
492
|
+
while (bytes > IMPORT_LIMITS.maxBytes && start < messages.length - 1) bytes -= importBytes([messages[start++]]);
|
|
493
|
+
return { source: c.source, externalId: c.externalId, folder: c.folder, title, secretsRemoved: removed, messages: messages.slice(start), omitted: start };
|
|
542
494
|
}
|
|
543
|
-
function
|
|
544
|
-
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
495
|
+
function scrubRecord(record) {
|
|
496
|
+
let removed = 0;
|
|
497
|
+
const walk = (value, key) => {
|
|
498
|
+
if (typeof value === "string") {
|
|
499
|
+
if (key === "signature" || key === "data") return value;
|
|
500
|
+
const r = scrubSecrets(value);
|
|
501
|
+
removed += r.count;
|
|
502
|
+
return r.text;
|
|
503
|
+
}
|
|
504
|
+
if (Array.isArray(value)) {
|
|
505
|
+
const out2 = [];
|
|
506
|
+
for (const item of value) {
|
|
507
|
+
const block = item;
|
|
508
|
+
if (block && typeof block === "object" && block.type === "thinking" && typeof block.thinking === "string") {
|
|
509
|
+
const r = scrubSecrets(block.thinking);
|
|
510
|
+
if (r.count) {
|
|
511
|
+
removed += r.count;
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
out2.push(item);
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (block && typeof block === "object" && block.type === "redacted_thinking") {
|
|
518
|
+
out2.push(item);
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
out2.push(walk(item));
|
|
522
|
+
}
|
|
523
|
+
return out2;
|
|
524
|
+
}
|
|
525
|
+
if (value && typeof value === "object") {
|
|
526
|
+
const o = value;
|
|
527
|
+
for (const k of Object.keys(o)) o[k] = walk(o[k], k);
|
|
528
|
+
return o;
|
|
529
|
+
}
|
|
530
|
+
return value;
|
|
531
|
+
};
|
|
532
|
+
walk(record);
|
|
533
|
+
return removed;
|
|
534
|
+
}
|
|
535
|
+
async function writeScrubbedTranscript(source, target) {
|
|
536
|
+
const out2 = (0, import_fs.createWriteStream)(target, { mode: 384, flags: "wx" });
|
|
537
|
+
let removed = 0;
|
|
538
|
+
const done = new Promise((resolve8, reject) => {
|
|
539
|
+
out2.on("finish", resolve8);
|
|
540
|
+
out2.on("error", reject);
|
|
541
|
+
});
|
|
542
|
+
const rl = (0, import_readline.createInterface)({ input: (0, import_fs.createReadStream)(source, { encoding: "utf8" }), crlfDelay: Infinity });
|
|
543
|
+
try {
|
|
544
|
+
for await (const line of rl) {
|
|
545
|
+
if (!line) continue;
|
|
546
|
+
let record;
|
|
547
|
+
try {
|
|
548
|
+
record = JSON.parse(line);
|
|
549
|
+
} catch {
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
removed += scrubRecord(record);
|
|
553
|
+
if (!out2.write(`${JSON.stringify(record)}
|
|
554
|
+
`)) await new Promise((r) => out2.once("drain", () => r()));
|
|
555
|
+
}
|
|
556
|
+
} finally {
|
|
557
|
+
rl.close();
|
|
558
|
+
out2.end();
|
|
559
|
+
}
|
|
560
|
+
await done;
|
|
561
|
+
return removed;
|
|
558
562
|
}
|
|
559
563
|
|
|
560
|
-
// src/application/services/workspaceSandbox/
|
|
561
|
-
var
|
|
562
|
-
var
|
|
563
|
-
var
|
|
564
|
-
var
|
|
564
|
+
// src/application/services/workspaceSandbox/backgroundService.ts
|
|
565
|
+
var import_child_process2 = require("child_process");
|
|
566
|
+
var import_crypto3 = require("crypto");
|
|
567
|
+
var import_fs5 = require("fs");
|
|
568
|
+
var import_path6 = require("path");
|
|
565
569
|
|
|
566
|
-
// src/application/services/workspaceSandbox/
|
|
567
|
-
var
|
|
568
|
-
function
|
|
569
|
-
|
|
570
|
-
if (/^\[REDACTED:/.test(v)) return false;
|
|
571
|
-
if (/^(true|false|null|undefined|none|nil|string|number|boolean|required|optional|redacted|changeme|example|placeholder|xxx+|\*+)$/i.test(v)) return false;
|
|
572
|
-
if (/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)+(\(\))?$/.test(v) || /^[A-Za-z_$][\w$]*\(\)?$/.test(v)) return false;
|
|
573
|
-
if (/^(\$\{?[A-Za-z_][\w]*\}?|\{\{.*\}\}|<[^>]*>|%[A-Za-z_]+%)$/.test(v)) return false;
|
|
574
|
-
if (/^[A-Za-z_]+$/.test(v) && v.length < 24) return false;
|
|
575
|
-
return true;
|
|
570
|
+
// src/application/services/workspaceSandbox/reasoning.ts
|
|
571
|
+
var REASONING_LEVELS = ["off", "low", "medium", "high", "xhigh", "max"];
|
|
572
|
+
function isReasoningLevel(value) {
|
|
573
|
+
return typeof value === "string" && REASONING_LEVELS.includes(value);
|
|
576
574
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
{
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
575
|
+
function parseReasoningCapability(raw) {
|
|
576
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
577
|
+
const r = raw;
|
|
578
|
+
if (!Array.isArray(r.levels)) return null;
|
|
579
|
+
const listed = [...r.levels, ...r.off === true ? ["off"] : []];
|
|
580
|
+
const levels = REASONING_LEVELS.filter((l) => listed.includes(l));
|
|
581
|
+
if (!levels.length) return null;
|
|
582
|
+
const def = isReasoningLevel(r.default) && levels.includes(r.default) ? r.default : levels[0];
|
|
583
|
+
return { levels, default: def };
|
|
584
|
+
}
|
|
585
|
+
function effectiveReasoning(requested, capability) {
|
|
586
|
+
if (!capability) return null;
|
|
587
|
+
if (requested && capability.levels.includes(requested)) return requested;
|
|
588
|
+
return capability.default;
|
|
589
|
+
}
|
|
590
|
+
function isMaxMode(level, capability) {
|
|
591
|
+
if (!level || level === "off" || !capability) return false;
|
|
592
|
+
return capability.levels[capability.levels.length - 1] === level;
|
|
593
|
+
}
|
|
594
|
+
function turnReasoning(level, capability) {
|
|
595
|
+
if (!capability || !level) return { options: {}, forceNoThinking: true, outputCeiling: false };
|
|
596
|
+
if (level === "off") return { options: { thinking: { type: "disabled" } }, forceNoThinking: true, outputCeiling: false };
|
|
597
|
+
return { options: { effort: level, thinking: { type: "adaptive" } }, forceNoThinking: false, outputCeiling: isMaxMode(level, capability) };
|
|
598
|
+
}
|
|
599
|
+
function engineModelCapabilities(aliases) {
|
|
600
|
+
const entries = ["-mid_conv_system"];
|
|
601
|
+
const seen = /* @__PURE__ */ new Set();
|
|
602
|
+
for (const { alias, reasoning } of aliases) {
|
|
603
|
+
const name = alias.trim();
|
|
604
|
+
if (!name || seen.has(name) || /[;=,]/.test(name)) continue;
|
|
605
|
+
seen.add(name);
|
|
606
|
+
entries.push(reasoning ? `${name}=effort,${reasoning.levels.includes("max") ? "" : "-"}max_effort,${reasoning.levels.includes("xhigh") ? "" : "-"}xhigh_effort` : `${name}=-effort,-max_effort,-xhigh_effort`);
|
|
598
607
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
608
|
+
return entries.join(";");
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// src/application/services/workspaceSandbox/autoRouting.ts
|
|
612
|
+
var LONG_INSTRUCTION_CHARS = 3e3;
|
|
613
|
+
var LONG_CODE_LINES = 60;
|
|
614
|
+
var MANY_FILES = 5;
|
|
615
|
+
var HARD_WORK = [
|
|
616
|
+
[/\b(refactor|refator|refactoriz)\w*\b[\s\S]{0,80}\b(across|between|all|every|entre|todos|todas|varios|varias|modul|servic|packages?|pacotes?|layers?|camadas?)/, "refactor across modules"],
|
|
617
|
+
[/\b(architect|arquitet|arquitect|redesign|re-architect)\w*/, "architecture"],
|
|
618
|
+
[/\b(root cause|causa raiz|causa-raiz|debug|depur|investigat|investig|stack ?trace|flaky|race condition|condicao de corrida|memory leak|vazamento de memoria|deadlock|intermittent|intermitente)\w*/, "debugging, root cause"],
|
|
619
|
+
[/\b(migrat|migra(c|t)a?o|migrar|migre|upgrade\b[\s\S]{0,40}\b(from|to|de|para)\b|port (this|the|it)\b[\s\S]{0,40}\bto\b)/, "migration"],
|
|
620
|
+
[/\b(vulnerab|cve-\d|security (fix|issue|flaw|hole|bug)|injection|injecao|xss|csrf|ssrf|rce\b|seguranca|seguridad|exploit)\w*/, "security fix"],
|
|
621
|
+
[/\b(every|all|each|todos os|todas as|cada|todos los|todas las) (the )?(files?|modules?|services?|endpoints?|packages?|arquivos?|modulos?|servicos?|archivos?)\b|\b(across|throughout) (the )?(whole )?(codebase|repo|repository|project|modules|services)\b|\b(codebase|base de codigo)[- ](wide|inteira|toda)\b/, "many files"],
|
|
622
|
+
[/\b(refactor|refator|refactoriz)\w*/, "refactor"],
|
|
623
|
+
[/\b(step[- ]by[- ]step|passo a passo|paso a paso|multi[- ]step|end[- ]to[- ]end|threat model|performance (issue|regression|problem)|regressao de performance|rewrite|reescrev|reescrib)\w*/, "multi-step work"]
|
|
624
|
+
];
|
|
625
|
+
var normalize = (text3) => text3.toLowerCase().normalize("NFKD").replace(new RegExp("\\p{M}", "gu"), "");
|
|
626
|
+
function namedFiles(text3) {
|
|
627
|
+
const found = /* @__PURE__ */ new Set();
|
|
628
|
+
for (const m of text3.matchAll(/(?:^|[\s`'"(\[])((?:\.{0,2}\/)?[\w.-]+(?:\/[\w.-]+)+\/?|[\w-]+\.(?:ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|kt|rb|php|cs|cpp|c|h|swift|scala|sql|yml|yaml|json|tf|vue|svelte))(?=$|[\s`'"),.:;\]])/g)) {
|
|
629
|
+
found.add(m[1].replace(/^\.\//, ""));
|
|
617
630
|
}
|
|
618
|
-
return
|
|
631
|
+
return found.size;
|
|
619
632
|
}
|
|
620
|
-
function
|
|
621
|
-
|
|
633
|
+
function classifyCodeTurn(s) {
|
|
634
|
+
if (s.maxMode) return { hard: true, reason: "Max Mode" };
|
|
635
|
+
if (s.previousTrouble) return { hard: true, reason: s.previousTrouble };
|
|
636
|
+
const text3 = s.content;
|
|
637
|
+
const codeLines = [...text3.matchAll(/```[\s\S]*?```/g)].reduce((n, block) => n + block[0].split("\n").length, 0);
|
|
638
|
+
if (codeLines > LONG_CODE_LINES) return { hard: true, reason: `${codeLines} lines of code in the request` };
|
|
639
|
+
if (text3.length > LONG_INSTRUCTION_CHARS) return { hard: true, reason: `long instruction (${text3.length} characters)` };
|
|
640
|
+
const plain = normalize(text3);
|
|
641
|
+
for (const [re, reason] of HARD_WORK) if (re.test(plain)) return { hard: true, reason };
|
|
642
|
+
const files = namedFiles(text3);
|
|
643
|
+
if (files >= MANY_FILES) return { hard: true, reason: `${files} files named` };
|
|
644
|
+
return { hard: false, reason: text3.length > 600 || codeLines > 0 ? "bounded task" : "short, direct request" };
|
|
622
645
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
646
|
+
var STREAM_REASONS = ["ORG_OUTPUT_TEXT_RULES", "SEMANTIC_INSPECTOR", "APPLICATION_OUTPUT_TEXT_RULES", "DESTINATION_NO_STREAM"];
|
|
647
|
+
function routeAutoTurn(boot, signals) {
|
|
648
|
+
if (boot.model !== "sq-auto") return null;
|
|
649
|
+
const aliases = boot.runtime.aliases ?? [];
|
|
650
|
+
if (!aliases.some((a) => a.tier)) return null;
|
|
651
|
+
const primary = boot.runtime.primaryModel || aliases.find((a) => a.tier === "MEDIUM")?.alias || null;
|
|
652
|
+
if (!primary) return null;
|
|
653
|
+
const decision = classifyCodeTurn(signals);
|
|
654
|
+
const max = decision.hard ? aliases.find((a) => a.tier === "COMPLEX") ?? aliases.find((a) => a.alias === "sq-auto-max") : void 0;
|
|
655
|
+
const chosen = max ?? aliases.find((a) => a.alias === primary) ?? { alias: primary, reasoning: null, tier: "MEDIUM" };
|
|
656
|
+
const fallback = decision.hard && !max;
|
|
657
|
+
const kind = decision.hard ? "hard task" : "everyday task";
|
|
658
|
+
return {
|
|
659
|
+
alias: chosen.alias,
|
|
660
|
+
tier: chosen.tier ?? null,
|
|
661
|
+
hard: decision.hard,
|
|
662
|
+
reason: decision.reason,
|
|
663
|
+
label: `SQ Auto \xB7 ${kind} \xB7 ${decision.reason}${fallback ? " (no hard-task model available, main model used)" : ""}`,
|
|
664
|
+
capability: chosen.reasoning ?? null,
|
|
665
|
+
maxOutputTokens: typeof chosen.maxOutputTokens === "number" && chosen.maxOutputTokens > 0 ? chosen.maxOutputTokens : null,
|
|
666
|
+
fallback
|
|
667
|
+
};
|
|
627
668
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
const
|
|
634
|
-
|
|
669
|
+
function credentialAlias(raw) {
|
|
670
|
+
const tier = raw.tier === "LIGHT" || raw.tier === "MEDIUM" || raw.tier === "COMPLEX" ? raw.tier : null;
|
|
671
|
+
const positive = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? Math.trunc(v) : null;
|
|
672
|
+
const max = positive(raw.maxOutputTokens), organization = positive(raw.organizationMaxOutputTokens);
|
|
673
|
+
const source = raw.outputLimitSource === "ORGANIZATION" || raw.outputLimitSource === "MODEL" || raw.outputLimitSource === "PLATFORM" ? raw.outputLimitSource : null;
|
|
674
|
+
const s = raw.stream && typeof raw.stream === "object" ? raw.stream : null;
|
|
675
|
+
const mode = s && (s.mode === "LIVE" || s.mode === "BUFFERED" || s.mode === "BUFFERED_POLICY") ? s.mode : null;
|
|
676
|
+
const reason = s && STREAM_REASONS.includes(s.reason) ? s.reason : null;
|
|
677
|
+
return {
|
|
678
|
+
alias: String(raw.alias),
|
|
679
|
+
reasoning: parseReasoningCapability(raw.reasoning),
|
|
680
|
+
...tier ? { tier } : {},
|
|
681
|
+
...max ? { maxOutputTokens: max } : {},
|
|
682
|
+
...source ? { outputLimitSource: source } : {},
|
|
683
|
+
...organization ? { organizationMaxOutputTokens: organization } : {},
|
|
684
|
+
...mode ? { stream: { mode, ...reason ? { reason } : {} } } : {}
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// src/application/services/workspaceSandbox/SessionTransport.ts
|
|
689
|
+
var SessionGoneError = class extends Error {
|
|
690
|
+
constructor(status) {
|
|
691
|
+
super(`session unavailable (${status})`);
|
|
692
|
+
this.status = status;
|
|
693
|
+
this.name = "SessionGoneError";
|
|
694
|
+
}
|
|
695
|
+
status;
|
|
635
696
|
};
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
}
|
|
697
|
+
var TransportError = class extends Error {
|
|
698
|
+
constructor(message, status, retryable) {
|
|
699
|
+
super(message);
|
|
700
|
+
this.status = status;
|
|
701
|
+
this.retryable = retryable;
|
|
702
|
+
this.name = "TransportError";
|
|
643
703
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
704
|
+
status;
|
|
705
|
+
retryable;
|
|
706
|
+
/** The API's error code (e.g. REPOSITORY_NOT_IN_SCOPE), when it sent one. */
|
|
707
|
+
code;
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
// src/application/services/workspaceSandbox/HttpSessionTransport.ts
|
|
711
|
+
var GONE = [401, 403, 404, 410];
|
|
712
|
+
var HttpSessionTransport = class {
|
|
713
|
+
constructor(opts) {
|
|
714
|
+
this.opts = opts;
|
|
715
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
716
|
+
this.gone = new Set(opts.goneStatuses ?? GONE);
|
|
717
|
+
const base = opts.baseUrl.replace(/\/+$/, "");
|
|
718
|
+
this.root = `${base}/api/ai-governance/internal/workspace-sessions/${encodeURIComponent(opts.sessionId)}`;
|
|
650
719
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
if (!line || line.length > MAX_LINE || line[0] !== "{") continue;
|
|
659
|
-
try {
|
|
660
|
-
const rec = JSON.parse(line);
|
|
661
|
-
if (rec && typeof rec === "object") yield rec;
|
|
662
|
-
} catch {
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
} finally {
|
|
666
|
-
rl.close();
|
|
720
|
+
opts;
|
|
721
|
+
fetchImpl;
|
|
722
|
+
root;
|
|
723
|
+
gone;
|
|
724
|
+
async bootstrap() {
|
|
725
|
+
const raw = await this.get(`/bootstrap?features=${ENGINE_FEATURES.join(",")}`, this.opts.requestTimeoutMs ?? 3e4);
|
|
726
|
+
return toSessionBootstrap(raw);
|
|
667
727
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
total = 0;
|
|
673
|
-
push(m) {
|
|
674
|
-
this.messages.push(m);
|
|
675
|
-
this.total++;
|
|
676
|
-
this.bytes += importBytes([m]);
|
|
677
|
-
while (this.bytes > READ_WINDOW_BYTES && this.messages.length > 1) this.bytes -= importBytes([this.messages.shift()]);
|
|
728
|
+
async pollCommands(waitSeconds, signal) {
|
|
729
|
+
const wait = Math.max(0, Math.min(25, Math.floor(waitSeconds)));
|
|
730
|
+
const body = await this.get(`/commands?wait=${wait}`, (wait + 15) * 1e3, signal);
|
|
731
|
+
return Array.isArray(body?.commands) ? body.commands : [];
|
|
678
732
|
}
|
|
679
|
-
|
|
680
|
-
|
|
733
|
+
async postEvents(events) {
|
|
734
|
+
if (events.length === 0) return;
|
|
735
|
+
await this.post("/events", { events }, this.opts.requestTimeoutMs ?? 3e4);
|
|
681
736
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
${text3}` : text3;
|
|
687
|
-
if (tools.length) m.tools = [...m.tools ?? [], ...tools].slice(0, IMPORT_LIMITS.maxToolsPerMessage);
|
|
688
|
-
this.bytes += importBytes([m]) - before;
|
|
737
|
+
callTool(name, args, approvalId) {
|
|
738
|
+
if (!/^[a-z][a-z0-9_]{0,63}$/.test(name)) throw new TransportError("invalid tool name", null, false);
|
|
739
|
+
return this.post(`/tools/${name}`, approvalId ? { args, approvalId } : { args }, this.opts.toolTimeoutMs ?? 12e4);
|
|
689
740
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
if (!Array.isArray(content)) return { text: "", tools: [] };
|
|
694
|
-
const texts = [];
|
|
695
|
-
const tools = [];
|
|
696
|
-
for (const b of content) {
|
|
697
|
-
if (b?.type === "text" && typeof b.text === "string" && !(role === "user" && WRAPPER.test(b.text))) texts.push(b.text);
|
|
698
|
-
else if (b?.type === "tool_use" && typeof b.name === "string") tools.push({ name: b.name.slice(0, 200), summary: summarizeToolInput(b.name, b.input) });
|
|
699
|
-
else if (b?.type === "image") texts.push("[image]");
|
|
741
|
+
async mcpTools() {
|
|
742
|
+
const raw = await this.get("/mcp/tools", this.opts.requestTimeoutMs ?? 6e4);
|
|
743
|
+
return orgMcpCatalogOf(raw);
|
|
700
744
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
745
|
+
mcpCall(req) {
|
|
746
|
+
return this.post("/mcp/call", req, this.opts.toolTimeoutMs ?? 18e4);
|
|
747
|
+
}
|
|
748
|
+
openPullRequest(req) {
|
|
749
|
+
return this.post("/pull-request", req, this.opts.toolTimeoutMs ?? 18e4);
|
|
750
|
+
}
|
|
751
|
+
async checkpoint(req) {
|
|
752
|
+
await this.post("/checkpoint", req, this.opts.requestTimeoutMs ?? 6e4);
|
|
753
|
+
}
|
|
754
|
+
async openRepository(repoFullName) {
|
|
755
|
+
const raw = await this.post("/repositories/open", { repoFullName }, this.opts.requestTimeoutMs ?? 3e4);
|
|
756
|
+
const text3 = (v, fallback = "") => typeof v === "string" ? v : fallback;
|
|
757
|
+
const defaultBranch = text3(raw?.defaultBranch, "main");
|
|
758
|
+
return {
|
|
759
|
+
cloneUrl: text3(raw?.cloneUrl),
|
|
760
|
+
scheme: text3(raw?.scheme),
|
|
761
|
+
token: text3(raw?.token),
|
|
762
|
+
provider: text3(raw?.provider),
|
|
763
|
+
repoFullName: text3(raw?.repoFullName, repoFullName),
|
|
764
|
+
defaultBranch,
|
|
765
|
+
branch: typeof raw?.branch === "string" ? raw.branch : defaultBranch
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
async importedHistory() {
|
|
769
|
+
const raw = await this.get("/imported", this.opts.requestTimeoutMs ?? 6e4);
|
|
770
|
+
const messages = Array.isArray(raw?.messages) ? raw.messages : [];
|
|
771
|
+
return {
|
|
772
|
+
messages: messages.filter((m) => !!m && typeof m === "object" && (m.role === "user" || m.role === "assistant")).map((m) => ({
|
|
773
|
+
role: m.role,
|
|
774
|
+
text: typeof m.text === "string" ? m.text : "",
|
|
775
|
+
at: typeof m.at === "string" ? m.at : null,
|
|
776
|
+
...Array.isArray(m.tools) ? { tools: m.tools.filter((t) => !!t && typeof t.name === "string" && typeof t.summary === "string") } : {}
|
|
777
|
+
}))
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
async saveTranscript(t) {
|
|
781
|
+
await this.post("/transcript", t, this.opts.requestTimeoutMs ?? 6e4);
|
|
782
|
+
}
|
|
783
|
+
async saveBaseMeasurement(r) {
|
|
784
|
+
await this.post("/measurements/base", r, this.opts.requestTimeoutMs ?? 3e4);
|
|
785
|
+
}
|
|
786
|
+
async reply(commandId, reply) {
|
|
787
|
+
if (!/^[A-Za-z0-9-]{1,80}$/.test(commandId)) throw new TransportError("invalid command id", null, false);
|
|
788
|
+
await this.post(`/replies/${commandId}`, reply, this.opts.requestTimeoutMs ?? 3e4);
|
|
789
|
+
}
|
|
790
|
+
async refreshRuntime() {
|
|
791
|
+
const raw = await this.post("/runtime", {}, this.opts.requestTimeoutMs ?? 3e4);
|
|
792
|
+
const boot = toSessionBootstrap({ session: { model: raw?.model, reasoning: raw?.reasoning }, runtime: raw?.runtime, repository: null });
|
|
793
|
+
return { model: boot.model, runtime: boot.runtime, reasoning: boot.reasoning ?? null };
|
|
794
|
+
}
|
|
795
|
+
headers(json) {
|
|
796
|
+
return {
|
|
797
|
+
"x-workspace-session-secret": this.opts.secret,
|
|
798
|
+
accept: "application/json",
|
|
799
|
+
...this.opts.userAgent ? { "user-agent": this.opts.userAgent } : {},
|
|
800
|
+
...json ? { "content-type": "application/json" } : {}
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
async get(path, timeoutMs, outer) {
|
|
804
|
+
const attempts = Math.max(1, this.opts.getAttempts ?? 4);
|
|
805
|
+
const delay = this.opts.retryDelayMs ?? 1e3;
|
|
806
|
+
let last;
|
|
807
|
+
for (let i = 0; i < attempts; i++) {
|
|
808
|
+
if (outer?.aborted) throw new TransportError("aborted", null, false);
|
|
809
|
+
try {
|
|
810
|
+
return await this.request("GET", path, void 0, timeoutMs, outer);
|
|
811
|
+
} catch (e) {
|
|
812
|
+
last = e;
|
|
813
|
+
if (e instanceof SessionGoneError) throw e;
|
|
814
|
+
if (e instanceof TransportError && !e.retryable) throw e;
|
|
815
|
+
if (i < attempts - 1) await sleep(delay * 2 ** i, outer);
|
|
731
816
|
}
|
|
732
|
-
continue;
|
|
733
817
|
}
|
|
734
|
-
|
|
735
|
-
if (!onlyToolResults) current = null;
|
|
736
|
-
if (!text3) continue;
|
|
737
|
-
const shown = r.isCompactSummary === true ? `[Summary of the earlier conversation]
|
|
738
|
-
${text3}` : text3;
|
|
739
|
-
if (!firstUser && r.isCompactSummary !== true) firstUser = text3;
|
|
740
|
-
win.push({ role, text: shown, at });
|
|
818
|
+
throw last;
|
|
741
819
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
const st = await (0, import_promises.lstat)(file).catch(() => null);
|
|
745
|
-
return { source: "CLAUDE_CODE", externalId, title, folder, messages: win.messages, updatedAt: st ? st.mtime.toISOString() : null };
|
|
746
|
-
}
|
|
747
|
-
async function regularFiles(dir, match) {
|
|
748
|
-
const out2 = [];
|
|
749
|
-
const names = await (0, import_promises.readdir)(dir).catch(() => []);
|
|
750
|
-
for (const name of names) {
|
|
751
|
-
if (!match(name)) continue;
|
|
752
|
-
const st = await (0, import_promises.lstat)((0, import_path.join)(dir, name)).catch(() => null);
|
|
753
|
-
if (st?.isFile()) out2.push({ path: (0, import_path.join)(dir, name), mtime: st.mtimeMs });
|
|
820
|
+
post(path, body, timeoutMs) {
|
|
821
|
+
return this.request("POST", path, body, timeoutMs);
|
|
754
822
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
823
|
+
async request(method, path, body, timeoutMs, outer) {
|
|
824
|
+
const signals = [AbortSignal.timeout(timeoutMs), ...outer ? [outer] : []];
|
|
825
|
+
const signal = signals.length > 1 ? anySignal(signals) : signals[0];
|
|
826
|
+
let res;
|
|
827
|
+
try {
|
|
828
|
+
res = await this.fetchImpl(this.root + path, {
|
|
829
|
+
method,
|
|
830
|
+
headers: this.headers(body !== void 0),
|
|
831
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
832
|
+
signal
|
|
833
|
+
});
|
|
834
|
+
} catch (e) {
|
|
835
|
+
throw new TransportError(`${method} ${routeLabel(path)} failed: ${e.name}`, null, true);
|
|
836
|
+
}
|
|
837
|
+
if (this.gone.has(res.status)) {
|
|
838
|
+
await res.body?.cancel().catch(() => void 0);
|
|
839
|
+
throw new SessionGoneError(res.status);
|
|
840
|
+
}
|
|
841
|
+
if (!res.ok) {
|
|
842
|
+
const code = await res.text().then((t) => JSON.parse(t)?.code, () => void 0).catch(() => void 0);
|
|
843
|
+
const retryable = res.status === 429 || res.status >= 500;
|
|
844
|
+
const error = new TransportError(`${method} ${routeLabel(path)} answered ${res.status}`, res.status, retryable);
|
|
845
|
+
if (typeof code === "string" && /^[A-Z_]{3,80}$/.test(code)) error.code = code;
|
|
846
|
+
throw error;
|
|
847
|
+
}
|
|
848
|
+
if (res.status === 204) return void 0;
|
|
849
|
+
const text3 = await res.text();
|
|
850
|
+
if (!text3) return void 0;
|
|
851
|
+
try {
|
|
852
|
+
return JSON.parse(text3);
|
|
853
|
+
} catch {
|
|
854
|
+
throw new TransportError(`${method} ${routeLabel(path)} returned invalid JSON`, res.status, false);
|
|
855
|
+
}
|
|
762
856
|
}
|
|
763
|
-
|
|
857
|
+
};
|
|
858
|
+
function anySignal(signals) {
|
|
859
|
+
const native = AbortSignal.any;
|
|
860
|
+
if (typeof native === "function") return native.call(AbortSignal, signals);
|
|
861
|
+
const ac = new AbortController();
|
|
862
|
+
for (const s of signals) {
|
|
863
|
+
if (s.aborted) {
|
|
864
|
+
ac.abort(s.reason);
|
|
865
|
+
return ac.signal;
|
|
866
|
+
}
|
|
867
|
+
s.addEventListener("abort", () => ac.abort(s.reason), { once: true });
|
|
868
|
+
}
|
|
869
|
+
return ac.signal;
|
|
764
870
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
for (const project of await subdirs((0, import_path.join)(claudeDir, "projects"))) files.push(...await regularFiles(project, (n) => n.endsWith(".jsonl")));
|
|
768
|
-
return files;
|
|
871
|
+
function routeLabel(path) {
|
|
872
|
+
return path.split("?")[0];
|
|
769
873
|
}
|
|
770
|
-
function
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
874
|
+
function sleep(ms, signal) {
|
|
875
|
+
return new Promise((resolve8) => {
|
|
876
|
+
const t = setTimeout(resolve8, ms);
|
|
877
|
+
signal?.addEventListener("abort", () => {
|
|
878
|
+
clearTimeout(t);
|
|
879
|
+
resolve8();
|
|
880
|
+
}, { once: true });
|
|
881
|
+
});
|
|
775
882
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
const text3 = codexText(p.content, p.role);
|
|
796
|
-
if (!text3) continue;
|
|
797
|
-
if (p.role === "user" && !firstUser) firstUser = text3;
|
|
798
|
-
const last = win.last;
|
|
799
|
-
if (p.role === "assistant" && last?.role === "assistant" && !last.text) win.grow(last, text3, []);
|
|
800
|
-
else win.push({ role: p.role, text: text3, at });
|
|
801
|
-
} else if ((p.type === "function_call" || p.type === "custom_tool_call" || p.type === "local_shell_call") && (typeof p.name === "string" || p.type === "local_shell_call")) {
|
|
802
|
-
const name = typeof p.name === "string" ? p.name : "shell";
|
|
803
|
-
const tool = { name: name.slice(0, 200), summary: summarizeToolInput(name, p.arguments ?? p.input ?? p.action) };
|
|
804
|
-
const last = win.last;
|
|
805
|
-
if (last?.role === "assistant") win.grow(last, "", [tool]);
|
|
806
|
-
else win.push({ role: "assistant", text: "", at, tools: [tool] });
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
const id = externalId ?? fromName;
|
|
810
|
-
if (!id || !isExternalId(id) || !win.total) return null;
|
|
811
|
-
const st = await (0, import_promises.lstat)(file).catch(() => null);
|
|
883
|
+
var ENGINE_FEATURES = ["message_queue_v1"];
|
|
884
|
+
function toSessionBootstrap(raw) {
|
|
885
|
+
if (raw && raw.repo && !raw.repository) return raw;
|
|
886
|
+
const session = raw?.session ?? {};
|
|
887
|
+
const repository = raw?.repository && typeof raw.repository === "object" ? raw.repository : null;
|
|
888
|
+
const runtime = raw?.runtime ?? {};
|
|
889
|
+
const text3 = (v, fallback = "") => typeof v === "string" ? v : fallback;
|
|
890
|
+
const defaultBranch = text3(repository?.defaultBranch, "main");
|
|
891
|
+
const repo2 = repository ? {
|
|
892
|
+
cloneUrl: text3(repository.cloneUrl),
|
|
893
|
+
scheme: text3(repository.scheme),
|
|
894
|
+
token: text3(repository.token),
|
|
895
|
+
provider: text3(repository.provider, text3(session.provider)),
|
|
896
|
+
repoFullName: text3(repository.repoFullName, text3(session.repoFullName)),
|
|
897
|
+
defaultBranch
|
|
898
|
+
} : null;
|
|
899
|
+
const repositories = scopeRepos(raw?.repositories) ?? scopeRepos(session.scope?.repos) ?? (repo2 ? [{ repoFullName: repo2.repoFullName, provider: repo2.provider, projectId: text3(session.projectId), defaultBranch: repo2.defaultBranch }] : []);
|
|
900
|
+
const rawScope = session.scope && typeof session.scope === "object" ? session.scope : null;
|
|
901
|
+
const kind = rawScope?.kind === "ALL" || rawScope?.kind === "TEAM" || rawScope?.kind === "BUSINESS_AREA" ? rawScope.kind : "PROJECTS";
|
|
812
902
|
return {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
903
|
+
repo: repo2,
|
|
904
|
+
repositories,
|
|
905
|
+
scope: {
|
|
906
|
+
kind,
|
|
907
|
+
teamId: typeof rawScope?.teamId === "string" ? rawScope.teamId : null,
|
|
908
|
+
projectIds: Array.isArray(rawScope?.projectIds) ? rawScope.projectIds.filter((p) => typeof p === "string") : typeof session.projectId === "string" && session.projectId ? [session.projectId] : [],
|
|
909
|
+
repos: repositories
|
|
910
|
+
},
|
|
911
|
+
branch: text3(session.branch, repo2 ? defaultBranch : ""),
|
|
912
|
+
projectId: text3(session.projectId),
|
|
913
|
+
model: text3(session.model, "sq-auto"),
|
|
914
|
+
runtime: {
|
|
915
|
+
baseUrl: text3(runtime.baseUrl),
|
|
916
|
+
token: text3(runtime.token),
|
|
917
|
+
models: runtime.models,
|
|
918
|
+
primaryModel: typeof runtime.primaryModel === "string" ? runtime.primaryModel : null,
|
|
919
|
+
fastModel: typeof runtime.fastModel === "string" ? runtime.fastModel : null,
|
|
920
|
+
maxOutputTokens: typeof runtime.maxOutputTokens === "number" ? runtime.maxOutputTokens : null,
|
|
921
|
+
maxOutputTokensCeiling: typeof runtime.maxOutputTokensCeiling === "number" ? runtime.maxOutputTokensCeiling : null,
|
|
922
|
+
reasoning: parseReasoningCapability(runtime.reasoning),
|
|
923
|
+
aliases: Array.isArray(runtime.aliases) ? runtime.aliases.filter((a) => typeof a?.alias === "string").map(credentialAlias) : void 0
|
|
924
|
+
},
|
|
925
|
+
reasoning: isReasoningLevel(session.reasoning) ? session.reasoning : null,
|
|
926
|
+
imported: importedInfo(session.imported),
|
|
927
|
+
checkpointPatch: typeof raw?.checkpointPatch === "string" ? raw.checkpointPatch : null,
|
|
928
|
+
sdkSessionId: typeof session.sdkSessionId === "string" ? session.sdkSessionId : null,
|
|
929
|
+
workspaceKind: session.workspaceKind === "LOCAL" ? "LOCAL" : "CLOUD",
|
|
930
|
+
projectName: typeof session.projectName === "string" ? session.projectName : typeof raw?.project?.name === "string" ? raw.project.name : null,
|
|
931
|
+
folderLink: typeof session.folderLink?.projectId === "string" && session.folderLink.projectId ? { projectId: session.folderLink.projectId } : null,
|
|
932
|
+
transcript: transcriptOf(raw?.transcript),
|
|
933
|
+
baseMeasurements: raw?.baseMeasurements && typeof raw.baseMeasurements === "object" && !Array.isArray(raw.baseMeasurements) ? raw.baseMeasurements : {},
|
|
934
|
+
pullRequests: pullRequestsOf(raw?.pullRequests),
|
|
935
|
+
skills: skillsOf(raw?.skills),
|
|
936
|
+
plugins: pluginsOf(raw?.plugins),
|
|
937
|
+
environments: environmentsOf(raw?.environments)
|
|
826
938
|
};
|
|
827
|
-
await walk((0, import_path.join)(codexDir, "sessions"), 0);
|
|
828
|
-
return files;
|
|
829
939
|
}
|
|
830
|
-
function
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
for (const t of m.tools ?? []) n += countSecrets(t.summary);
|
|
835
|
-
}
|
|
836
|
-
return n;
|
|
940
|
+
function transcriptOf(raw) {
|
|
941
|
+
const t = raw;
|
|
942
|
+
if (!t || typeof t !== "object" || typeof t.sdkSessionId !== "string" || t.encoding !== "gzip-base64" || typeof t.data !== "string") return null;
|
|
943
|
+
return { sdkSessionId: t.sdkSessionId, encoding: "gzip-base64", data: t.data, bytes: typeof t.bytes === "number" ? t.bytes : 0, sha256: typeof t.sha256 === "string" ? t.sha256 : "" };
|
|
837
944
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
].sort((a, b) => b.mtime - a.mtime).slice(0, MAX_FILES);
|
|
945
|
+
var strings = (v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).filter(([, x]) => typeof x === "string")) : {};
|
|
946
|
+
function environmentsOf(raw) {
|
|
947
|
+
if (!Array.isArray(raw)) return [];
|
|
948
|
+
return raw.filter((e) => !!e && typeof e === "object" && typeof e.projectId === "string").slice(0, 50).map((e) => ({ projectId: e.projectId, setupCommand: typeof e.setupCommand === "string" && e.setupCommand.trim() ? e.setupCommand : null, env: strings(e.env), secrets: strings(e.secrets) }));
|
|
843
949
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
const seen = /* @__PURE__ */ new Set();
|
|
848
|
-
for (const f of files) {
|
|
849
|
-
const c = await (f.source === "CLAUDE_CODE" ? parseClaudeCodeFile(f.path) : parseCodexFile(f.path)).catch(() => null);
|
|
850
|
-
if (!c || seen.has(`${c.source}:${c.externalId}`)) continue;
|
|
851
|
-
seen.add(`${c.source}:${c.externalId}`);
|
|
852
|
-
items.push({
|
|
853
|
-
source: c.source,
|
|
854
|
-
externalId: c.externalId,
|
|
855
|
-
title: oneLine(scrubSecrets(c.title).text, IMPORT_LIMITS.maxTitle),
|
|
856
|
-
folder: c.folder,
|
|
857
|
-
messageCount: Math.min(c.messages.length, IMPORT_LIMITS.maxMessages),
|
|
858
|
-
updatedAt: c.updatedAt,
|
|
859
|
-
secretsFound: secretsIn(c)
|
|
860
|
-
});
|
|
861
|
-
}
|
|
862
|
-
return items;
|
|
950
|
+
function pluginsOf(raw) {
|
|
951
|
+
if (!Array.isArray(raw)) return [];
|
|
952
|
+
return raw.filter((p) => !!p && typeof p === "object" && typeof p.name === "string" && Array.isArray(p.files)).slice(0, 20).map((p) => ({ name: p.name, files: p.files.filter((f) => !!f && typeof f.path === "string" && typeof f.content === "string").slice(0, 300) }));
|
|
863
953
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
else counts.codex++;
|
|
873
|
-
}
|
|
874
|
-
return counts;
|
|
954
|
+
function skillsOf(raw) {
|
|
955
|
+
if (!Array.isArray(raw)) return [];
|
|
956
|
+
return raw.filter((s) => !!s && typeof s === "object").filter((s) => typeof s.name === "string" && typeof s.body === "string").slice(0, 200).map((s) => ({
|
|
957
|
+
name: s.name,
|
|
958
|
+
description: typeof s.description === "string" ? s.description : "",
|
|
959
|
+
body: s.body,
|
|
960
|
+
argumentHint: typeof s.argumentHint === "string" ? s.argumentHint : null
|
|
961
|
+
}));
|
|
875
962
|
}
|
|
876
|
-
|
|
877
|
-
const
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
for (const f of files) {
|
|
888
|
-
if (Date.now() > deadline) break;
|
|
889
|
-
let n = 0;
|
|
890
|
-
try {
|
|
891
|
-
for await (const r of lines(f.path)) {
|
|
892
|
-
if (++n > 50) break;
|
|
893
|
-
const cwd = f.source === "CLAUDE_CODE" ? r.cwd : r.type === "session_meta" && r.payload && typeof r.payload === "object" && !(r.payload.source && typeof r.payload.source === "object" && r.payload.source.subagent) ? r.payload.cwd : void 0;
|
|
894
|
-
if (typeof cwd === "string" && cwd) {
|
|
895
|
-
if (!seen.has(cwd)) {
|
|
896
|
-
seen.add(cwd);
|
|
897
|
-
out2.push(cwd);
|
|
898
|
-
}
|
|
899
|
-
break;
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
} catch {
|
|
903
|
-
}
|
|
963
|
+
function pullRequestsOf(raw) {
|
|
964
|
+
const out2 = {};
|
|
965
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return out2;
|
|
966
|
+
for (const [repo2, v] of Object.entries(raw)) {
|
|
967
|
+
if (!v || typeof v.branch !== "string") continue;
|
|
968
|
+
out2[repo2] = {
|
|
969
|
+
branch: v.branch,
|
|
970
|
+
number: typeof v.number === "number" ? v.number : null,
|
|
971
|
+
url: typeof v.url === "string" ? v.url : null,
|
|
972
|
+
files: Array.isArray(v.files) ? v.files.filter((f) => typeof f === "string") : []
|
|
973
|
+
};
|
|
904
974
|
}
|
|
905
975
|
return out2;
|
|
906
976
|
}
|
|
907
|
-
|
|
908
|
-
if (!
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
return null;
|
|
919
|
-
}
|
|
920
|
-
function prepareImport(c) {
|
|
921
|
-
let removed = 0;
|
|
922
|
-
const scrub = (s) => {
|
|
923
|
-
const r = scrubSecrets(s);
|
|
924
|
-
removed += r.count;
|
|
925
|
-
return r.text;
|
|
977
|
+
function importedInfo(raw) {
|
|
978
|
+
if (!raw || typeof raw !== "object") return null;
|
|
979
|
+
const r = raw;
|
|
980
|
+
if (r.source !== "CLAUDE_CODE" && r.source !== "CODEX" || !isExternalId(r.externalId)) return null;
|
|
981
|
+
return {
|
|
982
|
+
source: r.source,
|
|
983
|
+
externalId: r.externalId,
|
|
984
|
+
title: typeof r.title === "string" ? r.title : "",
|
|
985
|
+
folder: typeof r.folder === "string" ? r.folder : null,
|
|
986
|
+
messageCount: typeof r.messageCount === "number" ? r.messageCount : 0,
|
|
987
|
+
nativeResume: r.nativeResume === true
|
|
926
988
|
};
|
|
927
|
-
const cap = (s, max) => Buffer.byteLength(s) > max ? `${Buffer.from(s).subarray(0, max - 32).toString("utf8").replace(/�+$/, "")}
|
|
928
|
-
[... cut ...]` : s;
|
|
929
|
-
const messages = c.messages.map((m) => ({
|
|
930
|
-
role: m.role,
|
|
931
|
-
text: cap(scrub(m.text), IMPORT_LIMITS.maxMessageBytes),
|
|
932
|
-
at: m.at,
|
|
933
|
-
...m.tools?.length ? { tools: m.tools.slice(0, IMPORT_LIMITS.maxToolsPerMessage).map((t) => ({ name: t.name, summary: oneLine(scrub(t.summary), IMPORT_LIMITS.maxToolSummary) })) } : {}
|
|
934
|
-
}));
|
|
935
|
-
const title = oneLine(scrubSecrets(c.title).text, IMPORT_LIMITS.maxTitle) || "Untitled conversation";
|
|
936
|
-
let start = Math.max(0, messages.length - IMPORT_LIMITS.maxMessages);
|
|
937
|
-
let bytes = importBytes(messages.slice(start));
|
|
938
|
-
while (bytes > IMPORT_LIMITS.maxBytes && start < messages.length - 1) bytes -= importBytes([messages[start++]]);
|
|
939
|
-
return { source: c.source, externalId: c.externalId, folder: c.folder, title, secretsRemoved: removed, messages: messages.slice(start), omitted: start };
|
|
940
989
|
}
|
|
941
|
-
function
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
}
|
|
950
|
-
if (Array.isArray(value)) {
|
|
951
|
-
const out2 = [];
|
|
952
|
-
for (const item of value) {
|
|
953
|
-
const block = item;
|
|
954
|
-
if (block && typeof block === "object" && block.type === "thinking" && typeof block.thinking === "string") {
|
|
955
|
-
const r = scrubSecrets(block.thinking);
|
|
956
|
-
if (r.count) {
|
|
957
|
-
removed += r.count;
|
|
958
|
-
continue;
|
|
959
|
-
}
|
|
960
|
-
out2.push(item);
|
|
961
|
-
continue;
|
|
962
|
-
}
|
|
963
|
-
if (block && typeof block === "object" && block.type === "redacted_thinking") {
|
|
964
|
-
out2.push(item);
|
|
965
|
-
continue;
|
|
966
|
-
}
|
|
967
|
-
out2.push(walk(item));
|
|
968
|
-
}
|
|
969
|
-
return out2;
|
|
970
|
-
}
|
|
971
|
-
if (value && typeof value === "object") {
|
|
972
|
-
const o = value;
|
|
973
|
-
for (const k of Object.keys(o)) o[k] = walk(o[k], k);
|
|
974
|
-
return o;
|
|
975
|
-
}
|
|
976
|
-
return value;
|
|
977
|
-
};
|
|
978
|
-
walk(record);
|
|
979
|
-
return removed;
|
|
990
|
+
function scopeRepos(raw) {
|
|
991
|
+
if (!Array.isArray(raw)) return null;
|
|
992
|
+
return raw.filter((r) => r && typeof r === "object" && typeof r.repoFullName === "string").map((r) => ({
|
|
993
|
+
repoFullName: r.repoFullName,
|
|
994
|
+
provider: typeof r.provider === "string" ? r.provider : "",
|
|
995
|
+
projectId: typeof r.projectId === "string" ? r.projectId : "",
|
|
996
|
+
defaultBranch: typeof r.defaultBranch === "string" ? r.defaultBranch : null
|
|
997
|
+
}));
|
|
980
998
|
}
|
|
981
|
-
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
continue;
|
|
997
|
-
}
|
|
998
|
-
removed += scrubRecord(record);
|
|
999
|
-
if (!out2.write(`${JSON.stringify(record)}
|
|
1000
|
-
`)) await new Promise((r) => out2.once("drain", () => r()));
|
|
1001
|
-
}
|
|
1002
|
-
} finally {
|
|
1003
|
-
rl.close();
|
|
1004
|
-
out2.end();
|
|
1005
|
-
}
|
|
1006
|
-
await done;
|
|
1007
|
-
return removed;
|
|
999
|
+
function orgMcpCatalogOf(raw) {
|
|
1000
|
+
const r = raw ?? {};
|
|
1001
|
+
const servers = (Array.isArray(r.servers) ? r.servers : []).filter((s) => !!s && typeof s === "object" && typeof s.server === "string" && /^[a-z0-9][a-z0-9-]{0,31}$/.test(s.server)).slice(0, 20).map((s) => ({
|
|
1002
|
+
server: s.server,
|
|
1003
|
+
description: typeof s.description === "string" ? s.description : null,
|
|
1004
|
+
approval: typeof s.approval === "string" ? s.approval : "WRITE",
|
|
1005
|
+
tools: (Array.isArray(s.tools) ? s.tools : []).filter((t) => !!t && typeof t === "object" && typeof t.name === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(t.name)).slice(0, 100).map((t) => ({
|
|
1006
|
+
name: t.name,
|
|
1007
|
+
description: typeof t.description === "string" ? t.description : "",
|
|
1008
|
+
inputSchema: t.inputSchema && typeof t.inputSchema === "object" && !Array.isArray(t.inputSchema) ? t.inputSchema : { type: "object" },
|
|
1009
|
+
readOnly: t.readOnly === true
|
|
1010
|
+
}))
|
|
1011
|
+
}));
|
|
1012
|
+
const unavailable = (Array.isArray(r.unavailable) ? r.unavailable : []).filter((u) => !!u && typeof u.server === "string").map((u) => ({ server: String(u.server).slice(0, 32), error: typeof u.error === "string" ? u.error.slice(0, 60) : "MCP_FAILED" }));
|
|
1013
|
+
return { servers, unavailable };
|
|
1008
1014
|
}
|
|
1009
1015
|
|
|
1010
|
-
// src/application/services/workspaceSandbox/backgroundService.ts
|
|
1011
|
-
var import_child_process2 = require("child_process");
|
|
1012
|
-
var import_crypto3 = require("crypto");
|
|
1013
|
-
var import_fs5 = require("fs");
|
|
1014
|
-
var import_path6 = require("path");
|
|
1015
|
-
|
|
1016
1016
|
// src/application/services/workspaceSandbox/localWorkspace.ts
|
|
1017
1017
|
var import_promises4 = require("fs/promises");
|
|
1018
1018
|
var import_os3 = require("os");
|
|
@@ -2532,6 +2532,37 @@ function parseConnectCode(code) {
|
|
|
2532
2532
|
if (!/^[A-Za-z0-9_\-.~+/=]{32,128}$/.test(secret)) return null;
|
|
2533
2533
|
return { sessionId, secret };
|
|
2534
2534
|
}
|
|
2535
|
+
var NotLocalSessionError = class extends Error {
|
|
2536
|
+
};
|
|
2537
|
+
var LocalSessionTransport = class extends HttpSessionTransport {
|
|
2538
|
+
constructor(local) {
|
|
2539
|
+
super({ goneStatuses: [401, 403, 404, 409, 410], ...local });
|
|
2540
|
+
this.local = local;
|
|
2541
|
+
}
|
|
2542
|
+
local;
|
|
2543
|
+
refused = false;
|
|
2544
|
+
async bootstrap() {
|
|
2545
|
+
try {
|
|
2546
|
+
const boot = await super.bootstrap();
|
|
2547
|
+
if (boot.workspaceKind !== "LOCAL") {
|
|
2548
|
+
this.refused = true;
|
|
2549
|
+
if (boot.repo) boot.repo.token = "";
|
|
2550
|
+
throw new NotLocalSessionError("not a local session");
|
|
2551
|
+
}
|
|
2552
|
+
return boot;
|
|
2553
|
+
} catch (e) {
|
|
2554
|
+
this.local.onBootstrapError?.(e);
|
|
2555
|
+
throw e;
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
async pollCommands(waitSeconds, signal) {
|
|
2559
|
+
return (await super.pollCommands(waitSeconds, signal)).filter((c) => !(c.kind === "approval" && this.local.decisions.offer(c.payload)));
|
|
2560
|
+
}
|
|
2561
|
+
async postEvents(events) {
|
|
2562
|
+
if (this.refused) return;
|
|
2563
|
+
await super.postEvents(events);
|
|
2564
|
+
}
|
|
2565
|
+
};
|
|
2535
2566
|
function makeStyle(color) {
|
|
2536
2567
|
const wrap = (open2, close) => (s) => color ? `\x1B[${open2}m${s}\x1B[${close}m` : s;
|
|
2537
2568
|
return { bold: wrap(1, 22), dim: wrap(2, 22), red: wrap(31, 39), green: wrap(32, 39), yellow: wrap(33, 39) };
|
|
@@ -3065,18 +3096,29 @@ function writeFileSafely(file, content, mode = 420) {
|
|
|
3065
3096
|
(0, import_fs5.renameSync)(tmp, file);
|
|
3066
3097
|
}
|
|
3067
3098
|
var failed = (r) => `${r.stderr || r.stdout}`.trim().split("\n").slice(-1)[0]?.slice(0, 200) || `exit ${r.code}`;
|
|
3099
|
+
var LAUNCHD_REPLACE_WAIT_MS = 3e4;
|
|
3100
|
+
var LAUNCHD_POLL_MS = 500;
|
|
3101
|
+
var LAUNCHD_BOOTSTRAP_ATTEMPTS = 6;
|
|
3068
3102
|
async function installLaunchd(env, bin, api) {
|
|
3069
3103
|
const names = serviceNames(api);
|
|
3070
3104
|
const plist = launchAgentPath(env, api);
|
|
3071
3105
|
(0, import_fs5.mkdirSync)(logDir(env), { recursive: true });
|
|
3072
3106
|
writeFileSafely(plist, launchAgentPlist(env, bin, api));
|
|
3073
3107
|
const domain = `gui/${env.uid}`;
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
const
|
|
3108
|
+
const sleep2 = env.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
3109
|
+
const target = `${domain}/${names.label}`;
|
|
3110
|
+
const loaded = async () => (await env.run("launchctl", ["print", target])).code === 0;
|
|
3111
|
+
await env.run("launchctl", ["bootout", target]);
|
|
3112
|
+
for (let waited = 0; waited < LAUNCHD_REPLACE_WAIT_MS && await loaded(); waited += LAUNCHD_POLL_MS) await sleep2(LAUNCHD_POLL_MS);
|
|
3113
|
+
await env.run("launchctl", ["enable", target]);
|
|
3114
|
+
let boot = await env.run("launchctl", ["bootstrap", domain, plist]);
|
|
3115
|
+
for (let attempt = 1; boot.code !== 0 && attempt < LAUNCHD_BOOTSTRAP_ATTEMPTS; attempt++) {
|
|
3116
|
+
await sleep2(LAUNCHD_POLL_MS);
|
|
3117
|
+
boot = await env.run("launchctl", ["bootstrap", domain, plist]);
|
|
3118
|
+
}
|
|
3077
3119
|
if (boot.code === 0) return { ok: true, manager: "launchd", hints: [] };
|
|
3078
3120
|
const legacy = await env.run("launchctl", ["load", "-w", plist]);
|
|
3079
|
-
if (legacy.code === 0) return { ok: true, manager: "launchd", hints: [] };
|
|
3121
|
+
if (legacy.code === 0 && await loaded()) return { ok: true, manager: "launchd", hints: [] };
|
|
3080
3122
|
(0, import_fs5.rmSync)(plist, { force: true });
|
|
3081
3123
|
return { ok: false, code: "LAUNCHD_REFUSED", message: `macOS did not accept the background service (${failed(boot)}). A device management profile may block login items.` };
|
|
3082
3124
|
}
|
|
@@ -11905,6 +11947,8 @@ ${patch}`;
|
|
|
11905
11947
|
/** The engine conversation, without secrets, kept by the API (only when it changed). */
|
|
11906
11948
|
async saveTranscript() {
|
|
11907
11949
|
if (!this.deps.persistTranscript || !this.sdkSessionId || !this.deps.transport.saveTranscript) return;
|
|
11950
|
+
const imported = this.boot?.imported;
|
|
11951
|
+
if (imported?.source === "CLAUDE_CODE" && (this.local && imported.nativeResume || imported.externalId === this.sdkSessionId)) return;
|
|
11908
11952
|
const pack = await packTranscript(this.deps.configDir, this.sdkSessionId, this.redactor);
|
|
11909
11953
|
if (!pack) return;
|
|
11910
11954
|
if ("tooLarge" in pack) {
|
|
@@ -12582,8 +12626,6 @@ var HOME = (0, import_os4.homedir)();
|
|
|
12582
12626
|
var SQ_HOME = (0, import_path16.join)(HOME, ".scalequality");
|
|
12583
12627
|
var ENGINE_HOME = (0, import_path16.join)(SQ_HOME, "workspace");
|
|
12584
12628
|
var credentials = new CredentialStore((0, import_path16.join)(SQ_HOME, "credentials.json"));
|
|
12585
|
-
var NotLocalSessionError = class extends Error {
|
|
12586
|
-
};
|
|
12587
12629
|
function startFailure(e, api) {
|
|
12588
12630
|
if (e instanceof NotLocalSessionError) return 'This code belongs to a cloud workspace session. In the AI Workspace, choose "Use a folder on your computer" to get a code for this command.';
|
|
12589
12631
|
if (e instanceof SessionGoneError) {
|
|
@@ -12619,14 +12661,6 @@ function createLocalEngine(o) {
|
|
|
12619
12661
|
if (o.verbose) say(style.dim(`${o.prefix ?? ""}[warn] ${msg} ${ctx ? JSON.stringify(ctx) : ""}`));
|
|
12620
12662
|
}
|
|
12621
12663
|
};
|
|
12622
|
-
const http = new HttpSessionTransport({
|
|
12623
|
-
baseUrl: o.api,
|
|
12624
|
-
sessionId: o.sessionId,
|
|
12625
|
-
secret: o.secret,
|
|
12626
|
-
// 409 is SESSION_CLOSED on the session routes: end instead of retrying.
|
|
12627
|
-
goneStatuses: [401, 403, 404, 409, 410],
|
|
12628
|
-
userAgent: userAgent(o.mode)
|
|
12629
|
-
});
|
|
12630
12664
|
const decisions = new CommandDecisions();
|
|
12631
12665
|
const gate = new BrowserCommandGate({
|
|
12632
12666
|
root: o.root,
|
|
@@ -12638,31 +12672,16 @@ function createLocalEngine(o) {
|
|
|
12638
12672
|
onWaiting: (command) => say(`${o.prefix ?? ""}${style.yellow("Waiting for your approval in the browser:")} $ ${visibleText(command.replace(/\s+/g, " ")).slice(0, 160)}`)
|
|
12639
12673
|
});
|
|
12640
12674
|
let startError = null;
|
|
12641
|
-
|
|
12642
|
-
|
|
12643
|
-
|
|
12644
|
-
|
|
12645
|
-
|
|
12646
|
-
|
|
12647
|
-
|
|
12648
|
-
|
|
12649
|
-
|
|
12650
|
-
|
|
12651
|
-
return boot;
|
|
12652
|
-
} catch (e) {
|
|
12653
|
-
startError = startFailure(e, o.api);
|
|
12654
|
-
throw e;
|
|
12655
|
-
}
|
|
12656
|
-
},
|
|
12657
|
-
pollCommands: async (w, s) => (await http.pollCommands(w, s)).filter((c) => !(c.kind === "approval" && decisions.offer(c.payload))),
|
|
12658
|
-
postEvents: (ev) => refused ? Promise.resolve() : http.postEvents(ev),
|
|
12659
|
-
callTool: (n, a, id) => http.callTool(n, a, id),
|
|
12660
|
-
openPullRequest: (r) => http.openPullRequest(r),
|
|
12661
|
-
checkpoint: (r) => http.checkpoint(r),
|
|
12662
|
-
// Never called in local mode (the folder is never cloned); the API refuses it anyway.
|
|
12663
|
-
openRepository: (r) => http.openRepository(r),
|
|
12664
|
-
importedHistory: () => http.importedHistory()
|
|
12665
|
-
};
|
|
12675
|
+
const transport = new LocalSessionTransport({
|
|
12676
|
+
baseUrl: o.api,
|
|
12677
|
+
sessionId: o.sessionId,
|
|
12678
|
+
secret: o.secret,
|
|
12679
|
+
userAgent: userAgent(o.mode),
|
|
12680
|
+
decisions,
|
|
12681
|
+
onBootstrapError: (e) => {
|
|
12682
|
+
startError = startFailure(e, o.api);
|
|
12683
|
+
}
|
|
12684
|
+
});
|
|
12666
12685
|
const sources = defaultImportSources(HOME);
|
|
12667
12686
|
return new WorkspaceEngine({
|
|
12668
12687
|
transport,
|
|
@@ -12678,6 +12697,9 @@ function createLocalEngine(o) {
|
|
|
12678
12697
|
return prepared;
|
|
12679
12698
|
},
|
|
12680
12699
|
createMeasurer: null,
|
|
12700
|
+
// The conversation is kept (without secrets) with each checkpoint, as in the cloud: moved to the cloud or
|
|
12701
|
+
// resumed on another computer, the session continues it. An imported Claude Code conversation never leaves.
|
|
12702
|
+
persistTranscript: true,
|
|
12681
12703
|
// A new conversation is named from its first message, through the gateway with the session's credential.
|
|
12682
12704
|
nameConversation: (r) => gatewayTitle(r),
|
|
12683
12705
|
// The copies of files changed outside any repository of the folder, per session, outside the folder.
|