mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
fix: harden skill discovery
This commit is contained in:
@@ -63,7 +63,16 @@ function spawnAcpAgent(cwd: string, env: NodeJS.ProcessEnv = process.env): Spawn
|
||||
const child = spawn(
|
||||
process.execPath,
|
||||
['--import', tsxLoader, binScript, configPath],
|
||||
{ cwd, env: { ...env, TSX_TSCONFIG_PATH: repoTsconfig }, stdio: ['pipe', 'pipe', 'pipe'] },
|
||||
{
|
||||
cwd,
|
||||
env: {
|
||||
...env,
|
||||
TSX_TSCONFIG_PATH: repoTsconfig,
|
||||
DSH_HOME: join(cwd, '.dsh'),
|
||||
DSH_AGENTS_HOME: join(cwd, '.agents'),
|
||||
},
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
},
|
||||
)
|
||||
const stderr: string[] = []
|
||||
child.stderr.setEncoding('utf8')
|
||||
@@ -110,7 +119,13 @@ describe('acp-agent over real stdio (no key required)', () => {
|
||||
// which this purity test never triggers). So this runs WITHOUT real creds.
|
||||
const child = spawn(process.execPath, ['--import', tsxLoader, binScript, configPath], {
|
||||
cwd: workdir,
|
||||
env: { ...process.env, DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY ?? 'sk-dummy-for-boot', TSX_TSCONFIG_PATH: repoTsconfig },
|
||||
env: {
|
||||
...process.env,
|
||||
DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY ?? 'sk-dummy-for-boot',
|
||||
TSX_TSCONFIG_PATH: repoTsconfig,
|
||||
DSH_HOME: join(workdir, '.dsh'),
|
||||
DSH_AGENTS_HOME: join(workdir, '.agents'),
|
||||
},
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
})
|
||||
const out: string[] = []
|
||||
|
||||
@@ -64,6 +64,7 @@ const SCENARIOS: Scenario[] = [
|
||||
{ name: 'tool-call-turn', hasModelTurn: true, recorded: true },
|
||||
{ name: 'fs-terminal-card', hasModelTurn: true, recorded: true },
|
||||
{ name: 'todo-plan', hasModelTurn: true, recorded: true },
|
||||
{ name: 'skill-load', hasModelTurn: true, recorded: false },
|
||||
{ name: 'workspace-edit', hasModelTurn: true, recorded: true },
|
||||
{ name: 'fs-read', hasModelTurn: true, recorded: true },
|
||||
{ name: 'fs-write', hasModelTurn: true, recorded: true },
|
||||
|
||||
@@ -155,6 +155,8 @@ export async function runScenario(input: InputScript, opts: RunOptions): Promise
|
||||
DSH_SNAPSHOT: opts.mode,
|
||||
DSH_SNAPSHOT_FILE: opts.fixtureFile,
|
||||
DSH_SNAPSHOT_SESSIONS_ROOT: sessionsRoot,
|
||||
DSH_HOME: join(cwd, '.dsh'),
|
||||
DSH_AGENTS_HOME: join(cwd, '.agents'),
|
||||
...opts.overrideFile !== undefined ? { DSH_SNAPSHOT_OVERRIDE: opts.overrideFile } : {},
|
||||
...opts.childFiles !== undefined && opts.childFiles.length > 0
|
||||
? { DSH_SNAPSHOT_CHILD_FILES: opts.childFiles.join(delimiter) }
|
||||
|
||||
7
examples/acp-agent/tests/snapshots/skill-load/input.json
Normal file
7
examples/acp-agent/tests/snapshots/skill-load/input.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"steps": [
|
||||
{ "op": "initialize" },
|
||||
{ "op": "newSession" },
|
||||
{ "op": "prompt", "text": "Load the dsh-skill-creator skill with the skill tool, then reply DONE." }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
[
|
||||
{
|
||||
"kind": "chunks",
|
||||
"chunks": [
|
||||
{ "type": "block-start", "index": 0, "blockType": "reasoning" },
|
||||
{ "type": "reasoning-delta", "index": 0, "text": "Load the requested skill." },
|
||||
{ "type": "block-start", "index": 1, "blockType": "tool-call" },
|
||||
{ "type": "tool-call-delta", "index": 1, "id": "call_skill_load", "name": "skill", "argumentsDelta": "{\"name\":\"dsh-skill-creator\"}" },
|
||||
{ "type": "block-end", "index": 0, "block": { "type": "reasoning", "text": "Load the requested skill." } },
|
||||
{ "type": "block-end", "index": 1, "block": { "type": "tool-call", "id": "call_skill_load", "name": "skill", "arguments": "{\"name\":\"dsh-skill-creator\"}" } },
|
||||
{ "type": "usage", "usage": { "inputTokens": 100, "outputTokens": 20, "cacheReadTokens": 0, "reasoningTokens": 5 } },
|
||||
{ "type": "finish", "reason": { "kind": "tool-calls" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "chunks",
|
||||
"chunks": [
|
||||
{ "type": "block-start", "index": 0, "blockType": "reasoning" },
|
||||
{ "type": "reasoning-delta", "index": 0, "text": "The skill is loaded." },
|
||||
{ "type": "block-start", "index": 1, "blockType": "text" },
|
||||
{ "type": "text-delta", "index": 1, "text": "DONE" },
|
||||
{ "type": "block-end", "index": 0, "block": { "type": "reasoning", "text": "The skill is loaded." } },
|
||||
{ "type": "block-end", "index": 1, "block": { "type": "text", "text": "DONE" } },
|
||||
{ "type": "usage", "usage": { "inputTokens": 180, "outputTokens": 10, "cacheReadTokens": 0, "reasoningTokens": 4 } },
|
||||
{ "type": "finish", "reason": { "kind": "stop" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
28
examples/acp-agent/tests/snapshots/skill-load/session.jsonl
Normal file
28
examples/acp-agent/tests/snapshots/skill-load/session.jsonl
Normal file
@@ -0,0 +1,28 @@
|
||||
{"type":"session","version":0,"id":"5ca6619f-135d-4d55-814b-35ac6524a1b2","createdAt":1783246260513,"cwd":"/var/folders/_g/59jgff8x2gqd39f5vy1wnbfc0000gn/T/acp-snap-cwd-nJAoRn"}
|
||||
{"type":"turn/start","seq":0,"time":1783246260515,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
|
||||
{"type":"user/message","seq":1,"time":1783246260516,"data":{"content":[{"type":"text","text":"Load the dsh-skill-creator skill with the skill tool, then reply DONE."}],"source":{"kind":"user"}},"surfaceOp":"append"}
|
||||
{"type":"step/start","seq":2,"time":1783246260516,"data":{"turn":1,"step":1}}
|
||||
{"type":"assistant/chunk","seq":3,"time":1783246260534,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
|
||||
{"type":"assistant/chunk","seq":4,"time":1783246260534,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"Load the requested skill."}}}
|
||||
{"type":"assistant/chunk","seq":5,"time":1783246260534,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}}
|
||||
{"type":"assistant/chunk","seq":6,"time":1783246260534,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_skill_load","name":"skill","argumentsDelta":"{\"name\":\"dsh-skill-creator\"}"}}}
|
||||
{"type":"assistant/chunk","seq":7,"time":1783246260534,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"Load the requested skill."}}}}
|
||||
{"type":"assistant/chunk","seq":8,"time":1783246260534,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_skill_load","name":"skill","arguments":"{\"name\":\"dsh-skill-creator\"}"}}}}
|
||||
{"type":"assistant/chunk","seq":9,"time":1783246260534,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":100,"outputTokens":20,"cacheReadTokens":0,"reasoningTokens":5}}}}
|
||||
{"type":"assistant/chunk","seq":10,"time":1783246260534,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
|
||||
{"type":"assistant/message","seq":11,"time":1783246260534,"data":{"turn":1,"step":1,"content":[{"type":"reasoning","text":"Load the requested skill."},{"type":"tool-call","id":"call_skill_load","name":"skill","arguments":"{\"name\":\"dsh-skill-creator\"}"}],"usage":{"inputTokens":100,"outputTokens":20,"cacheReadTokens":0,"reasoningTokens":5}},"sourceEventSeqs":[3,4,5,6,7,8,9,10],"surfaceOp":"append"}
|
||||
{"type":"tool/call","seq":12,"time":1783246260534,"data":{"turn":1,"step":1,"callId":"call_skill_load","name":"skill","arguments":"{\"name\":\"dsh-skill-creator\"}"}}
|
||||
{"type":"tool/result","seq":13,"time":1783246260535,"data":{"turn":1,"step":1,"callId":"call_skill_load","content":[{"type":"text","text":"<skill_content name=\"dsh-skill-creator\">\n# Skill: dsh-skill-creator\n\nUse this skill to write focused DeepSeek Harness skills.\n\nA skill is a directory `<name>/SKILL.md` or a flat `<name>.md` file with YAML frontmatter.\nFrontmatter must include kebab-case `name` and a concise `description` that tells the model when to load it.\nUse optional `whenToUse` for extra routing signal and `disableModelInvocation: true` for user-only skills.\nKeep the body procedural, evidence-oriented, and scoped to the workflow the skill owns.\n\nBase directory for this skill: /var/folders/_g/59jgff8x2gqd39f5vy1wnbfc0000gn/T/acp-snap-cwd-nJAoRn/.dsh/skills/.system/dsh-skill-creator\nResolve relative files mentioned by this skill against the base directory before using them.\n</skill_content>"}],"isError":false},"sourceEventSeqs":[12],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":14,"time":1783246260535,"data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","seq":15,"time":1783246260535,"data":{"turn":1,"step":2}}
|
||||
{"type":"assistant/chunk","seq":16,"time":1783246260535,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
|
||||
{"type":"assistant/chunk","seq":17,"time":1783246260535,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"The skill is loaded."}}}
|
||||
{"type":"assistant/chunk","seq":18,"time":1783246260535,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":1,"blockType":"text"}}}
|
||||
{"type":"assistant/chunk","seq":19,"time":1783246260535,"data":{"turn":1,"step":2,"chunk":{"type":"text-delta","index":1,"text":"DONE"}}}
|
||||
{"type":"assistant/chunk","seq":20,"time":1783246260535,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The skill is loaded."}}}}
|
||||
{"type":"assistant/chunk","seq":21,"time":1783246260535,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"DONE"}}}}
|
||||
{"type":"assistant/chunk","seq":22,"time":1783246260535,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":180,"outputTokens":10,"cacheReadTokens":0,"reasoningTokens":4}}}}
|
||||
{"type":"assistant/chunk","seq":23,"time":1783246260535,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
|
||||
{"type":"assistant/message","seq":24,"time":1783246260536,"data":{"turn":1,"step":2,"content":[{"type":"reasoning","text":"The skill is loaded."},{"type":"text","text":"DONE"}],"usage":{"inputTokens":180,"outputTokens":10,"cacheReadTokens":0,"reasoningTokens":4}},"sourceEventSeqs":[16,17,18,19,20,21,22,23],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":25,"time":1783246260536,"data":{"turn":1,"step":2}}
|
||||
{"type":"turn/end","seq":26,"time":1783246260536,"data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
@@ -0,0 +1,8 @@
|
||||
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentInfo":{"name":"deepseek-harness-acp","version":"0.0.1"},"agentCapabilities":{"loadSession":true,"promptCapabilities":{"image":false,"audio":false,"embeddedContext":false}},"authMethods":[]}}
|
||||
{"jsonrpc":"2.0","id":2,"result":{"sessionId":"{{sessionId}}"}}
|
||||
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":"Load the requested skill."}}}}
|
||||
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"tool_call","toolCallId":"call_skill_load","title":"Load skill dsh-skill-creator","kind":"read","status":"in_progress","rawInput":"dsh-skill-creator"}}}
|
||||
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"tool_call_update","toolCallId":"call_skill_load","status":"completed","content":[{"type":"content","content":{"type":"text","text":"<skill_content name=\"dsh-skill-creator\">\n# Skill: dsh-skill-creator\n\nUse this skill to write focused DeepSeek Harness skills.\n\nA skill is a directory `<name>/SKILL.md` or a flat `<name>.md` file with YAML frontmatter.\nFrontmatter must include kebab-case `name` and a concise `description` that tells the model when to load it.\nUse optional `whenToUse` for extra routing signal and `disableModelInvocation: true` for user-only skills.\nKeep the body procedural, evidence-oriented, and scoped to the workflow the skill owns.\n\nBase directory for this skill: {{cwd}}/.dsh/skills/.system/dsh-skill-creator\nResolve relative files mentioned by this skill against the base directory before using them.\n</skill_content>"}}]}}}
|
||||
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":"The skill is loaded."}}}}
|
||||
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"DONE"}}}}
|
||||
{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}
|
||||
@@ -62,6 +62,8 @@ async function bootAndEof(): Promise<{ stdout: string; code: number }> {
|
||||
// A dummy key so llm-deepseek's apply() (key-PRESENT check only) boots.
|
||||
// No prompt is sent, so the adapter never streams — no network call.
|
||||
DEEPSEEK_API_KEY: 'keyless-smoke-no-call',
|
||||
DSH_HOME: join(cwd, '.dsh'),
|
||||
DSH_AGENTS_HOME: join(cwd, '.agents'),
|
||||
},
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
},
|
||||
|
||||
@@ -63,7 +63,16 @@ async function runEcho(lines: string[]): Promise<{ stdout: string; code: number
|
||||
// requires it (mirrors the `demo:echo` script). The whole point is to boot
|
||||
// the example EXACTLY as it really runs, through the bin + Loader.
|
||||
['--expose-internals', '--import', tsxLoader, binScript, configPath],
|
||||
{ cwd, env: { ...process.env, TSX_TSCONFIG_PATH: repoTsconfig }, stdio: ['pipe', 'pipe', 'pipe'] },
|
||||
{
|
||||
cwd,
|
||||
env: {
|
||||
...process.env,
|
||||
TSX_TSCONFIG_PATH: repoTsconfig,
|
||||
DSH_HOME: join(cwd, '.dsh'),
|
||||
DSH_AGENTS_HOME: join(cwd, '.agents'),
|
||||
},
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
},
|
||||
)
|
||||
child = proc
|
||||
let stdout = ''
|
||||
|
||||
Reference in New Issue
Block a user