@1agents/session-reader 0.7.1 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/dist/src/dsh/adapter.js +34 -44
- package/dist/src/dsh/client.js +91 -54
- package/dist/src/dsh/copy.d.ts +26 -0
- package/dist/src/dsh/copy.js +19 -0
- package/dist/src/dsh/open.d.ts +56 -0
- package/dist/src/dsh/open.js +40 -0
- package/dist/src/dsh/plugin.js +44 -222
- package/dist/src/resolver.d.ts +1 -1
- package/dist/src/resolver.js +3 -1
- package/dist/src/web/app.js +90 -53
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -473,6 +473,28 @@ $ 1session graph ca8325e1
|
|
|
473
473
|
|
|
474
474
|
运行时捕获:若环境注入了 `SESSION_READER_CALLER_SESSION`,调用当下就直接落边(`observed / runtime:caller-env`),无需事后从历史里恢复。
|
|
475
475
|
|
|
476
|
+
## DSH 插件入口
|
|
477
|
+
|
|
478
|
+
从 npm 安装到 DSH Web profile(包内声明了 `dsh.bundle.patch`,安装后自动启用):
|
|
479
|
+
|
|
480
|
+
```sh
|
|
481
|
+
pnpm dsh plugin --profile web add @1agents/session-reader @1agents/dsh-acp
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
只查看历史时可只安装 `@1agents/session-reader`;插件按请求查询 ACP 服务,不把 ACP 注册为加载依赖。已安装独立 `dsh` 命令时省略开头的 `pnpm`;桌面版将 profile 改为 `desktop`。更新包后重启对应的 DSH 服务。原会话续聊还需单独启动 `@1agents/acp-service@^0.2.0`,具体配置见 [ACP 插件说明](https://github.com/scottzx/1acp/tree/main/packages/dsh-plugin)。
|
|
485
|
+
|
|
486
|
+
DSH 侧栏中的「历史会话」打开跨 Agent 会话浏览器。入口沿用「插件」「自动化任务」的主文字色、字号、行高、圆角、悬停与键盘焦点样式;展开时使用 16px 图标,折叠时使用 18px 图标和 36px 按钮,颜色跟随 DSH 的亮暗主题。
|
|
487
|
+
|
|
488
|
+
### 在 DSH 中继续原会话
|
|
489
|
+
|
|
490
|
+
安装并启用 `@1agents/dsh-acp`,启动其配置的 acp-service 后,Claude、Codex、Grok 的历史详情提供「在 DSH 中继续原会话」。session-reader 读取历史并调用 ACP 插件的 `oneagentsAcpSessions` 服务;1acp 恢复原 Agent 的原生会话,后续输入继续发给它。原工作目录必须存在;不会改用当前目录或新建空会话。DSH 来源直接打开原有 DSH 会话,不复制或重写日志。
|
|
491
|
+
|
|
492
|
+
ACP 未安装、Agent 未就绪或来源不支持时,历史仍可只读查看。导入、鉴权和恢复失败会显示原因并保留预览。重复导入打开同一个 DSH 会话;导入历史是一次性快照,不持续同步其他客户端新增的消息。旧版导入产生的历史副本不会自动改绑。外部工具调用与结果保留为只读历史记录,不触发 DSH 工具执行。
|
|
493
|
+
|
|
494
|
+
`session_open_in_dsh` 和 `POST /api/session-reader/open-in-dsh` 共用同一入口,接受 `sessionId`(ID、前缀或原始文件路径)和可选的 `provider`;provider 用于区分不同来源的同名 ID。外部成功结果包含 `dshSessionId`、`workspace`、`workspaceId`、`agent` 与 `continuation: "native"`,DSH 来源返回 `continuation: "dsh"`。`GET /api/session-reader/continuation?provider=codex` 返回续聊可用性及不可用原因。独立 CLI、解析库与历史搜索不需要安装 ACP 插件。
|
|
495
|
+
|
|
496
|
+
验证当前 DSH 事件兼容性:先运行 `npm run build`,再运行 `node scripts/check-dsh-import.mjs /absolute/path/DSH`(DSH checkout 须已构建)。`test/fixtures/dsh-import/` 保存用户消息、思考、工具调用与结果,以及未完成尾部的固定历史快照。
|
|
497
|
+
|
|
476
498
|
## `1session web`:独立浏览器
|
|
477
499
|
|
|
478
500
|
没有 DSH 也能看同一套历史会话列表、对话预览和文件账本:
|
package/dist/src/dsh/adapter.js
CHANGED
|
@@ -10,14 +10,16 @@ export function convertSessionToDshEvents(session, options = {}) {
|
|
|
10
10
|
let currentStep = 1;
|
|
11
11
|
let turnOpen = false;
|
|
12
12
|
let stepOpen = false;
|
|
13
|
+
let hasSystemHead = false;
|
|
13
14
|
const defaultTime = session.ref.createdAt
|
|
14
15
|
? Date.parse(session.ref.createdAt)
|
|
15
16
|
: Date.now();
|
|
17
|
+
const fallbackTime = Number.isFinite(defaultTime) ? defaultTime : Date.now();
|
|
16
18
|
function parseTime(timestamp) {
|
|
17
19
|
if (!timestamp)
|
|
18
|
-
return
|
|
20
|
+
return fallbackTime;
|
|
19
21
|
const parsed = Date.parse(timestamp);
|
|
20
|
-
return isNaN(parsed) ?
|
|
22
|
+
return isNaN(parsed) ? fallbackTime : parsed;
|
|
21
23
|
}
|
|
22
24
|
function emit(type, data, time, surfaceOp) {
|
|
23
25
|
events.push({
|
|
@@ -39,6 +41,13 @@ export function convertSessionToDshEvents(session, options = {}) {
|
|
|
39
41
|
if (!stepOpen) {
|
|
40
42
|
emit('step/start', { turn: turnNum, step: stepNum }, time);
|
|
41
43
|
stepOpen = true;
|
|
44
|
+
if (!hasSystemHead) {
|
|
45
|
+
emit('system/message', {
|
|
46
|
+
turn: turnNum, step: stepNum,
|
|
47
|
+
message: { id: `system-${session.ref.id}`, role: 'system', source: { kind: 'system-prompt' }, content: [] },
|
|
48
|
+
}, time, 'append');
|
|
49
|
+
hasSystemHead = true;
|
|
50
|
+
}
|
|
42
51
|
}
|
|
43
52
|
}
|
|
44
53
|
function closeStep(turnNum, stepNum, time) {
|
|
@@ -61,7 +70,7 @@ export function convertSessionToDshEvents(session, options = {}) {
|
|
|
61
70
|
title: session.ref.title.trim(),
|
|
62
71
|
messageSeqs: [],
|
|
63
72
|
source: { kind: 'user' },
|
|
64
|
-
},
|
|
73
|
+
}, fallbackTime);
|
|
65
74
|
}
|
|
66
75
|
for (let i = 0; i < session.turns.length; i++) {
|
|
67
76
|
const turnEvent = session.turns[i];
|
|
@@ -97,6 +106,7 @@ export function convertSessionToDshEvents(session, options = {}) {
|
|
|
97
106
|
source: { kind: 'model', provider: providerName, model: modelName },
|
|
98
107
|
},
|
|
99
108
|
stream: [
|
|
109
|
+
{ type: 'chunk', time: eventTime, chunk: { type: 'block-start', index: 0, blockType: 'reasoning' } },
|
|
100
110
|
{
|
|
101
111
|
type: 'chunk',
|
|
102
112
|
time: eventTime,
|
|
@@ -106,53 +116,31 @@ export function convertSessionToDshEvents(session, options = {}) {
|
|
|
106
116
|
text: thinkingText,
|
|
107
117
|
},
|
|
108
118
|
},
|
|
119
|
+
{ type: 'chunk', time: eventTime, chunk: { type: 'block-end', index: 0, block: { type: 'reasoning', text: thinkingText } } },
|
|
109
120
|
],
|
|
110
121
|
}, eventTime, 'append');
|
|
111
122
|
}
|
|
112
123
|
break;
|
|
113
124
|
}
|
|
114
|
-
case 'tool_call':
|
|
115
|
-
ensureStepStart(currentTurn, currentStep, eventTime);
|
|
116
|
-
const callId = turnEvent.id;
|
|
117
|
-
const toolName = turnEvent.toolName ?? 'tool';
|
|
118
|
-
const toolArgsStr = typeof turnEvent.toolArgs === 'string'
|
|
119
|
-
? turnEvent.toolArgs
|
|
120
|
-
: JSON.stringify(turnEvent.toolArgs ?? {});
|
|
121
|
-
emit('tool/call', {
|
|
122
|
-
turn: currentTurn,
|
|
123
|
-
step: currentStep,
|
|
124
|
-
callId,
|
|
125
|
-
name: toolName,
|
|
126
|
-
arguments: toolArgsStr,
|
|
127
|
-
}, eventTime);
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
125
|
+
case 'tool_call':
|
|
130
126
|
case 'tool_result': {
|
|
131
127
|
ensureStepStart(currentTurn, currentStep, eventTime);
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
...(turnEvent.isError ? { isError: true } : {}),
|
|
148
|
-
},
|
|
149
|
-
],
|
|
150
|
-
},
|
|
151
|
-
...(turnEvent.exitCode !== undefined ? { meta: { exitCode: turnEvent.exitCode } } : {}),
|
|
128
|
+
const label = turnEvent.kind === 'tool_call' ? 'External tool call' : 'External tool result';
|
|
129
|
+
const name = turnEvent.toolName ?? 'tool';
|
|
130
|
+
const detail = turnEvent.kind === 'tool_call' ? JSON.stringify(turnEvent.toolArgs ?? {}) : turnEvent.toolResult ?? '';
|
|
131
|
+
const status = `${turnEvent.isError ? ' · error' : ''}${turnEvent.exitCode !== undefined ? ` · exit ${turnEvent.exitCode}` : ''}`;
|
|
132
|
+
const text = `[${label} · ${name}${status}]\n${detail}`;
|
|
133
|
+
// Normalized providers do not preserve reliable call/result identities.
|
|
134
|
+
// External activity remains read-only transcript, as in live ACP output.
|
|
135
|
+
emit('assistant/message', {
|
|
136
|
+
turn: currentTurn, step: currentStep,
|
|
137
|
+
message: { id: `external-${i}-${turnEvent.id}`, role: 'assistant', source: { kind: 'model', provider: providerName, model: modelName }, content: [{ type: 'reasoning', text }] },
|
|
138
|
+
stream: [
|
|
139
|
+
{ type: 'chunk', time: eventTime, chunk: { type: 'block-start', index: 0, blockType: 'reasoning' } },
|
|
140
|
+
{ type: 'reasoning-chunks', time0: eventTime, index: 0, dt: [], texts: [text] },
|
|
141
|
+
{ type: 'chunk', time: eventTime, chunk: { type: 'block-end', index: 0, block: { type: 'reasoning', text } } },
|
|
142
|
+
],
|
|
152
143
|
}, eventTime, 'append');
|
|
153
|
-
// Tool execution concludes a step; next action starts a new step
|
|
154
|
-
closeStep(currentTurn, currentStep, eventTime);
|
|
155
|
-
currentStep++;
|
|
156
144
|
break;
|
|
157
145
|
}
|
|
158
146
|
case 'assistant': {
|
|
@@ -168,6 +156,7 @@ export function convertSessionToDshEvents(session, options = {}) {
|
|
|
168
156
|
source: { kind: 'model', provider: providerName, model: modelName },
|
|
169
157
|
},
|
|
170
158
|
stream: [
|
|
159
|
+
{ type: 'chunk', time: eventTime, chunk: { type: 'block-start', index: 0, blockType: 'text' } },
|
|
171
160
|
{
|
|
172
161
|
type: 'chunk',
|
|
173
162
|
time: eventTime,
|
|
@@ -177,6 +166,7 @@ export function convertSessionToDshEvents(session, options = {}) {
|
|
|
177
166
|
text,
|
|
178
167
|
},
|
|
179
168
|
},
|
|
169
|
+
{ type: 'chunk', time: eventTime, chunk: { type: 'block-end', index: 0, block: { type: 'text', text } } },
|
|
180
170
|
],
|
|
181
171
|
}, eventTime, 'append');
|
|
182
172
|
const nextEvent = session.turns[i + 1];
|
|
@@ -191,8 +181,8 @@ export function convertSessionToDshEvents(session, options = {}) {
|
|
|
191
181
|
}
|
|
192
182
|
// Final cleanup if the last turn was still open
|
|
193
183
|
if (turnOpen) {
|
|
194
|
-
const lastTime = events.length > 0 ? events[events.length - 1].time :
|
|
195
|
-
closeTurn(currentTurn, lastTime);
|
|
184
|
+
const lastTime = events.length > 0 ? events[events.length - 1].time : fallbackTime;
|
|
185
|
+
closeTurn(currentTurn, lastTime, 'interrupted');
|
|
196
186
|
}
|
|
197
187
|
return events;
|
|
198
188
|
}
|
package/dist/src/dsh/client.js
CHANGED
|
@@ -18080,54 +18080,89 @@ function mountConversation(container, options = {}) {
|
|
|
18080
18080
|
};
|
|
18081
18081
|
}
|
|
18082
18082
|
|
|
18083
|
+
// src/dsh/copy.ts
|
|
18084
|
+
var continuationDictionaries = {
|
|
18085
|
+
zh: {
|
|
18086
|
+
continue: "\u5728 DSH \u4E2D\u7EE7\u7EED\u539F\u4F1A\u8BDD",
|
|
18087
|
+
openDsh: "\u5728 DSH \u4E2D\u6253\u5F00",
|
|
18088
|
+
readOnly: "\u53EA\u8BFB\u67E5\u770B",
|
|
18089
|
+
loading: "\u6B63\u5728\u6062\u590D\u539F\u4F1A\u8BDD\u2026",
|
|
18090
|
+
failed: "\u7EED\u804A\u5931\u8D25\uFF1A",
|
|
18091
|
+
unavailable: "\u6682\u65F6\u65E0\u6CD5\u68C0\u67E5\u7EED\u804A\u72B6\u6001",
|
|
18092
|
+
navigationUnavailable: "DSH \u4F1A\u8BDD\u5BFC\u822A\u4E0D\u53EF\u7528\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u5386\u53F2\u9762\u677F"
|
|
18093
|
+
},
|
|
18094
|
+
en: {
|
|
18095
|
+
continue: "Continue original session in DSH",
|
|
18096
|
+
openDsh: "Open in DSH",
|
|
18097
|
+
readOnly: "Read only",
|
|
18098
|
+
loading: "Restoring session\u2026",
|
|
18099
|
+
failed: "Could not continue: ",
|
|
18100
|
+
unavailable: "Continuation status is unavailable",
|
|
18101
|
+
navigationUnavailable: "DSH navigation is unavailable; reopen the history panel"
|
|
18102
|
+
}
|
|
18103
|
+
};
|
|
18104
|
+
function continuationCopy(translate) {
|
|
18105
|
+
if (translate) return Object.fromEntries(Object.keys(continuationDictionaries.zh).map((key) => [key, translate(key)]));
|
|
18106
|
+
const locale = typeof document === "undefined" ? "zh" : document.documentElement.lang || navigator.language;
|
|
18107
|
+
return locale.startsWith("zh") ? continuationDictionaries.zh : continuationDictionaries.en;
|
|
18108
|
+
}
|
|
18109
|
+
|
|
18083
18110
|
// src/dsh/client.ts
|
|
18084
18111
|
var CSS_TEXT = `
|
|
18112
|
+
/* Match DSH SidebarRoot panel rows in expanded and collapsed layouts. */
|
|
18085
18113
|
[data-dsh-session-reader-entry] {
|
|
18086
18114
|
box-sizing: border-box;
|
|
18087
|
-
|
|
18088
|
-
height: 36px;
|
|
18089
|
-
|
|
18115
|
+
flex: none;
|
|
18116
|
+
min-height: 36px;
|
|
18117
|
+
margin: 0 2px 4px;
|
|
18118
|
+
color: var(--dsw-alias-label-primary);
|
|
18090
18119
|
cursor: pointer;
|
|
18091
18120
|
white-space: nowrap;
|
|
18092
18121
|
background: transparent;
|
|
18093
18122
|
border: none;
|
|
18094
|
-
border-radius:
|
|
18123
|
+
border-radius: var(--dsw-radius-md);
|
|
18095
18124
|
align-items: center;
|
|
18096
18125
|
gap: 8px;
|
|
18097
|
-
padding:
|
|
18098
|
-
font
|
|
18126
|
+
padding: 7px 8px;
|
|
18127
|
+
font: inherit;
|
|
18128
|
+
line-height: 22px;
|
|
18129
|
+
text-align: left;
|
|
18099
18130
|
display: flex;
|
|
18100
|
-
transition: background 0.15s ease, color 0.15s ease;
|
|
18101
18131
|
}
|
|
18102
18132
|
[data-dsh-session-reader-entry]:hover {
|
|
18103
|
-
background: var(--dsw-alias-interactive-bg-hover
|
|
18104
|
-
color: var(--dsw-alias-label-primary, #111);
|
|
18133
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
18105
18134
|
}
|
|
18106
|
-
|
|
18107
|
-
|
|
18108
|
-
|
|
18135
|
+
[data-dsh-session-reader-entry]:focus-visible {
|
|
18136
|
+
outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
18137
|
+
outline-offset: -2px;
|
|
18109
18138
|
}
|
|
18110
18139
|
[data-dsh-session-reader-entry] .sr-icon {
|
|
18111
18140
|
flex: none;
|
|
18112
18141
|
display: inline-flex;
|
|
18113
18142
|
align-items: center;
|
|
18114
18143
|
justify-content: center;
|
|
18115
|
-
|
|
18116
|
-
|
|
18144
|
+
}
|
|
18145
|
+
[data-dsh-session-reader-entry] .sr-icon svg {
|
|
18146
|
+
width: 16px;
|
|
18147
|
+
height: 16px;
|
|
18117
18148
|
}
|
|
18118
18149
|
[data-dsh-session-reader-entry] .sr-label {
|
|
18150
|
+
min-width: 0;
|
|
18119
18151
|
text-overflow: ellipsis;
|
|
18120
18152
|
overflow: hidden;
|
|
18121
18153
|
}
|
|
18122
18154
|
[data-dsh-frame][data-sidebar-collapsed] [data-dsh-session-reader-entry],
|
|
18123
18155
|
[data-sidebar-collapsed] [data-dsh-session-reader-entry] {
|
|
18124
|
-
border-radius: 50%;
|
|
18125
18156
|
justify-content: center;
|
|
18126
18157
|
width: 36px;
|
|
18127
18158
|
height: 36px;
|
|
18128
|
-
margin: 0
|
|
18159
|
+
margin: 0 0 12px;
|
|
18129
18160
|
padding: 0;
|
|
18130
18161
|
}
|
|
18162
|
+
[data-sidebar-collapsed] [data-dsh-session-reader-entry] .sr-icon svg {
|
|
18163
|
+
width: 18px;
|
|
18164
|
+
height: 18px;
|
|
18165
|
+
}
|
|
18131
18166
|
[data-dsh-frame][data-sidebar-collapsed] [data-dsh-session-reader-entry] .sr-label,
|
|
18132
18167
|
[data-sidebar-collapsed] [data-dsh-session-reader-entry] .sr-label {
|
|
18133
18168
|
display: none;
|
|
@@ -18497,7 +18532,7 @@ body[data-ds-dark-theme] .sr-tab-btn.active {
|
|
|
18497
18532
|
}
|
|
18498
18533
|
`;
|
|
18499
18534
|
var ICON_SVG = `<svg viewBox="0 0 16 16" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="8" cy="8" r="6.2"/><polyline points="8 4.2 8 8 10.5 9.5"/></svg>`;
|
|
18500
|
-
var inject = ["sessions", "workspaces"];
|
|
18535
|
+
var inject = ["sessions", "workspaces", "uiWorkspace", "locale"];
|
|
18501
18536
|
function getBoot() {
|
|
18502
18537
|
if (typeof window === "undefined") return {};
|
|
18503
18538
|
return window.__SR_UI__ ?? {};
|
|
@@ -18507,6 +18542,9 @@ function isStandalone() {
|
|
|
18507
18542
|
}
|
|
18508
18543
|
function apply(_ctx) {
|
|
18509
18544
|
if (typeof document === "undefined") return;
|
|
18545
|
+
if (_ctx.locale) _ctx.effect(() => _ctx.locale.register("sessionReaderContinuation", continuationDictionaries));
|
|
18546
|
+
const translate = _ctx.locale?.bind("sessionReaderContinuation");
|
|
18547
|
+
const getContinuationCopy = () => continuationCopy(translate);
|
|
18510
18548
|
if (!document.getElementById("dsh-session-reader-css")) {
|
|
18511
18549
|
const style = document.createElement("style");
|
|
18512
18550
|
style.id = "dsh-session-reader-css";
|
|
@@ -18586,48 +18624,35 @@ function apply(_ctx) {
|
|
|
18586
18624
|
cachedSessions = [];
|
|
18587
18625
|
currentTab = "chat";
|
|
18588
18626
|
}
|
|
18589
|
-
async function openInDshChat(sessionId, btn) {
|
|
18627
|
+
async function openInDshChat(sessionId, btn, provider) {
|
|
18628
|
+
const copy = getContinuationCopy();
|
|
18629
|
+
const label = btn?.textContent;
|
|
18590
18630
|
if (btn) {
|
|
18591
|
-
btn.
|
|
18631
|
+
btn.textContent = copy.loading;
|
|
18592
18632
|
btn.disabled = true;
|
|
18593
18633
|
}
|
|
18594
18634
|
try {
|
|
18595
|
-
const activeWs = getActiveWorkspaceInfo();
|
|
18596
18635
|
const res = await fetch("/api/session-reader/open-in-dsh", {
|
|
18597
18636
|
method: "POST",
|
|
18598
18637
|
headers: { "Content-Type": "application/json" },
|
|
18599
|
-
body: JSON.stringify({
|
|
18600
|
-
sessionId,
|
|
18601
|
-
currentCwd: activeWs.cwd,
|
|
18602
|
-
workspaceId: activeWs.workspaceId
|
|
18603
|
-
})
|
|
18638
|
+
body: JSON.stringify({ sessionId, provider })
|
|
18604
18639
|
});
|
|
18605
18640
|
const result = await res.json();
|
|
18606
|
-
if (!res.ok || !result.success)
|
|
18607
|
-
alert("\u63A5\u5165 DSH \u5931\u8D25: " + (result.error || res.statusText));
|
|
18608
|
-
if (btn) {
|
|
18609
|
-
btn.innerHTML = "\u{1F680} \u5728 DSH \u5B98\u65B9 Chat \u4E2D\u6253\u5F00";
|
|
18610
|
-
btn.disabled = false;
|
|
18611
|
-
}
|
|
18612
|
-
return;
|
|
18613
|
-
}
|
|
18614
|
-
const dshSessionId = result.dshSessionId;
|
|
18615
|
-
closePanel();
|
|
18641
|
+
if (!res.ok || !result.success) throw new Error(result.error || res.statusText);
|
|
18616
18642
|
const sessions = dshCtx?.get ? dshCtx.get("sessions") ?? dshCtx.sessions : dshCtx?.sessions;
|
|
18617
|
-
|
|
18618
|
-
|
|
18619
|
-
|
|
18620
|
-
|
|
18621
|
-
|
|
18622
|
-
sessions.open(dshSessionId);
|
|
18623
|
-
}
|
|
18624
|
-
} else {
|
|
18625
|
-
console.warn("[session-reader] Could not obtain DSH sessions service from ctx");
|
|
18626
|
-
}
|
|
18643
|
+
const navigation = dshCtx?.get ? dshCtx.get("uiWorkspace") : dshCtx?.uiWorkspace;
|
|
18644
|
+
if (!navigation) throw new Error(copy.navigationUnavailable);
|
|
18645
|
+
await sessions.refresh();
|
|
18646
|
+
navigation.openSession(result.dshSessionId);
|
|
18647
|
+
closePanel();
|
|
18627
18648
|
} catch (err) {
|
|
18628
|
-
|
|
18649
|
+
if (activeSessionData) {
|
|
18650
|
+
activeSessionData.continuationError = copy.failed + (err?.message || err);
|
|
18651
|
+
renderModalBody();
|
|
18652
|
+
}
|
|
18653
|
+
} finally {
|
|
18629
18654
|
if (btn) {
|
|
18630
|
-
btn.
|
|
18655
|
+
btn.textContent = label ?? copy.continue;
|
|
18631
18656
|
btn.disabled = false;
|
|
18632
18657
|
}
|
|
18633
18658
|
}
|
|
@@ -18658,6 +18683,19 @@ function apply(_ctx) {
|
|
|
18658
18683
|
const data = await res.json();
|
|
18659
18684
|
if (detailAbort !== ac) return;
|
|
18660
18685
|
activeSessionData = data;
|
|
18686
|
+
renderModalBody();
|
|
18687
|
+
if (!isStandalone()) {
|
|
18688
|
+
try {
|
|
18689
|
+
const status = await fetch(`/api/session-reader/continuation?provider=${encodeURIComponent(data.ref.provider)}`, { signal: ac.signal, cache: "no-store" });
|
|
18690
|
+
if (!status.ok) throw new Error(`HTTP ${status.status}`);
|
|
18691
|
+
const continuation = await status.json();
|
|
18692
|
+
if (detailAbort !== ac) return;
|
|
18693
|
+
activeSessionData.continuation = continuation;
|
|
18694
|
+
} catch (error) {
|
|
18695
|
+
if (detailAbort !== ac) return;
|
|
18696
|
+
activeSessionData.continuation = { available: false, reason: String(error) };
|
|
18697
|
+
}
|
|
18698
|
+
}
|
|
18661
18699
|
} catch (err) {
|
|
18662
18700
|
if (err?.name === "AbortError") {
|
|
18663
18701
|
if (detailAbort === ac) {
|
|
@@ -18749,9 +18787,6 @@ function apply(_ctx) {
|
|
|
18749
18787
|
item.classList.add("active");
|
|
18750
18788
|
loadSessionDetail(s3.id);
|
|
18751
18789
|
});
|
|
18752
|
-
item.addEventListener("dblclick", () => {
|
|
18753
|
-
openInDshChat(s3.id);
|
|
18754
|
-
});
|
|
18755
18790
|
listEl.appendChild(item);
|
|
18756
18791
|
});
|
|
18757
18792
|
}
|
|
@@ -18770,7 +18805,8 @@ function apply(_ctx) {
|
|
|
18770
18805
|
contentEl.innerHTML = `<div style="padding:60px;text-align:center;color:#e11d48;font-size:13px;">\u52A0\u8F7D\u5931\u8D25: ${activeSessionData.error}</div>`;
|
|
18771
18806
|
return;
|
|
18772
18807
|
}
|
|
18773
|
-
const { ref, overview, turns, files } = activeSessionData;
|
|
18808
|
+
const { ref, overview, turns, files, continuation, continuationError } = activeSessionData;
|
|
18809
|
+
const copy = getContinuationCopy();
|
|
18774
18810
|
const standalone = isStandalone();
|
|
18775
18811
|
const body = currentTab === "chat" ? '<div id="sr-chat-root" style="width:100%;height:100%;display:flex;flex-direction:column;overflow:hidden;"></div>' : currentTab === "files" ? renderFilesView(files) : renderOverviewView(overview);
|
|
18776
18812
|
contentEl.innerHTML = `
|
|
@@ -18780,8 +18816,8 @@ function apply(_ctx) {
|
|
|
18780
18816
|
${escapeHtml2(ref.title || "Untitled")}
|
|
18781
18817
|
</div>
|
|
18782
18818
|
<div class="sr-header-actions">
|
|
18783
|
-
${standalone ? "" : `<button class="sr-open-dsh-btn" id="sr-btn-open-dsh"
|
|
18784
|
-
|
|
18819
|
+
${standalone ? "" : `<button class="sr-open-dsh-btn" id="sr-btn-open-dsh" ${continuation?.available ? "" : "disabled"}>
|
|
18820
|
+
${escapeHtml2(continuation?.available ? (ref.provider === "dsh" ? copy.openDsh : copy.continue) + " \xB7 " + (continuation.agent || ref.provider) : copy.readOnly)}
|
|
18785
18821
|
</button>`}
|
|
18786
18822
|
<div class="sr-tabs">
|
|
18787
18823
|
<button class="sr-tab-btn ${currentTab === "chat" ? "active" : ""}" data-tab="chat">\u{1F4AC} \u5FEB\u901F\u9884\u89C8</button>
|
|
@@ -18790,6 +18826,7 @@ function apply(_ctx) {
|
|
|
18790
18826
|
</div>
|
|
18791
18827
|
</div>
|
|
18792
18828
|
</div>
|
|
18829
|
+
${!standalone && (continuationError || !continuation?.available) ? `<div class="sr-header-path" role="status">${escapeHtml2(continuationError || continuation?.reason || copy.unavailable)}</div>` : ""}
|
|
18793
18830
|
<div class="sr-header-path" title="${escapeHtml2(ref.path || "")}">
|
|
18794
18831
|
<span class="sr-header-provider">${ref.provider}</span> \xB7 \u8DEF\u5F84: ${escapeHtml2(ref.path || "")}
|
|
18795
18832
|
</div>
|
|
@@ -18801,7 +18838,7 @@ function apply(_ctx) {
|
|
|
18801
18838
|
const openDshBtn = contentEl.querySelector("#sr-btn-open-dsh");
|
|
18802
18839
|
if (openDshBtn) {
|
|
18803
18840
|
openDshBtn.addEventListener("click", () => {
|
|
18804
|
-
openInDshChat(activeSessionId, openDshBtn);
|
|
18841
|
+
openInDshChat(ref.path || activeSessionId, openDshBtn, ref.provider);
|
|
18805
18842
|
});
|
|
18806
18843
|
}
|
|
18807
18844
|
contentEl.querySelectorAll(".sr-tab-btn").forEach((btn) => {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Copy for native continuation, shared by the embedded browser entry. */
|
|
2
|
+
export declare const continuationDictionaries: {
|
|
3
|
+
zh: {
|
|
4
|
+
continue: string;
|
|
5
|
+
openDsh: string;
|
|
6
|
+
readOnly: string;
|
|
7
|
+
loading: string;
|
|
8
|
+
failed: string;
|
|
9
|
+
unavailable: string;
|
|
10
|
+
navigationUnavailable: string;
|
|
11
|
+
};
|
|
12
|
+
en: {
|
|
13
|
+
continue: string;
|
|
14
|
+
openDsh: string;
|
|
15
|
+
readOnly: string;
|
|
16
|
+
loading: string;
|
|
17
|
+
failed: string;
|
|
18
|
+
unavailable: string;
|
|
19
|
+
navigationUnavailable: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
type Copy = {
|
|
23
|
+
[K in keyof typeof continuationDictionaries.zh]: string;
|
|
24
|
+
};
|
|
25
|
+
export declare function continuationCopy(translate?: (key: keyof Copy) => string): Copy;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Copy for native continuation, shared by the embedded browser entry. */
|
|
2
|
+
export const continuationDictionaries = {
|
|
3
|
+
zh: {
|
|
4
|
+
continue: '在 DSH 中继续原会话', openDsh: '在 DSH 中打开', readOnly: '只读查看',
|
|
5
|
+
loading: '正在恢复原会话…', failed: '续聊失败:', unavailable: '暂时无法检查续聊状态',
|
|
6
|
+
navigationUnavailable: 'DSH 会话导航不可用,请重新打开历史面板',
|
|
7
|
+
},
|
|
8
|
+
en: {
|
|
9
|
+
continue: 'Continue original session in DSH', openDsh: 'Open in DSH', readOnly: 'Read only',
|
|
10
|
+
loading: 'Restoring session…', failed: 'Could not continue: ', unavailable: 'Continuation status is unavailable',
|
|
11
|
+
navigationUnavailable: 'DSH navigation is unavailable; reopen the history panel',
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
export function continuationCopy(translate) {
|
|
15
|
+
if (translate)
|
|
16
|
+
return Object.fromEntries(Object.keys(continuationDictionaries.zh).map(key => [key, translate(key)]));
|
|
17
|
+
const locale = typeof document === 'undefined' ? 'zh' : document.documentElement.lang || navigator.language;
|
|
18
|
+
return locale.startsWith('zh') ? continuationDictionaries.zh : continuationDictionaries.en;
|
|
19
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** DSH admission delegates native execution to the installed ACP plugin. */
|
|
2
|
+
import type { AgentProvider, SessionRef } from '../types.js';
|
|
3
|
+
import { type DshSessionEvent } from './adapter.js';
|
|
4
|
+
export interface Availability {
|
|
5
|
+
available: boolean;
|
|
6
|
+
agent?: string;
|
|
7
|
+
reason?: string;
|
|
8
|
+
}
|
|
9
|
+
interface OpenResult {
|
|
10
|
+
success: true;
|
|
11
|
+
dshSessionId: string;
|
|
12
|
+
workspace?: string;
|
|
13
|
+
workspaceId?: string;
|
|
14
|
+
agent?: string;
|
|
15
|
+
continuation: 'native' | 'dsh';
|
|
16
|
+
}
|
|
17
|
+
interface AcpSessions {
|
|
18
|
+
availability(provider: string): Promise<Availability>;
|
|
19
|
+
importSession(input: {
|
|
20
|
+
provider: string;
|
|
21
|
+
nativeSessionId: string;
|
|
22
|
+
cwd: string;
|
|
23
|
+
events: DshSessionEvent[];
|
|
24
|
+
}): Promise<OpenResult>;
|
|
25
|
+
}
|
|
26
|
+
export interface OpenContext {
|
|
27
|
+
get(key: 'oneagentsAcpSessions'): AcpSessions | undefined;
|
|
28
|
+
sessionController?: {
|
|
29
|
+
resolveAgent(id: string): Promise<{
|
|
30
|
+
error: Error;
|
|
31
|
+
} | {
|
|
32
|
+
agent: {
|
|
33
|
+
session: {
|
|
34
|
+
header: {
|
|
35
|
+
cwd?: string;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
}>;
|
|
40
|
+
};
|
|
41
|
+
workspaceRegistry?: {
|
|
42
|
+
create(cwd: string): Promise<{
|
|
43
|
+
id: string;
|
|
44
|
+
attachSession(id: string): Promise<void>;
|
|
45
|
+
}>;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export interface OpenRequest {
|
|
49
|
+
sessionId: string;
|
|
50
|
+
provider?: AgentProvider;
|
|
51
|
+
}
|
|
52
|
+
/** Resolve the source once; missing/unsupported ACP leaves history readable without creating a DSH session. */
|
|
53
|
+
export declare function continuationAvailability(ctx: OpenContext, ref: Pick<SessionRef, 'provider'>): Promise<Availability>;
|
|
54
|
+
/** Open DSH's original identity or import native history through oneagentsAcpSessions; errors are never returned as success. */
|
|
55
|
+
export declare function openSessionInDsh(ctx: OpenContext, request: OpenRequest): Promise<OpenResult>;
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { resolveSession } from '../resolver.js';
|
|
2
|
+
import { convertSessionToDshEvents } from './adapter.js';
|
|
3
|
+
/** Resolve the source once; missing/unsupported ACP leaves history readable without creating a DSH session. */
|
|
4
|
+
export async function continuationAvailability(ctx, ref) {
|
|
5
|
+
if (ref.provider === 'dsh')
|
|
6
|
+
return { available: true, agent: 'dsh' };
|
|
7
|
+
const service = ctx.get?.('oneagentsAcpSessions');
|
|
8
|
+
if (!service)
|
|
9
|
+
return { available: false, reason: '续聊需要安装并启用 @1agents/dsh-acp;历史仍可只读查看' };
|
|
10
|
+
return service.availability(ref.provider);
|
|
11
|
+
}
|
|
12
|
+
/** Open DSH's original identity or import native history through oneagentsAcpSessions; errors are never returned as success. */
|
|
13
|
+
export async function openSessionInDsh(ctx, request) {
|
|
14
|
+
const resolved = await resolveSession(request.sessionId, request.provider);
|
|
15
|
+
if (!resolved)
|
|
16
|
+
throw new Error(`Session not found: ${request.sessionId}`);
|
|
17
|
+
const ref = resolved.ref;
|
|
18
|
+
if (ref.provider === 'dsh') {
|
|
19
|
+
if (!ctx.sessionController || !ctx.workspaceRegistry)
|
|
20
|
+
throw new Error('DSH session services are unavailable');
|
|
21
|
+
const id = ref.id.startsWith('session-') ? ref.id : `session-${ref.id}`;
|
|
22
|
+
const result = await ctx.sessionController.resolveAgent(id);
|
|
23
|
+
if ('error' in result)
|
|
24
|
+
throw result.error;
|
|
25
|
+
const cwd = result.agent.session.header.cwd;
|
|
26
|
+
if (!cwd)
|
|
27
|
+
throw new Error('DSH session has no workspace');
|
|
28
|
+
const workspace = await ctx.workspaceRegistry.create(cwd);
|
|
29
|
+
await workspace.attachSession(id);
|
|
30
|
+
return { success: true, dshSessionId: id, workspace: cwd, workspaceId: workspace.id, continuation: 'dsh' };
|
|
31
|
+
}
|
|
32
|
+
const available = await continuationAvailability(ctx, ref);
|
|
33
|
+
if (!available.available)
|
|
34
|
+
throw new Error(available.reason);
|
|
35
|
+
const normalized = await resolved.adapter.parse(resolved.candidate);
|
|
36
|
+
return ctx.get('oneagentsAcpSessions').importSession({
|
|
37
|
+
provider: normalized.ref.provider, nativeSessionId: normalized.ref.id,
|
|
38
|
+
cwd: normalized.ref.workspace ?? '', events: convertSessionToDshEvents(normalized),
|
|
39
|
+
});
|
|
40
|
+
}
|
package/dist/src/dsh/plugin.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
1
|
import { resolveSession, searchSessions, buildOverview, summarizeTurns, turnDetail, eventDetails, } from '../index.js';
|
|
3
2
|
import { handleSessionApi } from '../web/api.js';
|
|
4
|
-
import {
|
|
3
|
+
import { openSessionInDsh, continuationAvailability } from './open.js';
|
|
5
4
|
export const name = 'session-reader';
|
|
6
|
-
|
|
5
|
+
// ACP is queried through ctx.get so history browsing remains available without it.
|
|
6
|
+
export const inject = ['tools', 'webServer', 'sessionController', 'workspaceRegistry'];
|
|
7
7
|
export function apply(ctx) {
|
|
8
8
|
// 1. Register tools if ctx.tools is present
|
|
9
9
|
if (ctx.tools && typeof ctx.tools.register === 'function') {
|
|
10
10
|
// session_search
|
|
11
|
-
ctx.tools.register({
|
|
11
|
+
ctx.effect(() => ctx.tools.register({
|
|
12
12
|
name: 'session_search',
|
|
13
13
|
description: 'Search past AI coding sessions across Claude Code, Codex, Antigravity, Grok and DSH. By default searches sessions in the current working directory / workspace.',
|
|
14
14
|
parameters: {
|
|
@@ -59,9 +59,9 @@ export function apply(ctx) {
|
|
|
59
59
|
})),
|
|
60
60
|
}));
|
|
61
61
|
},
|
|
62
|
-
});
|
|
62
|
+
}));
|
|
63
63
|
// session_overview
|
|
64
|
-
ctx.tools.register({
|
|
64
|
+
ctx.effect(() => ctx.tools.register({
|
|
65
65
|
name: 'session_overview',
|
|
66
66
|
description: 'Get statistics, files modified, commands executed, and end-state facts of a past session.',
|
|
67
67
|
parameters: {
|
|
@@ -86,9 +86,9 @@ export function apply(ctx) {
|
|
|
86
86
|
stats: overview.stats,
|
|
87
87
|
};
|
|
88
88
|
},
|
|
89
|
-
});
|
|
89
|
+
}));
|
|
90
90
|
// session_turns
|
|
91
|
-
ctx.tools.register({
|
|
91
|
+
ctx.effect(() => ctx.tools.register({
|
|
92
92
|
name: 'session_turns',
|
|
93
93
|
description: 'Get turn-by-turn summary of what was discussed and done in a session.',
|
|
94
94
|
parameters: {
|
|
@@ -105,9 +105,9 @@ export function apply(ctx) {
|
|
|
105
105
|
const normalized = await resolved.adapter.parse(resolved.candidate);
|
|
106
106
|
return summarizeTurns(normalized);
|
|
107
107
|
},
|
|
108
|
-
});
|
|
108
|
+
}));
|
|
109
109
|
// session_turn_detail
|
|
110
|
-
ctx.tools.register({
|
|
110
|
+
ctx.effect(() => ctx.tools.register({
|
|
111
111
|
name: 'session_turn_detail',
|
|
112
112
|
description: 'Inspect full untruncated events, tool calls, and results for a specific turn in a session.',
|
|
113
113
|
parameters: {
|
|
@@ -129,114 +129,27 @@ export function apply(ctx) {
|
|
|
129
129
|
}
|
|
130
130
|
return turnDetail(normalized, args.turn);
|
|
131
131
|
},
|
|
132
|
-
});
|
|
132
|
+
}));
|
|
133
133
|
// session_open_in_dsh
|
|
134
|
-
ctx.tools.register({
|
|
134
|
+
ctx.effect(() => ctx.tools.register({
|
|
135
135
|
name: 'session_open_in_dsh',
|
|
136
|
-
description: '
|
|
136
|
+
description: 'Open a historical session in DSH and continue with its original Agent. External sessions require the ACP plugin; unsupported sources remain read-only.',
|
|
137
137
|
parameters: {
|
|
138
|
-
sessionId: { type: 'string', required: true, description: 'Session ID
|
|
139
|
-
|
|
138
|
+
sessionId: { type: 'string', required: true, description: 'Session ID, prefix, or native transcript path' },
|
|
139
|
+
provider: { type: 'string', description: 'Source provider to disambiguate an ID: claude, codex, grok, dsh, or antigravity' },
|
|
140
140
|
},
|
|
141
141
|
output: {
|
|
142
142
|
schema: { type: 'object' },
|
|
143
143
|
render: (_args, value) => [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }],
|
|
144
144
|
},
|
|
145
|
-
async execute(args
|
|
146
|
-
|
|
147
|
-
if (!resolved)
|
|
148
|
-
throw new Error(`Session not found: ${args.sessionId}`);
|
|
149
|
-
const normalized = await resolved.adapter.parse(resolved.candidate);
|
|
150
|
-
const dshEvents = convertSessionToDshEvents(normalized);
|
|
151
|
-
const rawId = normalized.ref.id;
|
|
152
|
-
const sanitizedId = rawId.replace(/[^a-zA-Z0-9_-]/g, '-');
|
|
153
|
-
const dshSessionId = sanitizedId.startsWith('session-') ? sanitizedId : `session-${sanitizedId}`;
|
|
154
|
-
const currentSessionCwd = exec?.agent?.session?.header?.cwd ?? args.currentCwd;
|
|
155
|
-
let targetCwd;
|
|
156
|
-
if (normalized.ref.workspace && typeof normalized.ref.workspace === 'string' && normalized.ref.workspace.startsWith('/')) {
|
|
157
|
-
try {
|
|
158
|
-
const st = await fs.promises.stat(normalized.ref.workspace);
|
|
159
|
-
if (st.isDirectory()) {
|
|
160
|
-
targetCwd = await fs.promises.realpath(normalized.ref.workspace);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
catch { }
|
|
164
|
-
}
|
|
165
|
-
if (!targetCwd) {
|
|
166
|
-
const fallback = currentSessionCwd || process.cwd();
|
|
167
|
-
try {
|
|
168
|
-
const st = await fs.promises.stat(fallback);
|
|
169
|
-
if (st.isDirectory()) {
|
|
170
|
-
targetCwd = await fs.promises.realpath(fallback);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
catch { }
|
|
174
|
-
}
|
|
175
|
-
if (!targetCwd)
|
|
176
|
-
targetCwd = process.cwd();
|
|
177
|
-
const createdAt = normalized.ref.createdAt ? Date.parse(normalized.ref.createdAt) : Date.now();
|
|
178
|
-
const header = {
|
|
179
|
-
version: 2,
|
|
180
|
-
id: dshSessionId,
|
|
181
|
-
cwd: targetCwd,
|
|
182
|
-
createdAt: isNaN(createdAt) ? Date.now() : createdAt,
|
|
183
|
-
isSeeded: false,
|
|
184
|
-
delegationDepth: 0,
|
|
185
|
-
};
|
|
186
|
-
const persistence = ctx.get ? ctx.get('sessionPersistence') : ctx.sessionPersistence;
|
|
187
|
-
if (persistence && typeof persistence.create === 'function') {
|
|
188
|
-
try {
|
|
189
|
-
const existingStat = await persistence.stat(dshSessionId);
|
|
190
|
-
if (!existingStat) {
|
|
191
|
-
const handle = await persistence.create(header);
|
|
192
|
-
await handle.append(dshEvents);
|
|
193
|
-
await handle.flush();
|
|
194
|
-
await handle.close();
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
catch (e) {
|
|
198
|
-
console.warn('[session-reader] persistence notice:', e?.message || e);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
const sessions = ctx.get ? ctx.get('sessions') : ctx.sessions;
|
|
202
|
-
if (sessions && typeof sessions.create === 'function') {
|
|
203
|
-
try {
|
|
204
|
-
if (!sessions.get(dshSessionId)) {
|
|
205
|
-
sessions.create(dshSessionId, { seed: dshEvents, meta: header });
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
catch (e) {
|
|
209
|
-
console.warn('[session-reader] sessions.create notice:', e?.message || e);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
const workspaceRegistry = ctx.get ? (ctx.get('workspaceRegistry') ?? ctx.workspaceRegistry) : ctx.workspaceRegistry;
|
|
213
|
-
let attachedWorkspaceId;
|
|
214
|
-
if (workspaceRegistry && typeof workspaceRegistry.create === 'function') {
|
|
215
|
-
try {
|
|
216
|
-
const ws = await workspaceRegistry.create(targetCwd);
|
|
217
|
-
if (ws && typeof ws.attachSession === 'function') {
|
|
218
|
-
await ws.attachSession(dshSessionId);
|
|
219
|
-
attachedWorkspaceId = ws.id;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
catch (wsErr) {
|
|
223
|
-
console.warn('[session-reader] workspace attach notice:', wsErr?.message || wsErr);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
return {
|
|
227
|
-
success: true,
|
|
228
|
-
dshSessionId,
|
|
229
|
-
eventCount: dshEvents.length,
|
|
230
|
-
title: normalized.ref.title,
|
|
231
|
-
workspace: targetCwd,
|
|
232
|
-
workspaceId: attachedWorkspaceId,
|
|
233
|
-
};
|
|
145
|
+
async execute(args) {
|
|
146
|
+
return openSessionInDsh(ctx, { sessionId: args.sessionId, provider: args.provider });
|
|
234
147
|
},
|
|
235
|
-
});
|
|
148
|
+
}));
|
|
236
149
|
}
|
|
237
150
|
// 2. Register HTTP routes if ctx.webServer is present
|
|
238
151
|
if (ctx.webServer && typeof ctx.webServer.register === 'function') {
|
|
239
|
-
ctx.webServer.register({
|
|
152
|
+
ctx.effect(() => ctx.webServer.register({
|
|
240
153
|
kind: 'prefix',
|
|
241
154
|
path: '/api/session-reader',
|
|
242
155
|
handler: async (req, res) => {
|
|
@@ -252,128 +165,37 @@ export function apply(ctx) {
|
|
|
252
165
|
return;
|
|
253
166
|
}
|
|
254
167
|
try {
|
|
168
|
+
if (pathname === '/continuation') {
|
|
169
|
+
const provider = url.searchParams.get('provider');
|
|
170
|
+
if (!provider)
|
|
171
|
+
throw new Error('Missing provider');
|
|
172
|
+
res.end(JSON.stringify(await continuationAvailability(ctx, { provider })));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
255
175
|
if (pathname === '/open-in-dsh') {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const body = await new Promise((resolve) => {
|
|
260
|
-
let data = '';
|
|
261
|
-
req.on('data', chunk => data += chunk);
|
|
262
|
-
req.on('end', () => {
|
|
263
|
-
try {
|
|
264
|
-
resolve(JSON.parse(data));
|
|
265
|
-
}
|
|
266
|
-
catch {
|
|
267
|
-
resolve({});
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
req.on('error', () => resolve({}));
|
|
271
|
-
});
|
|
272
|
-
sessionId = body?.sessionId || body?.id;
|
|
273
|
-
currentCwd = body?.currentCwd || currentCwd;
|
|
274
|
-
}
|
|
275
|
-
if (!sessionId) {
|
|
276
|
-
res.statusCode = 400;
|
|
277
|
-
res.end(JSON.stringify({ error: 'Missing sessionId' }));
|
|
176
|
+
if (req.method !== 'POST') {
|
|
177
|
+
res.statusCode = 405;
|
|
178
|
+
res.end(JSON.stringify({ error: 'Use POST to open a session' }));
|
|
278
179
|
return;
|
|
279
180
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
res.
|
|
283
|
-
res.end(JSON.stringify({ error: `Session not found: ${sessionId}` }));
|
|
181
|
+
if (req.headers.origin && new URL(req.headers.origin).host !== req.headers.host) {
|
|
182
|
+
res.statusCode = 403;
|
|
183
|
+
res.end(JSON.stringify({ error: 'Cross-origin session imports are not allowed' }));
|
|
284
184
|
return;
|
|
285
185
|
}
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
let targetCwd;
|
|
292
|
-
if (normalized.ref.workspace && typeof normalized.ref.workspace === 'string' && normalized.ref.workspace.startsWith('/')) {
|
|
293
|
-
try {
|
|
294
|
-
const st = await fs.promises.stat(normalized.ref.workspace);
|
|
295
|
-
if (st.isDirectory()) {
|
|
296
|
-
targetCwd = await fs.promises.realpath(normalized.ref.workspace);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
catch { }
|
|
300
|
-
}
|
|
301
|
-
if (!targetCwd) {
|
|
302
|
-
const fallback = currentCwd || process.cwd();
|
|
303
|
-
try {
|
|
304
|
-
const st = await fs.promises.stat(fallback);
|
|
305
|
-
if (st.isDirectory()) {
|
|
306
|
-
targetCwd = await fs.promises.realpath(fallback);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
catch { }
|
|
186
|
+
let body = '';
|
|
187
|
+
for await (const chunk of req) {
|
|
188
|
+
body += String(chunk);
|
|
189
|
+
if (body.length > 16384)
|
|
190
|
+
throw new Error('Request body too large');
|
|
310
191
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
id: dshSessionId,
|
|
317
|
-
cwd: targetCwd,
|
|
318
|
-
createdAt: isNaN(createdAt) ? Date.now() : createdAt,
|
|
319
|
-
isSeeded: false,
|
|
320
|
-
delegationDepth: 0,
|
|
321
|
-
};
|
|
322
|
-
// 1. Persistence if available
|
|
323
|
-
const persistence = ctx.get ? ctx.get('sessionPersistence') : ctx.sessionPersistence;
|
|
324
|
-
if (persistence && typeof persistence.create === 'function') {
|
|
325
|
-
try {
|
|
326
|
-
const existingStat = await persistence.stat(dshSessionId);
|
|
327
|
-
if (!existingStat) {
|
|
328
|
-
const handle = await persistence.create(header);
|
|
329
|
-
await handle.append(dshEvents);
|
|
330
|
-
await handle.flush();
|
|
331
|
-
await handle.close();
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
catch (persistErr) {
|
|
335
|
-
console.warn('[session-reader] persistence notice:', persistErr?.message || persistErr);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
// 2. In-memory sessions if available
|
|
339
|
-
const sessions = ctx.get ? ctx.get('sessions') : ctx.sessions;
|
|
340
|
-
if (sessions && typeof sessions.create === 'function') {
|
|
341
|
-
try {
|
|
342
|
-
if (!sessions.get(dshSessionId)) {
|
|
343
|
-
sessions.create(dshSessionId, {
|
|
344
|
-
seed: dshEvents,
|
|
345
|
-
meta: header,
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
catch (sessionErr) {
|
|
350
|
-
console.warn('[session-reader] sessions.create notice:', sessionErr?.message || sessionErr);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
// 3. Workspace attachment if workspaceRegistry is available
|
|
354
|
-
const workspaceRegistry = ctx.get ? (ctx.get('workspaceRegistry') ?? ctx.workspaceRegistry) : ctx.workspaceRegistry;
|
|
355
|
-
let attachedWorkspaceId;
|
|
356
|
-
if (workspaceRegistry && typeof workspaceRegistry.create === 'function') {
|
|
357
|
-
try {
|
|
358
|
-
const ws = await workspaceRegistry.create(targetCwd);
|
|
359
|
-
if (ws && typeof ws.attachSession === 'function') {
|
|
360
|
-
await ws.attachSession(dshSessionId);
|
|
361
|
-
attachedWorkspaceId = ws.id;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
catch (wsErr) {
|
|
365
|
-
console.warn('[session-reader] workspace attach notice:', wsErr?.message || wsErr);
|
|
366
|
-
}
|
|
192
|
+
const value = JSON.parse(body);
|
|
193
|
+
if (typeof value.sessionId !== 'string' || !value.sessionId || (value.provider !== undefined && typeof value.provider !== 'string')) {
|
|
194
|
+
res.statusCode = 400;
|
|
195
|
+
res.end(JSON.stringify({ error: 'sessionId and optional provider must be strings' }));
|
|
196
|
+
return;
|
|
367
197
|
}
|
|
368
|
-
res.
|
|
369
|
-
res.end(JSON.stringify({
|
|
370
|
-
success: true,
|
|
371
|
-
dshSessionId,
|
|
372
|
-
eventCount: dshEvents.length,
|
|
373
|
-
title: normalized.ref.title,
|
|
374
|
-
workspace: targetCwd,
|
|
375
|
-
workspaceId: attachedWorkspaceId,
|
|
376
|
-
}));
|
|
198
|
+
res.end(JSON.stringify(await openSessionInDsh(ctx, { sessionId: value.sessionId, provider: value.provider })));
|
|
377
199
|
return;
|
|
378
200
|
}
|
|
379
201
|
const handled = await handleSessionApi(pathname, url, res, { fallbackCwd: process.cwd() });
|
|
@@ -387,6 +209,6 @@ export function apply(ctx) {
|
|
|
387
209
|
res.end(JSON.stringify({ error: err?.message ?? String(err) }));
|
|
388
210
|
}
|
|
389
211
|
},
|
|
390
|
-
});
|
|
212
|
+
}));
|
|
391
213
|
}
|
|
392
214
|
}
|
package/dist/src/resolver.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export declare function listRecentSessions(options?: ListOptions): Promise<Sessi
|
|
|
41
41
|
export declare function findSessionsByWorkspace(workspacePath: string, options?: Omit<ListOptions, 'workspace'>): Promise<SessionRef[]>;
|
|
42
42
|
export declare function findResolvedByWorkspace(workspacePath: string, options?: Omit<ListOptions, 'workspace'>): Promise<ResolvedSession[]>;
|
|
43
43
|
/** Locates a session by full id, id prefix, or native file path. */
|
|
44
|
-
export declare function resolveSession(sessionId: string): Promise<ResolvedSession | undefined>;
|
|
44
|
+
export declare function resolveSession(sessionId: string, provider?: AgentProvider): Promise<ResolvedSession | undefined>;
|
|
45
45
|
export declare function parseSession(sessionId: string): Promise<NormalizedSession>;
|
|
46
46
|
export interface LoadOptions {
|
|
47
47
|
/** Set false to bypass the index entirely (CI, read-only checkouts). */
|
package/dist/src/resolver.js
CHANGED
|
@@ -140,11 +140,13 @@ export async function findResolvedByWorkspace(workspacePath, options = {}) {
|
|
|
140
140
|
return listResolvedSessions({ ...options, limit: options.limit ?? 50, workspace: workspacePath });
|
|
141
141
|
}
|
|
142
142
|
/** Locates a session by full id, id prefix, or native file path. */
|
|
143
|
-
export async function resolveSession(sessionId) {
|
|
143
|
+
export async function resolveSession(sessionId, provider) {
|
|
144
144
|
const asPath = sessionId.includes('/') ? canonicalizePath(sessionId) : undefined;
|
|
145
145
|
const needle = sessionId.toLowerCase();
|
|
146
146
|
let prefixHit;
|
|
147
147
|
for (const adapter of adapters) {
|
|
148
|
+
if (provider && adapter.provider !== provider)
|
|
149
|
+
continue;
|
|
148
150
|
for (const candidate of await adapter.listCandidates()) {
|
|
149
151
|
if (asPath ? candidate.path === asPath : candidate.id.toLowerCase() === needle) {
|
|
150
152
|
return { ref: await adapter.scanRef(candidate), adapter, candidate };
|
package/dist/src/web/app.js
CHANGED
|
@@ -18049,54 +18049,89 @@ Please report this to https://github.com/markedjs/marked.`, e3) {
|
|
|
18049
18049
|
};
|
|
18050
18050
|
}
|
|
18051
18051
|
|
|
18052
|
+
// src/dsh/copy.ts
|
|
18053
|
+
var continuationDictionaries = {
|
|
18054
|
+
zh: {
|
|
18055
|
+
continue: "\u5728 DSH \u4E2D\u7EE7\u7EED\u539F\u4F1A\u8BDD",
|
|
18056
|
+
openDsh: "\u5728 DSH \u4E2D\u6253\u5F00",
|
|
18057
|
+
readOnly: "\u53EA\u8BFB\u67E5\u770B",
|
|
18058
|
+
loading: "\u6B63\u5728\u6062\u590D\u539F\u4F1A\u8BDD\u2026",
|
|
18059
|
+
failed: "\u7EED\u804A\u5931\u8D25\uFF1A",
|
|
18060
|
+
unavailable: "\u6682\u65F6\u65E0\u6CD5\u68C0\u67E5\u7EED\u804A\u72B6\u6001",
|
|
18061
|
+
navigationUnavailable: "DSH \u4F1A\u8BDD\u5BFC\u822A\u4E0D\u53EF\u7528\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u5386\u53F2\u9762\u677F"
|
|
18062
|
+
},
|
|
18063
|
+
en: {
|
|
18064
|
+
continue: "Continue original session in DSH",
|
|
18065
|
+
openDsh: "Open in DSH",
|
|
18066
|
+
readOnly: "Read only",
|
|
18067
|
+
loading: "Restoring session\u2026",
|
|
18068
|
+
failed: "Could not continue: ",
|
|
18069
|
+
unavailable: "Continuation status is unavailable",
|
|
18070
|
+
navigationUnavailable: "DSH navigation is unavailable; reopen the history panel"
|
|
18071
|
+
}
|
|
18072
|
+
};
|
|
18073
|
+
function continuationCopy(translate) {
|
|
18074
|
+
if (translate) return Object.fromEntries(Object.keys(continuationDictionaries.zh).map((key) => [key, translate(key)]));
|
|
18075
|
+
const locale = typeof document === "undefined" ? "zh" : document.documentElement.lang || navigator.language;
|
|
18076
|
+
return locale.startsWith("zh") ? continuationDictionaries.zh : continuationDictionaries.en;
|
|
18077
|
+
}
|
|
18078
|
+
|
|
18052
18079
|
// src/dsh/client.ts
|
|
18053
18080
|
var CSS_TEXT = `
|
|
18081
|
+
/* Match DSH SidebarRoot panel rows in expanded and collapsed layouts. */
|
|
18054
18082
|
[data-dsh-session-reader-entry] {
|
|
18055
18083
|
box-sizing: border-box;
|
|
18056
|
-
|
|
18057
|
-
height: 36px;
|
|
18058
|
-
|
|
18084
|
+
flex: none;
|
|
18085
|
+
min-height: 36px;
|
|
18086
|
+
margin: 0 2px 4px;
|
|
18087
|
+
color: var(--dsw-alias-label-primary);
|
|
18059
18088
|
cursor: pointer;
|
|
18060
18089
|
white-space: nowrap;
|
|
18061
18090
|
background: transparent;
|
|
18062
18091
|
border: none;
|
|
18063
|
-
border-radius:
|
|
18092
|
+
border-radius: var(--dsw-radius-md);
|
|
18064
18093
|
align-items: center;
|
|
18065
18094
|
gap: 8px;
|
|
18066
|
-
padding:
|
|
18067
|
-
font
|
|
18095
|
+
padding: 7px 8px;
|
|
18096
|
+
font: inherit;
|
|
18097
|
+
line-height: 22px;
|
|
18098
|
+
text-align: left;
|
|
18068
18099
|
display: flex;
|
|
18069
|
-
transition: background 0.15s ease, color 0.15s ease;
|
|
18070
18100
|
}
|
|
18071
18101
|
[data-dsh-session-reader-entry]:hover {
|
|
18072
|
-
background: var(--dsw-alias-interactive-bg-hover
|
|
18073
|
-
color: var(--dsw-alias-label-primary, #111);
|
|
18102
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
18074
18103
|
}
|
|
18075
|
-
|
|
18076
|
-
|
|
18077
|
-
|
|
18104
|
+
[data-dsh-session-reader-entry]:focus-visible {
|
|
18105
|
+
outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
18106
|
+
outline-offset: -2px;
|
|
18078
18107
|
}
|
|
18079
18108
|
[data-dsh-session-reader-entry] .sr-icon {
|
|
18080
18109
|
flex: none;
|
|
18081
18110
|
display: inline-flex;
|
|
18082
18111
|
align-items: center;
|
|
18083
18112
|
justify-content: center;
|
|
18084
|
-
|
|
18085
|
-
|
|
18113
|
+
}
|
|
18114
|
+
[data-dsh-session-reader-entry] .sr-icon svg {
|
|
18115
|
+
width: 16px;
|
|
18116
|
+
height: 16px;
|
|
18086
18117
|
}
|
|
18087
18118
|
[data-dsh-session-reader-entry] .sr-label {
|
|
18119
|
+
min-width: 0;
|
|
18088
18120
|
text-overflow: ellipsis;
|
|
18089
18121
|
overflow: hidden;
|
|
18090
18122
|
}
|
|
18091
18123
|
[data-dsh-frame][data-sidebar-collapsed] [data-dsh-session-reader-entry],
|
|
18092
18124
|
[data-sidebar-collapsed] [data-dsh-session-reader-entry] {
|
|
18093
|
-
border-radius: 50%;
|
|
18094
18125
|
justify-content: center;
|
|
18095
18126
|
width: 36px;
|
|
18096
18127
|
height: 36px;
|
|
18097
|
-
margin: 0
|
|
18128
|
+
margin: 0 0 12px;
|
|
18098
18129
|
padding: 0;
|
|
18099
18130
|
}
|
|
18131
|
+
[data-sidebar-collapsed] [data-dsh-session-reader-entry] .sr-icon svg {
|
|
18132
|
+
width: 18px;
|
|
18133
|
+
height: 18px;
|
|
18134
|
+
}
|
|
18100
18135
|
[data-dsh-frame][data-sidebar-collapsed] [data-dsh-session-reader-entry] .sr-label,
|
|
18101
18136
|
[data-sidebar-collapsed] [data-dsh-session-reader-entry] .sr-label {
|
|
18102
18137
|
display: none;
|
|
@@ -18475,6 +18510,9 @@ body[data-ds-dark-theme] .sr-tab-btn.active {
|
|
|
18475
18510
|
}
|
|
18476
18511
|
function apply(_ctx) {
|
|
18477
18512
|
if (typeof document === "undefined") return;
|
|
18513
|
+
if (_ctx.locale) _ctx.effect(() => _ctx.locale.register("sessionReaderContinuation", continuationDictionaries));
|
|
18514
|
+
const translate = _ctx.locale?.bind("sessionReaderContinuation");
|
|
18515
|
+
const getContinuationCopy = () => continuationCopy(translate);
|
|
18478
18516
|
if (!document.getElementById("dsh-session-reader-css")) {
|
|
18479
18517
|
const style = document.createElement("style");
|
|
18480
18518
|
style.id = "dsh-session-reader-css";
|
|
@@ -18554,48 +18592,35 @@ body[data-ds-dark-theme] .sr-tab-btn.active {
|
|
|
18554
18592
|
cachedSessions = [];
|
|
18555
18593
|
currentTab = "chat";
|
|
18556
18594
|
}
|
|
18557
|
-
async function openInDshChat(sessionId, btn) {
|
|
18595
|
+
async function openInDshChat(sessionId, btn, provider) {
|
|
18596
|
+
const copy = getContinuationCopy();
|
|
18597
|
+
const label = btn?.textContent;
|
|
18558
18598
|
if (btn) {
|
|
18559
|
-
btn.
|
|
18599
|
+
btn.textContent = copy.loading;
|
|
18560
18600
|
btn.disabled = true;
|
|
18561
18601
|
}
|
|
18562
18602
|
try {
|
|
18563
|
-
const activeWs = getActiveWorkspaceInfo();
|
|
18564
18603
|
const res = await fetch("/api/session-reader/open-in-dsh", {
|
|
18565
18604
|
method: "POST",
|
|
18566
18605
|
headers: { "Content-Type": "application/json" },
|
|
18567
|
-
body: JSON.stringify({
|
|
18568
|
-
sessionId,
|
|
18569
|
-
currentCwd: activeWs.cwd,
|
|
18570
|
-
workspaceId: activeWs.workspaceId
|
|
18571
|
-
})
|
|
18606
|
+
body: JSON.stringify({ sessionId, provider })
|
|
18572
18607
|
});
|
|
18573
18608
|
const result = await res.json();
|
|
18574
|
-
if (!res.ok || !result.success)
|
|
18575
|
-
alert("\u63A5\u5165 DSH \u5931\u8D25: " + (result.error || res.statusText));
|
|
18576
|
-
if (btn) {
|
|
18577
|
-
btn.innerHTML = "\u{1F680} \u5728 DSH \u5B98\u65B9 Chat \u4E2D\u6253\u5F00";
|
|
18578
|
-
btn.disabled = false;
|
|
18579
|
-
}
|
|
18580
|
-
return;
|
|
18581
|
-
}
|
|
18582
|
-
const dshSessionId = result.dshSessionId;
|
|
18583
|
-
closePanel();
|
|
18609
|
+
if (!res.ok || !result.success) throw new Error(result.error || res.statusText);
|
|
18584
18610
|
const sessions = dshCtx?.get ? dshCtx.get("sessions") ?? dshCtx.sessions : dshCtx?.sessions;
|
|
18585
|
-
|
|
18586
|
-
|
|
18587
|
-
|
|
18588
|
-
|
|
18589
|
-
|
|
18590
|
-
sessions.open(dshSessionId);
|
|
18591
|
-
}
|
|
18592
|
-
} else {
|
|
18593
|
-
console.warn("[session-reader] Could not obtain DSH sessions service from ctx");
|
|
18594
|
-
}
|
|
18611
|
+
const navigation = dshCtx?.get ? dshCtx.get("uiWorkspace") : dshCtx?.uiWorkspace;
|
|
18612
|
+
if (!navigation) throw new Error(copy.navigationUnavailable);
|
|
18613
|
+
await sessions.refresh();
|
|
18614
|
+
navigation.openSession(result.dshSessionId);
|
|
18615
|
+
closePanel();
|
|
18595
18616
|
} catch (err) {
|
|
18596
|
-
|
|
18617
|
+
if (activeSessionData) {
|
|
18618
|
+
activeSessionData.continuationError = copy.failed + (err?.message || err);
|
|
18619
|
+
renderModalBody();
|
|
18620
|
+
}
|
|
18621
|
+
} finally {
|
|
18597
18622
|
if (btn) {
|
|
18598
|
-
btn.
|
|
18623
|
+
btn.textContent = label ?? copy.continue;
|
|
18599
18624
|
btn.disabled = false;
|
|
18600
18625
|
}
|
|
18601
18626
|
}
|
|
@@ -18626,6 +18651,19 @@ body[data-ds-dark-theme] .sr-tab-btn.active {
|
|
|
18626
18651
|
const data = await res.json();
|
|
18627
18652
|
if (detailAbort !== ac) return;
|
|
18628
18653
|
activeSessionData = data;
|
|
18654
|
+
renderModalBody();
|
|
18655
|
+
if (!isStandalone()) {
|
|
18656
|
+
try {
|
|
18657
|
+
const status = await fetch(`/api/session-reader/continuation?provider=${encodeURIComponent(data.ref.provider)}`, { signal: ac.signal, cache: "no-store" });
|
|
18658
|
+
if (!status.ok) throw new Error(`HTTP ${status.status}`);
|
|
18659
|
+
const continuation = await status.json();
|
|
18660
|
+
if (detailAbort !== ac) return;
|
|
18661
|
+
activeSessionData.continuation = continuation;
|
|
18662
|
+
} catch (error) {
|
|
18663
|
+
if (detailAbort !== ac) return;
|
|
18664
|
+
activeSessionData.continuation = { available: false, reason: String(error) };
|
|
18665
|
+
}
|
|
18666
|
+
}
|
|
18629
18667
|
} catch (err) {
|
|
18630
18668
|
if (err?.name === "AbortError") {
|
|
18631
18669
|
if (detailAbort === ac) {
|
|
@@ -18717,9 +18755,6 @@ body[data-ds-dark-theme] .sr-tab-btn.active {
|
|
|
18717
18755
|
item.classList.add("active");
|
|
18718
18756
|
loadSessionDetail(s3.id);
|
|
18719
18757
|
});
|
|
18720
|
-
item.addEventListener("dblclick", () => {
|
|
18721
|
-
openInDshChat(s3.id);
|
|
18722
|
-
});
|
|
18723
18758
|
listEl.appendChild(item);
|
|
18724
18759
|
});
|
|
18725
18760
|
}
|
|
@@ -18738,7 +18773,8 @@ body[data-ds-dark-theme] .sr-tab-btn.active {
|
|
|
18738
18773
|
contentEl.innerHTML = `<div style="padding:60px;text-align:center;color:#e11d48;font-size:13px;">\u52A0\u8F7D\u5931\u8D25: ${activeSessionData.error}</div>`;
|
|
18739
18774
|
return;
|
|
18740
18775
|
}
|
|
18741
|
-
const { ref, overview, turns, files } = activeSessionData;
|
|
18776
|
+
const { ref, overview, turns, files, continuation, continuationError } = activeSessionData;
|
|
18777
|
+
const copy = getContinuationCopy();
|
|
18742
18778
|
const standalone = isStandalone();
|
|
18743
18779
|
const body = currentTab === "chat" ? '<div id="sr-chat-root" style="width:100%;height:100%;display:flex;flex-direction:column;overflow:hidden;"></div>' : currentTab === "files" ? renderFilesView(files) : renderOverviewView(overview);
|
|
18744
18780
|
contentEl.innerHTML = `
|
|
@@ -18748,8 +18784,8 @@ body[data-ds-dark-theme] .sr-tab-btn.active {
|
|
|
18748
18784
|
${escapeHtml2(ref.title || "Untitled")}
|
|
18749
18785
|
</div>
|
|
18750
18786
|
<div class="sr-header-actions">
|
|
18751
|
-
${standalone ? "" : `<button class="sr-open-dsh-btn" id="sr-btn-open-dsh"
|
|
18752
|
-
|
|
18787
|
+
${standalone ? "" : `<button class="sr-open-dsh-btn" id="sr-btn-open-dsh" ${continuation?.available ? "" : "disabled"}>
|
|
18788
|
+
${escapeHtml2(continuation?.available ? (ref.provider === "dsh" ? copy.openDsh : copy.continue) + " \xB7 " + (continuation.agent || ref.provider) : copy.readOnly)}
|
|
18753
18789
|
</button>`}
|
|
18754
18790
|
<div class="sr-tabs">
|
|
18755
18791
|
<button class="sr-tab-btn ${currentTab === "chat" ? "active" : ""}" data-tab="chat">\u{1F4AC} \u5FEB\u901F\u9884\u89C8</button>
|
|
@@ -18758,6 +18794,7 @@ body[data-ds-dark-theme] .sr-tab-btn.active {
|
|
|
18758
18794
|
</div>
|
|
18759
18795
|
</div>
|
|
18760
18796
|
</div>
|
|
18797
|
+
${!standalone && (continuationError || !continuation?.available) ? `<div class="sr-header-path" role="status">${escapeHtml2(continuationError || continuation?.reason || copy.unavailable)}</div>` : ""}
|
|
18761
18798
|
<div class="sr-header-path" title="${escapeHtml2(ref.path || "")}">
|
|
18762
18799
|
<span class="sr-header-provider">${ref.provider}</span> \xB7 \u8DEF\u5F84: ${escapeHtml2(ref.path || "")}
|
|
18763
18800
|
</div>
|
|
@@ -18769,7 +18806,7 @@ body[data-ds-dark-theme] .sr-tab-btn.active {
|
|
|
18769
18806
|
const openDshBtn = contentEl.querySelector("#sr-btn-open-dsh");
|
|
18770
18807
|
if (openDshBtn) {
|
|
18771
18808
|
openDshBtn.addEventListener("click", () => {
|
|
18772
|
-
openInDshChat(activeSessionId, openDshBtn);
|
|
18809
|
+
openInDshChat(ref.path || activeSessionId, openDshBtn, ref.provider);
|
|
18773
18810
|
});
|
|
18774
18811
|
}
|
|
18775
18812
|
contentEl.querySelectorAll(".sr-tab-btn").forEach((btn) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1agents/session-reader",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Read Plane: cross-agent session discovery, turn inspection, workspace aggregation and distillation from raw local session files.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -45,7 +45,11 @@
|
|
|
45
45
|
"patch": "./cordis.patch.yml"
|
|
46
46
|
},
|
|
47
47
|
"client": {
|
|
48
|
-
"platform": "web"
|
|
48
|
+
"platform": "web",
|
|
49
|
+
"inject": [
|
|
50
|
+
"@deepseek-ai/dsh-client-ui-workspace",
|
|
51
|
+
"@deepseek-ai/dsh-client-locale"
|
|
52
|
+
]
|
|
49
53
|
}
|
|
50
54
|
},
|
|
51
55
|
"bin": {
|
|
@@ -64,7 +68,7 @@
|
|
|
64
68
|
"scripts": {
|
|
65
69
|
"build": "tsc -p tsconfig.build.json && node scripts/bundle-client.js",
|
|
66
70
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
67
|
-
"test": "node --import tsx --test test/session-reader.test.ts test/dsh-adapter.test.ts test/web.test.ts",
|
|
71
|
+
"test": "node --import tsx --test test/session-reader.test.ts test/dsh-adapter.test.ts test/dsh-open.test.ts test/web.test.ts",
|
|
68
72
|
"prepack": "npm run build",
|
|
69
73
|
"1session": "tsx bin/1session.ts"
|
|
70
74
|
},
|