oh-my-codex 0.11.0 → 0.11.2
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/Cargo.lock +5 -5
- package/Cargo.toml +1 -1
- package/crates/omx-explore/src/main.rs +115 -9
- package/dist/cli/__tests__/ask.test.js +3 -3
- package/dist/cli/__tests__/ask.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-team.test.js +12 -2
- package/dist/cli/__tests__/doctor-team.test.js.map +1 -1
- package/dist/cli/__tests__/exec.test.js +4 -0
- package/dist/cli/__tests__/exec.test.js.map +1 -1
- package/dist/cli/__tests__/hooks.test.d.ts +2 -0
- package/dist/cli/__tests__/hooks.test.d.ts.map +1 -0
- package/dist/cli/__tests__/hooks.test.js +55 -0
- package/dist/cli/__tests__/hooks.test.js.map +1 -0
- package/dist/cli/__tests__/index.test.js +6 -2
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +3 -10
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-packaging.test.js +1 -1
- package/dist/cli/__tests__/sparkshell-packaging.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +4 -4
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/ask.js +1 -1
- package/dist/cli/ask.js.map +1 -1
- package/dist/cli/hooks.d.ts.map +1 -1
- package/dist/cli/hooks.js +2 -2
- package/dist/cli/hooks.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +26 -12
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/tmux-hook.d.ts.map +1 -1
- package/dist/cli/tmux-hook.js +5 -4
- package/dist/cli/tmux-hook.js.map +1 -1
- package/dist/hooks/__tests__/codebase-map.test.js +1 -1
- package/dist/hooks/__tests__/codebase-map.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +46 -36
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +6 -0
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +24 -24
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-linked-sync.test.js +45 -1
- package/dist/hooks/__tests__/notify-hook-linked-sync.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-modules.test.js +1 -1
- package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-native-dispatch-contract.test.js +5 -5
- package/dist/hooks/__tests__/notify-hook-native-dispatch-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-regression-205.test.js +22 -1
- package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +126 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +5 -4
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +141 -3
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js +11 -8
- package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-visual-verdict.test.js +1 -1
- package/dist/hooks/__tests__/notify-hook-visual-verdict.test.js.map +1 -1
- package/dist/hooks/__tests__/tmux-hook-engine-types-sync.test.js +1 -1
- package/dist/hooks/__tests__/tmux-hook-engine-types-sync.test.js.map +1 -1
- package/dist/hooks/__tests__/tmux-hook-engine.test.js +65 -1
- package/dist/hooks/__tests__/tmux-hook-engine.test.js.map +1 -1
- package/dist/hooks/extensibility/__tests__/sdk-public-surface.test.d.ts +2 -0
- package/dist/hooks/extensibility/__tests__/sdk-public-surface.test.d.ts.map +1 -0
- package/dist/hooks/extensibility/__tests__/sdk-public-surface.test.js +32 -0
- package/dist/hooks/extensibility/__tests__/sdk-public-surface.test.js.map +1 -0
- package/dist/hooks/extensibility/__tests__/sdk.test.js +172 -0
- package/dist/hooks/extensibility/__tests__/sdk.test.js.map +1 -1
- package/dist/hooks/extensibility/sdk/logging.d.ts +6 -0
- package/dist/hooks/extensibility/sdk/logging.d.ts.map +1 -0
- package/dist/hooks/extensibility/sdk/logging.js +32 -0
- package/dist/hooks/extensibility/sdk/logging.js.map +1 -0
- package/dist/hooks/extensibility/sdk/paths.d.ts +7 -0
- package/dist/hooks/extensibility/sdk/paths.d.ts.map +1 -0
- package/dist/hooks/extensibility/sdk/paths.js +22 -0
- package/dist/hooks/extensibility/sdk/paths.js.map +1 -0
- package/dist/hooks/extensibility/sdk/plugin-state.d.ts +5 -0
- package/dist/hooks/extensibility/sdk/plugin-state.d.ts.map +1 -0
- package/dist/hooks/extensibility/sdk/plugin-state.js +66 -0
- package/dist/hooks/extensibility/sdk/plugin-state.js.map +1 -0
- package/dist/hooks/extensibility/sdk/runtime-state.d.ts +3 -0
- package/dist/hooks/extensibility/sdk/runtime-state.d.ts.map +1 -0
- package/dist/hooks/extensibility/sdk/runtime-state.js +41 -0
- package/dist/hooks/extensibility/sdk/runtime-state.js.map +1 -0
- package/dist/hooks/extensibility/sdk/tmux.d.ts +10 -0
- package/dist/hooks/extensibility/sdk/tmux.d.ts.map +1 -0
- package/dist/hooks/extensibility/sdk/tmux.js +178 -0
- package/dist/hooks/extensibility/sdk/tmux.js.map +1 -0
- package/dist/hooks/extensibility/sdk.d.ts.map +1 -1
- package/dist/hooks/extensibility/sdk.js +14 -246
- package/dist/hooks/extensibility/sdk.js.map +1 -1
- package/dist/hooks/extensibility/types.d.ts +52 -0
- package/dist/hooks/extensibility/types.d.ts.map +1 -1
- package/dist/hud/__tests__/authority.test.js +2 -2
- package/dist/hud/__tests__/authority.test.js.map +1 -1
- package/dist/hud/__tests__/index.test.js +9 -2
- package/dist/hud/__tests__/index.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server-schema.test.js +10 -0
- package/dist/mcp/__tests__/state-server-schema.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server.test.js +152 -1
- package/dist/mcp/__tests__/state-server.test.js.map +1 -1
- package/dist/mcp/state-server.d.ts +4 -4
- package/dist/mcp/state-server.d.ts.map +1 -1
- package/dist/mcp/state-server.js +1 -0
- package/dist/mcp/state-server.js.map +1 -1
- package/dist/modes/__tests__/base-autoresearch-contract.test.js +17 -0
- package/dist/modes/__tests__/base-autoresearch-contract.test.js.map +1 -1
- package/dist/modes/base.d.ts +2 -2
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +1 -1
- package/dist/notifications/__tests__/reply-listener.test.js +375 -8
- package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
- package/dist/notifications/reply-listener.d.ts +57 -0
- package/dist/notifications/reply-listener.d.ts.map +1 -1
- package/dist/notifications/reply-listener.js +85 -40
- package/dist/notifications/reply-listener.js.map +1 -1
- package/dist/scripts/__tests__/smoke-packed-install.test.js +8 -41
- package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
- package/dist/scripts/__tests__/test-reply-listener-live.test.d.ts +2 -0
- package/dist/scripts/__tests__/test-reply-listener-live.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/test-reply-listener-live.test.js +82 -0
- package/dist/scripts/__tests__/test-reply-listener-live.test.js.map +1 -0
- package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.js +7 -3
- package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.js +4 -3
- package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-worker.js +27 -8
- package/dist/scripts/notify-hook/team-worker.js.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts +0 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js +123 -112
- package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
- package/dist/scripts/smoke-packed-install.d.ts +1 -3
- package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
- package/dist/scripts/smoke-packed-install.js +14 -155
- package/dist/scripts/smoke-packed-install.js.map +1 -1
- package/dist/scripts/test-reply-listener-live.d.ts +24 -0
- package/dist/scripts/test-reply-listener-live.d.ts.map +1 -0
- package/dist/scripts/test-reply-listener-live.js +138 -0
- package/dist/scripts/test-reply-listener-live.js.map +1 -0
- package/dist/scripts/tmux-hook-engine.d.ts +3 -3
- package/dist/scripts/tmux-hook-engine.d.ts.map +1 -1
- package/dist/scripts/tmux-hook-engine.js +17 -16
- package/dist/scripts/tmux-hook-engine.js.map +1 -1
- package/dist/team/__tests__/hardening-e2e.test.js +11 -1
- package/dist/team/__tests__/hardening-e2e.test.js.map +1 -1
- package/dist/team/__tests__/mcp-comm.test.js +11 -1
- package/dist/team/__tests__/mcp-comm.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +11 -1
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/state.test.js +9 -1
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/__tests__/tmux-test-fixture.d.ts +9 -3
- package/dist/team/__tests__/tmux-test-fixture.d.ts.map +1 -1
- package/dist/team/__tests__/tmux-test-fixture.js +69 -24
- package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
- package/dist/team/__tests__/tmux-test-fixture.test.js +80 -3
- package/dist/team/__tests__/tmux-test-fixture.test.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +2 -7
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +13 -0
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/dist/verification/__tests__/explore-harness-release-workflow.test.js +15 -6
- package/dist/verification/__tests__/explore-harness-release-workflow.test.js.map +1 -1
- package/dist/verification/__tests__/native-release-manifest.test.js +1 -1
- package/dist/verification/__tests__/native-release-manifest.test.js.map +1 -1
- package/package.json +2 -1
- package/src/scripts/__tests__/smoke-packed-install.test.ts +9 -50
- package/src/scripts/__tests__/test-reply-listener-live.test.ts +101 -0
- package/src/scripts/notify-hook/team-dispatch.ts +8 -2
- package/src/scripts/notify-hook/team-leader-nudge.ts +4 -3
- package/src/scripts/notify-hook/team-worker.ts +26 -8
- package/src/scripts/notify-hook/tmux-injection.ts +118 -107
- package/src/scripts/smoke-packed-install.ts +17 -164
- package/src/scripts/test-reply-listener-live.ts +188 -0
- package/src/scripts/tmux-hook-engine.ts +18 -16
|
@@ -1,67 +1,26 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdtemp, mkdir,
|
|
3
|
+
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { test } from 'node:test';
|
|
7
7
|
import {
|
|
8
8
|
ensureRepoDependencies,
|
|
9
|
-
hasSparkShellFallbackBanner,
|
|
10
9
|
hasUsableNodeModules,
|
|
11
|
-
|
|
10
|
+
PACKED_INSTALL_SMOKE_CORE_COMMANDS,
|
|
12
11
|
resolveGitCommonDir,
|
|
13
12
|
resolveReusableNodeModulesSource,
|
|
14
|
-
rewriteManifestDownloadUrls,
|
|
15
13
|
} from '../smoke-packed-install.js';
|
|
16
14
|
|
|
17
|
-
test('
|
|
15
|
+
test('packed install smoke stays limited to boot + core commands', () => {
|
|
16
|
+
assert.deepEqual(PACKED_INSTALL_SMOKE_CORE_COMMANDS, [
|
|
17
|
+
['--help'],
|
|
18
|
+
['version'],
|
|
19
|
+
]);
|
|
18
20
|
assert.equal(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
PACKED_INSTALL_SMOKE_CORE_COMMANDS.some((argv) => argv.includes('explore') || argv.includes('sparkshell')),
|
|
22
|
+
false,
|
|
21
23
|
);
|
|
22
|
-
assert.equal(hasSparkShellFallbackBanner('node v20.0.0\n'), false);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test('rewrites copied native manifest download urls to the local smoke server', async () => {
|
|
26
|
-
const root = await mkdtemp(join(tmpdir(), 'omx-smoke-packed-install-'));
|
|
27
|
-
try {
|
|
28
|
-
const sourceDir = join(root, 'source-release-assets');
|
|
29
|
-
await mkdir(sourceDir, { recursive: true });
|
|
30
|
-
await writeFile(join(sourceDir, 'omx-explore-harness-x86_64-unknown-linux-musl.tar.xz'), 'explore');
|
|
31
|
-
await writeFile(join(sourceDir, 'omx-sparkshell-x86_64-unknown-linux-musl.tar.xz'), 'sparkshell');
|
|
32
|
-
await writeFile(join(sourceDir, 'native-release-manifest.json'), JSON.stringify({
|
|
33
|
-
version: '0.9.0',
|
|
34
|
-
assets: [
|
|
35
|
-
{
|
|
36
|
-
product: 'omx-explore-harness',
|
|
37
|
-
archive: 'omx-explore-harness-x86_64-unknown-linux-musl.tar.xz',
|
|
38
|
-
download_url: 'https://github.com/example/omx-explore-harness-x86_64-unknown-linux-musl.tar.xz',
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
product: 'omx-sparkshell',
|
|
42
|
-
archive: 'omx-sparkshell-x86_64-unknown-linux-musl.tar.xz',
|
|
43
|
-
download_url: 'https://github.com/example/omx-sparkshell-x86_64-unknown-linux-musl.tar.xz',
|
|
44
|
-
},
|
|
45
|
-
],
|
|
46
|
-
}, null, 2));
|
|
47
|
-
|
|
48
|
-
const copiedDir = prepareLocalHydrationAssetDirectory(sourceDir, root);
|
|
49
|
-
rewriteManifestDownloadUrls(join(copiedDir, 'native-release-manifest.json'), 'http://127.0.0.1:43123');
|
|
50
|
-
|
|
51
|
-
const originalManifest = JSON.parse(await readFile(join(sourceDir, 'native-release-manifest.json'), 'utf-8')) as { assets: Array<{ download_url: string }> };
|
|
52
|
-
const copiedManifest = JSON.parse(await readFile(join(copiedDir, 'native-release-manifest.json'), 'utf-8')) as { assets: Array<{ download_url: string }> };
|
|
53
|
-
|
|
54
|
-
assert.match(originalManifest.assets[0].download_url, /^https:\/\/github\.com\//);
|
|
55
|
-
assert.deepEqual(
|
|
56
|
-
copiedManifest.assets.map((asset) => asset.download_url),
|
|
57
|
-
[
|
|
58
|
-
'http://127.0.0.1:43123/omx-explore-harness-x86_64-unknown-linux-musl.tar.xz',
|
|
59
|
-
'http://127.0.0.1:43123/omx-sparkshell-x86_64-unknown-linux-musl.tar.xz',
|
|
60
|
-
],
|
|
61
|
-
);
|
|
62
|
-
} finally {
|
|
63
|
-
await rm(root, { recursive: true, force: true });
|
|
64
|
-
}
|
|
65
24
|
});
|
|
66
25
|
|
|
67
26
|
test('resolveGitCommonDir resolves relative git common dir output against the repo root', () => {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { test } from 'node:test';
|
|
3
|
+
import {
|
|
4
|
+
resolveReplyListenerLiveEnv,
|
|
5
|
+
runReplyListenerLiveSmoke,
|
|
6
|
+
} from '../test-reply-listener-live.js';
|
|
7
|
+
|
|
8
|
+
function jsonResponse(body: unknown, init: ResponseInit = {}): Response {
|
|
9
|
+
const headers = new Headers(init.headers);
|
|
10
|
+
if (!headers.has('content-type')) {
|
|
11
|
+
headers.set('content-type', 'application/json');
|
|
12
|
+
}
|
|
13
|
+
return new Response(JSON.stringify(body), { ...init, headers });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test('resolveReplyListenerLiveEnv stays disabled until explicitly opted in', () => {
|
|
17
|
+
const result = resolveReplyListenerLiveEnv({
|
|
18
|
+
OMX_DISCORD_NOTIFIER_BOT_TOKEN: 'discord-token',
|
|
19
|
+
OMX_DISCORD_NOTIFIER_CHANNEL: 'channel-1',
|
|
20
|
+
OMX_TELEGRAM_BOT_TOKEN: 'telegram-token',
|
|
21
|
+
OMX_TELEGRAM_CHAT_ID: 'chat-1',
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
assert.deepEqual(result, {
|
|
25
|
+
enabled: false,
|
|
26
|
+
missing: [],
|
|
27
|
+
config: null,
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('resolveReplyListenerLiveEnv reports missing credentials when opted in', () => {
|
|
32
|
+
const result = resolveReplyListenerLiveEnv({
|
|
33
|
+
OMX_REPLY_LISTENER_LIVE: '1',
|
|
34
|
+
OMX_DISCORD_NOTIFIER_BOT_TOKEN: 'discord-token',
|
|
35
|
+
OMX_TELEGRAM_CHAT_ID: 'chat-1',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
assert.equal(result.enabled, true);
|
|
39
|
+
assert.deepEqual(result.missing, [
|
|
40
|
+
'OMX_DISCORD_NOTIFIER_CHANNEL',
|
|
41
|
+
'OMX_TELEGRAM_BOT_TOKEN',
|
|
42
|
+
]);
|
|
43
|
+
assert.equal(result.config, null);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('runReplyListenerLiveSmoke exercises Discord and Telegram send + cleanup requests', async () => {
|
|
47
|
+
const calls: Array<{ url: string; init?: RequestInit }> = [];
|
|
48
|
+
const logs: string[] = [];
|
|
49
|
+
|
|
50
|
+
const fetchImpl: typeof fetch = async (input, init) => {
|
|
51
|
+
const url = String(input);
|
|
52
|
+
calls.push({ url, init });
|
|
53
|
+
|
|
54
|
+
if (url === 'https://discord.com/api/v10/channels/channel-1/messages') {
|
|
55
|
+
assert.equal(init?.method, 'POST');
|
|
56
|
+
assert.match(String(init?.body), /Discord connectivity probe/);
|
|
57
|
+
return jsonResponse({ id: 'discord-message-1' });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (url === 'https://discord.com/api/v10/channels/channel-1/messages/discord-message-1') {
|
|
61
|
+
assert.equal(init?.method, 'DELETE');
|
|
62
|
+
return new Response(null, { status: 204 });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (url === 'https://api.telegram.org/bottelegram-token/sendMessage') {
|
|
66
|
+
assert.equal(init?.method, 'POST');
|
|
67
|
+
assert.match(String(init?.body), /Telegram connectivity probe/);
|
|
68
|
+
return jsonResponse({ ok: true, result: { message_id: 42 } });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (url === 'https://api.telegram.org/bottelegram-token/deleteMessage') {
|
|
72
|
+
assert.equal(init?.method, 'POST');
|
|
73
|
+
const payload = JSON.parse(String(init?.body)) as { chat_id: string; message_id: number };
|
|
74
|
+
assert.deepEqual(payload, { chat_id: 'chat-1', message_id: 42 });
|
|
75
|
+
return jsonResponse({ ok: true, result: true });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
throw new Error(`Unexpected live smoke fetch url: ${url}`);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const result = await runReplyListenerLiveSmoke(
|
|
82
|
+
{
|
|
83
|
+
discordBotToken: 'discord-token',
|
|
84
|
+
discordChannelId: 'channel-1',
|
|
85
|
+
telegramBotToken: 'telegram-token',
|
|
86
|
+
telegramChatId: 'chat-1',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
fetchImpl,
|
|
90
|
+
log: (message) => logs.push(message),
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
assert.deepEqual(result, {
|
|
95
|
+
discordMessageId: 'discord-message-1',
|
|
96
|
+
telegramMessageId: '42',
|
|
97
|
+
});
|
|
98
|
+
assert.equal(calls.length, 4);
|
|
99
|
+
assert.ok(logs.some((entry) => entry.includes('Discord probe message sent: discord-message-1')));
|
|
100
|
+
assert.ok(logs.some((entry) => entry.includes('Telegram probe message sent: 42')));
|
|
101
|
+
});
|
|
@@ -231,9 +231,15 @@ async function withMailboxLock(teamDirPath, workerName, fn) {
|
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
function resolveLeaderPaneId(config) {
|
|
235
|
+
return safeString(config?.leader_pane_id).trim();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
234
239
|
function defaultInjectTarget(request, config) {
|
|
235
240
|
if (request.to_worker === 'leader-fixed') {
|
|
236
|
-
|
|
241
|
+
const leaderPaneId = resolveLeaderPaneId(config);
|
|
242
|
+
if (leaderPaneId) return { type: 'pane', value: leaderPaneId };
|
|
237
243
|
return null;
|
|
238
244
|
}
|
|
239
245
|
if (request.pane_id) return { type: 'pane', value: request.pane_id };
|
|
@@ -489,7 +495,7 @@ export async function drainPendingTeamDispatch({
|
|
|
489
495
|
continue;
|
|
490
496
|
}
|
|
491
497
|
|
|
492
|
-
if (request.to_worker === 'leader-fixed' && !
|
|
498
|
+
if (request.to_worker === 'leader-fixed' && !resolveLeaderPaneId(config)) {
|
|
493
499
|
const nowIso = new Date().toISOString();
|
|
494
500
|
const alreadyDeferred = safeString(request.last_reason).trim() === LEADER_PANE_MISSING_DEFERRED_REASON;
|
|
495
501
|
request.updated_at = nowIso;
|
|
@@ -11,7 +11,7 @@ import { readJsonIfExists, getScopedStateDirsForCurrentSession } from './state-i
|
|
|
11
11
|
import { runProcess } from './process-runner.js';
|
|
12
12
|
import { logTmuxHookEvent } from './log.js';
|
|
13
13
|
import { evaluatePaneInjectionReadiness, sendPaneInput } from './team-tmux-guard.js';
|
|
14
|
-
import { DEFAULT_MARKER } from '../tmux-hook-engine.js';
|
|
14
|
+
import { DEFAULT_MARKER, resolveCodexPane } from '../tmux-hook-engine.js';
|
|
15
15
|
const LEADER_PANE_MISSING_NO_INJECTION_REASON = 'leader_pane_missing_no_injection';
|
|
16
16
|
const LEADER_PANE_SHELL_NO_INJECTION_REASON = 'leader_pane_shell_no_injection';
|
|
17
17
|
const LEADER_NOTIFICATION_DEFERRED_TYPE = 'leader_notification_deferred';
|
|
@@ -541,8 +541,9 @@ export async function maybeNudgeTeamLeader({ cwd, stateDir, logsDir, preComputed
|
|
|
541
541
|
const workerPaneIds = Array.isArray(workers)
|
|
542
542
|
? workers.map((w) => safeString(w && w.pane_id ? w.pane_id : '')).filter(Boolean)
|
|
543
543
|
: [];
|
|
544
|
-
|
|
545
|
-
|
|
544
|
+
const canonicalLeaderPaneId = safeString(resolveCodexPane() || leaderPaneId).trim();
|
|
545
|
+
if (!tmuxSession && !canonicalLeaderPaneId) continue;
|
|
546
|
+
const tmuxTarget = canonicalLeaderPaneId;
|
|
546
547
|
const paneStatus = tmuxSession
|
|
547
548
|
? await checkWorkerPanesAlive(tmuxSession, workerPaneIds)
|
|
548
549
|
: { alive: false, paneCount: 0 };
|
|
@@ -10,6 +10,7 @@ import { asNumber, safeString, isTerminalPhase } from './utils.js';
|
|
|
10
10
|
import { readJsonIfExists } from './state-io.js';
|
|
11
11
|
import { logTmuxHookEvent } from './log.js';
|
|
12
12
|
import { checkPaneReadyForTeamSendKeys, sendPaneInput } from './team-tmux-guard.js';
|
|
13
|
+
import { resolvePaneTarget } from './tmux-injection.js';
|
|
13
14
|
import { DEFAULT_MARKER } from '../tmux-hook-engine.js';
|
|
14
15
|
const LEADER_PANE_SHELL_NO_INJECTION_REASON = 'leader_pane_shell_no_injection';
|
|
15
16
|
|
|
@@ -257,6 +258,21 @@ export async function readTeamWorkersForIdleCheck(stateDir, teamName) {
|
|
|
257
258
|
}
|
|
258
259
|
}
|
|
259
260
|
|
|
261
|
+
async function resolveCanonicalLeaderPaneId(_tmuxSession, leaderPaneId) {
|
|
262
|
+
const normalizedLeaderPaneId = safeString(leaderPaneId).trim();
|
|
263
|
+
if (normalizedLeaderPaneId) {
|
|
264
|
+
try {
|
|
265
|
+
const resolved = await resolvePaneTarget({ type: 'pane', value: normalizedLeaderPaneId }, '', '', '');
|
|
266
|
+
const paneTarget = safeString(resolved?.paneTarget).trim();
|
|
267
|
+
if (paneTarget) return paneTarget;
|
|
268
|
+
} catch {
|
|
269
|
+
// fall through to tmux session scan
|
|
270
|
+
}
|
|
271
|
+
return normalizedLeaderPaneId;
|
|
272
|
+
}
|
|
273
|
+
return '';
|
|
274
|
+
}
|
|
275
|
+
|
|
260
276
|
async function emitLeaderPaneMissingDeferred({
|
|
261
277
|
stateDir,
|
|
262
278
|
logsDir,
|
|
@@ -342,6 +358,7 @@ export async function maybeNotifyLeaderAllWorkersIdle({ cwd, stateDir, logsDir,
|
|
|
342
358
|
const teamInfo = await readTeamWorkersForIdleCheck(stateDir, teamName);
|
|
343
359
|
if (!teamInfo) return;
|
|
344
360
|
const { workers, tmuxSession, leaderPaneId } = teamInfo;
|
|
361
|
+
const canonicalLeaderPaneId = await resolveCanonicalLeaderPaneId(tmuxSession, leaderPaneId);
|
|
345
362
|
|
|
346
363
|
// Check cooldown to prevent notification spam
|
|
347
364
|
const idleStatePath = join(stateDir, 'team', teamName, 'all-workers-idle.json');
|
|
@@ -364,7 +381,7 @@ export async function maybeNotifyLeaderAllWorkersIdle({ cwd, stateDir, logsDir,
|
|
|
364
381
|
);
|
|
365
382
|
if (!allIdle) return;
|
|
366
383
|
|
|
367
|
-
if (!
|
|
384
|
+
if (!canonicalLeaderPaneId) {
|
|
368
385
|
const nextIdleState = {
|
|
369
386
|
...idleState,
|
|
370
387
|
last_notified_at_ms: nowMs,
|
|
@@ -380,7 +397,7 @@ export async function maybeNotifyLeaderAllWorkersIdle({ cwd, stateDir, logsDir,
|
|
|
380
397
|
workerName,
|
|
381
398
|
sourceType: 'all_workers_idle',
|
|
382
399
|
tmuxSession,
|
|
383
|
-
leaderPaneId,
|
|
400
|
+
leaderPaneId: canonicalLeaderPaneId,
|
|
384
401
|
});
|
|
385
402
|
return;
|
|
386
403
|
}
|
|
@@ -388,7 +405,7 @@ export async function maybeNotifyLeaderAllWorkersIdle({ cwd, stateDir, logsDir,
|
|
|
388
405
|
const N = workers.length;
|
|
389
406
|
const nextAction = `Next: run omx team status ${teamName}, read unread worker messages, then decide whether to assign the next concrete task, reconcile results, or shut the team down.`;
|
|
390
407
|
const message = `[OMX] All ${N} worker${N === 1 ? '' : 's'} idle. ${nextAction} ${DEFAULT_MARKER}`;
|
|
391
|
-
const tmuxTarget =
|
|
408
|
+
const tmuxTarget = canonicalLeaderPaneId;
|
|
392
409
|
const paneGuard = await checkPaneReadyForTeamSendKeys(tmuxTarget);
|
|
393
410
|
if (!paneGuard.ok) {
|
|
394
411
|
const nextIdleState = {
|
|
@@ -409,7 +426,7 @@ export async function maybeNotifyLeaderAllWorkersIdle({ cwd, stateDir, logsDir,
|
|
|
409
426
|
paneCurrentCommand: paneGuard.paneCurrentCommand,
|
|
410
427
|
sourceType: 'all_workers_idle',
|
|
411
428
|
tmuxSession,
|
|
412
|
-
leaderPaneId,
|
|
429
|
+
leaderPaneId: canonicalLeaderPaneId,
|
|
413
430
|
});
|
|
414
431
|
return;
|
|
415
432
|
}
|
|
@@ -548,8 +565,9 @@ export async function maybeNotifyLeaderWorkerIdle({ cwd, stateDir, logsDir, pars
|
|
|
548
565
|
const teamInfo = await readTeamWorkersForIdleCheck(stateDir, teamName);
|
|
549
566
|
if (!teamInfo) return;
|
|
550
567
|
const { tmuxSession, leaderPaneId } = teamInfo;
|
|
568
|
+
const canonicalLeaderPaneId = await resolveCanonicalLeaderPaneId(tmuxSession, leaderPaneId);
|
|
551
569
|
|
|
552
|
-
if (!
|
|
570
|
+
if (!canonicalLeaderPaneId) {
|
|
553
571
|
await emitLeaderPaneMissingDeferred({
|
|
554
572
|
stateDir,
|
|
555
573
|
logsDir,
|
|
@@ -557,11 +575,11 @@ export async function maybeNotifyLeaderWorkerIdle({ cwd, stateDir, logsDir, pars
|
|
|
557
575
|
workerName,
|
|
558
576
|
sourceType: 'worker_idle',
|
|
559
577
|
tmuxSession,
|
|
560
|
-
leaderPaneId,
|
|
578
|
+
leaderPaneId: canonicalLeaderPaneId,
|
|
561
579
|
});
|
|
562
580
|
return;
|
|
563
581
|
}
|
|
564
|
-
const tmuxTarget =
|
|
582
|
+
const tmuxTarget = canonicalLeaderPaneId;
|
|
565
583
|
const paneGuard = await checkPaneReadyForTeamSendKeys(tmuxTarget);
|
|
566
584
|
if (!paneGuard.ok) {
|
|
567
585
|
try {
|
|
@@ -584,7 +602,7 @@ export async function maybeNotifyLeaderWorkerIdle({ cwd, stateDir, logsDir, pars
|
|
|
584
602
|
paneCurrentCommand: paneGuard.paneCurrentCommand,
|
|
585
603
|
sourceType: 'worker_idle',
|
|
586
604
|
tmuxSession,
|
|
587
|
-
leaderPaneId,
|
|
605
|
+
leaderPaneId: canonicalLeaderPaneId,
|
|
588
606
|
});
|
|
589
607
|
return;
|
|
590
608
|
}
|
|
@@ -23,75 +23,132 @@ import {
|
|
|
23
23
|
pickActiveMode,
|
|
24
24
|
evaluateInjectionGuards,
|
|
25
25
|
buildSendKeysArgv,
|
|
26
|
+
resolveCodexPane,
|
|
26
27
|
} from '../tmux-hook-engine.js';
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const lines = result.stdout
|
|
31
|
-
.split('\n')
|
|
32
|
-
.map((line: string) => line.trim())
|
|
33
|
-
.filter(Boolean);
|
|
34
|
-
if (lines.length === 0) return null;
|
|
35
|
-
const active = lines.find((line: string) => line.endsWith(' 1')) || lines[0];
|
|
36
|
-
const paneId = active.split(' ')[0];
|
|
37
|
-
return paneId || null;
|
|
29
|
+
function isHudPaneStartCommand(startCommand: any): boolean {
|
|
30
|
+
return /\bomx\b.*\bhud\b.*--watch/i.test(safeString(startCommand));
|
|
38
31
|
}
|
|
39
32
|
|
|
40
|
-
|
|
33
|
+
async function resolvePaneCwdMismatch(paneId: string, expectedCwd: any): Promise<any | null> {
|
|
41
34
|
if (!expectedCwd) return null;
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
try {
|
|
36
|
+
const paneCwdResult = await runProcess('tmux', ['display-message', '-p', '-t', paneId, '#{pane_current_path}']);
|
|
37
|
+
const paneCwd = safeString(paneCwdResult.stdout).trim();
|
|
38
|
+
if (paneCwd && resolvePath(paneCwd) !== resolvePath(expectedCwd)) {
|
|
39
|
+
return {
|
|
40
|
+
paneTarget: null,
|
|
41
|
+
reason: 'pane_cwd_mismatch',
|
|
42
|
+
pane_cwd: paneCwd,
|
|
43
|
+
expected_cwd: expectedCwd,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// Best effort only — if tmux cannot report cwd, keep the explicit pane target.
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function finalizeResolvedPane(paneId: string, reason: string, expectedCwd: any): Promise<any> {
|
|
53
|
+
const cwdMismatch = await resolvePaneCwdMismatch(paneId, expectedCwd);
|
|
54
|
+
if (cwdMismatch) return cwdMismatch;
|
|
55
|
+
let sessionName = '';
|
|
56
|
+
try {
|
|
57
|
+
const currentSession = await runProcess('tmux', ['display-message', '-p', '-t', paneId, '#S']);
|
|
58
|
+
sessionName = safeString(currentSession.stdout).trim();
|
|
59
|
+
} catch {
|
|
60
|
+
sessionName = '';
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
paneTarget: paneId,
|
|
64
|
+
reason,
|
|
65
|
+
matched_session: sessionName || null,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function resolveCanonicalPaneFromPaneTarget(paneTarget: any, expectedCwd: any): Promise<any> {
|
|
70
|
+
const paneResult = await runProcess('tmux', ['display-message', '-p', '-t', paneTarget, '#{pane_id}']);
|
|
71
|
+
const paneId = safeString(paneResult.stdout).trim();
|
|
72
|
+
if (!paneId) return { paneTarget: null, reason: 'target_not_found' };
|
|
73
|
+
|
|
74
|
+
let startCommand = '';
|
|
75
|
+
try {
|
|
76
|
+
const startResult = await runProcess('tmux', ['display-message', '-p', '-t', paneId, '#{pane_start_command}']);
|
|
77
|
+
startCommand = safeString(startResult.stdout).trim();
|
|
78
|
+
} catch {
|
|
79
|
+
startCommand = '';
|
|
80
|
+
}
|
|
81
|
+
if (!startCommand || !isHudPaneStartCommand(startCommand)) {
|
|
82
|
+
return finalizeResolvedPane(paneId, 'ok', expectedCwd);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let sessionName = '';
|
|
86
|
+
try {
|
|
87
|
+
const sessionResult = await runProcess('tmux', ['display-message', '-p', '-t', paneId, '#S']);
|
|
88
|
+
sessionName = safeString(sessionResult.stdout).trim();
|
|
89
|
+
} catch {
|
|
90
|
+
sessionName = '';
|
|
91
|
+
}
|
|
92
|
+
if (!sessionName) return { paneTarget: null, reason: 'target_is_hud_pane' };
|
|
93
|
+
|
|
94
|
+
const healedPaneId = await resolveSessionToPane(sessionName);
|
|
95
|
+
if (!healedPaneId) return { paneTarget: null, reason: 'target_is_hud_pane' };
|
|
96
|
+
return finalizeResolvedPane(healedPaneId, 'healed_hud_pane_target', expectedCwd);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function resolveSessionToPane(sessionName: any): Promise<string | null> {
|
|
100
|
+
const result = await runProcess('tmux', ['list-panes', '-t', sessionName, '-F', '#{pane_id}\t#{pane_active}\t#{pane_current_command}\t#{pane_start_command}']);
|
|
101
|
+
const rows = result.stdout
|
|
44
102
|
.split('\n')
|
|
45
103
|
.map((line: string) => line.trim())
|
|
46
|
-
.filter(Boolean)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
104
|
+
.filter(Boolean)
|
|
105
|
+
.map((line: string) => {
|
|
106
|
+
const parts = line.includes('\t')
|
|
107
|
+
? line.split('\t')
|
|
108
|
+
: line.split(/\s+/, 4);
|
|
109
|
+
const [paneId = '', activeRaw = '0', currentCommand = '', startCommand = ''] = parts;
|
|
110
|
+
return {
|
|
111
|
+
paneId,
|
|
112
|
+
active: activeRaw === '1',
|
|
113
|
+
currentCommand: safeString(currentCommand).trim().toLowerCase(),
|
|
114
|
+
startCommand: safeString(startCommand).trim(),
|
|
115
|
+
};
|
|
116
|
+
})
|
|
117
|
+
.filter((row: any) => row.paneId.startsWith('%'));
|
|
118
|
+
if (rows.length === 0) return null;
|
|
60
119
|
|
|
61
|
-
const
|
|
62
|
-
|
|
120
|
+
const nonHudRows = rows.filter((row: any) => !isHudPaneStartCommand(row.startCommand));
|
|
121
|
+
const canonicalRows = nonHudRows.filter((row: any) => /\bcodex\b/i.test(row.startCommand));
|
|
122
|
+
const activeCanonical = canonicalRows.find((row: any) => row.active);
|
|
123
|
+
if (activeCanonical) return activeCanonical.paneId;
|
|
124
|
+
if (canonicalRows[0]) return canonicalRows[0].paneId;
|
|
125
|
+
|
|
126
|
+
const activeNonHud = nonHudRows.find((row: any) => row.active);
|
|
127
|
+
if (activeNonHud) return activeNonHud.paneId;
|
|
128
|
+
return nonHudRows[0]?.paneId || null;
|
|
63
129
|
}
|
|
64
130
|
|
|
65
131
|
export async function resolvePaneTarget(target: any, fallbackPane: any, expectedCwd: any, modePane: any): Promise<any> {
|
|
132
|
+
const canonicalFallbackPane = safeString(fallbackPane).trim();
|
|
133
|
+
if (canonicalFallbackPane) {
|
|
134
|
+
try {
|
|
135
|
+
return await finalizeResolvedPane(canonicalFallbackPane, 'fallback_current_pane', expectedCwd);
|
|
136
|
+
} catch {
|
|
137
|
+
// Fall through to mode/config probes
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
66
141
|
if (modePane) {
|
|
67
142
|
try {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (!paneCwd || resolvePath(paneCwd) === resolvePath(expectedCwd)) {
|
|
75
|
-
const currentSession = await runProcess('tmux', ['display-message', '-p', '-t', paneId, '#S']);
|
|
76
|
-
const sessionName = safeString(currentSession.stdout).trim();
|
|
77
|
-
return {
|
|
78
|
-
paneTarget: paneId,
|
|
79
|
-
reason: 'fallback_mode_state_pane',
|
|
80
|
-
matched_session: sessionName || null,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
} else {
|
|
84
|
-
const currentSession = await runProcess('tmux', ['display-message', '-p', '-t', paneId, '#S']);
|
|
85
|
-
const sessionName = safeString(currentSession.stdout).trim();
|
|
86
|
-
return {
|
|
87
|
-
paneTarget: paneId,
|
|
88
|
-
reason: 'fallback_mode_state_pane',
|
|
89
|
-
matched_session: sessionName || null,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
143
|
+
const resolved = await resolveCanonicalPaneFromPaneTarget(modePane, expectedCwd);
|
|
144
|
+
if (resolved.paneTarget) {
|
|
145
|
+
return {
|
|
146
|
+
...resolved,
|
|
147
|
+
reason: resolved.reason === 'ok' ? 'fallback_mode_state_pane' : resolved.reason,
|
|
148
|
+
};
|
|
92
149
|
}
|
|
93
150
|
} catch {
|
|
94
|
-
// Fall through to config
|
|
151
|
+
// Fall through to config probes
|
|
95
152
|
}
|
|
96
153
|
}
|
|
97
154
|
|
|
@@ -99,65 +156,20 @@ export async function resolvePaneTarget(target: any, fallbackPane: any, expected
|
|
|
99
156
|
|
|
100
157
|
if (target.type === 'pane') {
|
|
101
158
|
try {
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
if (paneId) return { paneTarget: paneId, reason: 'ok' };
|
|
159
|
+
const resolved = await resolveCanonicalPaneFromPaneTarget(target.value, expectedCwd);
|
|
160
|
+
if (resolved.paneTarget) return resolved;
|
|
105
161
|
} catch {
|
|
106
|
-
// Fall through
|
|
162
|
+
// Fall through
|
|
107
163
|
}
|
|
108
164
|
} else {
|
|
109
165
|
try {
|
|
110
166
|
const paneId = await resolveSessionToPane(target.value);
|
|
111
|
-
if (paneId) return
|
|
112
|
-
} catch {
|
|
113
|
-
// Fall through to fallback probe
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (fallbackPane) {
|
|
118
|
-
try {
|
|
119
|
-
const currentPane = await runProcess('tmux', ['display-message', '-p', '-t', fallbackPane, '#{pane_id}']);
|
|
120
|
-
const paneId = safeString(currentPane.stdout).trim();
|
|
121
|
-
if (paneId) {
|
|
122
|
-
if (expectedCwd) {
|
|
123
|
-
const paneCwdResult = await runProcess('tmux', ['display-message', '-p', '-t', paneId, '#{pane_current_path}']);
|
|
124
|
-
const paneCwd = safeString(paneCwdResult.stdout).trim();
|
|
125
|
-
if (paneCwd && resolvePath(paneCwd) !== resolvePath(expectedCwd)) {
|
|
126
|
-
return {
|
|
127
|
-
paneTarget: null,
|
|
128
|
-
reason: 'pane_cwd_mismatch',
|
|
129
|
-
pane_cwd: paneCwd,
|
|
130
|
-
expected_cwd: expectedCwd,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const currentSession = await runProcess('tmux', ['display-message', '-p', '-t', paneId, '#S']);
|
|
136
|
-
const sessionName = safeString(currentSession.stdout).trim();
|
|
137
|
-
return {
|
|
138
|
-
paneTarget: paneId,
|
|
139
|
-
reason: 'fallback_current_pane',
|
|
140
|
-
matched_session: sessionName || null,
|
|
141
|
-
};
|
|
142
|
-
}
|
|
167
|
+
if (paneId) return await finalizeResolvedPane(paneId, 'ok', expectedCwd);
|
|
143
168
|
} catch {
|
|
144
169
|
// Fall through
|
|
145
170
|
}
|
|
146
171
|
}
|
|
147
172
|
|
|
148
|
-
try {
|
|
149
|
-
const match = await resolvePaneByCwd(expectedCwd);
|
|
150
|
-
if (match && match.paneId) {
|
|
151
|
-
return {
|
|
152
|
-
paneTarget: match.paneId,
|
|
153
|
-
reason: 'fallback_pane_by_cwd',
|
|
154
|
-
matched_session: match.sessionName,
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
} catch {
|
|
158
|
-
// Fall through
|
|
159
|
-
}
|
|
160
|
-
|
|
161
173
|
return { paneTarget: null, reason: 'target_not_found' };
|
|
162
174
|
}
|
|
163
175
|
|
|
@@ -268,8 +280,7 @@ export async function handleTmuxInjection({
|
|
|
268
280
|
turnId,
|
|
269
281
|
timestamp: nowIso,
|
|
270
282
|
}), sourceText);
|
|
271
|
-
const
|
|
272
|
-
const fallbackPane = resolveCodexPane() || safeString(process.env.TMUX_PANE || '');
|
|
283
|
+
const fallbackPane = resolveCodexPane();
|
|
273
284
|
const resolution = await resolvePaneTarget(config.target, fallbackPane, cwd, modePane);
|
|
274
285
|
if (!resolution.paneTarget) {
|
|
275
286
|
state.last_reason = resolution.reason;
|
|
@@ -307,8 +318,8 @@ export async function handleTmuxInjection({
|
|
|
307
318
|
return;
|
|
308
319
|
}
|
|
309
320
|
|
|
310
|
-
// Pane-canonical healing: persist resolved pane target so routing stops depending on session names.
|
|
311
|
-
if (config.target && config.target.type !== 'pane') {
|
|
321
|
+
// Pane-canonical healing: persist resolved pane target so routing stops depending on session names or stale pane ids.
|
|
322
|
+
if (config.target && (config.target.type !== 'pane' || safeString(config.target.value).trim() !== paneTarget)) {
|
|
312
323
|
try {
|
|
313
324
|
const healed = {
|
|
314
325
|
...(rawConfig && typeof rawConfig === 'object' ? rawConfig : {}),
|