@usabledev/usable-chat 1.149.0 → 1.150.0

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.
Files changed (2) hide show
  1. package/cli.js +149 -5
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -12309,9 +12309,9 @@ var init_markdown = __esm({
12309
12309
  const startNumber = typeof token.start === "number" ? token.start : 1;
12310
12310
  for (let i18 = 0; i18 < token.items.length; i18++) {
12311
12311
  const item = token.items[i18];
12312
- const bullet2 = token.ordered ? this.options.preserveOrderedListMarkers ? this.getOrderedListMarker(item) ?? `${startNumber + i18}. ` : `${startNumber + i18}. ` : "- ";
12312
+ const bullet3 = token.ordered ? this.options.preserveOrderedListMarkers ? this.getOrderedListMarker(item) ?? `${startNumber + i18}. ` : `${startNumber + i18}. ` : "- ";
12313
12313
  const taskMarker = item.task ? `[${item.checked ? "x" : " "}] ` : "";
12314
- const marker15 = bullet2 + taskMarker;
12314
+ const marker15 = bullet3 + taskMarker;
12315
12315
  const firstPrefix = indent + this.theme.listBullet(marker15);
12316
12316
  const continuationPrefix = indent + " ".repeat(visibleWidth(marker15));
12317
12317
  const itemWidth = Math.max(1, width - visibleWidth(firstPrefix));
@@ -23746,6 +23746,109 @@ Usable is the user's long-term memory. When a task touches project domain, past
23746
23746
  }
23747
23747
  });
23748
23748
 
23749
+ // src/core/orchestrator/harness-compaction.ts
23750
+ function toolCallsOf(msg) {
23751
+ const out = [];
23752
+ const m33 = msg;
23753
+ if (Array.isArray(m33.tool_calls)) {
23754
+ for (const tc of m33.tool_calls) {
23755
+ const fn4 = tc.function ?? tc;
23756
+ const name14 = String(fn4.name ?? tc.name ?? "");
23757
+ let args = {};
23758
+ const raw = fn4.arguments ?? tc.args ?? tc.input;
23759
+ if (typeof raw === "string") {
23760
+ try {
23761
+ args = JSON.parse(raw);
23762
+ } catch {
23763
+ args = {};
23764
+ }
23765
+ } else if (raw && typeof raw === "object") {
23766
+ args = raw;
23767
+ }
23768
+ if (name14) out.push({ name: name14, args });
23769
+ }
23770
+ }
23771
+ if (Array.isArray(msg.content)) {
23772
+ for (const part of msg.content) {
23773
+ const type = part.type;
23774
+ if (type === "tool_use" || type === "tool-call") {
23775
+ const name14 = String(part.name ?? part.toolName ?? "");
23776
+ const args = part.input ?? part.args ?? {};
23777
+ if (name14) out.push({ name: name14, args });
23778
+ }
23779
+ }
23780
+ }
23781
+ return out;
23782
+ }
23783
+ function extractFileOps(messages4) {
23784
+ const read = /* @__PURE__ */ new Set();
23785
+ const modified = /* @__PURE__ */ new Set();
23786
+ for (const msg of messages4) {
23787
+ if (msg.role !== "assistant") continue;
23788
+ for (const { name: name14, args } of toolCallsOf(msg)) {
23789
+ const path11 = args.path ?? args.file ?? args.pattern;
23790
+ if (!path11 || typeof path11 !== "string") continue;
23791
+ if (WRITE_TOOLS.has(name14)) modified.add(path11);
23792
+ else if (READ_TOOLS.has(name14)) read.add(path11);
23793
+ }
23794
+ }
23795
+ return { read: [...read], modified: [...modified] };
23796
+ }
23797
+ function isStepStart(messages4, i18) {
23798
+ if (i18 <= 0) return false;
23799
+ return messages4[i18].role === "assistant" && messages4[i18 - 1].role !== "assistant";
23800
+ }
23801
+ function findCutIndex(messages4, keepRecentTokens, countTokens2) {
23802
+ let sum2 = 0;
23803
+ for (let i18 = messages4.length - 1; i18 >= 1; i18--) {
23804
+ sum2 += countTokens2(messages4[i18]);
23805
+ if (sum2 >= keepRecentTokens && isStepStart(messages4, i18)) return i18;
23806
+ }
23807
+ return 0;
23808
+ }
23809
+ function bullet2(label, items) {
23810
+ if (items.length === 0) return "";
23811
+ const shown = items.slice(0, 30);
23812
+ const more = items.length > shown.length ? ` (+${items.length - shown.length} more)` : "";
23813
+ return `
23814
+ ${label}: ${shown.join(", ")}${more}`;
23815
+ }
23816
+ function goalText(messages4) {
23817
+ const first = messages4[0];
23818
+ if (!first || first.role !== "user") return "";
23819
+ if (typeof first.content === "string") return first.content;
23820
+ if (Array.isArray(first.content)) {
23821
+ const text2 = first.content.filter((p28) => p28.type === "text" && typeof p28.text === "string").map((p28) => p28.text).join("\n");
23822
+ return text2;
23823
+ }
23824
+ return "";
23825
+ }
23826
+ function buildCompactionMarker(goal, droppedCount, fileOps) {
23827
+ const text2 = (goal ? `Original task:
23828
+ ${goal}
23829
+
23830
+ ` : "") + `[context-compacted] ${droppedCount} earlier messages were pruned to free the context window; the original task above and the most recent steps are preserved.` + bullet2("Files read so far", fileOps.read) + bullet2("Files modified so far", fileOps.modified) + `
23831
+ Continue the task from where it left off \u2014 do NOT restart it. Re-read a file with the read tool if you need its current contents.`;
23832
+ return { role: "user", content: text2 };
23833
+ }
23834
+ function compactHarnessConversation(messages4, opts) {
23835
+ const minMessages = opts.minMessages ?? 8;
23836
+ if (messages4.length < minMessages) return { messages: messages4, compacted: false, droppedCount: 0 };
23837
+ const cut = findCutIndex(messages4, opts.keepRecentTokens, opts.countTokens);
23838
+ if (cut < 3) return { messages: messages4, compacted: false, droppedCount: 0 };
23839
+ const dropped = messages4.slice(0, cut);
23840
+ const marker15 = buildCompactionMarker(goalText(messages4), dropped.length, extractFileOps(dropped));
23841
+ return { messages: [marker15, ...messages4.slice(cut)], compacted: true, droppedCount: dropped.length };
23842
+ }
23843
+ var READ_TOOLS, WRITE_TOOLS;
23844
+ var init_harness_compaction = __esm({
23845
+ "src/core/orchestrator/harness-compaction.ts"() {
23846
+ "use strict";
23847
+ READ_TOOLS = /* @__PURE__ */ new Set(["read", "glob", "grep"]);
23848
+ WRITE_TOOLS = /* @__PURE__ */ new Set(["write", "edit"]);
23849
+ }
23850
+ });
23851
+
23749
23852
  // src/core/orchestrator/ask-user-question-prompt.ts
23750
23853
  var ASK_USER_QUESTION_PROMPT;
23751
23854
  var init_ask_user_question_prompt = __esm({
@@ -242907,6 +243010,37 @@ ${combinedSystemMessage}` : combinedSystemMessage;
242907
243010
  name: msg.name
242908
243011
  };
242909
243012
  };
243013
+ if (config3.localFilesystem) {
243014
+ const harnessCtxLen = getModelById(selectedModelId)?.capabilities.contextLength || 128e3;
243015
+ const envRatio = Number(process.env.USABLE_HARNESS_COMPACT_RATIO);
243016
+ const compactRatio = envRatio > 0 && envRatio <= 1 ? envRatio : HARNESS_COMPACT_RATIO;
243017
+ const tokensOf = (m33) => countTokens(typeof m33.content === "string" ? m33.content : JSON.stringify(m33.content));
243018
+ const estTokens = conversationMessages.reduce((s17, m33) => s17 + tokensOf(m33), 0);
243019
+ if (estTokens > harnessCtxLen * compactRatio) {
243020
+ const beforeLen = conversationMessages.length;
243021
+ const envKeep = Number(process.env.USABLE_HARNESS_KEEP_TOKENS);
243022
+ const keepRecentTokens = envKeep > 0 ? envKeep : Math.floor(harnessCtxLen * HARNESS_KEEP_RECENT_RATIO);
243023
+ const res = compactHarnessConversation(conversationMessages, {
243024
+ keepRecentTokens,
243025
+ countTokens: tokensOf
243026
+ });
243027
+ if (res.compacted) {
243028
+ conversationMessages = ensureToolCallIntegrity(res.messages);
243029
+ orchestrationLogger.warn("\u{1F9F9} Harness proactive compaction", {
243030
+ beforeLen,
243031
+ afterLen: conversationMessages.length,
243032
+ droppedCount: res.droppedCount,
243033
+ estTokens,
243034
+ contextLength: harnessCtxLen
243035
+ });
243036
+ const compactPlan = emitter.emit("plan", {
243037
+ plan: `\u{1F9F9} Compacted ${res.droppedCount} older messages to free context \u2014 continuing.`,
243038
+ steps: config3.maxSteps
243039
+ });
243040
+ multiplexer.send(compactPlan);
243041
+ }
243042
+ }
243043
+ }
242910
243044
  const filteredConversationMessages = filterImagesFromHistory(conversationMessages, {
242911
243045
  preserveLastUserMessageImages: !!modelInfo?.capabilities?.vision
242912
243046
  });
@@ -243096,7 +243230,7 @@ ${combinedSystemMessage}` : combinedSystemMessage;
243096
243230
  });
243097
243231
  multiplexer.send(compactionEvent);
243098
243232
  }
243099
- if (contextUsagePercent >= 85 && !forceNoTools) {
243233
+ if (contextUsagePercent >= 85 && !forceNoTools && !config3.localFilesystem) {
243100
243234
  forceNoTools = true;
243101
243235
  orchestrationLogger.warn("\u{1F6D1} Context at 85%+ - forcing synthesis, disabling tools", {
243102
243236
  contextUsagePercent: Math.round(contextUsagePercent)
@@ -243857,7 +243991,7 @@ function createOrchestratorRequest(messages4, context, config3, persona, apiKey,
243857
243991
  // Pass MCP tools through
243858
243992
  };
243859
243993
  }
243860
- var HARD_TOOL_RESULT_CEILING_CHARS, ASK_QUESTION_PLACEHOLDER;
243994
+ var HARNESS_COMPACT_RATIO, HARNESS_KEEP_RECENT_RATIO, HARD_TOOL_RESULT_CEILING_CHARS, ASK_QUESTION_PLACEHOLDER;
243861
243995
  var init_orchestrator = __esm({
243862
243996
  "src/core/orchestrator/index.ts"() {
243863
243997
  "use strict";
@@ -243868,6 +244002,7 @@ var init_orchestrator = __esm({
243868
244002
  init_config();
243869
244003
  init_code_execution_prompt();
243870
244004
  init_harness_prompt();
244005
+ init_harness_compaction();
243871
244006
  init_ask_user_question_prompt();
243872
244007
  init_artifact_mention_resolver();
243873
244008
  init_slack_mention_resolver();
@@ -243903,6 +244038,8 @@ var init_orchestrator = __esm({
243903
244038
  init_mcp_server_tools_prompt();
243904
244039
  init_spill_payload();
243905
244040
  init_token_counter();
244041
+ HARNESS_COMPACT_RATIO = 0.75;
244042
+ HARNESS_KEEP_RECENT_RATIO = 0.35;
243906
244043
  registerAllExperts();
243907
244044
  HARD_TOOL_RESULT_CEILING_CHARS = 4e4;
243908
244045
  ASK_QUESTION_PLACEHOLDER = "[Awaiting user answer \u2014 picker is open]";
@@ -261445,6 +261582,13 @@ Edit it, then /verify-extension ${arg.trim()} to check it loads, /trust (project
261445
261582
  (it7) => it7.kind === "tool" && it7.id === data2.id ? { ...it7, status: data2.success === false ? "error" : "done", result, error: error41 } : it7
261446
261583
  );
261447
261584
  ui2.requestRender();
261585
+ } else if (e14.type === "plan") {
261586
+ const plan = e14.data?.plan;
261587
+ if (plan && plan.startsWith("\u{1F9F9}")) {
261588
+ finalize();
261589
+ push({ kind: "system", text: plan, tone: "info" });
261590
+ ui2.requestRender();
261591
+ }
261448
261592
  }
261449
261593
  };
261450
261594
  try {
@@ -262279,7 +262423,7 @@ init_tui_select();
262279
262423
  init_model_registry();
262280
262424
 
262281
262425
  // package.json
262282
- var version = "1.149.0";
262426
+ var version = "1.150.0";
262283
262427
 
262284
262428
  // src/adapters/cli/model-catalog.ts
262285
262429
  init_codex_auth();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usabledev/usable-chat",
3
- "version": "1.149.0",
3
+ "version": "1.150.0",
4
4
  "description": "usable-chat — terminal harness for usable-chat (headless + TUI)",
5
5
  "type": "module",
6
6
  "bin": {