@v587d/capital-generation 2.8.3 → 2.9.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/CHANGELOG.md +81 -1
- package/README.md +40 -19
- package/chart-ui/README.md +5 -4
- package/chart-ui/client.src.cjs +7 -16
- package/chart-ui/index.js +12 -33
- package/chart-ui/package.json +2 -2
- package/config/client.js +18 -39
- package/config/client.src.cjs +79 -217
- package/config/index.js +22 -72
- package/config/package.json +2 -2
- package/cordis.patch.yml +46 -51
- package/lib/agents/bash-guard.d.ts +9 -55
- package/lib/agents/bash-guard.js +27 -67
- package/lib/agents/preset-roster.d.ts +22 -0
- package/lib/agents/preset-roster.js +87 -0
- package/lib/agents/role-tool-policy.d.ts +46 -0
- package/lib/agents/role-tool-policy.js +205 -0
- package/lib/agents/root-tool-policy.d.ts +28 -124
- package/lib/agents/root-tool-policy.js +164 -237
- package/lib/agents/selected-skills-provider.d.ts +2 -6
- package/lib/agents/selected-skills-provider.js +27 -84
- package/lib/capital-settings.d.ts +27 -0
- package/lib/capital-settings.js +23 -0
- package/lib/chart/artifact-ref.d.ts +13 -8
- package/lib/chart/artifact-ref.js +27 -10
- package/lib/chart/downsample.d.ts +2 -5
- package/lib/chart/downsample.js +3 -8
- package/lib/chart/errors.d.ts +3 -7
- package/lib/chart/errors.js +1 -2
- package/lib/chart/event-context.d.ts +8 -0
- package/lib/chart/event-context.js +39 -0
- package/lib/chart/events.d.ts +21 -78
- package/lib/chart/events.js +43 -189
- package/lib/chart/html.d.ts +2 -5
- package/lib/chart/html.js +8 -15
- package/lib/chart/present-tool.d.ts +28 -0
- package/lib/chart/present-tool.js +128 -0
- package/lib/chart/runtime.d.ts +5 -18
- package/lib/chart/runtime.js +14 -34
- package/lib/chart/series.d.ts +10 -17
- package/lib/chart/series.js +22 -47
- package/lib/chart/source-token.d.ts +5 -13
- package/lib/chart/source-token.js +18 -17
- package/lib/chart/spec.d.ts +6 -12
- package/lib/chart/spec.js +7 -31
- package/lib/chart/tool.d.ts +7 -29
- package/lib/chart/tool.js +15 -25
- package/lib/chart-render-entry.d.ts +10 -0
- package/lib/chart-render-entry.js +14 -0
- package/lib/chart-source-entry.d.ts +13 -0
- package/lib/chart-source-entry.js +20 -0
- package/lib/collect-entry.d.ts +9 -0
- package/lib/collect-entry.js +10 -0
- package/lib/collector-runtime.d.ts +22 -0
- package/lib/collector-runtime.js +97 -0
- package/lib/data-collector/args.d.ts +6 -24
- package/lib/data-collector/args.js +6 -24
- package/lib/data-collector/dataset-tools.d.ts +7 -1
- package/lib/data-collector/dataset-tools.js +27 -56
- package/lib/data-collector/describe.d.ts +8 -33
- package/lib/data-collector/describe.js +17 -70
- package/lib/data-collector/hub.d.ts +27 -85
- package/lib/data-collector/hub.js +19 -85
- package/lib/data-collector/query-time.d.ts +7 -26
- package/lib/data-collector/query-time.js +8 -27
- package/lib/data-collector/store.d.ts +41 -117
- package/lib/data-collector/store.js +32 -111
- package/lib/data-collector/time-axis.d.ts +16 -76
- package/lib/data-collector/time-axis.js +25 -111
- package/lib/data-collector/tools.d.ts +6 -6
- package/lib/data-collector/tools.js +34 -43
- package/lib/dataset-read-entry.d.ts +9 -0
- package/lib/dataset-read-entry.js +7 -0
- package/lib/index.d.ts +7 -117
- package/lib/index.js +43 -422
- package/lib/net/eastmoney-client.d.ts +13 -65
- package/lib/net/eastmoney-client.js +12 -53
- package/lib/net/throttle.d.ts +2 -13
- package/lib/net/throttle.js +3 -14
- package/lib/ocr/client.d.ts +12 -21
- package/lib/ocr/client.js +23 -40
- package/lib/ocr/markdown.d.ts +20 -26
- package/lib/ocr/markdown.js +23 -36
- package/lib/ocr/store.d.ts +14 -47
- package/lib/ocr/store.js +15 -45
- package/lib/ocr-entry.d.ts +9 -0
- package/lib/ocr-entry.js +10 -0
- package/lib/policy-entry.d.ts +17 -0
- package/lib/policy-entry.js +22 -0
- package/lib/present-entry.d.ts +9 -0
- package/lib/present-entry.js +13 -0
- package/lib/preset-claim.d.ts +22 -0
- package/lib/preset-claim.js +24 -0
- package/lib/preset-ids.d.ts +38 -0
- package/lib/preset-ids.js +46 -0
- package/lib/retriever-config.d.ts +37 -0
- package/lib/retriever-config.js +36 -0
- package/lib/retriever-runtime.d.ts +31 -0
- package/lib/retriever-runtime.js +82 -0
- package/lib/root-extras-entry.d.ts +17 -0
- package/lib/root-extras-entry.js +25 -0
- package/lib/runtime-entry.d.ts +15 -0
- package/lib/runtime-entry.js +5 -0
- package/lib/runtime.d.ts +21 -0
- package/lib/runtime.js +33 -0
- package/lib/search-entry.d.ts +9 -0
- package/lib/search-entry.js +9 -0
- package/lib/sources/eastmoney-http.js +20 -68
- package/lib/sources/ftshare-rest.d.ts +6 -34
- package/lib/sources/ftshare-rest.js +84 -292
- package/lib/sources/fuyao-core.d.ts +8 -18
- package/lib/sources/fuyao-core.js +10 -21
- package/lib/sources/fuyao-rest.d.ts +2 -6
- package/lib/sources/fuyao-rest.js +23 -102
- package/lib/sources/security-code.d.ts +6 -12
- package/lib/sources/security-code.js +6 -12
- package/lib/sources/security-identity.d.ts +1 -4
- package/lib/sources/security-identity.js +3 -8
- package/lib/sources/tencent-http.js +14 -28
- package/lib/sources/tencent-public-core.d.ts +8 -60
- package/lib/sources/tencent-public-core.js +12 -77
- package/lib/sources/wind-mcp.d.ts +0 -5
- package/lib/sources/wind-mcp.js +23 -78
- package/lib/sources-entry.d.ts +9 -0
- package/lib/sources-entry.js +9 -0
- package/lib/time/tools.d.ts +8 -31
- package/lib/time/tools.js +30 -66
- package/lib/time-entry.d.ts +9 -0
- package/lib/time-entry.js +7 -0
- package/lib/tool-exec.d.ts +45 -11
- package/lib/tool-exec.js +59 -6
- package/lib/user-customization.d.ts +14 -0
- package/lib/user-customization.js +47 -0
- package/lib/watchlist/tools.d.ts +3 -13
- package/lib/watchlist/tools.js +10 -30
- package/lib/watchlist-entry.d.ts +9 -0
- package/lib/watchlist-entry.js +6 -0
- package/lib/web-retriever/engines.d.ts +6 -11
- package/lib/web-retriever/engines.js +9 -14
- package/lib/web-retriever/html-markdown.d.ts +14 -34
- package/lib/web-retriever/html-markdown.js +39 -75
- package/lib/web-retriever/local-fetch.d.ts +22 -58
- package/lib/web-retriever/local-fetch.js +33 -77
- package/lib/web-retriever/retriever.d.ts +6 -18
- package/lib/web-retriever/retriever.js +7 -7
- package/lib/web-retriever/sources.d.ts +37 -124
- package/lib/web-retriever/sources.js +64 -196
- package/lib/web-retriever/tools.d.ts +43 -27
- package/lib/web-retriever/tools.js +99 -137
- package/lib/web-retriever/wind-client.d.ts +14 -31
- package/lib/web-retriever/wind-client.js +16 -32
- package/locale/zh.json +1 -1
- package/package.json +72 -7
- package/preset/capital-generation/README.md +87 -53
- package/preset/capital-generation/agent.patch.yml +532 -337
- package/preset/capital-generation/skills/capital-chart-protocol/SKILL.md +6 -4
- package/preset/capital-generation/skills/capital-data-profile/SKILL.md +2 -2
- package/preset/capital-generation/skills/capital-data-protocol/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-equity-research/SKILL.md +4 -2
- package/preset/capital-generation/skills/capital-event-impact/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-orchestration/SKILL.md +30 -33
- package/preset/capital-generation/skills/capital-portfolio-review/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-visualization-protocol/SKILL.md +14 -16
- package/preset/capital-generation/skills/capital-web-evidence/SKILL.md +1 -1
- package/watchlist/client.js +15 -148
- package/watchlist/client.src.cjs +148 -662
- package/watchlist/holding-rules.js +11 -38
- package/watchlist/index.js +127 -397
- package/watchlist/package.json +2 -2
package/lib/chart/events.d.ts
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
import type { SessionLike } from '../data-collector/store.js';
|
|
2
|
-
/**
|
|
3
|
-
* 登记本轮交付物用的官方事件名。
|
|
4
|
-
*
|
|
5
|
-
* 必须留在 `KNOWN_SESSION_EVENT_TYPES` 内——它是 `dsh-tool-present` 追加的同一类型,
|
|
6
|
-
* 因此在**任何** harness 版本上都能被读回。改这里等于改会话日志契约,
|
|
7
|
-
* `test/chart-turn-events.test.mjs` 有一条断言把它钉在 first-party 词汇表上。
|
|
8
|
-
*/
|
|
2
|
+
/** Must stay in `KNOWN_SESSION_EVENT_TYPES`; test/chart-turn-events.test.mjs pins it. */
|
|
9
3
|
export declare const CHART_DELIVERABLE_EVENT = "deliverables/presented";
|
|
10
|
-
/** `callId`
|
|
4
|
+
/** `callId` only needs to be a non-empty string; carries the chart handle for log tracing. */
|
|
11
5
|
export declare const CHART_CALL_ID_PREFIX = "capital-chart:";
|
|
12
|
-
/**
|
|
6
|
+
/** Per (session, turn) cap: stops model loops; overflow warns only. */
|
|
13
7
|
export declare const MAX_CHARTS_PER_TURN = 8;
|
|
14
|
-
/**
|
|
8
|
+
/** Root lookup cap (main → data_junior → specialist is one hop, plus headroom). */
|
|
15
9
|
export declare const MAX_OWNER_HOPS = 4;
|
|
16
10
|
interface AppendableSession extends SessionLike {
|
|
17
11
|
append(type: string, data: unknown): unknown;
|
|
@@ -19,102 +13,51 @@ interface AppendableSession extends SessionLike {
|
|
|
19
13
|
interface SessionsService {
|
|
20
14
|
get(id: string): unknown;
|
|
21
15
|
}
|
|
22
|
-
/**
|
|
16
|
+
/** Subset of host ctx used here; structural so tests can inject fakes. */
|
|
23
17
|
export interface ChartEventContext {
|
|
24
18
|
get(name: string): unknown;
|
|
25
|
-
/**
|
|
19
|
+
/** Fallback flush listener; must be registered on the unscoped root context. */
|
|
26
20
|
on?(event: string, listener: (...args: unknown[]) => void): unknown;
|
|
27
|
-
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* 为什么必须是它而不是 `turn/start`:一轮是否"最终"在事件发生的那一刻不可知——子 Agent 的
|
|
31
|
-
* 结算随时会再触发新一轮。实测(2026-09-20 真机 session `38bad3f9`):图表在 turn 4 与 5 之间
|
|
32
|
-
* 寄存,旧规则在下一次 `turn/start` 立刻冲刷,于是交付行落进了**空的过程轮** turn 5,而主 Agent
|
|
33
|
-
* 写下总结答复的是 turn 6——卡片出现在中间步骤。`agent/turn-stopping` 是官方"该轮即将关闭"的
|
|
34
|
-
* 钩子(`@mode serial`,轮仍打开、可安全 append),在它上面冲刷,交付行就落进**消费这份图的那一轮**。
|
|
35
|
-
*
|
|
36
|
-
* 必须注册在未打 scope 标签的 root context 上:`dsh-scope` 的准入规则会让 standing-scope 的
|
|
37
|
-
* 监听器收不到子 Agent 的事件,而我们要的正是 owner 自己那轮关闭的事实。
|
|
38
|
-
*/
|
|
21
|
+
/** Preferred flush hook: open-but-closing turn, on the unscoped root ctx (scoped listeners miss
|
|
22
|
+
* child events). A missing disposer must keep the turn/start fallback active. */
|
|
39
23
|
onTurnStopping?(listener: (...args: unknown[]) => void): unknown;
|
|
40
24
|
effect?(callback: () => unknown, label?: string): unknown;
|
|
41
|
-
/**
|
|
25
|
+
/** Unscoped root context; falls back to self when absent. */
|
|
42
26
|
readonly root?: ChartEventContext;
|
|
43
27
|
logger?: {
|
|
44
28
|
warn(message: string): void;
|
|
45
29
|
};
|
|
46
30
|
}
|
|
47
31
|
export interface ChartTurnEventInput {
|
|
48
|
-
/**
|
|
32
|
+
/** chart_ref owner scope (same semantics as ChartArtifactRegistry). */
|
|
49
33
|
ownerSessionId: string;
|
|
50
34
|
chart_id: string;
|
|
51
35
|
title: string;
|
|
52
|
-
/**
|
|
36
|
+
/** Workspace-relative chart.html path; the official preview resolves it against session.cwd. */
|
|
53
37
|
html_path: string;
|
|
54
38
|
}
|
|
55
39
|
export interface ChartEventPublisher {
|
|
56
|
-
/**
|
|
57
|
-
* 记录一张图。
|
|
58
|
-
* @returns 是否已受理:立即写入、或已寄存等下一个回合都算受理。
|
|
59
|
-
*/
|
|
60
40
|
publish(input: ChartTurnEventInput): boolean;
|
|
61
|
-
/**
|
|
41
|
+
/** Diagnostics: queued charts not yet written. */
|
|
62
42
|
pendingCount?(ownerSessionId?: string): number;
|
|
63
|
-
/**
|
|
64
|
-
* 诊断:最近一次 publish 的逐步结论(给回执/落盘用)。
|
|
65
|
-
*
|
|
66
|
-
* 为什么需要它:交付行"静默不出现"是本项目最难查的故障,而宿主终端并不总能拿到
|
|
67
|
-
* (cordis logger 只进内存;stderr 也可能落在用户看不到的地方)。把结论带回
|
|
68
|
-
* `render_chart` 的回执 / 落盘产物,就能**从磁盘**定位,不必依赖终端。
|
|
69
|
-
*/
|
|
43
|
+
/** Diagnostics: last publish trace, surfaced in receipts / disk so failures are visible without a terminal. */
|
|
70
44
|
lastTrace?(): string;
|
|
71
45
|
}
|
|
72
|
-
/** 只读:最近一次 publish 轨迹。 */
|
|
73
46
|
export declare function chartEventPublishTrace(): string;
|
|
74
|
-
/**
|
|
75
|
-
* 交付通道的**诊断开关**:`CAPITAL_CHART_TRACE=1` 时才输出进度日志与落盘 trace。
|
|
76
|
-
*
|
|
77
|
-
* 默认关闭——对外发布不该往用户终端刷进度、也不该往 workspace 写调试文件。
|
|
78
|
-
* 排查"图表/交付行不出现"时打开它(见 `deliverable-trace.txt` 与 docs/dev/chart-delivery-events.md §6.3 的说明)。
|
|
79
|
-
*/
|
|
47
|
+
/** Diagnostic switch; off by default to keep terminals and workspaces clean. */
|
|
80
48
|
export declare function chartTraceEnabled(): boolean;
|
|
81
|
-
/**
|
|
82
|
-
* 从 owner scope 出发向上走到根会话:用户看的是根会话的对话流,卡片必须挂在它上面。
|
|
83
|
-
* 找不到(会话已回收)时返回 undefined,调用方静默跳过。
|
|
84
|
-
*/
|
|
49
|
+
/** Walk up to the root session — the user watches the root conversation. Missing session → undefined. */
|
|
85
50
|
export declare function resolveOwnerSession(sessions: SessionsService, ownerSessionId: string, maxHops?: number): AppendableSession | undefined;
|
|
86
|
-
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* 形状与 `dsh-tool-present` 逐字段对齐(那边是 `{ turn, callId, files }`,
|
|
90
|
-
* 客户端 `isPresentedData()` 要求 `turn` 是 >=1 的安全整数、`callId` 非空字符串、
|
|
91
|
-
* `files` 是数组)。`files[].path` 必须是**工作区相对**路径:官方打开动作会拿它去
|
|
92
|
-
* `workspaceFiles.stat({ sessionId, workspaceRoot: session.cwd })` 解析,绝对路径过不了
|
|
93
|
-
* 「Presented file has no verified Host path」这一关。
|
|
94
|
-
*
|
|
95
|
-
* `callId` 只是官方契约里的一个非空字符串句柄,没有配对约束(`present` 那边用来关联
|
|
96
|
-
* tool/result),这里借它携带 `chart_id`,便于在日志里把一条交付回溯到具体图表。
|
|
97
|
-
*/
|
|
51
|
+
/** Matches `dsh-tool-present` ({ turn, callId, files }); files[].path must be workspace-relative
|
|
52
|
+
* because the official open action resolves it against session.cwd. */
|
|
98
53
|
export declare function buildChartEventPayload(input: ChartTurnEventInput, turn: number): Record<string, unknown>;
|
|
99
|
-
/**
|
|
100
|
-
* 进程内待登记条目的总量上限(安全阀)。
|
|
101
|
-
*
|
|
102
|
-
* 正常流程下条目会在 owner 那一轮关闭时被冲刷清空;但如果冲刷时机始终没接上
|
|
103
|
-
* (上游事件名消失、监听注册失败且 turn/start 也没来),模块级 Map 会一直攒着。
|
|
104
|
-
* 这是纯内存元数据(只有 chart_id / 标题 / 相对路径),上限只为防无界增长,
|
|
105
|
-
* 与 `MAX_CHARTS_PER_TURN` 的"防模型循环"是两件事。
|
|
106
|
-
*/
|
|
54
|
+
/** Safety valve if flushing never fires; metadata only, bounded against unbounded growth. */
|
|
107
55
|
export declare const MAX_PENDING_CHARTS = 64;
|
|
108
|
-
/**
|
|
109
|
-
* 只给测试用:清空待登记队列。
|
|
110
|
-
*
|
|
111
|
-
* `pending` 是模块级的(必须跨发布器实例存活,见下方说明),因此进程内所有测试
|
|
112
|
-
* 共享它。测试之间要隔离,就在用例开始时调用一次。
|
|
113
|
-
*/
|
|
56
|
+
/** Test-only: clear the shared queue between cases. */
|
|
114
57
|
export declare function resetChartEventPending(): void;
|
|
115
58
|
export declare function createChartEventPublisher(ctx: ChartEventContext): ChartEventPublisher | undefined;
|
|
116
|
-
/**
|
|
59
|
+
/** Test-only: drop the singleton cache. */
|
|
117
60
|
export declare function resetChartEventPublisher(): void;
|
|
118
|
-
/**
|
|
61
|
+
/** Owner scope at render time; same source as chart_ref (specialist renders land on the main session). */
|
|
119
62
|
export declare function chartEventOwnerSessionId(session: SessionLike, sourceSession?: SessionLike): string;
|
|
120
63
|
export {};
|
package/lib/chart/events.js
CHANGED
|
@@ -1,77 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 做法:`render_chart` 成功后,宿主往**用户正在看的那条会话**追加官方
|
|
8
|
-
* `deliverables/presented` 事件(`present` 工具用的同一条通道),把
|
|
9
|
-
* `capital-analysis/charts/<chart_id>/chart.html` 登记为本轮交付物。用户在收尾的
|
|
10
|
-
* 「本轮文件改动 / 交付」行点开该文件,右侧即用官方 document preview 的
|
|
11
|
-
* script-enabled iframe 渲染自包含图表。
|
|
12
|
-
*
|
|
13
|
-
* ⚠️ 为什么**绝不能**再发明自己的事件类型(2026-09-18 事故,实测根因):
|
|
14
|
-
* 会话日志的事件词汇表是**闭集**——`@deepseek-ai/dsh-session` 的
|
|
15
|
-
* `KNOWN_SESSION_EVENT_TYPES` 由 harness 仓库自己的 `SessionEventMap` 生成,
|
|
16
|
-
* 注释明写 "downstream (out-of-repo) plugin events are outside this list by
|
|
17
|
-
* construction"。读取侧 `validateStoredEvents()` 是 fail-closed 的:
|
|
18
|
-
*
|
|
19
|
-
* `KNOWN_SESSION_EVENT_TYPES.has(event.type) || event.ignorable === true`
|
|
20
|
-
*
|
|
21
|
-
* 否则抛 `SessionFormatUnsupportedError` 并拒绝解释**整份日志**。而 `Session.append`
|
|
22
|
-
* 的 options 只转发 `sourceEventSeqs` / `surfaceOp`——**调用方无法设置 `ignorable`**
|
|
23
|
-
* (实测 0.1.5-rc.2 与 0.1.6-alpha.2 都没有该选项)。于是自定义事件是"写时静默通过、
|
|
24
|
-
* 冷加载时整份会话打不开":本仓曾发 `capital/chart-rendered`,导致 6 份会话在重启后
|
|
25
|
-
* 全部 `failed to observe session`。只有 first-party 事件能持久化。
|
|
26
|
-
*
|
|
27
|
-
* ⚠️ 回合归属(实测教训 2026-09-17):交付行是**按回合**渲染的,只会出现在它所属回合的
|
|
28
|
-
* 收尾位置。而主 Agent 在等子 Agent 时会**先结束自己的回合**,于是出图往往发生在两个回合
|
|
29
|
-
* 之间——此时 `turnBoundary.lastTurn` 指向那个**已经结束**的回合,照抄它会把交付登记到
|
|
30
|
-
* 上一轮。因此:
|
|
31
|
-
* - 有回合打开(`openTurnStartSeq` 是数字)→ 立即写入该回合;
|
|
32
|
-
* - 没有回合打开 → 先寄存,等 owner 的**下一个 `turn/start`** 再写入(那正是会消费这份
|
|
33
|
-
* 子 Agent 结果、写下最终答复的回合)。
|
|
34
|
-
* 寄存的冲刷必须延到微任务:`session/event` 观察者在 `Session.append` 的 appending 窗口内同步执行,
|
|
35
|
-
* 在那里再次 append 会被 "session append cannot reenter" 拒绝。
|
|
36
|
-
*
|
|
37
|
-
* 为什么这不"污染 transcripts":
|
|
38
|
-
* - `deliverables/presented` 不在 surface 事件表里,`deriveEventMessage()` 返回 null,
|
|
39
|
-
* 因此它**永不进入** `deriveMessages()`(模型看到的消息历史);
|
|
40
|
-
* - payload 只有 `turn` / `callId`(`capital-chart:<chart_id>` 句柄)/ `files[]`
|
|
41
|
-
* (工作区相对 `html_path` + 标题),没有 rows、series、HTML、绝对路径。
|
|
42
|
-
*
|
|
43
|
-
* 容错:`sessions` / `sessionProjections` 任一不可用、或 append 抛错,都只告警并跳过——
|
|
44
|
-
* 出图本身绝不因此失败(文件已落盘、回执照样给 html_path)。
|
|
2
|
+
* Chart → conversation deliverable channel (first-party events only).
|
|
3
|
+
* Event types are a closed set: anything outside KNOWN_SESSION_EVENT_TYPES without `ignorable`
|
|
4
|
+
* makes the whole log unreadable on cold load, and append() cannot set it.
|
|
5
|
+
* Only `deliverables/presented` is safe; attribution and the owner-keyed queue are the invariants.
|
|
45
6
|
*/
|
|
46
7
|
import { chartSessionScopeId } from './artifact-ref.js';
|
|
47
|
-
/**
|
|
48
|
-
* 登记本轮交付物用的官方事件名。
|
|
49
|
-
*
|
|
50
|
-
* 必须留在 `KNOWN_SESSION_EVENT_TYPES` 内——它是 `dsh-tool-present` 追加的同一类型,
|
|
51
|
-
* 因此在**任何** harness 版本上都能被读回。改这里等于改会话日志契约,
|
|
52
|
-
* `test/chart-turn-events.test.mjs` 有一条断言把它钉在 first-party 词汇表上。
|
|
53
|
-
*/
|
|
8
|
+
/** Must stay in `KNOWN_SESSION_EVENT_TYPES`; test/chart-turn-events.test.mjs pins it. */
|
|
54
9
|
export const CHART_DELIVERABLE_EVENT = 'deliverables/presented';
|
|
55
|
-
/** `callId`
|
|
10
|
+
/** `callId` only needs to be a non-empty string; carries the chart handle for log tracing. */
|
|
56
11
|
export const CHART_CALL_ID_PREFIX = 'capital-chart:';
|
|
57
|
-
/**
|
|
12
|
+
/** Per (session, turn) cap: stops model loops; overflow warns only. */
|
|
58
13
|
export const MAX_CHARTS_PER_TURN = 8;
|
|
59
|
-
/**
|
|
14
|
+
/** Root lookup cap (main → data_junior → specialist is one hop, plus headroom). */
|
|
60
15
|
export const MAX_OWNER_HOPS = 4;
|
|
61
|
-
/**
|
|
62
|
-
* 最近一次 publish 的逐步轨迹(模块级:发布器每次出图都新建,轨迹要跨实例可读)。
|
|
63
|
-
*/
|
|
64
16
|
let lastPublishTrace = '';
|
|
65
|
-
/** 只读:最近一次 publish 轨迹。 */
|
|
66
17
|
export function chartEventPublishTrace() {
|
|
67
18
|
return lastPublishTrace;
|
|
68
19
|
}
|
|
69
|
-
/**
|
|
70
|
-
* 交付通道的**诊断开关**:`CAPITAL_CHART_TRACE=1` 时才输出进度日志与落盘 trace。
|
|
71
|
-
*
|
|
72
|
-
* 默认关闭——对外发布不该往用户终端刷进度、也不该往 workspace 写调试文件。
|
|
73
|
-
* 排查"图表/交付行不出现"时打开它(见 `deliverable-trace.txt` 与 docs/dev/chart-delivery-events.md §6.3 的说明)。
|
|
74
|
-
*/
|
|
20
|
+
/** Diagnostic switch; off by default to keep terminals and workspaces clean. */
|
|
75
21
|
export function chartTraceEnabled() {
|
|
76
22
|
try {
|
|
77
23
|
const env = globalThis.process?.env;
|
|
@@ -87,14 +33,14 @@ function isAppendableSession(value) {
|
|
|
87
33
|
&& typeof value.append === 'function'
|
|
88
34
|
&& typeof value.id === 'string';
|
|
89
35
|
}
|
|
36
|
+
/** `parentSession` alone is seed lineage (a user fork); only `origin: 'subagent'` is delegation. */
|
|
90
37
|
function parentSessionOf(session) {
|
|
38
|
+
if (session.header?.origin !== 'subagent')
|
|
39
|
+
return undefined;
|
|
91
40
|
const parent = session.header?.parentSession;
|
|
92
41
|
return typeof parent === 'string' && parent.length > 0 ? parent : undefined;
|
|
93
42
|
}
|
|
94
|
-
/**
|
|
95
|
-
* 从 owner scope 出发向上走到根会话:用户看的是根会话的对话流,卡片必须挂在它上面。
|
|
96
|
-
* 找不到(会话已回收)时返回 undefined,调用方静默跳过。
|
|
97
|
-
*/
|
|
43
|
+
/** Walk up to the root session — the user watches the root conversation. Missing session → undefined. */
|
|
98
44
|
export function resolveOwnerSession(sessions, ownerSessionId, maxHops = MAX_OWNER_HOPS) {
|
|
99
45
|
let current;
|
|
100
46
|
try {
|
|
@@ -123,18 +69,8 @@ export function resolveOwnerSession(sessions, ownerSessionId, maxHops = MAX_OWNE
|
|
|
123
69
|
}
|
|
124
70
|
return current;
|
|
125
71
|
}
|
|
126
|
-
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* 形状与 `dsh-tool-present` 逐字段对齐(那边是 `{ turn, callId, files }`,
|
|
130
|
-
* 客户端 `isPresentedData()` 要求 `turn` 是 >=1 的安全整数、`callId` 非空字符串、
|
|
131
|
-
* `files` 是数组)。`files[].path` 必须是**工作区相对**路径:官方打开动作会拿它去
|
|
132
|
-
* `workspaceFiles.stat({ sessionId, workspaceRoot: session.cwd })` 解析,绝对路径过不了
|
|
133
|
-
* 「Presented file has no verified Host path」这一关。
|
|
134
|
-
*
|
|
135
|
-
* `callId` 只是官方契约里的一个非空字符串句柄,没有配对约束(`present` 那边用来关联
|
|
136
|
-
* tool/result),这里借它携带 `chart_id`,便于在日志里把一条交付回溯到具体图表。
|
|
137
|
-
*/
|
|
72
|
+
/** Matches `dsh-tool-present` ({ turn, callId, files }); files[].path must be workspace-relative
|
|
73
|
+
* because the official open action resolves it against session.cwd. */
|
|
138
74
|
export function buildChartEventPayload(input, turn) {
|
|
139
75
|
return {
|
|
140
76
|
turn,
|
|
@@ -147,58 +83,17 @@ export function buildChartEventPayload(input, turn) {
|
|
|
147
83
|
],
|
|
148
84
|
};
|
|
149
85
|
}
|
|
150
|
-
/**
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
* ⚠️ 2026-09-20 真机事故的根因:`createChartEventPublisher()` 是在**每次 `render_chart`
|
|
154
|
-
* 时**惰性调用的(见 `src/index.ts` 的 `chartEvents: () => createChartEventPublisher(...)`),
|
|
155
|
-
* 所以每个实例都拿到一份**自己的** `pending`。于是:
|
|
156
|
-
* 1. 出图时回合是关的 → 图被寄存进**实例 A** 的 pending;
|
|
157
|
-
* 2. 稍后 owner 那一轮关闭 → 冲刷回调跑在**实例 B**(甚至没有实例)上;
|
|
158
|
-
* 3. B 的 pending 是空的 → 什么都没写 → 图**永久丢失**,且静默无报错。
|
|
159
|
-
* 旧代码之所以看不出这个问题,是因为"回合打开时立即 append"这条路径占多数,
|
|
160
|
-
* 只有"回合之间出图"(本项目的主 Agent 等子 Agent 时必然发生)才走寄存。
|
|
161
|
-
*
|
|
162
|
-
* 因此 pending 必须与进程内的服务实例同生命周期:模块级,并在注册冲刷监听时才清理。
|
|
163
|
-
*/
|
|
86
|
+
/** Process-level, not per publisher (which is rebuilt per render): keyed by owner (root) session
|
|
87
|
+
* id — the id flush events carry — and cleared only by flushing or an explicit reset. */
|
|
164
88
|
const pending = new Map();
|
|
165
|
-
/**
|
|
166
|
-
* 进程内待登记条目的总量上限(安全阀)。
|
|
167
|
-
*
|
|
168
|
-
* 正常流程下条目会在 owner 那一轮关闭时被冲刷清空;但如果冲刷时机始终没接上
|
|
169
|
-
* (上游事件名消失、监听注册失败且 turn/start 也没来),模块级 Map 会一直攒着。
|
|
170
|
-
* 这是纯内存元数据(只有 chart_id / 标题 / 相对路径),上限只为防无界增长,
|
|
171
|
-
* 与 `MAX_CHARTS_PER_TURN` 的"防模型循环"是两件事。
|
|
172
|
-
*/
|
|
89
|
+
/** Safety valve if flushing never fires; metadata only, bounded against unbounded growth. */
|
|
173
90
|
export const MAX_PENDING_CHARTS = 64;
|
|
174
|
-
/**
|
|
175
|
-
* 只给测试用:清空待登记队列。
|
|
176
|
-
*
|
|
177
|
-
* `pending` 是模块级的(必须跨发布器实例存活,见下方说明),因此进程内所有测试
|
|
178
|
-
* 共享它。测试之间要隔离,就在用例开始时调用一次。
|
|
179
|
-
*/
|
|
91
|
+
/** Test-only: clear the shared queue between cases. */
|
|
180
92
|
export function resetChartEventPending() {
|
|
181
93
|
pending.clear();
|
|
182
94
|
}
|
|
183
|
-
/**
|
|
184
|
-
*
|
|
185
|
-
* 在 preset scope 里正常可解析;缺失表示宿主组合不完整,此时静默降级)。
|
|
186
|
-
*/
|
|
187
|
-
/**
|
|
188
|
-
* 进程级单例发布器。
|
|
189
|
-
*
|
|
190
|
-
* ⚠️ 2026-09-20 真机事故的**真根因**:`src/index.ts` 的 `chartEvents: () => createChartEventPublisher(...)`
|
|
191
|
-
* 是**每次 `render_chart` 都调用一次**的。若每次都新建发布器:
|
|
192
|
-
* 1. 实例 A 出图时把图寄存进自己的队列,并注册了**绑定在 A 上**的冲刷监听;
|
|
193
|
-
* 2. 下一次出图再建实例 B(又注册一套监听);
|
|
194
|
-
* 3. 冲刷回调里 `flush` 闭包引用的是某个实例的队列,而实例不断增多,
|
|
195
|
-
* 寄存与冲刷落在不同实例上 —— 于是"专家确实出图,但主会话一条交付事件都没有"。
|
|
196
|
-
*
|
|
197
|
-
* 所以发布器必须**进程内唯一**:队列、监听、perTurn 计数都只有一份。
|
|
198
|
-
* 缓存键用 `ctx.root ?? ctx`(cordis 的 root context 是稳定对象;`index.ts` 传进来的
|
|
199
|
-
* 是个每次新建的 spread 字面量,不能用它做身份判断)。构造失败不缓存——宿主服务
|
|
200
|
-
* 可能稍后才就绪,下次出图要能重新解析(保留原有的惰性语义)。
|
|
201
|
-
*/
|
|
95
|
+
/** Process-wide singleton (queue, listeners, per-turn counts) keyed by `ctx.root ?? ctx`;
|
|
96
|
+
* never cache a failed build so later-mounting host services can be picked up. */
|
|
202
97
|
let cachedPublisher;
|
|
203
98
|
export function createChartEventPublisher(ctx) {
|
|
204
99
|
const ref = ctx.root ?? ctx;
|
|
@@ -209,7 +104,7 @@ export function createChartEventPublisher(ctx) {
|
|
|
209
104
|
cachedPublisher = { ref, publisher };
|
|
210
105
|
return publisher;
|
|
211
106
|
}
|
|
212
|
-
/**
|
|
107
|
+
/** Test-only: drop the singleton cache. */
|
|
213
108
|
export function resetChartEventPublisher() {
|
|
214
109
|
cachedPublisher = undefined;
|
|
215
110
|
}
|
|
@@ -222,23 +117,15 @@ function buildChartEventPublisher(ctx) {
|
|
|
222
117
|
return undefined;
|
|
223
118
|
const perTurn = new Map();
|
|
224
119
|
let warned = false;
|
|
225
|
-
/**
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
* ⚠️ 实测(2026-09-20):cordis 的 `ctx.logger` **只挂了一个内存 exporter**
|
|
229
|
-
* (`cordis/lib/index.js` 的 `LoggerService` 构造器),全仓没有任何地方注册 console
|
|
230
|
-
* exporter —— 也就是说 `ctx.logger.warn` 只进内存缓冲,**不会出现在 dsh 终端里**。
|
|
231
|
-
* 这正是"卡片不出现却零报错"、两轮排查都只能靠猜的原因。
|
|
232
|
-
* 因此这里**同时**写 `process.stderr`(宿主 stderr 一定能在终端看到);
|
|
233
|
-
* logger 照发,保留结构化疗日志,将来有人接 exporter 就能收到。
|
|
234
|
-
*/
|
|
120
|
+
/** `ctx.logger` only buffers in memory (no console exporter), so also write stderr; otherwise
|
|
121
|
+
* delivery failures are invisible. */
|
|
235
122
|
const emit = (message) => {
|
|
236
123
|
const line = `capital-generation: ${message}`;
|
|
237
124
|
try {
|
|
238
125
|
ctx.logger?.warn(line);
|
|
239
126
|
}
|
|
240
127
|
catch {
|
|
241
|
-
//
|
|
128
|
+
// Logger unavailable; diagnostics still go to stderr below.
|
|
242
129
|
}
|
|
243
130
|
try {
|
|
244
131
|
const stderr = globalThis.process?.stderr;
|
|
@@ -246,7 +133,7 @@ function buildChartEventPublisher(ctx) {
|
|
|
246
133
|
stderr.write(`${line}\n`);
|
|
247
134
|
}
|
|
248
135
|
catch {
|
|
249
|
-
//
|
|
136
|
+
// No process (non-Node host).
|
|
250
137
|
}
|
|
251
138
|
};
|
|
252
139
|
const warn = (message) => {
|
|
@@ -255,16 +142,7 @@ function buildChartEventPublisher(ctx) {
|
|
|
255
142
|
warned = true;
|
|
256
143
|
emit(message);
|
|
257
144
|
};
|
|
258
|
-
/**
|
|
259
|
-
* 进度日志:**只在 `CAPITAL_CHART_TRACE=1` 时输出**。
|
|
260
|
-
*
|
|
261
|
-
* 寄存/登记这两处状态迁移是排查"交付行不出现"的关键线索,但对**普通用户是噪音**
|
|
262
|
-
* (每张图两条)。所以默认静默、按需打开:
|
|
263
|
-
*
|
|
264
|
-
* CAPITAL_CHART_TRACE=1 dsh web
|
|
265
|
-
*
|
|
266
|
-
* 失败用的 `warn` 不受此开关影响——真出问题必须让人看见。
|
|
267
|
-
*/
|
|
145
|
+
/** Progress log only under CAPITAL_CHART_TRACE=1; warnings bypass the switch. */
|
|
268
146
|
const note = (message) => {
|
|
269
147
|
if (!chartTraceEnabled())
|
|
270
148
|
return;
|
|
@@ -278,7 +156,6 @@ function buildChartEventPublisher(ctx) {
|
|
|
278
156
|
return undefined;
|
|
279
157
|
}
|
|
280
158
|
};
|
|
281
|
-
/** 真正写一条事件;返回是否写入。 */
|
|
282
159
|
const append = (owner, turn, input) => {
|
|
283
160
|
const key = `${owner.id}:${turn}`;
|
|
284
161
|
const used = perTurn.get(key) ?? 0;
|
|
@@ -294,16 +171,10 @@ function buildChartEventPublisher(ctx) {
|
|
|
294
171
|
return false;
|
|
295
172
|
}
|
|
296
173
|
perTurn.set(key, used + 1);
|
|
297
|
-
// 与"已寄存"成对的成功日志:排查交付行不出现时,一眼能看出是没写还是没渲染。
|
|
298
174
|
note(`图表已登记为本轮交付(owner=${owner.id},turn=${turn},${input.chart_id})`);
|
|
299
175
|
return true;
|
|
300
176
|
};
|
|
301
|
-
/**
|
|
302
|
-
* 把寄存的图表写进某一轮。
|
|
303
|
-
*
|
|
304
|
-
* 冲刷时机决定交付行的落点:首选 `agent/turn-stopping`(该轮即将关闭),
|
|
305
|
-
* 只有在拿不到这个钩子时才退回 `turn/start`。
|
|
306
|
-
*/
|
|
177
|
+
/** Flush queued charts into a turn; the preferred hook is turn-stopping. */
|
|
307
178
|
const flush = (ownerSessionId, turn) => {
|
|
308
179
|
const queued = pending.get(ownerSessionId);
|
|
309
180
|
if (queued === undefined || queued.length === 0)
|
|
@@ -317,14 +188,9 @@ function buildChartEventPublisher(ctx) {
|
|
|
317
188
|
for (const item of queued)
|
|
318
189
|
append(owner, turn, item);
|
|
319
190
|
};
|
|
320
|
-
/**
|
|
321
|
-
* 首选时机是否可用。
|
|
322
|
-
*
|
|
323
|
-
* 关键:`turn-stopping` 缺席时**必须**退回 `turn/start`,否则寄存的图会永远不写(静默丢失)。
|
|
324
|
-
* 安装成功才置 true;effect 卸载时复位,避免失效后仍以为首选可用。
|
|
325
|
-
*/
|
|
191
|
+
/** Only a real registration may flip this; otherwise turn/start fallback stays live. */
|
|
326
192
|
let turnStoppingActive = false;
|
|
327
|
-
/**
|
|
193
|
+
/** Never append inside the appending window; defer to a microtask. */
|
|
328
194
|
const flushSoon = (ownerSessionId, turn) => {
|
|
329
195
|
queueMicrotask(() => {
|
|
330
196
|
try {
|
|
@@ -343,8 +209,7 @@ function buildChartEventPublisher(ctx) {
|
|
|
343
209
|
const event = args[1];
|
|
344
210
|
if (event?.type !== 'turn/start')
|
|
345
211
|
return;
|
|
346
|
-
//
|
|
347
|
-
// 交付行会被落到空轮里。只有拿不到 `agent/turn-stopping` 时才用它兜底。
|
|
212
|
+
// turn/start must not race ahead while turn-stopping is active; it is only the fallback.
|
|
348
213
|
if (turnStoppingActive)
|
|
349
214
|
return;
|
|
350
215
|
const sessionId = typeof session?.id === 'string' ? session.id : undefined;
|
|
@@ -356,7 +221,7 @@ function buildChartEventPublisher(ctx) {
|
|
|
356
221
|
flushSoon(sessionId, turn);
|
|
357
222
|
}
|
|
358
223
|
catch {
|
|
359
|
-
//
|
|
224
|
+
// Observers never throw.
|
|
360
225
|
}
|
|
361
226
|
});
|
|
362
227
|
if (typeof ctx.effect === 'function') {
|
|
@@ -379,21 +244,18 @@ function buildChartEventPublisher(ctx) {
|
|
|
379
244
|
flushSoon(sessionId, turn);
|
|
380
245
|
}
|
|
381
246
|
catch {
|
|
382
|
-
//
|
|
247
|
+
// Observers must not throw.
|
|
383
248
|
}
|
|
384
249
|
});
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
// (适配器 catch 后返回 undefined)时,闸门仍被打开、`turn/start` 兜底被永久关掉,
|
|
388
|
-
// 图就**永远滞留 pending**——表现是交付卡片从头到尾不出现,且零报错。
|
|
389
|
-
// 注册失败必须让 `turnStoppingActive` 保持 false,由 `turn/start` 接上。
|
|
250
|
+
// Only a real disposer means the preferred hook is active; failed registration must leave
|
|
251
|
+
// the turn/start fallback on, or queued charts stay pending forever.
|
|
390
252
|
if (typeof disposeTurnStopping === 'function')
|
|
391
253
|
turnStoppingActive = true;
|
|
392
254
|
else
|
|
393
255
|
warn('交付行的首选冲刷时机不可用(agent/turn-stopping 注册失败),已退回 turn/start 兜底;落点可能偏早一轮');
|
|
394
256
|
if (typeof ctx.effect === 'function') {
|
|
395
257
|
ctx.effect(() => () => {
|
|
396
|
-
//
|
|
258
|
+
// Teardown re-enables the fallback so queued charts are not lost.
|
|
397
259
|
turnStoppingActive = false;
|
|
398
260
|
if (typeof disposeTurnStopping === 'function')
|
|
399
261
|
disposeTurnStopping();
|
|
@@ -414,20 +276,15 @@ function buildChartEventPublisher(ctx) {
|
|
|
414
276
|
const openSeq = boundary?.openTurnStartSeq;
|
|
415
277
|
const lastTurn = boundary?.lastTurn;
|
|
416
278
|
if (typeof openSeq !== 'number') {
|
|
417
|
-
//
|
|
418
|
-
//
|
|
419
|
-
//
|
|
420
|
-
// ⚠️ 寄存队列必须按 **owner**(根会话)的 id 归档,不能按 `input.ownerSessionId`:
|
|
421
|
-
// 后者是发起出图的 scope(visualization_specialist,孙会话),而冲刷时机
|
|
422
|
-
// (`turn/start` / `agent/turn-stopping`)拿到的都是 owner 的 id。
|
|
423
|
-
// 两边键不一致时 `flush()` 永远查不到,图会**永久滞留在 pending**、静默不登记
|
|
424
|
-
// (2026-09-20 实测:3 个 specialist 会话 render_chart=2 而 deliverables=0)。
|
|
279
|
+
// Between turns (main agent closes its turn while waiting on children): queue until
|
|
280
|
+
// the owner's turn closes. Key by owner.id — flush events carry that id; a caller-scope
|
|
281
|
+
// key would never match and strands charts forever.
|
|
425
282
|
const queued = pending.get(owner.id) ?? [];
|
|
426
283
|
if (queued.length >= MAX_CHARTS_PER_TURN) {
|
|
427
284
|
warn(`图表对话流事件跳过:${owner.id} 已寄存 ${MAX_CHARTS_PER_TURN} 张,等不到新回合`);
|
|
428
285
|
return false;
|
|
429
286
|
}
|
|
430
|
-
//
|
|
287
|
+
// Unreachable in normal flow; bound memory if flushing is broken.
|
|
431
288
|
let total = 0;
|
|
432
289
|
for (const list of pending.values())
|
|
433
290
|
total += list.length;
|
|
@@ -437,14 +294,11 @@ function buildChartEventPublisher(ctx) {
|
|
|
437
294
|
}
|
|
438
295
|
queued.push(input);
|
|
439
296
|
pending.set(owner.id, queued);
|
|
440
|
-
// 出图时回合是关的 ⇒ 走寄存。这条日志是**必要的可观测性**:它之后若没有对应的
|
|
441
|
-
// "已登记" 日志,就说明冲刷时机没接上(2026-09-20 的静默丢失正是死在这里,
|
|
442
|
-
// 两轮排查都因为没有日志而只能猜)。只在进程内报一次,不刷屏。
|
|
443
297
|
note(`图表已寄存,待 owner 回合关闭时登记(owner=${owner.id},lastTurn=${String(lastTurn)},寄存 ${queued.length} 张)`);
|
|
444
298
|
lastPublishTrace = `${trace.join(' ')} 回合未打开(openTurnStartSeq=${String(openSeq)} lastTurn=${String(lastTurn)}) → 已寄存 ${queued.length} 张,等 turn-stopping/turn-start 冲刷`;
|
|
445
299
|
return true;
|
|
446
300
|
}
|
|
447
|
-
// turn 0 /
|
|
301
|
+
// turn 0 / invalid means the session never really opened a turn.
|
|
448
302
|
if (typeof lastTurn !== 'number' || !Number.isSafeInteger(lastTurn) || lastTurn < 1) {
|
|
449
303
|
lastPublishTrace = `${trace.join(' ')} 回合号非法(lastTurn=${String(lastTurn)}) → 未写入`;
|
|
450
304
|
warn('图表对话流事件跳过:owner 会话没有打开中的 turn');
|
|
@@ -454,7 +308,7 @@ function buildChartEventPublisher(ctx) {
|
|
|
454
308
|
lastPublishTrace = `${trace.join(' ')} 回合打开(turn=${lastTurn}) → ${written ? '已直接登记' : '写入失败'}`;
|
|
455
309
|
return written;
|
|
456
310
|
},
|
|
457
|
-
/**
|
|
311
|
+
/** Diagnostics/tests: queued charts not yet flushed. */
|
|
458
312
|
pendingCount(ownerSessionId) {
|
|
459
313
|
if (ownerSessionId !== undefined)
|
|
460
314
|
return pending.get(ownerSessionId)?.length ?? 0;
|
|
@@ -465,7 +319,7 @@ function buildChartEventPublisher(ctx) {
|
|
|
465
319
|
},
|
|
466
320
|
};
|
|
467
321
|
}
|
|
468
|
-
/**
|
|
322
|
+
/** Owner scope at render time; same source as chart_ref (specialist renders land on the main session). */
|
|
469
323
|
export function chartEventOwnerSessionId(session, sourceSession) {
|
|
470
324
|
return chartSessionScopeId(sourceSession ?? session);
|
|
471
325
|
}
|
package/lib/chart/html.d.ts
CHANGED
|
@@ -5,16 +5,13 @@ export interface VendoredChartLibrary {
|
|
|
5
5
|
license: string;
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* 路径相对 `import.meta.url`:编译后是 `lib/chart/vendor/...`(由 scripts/copy-assets.mjs
|
|
11
|
-
* 从 `vendor/` 复制过去)。缺失时给出可操作错误,而不是让 HTML 里静默少一段脚本。
|
|
8
|
+
* Path is relative to import.meta.url (assets copied into lib by scripts/copy-assets.mjs);
|
|
9
|
+
* a missing library is a hard, actionable error rather than silently missing script bytes.
|
|
12
10
|
*/
|
|
13
11
|
export declare function loadVendoredChartLibrary(): VendoredChartLibrary;
|
|
14
12
|
export interface BuildStandaloneHtmlInput {
|
|
15
13
|
payload: ChartPayload;
|
|
16
14
|
library: VendoredChartLibrary;
|
|
17
|
-
/** 生成时刻(epoch ms),仅用于页脚署名,不参与渲染。 */
|
|
18
15
|
generatedAt: number;
|
|
19
16
|
}
|
|
20
17
|
export declare function buildStandaloneHtml(input: BuildStandaloneHtmlInput): string;
|
package/lib/chart/html.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* 它只依赖"用户有一个浏览器",不依赖 DSH 的客户端插件系统、不依赖网络、
|
|
6
|
-
* 不依赖任何上游扩展点。即使 Slot / bundle / 路由全部被上游改动打坏,
|
|
7
|
-
* 用户依然能拿到一张可交互的图。
|
|
8
|
-
*
|
|
9
|
-
* 因此它必须:内联 vendored 库 + 内联数据 + 内联运行时,**零外部请求**。
|
|
2
|
+
* Standalone HTML: the L2 fallback that needs only a browser. Vendored library, data and
|
|
3
|
+
* runtime are inlined (zero external requests) so a broken client plugin, route or network
|
|
4
|
+
* cannot take charts down.
|
|
10
5
|
*/
|
|
11
6
|
import { ChartError } from './errors.js';
|
|
12
7
|
import { CHART_RUNTIME_SOURCE } from './runtime.js';
|
|
@@ -20,10 +15,8 @@ function nodeFs() {
|
|
|
20
15
|
}
|
|
21
16
|
let cachedLibrary;
|
|
22
17
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* 路径相对 `import.meta.url`:编译后是 `lib/chart/vendor/...`(由 scripts/copy-assets.mjs
|
|
26
|
-
* 从 `vendor/` 复制过去)。缺失时给出可操作错误,而不是让 HTML 里静默少一段脚本。
|
|
18
|
+
* Path is relative to import.meta.url (assets copied into lib by scripts/copy-assets.mjs);
|
|
19
|
+
* a missing library is a hard, actionable error rather than silently missing script bytes.
|
|
27
20
|
*/
|
|
28
21
|
export function loadVendoredChartLibrary() {
|
|
29
22
|
if (cachedLibrary)
|
|
@@ -41,15 +34,15 @@ export function loadVendoredChartLibrary() {
|
|
|
41
34
|
throw new ChartError('chart_runtime_unavailable', `vendored lightweight-charts is missing (${error instanceof Error ? error.message : String(error)}); run npm run vendor:charts && npm run build`);
|
|
42
35
|
}
|
|
43
36
|
}
|
|
44
|
-
/**
|
|
37
|
+
/** `</script` would close the inline script element early. */
|
|
45
38
|
function escapeScript(source) {
|
|
46
39
|
return source.replace(/<\/script/gi, '<\\/script');
|
|
47
40
|
}
|
|
48
|
-
/**
|
|
41
|
+
/** `<` becomes `\u003c` (valid JSON) so data can never close a tag. */
|
|
49
42
|
function escapeJson(value) {
|
|
50
43
|
return JSON.stringify(value).replace(/</g, '\\u003c');
|
|
51
44
|
}
|
|
52
|
-
/**
|
|
45
|
+
/** Data timestamps display in Asia/Shanghai per data convention; UTC is the fallback. */
|
|
53
46
|
function formatCapturedAt(epochMs) {
|
|
54
47
|
try {
|
|
55
48
|
const parts = new Intl.DateTimeFormat('en-CA', {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* present_charts: the root Agent's final-turn delivery step.
|
|
3
|
+
*
|
|
4
|
+
* render_chart only mints a chart_ref (the specialist row no longer auto-delivers). DSH's
|
|
5
|
+
* deliverable card replays only declarations recorded before the closing assistant message, so
|
|
6
|
+
* the card has to be written by the turn that carries the final answer: the root Agent presents
|
|
7
|
+
* its collected refs/deliverables here immediately before writing that answer. Never a custom
|
|
8
|
+
* event type — the session-event vocabulary is closed (see events.ts).
|
|
9
|
+
*/
|
|
10
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
11
|
+
import type { ChartArtifactRegistry } from './artifact-ref.js';
|
|
12
|
+
import { type ChartEventPublisher } from './events.js';
|
|
13
|
+
export interface PresentChartsInput {
|
|
14
|
+
artifacts: ChartArtifactRegistry;
|
|
15
|
+
/** Lazy: the host projection may mount after this row; an absent publisher fails the call loudly. */
|
|
16
|
+
chartEvents: () => ChartEventPublisher | undefined;
|
|
17
|
+
}
|
|
18
|
+
export interface PresentChartsReceipt {
|
|
19
|
+
presented: Array<{
|
|
20
|
+
chart_ref: string;
|
|
21
|
+
title: string;
|
|
22
|
+
}>;
|
|
23
|
+
failed: Array<{
|
|
24
|
+
chart_ref: string;
|
|
25
|
+
reason: string;
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
export declare function registerPresentChartsTool(ctx: Context, input: PresentChartsInput): void;
|