fix(tasks): bound per-session background work
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/examples/acp-demo/README.md
|
||||
README.md: edc45c9857a631cef72eb41b1a98c390f112291e
|
||||
README.zh.md: c2946aa3d1feaed558408cf0921e2480c031187d
|
||||
README.md: c1a15a424d9d66b90bbec451e220198bfe0a45df
|
||||
README.zh.md: 16a8d782c8fa38fe9e2ae1e634dbc406be1cbd12
|
||||
|
||||
@@ -34,6 +34,7 @@ The app does not install commands, user interaction, session navigation, configu
|
||||
| `workspaceContext` | required | Workspace-instruction byte budget/config, or `false`. |
|
||||
| `skills` | owner defaults | Skill registry, local provider, and model-facing skill tool. |
|
||||
| `toolBash` | owner defaults | Model-facing bash tool config. |
|
||||
| `tasks` | `{ maxConcurrentTasksPerOwner: 10 }` | Process-local per-owner active-task admission. |
|
||||
| `toolTasks` | owner defaults | Generic background-task control config, or `false`. |
|
||||
| `goals` | owner defaults | Persisted same-session goal domain and model tools, or `false`. |
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ ACP(Agent Client Protocol)自动化服务器应用:默认 agent(智能
|
||||
| `workspaceContext` | 必填 | 工作区指令字节预算/配置,或 `false`。 |
|
||||
| `skills` | 拥有者默认值 | skill 注册表、本地提供方和面向模型的 skill 工具。 |
|
||||
| `toolBash` | 拥有者默认值 | 面向模型的 bash 工具配置。 |
|
||||
| `tasks` | `{ maxConcurrentTasksPerOwner: 10 }` | 进程内按 owner 限制活动任务的准入配置。 |
|
||||
| `toolTasks` | 拥有者默认值 | 通用后台任务控制配置,或 `false`。 |
|
||||
| `goals` | 拥有者默认值 | 持久化的同会话目标领域与模型工具,或 `false`。 |
|
||||
|
||||
|
||||
@@ -65,6 +65,8 @@ export interface Config {
|
||||
skills?: agentCore.SkillConfig
|
||||
/** Model-facing bash tool config forwarded through agent-core. */
|
||||
toolBash?: NonNullable<agentCore.Config['toolBash']>
|
||||
/** Process-local background-task admission config forwarded through agent-core. */
|
||||
tasks?: NonNullable<agentCore.Config['tasks']>
|
||||
/** Generic background-task controls forwarded through agent-core; set false to omit their tool surface. */
|
||||
toolTasks?: NonNullable<agentCore.Config['toolTasks']>
|
||||
/** Persisted same-session goals; owner defaults enable them, or false disables the stack and tools. */
|
||||
@@ -92,6 +94,7 @@ export const Config: z<Config> = z.object({
|
||||
workspaceContext: z.union([z.const(false), workspaceContext.Config]).required(),
|
||||
skills: agentCore.SkillConfigSchema,
|
||||
toolBash: agentCore.ToolBashConfigSchema,
|
||||
tasks: agentCore.TasksConfigSchema,
|
||||
toolTasks: z.union([z.const(false), agentCore.ToolTasksConfigSchema]),
|
||||
goals: z.union([z.const(false), agentCore.GoalConfigSchema]),
|
||||
})
|
||||
|
||||
@@ -182,6 +182,19 @@ describe('dsh-acp-demo composition', () => {
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('forwards task admission config to the bundled task provider', async () => {
|
||||
const ctx = await mount({
|
||||
provider: 'mock',
|
||||
model: 'mock',
|
||||
tasks: { maxConcurrentTasksPerOwner: 2 },
|
||||
skills: await isolatedSkillsConfig(),
|
||||
workspaceContext: false,
|
||||
})
|
||||
expect((ctx.tasks as unknown as { config: { maxConcurrentTasksPerOwner: number } })
|
||||
.config.maxConcurrentTasksPerOwner).toBe(2)
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('forwards bundled tool config into agent-core', async () => {
|
||||
const ctx = await mount({
|
||||
provider: 'mock',
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/examples/agent-spine-demo/README.md
|
||||
README.md: 5957d9a8e9218e18d5d7d0f620b6be811f2c230f
|
||||
README.zh.md: 78372240764ff3c779ea0805aedd26a00baf1768
|
||||
README.md: 789715e53038f610d1e2db79cf56f9aabd681fac
|
||||
README.zh.md: 6561dd8948d9a4763123f9aae31e6dde64c54b27
|
||||
|
||||
@@ -55,11 +55,11 @@ This applies the [Service Definition / Service provider / Consumer separation](.
|
||||
|
||||
```ts
|
||||
import type { Config } from '@deepseek-ai/dsh-agent-spine-demo'
|
||||
// { agents?, maxParallelToolCalls?, includeHarnessIdentity?, persona?, toolOrder?, tools?, dshHome?, sessionTitle?, skills?, workspaceContext, toolBash?, toolTasks?, goals?, invariants? }
|
||||
// { agents?, maxParallelToolCalls?, includeHarnessIdentity?, persona?, toolOrder?, tools?, dshHome?, sessionTitle?, skills?, workspaceContext, toolBash?, tasks?, toolTasks?, goals?, invariants? }
|
||||
// workspaceContext requires { maxBytes } or false; the other owner schemas supply defaults.
|
||||
```
|
||||
|
||||
The bundle forwards each field to the child that owns it. App packages supply any pre-created agents: headless and JSON-RPC compositions create `main`, while the ACP app creates agents on demand at `session/new`. Prompt, tool, title, skill, workspace-context, invariant, goal, and task settings retain the schemas and defaults documented by their owning packages. `pickSpineConfig()` copies only fields owned by this bundle, and conflicting `dshHome` values fail during composition.
|
||||
The bundle forwards each field to the child that owns it. App packages supply any pre-created agents: headless and JSON-RPC compositions create `main`, while the ACP app creates agents on demand at `session/new`. Prompt, tool, title, skill, workspace-context, invariant, goal, and task settings retain the schemas and defaults documented by their owning packages; `tasks.maxConcurrentTasksPerOwner` configures the local provider independently of the model-facing `toolTasks` controls. `pickSpineConfig()` copies only fields owned by this bundle, and conflicting `dshHome` values fail during composition.
|
||||
|
||||
For example, `{ invariants: { enabled: true, package_allowlist: ['^@deepseek-ai/dsh-'], package_blocklist: ['agent-loop$'] } }` keeps the package-owned companions mounted but suppresses the blocked owner. Blocklist matches override allowlist matches; see [`dsh-invariants`](../../support/invariants/README.md) for regex and lifecycle rules.
|
||||
|
||||
|
||||
@@ -55,11 +55,11 @@
|
||||
|
||||
```ts
|
||||
import type { Config } from '@deepseek-ai/dsh-agent-spine-demo'
|
||||
// { agents?, maxParallelToolCalls?, includeHarnessIdentity?, persona?, toolOrder?, tools?, dshHome?, sessionTitle?, skills?, workspaceContext, toolBash?, toolTasks?, goals?, invariants? }
|
||||
// { agents?, maxParallelToolCalls?, includeHarnessIdentity?, persona?, toolOrder?, tools?, dshHome?, sessionTitle?, skills?, workspaceContext, toolBash?, tasks?, toolTasks?, goals?, invariants? }
|
||||
// workspaceContext requires { maxBytes } or false; the other owner schemas supply defaults.
|
||||
```
|
||||
|
||||
组合包将每个字段转发给拥有它的子节点。应用包提供预创建的 agent:无头和 JSON-RPC 组合会创建 `main`,ACP 应用则在 `session/new` 按需创建 agent。提示词、工具、标题、skill、工作区上下文、不变式、目标和任务设置沿用其所属包记录的 schema 与默认值。`pickSpineConfig()` 只复制该组合包拥有的字段,`dshHome` 值冲突会在组合时失败。
|
||||
组合包将每个字段转发给拥有它的子节点。应用包提供预创建的 agent:无头和 JSON-RPC 组合会创建 `main`,ACP 应用则在 `session/new` 按需创建 agent。提示词、工具、标题、skill、工作区上下文、不变式、目标和任务设置沿用其所属包记录的 schema 与默认值;`tasks.maxConcurrentTasksPerOwner` 配置本地 Service provider,并与面向模型的 `toolTasks` 控制工具相互独立。`pickSpineConfig()` 只复制该组合包拥有的字段,`dshHome` 值冲突会在组合时失败。
|
||||
|
||||
例如,`{ invariants: { enabled: true, package_allowlist: ['^@deepseek-ai/dsh-'], package_blocklist: ['agent-loop$'] } }` 会让包拥有的配套插件保持挂载,但抑制被阻止的拥有者。Blocklist 匹配优先于 allowlist 匹配;正则表达式与生命周期规则见 [`dsh-invariants`](../../support/invariants/README.md)。
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import AgentRegistry from '@deepseek-ai/dsh-agent'
|
||||
import GoalService, { type Config as GoalDomainConfig } from '@deepseek-ai/dsh-goal'
|
||||
import * as goalSession from '@deepseek-ai/dsh-goal-session'
|
||||
import * as toolGoal from '@deepseek-ai/dsh-tool-goal'
|
||||
import LocalTaskService from '@deepseek-ai/dsh-tasks-local'
|
||||
import LocalTaskService, { type Config as TasksConfig } from '@deepseek-ai/dsh-tasks-local'
|
||||
import InvariantService, { type Config as InvariantConfig } from '@deepseek-ai/dsh-invariants'
|
||||
import * as sessionInvariant from '@deepseek-ai/dsh-session/invariant'
|
||||
import * as agentInvariant from '@deepseek-ai/dsh-agent/invariant'
|
||||
@@ -75,9 +75,10 @@ export interface GoalConfig {
|
||||
* `dshHome` to bash environment and local skill discovery, `sessionTitle` to
|
||||
* the fallback title service, `skills` to the
|
||||
* skill registry/local provider/tool consumer, `workspaceContext` to the
|
||||
* workspace-context loader, and `toolBash`/`toolTasks` to the model-facing tool
|
||||
* plugins this bundle owns. Provider adapters own their `retryPolicy`; this
|
||||
* bundle always mounts its executor.
|
||||
* workspace-context loader, `tasks` to the process-local task provider, and
|
||||
* `toolBash`/`toolTasks` to the model-facing tool plugins this bundle owns.
|
||||
* Provider adapters own their `retryPolicy`; this bundle always mounts its
|
||||
* executor.
|
||||
* `goals` opts into and configures the persisted goal domain plus its model tool
|
||||
* and same-session driver; `invariants` configures global and package-filtered
|
||||
* relational checks. Owner schemas supply defaults for optional input;
|
||||
@@ -114,6 +115,8 @@ export interface Config {
|
||||
skills?: SkillConfig
|
||||
/** Model-facing bash tool config, or false when another plugin owns `bash`. */
|
||||
toolBash?: toolBash.Config | false
|
||||
/** Process-local background-task admission config. */
|
||||
tasks?: TasksConfig
|
||||
/** Generic background-task controls; set false to keep the task service without model-facing task tools. */
|
||||
toolTasks?: toolTasks.Config | false
|
||||
/** Global enablement and package-name filters for invariant companions. */
|
||||
@@ -138,6 +141,9 @@ export const SessionTitleConfigSchema: z<SessionTitleConfig> = SessionTitleServi
|
||||
export const ToolBashConfigSchema: z<toolBash.Config | false> =
|
||||
z.union([z.const(false), toolBash.Config])
|
||||
|
||||
/** The process-local task registry schema exported for app packages that forward `tasks`. */
|
||||
export const TasksConfigSchema: z<TasksConfig> = LocalTaskService.Config
|
||||
|
||||
/** The task-control-tool config schema exported for app packages that forward `toolTasks`. */
|
||||
export const ToolTasksConfigSchema: z<toolTasks.Config> = toolTasks.Config
|
||||
|
||||
@@ -158,10 +164,11 @@ export const Config = z.intersect([
|
||||
skills: SkillConfigSchema,
|
||||
workspaceContext: z.union([z.const(false), workspaceContext.Config]).required(),
|
||||
toolBash: ToolBashConfigSchema,
|
||||
tasks: TasksConfigSchema,
|
||||
toolTasks: z.union([z.const(false), ToolTasksConfigSchema]),
|
||||
invariants: InvariantService.Config,
|
||||
goals: z.union([z.const(false), GoalConfigSchema]),
|
||||
}) as unknown as z<Pick<Config, 'tools' | 'dshHome' | 'sessionTitle' | 'skills' | 'workspaceContext' | 'toolBash' | 'toolTasks' | 'invariants' | 'goals'>>,
|
||||
}) as unknown as z<Pick<Config, 'tools' | 'dshHome' | 'sessionTitle' | 'skills' | 'workspaceContext' | 'toolBash' | 'tasks' | 'toolTasks' | 'invariants' | 'goals'>>,
|
||||
]) as unknown as z<Config>
|
||||
|
||||
/**
|
||||
@@ -181,6 +188,7 @@ export function pickSpineConfig(config: Omit<Config, 'agents'>): Omit<Config, 'a
|
||||
workspaceContext: config.workspaceContext,
|
||||
...config.skills !== undefined ? { skills: config.skills } : {},
|
||||
...config.toolBash !== undefined ? { toolBash: config.toolBash } : {},
|
||||
...config.tasks !== undefined ? { tasks: config.tasks } : {},
|
||||
...config.toolTasks !== undefined ? { toolTasks: config.toolTasks } : {},
|
||||
...config.invariants !== undefined ? { invariants: config.invariants } : {},
|
||||
...config.goals !== undefined ? { goals: config.goals } : {},
|
||||
@@ -228,7 +236,7 @@ export function apply(ctx: Context, config: Config): void {
|
||||
ctx.plugin(toolGoal, config.goals.tool ?? {})
|
||||
ctx.plugin(goalSession)
|
||||
}
|
||||
ctx.plugin(LocalTaskService)
|
||||
ctx.plugin(LocalTaskService, config.tasks ?? {})
|
||||
ctx.plugin(InvariantService, config.invariants ?? {})
|
||||
ctx.plugin(sessionInvariant)
|
||||
ctx.plugin(agentInvariant)
|
||||
|
||||
@@ -10,6 +10,7 @@ import { agentEvents, type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import LocalBashExecutor from '@deepseek-ai/dsh-bash-local'
|
||||
import LocalFileSystem from '@deepseek-ai/dsh-fs-local'
|
||||
import LocalTaskService from '@deepseek-ai/dsh-tasks-local'
|
||||
import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
|
||||
import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
|
||||
import {
|
||||
@@ -157,6 +158,7 @@ describe('dsh-agent-spine-demo bundle', () => {
|
||||
expect(ctx.get('skills')).toBeDefined()
|
||||
expect(ctx.get('agents')).toBeDefined()
|
||||
expect(ctx.get('tasks')).toBeDefined()
|
||||
expect((ctx.tasks as LocalTaskService).config.maxConcurrentTasksPerOwner).toBe(10)
|
||||
expect(ctx.get('invariants')).toBeDefined()
|
||||
expect(ctx.get('agentLoop')).toBeDefined()
|
||||
expect(ctx.get('goals')).toBeUndefined()
|
||||
@@ -300,6 +302,15 @@ describe('dsh-agent-spine-demo bundle', () => {
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('forwards task admission config to the process-local provider', async () => {
|
||||
const ctx = await mount({
|
||||
tasks: { maxConcurrentTasksPerOwner: 3 },
|
||||
workspaceContext: false,
|
||||
})
|
||||
expect((ctx.tasks as LocalTaskService).config.maxConcurrentTasksPerOwner).toBe(3)
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('tolerates a schema-bypassing direct apply (the ?? fallbacks fire)', async () => {
|
||||
// ctx.plugin validates + defaults the bundle config first; a direct apply
|
||||
// skips the schema, so the forwarding `?? []` / `?? ''` are what fire.
|
||||
@@ -716,6 +727,7 @@ describe('dsh-agent-spine-demo bundle', () => {
|
||||
workspaceContext: false as const,
|
||||
skills: { enabled: false },
|
||||
toolBash: { enableRunInBackground: false },
|
||||
tasks: { maxConcurrentTasksPerOwner: 4 },
|
||||
toolTasks: false as const,
|
||||
invariants: { enabled: false },
|
||||
}
|
||||
@@ -730,6 +742,7 @@ describe('dsh-agent-spine-demo bundle', () => {
|
||||
workspaceContext: false,
|
||||
skills: appConfig.skills,
|
||||
toolBash: appConfig.toolBash,
|
||||
tasks: appConfig.tasks,
|
||||
toolTasks: appConfig.toolTasks,
|
||||
invariants: appConfig.invariants,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user