@namzu/sdk 38.0.0 → 38.2.1

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 (157) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/dist/agents/ReactiveAgent.d.ts.map +1 -1
  3. package/dist/agents/ReactiveAgent.js +1 -0
  4. package/dist/agents/ReactiveAgent.js.map +1 -1
  5. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  6. package/dist/bridge/sse/mapper.js +2 -0
  7. package/dist/bridge/sse/mapper.js.map +1 -1
  8. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  9. package/dist/manager/agent/lifecycle.js +4 -0
  10. package/dist/manager/agent/lifecycle.js.map +1 -1
  11. package/dist/manager/plan/lifecycle.d.ts +1 -1
  12. package/dist/manager/plan/lifecycle.d.ts.map +1 -1
  13. package/dist/manager/plan/lifecycle.js +14 -12
  14. package/dist/manager/plan/lifecycle.js.map +1 -1
  15. package/dist/manager/resident/agenda.d.ts +125 -0
  16. package/dist/manager/resident/agenda.d.ts.map +1 -0
  17. package/dist/manager/resident/agenda.js +551 -0
  18. package/dist/manager/resident/agenda.js.map +1 -0
  19. package/dist/manager/resident/delivery-window.d.ts +22 -0
  20. package/dist/manager/resident/delivery-window.d.ts.map +1 -0
  21. package/dist/manager/resident/delivery-window.js +78 -0
  22. package/dist/manager/resident/delivery-window.js.map +1 -0
  23. package/dist/manager/resident/host.d.ts +78 -0
  24. package/dist/manager/resident/host.d.ts.map +1 -0
  25. package/dist/manager/resident/host.js +252 -0
  26. package/dist/manager/resident/host.js.map +1 -0
  27. package/dist/manager/resident/initiative.d.ts +111 -0
  28. package/dist/manager/resident/initiative.d.ts.map +1 -0
  29. package/dist/manager/resident/initiative.js +121 -0
  30. package/dist/manager/resident/initiative.js.map +1 -0
  31. package/dist/manager/resident/learning.d.ts +483 -0
  32. package/dist/manager/resident/learning.d.ts.map +1 -0
  33. package/dist/manager/resident/learning.js +263 -0
  34. package/dist/manager/resident/learning.js.map +1 -0
  35. package/dist/manager/resident/loop.d.ts +31 -0
  36. package/dist/manager/resident/loop.d.ts.map +1 -0
  37. package/dist/manager/resident/loop.js +69 -0
  38. package/dist/manager/resident/loop.js.map +1 -0
  39. package/dist/manager/resident/outbox.d.ts +180 -0
  40. package/dist/manager/resident/outbox.d.ts.map +1 -0
  41. package/dist/manager/resident/outbox.js +214 -0
  42. package/dist/manager/resident/outbox.js.map +1 -0
  43. package/dist/manager/resident/proposal.d.ts +91 -0
  44. package/dist/manager/resident/proposal.d.ts.map +1 -0
  45. package/dist/manager/resident/proposal.js +75 -0
  46. package/dist/manager/resident/proposal.js.map +1 -0
  47. package/dist/manager/resident/store.d.ts +149 -0
  48. package/dist/manager/resident/store.d.ts.map +1 -0
  49. package/dist/manager/resident/store.js +175 -0
  50. package/dist/manager/resident/store.js.map +1 -0
  51. package/dist/prompt/contributions.d.ts +5 -2
  52. package/dist/prompt/contributions.d.ts.map +1 -1
  53. package/dist/prompt/contributions.js.map +1 -1
  54. package/dist/prompt/index.d.ts +2 -0
  55. package/dist/prompt/index.d.ts.map +1 -1
  56. package/dist/prompt/index.js +1 -0
  57. package/dist/prompt/index.js.map +1 -1
  58. package/dist/prompt/resident-step.d.ts +29 -0
  59. package/dist/prompt/resident-step.d.ts.map +1 -0
  60. package/dist/prompt/resident-step.js +74 -0
  61. package/dist/prompt/resident-step.js.map +1 -0
  62. package/dist/provider/capabilities.d.ts +1 -1
  63. package/dist/provider/capabilities.d.ts.map +1 -1
  64. package/dist/provider/capabilities.js +4 -1
  65. package/dist/provider/capabilities.js.map +1 -1
  66. package/dist/provider/fallback.d.ts.map +1 -1
  67. package/dist/provider/fallback.js +2 -0
  68. package/dist/provider/fallback.js.map +1 -1
  69. package/dist/provider/idle-timeout.d.ts.map +1 -1
  70. package/dist/provider/idle-timeout.js +5 -0
  71. package/dist/provider/idle-timeout.js.map +1 -1
  72. package/dist/provider/retry.d.ts.map +1 -1
  73. package/dist/provider/retry.js +5 -0
  74. package/dist/provider/retry.js.map +1 -1
  75. package/dist/public-runtime.d.ts +10 -1
  76. package/dist/public-runtime.d.ts.map +1 -1
  77. package/dist/public-runtime.js +10 -1
  78. package/dist/public-runtime.js.map +1 -1
  79. package/dist/public-types.d.ts +16 -2
  80. package/dist/public-types.d.ts.map +1 -1
  81. package/dist/registry/index.d.ts +1 -1
  82. package/dist/registry/index.d.ts.map +1 -1
  83. package/dist/registry/tool/execute.d.ts +18 -0
  84. package/dist/registry/tool/execute.d.ts.map +1 -1
  85. package/dist/registry/tool/execute.js +43 -6
  86. package/dist/registry/tool/execute.js.map +1 -1
  87. package/dist/runtime/query/events.d.ts +6 -1
  88. package/dist/runtime/query/events.d.ts.map +1 -1
  89. package/dist/runtime/query/events.js +21 -12
  90. package/dist/runtime/query/events.js.map +1 -1
  91. package/dist/runtime/query/index.d.ts.map +1 -1
  92. package/dist/runtime/query/index.js +1 -1
  93. package/dist/runtime/query/index.js.map +1 -1
  94. package/dist/runtime/query/prompt-cache.d.ts.map +1 -1
  95. package/dist/runtime/query/prompt-cache.js +21 -42
  96. package/dist/runtime/query/prompt-cache.js.map +1 -1
  97. package/dist/scheduler/local.d.ts.map +1 -1
  98. package/dist/scheduler/local.js +2 -0
  99. package/dist/scheduler/local.js.map +1 -1
  100. package/dist/testing.d.ts +1 -0
  101. package/dist/testing.d.ts.map +1 -1
  102. package/dist/testing.js +1 -0
  103. package/dist/testing.js.map +1 -1
  104. package/dist/tools/coordinator/index.d.ts.map +1 -1
  105. package/dist/tools/coordinator/index.js +3 -0
  106. package/dist/tools/coordinator/index.js.map +1 -1
  107. package/dist/types/agent/reactive.d.ts +4 -0
  108. package/dist/types/agent/reactive.d.ts.map +1 -1
  109. package/dist/types/agent/scheduler.d.ts +3 -0
  110. package/dist/types/agent/scheduler.d.ts.map +1 -1
  111. package/dist/types/agent/task.d.ts +3 -0
  112. package/dist/types/agent/task.d.ts.map +1 -1
  113. package/dist/types/provider/interface.d.ts +2 -0
  114. package/dist/types/provider/interface.d.ts.map +1 -1
  115. package/dist/types/run/events.d.ts +3 -0
  116. package/dist/types/run/events.d.ts.map +1 -1
  117. package/dist/types/run/events.js.map +1 -1
  118. package/dist/types/sandbox/index.d.ts +36 -3
  119. package/dist/types/sandbox/index.d.ts.map +1 -1
  120. package/dist/types/sandbox/index.js.map +1 -1
  121. package/package.json +1 -1
  122. package/src/agents/ReactiveAgent.ts +1 -0
  123. package/src/bridge/sse/mapper.ts +2 -0
  124. package/src/manager/agent/lifecycle.ts +4 -0
  125. package/src/manager/plan/lifecycle.ts +15 -13
  126. package/src/manager/resident/agenda.ts +853 -0
  127. package/src/manager/resident/delivery-window.ts +93 -0
  128. package/src/manager/resident/host.ts +356 -0
  129. package/src/manager/resident/initiative.ts +195 -0
  130. package/src/manager/resident/learning.ts +369 -0
  131. package/src/manager/resident/loop.ts +101 -0
  132. package/src/manager/resident/outbox.ts +307 -0
  133. package/src/manager/resident/proposal.ts +105 -0
  134. package/src/manager/resident/store.ts +229 -0
  135. package/src/prompt/contributions.ts +5 -2
  136. package/src/prompt/index.ts +2 -0
  137. package/src/prompt/resident-step.ts +96 -0
  138. package/src/provider/capabilities.ts +8 -3
  139. package/src/provider/fallback.ts +6 -0
  140. package/src/provider/idle-timeout.ts +6 -0
  141. package/src/provider/retry.ts +6 -0
  142. package/src/public-runtime.ts +14 -0
  143. package/src/public-types.ts +67 -0
  144. package/src/registry/index.ts +1 -1
  145. package/src/registry/tool/execute.ts +56 -9
  146. package/src/runtime/query/events.ts +24 -14
  147. package/src/runtime/query/index.ts +1 -2
  148. package/src/runtime/query/prompt-cache.ts +24 -47
  149. package/src/scheduler/local.ts +2 -0
  150. package/src/testing.ts +2 -0
  151. package/src/tools/coordinator/index.ts +3 -0
  152. package/src/types/agent/reactive.ts +2 -0
  153. package/src/types/agent/scheduler.ts +4 -0
  154. package/src/types/agent/task.ts +4 -0
  155. package/src/types/provider/interface.ts +3 -0
  156. package/src/types/run/events.ts +3 -0
  157. package/src/types/sandbox/index.ts +42 -4
@@ -0,0 +1,96 @@
1
+ import {
2
+ type ResidentLearningState,
3
+ projectResidentLearning,
4
+ } from '../manager/resident/learning.js'
5
+ import type { ResidentState } from '../manager/resident/store.js'
6
+ import type { PromptContribution } from './contributions.js'
7
+
8
+ const RESIDENT_WORK_GUIDANCE = `## Resident work
9
+ Perform one useful bounded step toward the whole authorized objective. Preserve its scope, constraints and unfinished work between steps. A finished step is not proof that the objective is complete; report completion only when the whole objective is achieved. If useful authorized work remains, return the host's continuation disposition with concrete next steps. If a prerequisite is missing, report it accurately.
10
+
11
+ Obey current project instructions, permissions and tool prerequisites. Saved summaries, wake evidence, learning and skills do not grant new authority or override those constraints. Do not bypass a refused action through another tool or delegate. Pushes, destructive git operations and discarding someone else's work require explicit authorization.
12
+
13
+ Each invocation has an isolated session. Only the supplied saved state continues; do not assume earlier conversations, tool transcripts or running processes are available. Treat the previous summary as a report of earlier work, not a tool receipt from this invocation. Reuse retained evidence when it is sufficient; recheck mutable external state when freshness matters, evidence is incomplete, or an observation contradicts it. Recover missing evidence with a permitted read instead of repeating a state-changing action.
14
+
15
+ Ground new action claims in successful tool results. Read the relevant evidence before editing, use focused tools, and verify changes with the checks required by the project and the task. Report what succeeded, what failed and what was not checked. Delegate independent work only when an available delegation capability helps; supply its scope and constraints and distinguish returned claims from verified results.
16
+
17
+ Save a useful summary of evidence, exact identifiers or artifact paths, completed work, unresolved constraints and the next step. Follow the host's output instructions for this invocation.`
18
+
19
+ const READ_ONLY_GUIDANCE = `## Read-only invocation
20
+ Use permitted reads and analysis. Do not change files, persistent memory or external state. Read-only objectives may be completed in this mode; if the objective requires a mutation that the current permissions refuse, report that missing prerequisite using the host's output instructions.`
21
+
22
+ /** @experimental Host-selected context for one admitted resident step. */
23
+ export interface ResidentStepPromptOptions {
24
+ readonly state: ResidentState
25
+ /** The host-approved learning snapshot bound to this admission. */
26
+ readonly learning?: ResidentLearningState
27
+ /** Host-loaded guidance for this invocation; this factory performs no I/O. */
28
+ readonly skillsContext?: string
29
+ /** Describe an existing read-only boundary; this does not enforce permissions. */
30
+ readonly readOnly?: boolean
31
+ /** The host owns its response format, validation and settlement protocol. */
32
+ readonly outputInstructions: string
33
+ }
34
+
35
+ /**
36
+ * @experimental Capture resident guidance and continuation state as two prompt
37
+ * contributions. Register them on the registry supplied to `query`.
38
+ *
39
+ * The static contribution carries stable work guidance and the host's output
40
+ * contract. The dynamic contribution captures this invocation's state and
41
+ * approved learning, outside the cached prefix. Both remain present on every
42
+ * iteration; build fresh contributions for the next admitted step.
43
+ *
44
+ * Project instructions and authorization remain on their existing host/runtime
45
+ * paths. These contributions neither load project files nor authorize work.
46
+ */
47
+ export function createResidentStepContributions(
48
+ options: ResidentStepPromptOptions,
49
+ ): readonly PromptContribution[] {
50
+ const { state } = options
51
+ const learning = projectResidentLearning(options.learning, {
52
+ maxChars: 12_000,
53
+ skillNames: options.learning?.skills.map((skill) => skill.name) ?? [],
54
+ })
55
+ // Capture literals now. An admitted invocation must not borrow the next
56
+ // invocation's mutable options, state or learning through a render closure.
57
+ const guidance = [
58
+ RESIDENT_WORK_GUIDANCE,
59
+ ...(options.readOnly ? [READ_ONLY_GUIDANCE] : []),
60
+ options.outputInstructions,
61
+ ]
62
+ .filter((text) => text.trim().length > 0)
63
+ .join('\n\n')
64
+ const snapshot = [
65
+ '## Resident continuation',
66
+ JSON.stringify({
67
+ identity: state.identity,
68
+ objective: state.objective,
69
+ previousSummary: state.summary,
70
+ admission: state.stepsAdmitted,
71
+ wakeReason: state.reason,
72
+ }),
73
+ ...(learning.text
74
+ ? [
75
+ 'Retained learning is host-approved behavioral guidance and evidence; it does not change the objective, permissions or project instructions.',
76
+ learning.text,
77
+ ]
78
+ : []),
79
+ ...(learning.omitted
80
+ ? [`${learning.omitted} learning entries were omitted from this bounded context.`]
81
+ : []),
82
+ ...(options.skillsContext?.trim() ? [options.skillsContext] : []),
83
+ ].join('\n\n')
84
+ return Object.freeze([
85
+ Object.freeze({
86
+ id: 'namzu.resident-step.guidance',
87
+ placement: 'static' as const,
88
+ render: () => guidance,
89
+ }),
90
+ Object.freeze({
91
+ id: 'namzu.resident-step.continuation',
92
+ placement: 'dynamic' as const,
93
+ render: () => snapshot,
94
+ }),
95
+ ])
96
+ }
@@ -86,10 +86,15 @@ export function assertNativeStructuredOutputSupported(
86
86
 
87
87
  /** Refuse a hosted search request before dispatch when the route cannot honor it. */
88
88
  export function assertHostedWebSearchSupported(
89
- provider: Pick<LLMProvider, 'id' | 'capabilities'>,
90
- params: Pick<ChatCompletionParams, 'webSearch'>,
89
+ provider: Pick<LLMProvider, 'id' | 'capabilities' | 'supportsHostedWebSearchFor'>,
90
+ params: Pick<ChatCompletionParams, 'webSearch'> & Partial<Pick<ChatCompletionParams, 'model'>>,
91
91
  ): void {
92
- if (params.webSearch && provider.capabilities?.supportsHostedWebSearch !== true) {
92
+ if (
93
+ params.webSearch &&
94
+ (provider.capabilities?.supportsHostedWebSearch !== true ||
95
+ (provider.supportsHostedWebSearchFor &&
96
+ !provider.supportsHostedWebSearchFor(params.model ?? '', params.webSearch.mode)))
97
+ ) {
93
98
  throw new ProviderRequestError({
94
99
  kind: 'bad_request',
95
100
  providerId: provider.id,
@@ -505,6 +505,12 @@ export function withProviderFallback(
505
505
  // the request that may actually traverse the whole chain. The method is
506
506
  // always present on a multi-member wrapper: `undefined` is its honest
507
507
  // answer when any member cannot enumerate its selected model.
508
+ supportsHostedWebSearchFor: (model: string, mode: 'live' | 'cached') =>
509
+ members.every(
510
+ (member) =>
511
+ member.provider.capabilities?.supportsHostedWebSearch === true &&
512
+ (member.provider.supportsHostedWebSearchFor?.(member.model ?? model, mode) ?? true),
513
+ ),
508
514
  reasoningEffortLevelsFor: (model: string, thinking?: ThinkingConfig) =>
509
515
  commonReasoningEffortLevels(members, model, thinking),
510
516
  // Present on the chain wrapper for the same reason the menu method is:
@@ -212,6 +212,12 @@ export function withStreamIdleTimeout(
212
212
  ...(provider.doctorCheck
213
213
  ? { doctorCheck: (model?: string) => provider.doctorCheck?.(model) }
214
214
  : {}),
215
+ ...(provider.supportsHostedWebSearchFor
216
+ ? {
217
+ supportsHostedWebSearchFor: (model: string, mode: 'live' | 'cached') =>
218
+ provider.supportsHostedWebSearchFor?.(model, mode) ?? false,
219
+ }
220
+ : {}),
215
221
  ...(provider.reasoningEffortLevelsFor
216
222
  ? {
217
223
  reasoningEffortLevelsFor: (
@@ -270,6 +270,12 @@ export function withProviderRetry(
270
270
  ...(provider.doctorCheck
271
271
  ? { doctorCheck: (model?: string) => provider.doctorCheck?.(model) }
272
272
  : {}),
273
+ ...(provider.supportsHostedWebSearchFor
274
+ ? {
275
+ supportsHostedWebSearchFor: (model: string, mode: 'live' | 'cached') =>
276
+ provider.supportsHostedWebSearchFor?.(model, mode) ?? false,
277
+ }
278
+ : {}),
273
279
  ...(provider.reasoningEffortLevelsFor
274
280
  ? {
275
281
  reasoningEffortLevelsFor: (
@@ -1217,6 +1217,7 @@ export {
1217
1217
  PromptContributionRegistry,
1218
1218
  SKILLS_CONTRIBUTION_ID,
1219
1219
  codingAgentDoctrineContribution,
1220
+ createResidentStepContributions,
1220
1221
  skillsContribution,
1221
1222
  } from './prompt/index.js'
1222
1223
 
@@ -1341,3 +1342,16 @@ export { DiskTokenBudgetStore, openTokenBudget } from './store/run/token-budget-
1341
1342
  export { validateTokenBudgetBinding } from './types/run/token-budget-store.js'
1342
1343
 
1343
1344
  export { snapshotRequestContext, diffRequestContext } from './runtime/query/request-context.js'
1345
+
1346
+ export { DiskResidentStore, ResidentConflictError } from './manager/resident/store.js'
1347
+ export { runResident, stepResident } from './manager/resident/loop.js'
1348
+ export { DiskResidentAgenda } from './manager/resident/agenda.js'
1349
+ export { ResidentHost } from './manager/resident/host.js'
1350
+
1351
+ export { createResidentSelector } from './manager/resident/initiative.js'
1352
+ export { validateResidentProposal } from './manager/resident/proposal.js'
1353
+
1354
+ export { deliverResidentMessage } from './manager/resident/outbox.js'
1355
+ export { createResidentDeliveryWindow } from './manager/resident/delivery-window.js'
1356
+
1357
+ export { hashResidentSkill, projectResidentLearning } from './manager/resident/learning.js'
@@ -173,6 +173,7 @@ export type {
173
173
  export type {
174
174
  ManagedRegistryConfig,
175
175
  ToolExecutionResult,
176
+ ToolRegistryForkOptions,
176
177
  } from './registry/index.js'
177
178
 
178
179
  export type { PluginLifecycleManagerConfig } from './plugin/lifecycle.js'
@@ -377,6 +378,7 @@ export type {
377
378
  PromptContribution,
378
379
  PromptContributionContext,
379
380
  PromptPlacement,
381
+ ResidentStepPromptOptions,
380
382
  } from './prompt/index.js'
381
383
 
382
384
  export type {
@@ -445,3 +447,68 @@ export type {
445
447
  RequestContextSnapshot,
446
448
  RequestContextChange,
447
449
  } from './runtime/query/request-context.js'
450
+
451
+ export type { ResidentDecision, ResidentState, ResidentStore } from './manager/resident/store.js'
452
+ export type {
453
+ ResidentStep,
454
+ ResidentStepResult,
455
+ ResidentLoopOptions,
456
+ } from './manager/resident/loop.js'
457
+ export type { ResidentExecutionStore } from './manager/resident/store.js'
458
+ export type {
459
+ ResidentAgendaState,
460
+ ResidentAgendaStore,
461
+ ResidentPursuit,
462
+ } from './manager/resident/agenda.js'
463
+ export type {
464
+ ResidentHostResult,
465
+ ResidentHostRunOptions,
466
+ ResidentPursuitStep,
467
+ } from './manager/resident/host.js'
468
+
469
+ export type {
470
+ ResidentObservation,
471
+ ResidentFeedback,
472
+ ResidentSelectionConfig,
473
+ ResidentCandidate,
474
+ ResidentSelection,
475
+ ResidentSelector,
476
+ } from './manager/resident/initiative.js'
477
+ export type { ResidentHostOptions, ResidentObserver } from './manager/resident/host.js'
478
+ export type {
479
+ ResidentProposal,
480
+ ResidentProposalLimits,
481
+ ResidentProposalOrigin,
482
+ } from './manager/resident/proposal.js'
483
+
484
+ export type { ResidentMessageFactory } from './manager/resident/host.js'
485
+ export type {
486
+ ResidentMessageInput,
487
+ ResidentOutboxMessage,
488
+ ResidentDeliveryOutcome,
489
+ ResidentOutboxStore,
490
+ ResidentDeliveryGate,
491
+ ResidentMessageTransport,
492
+ ResidentDeliveryOptions,
493
+ ResidentDeliveryResult,
494
+ } from './manager/resident/outbox.js'
495
+ export type { ResidentDeliveryWindowConfig } from './manager/resident/delivery-window.js'
496
+
497
+ export type { ResidentStepContext, ResidentContextualStep } from './manager/resident/host.js'
498
+ export type {
499
+ ResidentLearningEvidence,
500
+ ResidentSkillCandidate,
501
+ ResidentLearnedSkill,
502
+ ResidentLearningState,
503
+ ResidentProfileUpdate,
504
+ ResidentSkillEvaluation,
505
+ ResidentLearningProjectionOptions,
506
+ ResidentLearningProjection,
507
+ } from './manager/resident/learning.js'
508
+
509
+ export type {
510
+ ResidentArchiveRequest,
511
+ ResidentArchiveEntry,
512
+ ResidentArchivePage,
513
+ ResidentArchiveListOptions,
514
+ } from './manager/resident/agenda.js'
@@ -3,7 +3,7 @@ export { ManagedRegistry } from './ManagedRegistry.js'
3
3
  export type { ManagedRegistryConfig } from './ManagedRegistry.js'
4
4
 
5
5
  export { ToolNameCollisionError, ToolRegistry } from './tool/execute.js'
6
- export type { ToolExecutionResult } from './tool/execute.js'
6
+ export type { ToolExecutionResult, ToolRegistryForkOptions } from './tool/execute.js'
7
7
  export {
8
8
  ToolCatalog,
9
9
  createToolCatalogFromRegistry,
@@ -25,6 +25,17 @@ import { ToolResultHalted, screenToolResult } from './screen.js'
25
25
 
26
26
  export type { ToolExecutionResult }
27
27
 
28
+ /** Options for an independent registry membership and availability snapshot. */
29
+ export interface ToolRegistryForkOptions {
30
+ /**
31
+ * Defer currently active tools outside this exact list. Omission preserves
32
+ * every availability; an empty list defers every active tool. Listed tools
33
+ * that are already deferred or suspended keep that state. Names must be
34
+ * unique, valid registered names; a misspelling is refused.
35
+ */
36
+ readonly deferExcept?: readonly string[]
37
+ }
38
+
28
39
  // Tokens too generic to identify a tool by name — ignored when matching a
29
40
  // batched `search_tools` query so they can't activate the whole catalog
30
41
  // (every bridged tool name shares the `clawtool` prefix, for instance).
@@ -155,6 +166,45 @@ export class ToolRegistry extends ManagedRegistry<ToolDefinition> {
155
166
  this.resultGuardrails = config?.resultGuardrails
156
167
  }
157
168
 
169
+ /**
170
+ * Snapshot membership and availability for another run without changing this
171
+ * registry. Discovery, registration and suspension then affect only the fork.
172
+ * Definitions, handlers and configuration remain shared; this is not a deep
173
+ * clone or an authorization boundary. Prepared executions belong only to the
174
+ * registry that prepared them and do not transfer to the fork.
175
+ */
176
+ fork(options: ToolRegistryForkOptions = {}): ToolRegistry {
177
+ let retained: Set<string> | undefined
178
+ if (options.deferExcept !== undefined) {
179
+ if (!Array.isArray(options.deferExcept))
180
+ throw new TypeError('ToolRegistry.fork deferExcept must be an array of tool names.')
181
+ retained = new Set()
182
+ for (const name of options.deferExcept) {
183
+ if (typeof name !== 'string' || !TOOL_NAME_PATTERN.test(name))
184
+ throw new TypeError('ToolRegistry.fork deferExcept must contain valid tool names.')
185
+ if (retained.has(name))
186
+ throw new TypeError(`ToolRegistry.fork deferExcept contains duplicate tool "${name}".`)
187
+ this.getOrThrow(name)
188
+ retained.add(name)
189
+ }
190
+ }
191
+
192
+ const fork = new ToolRegistry({
193
+ logger: this.log,
194
+ tierConfig: this.tierConfig,
195
+ resultGuardrails: this.resultGuardrails,
196
+ })
197
+ fork.items = new Map(this.items)
198
+ fork.availability = new Map(this.availability)
199
+ if (retained) {
200
+ for (const name of fork.listNames()) {
201
+ if (fork.getAvailability(name) === 'active' && !retained.has(name))
202
+ fork.availability.set(name, 'deferred')
203
+ }
204
+ }
205
+ return fork
206
+ }
207
+
158
208
  override register(id: string, tool: ToolDefinition): void
159
209
  override register(tool: ToolDefinition, initialState?: ToolAvailability): void
160
210
  override register(tools: ToolDefinition[], initialState?: ToolAvailability): void
@@ -294,25 +344,20 @@ export class ToolRegistry extends ManagedRegistry<ToolDefinition> {
294
344
 
295
345
  /** Active matches use the same ranking, also recognizing exact short or generic names. */
296
346
  searchActive(query: string): ToolDefinition[] {
297
- return this.searchByAvailability(query, ['active'], true)
347
+ return this.searchByAvailability(query, ['active'])
298
348
  }
299
349
 
300
- private searchByAvailability(
301
- query: string,
302
- states: ToolAvailability[],
303
- allowExactName = false,
304
- ): ToolDefinition[] {
350
+ private searchByAvailability(query: string, states: ToolAvailability[]): ToolDefinition[] {
305
351
  const q = query.toLowerCase().trim()
306
352
  if (q.length === 0) return []
307
353
  const terms = q.split(/\s+/).filter((tok) => tok.length >= 3 && !SEARCH_STOP_TOKENS.has(tok))
308
- if (terms.length === 0 && !allowExactName) return []
309
354
 
310
355
  const scored: Array<{ tool: ToolDefinition; score: number }> = []
311
356
  for (const tool of this.getByAvailability(states)) {
312
357
  const name = tool.name.toLowerCase()
313
358
  const description = tool.description.toLowerCase()
314
359
  const argumentNames = listArgumentNames(tool)
315
- let score = allowExactName && terms.length === 0 && name === q ? SEARCH_WEIGHT_NAME_EXACT : 0
360
+ let score = terms.length === 0 && name === q ? SEARCH_WEIGHT_NAME_EXACT : 0
316
361
  for (const term of terms) {
317
362
  if (name === term) {
318
363
  score += SEARCH_WEIGHT_NAME_EXACT
@@ -388,7 +433,9 @@ Executable tool names, descriptions, and JSON input schemas are attached through
388
433
  })
389
434
  .join('\n')
390
435
  const deferredIntro =
391
- this.has('search_tools') && this.getAvailability('search_tools') === 'active'
436
+ this.has('search_tools') &&
437
+ this.getAvailability('search_tools') === 'active' &&
438
+ (!toolNames || toolNames.includes('search_tools'))
392
439
  ? 'Use search_tools to load these before use:'
393
440
  : 'Deferred tools are discoverable but not executable until the runtime activates them:'
394
441
  parts.push(`<deferred_tools>\n${deferredIntro}\n${entries}\n</deferred_tools>`)
@@ -7,8 +7,9 @@ import type { ProbeObservation } from '../../probe/registry.js'
7
7
  import type { ActivityEvent, ActivityStore } from '../../store/activity/memory.js'
8
8
  import type { RunId } from '../../types/ids/index.js'
9
9
  import type { FencingToken } from '../../types/run/checkpoint-store.js'
10
- import { isEphemeralEvent } from '../../types/run/events.js'
10
+ import { type PersistedRunEvent, isEphemeralEvent } from '../../types/run/events.js'
11
11
  import type { RunEvent } from '../../types/run/index.js'
12
+ import type { ReadRunEventsOptions } from '../../types/run/store.js'
12
13
  import type { TaskEvent, TaskStore } from '../../types/task/index.js'
13
14
  import { SCOPE_ATTRIBUTE } from '../../utils/log/types.js'
14
15
  import { type Logger, resolveLogger } from '../../utils/logger.js'
@@ -55,9 +56,28 @@ export class EventTranslator {
55
56
  */
56
57
  private generation: FencingToken | undefined
57
58
 
58
- /** Serializes sequence assignment against the append. See {@link emitEvent}. */
59
+ /** Serializes sequence assignment, appends, and transcript snapshots. */
59
60
  private appendChain: Promise<void> = Promise.resolve()
60
61
 
62
+ private async withTranscriptLock<T>(operation: () => Promise<T>): Promise<T> {
63
+ const previous = this.appendChain
64
+ let release!: () => void
65
+ this.appendChain = new Promise<void>((resolve) => {
66
+ release = resolve
67
+ })
68
+ try {
69
+ await previous
70
+ return await operation()
71
+ } finally {
72
+ release()
73
+ }
74
+ }
75
+
76
+ /** Keep a live transcript read between whole appends, including later queued writes. */
77
+ readEvents(options?: ReadRunEventsOptions): Promise<readonly PersistedRunEvent[]> {
78
+ return this.withTranscriptLock(() => this.runMgr.getRunStore().readEvents(options))
79
+ }
80
+
61
81
  setGeneration(fence: FencingToken | undefined): void {
62
82
  this.generation = fence
63
83
  }
@@ -109,15 +129,7 @@ export class EventTranslator {
109
129
  // took the number 15 and two took 12. A duplicated sequence is worse
110
130
  // than a missing one — a consumer asking for everything above 15 is
111
131
  // handed part of the run it already had, spliced in as if it were new.
112
- const previous = this.appendChain
113
- let release!: () => void
114
- this.appendChain = new Promise<void>((resolve) => {
115
- release = resolve
116
- })
117
-
118
- try {
119
- await previous
120
-
132
+ await this.withTranscriptLock(async () => {
121
133
  // The number is a claim that the event is IN the log, so it is taken
122
134
  // against the append and not before it. The candidate goes to the
123
135
  // store first; only a write that landed advances the counter and
@@ -143,9 +155,7 @@ export class EventTranslator {
143
155
 
144
156
  this.runMgr.commitEventSeq(seq)
145
157
  this.pendingEvents.push(stamped)
146
- } finally {
147
- release()
148
- }
158
+ })
149
159
  };
150
160
 
151
161
  *drainPending(): Generator<RunEvent> {
@@ -1741,8 +1741,7 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
1741
1741
  ...(params.maxToolCalls !== undefined
1742
1742
  ? {
1743
1743
  maxToolCalls: params.maxToolCalls,
1744
- readToolCallBudgetEvents: () =>
1745
- ctx.runMgr.getRunStore().readEvents({ integrity: 'strict' }),
1744
+ readToolCallBudgetEvents: () => eventTranslator.readEvents({ integrity: 'strict' }),
1746
1745
  }
1747
1746
  : {}),
1748
1747
  ...(params.maxToolConcurrency !== undefined
@@ -39,12 +39,6 @@ export class PromptCache {
39
39
  }
40
40
 
41
41
  getSystemPrompt(input: PromptCacheInput): string {
42
- const hash = this.computeConfigHash(input)
43
-
44
- if (this.cachedPrompt && this.cachedConfigHash === hash) {
45
- return this.cachedPrompt
46
- }
47
-
48
42
  const builder = new PromptBuilder({
49
43
  systemPrompt: input.systemPrompt,
50
44
  persona: input.persona,
@@ -56,7 +50,14 @@ export class PromptCache {
56
50
  ...(input.contributions ? { contributions: input.contributions } : {}),
57
51
  })
58
52
 
59
- this.cachedPrompt = builder.build()
53
+ const prompt = builder.build()
54
+ const hash = this.computeConfigHash(input, prompt)
55
+
56
+ if (this.cachedPrompt !== undefined && this.cachedConfigHash === hash) {
57
+ return this.cachedPrompt
58
+ }
59
+
60
+ this.cachedPrompt = prompt
60
61
  this.cachedConfigHash = hash
61
62
  return this.cachedPrompt
62
63
  }
@@ -67,7 +68,7 @@ export class PromptCache {
67
68
 
68
69
  needsRebuild(input: PromptCacheInput): boolean {
69
70
  if (!this.cachedConfigHash) return true
70
- return this.computeConfigHash(input) !== this.cachedConfigHash
71
+ return this.computeConfigHash(input, new PromptBuilder(input).build()) !== this.cachedConfigHash
71
72
  }
72
73
 
73
74
  getSystemPromptSegmented(
@@ -75,8 +76,6 @@ export class PromptCache {
75
76
  contextLevel: AgentContextLevel = 'full',
76
77
  workingDirectory?: string,
77
78
  ): PromptSegments {
78
- const staticHash = this.computeStaticHash(input)
79
-
80
79
  const builder = new PromptBuilder({
81
80
  systemPrompt: input.systemPrompt,
82
81
  persona: input.persona,
@@ -89,6 +88,7 @@ export class PromptCache {
89
88
  })
90
89
 
91
90
  const segments = builder.buildSegmented(contextLevel, workingDirectory)
91
+ const staticHash = this.computeStaticHash(segments.static)
92
92
 
93
93
  if (this.cachedStaticHash === staticHash && this.cachedStaticSegment !== undefined) {
94
94
  return {
@@ -110,47 +110,24 @@ export class PromptCache {
110
110
  this.cachedStaticHash = undefined
111
111
  }
112
112
 
113
- private computeStaticHash(input: PromptCacheInput): string {
114
- const parts: string[] = [
115
- this.agentId,
116
- input.systemPrompt ?? '',
117
- input.persona?.identity?.role ?? '',
118
- input.persona?.identity?.description ?? '',
119
- input.basePrompt ?? '',
120
- ...(input.skills?.map((s) => s.metadata.name) ?? []),
121
- // The STATIC ones only, because this hash guards the static
122
- // segment. A `dynamic` or `turn` contributor coming or going does
123
- // not change the cached prefix, and folding it in here would
124
- // invalidate that prefix for a change it does not describe.
125
- ...(input.contributions
126
- ?.list()
127
- .filter((c) => c.placement === 'static')
128
- .map((c) => c.id) ?? []),
129
- ]
130
-
131
- return createHash('sha256').update(parts.join('\0')).digest('hex').slice(0, 16)
113
+ private computeStaticHash(staticSegment: string): string {
114
+ // Static means stable within a run, but a cache can outlive that run.
115
+ // A fresh registry or a replacement can keep the same ids while its
116
+ // instructions change. Hash the text already built for this request,
117
+ // including persona, skills and context-level choices, without
118
+ // rendering any contribution twice or including dynamic/turn text.
119
+ return createHash('sha256').update(staticSegment).digest('hex').slice(0, 16)
132
120
  }
133
121
 
134
- private computeConfigHash(input: PromptCacheInput): string {
135
- const parts: string[] = [
122
+ private computeConfigHash(input: PromptCacheInput, prompt: string): string {
123
+ const parts = [
136
124
  this.agentId,
137
- input.systemPrompt ?? '',
138
- input.persona?.identity?.role ?? '',
139
- input.persona?.identity?.description ?? '',
140
- input.basePrompt ?? '',
141
- ...(input.skills?.map((s) => s.metadata.name) ?? []),
142
- ...(input.allowedTools ?? []),
143
- JSON.stringify(input.runtimeContext ?? {}),
144
- // Ids and placements, not rendered text. Rendering every
145
- // contribution to hash it would run them twice per request for a
146
- // value the cache exists to avoid computing — and a contributor
147
- // whose OUTPUT changes while its id does not is exactly the one
148
- // that must declare `dynamic` or `turn` rather than `static`.
149
- // Hashing the identity is what catches the change this cache can
150
- // actually be wrong about: a different SET of contributors.
151
- ...(input.contributions?.list().map((c) => `${c.id}:${c.placement}`) ?? []),
125
+ prompt,
126
+ // Keep placement changes visible to needsRebuild even when the
127
+ // unsegmented text happens to be identical.
128
+ input.contributions?.list().map((c) => [c.id, c.placement]) ?? [],
152
129
  ]
153
130
 
154
- return createHash('sha256').update(parts.join('\0')).digest('hex').slice(0, 16)
131
+ return createHash('sha256').update(JSON.stringify(parts)).digest('hex').slice(0, 16)
155
132
  }
156
133
  }
@@ -112,6 +112,8 @@ export class LocalTaskScheduler implements TaskScheduler {
112
112
  {
113
113
  agentId: options.agentId,
114
114
  beforeStart: options.beforeStart,
115
+ ...(options.planId ? { planId: options.planId } : {}),
116
+ ...(options.planStepId ? { planStepId: options.planStepId } : {}),
115
117
  input: {
116
118
  messages: [createUserMessage(options.prompt)],
117
119
  workingDirectory: options.workingDirectory,
package/src/testing.ts CHANGED
@@ -38,3 +38,5 @@ export {
38
38
  defineProviderDriverConformance,
39
39
  } from './provider/conformance.js'
40
40
  export type { ProviderDriverConformanceOptions } from './provider/conformance.js'
41
+
42
+ export { fixtureId } from './test-support/ids.js'
@@ -566,6 +566,7 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
566
566
  // could report `failed` or stay `executing` forever but never
567
567
  // `completed`.
568
568
  const planStepId = plan_step_id
569
+ const planId = planStepId ? getPlanManager?.()?.active?.id : undefined
569
570
  const reportStep = (status: 'running' | 'completed' | 'failed', error?: string): void => {
570
571
  if (!planStepId) return
571
572
  getPlanManager?.()?.updateStepStatus(planStepId, status, error)
@@ -595,6 +596,8 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
595
596
  prompt,
596
597
  workingDirectory: cwd,
597
598
  runtimeContext: opts.runtimeContext,
599
+ ...(planStepId ? { planStepId } : {}),
600
+ ...(planId ? { planId } : {}),
598
601
  // Hang the child run off THIS tool's span, so the delegation
599
602
  // shows up inside the turn that asked for it.
600
603
  ...(_context.parentSpan ? { parentSpan: _context.parentSpan } : {}),
@@ -19,6 +19,8 @@ import type { WorkingMemoryProvider } from './working-memory.js'
19
19
 
20
20
  export interface ReactiveAgentConfig extends BaseAgentConfig {
21
21
  systemPrompt?: string
22
+ /** Provider-hosted search for this run; the selected driver must support it. */
23
+ webSearch?: { mode: 'live' | 'cached' }
22
24
 
23
25
  persona?: AgentPersona
24
26
 
@@ -54,6 +54,10 @@ export interface CreateTaskOptions {
54
54
  */
55
55
  readonly parentSpan?: import('@opentelemetry/api').Span
56
56
 
57
+ /** Approved plan edge for live worker/progress correlation. */
58
+ readonly planId?: string
59
+ readonly planStepId?: string
60
+
57
61
  agentId: string
58
62
 
59
63
  /**
@@ -154,6 +154,10 @@ export interface SendMessageOptions {
154
154
  /** See {@link import('./scheduler.js').CreateTaskOptions.personaOverride}. */
155
155
  readonly personaOverride?: import('../persona/index.js').AgentPersona
156
156
 
157
+ /** Approved plan edge inherited from the delegation tool, when present. */
158
+ readonly planId?: string
159
+ readonly planStepId?: string
160
+
157
161
  agentId: string
158
162
 
159
163
  input: AgentInput
@@ -19,6 +19,9 @@ export interface LLMProvider {
19
19
  */
20
20
  readonly capabilities?: ProviderCapabilities
21
21
 
22
+ /** Refine hosted search support for a model and mode. Absent uses the driver-wide declaration. */
23
+ supportsHostedWebSearchFor?(model: string, mode: 'live' | 'cached'): boolean
24
+
22
25
  /**
23
26
  * Retry behaviour this DRIVER wants, when the generic default is wrong
24
27
  * for the vendor behind it.