Ver Fonte

fix(boot): stabilize profile module fallbacks

Dudu-0223 há 1 mês atrás
pai
commit
6e4fabdc1f

+ 1 - 1
.agents/notes/implemented/feature/2026-08-05-agent-teams.md

@@ -62,7 +62,7 @@ Worktree isolation is not a harness runtime behavior. A deployment or prompt may
 
 ## Testing
 
-Package tests cover identity, name and authority checks, provider selection, reserved-id persistence collisions, child-before-Lead flush ordering, durable provisioning failure and pending-inbox JSONL/SQLite reconciliation, concurrent target-local ordering, pending/history de-duplication, mailbox limits, post-flush notification, bounded disposal with in-flight creation and dispatch cancellation, failed-member cleanup, task CAS and DAG validation, write-scope warnings, wait cancellation/timeout, inbox-preserving interruption, ordinary-fork isolation, legacy-control shadowing, compact declared-schema result rendering, and scoped registration HMR at per-file 100% coverage. A keyless headless Loader snapshot assembles the real Team plugins and exercises two teammates, dependent tasks, peer delivery, waiting, completion, and Lead aggregation. A CLI product test loads the private Agent Teams profile bundle, starts `dsh run` through the real Loader and direct core front door, and verifies normal process exit with persisted Team and child logs.
+Package tests cover identity, name and authority checks, provider selection, reserved-id persistence collisions, child-before-Lead flush ordering, durable provisioning failure and pending-inbox JSONL/SQLite reconciliation, concurrent target-local ordering, pending/history de-duplication, mailbox limits, post-flush notification, bounded disposal with in-flight creation and dispatch cancellation, failed-member cleanup, task CAS and DAG validation, write-scope warnings, wait cancellation/timeout, inbox-preserving interruption, ordinary-fork isolation, legacy-control shadowing, compact declared-schema result rendering, and scoped registration HMR at per-file 100% coverage. A keyless product snapshot loads the private Agent Teams profile bundle through `dsh run` and pins its complete model-visible tool list, Team policy, and durable workflow projection for two teammates, dependent tasks, peer delivery, waiting, completion, and aggregation. A CLI e2e reuses the same deterministic adapter and verifies normal process exit with persisted Team and child logs.
 
 ## Consequences
 

+ 1 - 1
.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md

@@ -62,7 +62,7 @@ Worktree isolation 不是 harness runtime 行为。deployment 或 prompt 可以
 
 ## Testing
 
-Package test 以逐文件 100% coverage 覆盖身份、名字与权限检查、provider 选择、预留 id 持久化冲突、child-before-Lead flush 顺序、持久 provisioning 失败与 pending-inbox JSONL/SQLite 对账、target-local 并发顺序、pending/history 去重、mailbox 限额、flush 后 notification、取消在途创建与 dispatch 的有界 dispose、failed member cleanup、task CAS 与 DAG 校验、write-scope warning、wait cancel/timeout、保留 inbox 的 interrupt、普通 fork 隔离、旧 control shadowing、声明 schema 的紧凑结果渲染与 scoped registration HMR。一条 keyless headless Loader 快照会组合真实 Team 插件,并运行两个 teammate、依赖任务、peer 投递、等待、完成与 Lead 汇总。CLI 产品测试会加载私有 Agent Teams profile bundle,通过真实 Loader 与直接 core front door 启动 `dsh run`,并验证带持久 Team 与 child 日志的正常退出。
+Package test 以逐文件 100% coverage 覆盖身份、名字与权限检查、provider 选择、预留 id 持久化冲突、child-before-Lead flush 顺序、持久 provisioning 失败与 pending-inbox JSONL/SQLite 对账、target-local 并发顺序、pending/history 去重、mailbox 限额、flush 后 notification、取消在途创建与 dispatch 的有界 dispose、failed member cleanup、task CAS 与 DAG 校验、write-scope warning、wait cancel/timeout、保留 inbox 的 interrupt、普通 fork 隔离、旧 control shadowing、声明 schema 的紧凑结果渲染与 scoped registration HMR。一条 keyless 产品快照会通过 `dsh run` 加载私有 Agent Teams profile bundle,并为两个 teammate、依赖任务、peer 投递、等待、完成和汇总固定完整的面向模型工具列表、Team policy 与持久 workflow 投影。CLI e2e 会复用同一个确定性 adapter,并验证带持久 Team 与 child 日志的正常退出。
 
 ## Consequences
 

+ 3 - 1
apps/cli/tests/agent-team-headless.e2e.ts

@@ -8,7 +8,9 @@ import { resolveExampleLaunch } from '@deepseek-ai/dsh-loader-smoke'
 
 const dshBinScript = fileURLToPath(new URL('../src/bin.ts', import.meta.url))
 const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
-const fixturePlugin = pathToFileURL(fileURLToPath(new URL('./fixtures/team-llm.mjs', import.meta.url))).href
+const fixturePlugin = pathToFileURL(fileURLToPath(
+  new URL('../../../examples/headless-agent/tests/fixtures/team-llm.mjs', import.meta.url),
+)).href
 
 function records(content: string): Record<string, unknown>[] {
   return content.split('\n').filter(Boolean).map(line => JSON.parse(line) as Record<string, unknown>)

+ 0 - 194
apps/cli/tests/fixtures/team-llm.mjs

@@ -1,194 +0,0 @@
-/** Deterministic keyless Agent Teams adapter for the real `dsh run` product test. */
-
-import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
-
-let nextCall = 0
-
-function calls(messages) {
-  return messages.flatMap(message => message.role === 'assistant'
-    ? message.content.filter(block => block.type === 'tool-call').map(block => block.name)
-    : [])
-}
-
-function latestAssistantCalls(messages) {
-  const assistant = messages.findLast(message => message.role === 'assistant')
-  return assistant?.content.filter(block => block.type === 'tool-call').map(block => block.name) ?? []
-}
-
-function hasTaskAction(messages, action) {
-  return messages.some(message => message.role === 'assistant'
-    && message.content.some((block) => {
-      if (block.type !== 'tool-call' || block.name !== 'team_task_update') return false
-      try {
-        return JSON.parse(block.arguments).action === action
-      } catch {
-        return false
-      }
-    }))
-}
-
-function latestToolText(messages) {
-  const message = messages.findLast(candidate => candidate.content.some(block => block.type === 'tool-result'))
-  if (message === undefined) return ''
-  return message.content.flatMap(block => block.type === 'tool-result'
-    ? block.content.filter(item => item.type === 'text').map(item => item.text)
-    : []).join('\n')
-}
-
-function toolChunks(specs) {
-  const chunks = []
-  for (const [index, spec] of specs.entries()) {
-    const id = CallId(`team-fixture-${++nextCall}`)
-    const args = JSON.stringify(spec.args)
-    chunks.push(
-      { type: 'block-start', index, blockType: 'tool-call' },
-      { type: 'tool-call-delta', index, id, name: spec.name, argumentsDelta: args },
-      { type: 'block-end', index, block: { type: 'tool-call', id, name: spec.name, arguments: args } },
-    )
-  }
-  chunks.push(
-    { type: 'usage', usage: { inputTokens: 10, outputTokens: 5 } },
-    { type: 'finish', reason: { kind: 'tool-calls' } },
-  )
-  return chunks
-}
-
-function textChunks(text) {
-  return [
-    { type: 'block-start', index: 0, blockType: 'text' },
-    { type: 'text-delta', index: 0, text },
-    { type: 'block-end', index: 0, block: { type: 'text', text } },
-    { type: 'usage', usage: { inputTokens: 10, outputTokens: 3 } },
-    { type: 'finish', reason: { kind: 'stop' } },
-  ]
-}
-
-function researcher(messages) {
-  const names = calls(messages)
-  if (!names.includes('team_task_create')) {
-    return toolChunks([{ name: 'team_task_create', args: {
-      subject: 'Research', description: 'Collect the deterministic finding.', write_scopes: ['research'],
-    } }])
-  }
-  if (!names.includes('team_task_update')) {
-    return toolChunks([{ name: 'team_task_update', args: {
-      task_id: 'task-1', expected_revision: 1, action: 'claim',
-    } }])
-  }
-  if (!names.includes('send_message')) {
-    return toolChunks([
-      { name: 'team_task_update', args: { task_id: 'task-1', expected_revision: 2, action: 'complete' } },
-      { name: 'send_message', args: { target: 'implementer', message: 'Research complete: use the deterministic finding.' } },
-    ])
-  }
-  return textChunks('Research teammate complete.')
-}
-
-function implementer(messages) {
-  const names = calls(messages)
-  const last = latestAssistantCalls(messages)
-  const text = latestToolText(messages)
-  if (!names.includes('team_task_create')) {
-    if (last.includes('team_task_get') && text.includes('"subject":"Research"')) {
-      return toolChunks([{ name: 'team_task_create', args: {
-        subject: 'Implementation',
-        description: 'Apply the deterministic finding.',
-        blocked_by: ['task-1'],
-        write_scopes: ['implementation'],
-      } }])
-    }
-    if (last.includes('wait_agent')) {
-      return toolChunks([{ name: 'team_task_get', args: { task_id: 'task-1' } }])
-    }
-    return toolChunks([{ name: 'wait_agent', args: { timeout_ms: 10000 } }])
-  }
-  if (!hasTaskAction(messages, 'claim')) {
-    if (last.includes('team_task_get') && text.includes('"status":"completed"')) {
-      return toolChunks([{ name: 'team_task_update', args: {
-        task_id: 'task-2', expected_revision: 1, action: 'claim',
-      } }])
-    }
-    if (last.includes('team_task_get')) {
-      return toolChunks([{ name: 'team_task_get', args: { task_id: 'task-1' } }])
-    }
-    if (last.includes('wait_agent')) {
-      return toolChunks([{ name: 'team_task_get', args: { task_id: 'task-1' } }])
-    }
-    return toolChunks([{ name: 'team_task_get', args: { task_id: 'task-1' } }])
-  }
-  if (!names.includes('send_message')) {
-    return toolChunks([
-      { name: 'team_task_update', args: { task_id: 'task-2', expected_revision: 2, action: 'complete' } },
-      { name: 'send_message', args: { target: 'lead', message: 'Implementation complete and verified.' } },
-    ])
-  }
-  return textChunks('Implementation teammate complete.')
-}
-
-function lead(messages) {
-  const names = calls(messages)
-  const last = latestAssistantCalls(messages)
-  const spawned = names.filter(name => name === 'spawn_teammate').length
-  if (spawned === 0) {
-    return toolChunks([{
-      name: 'spawn_teammate',
-      args: {
-        name: 'implementer',
-        description: 'Own deterministic implementation.',
-        prompt: 'IMPLEMENTER_MARK: wait for research, complete dependent task 2, report to lead.',
-        context: 'fresh',
-      },
-    }])
-  }
-  if (spawned === 1) {
-    return toolChunks([{
-      name: 'spawn_teammate',
-      args: {
-        name: 'researcher',
-        description: 'Own deterministic research.',
-        prompt: 'RESEARCHER_MARK: complete research task 1, message implementer, then finish.',
-        context: 'fresh',
-      },
-    }])
-  }
-  const result = latestToolText(messages)
-  if (last.includes('team_task_list')) {
-    const completed = result.match(/"status":"completed"/gu)?.length ?? 0
-    if (completed >= 2) return toolChunks([{ name: 'list_agents', args: {} }])
-    return toolChunks([{ name: 'team_task_list', args: {} }])
-  }
-  if (last.includes('list_agents')) {
-    const inactive = result.match(/"status":"inactive"/gu)?.length ?? 0
-    if (inactive >= 2) return textChunks('TEAM_WORKFLOW_OK: both teammates and dependent tasks completed.')
-    return toolChunks([{ name: 'list_agents', args: {} }])
-  }
-  if (last.includes('wait_agent')) return toolChunks([{ name: 'team_task_list', args: {} }])
-  return toolChunks([{ name: 'wait_agent', args: { timeout_ms: 10000 } }])
-}
-
-class TeamFixtureAdapter extends LlmAdapter {
-  async * stream(options) {
-    const userText = options.messages.flatMap(message => message.role === 'user'
-      ? message.content.filter(block => block.type === 'text').map(block => block.text)
-      : []).join('\n')
-    const chunks = userText.includes('RESEARCHER_MARK')
-      ? researcher(options.messages)
-      : userText.includes('IMPLEMENTER_MARK')
-        ? implementer(options.messages)
-        : lead(options.messages)
-    for (const chunk of chunks) {
-      options.signal?.throwIfAborted()
-      yield chunk
-    }
-  }
-}
-
-/** Cordis plugin name. */
-export const name = 'team-fixture-llm'
-/** LLM registry dependency. */
-export const inject = ['llm']
-
-/** Register the keyless adapter on the shipped default provider route. */
-export function apply(ctx) {
-  ctx.llm.registerAdapter(['deepseek-official'], new TeamFixtureAdapter())
-}

+ 6 - 36
apps/cli/tests/profiles/headless/team.cordis.snapshot.yml

@@ -1,36 +1,6 @@
-# Keyless Agent Teams composition over the real headless app and deterministic fixture adapter.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
-  config:
-    path: ./cordis.yml
-    patches:
-      - id: llm-deepseek
-        name: '@deepseek-ai/dsh-llm-deepseek'
-        disabled: true
-      - id: tool-subagent-control
-        name: '@deepseek-ai/dsh-tool-subagent-control'
-        disabled: true
-      - id: tool-subagent-report
-        name: '@deepseek-ai/dsh-tool-subagent-report'
-        disabled: true
-      - id: tool-subagent
-        name: '@deepseek-ai/dsh-tool-subagent'
-        config:
-          provider: spawn
-          toolName: subagent
-          backgroundMode: one-shot
-          maxDepth: 1
-      - id: tool-subagent-fork
-        name: '@deepseek-ai/dsh-tool-subagent'
-        config:
-          provider: fork
-          toolName: subagent_fork
-          backgroundMode: one-shot
-          maxDepth: 1
-      - insert:
-          - id: agent-team
-            name: '@deepseek-ai/dsh-experimental-agent-team'
-          - id: tool-agent-team
-            name: '@deepseek-ai/dsh-experimental-tool-agent-team'
-          - id: team-fixture-llm
-            name: './tests/fixtures/team-llm.mjs'
+# Keyless overlay for the real Agent Teams profile bundle.
+- id: llm-deepseek
+  disabled: true
+- insert:
+    - id: team-fixture-llm
+      name: './tests/fixtures/team-llm.mjs'

+ 1 - 1
apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs

@@ -1,4 +1,4 @@
-/** Deterministic keyless Agent Teams adapter for the real headless Loader snapshot. */
+/** Deterministic keyless Agent Teams adapter shared by profile snapshot and CLI e2e. */
 
 import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
 

+ 0 - 1
packages/experimental/agent-team-profile/package.json

@@ -46,7 +46,6 @@
   },
   "devDependencies": {
     "@deepseek-ai/cordis-plugin-include": "workspace:^",
-    "@deepseek-ai/cordis-plugin-loader": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
     "@deepseek-ai/cordis": "workspace:^",
     "js-yaml": "^4.2.0"