@v587d/capital-generation 2.9.1 → 2.10.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/CHANGELOG.md +73 -0
- package/README.md +6 -3
- package/chart-ui/README.md +3 -3
- package/chart-ui/package.json +1 -1
- package/config/package.json +1 -1
- package/lib/agents/bash-guard.d.ts +10 -6
- package/lib/agents/bash-guard.js +15 -12
- package/lib/agents/root-tool-policy.js +2 -0
- package/lib/agents/settled-wake.d.ts +43 -0
- package/lib/agents/settled-wake.js +81 -0
- package/lib/agents/wait-for-agents.d.ts +79 -0
- package/lib/agents/wait-for-agents.js +256 -0
- package/lib/chart/artifact-ref.d.ts +4 -1
- package/lib/chart/artifact-ref.js +2 -1
- package/lib/chart/delivery.d.ts +80 -0
- package/lib/chart/delivery.js +157 -0
- package/lib/chart/events.d.ts +3 -1
- package/lib/chart/events.js +52 -1
- package/lib/chart/html.js +16 -0
- package/lib/chart/present-tool.d.ts +8 -0
- package/lib/chart/present-tool.js +92 -22
- package/lib/chart/tool.js +63 -9
- package/lib/data-collector/dataset-tools.js +1 -1
- package/lib/data-collector/store.d.ts +1 -0
- package/lib/data-collector/store.js +2 -2
- package/lib/orchestration-entry.d.ts +11 -0
- package/lib/orchestration-entry.js +6 -0
- package/lib/present-entry.d.ts +13 -3
- package/lib/present-entry.js +123 -1
- package/lib/web-retriever/local-fetch.d.ts +1 -1
- package/lib/web-retriever/local-fetch.js +1 -1
- package/lib/web-retriever/wind-client.d.ts +1 -1
- package/lib/web-retriever/wind-client.js +1 -1
- package/package.json +8 -2
- package/preset/capital-generation/README.md +34 -16
- package/preset/capital-generation/agent.patch.yml +30 -34
- package/preset/capital-generation/skills/capital-chart-protocol/SKILL.md +3 -2
- package/preset/capital-generation/skills/capital-data-profile/SKILL.md +9 -4
- package/preset/capital-generation/skills/capital-data-query/SKILL.md +47 -19
- package/preset/capital-generation/skills/capital-equity-research/SKILL.md +5 -1
- package/preset/capital-generation/skills/capital-orchestration/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-visualization-protocol/SKILL.md +12 -2
- package/watchlist/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,79 @@
|
|
|
7
7
|
- **第三位(patch)**:不破坏既有工具 / 配置 / 会话的新增与修复(如 2.1.1、2.1.2)。
|
|
8
8
|
- **第二位(minor)**:有需要用户知晓的行为变更,且段内附迁移说明(如 2.1.0 的图表呈现通道重做、2.2.0 的数据源与检索来源扩容、2.4.0 的**必须换宿主版本**、2.9.0 的选择器收敛与质检前置——本插件自己的工具与数据契约没变,所以只抬第二位)。
|
|
9
9
|
|
|
10
|
+
## [2.10.0] - 2026-10-23
|
|
11
|
+
|
|
12
|
+
自动模式收口为**单轮等待 + 一次交付**:主 Agent 委派后等整棵子树收敛,再把结论和图表一次交到最后
|
|
13
|
+
一轮;同时补上图表交付去重/标题校验、可读文件名,以及数据窗口、成交量单位与财务口径的硬规则。
|
|
14
|
+
本插件自己的工具与数据契约不变,不需要改配置。
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **自动模式单轮等待**:主 Agent 新增 `wait_for_agents`(仅根会话)。委派或发消息后先等整棵子 Agent
|
|
19
|
+
树收敛,子角色回传在同一轮内处理,不再每条回执都单独落成一个可见轮次。
|
|
20
|
+
- **四态回执**:`all_settled` / `message` / `user_input` / `timeout`;`timeout` 会列出仍在运行的
|
|
21
|
+
子 Agent,既不是完成也不是失败。
|
|
22
|
+
- **图表交付质量闸门**:`present_charts` 登记前按 `dataset + kind + 标题` 去重,并校验标题里的
|
|
23
|
+
6 位股票代码与 dataset 一致;重复图/错标题图进 `failed`,不再进入交付卡片。
|
|
24
|
+
- **可读图表产物与读图指引**:新图文件名带股票代码/ASCII 标题词与图形类型(如
|
|
25
|
+
`300803-kline-ab12cd34.html`),HTML 顶部补图形类型、来源、时间字段和横轴/纵轴/序列/K 线说明。
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- **等待不再以可见等待行结束回合**:主 Agent 先调用 `wait_for_agents`;`message` / `user_input`
|
|
30
|
+
先处理后继续等,`all_settled` 才进入逐行对账,`timeout` 才写等待行。等待期间仍禁止外部检索、
|
|
31
|
+
反复 `list_agents` 与轮询。
|
|
32
|
+
- **单轮等待只归根会话**:`tools/orchestration` 只挂在根 preset;子 Agent 继续用 `send_message`
|
|
33
|
+
回传,不能等待兄弟或父 Agent。
|
|
34
|
+
- **派生统计算子收紧**:`N 日` 一律按最近 N 个交易日计算;MA20/10 日高不再用自然日窗口,成交量
|
|
35
|
+
明确按股展示,量比注明分母是否含当日。
|
|
36
|
+
- **财务与估值口径对齐**:`operating_income` 与媒体“营业总收入”、H1 累计与 Q2 单季、归母与
|
|
37
|
+
净利润、结构化扣非与媒体扣非分开标注;PE/PB/PS/PCF 并列,倍数分化时写“估值分化”。
|
|
38
|
+
- **Specialist 调度收紧**:单次最多 8 个并发,使用 `Promise.allSettled`,按
|
|
39
|
+
`dataset_id + chart_source_ref` 记录成功并只重试失败项,禁止重发已成功图表。
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- **MA20/10 日高窗口错误**:修复把 12 个交易日当 MA20、8 个交易日当 10 日高的问题;同类问题
|
|
44
|
+
在 data_junior 的派生统计与主 Agent 的结论复核中都会被协议拦下。
|
|
45
|
+
- **成交量单位误写**:A 股 `history.volume` 原值为股,不再写成“万手”。
|
|
46
|
+
- **H1/单季与扣非混用**:不再把媒体 H1 营业总收入与结构化 Q2 `operating_income` 放在同一增速
|
|
47
|
+
对比,也不在“扣非缺失”的同时引用媒体扣非。
|
|
48
|
+
- **重复 K 线与错标题图**:`present_charts` 不再接受同 dataset/kind/标题的重复图,也不会把
|
|
49
|
+
“同花顺 300803”这类标题登记进交付卡片。
|
|
50
|
+
- **Specialist 并发失败吞任务**:`Promise.all` 遇到并发上限错误时不再吞掉整批,失败任务可被
|
|
51
|
+
单独重试;已成功的 K 线不再被重复创建。
|
|
52
|
+
|
|
53
|
+
## [2.9.2] - 2026-10-23
|
|
54
|
+
|
|
55
|
+
`data_junior` 接入官方 PTC(`run_code`):内层工具调用压进一段程序,多 Dataset 并发、`too_large`
|
|
56
|
+
收窄与 skill 读取不再逐次往返模型。实机同类任务由 15 分钟级降到 2–3 分钟级;工具、数据契约、
|
|
57
|
+
用户配置与宿主基线都不变。图表交付的收尾闸门同时修复结算通知竞争与同轮重复写入。
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
|
|
61
|
+
- **分析师的 PTC 执行面**:`data_junior` 只保留 `run_code` 直调,Dataset / skill / 出图委派 /
|
|
62
|
+
回传都通过 SDK `tools.*` 在程序内完成;不再挂 bash / pwsh,同时保住了“不读原始行”的边界。
|
|
63
|
+
- **多 Dataset 并发与 `too_large` 自愈**:同一程序里可以 `Promise.all` 多份 `describe_dataset`;
|
|
64
|
+
结果过大时按 hint 收窄后继续,不再多开模型轮次。
|
|
65
|
+
- **PTC 会话报告**:`npm run ptc:report` 汇总 `run_code` 段数、`Promise.all`、skill 打印、
|
|
66
|
+
`too_large` 与并发窗口,供实机验收。
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- **分析师提示词与三册协议同步 PTC 写法**:第一次加载协议就明确 skill 也要在 `run_code` 内用
|
|
71
|
+
`tools.skill`,不要直调原生工具;查询、profile 与可视化协议同步程序内调度。
|
|
72
|
+
- **图表交付继续由收尾流写入**:`present_charts` 只记录清单,宿主在最终答复前统一落
|
|
73
|
+
`deliverables/presented`;结算通知不再阻塞收尾,且同一 exchange 不会重复写卡。
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- **图表卡在结算通知竞争时丢失**:子 Agent 的 `subagent-settled` 通知在收尾流中途到达时,
|
|
78
|
+
不再被当成“还有待处理消息”;通知同时清掉已结算的直属子 Agent。
|
|
79
|
+
- **同一轮重复交付**:后续 `finish` 帧缺少 `turn` 时不再写第二张卡。
|
|
80
|
+
- **直调 skill 误调用**:软约束已把 `tools.skill` 提到第一次加载协议处;模型仍可能试错一次,
|
|
81
|
+
宿主拒绝后会自愈,不影响结果。
|
|
82
|
+
|
|
10
83
|
## [2.9.1] - 2026-10-21
|
|
11
84
|
|
|
12
85
|
修复实机 `dsh web` 反馈的两处收尾体验:图表交付不再散落在等待轮,最终答复不再只剩一句
|
package/README.md
CHANGED
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
<p align="center">
|
|
19
19
|
<a href="https://awesome-dsh-plugin.com"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="awesome · DSH plugin"></a>
|
|
20
20
|
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-green" alt="License"></a>
|
|
21
|
-
<a href="https://github.com/v587d/capital-generation/releases"><img src="https://img.shields.io/badge/version-2.
|
|
21
|
+
<a href="https://github.com/v587d/capital-generation/releases"><img src="https://img.shields.io/badge/version-2.10.0-9cf" alt="Version"></a>
|
|
22
22
|
<a href="https://www.npmjs.com/package/@v587d/capital-generation"><img src="https://img.shields.io/npm/v/%40v587d%2Fcapital-generation" alt="npm version"></a>
|
|
23
|
+
<a href="https://github.com/deepseek-ai/deepseek-harness"><img src="https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white" alt="powered-by-dsh"></a>
|
|
23
24
|
</p>
|
|
24
25
|
|
|
25
26
|
# Capital Generation
|
|
@@ -114,11 +115,13 @@ https://github.com/v587d/capital-generation
|
|
|
114
115
|
|
|
115
116
|
**每份结构化数据都会先做质检。** 无论你有没有提图表,自动模式收到行情或财务数据后都会先交给分析师检查结构、时间轴、缺失与基础统计;要不要图是检查之后的一个判断。明确说“不要图”时,质检照做,只是停在出图之前。
|
|
116
117
|
|
|
118
|
+
自动模式会把子角色工作期间的中间回执留在同一轮里处理,等材料收齐后再给出完整结论,而不是每条中间进展都单独回一段。
|
|
119
|
+
|
|
117
120
|
## 试试这么用
|
|
118
121
|
|
|
119
122
|
在新的自动模式会话中使用以下指令。将方括号内容换成自己的标的、公告或持仓;证券代码写完整市场后缀,例如 `600519.SH`。每条指令单独发送,便于观察结果。
|
|
120
123
|
|
|
121
|
-
[
|
|
124
|
+
[样例:指南针、同花顺复盘分析报告(20261023)](docs/sample/指南针-同花顺-复盘分析报告.md)
|
|
122
125
|
|
|
123
126
|
**完整投资研究报告**
|
|
124
127
|
|
|
@@ -190,7 +193,7 @@ https://github.com/v587d/capital-generation
|
|
|
190
193
|
- **安装后找不到模式?** 确认插件出现在「插件」→ 已安装列表;Web / CLI profile 安装后要重启该 profile,并在新会话选择模式。选择器里应看到自动模式、行情专家、资讯专员;分析师和绘图专员是自动模式的内部角色,不会出现在这里。
|
|
191
194
|
- **配置在哪?** 打开「插件」→ 已安装的 **Capital Generation**,页面顶部就是「数据源 / 精选 Skills / 包含的组件」三格,五把密钥都在第一格;不在 DSH 的普通「设置」页面,也不用点进任何一行组件。保存密钥后新建会话再试。
|
|
192
195
|
- **升级到 2.9.0 后,分析师和绘图专员去哪了?** 这一版把它们收进自动模式内部,不再出现在选择器与预设管理里,避免误开一个没有取数入口的空会话。旧会话仍能按原身份恢复;新任务直接用自动模式提出即可。
|
|
193
|
-
- **桌面端升级后仍是旧版?** 桌面端安装时会固定所解析的版本,不会自动升级。卸载后重新安装,再到插件详情查看版本号;如需指定版本,可在地址末尾加 `#v2.
|
|
196
|
+
- **桌面端升级后仍是旧版?** 桌面端安装时会固定所解析的版本,不会自动升级。卸载后重新安装,再到插件详情查看版本号;如需指定版本,可在地址末尾加 `#v2.10.0` 或 `@2.10.0`。
|
|
194
197
|
- **刚发布的新版本装不到?** 桌面端取包时会跳过**刚发布不久**的版本(宿主侧包管理器的发布冷却策略,实测会静默装成上一个版本),国内镜像的同步也可能滞后一段时间。急着用最新代码,改用仓库地址安装最直接。
|
|
195
198
|
- **网页抓取失败?** 配置卡片中的「允许启动本地提取网页内容」默认开启:AnySearch 提取失败时可尝试本机直连。关闭后,本机直连的具名来源查询也不可用;请检查该开关和网络连接。
|
|
196
199
|
- **Wind 那部分会消耗积分?** 会。公告与新闻检索、宏观 / 行业 / 汇率指标、按日期区间取的历史 K 线都按次消耗你在 Wind 侧的额度;额度与条款以 Wind 为准,插件不做用量封顶。没配 `WIND_API_KEY` 时这几类不可用,其余功能照常。
|
package/chart-ui/README.md
CHANGED
|
@@ -90,9 +90,9 @@ route owner 自己负责,平台自己的 `/api` 与首页分别走 `connection
|
|
|
90
90
|
|
|
91
91
|
## 本轮呈现(官方交付通道)
|
|
92
92
|
|
|
93
|
-
auto 模式下 `render_chart`
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
auto 模式下 `render_chart` 不再自动登记:`present_charts` 只记录本轮图表,宿主在**收尾流结束、
|
|
94
|
+
收尾消息落盘之前**写一条**官方** `deliverables/presented` 事件到"用户正在看的根会话",把
|
|
95
|
+
`capital-analysis/charts/<chart_id>/chart.html` 登记为**收尾轮交付物**(单体载体仍在出图时
|
|
96
96
|
自动登记):
|
|
97
97
|
|
|
98
98
|
- 载荷只有官方契约字段:`turn` / `callId`(`capital-chart:<chart_id>` 句柄)/ `files[]`
|
package/chart-ui/package.json
CHANGED
package/config/package.json
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* inspected (the sandbox blocks writes, not reads/network). Delegated approvals
|
|
3
|
-
* `never`, so escalation auto-rejects with a misleading "user rejected"; block early
|
|
4
|
-
* Root escalation still uses ask; do not pin delegated sessions read-only
|
|
5
|
-
* needs workspace-write). `tools.guard` is monotonic; pre-execute is a
|
|
1
|
+
/** Execution guard for bash/pwsh/run_code: blocks only doomed sandbox escalation; command/program
|
|
2
|
+
* content is never inspected (the sandbox blocks writes, not reads/network). Delegated approvals
|
|
3
|
+
* are fixed to `never`, so escalation auto-rejects with a misleading "user rejected"; block early
|
|
4
|
+
* instead. Root escalation still uses ask; do not pin delegated sessions read-only
|
|
5
|
+
* (`store.writeContext` needs workspace-write). `tools.guard` is monotonic; pre-execute is a
|
|
6
|
+
* rewritable waterfall. */
|
|
6
7
|
import { type GuardedExecution, type PolicyContext } from './root-tool-policy.js';
|
|
8
|
+
/** Both shell names on every platform plus the PTC transport: the preset mounts one shell pair for
|
|
9
|
+
* the root and `run_code` for data_junior, so matching only the local platform kills the Windows gate. */
|
|
10
|
+
export declare const EXECUTION_ESCALATION_TOOL_NAMES: readonly ["bash", "pwsh", "run_code"];
|
|
7
11
|
export type BashGuardExecution = GuardedExecution;
|
|
8
12
|
/** States that the rejection is the system's, not the user's, and gives the recovery path. */
|
|
9
13
|
export declare const SHELL_ESCALATION_UNREACHABLE: string;
|
|
10
|
-
/** Reads identity plus `sandbox_permissions`/`justification`, never `command`; unknown identity fails closed. */
|
|
14
|
+
/** Reads identity plus `sandbox_permissions`/`justification`, never `command`/`code`; unknown identity fails closed. */
|
|
11
15
|
export declare function bashGuardReason(exec: BashGuardExecution | undefined): string | undefined;
|
|
12
16
|
/** Installs on every Capital preset agent. Must listen on `ctx.root`: agent/created routes by
|
|
13
17
|
* the agent object, so tagged standing-scope listeners never fire. Failures only warn. */
|
package/lib/agents/bash-guard.js
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* inspected (the sandbox blocks writes, not reads/network). Delegated approvals
|
|
3
|
-
* `never`, so escalation auto-rejects with a misleading "user rejected"; block early
|
|
4
|
-
* Root escalation still uses ask; do not pin delegated sessions read-only
|
|
5
|
-
* needs workspace-write). `tools.guard` is monotonic; pre-execute is a
|
|
1
|
+
/** Execution guard for bash/pwsh/run_code: blocks only doomed sandbox escalation; command/program
|
|
2
|
+
* content is never inspected (the sandbox blocks writes, not reads/network). Delegated approvals
|
|
3
|
+
* are fixed to `never`, so escalation auto-rejects with a misleading "user rejected"; block early
|
|
4
|
+
* instead. Root escalation still uses ask; do not pin delegated sessions read-only
|
|
5
|
+
* (`store.writeContext` needs workspace-write). `tools.guard` is monotonic; pre-execute is a
|
|
6
|
+
* rewritable waterfall. */
|
|
6
7
|
import { agentSessionId, isCapitalPresetAgent, SHELL_TOOL_NAMES } from './root-tool-policy.js';
|
|
7
|
-
/** Both names on every platform
|
|
8
|
-
|
|
8
|
+
/** Both shell names on every platform plus the PTC transport: the preset mounts one shell pair for
|
|
9
|
+
* the root and `run_code` for data_junior, so matching only the local platform kills the Windows gate. */
|
|
10
|
+
export const EXECUTION_ESCALATION_TOOL_NAMES = [...SHELL_TOOL_NAMES, 'run_code'];
|
|
11
|
+
const executionToolNames = new Set(EXECUTION_ESCALATION_TOOL_NAMES);
|
|
9
12
|
/** One listener set per root context: host policy and legacy call sites install once. */
|
|
10
13
|
const installedRoots = new WeakSet();
|
|
11
14
|
/** States that the rejection is the system's, not the user's, and gives the recovery path. */
|
|
12
|
-
export const SHELL_ESCALATION_UNREACHABLE = '
|
|
15
|
+
export const SHELL_ESCALATION_UNREACHABLE = '执行工具(bash / pwsh / run_code)的沙箱升级只有主 Agent 的会话能拿到用户审批:被委派的子会话拿不到'
|
|
13
16
|
+ '(框架把委派会话的审批策略固定为 never,不会去问用户),当前调用方也不是可确认的主 Agent 会话。'
|
|
14
17
|
+ '不要重试升级:缺数值用 describe_dataset / query_dataset,缺数据用 send_message 发 data_gap 回主 Agent。';
|
|
15
18
|
function isRecord(value) {
|
|
16
19
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
17
20
|
}
|
|
18
|
-
/** Reads identity plus `sandbox_permissions`/`justification`, never `command`; unknown identity fails closed. */
|
|
21
|
+
/** Reads identity plus `sandbox_permissions`/`justification`, never `command`/`code`; unknown identity fails closed. */
|
|
19
22
|
export function bashGuardReason(exec) {
|
|
20
|
-
if (exec === undefined || typeof exec.name !== 'string' || !
|
|
23
|
+
if (exec === undefined || typeof exec.name !== 'string' || !executionToolNames.has(exec.name))
|
|
21
24
|
return undefined;
|
|
22
25
|
const args = isRecord(exec.arguments) ? exec.arguments : undefined;
|
|
23
26
|
const escalated = args !== undefined && (args.sandbox_permissions !== undefined || args.justification !== undefined);
|
|
@@ -64,7 +67,7 @@ export function registerBashGuard(ctx) {
|
|
|
64
67
|
return;
|
|
65
68
|
const dispose = installAgentGuard(agent);
|
|
66
69
|
if (dispose === undefined) {
|
|
67
|
-
ctx.logger?.warn('capital-generation:
|
|
70
|
+
ctx.logger?.warn('capital-generation: 执行工具闸门未生效(agent scope 上没有 tools.guard):沙箱升级拦截只剩 persona 软约束');
|
|
68
71
|
return;
|
|
69
72
|
}
|
|
70
73
|
const id = agentSessionId(agent);
|
|
@@ -72,7 +75,7 @@ export function registerBashGuard(ctx) {
|
|
|
72
75
|
guards.set(id, dispose);
|
|
73
76
|
}
|
|
74
77
|
catch (error) {
|
|
75
|
-
ctx.logger?.warn(`capital-generation:
|
|
78
|
+
ctx.logger?.warn(`capital-generation: 执行工具闸门安装异常:${error instanceof Error ? error.message : String(error)}`);
|
|
76
79
|
}
|
|
77
80
|
});
|
|
78
81
|
const stopDisposed = listenCtx.on?.('agent/disposed', (payload) => {
|
|
@@ -47,6 +47,8 @@ export const ROOT_AGENT_DENIED_TOOLS = [
|
|
|
47
47
|
'write',
|
|
48
48
|
'edit',
|
|
49
49
|
];
|
|
50
|
+
// ⛔ `run_code` belongs nowhere in this list: it is a reserved PTC transport name, so `restrict()`
|
|
51
|
+
// throws on it. The root stays native; deployment-level `dsh-tools.mode` must not be set to ptc.
|
|
50
52
|
export const CAPITAL_PRESET_ID = ROOT_PRESET_ID;
|
|
51
53
|
export function isRootAgent(agent) {
|
|
52
54
|
if (agent?.session === undefined)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes redundant `subagent-settled` notices at the closing frame; otherwise the host loop opens
|
|
3
|
+
* another step after a complete answer and folds it. Only children with an admitted relay are
|
|
4
|
+
* dropped — a silent child may carry its only outcome in the notice.
|
|
5
|
+
* Rationale: docs/dev/chart-delivery-events.md §6.3.
|
|
6
|
+
*/
|
|
7
|
+
import type { SessionLike } from '../data-collector/store.js';
|
|
8
|
+
export interface WakeMessageLike {
|
|
9
|
+
readonly id?: string;
|
|
10
|
+
readonly source?: {
|
|
11
|
+
readonly kind?: string;
|
|
12
|
+
readonly senderSessionId?: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
interface InboxLike {
|
|
16
|
+
readonly nextTurn?: readonly unknown[];
|
|
17
|
+
readonly nextStep?: readonly unknown[];
|
|
18
|
+
remove?(messageId: string): boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface WakeAgentLike {
|
|
21
|
+
readonly session?: SessionLike;
|
|
22
|
+
readonly inbox?: InboxLike;
|
|
23
|
+
}
|
|
24
|
+
export interface SettledWakeGuard {
|
|
25
|
+
/** `user/message` events: human resets the exchange, an admitted child relay marks that child. */
|
|
26
|
+
acceptUserMessage(session: SessionLike, source: {
|
|
27
|
+
kind?: string;
|
|
28
|
+
senderSessionId?: string;
|
|
29
|
+
}): void;
|
|
30
|
+
/** Terminal assistant-stream frame: the closing answer is being written. */
|
|
31
|
+
acceptClosing(agent: WakeAgentLike | undefined): void;
|
|
32
|
+
/** A notice inserted after the closing frame must not wake a new step. */
|
|
33
|
+
acceptInboxInserted(agent: WakeAgentLike | undefined, message: WakeMessageLike | undefined): void;
|
|
34
|
+
/** A fresh activation invalidates the previous exchange's relay marker. */
|
|
35
|
+
acceptSubagentStart(info: {
|
|
36
|
+
id?: string;
|
|
37
|
+
}): void;
|
|
38
|
+
/** Diagnostics/tests. */
|
|
39
|
+
closing(): boolean;
|
|
40
|
+
admitted(): readonly string[];
|
|
41
|
+
}
|
|
42
|
+
export declare function createSettledWakeGuard(): SettledWakeGuard;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
function messageOf(value) {
|
|
2
|
+
return value !== null && typeof value === 'object' ? value : undefined;
|
|
3
|
+
}
|
|
4
|
+
export function createSettledWakeGuard() {
|
|
5
|
+
let rootSessionId;
|
|
6
|
+
let closing = false;
|
|
7
|
+
const admitted = new Set();
|
|
8
|
+
const owns = (session) => {
|
|
9
|
+
if (session?.id === undefined)
|
|
10
|
+
return false;
|
|
11
|
+
// A child's own user/message events must not clear the root's state.
|
|
12
|
+
if (session.header?.origin === 'subagent')
|
|
13
|
+
return false;
|
|
14
|
+
rootSessionId ??= session.id;
|
|
15
|
+
return session.id === rootSessionId;
|
|
16
|
+
};
|
|
17
|
+
const dropNotice = (agent, message) => {
|
|
18
|
+
const childId = message.source?.senderSessionId;
|
|
19
|
+
if (typeof childId !== 'string' || !admitted.has(childId))
|
|
20
|
+
return false;
|
|
21
|
+
if (agent?.inbox?.remove === undefined || typeof message.id !== 'string')
|
|
22
|
+
return false;
|
|
23
|
+
let removed = false;
|
|
24
|
+
try {
|
|
25
|
+
removed = agent.inbox.remove(message.id);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
removed = false;
|
|
29
|
+
}
|
|
30
|
+
if (removed)
|
|
31
|
+
admitted.delete(childId);
|
|
32
|
+
return removed;
|
|
33
|
+
};
|
|
34
|
+
const dropPending = (agent) => {
|
|
35
|
+
if (agent?.inbox === undefined)
|
|
36
|
+
return;
|
|
37
|
+
for (const value of [...(agent.inbox.nextTurn ?? []), ...(agent.inbox.nextStep ?? [])]) {
|
|
38
|
+
const message = messageOf(value);
|
|
39
|
+
if (message?.source?.kind !== 'subagent-settled')
|
|
40
|
+
continue;
|
|
41
|
+
dropNotice(agent, message);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
acceptUserMessage(session, source) {
|
|
46
|
+
if (!owns(session))
|
|
47
|
+
return;
|
|
48
|
+
if (source.kind === 'user') {
|
|
49
|
+
admitted.clear();
|
|
50
|
+
closing = false;
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (source.kind !== 'agent-message')
|
|
54
|
+
return;
|
|
55
|
+
if (typeof source.senderSessionId !== 'string' || source.senderSessionId.length === 0)
|
|
56
|
+
return;
|
|
57
|
+
admitted.add(source.senderSessionId);
|
|
58
|
+
// The answer that follows this relay is not the previous closing answer.
|
|
59
|
+
closing = false;
|
|
60
|
+
},
|
|
61
|
+
acceptClosing(agent) {
|
|
62
|
+
if (agent?.session !== undefined && !owns(agent.session))
|
|
63
|
+
return;
|
|
64
|
+
closing = true;
|
|
65
|
+
dropPending(agent);
|
|
66
|
+
},
|
|
67
|
+
acceptInboxInserted(agent, message) {
|
|
68
|
+
if (agent?.session !== undefined && !owns(agent.session))
|
|
69
|
+
return;
|
|
70
|
+
if (message?.source?.kind !== 'subagent-settled' || !closing)
|
|
71
|
+
return;
|
|
72
|
+
dropNotice(agent, message);
|
|
73
|
+
},
|
|
74
|
+
acceptSubagentStart(info) {
|
|
75
|
+
if (typeof info?.id === 'string' && info.id.length > 0)
|
|
76
|
+
admitted.delete(info.id);
|
|
77
|
+
},
|
|
78
|
+
closing: () => closing,
|
|
79
|
+
admitted: () => [...admitted],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root-only `wait_for_agents`: hold one turn across child relays; returns on the first relay, on
|
|
3
|
+
* the tree settling, or after 30s. `waitForChildren` has no abort parameter; the slice races it.
|
|
4
|
+
* Host contract: docs/reference/dsh-surface-ledger.md L36.
|
|
5
|
+
*/
|
|
6
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
+
export interface WaitInboxLike {
|
|
8
|
+
readonly nextTurn?: readonly unknown[];
|
|
9
|
+
readonly nextStep?: readonly unknown[];
|
|
10
|
+
}
|
|
11
|
+
export interface WaitAgentLike {
|
|
12
|
+
readonly session?: {
|
|
13
|
+
readonly id?: string;
|
|
14
|
+
readonly header?: {
|
|
15
|
+
readonly parentSession?: string;
|
|
16
|
+
readonly origin?: 'subagent';
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
readonly inbox?: WaitInboxLike;
|
|
20
|
+
readonly status?: 'idle' | 'running';
|
|
21
|
+
}
|
|
22
|
+
export interface WaitDescendantLike {
|
|
23
|
+
readonly kind?: string;
|
|
24
|
+
readonly id?: string;
|
|
25
|
+
readonly label?: string;
|
|
26
|
+
readonly activity?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface WaitSubagentsLike {
|
|
29
|
+
waitForChildren(parent: WaitAgentLike): Promise<boolean>;
|
|
30
|
+
listDescendants?(rootSessionId: string, signal?: AbortSignal): Promise<readonly WaitDescendantLike[]>;
|
|
31
|
+
}
|
|
32
|
+
interface AgentsLike {
|
|
33
|
+
get?(id: string): {
|
|
34
|
+
readonly status?: 'idle' | 'running';
|
|
35
|
+
} | undefined;
|
|
36
|
+
}
|
|
37
|
+
interface EventContextLike {
|
|
38
|
+
on?(event: string, listener: (...args: unknown[]) => void): unknown;
|
|
39
|
+
}
|
|
40
|
+
export interface WaitForAgentsOptions {
|
|
41
|
+
/** Bounded slice; the model cannot extend it. */
|
|
42
|
+
readonly waitSliceMs?: number;
|
|
43
|
+
readonly now?: () => number;
|
|
44
|
+
}
|
|
45
|
+
export interface WaitForAgentsDeps extends WaitForAgentsOptions {
|
|
46
|
+
subagents: () => WaitSubagentsLike | undefined;
|
|
47
|
+
events?: EventContextLike;
|
|
48
|
+
agents?: () => AgentsLike | undefined;
|
|
49
|
+
}
|
|
50
|
+
export type WaitForAgentsStatus = 'all_settled' | 'message' | 'user_input' | 'timeout';
|
|
51
|
+
export interface WaitForAgentsReceipt {
|
|
52
|
+
readonly status: WaitForAgentsStatus;
|
|
53
|
+
readonly waited_ms: number;
|
|
54
|
+
readonly pending_messages: number;
|
|
55
|
+
readonly running: Array<{
|
|
56
|
+
readonly id: string;
|
|
57
|
+
readonly label?: string;
|
|
58
|
+
}>;
|
|
59
|
+
readonly hint: string;
|
|
60
|
+
}
|
|
61
|
+
declare function render(_args: unknown, value: unknown): Array<{
|
|
62
|
+
type: 'text';
|
|
63
|
+
text: string;
|
|
64
|
+
}>;
|
|
65
|
+
export declare function waitForAgentsToolDefinition(deps: WaitForAgentsDeps): {
|
|
66
|
+
name: string;
|
|
67
|
+
description: string;
|
|
68
|
+
parameters: object;
|
|
69
|
+
output: {
|
|
70
|
+
schema: object;
|
|
71
|
+
render: typeof render;
|
|
72
|
+
};
|
|
73
|
+
execute(_args: Record<string, unknown>, exec: {
|
|
74
|
+
signal: AbortSignal;
|
|
75
|
+
agent?: WaitAgentLike;
|
|
76
|
+
}): Promise<WaitForAgentsReceipt>;
|
|
77
|
+
};
|
|
78
|
+
export declare function registerWaitForAgentsTool(ctx: Context, options?: WaitForAgentsOptions): void;
|
|
79
|
+
export {};
|