mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-14 04:01:35 +00:00
fix(boot): stabilize profile module fallbacks
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>)
|
||||
|
||||
Vendored
-194
@@ -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())
|
||||
}
|
||||
@@ -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,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'
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user