@namzu/sdk 6.2.0 → 7.0.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 (144) hide show
  1. package/CHANGELOG.md +240 -0
  2. package/dist/agents/ReactiveAgent.d.ts.map +1 -1
  3. package/dist/agents/ReactiveAgent.js +5 -0
  4. package/dist/agents/ReactiveAgent.js.map +1 -1
  5. package/dist/agents/SupervisorAgent.d.ts.map +1 -1
  6. package/dist/agents/SupervisorAgent.js +4 -0
  7. package/dist/agents/SupervisorAgent.js.map +1 -1
  8. package/dist/agents/runAgent.d.ts +19 -1
  9. package/dist/agents/runAgent.d.ts.map +1 -1
  10. package/dist/agents/runAgent.js +2 -0
  11. package/dist/agents/runAgent.js.map +1 -1
  12. package/dist/connector/mcp/__tests__/positional-arrays.test.d.ts +2 -0
  13. package/dist/connector/mcp/__tests__/positional-arrays.test.d.ts.map +1 -0
  14. package/dist/connector/mcp/__tests__/positional-arrays.test.js +142 -0
  15. package/dist/connector/mcp/__tests__/positional-arrays.test.js.map +1 -0
  16. package/dist/connector/mcp/adapter.d.ts.map +1 -1
  17. package/dist/connector/mcp/adapter.js +123 -8
  18. package/dist/connector/mcp/adapter.js.map +1 -1
  19. package/dist/constants/agent/index.d.ts +5 -0
  20. package/dist/constants/agent/index.d.ts.map +1 -1
  21. package/dist/constants/agent/index.js +5 -0
  22. package/dist/constants/agent/index.js.map +1 -1
  23. package/dist/constants/plugin/index.d.ts +15 -0
  24. package/dist/constants/plugin/index.d.ts.map +1 -1
  25. package/dist/constants/plugin/index.js +15 -0
  26. package/dist/constants/plugin/index.js.map +1 -1
  27. package/dist/gateway/local.d.ts +11 -0
  28. package/dist/gateway/local.d.ts.map +1 -1
  29. package/dist/gateway/local.js +27 -1
  30. package/dist/gateway/local.js.map +1 -1
  31. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  32. package/dist/manager/agent/lifecycle.js +6 -0
  33. package/dist/manager/agent/lifecycle.js.map +1 -1
  34. package/dist/provider/thinking-support.d.ts +2 -1
  35. package/dist/provider/thinking-support.d.ts.map +1 -1
  36. package/dist/provider/thinking-support.js +14 -0
  37. package/dist/provider/thinking-support.js.map +1 -1
  38. package/dist/public-runtime.d.ts +1 -1
  39. package/dist/public-runtime.d.ts.map +1 -1
  40. package/dist/public-runtime.js +9 -1
  41. package/dist/public-runtime.js.map +1 -1
  42. package/dist/runtime/query/__tests__/context.test.js +18 -0
  43. package/dist/runtime/query/__tests__/context.test.js.map +1 -1
  44. package/dist/runtime/query/__tests__/effort-reaches-the-wire.test.d.ts +2 -0
  45. package/dist/runtime/query/__tests__/effort-reaches-the-wire.test.d.ts.map +1 -0
  46. package/dist/runtime/query/__tests__/effort-reaches-the-wire.test.js +118 -0
  47. package/dist/runtime/query/__tests__/effort-reaches-the-wire.test.js.map +1 -0
  48. package/dist/runtime/query/__tests__/tool-timeout.test.js +34 -0
  49. package/dist/runtime/query/__tests__/tool-timeout.test.js.map +1 -1
  50. package/dist/runtime/query/context.d.ts.map +1 -1
  51. package/dist/runtime/query/context.js +16 -1
  52. package/dist/runtime/query/context.js.map +1 -1
  53. package/dist/runtime/query/executor.d.ts.map +1 -1
  54. package/dist/runtime/query/executor.js +11 -1
  55. package/dist/runtime/query/executor.js.map +1 -1
  56. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  57. package/dist/runtime/query/iteration/index.js +5 -0
  58. package/dist/runtime/query/iteration/index.js.map +1 -1
  59. package/dist/telemetry/__tests__/model-call-span.test.js +22 -4
  60. package/dist/telemetry/__tests__/model-call-span.test.js.map +1 -1
  61. package/dist/telemetry/__tests__/span-closure.test.js +12 -5
  62. package/dist/telemetry/__tests__/span-closure.test.js.map +1 -1
  63. package/dist/tools/coordinator/__tests__/wait-with-idle-bound.test.d.ts +2 -0
  64. package/dist/tools/coordinator/__tests__/wait-with-idle-bound.test.d.ts.map +1 -0
  65. package/dist/tools/coordinator/__tests__/wait-with-idle-bound.test.js +193 -0
  66. package/dist/tools/coordinator/__tests__/wait-with-idle-bound.test.js.map +1 -0
  67. package/dist/tools/coordinator/index.d.ts +19 -0
  68. package/dist/tools/coordinator/index.d.ts.map +1 -1
  69. package/dist/tools/coordinator/index.js +82 -60
  70. package/dist/tools/coordinator/index.js.map +1 -1
  71. package/dist/tools/coordinator/wait-with-idle-bound.d.ts +66 -0
  72. package/dist/tools/coordinator/wait-with-idle-bound.d.ts.map +1 -0
  73. package/dist/tools/coordinator/wait-with-idle-bound.js +78 -0
  74. package/dist/tools/coordinator/wait-with-idle-bound.js.map +1 -0
  75. package/dist/types/agent/base.d.ts +16 -0
  76. package/dist/types/agent/base.d.ts.map +1 -1
  77. package/dist/types/agent/gateway.d.ts +23 -0
  78. package/dist/types/agent/gateway.d.ts.map +1 -1
  79. package/dist/types/agent/lifecycle-event.d.ts +9 -1
  80. package/dist/types/agent/lifecycle-event.d.ts.map +1 -1
  81. package/dist/types/agent/task.d.ts +5 -0
  82. package/dist/types/agent/task.d.ts.map +1 -1
  83. package/dist/types/hitl/index.d.ts +10 -0
  84. package/dist/types/hitl/index.d.ts.map +1 -1
  85. package/dist/types/hitl/index.js.map +1 -1
  86. package/dist/types/probe/registry.d.ts +6 -0
  87. package/dist/types/probe/registry.d.ts.map +1 -1
  88. package/dist/types/run/config.d.ts +25 -0
  89. package/dist/types/run/config.d.ts.map +1 -1
  90. package/dist/utils/__tests__/abort-reason.test.d.ts +2 -0
  91. package/dist/utils/__tests__/abort-reason.test.d.ts.map +1 -0
  92. package/dist/utils/__tests__/abort-reason.test.js +48 -0
  93. package/dist/utils/__tests__/abort-reason.test.js.map +1 -0
  94. package/dist/utils/abort.d.ts +26 -0
  95. package/dist/utils/abort.d.ts.map +1 -1
  96. package/dist/utils/abort.js +34 -0
  97. package/dist/utils/abort.js.map +1 -1
  98. package/dist/verification/__tests__/rule-order-and-reason.test.d.ts +2 -0
  99. package/dist/verification/__tests__/rule-order-and-reason.test.d.ts.map +1 -0
  100. package/dist/verification/__tests__/rule-order-and-reason.test.js +126 -0
  101. package/dist/verification/__tests__/rule-order-and-reason.test.js.map +1 -0
  102. package/dist/verification/gate.d.ts +17 -1
  103. package/dist/verification/gate.d.ts.map +1 -1
  104. package/dist/verification/gate.js +58 -2
  105. package/dist/verification/gate.js.map +1 -1
  106. package/dist/verification/index.d.ts +1 -1
  107. package/dist/verification/index.d.ts.map +1 -1
  108. package/dist/verification/index.js +1 -1
  109. package/dist/verification/index.js.map +1 -1
  110. package/package.json +1 -1
  111. package/src/agents/ReactiveAgent.ts +5 -0
  112. package/src/agents/SupervisorAgent.ts +4 -0
  113. package/src/agents/runAgent.ts +22 -1
  114. package/src/connector/mcp/__tests__/positional-arrays.test.ts +183 -0
  115. package/src/connector/mcp/adapter.ts +131 -7
  116. package/src/constants/agent/index.ts +5 -0
  117. package/src/constants/plugin/index.ts +15 -0
  118. package/src/gateway/local.ts +26 -1
  119. package/src/manager/agent/lifecycle.ts +6 -0
  120. package/src/provider/thinking-support.ts +19 -2
  121. package/src/public-runtime.ts +9 -0
  122. package/src/runtime/query/__tests__/context.test.ts +24 -0
  123. package/src/runtime/query/__tests__/effort-reaches-the-wire.test.ts +135 -0
  124. package/src/runtime/query/__tests__/tool-timeout.test.ts +38 -0
  125. package/src/runtime/query/context.ts +16 -1
  126. package/src/runtime/query/executor.ts +11 -1
  127. package/src/runtime/query/iteration/index.ts +5 -0
  128. package/src/telemetry/__tests__/model-call-span.test.ts +22 -5
  129. package/src/telemetry/__tests__/span-closure.test.ts +12 -5
  130. package/src/tools/coordinator/__tests__/wait-with-idle-bound.test.ts +247 -0
  131. package/src/tools/coordinator/index.ts +83 -63
  132. package/src/tools/coordinator/wait-with-idle-bound.ts +142 -0
  133. package/src/types/agent/base.ts +17 -0
  134. package/src/types/agent/gateway.ts +24 -0
  135. package/src/types/agent/lifecycle-event.ts +7 -0
  136. package/src/types/agent/task.ts +5 -0
  137. package/src/types/hitl/index.ts +10 -0
  138. package/src/types/probe/registry.ts +6 -0
  139. package/src/types/run/config.ts +26 -0
  140. package/src/utils/__tests__/abort-reason.test.ts +56 -0
  141. package/src/utils/abort.ts +34 -0
  142. package/src/verification/__tests__/rule-order-and-reason.test.ts +149 -0
  143. package/src/verification/gate.ts +61 -3
  144. package/src/verification/index.ts +1 -1
@@ -1,11 +1,27 @@
1
1
  import type { ToolDefinition } from '../types/tool/index.js';
2
- import type { GateEvaluationResult, VerificationGateConfig } from '../types/verification/index.js';
2
+ import type { GateEvaluationResult, VerificationGateConfig, VerificationRule } from '../types/verification/index.js';
3
3
  import type { Logger } from '../utils/logger.js';
4
4
  export interface ToolCallContext {
5
5
  readonly toolName: string;
6
6
  readonly toolInput: unknown;
7
7
  readonly toolDef: ToolDefinition | undefined;
8
8
  }
9
+ /**
10
+ * What the rule actually said, in words a model can act on.
11
+ *
12
+ * This used to be the rule TYPE and nothing else, so a denial reached the
13
+ * model as "Blocked by the verification gate: Matched rule: deny_by_name" —
14
+ * naming the kind of rule and nothing about it. Not which tool, not which
15
+ * pattern, not whether a different input would fare better.
16
+ *
17
+ * The difference is behavioural rather than cosmetic. Told only that it was
18
+ * denied, a model rewords the same call and tries again, because nothing in
19
+ * the message says a retry is pointless. Told that a pattern rule denies
20
+ * `git push*`, it can stop, say so, and do something else. A refusal that
21
+ * cannot be reasoned about produces thrashing; one that can produces a route
22
+ * around it.
23
+ */
24
+ export declare function describeRule(rule: VerificationRule): string;
9
25
  export declare class VerificationGate {
10
26
  private readonly rules;
11
27
  private readonly compiledPatterns;
@@ -1 +1 @@
1
- {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../src/verification/gate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAC5D,OAAO,KAAK,EACX,oBAAoB,EACpB,sBAAsB,EAEtB,MAAM,gCAAgC,CAAA;AAEvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAGhD,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS,CAAA;CAC5C;AAED,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAC1C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0B;IACnD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;IAC5B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,MAAM,EAAE,sBAAsB,EAAE,GAAG,EAAE,MAAM;IAsDvD,QAAQ,CAAC,GAAG,EAAE,eAAe,GAAG,oBAAoB;CAwDpD"}
1
+ {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../src/verification/gate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAC5D,OAAO,KAAK,EACX,oBAAoB,EACpB,sBAAsB,EACtB,gBAAgB,EAChB,MAAM,gCAAgC,CAAA;AAEvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAGhD,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS,CAAA;CAC5C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAwB3D;AAED,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAC1C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0B;IACnD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;IAC5B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,MAAM,EAAE,sBAAsB,EAAE,GAAG,EAAE,MAAM;IAuEvD,QAAQ,CAAC,GAAG,EAAE,eAAe,GAAG,oBAAoB;CAwDpD"}
@@ -1,6 +1,46 @@
1
1
  import { MAX_CUSTOM_PATTERN_LENGTH } from '../constants/verification/index.js';
2
2
  import { VerificationGateConfigSchema } from '../types/verification/index.js';
3
3
  import { evaluateRule } from './rules.js';
4
+ /**
5
+ * What the rule actually said, in words a model can act on.
6
+ *
7
+ * This used to be the rule TYPE and nothing else, so a denial reached the
8
+ * model as "Blocked by the verification gate: Matched rule: deny_by_name" —
9
+ * naming the kind of rule and nothing about it. Not which tool, not which
10
+ * pattern, not whether a different input would fare better.
11
+ *
12
+ * The difference is behavioural rather than cosmetic. Told only that it was
13
+ * denied, a model rewords the same call and tries again, because nothing in
14
+ * the message says a retry is pointless. Told that a pattern rule denies
15
+ * `git push*`, it can stop, say so, and do something else. A refusal that
16
+ * cannot be reasoned about produces thrashing; one that can produces a route
17
+ * around it.
18
+ */
19
+ export function describeRule(rule) {
20
+ switch (rule.type) {
21
+ case 'deny_dangerous_patterns':
22
+ return 'this matches a pattern the operator refuses outright; rewording it will not help';
23
+ case 'allow_read_only':
24
+ return 'allowed because this tool only observes';
25
+ case 'allow_by_name':
26
+ return `allowed by name (${rule.toolNames.join(', ')})`;
27
+ case 'deny_by_name':
28
+ return `denied by name (${rule.toolNames.join(', ')}) — this tool is refused for this run, so a different input will not change it`;
29
+ case 'allow_by_category':
30
+ return `allowed by category (${rule.categories.join(', ')})`;
31
+ case 'allow_by_tier':
32
+ return `allowed by tier (${rule.tiers.join(', ')})`;
33
+ case 'custom_pattern': {
34
+ const where = rule.target === 'both' ? 'name or arguments' : rule.target;
35
+ const verb = rule.decision === 'deny' ? 'denied' : 'allowed';
36
+ return `${verb} by a pattern rule matching the ${where}: ${rule.pattern}`;
37
+ }
38
+ default: {
39
+ const exhaustive = rule;
40
+ return `matched an unrecognised rule: ${JSON.stringify(exhaustive)}`;
41
+ }
42
+ }
43
+ }
4
44
  export class VerificationGate {
5
45
  rules;
6
46
  compiledPatterns;
@@ -14,13 +54,29 @@ export class VerificationGate {
14
54
  this.logDecisions = parsed.logDecisions;
15
55
  this.enabled = parsed.enabled;
16
56
  const expandedRules = [];
57
+ // Order is the whole meaning of this list, because the first rule to
58
+ // match decides and nothing after it is consulted.
59
+ //
60
+ // The dangerous-pattern denial goes FIRST and stays there: it is the
61
+ // floor, and an operator rule must not be able to open what it closes.
62
+ //
63
+ // The read-only allowance goes LAST, and it used to go second — ahead of
64
+ // the operator's own rules. With first-match-wins that made a rule like
65
+ // "prompt me before every read" UNREACHABLE while allowReadOnlyTools was
66
+ // on: not rejected, not warned about, just never consulted. Someone who
67
+ // writes a rule and is silently ignored gets the worst outcome available
68
+ // — they believe a control is in force and it is not.
69
+ //
70
+ // So it becomes what it always was in substance: a DEFAULT for tools
71
+ // nobody wrote a rule about, rather than an override of the rules they
72
+ // did write. The denial above still outranks both.
17
73
  if (parsed.denyDangerousPatterns) {
18
74
  expandedRules.push({ type: 'deny_dangerous_patterns' });
19
75
  }
76
+ expandedRules.push(...parsed.rules);
20
77
  if (parsed.allowReadOnlyTools) {
21
78
  expandedRules.push({ type: 'allow_read_only' });
22
79
  }
23
- expandedRules.push(...parsed.rules);
24
80
  this.rules = expandedRules;
25
81
  this.compiledPatterns = new Map();
26
82
  this.nameSets = new Map();
@@ -73,7 +129,7 @@ export class VerificationGate {
73
129
  const result = {
74
130
  decision,
75
131
  matchedRule: rule,
76
- reason: `Matched rule: ${rule.type}`,
132
+ reason: describeRule(rule),
77
133
  };
78
134
  if (this.logDecisions) {
79
135
  this.log.debug('Gate decision', {
@@ -1 +1 @@
1
- {"version":3,"file":"gate.js","sourceRoot":"","sources":["../../src/verification/gate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gate.js","sourceRoot":"","sources":["../../src/verification/gate.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- export { VerificationGate, type ToolCallContext } from './gate.js';
1
+ export { VerificationGate, describeRule, type ToolCallContext } from './gate.js';
2
2
  export { defaultSandboxedGateConfig, defaultSandboxedShellGateConfig } from './presets.js';
3
3
  export { evaluateRule } from './rules.js';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/verification/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,WAAW,CAAA;AAClE,OAAO,EAAE,0BAA0B,EAAE,+BAA+B,EAAE,MAAM,cAAc,CAAA;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/verification/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,WAAW,CAAA;AAChF,OAAO,EAAE,0BAA0B,EAAE,+BAA+B,EAAE,MAAM,cAAc,CAAA;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA"}
@@ -1,4 +1,4 @@
1
- export { VerificationGate } from './gate.js';
1
+ export { VerificationGate, describeRule } from './gate.js';
2
2
  export { defaultSandboxedGateConfig, defaultSandboxedShellGateConfig } from './presets.js';
3
3
  export { evaluateRule } from './rules.js';
4
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/verification/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAwB,MAAM,WAAW,CAAA;AAClE,OAAO,EAAE,0BAA0B,EAAE,+BAA+B,EAAE,MAAM,cAAc,CAAA;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/verification/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAwB,MAAM,WAAW,CAAA;AAChF,OAAO,EAAE,0BAA0B,EAAE,+BAA+B,EAAE,MAAM,cAAc,CAAA;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@namzu/sdk",
3
- "version": "6.2.0",
3
+ "version": "7.0.0",
4
4
  "description": "Open-source AI agent SDK with a built-in runtime. Nothing between you and your agents.",
5
5
  "license": "FSL-1.1-MIT",
6
6
  "type": "module",
@@ -110,6 +110,11 @@ export class ReactiveAgent extends AbstractAgent<ReactiveAgentConfig, ReactiveAg
110
110
  costLimitUsd: config.costLimitUsd,
111
111
  permissionMode: config.permissionMode,
112
112
  env: config.env,
113
+ // Hand-listed, so anything not named here is dropped in silence.
114
+ // That is how both of these came to be unreachable from every
115
+ // entry point except the raw kernel one.
116
+ ...(config.thinking ? { thinking: config.thinking } : {}),
117
+ ...(config.effort ? { effort: config.effort } : {}),
113
118
  },
114
119
  agentId: this.metadata.id,
115
120
  agentName: this.metadata.name,
@@ -287,6 +287,10 @@ export class SupervisorAgent extends AbstractAgent<SupervisorAgentConfig, Superv
287
287
  maxIterations: config.maxIterations,
288
288
  temperature: config.temperature,
289
289
  env: config.env,
290
+ // See ReactiveAgent: a hand-listed literal drops what nobody
291
+ // remembered to add, and reports nothing when it does.
292
+ ...(config.thinking ? { thinking: config.thinking } : {}),
293
+ ...(config.effort ? { effort: config.effort } : {}),
290
294
  },
291
295
  questionParks,
292
296
  pendingAnswers,
@@ -2,7 +2,7 @@ import { ToolRegistry } from '../registry/tool/execute.js'
2
2
  import { drainQuery } from '../runtime/query/index.js'
3
3
  import type { ProjectId, SessionId, TenantId, ThreadId } from '../types/ids/index.js'
4
4
  import type { Message } from '../types/message/index.js'
5
- import type { LLMProvider } from '../types/provider/index.js'
5
+ import type { LLMProvider, ReasoningEffort, ThinkingConfig } from '../types/provider/index.js'
6
6
  import type { Run, RunEventListener } from '../types/run/index.js'
7
7
  import type { Skill } from '../types/skills/index.js'
8
8
  import type { ToolRegistryContract } from '../types/tool/index.js'
@@ -83,6 +83,25 @@ export interface RunAgentOptions extends AgentIdentity {
83
83
  timeoutMs?: number
84
84
  temperature?: number
85
85
 
86
+ /**
87
+ * Extended-thinking request and response-effort level, forwarded on every
88
+ * model call.
89
+ *
90
+ * These are here because the run config below is assembled by HAND, and a
91
+ * hand-listed literal silently drops whatever nobody remembered to add —
92
+ * which is precisely what happened. `thinking` shipped on `AgentRunConfig`
93
+ * and was reachable only from the raw kernel entry point, because this
94
+ * function, `ReactiveAgent` and `SupervisorAgent` each rebuilt the object
95
+ * from a fixed list. So the capability existed and the front door could not
96
+ * open it.
97
+ *
98
+ * A live run is what found it: the unit tests passed because they drove the
99
+ * kernel directly, and a real agent run through this function put no effort
100
+ * on the wire at all.
101
+ */
102
+ thinking?: ThinkingConfig
103
+ effort?: ReasoningEffort
104
+
86
105
  /** Names the agent in traces and events. Defaults to `Agent`. */
87
106
  name?: string
88
107
 
@@ -184,6 +203,8 @@ export async function runAgent(options: RunAgentOptions): Promise<RunAgentResult
184
203
  tokenBudget: options.tokenBudget ?? DEFAULT_TOKEN_BUDGET,
185
204
  timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
186
205
  ...(options.temperature !== undefined ? { temperature: options.temperature } : {}),
206
+ ...(options.thinking ? { thinking: options.thinking } : {}),
207
+ ...(options.effort ? { effort: options.effort } : {}),
187
208
  },
188
209
  // One option covers both. `drainQuery` separates the id from the
189
210
  // display name because a fleet needs a stable key and a readable
@@ -0,0 +1,183 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { toSchemaDialect } from '../../../registry/tool/dialect.js'
4
+ import { renderToolSchema } from '../../../registry/tool/schema.js'
5
+ import type { MCPJsonSchema } from '../../../types/connector/index.js'
6
+ import { mcpJsonSchemaToZod } from '../adapter.js'
7
+
8
+ /**
9
+ * A bridged tool's schema makes a round trip — server JSON Schema → Zod →
10
+ * JSON Schema on the wire — so this file asserts what comes out the FAR end,
11
+ * not what the Zod type is. Two failures live at that far end and neither is
12
+ * visible from the Zod side:
13
+ *
14
+ * - whatever the conversion drops is dropped from what the MODEL is shown,
15
+ * and a positional array was being flattened to "an array of anything";
16
+ * - whatever it emits has to be a construct the receiving wire accepts, and
17
+ * a rejected tool schema fails the WHOLE request rather than degrading one
18
+ * tool. So a faithful conversion that cannot be sent is worse than a lossy
19
+ * one that can, which is why the tuple gate is narrow rather than eager.
20
+ */
21
+
22
+ function wire(schema: MCPJsonSchema): Record<string, unknown> {
23
+ const rendered = renderToolSchema(mcpJsonSchemaToZod(schema))
24
+ const properties = toSchemaDialect(rendered, '2020-12').properties as Record<
25
+ string,
26
+ Record<string, unknown>
27
+ >
28
+ return properties.a as Record<string, unknown>
29
+ }
30
+
31
+ const wrap = (a: Record<string, unknown>): MCPJsonSchema =>
32
+ ({ type: 'object', properties: { a }, required: ['a'] }) as unknown as MCPJsonSchema
33
+
34
+ describe('a server that pinned its positions gets a tuple', () => {
35
+ it('carries the draft-07 spelling through to bounded prefixItems', () => {
36
+ expect(
37
+ wire(
38
+ wrap({
39
+ type: 'array',
40
+ items: [{ type: 'string' }, { type: 'number' }],
41
+ additionalItems: false,
42
+ minItems: 2,
43
+ maxItems: 2,
44
+ }),
45
+ ),
46
+ ).toEqual({
47
+ type: 'array',
48
+ minItems: 2,
49
+ maxItems: 2,
50
+ prefixItems: [{ type: 'string' }, { type: 'number' }],
51
+ })
52
+ })
53
+
54
+ it('reaches the identical wire shape from the 2020-12 spelling', () => {
55
+ // The two spellings say the same thing and a server may use either.
56
+ // Converging them is the point: a bridged tool should not be shown
57
+ // differently to the model because of which dialect its author wrote.
58
+ expect(
59
+ wire(
60
+ wrap({
61
+ type: 'array',
62
+ prefixItems: [{ type: 'string' }, { type: 'number' }],
63
+ items: false,
64
+ minItems: 2,
65
+ }),
66
+ ),
67
+ ).toEqual({
68
+ type: 'array',
69
+ minItems: 2,
70
+ maxItems: 2,
71
+ prefixItems: [{ type: 'string' }, { type: 'number' }],
72
+ })
73
+ })
74
+ })
75
+
76
+ describe('a positional array the wire cannot carry keeps its shape in words', () => {
77
+ it('falls back when the server did not pin the length', () => {
78
+ // The inversion worth pinning: positional members do NOT constrain
79
+ // length. With no `minItems` the server is permitting a SHORTER array,
80
+ // and a tuple cannot express that — so an absent lower bound is a
81
+ // reason to fall back rather than a detail to round up.
82
+ const result = wire(
83
+ wrap({
84
+ type: 'array',
85
+ prefixItems: [{ type: 'string' }, { type: 'number' }],
86
+ }),
87
+ )
88
+
89
+ expect(result.prefixItems).toBeUndefined()
90
+ expect(result.description).toContain('[0] string')
91
+ expect(result.description).toContain('[1] number')
92
+ })
93
+
94
+ it('appends the shape to the description rather than replacing it', () => {
95
+ // The case this exists for is a server that documented its argument
96
+ // WELL and used a positional array. Assigning the description would
97
+ // have deleted its sentence to make room for ours.
98
+ const result = wire(
99
+ wrap({
100
+ type: 'array',
101
+ prefixItems: [{ type: 'string' }, { type: 'number' }],
102
+ description: 'A coordinate pair.',
103
+ }),
104
+ )
105
+
106
+ expect(result.description).toContain('A coordinate pair.')
107
+ expect(result.description).toContain('[0] string')
108
+ })
109
+
110
+ it('keeps the bounds the server did state', () => {
111
+ // The fallback is a ZodArray precisely so the ordinary constraint pass
112
+ // still carries `minItems`/`maxItems` onto it.
113
+ const result = wire(
114
+ wrap({ type: 'array', prefixItems: [{ type: 'string' }], minItems: 1, maxItems: 9 }),
115
+ )
116
+
117
+ expect(result.minItems).toBe(1)
118
+ expect(result.maxItems).toBe(9)
119
+ })
120
+
121
+ it('names enums and literals in the description, not just types', () => {
122
+ const result = wire(
123
+ wrap({
124
+ type: 'array',
125
+ prefixItems: [{ enum: ['r', 'w'] }, { const: 7 }],
126
+ }),
127
+ )
128
+
129
+ expect(result.description).toContain('"r"|"w"')
130
+ expect(result.description).toContain('7')
131
+ })
132
+ })
133
+
134
+ describe('an ordinary list is untouched', () => {
135
+ it('still renders as a homogeneous array', () => {
136
+ expect(wire(wrap({ type: 'array', items: { type: 'string' } }))).toEqual({
137
+ type: 'array',
138
+ items: { type: 'string' },
139
+ })
140
+ })
141
+ })
142
+
143
+ describe('a deep schema cannot take the process down', () => {
144
+ // `MAX_CONVERSION_DEPTH` promised in its own comment that a node past the
145
+ // ceiling is "left permissive rather than the process being taken down by
146
+ // a stack overflow". That was false for arrays and for unions: the counter
147
+ // was never passed down the array path, and even where it WAS passed
148
+ // correctly — the union path — nothing compared it to anything, because
149
+ // the only comparison lived in the object branch a pure array or union
150
+ // never reaches. A remote server's tool listing is untrusted input, so
151
+ // this was reachable denial of service.
152
+ const nestArrays = (depth: number): MCPJsonSchema => {
153
+ let inner: Record<string, unknown> = { type: 'string' }
154
+ for (let i = 0; i < depth; i += 1) inner = { type: 'array', items: inner }
155
+ return wrap(inner)
156
+ }
157
+
158
+ const nestUnions = (depth: number): MCPJsonSchema => {
159
+ let inner: Record<string, unknown> = { type: 'string' }
160
+ for (let i = 0; i < depth; i += 1) inner = { anyOf: [inner] }
161
+ return wrap(inner)
162
+ }
163
+
164
+ it('survives a deeply nested array', () => {
165
+ expect(() => mcpJsonSchemaToZod(nestArrays(5_000))).not.toThrow()
166
+ })
167
+
168
+ it('survives a deeply nested union', () => {
169
+ expect(() => mcpJsonSchemaToZod(nestUnions(5_000))).not.toThrow()
170
+ })
171
+
172
+ it('still converts a shallow schema faithfully', () => {
173
+ // The guard must not be so eager that it flattens ordinary nesting.
174
+ const result = wire(
175
+ wrap({ type: 'array', items: { type: 'array', items: { type: 'number' } } }),
176
+ )
177
+
178
+ expect(result).toEqual({
179
+ type: 'array',
180
+ items: { type: 'array', items: { type: 'number' } },
181
+ })
182
+ })
183
+ })
@@ -93,9 +93,20 @@ function jsonSchemaPropertyToZod(prop: unknown, depth = 0): z.ZodType {
93
93
  base = base.nullable()
94
94
  }
95
95
 
96
- const description = schema.description
97
- if (typeof description === 'string' && description.length > 0) {
98
- base = base.describe(description)
96
+ // Appended, not assigned. The conversion itself can produce a description
97
+ // a positional array that could not be expressed as a tuple carries its
98
+ // shape here, because that prose is the only place the model learns it —
99
+ // and `.describe()` REPLACES. Overwriting would have silently deleted the
100
+ // note in exactly the case it exists for: a server that documented its
101
+ // argument well AND used a positional array.
102
+ const carried = base.description
103
+ const declared = schema.description
104
+ const parts = [
105
+ typeof declared === 'string' && declared.length > 0 ? declared : undefined,
106
+ carried,
107
+ ].filter((part): part is string => typeof part === 'string' && part.length > 0)
108
+ if (parts.length > 0) {
109
+ base = base.describe(parts.join(' '))
99
110
  }
100
111
 
101
112
  if (schema.default !== undefined) {
@@ -195,6 +206,12 @@ function baseTypeToZod(schema: Record<string, unknown>, depth = 0): z.ZodType {
195
206
 
196
207
  const composite = (schema.anyOf ?? schema.oneOf) as unknown[] | undefined
197
208
  if (Array.isArray(composite) && composite.length > 0) {
209
+ // The ceiling has to be CHECKED here, not merely counted. `depth` was
210
+ // threaded correctly through this branch from the start, and a
211
+ // 5000-deep union still overflowed the stack — because the only
212
+ // comparison against `MAX_CONVERSION_DEPTH` lived in `objectToZod`,
213
+ // which a pure union never reaches.
214
+ if (depth >= MAX_CONVERSION_DEPTH) return z.unknown()
198
215
  const members = composite.map((member) => jsonSchemaPropertyToZod(member, depth + 1))
199
216
  return members.length === 1
200
217
  ? (members[0] as z.ZodType)
@@ -229,11 +246,37 @@ function baseTypeToZod(schema: Record<string, unknown>, depth = 0): z.ZodType {
229
246
  case 'null':
230
247
  return z.null()
231
248
  case 'array': {
249
+ // Both the counter and the check. `depth` was never passed to the
250
+ // element conversion below, so the counter reset to zero on every
251
+ // array level — and even threaded it would not have helped, since
252
+ // nothing on this path compared it to anything. Measured before
253
+ // and after: a 5000-deep array schema took the process down with a
254
+ // stack overflow, which is a denial of service reachable from a
255
+ // remote server's tool listing.
256
+ if (depth >= MAX_CONVERSION_DEPTH) return z.array(z.unknown())
257
+
258
+ // A positional array has two spellings and a server may use
259
+ // either: draft-07 puts the member schemas in `items` with the
260
+ // tail rule in `additionalItems`, 2020-12 moved them to
261
+ // `prefixItems` with the tail rule in `items`.
262
+ const positional = Array.isArray(schema.prefixItems)
263
+ ? (schema.prefixItems as unknown[])
264
+ : Array.isArray(schema.items)
265
+ ? (schema.items as unknown[])
266
+ : undefined
267
+ if (positional) return positionalToZod(positional, schema, depth)
268
+
232
269
  const items = schema.items
233
- // A tuple (`items` as an array) is rare in tool schemas; treat it
234
- // as a heterogeneous list rather than pretending to model it.
235
- if (Array.isArray(items)) return z.array(z.unknown())
236
- return z.array(items === undefined ? z.unknown() : jsonSchemaPropertyToZod(items))
270
+ return z.array(
271
+ // A boolean `items` is a tail RULE, not an element schema it
272
+ // only has meaning next to `prefixItems`, which was handled
273
+ // above. Reaching it here means the server closed an array
274
+ // that has no positions, and an unconstrained element type is
275
+ // the permissive reading of that.
276
+ items === undefined || typeof items === 'boolean'
277
+ ? z.unknown()
278
+ : jsonSchemaPropertyToZod(items, depth + 1),
279
+ )
237
280
  }
238
281
  case 'object':
239
282
  return objectToZod(schema, depth)
@@ -242,6 +285,87 @@ function baseTypeToZod(schema: Record<string, unknown>, depth = 0): z.ZodType {
242
285
  }
243
286
  }
244
287
 
288
+ /**
289
+ * How many positions we will express as a tuple.
290
+ *
291
+ * Not a correctness bound — a server may pin any arity it likes. It is a
292
+ * prompt-cost bound: every member renders its own schema into the tool
293
+ * definition the model is shown, and past a couple of dozen positions the
294
+ * thing being described is a data payload rather than a call signature. Past
295
+ * the cap the shape still reaches the model, in the description.
296
+ */
297
+ const MAX_TUPLE_ARITY = 32
298
+
299
+ /**
300
+ * A positional array: a tuple when the server pinned it, a described list
301
+ * otherwise.
302
+ *
303
+ * This used to be `z.array(z.unknown())` unconditionally, so a server that
304
+ * spelled out `[string, number]` had the model told "an array of anything" —
305
+ * the positions, their types and their order all dropped from what the model
306
+ * reads, not merely from what is validated locally.
307
+ *
308
+ * The reason it is not simply converted is that the schema makes a ROUND TRIP:
309
+ * server JSON Schema → Zod → JSON Schema on the wire. So whatever is emitted
310
+ * here has to be a construct the receiving wire accepts, and a construct it
311
+ * rejects fails the ENTIRE request rather than degrading one tool — taking
312
+ * down every run that offered the toolset. A faithful conversion that cannot
313
+ * be sent is strictly worse than a lossy one that can.
314
+ *
315
+ * Hence the narrow gate. A tuple is emitted only where the server itself
316
+ * pinned the arity and closed the tail, because that renders as bounded
317
+ * `prefixItems` — the one positional shape measured as accepted, and the same
318
+ * shape a first-party builtin already ships. Everything else keeps the
319
+ * permissive array and gains the positional shape in its description.
320
+ *
321
+ * The subtlety worth stating, because it inverts the intuition: positional
322
+ * `items`/`prefixItems` does not constrain LENGTH. Without `minItems` the
323
+ * server is permitting a SHORTER array, and a tuple cannot express that — so
324
+ * an absent lower bound is a reason to fall back, not a detail to round up.
325
+ */
326
+ function positionalToZod(
327
+ positional: readonly unknown[],
328
+ schema: Record<string, unknown>,
329
+ depth: number,
330
+ ): z.ZodType {
331
+ // draft-07 spells the tail rule `additionalItems`; 2020-12 spells it
332
+ // `items`, which is only a tail rule when `prefixItems` holds the members.
333
+ const tail = Array.isArray(schema.items) ? schema.additionalItems : schema.items
334
+
335
+ const arity = positional.length
336
+ const pinnedLow = num(schema.minItems) === arity
337
+ const closedHigh = tail === false || num(schema.maxItems) === arity
338
+
339
+ if (arity === 0 || arity > MAX_TUPLE_ARITY || !pinnedLow || !closedHigh) {
340
+ // A ZodArray, deliberately: `applyConstraints` then carries the
341
+ // server's own `minItems`/`maxItems` onto it, so the loose case keeps
342
+ // whatever bounds the server did state.
343
+ return z.array(z.unknown()).describe(describePositional(positional))
344
+ }
345
+
346
+ const members = positional.map((member) => jsonSchemaPropertyToZod(member, depth + 1))
347
+ // Never `.rest()`. It renders a tail schema this wire has not been measured
348
+ // against, and the gate above has already established there is no tail.
349
+ return z.tuple(members as [z.ZodType, ...z.ZodType[]])
350
+ }
351
+
352
+ /** The positional shape in prose, for the cases a tuple cannot carry. */
353
+ function describePositional(positional: readonly unknown[]): string {
354
+ const shape = positional
355
+ .map((member, index) => `[${index}] ${positionalTypeName(member)}`)
356
+ .join(', ')
357
+ return `Positional array — ${shape}.`
358
+ }
359
+
360
+ function positionalTypeName(member: unknown): string {
361
+ if (typeof member !== 'object' || member === null) return 'any'
362
+ const schema = member as Record<string, unknown>
363
+ if (schema.const !== undefined) return JSON.stringify(schema.const)
364
+ if (Array.isArray(schema.enum)) return schema.enum.map((v) => JSON.stringify(v)).join('|')
365
+ const type = Array.isArray(schema.type) ? schema.type.join('|') : schema.type
366
+ return typeof type === 'string' ? type : 'any'
367
+ }
368
+
245
369
  /**
246
370
  * Flatten `allOf` members into one object schema.
247
371
  *
@@ -1,6 +1,11 @@
1
1
  import type { AgentCapabilities } from '../../types/agent/base.js'
2
2
  import type { AgentManagerConfig } from '../../types/agent/task.js'
3
3
 
4
+ /**
5
+ * **Nothing reads this.** No activity list is trimmed to it anywhere.
6
+ *
7
+ * @deprecated Unused. Removed in the next major.
8
+ */
4
9
  export const MAX_RECENT_ACTIVITIES = 5
5
10
 
6
11
  export const AGENT_MANAGER_DEFAULTS: Readonly<AgentManagerConfig> = {
@@ -44,4 +44,19 @@ export const HOOK_TIMEOUT_MS = 5_000
44
44
  * can sort itself after.
45
45
  */
46
46
  export const DEFAULT_HOOK_PRIORITY = 100
47
+ /**
48
+ * **Nothing reads this.** Hooks run sequentially and always have.
49
+ *
50
+ * It reads as a concurrency cap that is in force, which is the misleading
51
+ * kind of dead: a reviewer sees a bound, assumes hook execution is batched
52
+ * at ten, and reasons about plugin behaviour that does not exist.
53
+ *
54
+ * Kept for one release rather than deleted, because it is reachable from the
55
+ * published typings. Do not "make it work" by batching hook execution —
56
+ * ordering is the contract hooks are written against, and parallelising them
57
+ * to justify a constant would change behaviour to match a number nobody
58
+ * chose deliberately.
59
+ *
60
+ * @deprecated Unused. Removed in the next major.
61
+ */
47
62
  export const HOOK_MAX_CONCURRENT = 10