toolnexus 0.20.0 → 0.22.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 (49) hide show
  1. package/README.md +40 -0
  2. package/dist/agents/agent.d.ts +12 -1
  3. package/dist/agents/agent.js +43 -7
  4. package/dist/agents/compaction.d.ts +19 -0
  5. package/dist/agents/compaction.js +26 -4
  6. package/dist/agents/runtime.d.ts +5 -0
  7. package/dist/agents/runtime.js +13 -2
  8. package/dist/batteries.d.ts +223 -0
  9. package/dist/batteries.js +488 -0
  10. package/dist/builtin.d.ts +16 -0
  11. package/dist/builtin.js +315 -17
  12. package/dist/builtin_extras.d.ts +80 -0
  13. package/dist/builtin_extras.js +248 -0
  14. package/dist/classifier.d.ts +14 -0
  15. package/dist/classifier.js +34 -1
  16. package/dist/client.d.ts +91 -15
  17. package/dist/client.js +377 -110
  18. package/dist/index.d.ts +8 -0
  19. package/dist/index.js +11 -0
  20. package/dist/judge.d.ts +98 -0
  21. package/dist/judge.js +199 -0
  22. package/dist/mcp.d.ts +32 -1
  23. package/dist/mcp.js +184 -6
  24. package/dist/models.d.ts +20 -0
  25. package/dist/models.js +46 -0
  26. package/dist/promptcache.d.ts +21 -0
  27. package/dist/promptcache.js +59 -0
  28. package/dist/store.d.ts +32 -0
  29. package/dist/store.js +131 -0
  30. package/dist/tooltiers.d.ts +49 -0
  31. package/dist/tooltiers.js +197 -0
  32. package/dist/types.d.ts +19 -0
  33. package/package.json +2 -2
  34. package/src/agents/agent.ts +39 -8
  35. package/src/agents/compaction.ts +45 -4
  36. package/src/agents/runtime.ts +16 -2
  37. package/src/batteries.ts +614 -0
  38. package/src/builtin.ts +333 -18
  39. package/src/builtin_extras.ts +285 -0
  40. package/src/classifier.ts +36 -1
  41. package/src/client.ts +415 -115
  42. package/src/index.ts +31 -0
  43. package/src/judge.ts +305 -0
  44. package/src/mcp.ts +204 -7
  45. package/src/models.ts +56 -0
  46. package/src/promptcache.ts +63 -0
  47. package/src/store.ts +142 -0
  48. package/src/tooltiers.ts +221 -0
  49. package/src/types.ts +20 -0
@@ -102,6 +102,26 @@ export interface CompactorOptions {
102
102
  * default. Composes with the memory builtin.
103
103
  */
104
104
  flushToMemory?: boolean
105
+ /**
106
+ * Called after each compaction with what changed, so a host can tell its UI or log (SPEC §4C).
107
+ * Never called on a no-op. Awaited before the next model call.
108
+ */
109
+ onCompact?: (info: CompactInfo) => void | Promise<void>
110
+ }
111
+
112
+ /** One compaction, for {@link CompactorOptions.onCompact}. Token counts come from `countTokens`. */
113
+ export interface CompactInfo {
114
+ tokensBefore: number
115
+ tokensAfter: number
116
+ messagesBefore: number
117
+ messagesAfter: number
118
+ /** How many messages were folded into the summary. */
119
+ summarized: number
120
+ /** The text `summarize` returned (without the header line). */
121
+ summary: string
122
+ /** The summarized messages, verbatim, so a host can persist them (the loop saves only the
123
+ * compacted transcript). */
124
+ folded: Message[]
105
125
  }
106
126
 
107
127
  /** The event a `beforeLLM` hook receives (§8). */
@@ -134,7 +154,8 @@ export function compactor(
134
154
 
135
155
  return async (ev: BeforeLLMEvent): Promise<{ messages: Message[] } | undefined> => {
136
156
  const msgs = ev.messages
137
- if (count(msgs) <= opts.maxTokens) return undefined // under budget → no-op
157
+ const before = count(msgs)
158
+ if (before <= opts.maxTokens) return undefined // under budget → no-op
138
159
 
139
160
  // Preserve a leading system prompt verbatim (identity / soul / skills — never summarized).
140
161
  const headEnd = msgs[0]?.role === "system" ? 1 : 0
@@ -143,9 +164,17 @@ export function compactor(
143
164
  const split = findTailStart(msgs, headEnd, keepTail, count)
144
165
  if (split <= headEnd) return undefined // nothing safely compactible
145
166
 
146
- const older = msgs.slice(headEnd, split)
147
- if (older.length === 0) return undefined
167
+ let older = msgs.slice(headEnd, split)
148
168
  const tail = msgs.slice(split)
169
+ // The latest user prompt is never summarized away. When the only clean boundary is the first
170
+ // message after the system prompt (one prompt followed by a long agentic run), keep that
171
+ // prompt verbatim and summarize the work after it: [system, prompt, summary].
172
+ let pinned: Message[] = []
173
+ if (split === msgs.length && headEnd < msgs.length && isBoundary(msgs[headEnd])) {
174
+ pinned = msgs.slice(headEnd, headEnd + 1)
175
+ older = msgs.slice(headEnd + 1)
176
+ }
177
+ if (older.length === 0) return undefined
149
178
 
150
179
  const flushNote: Message[] = opts.flushToMemory
151
180
  ? [{ role: "system", content: FLUSH_TO_MEMORY_REMINDER }]
@@ -154,7 +183,19 @@ export function compactor(
154
183
  const summary = await opts.summarize(older)
155
184
  const summaryMsg: Message = { role: "system", content: `${SUMMARY_PREFIX}${summary}` }
156
185
 
157
- return { messages: [...system, summaryMsg, ...flushNote, ...tail] }
186
+ const out = [...system, ...pinned, summaryMsg, ...flushNote, ...tail]
187
+ const after = count(out)
188
+ if (after >= before) return undefined // compaction would not shrink the transcript → no-op
189
+ await opts.onCompact?.({
190
+ tokensBefore: before,
191
+ tokensAfter: after,
192
+ messagesBefore: msgs.length,
193
+ messagesAfter: out.length,
194
+ summarized: older.length,
195
+ summary,
196
+ folded: older,
197
+ })
198
+ return { messages: out }
158
199
  }
159
200
  }
160
201
 
@@ -390,6 +390,11 @@ export interface RuntimeOptions {
390
390
  /** §8 observability sink applied to EVERY agent's client, unless that agent's def
391
391
  * sets its own `onMetric`. Resolves independently of `hooks`. Forwarded verbatim. */
392
392
  onMetric?: (ev: MetricEvent) => void
393
+ /** The runtime-level §10 interpreter (SPEC §4C): answers a suspension when no agent on the
394
+ * handle's chain has its own `waitFor`. Absent ⇒ unchanged (the suspension goes durable). A
395
+ * host running an agent as a tool (`asToolWith`) sets it so the child's asks reach the same
396
+ * bridge as the parent turn. */
397
+ waitFor?: (request: Request) => Promise<Answer>
393
398
  }
394
399
 
395
400
  interface GateWaiter {
@@ -855,11 +860,20 @@ export class AgentRuntime {
855
860
  * escalation traced (nearest interpreter wins; the Request's `data.path` names
856
861
  * the suspended subtree that is asking). */
857
862
  private wrapWaitFor(h: Handle): ((req: Request) => Promise<Answer>) | undefined {
858
- const waitFor = h.def.waitFor
863
+ const own = h.def.waitFor
864
+ let waitFor = own
865
+ if (!waitFor && this.opts.waitFor) {
866
+ // The runtime-level bridge answers only when NO agent on the chain has its own waitFor;
867
+ // otherwise the ask escalates to that nearest interpreter, as before.
868
+ let a: Handle | null = h.parent
869
+ while (a && !a.def.waitFor) a = a.parent
870
+ if (!a) waitFor = this.opts.waitFor
871
+ }
859
872
  if (!waitFor) return undefined
873
+ const answerer = own ? h.id : this.root.id
860
874
  return async (req: Request): Promise<Answer> => {
861
875
  const path = Array.isArray(req.data?.path) ? (req.data!.path as string[]).join("/") : h.id
862
- this.t(`${path}: escalate → ${h.id} answers ("nearest interpreter")`)
876
+ this.t(`${path}: escalate → ${answerer} answers ("nearest interpreter")`)
863
877
  return waitFor(req)
864
878
  }
865
879
  }