@scalequality/cli 0.4.8 → 0.4.9-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/connect.build.json +2 -2
- package/dist/connect.cjs +267 -41
- package/package.json +1 -1
package/dist/connect.build.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"sdkVersion": "0.3.281",
|
|
3
|
-
"sha256": "
|
|
4
|
-
"sourceCommit": "
|
|
3
|
+
"sha256": "5ad74aa136be8c43759fe4a89f977345c3af7ba6fea4c85657bab56398b3da4f",
|
|
4
|
+
"sourceCommit": "38ed6bf736d29da672865dfedeed01901e57b828"
|
|
5
5
|
}
|
package/dist/connect.cjs
CHANGED
|
@@ -2602,6 +2602,28 @@ var ConsoleLog = class {
|
|
|
2602
2602
|
}
|
|
2603
2603
|
}
|
|
2604
2604
|
};
|
|
2605
|
+
function turnTimingLine(ctx) {
|
|
2606
|
+
if (!ctx || ctx.event !== "WORKSPACE_TURN_TIMING") return null;
|
|
2607
|
+
const ms = (k) => typeof ctx[k] === "number" ? ctx[k] : null;
|
|
2608
|
+
const secs = (v) => v >= 1e4 ? `${(v / 1e3).toFixed(1)} s` : `${Math.round(v)} ms`;
|
|
2609
|
+
const parts = [];
|
|
2610
|
+
const first = ms("firstModelEventMs");
|
|
2611
|
+
parts.push(`first model event ${first === null ? "none" : secs(first)}`);
|
|
2612
|
+
const phases = [
|
|
2613
|
+
ms("prepareMs") !== null ? `prepare ${secs(ms("prepareMs"))}` : null,
|
|
2614
|
+
ctx.warmStart === true ? "engine started ahead" : ctx.warmStart === false ? "engine started for it" : null,
|
|
2615
|
+
ms("engineReadyMs") !== null ? `engine ready ${secs(ms("engineReadyMs"))}` : null,
|
|
2616
|
+
ms("checkpointMs") !== null ? `saved point ${secs(ms("checkpointMs"))}` : null,
|
|
2617
|
+
ms("toolWaitMs") !== null ? `tools waited ${secs(ms("toolWaitMs"))}` : null
|
|
2618
|
+
].filter(Boolean);
|
|
2619
|
+
if (phases.length) parts.push(`(${phases.join(", ")})`);
|
|
2620
|
+
const permissions = ms("permissions");
|
|
2621
|
+
if (permissions) parts.push(`; ${permissions} permission${permissions === 1 ? "" : "s"} ${secs(ms("permissionMs") ?? 0)}`);
|
|
2622
|
+
const total = ms("totalMs");
|
|
2623
|
+
if (total !== null) parts.push(`; total ${secs(total)}`);
|
|
2624
|
+
if (typeof ctx.outcome === "string" && ctx.outcome !== "completed") parts.push(`(${ctx.outcome})`);
|
|
2625
|
+
return `Timing: ${parts.join(" ").replace(/ ;/g, ";")}`;
|
|
2626
|
+
}
|
|
2605
2627
|
function banner(boot, local, style2, warnings) {
|
|
2606
2628
|
const model = boot.runtime.primaryModel || boot.model;
|
|
2607
2629
|
const repos = bootScopeRepos(boot);
|
|
@@ -2965,6 +2987,7 @@ function removeStableCopies(home, keep = null) {
|
|
|
2965
2987
|
function serviceArgs(env, bin, api) {
|
|
2966
2988
|
return [env.nodePath, bin, "up", "--api", api, "--service"];
|
|
2967
2989
|
}
|
|
2990
|
+
var LAUNCHD_PROCESS_TYPE = "Interactive";
|
|
2968
2991
|
var xml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2969
2992
|
function launchAgentPlist(env, bin, api) {
|
|
2970
2993
|
const names = serviceNames(api);
|
|
@@ -2990,7 +3013,7 @@ ${args}
|
|
|
2990
3013
|
<key>ThrottleInterval</key>
|
|
2991
3014
|
<integer>10</integer>
|
|
2992
3015
|
<key>ProcessType</key>
|
|
2993
|
-
<string
|
|
3016
|
+
<string>${LAUNCHD_PROCESS_TYPE}</string>
|
|
2994
3017
|
<key>WorkingDirectory</key>
|
|
2995
3018
|
<string>${xml(env.home)}</string>
|
|
2996
3019
|
<key>EnvironmentVariables</key>
|
|
@@ -4411,7 +4434,7 @@ function limitInEngineText(text3) {
|
|
|
4411
4434
|
// src/application/services/workspaceSandbox/scalequalityTools.ts
|
|
4412
4435
|
var import_crypto4 = require("crypto");
|
|
4413
4436
|
|
|
4414
|
-
//
|
|
4437
|
+
// ../../../../../../../Users/erikfernandes/GIT/load-n-scale/.codex-worktrees/aigov-code-queue/node_modules/zod/v3/external.js
|
|
4415
4438
|
var external_exports = {};
|
|
4416
4439
|
__export(external_exports, {
|
|
4417
4440
|
BRAND: () => BRAND,
|
|
@@ -4523,7 +4546,7 @@ __export(external_exports, {
|
|
|
4523
4546
|
void: () => voidType
|
|
4524
4547
|
});
|
|
4525
4548
|
|
|
4526
|
-
//
|
|
4549
|
+
// ../../../../../../../Users/erikfernandes/GIT/load-n-scale/.codex-worktrees/aigov-code-queue/node_modules/zod/v3/helpers/util.js
|
|
4527
4550
|
var util;
|
|
4528
4551
|
(function(util2) {
|
|
4529
4552
|
util2.assertEqual = (_) => {
|
|
@@ -4657,7 +4680,7 @@ var getParsedType = (data) => {
|
|
|
4657
4680
|
}
|
|
4658
4681
|
};
|
|
4659
4682
|
|
|
4660
|
-
//
|
|
4683
|
+
// ../../../../../../../Users/erikfernandes/GIT/load-n-scale/.codex-worktrees/aigov-code-queue/node_modules/zod/v3/ZodError.js
|
|
4661
4684
|
var ZodIssueCode = util.arrayToEnum([
|
|
4662
4685
|
"invalid_type",
|
|
4663
4686
|
"invalid_literal",
|
|
@@ -4775,7 +4798,7 @@ ZodError.create = (issues) => {
|
|
|
4775
4798
|
return error;
|
|
4776
4799
|
};
|
|
4777
4800
|
|
|
4778
|
-
//
|
|
4801
|
+
// ../../../../../../../Users/erikfernandes/GIT/load-n-scale/.codex-worktrees/aigov-code-queue/node_modules/zod/v3/locales/en.js
|
|
4779
4802
|
var errorMap = (issue, _ctx) => {
|
|
4780
4803
|
let message;
|
|
4781
4804
|
switch (issue.code) {
|
|
@@ -4878,7 +4901,7 @@ var errorMap = (issue, _ctx) => {
|
|
|
4878
4901
|
};
|
|
4879
4902
|
var en_default = errorMap;
|
|
4880
4903
|
|
|
4881
|
-
//
|
|
4904
|
+
// ../../../../../../../Users/erikfernandes/GIT/load-n-scale/.codex-worktrees/aigov-code-queue/node_modules/zod/v3/errors.js
|
|
4882
4905
|
var overrideErrorMap = en_default;
|
|
4883
4906
|
function setErrorMap(map) {
|
|
4884
4907
|
overrideErrorMap = map;
|
|
@@ -4887,7 +4910,7 @@ function getErrorMap() {
|
|
|
4887
4910
|
return overrideErrorMap;
|
|
4888
4911
|
}
|
|
4889
4912
|
|
|
4890
|
-
//
|
|
4913
|
+
// ../../../../../../../Users/erikfernandes/GIT/load-n-scale/.codex-worktrees/aigov-code-queue/node_modules/zod/v3/helpers/parseUtil.js
|
|
4891
4914
|
var makeIssue = (params) => {
|
|
4892
4915
|
const { data, path, errorMaps, issueData } = params;
|
|
4893
4916
|
const fullPath = [...path, ...issueData.path || []];
|
|
@@ -4997,14 +5020,14 @@ var isDirty = (x) => x.status === "dirty";
|
|
|
4997
5020
|
var isValid = (x) => x.status === "valid";
|
|
4998
5021
|
var isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise;
|
|
4999
5022
|
|
|
5000
|
-
//
|
|
5023
|
+
// ../../../../../../../Users/erikfernandes/GIT/load-n-scale/.codex-worktrees/aigov-code-queue/node_modules/zod/v3/helpers/errorUtil.js
|
|
5001
5024
|
var errorUtil;
|
|
5002
5025
|
(function(errorUtil2) {
|
|
5003
5026
|
errorUtil2.errToObj = (message) => typeof message === "string" ? { message } : message || {};
|
|
5004
5027
|
errorUtil2.toString = (message) => typeof message === "string" ? message : message?.message;
|
|
5005
5028
|
})(errorUtil || (errorUtil = {}));
|
|
5006
5029
|
|
|
5007
|
-
//
|
|
5030
|
+
// ../../../../../../../Users/erikfernandes/GIT/load-n-scale/.codex-worktrees/aigov-code-queue/node_modules/zod/v3/types.js
|
|
5008
5031
|
var ParseInputLazyPath = class {
|
|
5009
5032
|
constructor(parent, value, path, key) {
|
|
5010
5033
|
this._cachedPath = [];
|
|
@@ -9164,6 +9187,28 @@ var TurnTiming = class {
|
|
|
9164
9187
|
calls = 0;
|
|
9165
9188
|
answers = /* @__PURE__ */ new Set();
|
|
9166
9189
|
done = false;
|
|
9190
|
+
firstFrame = null;
|
|
9191
|
+
phase = {};
|
|
9192
|
+
permissionTotal = 0;
|
|
9193
|
+
permissionCount = 0;
|
|
9194
|
+
toolWaitTotal = 0;
|
|
9195
|
+
/** Durations (ms) and flags of the turn's own phases. */
|
|
9196
|
+
phases(p) {
|
|
9197
|
+
Object.assign(this.phase, p);
|
|
9198
|
+
}
|
|
9199
|
+
/** Any frame from the engine process (the first one says it is up and has the message). */
|
|
9200
|
+
engineFrame() {
|
|
9201
|
+
if (this.firstFrame === null) this.firstFrame = this.now();
|
|
9202
|
+
}
|
|
9203
|
+
/** A command or change permission the engine asked for (the rules, the person's mode or the person answered). */
|
|
9204
|
+
permission(ms) {
|
|
9205
|
+
this.permissionTotal += ms;
|
|
9206
|
+
this.permissionCount += 1;
|
|
9207
|
+
}
|
|
9208
|
+
/** A tool waited for the turn's saved point. */
|
|
9209
|
+
toolWait(ms) {
|
|
9210
|
+
this.toolWaitTotal += ms;
|
|
9211
|
+
}
|
|
9167
9212
|
/** Any frame of the main conversation that came from the model. */
|
|
9168
9213
|
modelEvent() {
|
|
9169
9214
|
if (this.firstModel === null) this.firstModel = this.now();
|
|
@@ -9200,6 +9245,10 @@ var TurnTiming = class {
|
|
|
9200
9245
|
firstVisibleEventMs: since(this.firstVisible),
|
|
9201
9246
|
totalMs: end - this.startedAt,
|
|
9202
9247
|
modelCalls: this.calls,
|
|
9248
|
+
...this.phase,
|
|
9249
|
+
engineReadyMs: since(this.firstFrame),
|
|
9250
|
+
...this.permissionCount ? { permissions: this.permissionCount, permissionMs: this.permissionTotal } : {},
|
|
9251
|
+
...this.toolWaitTotal ? { toolWaitMs: this.toolWaitTotal } : {},
|
|
9203
9252
|
outcome,
|
|
9204
9253
|
...extra
|
|
9205
9254
|
});
|
|
@@ -9836,6 +9885,7 @@ ${lines2.join("\n")}
|
|
|
9836
9885
|
}
|
|
9837
9886
|
|
|
9838
9887
|
// src/application/services/workspaceSandbox/WorkspaceEngine.ts
|
|
9888
|
+
var WARM_IDLE_MS = 15 * 6e4;
|
|
9839
9889
|
var STOP_GRACE_MS = 8e3;
|
|
9840
9890
|
var INTERRUPT_TIMEOUT_MS = 3e3;
|
|
9841
9891
|
var MODEL_TOOLS = ["Read", "Write", "Edit", "NotebookEdit", "Glob", "Grep", "Bash", "TaskStop", "TodoWrite", "Agent", "WebFetch", "Skill"];
|
|
@@ -10032,6 +10082,14 @@ var WorkspaceEngine = class {
|
|
|
10032
10082
|
envTrouble = [];
|
|
10033
10083
|
/** The organization's approved MCP servers: their catalog and the in-process servers that forward to the API. */
|
|
10034
10084
|
orgMcp = null;
|
|
10085
|
+
/**
|
|
10086
|
+
* The saved point of the turn the engine runs now: every tool waits for it (the PreToolUse hook and the permission
|
|
10087
|
+
* prompt), so the engine process starts while it is taken and no tool changes a file before it is recorded.
|
|
10088
|
+
*/
|
|
10089
|
+
toolGate = Promise.resolve();
|
|
10090
|
+
/** The engine process started for the next turn (warmStart), and the start options it was given (their hash). */
|
|
10091
|
+
warm = null;
|
|
10092
|
+
prewarming = null;
|
|
10035
10093
|
emit(e) {
|
|
10036
10094
|
if (e.type === "terminal" && e.data.chunk === void 0 && this.folderBaseline) this.sweepPending = true;
|
|
10037
10095
|
if (this.turnWatch) {
|
|
@@ -10221,6 +10279,7 @@ var WorkspaceEngine = class {
|
|
|
10221
10279
|
if (this.resumedFromCheckpoint) await this.diffNow();
|
|
10222
10280
|
this.phase("READY");
|
|
10223
10281
|
await this.sink.flush();
|
|
10282
|
+
this.prewarm();
|
|
10224
10283
|
return true;
|
|
10225
10284
|
}
|
|
10226
10285
|
/** Where a background command writes its output: inside the config dir, which Read may read. */
|
|
@@ -10556,6 +10615,7 @@ var WorkspaceEngine = class {
|
|
|
10556
10615
|
} finally {
|
|
10557
10616
|
this.turnRunning = null;
|
|
10558
10617
|
}
|
|
10618
|
+
this.prewarm();
|
|
10559
10619
|
})();
|
|
10560
10620
|
}
|
|
10561
10621
|
/** Waits for the current turn (test and shutdown helper). */
|
|
@@ -10839,14 +10899,16 @@ ${String(item.content)}` : String(item.content);
|
|
|
10839
10899
|
this.recordCheckpoint(turnId, await this.takeSnapshot());
|
|
10840
10900
|
}
|
|
10841
10901
|
async takeSnapshot() {
|
|
10902
|
+
const sdkSessionId = this.sdkSessionId;
|
|
10903
|
+
const resumeAt = this.resumeAt ?? this.lastChainUuid;
|
|
10842
10904
|
const trees = /* @__PURE__ */ new Map();
|
|
10843
|
-
await this.folderBaseline;
|
|
10905
|
+
await this.folderBaseline?.catch(() => void 0);
|
|
10844
10906
|
for (const repo2 of this.repos.values()) {
|
|
10845
10907
|
if (repo2.dormant) continue;
|
|
10846
10908
|
const tree = await repo2.tracker.snapshot().catch(() => null);
|
|
10847
10909
|
if (tree) trees.set(repo2.root, tree);
|
|
10848
10910
|
}
|
|
10849
|
-
return { trees, sdkSessionId
|
|
10911
|
+
return { trees, sdkSessionId, resumeAt };
|
|
10850
10912
|
}
|
|
10851
10913
|
recordCheckpoint(turnId, snap) {
|
|
10852
10914
|
const at = iso2();
|
|
@@ -11220,9 +11282,16 @@ ${json}
|
|
|
11220
11282
|
this.turnWatch = { lastExit: null, errored: false };
|
|
11221
11283
|
if (!compact && typeof payload.turnId === "string" && payload.turnId) this.messageStatus(turnId, "started");
|
|
11222
11284
|
this.setState("WORKING");
|
|
11223
|
-
|
|
11285
|
+
let checkpointMs = null;
|
|
11286
|
+
const saved = compact ? Promise.resolve() : this.checkpointTurn(turnId).then(() => {
|
|
11287
|
+
checkpointMs = Date.now() - turnStart;
|
|
11288
|
+
}, (e) => {
|
|
11289
|
+
this.deps.log.warn("turn checkpoint failed", { error: this.redactor.text(e.message) });
|
|
11290
|
+
});
|
|
11291
|
+
this.toolGate = saved;
|
|
11224
11292
|
if (!compact) {
|
|
11225
11293
|
await this.refreshSkills();
|
|
11294
|
+
if (!this.local) await saved;
|
|
11226
11295
|
this.startEnvSetups();
|
|
11227
11296
|
await Promise.all(this.envSetups.values());
|
|
11228
11297
|
}
|
|
@@ -11320,6 +11389,9 @@ ${text3}`;
|
|
|
11320
11389
|
timing: new TurnTiming({ sessionId: this.deps.sessionId ?? null, turnId, model, reasoning: this.reasoningLevel }, typeof payload.receivedAt === "number" ? payload.receivedAt : null, turnStart)
|
|
11321
11390
|
};
|
|
11322
11391
|
this.live = live;
|
|
11392
|
+
void saved.then(() => {
|
|
11393
|
+
if (checkpointMs !== null) live.timing.phases({ checkpointMs });
|
|
11394
|
+
});
|
|
11323
11395
|
const attempt = async (resume, at) => {
|
|
11324
11396
|
let sawInit = false;
|
|
11325
11397
|
let conversation = resume;
|
|
@@ -11356,41 +11428,26 @@ ${text3}`;
|
|
|
11356
11428
|
live.turnEnded = false;
|
|
11357
11429
|
live.headResult = null;
|
|
11358
11430
|
channel.start(userMessage(turnPrompt, context.images, messageUuid(turnId)));
|
|
11359
|
-
const
|
|
11360
|
-
root: this.deps.root,
|
|
11361
|
-
model,
|
|
11362
|
-
resume,
|
|
11363
|
-
resumeAt: resume ? at : null,
|
|
11364
|
-
abortController: ac,
|
|
11365
|
-
reasoning: reasoning.options,
|
|
11366
|
-
env: buildEngineEnv(boot, this.deps.configDir, model, { local: this.local, reasoning, output, extraEnv: this.projectEnv(), plan: planMode }),
|
|
11367
|
-
mcpServer: this.mcpServer,
|
|
11368
|
-
systemAppend: await this.systemAppend(),
|
|
11369
|
-
policy: {
|
|
11370
|
-
root: this.deps.root,
|
|
11371
|
-
extraReadRoots: [this.deps.configDir],
|
|
11372
|
-
deniedRoots: this.deps.privateDirs,
|
|
11373
|
-
local: this.local,
|
|
11374
|
-
plan: planMode,
|
|
11375
|
-
planDir: (0, import_path15.join)(this.deps.configDir, "plans"),
|
|
11376
|
-
orgMcpTools: orgMcpToolNames(Object.keys(off.mcpServers).length ? off.mcpCatalog : null)
|
|
11377
|
-
},
|
|
11378
|
-
plan: planMode,
|
|
11379
|
-
onPlan: (text3) => this.proposePlan(live.turnId, text3),
|
|
11380
|
-
skills: { dir: this.skillsDir(), enabled: enabledSkillNames(off.skillEntries), pluginDirs: off.pluginDirs },
|
|
11381
|
-
...Object.keys(off.mcpServers).length ? { orgMcpServers: off.mcpServers } : {},
|
|
11382
|
-
pathToClaudeCodeExecutable: this.deps.pathToClaudeCodeExecutable,
|
|
11383
|
-
commandGate: this.deps.commandGate,
|
|
11384
|
-
...this.localFolder?.kind === "folder" ? { beforeWrite: (path) => this.beforeFolderWrite(path) } : {}
|
|
11385
|
-
});
|
|
11431
|
+
const inputs = await this.engineInputs({ model, reasoning, output, planMode, off, resume, resumeAt: resume ? at : null });
|
|
11386
11432
|
if (!streaming) channel.close();
|
|
11387
11433
|
const single = (text3) => context.images.length ? (async function* () {
|
|
11388
11434
|
yield userMessage(text3, context.images);
|
|
11389
11435
|
})() : text3;
|
|
11390
|
-
const
|
|
11436
|
+
const warm = streaming ? await this.claimWarm(spawnKey(inputs)) : (this.dropWarm(), null);
|
|
11437
|
+
let q;
|
|
11438
|
+
if (warm) {
|
|
11439
|
+
const forward = () => warm.abort.abort();
|
|
11440
|
+
if (ac.signal.aborted) forward();
|
|
11441
|
+
else ac.signal.addEventListener("abort", forward, { once: true });
|
|
11442
|
+
q = warm.handle.query(channel);
|
|
11443
|
+
} else {
|
|
11444
|
+
q = sdk.query({ prompt: streaming ? channel : single(turnPrompt), options: this.queryOptions(inputs, ac, this.mcpServer) });
|
|
11445
|
+
}
|
|
11446
|
+
live.timing.phases({ prepareMs: Date.now() - turnStart, warmStart: !!warm });
|
|
11391
11447
|
live.query = q;
|
|
11392
11448
|
try {
|
|
11393
11449
|
for await (const msg of q) {
|
|
11450
|
+
live.timing.engineFrame();
|
|
11394
11451
|
const f = msg && typeof msg === "object" ? msg : {};
|
|
11395
11452
|
const main2 = !f.parent_tool_use_id;
|
|
11396
11453
|
if (f.type === "command_lifecycle") {
|
|
@@ -11445,6 +11502,7 @@ ${text3}`;
|
|
|
11445
11502
|
}
|
|
11446
11503
|
} finally {
|
|
11447
11504
|
const stopped = ac.signal.aborted || live.interrupted;
|
|
11505
|
+
await saved;
|
|
11448
11506
|
live.timing.end(this.deps.log, stopped ? "stopped" : "failed");
|
|
11449
11507
|
this.live = null;
|
|
11450
11508
|
this.turnAbort = null;
|
|
@@ -11585,6 +11643,7 @@ ${text3}`;
|
|
|
11585
11643
|
}
|
|
11586
11644
|
live.between = true;
|
|
11587
11645
|
live.snapshot = this.takeSnapshot();
|
|
11646
|
+
this.toolGate = live.snapshot.then(() => void 0, () => void 0);
|
|
11588
11647
|
}
|
|
11589
11648
|
/** The query ended: its input closes, and a message it never read runs as the next turn (or is dropped by a rewind). */
|
|
11590
11649
|
settleLive(live) {
|
|
@@ -11625,6 +11684,146 @@ ${text3}`;
|
|
|
11625
11684
|
output: { limit: own ?? boot.runtime.maxOutputTokensCeiling ?? boot.runtime.maxOutputTokens ?? null, ceiling: own ?? boot.runtime.maxOutputTokensCeiling ?? boot.runtime.maxOutputTokens ?? null }
|
|
11626
11685
|
};
|
|
11627
11686
|
}
|
|
11687
|
+
/** The data a turn's engine process starts with (see EngineInputs). */
|
|
11688
|
+
async engineInputs(t) {
|
|
11689
|
+
const boot = this.boot;
|
|
11690
|
+
const org = Object.keys(t.off.mcpServers).length > 0;
|
|
11691
|
+
return {
|
|
11692
|
+
model: t.model,
|
|
11693
|
+
resume: t.resume,
|
|
11694
|
+
resumeAt: t.resumeAt,
|
|
11695
|
+
reasoning: t.reasoning.options,
|
|
11696
|
+
env: buildEngineEnv(boot, this.deps.configDir, t.model, { local: this.local, reasoning: t.reasoning, output: t.output, extraEnv: this.projectEnv(), plan: t.planMode }),
|
|
11697
|
+
systemAppend: await this.systemAppend(),
|
|
11698
|
+
policy: {
|
|
11699
|
+
root: this.deps.root,
|
|
11700
|
+
extraReadRoots: [this.deps.configDir],
|
|
11701
|
+
deniedRoots: this.deps.privateDirs,
|
|
11702
|
+
local: this.local,
|
|
11703
|
+
plan: t.planMode,
|
|
11704
|
+
planDir: (0, import_path15.join)(this.deps.configDir, "plans"),
|
|
11705
|
+
orgMcpTools: orgMcpToolNames(org ? t.off.mcpCatalog : null)
|
|
11706
|
+
},
|
|
11707
|
+
plan: t.planMode,
|
|
11708
|
+
skills: { dir: this.skillsDir(), enabled: enabledSkillNames(t.off.skillEntries), pluginDirs: t.off.pluginDirs },
|
|
11709
|
+
skillsKey: this.skillsSent,
|
|
11710
|
+
orgMcpServers: org ? t.off.mcpServers : {},
|
|
11711
|
+
folderWrites: this.localFolder?.kind === "folder"
|
|
11712
|
+
};
|
|
11713
|
+
}
|
|
11714
|
+
/**
|
|
11715
|
+
* The query options of a turn's engine process. The callbacks read the engine's current turn (never one captured
|
|
11716
|
+
* here), so a process started ahead serves the turn that takes it: its plan goes to that turn, and its tools wait
|
|
11717
|
+
* for that turn's saved point.
|
|
11718
|
+
*/
|
|
11719
|
+
queryOptions(i, abortController, mcpServer) {
|
|
11720
|
+
const gate = this.deps.commandGate;
|
|
11721
|
+
return buildQueryOptions({
|
|
11722
|
+
root: this.deps.root,
|
|
11723
|
+
model: i.model,
|
|
11724
|
+
resume: i.resume,
|
|
11725
|
+
resumeAt: i.resumeAt,
|
|
11726
|
+
abortController,
|
|
11727
|
+
reasoning: i.reasoning,
|
|
11728
|
+
env: i.env,
|
|
11729
|
+
mcpServer,
|
|
11730
|
+
systemAppend: i.systemAppend,
|
|
11731
|
+
policy: i.policy,
|
|
11732
|
+
plan: i.plan,
|
|
11733
|
+
onPlan: (text3) => {
|
|
11734
|
+
const live = this.live;
|
|
11735
|
+
if (live) this.proposePlan(live.turnId, text3);
|
|
11736
|
+
},
|
|
11737
|
+
skills: i.skills,
|
|
11738
|
+
...Object.keys(i.orgMcpServers).length ? { orgMcpServers: i.orgMcpServers } : {},
|
|
11739
|
+
pathToClaudeCodeExecutable: this.deps.pathToClaudeCodeExecutable,
|
|
11740
|
+
commandGate: gate ? timedGate(gate, (ms) => this.live?.timing.permission(ms)) : void 0,
|
|
11741
|
+
beforeTool: async () => {
|
|
11742
|
+
const t0 = Date.now();
|
|
11743
|
+
await this.toolGate;
|
|
11744
|
+
const waited = Date.now() - t0;
|
|
11745
|
+
if (waited > 0) this.live?.timing.toolWait(waited);
|
|
11746
|
+
},
|
|
11747
|
+
...i.folderWrites ? { beforeWrite: (path) => this.beforeFolderWrite(path) } : {}
|
|
11748
|
+
});
|
|
11749
|
+
}
|
|
11750
|
+
/**
|
|
11751
|
+
* The inputs of the next turn as the person would most likely send it (same model, reasoning, mode and tools as the
|
|
11752
|
+
* session now, an everyday task), for the process started ahead. null when that process could not serve it safely:
|
|
11753
|
+
* the organization's MCP servers are in-process objects shared by every process, and one serves one process at a time.
|
|
11754
|
+
*/
|
|
11755
|
+
async nextTurnInputs() {
|
|
11756
|
+
const boot = this.boot;
|
|
11757
|
+
const off = this.turnOff(void 0);
|
|
11758
|
+
if (Object.keys(off.mcpServers).length) return null;
|
|
11759
|
+
const primary = boot.runtime.primaryModel || boot.model;
|
|
11760
|
+
const route = routeAutoTurn(boot, {
|
|
11761
|
+
content: "",
|
|
11762
|
+
previousTrouble: this.previousTrouble,
|
|
11763
|
+
maxMode: this.reasoningLevel === "max" || isMaxMode(this.reasoningLevel, this.reasoningCapability)
|
|
11764
|
+
});
|
|
11765
|
+
const { model, reasoning, output } = this.turnModel(primary, route);
|
|
11766
|
+
const canResume = !!this.sdkSessionId && (this.knownSessions.has(this.sdkSessionId) || await hasLocalTranscript(this.deps.configDir, this.sdkSessionId));
|
|
11767
|
+
return this.engineInputs({ model, reasoning, output, planMode: false, off, resume: canResume ? this.sdkSessionId : null, resumeAt: canResume ? this.resumeAt : null });
|
|
11768
|
+
}
|
|
11769
|
+
/**
|
|
11770
|
+
* Starts the engine process of the next turn now (warmStart), while the session waits for the person: Claude Code
|
|
11771
|
+
* keeps its process up between messages, and here the next message then goes to a ready process. Only when nothing
|
|
11772
|
+
* runs or waits; a process already started with the same inputs is kept.
|
|
11773
|
+
*/
|
|
11774
|
+
prewarm() {
|
|
11775
|
+
const sdk = this.sdk;
|
|
11776
|
+
if (!this.deps.warmStart || !sdk?.startup || !this.boot || this.stopping || this.turnRunning || this.queue.length || this.prewarming) return;
|
|
11777
|
+
this.prewarming = (async () => {
|
|
11778
|
+
const inputs = await this.nextTurnInputs();
|
|
11779
|
+
if (!inputs || this.stopping || this.turnRunning || this.queue.length) return;
|
|
11780
|
+
const key = spawnKey(inputs);
|
|
11781
|
+
if (this.warm?.key === key) return;
|
|
11782
|
+
this.dropWarm();
|
|
11783
|
+
const abort = new AbortController();
|
|
11784
|
+
const server = buildScaleQualityServer(sdk, this.toolHost());
|
|
11785
|
+
const ready = sdk.startup({ options: this.queryOptions(inputs, abort, server) }).catch((e) => {
|
|
11786
|
+
this.deps.log.warn("engine warm start failed", { error: this.redactor.text(e.message) });
|
|
11787
|
+
return null;
|
|
11788
|
+
});
|
|
11789
|
+
const timer = setTimeout(() => {
|
|
11790
|
+
if (this.warm?.abort === abort) this.dropWarm();
|
|
11791
|
+
}, this.deps.warmIdleMs ?? WARM_IDLE_MS);
|
|
11792
|
+
timer.unref?.();
|
|
11793
|
+
this.warm = { key, abort, ready, timer };
|
|
11794
|
+
})().catch((e) => {
|
|
11795
|
+
this.deps.log.warn("engine warm start failed", { error: this.redactor.text(e.message) });
|
|
11796
|
+
}).finally(() => {
|
|
11797
|
+
this.prewarming = null;
|
|
11798
|
+
});
|
|
11799
|
+
}
|
|
11800
|
+
/** The process started ahead, for a turn with this start key; any other is closed (the turn starts its own). */
|
|
11801
|
+
async claimWarm(key) {
|
|
11802
|
+
await this.prewarming;
|
|
11803
|
+
const w = this.warm;
|
|
11804
|
+
if (!w) return null;
|
|
11805
|
+
if (w.key !== key) {
|
|
11806
|
+
this.dropWarm();
|
|
11807
|
+
return null;
|
|
11808
|
+
}
|
|
11809
|
+
this.warm = null;
|
|
11810
|
+
clearTimeout(w.timer);
|
|
11811
|
+
const handle = await w.ready;
|
|
11812
|
+
return handle ? { handle, abort: w.abort } : null;
|
|
11813
|
+
}
|
|
11814
|
+
/** Closes the process started ahead, if any. */
|
|
11815
|
+
dropWarm() {
|
|
11816
|
+
const w = this.warm;
|
|
11817
|
+
if (!w) return;
|
|
11818
|
+
this.warm = null;
|
|
11819
|
+
clearTimeout(w.timer);
|
|
11820
|
+
void w.ready.then((h) => {
|
|
11821
|
+
try {
|
|
11822
|
+
h?.close();
|
|
11823
|
+
} catch {
|
|
11824
|
+
}
|
|
11825
|
+
}).finally(() => w.abort.abort());
|
|
11826
|
+
}
|
|
11628
11827
|
/** The imported conversation as a context block (fetched once); null when there is none or it cannot be read. */
|
|
11629
11828
|
importedHistoryBlock() {
|
|
11630
11829
|
const info = this.boot?.imported;
|
|
@@ -11736,6 +11935,7 @@ ${patch}`;
|
|
|
11736
11935
|
if (this.stopping) return;
|
|
11737
11936
|
this.stopping = true;
|
|
11738
11937
|
this.queue.length = 0;
|
|
11938
|
+
this.dropWarm();
|
|
11739
11939
|
this.turnAbort?.abort();
|
|
11740
11940
|
this.pollAbort.abort();
|
|
11741
11941
|
this.envAbort.abort();
|
|
@@ -12261,6 +12461,7 @@ function buildQueryOptions(o) {
|
|
|
12261
12461
|
};
|
|
12262
12462
|
const preToolUse = async (input) => {
|
|
12263
12463
|
if (input.hook_event_name !== "PreToolUse") return {};
|
|
12464
|
+
if (o.beforeTool) await o.beforeTool();
|
|
12264
12465
|
const name = String(input.tool_name ?? "");
|
|
12265
12466
|
if (name === "ExitPlanMode") return o.plan ? {} : { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: "Not in plan mode." } };
|
|
12266
12467
|
const provenance = input.mcp_server;
|
|
@@ -12276,6 +12477,7 @@ function buildQueryOptions(o) {
|
|
|
12276
12477
|
return d.behavior === "allow" ? { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "allow", updatedInput: d.updatedInput } } : { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: d.message } };
|
|
12277
12478
|
};
|
|
12278
12479
|
const canUseTool = async (toolName, input, opts) => {
|
|
12480
|
+
if (o.beforeTool) await o.beforeTool();
|
|
12279
12481
|
if (toolName.startsWith("mcp__") && opts?.mcpServer?.source && opts.mcpServer.source !== "sdk") {
|
|
12280
12482
|
return { behavior: "deny", message: "Only ScaleQuality tools are available in this workspace." };
|
|
12281
12483
|
}
|
|
@@ -12337,6 +12539,24 @@ function buildQueryOptions(o) {
|
|
|
12337
12539
|
...o.pathToClaudeCodeExecutable ? { pathToClaudeCodeExecutable: o.pathToClaudeCodeExecutable } : {}
|
|
12338
12540
|
};
|
|
12339
12541
|
}
|
|
12542
|
+
function spawnKey(i) {
|
|
12543
|
+
const { orgMcpServers, ...data } = i;
|
|
12544
|
+
return (0, import_crypto7.createHash)("sha256").update(JSON.stringify({ ...data, orgMcpServers: Object.keys(orgMcpServers).sort() })).digest("hex");
|
|
12545
|
+
}
|
|
12546
|
+
function timedGate(gate, took) {
|
|
12547
|
+
const timed = async (run2) => {
|
|
12548
|
+
const t0 = Date.now();
|
|
12549
|
+
try {
|
|
12550
|
+
return await run2();
|
|
12551
|
+
} finally {
|
|
12552
|
+
took(Date.now() - t0);
|
|
12553
|
+
}
|
|
12554
|
+
};
|
|
12555
|
+
return {
|
|
12556
|
+
check: (command, opts) => timed(() => gate.check(command, opts)),
|
|
12557
|
+
...gate.checkEdit ? { checkEdit: (path, opts) => timed(() => gate.checkEdit(path, opts)) } : {}
|
|
12558
|
+
};
|
|
12559
|
+
}
|
|
12340
12560
|
async function hasLocalTranscript(configDir, sessionId) {
|
|
12341
12561
|
if (!/^[A-Za-z0-9-]{8,80}$/.test(sessionId)) return false;
|
|
12342
12562
|
const projects = (0, import_path15.join)(configDir, "projects");
|
|
@@ -12390,6 +12610,10 @@ function createLocalEngine(o) {
|
|
|
12390
12610
|
const log = {
|
|
12391
12611
|
info: (msg, ctx) => {
|
|
12392
12612
|
if (o.verbose) say(style.dim(`${o.prefix ?? ""}[info] ${msg} ${ctx ? JSON.stringify(ctx) : ""}`));
|
|
12613
|
+
else if (msg === "WORKSPACE_TURN_TIMING") {
|
|
12614
|
+
const line = turnTimingLine(ctx);
|
|
12615
|
+
if (line) say(`${o.prefix ?? ""}${style.dim(` ${line}`)}`);
|
|
12616
|
+
}
|
|
12393
12617
|
},
|
|
12394
12618
|
warn: (msg, ctx) => {
|
|
12395
12619
|
if (o.verbose) say(style.dim(`${o.prefix ?? ""}[warn] ${msg} ${ctx ? JSON.stringify(ctx) : ""}`));
|
|
@@ -12465,7 +12689,9 @@ function createLocalEngine(o) {
|
|
|
12465
12689
|
// An imported Claude Code conversation of this computer and folder resumes from its own transcript.
|
|
12466
12690
|
resumeImported: (externalId) => copyClaudeTranscript(sources, externalId, o.root, configDir),
|
|
12467
12691
|
exit: (code, reason) => o.exit(code, reason, startError),
|
|
12468
|
-
pathToClaudeCodeExecutable: process.env.CLAUDE_CODE_EXECUTABLE || void 0
|
|
12692
|
+
pathToClaudeCodeExecutable: process.env.CLAUDE_CODE_EXECUTABLE || void 0,
|
|
12693
|
+
// The next message's engine process is started while the person reads, like Claude Code's process between messages.
|
|
12694
|
+
warmStart: true
|
|
12469
12695
|
});
|
|
12470
12696
|
}
|
|
12471
12697
|
async function connectMain(argv) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalequality/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9-rc.1",
|
|
4
4
|
"description": "ScaleQuality CLI. Connect your computer to the ScaleQuality AI Workspace (`scalequality login`), run its coding engine in any folder of your computer, and import your Claude Code and Codex conversations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|