@deepseek-ai/dsh-workspace 0.2.1-alpha.2 → 0.2.1-alpha.4
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.i18n.yaml +8 -8
- package/README.md +4 -4
- package/README.zh.md +4 -4
- package/lib/index.js +11 -6
- package/lib/types/entity.d.ts +7 -6
- package/lib/types/entity.js +3 -3
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +12 -4
- package/lib/types/types.d.ts +14 -5
- package/locale/en.json +5 -0
- package/locale/zh.json +6 -0
- package/package.json +18 -16
package/README.i18n.yaml
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
en: 7a12adba4e7766e3
|
|
10
10
|
zh: f6d5c8374f4512c8
|
|
11
11
|
/deepseek-ai-dsh-workspace/summary:
|
|
12
|
-
en:
|
|
13
|
-
zh:
|
|
12
|
+
en: 30aea62cabd8e8a8
|
|
13
|
+
zh: b22b30ef08fd4713
|
|
14
14
|
/deepseek-ai-dsh-workspace/table-of-contents:
|
|
15
15
|
en: d152484eb41ac6b4
|
|
16
16
|
zh: 09388d293f9be9cb
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
en: 217475057b6506ed
|
|
31
31
|
zh: be2312a29499c203
|
|
32
32
|
/deepseek-ai-dsh-workspace/use-this-package/grouping-sessions-under-a-project:
|
|
33
|
-
en:
|
|
34
|
-
zh:
|
|
33
|
+
en: 5a68da22516a6a3c
|
|
34
|
+
zh: a229d57acdab882c
|
|
35
35
|
/deepseek-ai-dsh-workspace/use-this-package/hiding-and-restoring-sessions-and-removing-projects:
|
|
36
36
|
en: 7b298aa5c48501d4
|
|
37
37
|
zh: 8ffeec728c1c13fe
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
en: 447ced401b3b14a8
|
|
40
40
|
zh: 3ce2105605223945
|
|
41
41
|
/deepseek-ai-dsh-workspace/understand-the-implementation/design-philosophy:
|
|
42
|
-
en:
|
|
43
|
-
zh:
|
|
42
|
+
en: e969314497b99389
|
|
43
|
+
zh: 82eafc832617a1eb
|
|
44
44
|
/deepseek-ai-dsh-workspace/understand-the-implementation/api-behavior:
|
|
45
45
|
en: 69327adb9ea52680
|
|
46
46
|
zh: fa32eea5019c7abb
|
|
@@ -75,8 +75,8 @@
|
|
|
75
75
|
en: 524afbded27fb06b
|
|
76
76
|
zh: 9dcf8539495e9c68
|
|
77
77
|
/deepseek-ai-dsh-workspace/known-limitations-and-deferred-work:
|
|
78
|
-
en:
|
|
79
|
-
zh:
|
|
78
|
+
en: 1a9c71a3dbfc4506
|
|
79
|
+
zh: 87b57414053f4d00
|
|
80
80
|
/deepseek-ai-dsh-workspace/known-limitations-and-deferred-work/dev-note:
|
|
81
81
|
en: ac1d08360a93382b
|
|
82
82
|
zh: 6c58749bae9a6e65
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
|
|
9
9
|
|
|
10
10
|
## Summary
|
|
11
11
|
|
|
12
|
-
Use this package to keep an ordered, persistent list of project directories and the sessions run in each directory. Hosts can build project sidebars, hide sessions from grouping without deleting their histories, and remove projects without deleting folders, files, or sessions. Re-adding a removed directory creates a fresh project
|
|
12
|
+
Use this package to keep an ordered, persistent list of project directories and the sessions run in each directory. Hosts can build project sidebars, hide sessions from grouping without deleting their histories, and remove projects without deleting folders, files, or sessions. Re-adding a removed directory creates a fresh project; an existing project's sessions stay grouped when its directory disappears. Choose it for GUI or host workflows that need durable project grouping; it is invisible to models and adds no prompt or request-context cost, but requires session persistence and storage backends.
|
|
13
13
|
|
|
14
14
|
## Table of Contents
|
|
15
15
|
|
|
@@ -70,7 +70,7 @@ The first successful registration records its identity durably. Repeated calls r
|
|
|
70
70
|
|
|
71
71
|
### Grouping sessions under a project
|
|
72
72
|
|
|
73
|
-
A session joins the project of the directory it runs in: create a session in a project's directory and it appears under that project, newest first. A session can only belong to one project. A session
|
|
73
|
+
A session joins the project of the directory it runs in: create a session in a project's directory and it appears under that project, newest first. A session can only belong to one project. A new session must have a recorded directory that resolves to the project's directory. Already-accounted sessions keep their project and order when their fully qualified recorded path is missing, including a dangling symlink; renaming or reordering the project does not remove that ownership. Missing headers, invalid recorded paths, existing mismatched directories, and filesystem failures other than ENOENT remain excluded. Keeping ownership does not recreate the directory or relax new-session attachment, so a fork can still fail to attach while the directory is missing.
|
|
74
74
|
|
|
75
75
|
### Hiding and restoring sessions, and removing projects
|
|
76
76
|
|
|
@@ -91,7 +91,7 @@ This section explains the design decisions behind the feature and points at the
|
|
|
91
91
|
### Design philosophy
|
|
92
92
|
|
|
93
93
|
- **One record per canonical path.** `fs.realpath` is the single uniqueness canon: paths are stored canonicalized, so a symlink to an owned directory collides, and uniqueness is string equality of canonical paths.
|
|
94
|
-
- **Membership
|
|
94
|
+
- **Membership starts from the recorded account.** The header index validates ordered `sessionIds`; missing-directory candidates retain their account, while other invalid candidates are filtered on read and pruned by the next mutation. The strict canonical-path index used for history bootstrap includes existing directories only.
|
|
95
95
|
- **Header-only reads.** Bootstrap and attach validation read `SessionHeader` fields only; event bodies are never loaded.
|
|
96
96
|
- **Two-write mutations with an explicit marker.** Create and delete persist a `pendingMutation` marker before the record/order pair can diverge, so startup completes exactly the interrupted operation and unmarked divergence fails loud as corruption.
|
|
97
97
|
- **Serialized writes.** Registry operations run on one operation chain; entity mutations go through `table.update` on the domain write chain, stamping `updatedAt` and deciding membership at their chain slot.
|
|
@@ -166,7 +166,7 @@ Independent of live requests: the package never touches a request prefix, so it
|
|
|
166
166
|
These limits define when the project list is a poor fit or needs special operational care. They are current package constraints, not a task backlog.
|
|
167
167
|
|
|
168
168
|
- **Removal never deletes data** — removing a project leaves its folder, files, and session histories in place; those sessions become ungrouped, and session deletion or folder removal are separate, absent capabilities.
|
|
169
|
-
- **
|
|
169
|
+
- **Missing paths retain recorded ownership, not verified symlink history** — a removed symlink and an incorrect but missing absolute cwd cannot be distinguished from existing records; both preserve an already-accounted id. No unaccounted session is assigned by this exception, and an account already removed from storage is not reconstructed.
|
|
170
170
|
- **External changes are seen late** — if another process deletes or damages a directory, the project reflects it only at the next refresh or restart.
|
|
171
171
|
- **Archive and unarchive enforce different session checks** — a restore only drops an id from the archive set, so an entry whose session is gone still unarchives and leaves no unknown referent; a restore of an id that is not archived resolves without writing, while `archiveSession` rejects a session that is neither live nor persisted.
|
|
172
172
|
- **The activity check and the archive write are not one atomic step** — a turn that starts between the providers' answer and the durable write is hidden while running, and every model step whose `agent/pre-step` precedes the write still runs with its tool calls; the API Session Controller's gate ends the first step proposed after the write as `blocked`, so the exposure is bounded by that write's latency, in practice one model step.
|
package/README.zh.md
CHANGED
|
@@ -9,7 +9,7 @@ kind: "package-reference"
|
|
|
9
9
|
|
|
10
10
|
## 概述
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
使用此包可以维护一个有序、持久的项目目录列表,以及在每个目录中运行的会话。宿主可以构建项目侧边栏、在不删除历史的情况下把会话从分组中隐藏,并在不删除文件夹、文件或会话的情况下移除项目。重新添加已移除的目录会创建一个全新项目;已有项目的目录消失时,其会话仍保持原分组。需要持久项目分组的 GUI 或宿主工作流适合使用它;它对模型不可见,不增加提示词或请求上下文成本,但需要会话持久化与存储后端。
|
|
13
13
|
|
|
14
14
|
## 目录
|
|
15
15
|
|
|
@@ -70,7 +70,7 @@ ctx.workspaceRegistry.list() // shows the project, newest first
|
|
|
70
70
|
|
|
71
71
|
### 将会话归入项目
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
会话加入它运行目录所在的项目:在项目目录中创建会话,它就会出现在该项目下,新到旧排列。一个会话只能属于一个项目。新会话必须记录可解析为项目目录的工作目录。已记账会话的完整绝对路径缺失时(包括悬空符号链接),仍保留原项目与顺序;重命名或排序项目不会移除该归属。头部缺失、无效记录路径、现存但不匹配的目录,以及 ENOENT 以外的文件系统错误仍会被排除。保留归属不会重建目录,也不放宽新会话挂接要求,因此目录缺失时 Fork 仍可能挂接失败。
|
|
74
74
|
|
|
75
75
|
### 隐藏、恢复会话与移除项目
|
|
76
76
|
|
|
@@ -91,7 +91,7 @@ Workspace 注册不意味着拥有其目录。未来的破坏性文件系统操
|
|
|
91
91
|
### 设计理念
|
|
92
92
|
|
|
93
93
|
- **每个规范路径一条记录。** `fs.realpath` 是唯一的一套唯一性规范:路径以规范化形式存储,因此指向已有记录目录的符号链接会与之冲突,唯一性即规范路径的字符串相等。
|
|
94
|
-
-
|
|
94
|
+
- **成员资格以已记录的归属为起点。** 头部索引校验有序的 `sessionIds`;目录缺失的候选保留归属,其他无效候选在读取时过滤,并在下一次变更时剪除。历史引导使用的严格规范路径索引只包含现存目录。
|
|
95
95
|
- **仅读取头部。** 引导与 attach 校验只读取 `SessionHeader` 字段;事件正文绝不加载。
|
|
96
96
|
- **两次写入的变更带显式标记。** 创建与删除在记录/顺序对可能分叉之前先持久化 `pendingMutation` 标记,因此启动只补全被中断的操作,未标记的分叉作为损坏明确报错。
|
|
97
97
|
- **串行化写入。** 注册表操作跑在同一条操作链上;实体变更通过领域写链上的 `table.update` 执行,写入 `updatedAt`,并在其所在的链位置决定成员资格。
|
|
@@ -167,7 +167,7 @@ Workspace 注册不意味着拥有其目录。未来的破坏性文件系统操
|
|
|
167
167
|
这些限制说明项目列表何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是任务积压。
|
|
168
168
|
|
|
169
169
|
- **移除绝不删除数据**——移除项目会保留其文件夹、文件与会话历史;这些会话变成 Ungrouped,而会话删除与文件夹移除是彼此独立且尚未提供的功能。
|
|
170
|
-
-
|
|
170
|
+
- **路径缺失时保留记录归属,而非重新验证符号链接历史**——现有记录无法区分被移除的符号链接与错误但不存在的绝对 cwd;两者均保留已记账 id。此例外不吸收未记账会话,也不重建已经从存储中删除的归属。
|
|
171
171
|
- **外部变更延迟可见**——如果另一进程删除或损坏目录,项目只能在下次刷新或重启后反映出来。
|
|
172
172
|
- **归档与取消归档执行不同的会话校验**——恢复只是从归档集合中移除 id,因此会话已不存在的条目仍能取消归档,也不会留下未知引用;对未归档 id 执行恢复不写盘即完成,而 `archiveSession` 会拒绝既非实时也未持久化的会话。
|
|
173
173
|
- **活动检查与归档写入不是一个原子步骤**——在提供方作答与持久化写入之间开始的回合会在隐藏状态下运行,`agent/pre-step` 先于该写入的每个模型步连同其工具调用照常执行;API Session Controller 的门禁把写入之后提出的第一步以 `blocked` 收口,因此暴露面以该写入的时延为界,实际上是一个模型步。
|
package/lib/index.js
CHANGED
|
@@ -100,7 +100,7 @@ var WorkspaceEntity = class {
|
|
|
100
100
|
return this.record.updatedAt;
|
|
101
101
|
}
|
|
102
102
|
get sessionIds() {
|
|
103
|
-
return this.record.sessionIds.filter((id) => this.host.
|
|
103
|
+
return this.record.sessionIds.filter((id) => this.host.candidateMatches(id, this.record.path));
|
|
104
104
|
}
|
|
105
105
|
async setTitle(title) {
|
|
106
106
|
await this.mutate((record) => ({
|
|
@@ -161,7 +161,7 @@ var WorkspaceEntity = class {
|
|
|
161
161
|
/**
|
|
162
162
|
* The single write path: run `fn` on the domain write chain via
|
|
163
163
|
* `table.update`, stamping `updatedAt` and pruning candidates that no
|
|
164
|
-
* longer pass the
|
|
164
|
+
* longer pass the candidate membership check, then swap the
|
|
165
165
|
* snapshot.
|
|
166
166
|
*
|
|
167
167
|
* `fn` sees the value current at its chain slot, so membership decisions
|
|
@@ -175,7 +175,7 @@ var WorkspaceEntity = class {
|
|
|
175
175
|
try {
|
|
176
176
|
next = await this.host.table().update(this.id, (current) => {
|
|
177
177
|
const changed = fn(current);
|
|
178
|
-
const sessionIds = changed.sessionIds.filter((id) => this.host.
|
|
178
|
+
const sessionIds = changed.sessionIds.filter((id) => this.host.candidateMatches(id, changed.path));
|
|
179
179
|
if (changed === current && sessionIds.length === current.sessionIds.length) throw unchangedSentinel;
|
|
180
180
|
return {
|
|
181
181
|
...changed,
|
|
@@ -359,14 +359,16 @@ var WorkspaceRegistry = class extends Service {
|
|
|
359
359
|
entities = /* @__PURE__ */ new Map();
|
|
360
360
|
headers = /* @__PURE__ */ new Map();
|
|
361
361
|
sessionPaths = /* @__PURE__ */ new Map();
|
|
362
|
+
missingSessionPaths = /* @__PURE__ */ new Set();
|
|
362
363
|
invalidSessionPaths = /* @__PURE__ */ new Map();
|
|
363
364
|
operationTail = Promise.resolve();
|
|
364
365
|
host = {
|
|
365
366
|
table: () => this.requireTable(),
|
|
366
|
-
|
|
367
|
+
candidateMatches: (id, path) => this.sessionPaths.get(id) === path || this.missingSessionPaths.has(id),
|
|
367
368
|
readSessionHeader: (id) => this.readSessionHeader(id),
|
|
368
369
|
rememberSessionPath: (id, path) => {
|
|
369
370
|
this.sessionPaths.set(id, path);
|
|
371
|
+
this.missingSessionPaths.delete(id);
|
|
370
372
|
this.invalidSessionPaths.delete(id);
|
|
371
373
|
}
|
|
372
374
|
};
|
|
@@ -863,6 +865,7 @@ var WorkspaceRegistry = class extends Service {
|
|
|
863
865
|
async replaceHeaderIndex(headers) {
|
|
864
866
|
this.headers.clear();
|
|
865
867
|
this.sessionPaths.clear();
|
|
868
|
+
this.missingSessionPaths.clear();
|
|
866
869
|
this.invalidSessionPaths.clear();
|
|
867
870
|
await this.indexHeaders(headers);
|
|
868
871
|
}
|
|
@@ -872,6 +875,7 @@ var WorkspaceRegistry = class extends Service {
|
|
|
872
875
|
async indexHeader(header) {
|
|
873
876
|
this.headers.set(header.id, header);
|
|
874
877
|
this.sessionPaths.delete(header.id);
|
|
878
|
+
this.missingSessionPaths.delete(header.id);
|
|
875
879
|
if (header.cwd === void 0) {
|
|
876
880
|
this.invalidSessionPaths.set(header.id, "header has no cwd");
|
|
877
881
|
return;
|
|
@@ -884,7 +888,8 @@ var WorkspaceRegistry = class extends Service {
|
|
|
884
888
|
}
|
|
885
889
|
this.sessionPaths.set(header.id, path);
|
|
886
890
|
this.invalidSessionPaths.delete(header.id);
|
|
887
|
-
} catch {
|
|
891
|
+
} catch (error) {
|
|
892
|
+
if (fullyQualifiedWorkspacePath(header.cwd) && error instanceof Error && "code" in error && error.code === "ENOENT") this.missingSessionPaths.add(header.id);
|
|
888
893
|
this.invalidSessionPaths.set(header.id, `cwd '${header.cwd}' does not resolve`);
|
|
889
894
|
}
|
|
890
895
|
}
|
|
@@ -901,8 +906,8 @@ var WorkspaceRegistry = class extends Service {
|
|
|
901
906
|
for (const entity of this.entities.values()) {
|
|
902
907
|
const record = this.requireTable().get(entity.id);
|
|
903
908
|
for (const sessionId of record.sessionIds) {
|
|
909
|
+
if (this.host.candidateMatches(sessionId, record.path)) continue;
|
|
904
910
|
const path = this.sessionPaths.get(sessionId);
|
|
905
|
-
if (path === record.path) continue;
|
|
906
911
|
const reason = this.invalidSessionPaths.get(sessionId) ?? (this.headers.has(sessionId) ? `canonical cwd '${path}' differs from workspace path '${record.path}'` : "session header is missing");
|
|
907
912
|
this.ctx.logger.warn(`workspace '${entity.id}' filtered session '${sessionId}' from membership: ${reason}`);
|
|
908
913
|
}
|
package/lib/types/entity.d.ts
CHANGED
|
@@ -30,12 +30,13 @@ export interface WorkspaceEntityHost {
|
|
|
30
30
|
*/
|
|
31
31
|
table(): KvTable<WorkspaceId, WorkspaceRecord>;
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* @
|
|
36
|
-
*
|
|
33
|
+
* Validate an already-accounted session, preserving ownership when its fully
|
|
34
|
+
* qualified cwd cannot resolve because a path component is missing.
|
|
35
|
+
* @param id - Session already present in this workspace's candidate account.
|
|
36
|
+
* @param path - Workspace's stored canonical directory.
|
|
37
|
+
* @returns whether the candidate remains a member.
|
|
37
38
|
*/
|
|
38
|
-
|
|
39
|
+
candidateMatches(id: SessionId, path: string): boolean;
|
|
39
40
|
/**
|
|
40
41
|
* Read one stored session header for attach validation.
|
|
41
42
|
* @param id - The session whose header to read.
|
|
@@ -74,7 +75,7 @@ export declare class WorkspaceEntity implements Workspace {
|
|
|
74
75
|
/**
|
|
75
76
|
* The single write path: run `fn` on the domain write chain via
|
|
76
77
|
* `table.update`, stamping `updatedAt` and pruning candidates that no
|
|
77
|
-
* longer pass the
|
|
78
|
+
* longer pass the candidate membership check, then swap the
|
|
78
79
|
* snapshot.
|
|
79
80
|
*
|
|
80
81
|
* `fn` sees the value current at its chain slot, so membership decisions
|
package/lib/types/entity.js
CHANGED
|
@@ -49,7 +49,7 @@ export class WorkspaceEntity {
|
|
|
49
49
|
return this.record.updatedAt;
|
|
50
50
|
}
|
|
51
51
|
get sessionIds() {
|
|
52
|
-
return this.record.sessionIds.filter(id => this.host.
|
|
52
|
+
return this.record.sessionIds.filter(id => this.host.candidateMatches(id, this.record.path));
|
|
53
53
|
}
|
|
54
54
|
async setTitle(title) {
|
|
55
55
|
await this.mutate(record => ({ ...record, title }));
|
|
@@ -124,7 +124,7 @@ export class WorkspaceEntity {
|
|
|
124
124
|
/**
|
|
125
125
|
* The single write path: run `fn` on the domain write chain via
|
|
126
126
|
* `table.update`, stamping `updatedAt` and pruning candidates that no
|
|
127
|
-
* longer pass the
|
|
127
|
+
* longer pass the candidate membership check, then swap the
|
|
128
128
|
* snapshot.
|
|
129
129
|
*
|
|
130
130
|
* `fn` sees the value current at its chain slot, so membership decisions
|
|
@@ -138,7 +138,7 @@ export class WorkspaceEntity {
|
|
|
138
138
|
try {
|
|
139
139
|
next = await this.host.table().update(this.id, (current) => {
|
|
140
140
|
const changed = fn(current);
|
|
141
|
-
const sessionIds = changed.sessionIds.filter(id => this.host.
|
|
141
|
+
const sessionIds = changed.sessionIds.filter(id => this.host.candidateMatches(id, changed.path));
|
|
142
142
|
if (changed === current && sessionIds.length === current.sessionIds.length) {
|
|
143
143
|
throw unchangedSentinel;
|
|
144
144
|
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -125,6 +125,7 @@ export declare class WorkspaceRegistry extends Service {
|
|
|
125
125
|
private readonly entities;
|
|
126
126
|
private readonly headers;
|
|
127
127
|
private readonly sessionPaths;
|
|
128
|
+
private readonly missingSessionPaths;
|
|
128
129
|
private readonly invalidSessionPaths;
|
|
129
130
|
private operationTail;
|
|
130
131
|
private readonly host;
|
package/lib/types/index.js
CHANGED
|
@@ -98,14 +98,16 @@ export class WorkspaceRegistry extends Service {
|
|
|
98
98
|
entities = new Map();
|
|
99
99
|
headers = new Map();
|
|
100
100
|
sessionPaths = new Map();
|
|
101
|
+
missingSessionPaths = new Set();
|
|
101
102
|
invalidSessionPaths = new Map();
|
|
102
103
|
operationTail = Promise.resolve();
|
|
103
104
|
host = {
|
|
104
105
|
table: () => this.requireTable(),
|
|
105
|
-
|
|
106
|
+
candidateMatches: (id, path) => this.sessionPaths.get(id) === path || this.missingSessionPaths.has(id),
|
|
106
107
|
readSessionHeader: id => this.readSessionHeader(id),
|
|
107
108
|
rememberSessionPath: (id, path) => {
|
|
108
109
|
this.sessionPaths.set(id, path);
|
|
110
|
+
this.missingSessionPaths.delete(id);
|
|
109
111
|
this.invalidSessionPaths.delete(id);
|
|
110
112
|
},
|
|
111
113
|
};
|
|
@@ -693,6 +695,7 @@ export class WorkspaceRegistry extends Service {
|
|
|
693
695
|
async replaceHeaderIndex(headers) {
|
|
694
696
|
this.headers.clear();
|
|
695
697
|
this.sessionPaths.clear();
|
|
698
|
+
this.missingSessionPaths.clear();
|
|
696
699
|
this.invalidSessionPaths.clear();
|
|
697
700
|
await this.indexHeaders(headers);
|
|
698
701
|
}
|
|
@@ -703,6 +706,7 @@ export class WorkspaceRegistry extends Service {
|
|
|
703
706
|
async indexHeader(header) {
|
|
704
707
|
this.headers.set(header.id, header);
|
|
705
708
|
this.sessionPaths.delete(header.id);
|
|
709
|
+
this.missingSessionPaths.delete(header.id);
|
|
706
710
|
if (header.cwd === undefined) {
|
|
707
711
|
this.invalidSessionPaths.set(header.id, 'header has no cwd');
|
|
708
712
|
return;
|
|
@@ -716,7 +720,11 @@ export class WorkspaceRegistry extends Service {
|
|
|
716
720
|
this.sessionPaths.set(header.id, path);
|
|
717
721
|
this.invalidSessionPaths.delete(header.id);
|
|
718
722
|
}
|
|
719
|
-
catch {
|
|
723
|
+
catch (error) {
|
|
724
|
+
if (fullyQualifiedWorkspacePath(header.cwd)
|
|
725
|
+
&& error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
726
|
+
this.missingSessionPaths.add(header.id);
|
|
727
|
+
}
|
|
720
728
|
this.invalidSessionPaths.set(header.id, `cwd '${header.cwd}' does not resolve`);
|
|
721
729
|
}
|
|
722
730
|
}
|
|
@@ -735,9 +743,9 @@ export class WorkspaceRegistry extends Service {
|
|
|
735
743
|
for (const entity of this.entities.values()) {
|
|
736
744
|
const record = this.requireTable().get(entity.id);
|
|
737
745
|
for (const sessionId of record.sessionIds) {
|
|
738
|
-
|
|
739
|
-
if (path === record.path)
|
|
746
|
+
if (this.host.candidateMatches(sessionId, record.path))
|
|
740
747
|
continue;
|
|
748
|
+
const path = this.sessionPaths.get(sessionId);
|
|
741
749
|
const reason = this.invalidSessionPaths.get(sessionId)
|
|
742
750
|
?? (this.headers.has(sessionId)
|
|
743
751
|
? `canonical cwd '${path}' differs from workspace path '${record.path}'`
|
package/lib/types/types.d.ts
CHANGED
|
@@ -6,12 +6,17 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { Branded } from '@deepseek-ai/dsh-brand';
|
|
8
8
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
9
|
+
import type { TypertResource } from '@deepseek-ai/dsh-typert-protocol';
|
|
9
10
|
/**
|
|
10
11
|
* Identifies one workspace record. A generated uuid, never the path: path
|
|
11
12
|
* normalization rewrites paths, and a reference anchor must stay stable.
|
|
12
13
|
*/
|
|
13
14
|
export type WorkspaceId = Branded<'WorkspaceId'>;
|
|
14
15
|
declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
16
|
+
interface TypertResourceMap {
|
|
17
|
+
/** One Workspace; the Workspace Controller registers its default `workspaceId` wire field. */
|
|
18
|
+
workspace: TypertResource<WorkspaceId>;
|
|
19
|
+
}
|
|
15
20
|
interface RemoteErrorDetailsMap {
|
|
16
21
|
/** No registration carries that Workspace identity. */
|
|
17
22
|
'workspace/not-found': {
|
|
@@ -50,9 +55,10 @@ export interface SessionActivity {
|
|
|
50
55
|
}
|
|
51
56
|
/**
|
|
52
57
|
* One workspace: a stable id over an existing directory, a display title, and
|
|
53
|
-
* an ordered candidate account of sessions. Membership
|
|
54
|
-
*
|
|
55
|
-
*
|
|
58
|
+
* an ordered candidate account of sessions. Membership validates header cwd
|
|
59
|
+
* against the workspace path, but preserves an existing account when its
|
|
60
|
+
* fully qualified cwd is missing. Consumers only see this interface; the
|
|
61
|
+
* implementation stays private.
|
|
56
62
|
*/
|
|
57
63
|
export interface Workspace {
|
|
58
64
|
/** Stable record id (generated uuid). */
|
|
@@ -74,8 +80,11 @@ export interface Workspace {
|
|
|
74
80
|
* prepended at attach, explicit reordering goes through
|
|
75
81
|
* `insertSessionBefore`, and activity never reorders. The durable candidate
|
|
76
82
|
* account is filtered synchronously: missing headers, invalid cwd values,
|
|
77
|
-
* and canonical cwd mismatches are never returned. A
|
|
78
|
-
*
|
|
83
|
+
* and canonical cwd mismatches are never returned. A fully qualified cwd
|
|
84
|
+
* whose realpath or directory stat fails with ENOENT preserves an existing
|
|
85
|
+
* candidate, including a dangling symlink; other filesystem failures do not.
|
|
86
|
+
* A subsequent mutation prunes filtered candidates, not missing-directory
|
|
87
|
+
* candidates. This does not admit new sessions or recreate directories.
|
|
79
88
|
*/
|
|
80
89
|
readonly sessionIds: readonly SessionId[];
|
|
81
90
|
/**
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-workspace",
|
|
3
3
|
"description": "Workspace entity registry (ctx.workspaceRegistry): durable workspace records with validated session attachment over the domain data form for the DeepSeek Harness",
|
|
4
|
-
"version": "0.2.1-alpha.
|
|
4
|
+
"version": "0.2.1-alpha.4",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -23,32 +23,34 @@
|
|
|
23
23
|
"default": "./lib/types/types.js"
|
|
24
24
|
},
|
|
25
25
|
"./src/*": "./src/*",
|
|
26
|
-
"./package.json": "./package.json"
|
|
26
|
+
"./package.json": "./package.json",
|
|
27
|
+
"./locale/*.json": "./locale/*.json"
|
|
27
28
|
},
|
|
28
29
|
"files": [
|
|
30
|
+
"locale/*.json",
|
|
29
31
|
"lib/index.js",
|
|
30
32
|
"lib/types/**/*.js",
|
|
31
33
|
"lib/types/**/*.d.ts"
|
|
32
34
|
],
|
|
33
35
|
"license": "MIT",
|
|
34
36
|
"peerDependencies": {
|
|
35
|
-
"@deepseek-ai/cordis": "~4.0.5-alpha.
|
|
36
|
-
"@deepseek-ai/dsh-session": "0.2.1-alpha.
|
|
37
|
-
"@deepseek-ai/dsh-session-persistence": "0.2.1-alpha.
|
|
38
|
-
"@deepseek-ai/dsh-storage": "0.2.1-alpha.
|
|
39
|
-
"@deepseek-ai/dsh-storage-domain": "0.2.1-alpha.
|
|
40
|
-
"@deepseek-ai/dsh-typert-protocol": "0.2.1-alpha.
|
|
37
|
+
"@deepseek-ai/cordis": "~4.0.5-alpha.2",
|
|
38
|
+
"@deepseek-ai/dsh-session": "0.2.1-alpha.4",
|
|
39
|
+
"@deepseek-ai/dsh-session-persistence": "0.2.1-alpha.4",
|
|
40
|
+
"@deepseek-ai/dsh-storage": "0.2.1-alpha.4",
|
|
41
|
+
"@deepseek-ai/dsh-storage-domain": "0.2.1-alpha.4",
|
|
42
|
+
"@deepseek-ai/dsh-typert-protocol": "0.2.1-alpha.4"
|
|
41
43
|
},
|
|
42
44
|
"dependencies": {
|
|
43
|
-
"@deepseek-ai/dsh-brand": "0.2.1-alpha.
|
|
44
|
-
"zod": "
|
|
45
|
+
"@deepseek-ai/dsh-brand": "0.2.1-alpha.4",
|
|
46
|
+
"zod": "4.4.3"
|
|
45
47
|
},
|
|
46
48
|
"devDependencies": {
|
|
47
|
-
"@deepseek-ai/cordis": "~4.0.5-alpha.
|
|
48
|
-
"@deepseek-ai/dsh-session": "0.2.1-alpha.
|
|
49
|
-
"@deepseek-ai/dsh-session-persistence": "0.2.1-alpha.
|
|
50
|
-
"@deepseek-ai/dsh-storage": "0.2.1-alpha.
|
|
51
|
-
"@deepseek-ai/dsh-storage-domain": "0.2.1-alpha.
|
|
52
|
-
"@deepseek-ai/dsh-typert-protocol": "0.2.1-alpha.
|
|
49
|
+
"@deepseek-ai/cordis": "~4.0.5-alpha.2",
|
|
50
|
+
"@deepseek-ai/dsh-session": "0.2.1-alpha.4",
|
|
51
|
+
"@deepseek-ai/dsh-session-persistence": "0.2.1-alpha.4",
|
|
52
|
+
"@deepseek-ai/dsh-storage": "0.2.1-alpha.4",
|
|
53
|
+
"@deepseek-ai/dsh-storage-domain": "0.2.1-alpha.4",
|
|
54
|
+
"@deepseek-ai/dsh-typert-protocol": "0.2.1-alpha.4"
|
|
53
55
|
}
|
|
54
56
|
}
|