dsh-mobile-plugin 0.1.72 → 0.1.73

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/lib/client.js CHANGED
@@ -514,7 +514,7 @@ window.__ModuleLoader__.load({
514
514
  webAccessOff: '公网网页管理已关闭',
515
515
  webAccessOpen: '开放网页管理 2 小时',
516
516
  webAccessClose: '立即关闭网页管理',
517
- webAccessHint: '默认关闭;开启后 2 小时自动关闭,访问不会延长时间,也不影响手机 App。只可从本机 DSH 设置页开关。',
517
+ webAccessHint: '默认关闭;开启后 2 小时自动关闭,访问不会延长时间。关闭后公网不可访问,本机和局域网仍可访问;不影响手机 App。只可从本机 DSH 设置页开关。',
518
518
  webAccessUnavailable: '正在读取本机访问状态…',
519
519
  webAccessError: '更新网页访问状态失败',
520
520
  },
@@ -531,7 +531,7 @@ window.__ModuleLoader__.load({
531
531
  webAccessOff: 'Public web management is closed',
532
532
  webAccessOpen: 'Open web management for 2 hours',
533
533
  webAccessClose: 'Close web management now',
534
- webAccessHint: 'Closed by default; opening lasts 2 hours and visits do not extend it. Mobile app access is unaffected. Only this local DSH settings page can toggle it.',
534
+ webAccessHint: 'Closed by default; opening lasts 2 hours and visits do not extend it. When closed, public access is blocked while this machine and its LAN can still access the page. Mobile app access is unaffected. Only this local DSH settings page can toggle it.',
535
535
  webAccessUnavailable: 'Reading local access status…',
536
536
  webAccessError: 'Failed to update web access status',
537
537
  },
@@ -1,13 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * 移动端「清理所有已归档的对话」:读宿主的会话清单与归档集合,把该删的交落地函数删掉。
4
+ * 移动端「清理所有已归档的对话」:按宿主归档集合确定清理范围,并交由落地函数删除。
5
5
  *
6
- * 与 agent 工具 session_cleanup(action='delete') 同一口径:**只删已归档的**会话
7
- * (blank / running 行一概跳过),且请求必须显式带 confirm: true —— 磁盘删除不可恢复,
8
- * 手机端「少点一次」不值得把它变成滑一下就没了的动作。
6
+ * 以 workspace.list 的 archivedSessionIds 为清理范围,不用可能过时的列表状态缩小范围;
7
+ * 请求必须显式带 confirm: true —— 磁盘删除不可恢复,手机端「少点一次」不值得把它变成滑一下就没了的动作。
9
8
  *
10
- * 删除动作不重写:zstd 读 header.id → 删会话目录 + 投影缓存这套只在 session-cleanup.cjs
9
+ * 删除动作不重写:读日志 header.id → 摘归档集合 → 删会话目录 + 投影缓存这套只在 session-cleanup.cjs
11
10
  * 里存在(见地图 A3-17),这里只是把它的触发面从 agent 工具扩到移动端路由。
12
11
  */
13
12
 
@@ -16,7 +15,7 @@ const { withIdempotency, requestIdOf } = require('./idempotency.js');
16
15
  const { canonicalDeviceKey } = require('./session-cache.js');
17
16
  const { invalidateSessionList } = require('./sessions.js');
18
17
  const { unwrap } = require('./envelope.js');
19
- const { deleteSessionData } = require('../session-cleanup.cjs');
18
+ const { deleteSessionData, forgetArchivedSessions } = require('../session-cleanup.cjs');
20
19
 
21
20
  /**
22
21
  * POST /api/mobile/v1/workspace/clear-archived(token)
@@ -29,29 +28,22 @@ async function handleClearArchived(ctx, req, res) {
29
28
  return;
30
29
  }
31
30
  const outcome = await withIdempotency(ctx, canonicalDeviceKey(req), requestIdOf(req, body), async () => {
32
- // 两个 RPC 都走信封:读响应必须 unwrap(A3-17 的坑——直读 response.value 会静默空转)。
33
- const listResponse = unwrap(await ctx.apiProxy.sessions.list({ rpcId: 'mobile', method: 'session.list', payload: {} }));
31
+ // 归档集合是清理范围的唯一依据;列表摘要里的 running / blank 可能过时,也不应缩小「全部归档」的范围。
34
32
  const workspaceResponse = unwrap(await ctx.apiProxy.workspace.list({ rpcId: 'mobile', method: 'workspace.list', payload: {} }));
35
- for (const [source, response] of [['session.list', listResponse], ['workspace.list', workspaceResponse]]) {
36
- if (response.error) {
37
- return { status: 400, body: { ok: false, error: { ...response.error, message: `${source} 失败:${response.error.message || response.error.code || '未知错误'}` } } };
38
- }
33
+ if (workspaceResponse.error) {
34
+ return { status: 400, body: { ok: false, error: { ...workspaceResponse.error, message: `workspace.list 失败:${workspaceResponse.error.message || workspaceResponse.error.code || '未知错误'}` } } };
39
35
  }
40
- const items = Array.isArray(listResponse.value?.items) ? listResponse.value.items : [];
41
36
  const archivedIds = Array.isArray(workspaceResponse.value?.archivedSessionIds) ? workspaceResponse.value.archivedSessionIds : [];
42
- // 与 session_cleanup 的 delete 同一过滤:正在跑的、空壳的会话哪怕在归档集合里也不删。
43
- const deletable = new Set(
44
- items
45
- .filter((item) => item && !item.blank && !item.running)
46
- .map((item) => item.sessionId)
47
- .filter((id) => typeof id === 'string' && id),
48
- );
49
- const selected = archivedIds.filter((id) => typeof id === 'string' && deletable.has(id));
37
+ const selected = archivedIds.filter((id) => typeof id === 'string' && id);
50
38
  if (selected.length === 0) return { status: 200, body: { ok: true, deleted: 0, archivedSessionIds: archivedIds } };
51
- const removed = deleteSessionData(selected);
39
+ // 先把它从宿主归档集合里摘掉,再删日志:宿主不会自己修剪那份集合,删完还留着
40
+ // 那些 id 就是「已归档」里一排点不开的死行。
41
+ await forgetArchivedSessions(ctx, selected, 'mobile');
42
+ const removed = await deleteSessionData(selected);
52
43
  // 删的是列表里可见的行:手机端正开着列表,不立刻失效最长 10 秒还能点到死行。
53
44
  invalidateSessionList(ctx);
54
- return { status: 200, body: { ok: true, deleted: removed.length, archivedSessionIds: archivedIds } };
45
+ const cleared = new Set(selected);
46
+ return { status: 200, body: { ok: true, deleted: removed.length, archivedSessionIds: archivedIds.filter((id) => !cleared.has(id)) } };
55
47
  });
56
48
  sendJson(res, outcome.status, outcome.body);
57
49
  }
@@ -15,6 +15,13 @@
15
15
  * 密钥值只在这条路上出现一次(手机 → 宿主)。宿主从不回传值:describe 只给
16
16
  * {configured, source?, writable},所以这里的响应里也永远不会有 value 字段。
17
17
  *
18
+ * 宿主 describe **回的就是一张平表**:键是引用名、值是上面那个视图
19
+ * (`dsh-api-settings-controller/lib/types/credentials.js` 的
20
+ * `Object.fromEntries(branded.map(...))`,0.2.0 的网关与旧 apiProxy 两条路都打到
21
+ * 同一个 Remote 命名空间)。曾经在这里多拆一层 `value.credentials` —— 那一层从来
22
+ * 不存在,于是每个引用都落成「未配置」,设置页永远显示「密钥未设置」,而测试假体
23
+ * 照抄了同一个错形状,绿的。读了宿主源码才看出来。
24
+ *
18
25
  * 「能不能写」由宿主的 credentials 服务说了算:如果某个引用是启动环境变量提供的,
19
26
  * 宿主会回 writable:false,set 则回 credential-rejected("supplied read-only by the
20
27
  * launching environment")。这里不自己判断,一律如实透传,让 App 显示宿主的原话。
@@ -76,9 +83,7 @@ async function handleCredentialsDescribe(ctx, req, res) {
76
83
  }));
77
84
  if (error) return sendJson(res, 400, { ok: false, error });
78
85
 
79
- const source = value && typeof value.credentials === 'object' && value.credentials !== null && !Array.isArray(value.credentials)
80
- ? value.credentials
81
- : {};
86
+ const source = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
82
87
  const credentials = {};
83
88
  for (const ref of refs) {
84
89
  const view = normalizeCredentialView(source[ref]);
@@ -83,6 +83,7 @@ const ENDPOINTS = {
83
83
  'workspace.unarchiveSession': inv('workspace', 'unarchiveSession'),
84
84
  'workspace.pinSession': inv('workspace', 'pinSession'),
85
85
  'workspace.unpinSession': inv('workspace', 'unpinSession'),
86
+ 'workspace.insertBefore': inv('workspace', 'insertBefore'),
86
87
  'workspace.insertSessionBefore': inv('workspace', 'insertSessionBefore'),
87
88
 
88
89
  // host.* → directoryPicker.*(宿主服务 browse 时),否则回落到插件自带的 browse 原语。
@@ -145,6 +145,17 @@ async function handleDeleteWorkspace(ctx, req, res) {
145
145
  * POST /api/mobile/v1/workspace/move-session(token){ workspaceId, sessionId, beforeSessionId? }
146
146
  * 透传宿主 workspace.insertSessionBefore,使移动端和桌面端共享工作区排序。
147
147
  */
148
+ async function handleMoveWorkspace(ctx, req, res) {
149
+ const body = await readJsonBody(req, 64 * 1024);
150
+ const workspaceId = body && typeof body.workspaceId === 'string' ? body.workspaceId.trim() : '';
151
+ const beforeWorkspaceId = body && typeof body.beforeWorkspaceId === 'string' ? body.beforeWorkspaceId.trim() : '';
152
+ if (!workspaceId) return sendJson(res, 400, { ok: false, error: { code: 'bad-request', message: '需要 workspaceId' } });
153
+ const payload = { workspaceId, ...(beforeWorkspaceId ? { beforeWorkspaceId } : {}) };
154
+ const { value, error } = unwrap(await callWorkspace(ctx, 'insertBefore', payload));
155
+ if (error) return sendJson(res, 400, { ok: false, error });
156
+ sendJson(res, 200, { ok: true, workspaceIds: value && value.workspaceIds });
157
+ }
158
+
148
159
  async function handleMoveSession(ctx, req, res) {
149
160
  const body = await readJsonBody(req, 64 * 1024);
150
161
  const workspaceId = body && typeof body.workspaceId === 'string' ? body.workspaceId.trim() : '';
@@ -255,6 +266,7 @@ async function handleUnpinSession(ctx, req, res) {
255
266
 
256
267
  module.exports = {
257
268
  buildWorkspacesBody,
269
+ handleMoveWorkspace,
258
270
  handleWorkspaces,
259
271
  handleListDirectory,
260
272
  handleCreateDirectory,