@feng0531/dsh-execution-bus 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -7
- package/THIRD_PARTY_NOTICES.md +2 -0
- package/dist/bus.d.ts +34 -1
- package/dist/bus.js +111 -10
- package/dist/handlers.js +5 -4
- package/dist/mode.d.ts +5 -0
- package/dist/mode.js +16 -0
- package/dist/plugin.js +28 -23
- package/dist/preset.d.ts +1 -1
- package/dist/preset.js +4 -3
- package/dist/product.d.ts +56 -0
- package/dist/product.js +48 -0
- package/dist/test-mode.d.ts +2 -5
- package/dist/test-mode.js +2 -15
- package/docs/INTEGRATION.md +35 -0
- package/docs/PRODUCT.md +46 -0
- package/docs/protocol.md +15 -3
- package/docs/releases/0.5.0.md +16 -0
- package/docs/releases/0.5.1.md +18 -0
- package/docs/validation.md +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
“萧何”是产品与模式显示名,执行总线是其技术功能;不新增人物 Agent,不进行角色扮演。仓库 dsh-execution-bus、npm 包名、execution_bus 工具名及 execution-bus-test 预设 ID 保持不变。历史版本发布说明保留当时名称。
|
|
6
6
|
|
|
7
|
-
DSH 独立插件:`@feng0531/dsh-execution-bus` · 当前版本 **0.
|
|
7
|
+
DSH 独立插件:`@feng0531/dsh-execution-bus` · 当前版本 **0.5.1**
|
|
8
8
|
|
|
9
9
|
> 萧何模式从宿主已声明的 PTC 组合派生,保留日常工具,再叠加可选执行总线。已验证隔离的真实 Node PTC → 原 read + 总线调用,以及脚本模型内存会话;真实模型、桌面界面与实际沙箱仍未验收。**不支持重启后恢复任务。**
|
|
10
10
|
|
|
@@ -83,13 +83,22 @@ flowchart TD
|
|
|
83
83
|
|
|
84
84
|
| Handler 名称 | 用途 | 输入 |
|
|
85
85
|
|---|---|---|
|
|
86
|
-
| `test
|
|
86
|
+
| `test`(诊断,非业务) | 仅在明确诊断时检查延迟、进度、成功、失败和取消 | `delayMs`:0–30000;`fail`:布尔值 |
|
|
87
87
|
| `host.read` | 经由 DSH 现有 `read` 工具读取文本 | `file_path`;可选 `offset` 和 `limit`,最多 100 行 |
|
|
88
88
|
|
|
89
89
|
`host.read` 在出队时重新进入宿主工具管线,沿用宿主当前权限、会话工作目录及输出限制。插件不自行启动命令、不提供任意远程执行服务。它也不会强制将现有工具改成总线调用。
|
|
90
90
|
|
|
91
91
|
## 选择“萧何”预设
|
|
92
92
|
|
|
93
|
+
正式模式入口为 `/mode`。新生成预设在原 PTC 组合末尾追加以下行,它不是整份预设:
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
- id: xiao-he-execution-bus
|
|
97
|
+
name: '@feng0531/dsh-execution-bus/mode'
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
旧 /test-mode 保留为兼容别名,两个入口指向同一正式模块,不表示测试专用模式,也不要同时配置新旧两行。必须先安装 0.5.1 或以上才能使用 /mode;旧包没有此导出。旧预设不会自动重写,旧入口继续有效。历史预设 ID execution-bus-test 和包级配置行 ID 不改,避免已有会话失联。
|
|
101
|
+
|
|
93
102
|
包内提供标准 DSH bundle patch(预设组合补丁)。宿主正常加载后会收到“萧何 / Xiao He”预设定义,由既有设置 → Agent 预设 → **自定义**列表展示。建议选择它新建专门会话,不改默认选择或正在使用的普通会话。已有会话的切换受宿主空白会话检查约束。**实际桌面卡片展示尚未验收**,不能把注册表测试当作界面已通过。
|
|
94
103
|
|
|
95
104
|
**0.3.0 起不再是只读验证模式。** 加载时读取宿主 `ptc` 预设的组合声明快照,保留原工具、平台表达式、禁用项和配置,只追加萧何总线与说明。不再强制 native 呈现或添加只读白名单;终端、写入、搜索、技能等能力是否可用,取决于原 PTC 配置和宿主服务。其他预设和默认模式不被修改,原 PTC 会话看不到新增总线。
|
|
@@ -115,7 +124,7 @@ flowchart TD
|
|
|
115
124
|
2. 在你选定的隔离 DSH 配置依赖目录安装固定版本,不要在 DSH 程序安装目录执行:
|
|
116
125
|
|
|
117
126
|
```powershell
|
|
118
|
-
npm install --ignore-scripts @feng0531/dsh-execution-bus@0.
|
|
127
|
+
npm install --ignore-scripts @feng0531/dsh-execution-bus@0.5.1
|
|
119
128
|
```
|
|
120
129
|
|
|
121
130
|
这是 npm 目录示例。若目标配置使用 pnpm,应沿用其包管理器,不混用锁文件。遇到宿主 peer 版本冲突就停止,不用强制安装绕过。
|
|
@@ -204,7 +213,7 @@ stateDiagram-v2
|
|
|
204
213
|
| `queued` | 已受理,尚未执行;不代表成功 |
|
|
205
214
|
| `running` | 正在执行;等待窗口超时不自动把它变成失败 |
|
|
206
215
|
| `succeeded` | 执行成功且总线保存了结果 |
|
|
207
|
-
| `failed` |
|
|
216
|
+
| `failed` | 处理器失败,或排队期限到达但尚未执行;查看 error 区分 |
|
|
208
217
|
| `cancelled` | 已确认取消 |
|
|
209
218
|
| `unknown` | 不能可靠保留或确认结果,不可盲目重跑有副作用操作 |
|
|
210
219
|
|
|
@@ -212,13 +221,19 @@ stateDiagram-v2
|
|
|
212
221
|
|
|
213
222
|
## 调度与诊断
|
|
214
223
|
|
|
215
|
-
0.4.0 起对已受理的排队请求按会话轮转,同一会话保持 FIFO
|
|
224
|
+
0.4.0 起对已受理的排队请求按会话轮转,同一会话保持 FIFO;0.5.0 再增加每会话准入配额与排队期限。不抢占运行任务,也不保证队列满时为其他会话预留名额。执行结果仍由 core 唯一提交,宿主 Job 只负责任务展示、控制与生命周期关联。
|
|
216
225
|
|
|
217
226
|
core 开发者可调用实例 `diagnostics()` 取得队列、运行数、保留结果字节及最近 128 条数值阶段轨迹。不含请求/会话 ID、输入、输出或错误文本,不写日志;诊断不增加模型调用工具,也不是跨重启审计记录。详见[优化范围与验证](<docs/OPTIMIZATION.md>)。
|
|
218
227
|
|
|
228
|
+
### 查看本会话状态
|
|
229
|
+
|
|
230
|
+
在 PTC 中按需执行 `return await tools.execution_bus_state({})`,仅在用户诊断时调用,不循环轮询。返回本会话排队/运行/终态数量、默认策略,以及最多 50 条任务元数据(活跃优先,其余最近受理的终态优先)。有 requestId、状态、等待原因、取消标记、单调时钟时长、固定错误码和 delivery,不含输入、结果或其他会话信息。delivery 不是模型已消费回执,也不能从视图断言 Job 正在清理。
|
|
231
|
+
|
|
232
|
+
core 开发者可用 `new ExecutionBus(options, {sessionQueueLimit:16,maxQueueWaitMs:120000})` 和 `inspect(trustedOwner)`;第二参数省略时保持旧版无限会话配额/无排队期限行为,原五项 options 不变。该可信 owner 由调用者提供,core 不是身份认证边界,宿主工具从真实 Agent 取得身份且只接受空对象。
|
|
233
|
+
|
|
219
234
|
## 容量与重要限制
|
|
220
235
|
|
|
221
|
-
|
|
236
|
+
宿主插件默认并发 **2**,全局排队上限 **32**,每会话排队上限 **16**;排队超过 **120 秒**且尚未开始执行时结算为 `failed / QUEUE_WAIT_TIMEOUT`,不调用 Handler。运行后不会被该排队期限中止,PTC 外层仍有独立时间预算。最多保存 **256** 条任务记录,单结果最多 **64 KiB**,完成结果保留期 **1 小时**,在新请求和查询时惰性清理;不是一小时一到即定时删除。队列或记录满时拒绝新任务;宿主 Job 自身也有容量限制。
|
|
222
237
|
|
|
223
238
|
- **全部状态在内存中。** 受理没有落盘,插件关闭或进程崩溃后,不能依靠它恢复原调用或保证请求不丢失。
|
|
224
239
|
- **不自动重跑任务。** 通信超时不等于执行失败,取消不等于回滚。Job 等待服务异常会在原执行排空后返回服务错误,执行可能已成功;应核对原 requestId 的状态,不要换编号盲目重跑。
|
|
@@ -248,9 +263,12 @@ npm run test:install # 临时目录安装,不操作日常 DSH
|
|
|
248
263
|
|
|
249
264
|
## 进一步阅读
|
|
250
265
|
|
|
266
|
+
- [产品结构与兼容契约](<docs/PRODUCT.md>):正式入口、模块职责及维护验收边界。
|
|
267
|
+
|
|
251
268
|
- [协议和投递语义](<docs/protocol.md>):字段、状态、重复处理与取消规则。
|
|
252
269
|
- [宿主兼容性与源码调用链](<docs/host-compatibility.md>):为什么可以等待,为什么不能跨重启恢复。
|
|
253
|
-
- [验证记录](<docs/validation.md>)、[0.
|
|
270
|
+
- [验证记录](<docs/validation.md>)、[0.5.1 发布说明](<docs/releases/0.5.1.md>):已验证与未验证范围。
|
|
271
|
+
- [同类项目源码整合](<docs/INTEGRATION.md>):固定来源、采用范围、许可与验证边界。
|
|
254
272
|
- [维护者发布流程](<docs/RELEASING.md>):标签 CI、安装包校验与正式发布门禁。
|
|
255
273
|
|
|
256
274
|
## 许可
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -3,3 +3,5 @@
|
|
|
3
3
|
本包没有捆绑 DSH、Cordis、TypeScript、测试依赖或宿主源码快照。DSH 与 Cordis 作为宿主 peer 使用,其独立包分别受原许可证约束,本项目非商业许可证不限制第三方已授予的权利。开发依赖不进入本项目 tgz。js-yaml 为 MIT 许可的独立运行依赖,由包管理器按声明安装,不捆绑进 tgz;其源码和许可见 https://github.com/nodeca/js-yaml ,本项目许可不限制其原有权利。
|
|
4
4
|
|
|
5
5
|
CI 与发布组织方式参考 dsh-vcs,并按本项目执行能力重新实现;本包不含 Monaco、React 或 SVN 实现。
|
|
6
|
+
|
|
7
|
+
0.5.0 定向参考 dsh-llm-rate-limit(MIT,hanruochong)、dsh-nightshift(MIT,contributors)和 dsh-input-traffic(MIT,contributors)的准入、生命周期和会话视图原则,按本项目契约自行实现,没有复制其源码或捆绑依赖。AsyncGate 与 cc-multi-process-mcp 仅用于边界比较,所查快照未发现 LICENSE,不复制代码。固定来源与阅读范围见 [整合说明](<docs/INTEGRATION.md>)。
|
package/dist/bus.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export interface AdmissionPolicy {
|
|
2
|
+
sessionQueueLimit?: number;
|
|
3
|
+
maxQueueWaitMs?: number;
|
|
4
|
+
}
|
|
1
5
|
export type State = "queued" | "running" | "succeeded" | "failed" | "cancelled" | "unknown";
|
|
2
6
|
export type Owner = {
|
|
3
7
|
sessionId: string;
|
|
@@ -50,6 +54,7 @@ export declare class ExecutionBus {
|
|
|
50
54
|
resultBytes: number;
|
|
51
55
|
retentionMs: number;
|
|
52
56
|
};
|
|
57
|
+
readonly admission: AdmissionPolicy;
|
|
53
58
|
private entries;
|
|
54
59
|
private handlers;
|
|
55
60
|
private queue;
|
|
@@ -89,7 +94,7 @@ export declare class ExecutionBus {
|
|
|
89
94
|
recordLimit: number;
|
|
90
95
|
resultBytes: number;
|
|
91
96
|
retentionMs: number;
|
|
92
|
-
});
|
|
97
|
+
}, admission?: AdmissionPolicy);
|
|
93
98
|
register(name: string, handler: Handler): () => void;
|
|
94
99
|
private key;
|
|
95
100
|
private emit;
|
|
@@ -105,6 +110,34 @@ export declare class ExecutionBus {
|
|
|
105
110
|
};
|
|
106
111
|
private attachment;
|
|
107
112
|
command(owner: Owner, raw: unknown): RecordView;
|
|
113
|
+
private removeQueued;
|
|
114
|
+
/** Trusted host owner required. Never exposes other sessions or business payloads. */
|
|
115
|
+
inspect(owner: Owner): {
|
|
116
|
+
phase: "open" | "cancelling" | "draining" | "closed";
|
|
117
|
+
policy: {
|
|
118
|
+
sessionQueueLimit?: number;
|
|
119
|
+
maxQueueWaitMs?: number;
|
|
120
|
+
};
|
|
121
|
+
counts: {
|
|
122
|
+
queued: number;
|
|
123
|
+
running: number;
|
|
124
|
+
terminal: number;
|
|
125
|
+
};
|
|
126
|
+
tasks: {
|
|
127
|
+
requestId: string;
|
|
128
|
+
state: State;
|
|
129
|
+
delivery: "returned" | "pending";
|
|
130
|
+
cancelRequested: boolean;
|
|
131
|
+
error: string | null;
|
|
132
|
+
progress: number | null;
|
|
133
|
+
waitReason: string | null;
|
|
134
|
+
phase: string;
|
|
135
|
+
queueMs: number;
|
|
136
|
+
runMs: number;
|
|
137
|
+
remainingQueueMs: number | null;
|
|
138
|
+
}[];
|
|
139
|
+
truncated: boolean;
|
|
140
|
+
};
|
|
108
141
|
private cancel;
|
|
109
142
|
private finish;
|
|
110
143
|
private pump;
|
package/dist/bus.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { randomUUID, createHash } from "node:crypto";
|
|
2
2
|
import { assertJson } from "./json.js";
|
|
3
3
|
import { Diagnostics } from "./diagnostics.js";
|
|
4
|
+
import { performance } from "node:perf_hooks";
|
|
5
|
+
import { CORE_DEFAULTS } from "./product.js";
|
|
4
6
|
export function object(v) {
|
|
5
7
|
if (!v || typeof v !== "object" || Array.isArray(v))
|
|
6
8
|
throw Error("INVALID_OBJECT");
|
|
@@ -56,6 +58,7 @@ const terminal = (s) => !["queued", "running"].includes(s);
|
|
|
56
58
|
/** Volatile single-process bus; no acceptance durability or automatic execution retry. */
|
|
57
59
|
export class ExecutionBus {
|
|
58
60
|
options;
|
|
61
|
+
admission;
|
|
59
62
|
entries = new Map();
|
|
60
63
|
handlers = new Map();
|
|
61
64
|
queue = [];
|
|
@@ -76,14 +79,25 @@ export class ExecutionBus {
|
|
|
76
79
|
}
|
|
77
80
|
closing;
|
|
78
81
|
shutdownPhase = "open";
|
|
79
|
-
constructor(options = {
|
|
80
|
-
concurrency: 2,
|
|
81
|
-
queueLimit: 32,
|
|
82
|
-
recordLimit: 256,
|
|
83
|
-
resultBytes: 65536,
|
|
84
|
-
retentionMs: 3600000,
|
|
85
|
-
}) {
|
|
82
|
+
constructor(options = CORE_DEFAULTS, admission = {}) {
|
|
86
83
|
this.options = options;
|
|
84
|
+
this.admission = admission;
|
|
85
|
+
const allowed = ["sessionQueueLimit", "maxQueueWaitMs"];
|
|
86
|
+
if (!admission ||
|
|
87
|
+
typeof admission !== "object" ||
|
|
88
|
+
Array.isArray(admission) ||
|
|
89
|
+
Reflect.ownKeys(admission).some((key) => typeof key !== "string" || !allowed.includes(key)))
|
|
90
|
+
throw Error("INVALID_ADMISSION_POLICY");
|
|
91
|
+
for (const key of Reflect.ownKeys(admission)) {
|
|
92
|
+
const descriptor = Object.getOwnPropertyDescriptor(admission, key);
|
|
93
|
+
if (!("value" in descriptor) ||
|
|
94
|
+
!descriptor.enumerable ||
|
|
95
|
+
!Number.isSafeInteger(descriptor.value) ||
|
|
96
|
+
descriptor.value < 1 ||
|
|
97
|
+
descriptor.value > 2147483647)
|
|
98
|
+
throw Error("INVALID_ADMISSION_POLICY");
|
|
99
|
+
}
|
|
100
|
+
this.admission = Object.freeze({ ...admission });
|
|
87
101
|
const keys = [
|
|
88
102
|
"concurrency",
|
|
89
103
|
"queueLimit",
|
|
@@ -213,6 +227,10 @@ export class ExecutionBus {
|
|
|
213
227
|
if (this.entries.size >= this.options.recordLimit ||
|
|
214
228
|
this.queue.length >= this.options.queueLimit)
|
|
215
229
|
throw Error("BACKPRESSURE");
|
|
230
|
+
if (this.admission.sessionQueueLimit !== undefined &&
|
|
231
|
+
this.queue.filter((entry) => entry.owner === owner.sessionId).length >=
|
|
232
|
+
this.admission.sessionQueueLimit)
|
|
233
|
+
throw Error("SESSION_BACKPRESSURE");
|
|
216
234
|
let resolve;
|
|
217
235
|
const done = new Promise((r) => (resolve = r));
|
|
218
236
|
e = {
|
|
@@ -245,6 +263,22 @@ export class ExecutionBus {
|
|
|
245
263
|
this.telemetry.record(e.trace, "accepted");
|
|
246
264
|
const attached = this.attachment(e, onEvent, m.messageId);
|
|
247
265
|
this.queue.push(e);
|
|
266
|
+
if (this.admission.maxQueueWaitMs !== undefined) {
|
|
267
|
+
e.deadline = performance.now() + this.admission.maxQueueWaitMs;
|
|
268
|
+
const entry = e;
|
|
269
|
+
const expire = () => {
|
|
270
|
+
if (entry.view.state !== "queued")
|
|
271
|
+
return;
|
|
272
|
+
const remaining = entry.deadline - performance.now();
|
|
273
|
+
if (remaining > 0) {
|
|
274
|
+
entry.queueTimer = setTimeout(expire, Math.ceil(remaining));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
this.removeQueued(entry);
|
|
278
|
+
this.finish(entry, "failed", undefined, "QUEUE_WAIT_TIMEOUT");
|
|
279
|
+
};
|
|
280
|
+
e.queueTimer = setTimeout(expire, this.admission.maxQueueWaitMs);
|
|
281
|
+
}
|
|
248
282
|
if (!this.readyOwners.includes(e.owner))
|
|
249
283
|
this.readyOwners.push(e.owner);
|
|
250
284
|
queueMicrotask(() => this.pump());
|
|
@@ -307,6 +341,61 @@ export class ExecutionBus {
|
|
|
307
341
|
}
|
|
308
342
|
return this.view(e);
|
|
309
343
|
}
|
|
344
|
+
removeQueued(e) {
|
|
345
|
+
this.queue = this.queue.filter((entry) => entry !== e);
|
|
346
|
+
if (!this.queue.some((entry) => entry.owner === e.owner))
|
|
347
|
+
this.readyOwners = this.readyOwners.filter((owner) => owner !== e.owner);
|
|
348
|
+
}
|
|
349
|
+
/** Trusted host owner required. Never exposes other sessions or business payloads. */
|
|
350
|
+
inspect(owner) {
|
|
351
|
+
if (!owner || typeof owner.sessionId !== "string" || !owner.sessionId)
|
|
352
|
+
throw Error("OWNING_SESSION_REQUIRED");
|
|
353
|
+
this.sweep();
|
|
354
|
+
const now = performance.now();
|
|
355
|
+
const records = [...this.entries.values()].filter((e) => e.owner === owner.sessionId);
|
|
356
|
+
// Active work must not disappear behind newer terminal history.
|
|
357
|
+
const active = records.filter((e) => !terminal(e.view.state));
|
|
358
|
+
const history = records.filter((e) => terminal(e.view.state)).reverse();
|
|
359
|
+
const selected = [...active, ...history].slice(0, 50);
|
|
360
|
+
const tasks = selected.map((e) => ({
|
|
361
|
+
requestId: e.view.requestId,
|
|
362
|
+
state: e.view.state,
|
|
363
|
+
delivery: e.view.delivery,
|
|
364
|
+
cancelRequested: e.view.cancelRequested,
|
|
365
|
+
error: e.view.error ?? null,
|
|
366
|
+
progress: e.view.progress ?? null,
|
|
367
|
+
waitReason: e.view.state === "queued"
|
|
368
|
+
? this.running >= this.options.concurrency
|
|
369
|
+
? "concurrency"
|
|
370
|
+
: "dispatch"
|
|
371
|
+
: null,
|
|
372
|
+
phase: terminal(e.view.state)
|
|
373
|
+
? e.view.delivery === "returned"
|
|
374
|
+
? "returned"
|
|
375
|
+
: "terminal"
|
|
376
|
+
: e.view.cancelRequested
|
|
377
|
+
? "cancelling"
|
|
378
|
+
: e.view.state,
|
|
379
|
+
queueMs: Math.max(0, (e.startedAt ?? e.finishedAt ?? now) - e.trace.at),
|
|
380
|
+
runMs: e.startedAt === undefined
|
|
381
|
+
? 0
|
|
382
|
+
: Math.max(0, (e.finishedAt ?? now) - e.startedAt),
|
|
383
|
+
remainingQueueMs: e.view.state === "queued" && e.deadline !== undefined
|
|
384
|
+
? Math.max(0, e.deadline - now)
|
|
385
|
+
: null,
|
|
386
|
+
}));
|
|
387
|
+
return freezeJson({
|
|
388
|
+
phase: this.shutdownPhase,
|
|
389
|
+
policy: { ...this.admission },
|
|
390
|
+
counts: {
|
|
391
|
+
queued: records.filter((e) => e.view.state === "queued").length,
|
|
392
|
+
running: records.filter((e) => e.view.state === "running").length,
|
|
393
|
+
terminal: records.filter((e) => terminal(e.view.state)).length,
|
|
394
|
+
},
|
|
395
|
+
tasks,
|
|
396
|
+
truncated: records.length > 50,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
310
399
|
cancel(e) {
|
|
311
400
|
if (terminal(e.view.state) || e.view.cancelRequested)
|
|
312
401
|
return;
|
|
@@ -315,15 +404,18 @@ export class ExecutionBus {
|
|
|
315
404
|
e.controller.abort();
|
|
316
405
|
this.emit(e, "cancel_requested", {});
|
|
317
406
|
if (e.view.state === "queued") {
|
|
318
|
-
this.
|
|
319
|
-
if (!this.queue.some((entry) => entry.owner === e.owner))
|
|
320
|
-
this.readyOwners = this.readyOwners.filter((owner) => owner !== e.owner);
|
|
407
|
+
this.removeQueued(e);
|
|
321
408
|
this.finish(e, "cancelled");
|
|
322
409
|
}
|
|
323
410
|
}
|
|
324
411
|
finish(e, state, result, error, resultBytes = 0) {
|
|
325
412
|
if (terminal(e.view.state))
|
|
326
413
|
return;
|
|
414
|
+
if (e.queueTimer !== undefined) {
|
|
415
|
+
clearTimeout(e.queueTimer);
|
|
416
|
+
e.queueTimer = undefined;
|
|
417
|
+
}
|
|
418
|
+
e.finishedAt = performance.now();
|
|
327
419
|
// Prepare the delivery snapshot BEFORE committing an irreversible terminal state.
|
|
328
420
|
let next = { ...e.view, state, sequence: e.view.sequence + 1 };
|
|
329
421
|
if (result !== undefined)
|
|
@@ -375,6 +467,15 @@ export class ExecutionBus {
|
|
|
375
467
|
const e = this.queue.splice(index, 1)[0];
|
|
376
468
|
if (e.view.state !== "queued")
|
|
377
469
|
continue;
|
|
470
|
+
if (e.deadline !== undefined && performance.now() >= e.deadline) {
|
|
471
|
+
this.finish(e, "failed", undefined, "QUEUE_WAIT_TIMEOUT");
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
if (e.queueTimer !== undefined) {
|
|
475
|
+
clearTimeout(e.queueTimer);
|
|
476
|
+
e.queueTimer = undefined;
|
|
477
|
+
}
|
|
478
|
+
e.startedAt = performance.now();
|
|
378
479
|
this.running++;
|
|
379
480
|
e.view.state = "running";
|
|
380
481
|
this.telemetry.record(e.trace, "started");
|
package/dist/handlers.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { setTimeout } from "node:timers/promises";
|
|
2
|
+
import { LIMITS } from "./product.js";
|
|
2
3
|
export const testHandler = {
|
|
3
4
|
validate(input) {
|
|
4
5
|
if (Object.keys(input).some((k) => !["delayMs", "fail"].includes(k)) ||
|
|
5
6
|
!Number.isSafeInteger(input.delayMs) ||
|
|
6
7
|
Number(input.delayMs) < 0 ||
|
|
7
|
-
Number(input.delayMs) >
|
|
8
|
+
Number(input.delayMs) > LIMITS.diagnosticDelayMs ||
|
|
8
9
|
typeof input.fail !== "boolean")
|
|
9
10
|
throw Error("INVALID_TEST_INPUT");
|
|
10
11
|
},
|
|
@@ -25,18 +26,18 @@ export const readHandler = {
|
|
|
25
26
|
if (Object.keys(input).some((k) => !["file_path", "offset", "limit"].includes(k)) ||
|
|
26
27
|
typeof input.file_path !== "string" ||
|
|
27
28
|
!input.file_path ||
|
|
28
|
-
input.file_path.length >
|
|
29
|
+
input.file_path.length > LIMITS.readPathChars)
|
|
29
30
|
throw Error("INVALID_READ_INPUT");
|
|
30
31
|
for (const k of ["offset", "limit"])
|
|
31
32
|
if (input[k] !== undefined &&
|
|
32
33
|
(!Number.isSafeInteger(input[k]) || Number(input[k]) < 1))
|
|
33
34
|
throw Error("INVALID_READ_INPUT");
|
|
34
|
-
if (Number(input.limit ??
|
|
35
|
+
if (Number(input.limit ?? LIMITS.readLines) > LIMITS.readLines)
|
|
35
36
|
throw Error("READ_LIMIT");
|
|
36
37
|
},
|
|
37
38
|
async run(input, ctx) {
|
|
38
39
|
if (!ctx.invoke)
|
|
39
40
|
throw Error("HOST_CAPABILITY_REQUIRED");
|
|
40
|
-
return ctx.invoke({ ...input, limit: input.limit ??
|
|
41
|
+
return ctx.invoke({ ...input, limit: input.limit ?? LIMITS.readLines }, ctx.signal);
|
|
41
42
|
},
|
|
42
43
|
};
|
package/dist/mode.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
export declare const name: "execution-bus-mode";
|
|
3
|
+
export declare const inject: string[];
|
|
4
|
+
/** An additive preset row: presentation, tool inventory and authority belong to the host PTC composition. */
|
|
5
|
+
export declare function apply(ctx: Context): void;
|
package/dist/mode.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { scopeOf } from "@deepseek-ai/dsh-scope";
|
|
2
|
+
import { apply as applyBus } from "./plugin.js";
|
|
3
|
+
import { PRODUCT, MODE_INSTRUCTIONS } from "./product.js";
|
|
4
|
+
export const name = PRODUCT.modePluginName;
|
|
5
|
+
export const inject = ["tools", "jobs", "systemPrompt"];
|
|
6
|
+
/** An additive preset row: presentation, tool inventory and authority belong to the host PTC composition. */
|
|
7
|
+
export function apply(ctx) {
|
|
8
|
+
if (scopeOf(ctx) === undefined)
|
|
9
|
+
throw Error("Xiao He mode requires an Agent preset scope");
|
|
10
|
+
ctx.effect(() => ctx.systemPrompt.section({
|
|
11
|
+
name: "XIAO_HE_EXECUTION_BUS",
|
|
12
|
+
order: 1700,
|
|
13
|
+
text: MODE_INSTRUCTIONS,
|
|
14
|
+
}));
|
|
15
|
+
applyBus(ctx);
|
|
16
|
+
}
|
package/dist/plugin.js
CHANGED
|
@@ -1,39 +1,44 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { ADMISSION_DEFAULTS, REQUEST_DESCRIPTION, REQUEST_PARAMETERS, STATE_PARAMETERS, } from "./product.js";
|
|
2
3
|
import { ExecutionOwner } from "./execution.js";
|
|
3
4
|
import { ExecutionBus, parse } from "./bus.js";
|
|
4
5
|
import { testHandler, readHandler } from "./handlers.js";
|
|
5
6
|
export const name = "execution-bus";
|
|
6
7
|
export const inject = ["tools", "jobs"];
|
|
7
8
|
export function apply(ctx) {
|
|
8
|
-
const bus = new ExecutionBus();
|
|
9
|
+
const bus = new ExecutionBus(undefined, ADMISSION_DEFAULTS);
|
|
9
10
|
bus.register("test", testHandler);
|
|
10
11
|
bus.register("host.read", readHandler);
|
|
11
12
|
const executions = new ExecutionOwner();
|
|
12
13
|
ctx.effect(() => () => executions.close(() => bus.close()));
|
|
13
14
|
ctx.tools.register({
|
|
14
|
-
name: "
|
|
15
|
-
description: "
|
|
16
|
-
parameters:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
},
|
|
15
|
+
name: "execution_bus_state",
|
|
16
|
+
description: "Read a bounded metadata-only snapshot of this agent session’s bus tasks. No inputs/results, no cross-session access. Do not poll during an awaited request.",
|
|
17
|
+
parameters: STATE_PARAMETERS,
|
|
18
|
+
output: {
|
|
19
|
+
schema: {},
|
|
20
|
+
render: (_a, v) => [{ type: "text", text: JSON.stringify(v) }],
|
|
21
|
+
},
|
|
22
|
+
async execute(raw, exec) {
|
|
23
|
+
if (executions.phase !== "open")
|
|
24
|
+
throw Error("CLOSED");
|
|
25
|
+
if (!exec.agent)
|
|
26
|
+
throw Error("OWNING_SESSION_REQUIRED");
|
|
27
|
+
if (!raw ||
|
|
28
|
+
typeof raw !== "object" ||
|
|
29
|
+
Array.isArray(raw) ||
|
|
30
|
+
Reflect.ownKeys(raw).length)
|
|
31
|
+
throw Error("INVALID_STATE_QUERY");
|
|
32
|
+
return bus.inspect({
|
|
33
|
+
sessionId: String(exec.agent.id),
|
|
34
|
+
callId: String(exec.callId),
|
|
35
|
+
});
|
|
36
36
|
},
|
|
37
|
+
});
|
|
38
|
+
ctx.tools.register({
|
|
39
|
+
name: "execution_bus",
|
|
40
|
+
description: REQUEST_DESCRIPTION,
|
|
41
|
+
parameters: REQUEST_PARAMETERS,
|
|
37
42
|
output: {
|
|
38
43
|
schema: {},
|
|
39
44
|
render: (_a, v) => [{ type: "text", text: JSON.stringify(v) }],
|
package/dist/preset.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
2
2
|
import type { PresetDefinition } from "@deepseek-ai/dsh-agent-preset-registry";
|
|
3
|
-
export declare const PRESET_ID
|
|
3
|
+
export declare const PRESET_ID: "execution-bus-test";
|
|
4
4
|
/** Capture the host-declared composition without evaluating !!js or changing disabled rows. */
|
|
5
5
|
export declare function derivePreset(content: string): PresetDefinition;
|
|
6
6
|
/** Host-plane registrar. readDocument is a public serialized declaration, not a private registry read. */
|
package/dist/preset.js
CHANGED
|
@@ -3,7 +3,8 @@ import { entryListProblem } from "@deepseek-ai/dsh-agent-preset-registry";
|
|
|
3
3
|
import { entryListSchema } from "@deepseek-ai/cordis-plugin-include";
|
|
4
4
|
import { load } from "js-yaml";
|
|
5
5
|
import { setTimeout as pause } from "node:timers/promises";
|
|
6
|
-
|
|
6
|
+
import { PRODUCT } from "./product.js";
|
|
7
|
+
export const PRESET_ID = PRODUCT.presetId;
|
|
7
8
|
/** Capture the host-declared composition without evaluating !!js or changing disabled rows. */
|
|
8
9
|
export function derivePreset(content) {
|
|
9
10
|
if (Buffer.byteLength(content) > 1048576)
|
|
@@ -30,14 +31,14 @@ export function derivePreset(content) {
|
|
|
30
31
|
inspect(rows);
|
|
31
32
|
return {
|
|
32
33
|
id: PRESET_ID,
|
|
33
|
-
name:
|
|
34
|
+
name: PRODUCT.name,
|
|
34
35
|
order: 20,
|
|
35
36
|
description: "继承宿主 PTC 工具组合,叠加执行请求排队、等待与结果回传。普通任务使用原工具,授权和沙箱由宿主管理;不支持重启恢复。",
|
|
36
37
|
plugins: [
|
|
37
38
|
...rows,
|
|
38
39
|
{
|
|
39
40
|
id: "xiao-he-execution-bus",
|
|
40
|
-
name:
|
|
41
|
+
name: PRODUCT.modeEntry,
|
|
41
42
|
},
|
|
42
43
|
],
|
|
43
44
|
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** Stable product identities and policy. Historical IDs are compatibility contracts, not display names. */
|
|
2
|
+
export declare const PRODUCT: Readonly<{
|
|
3
|
+
name: "萧何 / Xiao He";
|
|
4
|
+
packageName: "@feng0531/dsh-execution-bus";
|
|
5
|
+
presetId: "execution-bus-test";
|
|
6
|
+
presetRowId: "preset-execution-bus-test";
|
|
7
|
+
modeEntry: "@feng0531/dsh-execution-bus/mode";
|
|
8
|
+
modePluginName: "execution-bus-mode";
|
|
9
|
+
}>;
|
|
10
|
+
export declare const CORE_DEFAULTS: Readonly<{
|
|
11
|
+
concurrency: 2;
|
|
12
|
+
queueLimit: 32;
|
|
13
|
+
recordLimit: 256;
|
|
14
|
+
resultBytes: 65536;
|
|
15
|
+
retentionMs: 3600000;
|
|
16
|
+
}>;
|
|
17
|
+
export declare const ADMISSION_DEFAULTS: Readonly<{
|
|
18
|
+
sessionQueueLimit: 16;
|
|
19
|
+
maxQueueWaitMs: 120000;
|
|
20
|
+
}>;
|
|
21
|
+
export declare const LIMITS: Readonly<{
|
|
22
|
+
readLines: 100;
|
|
23
|
+
readPathChars: 2048;
|
|
24
|
+
diagnosticDelayMs: 30000;
|
|
25
|
+
}>;
|
|
26
|
+
export declare const REQUEST_PARAMETERS: {
|
|
27
|
+
readonly type: "object";
|
|
28
|
+
readonly additionalProperties: false;
|
|
29
|
+
readonly required: readonly ["protocolVersion", "messageId", "requestId", "type", "payload"];
|
|
30
|
+
readonly properties: {
|
|
31
|
+
readonly protocolVersion: {
|
|
32
|
+
readonly type: "integer";
|
|
33
|
+
readonly const: 1;
|
|
34
|
+
};
|
|
35
|
+
readonly messageId: {
|
|
36
|
+
readonly type: "string";
|
|
37
|
+
};
|
|
38
|
+
readonly requestId: {
|
|
39
|
+
readonly type: "string";
|
|
40
|
+
};
|
|
41
|
+
readonly type: {
|
|
42
|
+
readonly type: "string";
|
|
43
|
+
readonly enum: readonly ["request", "status", "result", "cancel"];
|
|
44
|
+
};
|
|
45
|
+
readonly payload: {
|
|
46
|
+
readonly type: "object";
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
export declare const STATE_PARAMETERS: {
|
|
51
|
+
readonly type: "object";
|
|
52
|
+
readonly properties: {};
|
|
53
|
+
readonly additionalProperties: false;
|
|
54
|
+
};
|
|
55
|
+
export declare const REQUEST_DESCRIPTION = "\u8427\u4F55 / Xiao He. Submit a version 1 request and WAIT for its terminal result in this tool call. Reuse requestId only for the same logical operation. Business handler: host.read {file_path,offset?,limit?<=100}. Diagnostic handler only: test {delayMs:0..30000,fail:boolean}; never use it to perform real work. Control status/result/cancel use empty payload. Acceptance is volatile, not success. No polling or restart resume.";
|
|
56
|
+
export declare const MODE_INSTRUCTIONS: string;
|
package/dist/product.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Stable product identities and policy. Historical IDs are compatibility contracts, not display names. */
|
|
2
|
+
export const PRODUCT = Object.freeze({
|
|
3
|
+
name: "萧何 / Xiao He",
|
|
4
|
+
packageName: "@feng0531/dsh-execution-bus",
|
|
5
|
+
presetId: "execution-bus-test",
|
|
6
|
+
presetRowId: "preset-execution-bus-test",
|
|
7
|
+
modeEntry: "@feng0531/dsh-execution-bus/mode",
|
|
8
|
+
modePluginName: "execution-bus-mode",
|
|
9
|
+
});
|
|
10
|
+
export const CORE_DEFAULTS = Object.freeze({
|
|
11
|
+
concurrency: 2,
|
|
12
|
+
queueLimit: 32,
|
|
13
|
+
recordLimit: 256,
|
|
14
|
+
resultBytes: 65536,
|
|
15
|
+
retentionMs: 3600000,
|
|
16
|
+
});
|
|
17
|
+
export const ADMISSION_DEFAULTS = Object.freeze({
|
|
18
|
+
sessionQueueLimit: 16,
|
|
19
|
+
maxQueueWaitMs: 120000,
|
|
20
|
+
});
|
|
21
|
+
export const LIMITS = Object.freeze({
|
|
22
|
+
readLines: 100,
|
|
23
|
+
readPathChars: 2048,
|
|
24
|
+
diagnosticDelayMs: 30000,
|
|
25
|
+
});
|
|
26
|
+
export const REQUEST_PARAMETERS = {
|
|
27
|
+
type: "object",
|
|
28
|
+
additionalProperties: false,
|
|
29
|
+
required: ["protocolVersion", "messageId", "requestId", "type", "payload"],
|
|
30
|
+
properties: {
|
|
31
|
+
protocolVersion: { type: "integer", const: 1 },
|
|
32
|
+
messageId: { type: "string" },
|
|
33
|
+
requestId: { type: "string" },
|
|
34
|
+
type: { type: "string", enum: ["request", "status", "result", "cancel"] },
|
|
35
|
+
payload: { type: "object" },
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
export const STATE_PARAMETERS = {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {},
|
|
41
|
+
additionalProperties: false,
|
|
42
|
+
};
|
|
43
|
+
export const REQUEST_DESCRIPTION = "萧何 / Xiao He. Submit a version 1 request and WAIT for its terminal result in this tool call. Reuse requestId only for the same logical operation. Business handler: host.read {file_path,offset?,limit?<=100}. Diagnostic handler only: test {delayMs:0..30000,fail:boolean}; never use it to perform real work. Control status/result/cancel use empty payload. Acceptance is volatile, not success. No polling or restart resume.";
|
|
44
|
+
export const MODE_INSTRUCTIONS = "你正在萧何模式中。这不是角色扮演。保留宿主 PTC 的日常工具能力与权限规则。普通编辑、搜索、命令、安装和其他工作使用原工具,不要声称此模式只读。execution_bus 是可选协调入口,实际业务 Handler 只有 host.read;test 仅用于用户明确要求的诊断,不做真实工作,不要强制所有任务走总线。通过 run_code 调用 await tools.execution_bus(...),受原有时间预算约束。每会话排队上限 " +
|
|
45
|
+
ADMISSION_DEFAULTS.sessionQueueLimit +
|
|
46
|
+
",排队超过 " +
|
|
47
|
+
ADMISSION_DEFAULTS.maxQueueWaitMs / 1000 +
|
|
48
|
+
" 秒且未执行会返回 QUEUE_WAIT_TIMEOUT,不自动重试。execution_bus_state 只用于用户明确诊断本会话元数据,不循环查询。请求只提交一次并等待结果,同一逻辑操作重发沿用 requestId。cancelRequested 不等于已取消,unknown 不代表执行失败,不承诺跨重启恢复。后台命令返回 Job ID 不代表已经完成。";
|
package/dist/test-mode.d.ts
CHANGED
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
export declare const inject: string[];
|
|
4
|
-
/** An additive preset row: presentation, tool inventory and authority belong to the host PTC composition. */
|
|
5
|
-
export declare function apply(ctx: Context): void;
|
|
1
|
+
/** @deprecated Compatibility-only forwarding entry. Use the package /mode export. */
|
|
2
|
+
export { name, inject, apply } from "./mode.js";
|
package/dist/test-mode.js
CHANGED
|
@@ -1,15 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export const name = "execution-bus-test-mode"; // legacy export identity, intentionally stable
|
|
4
|
-
export const inject = ["tools", "jobs", "systemPrompt"];
|
|
5
|
-
/** An additive preset row: presentation, tool inventory and authority belong to the host PTC composition. */
|
|
6
|
-
export function apply(ctx) {
|
|
7
|
-
if (scopeOf(ctx) === undefined)
|
|
8
|
-
throw Error("Xiao He mode requires an Agent preset scope");
|
|
9
|
-
ctx.effect(() => ctx.systemPrompt.section({
|
|
10
|
-
name: "XIAO_HE_EXECUTION_BUS",
|
|
11
|
-
order: 1700,
|
|
12
|
-
text: "你正在萧何模式中。这不是角色扮演。保留宿主 PTC 的日常工具能力与权限规则。普通编辑、搜索、命令、安装和其他工作使用原工具,不要声称此模式只读。execution_bus 是可选协调入口,目前只支持 test 与 host.read,不支持任意命令,不要强制所有任务走总线。通过 run_code 调用 await tools.execution_bus(...),受原有时间预算约束。总线请求只提交一次并等待结果,不循环查询;同一逻辑操作重发沿用 requestId。cancelRequested 不等于已取消,unknown 不代表执行失败,不承诺跨重启恢复。后台命令仅返回 Job ID 不代表命令已经完成。",
|
|
13
|
-
}));
|
|
14
|
-
applyBus(ctx);
|
|
15
|
-
}
|
|
1
|
+
/** @deprecated Compatibility-only forwarding entry. Use the package /mode export. */
|
|
2
|
+
export { name, inject, apply } from "./mode.js";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# 同类项目源码参考与整合(0.5.0)
|
|
2
|
+
|
|
3
|
+
## 本轮交付
|
|
4
|
+
|
|
5
|
+
以本项目 0.4.1 为基线,固定五个候选仓库提交,阅读相关源文件和测试,不将“看过 README”表述成源码审计或全部项目验收。没有运行这些第三方项目的测试,没有合入其整个插件或直接复制源码。
|
|
6
|
+
|
|
7
|
+
1. **会话准入配额**:宿主插件每会话最多 16 个排队请求,全局 32 不变;相同请求复用不占新槽,拒绝不保留 requestId。不是为每个会话预留槽位,不限制已运行数或终态记录数。
|
|
8
|
+
2. **排队等待期限**:宿主插件默认 120 秒。计时器和出队前均使用单调时钟检查;只有尚未运行任务超时才 failed / QUEUE_WAIT_TIMEOUT,不调用 Handler、不重试。运行任务继续等待或合作取消。
|
|
9
|
+
3. **会话只读快照**:新增 execution_bus_state({}),可信 Agent 身份由宿主提供。只返回本会话元数据,最多 50 项、活跃优先、其余最近受理终态优先;不返回输入/结果,不创建 Job,不增加后台轮询。没有 UI 面板或额外 HTTP 路由。
|
|
10
|
+
|
|
11
|
+
原 core 五项 options 与默认策略不变,新增第二构造参数 AdmissionPolicy;部署插件选择启用上述默认值。普通任务无权覆盖策略,宿主授权、PTC 原工具和 Job 生命周期不变。
|
|
12
|
+
|
|
13
|
+
## 固定来源、实际阅读范围与采用判断
|
|
14
|
+
|
|
15
|
+
| 来源(固定提交) | 相关文件/范围 | 采用与不采用 |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| [dsh-llm-rate-limit / 8222d017](https://github.com/Asong6824/dsh-llm-rate-limit/tree/8222d0179a64c898986c69062ca86d998873e756) | src/index.ts 的配置校验、settleEntry、pump、acquire、cancelQueued、release、卸载;src/invariant.ts;tests/rate-limit.spec.ts 的准入、取消、超时和卸载用例 | 参考“有界准入、期限先于执行、卸载清理计时器后排空”。会话配额是本项目自拟适配,不宣称上游提供同样会话公平策略。不引入令牌桶、LLM 中间件或自动限流 |
|
|
18
|
+
| [dsh-nightshift / 35805240](https://github.com/mikasaxin529/dsh-nightshift/tree/358052402749c2830045216bdb0345250f6f32f1) | lib/queue.js 与 test/queue.test.js;index.js 的排队/运行恢复、分发锁、只读状态、卸载路径;test/index.test.js 的并发分发、状态/历史和卸载用例;client.js 前端入口及状态读取结构 | 参考“由事实源生成有界视图、活跃与历史分开”。不复制前端/HTTP;不采用崩溃后 running 重新排队、自动 retry 或 continuation |
|
|
19
|
+
| [AsyncGate / 36197be8](https://github.com/PStryder/AsyncGate/tree/36197be84fca835b9edca16aa46fbf2f8d694b47) | tests/test_p18_stale_lease_cannot_write.py;src/asyncgate/db/repositories.py 的按 lease_id release | 对照旧执行者不能释放新授权的原则;本项目已有注册令牌和关联对象身份检查,无需引入远端租约/数据库。本快照未发现 LICENSE 文件,不复制代码,不推断可再分发授权 |
|
|
20
|
+
| [dsh-input-traffic / bc4b298b](https://github.com/drscrewdriver/dsh-input-traffic/tree/bc4b298be1a831cf09a68722a8f0d0607b5a9ff8) | src/client/freeze-store.ts 的 sessionId 键控与快照隔离;LICENSE | 参考会话隔离思路,不接管输入、冻结、重排或 UI store |
|
|
21
|
+
| [cc-multi-process-mcp / 234823f1](https://github.com/L-x-C/cc-multi-process-mcp/tree/234823f15616d7e9c759072e940396d939f823c2) | main.py 的进程检测、信号回收、同步/异步执行、结果文件和清理路径 | 不引入自建进程池或轮询。源码的 skip_permissions 默认和 /tmp 存储不适合本插件宿主边界;未发现 LICENSE 文件,不复制代码 |
|
|
22
|
+
|
|
23
|
+
这是相关路径的定向阅读,不是五仓全量逐文件安全审核。参考快照在开发目录,明确忽略,不进入源码提交或 npm 包。
|
|
24
|
+
|
|
25
|
+
## 许可与实现归属
|
|
26
|
+
|
|
27
|
+
dsh-llm-rate-limit、dsh-nightshift、dsh-input-traffic 的 LICENSE 已阅读,均为 MIT,分别保留各自权利;本轮只参考行为原则并重新实现,无第三方源码片段合入,无新增运行依赖。项目自身非商业源码公开许可不改变上游独立许可。未来复制实质代码需另行保留上游版权和完整许可。
|
|
28
|
+
|
|
29
|
+
## 验证及边界
|
|
30
|
+
|
|
31
|
+
新增 core 回归覆盖会话容量/拒绝可重试身份、满配额请求复用、队列超时不执行、运行任务不受队列期限中止、计时器延迟仍出队前拒绝、取消/关闭清理、不可变及会话隔离状态、50 项截断与活跃优先、非法策略和输入访问器。宿主 ToolRuntime 检查匿名/伪造 session 拒绝;真实 Node PTC 验证新工具绑定。
|
|
32
|
+
|
|
33
|
+
默认策略真实值由宿主组件测试核对;超时测试缩短 core 期限,未让宿主挂满 120 秒。真实 Node PTC 使用测试 Agent 和非约束策略,不代表当前桌面、实际沙箱或真实模型验收。
|
|
34
|
+
|
|
35
|
+
状态工具用于用户明确诊断,不用于等待中的模型轮询。requestId 是本会话元数据而非匿名统计,因此不要在编号里写敏感业务内容;core diagnostics 的脱敏数值轨迹仍不含请求编号。外部副作用不会因排队期限、取消或查询自动回滚。
|
package/docs/PRODUCT.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# 产品结构与维护契约
|
|
2
|
+
|
|
3
|
+
## 产品边界
|
|
4
|
+
|
|
5
|
+
萧何是 DSH 内的可选执行协调插件,提供独立可选择的 PTC 派生预设,不是 PTC 替代传输层,也不全局接管终端、编辑或其他工具。实际业务 Handler 为 host.read;test 是兼容保留的诊断 Handler,只有明确诊断需求时使用。测试能力存在不代表其他命令已接入总线。
|
|
6
|
+
|
|
7
|
+
0.5.1 按正式产品梳理入口和共享策略,不以更名宣称完成生产环境认证。当前真实模型、桌面 UI 和实际沙箱尚未完整验收;同进程内存状态不支持跨重启恢复。
|
|
8
|
+
|
|
9
|
+
## 入口及配置层次
|
|
10
|
+
|
|
11
|
+
| 入口 | 谁使用 | 责任 |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| 包级 bundle /preset | 目标 DSH 配置加载器 | 读取 ptc 声明快照,注册独立萧何预设 |
|
|
14
|
+
| Agent 内 /mode | 萧何预设末尾插件行 | 保留原 PTC 组合,追加总线工具和说明 |
|
|
15
|
+
| /test-mode | 旧配置兼容 | 直接映射同一个正式 mode 模块,不维护第二份实现 |
|
|
16
|
+
| 包根入口 | 明确自行组装的宿主 | 注册 execution_bus、execution_bus_state,不派生 PTC |
|
|
17
|
+
| /core | 可信嵌入方 | 自行注册 Handler 的内存核心,不提供外部身份认证 |
|
|
18
|
+
|
|
19
|
+
正式代码在 src/mode.ts,src/test-mode.ts 只重导出。package exports 的新旧入口均映射 dist/mode.js,避免 Cordis 因插件模块身份不同而重复激活;旧物理构建文件仍兼容重导出。不要同时配置两行别名。预设 ID execution-bus-test 和配置行 ID preset-execution-bus-test 是历史兼容身份,不继续用于推荐显示名称。
|
|
20
|
+
|
|
21
|
+
## 模块职责
|
|
22
|
+
|
|
23
|
+
| 模块 | 负责 | 不负责 |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| product | 固定身份、容量/准入默认值、工具参数与提示契约 | 不提供用户可覆盖的运行时配置入口 |
|
|
26
|
+
| preset | 声明派生、兼容检查和宿主注册 | 不偷偷重写源 ptc 或持久配置 |
|
|
27
|
+
| mode / test-mode 兼容层 | 作用域说明与工具安装 | 不改宿主权限/沙箱/呈现,不加载第二套 PTC |
|
|
28
|
+
| plugin | 工具身份校验、可信执行上下文和宿主 read 派发 | 不自行文件读取或创建通用命令执行端 |
|
|
29
|
+
| bus / json | 请求校验、幂等冲突、调度、取消、终态与有界状态 | 不以超时推定副作用失败、不自动重跑 |
|
|
30
|
+
| execution | Job 投影、活跃工具交付与关闭所有权 | 不作为第二个执行状态机 |
|
|
31
|
+
| handlers | 实际读取适配和明确诊断 Handler | 不把测试当成通用业务执行 |
|
|
32
|
+
| diagnostics | 有界数值轨迹 | 不保留请求内容、错误原文或充当授权审计 |
|
|
33
|
+
|
|
34
|
+
## 兼容和错误规则
|
|
35
|
+
|
|
36
|
+
稳定工具名、请求协议和历史预设身份保留。协议校验/背压错误与执行快照中的固定错误码分层;宿主服务错误继续保持真实失败,不统一吞掉,也不把 error 文本写入数值诊断。完整错误词汇见协议说明,本轮不重命名既有错误码。
|
|
37
|
+
|
|
38
|
+
单一策略定义不表示开放 YAML 调参:宿主插件目前使用固定默认值;core 构造参数仍按原契约使用。新增配置界面需单独定义校验、升级与误配置回归,不能只在文档放不存在的 config 键。
|
|
39
|
+
|
|
40
|
+
## 交付要求
|
|
41
|
+
|
|
42
|
+
- 修改身份、默认策略或工具提示时,验证共享 product 契约与实际宿主状态工具一致。
|
|
43
|
+
- 新旧 mode 必须指向同一个模块,派生预设必须使用正式入口。
|
|
44
|
+
- 严格包白名单覆盖正式模块、兼容转发与类型文件;生产隔离安装通过包导出导入两入口核对身份。
|
|
45
|
+
- 继续全量 core、真实宿主生命周期及 Node PTC 回归,不用文档测试替代桌面验收。
|
|
46
|
+
- 完整验证后统一标签 CI 和正式发布;发布不自动安装/重启日常 DSH。
|
package/docs/protocol.md
CHANGED
|
@@ -80,7 +80,7 @@ queued → running → succeeded / failed / cancelled / unknown
|
|
|
80
80
|
| 单结果 JSON | 64 KiB |
|
|
81
81
|
| 终态保留期 | 自完成起 1 小时 |
|
|
82
82
|
|
|
83
|
-
新执行请求受队列和记录容量限制;运行中重复附着受消息/监听名额限制;未过期终态复用不申请新 Job。宿主新 Job 准入还有独立容量要求。已受理的排队任务按会话轮转,各会话内部 FIFO;不抢占运行任务,没有完成时间 SLA
|
|
83
|
+
新执行请求受队列和记录容量限制;运行中重复附着受消息/监听名额限制;未过期终态复用不申请新 Job。宿主新 Job 准入还有独立容量要求。已受理的排队任务按会话轮转,各会话内部 FIFO;不抢占运行任务,没有完成时间 SLA,core 未启用会话配额时单会话可占满队列;宿主插件启用每会话16项,但终态记录与多会话总量仍可占满容量,不保证其他会话一定获得入队名额。
|
|
84
84
|
|
|
85
85
|
core 不传配置时用默认值;显式配置必须完整包含 concurrency、queueLimit、recordLimit、resultBytes、retentionMs 五项正安全整数。拒绝缺项、额外字段与访问器;构造时复制冻结,无动态更新接口。
|
|
86
86
|
|
|
@@ -94,13 +94,25 @@ core 不传配置时用默认值;显式配置必须完整包含 concurrency、
|
|
|
94
94
|
|
|
95
95
|
同存活实例、保存期内的请求复用不是端到端 exactly-once;每个实际工具调用仍有自己的宿主结果记录。没有外部终态注入接口,也没有跨重启恢复:任一阶段进程崩溃都会丢失插件内存状态。宿主日志不能复活旧 Promise,不追加聊天消息冒充原调用恢复。
|
|
96
96
|
|
|
97
|
-
## 6.
|
|
97
|
+
## 6. 准入与会话状态(0.5.0)
|
|
98
|
+
|
|
99
|
+
原五项 core options 不变。第二构造参数 AdmissionPolicy 可含 sessionQueueLimit、maxQueueWaitMs,均为 1..2147483647 安全整数,省略表示不启用该约束。策略复制冻结,未知键和访问器拒绝。宿主插件启用每会话排队 16、排队 120000ms,普通请求不可覆盖策略。
|
|
100
|
+
|
|
101
|
+
相同 requestId 的复用先于新准入检查,不重复占槽或重置原期限。每会话超额返回 SESSION_BACKPRESSURE,不受理、不占 requestId;全局/记录容量仍优先返回 BACKPRESSURE。两者只限制准入,不保留所有会话的专用槽。终态记录仍可占满 recordLimit。
|
|
102
|
+
|
|
103
|
+
期限使用单调时钟,计时器及出队前均检查,超期只将 queued 结算 failed / QUEUE_WAIT_TIMEOUT,不调用 Handler、不设置 cancelRequested、不重试,沿原 done/Job 返回。开始运行、取消或关闭时清除计时器。事件循环繁忙会令通知晚于期限,但不会让超期任务出队执行。已运行任务继续遵守原执行/取消规则。
|
|
104
|
+
|
|
105
|
+
execution_bus_state 工具仅接受 {},从宿主 Agent 取 sessionId,拒绝匿名或自报身份,关闭后拒绝查询。它读取 core inspect 的不可变快照,不创建 Job、不改变消息额度;查询会做正常终态 TTL 清理。不包含输入/输出,不访问其他会话。最多 50 条、活跃优先,其后按受理次序倒序列出终态;总数未截断,truncated 提示列表截断。元数据含 requestId、state、delivery、cancelRequested、固定 error/progress、waitReason、queueMs/runMs/remainingQueueMs。phase terminal 只表示执行终态且尚未标记返回,不代表可推断宿主 Job 清理进度。
|
|
106
|
+
|
|
107
|
+
core inspect(trustedOwner) 要求非空 sessionId,但不提供鉴权,可信身份和访问控制属于嵌入宿主。勿把 core 的任意 owner 参数直接暴露给远端。状态不是持久审计/模型消费回执,不应循环调用。
|
|
108
|
+
|
|
109
|
+
## 7. 诊断
|
|
98
110
|
|
|
99
111
|
core 实例的 `diagnostics()` 返回只读用途的独立快照:阶段计数、最近 128 条 `{operation,stage,elapsedMs}` 轨迹、关闭阶段、队列/运行/记录数及保留结果字节数。operation 是实例内递增数值,不是消息或会话 ID;elapsedMs 以单调时钟计算,阶段时间均相对受理时间,可相减得到排队/执行/返回延迟。复用计数仅在附着受理成功时增加;返回阶段按逻辑结果首次标记,不代表每个工具调用都持久化。
|
|
100
112
|
|
|
101
113
|
宿主执行所有者另有内部 Job 创建/释放/异常聚合轨迹,不作为模型工具或公开服务导出;其 operation 是独立编号,不能与 core 轨迹按编号关联。数据只在内存,不保存参数、结果、Handler 名称、请求/会话 ID、异常文本或聊天记录。总计数会持续累积,128 槽环形轨迹有界且按时间顺序导出;结果字节只在保存/过期/关闭时更新,查询不读取结果内容或重复序列化。异常计数不等于每次清理最终失败。
|
|
102
114
|
|
|
103
|
-
##
|
|
115
|
+
## 8. 扩展 Handler
|
|
104
116
|
|
|
105
117
|
包的 `./core` 导出 ExecutionBus、Handler、Capability 类型。Handler 只接收输入、requestId、signal、progress 和可选最小 capability,不接收聊天历史。register 返回绑定唯一注册令牌的幂等注销函数,不会误删后来同名注册;已受理请求继续持有原 Handler。
|
|
106
118
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# 萧何 / Xiao He 0.5.0
|
|
2
|
+
|
|
3
|
+
## 有意义的同类项目整合
|
|
4
|
+
|
|
5
|
+
阅读固定提交的同类 DSH 队列/限流插件与 MCP 执行项目相关源码和测试后,重新实现适合本插件的能力,不直接合并第三方源码或新增运行依赖。
|
|
6
|
+
|
|
7
|
+
- 宿主插件新增每会话排队上限 16(全局仍 32),拒绝错误 SESSION_BACKPRESSURE;重复请求复用不重复占名额。不是全局入队公平保证。
|
|
8
|
+
- 默认排队期限 120 秒,仅尚未运行任务到期 failed / QUEUE_WAIT_TIMEOUT,保证不调用 Handler。计时器及出队均校验单调期限,运行任务仍使用原合作取消,不超时强杀或自动重跑。
|
|
9
|
+
- 新增 execution_bus_state({}) 只读工具,身份来自宿主 Agent,拒绝匿名或自报身份;只返回本会话元数据/计数/策略,最多 50 项、活跃优先,其余终态按最近受理排序。不含输入或结果,不创建 Job。没有 UI 面板。
|
|
10
|
+
- core 原五项 options 不变,第二参数可选配置 AdmissionPolicy;不传策略保持旧行为。inspect(trustedOwner) 为可信嵌入方接口,不是独立鉴权系统。
|
|
11
|
+
|
|
12
|
+
## 验证与限制
|
|
13
|
+
|
|
14
|
+
新增准入、期限、竞态、关闭计时器、隐私隔离、活跃优先及策略校验回归;真实宿主和 Node PTC 验证状态工具与身份规则。120 秒语义用缩短 core 期限测试,默认值由宿主组件检查,不声称真实模型/桌面沙箱已验收。
|
|
15
|
+
|
|
16
|
+
不引入数据库、租约、崩溃重排、自动副作用重试或独立进程池。普通 PTC 工具能力、宿主权限/沙箱、总线原始调用等待不变。DSH 0.2.0-rc.2,非商业源码公开许可;不安装或重启日常 DSH。
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# 萧何 / Xiao He 0.5.1
|
|
2
|
+
|
|
3
|
+
## 正式产品结构整理
|
|
4
|
+
|
|
5
|
+
本版修正 test-mode 误导命名并梳理内部职责,保留执行能力和兼容规则,不通过更名宣称生产环境已全部验收。
|
|
6
|
+
|
|
7
|
+
- 正式实现移至 mode 模块;新增 /mode 导出,新派生预设默认使用它。/test-mode 是同模块兼容别名,旧源/构建文件只保留重导出,不维护两套插件。
|
|
8
|
+
- 产品身份、core/准入默认策略、读取/诊断限制、工具参数和模式提示集中到 product,减少重复定义漂移。
|
|
9
|
+
- 真实业务 host.read 与诊断 test 明确区分:兼容保留 test,但提示模型只在用户明确诊断时使用,不将其描述成真实业务处理能力。
|
|
10
|
+
- 增加正式/旧入口模块身份、默认策略与提示契约测试;真实 Loader 和生产隔离安装检查正式入口。
|
|
11
|
+
|
|
12
|
+
## 升级说明
|
|
13
|
+
|
|
14
|
+
先安装 0.5.1 或以上,再将 Agent 预设末尾叠加行改为 @feng0531/dsh-execution-bus/mode;旧版本没有该导出。旧配置不会自动重写且继续可用,不要同时放新旧两行。包级注册入口仍是 /preset,不能与 Agent 内入口混填。
|
|
15
|
+
|
|
16
|
+
历史预设 execution-bus-test、配置行 preset-execution-bus-test、工具名和协议不改,避免已有会话关联失效。普通 PTC、权限/沙箱、每会话16项和排队120秒等策略不变,不新增运行依赖。
|
|
17
|
+
|
|
18
|
+
不自动安装、重写日常配置或重启 DSH。真实模型、桌面 UI 与实际沙箱仍未完整验收;不支持跨重启恢复。
|
package/docs/validation.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 验证范围与使用限制
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
本文说明当前版本的证据边界,不是生产环境认证。当前能力基线为 **v0.5.0**,在此前生命周期、数值诊断及轮转上新增会话准入配额、排队期限和隔离状态工具;预设沿用 v0.3.0 的宿主 PTC 声明派生方式。验证环境为 Windows、Node.js 24.21.0,宿主组件锁定 DSH 0.2.0-rc.2、Cordis 4.0.4。
|
|
4
4
|
|
|
5
5
|
## 已验证什么?
|
|
6
6
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feng0531/dsh-execution-bus",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"exports": {
|
|
18
18
|
".": "./dist/plugin.js",
|
|
19
19
|
"./core": "./dist/bus.js",
|
|
20
|
-
"./
|
|
20
|
+
"./mode": "./dist/mode.js",
|
|
21
|
+
"./test-mode": "./dist/mode.js",
|
|
21
22
|
"./preset": "./dist/preset.js"
|
|
22
23
|
},
|
|
23
24
|
"files": [
|