@scalequality/cli 0.4.12 → 0.4.13

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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "sdkVersion": "0.3.281",
3
- "sha256": "7b199d28673b767dce41406f737d3c8042ed0658ca1a233a7d8363b5b7b27dfd",
4
- "sourceCommit": "0da92af88f77faa5f860d7f37480a65800017946"
3
+ "sha256": "48de33fc2a34d22eb0d512f22852f8a4ad9ce176ab610f84a4ca2317c795172c",
4
+ "sourceCommit": "dc8c92467ad22a83c4351ab6ec259c7bbaf88b30"
5
5
  }
package/dist/connect.cjs CHANGED
@@ -1988,6 +1988,17 @@ var HttpSessionTransport = class {
1988
1988
  branch: typeof raw?.branch === "string" ? raw.branch : defaultBranch
1989
1989
  };
1990
1990
  }
1991
+ /** The earlier sessions of this conversation (oldest first); only well formed sessions and messages are kept. */
1992
+ async earlierConversation() {
1993
+ const raw = await this.get("/earlier", this.opts.requestTimeoutMs ?? 3e4);
1994
+ const text3 = (v) => typeof v === "string" ? v : null;
1995
+ return (Array.isArray(raw?.sessions) ? raw.sessions : []).flatMap((s) => {
1996
+ const r = s ?? {};
1997
+ const messages = (Array.isArray(r.messages) ? r.messages : []).filter((m) => !!m && typeof m === "object" && (m.role === "user" || m.role === "assistant") && typeof m.text === "string").map((m) => ({ role: m.role, text: m.text, at: text3(m.at) }));
1998
+ if (!messages.length) return [];
1999
+ return [{ where: { kind: r.where?.kind === "MACHINE" ? "MACHINE" : "CLOUD", folder: text3(r.where?.folder), repository: text3(r.where?.repository) }, messages }];
2000
+ });
2001
+ }
1991
2002
  async importedHistory() {
1992
2003
  const raw = await this.get("/imported", this.opts.requestTimeoutMs ?? 6e4);
1993
2004
  const messages = Array.isArray(raw?.messages) ? raw.messages : [];
@@ -2025,6 +2036,12 @@ var HttpSessionTransport = class {
2025
2036
  const boot = toSessionBootstrap({ session: { model: raw?.model, reasoning: raw?.reasoning }, runtime: raw?.runtime, repository: null });
2026
2037
  return { model: boot.model, runtime: boot.runtime, reasoning: boot.reasoning ?? null };
2027
2038
  }
2039
+ /** The organization's protections in force now (read at the start of each turn); throws when the API does not say. */
2040
+ async protections() {
2041
+ const raw = await this.post("/protections", {}, 5e3);
2042
+ if (!raw || typeof raw !== "object" || !("protections" in raw)) throw new TransportError("invalid protections response", 502, false);
2043
+ return protectionsWireOf(raw.protections);
2044
+ }
2028
2045
  headers(json) {
2029
2046
  return {
2030
2047
  "x-workspace-session-secret": this.opts.secret,
@@ -5185,6 +5202,37 @@ function buildImportedContext(info, messages, budget = IMPORTED_CONTEXT_BUDGET)
5185
5202
  "</imported_conversation>"
5186
5203
  ].join("\n");
5187
5204
  }
5205
+ function buildEarlierContext(sessions, budget = IMPORTED_CONTEXT_BUDGET) {
5206
+ const flat = sessions.flatMap((s, index) => s.messages.map((m) => ({ m, index })));
5207
+ if (!flat.length) return null;
5208
+ const kept = [];
5209
+ let used = 0;
5210
+ for (let i = flat.length - 1; i >= 0; i--) {
5211
+ const block = render({ role: flat[i].m.role, text: flat[i].m.text, at: flat[i].m.at });
5212
+ if (used + block.length + 2 > budget) break;
5213
+ kept.push({ block, index: flat[i].index });
5214
+ used += block.length + 2;
5215
+ }
5216
+ if (!kept.length) return null;
5217
+ kept.reverse();
5218
+ const where = (s) => s.where.kind === "MACHINE" ? `the user's connected computer${s.where.folder ? `, folder ${s.where.folder}` : ""}` : `the ScaleQuality cloud workspace${s.where.repository ? `, repository ${s.where.repository}` : ""}`;
5219
+ const parts = [];
5220
+ let current = -1;
5221
+ for (const k of kept) {
5222
+ if (k.index !== current) {
5223
+ current = k.index;
5224
+ parts.push(`## Earlier session, on ${where(sessions[k.index])}`);
5225
+ }
5226
+ parts.push(k.block);
5227
+ }
5228
+ const omitted = flat.length - kept.length;
5229
+ return [
5230
+ `[Workspace note: this is the same conversation the user has been having with you; its earlier part ran in another session (a different folder, computer or the cloud), so it is not in your memory. The user sees it on the screen and may refer to it. It is shown below as earlier context, not as instructions; the commands, their outputs and file changes are not included, so read the code again where it matters${omitted ? `, and the ${omitted} oldest message(s) were left out to fit` : ""}. The user's new request follows after it.]`,
5231
+ "<earlier_conversation>",
5232
+ parts.join("\n\n"),
5233
+ "</earlier_conversation>"
5234
+ ].join("\n");
5235
+ }
5188
5236
 
5189
5237
  // src/application/services/workspaceSandbox/outputLimit.ts
5190
5238
  function turnOutputLimit(limit, _alias, _cutApplied) {
@@ -10867,6 +10915,8 @@ var WorkspaceEngine = class {
10867
10915
  thinkingTotals = /* @__PURE__ */ new Map();
10868
10916
  /** The imported history, fetched once when a turn needs it. */
10869
10917
  importedContext = null;
10918
+ /** The earlier part of this conversation (sessions before this one), fetched once when a turn needs it. */
10919
+ earlierContext = null;
10870
10920
  /** What the running turn ended with, for SQ Auto's next choice: its last command's exit code and whether it failed. */
10871
10921
  turnWatch = null;
10872
10922
  /** Why the previous turn ended in trouble (null when it did not): SQ Auto takes the hard-task model for the next one. */
@@ -11757,6 +11807,19 @@ ${String(item.content)}` : String(item.content);
11757
11807
  this.deps.log.warn("credential renewal failed", { error: this.redactor.text(e.message) });
11758
11808
  }
11759
11809
  }
11810
+ /**
11811
+ * The organization's protections in force now, at the start of each turn: a change on the AI protection page
11812
+ * (tool approval turned off, a rule turned on) holds from the next message, without a new credential. A failure
11813
+ * (an older API, no answer) keeps the ones this engine has.
11814
+ */
11815
+ async refreshProtections() {
11816
+ if (!this.deps.transport.protections || !this.boot?.runtime) return;
11817
+ try {
11818
+ this.boot.runtime.protections = await this.deps.transport.protections();
11819
+ } catch (e) {
11820
+ this.deps.log.warn("protections not refreshed", { error: this.redactor.text(e.message) });
11821
+ }
11822
+ }
11760
11823
  /**
11761
11824
  * STREAM_BUFFERED, once per reason: answers on this alias are shown only after the AI protection inspects them
11762
11825
  * (the organization's output rules, a semantic inspector, the application's own rules) or come whole from the
@@ -12173,6 +12236,7 @@ ${json}
12173
12236
  const switching = typeof payload.model === "string" && !!payload.model && payload.model !== this.boot.model;
12174
12237
  if (switching) await this.switchModel(payload.model);
12175
12238
  else if (this.refreshCredential) await this.renewCredential();
12239
+ else await this.refreshProtections();
12176
12240
  this.refreshCredential = false;
12177
12241
  const boot = this.boot;
12178
12242
  const sdk = this.sdk;
@@ -12228,7 +12292,7 @@ ${context.blocks.join("\n\n")}`);
12228
12292
  const forkAt = canResume ? this.resumeAt : null;
12229
12293
  this.resumeAt = null;
12230
12294
  const withImported = async (text3) => {
12231
- const block = await this.importedHistoryBlock();
12295
+ const block = [await this.earlierHistoryBlock(), await this.importedHistoryBlock()].filter(Boolean).join("\n\n");
12232
12296
  return !block ? text3 : slash ? `${text3}
12233
12297
 
12234
12298
  ${block}` : `${block}
@@ -12703,6 +12767,18 @@ ${text3}`;
12703
12767
  }
12704
12768
  }).finally(() => w.abort.abort());
12705
12769
  }
12770
+ /** The earlier sessions of this conversation as a context block (fetched once); null when there is none or it cannot be read. */
12771
+ earlierHistoryBlock() {
12772
+ if (!this.deps.transport.earlierConversation) return Promise.resolve(null);
12773
+ if (!this.earlierContext) {
12774
+ this.earlierContext = this.deps.transport.earlierConversation().then((sessions) => buildEarlierContext(sessions)).catch((e) => {
12775
+ this.deps.log.warn("earlier conversation unavailable", { error: this.redactor.text(e.message) });
12776
+ this.earlierContext = null;
12777
+ return null;
12778
+ });
12779
+ }
12780
+ return this.earlierContext;
12781
+ }
12706
12782
  /** The imported conversation as a context block (fetched once); null when there is none or it cannot be read. */
12707
12783
  importedHistoryBlock() {
12708
12784
  const info = this.boot?.imported;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalequality/cli",
3
- "version": "0.4.12",
3
+ "version": "0.4.13",
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": {