@deepseek-ai/dsh-workspace 0.1.5-rc.2 → 0.1.6-alpha.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.i18n.yaml +2 -2
- package/README.md +6 -6
- package/README.zh.md +12 -12
- package/lib/index.js +20 -0
- package/lib/types/index.d.ts +11 -0
- package/lib/types/index.js +23 -0
- package/package.json +14 -14
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/workspace/workspace/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 8ac3bdbf0a69c031f1ddd5cdadb98f9cc4d55084
|
|
6
|
+
README.zh.md: 225a24cc8fbedafe8e3ae6f64a849525eeb92067
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Use this package to keep an ordered, persistent list of project directories and
|
|
|
25
25
|
<a id="use-this-package"></a>
|
|
26
26
|
## Use this package
|
|
27
27
|
|
|
28
|
-
Use this package to give the product a project list: named directories the user works in, the sessions that ran in each, a stable order, and a way to hide sessions without losing them. The API contracts behind each action live in the implementation section.
|
|
28
|
+
Use this package to give the product a project list: named directories the user works in, the sessions that ran in each, a stable order, and a way to hide sessions without losing them or bring them back. The API contracts behind each action live in the implementation section.
|
|
29
29
|
|
|
30
30
|
### When to use it
|
|
31
31
|
|
|
@@ -63,9 +63,9 @@ ctx.workspaceRegistry.list() // shows the project, newest first
|
|
|
63
63
|
|
|
64
64
|
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 whose directory cannot be validated — no recorded directory, or a moved or deleted folder — cannot join and stays ungrouped.
|
|
65
65
|
|
|
66
|
-
### Hiding sessions and removing projects
|
|
66
|
+
### Hiding and restoring sessions, and removing projects
|
|
67
67
|
|
|
68
|
-
Hide a session from the grouping when it should stop appearing there: it disappears from the visible list, while its session, history, and place in the project stay intact. Remove a project when it is no longer needed: it leaves the list, and its folder, files, and session histories are never touched — those sessions become ungrouped. Adding the same directory again afterwards starts a fresh project without the old sessions.
|
|
68
|
+
Hide a session from the grouping when it should stop appearing there: it disappears from the visible list, while its session, history, and place in the project stay intact. Restore a hidden session when it should appear again: it returns to its recorded position under its project, or to the ungrouped sessions when it belongs to none. Remove a project when it is no longer needed: it leaves the list, and its folder, files, and session histories are never touched — those sessions become ungrouped. Adding the same directory again afterwards starts a fresh project without the old sessions.
|
|
69
69
|
|
|
70
70
|
-----
|
|
71
71
|
|
|
@@ -87,7 +87,7 @@ This section explains the design decisions behind the feature and points at the
|
|
|
87
87
|
|
|
88
88
|
### API behavior
|
|
89
89
|
|
|
90
|
-
The API is one small family with two owners: `WorkspaceRegistry` creates, orders, and deletes projects
|
|
90
|
+
The API is one small family with two owners: `WorkspaceRegistry` creates, orders, and deletes projects, manages their session accounting, and archives or restores single sessions; the `Workspace` entity exposes the display title, directory status, and the session projection. Per-method contracts live in the code, not this README — see [src/index.ts](src/index.ts) and [src/entity.ts](src/entity.ts).
|
|
91
91
|
|
|
92
92
|
### Source map
|
|
93
93
|
|
|
@@ -102,7 +102,7 @@ The API is one small family with two owners: `WorkspaceRegistry` creates, orders
|
|
|
102
102
|
|
|
103
103
|
### Durable shape
|
|
104
104
|
|
|
105
|
-
The registry opens the `workspace` domain (version 2): a `workspaces` table keyed by `WorkspaceId` plus one global state holding `workspaceIds` (the authoritative display order), `archivedSessionIds`, and the optional `pendingMutation` marker. Records written before `archivedSessionIds` existed parse with an empty set through the schema default.
|
|
105
|
+
The registry opens the `workspace` domain (version 2): a `workspaces` table keyed by `WorkspaceId` plus one global state holding `workspaceIds` (the authoritative display order), `archivedSessionIds`, and the optional `pendingMutation` marker. Records written before `archivedSessionIds` existed parse with an empty set through the schema default. Archiving and unarchiving both rewrite only that global state, so a restore is one filtered write of the same field; unarchive runs no session-existence probe, because dropping an id from the set cannot introduce an unknown one, while archive verifies the session before adding it.
|
|
106
106
|
|
|
107
107
|
### Lifecycle
|
|
108
108
|
|
|
@@ -160,7 +160,7 @@ These limits define when the project list is a poor fit or needs special operati
|
|
|
160
160
|
- **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 ([decision](../../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md)).
|
|
161
161
|
- **A session joins only with a recorded directory** — a session belongs to a project only when its record carries a directory that resolves to the project's path; sessions without one stay ungrouped, and a session from another directory cannot be moved in.
|
|
162
162
|
- **External changes are seen late** — if another process deletes or damages a directory, the project reflects it only at the next refresh or restart.
|
|
163
|
-
- **
|
|
163
|
+
- **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.
|
|
164
164
|
- **Re-adding a directory starts fresh** — after removal, adding the same directory again creates a new project with an empty session list; the old sessions do not come back automatically.
|
|
165
165
|
|
|
166
166
|
<a id="dev-note"></a>
|
package/README.zh.md
CHANGED
|
@@ -25,7 +25,7 @@ kind: "package-reference"
|
|
|
25
25
|
<a id="use-this-package"></a>
|
|
26
26
|
## 使用本包
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
使用此包为产品提供项目列表:用户工作的命名目录、每个目录中运行的会话、稳定顺序,以及在不丢失会话的前提下将其隐藏或重新取回的能力。每项操作背后的 API 约定放在实现章节中。
|
|
29
29
|
|
|
30
30
|
### 何时使用
|
|
31
31
|
|
|
@@ -50,7 +50,7 @@ kind: "package-reference"
|
|
|
50
50
|
|
|
51
51
|
### 创建与排序项目
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
从任何已存在的绝对目录路径创建项目:`C:\` 等文件系统根目录和普通目录都有效。相对路径、`C:work` 等 Windows 盘符相对路径、不存在的路径和文件都会被拒绝,且不会创建项目;为已有项目的目录再次创建会原样返回现有项目。你可以随时重命名项目,并把它移动到列表中的任意位置:
|
|
54
54
|
|
|
55
55
|
```text
|
|
56
56
|
// Host consumer code, after the composition above is loaded:
|
|
@@ -63,9 +63,9 @@ ctx.workspaceRegistry.list() // shows the project, newest first
|
|
|
63
63
|
|
|
64
64
|
会话加入它运行目录所在的项目:在项目目录中创建会话,它就会出现在该项目下,新到旧排列。一个会话只能属于一个项目。目录无法校验的会话——没有记录目录,或目录被移动、删除——无法加入,保持 Ungrouped。
|
|
65
65
|
|
|
66
|
-
###
|
|
66
|
+
### 隐藏、恢复会话与移除项目
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
当会话不应再出现在分组中时隐藏它:它会从可见列表中消失,但其会话、历史与在项目中的位置都保持不变。当被隐藏的会话应重新出现时恢复它:它会回到其项目下记录的位置;不属于任何项目时则回到 Ungrouped。项目不再需要时移除它:它离开列表,而其文件夹、文件与会话历史绝不受影响——这些会话变成 Ungrouped。之后再次添加同一目录会从空项目开始,不会带回旧会话。
|
|
69
69
|
|
|
70
70
|
-----
|
|
71
71
|
|
|
@@ -79,15 +79,15 @@ ctx.workspaceRegistry.list() // shows the project, newest first
|
|
|
79
79
|
|
|
80
80
|
### 设计理念
|
|
81
81
|
|
|
82
|
-
- **每个规范路径一条记录。** `fs.realpath`
|
|
83
|
-
- **成员资格是所有权加实时 cwd 事实。** 记录的 `sessionIds` 顺序是所有权真源;启动时的头部索引校验它,`sessionIds`
|
|
82
|
+
- **每个规范路径一条记录。** `fs.realpath` 是唯一的一套唯一性规范:路径以规范化形式存储,因此指向已有记录目录的符号链接会与之冲突,唯一性即规范路径的字符串相等。
|
|
83
|
+
- **成员资格是所有权加实时 cwd 事实。** 记录的 `sessionIds` 顺序是所有权真源;启动时的头部索引校验它,`sessionIds` 在读取时过滤,下一次变更会持久化剪除无效项。
|
|
84
84
|
- **仅读取头部。** 引导与 attach 校验只读取 `SessionHeader` 字段;事件正文绝不加载。
|
|
85
85
|
- **两次写入的变更带显式标记。** 创建与删除在记录/顺序对可能分叉之前先持久化 `pendingMutation` 标记,因此启动只补全被中断的操作,未标记的分叉作为损坏明确报错。
|
|
86
|
-
- **串行化写入。** 注册表操作跑在同一条操作链上;实体变更通过领域写链上的 `table.update`
|
|
86
|
+
- **串行化写入。** 注册表操作跑在同一条操作链上;实体变更通过领域写链上的 `table.update` 执行,写入 `updatedAt`,并在其所在的链位置决定成员资格。
|
|
87
87
|
|
|
88
88
|
### API 行为
|
|
89
89
|
|
|
90
|
-
该 API 是一个由两个所有者构成的小家族:`WorkspaceRegistry`
|
|
90
|
+
该 API 是一个由两个所有者构成的小家族:`WorkspaceRegistry` 负责创建、排序与删除项目、管理其会话记账,以及归档或恢复单个会话;`Workspace` 实体暴露显示标题、目录状态与会话投影。各方法的精确约定在代码中,而非本 README——参见 [src/index.ts](src/index.ts) 与 [src/entity.ts](src/entity.ts)。
|
|
91
91
|
|
|
92
92
|
### 源码地图
|
|
93
93
|
|
|
@@ -102,11 +102,11 @@ ctx.workspaceRegistry.list() // shows the project, newest first
|
|
|
102
102
|
|
|
103
103
|
### 持久形态
|
|
104
104
|
|
|
105
|
-
注册表打开 `workspace` 领域(版本 2):一张以 `WorkspaceId` 为键的 `workspaces` 表,加上一个持有 `workspaceIds`(权威显示顺序)、`archivedSessionIds` 与可选 `pendingMutation` 标记的全局状态。在 `archivedSessionIds` 存在之前写入的记录会通过 schema
|
|
105
|
+
注册表打开 `workspace` 领域(版本 2):一张以 `WorkspaceId` 为键的 `workspaces` 表,加上一个持有 `workspaceIds`(权威显示顺序)、`archivedSessionIds` 与可选 `pendingMutation` 标记的全局状态。在 `archivedSessionIds` 存在之前写入的记录会通过 schema 默认值解析为空集合。归档与取消归档都只重写该全局状态,因此恢复就是对同一字段的一次过滤写入;取消归档不做会话存在性探测,因为从集合中移除 id 不可能引入未知 id,而归档会在加入前校验会话。
|
|
106
106
|
|
|
107
107
|
### 生命周期
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
启动时,注册表打开领域、若存在标记则补全被标记的变更、校验已存状态——重复路径、重复会话记账与顺序漂移都会明确报错——并在尚未初始化时先凭持久化头部引导历史、最后写入已初始化标记,因此被中断的引导可以安全恢复。全新空注册表一旦初始化即成为正式状态,绝不会再次引导。
|
|
110
110
|
|
|
111
111
|
### 失败与恢复
|
|
112
112
|
|
|
@@ -136,7 +136,7 @@ ctx.workspaceRegistry.list() // shows the project, newest first
|
|
|
136
136
|
<a id="model-experience"></a>
|
|
137
137
|
## 模型体验
|
|
138
138
|
|
|
139
|
-
### Workspace
|
|
139
|
+
### Workspace 记录与会话记账
|
|
140
140
|
|
|
141
141
|
#### 模型看到什么
|
|
142
142
|
|
|
@@ -160,7 +160,7 @@ ctx.workspaceRegistry.list() // shows the project, newest first
|
|
|
160
160
|
- **移除绝不删除数据**——移除项目会保留其文件夹、文件与会话历史;这些会话变成 Ungrouped,而会话删除与文件夹移除是彼此独立且尚未提供的功能(参见[决策记录](../../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.zh.md))。
|
|
161
161
|
- **只有带记录目录的会话才能加入**——只有记录中带有可解析为项目路径的目录的会话才属于项目;没有目录的会话保持 Ungrouped,来自其他目录的会话无法移入。
|
|
162
162
|
- **外部变更延迟可见**——如果另一进程删除或损坏目录,项目只能在下次刷新或重启后反映出来。
|
|
163
|
-
-
|
|
163
|
+
- **归档与取消归档执行不同的会话校验**——恢复只是从归档集合中移除 id,因此会话已不存在的条目仍能取消归档,也不会留下未知引用;对未归档 id 执行恢复不写盘即完成,而 `archiveSession` 会拒绝既非实时也未持久化的会话。
|
|
164
164
|
- **重新添加目录从空开始**——移除后再次添加同一目录会创建空会话列表的新项目;旧会话不会自动回来。
|
|
165
165
|
|
|
166
166
|
<a id="dev-note"></a>
|
package/lib/index.js
CHANGED
|
@@ -455,6 +455,26 @@ var WorkspaceRegistry = class extends Service {
|
|
|
455
455
|
});
|
|
456
456
|
}
|
|
457
457
|
/**
|
|
458
|
+
* Unarchive one session durably by dropping it from the registry-global
|
|
459
|
+
* archive set; the accounting slot was never touched, so the session
|
|
460
|
+
* returns to its recorded position. Unarchiving runs no session-existence
|
|
461
|
+
* check because removing an id cannot introduce an unknown one, so an
|
|
462
|
+
* entry whose session is gone still resolves. An id that is not archived
|
|
463
|
+
* resolves without writing.
|
|
464
|
+
* @param sessionId - The session to unarchive.
|
|
465
|
+
* @returns resolution after durability.
|
|
466
|
+
*/
|
|
467
|
+
unarchiveSession(sessionId) {
|
|
468
|
+
return this.enqueueOperation(async () => {
|
|
469
|
+
const state = this.requireState();
|
|
470
|
+
if (!state.archivedSessionIds.includes(sessionId)) return;
|
|
471
|
+
await this.setState({
|
|
472
|
+
...state,
|
|
473
|
+
archivedSessionIds: state.archivedSessionIds.filter((id) => id !== sessionId)
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
458
478
|
* Whether a session is live, header-indexed, or present in a fresh
|
|
459
479
|
* persistence listing. Only a definite miss returns false — a failing
|
|
460
480
|
* `sessionPersistence.list()` propagates so storage faults never
|
package/lib/types/index.d.ts
CHANGED
|
@@ -122,6 +122,17 @@ export declare class WorkspaceRegistry extends Service {
|
|
|
122
122
|
* @returns resolution after durability.
|
|
123
123
|
*/
|
|
124
124
|
archiveSession(sessionId: SessionId): Promise<void>;
|
|
125
|
+
/**
|
|
126
|
+
* Unarchive one session durably by dropping it from the registry-global
|
|
127
|
+
* archive set; the accounting slot was never touched, so the session
|
|
128
|
+
* returns to its recorded position. Unarchiving runs no session-existence
|
|
129
|
+
* check because removing an id cannot introduce an unknown one, so an
|
|
130
|
+
* entry whose session is gone still resolves. An id that is not archived
|
|
131
|
+
* resolves without writing.
|
|
132
|
+
* @param sessionId - The session to unarchive.
|
|
133
|
+
* @returns resolution after durability.
|
|
134
|
+
*/
|
|
135
|
+
unarchiveSession(sessionId: SessionId): Promise<void>;
|
|
125
136
|
/**
|
|
126
137
|
* Whether a session is live, header-indexed, or present in a fresh
|
|
127
138
|
* persistence listing. Only a definite miss returns false — a failing
|
package/lib/types/index.js
CHANGED
|
@@ -213,6 +213,29 @@ export class WorkspaceRegistry extends Service {
|
|
|
213
213
|
await this.setState({ ...state, archivedSessionIds: [...state.archivedSessionIds, sessionId] });
|
|
214
214
|
});
|
|
215
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Unarchive one session durably by dropping it from the registry-global
|
|
218
|
+
* archive set; the accounting slot was never touched, so the session
|
|
219
|
+
* returns to its recorded position. Unarchiving runs no session-existence
|
|
220
|
+
* check because removing an id cannot introduce an unknown one, so an
|
|
221
|
+
* entry whose session is gone still resolves. An id that is not archived
|
|
222
|
+
* resolves without writing.
|
|
223
|
+
* @param sessionId - The session to unarchive.
|
|
224
|
+
* @returns resolution after durability.
|
|
225
|
+
*/
|
|
226
|
+
unarchiveSession(sessionId) {
|
|
227
|
+
return this.enqueueOperation(async () => {
|
|
228
|
+
// The chain slot serializes against every other registry write, so this
|
|
229
|
+
// check-then-write pair cannot interleave with a concurrent archive.
|
|
230
|
+
const state = this.requireState();
|
|
231
|
+
if (!state.archivedSessionIds.includes(sessionId))
|
|
232
|
+
return;
|
|
233
|
+
await this.setState({
|
|
234
|
+
...state,
|
|
235
|
+
archivedSessionIds: state.archivedSessionIds.filter(id => id !== sessionId),
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
}
|
|
216
239
|
/**
|
|
217
240
|
* Whether a session is live, header-indexed, or present in a fresh
|
|
218
241
|
* persistence listing. Only a definite miss returns false — a failing
|
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.1.
|
|
4
|
+
"version": "0.1.6-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -37,25 +37,25 @@
|
|
|
37
37
|
],
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"@deepseek-ai/dsh-invariants": "^0.1.5-rc.2",
|
|
41
40
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
42
|
-
"@deepseek-ai/dsh-
|
|
43
|
-
"@deepseek-ai/dsh-session-persistence": "^0.1.
|
|
44
|
-
"@deepseek-ai/dsh-storage": "^0.1.
|
|
45
|
-
"@deepseek-ai/dsh-
|
|
46
|
-
"@deepseek-ai/dsh-
|
|
41
|
+
"@deepseek-ai/dsh-storage": "^0.1.6-alpha.1",
|
|
42
|
+
"@deepseek-ai/dsh-session-persistence": "^0.1.6-alpha.1",
|
|
43
|
+
"@deepseek-ai/dsh-storage-domain": "^0.1.6-alpha.1",
|
|
44
|
+
"@deepseek-ai/dsh-session": "^0.1.6-alpha.1",
|
|
45
|
+
"@deepseek-ai/dsh-invariants": "^0.1.6-alpha.1",
|
|
46
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.6-alpha.1"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"zod": "^4.4.3",
|
|
50
|
-
"@deepseek-ai/dsh-brand": "^0.1.
|
|
50
|
+
"@deepseek-ai/dsh-brand": "^0.1.6-alpha.1"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
54
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
55
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
56
|
-
"@deepseek-ai/dsh-
|
|
57
|
-
"@deepseek-ai/dsh-
|
|
58
|
-
"@deepseek-ai/dsh-typert-protocol": "^0.1.
|
|
59
|
-
"@deepseek-ai/dsh-storage-domain": "^0.1.
|
|
54
|
+
"@deepseek-ai/dsh-invariants": "^0.1.6-alpha.1",
|
|
55
|
+
"@deepseek-ai/dsh-session-persistence": "^0.1.6-alpha.1",
|
|
56
|
+
"@deepseek-ai/dsh-storage": "^0.1.6-alpha.1",
|
|
57
|
+
"@deepseek-ai/dsh-session": "^0.1.6-alpha.1",
|
|
58
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.6-alpha.1",
|
|
59
|
+
"@deepseek-ai/dsh-storage-domain": "^0.1.6-alpha.1"
|
|
60
60
|
}
|
|
61
61
|
}
|