Merge remote-tracking branch 'origin/master' into worktree/agent-execution-context-rfc
# Conflicts: # docs/architecture.md # docs/config-catalog.md # docs/cordis-catalog/services.md # docs/module-graph.md # examples/coding-agent/tests/code-mode.e2e.ts # packages/context/workspace-context/tests/workspace-context.spec.ts # packages/core/README.md # packages/core/agent-loop/README.md # packages/core/agent-loop/tests/agent.spec.ts # packages/core/agent-loop/tests/cancel.spec.ts # packages/core/agent-loop/tests/config-session-id.spec.ts # packages/core/agent-loop/tests/contract-regressions.spec.ts # packages/core/agent-loop/tests/coverage-edges.spec.ts # packages/core/agent-loop/tests/interception.spec.ts # packages/core/agent-loop/tests/loop.spec.ts # packages/core/agent-loop/tests/properties.spec.ts # packages/core/agent-loop/tests/request-cache.e2e.ts # packages/core/agent-loop/tests/request-reconstruction.spec.ts # packages/core/agent-loop/tests/resume.spec.ts # packages/core/agent-loop/tests/scope-lifecycle.spec.ts # packages/core/agent-loop/tests/tool-calls.spec.ts # packages/core/agent-loop/tests/tool-order.spec.ts # packages/core/agent-loop/tests/turn-stop.spec.ts # packages/workflow/workflow-workerthread/tests/workflow-workerthread.e2e.ts # website/zh-CN/api/harness/agent-loop.md
This commit is contained in:
@@ -19,6 +19,7 @@ import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
|
||||
|
||||
export const name = 'acp-demo'
|
||||
const DEFAULT_PERSISTENCE_ROOT = './.sessions'
|
||||
|
||||
/**
|
||||
* App config: the swappable per-deployment values. `provider` and `model` configure the
|
||||
@@ -70,9 +71,7 @@ export const Config: z<Config> = z.object({
|
||||
toolOrder: z.array(z.string()).default(undefined as unknown as string[]),
|
||||
tools: ToolRegistry.Config,
|
||||
dshHome: z.string(),
|
||||
// TODO(single-default-literal): share this schema default and the defensive
|
||||
// apply() fallback through one named constant while retaining both boundaries.
|
||||
persistenceRoot: z.string().default('./.sessions'),
|
||||
persistenceRoot: z.string().default(DEFAULT_PERSISTENCE_ROOT),
|
||||
workspaceContext: z.union([z.const(false), workspaceContext.Config]).required(),
|
||||
skills: agentCore.SkillConfigSchema,
|
||||
toolBash: agentCore.ToolBashConfigSchema,
|
||||
@@ -90,6 +89,6 @@ export const Config: z<Config> = z.object({
|
||||
export function apply(ctx: Context, config: Config): void {
|
||||
ctx.plugin(agentCore, agentCore.pickSpineConfig(config))
|
||||
ctx.plugin(UserInteractionService)
|
||||
ctx.plugin(SessionPersistenceJsonl, { root: config.persistenceRoot ?? './.sessions' })
|
||||
ctx.plugin(SessionPersistenceJsonl, { root: config.persistenceRoot ?? DEFAULT_PERSISTENCE_ROOT })
|
||||
ctx.plugin(acp, { provider: config.provider, model: config.model })
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ describe('dsh-acp-demo composition', () => {
|
||||
})
|
||||
|
||||
it('defaults the persistence root when omitted', async () => {
|
||||
// Exercises the `?? './.sessions'` fallback for a direct-apply caller that
|
||||
// Exercises the `DEFAULT_PERSISTENCE_ROOT` fallback for a direct-apply caller that
|
||||
// bypasses the schema's `.default(...)`: call `apply` directly (not via
|
||||
// `ctx.plugin`, which validates+defaults the config first) with no
|
||||
// persistenceRoot, so the runtime fallback is the one that fires.
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Context } from 'cordis'
|
||||
import Loader from '@cordisjs/plugin-loader'
|
||||
import { TOOL_ORDER_REST } from '@deepseek-ai/dsh-system-prompt'
|
||||
import * as agentCore from '../src/index.ts'
|
||||
import { AgentId, agentEvents, type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { agentEvents, type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import LocalFileSystem from '@deepseek-ai/dsh-fs-local'
|
||||
import { MockAdapter, textResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
|
||||
@@ -86,10 +86,10 @@ async function withIsolatedSkillHomes<T>(run: () => Promise<T>): Promise<T> {
|
||||
}
|
||||
}
|
||||
|
||||
function waitForMainIdle(ctx: Context): Promise<void> {
|
||||
function waitForIdle(ctx: Context, target: Agent): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const dispose = ctx.on('agent/status', (agent, status) => {
|
||||
if (agent.id === 'main' && status === 'idle') {
|
||||
if (agent === target && status === 'idle') {
|
||||
dispose()
|
||||
resolve()
|
||||
}
|
||||
@@ -129,17 +129,19 @@ describe('dsh-agent-spine-demo bundle', () => {
|
||||
|
||||
it('defaults the agents list to empty (no pre-created agents)', async () => {
|
||||
const ctx = await mount({ workspaceContext: false })
|
||||
expect(ctx.get('agents')?.get(AgentId('main'))).toBeUndefined()
|
||||
expect(ctx.get('agents')?.get(SessionId('main'))).toBeUndefined()
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('forwards a pre-created agent to the loop and the persona to system-prompt', async () => {
|
||||
const ctx = await mount({
|
||||
agents: [{ id: AgentId('main'), provider: 'mock', model: 'mock' }],
|
||||
agents: [{ id: SessionId('main'), provider: 'mock', model: 'mock' }],
|
||||
persona: 'You are main.',
|
||||
workspaceContext: false,
|
||||
})
|
||||
expect(ctx.get('agents')?.get(AgentId('main'))).toBeDefined()
|
||||
const agent = ctx.get('agents')?.list()[0]
|
||||
expect(agent?.id).toBe(agent?.session.id)
|
||||
expect(agent?.id).toMatch(/^main-session-/)
|
||||
const assembly = await ctx.get('systemPrompt')!.assemble()
|
||||
expect(assembly.sections.find(s => s.name === 'deployment:persona')?.text).toBe('You are main.')
|
||||
await ctx.fiber.dispose()
|
||||
@@ -147,7 +149,7 @@ describe('dsh-agent-spine-demo bundle', () => {
|
||||
|
||||
it('forwards the global maxParallelToolCalls config to agent-loop', async () => {
|
||||
const ctx = await mount({
|
||||
agents: [{ id: AgentId('main'), provider: 'mock', model: 'mock' }],
|
||||
agents: [{ id: SessionId('main'), provider: 'mock', model: 'mock' }],
|
||||
maxParallelToolCalls: 3,
|
||||
workspaceContext: false,
|
||||
})
|
||||
@@ -178,7 +180,6 @@ describe('dsh-agent-spine-demo bundle', () => {
|
||||
await ctx.plugin(LocalFileSystem, { cwd: '/' })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
const handle = await ctx.agents.create({
|
||||
agentId: AgentId('main'),
|
||||
sessionId: SessionId('main-session'),
|
||||
meta: { cwd: root },
|
||||
agentOptions: { provider: 'mock', model: 'mock' },
|
||||
@@ -186,7 +187,7 @@ describe('dsh-agent-spine-demo bundle', () => {
|
||||
const agent = handle.agent
|
||||
|
||||
agent.send([{ type: 'text', text: 'hi' }])
|
||||
await waitForMainIdle(ctx)
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
const sentText = adapter.requests[0]?.messages.map(messageText).join('\n')
|
||||
expect(sentText).toContain('hi')
|
||||
@@ -209,14 +210,13 @@ describe('dsh-agent-spine-demo bundle', () => {
|
||||
const ctx = await mount({ workspaceContext: { maxBytes: 0 } })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
const handle = await ctx.agents.create({
|
||||
agentId: AgentId('main'),
|
||||
sessionId: SessionId('main-disabled-session'),
|
||||
meta: { cwd: root },
|
||||
agentOptions: { provider: 'mock', model: 'mock' },
|
||||
})
|
||||
|
||||
handle.agent.send([{ type: 'text', text: 'hi' }])
|
||||
await waitForMainIdle(ctx)
|
||||
await waitForIdle(ctx, handle.agent)
|
||||
|
||||
expect(adapter.requests[0]?.messages).toEqual([{ role: 'user', content: [{ type: 'text', text: 'hi' }] }])
|
||||
await handle.dispose()
|
||||
@@ -299,14 +299,13 @@ describe('dsh-agent-spine-demo bundle', () => {
|
||||
content: 'body',
|
||||
})
|
||||
const handle = await ctx.agents.create({
|
||||
agentId: AgentId('main'),
|
||||
sessionId: SessionId('prefix-order-session'),
|
||||
meta: { cwd: root },
|
||||
agentOptions: { provider: 'mock', model: 'mock' },
|
||||
})
|
||||
|
||||
handle.agent.send([{ type: 'text', text: 'hi' }])
|
||||
await waitForMainIdle(ctx)
|
||||
await waitForIdle(ctx, handle.agent)
|
||||
|
||||
expect(messageText(adapter.requests[0]?.messages[0])).toContain('workspace rule before skills')
|
||||
expect(messageText(adapter.requests[0]?.messages[1])).toContain('prefix-order-skill')
|
||||
|
||||
@@ -15,7 +15,7 @@ A terminal chat always wants the same cluster, so the package owns it rather tha
|
||||
| `@deepseek-ai/dsh-session-persistence-jsonl` | durable JSONL session log under `persistenceRoot` |
|
||||
| `@deepseek-ai/dsh-user-interaction` | the human question/answer seam used by confirmation tools |
|
||||
| `@deepseek-ai/dsh-tool-ask-user` | the model-facing `ask_user_question` tool |
|
||||
| `@deepseek-ai/dsh-stdio` | the readline UI, bound to the `main` agent |
|
||||
| `@deepseek-ai/dsh-stdio` | the readline UI, bound to the exact app-owned agent/session identity and rendering it as `main` |
|
||||
|
||||
`@cordisjs/plugin-hmr` (the dev/demo edit-reload loop) is deliberately a **leaf** entry, NOT baked in here: it is a Loader-only, subprocess-only dev plugin — its constructor throws without `node --expose-internals` + a live `loader`, and the in-process test tier cannot even import it (so a package whose `apply` statically pulled it in could never carry the per-file coverage gate). Unlike the console logger, a stray `hmr` is not a stdout-purity footgun, so leaving it at the leaf costs no safety. The `demo:echo` / `demo:repl` leaves load it and pass `--expose-internals`.
|
||||
|
||||
@@ -39,7 +39,7 @@ The leaf `cordis.yml` supplies only the **swappable backends** — an LLM adapte
|
||||
| `welcome` | `ready.` | the stdin-chat banner |
|
||||
| `resumeSessionId` | — | resume a persisted session id instead of starting fresh (sourced from an env var in the leaf) |
|
||||
|
||||
Fresh stdio sessions use the process launch directory as `session.header.cwd`, so project-scoped features such as skill discovery and default bash workdir follow the directory where `dsh-stdio-demo` was started. Resumed sessions keep the cwd stored in the persisted session header.
|
||||
Fresh stdio sessions use the process launch directory as `session.header.cwd` and mint one combined `main-session-<uuid>` agent/session id, so durable restarts cannot collide. The app passes that exact opaque id to both its config-created agent and UI; an AgentLoop-only reload resumes materialized history under that id, while the UI's `main` text remains only a display label and never selects another registry root by prefix or insertion order. Readline buffers nonblank startup input for that identity until `agent/session-start`, so piped stdin cannot outrun asynchronous exact-id restoration or let EOF discard the queued prompt; `agent-loop/config-start-failed` instead drains and reports buffered input so a missing or corrupt persisted session cannot hang EOF. A resumed run binds both components to the exact `resumeSessionId` and keeps the cwd stored in the persisted session header.
|
||||
|
||||
## The bin
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"@deepseek-ai/dsh-app-boot": "^0.0.1",
|
||||
"@cordisjs/plugin-logger-console": "^1.0.0",
|
||||
"@deepseek-ai/dsh-agent": "^0.0.1",
|
||||
"@deepseek-ai/dsh-agent-loop": "^0.0.1",
|
||||
"@deepseek-ai/dsh-llm": "^0.0.1",
|
||||
"@deepseek-ai/dsh-agent-spine-demo": "^0.0.1",
|
||||
"@deepseek-ai/dsh-workspace-context": "^0.0.1",
|
||||
@@ -53,6 +54,7 @@
|
||||
"@deepseek-ai/dsh-app-boot": "workspace:^",
|
||||
"@cordisjs/plugin-logger-console": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-spine-demo": "workspace:^",
|
||||
"@deepseek-ai/dsh-system-prompt": "workspace:^",
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
* The stdio chat app: the default agent spine ({@link @deepseek-ai/dsh-agent-spine-demo}) plus the
|
||||
* coupled front-door cluster a terminal chat needs — a console logger, the independently
|
||||
* packaged readline UI, JSONL session persistence, the user-interaction seam with its
|
||||
* `ask_user_question` tool, and a pre-created `main` agent the UI drives.
|
||||
* `ask_user_question` tool, and one pre-created agent whose exact shared
|
||||
* agent/session identity the UI drives under its `main` display label.
|
||||
* Swappable adapters, executors, optional tools, and HMR stay in the leaf. This
|
||||
* Loader plugin intentionally exposes named exports only; a default export
|
||||
* would hide its `Config` schema (see docs/postmortem/0001).
|
||||
@@ -10,9 +11,9 @@
|
||||
*/
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import ConsoleExporter from '@cordisjs/plugin-logger-console'
|
||||
import z from 'schemastery'
|
||||
import { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import ToolRegistry, { type Config as ToolsConfig } from '@deepseek-ai/dsh-tools'
|
||||
import * as agentCore from '@deepseek-ai/dsh-agent-spine-demo'
|
||||
@@ -23,6 +24,8 @@ import * as toolAskUser from '@deepseek-ai/dsh-tool-ask-user'
|
||||
import * as uiStdio from '@deepseek-ai/dsh-stdio'
|
||||
|
||||
export const name = 'stdio-demo'
|
||||
const DEFAULT_PERSISTENCE_ROOT = './.sessions'
|
||||
const DEFAULT_WELCOME = 'ready.'
|
||||
|
||||
/**
|
||||
* App config: the swappable per-demo values, each routed to where the app wires
|
||||
@@ -60,7 +63,7 @@ export interface Config {
|
||||
/** Generic background-task control-tool config forwarded through agent-core. */
|
||||
toolTasks?: NonNullable<agentCore.Config['toolTasks']>
|
||||
/**
|
||||
* If set, the `main` agent RESUMES this persisted session id instead of
|
||||
* If set, the pre-created agent RESUMES this persisted session id instead of
|
||||
* starting fresh. Sourced from an env var in the leaf `cordis.yml`
|
||||
* (`resumeSessionId: !!js process.env.RESUME_SESSION_ID`).
|
||||
*/
|
||||
@@ -80,10 +83,8 @@ export const Config: z<Config> = z.object({
|
||||
toolOrder: z.array(z.string()).default(undefined as unknown as string[]),
|
||||
tools: ToolRegistry.Config,
|
||||
dshHome: z.string(),
|
||||
// TODO(single-default-literal): share these schema defaults and defensive
|
||||
// apply() fallbacks through named constants while retaining both boundaries.
|
||||
persistenceRoot: z.string().default('./.sessions'),
|
||||
welcome: z.string().default('ready.'),
|
||||
persistenceRoot: z.string().default(DEFAULT_PERSISTENCE_ROOT),
|
||||
welcome: z.string().default(DEFAULT_WELCOME),
|
||||
skills: agentCore.SkillConfigSchema,
|
||||
toolBash: agentCore.ToolBashConfigSchema,
|
||||
toolTasks: agentCore.ToolTasksConfigSchema,
|
||||
@@ -92,26 +93,32 @@ export const Config: z<Config> = z.object({
|
||||
})
|
||||
|
||||
/**
|
||||
* Compose the spine with the stdio front door. The console logger comes first
|
||||
* (infra), then the agent-spine-demo bundle pre-creating the `main` agent from this
|
||||
* app's `model`/`resumeSessionId` with the deployment `persona`, then the JSONL
|
||||
* backend, then the readline UI bound to `main`. The `hmr` dev-reload plugin is
|
||||
* a leaf concern (see the module doc), so it is not mounted here.
|
||||
* Compose the spine with the stdio front door. Console logging, persistence,
|
||||
* and user interaction mount first; the readline UI then waits on the agent
|
||||
* registry and subscribes to config-start failures before agent-core can start
|
||||
* the configured identity. The ask-user tool waits on the completed spine.
|
||||
* The `hmr` dev-reload plugin is a leaf concern (see the module doc), so it is
|
||||
* not mounted here.
|
||||
*/
|
||||
export function apply(ctx: Context, config: Config): void {
|
||||
const resumeSessionId = config.resumeSessionId === '' ? undefined : config.resumeSessionId
|
||||
const sessionId = SessionId(resumeSessionId ?? `main-session-${randomUUID()}`)
|
||||
ctx.plugin(ConsoleExporter)
|
||||
ctx.plugin(SessionPersistenceJsonl, { root: config.persistenceRoot ?? DEFAULT_PERSISTENCE_ROOT })
|
||||
ctx.plugin(UserInteractionService)
|
||||
ctx.plugin(uiStdio, {
|
||||
welcome: config.welcome ?? DEFAULT_WELCOME,
|
||||
sessionId,
|
||||
})
|
||||
ctx.plugin(agentCore, {
|
||||
...agentCore.pickSpineConfig(config),
|
||||
agents: [{
|
||||
id: AgentId('main'),
|
||||
id: SessionId('main'),
|
||||
provider: config.provider,
|
||||
model: config.model,
|
||||
cwd: process.cwd(),
|
||||
...config.resumeSessionId !== undefined ? { resumeSessionId: SessionId(config.resumeSessionId) } : {},
|
||||
...resumeSessionId === undefined ? { sessionId } : { resumeSessionId: sessionId },
|
||||
}],
|
||||
})
|
||||
ctx.plugin(SessionPersistenceJsonl, { root: config.persistenceRoot ?? './.sessions' })
|
||||
ctx.plugin(UserInteractionService)
|
||||
ctx.plugin(toolAskUser)
|
||||
ctx.plugin(uiStdio, { welcome: config.welcome ?? 'ready.', agent: 'main' })
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import { join } from 'node:path'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { Context } from 'cordis'
|
||||
import Loader from '@cordisjs/plugin-loader'
|
||||
import { AgentId, agentEvents, type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { agentEvents, type Agent } from '@deepseek-ai/dsh-agent'
|
||||
|
||||
import type { Message } from '@deepseek-ai/dsh-llm'
|
||||
import { TOOL_ORDER_REST } from '@deepseek-ai/dsh-system-prompt'
|
||||
import * as stdioAgent from '../src/index.ts'
|
||||
@@ -73,24 +74,44 @@ describe('dsh-stdio-demo app', () => {
|
||||
expect(ctx.get('sessionPersistence')).toBeDefined()
|
||||
expect(ctx.get('userInteraction')).toBeDefined()
|
||||
expect(ctx.get('tools')?.get('ask_user_question')).toBeDefined()
|
||||
// The pre-created `main` agent the UI drives.
|
||||
const agent = ctx.get('agents')?.get(AgentId('main'))
|
||||
// The sole pre-created agent the UI drives. `main` is its stable config
|
||||
// label; each fresh process mints a durable combined agent/session id.
|
||||
await expect.poll(() => ctx.get('agents')?.list()).toHaveLength(1)
|
||||
const agent = ctx.get('agents')?.list()[0]
|
||||
expect(agent).toBeDefined()
|
||||
expect(agent?.id).toBe(agent?.session.id)
|
||||
expect(agent?.id).toMatch(/^main-session-/)
|
||||
expect(agent?.session.header.cwd).toBe(process.cwd())
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('normalizes an empty resume id to a fresh exact app identity', async () => {
|
||||
const ctx = await mount({
|
||||
provider: 'mock',
|
||||
model: 'mock',
|
||||
resumeSessionId: '',
|
||||
persistenceRoot: '/tmp/dsh-stdio-agent-spec-empty-resume',
|
||||
skills: await isolatedSkillsConfig(),
|
||||
workspaceContext: false,
|
||||
})
|
||||
await expect.poll(() => ctx.get('agents')?.list()).toHaveLength(1)
|
||||
const agent = ctx.get('agents')?.list()[0]
|
||||
expect(agent?.id).toMatch(/^main-session-[0-9a-f-]{36}$/)
|
||||
expect(agent?.id).toBe(agent?.session.id)
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('defaults persistenceRoot and welcome when omitted', async () => {
|
||||
// Direct apply (NOT via ctx.plugin, which validates+defaults the config
|
||||
// first) so the runtime `?? './.sessions'` / `?? 'ready.'` fallbacks on
|
||||
// first) so the runtime `DEFAULT_PERSISTENCE_ROOT` / `DEFAULT_WELCOME` fallbacks on
|
||||
// apply()'s last two lines are the ones that fire — covering a
|
||||
// schema-bypassing direct-mount caller.
|
||||
const ctx = new Context()
|
||||
// No persona: covers the omitted-persona forwarding branch too.
|
||||
stdioAgent.apply(ctx, { provider: 'mock', model: 'mock', skills: await isolatedSkillsConfig(), workspaceContext: false })
|
||||
await new Promise(resolve => setTimeout(resolve, 80))
|
||||
await expect.poll(() => ctx.get('agents')?.list()).toHaveLength(1)
|
||||
expect(ctx.get('sessionPersistence')).toBeDefined()
|
||||
expect(ctx.get('agents')?.get(AgentId('main'))).toBeDefined()
|
||||
expect(ctx.get('agents')?.list()[0]?.id).toMatch(/^main-session-/)
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
@@ -102,7 +123,8 @@ describe('dsh-stdio-demo app', () => {
|
||||
persistenceRoot: '/tmp/dsh-stdio-demo-spec-workspace-context',
|
||||
workspaceContext: false,
|
||||
})
|
||||
expect(ctx.get('agents')?.get(AgentId('main'))).toBeDefined()
|
||||
await expect.poll(() => ctx.get('agents')?.list()).toHaveLength(1)
|
||||
expect(ctx.get('agents')?.list()[0]?.id).toMatch(/^main-session-/)
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
@@ -119,7 +141,7 @@ describe('dsh-stdio-demo app', () => {
|
||||
|
||||
it('forwards resumeSessionId onto the pre-created agent when set', async () => {
|
||||
// A resume id defers agent creation until persistence loads; with no backing
|
||||
// session the resume is contained + logged, so no `main` agent registers —
|
||||
// session the resume is contained + logged, so no agent registers —
|
||||
// the branch that maps resumeSessionId through is what this covers.
|
||||
const ctx = await mount({
|
||||
provider: 'mock',
|
||||
@@ -130,7 +152,7 @@ describe('dsh-stdio-demo app', () => {
|
||||
skills: await isolatedSkillsConfig(),
|
||||
workspaceContext: false,
|
||||
})
|
||||
expect(ctx.get('agents')?.get(AgentId('main'))).toBeUndefined()
|
||||
expect(ctx.get('agents')?.list()).toEqual([])
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user