@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.
- package/CHANGELOG.md +240 -0
- package/dist/agents/ReactiveAgent.d.ts.map +1 -1
- package/dist/agents/ReactiveAgent.js +5 -0
- package/dist/agents/ReactiveAgent.js.map +1 -1
- package/dist/agents/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/SupervisorAgent.js +4 -0
- package/dist/agents/SupervisorAgent.js.map +1 -1
- package/dist/agents/runAgent.d.ts +19 -1
- package/dist/agents/runAgent.d.ts.map +1 -1
- package/dist/agents/runAgent.js +2 -0
- package/dist/agents/runAgent.js.map +1 -1
- package/dist/connector/mcp/__tests__/positional-arrays.test.d.ts +2 -0
- package/dist/connector/mcp/__tests__/positional-arrays.test.d.ts.map +1 -0
- package/dist/connector/mcp/__tests__/positional-arrays.test.js +142 -0
- package/dist/connector/mcp/__tests__/positional-arrays.test.js.map +1 -0
- package/dist/connector/mcp/adapter.d.ts.map +1 -1
- package/dist/connector/mcp/adapter.js +123 -8
- package/dist/connector/mcp/adapter.js.map +1 -1
- package/dist/constants/agent/index.d.ts +5 -0
- package/dist/constants/agent/index.d.ts.map +1 -1
- package/dist/constants/agent/index.js +5 -0
- package/dist/constants/agent/index.js.map +1 -1
- package/dist/constants/plugin/index.d.ts +15 -0
- package/dist/constants/plugin/index.d.ts.map +1 -1
- package/dist/constants/plugin/index.js +15 -0
- package/dist/constants/plugin/index.js.map +1 -1
- package/dist/gateway/local.d.ts +11 -0
- package/dist/gateway/local.d.ts.map +1 -1
- package/dist/gateway/local.js +27 -1
- package/dist/gateway/local.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +6 -0
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/provider/thinking-support.d.ts +2 -1
- package/dist/provider/thinking-support.d.ts.map +1 -1
- package/dist/provider/thinking-support.js +14 -0
- package/dist/provider/thinking-support.js.map +1 -1
- package/dist/public-runtime.d.ts +1 -1
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +9 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/runtime/query/__tests__/context.test.js +18 -0
- package/dist/runtime/query/__tests__/context.test.js.map +1 -1
- package/dist/runtime/query/__tests__/effort-reaches-the-wire.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/effort-reaches-the-wire.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/effort-reaches-the-wire.test.js +118 -0
- package/dist/runtime/query/__tests__/effort-reaches-the-wire.test.js.map +1 -0
- package/dist/runtime/query/__tests__/tool-timeout.test.js +34 -0
- package/dist/runtime/query/__tests__/tool-timeout.test.js.map +1 -1
- package/dist/runtime/query/context.d.ts.map +1 -1
- package/dist/runtime/query/context.js +16 -1
- package/dist/runtime/query/context.js.map +1 -1
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +11 -1
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +5 -0
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/telemetry/__tests__/model-call-span.test.js +22 -4
- package/dist/telemetry/__tests__/model-call-span.test.js.map +1 -1
- package/dist/telemetry/__tests__/span-closure.test.js +12 -5
- package/dist/telemetry/__tests__/span-closure.test.js.map +1 -1
- package/dist/tools/coordinator/__tests__/wait-with-idle-bound.test.d.ts +2 -0
- package/dist/tools/coordinator/__tests__/wait-with-idle-bound.test.d.ts.map +1 -0
- package/dist/tools/coordinator/__tests__/wait-with-idle-bound.test.js +193 -0
- package/dist/tools/coordinator/__tests__/wait-with-idle-bound.test.js.map +1 -0
- package/dist/tools/coordinator/index.d.ts +19 -0
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +82 -60
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/tools/coordinator/wait-with-idle-bound.d.ts +66 -0
- package/dist/tools/coordinator/wait-with-idle-bound.d.ts.map +1 -0
- package/dist/tools/coordinator/wait-with-idle-bound.js +78 -0
- package/dist/tools/coordinator/wait-with-idle-bound.js.map +1 -0
- package/dist/types/agent/base.d.ts +16 -0
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/agent/gateway.d.ts +23 -0
- package/dist/types/agent/gateway.d.ts.map +1 -1
- package/dist/types/agent/lifecycle-event.d.ts +9 -1
- package/dist/types/agent/lifecycle-event.d.ts.map +1 -1
- package/dist/types/agent/task.d.ts +5 -0
- package/dist/types/agent/task.d.ts.map +1 -1
- package/dist/types/hitl/index.d.ts +10 -0
- package/dist/types/hitl/index.d.ts.map +1 -1
- package/dist/types/hitl/index.js.map +1 -1
- package/dist/types/probe/registry.d.ts +6 -0
- package/dist/types/probe/registry.d.ts.map +1 -1
- package/dist/types/run/config.d.ts +25 -0
- package/dist/types/run/config.d.ts.map +1 -1
- package/dist/utils/__tests__/abort-reason.test.d.ts +2 -0
- package/dist/utils/__tests__/abort-reason.test.d.ts.map +1 -0
- package/dist/utils/__tests__/abort-reason.test.js +48 -0
- package/dist/utils/__tests__/abort-reason.test.js.map +1 -0
- package/dist/utils/abort.d.ts +26 -0
- package/dist/utils/abort.d.ts.map +1 -1
- package/dist/utils/abort.js +34 -0
- package/dist/utils/abort.js.map +1 -1
- package/dist/verification/__tests__/rule-order-and-reason.test.d.ts +2 -0
- package/dist/verification/__tests__/rule-order-and-reason.test.d.ts.map +1 -0
- package/dist/verification/__tests__/rule-order-and-reason.test.js +126 -0
- package/dist/verification/__tests__/rule-order-and-reason.test.js.map +1 -0
- package/dist/verification/gate.d.ts +17 -1
- package/dist/verification/gate.d.ts.map +1 -1
- package/dist/verification/gate.js +58 -2
- package/dist/verification/gate.js.map +1 -1
- package/dist/verification/index.d.ts +1 -1
- package/dist/verification/index.d.ts.map +1 -1
- package/dist/verification/index.js +1 -1
- package/dist/verification/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/ReactiveAgent.ts +5 -0
- package/src/agents/SupervisorAgent.ts +4 -0
- package/src/agents/runAgent.ts +22 -1
- package/src/connector/mcp/__tests__/positional-arrays.test.ts +183 -0
- package/src/connector/mcp/adapter.ts +131 -7
- package/src/constants/agent/index.ts +5 -0
- package/src/constants/plugin/index.ts +15 -0
- package/src/gateway/local.ts +26 -1
- package/src/manager/agent/lifecycle.ts +6 -0
- package/src/provider/thinking-support.ts +19 -2
- package/src/public-runtime.ts +9 -0
- package/src/runtime/query/__tests__/context.test.ts +24 -0
- package/src/runtime/query/__tests__/effort-reaches-the-wire.test.ts +135 -0
- package/src/runtime/query/__tests__/tool-timeout.test.ts +38 -0
- package/src/runtime/query/context.ts +16 -1
- package/src/runtime/query/executor.ts +11 -1
- package/src/runtime/query/iteration/index.ts +5 -0
- package/src/telemetry/__tests__/model-call-span.test.ts +22 -5
- package/src/telemetry/__tests__/span-closure.test.ts +12 -5
- package/src/tools/coordinator/__tests__/wait-with-idle-bound.test.ts +247 -0
- package/src/tools/coordinator/index.ts +83 -63
- package/src/tools/coordinator/wait-with-idle-bound.ts +142 -0
- package/src/types/agent/base.ts +17 -0
- package/src/types/agent/gateway.ts +24 -0
- package/src/types/agent/lifecycle-event.ts +7 -0
- package/src/types/agent/task.ts +5 -0
- package/src/types/hitl/index.ts +10 -0
- package/src/types/probe/registry.ts +6 -0
- package/src/types/run/config.ts +26 -0
- package/src/utils/__tests__/abort-reason.test.ts +56 -0
- package/src/utils/abort.ts +34 -0
- package/src/verification/__tests__/rule-order-and-reason.test.ts +149 -0
- package/src/verification/gate.ts +61 -3
- 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,
|
|
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:
|
|
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":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,oCAAoC,CAAA;AAO9E,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAA;AAE7E,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAQzC,MAAM,OAAO,gBAAgB;IACX,KAAK,CAAoB;IACzB,gBAAgB,CAAqB;IACrC,QAAQ,CAA0B;IAClC,GAAG,CAAQ;IACX,YAAY,CAAS;IACrB,OAAO,CAAS;IAEjC,YAAY,MAA8B,EAAE,GAAW;QACtD,MAAM,MAAM,GAAG,4BAA4B,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QACzD,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAA;QACvD,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,YAAY,CAAA;QACvC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAA;QAE7B,MAAM,aAAa,GAAuB,EAAE,CAAA;QAE5C,IAAI,MAAM,CAAC,qBAAqB,EAAE,CAAC;YAClC,aAAa,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,yBAAyB,EAAE,CAAC,CAAA;QACxD,CAAC;
|
|
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;
|
|
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;
|
|
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
|
@@ -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,
|
package/src/agents/runAgent.ts
CHANGED
|
@@ -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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|