@1agents/session-reader 0.3.0 → 0.4.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 CHANGED
@@ -22,7 +22,9 @@ npm install -g @1agents/session-reader # 作为 1session 命令
22
22
  npx @1agents/session-reader list # 不安装直接用
23
23
  ```
24
24
 
25
- 要求 Node.js >= 22.5(依赖内置的 `node:sqlite`),零运行时依赖。
25
+ 要求 Node.js >= 22.5(依赖内置的 `node:sqlite`)。
26
+ 唯一的运行时依赖是自家的 [`@1agents/dreammate-network`](https://github.com/scottzx/dreammate-network)
27
+ ——L0 协议定义,12 kB,本身零依赖——`npm install` 会自动带上,不需要单独装。
26
28
 
27
29
  ## 内置 skill:一条命令装到三家智能体
28
30
 
@@ -402,6 +404,52 @@ $ 1session graph ca8325e1
402
404
 
403
405
  运行时捕获:若环境注入了 `SESSION_READER_CALLER_SESSION`,调用当下就直接落边(`observed / runtime:caller-env`),无需事后从历史里恢复。
404
406
 
407
+ ## `1session serve`:接入 DreamMate Network
408
+
409
+ 把本地 Read Plane 原样暴露成网络能力——`1session overview <id>` 成为 `sessions.read`。
410
+ **不重新实现索引与事实层**,只是换一个调用入口。不引第三方 HTTP 框架,只用 `node:http`。
411
+
412
+ ```bash
413
+ 1session serve # 默认 127.0.0.1:7777
414
+ 1session serve --host 100.x.x.x --token <t> # 暴露到 tailnet
415
+ ```
416
+
417
+ ```
418
+ GET /manifest Node Manifest(dreammate-network node.schema.json)
419
+ GET /health
420
+ GET /v1/node 同 /manifest,本 Service 前缀下的同一份文档
421
+ GET /v1/sessions ?limit&scope&since&provider
422
+ GET /v1/sessions/:id 会话概要
423
+ GET /v1/sessions/:id/turns 逐轮概要
424
+ GET /v1/search ?q=&scope=&since=&limit=&provider=&kind=&regex=&case=
425
+ GET /v1/graph/:id 会话之间的引用关系
426
+ ```
427
+
428
+ 声明的能力:`sessions.list` `sessions.read` `sessions.turns` `sessions.search` `sessions.graph`。
429
+
430
+ 每个会话都带上网络内的地址:
431
+
432
+ ```
433
+ session://<node>/<runtime>/<session_id>
434
+ session://Scott-Mac.local/claude/87f7a60a-a86e-49c5-b711-e463156a5420
435
+ ```
436
+
437
+ **读取即落边。** 请求带 `X-Caller-Session: <调用方会话>` 时,读取当下就写入
438
+ `调用方 --references--> 目标`,与 CLI 的 `SESSION_READER_CALLER_SESSION` 是同一条路径。
439
+ 两端都必须是本地已索引的会话,否则静默跳过——悬空边比没有边更糟。
440
+
441
+ **节点身份**落在 `~/.1agents/session-reader/node.json`,首次启动生成,重启不变;
442
+ `DREAMMATE_NODE_ID` / `DREAMMATE_NODE_NAME` / `DREAMMATE_TAILSCALE_NAME` 可覆盖。
443
+
444
+ **只读、且默认只监听 loopback。** 会话原文含源码、shell 历史和恰好滚过屏幕的密钥,
445
+ 所以走出本机必须是一个刻意动作:显式 `--host`,并且最好配 `--token`(`Authorization: Bearer`)。
446
+ 非 loopback 且无 token 时启动会告警。非 GET 一律 405。
447
+
448
+ > 协议定义来自 [`@1agents/dreammate-network`](https://github.com/scottzx/dreammate-network)(L0)。
449
+ > `src/serve/node.ts` 直接 import 它的类型,不再本地抄一份——单向依赖 L2 → L0 是允许的,
450
+ > 而共用同一份定义才谈得上「公共语言」。**schema 是唯一事实源,改类型先去改那个包。**
451
+ > `/manifest` 的 `metadata.protocol_version` 报告本服务遵循的协议版本。
452
+
405
453
  ## 测试
406
454
 
407
455
  ```bash
@@ -24,6 +24,8 @@ const USAGE = `1session — cross-agent session Read Plane
24
24
  1session workspace [path] [--since 24h] [--limit <n>] [--digest] [--focus <f>] [--json]
25
25
  1session index [<session-id>] [--all] [--scope <path>|cwd|global] [--force] [--since 30d] 建立/刷新索引
26
26
  1session graph <session-id> [--json] 会话之间的引用关系
27
+ 1session serve [--port 7777] [--host 127.0.0.1] [--token <t>]
28
+ 起 HTTP Service,把本机会话接入 DreamMate Network
27
29
  1session skill install|status|uninstall [--agent claude,codex,antigravity]
28
30
  [--copy] [--force] [--dry-run] [--json] 装到三家智能体的 skills 目录
29
31
  1session search <query> [--scope <path>|cwd|global] [--since 24h] [--limit n] [--provider name]
@@ -423,6 +425,18 @@ async function main() {
423
425
  : `${row.id} 尚无关系边(没有任何会话通过 1session 查过它,它也没查过别人)`);
424
426
  break;
425
427
  }
428
+ case 'serve': {
429
+ const { serve } = await import('../src/serve/http.js');
430
+ await serve({
431
+ port: num(flags.port) ?? 7777,
432
+ ...(str(flags.host) ? { host: str(flags.host) } : {}),
433
+ ...(str(flags.token) ? { token: str(flags.token) } : {}),
434
+ ...(str(flags['base-url']) ? { baseUrl: str(flags['base-url']) } : {}),
435
+ });
436
+ // The server owns the process from here; nothing after this resolves.
437
+ await new Promise(() => { });
438
+ break;
439
+ }
426
440
  case 'skill': {
427
441
  const { describeState, installSkill, skillStatus, uninstallSkill, bundledSkillDir, } = await import('../src/skill.js');
428
442
  const agents = str(flags.agent)
@@ -18,3 +18,5 @@ export { captureRuntimeEdge, deriveEdges, edgeEvidence, edgesOf, invocationsOf,
18
18
  export { deriveFacts } from './store/facts.js';
19
19
  export { EDGE_VERSION, EXTRACTOR_VERSION, PARSER_VERSION, SCHEMA_VERSION, } from './store/schema.js';
20
20
  export { SKILL_NAME, agentTargets, bundledSkillDir, describeState, installSkill, skillStatus, uninstallSkill, type AgentStatus, type AgentTarget, type EntryState, type InstallMode, type InstallOptions, type InstallResult, type SkillAgent, type UninstallResult, } from './skill.js';
21
+ export { buildManifest, nodeIdentity, nodeIdentityPath, sessionUri, SESSION_CAPABILITIES, type AccessDescriptor, type NetworkService, type NodeManifest, } from './serve/node.js';
22
+ export { createServer, serve, type ServeOptions } from './serve/http.js';
package/dist/src/index.js CHANGED
@@ -16,3 +16,5 @@ export { captureRuntimeEdge, deriveEdges, edgeEvidence, edgesOf, invocationsOf,
16
16
  export { deriveFacts } from './store/facts.js';
17
17
  export { EDGE_VERSION, EXTRACTOR_VERSION, PARSER_VERSION, SCHEMA_VERSION, } from './store/schema.js';
18
18
  export { SKILL_NAME, agentTargets, bundledSkillDir, describeState, installSkill, skillStatus, uninstallSkill, } from './skill.js';
19
+ export { buildManifest, nodeIdentity, nodeIdentityPath, sessionUri, SESSION_CAPABILITIES, } from './serve/node.js';
20
+ export { createServer, serve } from './serve/http.js';
@@ -0,0 +1,24 @@
1
+ /**
2
+ * `1session serve` — session-reader 作为 DreamMate Network 的第一个标准 Service。
3
+ *
4
+ * 它把本地 Read Plane 原样暴露成网络能力:`1session overview <id>` 成为
5
+ * `sessions.read`。**不重新实现索引与事实层**,只是换一个调用入口。
6
+ *
7
+ * 零运行时依赖:只用 node:http。
8
+ */
9
+ import http from 'node:http';
10
+ export interface ServeOptions {
11
+ port?: number;
12
+ /**
13
+ * Defaults to loopback. Session transcripts contain source code, shell
14
+ * history and whatever secrets happened to scroll past, so going beyond this
15
+ * machine has to be a deliberate act — pass the tailnet address explicitly.
16
+ */
17
+ host?: string;
18
+ /** When set, every request must carry it as `Authorization: Bearer <token>`. */
19
+ token?: string;
20
+ /** Advertised base url, when behind a proxy or a different tailnet name. */
21
+ baseUrl?: string;
22
+ }
23
+ export declare function createServer(options?: ServeOptions): http.Server;
24
+ export declare function serve(options?: ServeOptions): Promise<http.Server>;
@@ -0,0 +1,181 @@
1
+ /**
2
+ * `1session serve` — session-reader 作为 DreamMate Network 的第一个标准 Service。
3
+ *
4
+ * 它把本地 Read Plane 原样暴露成网络能力:`1session overview <id>` 成为
5
+ * `sessions.read`。**不重新实现索引与事实层**,只是换一个调用入口。
6
+ *
7
+ * 零运行时依赖:只用 node:http。
8
+ */
9
+ import http from 'node:http';
10
+ import { listRecentSessions, loadSession } from '../resolver.js';
11
+ import { buildOverview } from '../overview.js';
12
+ import { summarizeTurns } from '../turns.js';
13
+ import { searchSessions } from '../search.js';
14
+ import { canonicalizePath } from '../util/paths.js';
15
+ import { buildManifest, nodeIdentity, sessionUri } from './node.js';
16
+ const json = (res, status, body) => {
17
+ const payload = JSON.stringify(body, null, 2);
18
+ res.writeHead(status, {
19
+ 'content-type': 'application/json; charset=utf-8',
20
+ 'content-length': Buffer.byteLength(payload),
21
+ });
22
+ res.end(payload);
23
+ };
24
+ const q = (ctx, name) => ctx.url.searchParams.get(name) ?? undefined;
25
+ const qn = (ctx, name) => {
26
+ const raw = q(ctx, name);
27
+ if (raw === undefined)
28
+ return undefined;
29
+ const parsed = Number(raw);
30
+ return Number.isFinite(parsed) ? parsed : undefined;
31
+ };
32
+ /**
33
+ * `?scope=` is a path whose subtree is included; omitting it means the whole
34
+ * machine. The CLI defaults to the cwd instead, which is meaningless for a
35
+ * long-running server.
36
+ */
37
+ const scopeOf = (ctx) => {
38
+ const scope = q(ctx, 'scope');
39
+ return scope && scope !== 'global' ? canonicalizePath(scope) : undefined;
40
+ };
41
+ /** Records `caller --references--> target` while the read is happening. */
42
+ async function noteRead(verb, target, caller) {
43
+ if (!caller)
44
+ return;
45
+ try {
46
+ const { openStore } = await import('../store/db.js');
47
+ const { captureRuntimeEdge } = await import('../store/edges.js');
48
+ captureRuntimeEdge(await openStore(), verb, target, caller);
49
+ }
50
+ catch {
51
+ // An edge is a nice-to-have; never fail the read over it.
52
+ }
53
+ }
54
+ async function route(ctx) {
55
+ const { pathname } = ctx.url;
56
+ const identity = nodeIdentity();
57
+ if (pathname === '/health') {
58
+ return { status: 200, body: { status: 'ok', node_id: identity.node_id, service: 'session-registry' } };
59
+ }
60
+ // `/manifest` is the network-wide contract; `/v1/node` is the same document
61
+ // under this service's own prefix.
62
+ if (pathname === '/manifest' || pathname === '/v1/node') {
63
+ return { status: 200, body: buildManifest(ctx.url.origin) };
64
+ }
65
+ if (pathname === '/v1/sessions') {
66
+ const refs = await listRecentSessions({
67
+ limit: qn(ctx, 'limit') ?? 20,
68
+ workspace: scopeOf(ctx),
69
+ since: q(ctx, 'since'),
70
+ provider: q(ctx, 'provider'),
71
+ useIndex: true,
72
+ });
73
+ return {
74
+ status: 200,
75
+ body: {
76
+ node: identity.name,
77
+ sessions: refs.map((ref) => ({ ...ref, uri: sessionUri(identity.name, ref.provider, ref.id) })),
78
+ },
79
+ };
80
+ }
81
+ const detail = /^\/v1\/sessions\/([^/]+)(\/turns)?$/.exec(pathname);
82
+ if (detail) {
83
+ const id = decodeURIComponent(detail[1]);
84
+ const wantTurns = detail[2] !== undefined;
85
+ const session = await loadSession(id, { useIndex: true });
86
+ await noteRead(wantTurns ? 'turns' : 'overview', id, ctx.caller);
87
+ const uri = sessionUri(identity.name, session.ref.provider, session.ref.id);
88
+ return wantTurns
89
+ ? { status: 200, body: { uri, turns: summarizeTurns(session) } }
90
+ : { status: 200, body: { uri, ...buildOverview(session) } };
91
+ }
92
+ if (pathname === '/v1/search') {
93
+ const query = q(ctx, 'q') ?? q(ctx, 'query');
94
+ if (!query)
95
+ return { status: 400, body: { error: 'missing ?q=' } };
96
+ const hits = await searchSessions(query, {
97
+ workspace: scopeOf(ctx),
98
+ since: q(ctx, 'since'),
99
+ limit: qn(ctx, 'limit'),
100
+ provider: q(ctx, 'provider'),
101
+ kinds: q(ctx, 'kind')?.split(',').map((k) => k.trim()).filter(Boolean),
102
+ regex: q(ctx, 'regex') === 'true',
103
+ caseSensitive: q(ctx, 'case') === 'true',
104
+ context: qn(ctx, 'context'),
105
+ maxPerSession: qn(ctx, 'max-hits'),
106
+ useIndex: true,
107
+ });
108
+ return {
109
+ status: 200,
110
+ body: {
111
+ query,
112
+ total: hits.reduce((sum, hit) => sum + hit.totalMatches, 0),
113
+ hits: hits.map((hit) => ({
114
+ ...hit,
115
+ uri: sessionUri(identity.name, hit.session.provider, hit.session.id),
116
+ })),
117
+ },
118
+ };
119
+ }
120
+ const graph = /^\/v1\/graph\/([^/]+)$/.exec(pathname);
121
+ if (graph) {
122
+ const id = decodeURIComponent(graph[1]);
123
+ const { openStore } = await import('../store/db.js');
124
+ const { edgeEvidence, edgesOf } = await import('../store/edges.js');
125
+ const { findSessionRow } = await import('../store/read.js');
126
+ await loadSession(id, { useIndex: true }); // make sure it is indexed first
127
+ await noteRead('graph', id, ctx.caller);
128
+ const db = await openStore();
129
+ const row = findSessionRow(db, id);
130
+ if (!row)
131
+ return { status: 404, body: { error: `session not found: ${id}` } };
132
+ const edges = edgesOf(db, row.id).map((edge) => ({
133
+ ...edge,
134
+ evidence: edgeEvidence(db, edge.from, edge.to, edge.relation),
135
+ }));
136
+ return { status: 200, body: { session: row.id, edges } };
137
+ }
138
+ return { status: 404, body: { error: `no route: ${pathname}` } };
139
+ }
140
+ export function createServer(options = {}) {
141
+ return http.createServer((req, res) => {
142
+ void (async () => {
143
+ try {
144
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
145
+ return json(res, 405, { error: 'read-only service: GET only' });
146
+ }
147
+ const url = new URL(req.url ?? '/', options.baseUrl ?? `http://${req.headers.host ?? 'localhost'}`);
148
+ if (options.token) {
149
+ const bearer = /^Bearer (.+)$/.exec(req.headers.authorization ?? '')?.[1];
150
+ if (bearer !== options.token)
151
+ return json(res, 401, { error: 'unauthorized' });
152
+ }
153
+ const caller = req.headers['x-caller-session'];
154
+ const { status, body } = await route({
155
+ url,
156
+ caller: typeof caller === 'string' ? caller : undefined,
157
+ });
158
+ json(res, status, body);
159
+ }
160
+ catch (error) {
161
+ const message = error instanceof Error ? error.message : String(error);
162
+ // A bad session id reads as "not found", not as a server fault.
163
+ json(res, /not found|missing|no session/i.test(message) ? 404 : 500, { error: message });
164
+ }
165
+ })();
166
+ });
167
+ }
168
+ export async function serve(options = {}) {
169
+ const host = options.host ?? '127.0.0.1';
170
+ const server = createServer(options);
171
+ await new Promise((resolve) => server.listen(options.port ?? 7777, host, resolve));
172
+ const { port } = server.address();
173
+ const identity = nodeIdentity();
174
+ console.log(`1session serve — node ${identity.name} (${identity.node_id})`);
175
+ console.log(` http://${host}:${port}/manifest`);
176
+ console.log(` capabilities: sessions.list, sessions.read, sessions.turns, sessions.search, sessions.graph`);
177
+ if (host !== '127.0.0.1' && host !== 'localhost' && !options.token) {
178
+ console.warn(` ⚠️ 绑定在 ${host} 且未设置 --token:会话原文(代码、shell 历史、密钥)将对该网络开放。`);
179
+ }
180
+ return server;
181
+ }
@@ -0,0 +1,26 @@
1
+ import { PROTOCOL_VERSION, type AccessDescriptor, type NodeManifest, type Service as NetworkService, type SessionURI } from '@1agents/dreammate-network';
2
+ export { PROTOCOL_VERSION };
3
+ export type { AccessDescriptor, NetworkService, NodeManifest };
4
+ /** Where the node identity is kept, next to the index db. */
5
+ export declare function nodeIdentityPath(): string;
6
+ interface StoredIdentity {
7
+ node_id: string;
8
+ name: string;
9
+ type: string;
10
+ }
11
+ /**
12
+ * A stable node id that survives restarts, generated on first use and kept in
13
+ * `~/.1agents/session-reader/node.json`.
14
+ *
15
+ * `DREAMMATE_NODE_ID` / `DREAMMATE_NODE_NAME` override it without touching the
16
+ * file, which is what a container or a second instance on one host wants.
17
+ */
18
+ export declare function nodeIdentity(): StoredIdentity;
19
+ /**
20
+ * The capabilities this Service answers for. Names are the network-facing
21
+ * spelling of the CLI verbs: `1session overview` is `sessions.read`.
22
+ */
23
+ export declare const SESSION_CAPABILITIES: readonly ["sessions.list", "sessions.read", "sessions.turns", "sessions.search", "sessions.graph"];
24
+ export declare function buildManifest(baseUrl: string): NodeManifest;
25
+ /** `session://<node>/<runtime>/<session_id>` — the network-wide address of one session. */
26
+ export declare function sessionUri(nodeName: string, provider: string, id: string): SessionURI;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Node identity and Manifest — session-reader 作为 DreamMate Network 的第一个标准 Service。
3
+ *
4
+ * 协议类型直接来自 L0 包 `@1agents/dreammate-network`,不再本地抄一份:
5
+ * 单向依赖 L2 → L0 是允许的,而共用同一份定义才谈得上「公共语言」。
6
+ */
7
+ import fs from 'node:fs';
8
+ import os from 'node:os';
9
+ import path from 'node:path';
10
+ import { randomUUID } from 'node:crypto';
11
+ import { PROTOCOL_VERSION, } from '@1agents/dreammate-network';
12
+ export { PROTOCOL_VERSION };
13
+ /* ---------- 身份 ---------- */
14
+ /** Where the node identity is kept, next to the index db. */
15
+ export function nodeIdentityPath() {
16
+ return path.join(os.homedir(), '.1agents', 'session-reader', 'node.json');
17
+ }
18
+ const PLATFORM_TYPE = {
19
+ darwin: 'macos',
20
+ linux: 'linux',
21
+ win32: 'windows',
22
+ };
23
+ /**
24
+ * A stable node id that survives restarts, generated on first use and kept in
25
+ * `~/.1agents/session-reader/node.json`.
26
+ *
27
+ * `DREAMMATE_NODE_ID` / `DREAMMATE_NODE_NAME` override it without touching the
28
+ * file, which is what a container or a second instance on one host wants.
29
+ */
30
+ export function nodeIdentity() {
31
+ const type = PLATFORM_TYPE[process.platform] ?? process.platform;
32
+ const envId = process.env.DREAMMATE_NODE_ID?.trim();
33
+ const envName = process.env.DREAMMATE_NODE_NAME?.trim();
34
+ if (envId && envName)
35
+ return { node_id: envId, name: envName, type };
36
+ const file = nodeIdentityPath();
37
+ let stored = {};
38
+ try {
39
+ stored = JSON.parse(fs.readFileSync(file, 'utf8'));
40
+ }
41
+ catch {
42
+ // First run, or an unreadable file we are about to overwrite.
43
+ }
44
+ if (!stored.node_id) {
45
+ stored = { node_id: `node_${randomUUID().replace(/-/g, '').slice(0, 12)}`, name: os.hostname(), type };
46
+ fs.mkdirSync(path.dirname(file), { recursive: true });
47
+ fs.writeFileSync(file, `${JSON.stringify(stored, null, 2)}\n`);
48
+ }
49
+ return {
50
+ node_id: envId ?? stored.node_id,
51
+ name: envName ?? stored.name ?? os.hostname(),
52
+ type,
53
+ };
54
+ }
55
+ /* ---------- Manifest ---------- */
56
+ /**
57
+ * The capabilities this Service answers for. Names are the network-facing
58
+ * spelling of the CLI verbs: `1session overview` is `sessions.read`.
59
+ */
60
+ export const SESSION_CAPABILITIES = [
61
+ 'sessions.list',
62
+ 'sessions.read',
63
+ 'sessions.turns',
64
+ 'sessions.search',
65
+ 'sessions.graph',
66
+ ];
67
+ export function buildManifest(baseUrl) {
68
+ const identity = nodeIdentity();
69
+ return {
70
+ ...identity,
71
+ tailscale_name: process.env.DREAMMATE_TAILSCALE_NAME?.trim() ?? identity.name,
72
+ online: true,
73
+ metadata: { protocol_version: PROTOCOL_VERSION },
74
+ services: [
75
+ {
76
+ id: 'session-registry',
77
+ name: 'session-reader',
78
+ kind: 'session_registry',
79
+ capabilities: [...SESSION_CAPABILITIES],
80
+ resources: [{ scheme: 'session', description: 'session://<node>/<runtime>/<session_id>' }],
81
+ access: [{ protocol: 'http', base_url: `${baseUrl}/v1` }],
82
+ },
83
+ ],
84
+ };
85
+ }
86
+ /** `session://<node>/<runtime>/<session_id>` — the network-wide address of one session. */
87
+ export function sessionUri(nodeName, provider, id) {
88
+ return `session://${nodeName}/${provider}/${id}`;
89
+ }
@@ -27,7 +27,13 @@ export declare function deriveEdges(db: DatabaseSync, id: string, session: Norma
27
27
  * Injected by a hook or the ACP context as `SESSION_READER_CALLER_SESSION`;
28
28
  * absent everywhere else, in which case nothing is written.
29
29
  */
30
- export declare function captureRuntimeEdge(db: DatabaseSync, verb: string, target: string): void;
30
+ export declare function captureRuntimeEdge(db: DatabaseSync, verb: string, target: string,
31
+ /**
32
+ * Who is doing the reading. Defaults to the CLI's injected caller; `1session
33
+ * serve` passes the `X-Caller-Session` header instead, so one process can
34
+ * serve several callers without going through the environment.
35
+ */
36
+ callerId?: string | undefined): void;
31
37
  export interface EdgeView {
32
38
  from: string;
33
39
  to: string;
@@ -102,8 +102,14 @@ export function deriveEdges(db, id, session) {
102
102
  * Injected by a hook or the ACP context as `SESSION_READER_CALLER_SESSION`;
103
103
  * absent everywhere else, in which case nothing is written.
104
104
  */
105
- export function captureRuntimeEdge(db, verb, target) {
106
- const caller = process.env.SESSION_READER_CALLER_SESSION?.trim();
105
+ export function captureRuntimeEdge(db, verb, target,
106
+ /**
107
+ * Who is doing the reading. Defaults to the CLI's injected caller; `1session
108
+ * serve` passes the `X-Caller-Session` header instead, so one process can
109
+ * serve several callers without going through the environment.
110
+ */
111
+ callerId = process.env.SESSION_READER_CALLER_SESSION) {
112
+ const caller = callerId?.trim();
107
113
  const relation = VERB_RELATION[verb];
108
114
  if (!caller || !relation)
109
115
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1agents/session-reader",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
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",
@@ -55,5 +55,8 @@
55
55
  "@types/node": "^22.10.2",
56
56
  "tsx": "^4.19.2",
57
57
  "typescript": "^5.7.2"
58
+ },
59
+ "dependencies": {
60
+ "@1agents/dreammate-network": "^0.1.0"
58
61
  }
59
62
  }