@namzu/sdk 62.9.0 → 62.11.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 (81) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  3. package/dist/bridge/a2a/mapper.js +1 -0
  4. package/dist/bridge/a2a/mapper.js.map +1 -1
  5. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  6. package/dist/bridge/sse/mapper.js +9 -0
  7. package/dist/bridge/sse/mapper.js.map +1 -1
  8. package/dist/compaction/loaded-skills.d.ts +77 -0
  9. package/dist/compaction/loaded-skills.d.ts.map +1 -0
  10. package/dist/compaction/loaded-skills.js +203 -0
  11. package/dist/compaction/loaded-skills.js.map +1 -0
  12. package/dist/compaction/manager.d.ts +10 -0
  13. package/dist/compaction/manager.d.ts.map +1 -1
  14. package/dist/compaction/manager.js +20 -0
  15. package/dist/compaction/manager.js.map +1 -1
  16. package/dist/compaction/manual.d.ts.map +1 -1
  17. package/dist/compaction/manual.js +17 -3
  18. package/dist/compaction/manual.js.map +1 -1
  19. package/dist/compaction/restore.d.ts +5 -0
  20. package/dist/compaction/restore.d.ts.map +1 -1
  21. package/dist/compaction/restore.js +18 -18
  22. package/dist/compaction/restore.js.map +1 -1
  23. package/dist/connector/http-operation.d.ts +1 -0
  24. package/dist/connector/http-operation.d.ts.map +1 -1
  25. package/dist/connector/http-operation.js +1 -1
  26. package/dist/connector/http-operation.js.map +1 -1
  27. package/dist/connector/mcp/adapter.d.ts.map +1 -1
  28. package/dist/connector/mcp/adapter.js +10 -0
  29. package/dist/connector/mcp/adapter.js.map +1 -1
  30. package/dist/connector/mcp/client.d.ts +12 -0
  31. package/dist/connector/mcp/client.d.ts.map +1 -1
  32. package/dist/connector/mcp/client.js +59 -10
  33. package/dist/connector/mcp/client.js.map +1 -1
  34. package/dist/constants/mcp/index.d.ts +15 -0
  35. package/dist/constants/mcp/index.d.ts.map +1 -1
  36. package/dist/constants/mcp/index.js +15 -0
  37. package/dist/constants/mcp/index.js.map +1 -1
  38. package/dist/contracts/session/api.d.ts +1 -1
  39. package/dist/contracts/session/api.d.ts.map +1 -1
  40. package/dist/execution/workflow/phases.d.ts +5 -2
  41. package/dist/execution/workflow/phases.d.ts.map +1 -1
  42. package/dist/execution/workflow/phases.js +32 -8
  43. package/dist/execution/workflow/phases.js.map +1 -1
  44. package/dist/runtime/query/iteration/phases/compaction.d.ts +1 -1
  45. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  46. package/dist/runtime/query/iteration/phases/compaction.js +76 -11
  47. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  48. package/dist/tools/builtins/skill.d.ts.map +1 -1
  49. package/dist/tools/builtins/skill.js +5 -0
  50. package/dist/tools/builtins/skill.js.map +1 -1
  51. package/dist/turn/reporter.d.ts.map +1 -1
  52. package/dist/turn/reporter.js +9 -0
  53. package/dist/turn/reporter.js.map +1 -1
  54. package/dist/types/connector/mcp.d.ts +19 -0
  55. package/dist/types/connector/mcp.d.ts.map +1 -1
  56. package/dist/types/session/events.d.ts +26 -3
  57. package/dist/types/session/events.d.ts.map +1 -1
  58. package/dist/types/session/events.js +4 -1
  59. package/dist/types/session/events.js.map +1 -1
  60. package/dist/types/session/records.d.ts.map +1 -1
  61. package/dist/types/session/records.js +1 -0
  62. package/dist/types/session/records.js.map +1 -1
  63. package/package.json +1 -1
  64. package/src/bridge/a2a/mapper.ts +1 -0
  65. package/src/bridge/sse/mapper.ts +10 -0
  66. package/src/compaction/loaded-skills.ts +245 -0
  67. package/src/compaction/manager.ts +24 -0
  68. package/src/compaction/manual.ts +20 -2
  69. package/src/compaction/restore.ts +33 -18
  70. package/src/connector/http-operation.ts +1 -1
  71. package/src/connector/mcp/adapter.ts +11 -0
  72. package/src/connector/mcp/client.ts +75 -12
  73. package/src/constants/mcp/index.ts +17 -0
  74. package/src/contracts/session/api.ts +1 -0
  75. package/src/execution/workflow/phases.ts +34 -8
  76. package/src/runtime/query/iteration/phases/compaction.ts +102 -16
  77. package/src/tools/builtins/skill.ts +5 -0
  78. package/src/turn/reporter.ts +10 -0
  79. package/src/types/connector/mcp.ts +19 -0
  80. package/src/types/session/events.ts +27 -2
  81. package/src/types/session/records.ts +1 -0
@@ -3,6 +3,13 @@ import {
3
3
  resolveTriggerThreshold,
4
4
  } from '../../../../compaction/context-window.js'
5
5
  import { findDanglingMessages } from '../../../../compaction/dangling.js'
6
+ import {
7
+ LOADED_SKILLS_ENTRY_KEY,
8
+ type RenderedSkills,
9
+ adoptableSkills,
10
+ handledSkillNames,
11
+ renderLoadedSkills,
12
+ } from '../../../../compaction/loaded-skills.js'
6
13
  import { replaceableSummaries, summarizeWithModel } from '../../../../compaction/model-summary.js'
7
14
  import {
8
15
  type CompactionPlan,
@@ -29,7 +36,11 @@ import { CHARS_PER_TOKEN } from '../../../../constants/limits.js'
29
36
  import { NAMZU } from '../../../../constants/telemetry/index.js'
30
37
  import { invariants } from '../../../../invariants/index.js'
31
38
  import { resolveTaskModel } from '../../../../model-router/task-router.js'
32
- import type { Message } from '../../../../types/message/index.js'
39
+ import {
40
+ type Message,
41
+ createTurnContextEntry,
42
+ turnContextEntries,
43
+ } from '../../../../types/message/index.js'
33
44
  import { applyLifecycleHookResults } from '../../plugin-hooks.js'
34
45
  import {
35
46
  COMPACTION_REFILL_MESSAGE,
@@ -168,6 +179,65 @@ async function emitToolResultClear(
168
179
  })
169
180
  }
170
181
 
182
+ /**
183
+ * Keep the skills this conversation loaded where the pass cannot cut them.
184
+ * Called before a pass edits the history: whatever the messages show of a
185
+ * skill now goes into the working state, which outlives the messages.
186
+ */
187
+ function adoptLoadedSkills(ctx: IterationContext): void {
188
+ ctx.workingStateManager?.adoptSkillBodies(adoptableSkills(ctx.recorder.messages))
189
+ }
190
+
191
+ /**
192
+ * After a pass that cut or cleared history: if a loaded skill's instructions
193
+ * are no longer in it, say them again, as reference.
194
+ *
195
+ * The entry goes at the END of the history, after everything the pass left.
196
+ * A clear already rewrites the request from the first result it cut, so the
197
+ * cached start of the request ends no earlier than it did; putting the skills
198
+ * anywhere before that point would move it further forward. At the end it
199
+ * also reads as the latest guidance, and the entry is never edited after, so
200
+ * later requests start with it unchanged.
201
+ */
202
+ async function carryLoadedSkills(
203
+ ctx: IterationContext,
204
+ contextWindowTokens: number,
205
+ ): Promise<void> {
206
+ const manager = ctx.workingStateManager
207
+ const loaded = manager?.getState().restorable?.skills
208
+ if (!loaded || loaded.length === 0) return
209
+ const handled = handledSkillNames(ctx.recorder.messages)
210
+ const missing = loaded.filter((skill) => !handled.has(skill.name))
211
+ if (missing.length === 0) return
212
+ const rendered = renderLoadedSkills(missing, { contextWindowTokens })
213
+ if (!rendered.text) return
214
+ const earlier = turnContextEntries(ctx.recorder.messages, LOADED_SKILLS_ENTRY_KEY)
215
+ ctx.recorder.pushMessage(
216
+ createTurnContextEntry(LOADED_SKILLS_ENTRY_KEY, rendered.text, new Date(), {
217
+ later: earlier.length > 0,
218
+ }),
219
+ )
220
+ await emitSkillsCarried(ctx, rendered)
221
+ }
222
+
223
+ async function emitSkillsCarried(
224
+ ctx: IterationContext,
225
+ rendered: {
226
+ carried: readonly string[]
227
+ cut: readonly string[]
228
+ left: readonly string[]
229
+ },
230
+ ): Promise<void> {
231
+ await ctx.emitEvent?.({
232
+ type: 'compaction_skills_carried',
233
+ turnId: ctx.recorder.turnId,
234
+ iteration: ctx.recorder.currentIteration,
235
+ skills: [...rendered.carried],
236
+ cut: [...rendered.cut],
237
+ leftOut: [...rendered.left],
238
+ })
239
+ }
240
+
171
241
  /** Install a clear-only pass and publish the state it established. */
172
242
  async function commitToolResultClear(
173
243
  ctx: IterationContext,
@@ -180,11 +250,13 @@ async function commitToolResultClear(
180
250
  // content in the same archive used by whole-message compaction before
181
251
  // changing the live history or publishing the clear.
182
252
  await recordShed(ctx, [...ctx.recorder.messages], plan.messages, reason)
253
+ adoptLoadedSkills(ctx)
183
254
  installMessages(ctx.recorder.messages, plan.messages)
184
255
  // The provider counted the pre-edit prompt. Leaving that measurement live
185
256
  // makes the next trigger compare the old prompt against the new history.
186
257
  ctx.recorder.clearLastPromptTokens?.()
187
258
  await emitToolResultClear(ctx, plan)
259
+ await carryLoadedSkills(ctx, contextWindowTokens)
188
260
  await emitContextUsageSnapshot(ctx, estimateTokens(ctx), contextWindowTokens, windowSource)
189
261
  // A host-requested pass is not the kernel's own relief of a full context,
190
262
  // so it must not reset the breaker's failure streak.
@@ -1136,6 +1208,9 @@ async function runCompactionCheckInner(
1136
1208
 
1137
1209
  // Skills the model loaded and files it read go back verbatim after the
1138
1210
  // summary, with whatever the host wants carried across (its plan file).
1211
+ // The skills come from the conversation as it stands, so a skill loaded in
1212
+ // an earlier turn, or before a resume, is put back too.
1213
+ adoptLoadedSkills(ctx)
1139
1214
  let hostNote: string | undefined
1140
1215
  try {
1141
1216
  hostNote = await ctx.postCompactionContext?.()
@@ -1146,21 +1221,6 @@ async function runCompactionCheckInner(
1146
1221
  'exception.message': error instanceof Error ? error.message : String(error),
1147
1222
  })
1148
1223
  }
1149
- const restored = renderRestoredContext(manager.getState(), {
1150
- contextWindowTokens: budget,
1151
- ...(hostNote ? { hostNote } : {}),
1152
- })
1153
- if (restored) compactedContent = `${compactedContent}\n\n${restored}`
1154
-
1155
- // The pointer is only worth a line when both tools it names are mounted.
1156
- const mounted = new Set(ctx.tools?.listNames?.() ?? [])
1157
- const compactionMessage = buildCompactionMessage(
1158
- compactedContent,
1159
- mounted.has('search_conversation') && mounted.has('read_conversation')
1160
- ? COMPACTION_RECOVERY_NOTE
1161
- : undefined,
1162
- )
1163
-
1164
1224
  // Drop a replaceable PRIOR `[COMPACTED CONTEXT]` summary from this turn's
1165
1225
  // leading floor — `serializeState` is cumulative, so the new summary
1166
1226
  // supersedes it. A retained summary came from outside this manager's state
@@ -1183,6 +1243,29 @@ async function runCompactionCheckInner(
1183
1243
  .slice(systemMessages.length, keepStart)
1184
1244
  .filter((_, offset) => retained.has(systemMessages.length + offset))
1185
1245
 
1246
+ // A skill whose instructions are in what the pass keeps as it is, whole or
1247
+ // shown by an earlier pass, is not put back a second time.
1248
+ const wholeSkills = handledSkillNames([...preservedSystem, ...retainedOlder, ...recentMessages])
1249
+ const putBack: { rendered?: RenderedSkills } = {}
1250
+ const restored = renderRestoredContext(manager.getState(), {
1251
+ contextWindowTokens: budget,
1252
+ wholeSkills,
1253
+ onSkills: (rendered) => {
1254
+ putBack.rendered = rendered
1255
+ },
1256
+ ...(hostNote ? { hostNote } : {}),
1257
+ })
1258
+ if (restored) compactedContent = `${compactedContent}\n\n${restored}`
1259
+
1260
+ // The pointer is only worth a line when both tools it names are mounted.
1261
+ const mounted = new Set(ctx.tools?.listNames?.() ?? [])
1262
+ const compactionMessage = buildCompactionMessage(
1263
+ compactedContent,
1264
+ mounted.has('search_conversation') && mounted.has('read_conversation')
1265
+ ? COMPACTION_RECOVERY_NOTE
1266
+ : undefined,
1267
+ )
1268
+
1186
1269
  const newMessages = [...preservedSystem, compactionMessage, ...retainedOlder, ...recentMessages]
1187
1270
 
1188
1271
  // OPAQUE survival guard: the pinned working-memory slot is a
@@ -1282,6 +1365,9 @@ async function runCompactionCheckInner(
1282
1365
  reachedResetThreshold: reachedReset,
1283
1366
  })
1284
1367
  await emitContextUsageSnapshot(ctx, newEstimate, budget, window.source)
1368
+ if (putBack.rendered && putBack.rendered.carried.length > 0) {
1369
+ await emitSkillsCarried(ctx, putBack.rendered)
1370
+ }
1285
1371
  compactionBreaker(ctx)?.recordCompaction(
1286
1372
  ctx.recorder.currentIteration,
1287
1373
  beforeTokens,
@@ -2,6 +2,7 @@ import { createHash } from 'node:crypto'
2
2
  import { z } from 'zod'
3
3
 
4
4
  import { parseAllowedTools } from '../../authorization/skill-grant.js'
5
+ import { forkedSkillReportNote } from '../../compaction/loaded-skills.js'
5
6
  import type { Sandbox } from '../../types/sandbox/index.js'
6
7
  import { isInvocableBy, skillInvocation } from '../../types/skills/index.js'
7
8
  import type { ToolContext } from '../../types/tool/index.js'
@@ -701,6 +702,10 @@ async function runForkedSkill(input: {
701
702
  })
702
703
  return {
703
704
  ...result,
705
+ // Says what the text is, so a later pass never carries a report as the skill's steps.
706
+ output: result.success
707
+ ? `${forkedSkillReportNote(input.name)}\n\n${result.output}`
708
+ : result.output,
704
709
  data: {
705
710
  ...(typeof result.data === 'object' && result.data !== null ? result.data : {}),
706
711
  skill: input.name,
@@ -350,6 +350,16 @@ export function createTurnReporter(parentLogger?: Logger): TurnReporter {
350
350
  })
351
351
  break
352
352
 
353
+ case 'compaction_skills_carried':
354
+ log.info('Put loaded skills back after a pass cut them', {
355
+ [NAMZU.TURN_ID]: event.turnId,
356
+ [NAMZU.ITERATION]: event.iteration,
357
+ 'namzu.compaction.skills': event.skills.join(','),
358
+ 'namzu.compaction.skills_cut': event.cut.join(','),
359
+ 'namzu.compaction.skills_left_out': event.leftOut.join(','),
360
+ })
361
+ break
362
+
353
363
  case 'compaction_failed':
354
364
  // warn rather than info: the turn is now continuing at a context
355
365
  // size it had already decided was too large.
@@ -522,6 +522,25 @@ export interface MCPClientConfig {
522
522
  * forever — no error, no failure, just a turn that stopped.
523
523
  */
524
524
  requestTimeoutMs?: number
525
+ /**
526
+ * Treat the deadline as an idle deadline for a tool call that reports
527
+ * progress: each accepted `notifications/progress` (one whose value rose)
528
+ * re-arms `requestTimeoutMs` for that call. Off by default, which keeps
529
+ * the single fixed deadline.
530
+ *
531
+ * Only a `tools/call` made with `onProgress` carries a progress token, so
532
+ * only such a call can be extended. The call is still bounded in total by
533
+ * `maxTotalTimeoutMs`, which progress never extends.
534
+ */
535
+ resetTimeoutOnProgress?: boolean
536
+ /**
537
+ * The most a progress-extended call may take in total, counted from when
538
+ * the request was issued, in milliseconds. An integer no smaller than
539
+ * `requestTimeoutMs`. Defaults to ten times `requestTimeoutMs`
540
+ * (`DEFAULT_MCP_TOTAL_TIMEOUT_FACTOR`). Applies only with
541
+ * `resetTimeoutOnProgress`.
542
+ */
543
+ maxTotalTimeoutMs?: number
525
544
  /**
526
545
  * How long `connect()`'s era probe waits for an answer before deciding
527
546
  * the peer speaks a legacy revision. Defaults to
@@ -304,6 +304,27 @@ type CoreSessionEvent =
304
304
  */
305
305
  reliefWasEnough: boolean
306
306
  }
307
+ /**
308
+ * A pass cut or summarised history and the instructions of skills the
309
+ * conversation had loaded were put back in front of the model, as reference.
310
+ *
311
+ * Emitted only when something was put back: a skill whose instructions are
312
+ * still whole in the context is never repeated, so most passes say nothing.
313
+ * Live only (ephemeral): it is not written to the session log, because the
314
+ * put-back text is a message of its own and is in the log as one.
315
+ */
316
+ | {
317
+ type: 'compaction_skills_carried'
318
+ sessionId: SessionId
319
+ turnId?: TurnId
320
+ iteration: number
321
+ /** Skills put back, oldest load first. */
322
+ skills: string[]
323
+ /** The ones among them cut to their per-skill cap, each with a line saying how to load it in full. */
324
+ cut: string[]
325
+ /** Skills left out for room, newest first; each is named in the text the model reads. */
326
+ leftOut: string[]
327
+ }
307
328
  /**
308
329
  * A compaction pass ran and shed nothing, so the history is unchanged.
309
330
  *
@@ -1443,7 +1464,9 @@ export type SessionEventListener = (event: SessionEvent) => void | Promise<void>
1443
1464
  * log is what cannot be written while they matter.
1444
1465
  * `compaction_started` is ephemeral too: it only says a pass is about to run,
1445
1466
  * and the `compaction` record or `compaction_failed` that ends it is the
1446
- * durable fact.
1467
+ * durable fact. `compaction_skills_carried` is ephemeral for the same kind of
1468
+ * reason: the text it announces is a message of its own and is in the log as
1469
+ * one, so a log carries no record an older reader would have to skip.
1447
1470
  */
1448
1471
  export const EPHEMERAL_EVENT_TYPES: ReadonlySet<SessionEventType> = new Set<SessionEventType>([
1449
1472
  'text_delta',
@@ -1452,6 +1475,7 @@ export const EPHEMERAL_EVENT_TYPES: ReadonlySet<SessionEventType> = new Set<Sess
1452
1475
  'tool_progress',
1453
1476
  'monitor_event',
1454
1477
  'compaction_started',
1478
+ 'compaction_skills_carried',
1455
1479
  'session_log_degraded',
1456
1480
  'session_log_recovered',
1457
1481
  ])
@@ -1460,7 +1484,7 @@ export function isEphemeralEvent(event: { readonly type: string }): boolean {
1460
1484
  return EPHEMERAL_EVENT_TYPES.has(event.type as SessionEventType)
1461
1485
  }
1462
1486
 
1463
- /** The event types a session log records: every literal except the ephemeral eight. */
1487
+ /** The event types a session log records: every literal except the ephemeral nine. */
1464
1488
  export type PersistedSessionEventType = Exclude<
1465
1489
  SessionEventType,
1466
1490
  | 'text_delta'
@@ -1469,6 +1493,7 @@ export type PersistedSessionEventType = Exclude<
1469
1493
  | 'tool_progress'
1470
1494
  | 'monitor_event'
1471
1495
  | 'compaction_started'
1496
+ | 'compaction_skills_carried'
1472
1497
  | 'session_log_degraded'
1473
1498
  | 'session_log_recovered'
1474
1499
  >
@@ -724,6 +724,7 @@ const SESSION_EVENT_TYPE_TABLE: Record<SessionEventType, true> = {
724
724
  compaction_shed: true,
725
725
  compaction_completed: true,
726
726
  compaction_tool_results_cleared: true,
727
+ compaction_skills_carried: true,
727
728
  compaction_failed: true,
728
729
  tool_executing: true,
729
730
  tool_progress: true,