@morlay/ui-conversation-message-actions 0.0.16 → 0.0.18
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 +19 -10
- package/dist/client.js +40 -40
- package/dist/index.d.mts +9 -13
- package/dist/index.mjs +1 -1
- package/dist/{plan-Cn3TPTa0.d.mts → plan-BSa7k9HO.d.mts} +3 -2
- package/dist/plan.d.mts +2 -2
- package/dist/plan.mjs +14 -1
- package/dist/{src-Dq1X3rOO.mjs → src-B4HwXsob.mjs} +13 -28
- package/dist/testing.d.mts +41 -54
- package/dist/testing.mjs +7432 -400
- package/package.json +4 -4
- package/src/client/chat-node/MessageItem.tsx +4 -2
- package/src/index.ts +25 -35
- package/src/plan.ts +25 -0
- package/src/shared.ts +2 -1
- package/src/testing.ts +6 -1
- package/src/types.ts +2 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
| `reroll` | 重生成最后一条已落定助手回复(使用原用户输入) |
|
|
14
14
|
| `retry` | 重试任意历史回合(`truncate` 只重放目标输入 / `preserve` 重放后续全部) |
|
|
15
15
|
| `rewind` | 截断式回退:原会话回退到闭合 `turn/end` 边界 |
|
|
16
|
-
| `recall` | 撤回 user 消息到输入框:只 `rewind` 截断,不重写、不重放(交给用户改后重发)
|
|
16
|
+
| `recall` | 撤回 user 消息到输入框:只 `rewind` 截断,不重写、不重放(交给用户改后重发) |
|
|
17
17
|
| `fork` | 从任意闭合边界派生**新会话**(唯一产生新 id 的操作) |
|
|
18
18
|
| `timeline` | 版本树投影(HTTP:`GET /session-editor?sessionId=…`) |
|
|
19
19
|
|
|
@@ -29,9 +29,11 @@
|
|
|
29
29
|
点不开的空项与轮号空洞;manualTurn 的轮号同样按保留前缀收敛(正常会话
|
|
30
30
|
等于目标轮号)。版本效果仍记录**原**目标轮号(操作历史);
|
|
31
31
|
- **只有 `fork` 创建新 id**(`ForkOperation` / `forkFrom`,纯 append 派生);
|
|
32
|
-
- **`recall`
|
|
33
|
-
`turn/end
|
|
34
|
-
|
|
32
|
+
- **`recall` 只截断不重写**:轮内消息沿用 `edit` 的轮首规则(轮首整轮截断到前一轮
|
|
33
|
+
`turn/end`,无前轮则清空全部;轮内 followup exclusive drop),轮外 user 消息
|
|
34
|
+
(`location` 无轮次归属的排队落定消息)按消息自身位置 exclusive drop(截断该消息
|
|
35
|
+
及其后全部),但只 `rewind` 即返回——不写版本效果、不重放;被撤回的 user 文本由
|
|
36
|
+
浏览器半回填到 composer,用户修改后自行发送;
|
|
35
37
|
- 版本效果事件(`session-branch/version`)携带 `ignorable: true`:**原样落库**
|
|
36
38
|
(rdb 不按信封过滤,与 JSONL 一致),非 branch 读者凭信封跳过;
|
|
37
39
|
- 重放输入经 agent 驱动(见下)排队到原会话,agent 基于截断后历史回复。
|
|
@@ -42,8 +44,13 @@
|
|
|
42
44
|
缺失时退化为「已 durable 的就地版本」(可随时 resume 续跑):
|
|
43
45
|
|
|
44
46
|
- **live agent**(会话驻留 / 已恢复):直接 `followup` 排队,不重建、不换 id;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
- **rewind 前必须停止运行中的 loop**:每次 rewind(edit / retry / reroll /
|
|
48
|
+
recall / HTTP `rewind`)都先对驻留 agent `cancel({ kind: "user" }, { keepInbox: true })`
|
|
49
|
+
再 `whenIdle()`,然后才截断——截断会重写 agent 的 session 内存 log,不能与
|
|
50
|
+
运行中的 loop 并发;`keepInbox` 表示停止本身不丢弃待处理输入,残留排队输入由
|
|
51
|
+
rewind 在截断后强制 durable 取消(session-rdb 的 live 会话钩子 `inbox.clear`);
|
|
52
|
+
实现无 `cancel` 能力时退化为等待其自然停下
|
|
53
|
+
([ADR 0005](./docs/adr/0005-rewind前主动停止运行中的loop.md));
|
|
47
54
|
- **cold 会话**:`resume` 已持久化会话(`create` 对已持久化日志必失败),
|
|
48
55
|
resume 后 agent 驻留(不 dispose,避免 session 被移出 store 破坏客户端窗口);
|
|
49
56
|
- 模型 provider/model 在 **rewind 之前**从 `request/header` 解析(就地编辑
|
|
@@ -56,10 +63,12 @@
|
|
|
56
63
|
|
|
57
64
|
- **shadow 注册**:`user` / `steering` 两个 key 以 `priority: -1` 重新注册
|
|
58
65
|
(最低优先级渲染,shadow 上游默认注册);其余 key 沿用上游渲染器;
|
|
59
|
-
- **编辑 / 重试按钮只挂在 user 消息**(`UserMessageNodeView
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
- **编辑 / 重试按钮只挂在 user 消息**(`UserMessageNodeView`):编辑只要消息存在
|
|
67
|
+
可编辑文本块就显示(不要求轮次归属——`location` 无 turn/step 的已落定消息同样
|
|
68
|
+
可撤回),点击后不再打开编辑弹窗,而是确认(`Modal` + `Button`)后调用
|
|
69
|
+
`recall`——服务端只 `rewind` 截断,客户端把消息文本 `setDraft` 回填到主输入框
|
|
70
|
+
(`conversation.input`),交给用户修改后自行发送;重试仍先弹确认,且只对已闭合
|
|
71
|
+
轮次开放;
|
|
63
72
|
- **操作后刷新**:就地编辑后优先调用客户端会话级 `resync()`(重置窗口并重新
|
|
64
73
|
拉取历史,不整页重载——rewind 的删除无法经 append-only 事件流表达,seq
|
|
65
74
|
回退只做增量会残留旧节点),随后丢弃该会话的全部投影行
|
package/dist/client.js
CHANGED
|
@@ -367,8 +367,8 @@ window.__ModuleLoader__.load({
|
|
|
367
367
|
return day;
|
|
368
368
|
}
|
|
369
369
|
//#endregion
|
|
370
|
-
//#region \0dsh-css:/Users/morlay/src/github.com/morlay/better-session/packages/ui-conversation-message-actions/src/client/chat-node/MessageIconActions.module.css.mjs
|
|
371
|
-
const css$1 = ".
|
|
370
|
+
//#region \0dsh-css:/Users/morlay/src/github.com/morlay/better-session/packages/session/ui-conversation-message-actions/src/client/chat-node/MessageIconActions.module.css.mjs
|
|
371
|
+
const css$1 = ".zUzlda_actions{align-items:center;gap:10px;height:28px;display:flex}.zUzlda_timeStart{color:var(--dsw-alias-label-tertiary);white-space:nowrap;padding-right:12px;font-size:14px;line-height:24px}.zUzlda_timeEnd{color:var(--dsw-alias-label-tertiary);white-space:nowrap;padding-left:12px;font-size:14px;line-height:24px}.zUzlda_runTimeDot{margin:0 10px}@media (hover:hover){[data-time-hover-root] :is(.zUzlda_timeStart,.zUzlda_timeEnd){opacity:0;transition:opacity 80ms}[data-time-hover-root]:hover :is(.zUzlda_timeStart,.zUzlda_timeEnd),[data-time-hover-root]:focus-within :is(.zUzlda_timeStart,.zUzlda_timeEnd){opacity:1}}.zUzlda_action{width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}.zUzlda_action:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.zUzlda_action[data-unavailable]{cursor:default;opacity:.4}.zUzlda_action[data-unavailable]:hover{color:var(--dsw-alias-label-tertiary);background:0 0}.zUzlda_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}";
|
|
372
372
|
const tagId$1 = "@morlay/ui-conversation-message-actions/MessageIconActions.module.css";
|
|
373
373
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
374
374
|
const tag = document.createElement("style");
|
|
@@ -378,12 +378,12 @@ window.__ModuleLoader__.load({
|
|
|
378
378
|
document.head.appendChild(tag);
|
|
379
379
|
}
|
|
380
380
|
var MessageIconActions_module_css_default = {
|
|
381
|
-
"
|
|
382
|
-
"
|
|
383
|
-
"
|
|
384
|
-
"
|
|
385
|
-
"
|
|
386
|
-
"
|
|
381
|
+
"timeStart": "zUzlda_timeStart",
|
|
382
|
+
"runTimeDot": "zUzlda_runTimeDot",
|
|
383
|
+
"visuallyHidden": "zUzlda_visuallyHidden",
|
|
384
|
+
"actions": "zUzlda_actions",
|
|
385
|
+
"timeEnd": "zUzlda_timeEnd",
|
|
386
|
+
"action": "zUzlda_action"
|
|
387
387
|
};
|
|
388
388
|
//#endregion
|
|
389
389
|
//#region src/client/chat-node/MessageIconActions.tsx
|
|
@@ -514,8 +514,8 @@ window.__ModuleLoader__.load({
|
|
|
514
514
|
});
|
|
515
515
|
}
|
|
516
516
|
//#endregion
|
|
517
|
-
//#region \0dsh-css:/Users/morlay/src/github.com/morlay/better-session/packages/ui-conversation-message-actions/src/client/chat-node/MessageItem.module.css.mjs
|
|
518
|
-
const css = ".
|
|
517
|
+
//#region \0dsh-css:/Users/morlay/src/github.com/morlay/better-session/packages/session/ui-conversation-message-actions/src/client/chat-node/MessageItem.module.css.mjs
|
|
518
|
+
const css = ".rSGQoW_userRow{flex-direction:column;align-items:flex-end;gap:6px;display:flex}.rSGQoW_userStack{flex-direction:column;align-items:flex-end;gap:8px;min-width:0;max-width:min(525px,82%);display:flex}.rSGQoW_bubble{background:var(--dsw-specific-bubble);max-width:100%;color:var(--dsw-alias-label-primary);border-radius:22px;padding:10px 16px;font-size:16px;line-height:24px}.rSGQoW_contextRow,.rSGQoW_compactionRow{padding:2px 0}.rSGQoW_compactionButton{width:100%;min-width:0;height:24px;color:inherit;font:inherit;text-align:left;background:0 0;border:none;border-radius:6px;align-items:center;padding:0;display:flex}.rSGQoW_compactionButton:not(:disabled){cursor:pointer}.rSGQoW_compactionButton:not(:disabled):hover{background:var(--dsw-alias-interactive-bg-hover)}.rSGQoW_compactionLeading{width:16px;height:16px;color:var(--dsw-alias-label-secondary);flex:none;place-items:center;margin-right:6px;display:inline-grid}.rSGQoW_compactionContextIcon,.rSGQoW_compactionDisclosureIcon{grid-area:1/1;justify-content:center;align-items:center;display:inline-flex}.rSGQoW_compactionDisclosureIcon,.rSGQoW_compactionButton:not(:disabled):hover .rSGQoW_compactionContextIcon,.rSGQoW_compactionButton:not(:disabled):focus-visible .rSGQoW_compactionContextIcon{opacity:0}.rSGQoW_compactionButton:not(:disabled):hover .rSGQoW_compactionDisclosureIcon,.rSGQoW_compactionButton:not(:disabled):focus-visible .rSGQoW_compactionDisclosureIcon{opacity:1}.rSGQoW_compactionTitle{color:var(--dsw-alias-label-primary-dimmed);flex:none;font-size:14px;line-height:24px}.rSGQoW_compactionSep{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.rSGQoW_compactionSummary{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:auto;font-size:14px;line-height:24px;overflow:hidden}.rSGQoW_compactionBody{color:var(--dsw-alias-label-tertiary);padding:4px 0 4px 22px;font-size:14px;line-height:24px}.rSGQoW_retryRow{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px}.rSGQoW_retrySummary{width:fit-content;color:inherit;cursor:pointer;user-select:none;border-radius:3px;align-items:center;gap:7px;padding:2px 0;list-style:none;display:inline-flex}.rSGQoW_retrySummary::-webkit-details-marker{display:none}.rSGQoW_retrySummary:after{content:\"\";opacity:.8;border-bottom:1.5px solid;border-right:1.5px solid;width:6px;height:6px;transition:transform .12s;transform:rotate(-45deg)}.rSGQoW_retrySummary:hover{color:var(--dsw-alias-label-secondary)}.rSGQoW_retrySummary:focus-visible{outline:1.5px solid var(--dsw-alias-button-info-fill);outline-offset:2px}.rSGQoW_retryText{color:inherit}.rSGQoW_retryRow[data-active] .rSGQoW_retryText{background:linear-gradient(90deg, var(--dsw-alias-label-tertiary) 0%, var(--dsw-alias-label-tertiary) 40%, var(--dsw-alias-label-secondary) 50%, var(--dsw-alias-label-tertiary) 60%, var(--dsw-alias-label-tertiary) 100%);color:#0000;background-position:100%;background-size:200% 100%;background-clip:text;animation:1.6s ease-in-out infinite rSGQoW_retry-shimmer}.rSGQoW_retryRow[open] .rSGQoW_retrySummary:after{transform:rotate(45deg)}.rSGQoW_retryDetails{overflow-wrap:anywhere;gap:2px;margin-top:3px;padding-left:14px;font-size:12px;line-height:18px;display:grid}.rSGQoW_retryDetailLabel{color:var(--dsw-alias-label-secondary)}.rSGQoW_turnErrorRow{grid-template-columns:10px minmax(0,1fr) auto;align-items:start;gap:8px;padding:2px 0;font-size:13px;line-height:20px;display:grid}.rSGQoW_turnErrorDot{margin-top:5px}.rSGQoW_turnErrorCopy{overflow-wrap:anywhere;min-width:0}.rSGQoW_turnErrorTitle{color:var(--dsw-alias-state-error-primary);margin-right:6px;font-weight:600}.rSGQoW_turnErrorMessage{color:var(--dsw-alias-label-secondary)}.rSGQoW_turnErrorCode{color:var(--dsw-alias-label-tertiary);font:var(--dsw-font-markdown-code-block-small)}.rSGQoW_maxTokensTitle{color:var(--dsw-alias-state-warn-primary);margin-right:6px;font-weight:600}@keyframes rSGQoW_retry-shimmer{0%{background-position:100%}to{background-position:0}}@media (prefers-reduced-motion:reduce){.rSGQoW_retryRow[data-active] .rSGQoW_retryText{color:inherit;background:0 0;animation:none}}.rSGQoW_refChip{color:var(--dsw-alias-label-primary);white-space:nowrap;vertical-align:baseline;background:#6187d838;border-radius:6px;margin:0 2px;padding:0 8px;font-size:.85em;line-height:1.6;display:inline-block}.rSGQoW_confirmActions{justify-content:flex-end;gap:8px;display:flex}";
|
|
519
519
|
const tagId = "@morlay/ui-conversation-message-actions/MessageItem.module.css";
|
|
520
520
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
521
521
|
const tag = document.createElement("style");
|
|
@@ -525,34 +525,34 @@ window.__ModuleLoader__.load({
|
|
|
525
525
|
document.head.appendChild(tag);
|
|
526
526
|
}
|
|
527
527
|
var MessageItem_module_css_default = {
|
|
528
|
-
"
|
|
529
|
-
"
|
|
530
|
-
"
|
|
531
|
-
"
|
|
532
|
-
"
|
|
533
|
-
"
|
|
534
|
-
"
|
|
535
|
-
"
|
|
536
|
-
"
|
|
537
|
-
"
|
|
538
|
-
"
|
|
539
|
-
"
|
|
540
|
-
"
|
|
541
|
-
"
|
|
542
|
-
"
|
|
543
|
-
"
|
|
544
|
-
"
|
|
545
|
-
"
|
|
546
|
-
"
|
|
547
|
-
"
|
|
548
|
-
"
|
|
549
|
-
"
|
|
550
|
-
"
|
|
551
|
-
"
|
|
552
|
-
"
|
|
553
|
-
"
|
|
554
|
-
"
|
|
555
|
-
"
|
|
528
|
+
"compactionSummary": "rSGQoW_compactionSummary",
|
|
529
|
+
"compactionContextIcon": "rSGQoW_compactionContextIcon",
|
|
530
|
+
"compactionTitle": "rSGQoW_compactionTitle",
|
|
531
|
+
"turnErrorCopy": "rSGQoW_turnErrorCopy",
|
|
532
|
+
"compactionRow": "rSGQoW_compactionRow",
|
|
533
|
+
"retrySummary": "rSGQoW_retrySummary",
|
|
534
|
+
"compactionLeading": "rSGQoW_compactionLeading",
|
|
535
|
+
"turnErrorMessage": "rSGQoW_turnErrorMessage",
|
|
536
|
+
"compactionButton": "rSGQoW_compactionButton",
|
|
537
|
+
"retryRow": "rSGQoW_retryRow",
|
|
538
|
+
"confirmActions": "rSGQoW_confirmActions",
|
|
539
|
+
"turnErrorRow": "rSGQoW_turnErrorRow",
|
|
540
|
+
"retryDetailLabel": "rSGQoW_retryDetailLabel",
|
|
541
|
+
"contextRow": "rSGQoW_contextRow",
|
|
542
|
+
"userStack": "rSGQoW_userStack",
|
|
543
|
+
"compactionSep": "rSGQoW_compactionSep",
|
|
544
|
+
"compactionDisclosureIcon": "rSGQoW_compactionDisclosureIcon",
|
|
545
|
+
"userRow": "rSGQoW_userRow",
|
|
546
|
+
"retry-shimmer": "rSGQoW_retry-shimmer",
|
|
547
|
+
"refChip": "rSGQoW_refChip",
|
|
548
|
+
"bubble": "rSGQoW_bubble",
|
|
549
|
+
"compactionBody": "rSGQoW_compactionBody",
|
|
550
|
+
"retryText": "rSGQoW_retryText",
|
|
551
|
+
"retryDetails": "rSGQoW_retryDetails",
|
|
552
|
+
"turnErrorCode": "rSGQoW_turnErrorCode",
|
|
553
|
+
"maxTokensTitle": "rSGQoW_maxTokensTitle",
|
|
554
|
+
"turnErrorDot": "rSGQoW_turnErrorDot",
|
|
555
|
+
"turnErrorTitle": "rSGQoW_turnErrorTitle"
|
|
556
556
|
};
|
|
557
557
|
//#endregion
|
|
558
558
|
//#region src/client/chat-node/MessageItem.tsx
|
|
@@ -624,10 +624,10 @@ window.__ModuleLoader__.load({
|
|
|
624
624
|
const retryable = turnLocation?.status === "closed";
|
|
625
625
|
const textBlockIndex = data.content.findIndex((block) => block.type === "text");
|
|
626
626
|
const textBlock = textBlockIndex === -1 ? void 0 : data.content[textBlockIndex];
|
|
627
|
-
const onEdit = textBlock === void 0
|
|
627
|
+
const onEdit = textBlock === void 0 ? void 0 : () => {
|
|
628
628
|
setConfirmingRecall({
|
|
629
629
|
key: `${node.anchorSeq}:${String(textBlockIndex)}`,
|
|
630
|
-
turn,
|
|
630
|
+
...turn === void 0 ? {} : { turn },
|
|
631
631
|
eventSeq: node.anchorSeq,
|
|
632
632
|
blockIndex: textBlockIndex,
|
|
633
633
|
kind: "user",
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { _ as
|
|
1
|
+
import { _ as RetryableTurn, b as SessionEditorResult, d as retryableTurns, f as EditOperation, g as RetryOperation, h as RerollOperation, m as RecallOperation, o as editableMessages, p as EditableMessageBlock, r as closedTurns, v as RewindOperation, y as SessionEditorOperation } from "./plan-BSa7k9HO.mjs";
|
|
2
2
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
3
|
-
import { Session, SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
|
|
3
|
+
import { AgentCancelCause, Session, SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
|
|
4
4
|
import { BranchBoundary, BranchTimeline, BranchTimeline as BranchTimeline$1, CascadePolicy, EditableBlockKind, SESSION_BRANCH_VERSION_SCHEMA, SessionBranchVersionEvent } from "@morlay/session-branch";
|
|
5
5
|
import { UserMessage } from "@deepseek-ai/dsh-llm";
|
|
6
6
|
//#region src/index.d.ts
|
|
@@ -14,6 +14,9 @@ interface EditorAgent {
|
|
|
14
14
|
followup(message: UserMessage): void;
|
|
15
15
|
/** 等待 agent 到达 quiescence(当前 turn/任务结束后 resolve)。 */
|
|
16
16
|
whenIdle(): Promise<void>;
|
|
17
|
+
cancel?(cause: AgentCancelCause, options?: {
|
|
18
|
+
keepInbox?: boolean;
|
|
19
|
+
}): void;
|
|
17
20
|
}
|
|
18
21
|
interface EditorAgentHandle {
|
|
19
22
|
readonly agent: EditorAgent;
|
|
@@ -66,20 +69,13 @@ declare class SessionEditor extends Service {
|
|
|
66
69
|
* 撤回(recall):只 rewind 截断,不重写、不重放——被撤回的 user 消息文本
|
|
67
70
|
* 由客户端回填到输入框,交给用户修改后重新发送。
|
|
68
71
|
*
|
|
69
|
-
*
|
|
70
|
-
* (无前轮则 -1),轮内 followup
|
|
71
|
-
*
|
|
72
|
+
* 边界见 {@link recallBoundary}:轮首消息整轮截断到前一轮 `turn/end`
|
|
73
|
+
* (无前轮则 -1),轮内 followup 与轮外 user 消息只截断到该消息本身
|
|
74
|
+
* (exclusive drop),避免留下悬空 `turn/start` 让下一次发送开到错误轮号。
|
|
72
75
|
*/
|
|
73
76
|
private recallOperation;
|
|
74
|
-
|
|
75
|
-
private prepareIdle;
|
|
77
|
+
private stopLoop;
|
|
76
78
|
private readEvents;
|
|
77
|
-
/**
|
|
78
|
-
* rewind 前确保 agent 可驱动重放:live agent 先等待其停下(rewind 会截断其
|
|
79
|
-
* session 内存 log,须在 quiescence 后执行);cold 会话先 resume 出驻留
|
|
80
|
-
* agent(此时会话完整,resume 的 prepare 不与截断冲突)。agents 服务缺失或
|
|
81
|
-
* 无需重放时返回空——调用方退化为就地截断版本。
|
|
82
|
-
*/
|
|
83
79
|
private prepareReplay;
|
|
84
80
|
}
|
|
85
81
|
interface HttpRequestLike {
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { n as SessionEditor, t as SESSION_BRANCH_VERSION_SCHEMA } from "./src-
|
|
1
|
+
import { n as SessionEditor, t as SESSION_BRANCH_VERSION_SCHEMA } from "./src-B4HwXsob.mjs";
|
|
2
2
|
import { closedTurns, editableMessages, retryableTurns } from "./plan.mjs";
|
|
3
3
|
export { SESSION_BRANCH_VERSION_SCHEMA, SessionEditor, SessionEditor as default, closedTurns, editableMessages, retryableTurns };
|
|
@@ -44,7 +44,7 @@ interface SessionEditorResult {
|
|
|
44
44
|
}
|
|
45
45
|
interface EditableMessageBlock {
|
|
46
46
|
key: string;
|
|
47
|
-
turn
|
|
47
|
+
turn?: number;
|
|
48
48
|
eventSeq: number;
|
|
49
49
|
blockIndex: number;
|
|
50
50
|
kind: "user" | "assistant.reasoning" | "assistant.response";
|
|
@@ -91,8 +91,9 @@ declare function retryableTurns(turns: readonly ClosedTurn[]): RetryableTurn[];
|
|
|
91
91
|
*/
|
|
92
92
|
declare function precedingContentIndex(turns: readonly ClosedTurn[], turnIndex: number): number;
|
|
93
93
|
declare function downstreamUsers(turns: readonly ClosedTurn[], start: number): UserMessage[];
|
|
94
|
+
declare function recallBoundary(events: readonly SessionEvent[], turns: readonly ClosedTurn[], eventSeq: number): number;
|
|
94
95
|
declare function editPlan(operation: EditOperation, turns: readonly ClosedTurn[]): OperationPlan;
|
|
95
96
|
declare function retryPlan(operation: RetryOperation, turns: readonly ClosedTurn[]): OperationPlan;
|
|
96
97
|
declare function rerollPlan(operation: RerollOperation, turns: readonly ClosedTurn[]): OperationPlan;
|
|
97
98
|
//#endregion
|
|
98
|
-
export {
|
|
99
|
+
export { RetryableTurn as _, editPlan as a, SessionEditorResult as b, recallBoundary as c, retryableTurns as d, EditOperation as f, RetryOperation as g, RerollOperation as h, downstreamUsers as i, rerollPlan as l, RecallOperation as m, OperationPlan as n, editableMessages as o, EditableMessageBlock as p, closedTurns as r, precedingContentIndex as s, ClosedTurn as t, retryPlan as u, RewindOperation as v, SessionEditorOperation as y };
|
package/dist/plan.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as editPlan, c as
|
|
2
|
-
export { ClosedTurn, OperationPlan, closedTurns, downstreamUsers, editPlan, editableMessages, precedingContentIndex, rerollPlan, retryPlan, retryableTurns };
|
|
1
|
+
import { a as editPlan, c as recallBoundary, d as retryableTurns, i as downstreamUsers, l as rerollPlan, n as OperationPlan, o as editableMessages, r as closedTurns, s as precedingContentIndex, t as ClosedTurn, u as retryPlan } from "./plan-BSa7k9HO.mjs";
|
|
2
|
+
export { ClosedTurn, OperationPlan, closedTurns, downstreamUsers, editPlan, editableMessages, precedingContentIndex, recallBoundary, rerollPlan, retryPlan, retryableTurns };
|
package/dist/plan.mjs
CHANGED
|
@@ -118,6 +118,19 @@ function precedingContentIndex(turns, turnIndex) {
|
|
|
118
118
|
function downstreamUsers(turns, start) {
|
|
119
119
|
return turns.slice(start).flatMap((turn) => turn.users.map((user) => cloneUser(user.data)));
|
|
120
120
|
}
|
|
121
|
+
function recallBoundary(events, turns, eventSeq) {
|
|
122
|
+
const turnIndex = turns.findIndex((turn) => eventSeq > turn.startSeq && (turn.endSeq === void 0 || eventSeq < turn.endSeq));
|
|
123
|
+
const turn = turns[turnIndex];
|
|
124
|
+
if (turn === void 0) {
|
|
125
|
+
const target = events.find((event) => event.seq === eventSeq);
|
|
126
|
+
if (target?.type !== "user/message" || target.data.source.kind !== "user") throw new SessionBranchError("所选消息不属于已落定回合。", "INVALID_BOUNDARY");
|
|
127
|
+
return eventSeq;
|
|
128
|
+
}
|
|
129
|
+
const userIndex = turn.users.findIndex((user) => user.seq === eventSeq);
|
|
130
|
+
if (userIndex === -1) throw new SessionBranchError("所选消息不存在或不可撤回。", "INVALID_BOUNDARY");
|
|
131
|
+
const preceding = precedingContentIndex(turns, turnIndex);
|
|
132
|
+
return userIndex === 0 ? preceding < 0 ? -1 : turns[preceding].endSeq : eventSeq;
|
|
133
|
+
}
|
|
121
134
|
function assistantReplacement(event, blockIndex, text) {
|
|
122
135
|
const replaced = replaceTextBlock(event.data.message.content, blockIndex, text).filter((block) => block.type === "text" || block.type === "reasoning");
|
|
123
136
|
return Object.freeze({
|
|
@@ -237,4 +250,4 @@ function rerollPlan(operation, turns) {
|
|
|
237
250
|
throw new SessionBranchError("当前会话没有可重生成的已落定助手回复。", "INVALID_BOUNDARY");
|
|
238
251
|
}
|
|
239
252
|
//#endregion
|
|
240
|
-
export { closedTurns, downstreamUsers, editPlan, editableMessages, precedingContentIndex, rerollPlan, retryPlan, retryableTurns };
|
|
253
|
+
export { closedTurns, downstreamUsers, editPlan, editableMessages, precedingContentIndex, recallBoundary, rerollPlan, retryPlan, retryableTurns };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { closedTurns, editPlan, editableMessages, precedingContentIndex, rerollPlan, retryPlan, retryableTurns } from "./plan.mjs";
|
|
1
|
+
import { closedTurns, editPlan, editableMessages, precedingContentIndex, recallBoundary, rerollPlan, retryPlan, retryableTurns } from "./plan.mjs";
|
|
2
2
|
import { Service } from "@deepseek-ai/cordis";
|
|
3
3
|
import { SessionSeq } from "@deepseek-ai/dsh-session";
|
|
4
4
|
import { SESSION_BRANCH_VERSION_SCHEMA as SESSION_BRANCH_VERSION_SCHEMA$1, SessionBranchError, balanceRewindPrefix } from "@morlay/session-branch";
|
|
@@ -144,7 +144,8 @@ var SessionEditor = class extends Service {
|
|
|
144
144
|
...meta === void 0 ? {} : { meta }
|
|
145
145
|
}, signal);
|
|
146
146
|
}
|
|
147
|
-
rewind(id, toBoundary, signal) {
|
|
147
|
+
async rewind(id, toBoundary, signal) {
|
|
148
|
+
await this.stopLoop(id, signal);
|
|
148
149
|
return this.ctx.sessionBranch.rewind(id, toBoundary, signal);
|
|
149
150
|
}
|
|
150
151
|
timeline(sessionId, signal) {
|
|
@@ -187,6 +188,7 @@ var SessionEditor = class extends Service {
|
|
|
187
188
|
turn: replayTurn
|
|
188
189
|
});
|
|
189
190
|
const replay = await this.prepareReplay(operation.sessionId, plan.queuedUsers, signal, headerConfig);
|
|
191
|
+
await this.stopLoop(operation.sessionId, signal);
|
|
190
192
|
const live = this.ctx.sessions.get(operation.sessionId);
|
|
191
193
|
await this.ctx.sessionBranch.rewind(operation.sessionId, boundary, signal);
|
|
192
194
|
if (seedSuffix.length > 0) {
|
|
@@ -232,22 +234,15 @@ var SessionEditor = class extends Service {
|
|
|
232
234
|
* 撤回(recall):只 rewind 截断,不重写、不重放——被撤回的 user 消息文本
|
|
233
235
|
* 由客户端回填到输入框,交给用户修改后重新发送。
|
|
234
236
|
*
|
|
235
|
-
*
|
|
236
|
-
* (无前轮则 -1),轮内 followup
|
|
237
|
-
*
|
|
237
|
+
* 边界见 {@link recallBoundary}:轮首消息整轮截断到前一轮 `turn/end`
|
|
238
|
+
* (无前轮则 -1),轮内 followup 与轮外 user 消息只截断到该消息本身
|
|
239
|
+
* (exclusive drop),避免留下悬空 `turn/start` 让下一次发送开到错误轮号。
|
|
238
240
|
*/
|
|
239
241
|
async recallOperation(operation, signal) {
|
|
240
242
|
signal?.throwIfAborted();
|
|
241
243
|
const events = await this.readEvents(operation.sessionId, signal);
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
const turn = turns[turnIndex];
|
|
245
|
-
if (turn === void 0) throw new SessionBranchError("所选消息不属于已落定回合。", "INVALID_BOUNDARY");
|
|
246
|
-
const userIndex = turn.users.findIndex((user) => user.seq === operation.eventSeq);
|
|
247
|
-
if (userIndex === -1) throw new SessionBranchError("所选消息不存在或不可撤回。", "INVALID_BOUNDARY");
|
|
248
|
-
const preceding = precedingContentIndex(turns, turnIndex);
|
|
249
|
-
const boundary = userIndex === 0 ? preceding < 0 ? -1 : turns[preceding].endSeq : operation.eventSeq;
|
|
250
|
-
await this.prepareIdle(operation.sessionId, signal);
|
|
244
|
+
const boundary = recallBoundary(events, closedTurns(events), operation.eventSeq);
|
|
245
|
+
await this.stopLoop(operation.sessionId, signal);
|
|
251
246
|
await this.ctx.sessionBranch.rewind(operation.sessionId, boundary, signal);
|
|
252
247
|
return {
|
|
253
248
|
sessionId: operation.sessionId,
|
|
@@ -255,11 +250,11 @@ var SessionEditor = class extends Service {
|
|
|
255
250
|
live: this.ctx.sessions.get(operation.sessionId) !== void 0
|
|
256
251
|
};
|
|
257
252
|
}
|
|
258
|
-
|
|
259
|
-
async prepareIdle(sessionId, signal) {
|
|
253
|
+
async stopLoop(sessionId, signal) {
|
|
260
254
|
signal?.throwIfAborted();
|
|
261
255
|
const existing = this.ctx.get("agents")?.get(sessionId);
|
|
262
256
|
if (existing === void 0) return;
|
|
257
|
+
existing.cancel?.({ kind: "user" }, { keepInbox: true });
|
|
263
258
|
await existing.whenIdle();
|
|
264
259
|
signal?.throwIfAborted();
|
|
265
260
|
}
|
|
@@ -268,23 +263,13 @@ var SessionEditor = class extends Service {
|
|
|
268
263
|
if (live !== void 0) return live.snapshotEvents();
|
|
269
264
|
return (await this.ctx.sessionBranch.readRawEvents(sessionId, signal)).events;
|
|
270
265
|
}
|
|
271
|
-
/**
|
|
272
|
-
* rewind 前确保 agent 可驱动重放:live agent 先等待其停下(rewind 会截断其
|
|
273
|
-
* session 内存 log,须在 quiescence 后执行);cold 会话先 resume 出驻留
|
|
274
|
-
* agent(此时会话完整,resume 的 prepare 不与截断冲突)。agents 服务缺失或
|
|
275
|
-
* 无需重放时返回空——调用方退化为就地截断版本。
|
|
276
|
-
*/
|
|
277
266
|
async prepareReplay(sessionId, queuedUsers, signal, headerConfig) {
|
|
278
267
|
if (queuedUsers.length === 0) return { agent: void 0 };
|
|
279
268
|
signal?.throwIfAborted();
|
|
280
269
|
const agents = this.ctx.get("agents");
|
|
281
270
|
if (agents === void 0) return { agent: void 0 };
|
|
282
271
|
const existing = agents.get(sessionId);
|
|
283
|
-
if (existing !== void 0) {
|
|
284
|
-
await existing.whenIdle();
|
|
285
|
-
signal?.throwIfAborted();
|
|
286
|
-
return { agent: existing };
|
|
287
|
-
}
|
|
272
|
+
if (existing !== void 0) return { agent: existing };
|
|
288
273
|
const provider = headerConfig?.provider ?? "";
|
|
289
274
|
const model = headerConfig?.model ?? "";
|
|
290
275
|
if (provider.length === 0 || model.length === 0) {
|
|
@@ -511,4 +496,4 @@ function jsonResponse(status, value) {
|
|
|
511
496
|
});
|
|
512
497
|
}
|
|
513
498
|
//#endregion
|
|
514
|
-
export { SessionEditor as n, SESSION_BRANCH_VERSION_SCHEMA$1 as t };
|
|
499
|
+
export { SessionEditor as n, SESSION_EDITOR_PATH as r, SESSION_BRANCH_VERSION_SCHEMA$1 as t };
|