Merge remote-tracking branch 'origin/master' into pr-265
# Conflicts: # docs/config-catalog.md # docs/cordis-catalog/services.md # docs/rfc/INDEX.md # examples/acp-agent/tests/snapshots/both-mode-turn/session.jsonl # examples/acp-agent/tests/snapshots/skill-load/session.jsonl # examples/acp-agent/tests/snapshots/text-turn/session.jsonl # examples/sandbox-acp-agent/tests/snapshots/escalation-approved/session.jsonl # examples/sandbox-acp-agent/tests/snapshots/escalation-rejected/session.jsonl # packages/core/agent-loop/README.md # packages/core/agent-loop/src/loop.ts # packages/core/tools/README.md # packages/core/tools/src/index.ts # packages/core/tools/src/schema.ts # packages/ui/acp/src/index.ts # packages/ui/stdio-agent/README.md
This commit is contained in:
@@ -11,15 +11,9 @@ import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
|
||||
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
|
||||
|
||||
/**
|
||||
* Shared harness for the fs-tools with-key e2e: a minimal real agent stack (the
|
||||
* DeepSeek adapter + the real fs provider + the read-before-write/edit policy +
|
||||
* the model-facing read/write/edit tools). Lives outside the *.e2e.ts pattern so
|
||||
* importing it never re-registers another file's tests.
|
||||
*
|
||||
* `fsCwd` is the local backend's default base; a per-session cwd (set via a
|
||||
* session header) overrides it, but this harness creates agents without a
|
||||
* session cwd, so the provider default IS the workspace. `persona` is the
|
||||
* deployment persona (the system-prompt plugin's per-context config).
|
||||
* Build the real fs-tool stack for with-key e2e tests. Agents have no session
|
||||
* cwd, so `fsCwd` is their workspace; `persona` configures the deployment prompt.
|
||||
* This helper lives outside the e2e glob so imports do not register tests.
|
||||
*/
|
||||
export async function fsHarness(fsCwd: string, persona = ''): Promise<Context> {
|
||||
const ctx = new Context()
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
/**
|
||||
* Integration tests: the real local backend (`dsh-fs-local`) plus the model
|
||||
* tools (`dsh-tool-fs`) as the executor, exercised through `ctx.tools.execute()`
|
||||
* so nothing bypasses the tool registry. Two deployments:
|
||||
*
|
||||
* - DEFAULT — with the real `dsh-fs-policy` policy gate plugin: read-before-
|
||||
* write/edit, version-guarded mutation, FS_NOT_OBSERVED for unread edits.
|
||||
* - BARE — WITHOUT the policy plugin: every `fs/*` waterfall falls through to
|
||||
* its undefined default, so write/edit are unconditional. This proves the
|
||||
* tool carries no dependency on the policy plugin.
|
||||
*
|
||||
* These verify the WORLD — files are read back from disk and asserted
|
||||
* byte-for-byte — not the tool's self-report.
|
||||
* End-to-end tool-registry tests against the real local backend. The policy deployment verifies
|
||||
* observed-state and guarded mutation; the bare deployment proves unconditional tools have no
|
||||
* policy-service dependency. Assertions read files back byte-for-byte rather than trusting tool
|
||||
* messages.
|
||||
*/
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
@@ -28,9 +20,7 @@ import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
|
||||
let dir: string
|
||||
let ctx: Context
|
||||
let fiber: Awaited<ReturnType<Context['plugin']>>
|
||||
// A stable session object stands in for an agent session (the file-state
|
||||
// owner). It carries a `header` (no `cwd`) so `sessionCwd(exec)` resolves to
|
||||
// `undefined` and the backend falls back to its configured cwd (= `dir`).
|
||||
// No header cwd: sessionCwd returns undefined and the provider's configured test dir applies.
|
||||
const session = { header: {} }
|
||||
|
||||
let callCounter = 0
|
||||
@@ -290,13 +280,9 @@ describe('bare provider (no dsh-fs-policy)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Per-session cwd: a relative file_path resolves against the CALLING session's
|
||||
// workspace (`exec.agent.session.header.cwd`), NOT the backend's config.cwd —
|
||||
// so an ACP editor's per-session dir wins, matching dsh-tool-bash. The regression
|
||||
// this guards: before the seam fix the tool passed no cwd, so a relative write
|
||||
// landed in config.cwd instead of the session dir.
|
||||
// --------------------------------------------------------------------------
|
||||
// Per-session cwd: a relative file_path resolves against the calling session's workspace
|
||||
// (`exec.agent.session.header.cwd`), not the backend's config.cwd — so an ACP editor's
|
||||
// per-session dir wins, matching dsh-tool-bash.
|
||||
describe('per-session cwd', () => {
|
||||
let sessionDir: string
|
||||
beforeEach(async () => {
|
||||
@@ -428,9 +414,8 @@ describe('signal, concurrency, and the fs/observed contract', () => {
|
||||
})
|
||||
|
||||
it('a throwing fs/observed listener surfaces as isError, but the mutation already hit disk', async () => {
|
||||
// fs/observed is a plain ctx.emit AFTER the write succeeded; a throwing
|
||||
// listener cannot roll the write back — it only turns the tool result into
|
||||
// isError. The file must still carry the written bytes.
|
||||
// fs/observed is a plain ctx.emit after the write succeeded; a throwing listener cannot
|
||||
// roll the write back — it only turns the tool result into isError.
|
||||
ctx.on('fs/observed', () => { throw new Error('recording bug') })
|
||||
const result = await callOwned('write', { file_path: 'w.txt', content: 'durable' })
|
||||
expect(result.isError).toBe(true)
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
/**
|
||||
* Consumer-surface tests for the filesystem tools as the EXECUTOR. They run the
|
||||
* REAL `@deepseek-ai/dsh-fs-policy` gate plugin (the genuine policy
|
||||
* collaborator, per the prefer-the-real-implementation rule) over a fake
|
||||
* `ctx.fs` provider, so they verify schemas, argument validation, result
|
||||
* formatting, FsError→isError propagation, and that each tool dispatches the
|
||||
* `fs/*` waterfalls + records observed-state through the gate (read authorizes a
|
||||
* later edit) — not just that it moved bytes.
|
||||
* Consumer-surface tests over a fake provider and the real policy collaborator: schemas,
|
||||
* validation, formatting, typed errors, intent dispatch, and observation-driven authorization.
|
||||
*/
|
||||
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
@@ -419,9 +414,8 @@ describe('tool-owned presentation (pure presentCall)', () => {
|
||||
})
|
||||
|
||||
describe('result-time contextual diff (meta + presentResult)', () => {
|
||||
// An edit records the applied contextual hunk on `tool/result` meta, and the
|
||||
// tool's presentResult narrows it back into a `diff` result card the bridge
|
||||
// renders. Drive execute end-to-end so the meta is the REAL computed hunk.
|
||||
// An edit records the applied contextual hunk on `tool/result` meta, and the tool's
|
||||
// presentResult narrows it back into a `diff` result card the bridge renders.
|
||||
const withContext = 'a\nb\nc\nOLD\nd\ne\nf\n'
|
||||
|
||||
it('edit: execute attaches the applied hunk as meta { diffs }', async () => {
|
||||
@@ -462,10 +456,9 @@ describe('result-time contextual diff (meta + presentResult)', () => {
|
||||
})
|
||||
|
||||
it('write CREATE: no before-version → no meta, but presentResult still renders a whole-file diff card', async () => {
|
||||
// A create has no prior content (no `meta`), yet the completed card must be a
|
||||
// `diff` — an ACP tool_call_update.content REPLACES the call's content, so a
|
||||
// non-diff result would clobber the pending new-file diff. The whole-file diff
|
||||
// is derived from the args (oldText:null), replay-safe.
|
||||
// A create has no prior content (no `meta`), yet the completed card must be a `diff` — an
|
||||
// ACP tool_call_update.content REPLACES the call's content, so a non-diff result would
|
||||
// clobber the pending new-file diff.
|
||||
const { ctx } = await setup()
|
||||
const session = { header: {} }
|
||||
const result = await call(ctx, 'write', { file_path: 'new.txt', content: 'fresh\n' }, { session })
|
||||
|
||||
Reference in New Issue
Block a user