@modesto_ma/dsh-channels 0.1.0 → 0.3.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 +89 -55
- package/docs/access-control-design.md +5 -5
- package/docs/feasibility-and-design.md +54 -26
- package/docs/feishu-onboarding.md +26 -20
- package/docs/persistence.md +4 -4
- package/docs/refactor-plan.md +6 -5
- package/docs/workspace-isolation-design.md +78 -0
- package/lib/client.js +1715 -882
- package/lib/client.js.map +1 -1
- package/lib/index.js +1556 -680
- 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 +256 -119
- package/src/client/api.ts +20 -0
- package/src/client/catalog.ts +1 -1
- package/src/client/index.ts +1 -1
- package/src/client/panel/access-card.tsx +294 -0
- package/src/client/panel/access-tab.tsx +46 -0
- package/src/client/panel/advanced-tab.tsx +80 -0
- package/src/client/panel/cleanup-card.tsx +106 -0
- package/src/client/panel/connection-tab.tsx +214 -0
- package/src/client/panel/credentials-card.tsx +104 -0
- package/src/client/panel/index.tsx +862 -0
- package/src/client/panel/model-tab.tsx +23 -0
- package/src/client/panel/sessions-tab.tsx +21 -0
- package/src/client/panel/shared.tsx +488 -0
- package/src/client/panel/workspace-tab.tsx +40 -0
- package/src/client/styles.ts +24 -6
- package/src/config.ts +44 -12
- package/src/domain.ts +34 -3
- package/src/feishu/agent-context.ts +9 -5
- package/src/index.ts +32 -14
- package/src/routing/inbound.ts +88 -43
- package/src/service.ts +259 -65
- package/src/session/isolation.ts +277 -0
- package/src/session/manager.ts +63 -20
- 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 +223 -47
- package/src/settings/directory-picker.ts +121 -0
- package/src/settings/routes.ts +49 -2
- package/src/settings/surface.ts +171 -25
- package/src/streamer.ts +70 -30
- package/src/workspaces.ts +50 -28
- package/src/client/panel.tsx +0 -1475
package/src/workspaces.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
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)
|
|
8
|
+
*
|
|
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.
|
|
7
16
|
*
|
|
8
17
|
* Attachments received before a topic exists are staged below
|
|
9
18
|
* `<baseDir>/_staging/<chatType>/<chat_id>/<open_id>/` and adopted into the
|
|
10
|
-
* topic
|
|
19
|
+
* topic directory once its first Session is created.
|
|
11
20
|
*
|
|
12
21
|
* Feishu ids (`ou_…`, `oc_…`) are safe path segments, but they are validated
|
|
13
22
|
* anyway so a hostile id can never escape `baseDir`.
|
|
@@ -123,36 +132,51 @@ export function topicTokenFor(
|
|
|
123
132
|
return kind === 'thread' ? safeSegment(routeKey, 'route key') : safeSegment(sessionId, 'session id')
|
|
124
133
|
}
|
|
125
134
|
|
|
135
|
+
/** Git branch (and worktree directory) name for one topic token. */
|
|
136
|
+
export function worktreeBranchFor(topicToken: string): string {
|
|
137
|
+
return `feishu-${safeSegment(topicToken, 'topic token')}`
|
|
138
|
+
}
|
|
139
|
+
|
|
126
140
|
/**
|
|
127
|
-
* Deterministic workspace directory for one
|
|
128
|
-
*
|
|
141
|
+
* Deterministic workspace directory for one user in one chat context: the
|
|
142
|
+
* durable project the user's Sessions are grouped under.
|
|
129
143
|
*
|
|
130
|
-
* - p2p: `<baseDir>/
|
|
131
|
-
* - group: `<baseDir>/
|
|
144
|
+
* - p2p: `<baseDir>/p2p/<open_id>/`
|
|
145
|
+
* - group: `<baseDir>/groups/<chat_id>/<open_id>/`
|
|
132
146
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
147
|
+
* These are the same paths older versions used as their shared (user × chat)
|
|
148
|
+
* workspaces, so an upgraded deployment reuses the existing registry record
|
|
149
|
+
* instead of creating a second project for the same user.
|
|
136
150
|
*/
|
|
137
151
|
export function workspacePathFor(
|
|
138
152
|
baseDir: string,
|
|
139
153
|
chatType: 'p2p' | 'group',
|
|
140
154
|
chatId: string,
|
|
141
155
|
senderOpenId: string,
|
|
142
|
-
topicToken: string,
|
|
143
156
|
): string {
|
|
144
|
-
const chat = safeSegment(chatId, 'chatId')
|
|
145
157
|
const user = safeSegment(senderOpenId, 'senderOpenId')
|
|
146
|
-
|
|
147
|
-
return
|
|
148
|
-
|
|
149
|
-
|
|
158
|
+
if (chatType === 'p2p') return path.join(baseDir, 'p2p', user)
|
|
159
|
+
return path.join(baseDir, 'groups', safeSegment(chatId, 'chatId'), user)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Per-topic working directory inside one user workspace. */
|
|
163
|
+
export function topicDirFor(workspacePath: string, topicToken: string): string {
|
|
164
|
+
return path.join(workspacePath, 'topics', safeSegment(topicToken, 'topic token'))
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Root of the legacy (one workspace per topic) directories. Legacy workspaces
|
|
169
|
+
* live strictly below this path, which is how the retention cleanup tells them
|
|
170
|
+
* apart from user workspaces that must never be deleted.
|
|
171
|
+
*/
|
|
172
|
+
export function legacyTopicWorkspaceRoot(baseDir: string): string {
|
|
173
|
+
return path.join(baseDir, 'topics')
|
|
150
174
|
}
|
|
151
175
|
|
|
152
176
|
/**
|
|
153
177
|
* Staging directory for attachments that arrive before their topic (and so
|
|
154
|
-
* its
|
|
155
|
-
*
|
|
178
|
+
* its topic directory) exists. Not a registered workspace: files move into the
|
|
179
|
+
* real topic directory as soon as the first Session of that topic is created.
|
|
156
180
|
*/
|
|
157
181
|
export function stagingPathFor(
|
|
158
182
|
baseDir: string,
|
|
@@ -170,17 +194,16 @@ export function workspaceTitleFor(
|
|
|
170
194
|
chatType: 'p2p' | 'group',
|
|
171
195
|
chatId: string,
|
|
172
196
|
senderOpenId: string,
|
|
173
|
-
topicToken?: string,
|
|
174
197
|
): string {
|
|
175
|
-
|
|
198
|
+
return chatType === 'p2p'
|
|
176
199
|
? `飞书私聊 · ${senderOpenId}`
|
|
177
200
|
: `飞书群聊 · ${chatId} · ${senderOpenId}`
|
|
178
|
-
return topicToken === undefined ? base : `${base} · ${topicToken}`
|
|
179
201
|
}
|
|
180
202
|
|
|
181
203
|
/**
|
|
182
|
-
* Ensure one
|
|
183
|
-
* host's workspace registry. Idempotent: repeated calls return the same record
|
|
204
|
+
* Ensure one user's workspace directory exists and is registered with the
|
|
205
|
+
* host's workspace registry. Idempotent: repeated calls return the same record,
|
|
206
|
+
* and an existing record keeps its (possibly renamed) title.
|
|
184
207
|
*/
|
|
185
208
|
export async function ensureWorkspace(
|
|
186
209
|
ctx: HostContext,
|
|
@@ -188,12 +211,11 @@ export async function ensureWorkspace(
|
|
|
188
211
|
chatType: 'p2p' | 'group',
|
|
189
212
|
chatId: string,
|
|
190
213
|
senderOpenId: string,
|
|
191
|
-
topicToken: string,
|
|
192
214
|
): Promise<Workspace> {
|
|
193
|
-
const dir = workspacePathFor(baseDir, chatType, chatId, senderOpenId
|
|
215
|
+
const dir = workspacePathFor(baseDir, chatType, chatId, senderOpenId)
|
|
194
216
|
await mkdir(dir, { recursive: true })
|
|
195
217
|
return ctx.workspaceRegistry.create(
|
|
196
218
|
dir,
|
|
197
|
-
workspaceTitleFor(chatType, chatId, senderOpenId
|
|
219
|
+
workspaceTitleFor(chatType, chatId, senderOpenId),
|
|
198
220
|
)
|
|
199
221
|
}
|