@feng0531/dsh-execution-bus 0.4.1 → 0.5.0

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 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.4.1**
7
+ DSH 独立插件:`@feng0531/dsh-execution-bus` · 当前版本 **0.5.0**
8
8
 
9
9
  > 萧何模式从宿主已声明的 PTC 组合派生,保留日常工具,再叠加可选执行总线。已验证隔离的真实 Node PTC → 原 read + 总线调用,以及脚本模型内存会话;真实模型、桌面界面与实际沙箱仍未验收。**不支持重启后恢复任务。**
10
10
 
@@ -115,7 +115,7 @@ flowchart TD
115
115
  2. 在你选定的隔离 DSH 配置依赖目录安装固定版本,不要在 DSH 程序安装目录执行:
116
116
 
117
117
  ```powershell
118
- npm install --ignore-scripts @feng0531/dsh-execution-bus@0.4.1
118
+ npm install --ignore-scripts @feng0531/dsh-execution-bus@0.5.0
119
119
  ```
120
120
 
121
121
  这是 npm 目录示例。若目标配置使用 pnpm,应沿用其包管理器,不混用锁文件。遇到宿主 peer 版本冲突就停止,不用强制安装绕过。
@@ -204,7 +204,7 @@ stateDiagram-v2
204
204
  | `queued` | 已受理,尚未执行;不代表成功 |
205
205
  | `running` | 正在执行;等待窗口超时不自动把它变成失败 |
206
206
  | `succeeded` | 执行成功且总线保存了结果 |
207
- | `failed` | 处理器执行失败 |
207
+ | `failed` | 处理器失败,或排队期限到达但尚未执行;查看 error 区分 |
208
208
  | `cancelled` | 已确认取消 |
209
209
  | `unknown` | 不能可靠保留或确认结果,不可盲目重跑有副作用操作 |
210
210
 
@@ -212,13 +212,19 @@ stateDiagram-v2
212
212
 
213
213
  ## 调度与诊断
214
214
 
215
- 0.4.0 起对已受理的排队请求按会话轮转,同一会话保持 FIFO;不抢占运行任务,也不保证队列满时为其他会话预留名额。执行结果仍由 core 唯一提交,宿主 Job 只负责任务展示、控制与生命周期关联。
215
+ 0.4.0 起对已受理的排队请求按会话轮转,同一会话保持 FIFO;0.5.0 再增加每会话准入配额与排队期限。不抢占运行任务,也不保证队列满时为其他会话预留名额。执行结果仍由 core 唯一提交,宿主 Job 只负责任务展示、控制与生命周期关联。
216
216
 
217
217
  core 开发者可调用实例 `diagnostics()` 取得队列、运行数、保留结果字节及最近 128 条数值阶段轨迹。不含请求/会话 ID、输入、输出或错误文本,不写日志;诊断不增加模型调用工具,也不是跨重启审计记录。详见[优化范围与验证](<docs/OPTIMIZATION.md>)。
218
218
 
219
+ ### 查看本会话状态
220
+
221
+ 在 PTC 中按需执行 `return await tools.execution_bus_state({})`,仅在用户诊断时调用,不循环轮询。返回本会话排队/运行/终态数量、默认策略,以及最多 50 条任务元数据(活跃优先,其余最近受理的终态优先)。有 requestId、状态、等待原因、取消标记、单调时钟时长、固定错误码和 delivery,不含输入、结果或其他会话信息。delivery 不是模型已消费回执,也不能从视图断言 Job 正在清理。
222
+
223
+ core 开发者可用 `new ExecutionBus(options, {sessionQueueLimit:16,maxQueueWaitMs:120000})` 和 `inspect(trustedOwner)`;第二参数省略时保持旧版无限会话配额/无排队期限行为,原五项 options 不变。该可信 owner 由调用者提供,core 不是身份认证边界,宿主工具从真实 Agent 取得身份且只接受空对象。
224
+
219
225
  ## 容量与重要限制
220
226
 
221
- 默认并发 **2**,排队上限 **32**,最多保存 **256** 条任务记录,单结果最多 **64 KiB**,完成结果保留期 **1 小时**,在新请求和查询时惰性清理;不是一小时一到即定时删除。队列或记录满时拒绝新任务;宿主 Job 自身也有容量限制。
227
+ 宿主插件默认并发 **2**,全局排队上限 **32**,每会话排队上限 **16**;排队超过 **120 秒**且尚未开始执行时结算为 `failed / QUEUE_WAIT_TIMEOUT`,不调用 Handler。运行后不会被该排队期限中止,PTC 外层仍有独立时间预算。最多保存 **256** 条任务记录,单结果最多 **64 KiB**,完成结果保留期 **1 小时**,在新请求和查询时惰性清理;不是一小时一到即定时删除。队列或记录满时拒绝新任务;宿主 Job 自身也有容量限制。
222
228
 
223
229
  - **全部状态在内存中。** 受理没有落盘,插件关闭或进程崩溃后,不能依靠它恢复原调用或保证请求不丢失。
224
230
  - **不自动重跑任务。** 通信超时不等于执行失败,取消不等于回滚。Job 等待服务异常会在原执行排空后返回服务错误,执行可能已成功;应核对原 requestId 的状态,不要换编号盲目重跑。
@@ -250,7 +256,8 @@ npm run test:install # 临时目录安装,不操作日常 DSH
250
256
 
251
257
  - [协议和投递语义](<docs/protocol.md>):字段、状态、重复处理与取消规则。
252
258
  - [宿主兼容性与源码调用链](<docs/host-compatibility.md>):为什么可以等待,为什么不能跨重启恢复。
253
- - [验证记录](<docs/validation.md>)、[0.4.1 发布说明](<docs/releases/0.4.1.md>):已验证与未验证范围。
259
+ - [验证记录](<docs/validation.md>)、[0.5.0 发布说明](<docs/releases/0.5.0.md>):已验证与未验证范围。
260
+ - [同类项目源码整合](<docs/INTEGRATION.md>):固定来源、采用范围、许可与验证边界。
254
261
  - [维护者发布流程](<docs/RELEASING.md>):标签 CI、安装包校验与正式发布门禁。
255
262
 
256
263
  ## 许可
@@ -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,7 @@
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";
4
5
  export function object(v) {
5
6
  if (!v || typeof v !== "object" || Array.isArray(v))
6
7
  throw Error("INVALID_OBJECT");
@@ -56,6 +57,7 @@ const terminal = (s) => !["queued", "running"].includes(s);
56
57
  /** Volatile single-process bus; no acceptance durability or automatic execution retry. */
57
58
  export class ExecutionBus {
58
59
  options;
60
+ admission;
59
61
  entries = new Map();
60
62
  handlers = new Map();
61
63
  queue = [];
@@ -82,8 +84,25 @@ export class ExecutionBus {
82
84
  recordLimit: 256,
83
85
  resultBytes: 65536,
84
86
  retentionMs: 3600000,
85
- }) {
87
+ }, admission = {}) {
86
88
  this.options = options;
89
+ this.admission = admission;
90
+ const allowed = ["sessionQueueLimit", "maxQueueWaitMs"];
91
+ if (!admission ||
92
+ typeof admission !== "object" ||
93
+ Array.isArray(admission) ||
94
+ Reflect.ownKeys(admission).some((key) => typeof key !== "string" || !allowed.includes(key)))
95
+ throw Error("INVALID_ADMISSION_POLICY");
96
+ for (const key of Reflect.ownKeys(admission)) {
97
+ const descriptor = Object.getOwnPropertyDescriptor(admission, key);
98
+ if (!("value" in descriptor) ||
99
+ !descriptor.enumerable ||
100
+ !Number.isSafeInteger(descriptor.value) ||
101
+ descriptor.value < 1 ||
102
+ descriptor.value > 2147483647)
103
+ throw Error("INVALID_ADMISSION_POLICY");
104
+ }
105
+ this.admission = Object.freeze({ ...admission });
87
106
  const keys = [
88
107
  "concurrency",
89
108
  "queueLimit",
@@ -213,6 +232,10 @@ export class ExecutionBus {
213
232
  if (this.entries.size >= this.options.recordLimit ||
214
233
  this.queue.length >= this.options.queueLimit)
215
234
  throw Error("BACKPRESSURE");
235
+ if (this.admission.sessionQueueLimit !== undefined &&
236
+ this.queue.filter((entry) => entry.owner === owner.sessionId).length >=
237
+ this.admission.sessionQueueLimit)
238
+ throw Error("SESSION_BACKPRESSURE");
216
239
  let resolve;
217
240
  const done = new Promise((r) => (resolve = r));
218
241
  e = {
@@ -245,6 +268,22 @@ export class ExecutionBus {
245
268
  this.telemetry.record(e.trace, "accepted");
246
269
  const attached = this.attachment(e, onEvent, m.messageId);
247
270
  this.queue.push(e);
271
+ if (this.admission.maxQueueWaitMs !== undefined) {
272
+ e.deadline = performance.now() + this.admission.maxQueueWaitMs;
273
+ const entry = e;
274
+ const expire = () => {
275
+ if (entry.view.state !== "queued")
276
+ return;
277
+ const remaining = entry.deadline - performance.now();
278
+ if (remaining > 0) {
279
+ entry.queueTimer = setTimeout(expire, Math.ceil(remaining));
280
+ return;
281
+ }
282
+ this.removeQueued(entry);
283
+ this.finish(entry, "failed", undefined, "QUEUE_WAIT_TIMEOUT");
284
+ };
285
+ e.queueTimer = setTimeout(expire, this.admission.maxQueueWaitMs);
286
+ }
248
287
  if (!this.readyOwners.includes(e.owner))
249
288
  this.readyOwners.push(e.owner);
250
289
  queueMicrotask(() => this.pump());
@@ -307,6 +346,61 @@ export class ExecutionBus {
307
346
  }
308
347
  return this.view(e);
309
348
  }
349
+ removeQueued(e) {
350
+ this.queue = this.queue.filter((entry) => entry !== e);
351
+ if (!this.queue.some((entry) => entry.owner === e.owner))
352
+ this.readyOwners = this.readyOwners.filter((owner) => owner !== e.owner);
353
+ }
354
+ /** Trusted host owner required. Never exposes other sessions or business payloads. */
355
+ inspect(owner) {
356
+ if (!owner || typeof owner.sessionId !== "string" || !owner.sessionId)
357
+ throw Error("OWNING_SESSION_REQUIRED");
358
+ this.sweep();
359
+ const now = performance.now();
360
+ const records = [...this.entries.values()].filter((e) => e.owner === owner.sessionId);
361
+ // Active work must not disappear behind newer terminal history.
362
+ const active = records.filter((e) => !terminal(e.view.state));
363
+ const history = records.filter((e) => terminal(e.view.state)).reverse();
364
+ const selected = [...active, ...history].slice(0, 50);
365
+ const tasks = selected.map((e) => ({
366
+ requestId: e.view.requestId,
367
+ state: e.view.state,
368
+ delivery: e.view.delivery,
369
+ cancelRequested: e.view.cancelRequested,
370
+ error: e.view.error ?? null,
371
+ progress: e.view.progress ?? null,
372
+ waitReason: e.view.state === "queued"
373
+ ? this.running >= this.options.concurrency
374
+ ? "concurrency"
375
+ : "dispatch"
376
+ : null,
377
+ phase: terminal(e.view.state)
378
+ ? e.view.delivery === "returned"
379
+ ? "returned"
380
+ : "terminal"
381
+ : e.view.cancelRequested
382
+ ? "cancelling"
383
+ : e.view.state,
384
+ queueMs: Math.max(0, (e.startedAt ?? e.finishedAt ?? now) - e.trace.at),
385
+ runMs: e.startedAt === undefined
386
+ ? 0
387
+ : Math.max(0, (e.finishedAt ?? now) - e.startedAt),
388
+ remainingQueueMs: e.view.state === "queued" && e.deadline !== undefined
389
+ ? Math.max(0, e.deadline - now)
390
+ : null,
391
+ }));
392
+ return freezeJson({
393
+ phase: this.shutdownPhase,
394
+ policy: { ...this.admission },
395
+ counts: {
396
+ queued: records.filter((e) => e.view.state === "queued").length,
397
+ running: records.filter((e) => e.view.state === "running").length,
398
+ terminal: records.filter((e) => terminal(e.view.state)).length,
399
+ },
400
+ tasks,
401
+ truncated: records.length > 50,
402
+ });
403
+ }
310
404
  cancel(e) {
311
405
  if (terminal(e.view.state) || e.view.cancelRequested)
312
406
  return;
@@ -315,15 +409,18 @@ export class ExecutionBus {
315
409
  e.controller.abort();
316
410
  this.emit(e, "cancel_requested", {});
317
411
  if (e.view.state === "queued") {
318
- this.queue = this.queue.filter((x) => x !== e);
319
- if (!this.queue.some((entry) => entry.owner === e.owner))
320
- this.readyOwners = this.readyOwners.filter((owner) => owner !== e.owner);
412
+ this.removeQueued(e);
321
413
  this.finish(e, "cancelled");
322
414
  }
323
415
  }
324
416
  finish(e, state, result, error, resultBytes = 0) {
325
417
  if (terminal(e.view.state))
326
418
  return;
419
+ if (e.queueTimer !== undefined) {
420
+ clearTimeout(e.queueTimer);
421
+ e.queueTimer = undefined;
422
+ }
423
+ e.finishedAt = performance.now();
327
424
  // Prepare the delivery snapshot BEFORE committing an irreversible terminal state.
328
425
  let next = { ...e.view, state, sequence: e.view.sequence + 1 };
329
426
  if (result !== undefined)
@@ -375,6 +472,15 @@ export class ExecutionBus {
375
472
  const e = this.queue.splice(index, 1)[0];
376
473
  if (e.view.state !== "queued")
377
474
  continue;
475
+ if (e.deadline !== undefined && performance.now() >= e.deadline) {
476
+ this.finish(e, "failed", undefined, "QUEUE_WAIT_TIMEOUT");
477
+ continue;
478
+ }
479
+ if (e.queueTimer !== undefined) {
480
+ clearTimeout(e.queueTimer);
481
+ e.queueTimer = undefined;
482
+ }
483
+ e.startedAt = performance.now();
378
484
  this.running++;
379
485
  e.view.state = "running";
380
486
  this.telemetry.record(e.trace, "started");
package/dist/plugin.js CHANGED
@@ -5,11 +5,38 @@ import { testHandler, readHandler } from "./handlers.js";
5
5
  export const name = "execution-bus";
6
6
  export const inject = ["tools", "jobs"];
7
7
  export function apply(ctx) {
8
- const bus = new ExecutionBus();
8
+ const bus = new ExecutionBus(undefined, {
9
+ sessionQueueLimit: 16,
10
+ maxQueueWaitMs: 120000,
11
+ });
9
12
  bus.register("test", testHandler);
10
13
  bus.register("host.read", readHandler);
11
14
  const executions = new ExecutionOwner();
12
15
  ctx.effect(() => () => executions.close(() => bus.close()));
16
+ ctx.tools.register({
17
+ name: "execution_bus_state",
18
+ 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.",
19
+ parameters: { type: "object", properties: {}, additionalProperties: false },
20
+ output: {
21
+ schema: {},
22
+ render: (_a, v) => [{ type: "text", text: JSON.stringify(v) }],
23
+ },
24
+ async execute(raw, exec) {
25
+ if (executions.phase !== "open")
26
+ throw Error("CLOSED");
27
+ if (!exec.agent)
28
+ throw Error("OWNING_SESSION_REQUIRED");
29
+ if (!raw ||
30
+ typeof raw !== "object" ||
31
+ Array.isArray(raw) ||
32
+ Reflect.ownKeys(raw).length)
33
+ throw Error("INVALID_STATE_QUERY");
34
+ return bus.inspect({
35
+ sessionId: String(exec.agent.id),
36
+ callId: String(exec.callId),
37
+ });
38
+ },
39
+ });
13
40
  ctx.tools.register({
14
41
  name: "execution_bus",
15
42
  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. Handlers: test {delayMs:0..30000,fail:boolean}, host.read {file_path,offset?,limit?<=100}. Control types status/result/cancel use empty payload. Acceptance is volatile, not execution success. No polling needed; no restart resume.",
package/dist/test-mode.js CHANGED
@@ -9,7 +9,7 @@ export function apply(ctx) {
9
9
  ctx.effect(() => ctx.systemPrompt.section({
10
10
  name: "XIAO_HE_EXECUTION_BUS",
11
11
  order: 1700,
12
- text: "你正在萧何模式中。这不是角色扮演。保留宿主 PTC 的日常工具能力与权限规则。普通编辑、搜索、命令、安装和其他工作使用原工具,不要声称此模式只读。execution_bus 是可选协调入口,目前只支持 test 与 host.read,不支持任意命令,不要强制所有任务走总线。通过 run_code 调用 await tools.execution_bus(...),受原有时间预算约束。总线请求只提交一次并等待结果,不循环查询;同一逻辑操作重发沿用 requestId。cancelRequested 不等于已取消,unknown 不代表执行失败,不承诺跨重启恢复。后台命令仅返回 Job ID 不代表命令已经完成。",
12
+ text: "你正在萧何模式中。这不是角色扮演。保留宿主 PTC 的日常工具能力与权限规则。普通编辑、搜索、命令、安装和其他工作使用原工具,不要声称此模式只读。execution_bus 是可选协调入口,目前只支持 test 与 host.read,不支持任意命令,不要强制所有任务走总线。通过 run_code 调用 await tools.execution_bus(...),受原有时间预算约束。总线默认每会话最多排队16项,排队超过120秒且尚未开始执行会返回QUEUE_WAIT_TIMEOUT,不自动重试。execution_bus_state只用于用户明确诊断本会话的元数据状态,不在等待期间循环查询。总线请求只提交一次并等待结果,不循环查询;同一逻辑操作重发沿用 requestId。cancelRequested 不等于已取消,unknown 不代表执行失败,不承诺跨重启恢复。后台命令仅返回 Job ID 不代表命令已经完成。",
13
13
  }));
14
14
  applyBus(ctx);
15
15
  }
@@ -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/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
- ## 7. 扩展 Handler
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。
@@ -1,6 +1,6 @@
1
1
  # 验证范围与使用限制
2
2
 
3
- 本文说明当前版本的证据边界,不是生产环境认证。当前优化基线为 **v0.4.0**,新增执行所有权/关闭、通知重入边界、数值诊断及会话轮转;预设沿用 v0.3.0 的宿主 PTC 声明派生方式。验证环境为 Windows、Node.js 24.21.0,宿主组件锁定 DSH 0.2.0-rc.2、Cordis 4.0.4。
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.4.1",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "license": "SEE LICENSE IN LICENSE",