nx-ce 0.1.7 → 0.1.8
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 +23 -3
- package/package.json +1 -1
- package/src/serve.js +69 -17
package/README.md
CHANGED
|
@@ -148,18 +148,38 @@ proj-a:D~project-a~src → SDK 会话 C
|
|
|
148
148
|
|
|
149
149
|
### Idle auto-reclaim / 空闲自动回收
|
|
150
150
|
|
|
151
|
-
客户端断开连接 5 分钟后,session
|
|
151
|
+
客户端断开连接 5 分钟后,session 自动销毁。**历史会话保留**:
|
|
152
|
+
内存中的 session 销毁,但磁盘状态文件保留并标记为 `lifecycleState: 'stopped'`。
|
|
153
|
+
`listSessions` 会同时返回活跃 session(绿色)和历史 session(灰色虚线,可恢复)。
|
|
152
154
|
|
|
153
155
|
### List sessions / 查看会话
|
|
154
156
|
|
|
155
157
|
```javascript
|
|
156
158
|
→ { "type": "listSessions" }
|
|
157
159
|
← { "type": "session_list", "sessions": [
|
|
158
|
-
{ "name": "proj-a", "cwd": "D:/project-a", "sessionId": "sess_xxx",
|
|
159
|
-
|
|
160
|
+
{ "name": "proj-a", "cwd": "D:/project-a", "sessionId": "sess_xxx",
|
|
161
|
+
"processing": false, "lifecycleState": "active" },
|
|
162
|
+
{ "name": "proj-b", "cwd": "D:/project-b", "sessionId": "sess_yyy",
|
|
163
|
+
"lifecycleState": "stopped",
|
|
164
|
+
"startedAt": "...", "updatedAt": "..." }
|
|
160
165
|
]}
|
|
161
166
|
```
|
|
162
167
|
|
|
168
|
+
| lifecycleState | 含义 |
|
|
169
|
+
|----------------|------|
|
|
170
|
+
| `active` | 内存中活跃,可直接发 query |
|
|
171
|
+
| `stopped` | 已关闭但保留在磁盘,发 query 会自动 resume(`options.resume = sessionId`) |
|
|
172
|
+
|
|
173
|
+
### Resume a historical session / 恢复历史会话
|
|
174
|
+
|
|
175
|
+
直接对历史 session 发 `query`,服务端会自动用磁盘上保存的 `sessionId` 续接:
|
|
176
|
+
|
|
177
|
+
```javascript
|
|
178
|
+
→ { "type": "query", "session": "proj-a", "cwd": "D:/project-a", "prompt": "继续上次的话题" }
|
|
179
|
+
// server: readState('proj-a:D~/project-a') → sessionId → options.resume
|
|
180
|
+
// 上下文自动恢复
|
|
181
|
+
```
|
|
182
|
+
|
|
163
183
|
---
|
|
164
184
|
|
|
165
185
|
## WebSocket Protocol / WebSocket 协议
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nx-ce",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Claude Engine — SDK adapter layer for native messaging host. Bridges @anthropic-ai/claude-agent-sdk calls over a length-prefixed JSON protocol.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/serve.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { WebSocketServer } from 'ws';
|
|
19
19
|
import { query as agentQuery } from '@anthropic-ai/claude-agent-sdk';
|
|
20
20
|
import { hostname, machine, platform, release } from 'node:os';
|
|
21
|
-
import { readState, writeState, deleteState, LifecycleState, createState } from './session-store.js';
|
|
21
|
+
import { readState, writeState, deleteState, listStates, LifecycleState, createState } from './session-store.js';
|
|
22
22
|
import { generateId, MonotonicClock, getMachineId } from './util.js';
|
|
23
23
|
|
|
24
24
|
/** 默认端口 */
|
|
@@ -365,8 +365,13 @@ class SessionManager {
|
|
|
365
365
|
/**
|
|
366
366
|
* 销毁一个 session。
|
|
367
367
|
* @param {string} key - 内部 key(name:cwd)
|
|
368
|
+
* @param {string} reason - 'shutdown' | 'client request' | 'idle timeout' | 'crash'
|
|
369
|
+
* @param {object} [opts]
|
|
370
|
+
* @param {boolean} [opts.keepHistory=true] - 是否保留磁盘状态(标记为 closed)
|
|
371
|
+
* 仅 'shutdown' 和 'crash' 会删除;其他情况保留为历史记录
|
|
368
372
|
*/
|
|
369
|
-
async destroy(key, reason = 'shutdown') {
|
|
373
|
+
async destroy(key, reason = 'shutdown', opts = {}) {
|
|
374
|
+
const { keepHistory = true } = opts;
|
|
370
375
|
const session = this.sessions.get(key);
|
|
371
376
|
if (!session || session.closed) return;
|
|
372
377
|
session.closed = true;
|
|
@@ -379,26 +384,39 @@ class SessionManager {
|
|
|
379
384
|
|
|
380
385
|
this.sessions.delete(key);
|
|
381
386
|
|
|
382
|
-
if (reason
|
|
387
|
+
if (reason === 'shutdown' || reason === 'crash' || !keepHistory) {
|
|
383
388
|
deleteState(key);
|
|
389
|
+
} else {
|
|
390
|
+
// 标记为 closed,保留历史供 listSessions / resume 使用
|
|
391
|
+
const prev = readState(key);
|
|
392
|
+
if (prev) {
|
|
393
|
+
writeState(key, {
|
|
394
|
+
...prev,
|
|
395
|
+
lifecycleState: LifecycleState.STOPPED,
|
|
396
|
+
closedAt: new Date().toISOString(),
|
|
397
|
+
});
|
|
398
|
+
}
|
|
384
399
|
}
|
|
385
400
|
}
|
|
386
401
|
|
|
387
402
|
/**
|
|
388
403
|
* 按客户端 name 销毁匹配的所有 session(包括不同 cwd)。
|
|
389
404
|
* @param {string} name - 客户端传入的 session 名称
|
|
405
|
+
* @param {object} [opts] - 透传给 destroy()
|
|
390
406
|
*/
|
|
391
|
-
async destroyByName(name) {
|
|
407
|
+
async destroyByName(name, opts = {}) {
|
|
392
408
|
const keys = [...this.sessions.keys()].filter(k => baseName(k) === name);
|
|
393
|
-
await Promise.allSettled(keys.map(k => this.destroy(k, 'client request')));
|
|
409
|
+
await Promise.allSettled(keys.map(k => this.destroy(k, 'client request', opts)));
|
|
394
410
|
}
|
|
395
411
|
|
|
396
412
|
/**
|
|
397
413
|
* 销毁所有 session。
|
|
414
|
+
* @param {string} reason
|
|
415
|
+
* @param {object} [opts]
|
|
398
416
|
*/
|
|
399
|
-
async destroyAll(reason = 'shutdown') {
|
|
417
|
+
async destroyAll(reason = 'shutdown', opts = {}) {
|
|
400
418
|
const keys = [...this.sessions.keys()];
|
|
401
|
-
await Promise.allSettled(keys.map(k => this.destroy(k, reason)));
|
|
419
|
+
await Promise.allSettled(keys.map(k => this.destroy(k, reason, opts)));
|
|
402
420
|
}
|
|
403
421
|
}
|
|
404
422
|
|
|
@@ -532,30 +550,64 @@ export async function startServe(options) {
|
|
|
532
550
|
|
|
533
551
|
case 'closeSession': {
|
|
534
552
|
if (req.cwd) {
|
|
535
|
-
// 精确关闭:name + cwd
|
|
553
|
+
// 精确关闭:name + cwd(保留历史)
|
|
536
554
|
const key = sessionKey(sessionName, req.cwd);
|
|
537
|
-
await sessionManager.destroy(key, 'client request');
|
|
555
|
+
await sessionManager.destroy(key, 'client request', { keepHistory: true });
|
|
538
556
|
ws.send(JSON.stringify({ type: 'session_closed', session: sessionName, cwd: req.cwd }));
|
|
539
557
|
} else {
|
|
540
|
-
// 关闭该 name 下所有 cwd
|
|
541
|
-
await sessionManager.destroyByName(sessionName);
|
|
558
|
+
// 关闭该 name 下所有 cwd 变体(保留历史)
|
|
559
|
+
await sessionManager.destroyByName(sessionName, { keepHistory: true });
|
|
542
560
|
ws.send(JSON.stringify({ type: 'session_closed', session: sessionName }));
|
|
543
561
|
}
|
|
544
562
|
break;
|
|
545
563
|
}
|
|
546
564
|
|
|
547
565
|
case 'listSessions': {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
566
|
+
// 合并:内存中活跃 session + 磁盘上历史 session
|
|
567
|
+
// 活跃优先(同 key 用活跃的元数据覆盖历史的)
|
|
568
|
+
const activeByKey = new Map();
|
|
569
|
+
for (const [key, s] of sessionManager.sessions) {
|
|
570
|
+
if (s.closed) continue;
|
|
571
|
+
activeByKey.set(key, {
|
|
552
572
|
key,
|
|
573
|
+
name: s.name,
|
|
553
574
|
cwd: s.cwd,
|
|
554
575
|
sessionId: s.sessionId,
|
|
576
|
+
model: s.sdkOptions?.model,
|
|
555
577
|
queueLength: s.queue.length,
|
|
556
578
|
processing: s.processing,
|
|
557
|
-
|
|
558
|
-
|
|
579
|
+
lifecycleState: 'active',
|
|
580
|
+
startedAt: s.existingState?.startedAt,
|
|
581
|
+
updatedAt: s.existingState?.updatedAt,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// 磁盘历史(每个 instance 文件对应一个 session)
|
|
586
|
+
// 跳过服务器级 entry(cwd === null 且 name 是 single token)
|
|
587
|
+
const historical = [];
|
|
588
|
+
for (const { name, state } of listStates()) {
|
|
589
|
+
// 服务器级 state 缺少 cwd 字段,跳过
|
|
590
|
+
if (!state || !state.cwd) continue;
|
|
591
|
+
// 已被活跃集合包含的,跳过
|
|
592
|
+
if (activeByKey.has(name)) continue;
|
|
593
|
+
historical.push({
|
|
594
|
+
key: name,
|
|
595
|
+
name: baseName(name),
|
|
596
|
+
cwd: state.cwd,
|
|
597
|
+
sessionId: state.sessionId,
|
|
598
|
+
model: state.model,
|
|
599
|
+
queueLength: 0,
|
|
600
|
+
processing: false,
|
|
601
|
+
lifecycleState: state.lifecycleState || 'closed',
|
|
602
|
+
startedAt: state.startedAt,
|
|
603
|
+
updatedAt: state.updatedAt,
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
ws.send(JSON.stringify({
|
|
608
|
+
type: 'session_list',
|
|
609
|
+
sessions: [...activeByKey.values(), ...historical],
|
|
610
|
+
}));
|
|
559
611
|
break;
|
|
560
612
|
}
|
|
561
613
|
|