@usabledev/usable-chat 1.149.1 → 1.151.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 +267 -22
  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({
@@ -157383,6 +157486,13 @@ var init_custom_expert = __esm({
157383
157486
  type: external_exports.literal("object"),
157384
157487
  properties: external_exports.record(external_exports.unknown()),
157385
157488
  required: external_exports.array(external_exports.string()).optional()
157489
+ }).optional(),
157490
+ skill: external_exports.object({
157491
+ id: external_exports.string().min(1).max(100).optional(),
157492
+ title: external_exports.string().min(1).max(200).optional(),
157493
+ summary: external_exports.string().max(1e3).optional(),
157494
+ tags: external_exports.array(external_exports.string().min(1).max(100)).max(20).optional(),
157495
+ scope: external_exports.enum(["global", "page"]).optional()
157386
157496
  }).optional()
157387
157497
  });
157388
157498
  MAX_PARENT_TOOLS = 32;
@@ -168237,29 +168347,38 @@ function buildDiscoverableSkillsPromptSection(skills) {
168237
168347
  "",
168238
168348
  "## DISCOVERABLE SKILLS IN CONTEXT",
168239
168349
  "",
168240
- "You have a catalog of reusable Skills scoped to the workspaces in this conversation. Each entry below lists the skill title, `/<name>` shortcut, UUID, and summary.",
168350
+ "You have a catalog of reusable Skills scoped to this conversation. Workspace skills are Usable fragments. Parent-local skills are provided dynamically by the embedding parent app and must be loaded through their parent tool.",
168241
168351
  "",
168242
168352
  "**BEFORE acting on the user's request, scan this catalog.** If any listed skill matches the user's intent \u2014 by title, `/<name>`, summary keywords, or obvious topical fit \u2014 you MUST use it instead of improvising:",
168243
168353
  "",
168244
- '1. Call `add-to-conversation-context` with `contextType: "fragment"` and `contextId: <fragmentId>` from the matching entry. Attaching a fragment pulls its FULL instructions into your system prompt for the rest of the conversation \u2014 that\'s how the skill gets "loaded".',
168245
- "2. Follow that skill's instructions exactly for the current request. Do not fall back to generic search once a matching skill is attached.",
168354
+ '1. For workspace skills, call `add-to-conversation-context` with `contextType: "fragment"` and `contextId: <fragmentId>` from the matching entry. Attaching a fragment pulls its FULL instructions into your system prompt for the rest of the conversation.',
168355
+ "2. For parent-local skills, call the listed `parent_<toolName>` tool first. The parent app returns the current skill instructions/content for this page or application. Follow that returned content exactly.",
168356
+ "3. Do not fall back to generic search once a matching skill is loaded.",
168246
168357
  "",
168247
168358
  '**Example** \u2014 the user asks "what tasks do we have?" and the catalog lists a `/usable-tasks` skill. You MUST attach that skill BEFORE searching for tasks yourself; the skill knows the correct queries, tag taxonomy, and output format.',
168248
168359
  "",
168249
168360
  "**Rules**",
168250
- "- The entries below are metadata only; their bodies are NOT loaded until you attach them.",
168251
- "- Load skills lazily \u2014 only attach the one(s) needed for the current request, not the whole catalog.",
168252
- "- If the user explicitly invokes a skill via `/<name>` or `/skill <uuid>`, the harness has already attached it; just follow its instructions.",
168253
- '- When a skill-driven task completes (the user moves to a new topic or confirms the work is done), call `remove-from-conversation-context` with `contextType: "fragment"` and the skill\'s fragmentId to free the skill token budget for the next activation.',
168361
+ "- The entries below are metadata only; their bodies are NOT loaded until you attach the workspace fragment or call the parent-local tool.",
168362
+ "- Load skills lazily \u2014 only load the one(s) needed for the current request, not the whole catalog.",
168363
+ "- If the user explicitly invokes a workspace skill via `/<name>` or `/skill <uuid>`, the harness has already attached it; just follow its instructions.",
168364
+ "- If the user explicitly invokes a parent-local skill via `/<name>` or `/skill parent:<id>`, call that skill's parent tool before doing the work.",
168365
+ '- When a workspace skill-driven task completes (the user moves to a new topic or confirms the work is done), call `remove-from-conversation-context` with `contextType: "fragment"` and the skill\'s fragmentId to free the skill token budget for the next activation.',
168254
168366
  ""
168255
168367
  ];
168256
168368
  for (const [workspaceId, workspaceSkills] of skillsByWorkspace.entries()) {
168257
168369
  section.push(`### Workspace: ${workspaceSkills.workspaceName} (\`${workspaceId}\`)`);
168258
168370
  for (const skill of workspaceSkills.skills) {
168259
168371
  const shortcut = skill.name ? ` (\`/${skill.name}\`)` : "";
168260
- section.push(
168261
- `- **${skill.title}**${shortcut} \u2014 \`${skill.fragmentId}\`${skill.summary ? ` \u2014 ${skill.summary}` : ""}`
168262
- );
168372
+ if (skill.source === "parent" && skill.parentToolName) {
168373
+ const scope = skill.parentScope === "global" ? "global" : "page";
168374
+ section.push(
168375
+ `- **${skill.title}**${shortcut} \u2014 parent-local \`${skill.fragmentId}\` \u2014 load with \`parent_${skill.parentToolName}\` \u2014 scope: ${scope}${skill.summary ? ` \u2014 ${skill.summary}` : ""}`
168376
+ );
168377
+ } else {
168378
+ section.push(
168379
+ `- **${skill.title}**${shortcut} \u2014 \`${skill.fragmentId}\`${skill.summary ? ` \u2014 ${skill.summary}` : ""}`
168380
+ );
168381
+ }
168263
168382
  }
168264
168383
  section.push("");
168265
168384
  }
@@ -168767,14 +168886,21 @@ var init_skill_decay = __esm({
168767
168886
  // src/lib/skill-command.ts
168768
168887
  var skill_command_exports = {};
168769
168888
  __export(skill_command_exports, {
168889
+ PARENT_SKILL_COMMAND_ID_REGEX: () => PARENT_SKILL_COMMAND_ID_REGEX,
168890
+ PARENT_SKILL_ID_PREFIX: () => PARENT_SKILL_ID_PREFIX,
168770
168891
  SKILL_COMMAND_UUID_REGEX: () => SKILL_COMMAND_UUID_REGEX,
168771
168892
  addSkillToPendingContext: () => addSkillToPendingContext,
168893
+ buildParentLocalSkillsFromTools: () => buildParentLocalSkillsFromTools,
168772
168894
  buildSkillCommandMessage: () => buildSkillCommandMessage,
168773
168895
  dedupeSkillsByName: () => dedupeSkillsByName,
168774
168896
  ensureSkillConversationContext: () => ensureSkillConversationContext,
168775
168897
  extractSkillName: () => extractSkillName,
168776
- findSkillByCommandName: () => findSkillByCommandName
168898
+ findSkillByCommandName: () => findSkillByCommandName,
168899
+ isParentLocalSkill: () => isParentLocalSkill
168777
168900
  });
168901
+ function isParentLocalSkill(skill) {
168902
+ return skill.source === "parent";
168903
+ }
168778
168904
  function kebabCase(value) {
168779
168905
  return value.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
168780
168906
  }
@@ -168816,11 +168942,43 @@ function buildSkillCommandMessage(skill, userArgs) {
168816
168942
  const name14 = extractSkillName(skill);
168817
168943
  const shortcut = name14 ? ` (\`/${name14}\`)` : "";
168818
168944
  const normalizedArgs = userArgs.trim();
168945
+ if (isParentLocalSkill(skill)) {
168946
+ return [
168947
+ `\u26A1 Activating parent skill **${skill.title}**${shortcut}. Call \`parent_${skill.parentToolName}\` to load and follow the parent-provided skill instructions.`,
168948
+ `Args: ${normalizedArgs || "(none)"}`
168949
+ ].join("\n");
168950
+ }
168819
168951
  return [
168820
168952
  `\u26A1 Activating skill **${skill.title}**${shortcut}. Follow its attached instructions.`,
168821
168953
  `Args: ${normalizedArgs || "(none)"}`
168822
168954
  ].join("\n");
168823
168955
  }
168956
+ function buildParentLocalSkillsFromTools(tools) {
168957
+ const skills = [];
168958
+ const seen = /* @__PURE__ */ new Set();
168959
+ for (const tool of tools) {
168960
+ if (!tool.skill) continue;
168961
+ const scope = tool.skill.scope ?? "page";
168962
+ const id = (tool.skill.id || tool.name).trim().toLowerCase();
168963
+ if (!/^[a-z][a-z0-9_-]{0,99}$/i.test(id)) continue;
168964
+ const fragmentId = `${PARENT_SKILL_ID_PREFIX}${id}`;
168965
+ if (seen.has(fragmentId)) continue;
168966
+ seen.add(fragmentId);
168967
+ const tags = Array.from(/* @__PURE__ */ new Set(["skill", `skill:${id}`, ...tool.skill.tags || []]));
168968
+ skills.push({
168969
+ source: "parent",
168970
+ fragmentId,
168971
+ workspaceId: scope === "global" ? "parent-global" : "parent-page",
168972
+ workspaceName: scope === "global" ? "Parent app" : "Current page",
168973
+ title: tool.skill.title || id,
168974
+ summary: tool.skill.summary || tool.description,
168975
+ tags,
168976
+ parentToolName: tool.name,
168977
+ parentScope: scope
168978
+ });
168979
+ }
168980
+ return skills;
168981
+ }
168824
168982
  function addSkillToPendingContext(pendingContextItems, skill) {
168825
168983
  if (pendingContextItems.some(
168826
168984
  (item) => item.contextType === "fragment" && item.contextId === skill.fragmentId
@@ -168874,11 +169032,13 @@ async function ensureSkillConversationContext(params) {
168874
169032
  throw new Error(`Failed to attach skill context (${addResponse.status})`);
168875
169033
  }
168876
169034
  }
168877
- var SKILL_COMMAND_UUID_REGEX, SKILL_NAME_TAG_PREFIX;
169035
+ var SKILL_COMMAND_UUID_REGEX, PARENT_SKILL_ID_PREFIX, PARENT_SKILL_COMMAND_ID_REGEX, SKILL_NAME_TAG_PREFIX;
168878
169036
  var init_skill_command = __esm({
168879
169037
  "src/lib/skill-command.ts"() {
168880
169038
  "use strict";
168881
169039
  SKILL_COMMAND_UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
169040
+ PARENT_SKILL_ID_PREFIX = "parent:";
169041
+ PARENT_SKILL_COMMAND_ID_REGEX = /^parent:[a-z][a-z0-9_-]{0,99}$/i;
168882
169042
  SKILL_NAME_TAG_PREFIX = "skill:";
168883
169043
  }
168884
169044
  });
@@ -241711,6 +241871,26 @@ Folder behavior:
241711
241871
  largeContextWarning: contextSizeWarning
241712
241872
  });
241713
241873
  }
241874
+ let discoverableSkillsSectionInjected = false;
241875
+ const buildParentPromptSkills = async () => {
241876
+ if (!context.registeredParentToolSchemas || context.registeredParentToolSchemas.size === 0) {
241877
+ return [];
241878
+ }
241879
+ const { buildParentLocalSkillsFromTools: buildParentLocalSkillsFromTools2, extractSkillName: extractSkillName2 } = await Promise.resolve().then(() => (init_skill_command(), skill_command_exports));
241880
+ return buildParentLocalSkillsFromTools2(
241881
+ Array.from(context.registeredParentToolSchemas.values())
241882
+ ).map((skill) => ({
241883
+ source: "parent",
241884
+ fragmentId: skill.fragmentId,
241885
+ workspaceId: skill.workspaceId,
241886
+ workspaceName: skill.workspaceName,
241887
+ title: skill.title,
241888
+ summary: skill.summary,
241889
+ name: extractSkillName2(skill),
241890
+ parentToolName: skill.parentToolName,
241891
+ parentScope: skill.parentScope
241892
+ }));
241893
+ };
241714
241894
  if (allowedWorkspaceIds.length > 0) {
241715
241895
  const workspaceRules = [
241716
241896
  "",
@@ -241818,18 +241998,25 @@ Folder behavior:
241818
241998
  });
241819
241999
  const { dedupeSkillsByName: dedupeSkillsByName2, extractSkillName: extractSkillName2 } = await Promise.resolve().then(() => (init_skill_command(), skill_command_exports));
241820
242000
  const dedupedSkills = dedupeSkillsByName2(skillDiscovery.skills);
242001
+ const parentPromptSkills = await buildParentPromptSkills();
241821
242002
  const discoverableSkillsSection = buildDiscoverableSkillsPromptSection(
241822
- dedupedSkills.map((skill) => ({
241823
- ...skill,
241824
- name: extractSkillName2(skill)
241825
- }))
242003
+ [
242004
+ ...dedupedSkills.map((skill) => ({
242005
+ source: "workspace",
242006
+ ...skill,
242007
+ name: extractSkillName2(skill)
242008
+ })),
242009
+ ...parentPromptSkills
242010
+ ]
241826
242011
  );
241827
242012
  if (discoverableSkillsSection) {
241828
242013
  systemMessageParts.push(discoverableSkillsSection);
242014
+ discoverableSkillsSectionInjected = true;
241829
242015
  orchestrationLogger.info("Discoverable skills injected into system prompt", {
241830
242016
  workspaceCount: new Set(skillDiscovery.skills.map((skill) => skill.workspaceId)).size,
241831
- skillCount: dedupedSkills.length,
242017
+ skillCount: dedupedSkills.length + parentPromptSkills.length,
241832
242018
  rawSkillCount: skillDiscovery.skills.length,
242019
+ parentSkillCount: parentPromptSkills.length,
241833
242020
  stale: skillDiscovery.stale
241834
242021
  });
241835
242022
  }
@@ -241861,6 +242048,23 @@ Folder behavior:
241861
242048
  conversationId
241862
242049
  });
241863
242050
  }
242051
+ if (!discoverableSkillsSectionInjected) {
242052
+ try {
242053
+ const parentPromptSkills = await buildParentPromptSkills();
242054
+ const discoverableSkillsSection = buildDiscoverableSkillsPromptSection(parentPromptSkills);
242055
+ if (discoverableSkillsSection) {
242056
+ systemMessageParts.push(discoverableSkillsSection);
242057
+ discoverableSkillsSectionInjected = true;
242058
+ orchestrationLogger.info("Parent-local discoverable skills injected into system prompt", {
242059
+ parentSkillCount: parentPromptSkills.length
242060
+ });
242061
+ }
242062
+ } catch (error41) {
242063
+ orchestrationLogger.warn("Failed to inject parent-local skills into prompt", {
242064
+ error: error41 instanceof Error ? error41.message : String(error41)
242065
+ });
242066
+ }
242067
+ }
241864
242068
  const contextManagementGuidance = [
241865
242069
  "",
241866
242070
  "## CONTEXT WINDOW MANAGEMENT - INTELLIGENT STRATEGY",
@@ -242907,6 +243111,37 @@ ${combinedSystemMessage}` : combinedSystemMessage;
242907
243111
  name: msg.name
242908
243112
  };
242909
243113
  };
243114
+ if (config3.localFilesystem) {
243115
+ const harnessCtxLen = getModelById(selectedModelId)?.capabilities.contextLength || 128e3;
243116
+ const envRatio = Number(process.env.USABLE_HARNESS_COMPACT_RATIO);
243117
+ const compactRatio = envRatio > 0 && envRatio <= 1 ? envRatio : HARNESS_COMPACT_RATIO;
243118
+ const tokensOf = (m33) => countTokens(typeof m33.content === "string" ? m33.content : JSON.stringify(m33.content));
243119
+ const estTokens = conversationMessages.reduce((s17, m33) => s17 + tokensOf(m33), 0);
243120
+ if (estTokens > harnessCtxLen * compactRatio) {
243121
+ const beforeLen = conversationMessages.length;
243122
+ const envKeep = Number(process.env.USABLE_HARNESS_KEEP_TOKENS);
243123
+ const keepRecentTokens = envKeep > 0 ? envKeep : Math.floor(harnessCtxLen * HARNESS_KEEP_RECENT_RATIO);
243124
+ const res = compactHarnessConversation(conversationMessages, {
243125
+ keepRecentTokens,
243126
+ countTokens: tokensOf
243127
+ });
243128
+ if (res.compacted) {
243129
+ conversationMessages = ensureToolCallIntegrity(res.messages);
243130
+ orchestrationLogger.warn("\u{1F9F9} Harness proactive compaction", {
243131
+ beforeLen,
243132
+ afterLen: conversationMessages.length,
243133
+ droppedCount: res.droppedCount,
243134
+ estTokens,
243135
+ contextLength: harnessCtxLen
243136
+ });
243137
+ const compactPlan = emitter.emit("plan", {
243138
+ plan: `\u{1F9F9} Compacted ${res.droppedCount} older messages to free context \u2014 continuing.`,
243139
+ steps: config3.maxSteps
243140
+ });
243141
+ multiplexer.send(compactPlan);
243142
+ }
243143
+ }
243144
+ }
242910
243145
  const filteredConversationMessages = filterImagesFromHistory(conversationMessages, {
242911
243146
  preserveLastUserMessageImages: !!modelInfo?.capabilities?.vision
242912
243147
  });
@@ -243096,7 +243331,7 @@ ${combinedSystemMessage}` : combinedSystemMessage;
243096
243331
  });
243097
243332
  multiplexer.send(compactionEvent);
243098
243333
  }
243099
- if (contextUsagePercent >= 85 && !forceNoTools) {
243334
+ if (contextUsagePercent >= 85 && !forceNoTools && !config3.localFilesystem) {
243100
243335
  forceNoTools = true;
243101
243336
  orchestrationLogger.warn("\u{1F6D1} Context at 85%+ - forcing synthesis, disabling tools", {
243102
243337
  contextUsagePercent: Math.round(contextUsagePercent)
@@ -243857,7 +244092,7 @@ function createOrchestratorRequest(messages4, context, config3, persona, apiKey,
243857
244092
  // Pass MCP tools through
243858
244093
  };
243859
244094
  }
243860
- var HARD_TOOL_RESULT_CEILING_CHARS, ASK_QUESTION_PLACEHOLDER;
244095
+ var HARNESS_COMPACT_RATIO, HARNESS_KEEP_RECENT_RATIO, HARD_TOOL_RESULT_CEILING_CHARS, ASK_QUESTION_PLACEHOLDER;
243861
244096
  var init_orchestrator = __esm({
243862
244097
  "src/core/orchestrator/index.ts"() {
243863
244098
  "use strict";
@@ -243868,6 +244103,7 @@ var init_orchestrator = __esm({
243868
244103
  init_config();
243869
244104
  init_code_execution_prompt();
243870
244105
  init_harness_prompt();
244106
+ init_harness_compaction();
243871
244107
  init_ask_user_question_prompt();
243872
244108
  init_artifact_mention_resolver();
243873
244109
  init_slack_mention_resolver();
@@ -243903,6 +244139,8 @@ var init_orchestrator = __esm({
243903
244139
  init_mcp_server_tools_prompt();
243904
244140
  init_spill_payload();
243905
244141
  init_token_counter();
244142
+ HARNESS_COMPACT_RATIO = 0.75;
244143
+ HARNESS_KEEP_RECENT_RATIO = 0.35;
243906
244144
  registerAllExperts();
243907
244145
  HARD_TOOL_RESULT_CEILING_CHARS = 4e4;
243908
244146
  ASK_QUESTION_PLACEHOLDER = "[Awaiting user answer \u2014 picker is open]";
@@ -261445,6 +261683,13 @@ Edit it, then /verify-extension ${arg.trim()} to check it loads, /trust (project
261445
261683
  (it7) => it7.kind === "tool" && it7.id === data2.id ? { ...it7, status: data2.success === false ? "error" : "done", result, error: error41 } : it7
261446
261684
  );
261447
261685
  ui2.requestRender();
261686
+ } else if (e14.type === "plan") {
261687
+ const plan = e14.data?.plan;
261688
+ if (plan && plan.startsWith("\u{1F9F9}")) {
261689
+ finalize();
261690
+ push({ kind: "system", text: plan, tone: "info" });
261691
+ ui2.requestRender();
261692
+ }
261448
261693
  }
261449
261694
  };
261450
261695
  try {
@@ -262279,7 +262524,7 @@ init_tui_select();
262279
262524
  init_model_registry();
262280
262525
 
262281
262526
  // package.json
262282
- var version = "1.149.1";
262527
+ var version = "1.151.0";
262283
262528
 
262284
262529
  // src/adapters/cli/model-catalog.ts
262285
262530
  init_codex_auth();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usabledev/usable-chat",
3
- "version": "1.149.1",
3
+ "version": "1.151.0",
4
4
  "description": "usable-chat — terminal harness for usable-chat (headless + TUI)",
5
5
  "type": "module",
6
6
  "bin": {