@scalequality/cli 0.4.20 → 0.4.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/connect.build.json +2 -2
- package/dist/connect.cjs +64 -7
- 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": "64b584e7f307f8bed9bee151ca7dd57c5fe8e54c8adf6e9fd76ba11dbfba93c1",
|
|
4
|
+
"sourceCommit": "a4093e270f574ff0830763c574a17585cf269fe6"
|
|
5
5
|
}
|
package/dist/connect.cjs
CHANGED
|
@@ -4893,14 +4893,16 @@ function sandboxTestEnv() {
|
|
|
4893
4893
|
var ApprovalBroker = class {
|
|
4894
4894
|
waiting = /* @__PURE__ */ new Map();
|
|
4895
4895
|
early = /* @__PURE__ */ new Map();
|
|
4896
|
+
/** True when a call was waiting for this decision (it returns now); false when it is kept for a later wait. */
|
|
4896
4897
|
resolve(approvalId, decision) {
|
|
4897
4898
|
const w = this.waiting.get(approvalId);
|
|
4898
4899
|
if (w) {
|
|
4899
4900
|
this.waiting.delete(approvalId);
|
|
4900
4901
|
w(decision);
|
|
4901
|
-
|
|
4902
|
-
this.early.set(approvalId, decision);
|
|
4902
|
+
return true;
|
|
4903
4903
|
}
|
|
4904
|
+
this.early.set(approvalId, decision);
|
|
4905
|
+
return false;
|
|
4904
4906
|
}
|
|
4905
4907
|
wait(approvalId, signal) {
|
|
4906
4908
|
const ready = this.early.get(approvalId);
|
|
@@ -10257,7 +10259,8 @@ function buildSystemAppend(c) {
|
|
|
10257
10259
|
"- Lead with the outcome in one or two sentences: what changed, what was found, or what you need from the user.",
|
|
10258
10260
|
"- The screen already shows every command you ran and every file you edited, with their output and diff. Do not list them again, and do not restate test counts or tool results the user can open; mention one only when it matters for what comes next (a test that still fails, a risk that remains).",
|
|
10259
10261
|
"- Keep it short, like a colleague reporting in a chat: a few lines for most turns, no headings, a short list only when there are several separate items. Never repeat the same fact in two places.",
|
|
10260
|
-
"- End with at most one question or one suggested next step, never a menu of options. When there is an obvious next step, say it in one line."
|
|
10262
|
+
"- End with at most one question or one suggested next step, never a menu of options. When there is an obvious next step, say it in one line.",
|
|
10263
|
+
"- To show the user an image (a screenshot, a design, a reference board, a chart), write it as a Markdown image with its path in the workspace, for example ``: the screen displays it in the conversation, and the user can open it larger. An https image address works the same way. Never say you cannot show an image; read it yourself first when you need to know what it shows."
|
|
10261
10264
|
].join("\n");
|
|
10262
10265
|
}
|
|
10263
10266
|
|
|
@@ -10421,7 +10424,7 @@ async function restoreTranscript(configDir, cwd, t) {
|
|
|
10421
10424
|
// src/application/services/workspaceSandbox/workspaceFiles.ts
|
|
10422
10425
|
var import_promises12 = require("fs/promises");
|
|
10423
10426
|
var import_path13 = require("path");
|
|
10424
|
-
var FILE_CAPS = { maxEntries: 500, maxReadBytes: 256 * 1024, maxFileBytes: 20 * 1024 * 1024, maxLines: 5e3 };
|
|
10427
|
+
var FILE_CAPS = { maxEntries: 500, maxReadBytes: 256 * 1024, maxFileBytes: 20 * 1024 * 1024, maxLines: 5e3, maxImageBytes: 6 * 1024 * 1024 };
|
|
10425
10428
|
var FileRequestError = class extends Error {
|
|
10426
10429
|
constructor(code) {
|
|
10427
10430
|
super(code);
|
|
@@ -10502,6 +10505,48 @@ async function readTextFile(root, rel, range = {}, denied = []) {
|
|
|
10502
10505
|
}
|
|
10503
10506
|
return { path: clean, from, to: Math.max(from, Math.min(last, total)), totalLines: total, content, truncated, size: st.size };
|
|
10504
10507
|
}
|
|
10508
|
+
function imageMime(buf) {
|
|
10509
|
+
const at = (offset, hex) => buf.length >= offset + hex.length / 2 && buf.subarray(offset, offset + hex.length / 2).toString("hex") === hex;
|
|
10510
|
+
if (at(0, "89504e470d0a1a0a")) return "image/png";
|
|
10511
|
+
if (at(0, "ffd8ff")) return "image/jpeg";
|
|
10512
|
+
if (at(0, "474946383761") || at(0, "474946383961")) return "image/gif";
|
|
10513
|
+
if (at(0, "52494646") && at(8, "57454250")) return "image/webp";
|
|
10514
|
+
if (at(4, "66747970") && /^(avif|avis)$/.test(buf.subarray(8, 12).toString("latin1"))) return "image/avif";
|
|
10515
|
+
if (at(0, "424d")) return "image/bmp";
|
|
10516
|
+
const head = buf.subarray(0, 2048).toString("utf8").replace(/^\uFEFF/, "").trimStart();
|
|
10517
|
+
if (/^(<\?xml[^>]*>\s*)?(<!--[\s\S]*?-->\s*)*(<!DOCTYPE svg[^>]*>\s*)?<svg[\s>]/i.test(head)) return "image/svg+xml";
|
|
10518
|
+
return null;
|
|
10519
|
+
}
|
|
10520
|
+
async function insideRelative(path, bases) {
|
|
10521
|
+
const clean = (path ?? "").trim().replace(/^file:\/\//i, "");
|
|
10522
|
+
if (!(0, import_path13.isAbsolute)(clean)) return clean.replace(/^\.\//, "");
|
|
10523
|
+
for (const base of bases) {
|
|
10524
|
+
for (const root of [(0, import_path13.resolve)(base), await (0, import_promises12.realpath)(base).catch(() => (0, import_path13.resolve)(base))]) {
|
|
10525
|
+
const rel = (0, import_path13.relative)(root, (0, import_path13.resolve)(clean));
|
|
10526
|
+
if (!rel) return "";
|
|
10527
|
+
if (rel !== ".." && !rel.startsWith(`..${import_path13.sep}`) && !(0, import_path13.isAbsolute)(rel)) return rel.split(import_path13.sep).join("/");
|
|
10528
|
+
}
|
|
10529
|
+
}
|
|
10530
|
+
throw new FileRequestError("PATH_OUTSIDE_WORKSPACE");
|
|
10531
|
+
}
|
|
10532
|
+
async function readImageFile(root, rel, denied = []) {
|
|
10533
|
+
const { abs, rel: clean } = await fenced(root, rel, denied);
|
|
10534
|
+
const st = await (0, import_promises12.stat)(abs).catch(() => null);
|
|
10535
|
+
if (!st) throw new FileRequestError("NOT_FOUND");
|
|
10536
|
+
if (!st.isFile()) throw new FileRequestError("NOT_A_FILE");
|
|
10537
|
+
if (st.size > FILE_CAPS.maxImageBytes) throw new FileRequestError("FILE_TOO_LARGE");
|
|
10538
|
+
const fh = await (0, import_promises12.open)(abs, "r");
|
|
10539
|
+
let buf;
|
|
10540
|
+
try {
|
|
10541
|
+
buf = Buffer.alloc(st.size);
|
|
10542
|
+
await fh.read(buf, 0, st.size, 0);
|
|
10543
|
+
} finally {
|
|
10544
|
+
await fh.close();
|
|
10545
|
+
}
|
|
10546
|
+
const mime = imageMime(buf);
|
|
10547
|
+
if (!mime) throw new FileRequestError("NOT_AN_IMAGE");
|
|
10548
|
+
return { path: clean, mime, size: st.size, data: buf.toString("base64") };
|
|
10549
|
+
}
|
|
10505
10550
|
|
|
10506
10551
|
// src/application/services/workspaceSandbox/orgMcp.ts
|
|
10507
10552
|
var import_module = require("module");
|
|
@@ -11392,7 +11437,7 @@ var WorkspaceEngine = class {
|
|
|
11392
11437
|
}
|
|
11393
11438
|
case "approval": {
|
|
11394
11439
|
const parsed = ApprovalBroker.fromPayload(p);
|
|
11395
|
-
if (parsed
|
|
11440
|
+
if (parsed && this.broker.resolve(parsed.approvalId, parsed.decision) && parsed.decision.decision === "REJECT" && this.live) this.live.refused = true;
|
|
11396
11441
|
return;
|
|
11397
11442
|
}
|
|
11398
11443
|
case "stop":
|
|
@@ -11536,6 +11581,13 @@ var WorkspaceEngine = class {
|
|
|
11536
11581
|
const denied = this.deps.privateDirs ?? [];
|
|
11537
11582
|
const path = typeof p.path === "string" ? p.path : "";
|
|
11538
11583
|
const range = { from: typeof p.from === "number" ? p.from : void 0, to: typeof p.to === "number" ? p.to : void 0 };
|
|
11584
|
+
if (kind === "read_file" && p.as === "image") {
|
|
11585
|
+
const rel = await insideRelative(path, [repo2?.root ?? this.deps.root]);
|
|
11586
|
+
const image = await this.fencedFiles(repo2, rel, (root, r) => readImageFile(root, r, denied));
|
|
11587
|
+
const { data, ...meta } = image;
|
|
11588
|
+
await this.deps.transport.reply(commandId, { ok: true, result: { ...repoFullName ? { repoFullName } : {}, ...this.redactor.deep(meta), data } }).catch((e) => this.deps.log.warn("file request not answered", { error: e.message }));
|
|
11589
|
+
return;
|
|
11590
|
+
}
|
|
11539
11591
|
const result = kind === "list_files" ? await this.fencedFiles(repo2, path, (root, rel) => listFiles(root, rel, denied)) : await this.fencedFiles(repo2, path, (root, rel) => readTextFile(root, rel, range, denied));
|
|
11540
11592
|
reply = { ok: true, result: { ...repoFullName ? { repoFullName } : {}, ...result } };
|
|
11541
11593
|
} catch (e) {
|
|
@@ -11694,6 +11746,7 @@ ${String(item.content)}` : String(item.content);
|
|
|
11694
11746
|
}
|
|
11695
11747
|
if (moved && !live.foreground.size) return;
|
|
11696
11748
|
}
|
|
11749
|
+
if (live.refused) return;
|
|
11697
11750
|
await this.interruptTurn();
|
|
11698
11751
|
}
|
|
11699
11752
|
/**
|
|
@@ -12385,6 +12438,7 @@ ${text3}`;
|
|
|
12385
12438
|
pushChain: Promise.resolve(),
|
|
12386
12439
|
foreground: /* @__PURE__ */ new Map(),
|
|
12387
12440
|
interrupted: false,
|
|
12441
|
+
refused: false,
|
|
12388
12442
|
stopGuard: null,
|
|
12389
12443
|
between: false,
|
|
12390
12444
|
snapshot: null,
|
|
@@ -12506,8 +12560,9 @@ ${text3}`;
|
|
|
12506
12560
|
}
|
|
12507
12561
|
} catch (e) {
|
|
12508
12562
|
if (!ac.signal.aborted) {
|
|
12509
|
-
|
|
12510
|
-
this.
|
|
12563
|
+
const ended = live.turnEnded || live.interrupted;
|
|
12564
|
+
this.deps.log.warn(ended ? "engine process ended after the turn" : "turn failed", { error: this.redactor.text(e.message) });
|
|
12565
|
+
if (!ended) this.error("TURN_FAILED");
|
|
12511
12566
|
}
|
|
12512
12567
|
} finally {
|
|
12513
12568
|
const stopped = ac.signal.aborted || live.interrupted;
|
|
@@ -12539,6 +12594,7 @@ ${text3}`;
|
|
|
12539
12594
|
if (ev.type === "message_start") live.timing.modelCall(typeof ev.message?.id === "string" ? ev.message.id : null);
|
|
12540
12595
|
} else if (f.type === "assistant") {
|
|
12541
12596
|
live.timing.modelEvent();
|
|
12597
|
+
live.refused = false;
|
|
12542
12598
|
const msg = f.message ?? {};
|
|
12543
12599
|
if (!f.error && typeof msg.model === "string") this.checkModel(live, msg.model);
|
|
12544
12600
|
live.timing.modelCall(typeof msg.id === "string" ? msg.id : null);
|
|
@@ -12614,6 +12670,7 @@ ${text3}`;
|
|
|
12614
12670
|
if (snap) this.recordCheckpoint(turnId, snap);
|
|
12615
12671
|
items.forEach((item, i) => this.messageStatus(String(item.turnId), i === 0 ? "started" : "absorbed"));
|
|
12616
12672
|
live.interrupted = false;
|
|
12673
|
+
live.refused = false;
|
|
12617
12674
|
live.turnId = turnId;
|
|
12618
12675
|
live.fellBack = false;
|
|
12619
12676
|
live.headUuid = members[0];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalequality/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.21",
|
|
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": {
|