@modesto_ma/dsh-channels 0.2.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 +115 -39
- package/docs/access-control-design.md +1 -1
- package/docs/attachment-pipeline-v2.md +318 -0
- package/docs/feasibility-and-design.md +54 -26
- package/docs/feishu-onboarding.md +123 -15
- package/docs/persistence.md +4 -4
- package/docs/refactor-plan.md +7 -6
- package/docs/workspace-isolation-design.md +81 -0
- package/lib/client.js +30 -15
- package/lib/client.js.map +1 -1
- package/lib/index.js +11008 -5132
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cards/index.ts +1 -0
- package/src/cards/onboarding.ts +23 -0
- package/src/cards/primitives.ts +20 -0
- package/src/cards/streaming.ts +310 -291
- package/src/cleanup.ts +267 -119
- package/src/client/panel/cleanup-card.tsx +4 -2
- package/src/client/panel/workspace-tab.tsx +1 -1
- package/src/config.ts +221 -13
- package/src/domain.ts +141 -3
- package/src/feishu/agent-context.ts +66 -16
- package/src/feishu/inbound-attachments.ts +440 -3
- package/src/feishu/manifest.ts +270 -0
- package/src/feishu/message-read.ts +1333 -0
- package/src/feishu/native-attachments.ts +998 -0
- package/src/feishu/pending-attachments.ts +562 -0
- package/src/feishu/tools.ts +2345 -0
- package/src/feishu-channel.ts +55 -0
- package/src/host/agent.ts +55 -0
- package/src/host/attachments.ts +210 -0
- package/src/host/index.ts +1 -0
- package/src/index.ts +8 -6
- package/src/routing/inbound.ts +730 -81
- package/src/service.ts +536 -50
- package/src/session/isolation.ts +277 -0
- package/src/session/manager.ts +120 -23
- package/src/session/repair.ts +2 -3
- package/src/session/routes.ts +2 -0
- package/src/session-commands.ts +41 -14
- package/src/settings/contract.ts +43 -21
- package/src/settings/overlay.ts +124 -13
- package/src/settings/surface.ts +147 -14
- package/src/streamer.ts +70 -30
- package/src/workspaces.ts +74 -31
package/src/workspaces.ts
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Workspace directory resolution: one DSH workspace per
|
|
2
|
+
* Workspace directory resolution: one DSH workspace per user, one working
|
|
3
|
+
* directory per Feishu topic.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
5
|
+
* - p2p: workspace `<baseDir>/p2p/<open_id>/`
|
|
6
|
+
* - group: workspace `<baseDir>/groups/<chat_id>/<open_id>/`
|
|
7
|
+
* - topic: `<workspace>/topics/<topic-token>/` (the Session's effective cwd)
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* topic
|
|
9
|
+
* The workspace root is the Session header cwd: it keeps the Session grouped
|
|
10
|
+
* under the user's project in the GUI and stays the sandbox write boundary.
|
|
11
|
+
* The per-topic directory is where the tools actually work (see
|
|
12
|
+
* `./session/isolation.ts`), so files produced by different topics never mix.
|
|
13
|
+
* Older versions registered one workspace per topic under `<baseDir>/topics/`;
|
|
14
|
+
* those directories are kept verbatim for legacy routes and are only removed
|
|
15
|
+
* by the retention cleanup, never migrated.
|
|
16
|
+
*
|
|
17
|
+
* Attachments received before a topic exists were staged by older plugin
|
|
18
|
+
* versions below `<baseDir>/_staging/<chatType>/<chat_id>/<open_id>/` and
|
|
19
|
+
* adopted into the topic directory once its first Session is created. The v2
|
|
20
|
+
* pipeline stores inbound attachments in the DSH native attachment library and
|
|
21
|
+
* never writes staging, so this is a one-release compatibility path only: it is
|
|
22
|
+
* scheduled for removal in the release cycle after attachment_pipeline_v2 ships
|
|
23
|
+
* (see Roadmap decisions D12 and docs/attachment-pipeline-v2.md).
|
|
11
24
|
*
|
|
12
25
|
* Feishu ids (`ou_…`, `oc_…`) are safe path segments, but they are validated
|
|
13
26
|
* anyway so a hostile id can never escape `baseDir`.
|
|
@@ -123,36 +136,68 @@ export function topicTokenFor(
|
|
|
123
136
|
return kind === 'thread' ? safeSegment(routeKey, 'route key') : safeSegment(sessionId, 'session id')
|
|
124
137
|
}
|
|
125
138
|
|
|
139
|
+
/** Git branch (and worktree directory) name for one topic token. */
|
|
140
|
+
export function worktreeBranchFor(topicToken: string): string {
|
|
141
|
+
return `feishu-${safeSegment(topicToken, 'topic token')}`
|
|
142
|
+
}
|
|
143
|
+
|
|
126
144
|
/**
|
|
127
|
-
* Deterministic workspace directory for one
|
|
128
|
-
*
|
|
145
|
+
* Deterministic workspace directory for one user in one chat context: the
|
|
146
|
+
* durable project the user's Sessions are grouped under.
|
|
129
147
|
*
|
|
130
|
-
* - p2p: `<baseDir>/
|
|
131
|
-
* - group: `<baseDir>/
|
|
148
|
+
* - p2p: `<baseDir>/p2p/<open_id>/`
|
|
149
|
+
* - group: `<baseDir>/groups/<chat_id>/<open_id>/`
|
|
132
150
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
151
|
+
* These are the same paths older versions used as their shared (user × chat)
|
|
152
|
+
* workspaces, so an upgraded deployment reuses the existing registry record
|
|
153
|
+
* instead of creating a second project for the same user.
|
|
136
154
|
*/
|
|
137
155
|
export function workspacePathFor(
|
|
138
156
|
baseDir: string,
|
|
139
157
|
chatType: 'p2p' | 'group',
|
|
140
158
|
chatId: string,
|
|
141
159
|
senderOpenId: string,
|
|
142
|
-
topicToken: string,
|
|
143
160
|
): string {
|
|
144
|
-
const chat = safeSegment(chatId, 'chatId')
|
|
145
161
|
const user = safeSegment(senderOpenId, 'senderOpenId')
|
|
146
|
-
|
|
147
|
-
return
|
|
148
|
-
|
|
149
|
-
|
|
162
|
+
if (chatType === 'p2p') return path.join(baseDir, 'p2p', user)
|
|
163
|
+
return path.join(baseDir, 'groups', safeSegment(chatId, 'chatId'), user)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Per-topic working directory inside one user workspace. */
|
|
167
|
+
export function topicDirFor(workspacePath: string, topicToken: string): string {
|
|
168
|
+
return path.join(workspacePath, 'topics', safeSegment(topicToken, 'topic token'))
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Root of the legacy (one workspace per topic) directories. Legacy workspaces
|
|
173
|
+
* live strictly below this path, which is how the retention cleanup tells them
|
|
174
|
+
* apart from user workspaces that must never be deleted.
|
|
175
|
+
*/
|
|
176
|
+
export function legacyTopicWorkspaceRoot(baseDir: string): string {
|
|
177
|
+
return path.join(baseDir, 'topics')
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Legacy pre-topic attachment staging root.
|
|
182
|
+
*
|
|
183
|
+
* Only older plugin versions wrote here; the v2 native pipeline never does and
|
|
184
|
+
* the retention cleanup never deletes it. Scheduled for removal together with
|
|
185
|
+
* the one-time adoption in the release cycle after attachment_pipeline_v2
|
|
186
|
+
* (decisions D12).
|
|
187
|
+
*
|
|
188
|
+
* @deprecated legacy archive-upload path, kept for one release cycle only.
|
|
189
|
+
*/
|
|
190
|
+
export function stagingRoot(baseDir: string): string {
|
|
191
|
+
return path.join(baseDir, '_staging')
|
|
150
192
|
}
|
|
151
193
|
|
|
152
194
|
/**
|
|
153
195
|
* Staging directory for attachments that arrive before their topic (and so
|
|
154
|
-
* its
|
|
155
|
-
*
|
|
196
|
+
* its topic directory) exists. Not a registered workspace: files move into the
|
|
197
|
+
* real topic directory as soon as the first Session of that topic is created.
|
|
198
|
+
*
|
|
199
|
+
* @deprecated v2 never writes staging; kept for one-time adoption of legacy
|
|
200
|
+
* files for one release cycle (decisions D12).
|
|
156
201
|
*/
|
|
157
202
|
export function stagingPathFor(
|
|
158
203
|
baseDir: string,
|
|
@@ -162,7 +207,7 @@ export function stagingPathFor(
|
|
|
162
207
|
): string {
|
|
163
208
|
const chat = safeSegment(chatId, 'chatId')
|
|
164
209
|
const user = safeSegment(senderOpenId, 'senderOpenId')
|
|
165
|
-
return path.join(baseDir,
|
|
210
|
+
return path.join(stagingRoot(baseDir), chatType, chat, user)
|
|
166
211
|
}
|
|
167
212
|
|
|
168
213
|
/** Human-friendly workspace title shown in the DSH workspace list. */
|
|
@@ -170,17 +215,16 @@ export function workspaceTitleFor(
|
|
|
170
215
|
chatType: 'p2p' | 'group',
|
|
171
216
|
chatId: string,
|
|
172
217
|
senderOpenId: string,
|
|
173
|
-
topicToken?: string,
|
|
174
218
|
): string {
|
|
175
|
-
|
|
219
|
+
return chatType === 'p2p'
|
|
176
220
|
? `飞书私聊 · ${senderOpenId}`
|
|
177
221
|
: `飞书群聊 · ${chatId} · ${senderOpenId}`
|
|
178
|
-
return topicToken === undefined ? base : `${base} · ${topicToken}`
|
|
179
222
|
}
|
|
180
223
|
|
|
181
224
|
/**
|
|
182
|
-
* Ensure one
|
|
183
|
-
* host's workspace registry. Idempotent: repeated calls return the same record
|
|
225
|
+
* Ensure one user's workspace directory exists and is registered with the
|
|
226
|
+
* host's workspace registry. Idempotent: repeated calls return the same record,
|
|
227
|
+
* and an existing record keeps its (possibly renamed) title.
|
|
184
228
|
*/
|
|
185
229
|
export async function ensureWorkspace(
|
|
186
230
|
ctx: HostContext,
|
|
@@ -188,12 +232,11 @@ export async function ensureWorkspace(
|
|
|
188
232
|
chatType: 'p2p' | 'group',
|
|
189
233
|
chatId: string,
|
|
190
234
|
senderOpenId: string,
|
|
191
|
-
topicToken: string,
|
|
192
235
|
): Promise<Workspace> {
|
|
193
|
-
const dir = workspacePathFor(baseDir, chatType, chatId, senderOpenId
|
|
236
|
+
const dir = workspacePathFor(baseDir, chatType, chatId, senderOpenId)
|
|
194
237
|
await mkdir(dir, { recursive: true })
|
|
195
238
|
return ctx.workspaceRegistry.create(
|
|
196
239
|
dir,
|
|
197
|
-
workspaceTitleFor(chatType, chatId, senderOpenId
|
|
240
|
+
workspaceTitleFor(chatType, chatId, senderOpenId),
|
|
198
241
|
)
|
|
199
242
|
}
|