dsh-monkeyai-sessions 0.1.3 → 0.1.5
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 +2 -2
- package/README.zh.md +2 -2
- package/client.js +42 -1
- package/lib/client-runtime.cjs +42 -1
- package/package.json +1 -1
- package/src/client/index.js +28 -3
- package/src/host/monkeyai-remote.js +46 -0
- package/src/shared/projection.mjs +13 -1
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ All three request `device:connect`, `session:read`, and `session:control` throug
|
|
|
39
39
|
|
|
40
40
|
## Sessions
|
|
41
41
|
|
|
42
|
-
Full conversation history is loaded over the runtime's HTTP read channel (`POST {http_base}/read/session` with `_monkeyai/session/messages`, paginated by cursor) instead of the bounded ACP `session/load` replay, so long sessions show every turn including the latest replies. After mounting the session with the same `historyHTTP` capabilities as the MonkeyAI client, the panel subscribes to ACP live updates: messages and assistant chunks started in the MonkeyAI desktop client appear as they stream, without refreshing. For large replies persisted by the runtime, `history_changed` triggers a cursor-based HTTP delta refresh and replaces any partial snapshot. While a turn is running the composer shows a stop button that sends `session/cancel`; the model switcher uses `_monkeyai/session/model/set` on the live session connection. Assistant messages render lightweight markdown (code blocks, inline code, bold, headings, lists).
|
|
42
|
+
Full conversation history is loaded over the runtime's HTTP read channel (`POST {http_base}/read/session` with `_monkeyai/session/messages`, paginated by cursor) instead of the bounded ACP `session/load` replay, so long sessions show every turn including the latest replies. After mounting the session with the same `historyHTTP` capabilities as the MonkeyAI client, the panel subscribes to ACP live updates: messages and assistant chunks started in the MonkeyAI desktop client appear as they stream, without refreshing. For large replies persisted by the runtime, `history_changed` triggers a cursor-based HTTP delta refresh and replaces any partial snapshot. A status badge next to the session title shows the turn state (running / idle / error / interrupted), driven by the runtime's `_monkeyai/session/state` notifications, so turns started from the MonkeyAI desktop client also show as running; when the panel opens while a turn is already in flight, the host fetches the current state to seed the badge. While a turn is running (from either side) the composer shows a stop button that sends `session/cancel`; the model switcher uses `_monkeyai/session/model/set` on the live session connection. Assistant messages render lightweight markdown (code blocks, inline code, bold, headings, lists).
|
|
43
43
|
|
|
44
44
|
## Installation
|
|
45
45
|
|
|
@@ -61,7 +61,7 @@ From a checkout:
|
|
|
61
61
|
npm install
|
|
62
62
|
npm run build
|
|
63
63
|
npm pack
|
|
64
|
-
dsh plugin --profile web add ./dsh-monkeyai-sessions-0.1.
|
|
64
|
+
dsh plugin --profile web add ./dsh-monkeyai-sessions-0.1.5.tgz
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
Verified against a real DSH `0.2.2-rc.1` web profile and a live MonkeyAI runtime: authorization, full history loading, prompt delivery, model switching, and turn cancellation all work end to end.
|
package/README.zh.md
CHANGED
|
@@ -28,7 +28,7 @@ https://artenx.github.io/dsh-monkeyai-sessions/index.json
|
|
|
28
28
|
npm install
|
|
29
29
|
npm run build
|
|
30
30
|
npm pack
|
|
31
|
-
dsh plugin --profile web add ./dsh-monkeyai-sessions-0.1.
|
|
31
|
+
dsh plugin --profile web add ./dsh-monkeyai-sessions-0.1.5.tgz
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
已在真实的 DSH `0.2.2-rc.1` web profile 与在线 MonkeyAI runtime 上端到端验证:授权、完整历史加载、指令下发、模型切换、回合取消均正常工作。
|
|
@@ -47,7 +47,7 @@ dsh plugin --profile web add ./dsh-monkeyai-sessions-0.1.3.tgz
|
|
|
47
47
|
|
|
48
48
|
完整对话历史通过 runtime 的 HTTP 读取通道加载(`POST {http_base}/read/session`,方法 `_monkeyai/session/messages`,按游标翻页),而不是 ACP `session/load` 的有界回放,因此长会话能看到每一轮内容,包括最新回复。会话使用与 MonkeyAI 客户端相同的 `historyHTTP` 能力挂载后,面板会订阅 ACP 实时更新:在 MonkeyAI 桌面客户端发起的用户消息和助手回复都会边生成边显示,无需刷新。对于 runtime 先持久化再通知的超大回复,收到 `history_changed` 后会按游标增量读取,并用完整快照补齐部分内容。
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
会话标题旁会显示会话状态徽章(进行中 / 空闲 / 出错 / 已中断),数据来自 runtime 广播的 `_monkeyai/session/state` 通知,因此由 MonkeyAI 桌面客户端发起的回合同样会显示为「进行中」——打开面板时如果回合已经在跑,Host 会主动读取一次当前状态补齐。回合执行期间(无论哪一端发起)输入框旁会显示停止按钮,点击发送 `session/cancel`;模型切换在会话自己的连接上调用 `_monkeyai/session/model/set`。助手消息支持轻量 Markdown 渲染(代码块、行内代码、粗体、标题、列表)。
|
|
51
51
|
|
|
52
52
|
## 界面说明
|
|
53
53
|
|
package/client.js
CHANGED
|
@@ -331,6 +331,12 @@ window.__ModuleLoader__.load({
|
|
|
331
331
|
failed: "\u5931\u8D25",
|
|
332
332
|
cancelled: "\u5DF2\u53D6\u6D88"
|
|
333
333
|
});
|
|
334
|
+
var SESSION_STATUS_LABEL = Object.freeze({
|
|
335
|
+
running: "\u8FDB\u884C\u4E2D",
|
|
336
|
+
idle: "\u7A7A\u95F2",
|
|
337
|
+
error: "\u51FA\u9519",
|
|
338
|
+
interrupted: "\u5DF2\u4E2D\u65AD"
|
|
339
|
+
});
|
|
334
340
|
function contentText(content) {
|
|
335
341
|
const blocks = Array.isArray(content) ? content : content === void 0 || content === null ? [] : [content];
|
|
336
342
|
const parts = [];
|
|
@@ -495,6 +501,12 @@ window.__ModuleLoader__.load({
|
|
|
495
501
|
continue;
|
|
496
502
|
}
|
|
497
503
|
if (frame.type === "notification" && typeof frame.method === "string" && frame.method.startsWith("_monkeyai/")) {
|
|
504
|
+
if (frame.method === "_monkeyai/session/state") {
|
|
505
|
+
const status = frame.data?.state?.status;
|
|
506
|
+
if (typeof status === "string" && SESSION_STATUS_LABEL[status]) {
|
|
507
|
+
entries.push({ kind: "system", label: `\u4F1A\u8BDD\u72B6\u6001\uFF1A${SESSION_STATUS_LABEL[status]}`, key: `sys-state:${entries.length}` });
|
|
508
|
+
}
|
|
509
|
+
}
|
|
498
510
|
continue;
|
|
499
511
|
}
|
|
500
512
|
if (frame.type === "error") {
|
|
@@ -808,6 +820,7 @@ window.__ModuleLoader__.load({
|
|
|
808
820
|
const [visibleCount, setVisibleCount] = useState(RECENT_LIMIT);
|
|
809
821
|
const [turnActive, setTurnActive] = useState(false);
|
|
810
822
|
const [stopping, setStopping] = useState(false);
|
|
823
|
+
const [sessionStatus, setSessionStatus] = useState("");
|
|
811
824
|
const [signInEmail, setSignInEmail] = useState("");
|
|
812
825
|
const [signInPassword, setSignInPassword] = useState("");
|
|
813
826
|
const streamRef = useRef(null);
|
|
@@ -1048,6 +1061,7 @@ window.__ModuleLoader__.load({
|
|
|
1048
1061
|
setLastDelivery(void 0);
|
|
1049
1062
|
setTurnActive(false);
|
|
1050
1063
|
setStopping(false);
|
|
1064
|
+
setSessionStatus("");
|
|
1051
1065
|
setError("");
|
|
1052
1066
|
setAnswerText("");
|
|
1053
1067
|
setCurrentModel("");
|
|
@@ -1066,6 +1080,7 @@ window.__ModuleLoader__.load({
|
|
|
1066
1080
|
historyLoadedRef.current = true;
|
|
1067
1081
|
setStatus("\u5386\u53F2\u5DF2\u52A0\u8F7D");
|
|
1068
1082
|
setHistoryLoading(false);
|
|
1083
|
+
setSessionStatus((current) => current || "idle");
|
|
1069
1084
|
if (frame.model) setCurrentModel(frame.model);
|
|
1070
1085
|
}
|
|
1071
1086
|
if (frame.type === "history_incomplete") {
|
|
@@ -1090,6 +1105,14 @@ window.__ModuleLoader__.load({
|
|
|
1090
1105
|
if (frame.type === "request") {
|
|
1091
1106
|
setPendingRequests((current) => [...current.filter((item) => item.requestId !== frame.requestId), frame]);
|
|
1092
1107
|
}
|
|
1108
|
+
if (frame.type === "notification" && frame.method === "_monkeyai/session/state") {
|
|
1109
|
+
const status2 = frame.data?.state?.status;
|
|
1110
|
+
if (typeof status2 === "string" && SESSION_STATUS_LABEL[status2]) {
|
|
1111
|
+
setSessionStatus(status2);
|
|
1112
|
+
setTurnActive(status2 === "running");
|
|
1113
|
+
if (status2 !== "running") setStopping(false);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1093
1116
|
if (frame.type === "delivery") {
|
|
1094
1117
|
const accepted = Boolean(frame.data?.accepted);
|
|
1095
1118
|
setLastDelivery({ accepted, code: frame.data?.code, frame: frame.type });
|
|
@@ -1120,12 +1143,14 @@ window.__ModuleLoader__.load({
|
|
|
1120
1143
|
setHistoryLoading(false);
|
|
1121
1144
|
setTurnActive(false);
|
|
1122
1145
|
setStopping(false);
|
|
1146
|
+
setSessionStatus("");
|
|
1123
1147
|
} catch (reason) {
|
|
1124
1148
|
setStatus("\u8FDC\u7A0B\u4F1A\u8BDD\u4E0D\u53EF\u7528");
|
|
1125
1149
|
setError(errorText(reason));
|
|
1126
1150
|
setHistoryLoading(false);
|
|
1127
1151
|
setTurnActive(false);
|
|
1128
1152
|
setStopping(false);
|
|
1153
|
+
setSessionStatus("");
|
|
1129
1154
|
}
|
|
1130
1155
|
}
|
|
1131
1156
|
function sendPrompt() {
|
|
@@ -1636,10 +1661,26 @@ window.__ModuleLoader__.load({
|
|
|
1636
1661
|
setSessionId("");
|
|
1637
1662
|
setTurnActive(false);
|
|
1638
1663
|
setStopping(false);
|
|
1664
|
+
setSessionStatus("");
|
|
1639
1665
|
setStatus("\u8FDC\u7A0B\u4F1A\u8BDD\u5DF2\u5173\u95ED");
|
|
1640
1666
|
}
|
|
1641
1667
|
}, "\u2190"),
|
|
1642
1668
|
h("strong", { style: { flex: 1, fontSize: 13.5, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, title }, title),
|
|
1669
|
+
sessionStatus ? h(
|
|
1670
|
+
"span",
|
|
1671
|
+
{
|
|
1672
|
+
style: {
|
|
1673
|
+
...style.statusBadge,
|
|
1674
|
+
background: sessionStatus === "running" ? t.accentSoft : sessionStatus === "error" ? t.dangerBg : t.surfaceAlt,
|
|
1675
|
+
borderColor: sessionStatus === "running" ? t.accent : sessionStatus === "error" ? t.dangerBorder : t.border,
|
|
1676
|
+
color: sessionStatus === "running" ? t.accent : sessionStatus === "error" ? t.danger : t.text2
|
|
1677
|
+
},
|
|
1678
|
+
title: "\u4F1A\u8BDD\u72B6\u6001\uFF08\u542B\u8FDC\u7AEF\u5BA2\u6237\u7AEF\u53D1\u8D77\u7684\u56DE\u5408\uFF09",
|
|
1679
|
+
"aria-label": `\u4F1A\u8BDD\u72B6\u6001\uFF1A${SESSION_STATUS_LABEL[sessionStatus]}`
|
|
1680
|
+
},
|
|
1681
|
+
sessionStatus === "running" ? h("span", { className: "mai-pulse", style: { ...style.dot, background: t.accent } }) : null,
|
|
1682
|
+
SESSION_STATUS_LABEL[sessionStatus]
|
|
1683
|
+
) : null,
|
|
1643
1684
|
h(
|
|
1644
1685
|
"select",
|
|
1645
1686
|
{
|
|
@@ -1823,7 +1864,7 @@ window.__ModuleLoader__.load({
|
|
|
1823
1864
|
"div",
|
|
1824
1865
|
{ style: { fontSize: 11.5, color: turnActive ? t.accent : lastDelivery?.accepted ? t.success : lastDelivery?.frame === "pending" ? t.text3 : t.danger, textAlign: "right", display: "flex", alignItems: "center", justifyContent: "flex-end", gap: 6 } },
|
|
1825
1866
|
turnActive ? h("span", { className: "mai-pulse", style: { ...style.dot, background: t.accent } }) : null,
|
|
1826
|
-
turnActive ? stopping ? "\u6B63\u5728\u505C\u6B62\u5F53\u524D\u56DE\u5408\u2026" : "MonkeyAI \u6B63\u5728\u5904\u7406\u2026" : lastDelivery?.accepted ? "\u56DE\u5408\u5DF2\u7ED3\u675F" : lastDelivery?.frame === "pending" ? "\u7B49\u5F85\u6295\u9012" : "\u6295\u9012\u88AB\u62D2\u7EDD"
|
|
1867
|
+
turnActive ? stopping ? "\u6B63\u5728\u505C\u6B62\u5F53\u524D\u56DE\u5408\u2026" : sessionStatus === "running" ? "MonkeyAI \u6B63\u5728\u5904\u7406\uFF08\u8FDC\u7AEF\u5BA2\u6237\u7AEF\u53D1\u8D77\uFF09\u2026" : "MonkeyAI \u6B63\u5728\u5904\u7406\u2026" : lastDelivery?.accepted ? "\u56DE\u5408\u5DF2\u7ED3\u675F" : lastDelivery?.frame === "pending" ? "\u7B49\u5F85\u6295\u9012" : "\u6295\u9012\u88AB\u62D2\u7EDD"
|
|
1827
1868
|
) : null
|
|
1828
1869
|
) : null
|
|
1829
1870
|
);
|
package/lib/client-runtime.cjs
CHANGED
|
@@ -326,6 +326,12 @@ var TOOL_STATUS_LABEL = Object.freeze({
|
|
|
326
326
|
failed: "\u5931\u8D25",
|
|
327
327
|
cancelled: "\u5DF2\u53D6\u6D88"
|
|
328
328
|
});
|
|
329
|
+
var SESSION_STATUS_LABEL = Object.freeze({
|
|
330
|
+
running: "\u8FDB\u884C\u4E2D",
|
|
331
|
+
idle: "\u7A7A\u95F2",
|
|
332
|
+
error: "\u51FA\u9519",
|
|
333
|
+
interrupted: "\u5DF2\u4E2D\u65AD"
|
|
334
|
+
});
|
|
329
335
|
function contentText(content) {
|
|
330
336
|
const blocks = Array.isArray(content) ? content : content === void 0 || content === null ? [] : [content];
|
|
331
337
|
const parts = [];
|
|
@@ -490,6 +496,12 @@ function projectFrames(frames) {
|
|
|
490
496
|
continue;
|
|
491
497
|
}
|
|
492
498
|
if (frame.type === "notification" && typeof frame.method === "string" && frame.method.startsWith("_monkeyai/")) {
|
|
499
|
+
if (frame.method === "_monkeyai/session/state") {
|
|
500
|
+
const status = frame.data?.state?.status;
|
|
501
|
+
if (typeof status === "string" && SESSION_STATUS_LABEL[status]) {
|
|
502
|
+
entries.push({ kind: "system", label: `\u4F1A\u8BDD\u72B6\u6001\uFF1A${SESSION_STATUS_LABEL[status]}`, key: `sys-state:${entries.length}` });
|
|
503
|
+
}
|
|
504
|
+
}
|
|
493
505
|
continue;
|
|
494
506
|
}
|
|
495
507
|
if (frame.type === "error") {
|
|
@@ -803,6 +815,7 @@ function RemoteSessionPage({ api }) {
|
|
|
803
815
|
const [visibleCount, setVisibleCount] = useState(RECENT_LIMIT);
|
|
804
816
|
const [turnActive, setTurnActive] = useState(false);
|
|
805
817
|
const [stopping, setStopping] = useState(false);
|
|
818
|
+
const [sessionStatus, setSessionStatus] = useState("");
|
|
806
819
|
const [signInEmail, setSignInEmail] = useState("");
|
|
807
820
|
const [signInPassword, setSignInPassword] = useState("");
|
|
808
821
|
const streamRef = useRef(null);
|
|
@@ -1043,6 +1056,7 @@ function RemoteSessionPage({ api }) {
|
|
|
1043
1056
|
setLastDelivery(void 0);
|
|
1044
1057
|
setTurnActive(false);
|
|
1045
1058
|
setStopping(false);
|
|
1059
|
+
setSessionStatus("");
|
|
1046
1060
|
setError("");
|
|
1047
1061
|
setAnswerText("");
|
|
1048
1062
|
setCurrentModel("");
|
|
@@ -1061,6 +1075,7 @@ function RemoteSessionPage({ api }) {
|
|
|
1061
1075
|
historyLoadedRef.current = true;
|
|
1062
1076
|
setStatus("\u5386\u53F2\u5DF2\u52A0\u8F7D");
|
|
1063
1077
|
setHistoryLoading(false);
|
|
1078
|
+
setSessionStatus((current) => current || "idle");
|
|
1064
1079
|
if (frame.model) setCurrentModel(frame.model);
|
|
1065
1080
|
}
|
|
1066
1081
|
if (frame.type === "history_incomplete") {
|
|
@@ -1085,6 +1100,14 @@ function RemoteSessionPage({ api }) {
|
|
|
1085
1100
|
if (frame.type === "request") {
|
|
1086
1101
|
setPendingRequests((current) => [...current.filter((item) => item.requestId !== frame.requestId), frame]);
|
|
1087
1102
|
}
|
|
1103
|
+
if (frame.type === "notification" && frame.method === "_monkeyai/session/state") {
|
|
1104
|
+
const status2 = frame.data?.state?.status;
|
|
1105
|
+
if (typeof status2 === "string" && SESSION_STATUS_LABEL[status2]) {
|
|
1106
|
+
setSessionStatus(status2);
|
|
1107
|
+
setTurnActive(status2 === "running");
|
|
1108
|
+
if (status2 !== "running") setStopping(false);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1088
1111
|
if (frame.type === "delivery") {
|
|
1089
1112
|
const accepted = Boolean(frame.data?.accepted);
|
|
1090
1113
|
setLastDelivery({ accepted, code: frame.data?.code, frame: frame.type });
|
|
@@ -1115,12 +1138,14 @@ function RemoteSessionPage({ api }) {
|
|
|
1115
1138
|
setHistoryLoading(false);
|
|
1116
1139
|
setTurnActive(false);
|
|
1117
1140
|
setStopping(false);
|
|
1141
|
+
setSessionStatus("");
|
|
1118
1142
|
} catch (reason) {
|
|
1119
1143
|
setStatus("\u8FDC\u7A0B\u4F1A\u8BDD\u4E0D\u53EF\u7528");
|
|
1120
1144
|
setError(errorText(reason));
|
|
1121
1145
|
setHistoryLoading(false);
|
|
1122
1146
|
setTurnActive(false);
|
|
1123
1147
|
setStopping(false);
|
|
1148
|
+
setSessionStatus("");
|
|
1124
1149
|
}
|
|
1125
1150
|
}
|
|
1126
1151
|
function sendPrompt() {
|
|
@@ -1631,10 +1656,26 @@ function RemoteSessionPage({ api }) {
|
|
|
1631
1656
|
setSessionId("");
|
|
1632
1657
|
setTurnActive(false);
|
|
1633
1658
|
setStopping(false);
|
|
1659
|
+
setSessionStatus("");
|
|
1634
1660
|
setStatus("\u8FDC\u7A0B\u4F1A\u8BDD\u5DF2\u5173\u95ED");
|
|
1635
1661
|
}
|
|
1636
1662
|
}, "\u2190"),
|
|
1637
1663
|
h("strong", { style: { flex: 1, fontSize: 13.5, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, title }, title),
|
|
1664
|
+
sessionStatus ? h(
|
|
1665
|
+
"span",
|
|
1666
|
+
{
|
|
1667
|
+
style: {
|
|
1668
|
+
...style.statusBadge,
|
|
1669
|
+
background: sessionStatus === "running" ? t.accentSoft : sessionStatus === "error" ? t.dangerBg : t.surfaceAlt,
|
|
1670
|
+
borderColor: sessionStatus === "running" ? t.accent : sessionStatus === "error" ? t.dangerBorder : t.border,
|
|
1671
|
+
color: sessionStatus === "running" ? t.accent : sessionStatus === "error" ? t.danger : t.text2
|
|
1672
|
+
},
|
|
1673
|
+
title: "\u4F1A\u8BDD\u72B6\u6001\uFF08\u542B\u8FDC\u7AEF\u5BA2\u6237\u7AEF\u53D1\u8D77\u7684\u56DE\u5408\uFF09",
|
|
1674
|
+
"aria-label": `\u4F1A\u8BDD\u72B6\u6001\uFF1A${SESSION_STATUS_LABEL[sessionStatus]}`
|
|
1675
|
+
},
|
|
1676
|
+
sessionStatus === "running" ? h("span", { className: "mai-pulse", style: { ...style.dot, background: t.accent } }) : null,
|
|
1677
|
+
SESSION_STATUS_LABEL[sessionStatus]
|
|
1678
|
+
) : null,
|
|
1638
1679
|
h(
|
|
1639
1680
|
"select",
|
|
1640
1681
|
{
|
|
@@ -1818,7 +1859,7 @@ function RemoteSessionPage({ api }) {
|
|
|
1818
1859
|
"div",
|
|
1819
1860
|
{ style: { fontSize: 11.5, color: turnActive ? t.accent : lastDelivery?.accepted ? t.success : lastDelivery?.frame === "pending" ? t.text3 : t.danger, textAlign: "right", display: "flex", alignItems: "center", justifyContent: "flex-end", gap: 6 } },
|
|
1820
1861
|
turnActive ? h("span", { className: "mai-pulse", style: { ...style.dot, background: t.accent } }) : null,
|
|
1821
|
-
turnActive ? stopping ? "\u6B63\u5728\u505C\u6B62\u5F53\u524D\u56DE\u5408\u2026" : "MonkeyAI \u6B63\u5728\u5904\u7406\u2026" : lastDelivery?.accepted ? "\u56DE\u5408\u5DF2\u7ED3\u675F" : lastDelivery?.frame === "pending" ? "\u7B49\u5F85\u6295\u9012" : "\u6295\u9012\u88AB\u62D2\u7EDD"
|
|
1862
|
+
turnActive ? stopping ? "\u6B63\u5728\u505C\u6B62\u5F53\u524D\u56DE\u5408\u2026" : sessionStatus === "running" ? "MonkeyAI \u6B63\u5728\u5904\u7406\uFF08\u8FDC\u7AEF\u5BA2\u6237\u7AEF\u53D1\u8D77\uFF09\u2026" : "MonkeyAI \u6B63\u5728\u5904\u7406\u2026" : lastDelivery?.accepted ? "\u56DE\u5408\u5DF2\u7ED3\u675F" : lastDelivery?.frame === "pending" ? "\u7B49\u5F85\u6295\u9012" : "\u6295\u9012\u88AB\u62D2\u7EDD"
|
|
1822
1863
|
) : null
|
|
1823
1864
|
) : null
|
|
1824
1865
|
);
|
package/package.json
CHANGED
package/src/client/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { TYPERT_REMOTE } from '../remote.js'
|
|
3
|
-
import { projectFrames, TOOL_STATUS_LABEL } from '../shared/projection.mjs'
|
|
3
|
+
import { projectFrames, TOOL_STATUS_LABEL, SESSION_STATUS_LABEL } from '../shared/projection.mjs'
|
|
4
4
|
|
|
5
5
|
const PANEL_ID = 'monkeyai-sessions'
|
|
6
6
|
const MANUAL_DEVICES_KEY = 'monkeyai-sessions/manual-devices'
|
|
@@ -290,6 +290,7 @@ function RemoteSessionPage({ api }) {
|
|
|
290
290
|
const [visibleCount, setVisibleCount] = useState(RECENT_LIMIT)
|
|
291
291
|
const [turnActive, setTurnActive] = useState(false)
|
|
292
292
|
const [stopping, setStopping] = useState(false)
|
|
293
|
+
const [sessionStatus, setSessionStatus] = useState('')
|
|
293
294
|
const [signInEmail, setSignInEmail] = useState('')
|
|
294
295
|
const [signInPassword, setSignInPassword] = useState('')
|
|
295
296
|
const streamRef = useRef(null)
|
|
@@ -540,6 +541,7 @@ function RemoteSessionPage({ api }) {
|
|
|
540
541
|
setLastDelivery(undefined)
|
|
541
542
|
setTurnActive(false)
|
|
542
543
|
setStopping(false)
|
|
544
|
+
setSessionStatus('')
|
|
543
545
|
setError('')
|
|
544
546
|
setAnswerText('')
|
|
545
547
|
setCurrentModel('')
|
|
@@ -558,6 +560,7 @@ function RemoteSessionPage({ api }) {
|
|
|
558
560
|
historyLoadedRef.current = true
|
|
559
561
|
setStatus('历史已加载')
|
|
560
562
|
setHistoryLoading(false)
|
|
563
|
+
setSessionStatus(current => current || 'idle')
|
|
561
564
|
if (frame.model) setCurrentModel(frame.model)
|
|
562
565
|
}
|
|
563
566
|
if (frame.type === 'history_incomplete') {
|
|
@@ -582,6 +585,14 @@ function RemoteSessionPage({ api }) {
|
|
|
582
585
|
if (frame.type === 'request') {
|
|
583
586
|
setPendingRequests(current => [...current.filter(item => item.requestId !== frame.requestId), frame])
|
|
584
587
|
}
|
|
588
|
+
if (frame.type === 'notification' && frame.method === '_monkeyai/session/state') {
|
|
589
|
+
const status = frame.data?.state?.status
|
|
590
|
+
if (typeof status === 'string' && SESSION_STATUS_LABEL[status]) {
|
|
591
|
+
setSessionStatus(status)
|
|
592
|
+
setTurnActive(status === 'running')
|
|
593
|
+
if (status !== 'running') setStopping(false)
|
|
594
|
+
}
|
|
595
|
+
}
|
|
585
596
|
if (frame.type === 'delivery') {
|
|
586
597
|
const accepted = Boolean(frame.data?.accepted)
|
|
587
598
|
setLastDelivery({ accepted, code: frame.data?.code, frame: frame.type })
|
|
@@ -612,12 +623,14 @@ function RemoteSessionPage({ api }) {
|
|
|
612
623
|
setHistoryLoading(false)
|
|
613
624
|
setTurnActive(false)
|
|
614
625
|
setStopping(false)
|
|
626
|
+
setSessionStatus('')
|
|
615
627
|
} catch (reason) {
|
|
616
628
|
setStatus('远程会话不可用')
|
|
617
629
|
setError(errorText(reason))
|
|
618
630
|
setHistoryLoading(false)
|
|
619
631
|
setTurnActive(false)
|
|
620
632
|
setStopping(false)
|
|
633
|
+
setSessionStatus('')
|
|
621
634
|
}
|
|
622
635
|
}
|
|
623
636
|
|
|
@@ -1014,9 +1027,21 @@ function RemoteSessionPage({ api }) {
|
|
|
1014
1027
|
h('div', { style: style.row },
|
|
1015
1028
|
h('button', {
|
|
1016
1029
|
type: 'button', style: { ...style.button, flexShrink: 0 }, title: '关闭会话并返回列表',
|
|
1017
|
-
onClick: () => { streamRef.current?.dispose(); streamRef.current = null; setSessionId(''); setTurnActive(false); setStopping(false); setStatus('远程会话已关闭') },
|
|
1030
|
+
onClick: () => { streamRef.current?.dispose(); streamRef.current = null; setSessionId(''); setTurnActive(false); setStopping(false); setSessionStatus(''); setStatus('远程会话已关闭') },
|
|
1018
1031
|
}, '←'),
|
|
1019
1032
|
h('strong', { style: { flex: 1, fontSize: 13.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, title }, title),
|
|
1033
|
+
sessionStatus ? h('span', {
|
|
1034
|
+
style: {
|
|
1035
|
+
...style.statusBadge,
|
|
1036
|
+
background: sessionStatus === 'running' ? t.accentSoft : sessionStatus === 'error' ? t.dangerBg : t.surfaceAlt,
|
|
1037
|
+
borderColor: sessionStatus === 'running' ? t.accent : sessionStatus === 'error' ? t.dangerBorder : t.border,
|
|
1038
|
+
color: sessionStatus === 'running' ? t.accent : sessionStatus === 'error' ? t.danger : t.text2,
|
|
1039
|
+
},
|
|
1040
|
+
title: '会话状态(含远端客户端发起的回合)',
|
|
1041
|
+
'aria-label': `会话状态:${SESSION_STATUS_LABEL[sessionStatus]}`,
|
|
1042
|
+
},
|
|
1043
|
+
sessionStatus === 'running' ? h('span', { className: 'mai-pulse', style: { ...style.dot, background: t.accent } }) : null,
|
|
1044
|
+
SESSION_STATUS_LABEL[sessionStatus]) : null,
|
|
1020
1045
|
h('select', {
|
|
1021
1046
|
style: { ...style.input, flex: '0 1 190px', minWidth: 130 },
|
|
1022
1047
|
value: currentModel,
|
|
@@ -1134,7 +1159,7 @@ function RemoteSessionPage({ api }) {
|
|
|
1134
1159
|
h('path', { d: 'M8 12.5V3.5M8 3.5L4 7.5M8 3.5L12 7.5', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round' })))),
|
|
1135
1160
|
lastDelivery || turnActive ? h('div', { style: { fontSize: 11.5, color: turnActive ? t.accent : lastDelivery?.accepted ? t.success : lastDelivery?.frame === 'pending' ? t.text3 : t.danger, textAlign: 'right', display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 6 } },
|
|
1136
1161
|
turnActive ? h('span', { className: 'mai-pulse', style: { ...style.dot, background: t.accent } }) : null,
|
|
1137
|
-
turnActive ? (stopping ? '正在停止当前回合…' : 'MonkeyAI 正在处理…') : lastDelivery?.accepted ? '回合已结束' : lastDelivery?.frame === 'pending' ? '等待投递' : '投递被拒绝') : null)
|
|
1162
|
+
turnActive ? (stopping ? '正在停止当前回合…' : sessionStatus === 'running' ? 'MonkeyAI 正在处理(远端客户端发起)…' : 'MonkeyAI 正在处理…') : lastDelivery?.accepted ? '回合已结束' : lastDelivery?.frame === 'pending' ? '等待投递' : '投递被拒绝') : null)
|
|
1138
1163
|
: null)
|
|
1139
1164
|
}
|
|
1140
1165
|
|
|
@@ -972,6 +972,24 @@ export class MonkeyAIRemoteService extends TypertRemoteService {
|
|
|
972
972
|
historyRefreshPromise = historyRefreshPromise.then(() => refreshPersistedHistory())
|
|
973
973
|
await historyRefreshPromise
|
|
974
974
|
}
|
|
975
|
+
// The desktop client tracks turn state via _monkeyai/session/state
|
|
976
|
+
// notifications. Seed the panel with the current status so a turn
|
|
977
|
+
// already running when the panel opens is shown as 进行中.
|
|
978
|
+
try {
|
|
979
|
+
const state = await readSessionState({ httpBase: ticketHttpBase, ticket: ticketValue, sessionId: selectedSession, signal })
|
|
980
|
+
const status = state?.state?.status
|
|
981
|
+
if (typeof status === 'string' && ['running', 'idle', 'error', 'interrupted'].includes(status)) {
|
|
982
|
+
emit({
|
|
983
|
+
type: 'notification',
|
|
984
|
+
sessionId: selectedSession,
|
|
985
|
+
method: '_monkeyai/session/state',
|
|
986
|
+
data: { sessionId: selectedSession, state: { status }, eventSeq: state.eventSeq },
|
|
987
|
+
})
|
|
988
|
+
}
|
|
989
|
+
} catch {
|
|
990
|
+
// Older runtimes may not expose the state read; live notifications
|
|
991
|
+
// still drive the status once the next turn starts.
|
|
992
|
+
}
|
|
975
993
|
} catch (error) {
|
|
976
994
|
if (signal?.aborted) throw error
|
|
977
995
|
const message = error instanceof Error ? error.message : String(error)
|
|
@@ -1361,6 +1379,34 @@ async function readSessionHistoryPage({ httpBase, ticket, sessionId, cursor, dir
|
|
|
1361
1379
|
return result
|
|
1362
1380
|
}
|
|
1363
1381
|
|
|
1382
|
+
async function readSessionState({ httpBase, ticket, sessionId, signal }) {
|
|
1383
|
+
const timeout = AbortSignal.any([AbortSignal.timeout(15_000), ...(signal ? [signal] : [])])
|
|
1384
|
+
const response = await fetch(`${httpBase.endsWith('/') ? httpBase : `${httpBase}/`}read/session`, {
|
|
1385
|
+
method: 'POST',
|
|
1386
|
+
headers: { authorization: `Bearer ${ticket}`, 'content-type': 'application/json' },
|
|
1387
|
+
body: JSON.stringify({ method: '_monkeyai/session/state/get', params: { sessionId } }),
|
|
1388
|
+
signal: timeout,
|
|
1389
|
+
})
|
|
1390
|
+
const text = await readHistoryBodyText(response).catch(error => {
|
|
1391
|
+
if (response.ok) throw error
|
|
1392
|
+
return ''
|
|
1393
|
+
})
|
|
1394
|
+
if (!response.ok) {
|
|
1395
|
+
throw new Error(`MonkeyAI state read failed (${response.status}): ${text.slice(0, 200)}`)
|
|
1396
|
+
}
|
|
1397
|
+
let data
|
|
1398
|
+
try {
|
|
1399
|
+
data = JSON.parse(text)
|
|
1400
|
+
} catch {
|
|
1401
|
+
throw new Error('MonkeyAI state read returned invalid JSON')
|
|
1402
|
+
}
|
|
1403
|
+
const result = data?.result
|
|
1404
|
+
if (!result || typeof result !== 'object' || !result.state || typeof result.state !== 'object') {
|
|
1405
|
+
throw new Error('MonkeyAI state read returned an invalid response')
|
|
1406
|
+
}
|
|
1407
|
+
return result
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1364
1410
|
function historyMessageText(parts) {
|
|
1365
1411
|
return (Array.isArray(parts) ? parts : [])
|
|
1366
1412
|
.filter(part => part?.type === 'text' && typeof part.text === 'string')
|
|
@@ -14,6 +14,10 @@ export const TOOL_STATUS_LABEL = Object.freeze({
|
|
|
14
14
|
pending: '排队中', in_progress: '执行中', completed: '完成', failed: '失败', cancelled: '已取消',
|
|
15
15
|
})
|
|
16
16
|
|
|
17
|
+
export const SESSION_STATUS_LABEL = Object.freeze({
|
|
18
|
+
running: '进行中', idle: '空闲', error: '出错', interrupted: '已中断',
|
|
19
|
+
})
|
|
20
|
+
|
|
17
21
|
function contentText(content) {
|
|
18
22
|
const blocks = Array.isArray(content) ? content : content === undefined || content === null ? [] : [content]
|
|
19
23
|
const parts = []
|
|
@@ -139,7 +143,15 @@ function projectFrames(frames) {
|
|
|
139
143
|
if (frame.type === 'request') { continue } // rendered as an approval card from pendingRequests
|
|
140
144
|
if (frame.type === 'model_switched') { entries.push({ kind: 'system', label: `已切换模型:${frame.data?.model ?? '所选模型'}`, key: `sys-model:${entries.length}` }); continue }
|
|
141
145
|
if (frame.type === 'model_switch_failed') { continue } // surfaced via the error banner
|
|
142
|
-
if (frame.type === 'notification' && typeof frame.method === 'string' && frame.method.startsWith('_monkeyai/')) {
|
|
146
|
+
if (frame.type === 'notification' && typeof frame.method === 'string' && frame.method.startsWith('_monkeyai/')) {
|
|
147
|
+
if (frame.method === '_monkeyai/session/state') {
|
|
148
|
+
const status = frame.data?.state?.status
|
|
149
|
+
if (typeof status === 'string' && SESSION_STATUS_LABEL[status]) {
|
|
150
|
+
entries.push({ kind: 'system', label: `会话状态:${SESSION_STATUS_LABEL[status]}`, key: `sys-state:${entries.length}` })
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
continue // internal runtime state notifications
|
|
154
|
+
}
|
|
143
155
|
if (frame.type === 'error') { entries.push({ kind: 'raw', label: frame.code || 'error', data: { message: frame.message || frame.code }, key: `sys-err:${entries.length}` }); continue }
|
|
144
156
|
entries.push({ kind: 'raw', label: frame.method || frame.type, data: frame.data, key: `raw:${frame.type}:${entries.length}` })
|
|
145
157
|
}
|