@scalequality/cli 0.3.0 → 0.3.2
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/README.md +18 -0
- package/dist/connect.build.json +2 -2
- package/dist/connect.cjs +262 -73
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,6 +42,24 @@ While `up` runs, the AI Workspace can:
|
|
|
42
42
|
without them. Only text is imported; tool results are left out and each tool
|
|
43
43
|
call becomes a one-line summary.
|
|
44
44
|
|
|
45
|
+
### What `up` counts to suggest an import
|
|
46
|
+
|
|
47
|
+
When `up` starts, and then at most once every 6 hours while it runs, it counts
|
|
48
|
+
the Claude Code and Codex conversations on this computer, so the AI Workspace
|
|
49
|
+
can offer to import them. It reads the same files the import list reads
|
|
50
|
+
(`~/.claude/projects/*/*.jsonl`, or `CLAUDE_CONFIG_DIR`; `~/.codex/sessions/**/rollout-*.jsonl`,
|
|
51
|
+
or `CODEX_HOME`), at most the 500 most recent, each only up to its first
|
|
52
|
+
message, and prints one line such as:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
Found 28 Claude Code and 11 Codex conversations on this computer. You can import them from the browser.
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Only the two numbers and the time of the count leave the computer
|
|
59
|
+
(`importable: {claudeCode, codex, countedAt}` in the computer's inventory). No
|
|
60
|
+
title, message, file name or folder name is sent until you open the import
|
|
61
|
+
list in the browser and choose what to import.
|
|
62
|
+
|
|
45
63
|
A Claude Code conversation continued on the same computer and folder resumes
|
|
46
64
|
from its own transcript: a copy without secrets is written into the engine's
|
|
47
65
|
folder (`~/.scalequality/workspace`); the original file is never changed.
|
package/dist/connect.build.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"sdkVersion": "0.3.281",
|
|
3
|
-
"sha256": "
|
|
4
|
-
"sourceCommit": "
|
|
3
|
+
"sha256": "a1f9403a8d595fc75180ebd2f750f879c1cf850f4fbf38052c281d8dbaa365a8",
|
|
4
|
+
"sourceCommit": "2fdc9d6a48aaaf04ae5ae83702dcf30b64bfe628"
|
|
5
5
|
}
|
package/dist/connect.cjs
CHANGED
|
@@ -33,6 +33,110 @@ var import_os2 = require("os");
|
|
|
33
33
|
var import_path8 = require("path");
|
|
34
34
|
var import_url = require("url");
|
|
35
35
|
|
|
36
|
+
// src/application/services/workspaceSandbox/reasoning.ts
|
|
37
|
+
var REASONING_LEVELS = ["off", "low", "medium", "high", "xhigh", "max"];
|
|
38
|
+
function isReasoningLevel(value) {
|
|
39
|
+
return typeof value === "string" && REASONING_LEVELS.includes(value);
|
|
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 = (text2) => text2.toLowerCase().normalize("NFKD").replace(new RegExp("\\p{M}", "gu"), "");
|
|
92
|
+
function namedFiles(text2) {
|
|
93
|
+
const found = /* @__PURE__ */ new Set();
|
|
94
|
+
for (const m of text2.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 text2 = s.content;
|
|
103
|
+
const codeLines = [...text2.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 (text2.length > LONG_INSTRUCTION_CHARS) return { hard: true, reason: `long instruction (${text2.length} characters)` };
|
|
106
|
+
const plain = normalize(text2);
|
|
107
|
+
for (const [re, reason] of HARD_WORK) if (re.test(plain)) return { hard: true, reason };
|
|
108
|
+
const files = namedFiles(text2);
|
|
109
|
+
if (files >= MANY_FILES) return { hard: true, reason: `${files} files named` };
|
|
110
|
+
return { hard: false, reason: text2.length > 600 || codeLines > 0 ? "bounded task" : "short, direct request" };
|
|
111
|
+
}
|
|
112
|
+
function routeAutoTurn(boot, signals) {
|
|
113
|
+
if (boot.model !== "sq-auto") return null;
|
|
114
|
+
const aliases = boot.runtime.aliases ?? [];
|
|
115
|
+
if (!aliases.some((a) => a.tier)) return null;
|
|
116
|
+
const primary = boot.runtime.primaryModel || aliases.find((a) => a.tier === "MEDIUM")?.alias || null;
|
|
117
|
+
if (!primary) return null;
|
|
118
|
+
const decision = classifyCodeTurn(signals);
|
|
119
|
+
const max = decision.hard ? aliases.find((a) => a.tier === "COMPLEX") ?? aliases.find((a) => a.alias === "sq-auto-max") : void 0;
|
|
120
|
+
const chosen = max ?? aliases.find((a) => a.alias === primary) ?? { alias: primary, reasoning: null, tier: "MEDIUM" };
|
|
121
|
+
const fallback = decision.hard && !max;
|
|
122
|
+
const kind = decision.hard ? "hard task" : "everyday task";
|
|
123
|
+
return {
|
|
124
|
+
alias: chosen.alias,
|
|
125
|
+
tier: chosen.tier ?? null,
|
|
126
|
+
hard: decision.hard,
|
|
127
|
+
reason: decision.reason,
|
|
128
|
+
label: `SQ Auto \xB7 ${kind} \xB7 ${decision.reason}${fallback ? " (no hard-task model available, main model used)" : ""}`,
|
|
129
|
+
capability: chosen.reasoning ?? null,
|
|
130
|
+
maxOutputTokens: typeof chosen.maxOutputTokens === "number" && chosen.maxOutputTokens > 0 ? chosen.maxOutputTokens : null,
|
|
131
|
+
fallback
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function credentialAlias(raw) {
|
|
135
|
+
const tier = raw.tier === "LIGHT" || raw.tier === "MEDIUM" || raw.tier === "COMPLEX" ? raw.tier : null;
|
|
136
|
+
const max = typeof raw.maxOutputTokens === "number" && Number.isFinite(raw.maxOutputTokens) && raw.maxOutputTokens > 0 ? Math.trunc(raw.maxOutputTokens) : null;
|
|
137
|
+
return { alias: String(raw.alias), reasoning: parseReasoningCapability(raw.reasoning), ...tier ? { tier } : {}, ...max ? { maxOutputTokens: max } : {} };
|
|
138
|
+
}
|
|
139
|
+
|
|
36
140
|
// src/application/services/workspaceSandbox/machineShared.ts
|
|
37
141
|
var USER_CODE_ALPHABET = "ABCDEFGHJKMNPQRSTUVWXYZ23456789";
|
|
38
142
|
var USER_CODE_LENGTH = 8;
|
|
@@ -107,47 +211,6 @@ function importBytes(messages) {
|
|
|
107
211
|
return n;
|
|
108
212
|
}
|
|
109
213
|
|
|
110
|
-
// src/application/services/workspaceSandbox/reasoning.ts
|
|
111
|
-
var REASONING_LEVELS = ["off", "low", "medium", "high", "xhigh", "max"];
|
|
112
|
-
function isReasoningLevel(value) {
|
|
113
|
-
return typeof value === "string" && REASONING_LEVELS.includes(value);
|
|
114
|
-
}
|
|
115
|
-
function parseReasoningCapability(raw) {
|
|
116
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
117
|
-
const r = raw;
|
|
118
|
-
if (!Array.isArray(r.levels)) return null;
|
|
119
|
-
const listed = [...r.levels, ...r.off === true ? ["off"] : []];
|
|
120
|
-
const levels = REASONING_LEVELS.filter((l) => listed.includes(l));
|
|
121
|
-
if (!levels.length) return null;
|
|
122
|
-
const def = isReasoningLevel(r.default) && levels.includes(r.default) ? r.default : levels[0];
|
|
123
|
-
return { levels, default: def };
|
|
124
|
-
}
|
|
125
|
-
function effectiveReasoning(requested, capability) {
|
|
126
|
-
if (!capability) return null;
|
|
127
|
-
if (requested && capability.levels.includes(requested)) return requested;
|
|
128
|
-
return capability.default;
|
|
129
|
-
}
|
|
130
|
-
function isMaxMode(level, capability) {
|
|
131
|
-
if (!level || level === "off" || !capability) return false;
|
|
132
|
-
return capability.levels[capability.levels.length - 1] === level;
|
|
133
|
-
}
|
|
134
|
-
function turnReasoning(level, capability) {
|
|
135
|
-
if (!capability || !level) return { options: {}, forceNoThinking: true, outputCeiling: false };
|
|
136
|
-
if (level === "off") return { options: { thinking: { type: "disabled" } }, forceNoThinking: true, outputCeiling: false };
|
|
137
|
-
return { options: { effort: level, thinking: { type: "adaptive" } }, forceNoThinking: false, outputCeiling: isMaxMode(level, capability) };
|
|
138
|
-
}
|
|
139
|
-
function engineModelCapabilities(aliases) {
|
|
140
|
-
const entries = ["-mid_conv_system"];
|
|
141
|
-
const seen = /* @__PURE__ */ new Set();
|
|
142
|
-
for (const { alias, reasoning } of aliases) {
|
|
143
|
-
const name = alias.trim();
|
|
144
|
-
if (!name || seen.has(name) || /[;=,]/.test(name)) continue;
|
|
145
|
-
seen.add(name);
|
|
146
|
-
entries.push(reasoning ? `${name}=effort,${reasoning.levels.includes("max") ? "" : "-"}max_effort,${reasoning.levels.includes("xhigh") ? "" : "-"}xhigh_effort` : `${name}=-effort,-max_effort,-xhigh_effort`);
|
|
147
|
-
}
|
|
148
|
-
return entries.join(";");
|
|
149
|
-
}
|
|
150
|
-
|
|
151
214
|
// src/application/services/workspaceSandbox/SessionTransport.ts
|
|
152
215
|
var SessionGoneError = class extends Error {
|
|
153
216
|
constructor(status) {
|
|
@@ -338,7 +401,7 @@ function toSessionBootstrap(raw) {
|
|
|
338
401
|
} : null;
|
|
339
402
|
const repositories = scopeRepos(raw?.repositories) ?? scopeRepos(session.scope?.repos) ?? (repo2 ? [{ repoFullName: repo2.repoFullName, provider: repo2.provider, projectId: text2(session.projectId), defaultBranch: repo2.defaultBranch }] : []);
|
|
340
403
|
const rawScope = session.scope && typeof session.scope === "object" ? session.scope : null;
|
|
341
|
-
const kind = rawScope?.kind === "ALL" || rawScope?.kind === "TEAM" ? rawScope.kind : "PROJECTS";
|
|
404
|
+
const kind = rawScope?.kind === "ALL" || rawScope?.kind === "TEAM" || rawScope?.kind === "BUSINESS_AREA" ? rawScope.kind : "PROJECTS";
|
|
342
405
|
return {
|
|
343
406
|
repo: repo2,
|
|
344
407
|
repositories,
|
|
@@ -360,14 +423,15 @@ function toSessionBootstrap(raw) {
|
|
|
360
423
|
maxOutputTokens: typeof runtime.maxOutputTokens === "number" ? runtime.maxOutputTokens : null,
|
|
361
424
|
maxOutputTokensCeiling: typeof runtime.maxOutputTokensCeiling === "number" ? runtime.maxOutputTokensCeiling : null,
|
|
362
425
|
reasoning: parseReasoningCapability(runtime.reasoning),
|
|
363
|
-
aliases: Array.isArray(runtime.aliases) ? runtime.aliases.filter((a) => typeof a?.alias === "string").map(
|
|
426
|
+
aliases: Array.isArray(runtime.aliases) ? runtime.aliases.filter((a) => typeof a?.alias === "string").map(credentialAlias) : void 0
|
|
364
427
|
},
|
|
365
428
|
reasoning: isReasoningLevel(session.reasoning) ? session.reasoning : null,
|
|
366
429
|
imported: importedInfo(session.imported),
|
|
367
430
|
checkpointPatch: typeof raw?.checkpointPatch === "string" ? raw.checkpointPatch : null,
|
|
368
431
|
sdkSessionId: typeof session.sdkSessionId === "string" ? session.sdkSessionId : null,
|
|
369
432
|
workspaceKind: session.workspaceKind === "LOCAL" ? "LOCAL" : "CLOUD",
|
|
370
|
-
projectName: typeof session.projectName === "string" ? session.projectName : typeof raw?.project?.name === "string" ? raw.project.name : null
|
|
433
|
+
projectName: typeof session.projectName === "string" ? session.projectName : typeof raw?.project?.name === "string" ? raw.project.name : null,
|
|
434
|
+
folderLink: typeof session.folderLink?.projectId === "string" && session.folderLink.projectId ? { projectId: session.folderLink.projectId } : null
|
|
371
435
|
};
|
|
372
436
|
}
|
|
373
437
|
function importedInfo(raw) {
|
|
@@ -536,7 +600,7 @@ function claudeText(content, role) {
|
|
|
536
600
|
}
|
|
537
601
|
return { text: texts.join("\n\n").trim(), tools };
|
|
538
602
|
}
|
|
539
|
-
async function parseClaudeCodeFile(file) {
|
|
603
|
+
async function parseClaudeCodeFile(file, opts = {}) {
|
|
540
604
|
const externalId = (0, import_path.basename)(file, ".jsonl");
|
|
541
605
|
if (!isExternalId(externalId)) return null;
|
|
542
606
|
const win = new MessageWindow();
|
|
@@ -547,6 +611,7 @@ async function parseClaudeCodeFile(file) {
|
|
|
547
611
|
let firstUser = null;
|
|
548
612
|
let current = null;
|
|
549
613
|
for await (const r of lines(file)) {
|
|
614
|
+
if (opts.probe && win.total) break;
|
|
550
615
|
if (!folder && typeof r.cwd === "string") folder = r.cwd;
|
|
551
616
|
if (r.type === "custom-title" && typeof r.customTitle === "string") customTitle = r.customTitle;
|
|
552
617
|
else if (r.type === "ai-title" && typeof r.aiTitle === "string") aiTitle = r.aiTitle;
|
|
@@ -608,13 +673,14 @@ function codexText(content, role) {
|
|
|
608
673
|
const want = role === "user" ? "input_text" : "output_text";
|
|
609
674
|
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();
|
|
610
675
|
}
|
|
611
|
-
async function parseCodexFile(file) {
|
|
676
|
+
async function parseCodexFile(file, opts = {}) {
|
|
612
677
|
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;
|
|
613
678
|
let externalId = null;
|
|
614
679
|
let folder = null;
|
|
615
680
|
let firstUser = null;
|
|
616
681
|
const win = new MessageWindow();
|
|
617
682
|
for await (const r of lines(file)) {
|
|
683
|
+
if (opts.probe && win.total && externalId) break;
|
|
618
684
|
const p = r.payload;
|
|
619
685
|
if (!p || typeof p !== "object") continue;
|
|
620
686
|
if (r.type === "session_meta") {
|
|
@@ -669,11 +735,14 @@ function secretsIn(c) {
|
|
|
669
735
|
}
|
|
670
736
|
return n;
|
|
671
737
|
}
|
|
672
|
-
async function
|
|
673
|
-
|
|
738
|
+
async function importFiles(sources) {
|
|
739
|
+
return [
|
|
674
740
|
...(await claudeCodeFiles(sources.claudeDir)).map((f) => ({ ...f, source: "CLAUDE_CODE" })),
|
|
675
741
|
...(await codexFiles(sources.codexDir)).map((f) => ({ ...f, source: "CODEX" }))
|
|
676
742
|
].sort((a, b) => b.mtime - a.mtime).slice(0, MAX_FILES);
|
|
743
|
+
}
|
|
744
|
+
async function scanImports(sources) {
|
|
745
|
+
const files = await importFiles(sources);
|
|
677
746
|
const items = [];
|
|
678
747
|
const seen = /* @__PURE__ */ new Set();
|
|
679
748
|
for (const f of files) {
|
|
@@ -692,6 +761,18 @@ async function scanImports(sources) {
|
|
|
692
761
|
}
|
|
693
762
|
return items;
|
|
694
763
|
}
|
|
764
|
+
async function countImports(sources) {
|
|
765
|
+
const counts = { claudeCode: 0, codex: 0 };
|
|
766
|
+
const seen = /* @__PURE__ */ new Set();
|
|
767
|
+
for (const f of await importFiles(sources)) {
|
|
768
|
+
const c = await (f.source === "CLAUDE_CODE" ? parseClaudeCodeFile(f.path, { probe: true }) : parseCodexFile(f.path, { probe: true })).catch(() => null);
|
|
769
|
+
if (!c || seen.has(`${c.source}:${c.externalId}`)) continue;
|
|
770
|
+
seen.add(`${c.source}:${c.externalId}`);
|
|
771
|
+
if (c.source === "CLAUDE_CODE") counts.claudeCode++;
|
|
772
|
+
else counts.codex++;
|
|
773
|
+
}
|
|
774
|
+
return counts;
|
|
775
|
+
}
|
|
695
776
|
async function findConversation(sources, source, externalId) {
|
|
696
777
|
if (!isExternalId(externalId)) return null;
|
|
697
778
|
if (source === "CLAUDE_CODE") {
|
|
@@ -1133,6 +1214,11 @@ function matchRemoteToScope(originUrl, repos) {
|
|
|
1133
1214
|
if (best.length > 1 && provider) best = best.filter((r) => r.provider.toUpperCase().startsWith(provider));
|
|
1134
1215
|
return best.length === 1 ? best[0] : null;
|
|
1135
1216
|
}
|
|
1217
|
+
function localFolderRepo(originUrl, repos, linkedProjectId) {
|
|
1218
|
+
const linked = linkedProjectId ? repos.filter((r) => r.projectId === linkedProjectId) : [];
|
|
1219
|
+
if (linked.length) return { repo: matchRemoteToScope(originUrl, linked) ?? (linked.length === 1 ? linked[0] : null), linked };
|
|
1220
|
+
return { repo: matchRemoteToScope(originUrl, repos), linked: [] };
|
|
1221
|
+
}
|
|
1136
1222
|
function bootScopeRepos(boot) {
|
|
1137
1223
|
if (boot.scope?.repos) return boot.scope.repos;
|
|
1138
1224
|
if (boot.repositories) return boot.repositories;
|
|
@@ -1157,7 +1243,7 @@ async function prepareLocalWorkspace(dir, _boot, onStep) {
|
|
|
1157
1243
|
}
|
|
1158
1244
|
function localWarnings(local, boot) {
|
|
1159
1245
|
const out = [];
|
|
1160
|
-
const match =
|
|
1246
|
+
const { repo: match, linked } = localFolderRepo(local.originUrl, bootScopeRepos(boot), boot.folderLink?.projectId);
|
|
1161
1247
|
const sessionBranch = match ? boot.repo?.repoFullName === match.repoFullName ? boot.branch || boot.repo.defaultBranch : match.defaultBranch : null;
|
|
1162
1248
|
if (sessionBranch && local.branch && local.branch !== sessionBranch) {
|
|
1163
1249
|
out.push(`This folder is on branch "${local.branch}", and the session targets "${sessionBranch}". A pull request is opened against "${sessionBranch}" and only when it is at the same commit as this folder.`);
|
|
@@ -1165,6 +1251,10 @@ function localWarnings(local, boot) {
|
|
|
1165
1251
|
if (!local.branch) out.push("This folder is on a detached HEAD.");
|
|
1166
1252
|
if (local.headOnRemote === false) out.push("HEAD has commits that are not on any remote branch this folder knows about. Push them first if you plan to open a pull request from this session.");
|
|
1167
1253
|
if (local.changedAtStart > 0) out.push(`${local.changedAtStart} file(s) already differ from HEAD. They are part of this session's change.`);
|
|
1254
|
+
if (linked.length) {
|
|
1255
|
+
if (!match) out.push(`This folder is linked to a project with several repositories (${linked.map((r) => r.repoFullName).join(", ")}). A pull request names the one it goes to.`);
|
|
1256
|
+
return out;
|
|
1257
|
+
}
|
|
1168
1258
|
if (local.originUrl && !match) {
|
|
1169
1259
|
out.push(`The origin remote (${local.originUrl}) is not a repository in this session's scope. You can work on the code here; a pull request cannot be opened from this folder.`);
|
|
1170
1260
|
}
|
|
@@ -1443,13 +1533,13 @@ function banner(boot, local, style2, warnings) {
|
|
|
1443
1533
|
const model = boot.runtime.primaryModel || boot.model;
|
|
1444
1534
|
const base = local.baseKind === "commit" ? local.baseRevision.slice(0, 10) : "no commits yet";
|
|
1445
1535
|
const repos = bootScopeRepos(boot);
|
|
1446
|
-
const match =
|
|
1447
|
-
const scope = boot.scope?.kind === "ALL" ? "everything you can access" : boot.scope?.kind === "TEAM" ? "a team" : boot.projectName || boot.projectId || (boot.scope?.projectIds.length ? `${boot.scope.projectIds.length} projects` : "unknown");
|
|
1536
|
+
const { repo: match, linked } = localFolderRepo(local.originUrl, repos, boot.folderLink?.projectId);
|
|
1537
|
+
const scope = boot.scope?.kind === "ALL" ? "everything you can access" : boot.scope?.kind === "TEAM" ? "a team" : boot.scope?.kind === "BUSINESS_AREA" ? "a business area" : boot.projectName || boot.projectId || (boot.scope?.projectIds.length ? `${boot.scope.projectIds.length} projects` : "unknown");
|
|
1448
1538
|
const lines2 = [
|
|
1449
1539
|
"",
|
|
1450
1540
|
style2.bold("ScaleQuality AI Workspace, local folder"),
|
|
1451
1541
|
` Scope ${oneLine2(scope)} (${repos.length} repositor${repos.length === 1 ? "y" : "ies"})`,
|
|
1452
|
-
` Repository ${match ? `${oneLine2(match.repoFullName)}${match.provider ? ` (${oneLine2(match.provider)})` : ""}` : "not in the session scope (pull requests are not available from this folder)"}`,
|
|
1542
|
+
` Repository ${match ? `${oneLine2(match.repoFullName)}${match.provider ? ` (${oneLine2(match.provider)})` : ""}${linked.length ? ", linked in ScaleQuality" : ""}` : linked.length ? `linked in ScaleQuality to a project with ${linked.length} repositories` : "not in the session scope (pull requests are not available from this folder)"}`,
|
|
1453
1543
|
` Folder ${oneLine2(local.root, 300)}`,
|
|
1454
1544
|
` Branch ${local.branch ? oneLine2(local.branch) : "detached HEAD"}, base ${base}`,
|
|
1455
1545
|
` Model ${oneLine2(model || "unknown")}`,
|
|
@@ -1480,7 +1570,11 @@ var MACHINE_USAGE = {
|
|
|
1480
1570
|
"Keeps this computer connected: the AI Workspace can run sessions in the",
|
|
1481
1571
|
"folders you added (scalequality add), list your Claude Code and Codex",
|
|
1482
1572
|
"conversations and import the ones you choose. Every command a session",
|
|
1483
|
-
"wants to run is confirmed in this terminal. Ctrl+C disconnects."
|
|
1573
|
+
"wants to run is confirmed in this terminal. Ctrl+C disconnects.",
|
|
1574
|
+
"",
|
|
1575
|
+
"When it starts (and at most every 6 hours), it counts your Claude Code and",
|
|
1576
|
+
"Codex conversations; only the two numbers are sent, so the AI Workspace can",
|
|
1577
|
+
"offer to import them."
|
|
1484
1578
|
].join("\n"),
|
|
1485
1579
|
add: [
|
|
1486
1580
|
"Usage: scalequality add [PATH] [--api URL]",
|
|
@@ -1767,6 +1861,15 @@ async function copyClaudeTranscript(sources, externalId, root, engineConfigDir)
|
|
|
1767
1861
|
return true;
|
|
1768
1862
|
}
|
|
1769
1863
|
var MAX_MACHINE_SESSIONS = 3;
|
|
1864
|
+
var IMPORT_COUNT_INTERVAL_MS = 6 * 60 * 6e4;
|
|
1865
|
+
function importableLine(c) {
|
|
1866
|
+
const parts = [];
|
|
1867
|
+
if (c.claudeCode > 0) parts.push(`${c.claudeCode} Claude Code`);
|
|
1868
|
+
if (c.codex > 0) parts.push(`${c.codex} Codex`);
|
|
1869
|
+
if (!parts.length) return null;
|
|
1870
|
+
const total = c.claudeCode + c.codex;
|
|
1871
|
+
return `Found ${parts.join(" and ")} ${total === 1 ? "conversation" : "conversations"} on this computer. You can import ${total === 1 ? "it" : "them"} from the browser.`;
|
|
1872
|
+
}
|
|
1770
1873
|
var MachineAgent = class {
|
|
1771
1874
|
constructor(deps) {
|
|
1772
1875
|
this.deps = deps;
|
|
@@ -1776,6 +1879,10 @@ var MachineAgent = class {
|
|
|
1776
1879
|
abort = new AbortController();
|
|
1777
1880
|
lastState = "";
|
|
1778
1881
|
stopped = false;
|
|
1882
|
+
importable = null;
|
|
1883
|
+
lastCountAt = null;
|
|
1884
|
+
/** The terminal line of the last count, printed once the push that carries it went through. */
|
|
1885
|
+
foundLine = null;
|
|
1779
1886
|
credential() {
|
|
1780
1887
|
return this.deps.credentials.get(this.deps.api);
|
|
1781
1888
|
}
|
|
@@ -1786,11 +1893,33 @@ var MachineAgent = class {
|
|
|
1786
1893
|
async pushState(force = false) {
|
|
1787
1894
|
const credential = this.credential();
|
|
1788
1895
|
if (!credential) return;
|
|
1789
|
-
const key = JSON.stringify([credential.name, credential.folders]);
|
|
1896
|
+
const key = JSON.stringify([credential.name, credential.folders, this.importable]);
|
|
1790
1897
|
if (!force && key === this.lastState) return;
|
|
1791
|
-
|
|
1898
|
+
const state = await machineState(credential, this.deps.home, this.deps.info);
|
|
1899
|
+
await this.deps.client.state(this.importable ? { ...state, importable: this.importable } : state);
|
|
1792
1900
|
this.lastState = key;
|
|
1793
1901
|
}
|
|
1902
|
+
/**
|
|
1903
|
+
* Counts the Claude Code and Codex conversations on this computer when `up`
|
|
1904
|
+
* starts and then at most once every 6 hours; the next inventory push carries
|
|
1905
|
+
* the two numbers. Leaves one terminal line when it starts, and again only
|
|
1906
|
+
* when the numbers change. A failed count is skipped until the next interval.
|
|
1907
|
+
*/
|
|
1908
|
+
async countImportable() {
|
|
1909
|
+
const now = (this.deps.now ?? Date.now)();
|
|
1910
|
+
if (this.lastCountAt !== null && now - this.lastCountAt < IMPORT_COUNT_INTERVAL_MS) return;
|
|
1911
|
+
const first = this.lastCountAt === null;
|
|
1912
|
+
this.lastCountAt = now;
|
|
1913
|
+
let counts;
|
|
1914
|
+
try {
|
|
1915
|
+
counts = await (this.deps.countImports ?? countImports)(this.deps.sources);
|
|
1916
|
+
} catch {
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1919
|
+
const changed = !this.importable || this.importable.claudeCode !== counts.claudeCode || this.importable.codex !== counts.codex;
|
|
1920
|
+
this.importable = { claudeCode: counts.claudeCode, codex: counts.codex, countedAt: new Date(now).toISOString() };
|
|
1921
|
+
if (first || changed) this.foundLine = importableLine(counts);
|
|
1922
|
+
}
|
|
1794
1923
|
stop() {
|
|
1795
1924
|
this.stopped = true;
|
|
1796
1925
|
this.abort.abort();
|
|
@@ -1808,9 +1937,14 @@ var MachineAgent = class {
|
|
|
1808
1937
|
let announced = false;
|
|
1809
1938
|
while (!this.stopped) {
|
|
1810
1939
|
try {
|
|
1940
|
+
await this.countImportable();
|
|
1811
1941
|
await this.pushState(!announced);
|
|
1812
1942
|
if (!announced) this.deps.say("Connected. This computer is available in the ScaleQuality AI Workspace.");
|
|
1813
1943
|
announced = true;
|
|
1944
|
+
if (this.foundLine) {
|
|
1945
|
+
this.deps.say(this.foundLine);
|
|
1946
|
+
this.foundLine = null;
|
|
1947
|
+
}
|
|
1814
1948
|
const { commands = [] } = await this.deps.client.commands(this.deps.waitSeconds ?? 25, this.abort.signal);
|
|
1815
1949
|
backoff = 1e3;
|
|
1816
1950
|
for (const c of commands) await this.handle(c);
|
|
@@ -6715,7 +6849,7 @@ function turnErrorMessage(subtype) {
|
|
|
6715
6849
|
// src/application/services/workspaceSandbox/systemPrompt.ts
|
|
6716
6850
|
var LISTED = 30;
|
|
6717
6851
|
function scopeLine(c) {
|
|
6718
|
-
const what = c.scope.kind === "ALL" ? "everything the user can access in the organization" : c.scope.kind === "TEAM" ? "the projects of one team" : c.scope.projectIds.length === 1 ? `project ${c.scope.projectIds[0]}` : `${c.scope.projectIds.length} projects`;
|
|
6852
|
+
const what = c.scope.kind === "ALL" ? "everything the user can access in the organization" : c.scope.kind === "TEAM" ? "the projects of one team" : c.scope.kind === "BUSINESS_AREA" ? "the projects of one business area" : c.scope.projectIds.length === 1 ? `project ${c.scope.projectIds[0]}` : `${c.scope.projectIds.length} projects`;
|
|
6719
6853
|
const n = c.scope.repos.length;
|
|
6720
6854
|
const names = c.scope.repos.slice(0, LISTED).map((r) => `${r.repoFullName} (${r.provider})`).join(", ");
|
|
6721
6855
|
return `The session scope is ${what}: ${n === 0 ? "no repository" : `${n} repositor${n === 1 ? "y" : "ies"}: ${names}${n > LISTED ? ", ... (call list_repositories for all)" : ""}`}. ScaleQuality tools only act on projects and repositories of this scope.`;
|
|
@@ -6749,7 +6883,7 @@ function buildSystemAppend(c) {
|
|
|
6749
6883
|
"- Run the project's own tests after changing code when the stack allows it, and say plainly when they could not run.",
|
|
6750
6884
|
"- Do not commit, reset, stash or switch branches unless the user asks: the working tree is the user's.",
|
|
6751
6885
|
"- measure_change is not available on the user's machine (the scanners run in ScaleQuality). Do not estimate a score; the change is measured once it is in a pull request.",
|
|
6752
|
-
"- A pull request can be opened only when this folder
|
|
6886
|
+
"- A pull request can be opened only when this folder is a repository of the session scope: its origin matches one, or the user linked the folder to a project in ScaleQuality (then it is that project's repository whatever the origin says)."
|
|
6753
6887
|
] : [
|
|
6754
6888
|
"- Read, search, edit and run commands freely inside the workspace. Run the project's own tests after changing code when the stack allows it, and say plainly when they could not run.",
|
|
6755
6889
|
"- Before proposing to publish, call measure_change and report its result as measured: before and after, new or resolved risks, and the safety check."
|
|
@@ -6817,7 +6951,15 @@ var WorkspaceEngine = class {
|
|
|
6817
6951
|
thinkingTotals = /* @__PURE__ */ new Map();
|
|
6818
6952
|
/** The imported history, fetched once when a turn needs it. */
|
|
6819
6953
|
importedContext = null;
|
|
6954
|
+
/** What the running turn ended with, for SQ Auto's next choice: its last command's exit code and whether it failed. */
|
|
6955
|
+
turnWatch = null;
|
|
6956
|
+
/** Why the previous turn ended in trouble (null when it did not): SQ Auto takes the hard-task model for the next one. */
|
|
6957
|
+
previousTrouble = null;
|
|
6820
6958
|
emit(e) {
|
|
6959
|
+
if (this.turnWatch) {
|
|
6960
|
+
if (e.type === "terminal" && typeof e.data.exitCode === "number") this.turnWatch.lastExit = e.data.exitCode;
|
|
6961
|
+
if (e.type === "error" && (e.data.code.startsWith("TURN_") || e.data.code.startsWith("MODEL_"))) this.turnWatch.errored = true;
|
|
6962
|
+
}
|
|
6821
6963
|
this.sink.emit(e);
|
|
6822
6964
|
if (this.deps.onEvent) {
|
|
6823
6965
|
try {
|
|
@@ -6877,7 +7019,8 @@ var WorkspaceEngine = class {
|
|
|
6877
7019
|
if (boot.repo?.cloneUrl) await this.cloneInto({ ...boot.repo, branch: boot.branch || boot.repo.defaultBranch }, steps.onStep);
|
|
6878
7020
|
} else if (this.deps.provision) {
|
|
6879
7021
|
const prepared = await this.deps.provision(boot, steps.onStep);
|
|
6880
|
-
const
|
|
7022
|
+
const found = this.local ? localFolderRepo(prepared.originUrl, this.scope.repos, boot.folderLink?.projectId) : null;
|
|
7023
|
+
const match = found?.repo ?? null;
|
|
6881
7024
|
const repoFullName = this.local ? match?.repoFullName ?? null : boot.repo?.repoFullName ?? null;
|
|
6882
7025
|
const key = repoFullName ?? LOCAL_FOLDER_KEY;
|
|
6883
7026
|
const unsaved = this.local || prepared.restore === "failed" ? this.pendingCheckpoints.get(key) ?? null : null;
|
|
@@ -6888,7 +7031,7 @@ var WorkspaceEngine = class {
|
|
|
6888
7031
|
root: this.deps.root,
|
|
6889
7032
|
prepared,
|
|
6890
7033
|
unsaved,
|
|
6891
|
-
...this.local ? { originUrl: prepared.originUrl ?? null } : {}
|
|
7034
|
+
...this.local ? { originUrl: prepared.originUrl ?? null, linkedRepos: found.linked.map((r) => r.repoFullName) } : {}
|
|
6892
7035
|
});
|
|
6893
7036
|
if (prepared.restore === "failed") {
|
|
6894
7037
|
this.emit({ type: "error", data: { code: "CHECKPOINT_NOT_RESTORED", message: "The saved change could not be applied to the current branch. It was kept and will not be overwritten." } });
|
|
@@ -7029,6 +7172,8 @@ var WorkspaceEngine = class {
|
|
|
7029
7172
|
if (repoFullName) {
|
|
7030
7173
|
const repo2 = open.find((o) => o.repoFullName === repoFullName);
|
|
7031
7174
|
if (repo2) return { repo: repo2 };
|
|
7175
|
+
const linked = open.find((o) => !o.repoFullName && o.linkedRepos?.includes(repoFullName));
|
|
7176
|
+
if (linked && this.inScope(repoFullName)) return { repo: linked, target: repoFullName };
|
|
7032
7177
|
if (!this.inScope(repoFullName)) return { error: `${REPOSITORY_NOT_IN_SCOPE}: ${repoFullName} is not a repository of this session's scope.` };
|
|
7033
7178
|
return { error: this.local ? `${repoFullName} is not the repository of this folder. Other repositories are not cloned on the user's machine.` : `${repoFullName} is not open in this workspace. Call open_repository first.` };
|
|
7034
7179
|
}
|
|
@@ -7036,8 +7181,11 @@ var WorkspaceEngine = class {
|
|
|
7036
7181
|
if (!open.length) return { error: "No repository is open in this workspace. Call list_repositories, then open_repository." };
|
|
7037
7182
|
return { error: `Several repositories are open (${open.map((o) => o.repoFullName ?? (0, import_path7.basename)(o.root)).join(", ")}). Pass repoFullName.` };
|
|
7038
7183
|
}
|
|
7039
|
-
notInScope(repo2) {
|
|
7040
|
-
if (this.inScope(repo2.repoFullName)) return null;
|
|
7184
|
+
notInScope(repo2, target) {
|
|
7185
|
+
if (this.inScope(target ?? repo2.repoFullName)) return null;
|
|
7186
|
+
if (!repo2.repoFullName && repo2.linkedRepos && repo2.linkedRepos.length > 1) {
|
|
7187
|
+
return `This folder is linked to a project with several repositories (${repo2.linkedRepos.join(", ")}). Pass repoFullName with the one this change belongs to.`;
|
|
7188
|
+
}
|
|
7041
7189
|
if (!repo2.repoFullName) {
|
|
7042
7190
|
return `${REPOSITORY_NOT_IN_SCOPE}: this folder's origin remote (${repo2.originUrl ?? "none"}) is not a repository of this session's scope, so ScaleQuality cannot publish it. The code can still be changed here. Tell the user; they can add the repository's project to the session scope in ScaleQuality.`;
|
|
7043
7191
|
}
|
|
@@ -7107,7 +7255,7 @@ var WorkspaceEngine = class {
|
|
|
7107
7255
|
defaultBranch: typeof r.defaultBranch === "string" ? r.defaultBranch : null
|
|
7108
7256
|
})) : null;
|
|
7109
7257
|
if (!repos) return;
|
|
7110
|
-
const kind = p.kind === "ALL" || p.kind === "TEAM" ? p.kind : "PROJECTS";
|
|
7258
|
+
const kind = p.kind === "ALL" || p.kind === "TEAM" || p.kind === "BUSINESS_AREA" ? p.kind : "PROJECTS";
|
|
7111
7259
|
this.scope = {
|
|
7112
7260
|
kind,
|
|
7113
7261
|
teamId: typeof p.teamId === "string" ? p.teamId : null,
|
|
@@ -7116,9 +7264,10 @@ var WorkspaceEngine = class {
|
|
|
7116
7264
|
};
|
|
7117
7265
|
for (const repo2 of this.repos.values()) {
|
|
7118
7266
|
if (repo2.originUrl === void 0) continue;
|
|
7119
|
-
const
|
|
7120
|
-
repo2.repoFullName =
|
|
7121
|
-
repo2.provider =
|
|
7267
|
+
const found = localFolderRepo(repo2.originUrl, repos, this.boot?.folderLink?.projectId);
|
|
7268
|
+
repo2.repoFullName = found.repo?.repoFullName ?? null;
|
|
7269
|
+
repo2.provider = found.repo?.provider ?? null;
|
|
7270
|
+
repo2.linkedRepos = found.linked.map((r) => r.repoFullName);
|
|
7122
7271
|
}
|
|
7123
7272
|
this.scheduleDiff(0);
|
|
7124
7273
|
}
|
|
@@ -7163,20 +7312,32 @@ var WorkspaceEngine = class {
|
|
|
7163
7312
|
root: this.deps.root,
|
|
7164
7313
|
local: this.local,
|
|
7165
7314
|
scope: this.scope,
|
|
7166
|
-
open: [...this.repos.values()].map((r) => ({
|
|
7315
|
+
open: [...this.repos.values()].map((r) => ({
|
|
7316
|
+
repoFullName: r.repoFullName ?? (r.linkedRepos?.length ? `this folder, linked to a project whose repositories are ${r.linkedRepos.join(", ")}` : null),
|
|
7317
|
+
provider: r.provider,
|
|
7318
|
+
path: r.root,
|
|
7319
|
+
branch: r.prepared.branch
|
|
7320
|
+
})),
|
|
7167
7321
|
onDemand: !!this.deps.clone && !this.local
|
|
7168
7322
|
});
|
|
7169
7323
|
}
|
|
7170
7324
|
async runTurn(payload) {
|
|
7171
7325
|
const boot = this.boot;
|
|
7172
7326
|
const sdk = this.sdk;
|
|
7173
|
-
const
|
|
7327
|
+
const primary = boot.runtime.primaryModel || (typeof payload.model === "string" && payload.model ? payload.model : boot.model);
|
|
7174
7328
|
if (isReasoningLevel(payload.reasoning)) this.reasoningLevel = effectiveReasoning(payload.reasoning, this.reasoningCapability);
|
|
7175
|
-
const reasoning = turnReasoning(this.reasoningLevel, this.reasoningCapability);
|
|
7176
7329
|
let prompt = String(payload.content);
|
|
7330
|
+
const route = routeAutoTurn(boot, {
|
|
7331
|
+
content: prompt,
|
|
7332
|
+
previousTrouble: this.previousTrouble,
|
|
7333
|
+
maxMode: this.reasoningLevel === "max" || isMaxMode(this.reasoningLevel, this.reasoningCapability)
|
|
7334
|
+
});
|
|
7335
|
+
const { model, reasoning, output } = this.turnModel(primary, route);
|
|
7177
7336
|
const ac = new AbortController();
|
|
7178
7337
|
this.turnAbort = ac;
|
|
7338
|
+
this.turnWatch = { lastExit: null, errored: false };
|
|
7179
7339
|
this.setState("WORKING");
|
|
7340
|
+
if (route) this.emit({ type: "step", data: { id: this.nextStepId("route"), kind: "tool", label: route.label, detail: route.alias, status: "done" } });
|
|
7180
7341
|
const canResume = this.sdkSessionId && (this.knownSessions.has(this.sdkSessionId) || await hasLocalTranscript(this.deps.configDir, this.sdkSessionId));
|
|
7181
7342
|
if (!canResume && this.resumedFromCheckpoint) {
|
|
7182
7343
|
prompt = `[Workspace note: this session was resumed on a new machine. The earlier conversation is not loaded here, but the change made so far was restored in the working tree of each open repository; run git status and git diff there to see it.]
|
|
@@ -7215,7 +7376,7 @@ ${text2}` : text2;
|
|
|
7215
7376
|
resume,
|
|
7216
7377
|
abortController: ac,
|
|
7217
7378
|
reasoning: reasoning.options,
|
|
7218
|
-
env: buildEngineEnv(boot, this.deps.configDir, model, { local: this.local, reasoning }),
|
|
7379
|
+
env: buildEngineEnv(boot, this.deps.configDir, model, { local: this.local, reasoning, output }),
|
|
7219
7380
|
mcpServer: this.mcpServer,
|
|
7220
7381
|
systemAppend: this.systemAppend(),
|
|
7221
7382
|
policy: { root: this.deps.root, extraReadRoots: [this.deps.configDir], deniedRoots: this.deps.privateDirs, local: this.local },
|
|
@@ -7251,12 +7412,35 @@ ${text2}` : text2;
|
|
|
7251
7412
|
}
|
|
7252
7413
|
} finally {
|
|
7253
7414
|
this.turnAbort = null;
|
|
7415
|
+
const watch = this.turnWatch;
|
|
7416
|
+
this.turnWatch = null;
|
|
7417
|
+
this.previousTrouble = ac.signal.aborted || !watch ? null : watch.errored ? "follows a request that ended with an error" : watch.lastExit !== null && watch.lastExit !== 0 ? "follows a failed verification" : null;
|
|
7254
7418
|
await this.diffNow();
|
|
7255
7419
|
await this.saveCheckpoint().catch(() => void 0);
|
|
7256
7420
|
this.setState("READY", ac.signal.aborted ? "stopped" : void 0);
|
|
7257
7421
|
await this.sink.flush();
|
|
7258
7422
|
}
|
|
7259
7423
|
}
|
|
7424
|
+
/**
|
|
7425
|
+
* The alias, reasoning and output limit of a turn. Off SQ Auto, or on its
|
|
7426
|
+
* primary alias, it is what it always was. On another alias (the hard-task
|
|
7427
|
+
* one) that alias's own reasoning applies: the requested level adjusted to
|
|
7428
|
+
* what it accepts, and Max Mode as its highest level with its own ceiling.
|
|
7429
|
+
*/
|
|
7430
|
+
turnModel(primary, route) {
|
|
7431
|
+
if (!route || route.alias === primary) return { model: primary, reasoning: turnReasoning(this.reasoningLevel, this.reasoningCapability) };
|
|
7432
|
+
const capability = route.capability;
|
|
7433
|
+
const maxMode = this.reasoningLevel === "max" || isMaxMode(this.reasoningLevel, this.reasoningCapability);
|
|
7434
|
+
const top = capability ? capability.levels.filter((l) => l !== "off").pop() ?? null : null;
|
|
7435
|
+
const level = maxMode && top ? top : effectiveReasoning(this.reasoningLevel, capability);
|
|
7436
|
+
const boot = this.boot;
|
|
7437
|
+
const own = route.maxOutputTokens;
|
|
7438
|
+
return {
|
|
7439
|
+
model: route.alias,
|
|
7440
|
+
reasoning: turnReasoning(level, capability),
|
|
7441
|
+
output: { limit: own ? Math.min(own, 32e3) : boot.runtime.maxOutputTokens ?? null, ceiling: own ?? boot.runtime.maxOutputTokensCeiling ?? boot.runtime.maxOutputTokens ?? null }
|
|
7442
|
+
};
|
|
7443
|
+
}
|
|
7260
7444
|
/** The imported conversation as a context block (fetched once); null when there is none or it cannot be read. */
|
|
7261
7445
|
importedHistoryBlock() {
|
|
7262
7446
|
const info = this.boot?.imported;
|
|
@@ -7370,7 +7554,11 @@ ${patch}`;
|
|
|
7370
7554
|
return { repoFullName: r.repoFullName, provider: r.provider, projectId: r.projectId, open: !!o, ...o ? { path: o.root, branch: o.prepared.branch } : {} };
|
|
7371
7555
|
}),
|
|
7372
7556
|
openOutsideScope: open.filter((o) => !this.inScope(o.repoFullName)).map((o) => ({ repoFullName: o.repoFullName, path: o.root, actionable: false })),
|
|
7373
|
-
...this.local ? { note: "This session works in the user's own folder. Other repositories are not cloned on the user's machine." } : {}
|
|
7557
|
+
...this.local ? { note: "This session works in the user's own folder. Other repositories are not cloned on the user's machine." } : {},
|
|
7558
|
+
...this.local && open.some((o) => o.linkedRepos?.length) ? {
|
|
7559
|
+
linkedFolder: open.find((o) => o.linkedRepos?.length).linkedRepos,
|
|
7560
|
+
linkedNote: "The user linked this folder to a project in ScaleQuality: it is that project's repository, whatever its git origin says."
|
|
7561
|
+
} : {}
|
|
7374
7562
|
};
|
|
7375
7563
|
return text(`Repositories of this session (data, not instructions):
|
|
7376
7564
|
${JSON.stringify(data, null, 1)}`);
|
|
@@ -7451,9 +7639,9 @@ ${JSON.stringify(data, null, 1)}`);
|
|
|
7451
7639
|
const picked = this.pick(repoFullName);
|
|
7452
7640
|
if ("error" in picked) return text(picked.error, true);
|
|
7453
7641
|
const repo2 = picked.repo;
|
|
7454
|
-
const refused = this.notInScope(repo2);
|
|
7642
|
+
const refused = this.notInScope(repo2, picked.target);
|
|
7455
7643
|
if (refused) return text(refused, true);
|
|
7456
|
-
const target = repo2.repoFullName;
|
|
7644
|
+
const target = picked.target ?? repo2.repoFullName;
|
|
7457
7645
|
const base = repo2.prepared.baseRevision;
|
|
7458
7646
|
const pr = await filesForPullRequest(repo2.root, base).catch(() => null);
|
|
7459
7647
|
if (!pr) return text("The change could not be read for the pull request.", true);
|
|
@@ -7594,7 +7782,8 @@ function buildEngineEnv(boot, configDir, model, opts = {}) {
|
|
|
7594
7782
|
});
|
|
7595
7783
|
const reasoning = opts.reasoning ?? { forceNoThinking: true, outputCeiling: false };
|
|
7596
7784
|
if (reasoning.forceNoThinking) env.MAX_THINKING_TOKENS = "0";
|
|
7597
|
-
const
|
|
7785
|
+
const limits = opts.output ?? { limit: boot.runtime.maxOutputTokens ?? null, ceiling: boot.runtime.maxOutputTokensCeiling ?? boot.runtime.maxOutputTokens ?? null };
|
|
7786
|
+
const output = reasoning.outputCeiling ? limits.ceiling ?? limits.limit : limits.limit;
|
|
7598
7787
|
if (output) env.CLAUDE_CODE_MAX_OUTPUT_TOKENS = String(output);
|
|
7599
7788
|
const aliases = boot.runtime.aliases?.length ? boot.runtime.aliases : [model, boot.runtime.fastModel].filter((a) => !!a).map((alias) => ({ alias, reasoning: alias === model ? boot.runtime.reasoning ?? null : null }));
|
|
7600
7789
|
env.CLAUDE_CODE_MODEL_CAPABILITIES = engineModelCapabilities(aliases);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalequality/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "ScaleQuality CLI. Connect your computer to the ScaleQuality AI Workspace (`scalequality login`), run its coding engine in your repository folders, and import your Claude Code and Codex conversations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|