mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-10-26 16:55:08 +00:00
fix(sdk): address approval interface review findings
This commit is contained in:
1 parent
0ff3d1ddd2
commit
50fb069e7e
19 files changed
+52
-37
No files matched your search
@@ -15,8 +15,8 @@
|
||||
en: d152484eb41ac6b4
|
||||
zh: 09388d293f9be9cb
|
||||
/deepseek-ai-dsh-sdk-client/use-this-package:
|
||||
en: 38bb14f9c588dea2
|
||||
zh: 3f8218a0eb4f7ec3
|
||||
en: 185eaa87c3261f0f
|
||||
zh: 12ba166f17be15b3
|
||||
/deepseek-ai-dsh-sdk-client/use-this-package/running-agent-turns-with-deepseekharness:
|
||||
en: 01c58dddd7e62de2
|
||||
zh: 6dbd46e244f3f135
|
||||
|
||||
@@ -25,14 +25,14 @@ English | [中文](README.zh.md)
|
||||
<a id="use-this-package"></a>
|
||||
## Use this package
|
||||
|
||||
For client-owned approvals, call `harness.client.start()`, install `harness.client.onRequest(handler)`, then call `harness.start()` with `approvalRequests: true` in the Harness options. The handler receives `approval/request` parameters and returns `{ outcome: "allowed-once" | "rejected" | "cancelled" }`; a rejected handler returns a JSON-RPC error and the runtime fails that approval closed. A replacement client needs its own handler; a closed client cannot restart. Without the opt-in, existing approval answerers remain in control. See the [wire protocol](../protocol/README.md) for request fields and cancellation.
|
||||
Use this client when TypeScript code must drive a complete Harness runtime from another process and you can name the runtime executable explicitly. The common path is minimal: construct a `DeepSeekHarness` with a launch spec, run prompts, and close it so the child process is always reaped.
|
||||
|
||||
For client-owned approvals, call `harness.client.start()`, install `harness.client.onRequest(handler)`, then call `harness.start()` with `approvalRequests: true` in the Harness options. The handler receives `approval/request` parameters and returns `{ outcome: "allowed-once" | "rejected" | "cancelled" }`; a rejected handler returns a JSON-RPC error and the runtime fails that approval closed. A replacement client needs its own handler; a closed client cannot restart. Enabling the flag without installing a handler returns `-32601` to server requests and fails approvals closed as `unavailable`. Without the opt-in, existing approval answerers remain in control. See the [wire protocol](../protocol/README.md) for request fields and cancellation.
|
||||
|
||||
`await harness.resumeSession(savedId)` restores a persisted session from the configured `dshHome` and returns a `HarnessSession` ready for `run()`. Save the original session id and reuse its home and workspace. Restoration preserves conversation history and its directory, repairs an interrupted final turn, and sends no new input. Missing, unreadable, incompatible, or occupied sessions reject without creating a replacement. `session(id)` remains a lazy handle for an in-process session; it does not request persisted restoration. Shell, browser, and other external processes are not restored by this API.
|
||||
|
||||
`HarnessSession.getWorkingDirectory()` reads the effective directory; `setWorkingDirectory(path)` changes it and returns the validated absolute path. Either method creates an unknown Session without starting a model turn. Relative paths use the Session's current directory. Origin metadata, permissions, and existing processes remain unchanged. `HarnessClient` exposes the same methods with a leading `sessionId` argument.
|
||||
|
||||
Use this client when TypeScript code must drive a complete Harness runtime from another process and you can name the runtime executable explicitly. The common path is minimal: construct a `DeepSeekHarness` with a launch spec, run prompts, and close it so the child process is always reaped.
|
||||
|
||||
### Running agent turns with DeepSeekHarness
|
||||
|
||||
```ts
|
||||
|
||||
@@ -25,14 +25,14 @@ kind: "package-library"
|
||||
<a id="use-this-package"></a>
|
||||
## 使用本包
|
||||
|
||||
若由客户端处理审批,先调用 `harness.client.start()`,安装 `harness.client.onRequest(handler)`,再调用 `harness.start()`,并在 Harness 选项中设置 `approvalRequests: true`。处理器接收 `approval/request` 参数,返回 `{ outcome: "allowed-once" | "rejected" | "cancelled" }`;处理器拒绝时返回 JSON-RPC 错误,运行时拒绝放行该审批。替换客户端时需要为新实例安装处理器;已关闭的客户端不能重启。未启用时保留现有审批应答链。请求字段与取消规则见[协议](../protocol/README.zh.md)。
|
||||
当 TypeScript 代码需要从另一进程驱动完整 Harness 运行时、且你能显式指名运行时可执行文件时,使用本客户端。常用路径极简:用启动规格构造 `DeepSeekHarness`,运行提示词,然后关闭它,使子进程总能被回收。
|
||||
|
||||
若由客户端处理审批,先调用 `harness.client.start()`,安装 `harness.client.onRequest(handler)`,再调用 `harness.start()`,并在 Harness 选项中设置 `approvalRequests: true`。处理器接收 `approval/request` 参数,返回 `{ outcome: "allowed-once" | "rejected" | "cancelled" }`;处理器拒绝时返回 JSON-RPC 错误,运行时拒绝放行该审批。替换客户端时需要为新实例安装处理器;已关闭的客户端不能重启。启用开关但未安装处理器时,服务端请求收到 `-32601`,审批以 `unavailable` 拒绝放行。未启用时保留现有审批应答链。请求字段与取消规则见[协议](../protocol/README.zh.md)。
|
||||
|
||||
`await harness.resumeSession(savedId)` 从配置的 `dshHome` 恢复持久化会话,并返回可调用 `run()` 的 `HarnessSession`。请保存原会话 id,并沿用其 home 和工作区。恢复会保留对话历史及其目录、修复最后一个被中断的轮次,且不发送新输入。会话缺失、不可读、不兼容或被占用时,请求失败且不创建替代会话。`session(id)` 仍是当前进程内会话的惰性句柄,不会请求持久化恢复。此 API 不恢复 shell、浏览器或其他外部进程。
|
||||
|
||||
`HarnessSession.getWorkingDirectory()` 读取有效目录;`setWorkingDirectory(path)` 修改目录并返回校验后的绝对路径。两个方法都可创建未知 Session,而不启动模型轮次。相对路径以 Session 当前目录解析。起始目录元数据、权限与已有进程保持不变。`HarnessClient` 提供同名方法,并以 `sessionId` 作为首个参数。
|
||||
|
||||
当 TypeScript 代码需要从另一进程驱动完整 Harness 运行时、且你能显式指名运行时可执行文件时,使用本客户端。常用路径极简:用启动规格构造 `DeepSeekHarness`,运行提示词,然后关闭它,使子进程总能被回收。
|
||||
|
||||
### 用 DeepSeekHarness 运行 agent 轮次
|
||||
|
||||
```ts
|
||||
|
||||
@@ -274,7 +274,10 @@ export class HarnessClient {
|
||||
* @throws when the client has not started or has already closed.
|
||||
*/
|
||||
onRequest(handler: (method: string, params: Record<string, unknown>) => Promise<unknown>): void {
|
||||
if (this.transport === undefined || this.closeTask !== undefined) {
|
||||
if (this.closeTask !== undefined) {
|
||||
throw new TransportClosedError('DeepSeek Harness runtime client is closed; create a new client to install a request handler')
|
||||
}
|
||||
if (this.transport === undefined) {
|
||||
throw new TransportClosedError('start the SDK client before installing a request handler')
|
||||
}
|
||||
this.transport.onRequest(handler)
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
* - `FAKE_EXIT_BEFORE_INIT`: exit 3 immediately (spawn-then-die probe).
|
||||
* - `FAKE_STDERR`: write this line to stderr at boot (diagnostics-tail probe).
|
||||
* - `FAKE_STDERR_NO_NEWLINE`: write this to stderr WITHOUT a newline (buffer-flush probe).
|
||||
* - `FAKE_APPROVAL`: send one server approval request after initialization and echo its response as a notification.
|
||||
* - `FAKE_RECORD_INIT`: append each `initialize` params JSON to this file (handshake probe).
|
||||
*/
|
||||
|
||||
|
||||
@@ -768,7 +768,6 @@ describe('pure helpers', () => {
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe('server-to-client SDK requests', () => {
|
||||
it('requires a started client and returns client decisions over the wire', async () => {
|
||||
const client = processClient(fakeLaunch({ FAKE_APPROVAL: '1' }))
|
||||
@@ -785,11 +784,10 @@ describe('server-to-client SDK requests', () => {
|
||||
expect(await notifications.next()).toMatchObject({ method: 'approval/response', params: { result: { outcome: 'rejected' } } })
|
||||
notifications.close()
|
||||
await client.close()
|
||||
expect(() => { client.onRequest(async () => ({})) }).toThrow('start the SDK client')
|
||||
expect(() => { client.onRequest(async () => ({})) }).toThrow('client is closed; create a new client')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe('SDK cancellation reason projection', () => {
|
||||
it.each(['user', 'parent', 'disposed', 'legacy'])('retains the %s cancellation cause', async (kind) => {
|
||||
const harness = harnessWith({ FAKE_REASON_KIND: 'aborted', FAKE_ABORT_REASON_KIND: kind })
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
en: d152484eb41ac6b4
|
||||
zh: 09388d293f9be9cb
|
||||
/deepseek-ai-dsh-sdk-protocol/use-this-package:
|
||||
en: 3b1752b7f9f6c317
|
||||
zh: 066b03b904b6448b
|
||||
en: 4e8a8a89c1e2c0c3
|
||||
zh: 68b555e03ed73374
|
||||
/deepseek-ai-dsh-sdk-protocol/use-this-package/framing-and-transport:
|
||||
en: be53c71505c0d1b4
|
||||
zh: 6210dee577715539
|
||||
/deepseek-ai-dsh-sdk-protocol/use-this-package/the-sdk-methods:
|
||||
en: 5bd04dbafb8b0fab
|
||||
zh: de0225a5c9794c34
|
||||
en: 06a698d9edab9370
|
||||
zh: 19513f1d66b759f0
|
||||
/deepseek-ai-dsh-sdk-protocol/use-this-package/payload-semantics:
|
||||
en: 3cf5d4ffbaf37f69
|
||||
zh: 23c4450e31c91d67
|
||||
|
||||
@@ -25,10 +25,10 @@ English | [中文](README.zh.md)
|
||||
<a id="use-this-package"></a>
|
||||
## Use this package
|
||||
|
||||
`InitializeParams.approvalRequests: true` enables `approval/request` server requests, indexed by `HarnessSdkClientRequestMap`. `ApprovalRequestParams` contains `sessionId`, `toolName`, optional `callId`, and optional `reason`; `ApprovalRequestResult` is `{ outcome: "allowed-once" | "rejected" | "cancelled" }`. Omission or `false` leaves existing answerers in control. Only `allowed-once` grants one operation. Cancellation discards the pending request and late responses; shutdown and transport loss fail pending decisions closed. The client must keep reading and answering requests while waiting for an Agent to finish.
|
||||
|
||||
Use this package when you build or debug an SDK wire end — the serving plugin, a client library, or custom tooling that speaks the SDK protocol. It gives you one transport for JSON-RPC 2.0 over caller-owned byte streams and the typed shapes for every SDK method and notification.
|
||||
|
||||
`InitializeParams.approvalRequests: true` enables `approval/request` server requests, indexed by `HarnessSdkClientRequestMap`. `ApprovalRequestParams` contains `sessionId`, `toolName`, optional `callId`, and optional `reason`; `ApprovalRequestResult` is `{ outcome: "allowed-once" | "rejected" | "cancelled" }`. Omission or `false` leaves existing answerers in control. Only `allowed-once` grants one operation. Cancellation discards the pending request and late responses; shutdown and transport loss fail pending decisions closed. The client must keep reading and answering requests while waiting for an Agent to finish.
|
||||
|
||||
### Framing and transport
|
||||
|
||||
Wire one JSON-RPC 2.0 message per `\n`-terminated line over byte streams you own. A frame with both `id` and `method` is a request, `id` alone is a response, and `method` alone is a notification; malformed lines are ignored. Requests with no registered handler answer `-32601`, handler failures answer `-32603`, and error responses reject the pending request with `JsonRpcResponseError`, which preserves the wire `code` and optional `data`. `start()` attaches stream listeners and `close()` detaches them and rejects pending requests without destroying the streams.
|
||||
@@ -46,12 +46,13 @@ Both wire ends share one method set: client-to-server requests, opt-in server-to
|
||||
| client→server | `session/working-directory/get` | `SessionWorkingDirectoryParams` → `SessionWorkingDirectoryResult` |
|
||||
| client→server | `session/working-directory/set` | `SessionWorkingDirectorySetParams` → `SessionWorkingDirectoryResult` |
|
||||
| client→server | `shutdown` | no params → `{}` |
|
||||
| server→client | `approval/request` | `ApprovalRequestParams` → `ApprovalRequestResult` |
|
||||
| server→client | `session.event` | `SessionEventNotification` (every session in the runtime, unfiltered) |
|
||||
| server→client | `session.status` | `SessionStatusNotification` (whole-agent `running`/`idle` transition) |
|
||||
| server→client | `subagent.started` | `SubagentStartedNotification` |
|
||||
| server→client | `subagent.finished` | `SubagentFinishedNotification` (in-process runs only) |
|
||||
|
||||
`HarnessSdkRequestMap` and `HarnessSdkNotificationMap` index these shapes by method name; the package root exports them together with the transport.
|
||||
`HarnessSdkRequestMap`, `HarnessSdkClientRequestMap`, and `HarnessSdkNotificationMap` index these shapes by method name; the package root exports them together with the transport.
|
||||
|
||||
`session/wait` accepts `{ sessionId }` for an existing SDK-owned session and returns `{}` after the root stays idle and progressing managed descendant work finishes. Idle descendants with parked input remain resident without blocking this wait. Unknown ids and live Agent failures without a newer committed terminal are rejected. Notifications committed during the wait precede its response on the same transport; raw `session.status` transitions retain their whole-agent meaning.
|
||||
|
||||
|
||||
@@ -25,10 +25,10 @@ kind: "package-library"
|
||||
<a id="use-this-package"></a>
|
||||
## 使用本包
|
||||
|
||||
`InitializeParams.approvalRequests: true` 启用由 `HarnessSdkClientRequestMap` 索引的服务端请求 `approval/request`。`ApprovalRequestParams` 包含 `sessionId`、`toolName`,以及可选的 `callId` 和 `reason`;`ApprovalRequestResult` 为 `{ outcome: "allowed-once" | "rejected" | "cancelled" }`。省略或设为 `false` 时保留现有应答链。只有 `allowed-once` 批准单次操作。取消会丢弃待处理请求及迟到响应;shutdown 和传输断开使待处理决定拒绝放行。客户端等待 Agent 完成期间,必须继续读取并回答请求。
|
||||
|
||||
当你构建或调试 SDK 协议端——服务插件、客户端库或使用该协议的自定义工具——时使用本包。它为你提供一个在调用方持有的字节流上承载 JSON-RPC 2.0 的传输,以及每个 SDK 方法与通知的类型化结构。
|
||||
|
||||
`InitializeParams.approvalRequests: true` 启用由 `HarnessSdkClientRequestMap` 索引的服务端请求 `approval/request`。`ApprovalRequestParams` 包含 `sessionId`、`toolName`,以及可选的 `callId` 和 `reason`;`ApprovalRequestResult` 为 `{ outcome: "allowed-once" | "rejected" | "cancelled" }`。省略或设为 `false` 时保留现有应答链。只有 `allowed-once` 批准单次操作。取消会丢弃待处理请求及迟到响应;shutdown 和传输断开使待处理决定拒绝放行。客户端等待 Agent 完成期间,必须继续读取并回答请求。
|
||||
|
||||
### 分帧与传输
|
||||
|
||||
在你拥有的字节流上,每个 `\n` 结尾的行承载一条 JSON-RPC 2.0 消息。同时带 `id` 与 `method` 的帧是请求,仅 `id` 是响应,仅 `method` 是通知;格式错误的行会被忽略。没有注册处理器的请求应答 `-32601`,处理器失败应答 `-32603`,错误响应会以 `JsonRpcResponseError` 拒绝挂起的请求,并保留协议中的 `code` 与可选 `data`。`start()` 挂接流监听器,`close()` 移除监听器并拒绝挂起请求,但不销毁流。
|
||||
@@ -46,12 +46,13 @@ kind: "package-library"
|
||||
| 客户端→服务端 | `session/working-directory/get` | `SessionWorkingDirectoryParams` → `SessionWorkingDirectoryResult` |
|
||||
| 客户端→服务端 | `session/working-directory/set` | `SessionWorkingDirectorySetParams` → `SessionWorkingDirectoryResult` |
|
||||
| client→server | `shutdown` | 无参数 → `{}` |
|
||||
| server→client | `approval/request` | `ApprovalRequestParams` → `ApprovalRequestResult` |
|
||||
| server→client | `session.event` | `SessionEventNotification`(运行时内每个会话,不过滤) |
|
||||
| server→client | `session.status` | `SessionStatusNotification`(整个 agent(智能体)的 `running`/`idle` 转换) |
|
||||
| server→client | `subagent.started` | `SubagentStartedNotification` |
|
||||
| server→client | `subagent.finished` | `SubagentFinishedNotification`(仅进程内运行) |
|
||||
|
||||
`HarnessSdkRequestMap` 与 `HarnessSdkNotificationMap` 按方法名索引这些结构;包根与传输一起导出它们。
|
||||
`HarnessSdkRequestMap`、`HarnessSdkClientRequestMap` 与 `HarnessSdkNotificationMap` 按方法名索引这些结构;包根与传输一起导出它们。
|
||||
|
||||
`session/wait` 接受已有 SDK 会话的 `{ sessionId }`,在根 Agent 持续空闲且活跃的受管理后代工作结束后返回 `{}`。带有停放输入的空闲后代保持驻留,不阻塞这次等待。未知 id,以及没有后续终止事件记录的 Agent 运行时错误,都会使请求失败。等待期间提交的通知在同一传输上先于响应到达;原始 `session.status` 转换仍表示整个 Agent 的状态。
|
||||
|
||||
|
||||
@@ -57,7 +57,9 @@ interface PendingRequest {
|
||||
/**
|
||||
* Line-delimited endpoint over caller-owned streams. {@link start} attaches
|
||||
* listeners; {@link close} detaches them and rejects pending requests without
|
||||
* destroying the streams. Missing request handlers return `-32601`; handler
|
||||
* destroying the streams. Closure, input end, and input errors permanently reject
|
||||
* later outbound requests with the first failure; start does not reset that failure.
|
||||
* Missing request handlers return `-32601`; handler
|
||||
* failures return `-32603`. Notifications without a handler are dropped.
|
||||
*/
|
||||
export class JsonRpcLineTransport implements JsonRpcTransportPeer {
|
||||
@@ -84,7 +86,8 @@ export class JsonRpcLineTransport implements JsonRpcTransportPeer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach listeners and reject pending requests. Safe before {@link start}.
|
||||
* Detach listeners and reject pending and future outbound requests. Safe before
|
||||
* {@link start}; starting again does not reset the request failure.
|
||||
*/
|
||||
close(): void {
|
||||
this.input.off('data', this.onData)
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
en: d152484eb41ac6b4
|
||||
zh: 09388d293f9be9cb
|
||||
/deepseek-ai-dsh-sdk-jsonrpc-server/use-this-package:
|
||||
en: c7db38347b9da536
|
||||
zh: 05767011d6b389bf
|
||||
en: cf1a1511586ed638
|
||||
zh: a7e4a9a7be9993f5
|
||||
/deepseek-ai-dsh-sdk-jsonrpc-server/use-this-package/wiring:
|
||||
en: 47a33e9397e36e15
|
||||
zh: e6d173820f9ea6e5
|
||||
|
||||
@@ -25,14 +25,14 @@ English | [中文](README.zh.md)
|
||||
<a id="use-this-package"></a>
|
||||
## Use this package
|
||||
|
||||
Mount this plugin when a runtime must serve SDK clients: add it to a `cordis.yml` that composes the agent service, boot the runtime, and clients connect over stdio. The common path is explicit — the plugin needs the `agents` service; every other capability comes from the surrounding tree.
|
||||
|
||||
Set `initialize.approvalRequests` to `true` to send internal approvals as server-to-client `approval/request` calls. The bridge handles SDK-owned Agents and their live in-process descendants; unrelated Agents and clients that omit the flag continue through the composed answerer chain. The approval service still applies `never` policy before dispatch and records the ask/decision audit pair. The bridge accepts only `allowed-once`, `rejected`, or `cancelled`; an invalid response, client error, or transport loss yields the service's fail-closed `unavailable` outcome. Request cancellation abandons the pending wire waiter; shutdown abandons all such waiters before draining Agents. Late replies cannot grant an abandoned operation. See the [protocol](../protocol/README.md) for the payload and the [Python example](../../../python/sdk/README.md#client-approvals) for client handling.
|
||||
|
||||
`session/resume` accepts a persisted `{ sessionId }` after initialization and returns `{}` once its Agent is ready. It loads history through `agents.resume()`, repairs an interrupted final turn, and preserves the stored origin and effective directory while applying the initialized model route. It sends no new input. Missing, unreadable, incompatible, or occupied sessions reject without creating a replacement. Prompts for an identity currently being restored wait for that restoration; shutdown drains pending restoration and releases its Agent. Historical events are retained in the log rather than streamed again as new notifications.
|
||||
|
||||
`session/working-directory/get` accepts `{ sessionId }`; `session/working-directory/set` accepts `{ sessionId, path }`. Both return `{ cwd }` and create an unknown Session without starting a model turn. Reads recover missing directories through the working-directory service; changes preserve origin metadata and permissions, and reach the model through logged user context.
|
||||
|
||||
Mount this plugin when a runtime must serve SDK clients: add it to a `cordis.yml` that composes the agent service, boot the runtime, and clients connect over stdio. The common path is explicit — the plugin needs the `agents` service; every other capability comes from the surrounding tree.
|
||||
|
||||
### Wiring
|
||||
|
||||
The plugin creates one agent per `sessionId` on first use. A registered model adapter wins the route; an unowned `deepseek-official` route mounts the DeepSeek adapter, and any other unowned provider fails initialization. The selected adapter resolves the exact model and optional reasoning effort before initialization succeeds.
|
||||
|
||||
@@ -25,14 +25,14 @@ kind: "package-reference"
|
||||
<a id="use-this-package"></a>
|
||||
## 使用本包
|
||||
|
||||
当运行时必须服务 SDK 客户端时挂载本插件:把它加入组合了 agent 服务的 `cordis.yml`,启动运行时,客户端即可通过 stdio 连接。常用路径是显式的——插件需要 `agents` 服务;其余每个能力都来自外围插件树。
|
||||
|
||||
将 `initialize.approvalRequests` 设为 `true`,即可把内部审批作为服务端到客户端的 `approval/request` 请求发送。桥接处理 SDK 拥有的 Agent 及其仍在运行的进程内后代;其他 Agent,以及未提供该标志的客户端,继续使用已组合的应答链。审批服务仍在分发前执行 `never` 策略,并记录询问与决定的审计事件。桥接仅接受 `allowed-once`、`rejected` 或 `cancelled`;无效响应、客户端错误或传输断开会产生审批服务的拒绝放行结果 `unavailable`。请求取消时释放待处理的协议等待项;shutdown 在等待 Agent 结束前释放所有此类等待项。迟到响应不能授权已放弃的操作。载荷见[协议](../protocol/README.zh.md),客户端处理见 [Python 示例](../../../python/sdk/README.zh.md#client-approvals)。
|
||||
|
||||
`session/resume` 在初始化后接受持久化的 `{ sessionId }`,待其 Agent 就绪后返回 `{}`。它通过 `agents.resume()` 加载历史、修复最后一个被中断的轮次,并保留存储的起始目录和当前有效目录,同时应用初始化时选定的模型路由。它不发送新输入。会话缺失、不可读、不兼容或被占用时,请求失败且不创建替代会话。对正在恢复的标识发送提示词时,会等待恢复完成;shutdown 会等待进行中的恢复并释放其 Agent。历史事件保留在日志中,不会作为新通知重新发送。
|
||||
|
||||
`session/working-directory/get` 接收 `{ sessionId }`;`session/working-directory/set` 接收 `{ sessionId, path }`。两者均返回 `{ cwd }`,并可创建未知 Session,而不启动模型轮次。读取通过工作目录服务恢复缺失目录;修改保留起始目录元数据与权限,并通过已记录的用户上下文告知模型。
|
||||
|
||||
当运行时必须服务 SDK 客户端时挂载本插件:把它加入组合了 agent 服务的 `cordis.yml`,启动运行时,客户端即可通过 stdio 连接。常用路径是显式的——插件需要 `agents` 服务;其余每个能力都来自外围插件树。
|
||||
|
||||
### 组装
|
||||
|
||||
插件在首次使用时为每个 `sessionId` 创建一个 agent。已注册的模型适配器优先用于该路由;尚无适配器负责的 `deepseek-official` 路由会挂载 DeepSeek 适配器,任何其他尚无适配器负责的提供方都会导致初始化失败。初始化成功前,所选适配器会解析确切模型与可选推理强度。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** SDK approvals use the real bidirectional wire and the internal audit service. */
|
||||
import { PassThrough } from 'node:stream'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { describe, expect, it, onTestFinished } from 'vitest'
|
||||
import { describe, expect, it, onTestFinished, vi } from 'vitest'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
@@ -42,7 +42,7 @@ async function setup(enabled?: boolean) {
|
||||
await client.request('session/working-directory/get', { sessionId: 'main' })
|
||||
const agent = ctx.agents.get(SessionId('main'))!
|
||||
agent.session.append('turn/start', { turn: 1 })
|
||||
return { ctx, server, serverWire, client, agent }
|
||||
return { ctx, server, serverWire, client, agent, input }
|
||||
}
|
||||
|
||||
describe('SDK approval bridge', () => {
|
||||
@@ -115,7 +115,11 @@ describe('SDK approval bridge', () => {
|
||||
else if (action === 'shutdown') await h.server.shutdown()
|
||||
else h.serverWire.close()
|
||||
await expect(pending).resolves.toBe(action === 'cancel' ? 'cancelled' : 'unavailable')
|
||||
const delivered = Promise.withResolvers<Buffer>()
|
||||
h.input.once('data', delivered.resolve)
|
||||
onTestFinished(() => { h.input.off('data', delivered.resolve) })
|
||||
answer.resolve({ outcome: 'allowed-once' })
|
||||
expect(JSON.parse(String(await delivered.promise))).toMatchObject({ result: { outcome: 'allowed-once' } })
|
||||
expect(h.agent.session.snapshotEvents().filter(e => e.type === 'approval/decided').map(e => e.data.outcome))
|
||||
.toEqual([action === 'cancel' ? 'cancelled' : 'unavailable'])
|
||||
})
|
||||
@@ -125,7 +129,11 @@ describe('SDK approval bridge', () => {
|
||||
const foreign = await h.ctx.agents.create({ sessionId: SessionId('foreign'), meta: { cwd: process.cwd() } })
|
||||
onTestFinished(() => foreign.dispose())
|
||||
foreign.agent.session.append('turn/start', { turn: 1 })
|
||||
await expect(h.ctx.approval.request({ agent: foreign.agent, toolName: 'bash' })).resolves.toBe('unavailable')
|
||||
const clientDecision = vi.fn(async () => ({ outcome: 'allowed-once' }))
|
||||
h.client.onRequest(clientDecision)
|
||||
h.ctx.on('approval/request', () => Promise.resolve('rejected'))
|
||||
await expect(h.ctx.approval.request({ agent: foreign.agent, toolName: 'bash' })).resolves.toBe('rejected')
|
||||
expect(clientDecision).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('serves in-process descendants and preserves never policy', async () => {
|
||||
|
||||
Vendored
File renamed without changes.
@@ -76,7 +76,7 @@ with HarnessClient(HarnessConfig(dsh_home="/absolute/path/to/dsh-home")) as clie
|
||||
print(question.toolName, question.reason)
|
||||
client.respond(request.id, {"outcome": "rejected"})
|
||||
else:
|
||||
client.respond_error(request.id, -32601, "Unsupported client request")
|
||||
client.respond_error(request.id, code=-32601, message="Unsupported client request")
|
||||
```
|
||||
|
||||
## Customize plugins
|
||||
|
||||
@@ -76,7 +76,7 @@ with HarnessClient(HarnessConfig(dsh_home="/absolute/path/to/dsh-home")) as clie
|
||||
print(question.toolName, question.reason)
|
||||
client.respond(request.id, {"outcome": "rejected"})
|
||||
else:
|
||||
client.respond_error(request.id, -32601, "Unsupported client request")
|
||||
client.respond_error(request.id, code=-32601, message="Unsupported client request")
|
||||
```
|
||||
|
||||
## 自定义插件
|
||||
|
||||
@@ -23,7 +23,7 @@ def test_python_approval_controls_runtime_operation(tmp_path: Path, outcome: str
|
||||
binary = repo / "apps/cli/lib/bin.js"
|
||||
if node is None or not binary.exists():
|
||||
pytest.skip("requires Node and built dsh CLI")
|
||||
fixture = repo / "apps/cli/tests/profiles/sdk/fixtures/approval.mjs"
|
||||
fixture = repo / "packages/sdk/server/tests/fixtures/approval.mjs"
|
||||
marker = tmp_path / "approved.txt"
|
||||
patch = tmp_path / "approval.patch.yml"
|
||||
patch.write_text("- insert:\n - id: approval-fixture\n name: " + json.dumps(str(fixture))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
- insert:
|
||||
- id: sdk-approval-fixture
|
||||
name: ../../../apps/cli/tests/profiles/sdk/fixtures/approval.mjs
|
||||
name: ../../../packages/sdk/server/tests/fixtures/approval.mjs
|
||||
config:
|
||||
path: !!js "process.getBuiltinModule('node:path').join(process.cwd(), 'approved.txt')"
|
||||
Reference in new issue
Block a user