Merge remote-tracking branch 'origin/master' into worktree/agent-execution-context-rfc

# Conflicts:
#	docs/architecture.md
#	docs/config-catalog.md
#	docs/cordis-catalog/services.md
#	docs/module-graph.md
#	examples/coding-agent/tests/code-mode.e2e.ts
#	packages/context/workspace-context/tests/workspace-context.spec.ts
#	packages/core/README.md
#	packages/core/agent-loop/README.md
#	packages/core/agent-loop/tests/agent.spec.ts
#	packages/core/agent-loop/tests/cancel.spec.ts
#	packages/core/agent-loop/tests/config-session-id.spec.ts
#	packages/core/agent-loop/tests/contract-regressions.spec.ts
#	packages/core/agent-loop/tests/coverage-edges.spec.ts
#	packages/core/agent-loop/tests/interception.spec.ts
#	packages/core/agent-loop/tests/loop.spec.ts
#	packages/core/agent-loop/tests/properties.spec.ts
#	packages/core/agent-loop/tests/request-cache.e2e.ts
#	packages/core/agent-loop/tests/request-reconstruction.spec.ts
#	packages/core/agent-loop/tests/resume.spec.ts
#	packages/core/agent-loop/tests/scope-lifecycle.spec.ts
#	packages/core/agent-loop/tests/tool-calls.spec.ts
#	packages/core/agent-loop/tests/tool-order.spec.ts
#	packages/core/agent-loop/tests/turn-stop.spec.ts
#	packages/workflow/workflow-workerthread/tests/workflow-workerthread.e2e.ts
#	website/zh-CN/api/harness/agent-loop.md
This commit is contained in:
Tianyi Cui
2026-07-19 11:44:50 +08:00
295 changed files with 5544 additions and 4182 deletions

View File

@@ -257,6 +257,7 @@ describe('CreateWizard and scaffolder', () => {
expect(index).toContain('SdkBootContext')
expect(index).toContain('ctx.agents.create')
expect(index).toContain('agentOptions: { model: "deepseek-v4-flash" }')
expect(index).not.toContain('AgentId')
const tsconfig = parseGeneratedTsConfig(await readFile(join(target, 'tsconfig.base.json'), 'utf8'))
const manifest = parseGeneratedPackageManifest(await readFile(join(target, 'package.json'), 'utf8'))
expect(tsconfig.compilerOptions.types).toEqual(['node'])

View File

@@ -4,6 +4,7 @@
* @module @deepseek-ai/dsh-helper/features/builtin/app
*/
import { JsExpression } from '../../documents/cordis-yaml-file.ts'
import { featureId } from '../../ids.ts'
import type { ProjectProfile } from '../../project/types.ts'
import {
@@ -94,11 +95,11 @@ class AppOption extends FeatureOption {
name: '@deepseek-ai/dsh-stdio',
config: {
welcome: 'agent REPL ready. Give it a coding task.',
agent: 'main',
sessionId: new JsExpression('process.env.DSH_SDK_SESSION_ID'),
},
}, ['welcome', 'agent'], config => [
}, ['welcome', 'sessionId'], config => [
...optionalString(config, 'welcome'),
...requiredString(config, 'agent'),
...config.sessionId instanceof JsExpression ? [] : requiredString(config, 'sessionId'),
]),
])
case 'embed':

View File

@@ -23,7 +23,7 @@ const EXTERNAL_NPM_DEPENDENCY_SPECS: Readonly<Record<string, string>> = {
'@cordisjs/plugin-timer': '^1.1.2',
'@types/node': '^22.20.0',
cordis: '^4.0.0-rc.7',
tsdown: '^0.22.2',
tsdown: '0.22.2',
tsx: '^4.22.4',
typescript: '^6.0.3',
}

View File

@@ -2,14 +2,12 @@
import { startSDK, type SdkBootContext } from '@deepseek-ai/dsh-scripts'
{{else}}
import { randomUUID } from 'node:crypto'
import { AgentId } from '@deepseek-ai/dsh-agent'
import { SessionId } from '@deepseek-ai/dsh-session'
import { startSDK, type SdkBootContext } from '@deepseek-ai/dsh-scripts'
{{/if}}
/** Boot this project's cordis.yml when invoked by dsh-scripts. */
export async function main(boot: SdkBootContext) {
const ctx = await startSDK(new URL('./cordis.yml', import.meta.url))
{{#if isStdio}}
const model = boot.args.model
if (typeof model !== 'string' || model.length === 0) throw new Error('stdio startup requires --model=<name>')
@@ -17,24 +15,35 @@ export async function main(boot: SdkBootContext) {
if (resume !== undefined && (typeof resume !== 'string' || resume.length === 0)) {
throw new Error('stdio startup requires --resume=<session-id>')
}
if (resume === undefined) {
await ctx.agents.create({
agentId: AgentId('main'),
sessionId: SessionId(`main-session-${randomUUID()}`),
meta: { cwd: boot.cwd },
agentOptions: { model },
})
} else {
await ctx.agents.resume({
agentId: AgentId('main'),
resumeSessionId: SessionId(resume),
agentOptions: { model },
})
const sessionId = SessionId(resume ?? `main-session-${randomUUID()}`)
process.env.DSH_SDK_SESSION_ID = sessionId
{{/if}}
const ctx = await startSDK(new URL('./cordis.yml', import.meta.url))
{{#if isStdio}}
try {
if (resume === undefined) {
await ctx.agents.create({
sessionId,
meta: { cwd: boot.cwd },
agentOptions: { model },
})
} else {
await ctx.agents.resume({
resumeSessionId: sessionId,
agentOptions: { model },
})
}
} catch (error) {
try {
await ctx.fiber.dispose()
} catch (disposeError) {
throw new AggregateError([error, disposeError], 'stdio startup and cleanup failed')
}
throw error
}
{{else}}
{{#if isEmbed}}
await ctx.agents.create({
agentId: AgentId('main'),
sessionId: SessionId(`main-session-${randomUUID()}`),
meta: { cwd: boot.cwd },
agentOptions: { model: {{modelLiteral}} },

View File

@@ -282,6 +282,7 @@ describe('package manager strategies', () => {
section: 'devDependencies', spec: '^4.0.0-rc.7',
})
expect(resolveNpmDependency('@cordisjs/plugin-hmr', 'dependencies', '0.0.1').spec).toBe('^1.0.15')
expect(resolveNpmDependency('tsdown', 'devDependencies', '0.0.1').spec).toBe('0.22.2')
expect(resolveNpmDependency('@deepseek-ai/dsh-tools', 'dependencies', '1.2.3').spec).toBe('^1.2.3')
expect(() => resolveNpmDependency('unknown', 'dependencies', '0.0.1')).toThrow('no generated-project')
})

View File

@@ -167,6 +167,12 @@ describe('SdkProject and ProjectEditSession', () => {
expect(index).toContain('SdkBootContext')
expect(index).toContain('agents.create')
expect(index).toContain('boot.args.resume')
expect(index).not.toContain('AgentId')
expect(index).toContain('const sessionId = SessionId(resume ?? `main-session-${randomUUID()}`)')
expect(index).toContain('process.env.DSH_SDK_SESSION_ID = sessionId')
expect(index).toContain('resumeSessionId: sessionId')
expect(index).toContain('await ctx.fiber.dispose()')
expect(index).toContain("new AggregateError([error, disposeError], 'stdio startup and cleanup failed')")
expect(project.packageManifest().scripts).toEqual({
dev: 'dsh-sdk dev index.ts -- --model="deepseek-v4-flash"',
build: 'dsh-sdk build',
@@ -175,7 +181,11 @@ describe('SdkProject and ProjectEditSession', () => {
config: 'dsh-sdk config',
})
expect(await readFile(join(project.root, '.env.example'), 'utf8')).toContain('EXA_API_KEY=')
expect(project.cordis.entry('stdio')?.config).toMatchObject({ agent: 'main' })
expect(project.cordis.entry('stdio')?.config?.sessionId).toMatchObject({
source: 'process.env.DSH_SDK_SESSION_ID',
})
expect(await readFile(join(project.root, 'cordis.yml'), 'utf8'))
.toContain('sessionId: !!js process.env.DSH_SDK_SESSION_ID')
expect(project.cordis.entry('stdio')?.config).not.toHaveProperty('model')
expect(project.cordis.entry('agent-loop')?.config).toEqual({ agents: [] })
expect(project.cordis.entry('system-prompt')?.config?.persona).toContain('{{cwd}}')
@@ -286,7 +296,10 @@ describe('SdkProject and ProjectEditSession', () => {
const embed = (await embedEdit.commit()).project
expect(embed.profile.runInterface).toBe('embed')
expect(await readFile(join(embed.root, 'README.md'), 'utf8')).toContain('Embed the harness')
expect(await readFile(join(embed.root, 'index.ts'), 'utf8')).toContain('agents.create')
const embedIndex = await readFile(join(embed.root, 'index.ts'), 'utf8')
expect(embedIndex).toContain('agents.create')
expect(embedIndex).toContain("import { SessionId } from '@deepseek-ai/dsh-session'")
expect(embedIndex).not.toContain('AgentId')
await writeFile(join(embed.root, 'README.md'), '# Custom README\n')
const modified = await SdkProject.open(embed.root)
@@ -862,6 +875,12 @@ describe('extension points', () => {
resource.kind === 'cordis-config-entry' && resource.entry.id === 'acp')
expect(acpEntry?.entry.id).toBe('acp')
expect(acpEntry?.validateConfig?.({ model: '' })).toHaveLength(1)
const stdioEntry = builtins.get(featureId('app')).contribution(selection('app', ['stdio']), profile).resources
.find((resource): resource is CordisConfigEntryResource =>
resource.kind === 'cordis-config-entry' && resource.entry.id === 'stdio')
expect(stdioEntry?.validateConfig?.({ welcome: 'ready', sessionId: 1 })).toEqual([
'sessionId must be a non-empty string',
])
const embedOption = app.options.find(option => option.id === 'embed')
expect(embedOption?.markerConfigEntries(profile)).toEqual([])
expect(embedOption?.contribution(profile, {}).resources.map(resource => resource.kind)).toEqual([