Merge remote-tracking branch 'origin/master' into xtr/identified-immutable-messages

# Conflicts:
#	docs/cordis-catalog/events.md
#	docs/core-data-structures/core.i18n.yaml
#	docs/event-producer-consumer.md
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl
#	packages/core/agent-loop/README.i18n.yaml
#	packages/core/agent/README.i18n.yaml
This commit is contained in:
_Kerman
2026-07-28 18:49:23 +08:00
59 changed files with 364 additions and 97 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/ui/jsonrpc/README.md
README.md: 48eb6106fe4b015f114b264a312249a92128266f
README.zh.md: 8ec2d57a206d770d0b77ee69036457e3b2864303
README.md: b1219ba10269fc7d046da22c280ff1b91424a5ae
README.zh.md: 63615654769bf4ed7a69c09dc818af034c3a3c3c

View File

@@ -22,7 +22,7 @@ The plugin answers `shutdown`, disposes SDK-owned agents and subscriptions to qu
## Wire notes
`initialize.serverInfo.name` is the wire-stable `deepseek-harness-sdk-runtime`. A session accepts one in-flight prompt; overlap fails immediately, other sessions remain independent, and the session is reusable after settlement. `session.finished` reports that prompt's message-triggered turn outcome; later between-turn records still stream as `session.event` notifications but cannot replace the prompt status. Persistence roots and persona come from `cordis.yml`.
`initialize.serverInfo.name` is the wire-stable `deepseek-harness-sdk-runtime`. An optional positive `initialize.maxTokens` becomes the request output cap of each SDK-created agent and its in-process descendants; invalid values reject initialization, while omission sends no cap and preserves provider defaults. A session accepts one in-flight prompt; overlap fails immediately, other sessions remain independent, and the session is reusable after settlement. `session.finished` reports that prompt's message-triggered turn outcome; later between-turn records still stream as `session.event` notifications but cannot replace the prompt status. Persistence roots and persona come from `cordis.yml`.
## Model Experience

View File

@@ -22,7 +22,7 @@ Stdout 只承载 JSON-RPC 帧。部署不得组合 stdout logger诊断应写
## 协议说明
`initialize.serverInfo.name` 的协议稳定值为 `deepseek-harness-sdk-runtime`。一个会话只接受一个进行中的提示词;重叠请求会立即失败,其他会话保持独立,当前请求结算后该会话可再次使用。`session.finished` 报告由该提示词消息触发的轮次结果;后续轮次间记录仍会作为 `session.event` 通知流式发出,但不能替换该提示词的状态。持久化根目录和 persona 由 `cordis.yml` 提供。
`initialize.serverInfo.name` 的协议稳定值为 `deepseek-harness-sdk-runtime`可选的正整数 `initialize.maxTokens` 会成为每个 SDK 创建的 agent 及其进程内后代的请求输出上限;非法值会使初始化失败,省略时则不发送上限并保留提供方默认值。一个会话只接受一个进行中的提示词;重叠请求会立即失败,其他会话保持独立,当前请求结算后该会话可再次使用。`session.finished` 报告由该提示词消息触发的轮次结果;后续轮次间记录仍会作为 `session.event` 通知流式发出,但不能替换该提示词的状态。持久化根目录和 persona 由 `cordis.yml` 提供。
## 模型体验

View File

@@ -57,6 +57,7 @@ export class HarnessSdkServer {
private cwd = process.cwd()
private provider = 'deepseek'
private model = 'deepseek'
private maxTokens: number | undefined
private llmFiber: { dispose(): Promise<void> } | undefined
private readonly sessions = new Map<string, SessionRecord>()
private readonly sessionCreations = new Map<string, Promise<SessionRecord>>()
@@ -114,9 +115,14 @@ export class HarnessSdkServer {
* @returns server identity for the handshake.
*/
async initialize(params: InitializeParams): Promise<InitializeResult> {
if (params.maxTokens !== undefined
&& (!Number.isSafeInteger(params.maxTokens) || params.maxTokens <= 0)) {
throw new TypeError('initialize maxTokens must be a positive safe integer')
}
this.cwd = resolve(params.cwd)
this.provider = params.provider
this.model = params.model
this.maxTokens = params.maxTokens
if (!this.hasAdapterFor(this.provider)) {
if (this.provider !== 'deepseek') throw new Error(`no adapter registered for provider "${this.provider}"`)
this.llmFiber = await this.ctx.plugin(LlmDeepSeek, {})
@@ -232,7 +238,11 @@ export class HarnessSdkServer {
const handle = await this.ctx.agents.create({
sessionId: SessionId(sessionId),
meta: { cwd: this.cwd },
agentOptions: { provider: this.provider, model: this.model },
agentOptions: {
provider: this.provider,
model: this.model,
...this.maxTokens === undefined ? {} : { maxTokens: this.maxTokens },
},
})
const rec: SessionRecord = { handle, lastTurnEnd: undefined, activePrompt: false }
this.sessions.set(sessionId, rec)

View File

@@ -123,6 +123,7 @@ describe('HarnessSdkServer', () => {
cwd: storageDir,
provider: 'deepseek',
model: 'dsagent-model',
maxTokens: 321,
}) as { serverInfo: { name: string } }
expect(init.serverInfo.name).toBe('deepseek-harness-sdk-runtime')
@@ -132,8 +133,9 @@ describe('HarnessSdkServer', () => {
})
expect(llmServer.requests).toHaveLength(1)
const body = llmServer.requests[0] as { model: string; messages: { role: string }[] }
const body = llmServer.requests[0] as { model: string; messages: { role: string }[]; max_tokens?: number }
expect(body.model).toBe('dsagent-model')
expect(body.max_tokens).toBe(321)
expect(body.messages[0]?.role).toBe('system')
expect(body.messages.at(-1)?.role).toBe('user')
expect(llmServer.headers[0]?.authorization).toBe('Bearer test-key')
@@ -860,6 +862,27 @@ describe('HarnessSdkServer', () => {
}
})
it.each([0, -1, 1.5, Number.NaN, Number.MAX_SAFE_INTEGER + 1])(
'rejects invalid initialize maxTokens %s at the wire boundary',
async (maxTokens) => {
const storageDir = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-invalid-max-tokens-'))
const ctx = await makeHarness(storageDir)
try {
const server = new HarnessSdkServer(ctx, new FakeTransport())
await expect(server.initialize({
cwd: storageDir,
provider: 'deepseek',
model: 'model',
maxTokens,
})).rejects.toThrow('initialize maxTokens must be a positive safe integer')
await server.shutdown()
} finally {
await ctx.fiber.dispose()
await rm(storageDir, { recursive: true, force: true })
}
},
)
it('classifies defensive finish states', async () => {
const storageDir = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-finish-states-'))
const ctx = await makeHarness(storageDir)
@@ -974,15 +997,18 @@ describe('HarnessSdkServer', () => {
get: () => ({ listProviders: () => [{ id: 'mock', name: 'Mock' }] }),
} as unknown as Context
const server = new HarnessSdkServer(ctx, new FakeTransport()) as unknown as {
initialize(params: { cwd: string; provider: string; model: string }): Promise<unknown>
initialize(params: { cwd: string; provider: string; model: string; maxTokens?: number }): Promise<unknown>
getOrCreateSession(sessionId: string): Promise<unknown>
shutdown(): Promise<Record<string, never>>
}
await server.initialize({ cwd: '.', provider: 'mock', model: 'model' })
await server.initialize({ cwd: '.', provider: 'mock', model: 'model', maxTokens: 123 })
await server.getOrCreateSession('relative')
expect(create).toHaveBeenCalledWith(expect.objectContaining({ meta: { cwd: process.cwd() } }))
expect(create).toHaveBeenCalledWith(expect.objectContaining({
meta: { cwd: process.cwd() },
agentOptions: { provider: 'mock', model: 'model', maxTokens: 123 },
}))
await server.shutdown()
})