@scalequality/cli 0.4.9 → 0.4.11
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 +3 -3
- package/dist/connect.cjs +174 -37
- 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": "
|
|
5
|
-
}
|
|
3
|
+
"sha256": "c2e9fefe31fcd4f9b46e4f5323336571b20380b6756c90b494584c1727d9f787",
|
|
4
|
+
"sourceCommit": "6437124ad656162948b2c83adfd87e5afc2d82a7"
|
|
5
|
+
}
|
package/dist/connect.cjs
CHANGED
|
@@ -1290,13 +1290,14 @@ async function decideToolUse(toolName, input, ctx) {
|
|
|
1290
1290
|
}
|
|
1291
1291
|
|
|
1292
1292
|
// src/application/services/workspaceSandbox/protections.ts
|
|
1293
|
-
var NO_PROTECTIONS = Object.freeze({ secretFiles: false, protectedPush: false, deployApproval: false, destructiveCommands: false, unknownPackage: null });
|
|
1293
|
+
var NO_PROTECTIONS = Object.freeze({ secretFiles: false, protectedPush: false, deployApproval: false, destructiveCommands: false, unknownPackage: null, toolApproval: false });
|
|
1294
1294
|
var KNOWN_RULES = {
|
|
1295
1295
|
"secret-files": ["block"],
|
|
1296
1296
|
"protected-push": ["require"],
|
|
1297
1297
|
"deploy-approval": ["require"],
|
|
1298
1298
|
"destructive-commands": ["require"],
|
|
1299
|
-
"unknown-package": ["block", "warn"]
|
|
1299
|
+
"unknown-package": ["block", "warn"],
|
|
1300
|
+
"tool-approval": ["require"]
|
|
1300
1301
|
};
|
|
1301
1302
|
function protectionsWireOf(raw) {
|
|
1302
1303
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
@@ -1318,7 +1319,8 @@ function parseProtections(raw) {
|
|
|
1318
1319
|
protectedPush: r["protected-push"] === "require",
|
|
1319
1320
|
deployApproval: r["deploy-approval"] === "require",
|
|
1320
1321
|
destructiveCommands: r["destructive-commands"] === "require",
|
|
1321
|
-
unknownPackage: r["unknown-package"] === "block" || r["unknown-package"] === "warn" ? r["unknown-package"] : null
|
|
1322
|
+
unknownPackage: r["unknown-package"] === "block" || r["unknown-package"] === "warn" ? r["unknown-package"] : null,
|
|
1323
|
+
toolApproval: r["tool-approval"] === "require"
|
|
1322
1324
|
};
|
|
1323
1325
|
}
|
|
1324
1326
|
var SECRET_FILE_MESSAGE = "Blocked by your organization's protection: secret files are never sent to the model. Do not try to read this file another way; ask the user for the specific value your task needs, or use the example file (such as .env.example).";
|
|
@@ -1857,6 +1859,7 @@ var RegistryChecker = class {
|
|
|
1857
1859
|
}
|
|
1858
1860
|
};
|
|
1859
1861
|
function mayNeedProtection(command, p) {
|
|
1862
|
+
if (p.toolApproval) return true;
|
|
1860
1863
|
if (p.destructiveCommands && isDestructiveCommand(command)) return true;
|
|
1861
1864
|
if (p.deployApproval && isDeployCommand(command)) return true;
|
|
1862
1865
|
if (p.protectedPush && gitPushes(command).length > 0) return true;
|
|
@@ -1889,6 +1892,7 @@ async function protectionVerdict(command, p, deps) {
|
|
|
1889
1892
|
if (p.destructiveCommands && isDestructiveCommand(command)) reasons.push("PROTECTION_DESTRUCTIVE_COMMAND");
|
|
1890
1893
|
if (p.deployApproval && isDeployCommand(command)) reasons.push("PROTECTION_DEPLOY");
|
|
1891
1894
|
if (p.protectedPush && await isProtectedPush(command, deps.currentBranch)) reasons.push("PROTECTION_PROTECTED_PUSH");
|
|
1895
|
+
if (p.toolApproval && !reasons.length) reasons.push("PROTECTION_TOOL_APPROVAL");
|
|
1892
1896
|
if (!reasons.length) return { kind: "allow" };
|
|
1893
1897
|
return { kind: "ask", reasons, ...pkg ? { package: pkg.name, packageReason: pkg.reason } : {} };
|
|
1894
1898
|
}
|
|
@@ -1896,7 +1900,12 @@ var PROTECTION_MESSAGES = {
|
|
|
1896
1900
|
rejected: "Your organization's protection requires a person to approve this command, and the user denied it. It did not run. Do not run it again or another way unless the user asks.",
|
|
1897
1901
|
timedOut: (minutes) => `Your organization's protection requires a person to approve this command, and nobody answered within ${minutes} minutes. It did not run. Ask the user before trying it again.`,
|
|
1898
1902
|
stopped: "The request was stopped before the command ran.",
|
|
1899
|
-
unavailable: "Your organization's protection requires a person to approve this command, and the approval could not be requested. It did not run. Tell the user; do not try to run it another way."
|
|
1903
|
+
unavailable: "Your organization's protection requires a person to approve this command, and the approval could not be requested. It did not run. Tell the user; do not try to run it another way.",
|
|
1904
|
+
// A file change under tool-approval.
|
|
1905
|
+
editRejected: (path) => `Your organization's protection requires a person to approve every change, and the user denied this change to ${path}. The file was not changed. Do not change it another way unless the user asks.`,
|
|
1906
|
+
editTimedOut: (path, minutes) => `Your organization's protection requires a person to approve every change, and nobody answered about ${path} within ${minutes} minutes. The file was not changed. Ask the user before trying again.`,
|
|
1907
|
+
editStopped: "The request was stopped before the file was changed.",
|
|
1908
|
+
editUnavailable: (path) => `Your organization's protection requires a person to approve every change, and the approval for ${path} could not be requested. The file was not changed. Tell the user; do not change it another way.`
|
|
1900
1909
|
};
|
|
1901
1910
|
|
|
1902
1911
|
// src/application/services/workspaceSandbox/SessionTransport.ts
|
|
@@ -2010,8 +2019,9 @@ var HttpSessionTransport = class {
|
|
|
2010
2019
|
async cancelCommandApproval(approvalId, reason) {
|
|
2011
2020
|
await this.post(`/command-approvals/${encodeURIComponent(approvalId)}/cancel`, { reason }, this.opts.requestTimeoutMs ?? 3e4);
|
|
2012
2021
|
}
|
|
2013
|
-
|
|
2014
|
-
|
|
2022
|
+
/** `current`: the model this engine works with now, which the API keeps for the session when the new one is refused. */
|
|
2023
|
+
async refreshRuntime(current) {
|
|
2024
|
+
const raw = await this.post("/runtime", current?.model ? { current: current.model } : {}, this.opts.requestTimeoutMs ?? 3e4);
|
|
2015
2025
|
const boot = toSessionBootstrap({ session: { model: raw?.model, reasoning: raw?.reasoning }, runtime: raw?.runtime, repository: null });
|
|
2016
2026
|
return { model: boot.model, runtime: boot.runtime, reasoning: boot.reasoning ?? null };
|
|
2017
2027
|
}
|
|
@@ -2129,7 +2139,13 @@ function toSessionBootstrap(raw) {
|
|
|
2129
2139
|
kind,
|
|
2130
2140
|
teamId: typeof rawScope?.teamId === "string" ? rawScope.teamId : null,
|
|
2131
2141
|
projectIds: Array.isArray(rawScope?.projectIds) ? rawScope.projectIds.filter((p) => typeof p === "string") : typeof session.projectId === "string" && session.projectId ? [session.projectId] : [],
|
|
2132
|
-
repos: repositories
|
|
2142
|
+
repos: repositories,
|
|
2143
|
+
// An area: its name, the chosen initiative's name and the project that measures it (an older API sends none).
|
|
2144
|
+
...kind === "BUSINESS_AREA" ? {
|
|
2145
|
+
areaName: text3(rawScope?.areaName) || null,
|
|
2146
|
+
initiativeName: text3(rawScope?.initiativeName) || null,
|
|
2147
|
+
initiativeProjectId: text3(rawScope?.initiativeProjectId) || null
|
|
2148
|
+
} : {}
|
|
2133
2149
|
},
|
|
2134
2150
|
branch: text3(session.branch, repo2 ? defaultBranch : ""),
|
|
2135
2151
|
projectId: text3(session.projectId),
|
|
@@ -3024,6 +3040,15 @@ var BrowserCommandGate = class {
|
|
|
3024
3040
|
this.chain = next.catch(() => void 0);
|
|
3025
3041
|
return next;
|
|
3026
3042
|
}
|
|
3043
|
+
checkProtectedEdit(path, opts = {}) {
|
|
3044
|
+
const next = this.chain.then(() => this.ask(`edit ${path}`, {
|
|
3045
|
+
signal: opts.signal,
|
|
3046
|
+
edit: { path, ...opts.preview ? { preview: opts.preview } : {} },
|
|
3047
|
+
protection: { reasons: ["PROTECTION_TOOL_APPROVAL"] }
|
|
3048
|
+
}));
|
|
3049
|
+
this.chain = next.catch(() => void 0);
|
|
3050
|
+
return next;
|
|
3051
|
+
}
|
|
3027
3052
|
checkEdit(path, opts = {}) {
|
|
3028
3053
|
const next = this.chain.then(() => this.ask(`edit ${path}`, { signal: opts.signal, edit: { path, ...opts.preview ? { preview: opts.preview } : {} } }));
|
|
3029
3054
|
this.chain = next.catch(() => void 0);
|
|
@@ -3040,7 +3065,7 @@ var BrowserCommandGate = class {
|
|
|
3040
3065
|
command,
|
|
3041
3066
|
cwd: this.o.root,
|
|
3042
3067
|
...opts.description ? { description: opts.description.slice(0, 1e3) } : {},
|
|
3043
|
-
reasons:
|
|
3068
|
+
reasons: protection ? [...protection.reasons, ...opts.edit ? [] : approvalReasons(command)] : opts.edit ? [] : approvalReasons(command),
|
|
3044
3069
|
suggestedRule: opts.edit || protection ? null : suggestRule(command),
|
|
3045
3070
|
...opts.edit ? { edit: { path: opts.edit.path, ...opts.edit.preview ? { preview: opts.edit.preview.slice(0, 4e3) } : {} } } : {},
|
|
3046
3071
|
...protection?.package ? { package: protection.package, ...protection.packageReason ? { packageReason: protection.packageReason } : {} } : {}
|
|
@@ -3048,6 +3073,7 @@ var BrowserCommandGate = class {
|
|
|
3048
3073
|
} catch (e) {
|
|
3049
3074
|
this.o.log?.(`command approval could not be requested: ${e.message}`);
|
|
3050
3075
|
const code = e instanceof CommandApprovalError ? e.code : null;
|
|
3076
|
+
if (protection && opts.edit) return { allow: false, message: PROTECTION_MESSAGES.editUnavailable(opts.edit.path) };
|
|
3051
3077
|
if (protection && code !== "COMMAND_NOT_ALLOWED") return { allow: false, message: PROTECTION_MESSAGES.unavailable };
|
|
3052
3078
|
return {
|
|
3053
3079
|
allow: false,
|
|
@@ -3065,10 +3091,12 @@ var BrowserCommandGate = class {
|
|
|
3065
3091
|
if (!decision) {
|
|
3066
3092
|
const reason = opts.signal?.aborted ? "STOPPED" : "TIMED_OUT";
|
|
3067
3093
|
void this.o.api.cancel(approvalId, reason).catch(() => void 0);
|
|
3094
|
+
if (protection && opts.edit) return { allow: false, message: reason === "STOPPED" ? PROTECTION_MESSAGES.editStopped : PROTECTION_MESSAGES.editTimedOut(opts.edit.path, Math.round(timeoutMs / 6e4)) };
|
|
3068
3095
|
if (protection) return { allow: false, message: reason === "STOPPED" ? PROTECTION_MESSAGES.stopped : PROTECTION_MESSAGES.timedOut(Math.round(timeoutMs / 6e4)) };
|
|
3069
3096
|
return reason === "STOPPED" ? { allow: false, message: STOPPED } : { allow: false, message: `The user did not answer the approval for this command within ${Math.round(timeoutMs / 6e4)} minutes, so it did not run. Ask the user before trying it again.` };
|
|
3070
3097
|
}
|
|
3071
3098
|
if (decision.decision === "REJECT") {
|
|
3099
|
+
if (protection && opts.edit) return { allow: false, message: PROTECTION_MESSAGES.editRejected(opts.edit.path) };
|
|
3072
3100
|
if (protection) return { allow: false, message: PROTECTION_MESSAGES.rejected };
|
|
3073
3101
|
return opts.edit ? { allow: false, message: `The user declined this change to ${opts.edit.path} in the browser. The file was not changed. Ask the user what to do differently.` } : { allow: false, message: "The user denied this command in the browser. It did not run. Do not run the same command again unless the user asks; continue another way or ask the user." };
|
|
3074
3102
|
}
|
|
@@ -4814,6 +4842,8 @@ function sandboxTestEnv() {
|
|
|
4814
4842
|
"BUNDLE_PATH",
|
|
4815
4843
|
"PUB_CACHE",
|
|
4816
4844
|
"FLUTTER_ROOT",
|
|
4845
|
+
"MIX_HOME",
|
|
4846
|
+
"HEX_HOME",
|
|
4817
4847
|
"MISE_DATA_DIR",
|
|
4818
4848
|
"MISE_CACHE_DIR",
|
|
4819
4849
|
"ELIXIR_ERL_OPTIONS"
|
|
@@ -4911,6 +4941,9 @@ var ENGINE_MESSAGES = {
|
|
|
4911
4941
|
MODEL_SWITCH_FAILED: "The model could not be changed now; this request uses {model}.",
|
|
4912
4942
|
MODEL_SWITCHED: "Now using {model}.",
|
|
4913
4943
|
GATEWAY_REFUSED: "The AI gateway refused this request ({code}).",
|
|
4944
|
+
// The AI Workspace's own credit (gateway BUDGET codes): said as credit, not as a budget the organization set.
|
|
4945
|
+
MANAGED_ALLOWANCE_EXHAUSTED: "The AI Workspace credit ran out ({code}).",
|
|
4946
|
+
MANAGED_PREPAID_REQUIRED: "This model uses purchased credit, and the purchased credit ran out ({code}).",
|
|
4914
4947
|
// Files, rewind and hunks.
|
|
4915
4948
|
DISCARD_FAILED: "The change to that file could not be discarded.",
|
|
4916
4949
|
DISCARD_REPOSITORY_REQUIRED: "Say which repository the file belongs to.",
|
|
@@ -4933,6 +4966,8 @@ var ENGINE_MESSAGES = {
|
|
|
4933
4966
|
// Pull requests.
|
|
4934
4967
|
BASE_ADVANCED: "The pull request could not be opened: the base branch in the repository is not at the commit this change was made on.",
|
|
4935
4968
|
PR_FAILED: "The pull request could not be opened.",
|
|
4969
|
+
PROVIDER_CONNECTION_REQUIRED: "The pull request could not be opened: the provider connection of {repoFullName} needs to be reconnected in ScaleQuality.",
|
|
4970
|
+
REPOSITORY_NOT_IN_PROJECT: "The pull request could not be opened: {repoFullName} is no longer a repository of this project.",
|
|
4936
4971
|
// Attachments and mentions.
|
|
4937
4972
|
ATTACHMENT_SKIPPED: "An attached image could not be read and was left out.",
|
|
4938
4973
|
MENTION_SKIPPED: "{path} could not be attached ({reason}).",
|
|
@@ -4958,10 +4993,66 @@ function engineMessage(code, params) {
|
|
|
4958
4993
|
return text3.replace(/\{(\w+)\}/g, (_m, k) => k === "code" && !(params && k in params) ? code : params && params[k] !== void 0 && params[k] !== null ? String(params[k]) : "");
|
|
4959
4994
|
}
|
|
4960
4995
|
var SQ_CODE = /"scalequality_code"\s*:\s*"([A-Z][A-Z0-9_]{2,79})"/;
|
|
4996
|
+
var GATEWAY_CODE_CATEGORIES = Object.freeze({
|
|
4997
|
+
...Object.fromEntries([
|
|
4998
|
+
"BUDGET_EXHAUSTED",
|
|
4999
|
+
"ORGANIZATION_BUDGET_EXHAUSTED",
|
|
5000
|
+
"ENVIRONMENT_BUDGET_EXHAUSTED",
|
|
5001
|
+
"TEAM_BUDGET_EXHAUSTED",
|
|
5002
|
+
"BUSINESS_UNIT_BUDGET_EXHAUSTED",
|
|
5003
|
+
"AREA_BUDGET_EXHAUSTED",
|
|
5004
|
+
"MODEL_BUDGET_EXHAUSTED",
|
|
5005
|
+
"MANAGED_ALLOWANCE_EXHAUSTED",
|
|
5006
|
+
"MANAGED_PREPAID_REQUIRED"
|
|
5007
|
+
].map((c) => [c, "BUDGET"])),
|
|
5008
|
+
PERSON_BUDGET_EXHAUSTED: "PERSON_BUDGET",
|
|
5009
|
+
CONCURRENCY_LIMIT: "CONCURRENCY",
|
|
5010
|
+
ORGANIZATION_CONCURRENCY_LIMIT: "CONCURRENCY",
|
|
5011
|
+
...Object.fromEntries([
|
|
5012
|
+
"INPUT_POLICY_BLOCK",
|
|
5013
|
+
"OUTPUT_POLICY_BLOCK",
|
|
5014
|
+
"SEMANTIC_INPUT_BLOCK",
|
|
5015
|
+
"SEMANTIC_OUTPUT_BLOCK",
|
|
5016
|
+
"TOOL_OUTPUT_POLICY_BLOCK",
|
|
5017
|
+
"TOOL_OUTPUT_NOT_ALLOWED",
|
|
5018
|
+
"TOOL_OUTPUT_INVALID",
|
|
5019
|
+
"PROTECTION_PROMPT_ATTACK",
|
|
5020
|
+
"PROTECTION_INDIRECT_INJECTION",
|
|
5021
|
+
"PROTECTION_HARMFUL_CONTENT",
|
|
5022
|
+
"PROTECTION_PROHIBITED_PRACTICE",
|
|
5023
|
+
"PROTECTION_SENSITIVE_INFERENCE",
|
|
5024
|
+
"PROTECTION_COMPANY_TOPIC",
|
|
5025
|
+
"PROTECTION_UNGROUNDED_ANSWER"
|
|
5026
|
+
].map((c) => [c, "POLICY"])),
|
|
5027
|
+
...Object.fromEntries([
|
|
5028
|
+
"MODEL_ALIAS_NOT_ALLOWED",
|
|
5029
|
+
"NO_PUBLISHED_POLICY",
|
|
5030
|
+
"MODEL_DISABLED_BY_ORGANIZATION",
|
|
5031
|
+
"MODEL_EXCLUDED_FROM_AUTO",
|
|
5032
|
+
"MANAGED_MODEL_PLAN_RESTRICTED",
|
|
5033
|
+
"MANAGED_ROUTE_NOT_ALLOWED",
|
|
5034
|
+
"MANAGED_CODE_PROFILE_REQUIRED",
|
|
5035
|
+
"CONNECTION_NOT_AVAILABLE"
|
|
5036
|
+
].map((c) => [c, "MODEL_ACCESS"])),
|
|
5037
|
+
...Object.fromEntries([
|
|
5038
|
+
"MANAGED_NOT_AVAILABLE",
|
|
5039
|
+
"WORKSPACE_ENTITLEMENT_REQUIRED",
|
|
5040
|
+
"MANAGED_ENTITLEMENT_REQUIRED",
|
|
5041
|
+
"TRIAL_UPGRADE_REQUIRED",
|
|
5042
|
+
"WORKSPACE_ACTIVE_PEOPLE_LIMIT"
|
|
5043
|
+
].map((c) => [c, "ENTITLEMENT"])),
|
|
5044
|
+
PROVIDER_RATE_LIMITED: "PROVIDER"
|
|
5045
|
+
});
|
|
5046
|
+
var BARE_CODE = /API Error:\s*(\d{3})\s+([A-Z][A-Z0-9_]{2,79})(?![A-Za-z0-9_])/;
|
|
5047
|
+
function bareGatewayError(text3) {
|
|
5048
|
+
const m = BARE_CODE.exec(text3);
|
|
5049
|
+
const category = m ? GATEWAY_CODE_CATEGORIES[m[2]] : void 0;
|
|
5050
|
+
return m && category ? { code: m[2], params: { status: Number(m[1]), category } } : null;
|
|
5051
|
+
}
|
|
4961
5052
|
function gatewayError(text3) {
|
|
4962
5053
|
if (!text3) return null;
|
|
4963
5054
|
const m = SQ_CODE.exec(text3);
|
|
4964
|
-
if (!m) return
|
|
5055
|
+
if (!m) return bareGatewayError(text3);
|
|
4965
5056
|
const params = {};
|
|
4966
5057
|
const status = /API Error:\s*(\d{3})/.exec(text3);
|
|
4967
5058
|
if (status) params.status = Number(status[1]);
|
|
@@ -9934,12 +10025,16 @@ var TurnTiming = class {
|
|
|
9934
10025
|
// src/application/services/workspaceSandbox/protectionApproval.ts
|
|
9935
10026
|
function gateProtectionApprover(gate) {
|
|
9936
10027
|
if (!gate.checkProtected) return null;
|
|
9937
|
-
return {
|
|
10028
|
+
return {
|
|
10029
|
+
ask: (command, check) => gate.checkProtected(command, check),
|
|
10030
|
+
...gate.checkProtectedEdit ? { askEdit: (path, check) => gate.checkProtectedEdit(path, check) } : {}
|
|
10031
|
+
};
|
|
9938
10032
|
}
|
|
9939
10033
|
function cloudProtectionApprover(o) {
|
|
9940
10034
|
let chain = Promise.resolve();
|
|
9941
10035
|
const ask = async (command, check) => {
|
|
9942
|
-
|
|
10036
|
+
const edit = check.edit;
|
|
10037
|
+
if (check.signal?.aborted) return { allow: false, message: edit ? PROTECTION_MESSAGES.editStopped : PROTECTION_MESSAGES.stopped };
|
|
9943
10038
|
let approvalId;
|
|
9944
10039
|
try {
|
|
9945
10040
|
({ approvalId } = await o.calls.requestCommandApproval({
|
|
@@ -9948,11 +10043,12 @@ function cloudProtectionApprover(o) {
|
|
|
9948
10043
|
...check.description ? { description: check.description.slice(0, 1e3) } : {},
|
|
9949
10044
|
reasons: [...check.reasons],
|
|
9950
10045
|
suggestedRule: null,
|
|
10046
|
+
...edit ? { edit: { path: edit.path, ...edit.preview ? { preview: edit.preview.slice(0, 4e3) } : {} } } : {},
|
|
9951
10047
|
...check.package ? { package: check.package, ...check.packageReason ? { packageReason: check.packageReason } : {} } : {}
|
|
9952
10048
|
}));
|
|
9953
10049
|
} catch (e) {
|
|
9954
10050
|
o.log?.(`protection approval could not be requested: ${e.message}`);
|
|
9955
|
-
return { allow: false, message: PROTECTION_MESSAGES.unavailable };
|
|
10051
|
+
return { allow: false, message: edit ? PROTECTION_MESSAGES.editUnavailable(edit.path) : PROTECTION_MESSAGES.unavailable };
|
|
9956
10052
|
}
|
|
9957
10053
|
const timeoutMs = o.timeoutMs ?? COMMAND_APPROVAL_TIMEOUT_MS;
|
|
9958
10054
|
const timer = new AbortController();
|
|
@@ -9963,16 +10059,21 @@ function cloudProtectionApprover(o) {
|
|
|
9963
10059
|
if (!decision) {
|
|
9964
10060
|
const reason = check.signal?.aborted ? "STOPPED" : "TIMED_OUT";
|
|
9965
10061
|
void o.calls.cancelCommandApproval(approvalId, reason).catch(() => void 0);
|
|
9966
|
-
|
|
10062
|
+
const minutes = Math.round(timeoutMs / 6e4);
|
|
10063
|
+
if (edit) return { allow: false, message: reason === "STOPPED" ? PROTECTION_MESSAGES.editStopped : PROTECTION_MESSAGES.editTimedOut(edit.path, minutes) };
|
|
10064
|
+
return { allow: false, message: reason === "STOPPED" ? PROTECTION_MESSAGES.stopped : PROTECTION_MESSAGES.timedOut(minutes) };
|
|
9967
10065
|
}
|
|
9968
|
-
|
|
10066
|
+
if (decision.decision === "APPROVE") return { allow: true };
|
|
10067
|
+
return { allow: false, message: edit ? PROTECTION_MESSAGES.editRejected(edit.path) : PROTECTION_MESSAGES.rejected };
|
|
10068
|
+
};
|
|
10069
|
+
const queued = (run2) => {
|
|
10070
|
+
const next = chain.then(run2);
|
|
10071
|
+
chain = next.catch(() => void 0);
|
|
10072
|
+
return next;
|
|
9969
10073
|
};
|
|
9970
10074
|
return {
|
|
9971
|
-
ask(command, check)
|
|
9972
|
-
|
|
9973
|
-
chain = next.catch(() => void 0);
|
|
9974
|
-
return next;
|
|
9975
|
-
}
|
|
10075
|
+
ask: (command, check) => queued(() => ask(command, check)),
|
|
10076
|
+
askEdit: (path, check) => queued(() => ask(`edit ${path}`, { reasons: ["PROTECTION_TOOL_APPROVAL"], signal: check.signal, edit: { path, ...check.preview ? { preview: check.preview } : {} } }))
|
|
9976
10077
|
};
|
|
9977
10078
|
}
|
|
9978
10079
|
|
|
@@ -9997,10 +10098,15 @@ function layoutLines(root, l) {
|
|
|
9997
10098
|
}
|
|
9998
10099
|
var LISTED = 30;
|
|
9999
10100
|
function scopeLine(c) {
|
|
10000
|
-
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" ?
|
|
10101
|
+
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 the business area${c.scope.areaName ? ` "${c.scope.areaName}"` : ""} (the ones associated with it and the ones that measure its initiatives)` : c.scope.projectIds.length === 1 ? `project ${c.scope.projectIds[0]}` : `${c.scope.projectIds.length} projects`;
|
|
10001
10102
|
const n = c.scope.repos.length;
|
|
10002
10103
|
const names = c.scope.repos.slice(0, LISTED).map((r) => `${r.repoFullName} (${r.provider})`).join(", ");
|
|
10003
|
-
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
|
|
10104
|
+
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.${initiativeLine(c)}`;
|
|
10105
|
+
}
|
|
10106
|
+
function initiativeLine(c) {
|
|
10107
|
+
if (c.scope.kind !== "BUSINESS_AREA" || !c.scope.initiativeName) return "";
|
|
10108
|
+
const own = c.scope.initiativeProjectId ? c.scope.repos.filter((r) => r.projectId === c.scope.initiativeProjectId).map((r) => r.repoFullName) : [];
|
|
10109
|
+
return ` The user is working on the initiative "${c.scope.initiativeName}"${own.length ? `; its code is ${own.join(", ")} (the project that measures the initiative). Start there when the user talks about the initiative's code.` : "; no project measures this initiative yet."}`;
|
|
10004
10110
|
}
|
|
10005
10111
|
function openLine(c) {
|
|
10006
10112
|
if (!c.open.length) return "No repository is open yet.";
|
|
@@ -11242,11 +11348,15 @@ var WorkspaceEngine = class {
|
|
|
11242
11348
|
})) : null;
|
|
11243
11349
|
if (!repos) return;
|
|
11244
11350
|
const kind = p.kind === "ALL" || p.kind === "TEAM" || p.kind === "BUSINESS_AREA" ? p.kind : "PROJECTS";
|
|
11351
|
+
const named = (v) => typeof v === "string" && v ? v : null;
|
|
11245
11352
|
this.scope = {
|
|
11246
11353
|
kind,
|
|
11247
11354
|
teamId: typeof p.teamId === "string" ? p.teamId : null,
|
|
11248
11355
|
projectIds: Array.isArray(p.projectIds) ? p.projectIds.filter((x) => typeof x === "string") : [],
|
|
11249
|
-
repos
|
|
11356
|
+
repos,
|
|
11357
|
+
areaName: named(p.areaName),
|
|
11358
|
+
initiativeName: named(p.initiativeName),
|
|
11359
|
+
initiativeProjectId: named(p.initiativeProjectId)
|
|
11250
11360
|
};
|
|
11251
11361
|
for (const repo2 of this.repos.values()) {
|
|
11252
11362
|
if (repo2.localId) {
|
|
@@ -11575,7 +11685,7 @@ ${String(item.content)}` : String(item.content);
|
|
|
11575
11685
|
if (!model || model === boot.model || !this.deps.transport.refreshRuntime) return;
|
|
11576
11686
|
const step = this.ownStep("model", "tool", `Switching to ${model}`, "STEP_MODEL_SWITCH", { model });
|
|
11577
11687
|
try {
|
|
11578
|
-
const fresh = await this.deps.transport.refreshRuntime();
|
|
11688
|
+
const fresh = await this.deps.transport.refreshRuntime({ model: boot.model });
|
|
11579
11689
|
if (!fresh.runtime.token || !fresh.runtime.baseUrl) throw new Error("RUNTIME_INCOMPLETE");
|
|
11580
11690
|
this.redactor.add(fresh.runtime.token);
|
|
11581
11691
|
boot.runtime = fresh.runtime;
|
|
@@ -11632,10 +11742,11 @@ ${String(item.content)}` : String(item.content);
|
|
|
11632
11742
|
async renewCredential() {
|
|
11633
11743
|
if (!this.deps.transport.refreshRuntime) return;
|
|
11634
11744
|
try {
|
|
11635
|
-
const fresh = await this.deps.transport.refreshRuntime();
|
|
11745
|
+
const fresh = await this.deps.transport.refreshRuntime({ model: this.boot.model });
|
|
11636
11746
|
if (!fresh.runtime.token || !fresh.runtime.baseUrl) throw new Error("RUNTIME_INCOMPLETE");
|
|
11637
11747
|
this.redactor.add(fresh.runtime.token);
|
|
11638
11748
|
this.boot.runtime = fresh.runtime;
|
|
11749
|
+
if (fresh.model) this.boot.model = fresh.model;
|
|
11639
11750
|
this.reasoningCapability = parseReasoningCapability(fresh.runtime.reasoning ?? null);
|
|
11640
11751
|
this.reasoningLevel = effectiveReasoning(fresh.reasoning ?? this.reasoningLevel, this.reasoningCapability);
|
|
11641
11752
|
} catch (e) {
|
|
@@ -13074,16 +13185,33 @@ _Measured on an earlier version of this change._`);
|
|
|
13074
13185
|
if (e instanceof TransportError && e.code === REPOSITORY_NOT_IN_SCOPE) {
|
|
13075
13186
|
return text(`${REPOSITORY_NOT_IN_SCOPE}: ${target} left this session's scope before the pull request was opened. It was not opened.`, true);
|
|
13076
13187
|
}
|
|
13077
|
-
|
|
13078
|
-
|
|
13079
|
-
|
|
13080
|
-
return text(`The pull request could not be opened: the base branch in the repository is not at the commit this change was made on.${where} Tell the user; do not retry without being asked.`, true);
|
|
13081
|
-
}
|
|
13082
|
-
this.error("PR_FAILED", { repoFullName: target });
|
|
13083
|
-
return text("The pull request could not be opened. Tell the user; do not retry without being asked.", true);
|
|
13188
|
+
const failure = pullRequestFailure(status, e instanceof TransportError ? e.code : void 0, target, this.local);
|
|
13189
|
+
this.error(failure.code, failure.params);
|
|
13190
|
+
return text(`${failure.say} Tell the user; do not retry without being asked.`, true);
|
|
13084
13191
|
}
|
|
13085
13192
|
}
|
|
13086
13193
|
};
|
|
13194
|
+
function pullRequestFailure(status, code, repoFullName, local) {
|
|
13195
|
+
if (status === 409 && code === "PROVIDER_CONNECTION_REQUIRED") return {
|
|
13196
|
+
code: "PROVIDER_CONNECTION_REQUIRED",
|
|
13197
|
+
params: { repoFullName },
|
|
13198
|
+
say: `The pull request could not be opened: the provider connection of ${repoFullName} needs to be reconnected in ScaleQuality.`
|
|
13199
|
+
};
|
|
13200
|
+
if (status === 409 && code === "REPOSITORY_NOT_IN_PROJECT") return {
|
|
13201
|
+
code: "REPOSITORY_NOT_IN_PROJECT",
|
|
13202
|
+
params: { repoFullName },
|
|
13203
|
+
say: `The pull request could not be opened: ${repoFullName} is no longer a repository of this project.`
|
|
13204
|
+
};
|
|
13205
|
+
if (status === 409 && (!code || code === "BASE_ADVANCED")) {
|
|
13206
|
+
const where = local ? " Update this folder to the latest commit of the base branch (git pull), then ask again." : "";
|
|
13207
|
+
return {
|
|
13208
|
+
code: "BASE_ADVANCED",
|
|
13209
|
+
params: { repoFullName, local },
|
|
13210
|
+
say: `The pull request could not be opened: the base branch in the repository is not at the commit this change was made on.${where}`
|
|
13211
|
+
};
|
|
13212
|
+
}
|
|
13213
|
+
return { code: "PR_FAILED", params: { repoFullName, ...code ? { reason: code } : {} }, say: "The pull request could not be opened." };
|
|
13214
|
+
}
|
|
13087
13215
|
var LOCAL_ENV_PASSTHROUGH = [
|
|
13088
13216
|
"LOGNAME",
|
|
13089
13217
|
"COLORTERM",
|
|
@@ -13236,11 +13364,11 @@ function buildQueryOptions(o) {
|
|
|
13236
13364
|
}
|
|
13237
13365
|
const protections = o.protections?.() ?? NO_PROTECTIONS;
|
|
13238
13366
|
const d = await decideToolUse(name, input.tool_input ?? {}, { ...o.policy, protections });
|
|
13239
|
-
const guarded = d.behavior === "allow" && name === "Bash" && mayNeedProtection(String(d.updatedInput.command ?? ""), protections);
|
|
13367
|
+
const guarded = d.behavior === "allow" && (name === "Bash" && mayNeedProtection(String(d.updatedInput.command ?? ""), protections) || protections.toolApproval && !o.plan && name in WRITE_TOOL_PATHS);
|
|
13240
13368
|
const gated = guarded || d.behavior === "allow" && !!gate && (name === "Bash" || !!gate.checkEdit && !o.plan && name in WRITE_TOOL_PATHS);
|
|
13241
13369
|
if (d.behavior === "allow" && !gated) await beforeWrite(name, d.updatedInput);
|
|
13242
13370
|
if (gated) {
|
|
13243
|
-
return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "ask", permissionDecisionReason: guarded ? "This
|
|
13371
|
+
return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "ask", permissionDecisionReason: guarded ? "This action follows the organization's protections." : "Commands and changes on this machine follow the person's permission mode." } };
|
|
13244
13372
|
}
|
|
13245
13373
|
return d.behavior === "allow" ? { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "allow", updatedInput: d.updatedInput } } : { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: d.message } };
|
|
13246
13374
|
};
|
|
@@ -13278,10 +13406,18 @@ function buildQueryOptions(o) {
|
|
|
13278
13406
|
protectionApproved = true;
|
|
13279
13407
|
}
|
|
13280
13408
|
}
|
|
13281
|
-
|
|
13409
|
+
const changes = !o.plan && toolName in WRITE_TOOL_PATHS;
|
|
13410
|
+
const changePath = () => {
|
|
13282
13411
|
const field = WRITE_TOOL_PATHS[toolName];
|
|
13283
|
-
|
|
13284
|
-
|
|
13412
|
+
return typeof d.updatedInput[field] === "string" ? (0, import_path15.relative)(o.root, d.updatedInput[field]) || String(d.updatedInput[field]) : "";
|
|
13413
|
+
};
|
|
13414
|
+
if (changes && protections.toolApproval) {
|
|
13415
|
+
const path = changePath(), askEdit = o.protection?.approver?.askEdit;
|
|
13416
|
+
if (!askEdit) return { behavior: "deny", message: PROTECTION_MESSAGES.editUnavailable(path) };
|
|
13417
|
+
const r = await askEdit(path, { preview: editPreview(toolName, d.updatedInput), signal: opts?.signal ?? o.abortController.signal });
|
|
13418
|
+
if (!r.allow) return { behavior: "deny", message: r.message };
|
|
13419
|
+
} else if (gate?.checkEdit && changes) {
|
|
13420
|
+
const r = await gate.checkEdit(changePath(), { preview: editPreview(toolName, d.updatedInput), signal: opts?.signal ?? o.abortController.signal });
|
|
13285
13421
|
if (!r.allow) return { behavior: "deny", message: r.message };
|
|
13286
13422
|
}
|
|
13287
13423
|
await beforeWrite(toolName, d.updatedInput);
|
|
@@ -13344,7 +13480,8 @@ function timedGate(gate, took) {
|
|
|
13344
13480
|
return {
|
|
13345
13481
|
check: (command, opts) => timed(() => gate.check(command, opts)),
|
|
13346
13482
|
...gate.checkEdit ? { checkEdit: (path, opts) => timed(() => gate.checkEdit(path, opts)) } : {},
|
|
13347
|
-
...gate.checkProtected ? { checkProtected: (command, opts) => timed(() => gate.checkProtected(command, opts)) } : {}
|
|
13483
|
+
...gate.checkProtected ? { checkProtected: (command, opts) => timed(() => gate.checkProtected(command, opts)) } : {},
|
|
13484
|
+
...gate.checkProtectedEdit ? { checkProtectedEdit: (path, opts) => timed(() => gate.checkProtectedEdit(path, opts)) } : {}
|
|
13348
13485
|
};
|
|
13349
13486
|
}
|
|
13350
13487
|
async function hasLocalTranscript(configDir, sessionId) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalequality/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.11",
|
|
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": {
|