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
|
-
/**
|
|
3
|
-
*
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
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
|
-
|
|
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 (
|
|
1919
|
+
if (tmuxSessionName && tmuxSessionName === own?.tmuxSessionName)
|
|
1925
1920
|
return [];
|
|
1926
|
-
if (
|
|
1921
|
+
if (tmuxSessionName) {
|
|
1927
1922
|
try {
|
|
1928
|
-
await runTmux(['has-session', '-t', `=${
|
|
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(
|
|
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,
|
package/docs/integrations.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
package/runtime-manifest.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"packageName": "termdock",
|
|
4
|
-
"version": "1.4.
|
|
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
|
|
9
|
+
"serverBundleHash": "sha256-7LaEwk0ur5djmnXCjBBkhMPg2z5dB5dcYm7tmh14Pko=",
|
|
10
10
|
"clientBundleHash": "sha256-HOoNMOYhnph9XwUhSYE5W2CdI9Vb4g0c64ubW49LnXE=",
|
|
11
11
|
"entrypoint": "dist/server/cli.js",
|
|
12
12
|
"clientEntrypoint": "dist/client/index.html"
|