@namzu/sdk 1.0.0 → 1.2.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 +253 -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 +22 -1
- package/dist/agents/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/SupervisorAgent.js +47 -14
- package/dist/agents/SupervisorAgent.js.map +1 -1
- package/dist/agents/__tests__/supervisor-ledger.test.d.ts +2 -0
- package/dist/agents/__tests__/supervisor-ledger.test.d.ts.map +1 -0
- package/dist/agents/__tests__/supervisor-ledger.test.js +131 -0
- package/dist/agents/__tests__/supervisor-ledger.test.js.map +1 -0
- package/dist/bridge/a2a/mapper.d.ts.map +1 -1
- package/dist/bridge/a2a/mapper.js +2 -0
- package/dist/bridge/a2a/mapper.js.map +1 -1
- package/dist/bridge/sse/mapper.d.ts.map +1 -1
- package/dist/bridge/sse/mapper.js +4 -0
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/config/runtime.d.ts +24 -0
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +9 -0
- package/dist/config/runtime.js.map +1 -1
- package/dist/connector/builtins/webhook.test.js +17 -0
- package/dist/connector/builtins/webhook.test.js.map +1 -1
- package/dist/connector/index.d.ts +1 -0
- package/dist/connector/index.d.ts.map +1 -1
- package/dist/connector/index.js +1 -0
- package/dist/connector/index.js.map +1 -1
- package/dist/connector/mcp/client.d.ts.map +1 -1
- package/dist/connector/mcp/client.js +4 -0
- package/dist/connector/mcp/client.js.map +1 -1
- package/dist/connector/mcp/index.d.ts +1 -0
- package/dist/connector/mcp/index.d.ts.map +1 -1
- package/dist/connector/mcp/index.js +1 -0
- package/dist/connector/mcp/index.js.map +1 -1
- package/dist/connector/mcp/streamable-http.d.ts +22 -0
- package/dist/connector/mcp/streamable-http.d.ts.map +1 -0
- package/dist/connector/mcp/streamable-http.js +128 -0
- package/dist/connector/mcp/streamable-http.js.map +1 -0
- package/dist/connector/mcp/streamable-http.test.d.ts +2 -0
- package/dist/connector/mcp/streamable-http.test.d.ts.map +1 -0
- package/dist/connector/mcp/streamable-http.test.js +132 -0
- package/dist/connector/mcp/streamable-http.test.js.map +1 -0
- package/dist/{runtime/query → constants}/continuation.d.ts +5 -0
- package/dist/constants/continuation.d.ts.map +1 -0
- package/dist/{runtime/query → constants}/continuation.js +5 -0
- package/dist/constants/continuation.js.map +1 -0
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +1 -0
- package/dist/constants/index.js.map +1 -1
- package/dist/manager/run/persistence.d.ts +11 -0
- package/dist/manager/run/persistence.d.ts.map +1 -1
- package/dist/manager/run/persistence.js +30 -1
- package/dist/manager/run/persistence.js.map +1 -1
- package/dist/persona/assembler.d.ts.map +1 -1
- package/dist/persona/assembler.js +18 -9
- package/dist/persona/assembler.js.map +1 -1
- package/dist/provider/capabilities.d.ts +27 -0
- package/dist/provider/capabilities.d.ts.map +1 -0
- package/dist/provider/capabilities.js +29 -0
- package/dist/provider/capabilities.js.map +1 -0
- package/dist/provider/collect.d.ts.map +1 -1
- package/dist/provider/collect.js +4 -1
- package/dist/provider/collect.js.map +1 -1
- package/dist/provider/index.d.ts +2 -0
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +1 -0
- package/dist/provider/index.js.map +1 -1
- package/dist/public-runtime.d.ts +4 -3
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +3 -3
- package/dist/public-runtime.js.map +1 -1
- package/dist/registry/tool/execute.d.ts +27 -0
- package/dist/registry/tool/execute.d.ts.map +1 -1
- package/dist/registry/tool/execute.js +124 -21
- package/dist/registry/tool/execute.js.map +1 -1
- package/dist/registry/tool/execute.test.d.ts +9 -6
- package/dist/registry/tool/execute.test.d.ts.map +1 -1
- package/dist/registry/tool/execute.test.js +72 -10
- package/dist/registry/tool/execute.test.js.map +1 -1
- package/dist/registry/toolset/catalog.test.js +62 -0
- package/dist/registry/toolset/catalog.test.js.map +1 -1
- package/dist/run/reporter.d.ts.map +1 -1
- package/dist/run/reporter.js +8 -0
- package/dist/run/reporter.js.map +1 -1
- package/dist/runtime/query/__tests__/capability-negotiation.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/capability-negotiation.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/capability-negotiation.test.js +184 -0
- package/dist/runtime/query/__tests__/capability-negotiation.test.js.map +1 -0
- package/dist/runtime/query/__tests__/checkpoint-store.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/checkpoint-store.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/checkpoint-store.test.js +259 -0
- package/dist/runtime/query/__tests__/checkpoint-store.test.js.map +1 -0
- package/dist/runtime/query/__tests__/checkpoint.test.js +9 -3
- package/dist/runtime/query/__tests__/checkpoint.test.js.map +1 -1
- package/dist/runtime/query/__tests__/deferred-tools.test.js +43 -5
- package/dist/runtime/query/__tests__/deferred-tools.test.js.map +1 -1
- package/dist/runtime/query/__tests__/long-document-flow.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/long-document-flow.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/long-document-flow.test.js +154 -0
- package/dist/runtime/query/__tests__/long-document-flow.test.js.map +1 -0
- package/dist/runtime/query/__tests__/orphan-task-turn-end.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/orphan-task-turn-end.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/orphan-task-turn-end.test.js +110 -0
- package/dist/runtime/query/__tests__/orphan-task-turn-end.test.js.map +1 -0
- package/dist/runtime/query/__tests__/prompt.test.js +15 -11
- package/dist/runtime/query/__tests__/prompt.test.js.map +1 -1
- package/dist/runtime/query/checkpoint.d.ts +16 -2
- package/dist/runtime/query/checkpoint.d.ts.map +1 -1
- package/dist/runtime/query/checkpoint.js +21 -7
- package/dist/runtime/query/checkpoint.js.map +1 -1
- package/dist/runtime/query/context.d.ts +7 -0
- package/dist/runtime/query/context.d.ts.map +1 -1
- package/dist/runtime/query/context.js +1 -0
- package/dist/runtime/query/context.js.map +1 -1
- package/dist/runtime/query/index.d.ts +29 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +98 -8
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +3 -55
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +260 -656
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/checkpoint.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/checkpoint.js +24 -2
- package/dist/runtime/query/iteration/phases/checkpoint.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts +8 -0
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +84 -3
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +13 -2
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js +53 -0
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/phases/hitl-abort-race.test.d.ts +2 -0
- package/dist/runtime/query/iteration/phases/hitl-abort-race.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/hitl-abort-race.test.js +73 -0
- package/dist/runtime/query/iteration/phases/hitl-abort-race.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/hitl-answer-question.test.d.ts +2 -0
- package/dist/runtime/query/iteration/phases/hitl-answer-question.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/hitl-answer-question.test.js +120 -0
- package/dist/runtime/query/iteration/phases/hitl-answer-question.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/tool-review.d.ts +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +7 -2
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/iteration/phases/working-memory-compaction.test.d.ts +19 -0
- package/dist/runtime/query/iteration/phases/working-memory-compaction.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/working-memory-compaction.test.js +177 -0
- package/dist/runtime/query/iteration/phases/working-memory-compaction.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/working-memory.d.ts +31 -0
- package/dist/runtime/query/iteration/phases/working-memory.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/working-memory.js +81 -0
- package/dist/runtime/query/iteration/phases/working-memory.js.map +1 -0
- package/dist/runtime/query/iteration/stream-turn.d.ts +35 -0
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -0
- package/dist/runtime/query/iteration/stream-turn.js +341 -0
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -0
- package/dist/runtime/query/replay/list.d.ts +17 -1
- package/dist/runtime/query/replay/list.d.ts.map +1 -1
- package/dist/runtime/query/replay/list.js +21 -4
- package/dist/runtime/query/replay/list.js.map +1 -1
- package/dist/runtime/query/replay/prepare.d.ts +10 -0
- package/dist/runtime/query/replay/prepare.d.ts.map +1 -1
- package/dist/runtime/query/replay/prepare.js +20 -4
- package/dist/runtime/query/replay/prepare.js.map +1 -1
- package/dist/session/workspace/__tests__/shared-run.test.js +1 -1
- package/dist/session/workspace/__tests__/shared-run.test.js.map +1 -1
- package/dist/session/workspace/shared-run.js +1 -0
- package/dist/session/workspace/shared-run.js.map +1 -1
- package/dist/skills/loader.test.js +8 -8
- package/dist/skills/loader.test.js.map +1 -1
- package/dist/store/index.d.ts +1 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +1 -0
- package/dist/store/index.js.map +1 -1
- package/dist/store/run/checkpoint-disk.d.ts +28 -0
- package/dist/store/run/checkpoint-disk.d.ts.map +1 -0
- package/dist/store/run/checkpoint-disk.js +55 -0
- package/dist/store/run/checkpoint-disk.js.map +1 -0
- package/dist/streaming/coalesce.test.js.map +1 -1
- package/dist/tools/builtins/__tests__/payload-budget.test.js +48 -0
- package/dist/tools/builtins/__tests__/payload-budget.test.js.map +1 -1
- package/dist/tools/builtins/__tests__/read-file.test.js +9 -0
- package/dist/tools/builtins/__tests__/read-file.test.js.map +1 -1
- package/dist/tools/builtins/read-file.d.ts.map +1 -1
- package/dist/tools/builtins/read-file.js +50 -2
- package/dist/tools/builtins/read-file.js.map +1 -1
- package/dist/tools/builtins/search-tools.d.ts.map +1 -1
- package/dist/tools/builtins/search-tools.js +32 -7
- package/dist/tools/builtins/search-tools.js.map +1 -1
- package/dist/tools/coordinator/__tests__/approve-plan.test.d.ts +2 -0
- package/dist/tools/coordinator/__tests__/approve-plan.test.d.ts.map +1 -0
- package/dist/tools/coordinator/__tests__/approve-plan.test.js +114 -0
- package/dist/tools/coordinator/__tests__/approve-plan.test.js.map +1 -0
- package/dist/tools/coordinator/__tests__/ask-user-question.test.d.ts +2 -0
- package/dist/tools/coordinator/__tests__/ask-user-question.test.d.ts.map +1 -0
- package/dist/tools/coordinator/__tests__/ask-user-question.test.js +309 -0
- package/dist/tools/coordinator/__tests__/ask-user-question.test.js.map +1 -0
- package/dist/tools/coordinator/index.d.ts +8 -0
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +195 -15
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/types/agent/index.d.ts +1 -0
- package/dist/types/agent/index.d.ts.map +1 -1
- package/dist/types/agent/index.js +1 -0
- package/dist/types/agent/index.js.map +1 -1
- package/dist/types/agent/reactive.d.ts +15 -0
- package/dist/types/agent/reactive.d.ts.map +1 -1
- package/dist/types/agent/supervisor.d.ts +18 -0
- package/dist/types/agent/supervisor.d.ts.map +1 -1
- package/dist/types/agent/working-memory.d.ts +29 -0
- package/dist/types/agent/working-memory.d.ts.map +1 -0
- package/dist/types/agent/working-memory.js +2 -0
- package/dist/types/agent/working-memory.js.map +1 -0
- package/dist/types/common/index.d.ts +10 -0
- package/dist/types/common/index.d.ts.map +1 -1
- package/dist/types/common/index.js +18 -0
- package/dist/types/common/index.js.map +1 -1
- package/dist/types/common/usage-merge.test.d.ts +2 -0
- package/dist/types/common/usage-merge.test.d.ts.map +1 -0
- package/dist/types/common/usage-merge.test.js +48 -0
- package/dist/types/common/usage-merge.test.js.map +1 -0
- package/dist/types/connector/mcp.d.ts +9 -2
- package/dist/types/connector/mcp.d.ts.map +1 -1
- package/dist/types/hitl/index.d.ts +42 -0
- package/dist/types/hitl/index.d.ts.map +1 -1
- package/dist/types/hitl/index.js +11 -0
- package/dist/types/hitl/index.js.map +1 -1
- package/dist/types/provider/chat.d.ts +9 -0
- package/dist/types/provider/chat.d.ts.map +1 -1
- package/dist/types/provider/config.d.ts +18 -0
- package/dist/types/provider/config.d.ts.map +1 -1
- package/dist/types/provider/interface.d.ts +10 -0
- package/dist/types/provider/interface.d.ts.map +1 -1
- package/dist/types/run/checkpoint-store.d.ts +64 -0
- package/dist/types/run/checkpoint-store.d.ts.map +1 -0
- package/dist/types/run/checkpoint-store.js +12 -0
- package/dist/types/run/checkpoint-store.js.map +1 -0
- package/dist/types/run/config.d.ts +27 -0
- package/dist/types/run/config.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +6 -0
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/events.js.map +1 -1
- package/dist/types/run/index.d.ts +1 -0
- package/dist/types/run/index.d.ts.map +1 -1
- package/dist/types/run/index.js +1 -0
- package/dist/types/run/index.js.map +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +25 -4
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/ReactiveAgent.ts +5 -0
- package/src/agents/SupervisorAgent.ts +56 -16
- package/src/agents/__tests__/supervisor-ledger.test.ts +185 -0
- package/src/bridge/a2a/mapper.ts +3 -0
- package/src/bridge/sse/mapper.ts +5 -0
- package/src/config/runtime.ts +9 -0
- package/src/connector/builtins/webhook.test.ts +21 -0
- package/src/connector/index.ts +1 -0
- package/src/connector/mcp/client.ts +4 -0
- package/src/connector/mcp/index.ts +1 -0
- package/src/connector/mcp/streamable-http.test.ts +169 -0
- package/src/connector/mcp/streamable-http.ts +152 -0
- package/src/{runtime/query → constants}/continuation.ts +5 -0
- package/src/constants/index.ts +1 -0
- package/src/manager/run/persistence.ts +34 -1
- package/src/persona/assembler.ts +18 -6
- package/src/provider/capabilities.ts +47 -0
- package/src/provider/collect.ts +4 -1
- package/src/provider/index.ts +5 -0
- package/src/public-runtime.ts +5 -0
- package/src/registry/tool/execute.test.ts +85 -10
- package/src/registry/tool/execute.ts +124 -21
- package/src/registry/toolset/catalog.test.ts +68 -0
- package/src/run/reporter.ts +9 -0
- package/src/runtime/query/__tests__/capability-negotiation.test.ts +248 -0
- package/src/runtime/query/__tests__/checkpoint-store.test.ts +322 -0
- package/src/runtime/query/__tests__/checkpoint.test.ts +15 -6
- package/src/runtime/query/__tests__/deferred-tools.test.ts +52 -5
- package/src/runtime/query/__tests__/long-document-flow.test.ts +180 -0
- package/src/runtime/query/__tests__/orphan-task-turn-end.test.ts +127 -0
- package/src/runtime/query/__tests__/prompt.test.ts +15 -11
- package/src/runtime/query/checkpoint.ts +23 -9
- package/src/runtime/query/context.ts +9 -0
- package/src/runtime/query/index.ts +148 -29
- package/src/runtime/query/iteration/index.ts +316 -831
- package/src/runtime/query/iteration/phases/checkpoint.ts +32 -2
- package/src/runtime/query/iteration/phases/compaction.ts +89 -3
- package/src/runtime/query/iteration/phases/context.ts +59 -3
- package/src/runtime/query/iteration/phases/hitl-abort-race.test.ts +86 -0
- package/src/runtime/query/iteration/phases/hitl-answer-question.test.ts +139 -0
- package/src/runtime/query/iteration/phases/tool-review.ts +7 -3
- package/src/runtime/query/iteration/phases/working-memory-compaction.test.ts +215 -0
- package/src/runtime/query/iteration/phases/working-memory.ts +86 -0
- package/src/runtime/query/iteration/stream-turn.ts +396 -0
- package/src/runtime/query/replay/list.ts +38 -4
- package/src/runtime/query/replay/prepare.ts +37 -4
- package/src/session/workspace/__tests__/shared-run.test.ts +1 -1
- package/src/session/workspace/shared-run.ts +1 -0
- package/src/skills/loader.test.ts +8 -8
- package/src/store/index.ts +1 -0
- package/src/store/run/checkpoint-disk.ts +67 -0
- package/src/streaming/coalesce.test.ts +3 -3
- package/src/tools/builtins/__tests__/payload-budget.test.ts +63 -0
- package/src/tools/builtins/__tests__/read-file.test.ts +12 -0
- package/src/tools/builtins/read-file.ts +50 -2
- package/src/tools/builtins/search-tools.ts +38 -7
- package/src/tools/coordinator/__tests__/approve-plan.test.ts +139 -0
- package/src/tools/coordinator/__tests__/ask-user-question.test.ts +370 -0
- package/src/tools/coordinator/index.ts +227 -17
- package/src/types/agent/index.ts +1 -0
- package/src/types/agent/reactive.ts +17 -0
- package/src/types/agent/supervisor.ts +20 -0
- package/src/types/agent/working-memory.ts +29 -0
- package/src/types/common/index.ts +19 -0
- package/src/types/common/usage-merge.test.ts +53 -0
- package/src/types/connector/mcp.ts +14 -2
- package/src/types/hitl/index.ts +52 -1
- package/src/types/provider/chat.ts +10 -0
- package/src/types/provider/config.ts +18 -0
- package/src/types/provider/interface.ts +11 -0
- package/src/types/run/checkpoint-store.ts +72 -0
- package/src/types/run/config.ts +30 -0
- package/src/types/run/events.ts +13 -0
- package/src/types/run/index.ts +1 -0
- package/src/version.ts +26 -4
- package/dist/runtime/query/continuation.d.ts.map +0 -1
- package/dist/runtime/query/continuation.js.map +0 -1
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { PlanManager } from '../../../manager/plan/lifecycle.js'
|
|
4
|
+
import type { TaskGateway } from '../../../types/agent/gateway.js'
|
|
5
|
+
import type { RunId } from '../../../types/ids/index.js'
|
|
6
|
+
import type { PlanApprovalResponse } from '../../../types/plan/index.js'
|
|
7
|
+
import type { ToolContext } from '../../../types/tool/index.js'
|
|
8
|
+
import { buildCoordinatorTools } from '../index.js'
|
|
9
|
+
|
|
10
|
+
function unusedGateway(): TaskGateway {
|
|
11
|
+
return {
|
|
12
|
+
async createTask() {
|
|
13
|
+
throw new Error('not used')
|
|
14
|
+
},
|
|
15
|
+
async waitForTask() {
|
|
16
|
+
throw new Error('not used')
|
|
17
|
+
},
|
|
18
|
+
async continueTask() {},
|
|
19
|
+
cancelTask() {},
|
|
20
|
+
getTask() {
|
|
21
|
+
return undefined
|
|
22
|
+
},
|
|
23
|
+
listTasks() {
|
|
24
|
+
return []
|
|
25
|
+
},
|
|
26
|
+
onTaskCompleted() {
|
|
27
|
+
return () => {}
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function testToolContext(): ToolContext {
|
|
33
|
+
return {
|
|
34
|
+
runId: 'run_approve_plan_test' as RunId,
|
|
35
|
+
workingDirectory: '/tmp/test',
|
|
36
|
+
abortSignal: new AbortController().signal,
|
|
37
|
+
env: {},
|
|
38
|
+
log: () => {},
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function executeApprovePlan(approval: PlanApprovalResponse) {
|
|
43
|
+
const pm = new PlanManager('run_approve_plan_test' as RunId, async () => approval)
|
|
44
|
+
const tools = buildCoordinatorTools({
|
|
45
|
+
gateway: unusedGateway(),
|
|
46
|
+
workingDirectory: '/tmp/test',
|
|
47
|
+
allowedAgentIds: ['sales-strategy'],
|
|
48
|
+
getPlanManager: () => pm,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const approvePlan = tools.find((tool) => tool.name === 'approve_plan')
|
|
52
|
+
if (!approvePlan) throw new Error('approve_plan tool missing from coordinator builder')
|
|
53
|
+
|
|
54
|
+
return approvePlan.execute(
|
|
55
|
+
{
|
|
56
|
+
title: 'Review uploaded documents',
|
|
57
|
+
summary: 'Inspect the documents and prepare the worker plan.',
|
|
58
|
+
steps: [{ description: 'Extract uploaded DOCX files' }],
|
|
59
|
+
},
|
|
60
|
+
testToolContext(),
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
describe('coordinator approve_plan tool', () => {
|
|
65
|
+
it('normalizes plain-text plan steps into canonical step objects', () => {
|
|
66
|
+
const tools = buildCoordinatorTools({
|
|
67
|
+
gateway: unusedGateway(),
|
|
68
|
+
workingDirectory: '/tmp/test',
|
|
69
|
+
allowedAgentIds: ['sales-strategy'],
|
|
70
|
+
getPlanManager: () => undefined,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const approvePlan = tools.find((tool) => tool.name === 'approve_plan')
|
|
74
|
+
if (!approvePlan) throw new Error('approve_plan tool missing from coordinator builder')
|
|
75
|
+
|
|
76
|
+
const parsed = approvePlan.inputSchema.safeParse({
|
|
77
|
+
title: 'Review uploaded documents',
|
|
78
|
+
summary: 'Inspect the documents and prepare the worker plan.',
|
|
79
|
+
steps: '1. Extract uploaded DOCX files\n2. Launch architecture worker',
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
expect(parsed.success).toBe(true)
|
|
83
|
+
if (!parsed.success) return
|
|
84
|
+
const data = parsed.data as { steps: Array<{ description: string }> }
|
|
85
|
+
expect(data.steps).toEqual([
|
|
86
|
+
{ description: 'Extract uploaded DOCX files' },
|
|
87
|
+
{ description: 'Launch architecture worker' },
|
|
88
|
+
])
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('keeps the bare-approve tool_result output byte-identical', async () => {
|
|
92
|
+
const result = await executeApprovePlan({ approved: true })
|
|
93
|
+
|
|
94
|
+
expect(result.success).toBe(true)
|
|
95
|
+
expect(result.output).toBe(
|
|
96
|
+
'Plan approved by user. Proceed with execution — launch workers via create_task.',
|
|
97
|
+
)
|
|
98
|
+
expect(result.data).toEqual({ approved: true, feedback: undefined })
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('embeds approve-with-edits feedback in the output and data', async () => {
|
|
102
|
+
const result = await executeApprovePlan({
|
|
103
|
+
approved: true,
|
|
104
|
+
feedback: 'Skip step 2 and use the staging database instead.',
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
expect(result.success).toBe(true)
|
|
108
|
+
expect(result.output).toBe(
|
|
109
|
+
'Plan approved by user with required edits — apply them during execution:\n' +
|
|
110
|
+
'Skip step 2 and use the staging database instead.\n' +
|
|
111
|
+
'Proceed with execution — launch workers via create_task.',
|
|
112
|
+
)
|
|
113
|
+
expect(result.data).toEqual({
|
|
114
|
+
approved: true,
|
|
115
|
+
feedback: 'Skip step 2 and use the staging database instead.',
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('keeps the rejection output carrying the feedback verbatim with follow-the-feedback guidance', async () => {
|
|
120
|
+
const result = await executeApprovePlan({
|
|
121
|
+
approved: false,
|
|
122
|
+
feedback: 'Wrong scope — focus on invoices only',
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
expect(result.success).toBe(false)
|
|
126
|
+
// Guidance must FOLLOW the feedback rather than bake in a revise
|
|
127
|
+
// loop — stop-style feedback ("do not plan again") and a trailing
|
|
128
|
+
// "revise and call approve_plan again" are contradictory
|
|
129
|
+
// instructions, and the model kept generating plans after a
|
|
130
|
+
// rejection meant to halt.
|
|
131
|
+
expect(result.output).toBe(
|
|
132
|
+
'Plan rejected. User feedback: Wrong scope — focus on invoices only. Follow this feedback: if it requests changes, revise your plan and call approve_plan again; if it asks you to stop, acknowledge briefly and end your turn. If no feedback was provided, ask the user how to proceed before planning again.',
|
|
133
|
+
)
|
|
134
|
+
expect(result.data).toEqual({
|
|
135
|
+
approved: false,
|
|
136
|
+
feedback: 'Wrong scope — focus on invoices only',
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
})
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import type { TaskGateway } from '../../../types/agent/gateway.js'
|
|
4
|
+
import type {
|
|
5
|
+
HITLDecisionRequest,
|
|
6
|
+
HITLResumeDecision,
|
|
7
|
+
ResumeHandler,
|
|
8
|
+
} from '../../../types/hitl/index.js'
|
|
9
|
+
import type { RunId } from '../../../types/ids/index.js'
|
|
10
|
+
import type { ToolContext, ToolDefinition } from '../../../types/tool/index.js'
|
|
11
|
+
import { buildCoordinatorTools } from '../index.js'
|
|
12
|
+
|
|
13
|
+
const RUN_ID = 'run_ask_user_question_test' as RunId
|
|
14
|
+
const TOOL_USE_ID = 'toolu_question_1'
|
|
15
|
+
|
|
16
|
+
const NO_ANSWER_SENTINEL =
|
|
17
|
+
'The user did not answer this question. Do not assume a choice or consent; proceed on your best judgment or continue without this information.'
|
|
18
|
+
|
|
19
|
+
function unusedGateway(): TaskGateway {
|
|
20
|
+
return {
|
|
21
|
+
async createTask() {
|
|
22
|
+
throw new Error('not used')
|
|
23
|
+
},
|
|
24
|
+
async waitForTask() {
|
|
25
|
+
throw new Error('not used')
|
|
26
|
+
},
|
|
27
|
+
async continueTask() {},
|
|
28
|
+
cancelTask() {},
|
|
29
|
+
getTask() {
|
|
30
|
+
return undefined
|
|
31
|
+
},
|
|
32
|
+
listTasks() {
|
|
33
|
+
return []
|
|
34
|
+
},
|
|
35
|
+
onTaskCompleted() {
|
|
36
|
+
return () => {}
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function testToolContext(): ToolContext {
|
|
42
|
+
return {
|
|
43
|
+
runId: RUN_ID,
|
|
44
|
+
workingDirectory: '/tmp/test',
|
|
45
|
+
abortSignal: new AbortController().signal,
|
|
46
|
+
env: {},
|
|
47
|
+
log: () => {},
|
|
48
|
+
toolUseId: TOOL_USE_ID,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function contextWithoutToolUseId(): ToolContext {
|
|
53
|
+
return {
|
|
54
|
+
runId: RUN_ID,
|
|
55
|
+
workingDirectory: '/tmp/test',
|
|
56
|
+
abortSignal: new AbortController().signal,
|
|
57
|
+
env: {},
|
|
58
|
+
log: () => {},
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildTools(opts: { resumeHandler?: ResumeHandler; runId?: RunId }): ToolDefinition[] {
|
|
63
|
+
return buildCoordinatorTools({
|
|
64
|
+
gateway: unusedGateway(),
|
|
65
|
+
workingDirectory: '/tmp/test',
|
|
66
|
+
allowedAgentIds: ['sales-strategy'],
|
|
67
|
+
...opts,
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function askTool(handler: ResumeHandler): ToolDefinition {
|
|
72
|
+
const tools = buildTools({ resumeHandler: handler, runId: RUN_ID })
|
|
73
|
+
const tool = tools.find((t) => t.name === 'ask_user_question')
|
|
74
|
+
if (!tool) throw new Error('ask_user_question tool missing from coordinator builder')
|
|
75
|
+
return tool
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const baseInput = {
|
|
79
|
+
question: 'Who is the audience?',
|
|
80
|
+
options: [
|
|
81
|
+
{ label: 'Board (Recommended)', description: 'Executive framing, business outcomes first' },
|
|
82
|
+
{ label: 'Engineering team' },
|
|
83
|
+
{ label: 'Customer' },
|
|
84
|
+
],
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Mirror the registry's execution path: input is validated/defaulted
|
|
89
|
+
* through the tool's own zod schema before reaching execute.
|
|
90
|
+
*/
|
|
91
|
+
async function executeAsk(opts: {
|
|
92
|
+
decision: HITLResumeDecision
|
|
93
|
+
input?: unknown
|
|
94
|
+
context?: ToolContext
|
|
95
|
+
}) {
|
|
96
|
+
const requests: HITLDecisionRequest[] = []
|
|
97
|
+
const tool = askTool(async (request) => {
|
|
98
|
+
requests.push(request)
|
|
99
|
+
return opts.decision
|
|
100
|
+
})
|
|
101
|
+
const parsed = tool.inputSchema.parse(opts.input ?? baseInput)
|
|
102
|
+
const result = await tool.execute(parsed, opts.context ?? testToolContext())
|
|
103
|
+
return { result, requests }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
describe('coordinator ask_user_question registration', () => {
|
|
107
|
+
const noopHandler: ResumeHandler = async () => ({ action: 'continue' })
|
|
108
|
+
|
|
109
|
+
it('registers the tool only when BOTH resumeHandler and runId are present', () => {
|
|
110
|
+
const present = buildTools({ resumeHandler: noopHandler, runId: RUN_ID })
|
|
111
|
+
expect(present.some((t) => t.name === 'ask_user_question')).toBe(true)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('does not register without a runId', () => {
|
|
115
|
+
const tools = buildTools({ resumeHandler: noopHandler })
|
|
116
|
+
expect(tools.some((t) => t.name === 'ask_user_question')).toBe(false)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('does not register without a resumeHandler', () => {
|
|
120
|
+
const tools = buildTools({ runId: RUN_ID })
|
|
121
|
+
expect(tools.some((t) => t.name === 'ask_user_question')).toBe(false)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('does not register with neither', () => {
|
|
125
|
+
const tools = buildTools({})
|
|
126
|
+
expect(tools.some((t) => t.name === 'ask_user_question')).toBe(false)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('pins the gate-relevant flags — custom, read-only, non-destructive, NOT concurrency-safe', () => {
|
|
130
|
+
const tool = askTool(noopHandler)
|
|
131
|
+
expect(tool.category).toBe('custom')
|
|
132
|
+
expect(tool.permissions).toEqual([])
|
|
133
|
+
expect(tool.isReadOnly?.({})).toBe(true)
|
|
134
|
+
expect(tool.isDestructive?.({})).toBe(false)
|
|
135
|
+
// The single most load-bearing flag: hosts key park registries by
|
|
136
|
+
// runId, so intra-turn questions MUST serialize. true would let
|
|
137
|
+
// executeBatch open concurrent parks that clobber and deadlock.
|
|
138
|
+
expect(tool.isConcurrencySafe?.({})).toBe(false)
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
describe('coordinator ask_user_question input schema', () => {
|
|
143
|
+
const noopHandler: ResumeHandler = async () => ({ action: 'continue' })
|
|
144
|
+
|
|
145
|
+
it('rejects fewer than 2 options and more than 4', () => {
|
|
146
|
+
const tool = askTool(noopHandler)
|
|
147
|
+
expect(
|
|
148
|
+
tool.inputSchema.safeParse({
|
|
149
|
+
question: 'Pick?',
|
|
150
|
+
options: [{ label: 'Only one' }],
|
|
151
|
+
}).success,
|
|
152
|
+
).toBe(false)
|
|
153
|
+
expect(
|
|
154
|
+
tool.inputSchema.safeParse({
|
|
155
|
+
question: 'Pick?',
|
|
156
|
+
options: [{ label: 'A' }, { label: 'B' }, { label: 'C' }, { label: 'D' }, { label: 'E' }],
|
|
157
|
+
}).success,
|
|
158
|
+
).toBe(false)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('rejects headers longer than 24 characters', () => {
|
|
162
|
+
const tool = askTool(noopHandler)
|
|
163
|
+
expect(
|
|
164
|
+
tool.inputSchema.safeParse({
|
|
165
|
+
...baseInput,
|
|
166
|
+
header: 'x'.repeat(25),
|
|
167
|
+
}).success,
|
|
168
|
+
).toBe(false)
|
|
169
|
+
expect(
|
|
170
|
+
tool.inputSchema.safeParse({
|
|
171
|
+
...baseInput,
|
|
172
|
+
header: 'Audience',
|
|
173
|
+
}).success,
|
|
174
|
+
).toBe(true)
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
describe('coordinator ask_user_question request synthesis', () => {
|
|
179
|
+
it('synthesizes the park request from its own context — questionId = toolUseId', async () => {
|
|
180
|
+
const { requests } = await executeAsk({
|
|
181
|
+
decision: { action: 'answer_question', selectedOptionIds: ['opt_1'] },
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
expect(requests).toHaveLength(1)
|
|
185
|
+
const request = requests[0]
|
|
186
|
+
if (!request || request.type !== 'user_question') {
|
|
187
|
+
throw new Error('expected a user_question request')
|
|
188
|
+
}
|
|
189
|
+
expect(request.runId).toBe(RUN_ID)
|
|
190
|
+
expect(request.checkpointId).toBe(`cp_question_${TOOL_USE_ID}`)
|
|
191
|
+
expect(request.question.questionId).toBe(TOOL_USE_ID)
|
|
192
|
+
expect(request.question.question).toBe('Who is the audience?')
|
|
193
|
+
// zod defaults applied by the schema parse, exactly like the registry
|
|
194
|
+
expect(request.question.multiSelect).toBe(false)
|
|
195
|
+
expect(request.question.allowFreeText).toBe(true)
|
|
196
|
+
expect(request.question.options).toEqual([
|
|
197
|
+
{
|
|
198
|
+
id: 'opt_1',
|
|
199
|
+
label: 'Board (Recommended)',
|
|
200
|
+
description: 'Executive framing, business outcomes first',
|
|
201
|
+
},
|
|
202
|
+
{ id: 'opt_2', label: 'Engineering team' },
|
|
203
|
+
{ id: 'opt_3', label: 'Customer' },
|
|
204
|
+
])
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('threads header / multiSelect / allowFreeText through verbatim', async () => {
|
|
208
|
+
const { requests } = await executeAsk({
|
|
209
|
+
decision: { action: 'answer_question', selectedOptionIds: ['opt_1'] },
|
|
210
|
+
input: {
|
|
211
|
+
...baseInput,
|
|
212
|
+
header: 'Audience',
|
|
213
|
+
multiSelect: true,
|
|
214
|
+
allowFreeText: false,
|
|
215
|
+
},
|
|
216
|
+
})
|
|
217
|
+
const request = requests[0]
|
|
218
|
+
if (!request || request.type !== 'user_question') {
|
|
219
|
+
throw new Error('expected a user_question request')
|
|
220
|
+
}
|
|
221
|
+
expect(request.question.header).toBe('Audience')
|
|
222
|
+
expect(request.question.multiSelect).toBe(true)
|
|
223
|
+
expect(request.question.allowFreeText).toBe(false)
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it('hard-fails without ctx.toolUseId and never parks', async () => {
|
|
227
|
+
const { result, requests } = await executeAsk({
|
|
228
|
+
decision: { action: 'answer_question', selectedOptionIds: ['opt_1'] },
|
|
229
|
+
context: contextWithoutToolUseId(),
|
|
230
|
+
})
|
|
231
|
+
expect(result.success).toBe(false)
|
|
232
|
+
expect(result.error).toContain('toolUseId')
|
|
233
|
+
expect(requests).toHaveLength(0)
|
|
234
|
+
})
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
describe('coordinator ask_user_question decision -> output mapping', () => {
|
|
238
|
+
it('quotes question and selected label verbatim, stripping " (Recommended)"', async () => {
|
|
239
|
+
const { result } = await executeAsk({
|
|
240
|
+
decision: { action: 'answer_question', selectedOptionIds: ['opt_1'] },
|
|
241
|
+
})
|
|
242
|
+
expect(result.success).toBe(true)
|
|
243
|
+
expect(result.output).toBe('User answered "Who is the audience?": "Board"')
|
|
244
|
+
expect(result.data).toEqual({
|
|
245
|
+
question: 'Who is the audience?',
|
|
246
|
+
selected: [{ id: 'opt_1', label: 'Board' }],
|
|
247
|
+
answered: true,
|
|
248
|
+
})
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('joins multiple selected labels', async () => {
|
|
252
|
+
const { result } = await executeAsk({
|
|
253
|
+
decision: { action: 'answer_question', selectedOptionIds: ['opt_2', 'opt_3'] },
|
|
254
|
+
input: { ...baseInput, multiSelect: true },
|
|
255
|
+
})
|
|
256
|
+
expect(result.success).toBe(true)
|
|
257
|
+
expect(result.output).toBe(
|
|
258
|
+
'User answered "Who is the audience?": "Engineering team", "Customer"',
|
|
259
|
+
)
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it('renders a free-text-only answer "in their own words"', async () => {
|
|
263
|
+
const { result } = await executeAsk({
|
|
264
|
+
decision: {
|
|
265
|
+
action: 'answer_question',
|
|
266
|
+
selectedOptionIds: [],
|
|
267
|
+
freeText: 'A mixed partner audience',
|
|
268
|
+
},
|
|
269
|
+
})
|
|
270
|
+
expect(result.success).toBe(true)
|
|
271
|
+
expect(result.output).toBe(
|
|
272
|
+
'User answered "Who is the audience?" in their own words: "A mixed partner audience"',
|
|
273
|
+
)
|
|
274
|
+
expect(result.data).toEqual({
|
|
275
|
+
question: 'Who is the audience?',
|
|
276
|
+
selected: [],
|
|
277
|
+
freeText: 'A mixed partner audience',
|
|
278
|
+
answered: true,
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it('emits both the selection line and the additional note when both are present', async () => {
|
|
283
|
+
const { result } = await executeAsk({
|
|
284
|
+
decision: {
|
|
285
|
+
action: 'answer_question',
|
|
286
|
+
selectedOptionIds: ['opt_2'],
|
|
287
|
+
freeText: 'Keep it under 10 slides',
|
|
288
|
+
},
|
|
289
|
+
})
|
|
290
|
+
expect(result.success).toBe(true)
|
|
291
|
+
expect(result.output).toBe(
|
|
292
|
+
'User answered "Who is the audience?": "Engineering team"\n' +
|
|
293
|
+
'Additional note from the user: "Keep it under 10 slides"',
|
|
294
|
+
)
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
it('maps an empty answer to the explicit no-answer sentinel (answered: false, not consent)', async () => {
|
|
298
|
+
const { result } = await executeAsk({
|
|
299
|
+
decision: { action: 'answer_question', selectedOptionIds: [] },
|
|
300
|
+
})
|
|
301
|
+
expect(result.success).toBe(true)
|
|
302
|
+
expect(result.output).toBe(NO_ANSWER_SENTINEL)
|
|
303
|
+
expect(result.data).toEqual({ question: 'Who is the audience?', answered: false })
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('treats whitespace-only free text as no answer', async () => {
|
|
307
|
+
const { result } = await executeAsk({
|
|
308
|
+
decision: { action: 'answer_question', selectedOptionIds: [], freeText: ' ' },
|
|
309
|
+
})
|
|
310
|
+
expect(result.output).toBe(NO_ANSWER_SENTINEL)
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
it('drops unknown option ids; all-unknown degrades to the sentinel', async () => {
|
|
314
|
+
const { result } = await executeAsk({
|
|
315
|
+
decision: { action: 'answer_question', selectedOptionIds: ['opt_9'] },
|
|
316
|
+
})
|
|
317
|
+
expect(result.output).toBe(NO_ANSWER_SENTINEL)
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('maps abort to a declined, turn-ending result', async () => {
|
|
321
|
+
const { result } = await executeAsk({
|
|
322
|
+
decision: { action: 'abort', reason: 'user dismissed' },
|
|
323
|
+
})
|
|
324
|
+
expect(result.success).toBe(false)
|
|
325
|
+
expect(result.output).toBe(
|
|
326
|
+
'The user declined to answer and asked to stop. Acknowledge briefly and end your turn.',
|
|
327
|
+
)
|
|
328
|
+
expect(result.data).toEqual({
|
|
329
|
+
question: 'Who is the audience?',
|
|
330
|
+
answered: false,
|
|
331
|
+
declined: true,
|
|
332
|
+
})
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
it('converts misdirected legacy decisions to the sentinel (rolling-deploy guard)', async () => {
|
|
336
|
+
for (const decision of [
|
|
337
|
+
{ action: 'continue' },
|
|
338
|
+
{ action: 'approve_tools' },
|
|
339
|
+
{ action: 'approve_plan' },
|
|
340
|
+
] as const) {
|
|
341
|
+
const { result } = await executeAsk({ decision })
|
|
342
|
+
expect(result.success).toBe(true)
|
|
343
|
+
expect(result.output).toBe(NO_ANSWER_SENTINEL)
|
|
344
|
+
}
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
it('degrades a questionId mismatch to the sentinel — never fabricates against the wrong question', async () => {
|
|
348
|
+
const { result } = await executeAsk({
|
|
349
|
+
decision: {
|
|
350
|
+
action: 'answer_question',
|
|
351
|
+
selectedOptionIds: ['opt_1'],
|
|
352
|
+
questionId: 'toolu_some_other_question',
|
|
353
|
+
},
|
|
354
|
+
})
|
|
355
|
+
expect(result.success).toBe(true)
|
|
356
|
+
expect(result.output).toBe(NO_ANSWER_SENTINEL)
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
it('accepts the answer when questionId matches the asking toolUseId', async () => {
|
|
360
|
+
const { result } = await executeAsk({
|
|
361
|
+
decision: {
|
|
362
|
+
action: 'answer_question',
|
|
363
|
+
selectedOptionIds: ['opt_1'],
|
|
364
|
+
questionId: TOOL_USE_ID,
|
|
365
|
+
},
|
|
366
|
+
})
|
|
367
|
+
expect(result.success).toBe(true)
|
|
368
|
+
expect(result.output).toBe('User answered "Who is the audience?": "Board"')
|
|
369
|
+
})
|
|
370
|
+
})
|