odai-dsh-agent 0.2.15 → 0.2.16
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 +20 -6
- package/build/bin/odai-dsh-agent.mjs +88 -7
- package/build/bin/odai-dsh-agent.mjs.map +1 -1
- package/build/src/control-center-installer.d.mts +32 -0
- package/build/src/control-center-installer.d.mts.map +1 -0
- package/build/src/control-center-installer.mjs +112 -0
- package/build/src/control-center-installer.mjs.map +1 -0
- package/build/src/control-center-prompt.d.mts +4 -0
- package/build/src/control-center-prompt.d.mts.map +1 -0
- package/build/src/control-center-prompt.mjs +15 -0
- package/build/src/control-center-prompt.mjs.map +1 -0
- package/build/src/installer.d.mts +2 -0
- package/build/src/installer.d.mts.map +1 -1
- package/build/src/installer.mjs +9 -0
- package/build/src/installer.mjs.map +1 -1
- package/client/client.js +517 -0
- package/control-center.cordis.patch.yml +3 -0
- package/package.json +23 -4
- package/preset/odai/runtime/control-center-host.d.mts +10 -0
- package/preset/odai/runtime/control-center-host.d.mts.map +1 -0
- package/preset/odai/runtime/control-center-host.mjs +15 -0
- package/preset/odai/runtime/control-center-host.mjs.map +1 -0
- package/preset/odai/runtime/control-center-runtime.d.mts +26 -0
- package/preset/odai/runtime/control-center-runtime.d.mts.map +1 -0
- package/preset/odai/runtime/control-center-runtime.mjs +118 -0
- package/preset/odai/runtime/control-center-runtime.mjs.map +1 -0
- package/preset/odai/runtime/index.d.mts.map +1 -1
- package/preset/odai/runtime/index.mjs +9 -0
- package/preset/odai/runtime/index.mjs.map +1 -1
- package/preset/odai/runtime/routing-config.d.mts +10 -0
- package/preset/odai/runtime/routing-config.d.mts.map +1 -1
- package/preset/odai/runtime/routing-config.mjs +83 -72
- package/preset/odai/runtime/routing-config.mjs.map +1 -1
- package/preset/odai/runtime/runtime-types.d.mts +1 -0
- package/preset/odai/runtime/runtime-types.d.mts.map +1 -1
package/client/client.js
ADDED
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "odai-dsh-agent",
|
|
3
|
+
factory: (require_) => {
|
|
4
|
+
const module = { exports: {} };
|
|
5
|
+
const exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
const React = require_("react");
|
|
8
|
+
const ReactDOM = require_("react-dom");
|
|
9
|
+
const { createElement: h, useEffect, useMemo, useRef, useState } = React;
|
|
10
|
+
const PACKAGE_ID = "odai-dsh-agent";
|
|
11
|
+
const ROLES = Object.freeze(["researcher", "planner", "reviewer", "frontend"]);
|
|
12
|
+
const ROLE_LABELS = Object.freeze({
|
|
13
|
+
controller: "总控",
|
|
14
|
+
researcher: "调查",
|
|
15
|
+
planner: "规划",
|
|
16
|
+
reviewer: "审查",
|
|
17
|
+
frontend: "设计",
|
|
18
|
+
});
|
|
19
|
+
const STATE_LABELS = Object.freeze({
|
|
20
|
+
proposal: "已提议",
|
|
21
|
+
accepted: "已受理",
|
|
22
|
+
decision: "路由决策",
|
|
23
|
+
direct: "总控直办",
|
|
24
|
+
"same-turn": "同轮职责",
|
|
25
|
+
child: "子代理",
|
|
26
|
+
handback: "已回交",
|
|
27
|
+
blocked: "受阻",
|
|
28
|
+
interrupted: "已中断",
|
|
29
|
+
configured: "已配置",
|
|
30
|
+
evidence: "证据",
|
|
31
|
+
});
|
|
32
|
+
function record(value) {
|
|
33
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
34
|
+
}
|
|
35
|
+
function text(value, fallback = "") {
|
|
36
|
+
return typeof value === "string" && value.trim() ? value.trim() : fallback;
|
|
37
|
+
}
|
|
38
|
+
function positiveInteger(value) {
|
|
39
|
+
const parsed = typeof value === "string" && value.trim() ? Number(value) : value;
|
|
40
|
+
return typeof parsed === "number" && Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
41
|
+
}
|
|
42
|
+
function roleOf(data) {
|
|
43
|
+
const value = data?.responsibility ?? data?.targetRole ?? data?.role ?? data?.from;
|
|
44
|
+
return typeof value === "string" && [...ROLES, "controller"].includes(value) ? value : "controller";
|
|
45
|
+
}
|
|
46
|
+
function stateOf(type, data) {
|
|
47
|
+
if (type === "odai/responsibility-gap")
|
|
48
|
+
return "proposal";
|
|
49
|
+
if (type === "odai/responsibility-gap-consumed")
|
|
50
|
+
return "accepted";
|
|
51
|
+
if (type === "odai/responsibility-gap-deferred" || type === "odai/route-protection")
|
|
52
|
+
return "blocked";
|
|
53
|
+
if (type === "odai/responsibility-returned")
|
|
54
|
+
return "handback";
|
|
55
|
+
if (type.includes("interruption") || type === "odai/controller-output-interrupted")
|
|
56
|
+
return "interrupted";
|
|
57
|
+
if (type === "odai/responsibility-scope-started" || type === "odai/responsibility-scope-claimed" || type === "odai/route-upgrade" || type === "odai/route-applied")
|
|
58
|
+
return "same-turn";
|
|
59
|
+
if (type === "odai/route-result") {
|
|
60
|
+
if (data?.status === "fallback" || data?.status === "failed")
|
|
61
|
+
return "blocked";
|
|
62
|
+
return data?.action === "delegate" ? "child" : "direct";
|
|
63
|
+
}
|
|
64
|
+
if (type === "odai/route-fallback" || type === "odai/route-config-missing" || type === "odai/governance-denied")
|
|
65
|
+
return "blocked";
|
|
66
|
+
if (type === "odai/route-decided")
|
|
67
|
+
return data?.action === "direct" ? "direct" : "decision";
|
|
68
|
+
if (type.includes("configured"))
|
|
69
|
+
return "configured";
|
|
70
|
+
return "evidence";
|
|
71
|
+
}
|
|
72
|
+
function titleOf(type, data) {
|
|
73
|
+
const role = ROLE_LABELS[roleOf(data)] ?? roleOf(data);
|
|
74
|
+
switch (type) {
|
|
75
|
+
case "odai/responsibility-gap": return `${role}职责缺口已提议`;
|
|
76
|
+
case "odai/responsibility-gap-consumed": return `${role}提议已受理`;
|
|
77
|
+
case "odai/responsibility-gap-deferred": return `${role}提议已暂缓`;
|
|
78
|
+
case "odai/responsibility-returned": return `${role}已回交总控`;
|
|
79
|
+
case "odai/responsibility-return-missing": return `${role}缺少回交`;
|
|
80
|
+
case "odai/responsibility-scope-started": return `${role}职责已启动`;
|
|
81
|
+
case "odai/responsibility-scope-claimed": return `${role}职责已接管`;
|
|
82
|
+
case "odai/responsibility-scope-stopped": return `${role}职责已停止`;
|
|
83
|
+
case "odai/route-decided": return `${role}路由已决策 · ${text(data?.action, "完成")}`;
|
|
84
|
+
case "odai/route-upgrade": return `${role}已切换同轮职责`;
|
|
85
|
+
case "odai/route-applied": return `${role}路由已应用`;
|
|
86
|
+
case "odai/route-result": return `${role}执行结果 · ${text(data?.status, "完成")}`;
|
|
87
|
+
case "odai/route-fallback": return `${role}路由已回退`;
|
|
88
|
+
case "odai/route-config-missing": return `${role}路由不可用`;
|
|
89
|
+
case "odai/routing-configured": return `${role}映射已配置`;
|
|
90
|
+
case "odai/route-health": return `${role}路由健康检查`;
|
|
91
|
+
case "odai/controller-output-interrupted": return "总控输出已中断";
|
|
92
|
+
case "odai/responsibility-interrupted": return `${role}职责已中断`;
|
|
93
|
+
default: return type.replace(/^odai\//u, "").replaceAll("-", " ");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function detailOf(data) {
|
|
97
|
+
const reason = typeof data?.reason === "string" ? data.reason : data?.reason?.kind;
|
|
98
|
+
return text(data?.summary) || text(data?.gap) || text(data?.expectedChange) || text(data?.stopReason) || text(data?.reasonCode) || text(reason) || text(data?.status);
|
|
99
|
+
}
|
|
100
|
+
function routeOf(data) {
|
|
101
|
+
const route = record(data?.actualRoute ?? data?.requestedRoute ?? data?.effectiveRoute ?? data?.route);
|
|
102
|
+
if (!route || !text(route.provider) || !text(route.model))
|
|
103
|
+
return undefined;
|
|
104
|
+
return Object.freeze({
|
|
105
|
+
provider: text(route.provider),
|
|
106
|
+
model: text(route.model),
|
|
107
|
+
reasoningEffort: text(route.reasoningEffort) || undefined,
|
|
108
|
+
maxTokens: positiveInteger(route.maxTokens),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function compact(value, depth = 0) {
|
|
112
|
+
if (depth >= 6)
|
|
113
|
+
return "[truncated]";
|
|
114
|
+
if (Array.isArray(value))
|
|
115
|
+
return value.slice(0, 80).map((entry) => compact(entry, depth + 1));
|
|
116
|
+
const input = record(value);
|
|
117
|
+
if (!input)
|
|
118
|
+
return typeof value === "string" && value.length > 1200 ? `${value.slice(0, 1200)}…` : value;
|
|
119
|
+
const output = {};
|
|
120
|
+
for (const [key, entry] of Object.entries(input)) {
|
|
121
|
+
if (/token|secret|credential|api[-_]?key/iu.test(key))
|
|
122
|
+
output[key] = "[redacted]";
|
|
123
|
+
else
|
|
124
|
+
output[key] = compact(entry, depth + 1);
|
|
125
|
+
}
|
|
126
|
+
return output;
|
|
127
|
+
}
|
|
128
|
+
function projectTrace(events = []) {
|
|
129
|
+
const items = [];
|
|
130
|
+
const turns = new Map();
|
|
131
|
+
for (const [index, eventValue] of events.entries()) {
|
|
132
|
+
const event = record(eventValue);
|
|
133
|
+
const type = text(event?.type);
|
|
134
|
+
if (!type.startsWith("odai/"))
|
|
135
|
+
continue;
|
|
136
|
+
const data = record(event?.data) ?? {};
|
|
137
|
+
const turn = positiveInteger(data.turn);
|
|
138
|
+
const item = Object.freeze({
|
|
139
|
+
key: `${event?.seq ?? index}:${type}`,
|
|
140
|
+
seq: Number.isSafeInteger(event?.seq) ? Number(event?.seq) : index,
|
|
141
|
+
time: typeof event?.time === "number" ? event.time : undefined,
|
|
142
|
+
turn,
|
|
143
|
+
step: positiveInteger(data.step),
|
|
144
|
+
type,
|
|
145
|
+
role: roleOf(data),
|
|
146
|
+
state: stateOf(type, data),
|
|
147
|
+
title: titleOf(type, data),
|
|
148
|
+
detail: detailOf(data),
|
|
149
|
+
route: routeOf(data),
|
|
150
|
+
raw: compact(data),
|
|
151
|
+
});
|
|
152
|
+
items.push(item);
|
|
153
|
+
const key = turn === undefined ? "session" : `turn-${turn}`;
|
|
154
|
+
if (!turns.has(key))
|
|
155
|
+
turns.set(key, { key, turn, items: [] });
|
|
156
|
+
turns.get(key)?.items.push(item);
|
|
157
|
+
}
|
|
158
|
+
const groups = [...turns.values()].map((group) => Object.freeze({
|
|
159
|
+
...group,
|
|
160
|
+
title: group.turn === undefined ? "会话级事件" : `第 ${group.turn} 轮`,
|
|
161
|
+
items: Object.freeze(group.items),
|
|
162
|
+
})).sort((left, right) => (left.turn ?? -1) - (right.turn ?? -1));
|
|
163
|
+
const currentTurn = [...groups].reverse().find((group) => group.turn !== undefined) ?? groups.at(-1);
|
|
164
|
+
const currentRoles = Object.fromEntries(ROLES.map((role) => {
|
|
165
|
+
const roleItems = currentTurn?.items.filter((item) => item.role === role) ?? [];
|
|
166
|
+
return [role, roleItems.at(-1)];
|
|
167
|
+
}));
|
|
168
|
+
return Object.freeze({ items: Object.freeze(items), turns: Object.freeze(groups), currentTurn, currentRoles: Object.freeze(currentRoles) });
|
|
169
|
+
}
|
|
170
|
+
function formatTime(value) {
|
|
171
|
+
if (typeof value !== "number")
|
|
172
|
+
return "—";
|
|
173
|
+
try {
|
|
174
|
+
return new Intl.DateTimeFormat(undefined, { hour: "2-digit", minute: "2-digit", second: "2-digit" }).format(value);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return "—";
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function formatRoute(route) {
|
|
181
|
+
if (!route)
|
|
182
|
+
return "未配置";
|
|
183
|
+
const extras = [route.reasoningEffort, route.maxTokens ? `${route.maxTokens} tokens` : ""].filter(Boolean);
|
|
184
|
+
return `${route.provider}/${route.model}${extras.length ? ` · ${extras.join(" · ")}` : ""}`;
|
|
185
|
+
}
|
|
186
|
+
function sourceLabel(source, fallback = "部署默认") {
|
|
187
|
+
if (source === "persisted-mapping" || source === "persisted-config")
|
|
188
|
+
return "本地持久配置";
|
|
189
|
+
if (source === "deployment-config")
|
|
190
|
+
return "部署配置";
|
|
191
|
+
return fallback;
|
|
192
|
+
}
|
|
193
|
+
function dispatchLabel(value) {
|
|
194
|
+
return { default: "部署默认", "same-turn": "同轮职责", child: "子代理" }[value] ?? value;
|
|
195
|
+
}
|
|
196
|
+
async function callRouting(connection, payload, signal) {
|
|
197
|
+
const response = await connection.rpc.call("/odai-control-center", "routing", payload, signal);
|
|
198
|
+
if (!response?.ok)
|
|
199
|
+
throw new Error(response?.error?.message || "Control Center RPC failed");
|
|
200
|
+
if (!response.value?.ok)
|
|
201
|
+
throw new Error(response.value?.error?.message || "Routing configuration failed");
|
|
202
|
+
return response.value.config;
|
|
203
|
+
}
|
|
204
|
+
async function callEvidence(connection, sessionId, signal) {
|
|
205
|
+
const response = await connection.rpc.call("/odai-control-center", "evidence", { sessionId }, signal);
|
|
206
|
+
if (!response?.ok)
|
|
207
|
+
throw new Error(response?.error?.message || "Control Center evidence RPC failed");
|
|
208
|
+
if (!response.value?.ok)
|
|
209
|
+
throw new Error(response.value?.error?.message || "Session evidence could not be read");
|
|
210
|
+
return Array.isArray(response.value.events) ? response.value.events : [];
|
|
211
|
+
}
|
|
212
|
+
function controlCenterError(cause) {
|
|
213
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
214
|
+
return /HTTP 405|transport failure.*odai-control-center/iu.test(message)
|
|
215
|
+
? "当前 DSH 进程尚未加载 Control Center host。安装后需重启一次 Web profile。"
|
|
216
|
+
: message;
|
|
217
|
+
}
|
|
218
|
+
function GraphIcon() {
|
|
219
|
+
return h("span", { className: "odaiCC__graphIcon", "aria-hidden": true }, h("i", { className: "odaiCC__graphNode odaiCC__graphNode--a" }), h("i", { className: "odaiCC__graphLine" }), h("i", { className: "odaiCC__graphNode odaiCC__graphNode--b" }), h("i", { className: "odaiCC__graphNode odaiCC__graphNode--c" }));
|
|
220
|
+
}
|
|
221
|
+
function RoleBadge({ role }) {
|
|
222
|
+
return h("span", { className: `odaiCC__role odaiCC__role--${role}` }, h("i", { "aria-hidden": true }), ROLE_LABELS[role] ?? role);
|
|
223
|
+
}
|
|
224
|
+
function StateBadge({ state }) {
|
|
225
|
+
return h("span", { className: `odaiCC__state odaiCC__state--${state}` }, STATE_LABELS[state] ?? state);
|
|
226
|
+
}
|
|
227
|
+
function EventRow({ item, selected, onSelect }) {
|
|
228
|
+
return h("button", {
|
|
229
|
+
type: "button",
|
|
230
|
+
className: `odaiCC__event${selected ? " is-selected" : ""}`,
|
|
231
|
+
onClick: () => onSelect(item.key),
|
|
232
|
+
"aria-pressed": selected,
|
|
233
|
+
}, h("span", { className: "odaiCC__eventTop" }, h("code", null, `#${item.seq}`), h(StateBadge, { state: item.state }), h("time", null, formatTime(item.time))), h("strong", null, item.title), item.detail ? h("span", { className: "odaiCC__eventDetail" }, item.detail) : null, h("span", { className: "odaiCC__eventBottom" }, h(RoleBadge, { role: item.role }), item.turn ? h("span", null, `第 ${item.turn} 轮${item.step ? ` / 第 ${item.step} 步` : ""}`) : h("span", null, "会话")));
|
|
234
|
+
}
|
|
235
|
+
function FlowBoard({ trace, onSelect }) {
|
|
236
|
+
const turn = trace.currentTurn;
|
|
237
|
+
return h("section", { className: "odaiCC__flow" }, h("div", { className: "odaiCC__sectionHead" }, h("div", null, h("span", { className: "odaiCC__kicker" }, "当前轮次"), h("h3", null, turn?.title ?? "暂无轮次证据")), h("span", { className: "odaiCC__eventCount" }, `${turn?.items.length ?? 0} 个事件`)), h("div", { className: "odaiCC__flowGrid" }, h("div", { className: "odaiCC__controllerNode" }, h("span", { className: "odaiCC__nodeEyebrow" }, "总控"), h("strong", null, "Odai Controller"), h("small", null, turn ? "当前会话的交付责任方" : "等待职责证据")), h("div", { className: "odaiCC__roleStack" }, ROLES.map((role) => {
|
|
238
|
+
const item = trace.currentRoles[role];
|
|
239
|
+
const state = item?.state ?? "evidence";
|
|
240
|
+
return h("button", {
|
|
241
|
+
key: role,
|
|
242
|
+
type: "button",
|
|
243
|
+
className: `odaiCC__flowRole odaiCC__flowRole--${state}`,
|
|
244
|
+
onClick: () => item && onSelect(item.key),
|
|
245
|
+
disabled: !item,
|
|
246
|
+
}, h("span", { className: "odaiCC__edge", "data-state": state }, h("i"), h("em", null, item ? STATE_LABELS[state] ?? state : "未触发")), h("span", { className: "odaiCC__roleNode" }, h(RoleBadge, { role }), h("strong", null, item?.title ?? "暂无执行证据"), h("small", null, item?.route ? formatRoute(item.route) : item?.detail || "—")));
|
|
247
|
+
}))));
|
|
248
|
+
}
|
|
249
|
+
function Timeline({ trace, selectedKey, onSelect }) {
|
|
250
|
+
return h("section", { className: "odaiCC__timeline" }, h("div", { className: "odaiCC__sectionHead" }, h("h3", null, "会话时间线"), h("span", { className: "odaiCC__eventCount" }, `${trace.items.length} 个事件`)), trace.turns.length ? h("div", { className: "odaiCC__turns" }, trace.turns.map((turn) => h("details", {
|
|
251
|
+
key: turn.key,
|
|
252
|
+
open: turn.key === trace.currentTurn?.key,
|
|
253
|
+
className: "odaiCC__turn",
|
|
254
|
+
}, h("summary", null, h("strong", null, turn.title), h("span", null, `${turn.items.length} 个事件`)), h("div", { className: "odaiCC__events" }, turn.items.map((item) => h(EventRow, {
|
|
255
|
+
key: item.key,
|
|
256
|
+
item,
|
|
257
|
+
selected: selectedKey === item.key,
|
|
258
|
+
onSelect,
|
|
259
|
+
})))))) : h("div", { className: "odaiCC__empty" }, "当前会话窗口中还没有 Odai 职责证据。"));
|
|
260
|
+
}
|
|
261
|
+
function Inspector({ item }) {
|
|
262
|
+
return h("aside", { className: "odaiCC__inspector" }, h("div", { className: "odaiCC__sectionHead" }, h("h3", null, "事件检查器"), item ? h("code", null, `#${item.seq}`) : null), item ? h(React.Fragment, null, h("dl", { className: "odaiCC__facts" }, h("div", null, h("dt", null, "类型"), h("dd", null, h("code", null, item.type))), h("div", null, h("dt", null, "状态"), h("dd", null, h(StateBadge, { state: item.state }))), h("div", null, h("dt", null, "责任方"), h("dd", null, h(RoleBadge, { role: item.role }))), h("div", null, h("dt", null, "模型路由"), h("dd", null, formatRoute(item.route)))), h("pre", { className: "odaiCC__raw" }, JSON.stringify(item.raw, null, 2))) : h("div", { className: "odaiCC__empty" }, "选择一条事件查看结构化证据。"));
|
|
263
|
+
}
|
|
264
|
+
function LiveView({ trace, evidenceError }) {
|
|
265
|
+
const [selectedKey, setSelectedKey] = useState(() => trace.items.at(-1)?.key);
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
if (!trace.items.some((item) => item.key === selectedKey))
|
|
268
|
+
setSelectedKey(trace.items.at(-1)?.key);
|
|
269
|
+
}, [trace.items, selectedKey]);
|
|
270
|
+
const selected = trace.items.find((item) => item.key === selectedKey) ?? trace.items.at(-1);
|
|
271
|
+
return h("div", { className: "odaiCC__live" }, evidenceError ? h("div", { className: "odaiCC__alert is-error", role: "alert" }, evidenceError) : null, h(FlowBoard, { trace, onSelect: setSelectedKey }), h("div", { className: "odaiCC__evidenceGrid" }, h(Timeline, { trace, selectedKey: selected?.key, onSelect: setSelectedKey }), h(Inspector, { item: selected })));
|
|
272
|
+
}
|
|
273
|
+
function RouteEditor({ role, config, busy, onApply }) {
|
|
274
|
+
const route = config?.roles?.[role];
|
|
275
|
+
const source = config?.sources?.[role];
|
|
276
|
+
const dispatch = config?.dispatch?.[role];
|
|
277
|
+
const dispatchSource = config?.dispatchSources?.[role];
|
|
278
|
+
const [editing, setEditing] = useState(false);
|
|
279
|
+
const [provider, setProvider] = useState(route?.provider ?? "");
|
|
280
|
+
const [model, setModel] = useState(route?.model ?? "");
|
|
281
|
+
const [effort, setEffort] = useState(route?.reasoningEffort ?? "");
|
|
282
|
+
const [maxTokens, setMaxTokens] = useState(route?.maxTokens ? String(route.maxTokens) : "");
|
|
283
|
+
useEffect(() => {
|
|
284
|
+
if (editing)
|
|
285
|
+
return;
|
|
286
|
+
setProvider(route?.provider ?? "");
|
|
287
|
+
setModel(route?.model ?? "");
|
|
288
|
+
setEffort(route?.reasoningEffort ?? "");
|
|
289
|
+
setMaxTokens(route?.maxTokens ? String(route.maxTokens) : "");
|
|
290
|
+
}, [route, editing]);
|
|
291
|
+
const save = () => onApply({
|
|
292
|
+
action: "set",
|
|
293
|
+
responsibility: role,
|
|
294
|
+
provider,
|
|
295
|
+
model,
|
|
296
|
+
...(effort.trim() ? { reasoningEffort: effort.trim() } : {}),
|
|
297
|
+
...(maxTokens.trim() ? { maxTokens: Number(maxTokens) } : {}),
|
|
298
|
+
}).then(() => setEditing(false));
|
|
299
|
+
const remove = () => {
|
|
300
|
+
if (!window.confirm(`确认移除“${ROLE_LABELS[role]}”的本地模型映射?`))
|
|
301
|
+
return;
|
|
302
|
+
onApply({ action: "remove", responsibility: role });
|
|
303
|
+
};
|
|
304
|
+
const setDispatch = (value) => {
|
|
305
|
+
if (value === "default")
|
|
306
|
+
onApply({ action: "reset-dispatch", responsibility: role });
|
|
307
|
+
else
|
|
308
|
+
onApply({ action: "set-dispatch", responsibility: role, dispatch: value });
|
|
309
|
+
};
|
|
310
|
+
return h("article", { className: "odaiCC__routeRow" }, h("div", { className: "odaiCC__routeSummary" }, h(RoleBadge, { role }), h("div", { className: "odaiCC__routeValue" }, h("strong", null, formatRoute(route)), h("span", null, sourceLabel(source))), h("button", { type: "button", className: "odaiCC__quietButton", onClick: () => setEditing((value) => !value), disabled: busy }, editing ? "取消" : "编辑")), h("div", { className: "odaiCC__dispatch" }, h("span", null, "调度方式"), h("div", { className: "odaiCC__segments", role: "group", "aria-label": `${ROLE_LABELS[role]}调度方式` }, ["default", "same-turn", "child"].map((value) => h("button", {
|
|
311
|
+
key: value,
|
|
312
|
+
type: "button",
|
|
313
|
+
className: (dispatch ?? "default") === value ? "is-active" : "",
|
|
314
|
+
onClick: () => setDispatch(value),
|
|
315
|
+
disabled: busy,
|
|
316
|
+
}, dispatchLabel(value)))), h("small", null, sourceLabel(dispatchSource, "兼容默认"))), editing ? h("div", { className: "odaiCC__routeForm" }, h("label", null, h("span", null, "Provider"), h("input", { value: provider, onChange: (event) => setProvider(event.target.value), placeholder: "Provider ID", disabled: busy })), h("label", null, h("span", null, "模型"), h("input", { value: model, onChange: (event) => setModel(event.target.value), placeholder: "模型 ID", disabled: busy })), h("label", null, h("span", null, "推理档位"), h("input", { value: effort, onChange: (event) => setEffort(event.target.value), placeholder: "可选", disabled: busy })), h("label", null, h("span", null, "最大 tokens"), h("input", { value: maxTokens, onChange: (event) => setMaxTokens(event.target.value), inputMode: "numeric", placeholder: "可选", disabled: busy })), h("div", { className: "odaiCC__formActions" }, route ? h("button", { type: "button", className: "odaiCC__dangerButton", onClick: remove, disabled: busy }, "移除映射") : null, h("button", { type: "button", className: "odaiCC__primaryButton", onClick: save, disabled: busy || !provider.trim() || !model.trim() || (maxTokens.trim() && !positiveInteger(maxTokens)) }, busy ? "正在验证…" : "验证并保存"))) : null);
|
|
317
|
+
}
|
|
318
|
+
function RoutingView({ connection, config, setConfig, loading, error, reload }) {
|
|
319
|
+
const [busyRole, setBusyRole] = useState(undefined);
|
|
320
|
+
const [notice, setNotice] = useState("");
|
|
321
|
+
const apply = async (payload) => {
|
|
322
|
+
setBusyRole(payload.responsibility);
|
|
323
|
+
setNotice("");
|
|
324
|
+
try {
|
|
325
|
+
const next = await callRouting(connection, payload);
|
|
326
|
+
setConfig(next);
|
|
327
|
+
setNotice(next.requiresNextTurn ? "已保存,将从下一次用户轮次开始生效。" : "配置已加载。");
|
|
328
|
+
return next;
|
|
329
|
+
}
|
|
330
|
+
catch (cause) {
|
|
331
|
+
setNotice(controlCenterError(cause));
|
|
332
|
+
throw cause;
|
|
333
|
+
}
|
|
334
|
+
finally {
|
|
335
|
+
setBusyRole(undefined);
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
return h("div", { className: "odaiCC__routing" }, h("div", { className: "odaiCC__routingHead" }, h("div", null, h("span", { className: "odaiCC__kicker" }, "职责路由"), h("h3", null, "模型与调度")), h("button", { type: "button", className: "odaiCC__quietButton", onClick: reload, disabled: loading }, loading ? "正在加载…" : "刷新")), error ? h("div", { className: "odaiCC__alert is-error", role: "alert" }, error) : null, notice ? h("div", { className: `odaiCC__alert${/已保存|已加载/u.test(notice) ? " is-success" : " is-error"}`, role: "status" }, notice) : null, h("div", { className: "odaiCC__controllerLock" }, h(RoleBadge, { role: "controller" }), h("div", null, h("strong", null, "宿主管理的基础路由"), h("span", null, "总控模型由 DSH 会话配置管理,不属于 Odai 职责映射。")), h("span", { className: "odaiCC__lock" }, "只读")), config ? h("div", { className: "odaiCC__routeList" }, ROLES.map((role) => h(RouteEditor, {
|
|
339
|
+
key: role,
|
|
340
|
+
role,
|
|
341
|
+
config,
|
|
342
|
+
busy: busyRole === role,
|
|
343
|
+
onApply: apply,
|
|
344
|
+
}))) : loading ? h("div", { className: "odaiCC__empty" }, "正在加载路由配置…") : null);
|
|
345
|
+
}
|
|
346
|
+
function ControlCenter({ useSession, connection, onClose }) {
|
|
347
|
+
const sessionId = useSession((snapshot) => snapshot.sessionId);
|
|
348
|
+
const traceView = useSession((snapshot) => snapshot.views.get("odaiControlCenter") ?? { events: [] });
|
|
349
|
+
const [storedEvidence, setStoredEvidence] = useState([]);
|
|
350
|
+
const [evidenceError, setEvidenceError] = useState("");
|
|
351
|
+
const traceEvents = storedEvidence.length ? storedEvidence : traceView.events ?? [];
|
|
352
|
+
const trace = useMemo(() => projectTrace(traceEvents), [traceEvents]);
|
|
353
|
+
const [tab, setTab] = useState("live");
|
|
354
|
+
const [config, setConfig] = useState(undefined);
|
|
355
|
+
const [loading, setLoading] = useState(false);
|
|
356
|
+
const [routingAttempted, setRoutingAttempted] = useState(false);
|
|
357
|
+
const [error, setError] = useState("");
|
|
358
|
+
const closeRef = useRef(null);
|
|
359
|
+
const reload = async () => {
|
|
360
|
+
setRoutingAttempted(true);
|
|
361
|
+
setLoading(true);
|
|
362
|
+
setError("");
|
|
363
|
+
try {
|
|
364
|
+
setConfig(await callRouting(connection, { action: "show" }));
|
|
365
|
+
}
|
|
366
|
+
catch (cause) {
|
|
367
|
+
setError(controlCenterError(cause));
|
|
368
|
+
}
|
|
369
|
+
finally {
|
|
370
|
+
setLoading(false);
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
useEffect(() => {
|
|
374
|
+
const onKey = (event) => {
|
|
375
|
+
if (event.key === "Escape")
|
|
376
|
+
onClose();
|
|
377
|
+
};
|
|
378
|
+
document.addEventListener("keydown", onKey);
|
|
379
|
+
closeRef.current?.focus();
|
|
380
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
381
|
+
}, [onClose]);
|
|
382
|
+
useEffect(() => {
|
|
383
|
+
if (!sessionId || tab !== "live")
|
|
384
|
+
return undefined;
|
|
385
|
+
const controller = new AbortController();
|
|
386
|
+
let active = true;
|
|
387
|
+
const refreshEvidence = async () => {
|
|
388
|
+
try {
|
|
389
|
+
const events = await callEvidence(connection, sessionId, controller.signal);
|
|
390
|
+
if (active) {
|
|
391
|
+
setStoredEvidence(events);
|
|
392
|
+
setEvidenceError("");
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
catch (cause) {
|
|
396
|
+
if (active && !controller.signal.aborted)
|
|
397
|
+
setEvidenceError(controlCenterError(cause));
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
void refreshEvidence();
|
|
401
|
+
const timer = window.setInterval(refreshEvidence, 1_500);
|
|
402
|
+
return () => {
|
|
403
|
+
active = false;
|
|
404
|
+
controller.abort();
|
|
405
|
+
window.clearInterval(timer);
|
|
406
|
+
};
|
|
407
|
+
}, [connection, sessionId, tab]);
|
|
408
|
+
useEffect(() => {
|
|
409
|
+
if (tab === "routing" && !routingAttempted)
|
|
410
|
+
void reload();
|
|
411
|
+
}, [tab, routingAttempted]);
|
|
412
|
+
return ReactDOM.createPortal(h("div", { className: "odaiCC", "data-odai-control-center": PACKAGE_ID }, h("button", { type: "button", className: "odaiCC__backdrop", onClick: onClose, "aria-label": "关闭 Odai 控制中心" }), h("section", { className: "odaiCC__drawer", role: "dialog", "aria-modal": true, "aria-label": "Odai 控制中心" }, h("header", { className: "odaiCC__header" }, h("div", { className: "odaiCC__brand" }, h(GraphIcon), h("div", null, h("h2", null, "Odai 控制中心"), h("span", null, "会话证据 · 路由配置"))), h("nav", { className: "odaiCC__tabs", "aria-label": "控制中心视图" }, h("button", { type: "button", className: tab === "live" ? "is-active" : "", onClick: () => setTab("live") }, "执行"), h("button", { type: "button", className: tab === "routing" ? "is-active" : "", onClick: () => setTab("routing") }, "路由")), h("button", { ref: closeRef, type: "button", className: "odaiCC__close", onClick: onClose, "aria-label": "关闭" }, "×")), h("main", { className: "odaiCC__body" }, tab === "live"
|
|
413
|
+
? h(LiveView, { trace, evidenceError })
|
|
414
|
+
: h(RoutingView, { connection, config, setConfig, loading, error, reload })))), document.body);
|
|
415
|
+
}
|
|
416
|
+
function Launcher(props) {
|
|
417
|
+
const [open, setOpen] = useState(false);
|
|
418
|
+
return h(React.Fragment, null, h("button", {
|
|
419
|
+
type: "button",
|
|
420
|
+
className: "odaiCC__launcher",
|
|
421
|
+
onClick: () => setOpen(true),
|
|
422
|
+
title: "Odai 控制中心",
|
|
423
|
+
"aria-label": "打开 Odai 控制中心",
|
|
424
|
+
"aria-expanded": open,
|
|
425
|
+
}, h(GraphIcon), h("span", null, "控制")), open ? h(ControlCenter, { ...props, onClose: () => setOpen(false) }) : null);
|
|
426
|
+
}
|
|
427
|
+
function injectStyles() {
|
|
428
|
+
const styleId = "odai-control-center/client.css";
|
|
429
|
+
if (document.querySelector(`style[data-plugin-css="${styleId}"]`))
|
|
430
|
+
return;
|
|
431
|
+
const tag = document.createElement("style");
|
|
432
|
+
tag.dataset.plugin = PACKAGE_ID;
|
|
433
|
+
tag.dataset.pluginCss = styleId;
|
|
434
|
+
tag.textContent = `
|
|
435
|
+
.odaiCC__launcher,.odaiCC{--odai-cc-bg:#f5f7fa;--odai-cc-panel:#fff;--odai-cc-surface:#f8fafc;--odai-cc-surface-2:#edf1f5;--odai-cc-text:#151922;--odai-cc-text-2:#4c5665;--odai-cc-text-3:#778190;--odai-cc-border:#d8dee7;--odai-cc-border-strong:#bdc6d2;--odai-cc-hover:#edf3ff;--odai-cc-accent:#2f6feb}
|
|
436
|
+
.odaiCC__launcher{box-sizing:border-box;height:28px;border:1px solid var(--odai-cc-border);border-radius:6px;background:transparent;color:var(--odai-cc-text-2);display:inline-flex;align-items:center;gap:6px;padding:0 8px;cursor:pointer;font:600 11px/1 inherit;letter-spacing:0}.odaiCC__launcher:hover{background:var(--odai-cc-hover);color:var(--odai-cc-text)}
|
|
437
|
+
.odaiCC__graphIcon{position:relative;width:17px;height:15px;display:inline-block;flex:none}.odaiCC__graphNode{position:absolute;width:5px;height:5px;border:1.5px solid currentColor;border-radius:50%;box-sizing:border-box}.odaiCC__graphNode--a{left:0;top:5px}.odaiCC__graphNode--b{right:0;top:0}.odaiCC__graphNode--c{right:0;bottom:0}.odaiCC__graphLine{position:absolute;left:5px;top:7px;width:7px;height:1px;background:currentColor}.odaiCC__graphLine:before,.odaiCC__graphLine:after{content:"";position:absolute;right:0;width:5px;height:1px;background:currentColor;transform-origin:right center}.odaiCC__graphLine:before{transform:rotate(-38deg)}.odaiCC__graphLine:after{transform:rotate(38deg)}
|
|
438
|
+
.odaiCC{position:fixed;inset:0;z-index:120;color:var(--odai-cc-text);font-family:inherit;letter-spacing:0}.odaiCC *{box-sizing:border-box}.odaiCC__backdrop{position:absolute;inset:0;border:0;background:rgba(0,0,0,.28)}.odaiCC__drawer{position:absolute;inset:14px;max-width:1180px;margin-left:auto;border:1px solid var(--odai-cc-border-strong);border-radius:8px;background:var(--odai-cc-panel);box-shadow:0 24px 80px rgba(0,0,0,.42);display:flex;flex-direction:column;overflow:hidden}.odaiCC__header{min-height:62px;border-bottom:1px solid var(--odai-cc-border);display:grid;grid-template-columns:minmax(230px,1fr) auto minmax(44px,1fr);align-items:center;gap:16px;padding:10px 14px}.odaiCC__brand{display:flex;align-items:center;gap:10px;min-width:0}.odaiCC__brand>.odaiCC__graphIcon{width:22px;height:19px;color:var(--odai-cc-accent)}.odaiCC__brand h2{font-size:15px;line-height:20px;margin:0}.odaiCC__brand span{display:block;color:var(--odai-cc-text-3);font-size:10px;line-height:15px}.odaiCC__tabs{display:flex;border:1px solid var(--odai-cc-border);border-radius:6px;padding:2px;background:var(--odai-cc-surface)}.odaiCC__tabs button{height:28px;min-width:72px;border:0;border-radius:4px;background:transparent;color:var(--odai-cc-text-3);cursor:pointer;font:600 11px/1 inherit}.odaiCC__tabs button.is-active{background:var(--odai-cc-panel);color:var(--odai-cc-text);box-shadow:0 1px 4px rgba(0,0,0,.2)}.odaiCC__close{justify-self:end;width:32px;height:32px;border:0;border-radius:6px;background:transparent;color:var(--odai-cc-text-2);font-size:22px;line-height:1;cursor:pointer}.odaiCC__close:hover{background:var(--odai-cc-hover)}.odaiCC__body{min-height:0;flex:1;overflow:auto}.odaiCC__live{padding:14px}.odaiCC__kicker{display:block;color:var(--odai-cc-text-3);font:700 9px/14px inherit;letter-spacing:.08em}.odaiCC__sectionHead{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-bottom:10px}.odaiCC__sectionHead h3{font-size:12px;line-height:18px;margin:0}.odaiCC__eventCount{color:var(--odai-cc-text-3);font-size:10px}.odaiCC__flow{border:1px solid var(--odai-cc-border);border-radius:8px;background:var(--odai-cc-surface);padding:12px}.odaiCC__flowGrid{display:grid;grid-template-columns:minmax(160px,220px) minmax(360px,1fr);gap:22px;align-items:center;min-height:265px}.odaiCC__controllerNode{border:1px solid rgba(119,167,255,.45);border-radius:8px;background:rgba(119,167,255,.08);padding:18px}.odaiCC__controllerNode strong,.odaiCC__roleNode strong{display:block;font-size:12px;line-height:18px}.odaiCC__controllerNode small,.odaiCC__roleNode small{display:block;color:var(--odai-cc-text-3);font-size:10px;line-height:15px;margin-top:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.odaiCC__nodeEyebrow{color:#2f6feb;font-size:9px;font-weight:700}.odaiCC__roleStack{display:flex;flex-direction:column;gap:8px}.odaiCC__flowRole{display:grid;grid-template-columns:92px minmax(0,1fr);align-items:center;border:0;background:transparent;color:inherit;padding:0;text-align:left}.odaiCC__flowRole:not(:disabled){cursor:pointer}.odaiCC__flowRole:disabled{opacity:1}.odaiCC__edge{position:relative;height:1px;background:var(--odai-cc-border-strong);margin-right:12px}.odaiCC__edge i{position:absolute;right:-1px;top:-3px;width:7px;height:7px;border-top:1px solid currentColor;border-right:1px solid currentColor;transform:rotate(45deg)}.odaiCC__edge em{position:absolute;left:5px;top:-17px;color:var(--odai-cc-text-3);font:500 9px/12px inherit;font-style:normal}.odaiCC__edge[data-state=proposal],.odaiCC__edge[data-state=decision]{background:repeating-linear-gradient(90deg,#65c6b8 0 5px,transparent 5px 9px);color:#65c6b8}.odaiCC__edge[data-state=same-turn]{background:#b99cff;color:#b99cff}.odaiCC__edge[data-state=child]{background:var(--odai-cc-accent);color:var(--odai-cc-accent)}.odaiCC__edge[data-state=handback]{background:#f1bb61;color:#f1bb61}.odaiCC__edge[data-state=blocked],.odaiCC__edge[data-state=interrupted]{background:#ef7e78;color:#ef7e78}.odaiCC__roleNode{border:1px solid var(--odai-cc-border);border-radius:7px;background:var(--odai-cc-panel);padding:8px 10px;min-width:0}.odaiCC__flowRole:not(:disabled):hover .odaiCC__roleNode{border-color:var(--odai-cc-accent)}.odaiCC__role{display:inline-flex;align-items:center;gap:5px;color:var(--odai-cc-text-2);font-size:9px;line-height:14px}.odaiCC__role i{width:6px;height:6px;border-radius:50%;background:#8f969e}.odaiCC__role--controller i{background:var(--odai-cc-accent)}.odaiCC__role--researcher i{background:#65c6b8}.odaiCC__role--planner i{background:#b99cff}.odaiCC__role--reviewer i{background:#f1bb61}.odaiCC__role--frontend i{background:#ef8fbd}.odaiCC__evidenceGrid{display:grid;grid-template-columns:minmax(0,1.2fr) minmax(300px,.8fr);gap:12px;margin-top:12px}.odaiCC__timeline,.odaiCC__inspector{min-width:0;border:1px solid var(--odai-cc-border);border-radius:8px;background:var(--odai-cc-surface);padding:10px}.odaiCC__turn{border-top:1px solid var(--odai-cc-border);padding:8px 0}.odaiCC__turn:first-child{border-top:0;padding-top:0}.odaiCC__turn summary{display:flex;justify-content:space-between;gap:10px;cursor:pointer;list-style:none;font-size:10px}.odaiCC__turn summary::-webkit-details-marker{display:none}.odaiCC__turn summary span{color:var(--odai-cc-text-3)}.odaiCC__events{display:flex;flex-direction:column;gap:6px;margin-top:8px}.odaiCC__event{width:100%;border:1px solid var(--odai-cc-border);border-radius:7px;background:var(--odai-cc-panel);color:inherit;padding:8px;text-align:left;cursor:pointer}.odaiCC__event:hover,.odaiCC__event.is-selected{border-color:var(--odai-cc-accent);background:var(--odai-cc-hover)}.odaiCC__eventTop{display:flex;align-items:center;gap:6px;color:var(--odai-cc-text-3);font-size:9px}.odaiCC__eventTop time{margin-left:auto}.odaiCC__eventTop code{font-size:9px}.odaiCC__event>strong{display:block;margin-top:5px;font-size:11px;line-height:16px}.odaiCC__eventDetail{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;color:var(--odai-cc-text-2);font-size:10px;line-height:15px;margin-top:2px}.odaiCC__eventBottom{display:flex;gap:8px;align-items:center;color:var(--odai-cc-text-3);font-size:9px;margin-top:6px}.odaiCC__state{display:inline-flex;border-radius:999px;background:var(--odai-cc-surface-2);color:var(--odai-cc-text-2);padding:1px 6px;font-size:9px;line-height:13px}.odaiCC__state--proposal{background:rgba(101,198,184,.15);color:#18756b}.odaiCC__state--same-turn{background:rgba(185,156,255,.15);color:#6f42c1}.odaiCC__state--child{background:rgba(119,167,255,.15);color:#245ec7}.odaiCC__state--handback{background:rgba(241,187,97,.15);color:#946000}.odaiCC__state--blocked,.odaiCC__state--interrupted{background:rgba(239,126,120,.15);color:#b42318}.odaiCC__facts{margin:0;display:flex;flex-direction:column;gap:7px}.odaiCC__facts>div{display:grid;grid-template-columns:56px minmax(0,1fr);gap:8px;font-size:10px}.odaiCC__facts dt{color:var(--odai-cc-text-3)}.odaiCC__facts dd{margin:0;overflow-wrap:anywhere}.odaiCC__facts code{font-size:9px}.odaiCC__raw{max-height:360px;overflow:auto;border:1px solid var(--odai-cc-border);border-radius:7px;background:var(--odai-cc-panel);color:var(--odai-cc-text-2);font:9px/14px ui-monospace,SFMono-Regular,Menlo,monospace;padding:8px;margin:10px 0 0}.odaiCC__empty{border:1px dashed var(--odai-cc-border-strong);border-radius:7px;color:var(--odai-cc-text-3);font-size:10px;line-height:16px;padding:18px;text-align:center}
|
|
439
|
+
.odaiCC__routing{max-width:920px;margin:0 auto;padding:18px}.odaiCC__routingHead{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}.odaiCC__routingHead h3{font-size:14px;margin:0}.odaiCC__quietButton,.odaiCC__primaryButton,.odaiCC__dangerButton{height:28px;border-radius:6px;padding:0 10px;font:600 10px/1 inherit;cursor:pointer}.odaiCC__quietButton{border:1px solid var(--odai-cc-border);background:transparent;color:var(--odai-cc-text-2)}.odaiCC__primaryButton{border:1px solid var(--odai-cc-accent);background:var(--odai-cc-accent);color:#fff}.odaiCC__dangerButton{border:1px solid rgba(239,126,120,.45);background:transparent;color:#b42318}.odaiCC button:disabled:not(.odaiCC__flowRole){opacity:.45;cursor:default}.odaiCC__flowRole:disabled{opacity:1;cursor:default}.odaiCC__alert{border:1px solid var(--odai-cc-border);border-radius:7px;padding:8px 10px;font-size:10px;line-height:15px;margin-bottom:10px}.odaiCC__alert.is-error{border-color:rgba(239,126,120,.4);background:rgba(239,126,120,.08);color:#b42318}.odaiCC__alert.is-success{border-color:rgba(101,198,184,.4);background:rgba(101,198,184,.08);color:#18756b}.odaiCC__controllerLock,.odaiCC__routeRow{border-top:1px solid var(--odai-cc-border);padding:12px 0}.odaiCC__controllerLock{display:grid;grid-template-columns:120px minmax(0,1fr) auto;gap:12px;align-items:center}.odaiCC__controllerLock strong{display:block;font-size:11px}.odaiCC__controllerLock span:not(.odaiCC__role):not(.odaiCC__lock){display:block;color:var(--odai-cc-text-3);font-size:10px}.odaiCC__lock{border:1px solid var(--odai-cc-border);border-radius:999px;color:var(--odai-cc-text-3);font-size:9px;padding:3px 7px}.odaiCC__routeSummary{display:grid;grid-template-columns:120px minmax(0,1fr) auto;gap:12px;align-items:center}.odaiCC__routeValue strong{display:block;font-size:11px;overflow-wrap:anywhere}.odaiCC__routeValue span{display:block;color:var(--odai-cc-text-3);font-size:9px;margin-top:2px}.odaiCC__dispatch{display:grid;grid-template-columns:120px auto 1fr;gap:12px;align-items:center;margin-top:9px}.odaiCC__dispatch>span{color:var(--odai-cc-text-3);font-size:9px}.odaiCC__dispatch small{color:var(--odai-cc-text-3);font-size:9px}.odaiCC__segments{display:flex;border:1px solid var(--odai-cc-border);border-radius:6px;overflow:hidden}.odaiCC__segments button{height:25px;border:0;border-right:1px solid var(--odai-cc-border);background:transparent;color:var(--odai-cc-text-3);padding:0 9px;font:500 9px/1 inherit;cursor:pointer}.odaiCC__segments button:last-child{border-right:0}.odaiCC__segments button.is-active{background:var(--odai-cc-hover);color:var(--odai-cc-text)}.odaiCC__routeForm{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin:10px 0 0 132px;padding:10px;border:1px solid var(--odai-cc-border);border-radius:7px;background:var(--odai-cc-surface)}.odaiCC__routeForm label span{display:block;color:var(--odai-cc-text-3);font-size:9px;margin-bottom:4px}.odaiCC__routeForm input{width:100%;height:30px;border:1px solid var(--odai-cc-border);border-radius:5px;background:var(--odai-cc-panel);color:var(--odai-cc-text);padding:0 7px;font:10px/1 inherit;outline:none}.odaiCC__routeForm input:focus{border-color:var(--odai-cc-accent)}.odaiCC__formActions{grid-column:1/-1;display:flex;justify-content:flex-end;gap:8px;margin-top:2px}
|
|
440
|
+
@media(max-width:760px){.odaiCC__launcher span:last-child{display:none}.odaiCC__drawer{inset:6px}.odaiCC__header{grid-template-columns:minmax(0,1fr) auto auto;gap:7px;padding:8px}.odaiCC__brand span{display:none}.odaiCC__tabs button{min-width:56px}.odaiCC__live{padding:8px}.odaiCC__flowGrid{grid-template-columns:1fr;gap:10px;min-height:0}.odaiCC__controllerNode{padding:10px}.odaiCC__flowRole{grid-template-columns:65px minmax(0,1fr)}.odaiCC__evidenceGrid{grid-template-columns:1fr}.odaiCC__inspector{max-height:none}.odaiCC__routing{padding:10px}.odaiCC__controllerLock,.odaiCC__routeSummary{grid-template-columns:92px minmax(0,1fr) auto}.odaiCC__dispatch{grid-template-columns:92px 1fr}.odaiCC__dispatch small{grid-column:2}.odaiCC__routeForm{grid-template-columns:1fr 1fr;margin-left:0}.odaiCC__raw{max-height:240px}}
|
|
441
|
+
@media(prefers-color-scheme:dark){.odaiCC__launcher,.odaiCC{--odai-cc-bg:#14171b;--odai-cc-panel:#191d22;--odai-cc-surface:#1f242a;--odai-cc-surface-2:#282e35;--odai-cc-text:#f2f4f7;--odai-cc-text-2:#c5cbd3;--odai-cc-text-3:#929ba7;--odai-cc-border:#333a43;--odai-cc-border-strong:#46505c;--odai-cc-hover:#26354b;--odai-cc-accent:#77a7ff}.odaiCC__nodeEyebrow{color:#91b7ff}.odaiCC__state--proposal,.odaiCC__alert.is-success{color:#90d9cf}.odaiCC__state--same-turn{color:#cbb8ff}.odaiCC__state--child{color:#a6c3ff}.odaiCC__state--handback{color:#f4ca84}.odaiCC__state--blocked,.odaiCC__state--interrupted,.odaiCC__dangerButton,.odaiCC__alert.is-error{color:#f3a39e}}
|
|
442
|
+
@media(prefers-reduced-motion:no-preference){.odaiCC__drawer{animation:odaiCCIn .15s ease-out}}@keyframes odaiCCIn{from{opacity:.72;transform:translateX(8px)}to{opacity:1;transform:none}}
|
|
443
|
+
`;
|
|
444
|
+
document.head.appendChild(tag);
|
|
445
|
+
}
|
|
446
|
+
function shouldOwnSurface() {
|
|
447
|
+
const entries = globalThis.__DSH_BOOT__?.entries;
|
|
448
|
+
const ids = Array.isArray(entries) ? entries.map((entry) => entry?.id) : [];
|
|
449
|
+
const preferred = ids.includes("odai-dsh-plugin") ? "odai-dsh-plugin" : "odai-dsh-agent";
|
|
450
|
+
return PACKAGE_ID === preferred;
|
|
451
|
+
}
|
|
452
|
+
function apply(ctx) {
|
|
453
|
+
if (!shouldOwnSurface())
|
|
454
|
+
return;
|
|
455
|
+
injectStyles();
|
|
456
|
+
ctx.conversationViews.register({
|
|
457
|
+
target: "odaiControlCenter",
|
|
458
|
+
create() {
|
|
459
|
+
let events = [];
|
|
460
|
+
return {
|
|
461
|
+
empty: Object.freeze({ events: Object.freeze([]) }),
|
|
462
|
+
replace({ nodes }) {
|
|
463
|
+
events = nodes.map((node) => node.data).filter((value) => record(value) !== undefined).sort((left, right) => Number(left.seq) - Number(right.seq));
|
|
464
|
+
return Object.freeze({ events: Object.freeze(events) });
|
|
465
|
+
},
|
|
466
|
+
apply({ upserts }) {
|
|
467
|
+
const byKey = new Map(events.map((event) => [`${event.seq}:${event.type}`, event]));
|
|
468
|
+
for (const node of upserts) {
|
|
469
|
+
const data = record(node.data);
|
|
470
|
+
if (data)
|
|
471
|
+
byKey.set(`${data.seq}:${data.type}`, data);
|
|
472
|
+
}
|
|
473
|
+
events = [...byKey.values()].sort((left, right) => Number(left.seq) - Number(right.seq));
|
|
474
|
+
return Object.freeze({ events: Object.freeze(events) });
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
ctx.conversationEvents.register({
|
|
480
|
+
kind: "odai-control-center:event",
|
|
481
|
+
target: "odaiControlCenter",
|
|
482
|
+
match(event) {
|
|
483
|
+
return text(event?.type).startsWith("odai/") ? { id: `${event.seq}:${event.type}`, role: "start" } : null;
|
|
484
|
+
},
|
|
485
|
+
start(_context, match) {
|
|
486
|
+
return { event: match.event };
|
|
487
|
+
},
|
|
488
|
+
update(context) {
|
|
489
|
+
return context.state;
|
|
490
|
+
},
|
|
491
|
+
buildViewNode(context) {
|
|
492
|
+
const event = context.state?.event;
|
|
493
|
+
if (!event)
|
|
494
|
+
return null;
|
|
495
|
+
return {
|
|
496
|
+
key: `${event.seq}:${event.type}`,
|
|
497
|
+
kind: "odai-control-center:event",
|
|
498
|
+
id: `${event.seq}:${event.type}`,
|
|
499
|
+
target: "odaiControlCenter",
|
|
500
|
+
data: { seq: event.seq, time: event.time, type: event.type, data: record(event.data) ?? {} },
|
|
501
|
+
};
|
|
502
|
+
},
|
|
503
|
+
});
|
|
504
|
+
const Entry = (props) => h(Launcher, { ...props, connection: ctx.connection });
|
|
505
|
+
ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
|
|
506
|
+
name: "conversation.session.header.utilities",
|
|
507
|
+
id: "odai-control-center",
|
|
508
|
+
order: 60,
|
|
509
|
+
label: "Odai Control Center",
|
|
510
|
+
}, Entry));
|
|
511
|
+
}
|
|
512
|
+
exports.apply = apply;
|
|
513
|
+
exports.inject = ["slots", "sessions", "conversationEvents", "conversationViews", "connection"];
|
|
514
|
+
exports.__testing = { projectTrace, stateOf, roleOf, shouldOwnSurface };
|
|
515
|
+
return module.exports;
|
|
516
|
+
},
|
|
517
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "odai-dsh-agent",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.16",
|
|
4
4
|
"description": "完整继承 DSH 标准模式 全部能力的 Odai Agent preset。主要用于治理、规划、测试、验证 等,可当成默认总控使用。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
"main": "./build/src/installer.mjs",
|
|
17
17
|
"exports": {
|
|
18
18
|
".": "./build/src/installer.mjs",
|
|
19
|
+
"./control-center": "./build/src/control-center-installer.mjs",
|
|
20
|
+
"./control-center-host": "./preset/odai/runtime/control-center-host.mjs",
|
|
21
|
+
"./client": "./client/client.js",
|
|
19
22
|
"./package.json": "./package.json"
|
|
20
23
|
},
|
|
21
24
|
"bin": {
|
|
@@ -28,21 +31,37 @@
|
|
|
28
31
|
"files": [
|
|
29
32
|
"build/bin",
|
|
30
33
|
"build/src",
|
|
34
|
+
"client",
|
|
31
35
|
"preset",
|
|
36
|
+
"control-center.cordis.patch.yml",
|
|
32
37
|
"README.md",
|
|
33
38
|
"LICENSE"
|
|
34
39
|
],
|
|
40
|
+
"dsh": {
|
|
41
|
+
"bundle": {
|
|
42
|
+
"patch": "./control-center.cordis.patch.yml"
|
|
43
|
+
},
|
|
44
|
+
"client": {
|
|
45
|
+
"inject": [
|
|
46
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
47
|
+
"@deepseek-ai/dsh-client-connection",
|
|
48
|
+
"@deepseek-ai/dsh-client-ui-layout",
|
|
49
|
+
"@deepseek-ai/dsh-client-ui-conversation"
|
|
50
|
+
],
|
|
51
|
+
"platform": "web"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
35
54
|
"scripts": {
|
|
36
55
|
"build:dsh": "npm --prefix ../.. run build:dsh",
|
|
37
56
|
"typecheck:dsh": "npm --prefix ../.. run typecheck:dsh",
|
|
38
57
|
"check": "npm run typecheck:dsh && npm run build:dsh && find ../runtime/build build/bin build/src -name '*.mjs' -print0 | xargs -0 -n1 node --check",
|
|
39
|
-
"prepare:artifact": "npm run build:dsh && node ../../scripts/package-odai-artifact.mjs prepare --skill-root preset/odai/skills --runtime-root preset/odai/runtime",
|
|
40
|
-
"clean:artifact": "node ../../scripts/package-odai-artifact.mjs clean --skill-root preset/odai/skills --runtime-root preset/odai/runtime",
|
|
58
|
+
"prepare:artifact": "npm run build:dsh && node ../../scripts/package-odai-artifact.mjs prepare --skill-root preset/odai/skills --runtime-root preset/odai/runtime --client-root client --client-package odai-dsh-agent",
|
|
59
|
+
"clean:artifact": "node ../../scripts/package-odai-artifact.mjs clean --skill-root preset/odai/skills --runtime-root preset/odai/runtime --client-root client --client-package odai-dsh-agent",
|
|
41
60
|
"prepare": "npm run prepare:artifact",
|
|
42
61
|
"verify:version": "node ../../scripts/verify-dsh-package-versions.mjs",
|
|
43
62
|
"prepack": "npm run verify:version && node ../../scripts/validate-odai-skill.mjs",
|
|
44
63
|
"postpack": "npm run clean:artifact",
|
|
45
|
-
"pack:dry-run": "node ../../scripts/run-package-pack.mjs --clean preset/odai/runtime --clean preset/odai/skills -- --dry-run",
|
|
64
|
+
"pack:dry-run": "node ../../scripts/run-package-pack.mjs --clean preset/odai/runtime --clean preset/odai/skills --clean client -- --dry-run",
|
|
46
65
|
"test": "npm run verify:version && npm run build:dsh && npm --prefix ../.. exec -- tsx --test ./tests/*.test.mts",
|
|
47
66
|
"verify:dsh": "npm run build:dsh && npm --prefix ../.. exec -- tsx ./tests/verify-agent-load.mts"
|
|
48
67
|
},
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DshRuntimeContext, UnknownRecord } from "./runtime-types.mjs";
|
|
2
|
+
export declare const name = "odai-control-center-host";
|
|
3
|
+
export declare const inject: string[];
|
|
4
|
+
export declare function apply(ctx: DshRuntimeContext, rawConfig?: unknown): void;
|
|
5
|
+
export type ControlCenterHostConfig = UnknownRecord & {
|
|
6
|
+
routing?: {
|
|
7
|
+
configPath?: string;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=control-center-host.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"control-center-host.d.mts","sourceRoot":"","sources":["../src/control-center-host.mts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAG5E,eAAO,MAAM,IAAI,6BAA6B,CAAC;AAC/C,eAAO,MAAM,MAAM,UAAwB,CAAC;AAE5C,wBAAgB,KAAK,CAAC,GAAG,EAAE,iBAAiB,EAAE,SAAS,GAAE,OAAY,GAAG,IAAI,CAO3E;AAED,MAAM,MAAM,uBAAuB,GAAG,aAAa,GAAG;IACpD,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACnC,CAAC"}
|