@namzu/sdk 3.1.0 → 3.3.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 +221 -0
- package/dist/advisory/__tests__/consultation-context.test.d.ts +2 -0
- package/dist/advisory/__tests__/consultation-context.test.d.ts.map +1 -0
- package/dist/advisory/__tests__/consultation-context.test.js +124 -0
- package/dist/advisory/__tests__/consultation-context.test.js.map +1 -0
- package/dist/advisory/context.d.ts +25 -0
- package/dist/advisory/context.d.ts.map +1 -1
- package/dist/advisory/context.js +18 -0
- package/dist/advisory/context.js.map +1 -1
- package/dist/advisory/executor.d.ts.map +1 -1
- package/dist/advisory/executor.js +25 -3
- package/dist/advisory/executor.js.map +1 -1
- package/dist/agents/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/SupervisorAgent.js +6 -0
- package/dist/agents/SupervisorAgent.js.map +1 -1
- package/dist/agents/__tests__/supervisor-hands-down-hitl.test.d.ts +2 -0
- package/dist/agents/__tests__/supervisor-hands-down-hitl.test.d.ts.map +1 -0
- package/dist/agents/__tests__/supervisor-hands-down-hitl.test.js +112 -0
- package/dist/agents/__tests__/supervisor-hands-down-hitl.test.js.map +1 -0
- package/dist/compaction/__tests__/context-reducer.test.d.ts +2 -0
- package/dist/compaction/__tests__/context-reducer.test.d.ts.map +1 -0
- package/dist/compaction/__tests__/context-reducer.test.js +197 -0
- package/dist/compaction/__tests__/context-reducer.test.js.map +1 -0
- package/dist/compaction/factory.d.ts +7 -0
- package/dist/compaction/factory.d.ts.map +1 -1
- package/dist/compaction/factory.js +7 -0
- package/dist/compaction/factory.js.map +1 -1
- package/dist/compaction/index.d.ts +2 -0
- package/dist/compaction/index.d.ts.map +1 -1
- package/dist/compaction/index.js +1 -0
- package/dist/compaction/index.js.map +1 -1
- package/dist/compaction/interface.d.ts +13 -0
- package/dist/compaction/interface.d.ts.map +1 -1
- package/dist/compaction/managers/null.d.ts +3 -0
- package/dist/compaction/managers/null.d.ts.map +1 -1
- package/dist/compaction/managers/null.js +3 -0
- package/dist/compaction/managers/null.js.map +1 -1
- package/dist/compaction/managers/slidingWindow.d.ts +6 -0
- package/dist/compaction/managers/slidingWindow.d.ts.map +1 -1
- package/dist/compaction/managers/slidingWindow.js +6 -0
- package/dist/compaction/managers/slidingWindow.js.map +1 -1
- package/dist/compaction/managers/structured.d.ts +10 -0
- package/dist/compaction/managers/structured.d.ts.map +1 -1
- package/dist/compaction/managers/structured.js +10 -0
- package/dist/compaction/managers/structured.js.map +1 -1
- package/dist/compaction/reducer.d.ts +86 -0
- package/dist/compaction/reducer.d.ts.map +1 -0
- package/dist/compaction/reducer.js +77 -0
- package/dist/compaction/reducer.js.map +1 -0
- package/dist/connector/builtins/__tests__/oauth2-auth.test.d.ts +2 -0
- package/dist/connector/builtins/__tests__/oauth2-auth.test.d.ts.map +1 -0
- package/dist/connector/builtins/__tests__/oauth2-auth.test.js +54 -0
- package/dist/connector/builtins/__tests__/oauth2-auth.test.js.map +1 -0
- package/dist/connector/builtins/http.d.ts.map +1 -1
- package/dist/connector/builtins/http.js +24 -2
- package/dist/connector/builtins/http.js.map +1 -1
- package/dist/connector/builtins/http.test.js +18 -2
- package/dist/connector/builtins/http.test.js.map +1 -1
- package/dist/connector/index.d.ts +3 -2
- package/dist/connector/index.d.ts.map +1 -1
- package/dist/connector/index.js +2 -1
- package/dist/connector/index.js.map +1 -1
- package/dist/connector/mcp/__tests__/prompt-as-tool.test.d.ts +2 -0
- package/dist/connector/mcp/__tests__/prompt-as-tool.test.d.ts.map +1 -0
- package/dist/connector/mcp/__tests__/prompt-as-tool.test.js +122 -0
- package/dist/connector/mcp/__tests__/prompt-as-tool.test.js.map +1 -0
- package/dist/connector/mcp/__tests__/prompts-and-lifecycle.test.d.ts +2 -0
- package/dist/connector/mcp/__tests__/prompts-and-lifecycle.test.d.ts.map +1 -0
- package/dist/connector/mcp/__tests__/prompts-and-lifecycle.test.js +214 -0
- package/dist/connector/mcp/__tests__/prompts-and-lifecycle.test.js.map +1 -0
- package/dist/connector/mcp/client.d.ts +52 -1
- package/dist/connector/mcp/client.d.ts.map +1 -1
- package/dist/connector/mcp/client.js +86 -0
- package/dist/connector/mcp/client.js.map +1 -1
- package/dist/connector/mcp/discovery.d.ts +27 -2
- package/dist/connector/mcp/discovery.d.ts.map +1 -1
- package/dist/connector/mcp/discovery.js +58 -5
- package/dist/connector/mcp/discovery.js.map +1 -1
- package/dist/connector/mcp/index.d.ts +4 -3
- package/dist/connector/mcp/index.d.ts.map +1 -1
- package/dist/connector/mcp/index.js +3 -2
- package/dist/connector/mcp/index.js.map +1 -1
- package/dist/connector/mcp/policy.d.ts +17 -0
- package/dist/connector/mcp/policy.d.ts.map +1 -1
- package/dist/connector/mcp/policy.js +18 -7
- package/dist/connector/mcp/policy.js.map +1 -1
- package/dist/connector/mcp/prompt-adapter.d.ts +39 -0
- package/dist/connector/mcp/prompt-adapter.d.ts.map +1 -0
- package/dist/connector/mcp/prompt-adapter.js +113 -0
- package/dist/connector/mcp/prompt-adapter.js.map +1 -0
- package/dist/connector/mcp/server.d.ts +42 -1
- package/dist/connector/mcp/server.d.ts.map +1 -1
- package/dist/connector/mcp/server.js +77 -4
- package/dist/connector/mcp/server.js.map +1 -1
- package/dist/manager/agent/__tests__/depth-limit-authority.test.d.ts +2 -0
- package/dist/manager/agent/__tests__/depth-limit-authority.test.d.ts.map +1 -0
- package/dist/manager/agent/__tests__/depth-limit-authority.test.js +58 -0
- package/dist/manager/agent/__tests__/depth-limit-authority.test.js.map +1 -0
- package/dist/manager/agent/__tests__/hitl-across-spawn.test.d.ts +2 -0
- package/dist/manager/agent/__tests__/hitl-across-spawn.test.d.ts.map +1 -0
- package/dist/manager/agent/__tests__/hitl-across-spawn.test.js +226 -0
- package/dist/manager/agent/__tests__/hitl-across-spawn.test.js.map +1 -0
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +13 -0
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/plugin/__tests__/discovery-scopes.test.d.ts +2 -0
- package/dist/plugin/__tests__/discovery-scopes.test.d.ts.map +1 -0
- package/dist/plugin/__tests__/discovery-scopes.test.js +97 -0
- package/dist/plugin/__tests__/discovery-scopes.test.js.map +1 -0
- package/dist/plugin/__tests__/enable-contributions.test.js +5 -1
- package/dist/plugin/__tests__/enable-contributions.test.js.map +1 -1
- package/dist/plugin/__tests__/mcp-admission.test.d.ts +2 -0
- package/dist/plugin/__tests__/mcp-admission.test.d.ts.map +1 -0
- package/dist/plugin/__tests__/mcp-admission.test.js +227 -0
- package/dist/plugin/__tests__/mcp-admission.test.js.map +1 -0
- package/dist/plugin/lifecycle.d.ts +41 -0
- package/dist/plugin/lifecycle.d.ts.map +1 -1
- package/dist/plugin/lifecycle.js +39 -1
- package/dist/plugin/lifecycle.js.map +1 -1
- package/dist/plugin/loader.d.ts +39 -3
- package/dist/plugin/loader.d.ts.map +1 -1
- package/dist/plugin/loader.js +37 -4
- package/dist/plugin/loader.js.map +1 -1
- package/dist/public-runtime.d.ts +4 -2
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +5 -2
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-types.d.ts +2 -2
- package/dist/public-types.d.ts.map +1 -1
- package/dist/rag/__tests__/namespace-isolation.test.d.ts +2 -0
- package/dist/rag/__tests__/namespace-isolation.test.d.ts.map +1 -0
- package/dist/rag/__tests__/namespace-isolation.test.js +80 -0
- package/dist/rag/__tests__/namespace-isolation.test.js.map +1 -0
- package/dist/rag/ingestion.d.ts.map +1 -1
- package/dist/rag/ingestion.js +1 -0
- package/dist/rag/ingestion.js.map +1 -1
- package/dist/rag/retriever.d.ts.map +1 -1
- package/dist/rag/retriever.js +2 -0
- package/dist/rag/retriever.js.map +1 -1
- package/dist/rag/vector-store.d.ts.map +1 -1
- package/dist/rag/vector-store.js +6 -0
- package/dist/rag/vector-store.js.map +1 -1
- package/dist/registry/tool/execute.d.ts.map +1 -1
- package/dist/registry/tool/execute.js +113 -109
- package/dist/registry/tool/execute.js.map +1 -1
- package/dist/runtime/query/__tests__/per-step-skills.test.d.ts +10 -0
- package/dist/runtime/query/__tests__/per-step-skills.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/per-step-skills.test.js +122 -0
- package/dist/runtime/query/__tests__/per-step-skills.test.js.map +1 -0
- package/dist/runtime/query/__tests__/per-step-tool-choice.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/per-step-tool-choice.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/per-step-tool-choice.test.js +153 -0
- package/dist/runtime/query/__tests__/per-step-tool-choice.test.js.map +1 -0
- package/dist/runtime/query/__tests__/resume-run.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/resume-run.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/resume-run.test.js +211 -0
- package/dist/runtime/query/__tests__/resume-run.test.js.map +1 -0
- package/dist/runtime/query/index.d.ts +10 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +26 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +50 -33
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction-model-routing.test.d.ts +2 -0
- package/dist/runtime/query/iteration/phases/compaction-model-routing.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/compaction-model-routing.test.js +96 -0
- package/dist/runtime/query/iteration/phases/compaction-model-routing.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +95 -5
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context-reducer-dispatch.test.d.ts +2 -0
- package/dist/runtime/query/iteration/phases/context-reducer-dispatch.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/context-reducer-dispatch.test.js +180 -0
- package/dist/runtime/query/iteration/phases/context-reducer-dispatch.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/context.d.ts +9 -0
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/resume-run.d.ts +70 -0
- package/dist/runtime/query/resume-run.d.ts.map +1 -0
- package/dist/runtime/query/resume-run.js +46 -0
- package/dist/runtime/query/resume-run.js.map +1 -0
- package/dist/telemetry/__tests__/model-call-span.test.d.ts +2 -0
- package/dist/telemetry/__tests__/model-call-span.test.d.ts.map +1 -0
- package/dist/telemetry/__tests__/model-call-span.test.js +152 -0
- package/dist/telemetry/__tests__/model-call-span.test.js.map +1 -0
- package/dist/telemetry/__tests__/span-closure.test.d.ts +2 -0
- package/dist/telemetry/__tests__/span-closure.test.d.ts.map +1 -0
- package/dist/telemetry/__tests__/span-closure.test.js +124 -0
- package/dist/telemetry/__tests__/span-closure.test.js.map +1 -0
- package/dist/tools/advisory/index.js +1 -1
- package/dist/tools/advisory/index.js.map +1 -1
- package/dist/tools/coordinator/__tests__/plan-dependencies.test.d.ts +2 -0
- package/dist/tools/coordinator/__tests__/plan-dependencies.test.d.ts.map +1 -0
- package/dist/tools/coordinator/__tests__/plan-dependencies.test.js +126 -0
- package/dist/tools/coordinator/__tests__/plan-dependencies.test.js.map +1 -0
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +13 -1
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/tools/coordinator/plan-dependencies.d.ts +43 -0
- package/dist/tools/coordinator/plan-dependencies.d.ts.map +1 -0
- package/dist/tools/coordinator/plan-dependencies.js +148 -0
- package/dist/tools/coordinator/plan-dependencies.js.map +1 -0
- package/dist/types/agent/base.d.ts +26 -0
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/agent/supervisor.d.ts +15 -0
- package/dist/types/agent/supervisor.d.ts.map +1 -1
- package/dist/types/agent/task.d.ts +17 -0
- package/dist/types/agent/task.d.ts.map +1 -1
- package/dist/types/agent/task.js.map +1 -1
- package/dist/types/connector/core.d.ts +34 -0
- package/dist/types/connector/core.d.ts.map +1 -1
- package/dist/types/connector/definition.d.ts +10 -0
- package/dist/types/connector/definition.d.ts.map +1 -1
- package/dist/types/connector/mcp.d.ts +13 -0
- package/dist/types/connector/mcp.d.ts.map +1 -1
- package/dist/types/rag/retrieval.d.ts +16 -0
- package/dist/types/rag/retrieval.d.ts.map +1 -1
- package/dist/types/rag/storage.d.ts +9 -0
- package/dist/types/rag/storage.d.ts.map +1 -1
- package/dist/types/rag/vector.d.ts +11 -0
- package/dist/types/rag/vector.d.ts.map +1 -1
- package/dist/types/router/task-router.d.ts +19 -0
- package/dist/types/router/task-router.d.ts.map +1 -1
- package/dist/types/run/prepare-step.d.ts +56 -3
- package/dist/types/run/prepare-step.d.ts.map +1 -1
- package/dist/types/toolset/index.d.ts +22 -0
- package/dist/types/toolset/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/advisory/__tests__/consultation-context.test.ts +191 -0
- package/src/advisory/context.ts +32 -0
- package/src/advisory/executor.ts +30 -3
- package/src/agents/SupervisorAgent.ts +6 -0
- package/src/agents/__tests__/supervisor-hands-down-hitl.test.ts +131 -0
- package/src/compaction/__tests__/context-reducer.test.ts +239 -0
- package/src/compaction/factory.ts +7 -0
- package/src/compaction/index.ts +8 -0
- package/src/compaction/interface.ts +13 -0
- package/src/compaction/managers/null.ts +3 -0
- package/src/compaction/managers/slidingWindow.ts +6 -0
- package/src/compaction/managers/structured.ts +10 -0
- package/src/compaction/reducer.ts +154 -0
- package/src/connector/builtins/__tests__/oauth2-auth.test.ts +73 -0
- package/src/connector/builtins/http.test.ts +28 -2
- package/src/connector/builtins/http.ts +26 -2
- package/src/connector/index.ts +7 -2
- package/src/connector/mcp/__tests__/prompt-as-tool.test.ts +157 -0
- package/src/connector/mcp/__tests__/prompts-and-lifecycle.test.ts +286 -0
- package/src/connector/mcp/client.ts +95 -0
- package/src/connector/mcp/discovery.ts +67 -5
- package/src/connector/mcp/index.ts +8 -3
- package/src/connector/mcp/policy.ts +23 -8
- package/src/connector/mcp/prompt-adapter.ts +139 -0
- package/src/connector/mcp/server.ts +101 -3
- package/src/manager/agent/__tests__/depth-limit-authority.test.ts +74 -0
- package/src/manager/agent/__tests__/hitl-across-spawn.test.ts +273 -0
- package/src/manager/agent/lifecycle.ts +12 -0
- package/src/plugin/__tests__/discovery-scopes.test.ts +133 -0
- package/src/plugin/__tests__/enable-contributions.test.ts +5 -1
- package/src/plugin/__tests__/mcp-admission.test.ts +286 -0
- package/src/plugin/lifecycle.ts +68 -1
- package/src/plugin/loader.ts +57 -3
- package/src/public-runtime.ts +10 -0
- package/src/public-types.ts +5 -0
- package/src/rag/__tests__/namespace-isolation.test.ts +109 -0
- package/src/rag/ingestion.ts +1 -0
- package/src/rag/retriever.ts +2 -0
- package/src/rag/vector-store.ts +5 -0
- package/src/registry/tool/execute.ts +123 -119
- package/src/runtime/query/__tests__/per-step-skills.test.ts +154 -0
- package/src/runtime/query/__tests__/per-step-tool-choice.test.ts +180 -0
- package/src/runtime/query/__tests__/resume-run.test.ts +262 -0
- package/src/runtime/query/index.ts +39 -0
- package/src/runtime/query/iteration/index.ts +55 -34
- package/src/runtime/query/iteration/phases/compaction-model-routing.test.ts +125 -0
- package/src/runtime/query/iteration/phases/compaction.ts +106 -5
- package/src/runtime/query/iteration/phases/context-reducer-dispatch.test.ts +238 -0
- package/src/runtime/query/iteration/phases/context.ts +11 -0
- package/src/runtime/query/resume-run.ts +93 -0
- package/src/telemetry/__tests__/model-call-span.test.ts +195 -0
- package/src/telemetry/__tests__/span-closure.test.ts +153 -0
- package/src/tools/advisory/index.ts +1 -1
- package/src/tools/coordinator/__tests__/plan-dependencies.test.ts +186 -0
- package/src/tools/coordinator/index.ts +14 -1
- package/src/tools/coordinator/plan-dependencies.ts +175 -0
- package/src/types/agent/base.ts +27 -0
- package/src/types/agent/supervisor.ts +15 -0
- package/src/types/agent/task.ts +18 -0
- package/src/types/connector/core.ts +34 -0
- package/src/types/connector/definition.ts +10 -0
- package/src/types/connector/mcp.ts +14 -0
- package/src/types/rag/retrieval.ts +16 -0
- package/src/types/rag/storage.ts +9 -0
- package/src/types/rag/vector.ts +11 -0
- package/src/types/router/task-router.ts +19 -0
- package/src/types/run/prepare-step.ts +58 -3
- package/src/types/toolset/index.ts +22 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { AgentRegistry } from '../../../registry/agent/definitions.js'
|
|
4
|
+
import { DefaultCapacityValidator } from '../../../session/handoff/capacity.js'
|
|
5
|
+
import { SessionSummaryMaterializer } from '../../../session/summary/materialize.js'
|
|
6
|
+
import { WorkspaceBackendRegistry } from '../../../session/workspace/registry.js'
|
|
7
|
+
import { InMemorySessionStore } from '../../../store/session/memory.js'
|
|
8
|
+
import { InMemoryThreadStore } from '../../../store/thread/memory.js'
|
|
9
|
+
import type { BaseAgentConfig, BaseAgentResult } from '../../../types/agent/base.js'
|
|
10
|
+
import type { Agent } from '../../../types/agent/core.js'
|
|
11
|
+
import type { AgentTaskContext, SendMessageOptions } from '../../../types/agent/task.js'
|
|
12
|
+
import type { ResumeHandler } from '../../../types/hitl/index.js'
|
|
13
|
+
import type { TenantId } from '../../../types/ids/index.js'
|
|
14
|
+
import type { ActorRef } from '../../../types/session/actor.js'
|
|
15
|
+
import { ThreadManager } from '../../thread/lifecycle.js'
|
|
16
|
+
import { AgentManager } from '../lifecycle.js'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A human's approval did not cross the spawn boundary.
|
|
20
|
+
*
|
|
21
|
+
* `BaseAgentConfig` carried no resume handler, and
|
|
22
|
+
* `SendMessageOptions.configOverrides` is a `Partial` of it — so a parent
|
|
23
|
+
* could not hand its channel to a child AT THE TYPE LEVEL. Every delegated
|
|
24
|
+
* child fell through to `autoApproveHandler` however carefully its parent
|
|
25
|
+
* had been wired.
|
|
26
|
+
*
|
|
27
|
+
* The cost is narrower than "no gate in children", and worth stating
|
|
28
|
+
* exactly. A `VerificationGate` DENY still bites inside a child, because
|
|
29
|
+
* denials are threaded into the executor and no later approval releases
|
|
30
|
+
* them. What was lost is the REVIEW tier — every call the gate left
|
|
31
|
+
* undecided reached the resume handler, and for a child that handler
|
|
32
|
+
* auto-approved. A host running "ask before acting" had a human review
|
|
33
|
+
* `write` at the top level and never see the same `write` one hop down.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
const tenant = 'tnt_hitl' as TenantId
|
|
37
|
+
const actor = (tenantId: TenantId): ActorRef =>
|
|
38
|
+
({ kind: 'user', userId: 'usr_root', tenantId }) as unknown as ActorRef
|
|
39
|
+
|
|
40
|
+
/** Records the config the child was actually handed. */
|
|
41
|
+
function recordingAgent(seen: BaseAgentConfig[]): Agent<BaseAgentConfig, BaseAgentResult> {
|
|
42
|
+
return {
|
|
43
|
+
metadata: {
|
|
44
|
+
type: 'reactive',
|
|
45
|
+
id: 'worker',
|
|
46
|
+
name: 'Worker',
|
|
47
|
+
version: '1',
|
|
48
|
+
category: 'test',
|
|
49
|
+
description: 'records its config',
|
|
50
|
+
capabilities: {
|
|
51
|
+
supportsTools: true,
|
|
52
|
+
supportsStreaming: true,
|
|
53
|
+
supportsConcurrency: true,
|
|
54
|
+
supportsSubAgents: false,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
async run(_input: unknown, config: BaseAgentConfig) {
|
|
58
|
+
seen.push(config)
|
|
59
|
+
return {
|
|
60
|
+
runId: 'run_child' as never,
|
|
61
|
+
status: 'completed',
|
|
62
|
+
usage: { promptTokens: 1, completionTokens: 1, totalTokens: 2 },
|
|
63
|
+
cost: { totalCost: 0 },
|
|
64
|
+
iterations: 1,
|
|
65
|
+
durationMs: 1,
|
|
66
|
+
messages: [],
|
|
67
|
+
result: 'done',
|
|
68
|
+
} as unknown as BaseAgentResult
|
|
69
|
+
},
|
|
70
|
+
} as unknown as Agent<BaseAgentConfig, BaseAgentResult>
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function harness() {
|
|
74
|
+
const seen: BaseAgentConfig[] = []
|
|
75
|
+
const store = new InMemorySessionStore()
|
|
76
|
+
const threadStore = new InMemoryThreadStore()
|
|
77
|
+
const threadManager = new ThreadManager({ threadStore, sessionStore: store })
|
|
78
|
+
const project = await store.createProject({ tenantId: tenant, name: 'p' }, tenant)
|
|
79
|
+
const thread = await threadStore.createThread({ projectId: project.id, title: 'hitl' }, tenant)
|
|
80
|
+
const parent = await store.createSession(
|
|
81
|
+
{ threadId: thread.id, projectId: project.id, currentActor: actor(tenant) },
|
|
82
|
+
tenant,
|
|
83
|
+
)
|
|
84
|
+
await store.updateSession({ ...parent, status: 'active' }, tenant)
|
|
85
|
+
|
|
86
|
+
const registry = new AgentRegistry()
|
|
87
|
+
const agent = recordingAgent(seen)
|
|
88
|
+
registry.register({
|
|
89
|
+
info: {
|
|
90
|
+
id: agent.metadata.id,
|
|
91
|
+
name: agent.metadata.name,
|
|
92
|
+
version: agent.metadata.version,
|
|
93
|
+
category: agent.metadata.category,
|
|
94
|
+
description: agent.metadata.description,
|
|
95
|
+
tools: [],
|
|
96
|
+
defaults: { model: 'test', tokenBudget: 1_000 },
|
|
97
|
+
},
|
|
98
|
+
typedAgent: agent,
|
|
99
|
+
} as never)
|
|
100
|
+
|
|
101
|
+
// A second registration WITH a configBuilder. That is the branch a real
|
|
102
|
+
// registered agent takes, and the first version of these tests covered
|
|
103
|
+
// only the bare one — so three mutations of the branch that matters
|
|
104
|
+
// survived untouched.
|
|
105
|
+
registry.register({
|
|
106
|
+
info: {
|
|
107
|
+
id: 'built-worker',
|
|
108
|
+
name: 'Built Worker',
|
|
109
|
+
version: '1',
|
|
110
|
+
category: 'test',
|
|
111
|
+
description: 'has a configBuilder',
|
|
112
|
+
tools: [],
|
|
113
|
+
defaults: { model: 'test', tokenBudget: 1_000 },
|
|
114
|
+
},
|
|
115
|
+
typedAgent: agent,
|
|
116
|
+
configBuilder: (opts: Record<string, unknown>) => ({
|
|
117
|
+
model: 'test',
|
|
118
|
+
tokenBudget: (opts.tokenBudget as number) ?? 1_000,
|
|
119
|
+
timeoutMs: (opts.timeoutMs as number) ?? 30_000,
|
|
120
|
+
}),
|
|
121
|
+
} as never)
|
|
122
|
+
|
|
123
|
+
const manager = new AgentManager(registry, undefined, {
|
|
124
|
+
sessionStore: store,
|
|
125
|
+
summaryMaterializer: new SessionSummaryMaterializer({
|
|
126
|
+
store,
|
|
127
|
+
generateSummaryId: () => 'sum_1' as never,
|
|
128
|
+
}),
|
|
129
|
+
workspaceRegistry: new WorkspaceBackendRegistry(),
|
|
130
|
+
capacity: new DefaultCapacityValidator(store),
|
|
131
|
+
threadManager,
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const context = (over: Partial<AgentTaskContext> = {}): AgentTaskContext =>
|
|
135
|
+
({
|
|
136
|
+
parentRunId: 'run_parent',
|
|
137
|
+
parentAgentId: 'supervisor',
|
|
138
|
+
parentAbortController: new AbortController(),
|
|
139
|
+
depth: 0,
|
|
140
|
+
budgetTracker: { total: 100_000, remaining: 100_000 },
|
|
141
|
+
tenantId: tenant,
|
|
142
|
+
threadId: thread.id,
|
|
143
|
+
sessionId: parent.id,
|
|
144
|
+
projectId: project.id,
|
|
145
|
+
parentActor: actor(tenant),
|
|
146
|
+
...over,
|
|
147
|
+
}) as AgentTaskContext
|
|
148
|
+
|
|
149
|
+
const options = (over: Partial<SendMessageOptions> = {}): SendMessageOptions =>
|
|
150
|
+
({
|
|
151
|
+
agentId: 'worker',
|
|
152
|
+
input: { messages: [], workingDirectory: '/tmp' },
|
|
153
|
+
parentSessionId: parent.id,
|
|
154
|
+
tenantId: tenant,
|
|
155
|
+
projectId: project.id,
|
|
156
|
+
parentActor: actor(tenant),
|
|
157
|
+
...over,
|
|
158
|
+
}) as SendMessageOptions
|
|
159
|
+
|
|
160
|
+
const spawn = async (ctx: AgentTaskContext, opts?: Partial<SendMessageOptions>) => {
|
|
161
|
+
await manager.sendMessage(options(opts), ctx)
|
|
162
|
+
// The child runs detached; give it a tick to reach `agent.run`.
|
|
163
|
+
await new Promise((r) => setTimeout(r, 20))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { seen, context, spawn }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
describe('a child asks the same person its parent asks', () => {
|
|
170
|
+
it('inherits the parent channel', async () => {
|
|
171
|
+
const h = await harness()
|
|
172
|
+
const handler = vi.fn(async () => ({ action: 'approve_tools' })) as unknown as ResumeHandler
|
|
173
|
+
|
|
174
|
+
await h.spawn(h.context({ resumeHandler: handler }))
|
|
175
|
+
|
|
176
|
+
expect(h.seen[0]?.resumeHandler).toBe(handler)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('leaves a child without one when the parent has none', async () => {
|
|
180
|
+
const h = await harness()
|
|
181
|
+
|
|
182
|
+
await h.spawn(h.context())
|
|
183
|
+
|
|
184
|
+
// Absent still means auto-approve, so a host that never wired a
|
|
185
|
+
// handler is unaffected by any of this.
|
|
186
|
+
expect(h.seen[0]?.resumeHandler).toBeUndefined()
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('lets an explicit override win, so one child can be given a different channel', async () => {
|
|
190
|
+
const h = await harness()
|
|
191
|
+
const parentHandler = vi.fn() as unknown as ResumeHandler
|
|
192
|
+
const childHandler = vi.fn() as unknown as ResumeHandler
|
|
193
|
+
|
|
194
|
+
await h.spawn(h.context({ resumeHandler: parentHandler }), {
|
|
195
|
+
configOverrides: { resumeHandler: childHandler },
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
expect(h.seen[0]?.resumeHandler).toBe(childHandler)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('does not disturb the scoping the manager already stamps', async () => {
|
|
202
|
+
const h = await harness()
|
|
203
|
+
const handler = vi.fn() as unknown as ResumeHandler
|
|
204
|
+
|
|
205
|
+
await h.spawn(h.context({ resumeHandler: handler }))
|
|
206
|
+
|
|
207
|
+
// The handler is stamped beside the trace parent and the tenant
|
|
208
|
+
// triple, for the same reason: a configBuilder cannot be trusted to
|
|
209
|
+
// forward something it was never told about.
|
|
210
|
+
const config = h.seen[0]
|
|
211
|
+
expect(config?.tenantId).toBe(tenant)
|
|
212
|
+
expect(config?.depth).toBe(1)
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
describe('the type allows what the spawn path needs', () => {
|
|
217
|
+
it('accepts a handler through configOverrides', () => {
|
|
218
|
+
const handler = vi.fn() as unknown as ResumeHandler
|
|
219
|
+
|
|
220
|
+
// The assertion the whole change exists for. The field lived on
|
|
221
|
+
// `ReactiveAgentConfig`, not on `BaseAgentConfig`, and
|
|
222
|
+
// `configOverrides` is `Partial<BaseAgentConfig>` — so this line did
|
|
223
|
+
// not compile, which is why no runtime path could ever have carried
|
|
224
|
+
// one.
|
|
225
|
+
const overrides: Partial<BaseAgentConfig> = { resumeHandler: handler }
|
|
226
|
+
|
|
227
|
+
expect(overrides.resumeHandler).toBe(handler)
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* The same three questions against the branch a REAL registered agent
|
|
233
|
+
* takes. `configBuilder` is written by whoever registered the agent and
|
|
234
|
+
* cannot be trusted to forward something it was never told about, so the
|
|
235
|
+
* manager stamps the handler afterwards — exactly as it does the trace
|
|
236
|
+
* parent and the tenant triple.
|
|
237
|
+
*
|
|
238
|
+
* These exist because the first version of this file covered only the
|
|
239
|
+
* bare-config branch, and three mutations of this one survived unnoticed.
|
|
240
|
+
*/
|
|
241
|
+
describe('a child built by a configBuilder inherits it too', () => {
|
|
242
|
+
it('inherits the parent channel', async () => {
|
|
243
|
+
const h = await harness()
|
|
244
|
+
const handler = vi.fn() as unknown as ResumeHandler
|
|
245
|
+
|
|
246
|
+
await h.spawn(h.context({ resumeHandler: handler }), { agentId: 'built-worker' })
|
|
247
|
+
|
|
248
|
+
expect(h.seen[0]?.resumeHandler).toBe(handler)
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('is left without one when the parent has none', async () => {
|
|
252
|
+
const h = await harness()
|
|
253
|
+
|
|
254
|
+
await h.spawn(h.context(), { agentId: 'built-worker' })
|
|
255
|
+
|
|
256
|
+
// Nothing is invented. A host that never wired a handler still gets
|
|
257
|
+
// the auto-approving default, which is what every child got before.
|
|
258
|
+
expect(h.seen[0]?.resumeHandler).toBeUndefined()
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it('lets an explicit override win over the parent', async () => {
|
|
262
|
+
const h = await harness()
|
|
263
|
+
const parentHandler = vi.fn() as unknown as ResumeHandler
|
|
264
|
+
const childHandler = vi.fn() as unknown as ResumeHandler
|
|
265
|
+
|
|
266
|
+
await h.spawn(h.context({ resumeHandler: parentHandler }), {
|
|
267
|
+
agentId: 'built-worker',
|
|
268
|
+
configOverrides: { resumeHandler: childHandler },
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
expect(h.seen[0]?.resumeHandler).toBe(childHandler)
|
|
272
|
+
})
|
|
273
|
+
})
|
|
@@ -273,6 +273,17 @@ export class AgentManager {
|
|
|
273
273
|
if (options.configOverrides?.parentSpan) {
|
|
274
274
|
childConfig.parentSpan = options.configOverrides.parentSpan
|
|
275
275
|
}
|
|
276
|
+
// Stamped for the same reason as the trace parent above: a
|
|
277
|
+
// `configBuilder` is written by whoever registered the agent and
|
|
278
|
+
// cannot be trusted to forward something it was never told about.
|
|
279
|
+
// An explicit override still wins, so a host can hand one child a
|
|
280
|
+
// different channel — or none.
|
|
281
|
+
//
|
|
282
|
+
// Without this every delegated child fell through to
|
|
283
|
+
// `autoApproveHandler`, so a host's "ask before acting" gate
|
|
284
|
+
// covered the top-level run and nothing it delegated.
|
|
285
|
+
const inheritedHandler = options.configOverrides?.resumeHandler ?? context.resumeHandler
|
|
286
|
+
if (inheritedHandler) childConfig.resumeHandler = inheritedHandler
|
|
276
287
|
} else {
|
|
277
288
|
this.log.warn('No configBuilder, using bare config', {
|
|
278
289
|
agentId: options.agentId,
|
|
@@ -292,6 +303,7 @@ export class AgentManager {
|
|
|
292
303
|
tenantId: context.tenantId,
|
|
293
304
|
parentRunId: context.parentRunId,
|
|
294
305
|
depth: context.depth + 1,
|
|
306
|
+
resumeHandler: options.configOverrides?.resumeHandler ?? context.resumeHandler,
|
|
295
307
|
}
|
|
296
308
|
}
|
|
297
309
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { tmpdir } from 'node:os'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
|
|
6
|
+
import { PluginRuntimeConfigSchema } from '../../config/runtime.js'
|
|
7
|
+
import {
|
|
8
|
+
PLUGIN_MANIFEST_FILENAME,
|
|
9
|
+
PROJECT_PLUGIN_DIR,
|
|
10
|
+
USER_PLUGIN_DIR,
|
|
11
|
+
} from '../../constants/plugin/index.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* `allowedScopes` read exactly like a trust boundary and enforced nothing.
|
|
15
|
+
*
|
|
16
|
+
* A plugin is arbitrary code with hooks into tool execution, and the two
|
|
17
|
+
* scopes are not equally trusted: a project plugin is reviewable in the repo
|
|
18
|
+
* the agent works on, a user plugin comes from a home directory the repo's
|
|
19
|
+
* reviewers never see. `PluginRuntimeConfig` carried `enabled`,
|
|
20
|
+
* `autoDiscovery` and `allowedScopes`; nothing anywhere consulted any of the
|
|
21
|
+
* three, and discovery scanned both locations unconditionally.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
let home: string
|
|
25
|
+
let workdir: string
|
|
26
|
+
|
|
27
|
+
vi.mock('node:os', async () => {
|
|
28
|
+
const actual = await vi.importActual<typeof import('node:os')>('node:os')
|
|
29
|
+
return { ...actual, homedir: () => home }
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
async function plantPlugin(dir: string, name: string): Promise<void> {
|
|
33
|
+
const pluginDir = join(dir, name)
|
|
34
|
+
await mkdir(pluginDir, { recursive: true })
|
|
35
|
+
await writeFile(
|
|
36
|
+
join(pluginDir, PLUGIN_MANIFEST_FILENAME),
|
|
37
|
+
JSON.stringify({ name, version: '1.0.0' }),
|
|
38
|
+
'utf-8',
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
beforeEach(async () => {
|
|
43
|
+
home = await mkdtemp(join(tmpdir(), 'namzu-home-'))
|
|
44
|
+
workdir = await mkdtemp(join(tmpdir(), 'namzu-work-'))
|
|
45
|
+
await plantPlugin(join(workdir, PROJECT_PLUGIN_DIR), 'project-plugin')
|
|
46
|
+
await plantPlugin(join(home, USER_PLUGIN_DIR), 'user-plugin')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
afterEach(async () => {
|
|
50
|
+
await Promise.all([
|
|
51
|
+
rm(home, { recursive: true, force: true }),
|
|
52
|
+
rm(workdir, { recursive: true, force: true }),
|
|
53
|
+
])
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
async function discover(
|
|
57
|
+
options?: Parameters<typeof import('../loader.js').discoverAllPluginDirs>[1],
|
|
58
|
+
) {
|
|
59
|
+
const { discoverAllPluginDirs } = await import('../loader.js')
|
|
60
|
+
return discoverAllPluginDirs(workdir, options)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
describe('a scope the host did not allow is not scanned', () => {
|
|
64
|
+
it('finds both when nothing is said', async () => {
|
|
65
|
+
const found = await discover()
|
|
66
|
+
|
|
67
|
+
expect(found.project).toHaveLength(1)
|
|
68
|
+
expect(found.user).toHaveLength(1)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('keeps user plugins out when only project is allowed', async () => {
|
|
72
|
+
const found = await discover({ allowedScopes: ['project'] })
|
|
73
|
+
|
|
74
|
+
expect(found.project).toHaveLength(1)
|
|
75
|
+
// The setting that reads like a boundary now is one.
|
|
76
|
+
expect(found.user).toEqual([])
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('keeps project plugins out when only user is allowed', async () => {
|
|
80
|
+
const found = await discover({ allowedScopes: ['user'] })
|
|
81
|
+
|
|
82
|
+
expect(found.project).toEqual([])
|
|
83
|
+
expect(found.user).toHaveLength(1)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('finds nothing when neither scope is allowed', async () => {
|
|
87
|
+
const found = await discover({ allowedScopes: [] })
|
|
88
|
+
|
|
89
|
+
expect(found).toEqual({ project: [], user: [] })
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
describe('the runtime switches turn discovery off', () => {
|
|
94
|
+
it('discovers nothing when the plugin runtime is disabled', async () => {
|
|
95
|
+
const found = await discover({ enabled: false })
|
|
96
|
+
|
|
97
|
+
expect(found).toEqual({ project: [], user: [] })
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('discovers nothing when auto-discovery is off', async () => {
|
|
101
|
+
const found = await discover({ autoDiscovery: false })
|
|
102
|
+
|
|
103
|
+
expect(found).toEqual({ project: [], user: [] })
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('a default-parsed config discovers nothing, because the runtime ships off', async () => {
|
|
107
|
+
// `enabled` defaults to false in the schema. A host who parses the
|
|
108
|
+
// config and passes it gets the documented default rather than a
|
|
109
|
+
// silently-on plugin runtime.
|
|
110
|
+
const found = await discover(PluginRuntimeConfigSchema.parse({}))
|
|
111
|
+
|
|
112
|
+
expect(found).toEqual({ project: [], user: [] })
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('an enabled default-parsed config finds both scopes', async () => {
|
|
116
|
+
const found = await discover(PluginRuntimeConfigSchema.parse({ enabled: true }))
|
|
117
|
+
|
|
118
|
+
expect(found.project).toHaveLength(1)
|
|
119
|
+
expect(found.user).toHaveLength(1)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('a parsed config carries its scope restriction through', async () => {
|
|
123
|
+
const config = PluginRuntimeConfigSchema.parse({
|
|
124
|
+
enabled: true,
|
|
125
|
+
allowedScopes: ['project'],
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
const found = await discover(config)
|
|
129
|
+
|
|
130
|
+
expect(found.project).toHaveLength(1)
|
|
131
|
+
expect(found.user).toEqual([])
|
|
132
|
+
})
|
|
133
|
+
})
|
|
@@ -11,11 +11,15 @@ const mockDisconnect = vi.fn(async (): Promise<void> => undefined)
|
|
|
11
11
|
const mockListTools = vi.fn(async (): Promise<unknown[]> => [])
|
|
12
12
|
|
|
13
13
|
vi.mock('../../connector/mcp/client.js', () => ({
|
|
14
|
-
MCPClient: vi.fn().mockImplementation(() => ({
|
|
14
|
+
MCPClient: vi.fn().mockImplementation((config: { serverName: string }) => ({
|
|
15
15
|
id: 'mcp-client-mock',
|
|
16
16
|
connect: mockConnect,
|
|
17
17
|
disconnect: mockDisconnect,
|
|
18
18
|
listTools: mockListTools,
|
|
19
|
+
// The real client has always had this; the mock did not, which went
|
|
20
|
+
// unnoticed while nothing on this path asked the client which server
|
|
21
|
+
// it was talking to. Admission does — a policy is per server name.
|
|
22
|
+
getState: () => ({ serverName: config.serverName }),
|
|
19
23
|
})),
|
|
20
24
|
}))
|
|
21
25
|
|