Package the portable DSH profiles, local plugins, desktop integration, Firefox wrapper, and ownership-aware installer lifecycle. Bundle checksum-pinned OpenVSCode, editor extensions, translation assets, and cloudflared so unreliable upstream artifact downloads cannot break a clean installation.
250 lines
12 KiB
JavaScript
250 lines
12 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import { execFile } from 'node:child_process'
|
|
import { mkdtemp, mkdir, readFile, realpath, rm, writeFile } from 'node:fs/promises'
|
|
import { createRequire } from 'node:module'
|
|
import { homedir, tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { promisify } from 'node:util'
|
|
import test from 'node:test'
|
|
import { TouchedGitStore } from '../lib/index.js'
|
|
import { registerGitWriteGuard } from '../lib/git-write-guard.js'
|
|
|
|
const installedRuntime = createRequire(process.env.DSH_RUNTIME_PACKAGE_JSON || join(homedir(), '.local/lib/node_modules/@deepseek-ai/dsh/package.json'))
|
|
const { Context } = await import(installedRuntime.resolve('@deepseek-ai/cordis'))
|
|
const { SystemPrompt } = await import(installedRuntime.resolve('@deepseek-ai/dsh-system-prompt'))
|
|
const { ToolRuntime, defineTool } = await import(installedRuntime.resolve('@deepseek-ai/dsh-tools'))
|
|
const { LocalFileSystem } = await import(installedRuntime.resolve('@deepseek-ai/dsh-fs-local'))
|
|
const { apply: applyObservationPolicy } = await import(installedRuntime.resolve('@deepseek-ai/dsh-fs-observation-policy'))
|
|
const execute = promisify(execFile)
|
|
|
|
const mutationOutput = {
|
|
type: 'object',
|
|
additionalProperties: false,
|
|
properties: {
|
|
path: { type: 'string', required: true },
|
|
before: { required: true, oneOf: [{ type: 'string' }, { type: 'null' }] },
|
|
after: { type: 'string', required: true },
|
|
},
|
|
}
|
|
|
|
function registerFixtureMutation(context, name, counters) {
|
|
const parameters = name === 'write'
|
|
? { content: { type: 'string', required: true } }
|
|
: { old_string: { type: 'string', required: true }, new_string: { type: 'string', required: true } }
|
|
context.tools.register(defineTool({
|
|
name,
|
|
description: `Integration fixture ${name} using the real filesystem provider.`,
|
|
parameters: { file_path: { type: 'string', required: true }, ...parameters },
|
|
output: {
|
|
schema: mutationOutput,
|
|
render: (_args, value) => [{ type: 'text', text: `${name} completed: ${value.path}` }],
|
|
},
|
|
async execute(args, execution) {
|
|
counters.bodies[name]++
|
|
const target = await context.fs.resolve(args.file_path, { cwd: execution.agent.session.header.cwd, signal: execution.signal })
|
|
const intent = await context.waterfall(`fs/${name}-intent`, target, execution, () => undefined)
|
|
const outcome = name === 'write'
|
|
? await context.fs.writeText(target, args.content, intent, execution.signal)
|
|
: await context.fs.editText(target, { oldString: args.old_string, newString: args.new_string, replaceAll: false }, intent, execution.signal)
|
|
counters.mutations[name]++
|
|
context.emit('fs/observed', target, { kind: 'present', version: outcome.version }, execution)
|
|
return { path: target.displayPath, before: outcome.before, after: outcome.after }
|
|
},
|
|
}))
|
|
}
|
|
|
|
function registerFixtureRead(context) {
|
|
context.tools.register(defineTool({
|
|
name: 'read',
|
|
description: 'Integration fixture read that records the provider observation.',
|
|
parameters: { file_path: { type: 'string', required: true } },
|
|
output: {
|
|
schema: {
|
|
type: 'object', additionalProperties: false,
|
|
properties: { text: { type: 'string', required: true } },
|
|
},
|
|
render: (_args, value) => [{ type: 'text', text: value.text }],
|
|
},
|
|
async execute(args, execution) {
|
|
const target = await context.fs.resolve(args.file_path, { cwd: execution.agent.session.header.cwd, signal: execution.signal })
|
|
const text = await context.fs.readText(target, execution.signal)
|
|
const info = await context.fs.stat(target, execution.signal)
|
|
context.emit('fs/observed', target, { kind: 'present', version: info.version }, execution)
|
|
return { text }
|
|
},
|
|
}))
|
|
}
|
|
|
|
async function fixture(t, options = {}) {
|
|
const root = await realpath(await mkdtemp(join(tmpdir(), 'dsh-git-runtime-')))
|
|
const context = new Context()
|
|
let disposeGuard
|
|
t.after(async () => {
|
|
disposeGuard?.()
|
|
await context.fiber.dispose()
|
|
await rm(root, { recursive: true, force: true })
|
|
})
|
|
const repository = join(root, 'repository')
|
|
await mkdir(repository)
|
|
await execute('git', ['init', '--quiet', repository])
|
|
const store = new TouchedGitStore(join(root, 'store.json'))
|
|
await store.ready
|
|
new SystemPrompt(context, { includeHarnessIdentity: false })
|
|
new ToolRuntime(context)
|
|
new LocalFileSystem(context, { cwd: root, diffBasisMaxBytes: 1024 * 1024 })
|
|
applyObservationPolicy(context)
|
|
const counters = { bodies: { write: 0, edit: 0 }, mutations: { write: 0, edit: 0 } }
|
|
registerFixtureMutation(context, 'write', counters)
|
|
registerFixtureMutation(context, 'edit', counters)
|
|
registerFixtureRead(context)
|
|
const recorded = []
|
|
disposeGuard = registerGitWriteGuard(context, {
|
|
store,
|
|
onSuccessfulMutation: options.onSuccessfulMutation ?? (async mutation => { recorded.push(mutation) }),
|
|
})
|
|
const agent = { session: { header: { id: 'integration-session', cwd: repository } } }
|
|
let callNumber = 0
|
|
|
|
return {
|
|
root, repository, context, store, counters, recorded, agent,
|
|
async register() {
|
|
await store.upsert(agent.session.header.id, { id: repository, path: repository, workingDirectory: repository, repository, ok: true })
|
|
},
|
|
run(name, args, extra = {}) {
|
|
return context.tools.execute({ name, arguments: args, callId: `integration-call-${++callNumber}`, agent, signal: new AbortController().signal, ...extra })
|
|
},
|
|
}
|
|
}
|
|
|
|
function mutationArgs(name, filePath) {
|
|
return name === 'write'
|
|
? { file_path: filePath, content: 'updated content' }
|
|
: { file_path: filePath, old_string: 'original', new_string: 'updated' }
|
|
}
|
|
|
|
function resultText(result) {
|
|
return result.content.filter(block => block.type === 'text').map(block => block.text).join('\n')
|
|
}
|
|
|
|
for (const name of ['write', 'edit']) {
|
|
test(`actual registry denies unregistered ${name} before entering its body or changing the file`, async t => {
|
|
const world = await fixture(t)
|
|
const target = join(world.repository, 'existing.txt')
|
|
await writeFile(target, 'original content')
|
|
const result = await world.run(name, mutationArgs(name, target))
|
|
assert.equal(result.isError, true)
|
|
assert.match(result.error.message, /not registered/)
|
|
assert.equal(Object.hasOwn(result, 'value'), false)
|
|
assert.equal(world.counters.bodies[name], 0)
|
|
assert.equal(world.counters.mutations[name], 0)
|
|
assert.equal(await readFile(target, 'utf8'), 'original content')
|
|
assert.equal(world.recorded.length, 0)
|
|
assert.ok(Object.isFrozen(result))
|
|
})
|
|
|
|
test(`actual registry accepts registered observed ${name} and publishes a canonical successful result`, async t => {
|
|
const world = await fixture(t)
|
|
await world.register()
|
|
const target = join(world.repository, 'existing.txt')
|
|
await writeFile(target, 'original content')
|
|
const observed = await world.run('read', { file_path: target })
|
|
assert.equal(observed.isError, false)
|
|
const result = await world.run(name, mutationArgs(name, target))
|
|
assert.equal(result.isError, false)
|
|
assert.equal(await readFile(target, 'utf8'), 'updated content')
|
|
assert.equal(world.counters.bodies[name], 1)
|
|
assert.equal(world.counters.mutations[name], 1)
|
|
assert.deepEqual(result.value, { path: target, before: 'original content', after: 'updated content' })
|
|
assert.ok(Object.isFrozen(result.value))
|
|
assert.equal(world.recorded.length, 1)
|
|
assert.equal(world.recorded[0].repository, world.repository)
|
|
assert.equal(world.recorded[0].toolName, name)
|
|
assert.equal(world.recorded[0].sessionId, world.agent.session.header.id)
|
|
})
|
|
|
|
test(`actual registry preserves outside-Git ${name} success and appends a warning without altering output schema`, async t => {
|
|
const world = await fixture(t)
|
|
const target = join(world.root, 'outside.txt')
|
|
await writeFile(target, 'original content')
|
|
await world.run('read', { file_path: target })
|
|
const result = await world.run(name, mutationArgs(name, target))
|
|
assert.equal(result.isError, false)
|
|
assert.equal(await readFile(target, 'utf8'), 'updated content')
|
|
assert.equal(world.counters.mutations[name], 1)
|
|
assert.match(resultText(result), /WARNING:.*outside a Git repository/)
|
|
assert.deepEqual(Object.keys(result.value).sort(), ['after', 'before', 'path'])
|
|
assert.equal(world.recorded[0].repository, null)
|
|
assert.match(world.recorded[0].warning, /outside a Git repository/)
|
|
})
|
|
|
|
test(`actual monotonic guard blocks ${name} when an outer pre listener skips classification`, async t => {
|
|
const world = await fixture(t)
|
|
await world.register()
|
|
const target = join(world.repository, 'existing.txt')
|
|
await writeFile(target, 'original content')
|
|
await world.run('read', { file_path: target })
|
|
world.context.on('tools/pre-execute', async (execution, next) => execution.name === name ? { kind: 'allow' } : next(), true)
|
|
const result = await world.run(name, mutationArgs(name, target))
|
|
assert.equal(result.isError, true)
|
|
assert.match(result.error.message, /pre-execution check did not complete/)
|
|
assert.equal(world.counters.bodies[name], 0)
|
|
assert.equal(world.counters.mutations[name], 0)
|
|
assert.equal(await readFile(target, 'utf8'), 'original content')
|
|
})
|
|
|
|
test(`real observation policy still rejects unread ${name} through the prepended Git intent hook`, async t => {
|
|
const world = await fixture(t)
|
|
await world.register()
|
|
const target = join(world.repository, 'existing.txt')
|
|
await writeFile(target, 'original content')
|
|
const unread = await world.run(name, mutationArgs(name, target))
|
|
assert.equal(unread.isError, true)
|
|
assert.equal(unread.error.info.code, 'FS_NOT_OBSERVED')
|
|
assert.equal(world.counters.bodies[name], 1)
|
|
assert.equal(world.counters.mutations[name], 0)
|
|
assert.equal(world.recorded.length, 0)
|
|
assert.equal(await readFile(target, 'utf8'), 'original content')
|
|
await world.run('read', { file_path: target })
|
|
const observed = await world.run(name, mutationArgs(name, target))
|
|
assert.equal(observed.isError, false)
|
|
assert.equal(world.counters.mutations[name], 1)
|
|
assert.equal(await readFile(target, 'utf8'), 'updated content')
|
|
})
|
|
}
|
|
|
|
test('actual registry creates a new registered file without requiring a prior read', async t => {
|
|
const world = await fixture(t)
|
|
await world.register()
|
|
const target = join(world.repository, 'new-directory', 'new.txt')
|
|
const result = await world.run('write', mutationArgs('write', target))
|
|
assert.equal(result.isError, false)
|
|
assert.equal(result.value.before, null)
|
|
assert.equal(await readFile(target, 'utf8'), 'updated content')
|
|
})
|
|
|
|
test('actual registry materializes warning contexts when downstream post policy replaces the canonical value', async t => {
|
|
const world = await fixture(t)
|
|
const target = join(world.root, 'outside.txt')
|
|
world.context.on('tools/post-execute', async (execution, result, next) => execution.name === 'write' && !result.isError
|
|
? { kind: 'accept', value: { ...result.value, after: 'policy-projected value' } }
|
|
: next())
|
|
const result = await world.run('write', mutationArgs('write', target))
|
|
assert.equal(result.isError, false)
|
|
assert.equal(result.value.after, 'policy-projected value')
|
|
assert.equal(await readFile(target, 'utf8'), 'updated content')
|
|
assert.equal(result.additionalContexts.length, 1)
|
|
assert.equal(result.additionalContexts[0].role, 'user')
|
|
assert.equal(result.additionalContexts[0].source.plugin, 'dsh-touched-git')
|
|
assert.match(result.additionalContexts[0].content[0].text, /outside a Git repository/)
|
|
assert.ok(Object.isFrozen(result.additionalContexts[0]))
|
|
})
|
|
|
|
test('actual successful mutation is not falsely rejected when persistence callback fails', async t => {
|
|
const world = await fixture(t, { onSuccessfulMutation: async () => { throw new Error('fixture persistence failure') } })
|
|
await world.register()
|
|
const target = join(world.repository, 'new.txt')
|
|
const result = await world.run('write', mutationArgs('write', target))
|
|
assert.equal(result.isError, false)
|
|
assert.equal(await readFile(target, 'utf8'), 'updated content')
|
|
assert.match(resultText(result), /operation completed.*could not be saved: fixture persistence failure/)
|
|
})
|