@1agents/session-reader 0.6.1 → 0.7.0
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 +42 -0
- package/cordis.patch.yml +4 -0
- package/dist/bin/1session.js +26 -1
- package/dist/src/dsh/adapter.d.ts +18 -0
- package/dist/src/dsh/adapter.js +198 -0
- package/dist/src/dsh/client.d.ts +2 -0
- package/dist/src/dsh/client.js +18997 -0
- package/dist/src/dsh/index.d.ts +2 -0
- package/dist/src/dsh/index.js +2 -0
- package/dist/src/dsh/plugin.d.ts +3 -0
- package/dist/src/dsh/plugin.js +392 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +2 -0
- package/dist/src/resolver.d.ts +7 -0
- package/dist/src/resolver.js +13 -8
- package/dist/src/serve/http.js +13 -1
- package/dist/src/store/nul.d.ts +12 -0
- package/dist/src/store/nul.js +52 -0
- package/dist/src/store/read.d.ts +6 -2
- package/dist/src/store/read.js +18 -15
- package/dist/src/store/schema.d.ts +1 -1
- package/dist/src/store/schema.js +1 -1
- package/dist/src/store/write.js +3 -2
- package/dist/src/web/api.d.ts +72 -0
- package/dist/src/web/api.js +126 -0
- package/dist/src/web/app.d.ts +1 -0
- package/dist/src/web/app.js +18975 -0
- package/dist/src/web/server.d.ts +20 -0
- package/dist/src/web/server.js +126 -0
- package/package.json +25 -4
- package/skills/1session/SKILL.md +46 -8
- package/skills/1session/evals/evals.json +43 -1
- package/skills/1session/references/cli.md +16 -0
- package/skills/1session/references/install.md +195 -0
package/README.md
CHANGED
|
@@ -71,6 +71,21 @@ npx @1agents/session-reader list # 不安装直接用
|
|
|
71
71
|
其他开关:`--agent claude,codex`(只装指定的几家,即使该智能体尚未安装也会建目录,
|
|
72
72
|
方便先装 skill 后装智能体)、`--dry-run`(只说会做什么)、`--force`(覆盖同名条目)。
|
|
73
73
|
|
|
74
|
+
**别用 `npx` 跑 `skill install`。** 默认的链接模式会指回包所在目录,而 npx 装的那份在
|
|
75
|
+
`~/.npm/_npx/<hash>/` 的缓存里,npm 自己会清。清掉那天五家的 skill 一起静悄悄消失。
|
|
76
|
+
先 `npm i -g` 再 `skill install`;实在要从 npx 引导就加 `--copy`,把字节交给各家自己拿着。
|
|
77
|
+
|
|
78
|
+
传播给别人时,两行就够——第二行是大家会忘的那行:只装 CLI 的人得自己记着它存在,
|
|
79
|
+
两行都跑了的人是智能体替他记着。
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm i -g @1agents/session-reader
|
|
83
|
+
1session skill install
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
skill 自己也带着这套安装说明(`skills/1session/references/install.md`):即便对方只拿到
|
|
87
|
+
一份 SKILL.md、机器上没有 CLI,它也会先用 `npx` 把问题回答掉,再回头提一次永久安装。
|
|
88
|
+
|
|
74
89
|
## CLI
|
|
75
90
|
|
|
76
91
|
```bash
|
|
@@ -458,6 +473,18 @@ $ 1session graph ca8325e1
|
|
|
458
473
|
|
|
459
474
|
运行时捕获:若环境注入了 `SESSION_READER_CALLER_SESSION`,调用当下就直接落边(`observed / runtime:caller-env`),无需事后从历史里恢复。
|
|
460
475
|
|
|
476
|
+
## `1session web`:独立浏览器
|
|
477
|
+
|
|
478
|
+
没有 DSH 也能看同一套历史会话列表、对话预览和文件账本:
|
|
479
|
+
|
|
480
|
+
```bash
|
|
481
|
+
1session web # http://127.0.0.1:7780
|
|
482
|
+
1session web --open # 启动后打开默认浏览器
|
|
483
|
+
1session web --scope global # 默认列出全部工作区
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
列表只拉元数据,点击某一条会话后再加载对话与文件。这和 `1session serve`(DreamMate Network 的 JSON 服务)不是同一件事。
|
|
487
|
+
|
|
461
488
|
## `1session serve`:接入 DreamMate Network
|
|
462
489
|
|
|
463
490
|
把本地 Read Plane 原样暴露成网络能力——`1session overview <id>` 成为 `sessions.read`。
|
|
@@ -538,6 +565,21 @@ base_url http://scott-mac.tailfb4720.ts.net:7777/v1 ← MagicDNS,跨机可
|
|
|
538
565
|
> 而共用同一份定义才谈得上「公共语言」。**schema 是唯一事实源,改类型先去改那个包。**
|
|
539
566
|
> `/manifest` 的 `metadata.protocol_version` 报告本服务遵循的协议版本。
|
|
540
567
|
|
|
568
|
+
### SQLite 的 TEXT 列会在 NUL 处截断
|
|
569
|
+
|
|
570
|
+
存进去 `A<NUL>B`,读出来只剩 `A`——**不报错**,安静地丢掉后面全部内容。
|
|
571
|
+
实测一个 antigravity 会话里 `wsl -l -v` 的 UTF-16 输出被当 UTF-8 读,产生交错
|
|
572
|
+
的 NUL,502 字符的 `tool_result` 存完只剩 342,后面 160 个字符凭空消失。
|
|
573
|
+
|
|
574
|
+
改存 BLOB 能保真,但 `text` / `tool_result` 上有 SQL 搜索(`LIKE` 对 BLOB 不
|
|
575
|
+
工作),所以走**写入转义、读取还原**(`src/store/nul.ts`)。引导符用 U+FFFF:
|
|
576
|
+
Unicode 明确规定的 noncharacter,不会出现在有效文本里;万一真出现也会被双写,
|
|
577
|
+
还原无歧义。绝大多数内容两个字符都不含,直接原样返回,常态零开销。
|
|
578
|
+
|
|
579
|
+
这类 bug 的可怕之处在于**没有任何报错**——只有 round-trip 测试
|
|
580
|
+
(`readSession(db,row)` 深度等于 `adapter.parse(candidate)`)能抓到它。
|
|
581
|
+
那条断言就是整个索引层的安全网:一旦索引在改写事实而不是缓存事实,它就会红。
|
|
582
|
+
|
|
541
583
|
## 测试
|
|
542
584
|
|
|
543
585
|
```bash
|
package/cordis.patch.yml
ADDED
package/dist/bin/1session.js
CHANGED
|
@@ -27,6 +27,8 @@ const USAGE = `1session — cross-agent session Read Plane
|
|
|
27
27
|
1session graph <session-id> [--json] 会话之间的引用关系
|
|
28
28
|
1session serve [--port 7777] [--host 127.0.0.1] [--token <t>] [--no-report]
|
|
29
29
|
起 HTTP Service,把本机会话接入 DreamMate Network
|
|
30
|
+
1session web [--port 7780] [--host 127.0.0.1] [--open] [--scope <path>|cwd|global]
|
|
31
|
+
起独立 Web 端,浏览历史会话列表、对话与文件(无需 DSH)
|
|
30
32
|
1session skill install|status|uninstall [--agent claude,codex,antigravity,grok,dsh]
|
|
31
33
|
[--copy] [--force] [--dry-run] [--json] 装到五家智能体的 skills 目录
|
|
32
34
|
1session search <query> [--scope <path>|cwd|global] [--since 24h] [--limit n] [--provider name]
|
|
@@ -48,7 +50,7 @@ Providers: antigravity (~/.gemini/antigravity/brain), claude (~/.claude/projects
|
|
|
48
50
|
/** Flags that never take a value, so they cannot swallow a positional. */
|
|
49
51
|
const BOOLEAN_FLAGS = new Set([
|
|
50
52
|
'json', 'failed', 'digest', 'regex', 'case', 'all', 'force', 'no-index', 'global',
|
|
51
|
-
'copy', 'dry-run', 'no-report', 'include-self',
|
|
53
|
+
'copy', 'dry-run', 'no-report', 'include-self', 'open',
|
|
52
54
|
]);
|
|
53
55
|
function parseArgs(argv) {
|
|
54
56
|
const [command = 'help', ...rest] = argv;
|
|
@@ -479,6 +481,29 @@ async function main() {
|
|
|
479
481
|
await new Promise(() => { });
|
|
480
482
|
break;
|
|
481
483
|
}
|
|
484
|
+
case 'web': {
|
|
485
|
+
const { serveWeb, DEFAULT_WEB_PORT } = await import('../src/web/server.js');
|
|
486
|
+
const scope = str(flags.scope) ?? (flags.global === true ? 'global' : 'cwd');
|
|
487
|
+
let cwd = process.cwd();
|
|
488
|
+
let defaultScope = 'cwd';
|
|
489
|
+
if (scope === 'global') {
|
|
490
|
+
defaultScope = 'global';
|
|
491
|
+
}
|
|
492
|
+
else if (scope !== 'cwd') {
|
|
493
|
+
cwd = canonicalizePath(scope);
|
|
494
|
+
if (!existsSync(cwd))
|
|
495
|
+
throw new Error(`--scope 目录不存在:${scope}`);
|
|
496
|
+
}
|
|
497
|
+
await serveWeb({
|
|
498
|
+
port: num(flags.port) ?? DEFAULT_WEB_PORT,
|
|
499
|
+
host: str(flags.host) ?? '127.0.0.1',
|
|
500
|
+
cwd,
|
|
501
|
+
defaultScope,
|
|
502
|
+
open: flags.open === true,
|
|
503
|
+
});
|
|
504
|
+
await new Promise(() => { });
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
482
507
|
case 'skill': {
|
|
483
508
|
const { describeState, installSkill, skillStatus, uninstallSkill, bundledSkillDir, } = await import('../src/skill.js');
|
|
484
509
|
const agents = str(flags.agent)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { NormalizedSession } from '../types.js';
|
|
2
|
+
export interface DshSessionEvent {
|
|
3
|
+
type: string;
|
|
4
|
+
seq: number;
|
|
5
|
+
time: number;
|
|
6
|
+
surfaceOp?: 'append';
|
|
7
|
+
data: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
export interface ConvertOptions {
|
|
10
|
+
/** Initial sequence number, defaults to 0 (DSH 0-based sequence convention). */
|
|
11
|
+
startSeq?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Converts a normalized session from @1agents/session-reader (across Claude, Codex,
|
|
15
|
+
* Antigravity, Grok, or DSH) into an ordered sequence of DSH SessionEvents suitable
|
|
16
|
+
* for direct ingestion and rendering by DSH Client UI (@deepseek-ai/dsh-client-ui-chat).
|
|
17
|
+
*/
|
|
18
|
+
export declare function convertSessionToDshEvents(session: NormalizedSession, options?: ConvertOptions): DshSessionEvent[];
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a normalized session from @1agents/session-reader (across Claude, Codex,
|
|
3
|
+
* Antigravity, Grok, or DSH) into an ordered sequence of DSH SessionEvents suitable
|
|
4
|
+
* for direct ingestion and rendering by DSH Client UI (@deepseek-ai/dsh-client-ui-chat).
|
|
5
|
+
*/
|
|
6
|
+
export function convertSessionToDshEvents(session, options = {}) {
|
|
7
|
+
const events = [];
|
|
8
|
+
let seq = options.startSeq ?? 0;
|
|
9
|
+
let currentTurn = 1;
|
|
10
|
+
let currentStep = 1;
|
|
11
|
+
let turnOpen = false;
|
|
12
|
+
let stepOpen = false;
|
|
13
|
+
const defaultTime = session.ref.createdAt
|
|
14
|
+
? Date.parse(session.ref.createdAt)
|
|
15
|
+
: Date.now();
|
|
16
|
+
function parseTime(timestamp) {
|
|
17
|
+
if (!timestamp)
|
|
18
|
+
return defaultTime;
|
|
19
|
+
const parsed = Date.parse(timestamp);
|
|
20
|
+
return isNaN(parsed) ? defaultTime : parsed;
|
|
21
|
+
}
|
|
22
|
+
function emit(type, data, time, surfaceOp) {
|
|
23
|
+
events.push({
|
|
24
|
+
type,
|
|
25
|
+
seq: seq++,
|
|
26
|
+
time,
|
|
27
|
+
...(surfaceOp ? { surfaceOp } : {}),
|
|
28
|
+
data,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function ensureTurnStart(turnNum, time) {
|
|
32
|
+
if (!turnOpen) {
|
|
33
|
+
emit('turn/start', { turn: turnNum }, time);
|
|
34
|
+
turnOpen = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function ensureStepStart(turnNum, stepNum, time) {
|
|
38
|
+
ensureTurnStart(turnNum, time);
|
|
39
|
+
if (!stepOpen) {
|
|
40
|
+
emit('step/start', { turn: turnNum, step: stepNum }, time);
|
|
41
|
+
stepOpen = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function closeStep(turnNum, stepNum, time) {
|
|
45
|
+
if (stepOpen) {
|
|
46
|
+
emit('step/end', { turn: turnNum, step: stepNum }, time);
|
|
47
|
+
stepOpen = false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function closeTurn(turnNum, time, reason = 'completed') {
|
|
51
|
+
closeStep(turnNum, currentStep, time);
|
|
52
|
+
if (turnOpen) {
|
|
53
|
+
emit('turn/end', { turn: turnNum, reason: { kind: reason } }, time);
|
|
54
|
+
turnOpen = false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const providerName = session.ref.provider || 'external';
|
|
58
|
+
const modelName = session.stats.models[0] || 'assistant';
|
|
59
|
+
if (session.ref.title && session.ref.title.trim()) {
|
|
60
|
+
emit('session/title', {
|
|
61
|
+
title: session.ref.title.trim(),
|
|
62
|
+
messageSeqs: [],
|
|
63
|
+
source: { kind: 'user' },
|
|
64
|
+
}, defaultTime);
|
|
65
|
+
}
|
|
66
|
+
for (let i = 0; i < session.turns.length; i++) {
|
|
67
|
+
const turnEvent = session.turns[i];
|
|
68
|
+
const eventTime = parseTime(turnEvent.timestamp);
|
|
69
|
+
switch (turnEvent.kind) {
|
|
70
|
+
case 'user': {
|
|
71
|
+
// A new user prompt begins a new turn
|
|
72
|
+
if (turnOpen) {
|
|
73
|
+
closeTurn(currentTurn, eventTime);
|
|
74
|
+
currentTurn++;
|
|
75
|
+
currentStep = 1;
|
|
76
|
+
}
|
|
77
|
+
ensureStepStart(currentTurn, currentStep, eventTime);
|
|
78
|
+
emit('user/message', {
|
|
79
|
+
id: `msg-${turnEvent.id}`,
|
|
80
|
+
role: 'user',
|
|
81
|
+
content: [{ type: 'text', text: turnEvent.text ?? '' }],
|
|
82
|
+
source: { kind: 'user' },
|
|
83
|
+
}, eventTime, 'append');
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case 'thinking': {
|
|
87
|
+
ensureStepStart(currentTurn, currentStep, eventTime);
|
|
88
|
+
const thinkingText = turnEvent.text ?? '';
|
|
89
|
+
if (thinkingText.trim()) {
|
|
90
|
+
emit('assistant/message', {
|
|
91
|
+
turn: currentTurn,
|
|
92
|
+
step: currentStep,
|
|
93
|
+
message: {
|
|
94
|
+
id: `asst-think-${turnEvent.id}`,
|
|
95
|
+
role: 'assistant',
|
|
96
|
+
content: [{ type: 'reasoning', text: thinkingText }],
|
|
97
|
+
source: { kind: 'model', provider: providerName, model: modelName },
|
|
98
|
+
},
|
|
99
|
+
stream: [
|
|
100
|
+
{
|
|
101
|
+
type: 'chunk',
|
|
102
|
+
time: eventTime,
|
|
103
|
+
chunk: {
|
|
104
|
+
type: 'reasoning-delta',
|
|
105
|
+
index: 0,
|
|
106
|
+
text: thinkingText,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
}, eventTime, 'append');
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
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
|
+
}
|
|
130
|
+
case 'tool_result': {
|
|
131
|
+
ensureStepStart(currentTurn, currentStep, eventTime);
|
|
132
|
+
const callId = turnEvent.id;
|
|
133
|
+
const toolName = turnEvent.toolName ?? 'tool';
|
|
134
|
+
const resultContent = turnEvent.toolResult ?? '';
|
|
135
|
+
emit('tool/result', {
|
|
136
|
+
turn: currentTurn,
|
|
137
|
+
step: currentStep,
|
|
138
|
+
message: {
|
|
139
|
+
id: `tool-res-${callId}`,
|
|
140
|
+
role: 'user',
|
|
141
|
+
source: { kind: 'tool', callId, tool: toolName },
|
|
142
|
+
content: [
|
|
143
|
+
{
|
|
144
|
+
type: 'tool-result',
|
|
145
|
+
toolCallId: callId,
|
|
146
|
+
content: [{ type: 'text', text: resultContent }],
|
|
147
|
+
...(turnEvent.isError ? { isError: true } : {}),
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
...(turnEvent.exitCode !== undefined ? { meta: { exitCode: turnEvent.exitCode } } : {}),
|
|
152
|
+
}, eventTime, 'append');
|
|
153
|
+
// Tool execution concludes a step; next action starts a new step
|
|
154
|
+
closeStep(currentTurn, currentStep, eventTime);
|
|
155
|
+
currentStep++;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
case 'assistant': {
|
|
159
|
+
ensureStepStart(currentTurn, currentStep, eventTime);
|
|
160
|
+
const text = turnEvent.text ?? '';
|
|
161
|
+
emit('assistant/message', {
|
|
162
|
+
turn: currentTurn,
|
|
163
|
+
step: currentStep,
|
|
164
|
+
message: {
|
|
165
|
+
id: `asst-${turnEvent.id}`,
|
|
166
|
+
role: 'assistant',
|
|
167
|
+
content: [{ type: 'text', text }],
|
|
168
|
+
source: { kind: 'model', provider: providerName, model: modelName },
|
|
169
|
+
},
|
|
170
|
+
stream: [
|
|
171
|
+
{
|
|
172
|
+
type: 'chunk',
|
|
173
|
+
time: eventTime,
|
|
174
|
+
chunk: {
|
|
175
|
+
type: 'text-delta',
|
|
176
|
+
index: 0,
|
|
177
|
+
text,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
}, eventTime, 'append');
|
|
182
|
+
const nextEvent = session.turns[i + 1];
|
|
183
|
+
if (!nextEvent || nextEvent.kind === 'user') {
|
|
184
|
+
closeTurn(currentTurn, eventTime);
|
|
185
|
+
currentTurn++;
|
|
186
|
+
currentStep = 1;
|
|
187
|
+
}
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Final cleanup if the last turn was still open
|
|
193
|
+
if (turnOpen) {
|
|
194
|
+
const lastTime = events.length > 0 ? events[events.length - 1].time : defaultTime;
|
|
195
|
+
closeTurn(currentTurn, lastTime);
|
|
196
|
+
}
|
|
197
|
+
return events;
|
|
198
|
+
}
|