@cubicecho/agent-core 2.0.4 → 2.0.6

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/side-task.js CHANGED
@@ -61,7 +61,16 @@ function rejectedTheRequest(error) {
61
61
  * model that spends it deliberating is cut off mid-scratchpad and the closing tag never
62
62
  * arrives — and the whole deliberation was then returned to the caller as the answer.
63
63
  */
64
- const stripThinking = (text) => text.replace(/<think>[\s\S]*?<\/think>/gi, "").replace(/<think>[\s\S]*$/i, "");
64
+ const stripThinking = (text) => text
65
+ .replace(/<think>[\s\S]*?<\/think>/gi, "")
66
+ .replace(/<think>[\s\S]*$/i, "")
67
+ // And the fence that never opens. Several chat templates put the opening tag at the end of
68
+ // the prompt rather than leaving the model to write it, so what comes back is deliberation
69
+ // first and only the closing tag to mark where it stops. Neither pattern above matches that,
70
+ // and the whole scratchpad went to the caller as the answer — a session title, a tool
71
+ // preselection, a suggestion list. Every real `<think>` is gone by this point, so a `</think>`
72
+ // still here opened in the prompt; the first one is taken, which keeps the most text.
73
+ .replace(/^[\s\S]*?<\/think>/i, "");
65
74
  /**
66
75
  * Runs a side task and returns the reply text, thinking stripped. Throws like any request.
67
76
  *
@@ -88,6 +88,7 @@ export declare function expandNames(requested: string[], catalog: CatalogServer[
88
88
  name: string;
89
89
  hits: string[];
90
90
  }[];
91
+ deferred: string[];
91
92
  };
92
93
  /**
93
94
  * What `load_tools` reports back: the descriptions, now that they are worth their tokens.
@@ -95,7 +96,7 @@ export declare function expandNames(requested: string[], catalog: CatalogServer[
95
96
  * @param expanded What `expandNames` resolved: the matches, the misses, and the over-broad asks.
96
97
  * @param catalog The servers, read for the descriptions now worth their tokens.
97
98
  */
98
- export declare function loadResult({ matched, unknown, overBroad }: ReturnType<typeof expandNames>, catalog: CatalogServer[]): string;
99
+ export declare function loadResult({ matched, unknown, overBroad, deferred }: ReturnType<typeof expandNames>, catalog: CatalogServer[]): string;
99
100
  /**
100
101
  * Whether the catalogue holds a tool by this name.
101
102
  *
@@ -137,6 +137,7 @@ export function expandNames(requested, catalog) {
137
137
  const matched = new Set();
138
138
  const unknown = [];
139
139
  const overBroad = [];
140
+ const deferred = [];
140
141
  const known = new Set(all.map((tool) => tool.name));
141
142
  const resolve = (name) => {
142
143
  if (name.endsWith("*")) {
@@ -172,13 +173,21 @@ export function expandNames(requested, catalog) {
172
173
  // model that is meant to be choosing from twelve. Already-matched names are free, so a
173
174
  // name that only repeats an earlier one never spends budget.
174
175
  const fresh = hits.filter((hit) => !matched.has(hit));
175
- if (matched.size + fresh.length > MAX_PER_LOAD)
176
+ // Two different refusals, and answering both with "narrow it down" made one of them
177
+ // impossible to act on. A name that would not fit an empty call is over-broad, and the
178
+ // names are what the model needs. A name that only does not fit *this* call is precise
179
+ // enough already — telling a model that asked for one exact tool that it matches one tool,
180
+ // "more than the twelve one call may load", and to choose from a list holding just that
181
+ // name, leaves it nothing to do but send the identical call again.
182
+ if (fresh.length > MAX_PER_LOAD)
176
183
  overBroad.push({ name, hits });
184
+ else if (matched.size + fresh.length > MAX_PER_LOAD)
185
+ deferred.push(name);
177
186
  else
178
187
  for (const hit of hits)
179
188
  matched.add(hit);
180
189
  }
181
- return { matched: [...matched], unknown, overBroad };
190
+ return { matched: [...matched], unknown, overBroad, deferred };
182
191
  }
183
192
  /**
184
193
  * What `load_tools` reports back: the descriptions, now that they are worth their tokens.
@@ -186,7 +195,7 @@ export function expandNames(requested, catalog) {
186
195
  * @param expanded What `expandNames` resolved: the matches, the misses, and the over-broad asks.
187
196
  * @param catalog The servers, read for the descriptions now worth their tokens.
188
197
  */
189
- export function loadResult({ matched, unknown, overBroad }, catalog) {
198
+ export function loadResult({ matched, unknown, overBroad, deferred }, catalog) {
190
199
  const byName = new Map(flatten(catalog).map((tool) => [tool.name, tool.description]));
191
200
  const lines = [];
192
201
  if (matched.length) {
@@ -199,6 +208,11 @@ export function loadResult({ matched, unknown, overBroad }, catalog) {
199
208
  lines.push("");
200
209
  lines.push(`\`${name}\` matches ${hits.length} tools, more than the ${MAX_PER_LOAD} one call may load.`, "Name the ones you need from:", ...hits.map((hit) => ` ${hit}`));
201
210
  }
211
+ if (deferred.length) {
212
+ if (lines.length)
213
+ lines.push("");
214
+ lines.push(`This call is full at ${MAX_PER_LOAD} tools, so these were not loaded: ${deferred.join(", ")}.`, "Ask for them on your next step.");
215
+ }
202
216
  if (unknown.length) {
203
217
  if (lines.length)
204
218
  lines.push("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cubicecho/agent-core",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
4
4
  "description": "The endpoint-agnostic half of an OpenAI-compatible agent loop: tool-schema compatibility, on-demand tool loading, one-shot side tasks, run events, and a pooled client.",
5
5
  "keywords": [
6
6
  "openai",