termdock 1.4.303 → 1.4.304

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.
@@ -1,26 +1,50 @@
1
1
  import { integrationError } from './integrationStore.js';
2
- /** Last-known bindings select uncertain conflicts; only process observations
3
- * prove a conversation is running. The caller separately verifies its own pane. */
2
+ /** A tmux remains observable without a browser/PTY attachment, including after
3
+ * service restart. Inventory UUIDs never filter the candidate set. */
4
+ export function collectNativeResumeOwnerCandidates(inventory, backends) {
5
+ const candidates = [];
6
+ const seenTmux = new Set(), seenBackend = new Set();
7
+ for (const entry of inventory) {
8
+ if (entry.mode !== 'tmux' || !entry.tmuxSessionName || seenTmux.has(entry.tmuxSessionName))
9
+ continue;
10
+ const backend = entry.backendSessionId ? backends.get(entry.backendSessionId) : null;
11
+ candidates.push({ backendSessionId: entry.backendSessionId ?? `inventory:${entry.sessionId}`,
12
+ tmuxSessionName: entry.tmuxSessionName, cachedSlug: backend?.agent?.slug ?? entry.agentResume?.slug ?? null,
13
+ cachedNativeId: backend?.agentSession?.sessionId ?? entry.agentResume?.sessionId ?? null });
14
+ seenTmux.add(entry.tmuxSessionName);
15
+ if (entry.backendSessionId)
16
+ seenBackend.add(entry.backendSessionId);
17
+ }
18
+ for (const [backendSessionId, backend] of backends) {
19
+ if (seenBackend.has(backendSessionId) || backend.mode === 'tmux' && backend.tmuxSessionName && seenTmux.has(backend.tmuxSessionName))
20
+ continue;
21
+ candidates.push({ backendSessionId, tmuxSessionName: backend.mode === 'tmux' ? backend.tmuxSessionName : null,
22
+ cachedSlug: backend.agent?.slug ?? null, cachedNativeId: backend.agentSession?.sessionId ?? null });
23
+ if (backend.mode === 'tmux' && backend.tmuxSessionName)
24
+ seenTmux.add(backend.tmuxSessionName);
25
+ }
26
+ return candidates;
27
+ }
28
+ /** Only process observations prove presence or absence of another owner.
29
+ * Cached UUIDs cannot exclude a terminal; the caller verifies its own pane. */
4
30
  export async function assertNativeResumeAvailable(target, excludedBackendSessionId, candidates, observe) {
5
31
  let uncertain = false;
6
32
  for (const candidate of candidates) {
7
33
  if (candidate.backendSessionId === excludedBackendSessionId)
8
34
  continue;
9
- const cachedMatch = candidate.cachedSlug === target.slug && candidate.cachedNativeId === target.nativeSessionId;
10
35
  let processes;
11
36
  try {
12
37
  processes = await observe(candidate);
13
38
  }
14
39
  catch {
15
- if (cachedMatch)
16
- uncertain = true;
40
+ uncertain = true;
17
41
  continue;
18
42
  }
19
43
  for (const process of processes) {
20
44
  if (process.confirmed && process.agentSlug === target.slug && process.nativeId === target.nativeSessionId) {
21
45
  integrationError('NATIVE_SESSION_ALREADY_RUNNING', 'Another terminal process proves the exact native conversation is running', 409);
22
46
  }
23
- if (cachedMatch && (!process.confirmed || process.agentSlug === target.slug && !process.nativeId))
47
+ if (!process.confirmed || process.agentSlug === target.slug && !process.nativeId)
24
48
  uncertain = true;
25
49
  }
26
50
  }
@@ -1,6 +1,6 @@
1
1
  import { collaborationSessionDisplayName } from '../utils/sessionDisplayName.js';
2
2
  import { integrationError } from '../agent/integrationStore.js';
3
- import { assertNativeResumeAvailable } from '../agent/nativeResumeOwner.js';
3
+ import { assertNativeResumeAvailable, collectNativeResumeOwnerCandidates } from '../agent/nativeResumeOwner.js';
4
4
  import { integrationLaunchCommand } from '../agent/integrationSessions.js';
5
5
  import { ensureTeam } from '../agent/collaborationTeam.js';
6
6
  import { androidRecordings } from '../android/recording.js';
@@ -1910,29 +1910,24 @@ async function assertIntegrationNativeResumeAvailable(record) {
1910
1910
  // Never use a recovered hook or the last-known UUID as live-owner proof.
1911
1911
  if (process.platform === 'linux')
1912
1912
  await getProcessSnapshot(true);
1913
- const candidates = [...terminalSessions].map(([backendSessionId, backend]) => {
1914
- const persisted = globalSessionState.sessions.find(entry => entry.backendSessionId === backendSessionId)?.agentResume;
1915
- return { backendSessionId, cachedSlug: backend.agent?.slug ?? persisted?.slug ?? null,
1916
- cachedNativeId: backend.agentSession?.sessionId ?? persisted?.sessionId ?? null };
1917
- });
1913
+ const candidates = collectNativeResumeOwnerCandidates(globalSessionState.sessions, terminalSessions);
1918
1914
  await assertNativeResumeAvailable({ slug: record.agent_slug, nativeSessionId: record.agent_native_session_id }, own?.backendSessionId ?? null, candidates, async (candidate) => {
1919
1915
  const backend = terminalSessions.get(candidate.backendSessionId);
1920
- if (!backend)
1921
- return [];
1916
+ const tmuxSessionName = candidate.tmuxSessionName;
1922
1917
  // Duplicate inventory attachments to the original owned pane are not
1923
1918
  // another process; launchIntegrationTerminal verifies that pane again.
1924
- if (backend.mode === 'tmux' && backend.tmuxSessionName === own?.tmuxSessionName)
1919
+ if (tmuxSessionName && tmuxSessionName === own?.tmuxSessionName)
1925
1920
  return [];
1926
- if (backend.mode === 'tmux' && backend.tmuxSessionName) {
1921
+ if (tmuxSessionName) {
1927
1922
  try {
1928
- await runTmux(['has-session', '-t', `=${backend.tmuxSessionName}`]);
1923
+ await runTmux(['has-session', '-t', `=${tmuxSessionName}`]);
1929
1924
  }
1930
1925
  catch (error) {
1931
1926
  if (/can't find session|no server running|no sessions|error connecting.*No such file/i.test(getErrorMessage(error)))
1932
1927
  return [];
1933
1928
  throw error;
1934
1929
  }
1935
- const layout = await getTmuxLayout(backend.tmuxSessionName);
1930
+ const layout = await getTmuxLayout(tmuxSessionName);
1936
1931
  return Promise.all(layout.windows.flatMap(window => window.panes).map(async (pane) => {
1937
1932
  const program = await resolveTmuxPaneProgram(pane, true, true);
1938
1933
  const agent = detectAgentFromCommand(program?.rawArgs ?? program?.command ?? '', agentCustomCommands());
@@ -1943,6 +1938,8 @@ async function assertIntegrationNativeResumeAvailable(record) {
1943
1938
  nativeId: agent && program?.rawArgs ? inferResumeSessionId(agent, splitCommandToArgv(program.rawArgs)) : null };
1944
1939
  }));
1945
1940
  }
1941
+ if (!backend)
1942
+ return [];
1946
1943
  const program = await detectShellActiveProgram(backend);
1947
1944
  const agent = detectAgentFromCommand(program?.rawArgs ?? program?.command ?? '', agentCustomCommands());
1948
1945
  return [{ confirmed: !!program && program.source !== 'unknown', agentSlug: agent?.slug ?? null,
@@ -4,7 +4,8 @@
4
4
  启动输入条件与保留凭据的策略更新要求 **CLI / 实际运行服务均至少 1.4.301**;协议仍为 1。
5
5
  插件原生 ID 的前台 argv 回读及服务重启后的运行中 restore 核验修复要求
6
6
  **实际运行服务至少 1.4.302**。退出后的重复原生会话检查修复要求
7
- **实际运行服务至少 1.4.303**;建议 CLI 与服务同步安装 1.4.303(CLI 最低仍为 1.4.301)。
7
+ **实际运行服务至少 1.4.303**。没有 PTY 附着的已登记 tmux 的重复保护修复要求
8
+ **实际运行服务至少 1.4.304**;建议 CLI 与服务同步安装 1.4.304(CLI 最低仍为 1.4.301)。
8
9
  现有 `--session`、消息投递、任务报告与自动协作入口继续兼容;旧的
9
10
  `task create --integration` 仍表示代码集成子任务,新身份使用 `--principal`。
10
11
 
@@ -303,6 +304,12 @@ operation/pane,不启动第二个 Agent;进程携带不同 UUID 时继续拒
303
304
  返回 `NATIVE_SESSION_OWNER_UNCONFIRMED` 并停止恢复;应检查该终端,不绕过保护。
304
305
  此检查覆盖服务已登记终端,不声称枚举其他应用中全部原生会话。
305
306
 
307
+ 1.4.304 起,检查合并持久终端清单和当前 PTY backend;服务重启后无需网页挂接,
308
+ 已登记 tmux 的每个 pane 都会现场核验。旧登记 UUID C 不会排除当前实际运行 UUID A
309
+ 的终端,终端的协作路由为 recovering/offline 或绑定不匹配也不会跳过检查。
310
+ 仅在确认 tmux 已消失或当前进程排除冲突时继续;未知进程、同 Agent 缺少精确 UUID、
311
+ 观察失败均返回 `NATIVE_SESSION_OWNER_UNCONFIRMED`,不以旧 UUID 证明没有冲突。
312
+
306
313
  常见失败码(每项都是完整字符串):
307
314
 
308
315
  - 配置/权限:`LAUNCH_PROFILE_DENIED`, `LAUNCHER_UNAVAILABLE`, `COLLAB_AGENT_UNAVAILABLE`, `SESSION_CWD_MISSING`, `SESSION_CWD_DENIED`, `INTEGRATION_SESSION_NOT_FOUND`, `INTEGRATION_SESSION_LIMIT`。
@@ -325,7 +332,10 @@ operation/pane,不启动第二个 Agent;进程携带不同 UUID 时继续拒
325
332
  不重复启动、不同 UUID 拒绝,以及进程退出后缓存 UUID 不再阻挡恢复。使用方已报告
326
333
  1.4.302 真实 TraeX 服务重启回读、运行中恢复不重启、不同 UUID 拒绝通过;显式恢复
327
334
  原 UUID 也已回读 ready,但配置的模型 footer 条件被暂态提示替换,输入仍保持待投递。
328
- **1.4.303 的真实 TraeX 退出后恢复与稳定输入条件仍须使用方实机复验**。
335
+ 使用方已报告 1.4.303 退出后精确恢复通过,也发现未挂接 PTY 的已登记 tmux 重复保护
336
+ 遗漏;维护机已用“旧 UUID C、实际运行 UUID A、无 PTY 附着”的真实隔离 tmux 复现,
337
+ 并验证 1.4.304 编译后检查在该状态下拒绝再次启动。
338
+ **1.4.304 的真实 TraeX 重复保护与稳定输入条件仍须使用方实机复验**。
329
339
  回读 capabilities 后,应核对原 TD ID、真实 TraeX UUID 与原 argv/cwd/包装器快照,
330
340
  并验证 Agent 真正加载原对话。
331
341
  先完成测试环境的创建→追问→关联答复,再验重复键、追加咨询、断线重投和精确恢复。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termdock",
3
- "version": "1.4.303",
3
+ "version": "1.4.304",
4
4
  "description": "A complete web-based terminal application with modern UI",
5
5
  "type": "module",
6
6
  "main": ".desktop-dist/main.js",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "packageName": "termdock",
4
- "version": "1.4.303",
4
+ "version": "1.4.304",
5
5
  "runtimeProtocolVersion": 1,
6
6
  "minimumDesktopVersion": "1.4.46",
7
7
  "nodeMajor": 22,
8
8
  "dependencyHash": "sha256-E9GDa4nrCXADRXdyPKufusZt6IcCr4m2mGdx5h34KMQ=",
9
- "serverBundleHash": "sha256-+P0Zwo5Zg52vSfyTrvxcfAIAuwY0+n4i4f0kTf8BC0g=",
9
+ "serverBundleHash": "sha256-7LaEwk0ur5djmnXCjBBkhMPg2z5dB5dcYm7tmh14Pko=",
10
10
  "clientBundleHash": "sha256-HOoNMOYhnph9XwUhSYE5W2CdI9Vb4g0c64ubW49LnXE=",
11
11
  "entrypoint": "dist/server/cli.js",
12
12
  "clientEntrypoint": "dist/client/index.html"