oh-my-codex 0.11.12 → 0.11.13
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/README.md +23 -0
- package/README.vi.md +144 -185
- package/crates/omx-runtime-core/src/engine.rs +122 -4
- package/crates/omx-runtime-core/src/lib.rs +17 -0
- package/dist/cli/__tests__/autoresearch.test.js +11 -0
- package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
- package/dist/cli/__tests__/cleanup.test.js +117 -4
- package/dist/cli/__tests__/cleanup.test.js.map +1 -1
- package/dist/cli/__tests__/error-handling-warnings.test.js +13 -0
- package/dist/cli/__tests__/error-handling-warnings.test.js.map +1 -1
- package/dist/cli/__tests__/exec.test.js +6 -0
- package/dist/cli/__tests__/exec.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +94 -1
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +3 -0
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +10 -0
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/packaged-script-resolution.test.js +4 -3
- package/dist/cli/__tests__/packaged-script-resolution.test.js.map +1 -1
- package/dist/cli/__tests__/resume.test.js +6 -0
- package/dist/cli/__tests__/resume.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +29 -12
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/star-prompt.test.js +16 -0
- package/dist/cli/__tests__/star-prompt.test.js.map +1 -1
- package/dist/cli/__tests__/uninstall.test.js +112 -1
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/__tests__/windows-popup-loop-contract.test.d.ts +2 -0
- package/dist/cli/__tests__/windows-popup-loop-contract.test.d.ts.map +1 -0
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js +30 -0
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -0
- package/dist/cli/cleanup.d.ts +2 -0
- package/dist/cli/cleanup.d.ts.map +1 -1
- package/dist/cli/cleanup.js +26 -1
- package/dist/cli/cleanup.js.map +1 -1
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +161 -50
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +15 -14
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/star-prompt.d.ts.map +1 -1
- package/dist/cli/star-prompt.js +1 -0
- package/dist/cli/star-prompt.js.map +1 -1
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +5 -1
- package/dist/cli/team.js.map +1 -1
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +26 -0
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +1 -0
- package/dist/cli/update.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +4 -4
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/mcp-registry.test.js +13 -16
- package/dist/config/__tests__/mcp-registry.test.js.map +1 -1
- package/dist/config/mcp-registry.d.ts +1 -0
- package/dist/config/mcp-registry.d.ts.map +1 -1
- package/dist/config/mcp-registry.js +4 -4
- package/dist/config/mcp-registry.js.map +1 -1
- package/dist/config/models.d.ts +1 -0
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +39 -1
- package/dist/config/models.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +12 -1
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +499 -17
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +140 -14
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-modules.test.js +5 -0
- package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.d.ts +2 -0
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +597 -0
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -0
- package/dist/hooks/__tests__/notify-hook-regression-205.test.js +15 -1
- package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js +73 -53
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +193 -2
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +183 -0
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +255 -97
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js +0 -0
- package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +46 -0
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +1 -0
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +48 -0
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +1 -0
- package/dist/hooks/session.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +70 -1
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +10 -37
- package/dist/hud/state.js.map +1 -1
- package/dist/mcp/state-server.d.ts.map +1 -1
- package/dist/mcp/state-server.js +5 -0
- package/dist/mcp/state-server.js.map +1 -1
- package/dist/modes/__tests__/base-session-scope.test.js +46 -0
- package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +4 -0
- package/dist/modes/base.js.map +1 -1
- package/dist/notifications/__tests__/custom-alias-enablement.test.d.ts +2 -0
- package/dist/notifications/__tests__/custom-alias-enablement.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/custom-alias-enablement.test.js +84 -0
- package/dist/notifications/__tests__/custom-alias-enablement.test.js.map +1 -0
- package/dist/notifications/__tests__/idle-cooldown.test.js +55 -0
- package/dist/notifications/__tests__/idle-cooldown.test.js.map +1 -1
- package/dist/notifications/idle-cooldown.d.ts +8 -6
- package/dist/notifications/idle-cooldown.d.ts.map +1 -1
- package/dist/notifications/idle-cooldown.js +53 -22
- package/dist/notifications/idle-cooldown.js.map +1 -1
- package/dist/notifications/notifier.js +1 -1
- package/dist/notifications/notifier.js.map +1 -1
- package/dist/notifications/reply-listener.d.ts.map +1 -1
- package/dist/notifications/reply-listener.js +1 -0
- package/dist/notifications/reply-listener.js.map +1 -1
- package/dist/openclaw/config.js +2 -2
- package/dist/openclaw/config.js.map +1 -1
- package/dist/runtime/bridge.d.ts +1 -0
- package/dist/runtime/bridge.d.ts.map +1 -1
- package/dist/runtime/bridge.js +2 -6
- package/dist/runtime/bridge.js.map +1 -1
- package/dist/scripts/notify-fallback-watcher.js +97 -59
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.d.ts +2 -1
- package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.js +72 -238
- package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/managed-tmux.d.ts +19 -0
- package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -0
- package/dist/scripts/notify-hook/managed-tmux.js +320 -0
- package/dist/scripts/notify-hook/managed-tmux.js.map +1 -0
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts +22 -0
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -0
- package/dist/scripts/notify-hook/ralph-session-resume.js +277 -0
- package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -0
- package/dist/scripts/notify-hook/state-io.d.ts +1 -1
- package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
- package/dist/scripts/notify-hook/state-io.js +2 -10
- package/dist/scripts/notify-hook/state-io.js.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.js +60 -59
- package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.js +13 -5
- package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.js +1 -19
- package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker.js +4 -4
- package/dist/scripts/notify-hook/team-worker.js.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts +1 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js +102 -35
- package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
- package/dist/scripts/notify-hook.js +144 -20
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/tmux-hook-engine.d.ts +1 -0
- package/dist/scripts/tmux-hook-engine.d.ts.map +1 -1
- package/dist/scripts/tmux-hook-engine.js +3 -0
- package/dist/scripts/tmux-hook-engine.js.map +1 -1
- package/dist/team/__tests__/api-interop.test.js +96 -4
- package/dist/team/__tests__/api-interop.test.js.map +1 -1
- package/dist/team/__tests__/leader-activity.test.js +107 -2
- package/dist/team/__tests__/leader-activity.test.js.map +1 -1
- package/dist/team/__tests__/runtime-cli.test.js +32 -0
- package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +148 -0
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/shutdown-fallback.test.js +13 -0
- package/dist/team/__tests__/shutdown-fallback.test.js.map +1 -1
- package/dist/team/__tests__/state-root.test.js +11 -1
- package/dist/team/__tests__/state-root.test.js.map +1 -1
- package/dist/team/__tests__/state.test.js +16 -5
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +460 -2
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/api-interop.d.ts.map +1 -1
- package/dist/team/api-interop.js +34 -7
- package/dist/team/api-interop.js.map +1 -1
- package/dist/team/commit-hygiene.d.ts +60 -0
- package/dist/team/commit-hygiene.d.ts.map +1 -0
- package/dist/team/commit-hygiene.js +232 -0
- package/dist/team/commit-hygiene.js.map +1 -0
- package/dist/team/leader-activity.d.ts.map +1 -1
- package/dist/team/leader-activity.js +17 -35
- package/dist/team/leader-activity.js.map +1 -1
- package/dist/team/runtime-cli.d.ts +9 -1
- package/dist/team/runtime-cli.d.ts.map +1 -1
- package/dist/team/runtime-cli.js +15 -6
- package/dist/team/runtime-cli.js.map +1 -1
- package/dist/team/runtime.d.ts +7 -2
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +391 -63
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/state/dispatch.js +1 -1
- package/dist/team/state/dispatch.js.map +1 -1
- package/dist/team/state/mailbox.d.ts +1 -0
- package/dist/team/state/mailbox.d.ts.map +1 -1
- package/dist/team/state/mailbox.js +54 -8
- package/dist/team/state/mailbox.js.map +1 -1
- package/dist/team/state-root.d.ts +1 -1
- package/dist/team/state-root.d.ts.map +1 -1
- package/dist/team/state-root.js +8 -3
- package/dist/team/state-root.js.map +1 -1
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +66 -3
- package/dist/team/state.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +69 -27
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/utils/__tests__/platform-command.test.js +101 -2
- package/dist/utils/__tests__/platform-command.test.js.map +1 -1
- package/dist/utils/git-layout.d.ts +8 -0
- package/dist/utils/git-layout.d.ts.map +1 -0
- package/dist/utils/git-layout.js +58 -0
- package/dist/utils/git-layout.js.map +1 -0
- package/dist/utils/platform-command.d.ts.map +1 -1
- package/dist/utils/platform-command.js +32 -1
- package/dist/utils/platform-command.js.map +1 -1
- package/package.json +6 -6
- package/src/scripts/notify-fallback-watcher.ts +96 -58
- package/src/scripts/notify-hook/auto-nudge.ts +75 -230
- package/src/scripts/notify-hook/managed-tmux.ts +324 -0
- package/src/scripts/notify-hook/ralph-session-resume.ts +337 -0
- package/src/scripts/notify-hook/state-io.ts +2 -10
- package/src/scripts/notify-hook/team-dispatch.ts +70 -54
- package/src/scripts/notify-hook/team-leader-nudge.ts +19 -5
- package/src/scripts/notify-hook/team-tmux-guard.ts +0 -20
- package/src/scripts/notify-hook/team-worker.ts +4 -4
- package/src/scripts/notify-hook/tmux-injection.ts +103 -33
- package/src/scripts/notify-hook.ts +150 -21
- package/src/scripts/tmux-hook-engine.ts +4 -0
|
@@ -10,7 +10,7 @@ import { resolveBridgeStateDir, resolveRuntimeBinaryPath } from '../../runtime/b
|
|
|
10
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
11
|
const __dirname = dirname(__filename);
|
|
12
12
|
import { runProcess } from './process-runner.js';
|
|
13
|
-
import { resolvePaneTarget } from './tmux-injection.js';
|
|
13
|
+
import { resolvePaneTarget, resolveSessionToPane } from './tmux-injection.js';
|
|
14
14
|
import { evaluatePaneInjectionReadiness, sendPaneInput } from './team-tmux-guard.js';
|
|
15
15
|
import {
|
|
16
16
|
buildCapturePaneArgv,
|
|
@@ -70,7 +70,7 @@ async function readBridgeDispatchRequests(stateDir, teamName) {
|
|
|
70
70
|
transport_preference: safeString(metadata.transport_preference).trim() || 'hook_preferred_with_fallback',
|
|
71
71
|
fallback_allowed: typeof metadata.fallback_allowed === 'boolean' ? metadata.fallback_allowed : true,
|
|
72
72
|
status: safeString(record.status).trim() || 'pending',
|
|
73
|
-
attempt_count: 0,
|
|
73
|
+
attempt_count: Number.isFinite(metadata.attempt_count) ? Number(metadata.attempt_count) : 0,
|
|
74
74
|
created_at: safeString(record.created_at).trim() || new Date().toISOString(),
|
|
75
75
|
updated_at:
|
|
76
76
|
safeString(record.delivered_at).trim()
|
|
@@ -182,8 +182,7 @@ function parseTriggerCooldownEntry(entry) {
|
|
|
182
182
|
};
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
async function
|
|
186
|
-
const lockDir = join(teamDirPath, 'dispatch', '.lock');
|
|
185
|
+
async function withLockDirectory(lockDir, timeoutError, fn) {
|
|
187
186
|
const ownerPath = join(lockDir, 'owner');
|
|
188
187
|
const ownerToken = `${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
|
|
189
188
|
const deadline = Date.now() + 5_000;
|
|
@@ -210,7 +209,7 @@ async function withDispatchLock(teamDirPath, fn) {
|
|
|
210
209
|
} catch {
|
|
211
210
|
// best effort
|
|
212
211
|
}
|
|
213
|
-
if (Date.now() > deadline) throw new Error(
|
|
212
|
+
if (Date.now() > deadline) throw new Error(timeoutError);
|
|
214
213
|
await new Promise((resolveDelay) => setTimeout(resolveDelay, 25));
|
|
215
214
|
}
|
|
216
215
|
}
|
|
@@ -229,57 +228,63 @@ async function withDispatchLock(teamDirPath, fn) {
|
|
|
229
228
|
}
|
|
230
229
|
}
|
|
231
230
|
|
|
232
|
-
async function
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
while (true) {
|
|
240
|
-
try {
|
|
241
|
-
await mkdir(lockDir, { recursive: false });
|
|
242
|
-
try {
|
|
243
|
-
await writeFile(ownerPath, ownerToken, 'utf8');
|
|
244
|
-
} catch (error) {
|
|
245
|
-
await rm(lockDir, { recursive: true, force: true });
|
|
246
|
-
throw error;
|
|
247
|
-
}
|
|
248
|
-
break;
|
|
249
|
-
} catch (error) {
|
|
250
|
-
if (error?.code !== 'EEXIST') throw error;
|
|
251
|
-
try {
|
|
252
|
-
const info = await stat(lockDir);
|
|
253
|
-
if (Date.now() - info.mtimeMs > DISPATCH_LOCK_STALE_MS) {
|
|
254
|
-
await rm(lockDir, { recursive: true, force: true });
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
} catch {
|
|
258
|
-
// best effort
|
|
259
|
-
}
|
|
260
|
-
if (Date.now() > deadline) throw new Error(`Timed out acquiring mailbox lock for ${teamDirPath}/${workerName}`);
|
|
261
|
-
await new Promise((resolveDelay) => setTimeout(resolveDelay, 25));
|
|
262
|
-
}
|
|
263
|
-
}
|
|
231
|
+
async function withDispatchLock(teamDirPath, fn) {
|
|
232
|
+
return await withLockDirectory(
|
|
233
|
+
join(teamDirPath, 'dispatch', '.lock'),
|
|
234
|
+
`Timed out acquiring dispatch lock for ${teamDirPath}`,
|
|
235
|
+
fn,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
264
238
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
await rm(lockDir, { recursive: true, force: true });
|
|
272
|
-
}
|
|
273
|
-
} catch {
|
|
274
|
-
// best effort
|
|
275
|
-
}
|
|
276
|
-
}
|
|
239
|
+
async function withMailboxLock(teamDirPath, workerName, fn) {
|
|
240
|
+
return await withLockDirectory(
|
|
241
|
+
join(teamDirPath, 'mailbox', `.lock-${workerName}`),
|
|
242
|
+
`Timed out acquiring mailbox lock for ${teamDirPath}/${workerName}`,
|
|
243
|
+
fn,
|
|
244
|
+
);
|
|
277
245
|
}
|
|
278
246
|
|
|
279
247
|
function resolveLeaderPaneId(config) {
|
|
280
248
|
return safeString(config?.leader_pane_id).trim();
|
|
281
249
|
}
|
|
282
250
|
|
|
251
|
+
function serializeDispatchRequestRecord(request) {
|
|
252
|
+
return {
|
|
253
|
+
request_id: safeString(request.request_id).trim(),
|
|
254
|
+
target: safeString(request.to_worker).trim(),
|
|
255
|
+
status: safeString(request.status).trim() || 'pending',
|
|
256
|
+
created_at: safeString(request.created_at).trim() || new Date().toISOString(),
|
|
257
|
+
notified_at: safeString(request.notified_at).trim() || null,
|
|
258
|
+
delivered_at: safeString(request.delivered_at).trim() || null,
|
|
259
|
+
failed_at: safeString(request.failed_at).trim() || null,
|
|
260
|
+
reason: safeString(request.last_reason).trim() || null,
|
|
261
|
+
metadata: {
|
|
262
|
+
kind: safeString(request.kind).trim() || 'inbox',
|
|
263
|
+
team_name: safeString(request.team_name).trim(),
|
|
264
|
+
worker_index: Number.isFinite(request.worker_index) ? Number(request.worker_index) : undefined,
|
|
265
|
+
pane_id: safeString(request.pane_id).trim() || undefined,
|
|
266
|
+
trigger_message: safeString(request.trigger_message).trim(),
|
|
267
|
+
message_id: safeString(request.message_id).trim() || undefined,
|
|
268
|
+
inbox_correlation_key: safeString(request.inbox_correlation_key).trim() || undefined,
|
|
269
|
+
transport_preference: safeString(request.transport_preference).trim() || 'hook_preferred_with_fallback',
|
|
270
|
+
fallback_allowed: typeof request.fallback_allowed === 'boolean' ? request.fallback_allowed : true,
|
|
271
|
+
attempt_count: Number.isFinite(request.attempt_count) ? Number(request.attempt_count) : 0,
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function writeBridgeDispatchCompat(stateDir, teamName, requests) {
|
|
277
|
+
const compatPath = join(stateDir, 'dispatch.json');
|
|
278
|
+
const current = await readJson(compatPath, { records: [] });
|
|
279
|
+
const existing = Array.isArray(current?.records) ? current.records : [];
|
|
280
|
+
const otherTeams = existing.filter((record) => {
|
|
281
|
+
const metadata = record?.metadata && typeof record.metadata === 'object' ? record.metadata : {};
|
|
282
|
+
return safeString(metadata.team_name).trim() !== teamName;
|
|
283
|
+
});
|
|
284
|
+
const records = [...otherTeams, ...requests.map(serializeDispatchRequestRecord)];
|
|
285
|
+
await writeJsonAtomic(compatPath, { records });
|
|
286
|
+
}
|
|
287
|
+
|
|
283
288
|
|
|
284
289
|
function defaultInjectTarget(request, config) {
|
|
285
290
|
if (request.to_worker === 'leader-fixed') {
|
|
@@ -367,7 +372,15 @@ async function injectDispatchRequest(request, config, cwd, stateDir) {
|
|
|
367
372
|
if (!target) {
|
|
368
373
|
return { ok: false, reason: 'missing_tmux_target' };
|
|
369
374
|
}
|
|
370
|
-
|
|
375
|
+
let resolution;
|
|
376
|
+
if (target.type === 'session') {
|
|
377
|
+
const paneId = await resolveSessionToPane(target.value).catch(() => null);
|
|
378
|
+
resolution = paneId
|
|
379
|
+
? { paneTarget: paneId, reason: 'session_target_resolved' }
|
|
380
|
+
: { paneTarget: null, reason: 'target_not_found' };
|
|
381
|
+
} else {
|
|
382
|
+
resolution = await resolvePaneTarget(target, '', '', '', {});
|
|
383
|
+
}
|
|
371
384
|
if (!resolution.paneTarget) {
|
|
372
385
|
return { ok: false, reason: `target_resolution_failed:${resolution.reason}` };
|
|
373
386
|
}
|
|
@@ -676,9 +689,11 @@ export async function drainPendingTeamDispatch({
|
|
|
676
689
|
request.notified_at = nowIso;
|
|
677
690
|
request.last_reason = result.reason;
|
|
678
691
|
runtimeExec({ command: 'MarkNotified', request_id: request.request_id, channel: 'tmux' }, stateDir);
|
|
679
|
-
if (
|
|
692
|
+
if (request.kind === 'mailbox' && request.message_id) {
|
|
680
693
|
runtimeExec({ command: 'MarkMailboxNotified', message_id: request.message_id }, stateDir);
|
|
681
|
-
|
|
694
|
+
if (usingLegacyRequests) {
|
|
695
|
+
await updateMailboxNotified(stateDir, teamName, request.to_worker, request.message_id).catch(() => {});
|
|
696
|
+
}
|
|
682
697
|
}
|
|
683
698
|
processed += 1;
|
|
684
699
|
mutated = true;
|
|
@@ -725,8 +740,9 @@ export async function drainPendingTeamDispatch({
|
|
|
725
740
|
await writeJsonAtomic(issueCooldownStatePath(teamDirPath), issueCooldownState);
|
|
726
741
|
triggerCooldownState.by_trigger = triggerCooldownByKey;
|
|
727
742
|
await writeJsonAtomic(triggerCooldownStatePath(teamDirPath), triggerCooldownState);
|
|
728
|
-
|
|
729
|
-
|
|
743
|
+
await writeJsonAtomic(requestsPath, requests);
|
|
744
|
+
if (!usingLegacyRequests) {
|
|
745
|
+
await writeBridgeDispatchCompat(stateDir, teamName, requests);
|
|
730
746
|
}
|
|
731
747
|
}
|
|
732
748
|
});
|
|
@@ -510,7 +510,13 @@ async function emitLeaderNudgeDeferredEvent(cwd, teamName, reason, nowIso, { tmu
|
|
|
510
510
|
}
|
|
511
511
|
}
|
|
512
512
|
|
|
513
|
-
export async function maybeNudgeTeamLeader({
|
|
513
|
+
export async function maybeNudgeTeamLeader({
|
|
514
|
+
cwd,
|
|
515
|
+
stateDir,
|
|
516
|
+
logsDir,
|
|
517
|
+
preComputedLeaderStale,
|
|
518
|
+
allowFreshMailboxNudges = true,
|
|
519
|
+
}) {
|
|
514
520
|
const intervalMs = resolveLeaderNudgeIntervalMs();
|
|
515
521
|
const idleCooldownMs = resolveLeaderAllIdleNudgeCooldownMs();
|
|
516
522
|
const fallbackProgressStallThresholdMs = resolveFallbackProgressStallThresholdMs();
|
|
@@ -683,7 +689,8 @@ export async function maybeNudgeTeamLeader({ cwd, stateDir, logsDir, preComputed
|
|
|
683
689
|
const stalledTeamNudge = teamProgressStalled && (dueByTime || !previousStalledTeamNudge);
|
|
684
690
|
const staleFollowupDue = stalePanesNudge && dueByTime;
|
|
685
691
|
|
|
686
|
-
|
|
692
|
+
const hasActionableNewMessage = hasNewMessage && (allowFreshMailboxNudges || leaderStale);
|
|
693
|
+
if (!shouldSendAllIdleNudge && !hasActionableNewMessage && !stalledTeamNudge && !staleFollowupDue) continue;
|
|
687
694
|
|
|
688
695
|
let nudgeReason = '';
|
|
689
696
|
let text = '';
|
|
@@ -717,7 +724,7 @@ export async function maybeNudgeTeamLeader({ cwd, stateDir, logsDir, preComputed
|
|
|
717
724
|
`Team ${teamName}: ${stallPrefix}no progress ${formatDurationMs(stalledForMs)}. `
|
|
718
725
|
+ `${leaderActionGuidance} `
|
|
719
726
|
+ `(p:${pending} ip:${in_progress} b:${blocked}${missingSignals})`;
|
|
720
|
-
} else if (stalePanesNudge &&
|
|
727
|
+
} else if (stalePanesNudge && hasActionableNewMessage) {
|
|
721
728
|
nudgeReason = 'stale_leader_with_messages';
|
|
722
729
|
text =
|
|
723
730
|
`Team ${teamName}: leader stale, ${paneStatus.paneCount} pane(s) active, ${messages.length} msg(s) pending. `
|
|
@@ -727,7 +734,7 @@ export async function maybeNudgeTeamLeader({ cwd, stateDir, logsDir, preComputed
|
|
|
727
734
|
text =
|
|
728
735
|
`Team ${teamName}: leader stale, ${paneStatus.paneCount} worker pane(s) still active. `
|
|
729
736
|
+ leaderActionGuidance;
|
|
730
|
-
} else if (
|
|
737
|
+
} else if (hasActionableNewMessage) {
|
|
731
738
|
nudgeReason = 'new_mailbox_message';
|
|
732
739
|
text = `Team ${teamName}: ${messages.length} msg(s) for leader. ${buildMailboxCheckReminder(teamName)}`;
|
|
733
740
|
} else {
|
|
@@ -763,7 +770,14 @@ export async function maybeNudgeTeamLeader({ cwd, stateDir, logsDir, preComputed
|
|
|
763
770
|
continue;
|
|
764
771
|
}
|
|
765
772
|
|
|
766
|
-
const paneGuard = await evaluatePaneInjectionReadiness(tmuxTarget, {
|
|
773
|
+
const paneGuard = await evaluatePaneInjectionReadiness(tmuxTarget, {
|
|
774
|
+
skipIfScrolling: true,
|
|
775
|
+
// Leader nudges should still queue into a live Codex pane even while the
|
|
776
|
+
// agent is busy; shell/copy-mode guards stay enforced.
|
|
777
|
+
requireRunningAgent: true,
|
|
778
|
+
requireReady: false,
|
|
779
|
+
requireIdle: false,
|
|
780
|
+
});
|
|
767
781
|
if (!paneGuard.ok) {
|
|
768
782
|
const deferredReason = paneGuard.reason === 'pane_running_shell'
|
|
769
783
|
? LEADER_PANE_SHELL_NO_INJECTION_REASON
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
isPaneRunningShell,
|
|
9
9
|
paneHasActiveTask,
|
|
10
10
|
paneLooksReady,
|
|
11
|
-
resolveCodexPane,
|
|
12
11
|
} from '../tmux-hook-engine.js';
|
|
13
12
|
|
|
14
13
|
export function mapPaneInjectionReadinessReason(reason: any): any {
|
|
@@ -33,25 +32,6 @@ export async function evaluatePaneInjectionReadiness(paneTarget: any, {
|
|
|
33
32
|
paneCapture: '',
|
|
34
33
|
};
|
|
35
34
|
}
|
|
36
|
-
|
|
37
|
-
// Canonical bypass: if resolveCodexPane confirms this is a codex pane
|
|
38
|
-
// (via pane_start_command), skip all readiness guards. The pane IS running
|
|
39
|
-
// codex even though tmux may report cmd=sh (shell wrapper).
|
|
40
|
-
try {
|
|
41
|
-
if (resolveCodexPane() === target) {
|
|
42
|
-
return {
|
|
43
|
-
ok: true,
|
|
44
|
-
sent: false,
|
|
45
|
-
reason: 'ok',
|
|
46
|
-
paneTarget: target,
|
|
47
|
-
paneCurrentCommand: 'codex',
|
|
48
|
-
paneCapture: '',
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
} catch {
|
|
52
|
-
// Non-fatal: fall through to normal readiness checks
|
|
53
|
-
}
|
|
54
|
-
|
|
55
35
|
if (skipIfScrolling) {
|
|
56
36
|
try {
|
|
57
37
|
const modeResult = await runProcess('tmux', buildPaneInModeArgv(target), 1000);
|
|
@@ -262,7 +262,7 @@ async function resolveCanonicalLeaderPaneId(_tmuxSession, leaderPaneId) {
|
|
|
262
262
|
const normalizedLeaderPaneId = safeString(leaderPaneId).trim();
|
|
263
263
|
if (normalizedLeaderPaneId) {
|
|
264
264
|
try {
|
|
265
|
-
const resolved = await resolvePaneTarget({ type: 'pane', value: normalizedLeaderPaneId }, '', '', '');
|
|
265
|
+
const resolved = await resolvePaneTarget({ type: 'pane', value: normalizedLeaderPaneId }, '', '', '', {});
|
|
266
266
|
const paneTarget = safeString(resolved?.paneTarget).trim();
|
|
267
267
|
if (paneTarget) return paneTarget;
|
|
268
268
|
} catch {
|
|
@@ -541,8 +541,8 @@ export async function maybeNotifyLeaderWorkerIdle({ cwd, stateDir, logsDir, pars
|
|
|
541
541
|
await rename(tmpPath, prevStatePath);
|
|
542
542
|
} catch { /* best effort */ }
|
|
543
543
|
|
|
544
|
-
//
|
|
545
|
-
if (currentState !== 'idle') return;
|
|
544
|
+
// Fire when a worker leaves active work into an idle-ish terminal state.
|
|
545
|
+
if (currentState !== 'idle' && currentState !== 'done') return;
|
|
546
546
|
if (!statusFresh) return;
|
|
547
547
|
if (prevState === 'idle' || prevState === 'done') return;
|
|
548
548
|
|
|
@@ -608,7 +608,7 @@ export async function maybeNotifyLeaderWorkerIdle({ cwd, stateDir, logsDir, pars
|
|
|
608
608
|
}
|
|
609
609
|
|
|
610
610
|
// Build notification message with context
|
|
611
|
-
const parts = [`[OMX] ${workerName}
|
|
611
|
+
const parts = [`[OMX] ${workerName} ${currentState}`];
|
|
612
612
|
if (prevState && prevState !== 'unknown') parts.push(`(was: ${prevState})`);
|
|
613
613
|
if (currentTaskId) parts.push(`task: ${currentTaskId}`);
|
|
614
614
|
if (currentReason) parts.push(`reason: ${currentReason}`);
|
|
@@ -17,13 +17,13 @@ import {
|
|
|
17
17
|
} from './state-io.js';
|
|
18
18
|
import { runProcess } from './process-runner.js';
|
|
19
19
|
import { logTmuxHookEvent } from './log.js';
|
|
20
|
+
import { resolveManagedCurrentPane, resolveManagedSessionContext, verifyManagedPaneTarget } from './managed-tmux.js';
|
|
20
21
|
import { evaluatePaneInjectionReadiness, mapPaneInjectionReadinessReason, sendPaneInput } from './team-tmux-guard.js';
|
|
21
22
|
import {
|
|
22
23
|
normalizeTmuxHookConfig,
|
|
23
24
|
pickActiveMode,
|
|
24
25
|
evaluateInjectionGuards,
|
|
25
26
|
buildSendKeysArgv,
|
|
26
|
-
resolveCodexPane,
|
|
27
27
|
} from '../tmux-hook-engine.js';
|
|
28
28
|
|
|
29
29
|
function isHudPaneStartCommand(startCommand: any): boolean {
|
|
@@ -96,6 +96,25 @@ async function resolveCanonicalPaneFromPaneTarget(paneTarget: any, expectedCwd:
|
|
|
96
96
|
return finalizeResolvedPane(healedPaneId, 'healed_hud_pane_target', expectedCwd);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
async function resolvePreferredModePane(stateDir: string, allowedModes: string[]): Promise<{ mode: string; state: any; pane: string } | null> {
|
|
100
|
+
const scopedDirs = await getScopedStateDirsForCurrentSession(stateDir).catch(() => [stateDir]);
|
|
101
|
+
const dirs = [...scopedDirs];
|
|
102
|
+
if (!dirs.map((dir) => resolvePath(dir)).includes(resolvePath(stateDir))) {
|
|
103
|
+
dirs.push(stateDir);
|
|
104
|
+
}
|
|
105
|
+
for (const dir of dirs) {
|
|
106
|
+
for (const mode of allowedModes || []) {
|
|
107
|
+
const path = join(dir, `${mode}-state.json`);
|
|
108
|
+
const parsed = await readJsonIfExists(path, null);
|
|
109
|
+
const pane = safeString(parsed?.tmux_pane_id || '').trim();
|
|
110
|
+
if (parsed?.active && pane) {
|
|
111
|
+
return { mode, state: parsed, pane };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
99
118
|
export async function resolveSessionToPane(sessionName: any): Promise<string | null> {
|
|
100
119
|
const result = await runProcess('tmux', ['list-panes', '-t', sessionName, '-F', '#{pane_id}\t#{pane_active}\t#{pane_current_command}\t#{pane_start_command}']);
|
|
101
120
|
const rows = result.stdout
|
|
@@ -128,27 +147,35 @@ export async function resolveSessionToPane(sessionName: any): Promise<string | n
|
|
|
128
147
|
return nonHudRows[0]?.paneId || null;
|
|
129
148
|
}
|
|
130
149
|
|
|
131
|
-
export async function resolvePaneTarget(target: any,
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
150
|
+
export async function resolvePaneTarget(target: any, expectedCwd: any, modePane: any, cwd: string, payload: any): Promise<any> {
|
|
151
|
+
const requiresManagedOwnership = safeString(cwd).trim() !== '' && safeString(payload?.session_id || payload?.['session-id'] || process.env.OMX_SESSION_ID || '').trim() !== '';
|
|
152
|
+
const managedContext = requiresManagedOwnership
|
|
153
|
+
? await resolveManagedSessionContext(cwd, payload, { allowTeamWorker: false })
|
|
154
|
+
: { managed: false, reason: 'not_required', invocationSessionId: '', sessionState: null, expectedTmuxSessionName: '', currentTmuxSessionName: '' };
|
|
155
|
+
if (requiresManagedOwnership && !managedContext.managed) {
|
|
156
|
+
return { paneTarget: null, reason: managedContext.reason || 'unmanaged_session' };
|
|
139
157
|
}
|
|
140
158
|
|
|
141
|
-
|
|
159
|
+
const canonicalModePane = safeString(modePane).trim();
|
|
160
|
+
if (canonicalModePane) {
|
|
142
161
|
try {
|
|
143
|
-
const resolved = await resolveCanonicalPaneFromPaneTarget(
|
|
162
|
+
const resolved = await resolveCanonicalPaneFromPaneTarget(canonicalModePane, expectedCwd);
|
|
144
163
|
if (resolved.paneTarget) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
164
|
+
const ownership = requiresManagedOwnership
|
|
165
|
+
? await verifyManagedPaneTarget(resolved.paneTarget, cwd, payload, { allowTeamWorker: false })
|
|
166
|
+
: { ok: true };
|
|
167
|
+
if (ownership.ok) {
|
|
168
|
+
return {
|
|
169
|
+
...resolved,
|
|
170
|
+
reason: resolved.reason === 'ok' ? 'fallback_mode_state_pane' : resolved.reason,
|
|
171
|
+
source: 'mode_state',
|
|
172
|
+
healTarget: true,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return { paneTarget: null, reason: ownership.reason || 'pane_not_managed_session' };
|
|
149
176
|
}
|
|
150
177
|
} catch {
|
|
151
|
-
// Fall through to config
|
|
178
|
+
// Fall through to explicit config target
|
|
152
179
|
}
|
|
153
180
|
}
|
|
154
181
|
|
|
@@ -157,20 +184,52 @@ export async function resolvePaneTarget(target: any, fallbackPane: any, expected
|
|
|
157
184
|
if (target.type === 'pane') {
|
|
158
185
|
try {
|
|
159
186
|
const resolved = await resolveCanonicalPaneFromPaneTarget(target.value, expectedCwd);
|
|
160
|
-
if (resolved.paneTarget)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
187
|
+
if (resolved.paneTarget) {
|
|
188
|
+
const ownership = requiresManagedOwnership
|
|
189
|
+
? await verifyManagedPaneTarget(resolved.paneTarget, cwd, payload, { allowTeamWorker: false })
|
|
190
|
+
: { ok: true };
|
|
191
|
+
if (ownership.ok) {
|
|
192
|
+
return {
|
|
193
|
+
...resolved,
|
|
194
|
+
reason: resolved.reason === 'ok' ? 'explicit_pane_target' : resolved.reason,
|
|
195
|
+
source: 'explicit_target',
|
|
196
|
+
healTarget: true,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return { paneTarget: null, reason: ownership.reason || 'pane_not_managed_session' };
|
|
200
|
+
}
|
|
168
201
|
} catch {
|
|
169
202
|
// Fall through
|
|
170
203
|
}
|
|
204
|
+
return { paneTarget: null, reason: 'target_not_found' };
|
|
171
205
|
}
|
|
172
206
|
|
|
173
|
-
|
|
207
|
+
try {
|
|
208
|
+
if (!requiresManagedOwnership) return { paneTarget: null, reason: 'target_session_requires_managed_context' };
|
|
209
|
+
const explicitSessionTarget = safeString(target.value).trim();
|
|
210
|
+
const expectedSessionTarget = safeString(managedContext.expectedTmuxSessionName).trim();
|
|
211
|
+
const sessionIdTarget = safeString(managedContext.invocationSessionId).trim();
|
|
212
|
+
const stateSessionTarget = safeString(managedContext.sessionState?.session_id).trim();
|
|
213
|
+
const allowedSessionTargets = new Set([expectedSessionTarget, sessionIdTarget, stateSessionTarget].filter(Boolean));
|
|
214
|
+
if (!allowedSessionTargets.has(explicitSessionTarget)) {
|
|
215
|
+
return { paneTarget: null, reason: 'target_session_not_managed' };
|
|
216
|
+
}
|
|
217
|
+
const paneId = await resolveSessionToPane(expectedSessionTarget);
|
|
218
|
+
if (!paneId) return { paneTarget: null, reason: 'target_not_found' };
|
|
219
|
+
const resolved = await finalizeResolvedPane(paneId, 'managed_session_target', expectedCwd);
|
|
220
|
+
if (!resolved.paneTarget) return resolved;
|
|
221
|
+
const ownership = await verifyManagedPaneTarget(resolved.paneTarget, cwd, payload, { allowTeamWorker: false });
|
|
222
|
+
if (!ownership.ok) {
|
|
223
|
+
return { paneTarget: null, reason: ownership.reason || 'pane_not_managed_session' };
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
...resolved,
|
|
227
|
+
source: 'explicit_target',
|
|
228
|
+
healTarget: true,
|
|
229
|
+
};
|
|
230
|
+
} catch {
|
|
231
|
+
return { paneTarget: null, reason: 'target_not_found' };
|
|
232
|
+
}
|
|
174
233
|
}
|
|
175
234
|
|
|
176
235
|
export async function handleTmuxInjection({
|
|
@@ -202,7 +261,6 @@ export async function handleTmuxInjection({
|
|
|
202
261
|
const activeModes: string[] = [];
|
|
203
262
|
const activeModeStates: Record<string, any> = {};
|
|
204
263
|
const scannedStateDirs = new Set<string>();
|
|
205
|
-
const payloadSessionId = safeString(payload.session_id || payload['session-id'] || '');
|
|
206
264
|
const scanActiveModeStateDirs = async (dirs: string[], preserveExisting = false) => {
|
|
207
265
|
for (const scopedDir of dirs) {
|
|
208
266
|
const resolvedScopedDir = resolvePath(scopedDir);
|
|
@@ -225,7 +283,7 @@ export async function handleTmuxInjection({
|
|
|
225
283
|
}
|
|
226
284
|
};
|
|
227
285
|
try {
|
|
228
|
-
const scopedDirs = await getScopedStateDirsForCurrentSession(stateDir
|
|
286
|
+
const scopedDirs = await getScopedStateDirsForCurrentSession(stateDir);
|
|
229
287
|
await scanActiveModeStateDirs(scopedDirs);
|
|
230
288
|
|
|
231
289
|
if (!pickActiveMode(activeModes, config.allowed_modes) && !scannedStateDirs.has(resolvePath(stateDir))) {
|
|
@@ -235,9 +293,10 @@ export async function handleTmuxInjection({
|
|
|
235
293
|
// Non-fatal
|
|
236
294
|
}
|
|
237
295
|
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
const
|
|
296
|
+
const preferredModePane = await resolvePreferredModePane(stateDir, config.allowed_modes).catch(() => null);
|
|
297
|
+
const mode = preferredModePane?.mode || pickActiveMode(activeModes, config.allowed_modes);
|
|
298
|
+
const modeState = preferredModePane?.state || (mode ? (activeModeStates[mode] || {}) : {});
|
|
299
|
+
const modePane = preferredModePane?.pane || safeString(modeState.tmux_pane_id || '');
|
|
241
300
|
const preGuard = evaluateInjectionGuards({
|
|
242
301
|
config,
|
|
243
302
|
mode,
|
|
@@ -280,8 +339,19 @@ export async function handleTmuxInjection({
|
|
|
280
339
|
turnId,
|
|
281
340
|
timestamp: nowIso,
|
|
282
341
|
}), sourceText);
|
|
283
|
-
const
|
|
284
|
-
|
|
342
|
+
const preferredPaneTarget = modePane || await resolveManagedCurrentPane(cwd, payload, { allowTeamWorker: false });
|
|
343
|
+
let resolution = preferredModePane
|
|
344
|
+
? await resolveCanonicalPaneFromPaneTarget(preferredModePane.pane, cwd).then((resolved) => (
|
|
345
|
+
resolved.paneTarget
|
|
346
|
+
? { ...resolved, reason: 'fallback_mode_state_pane', source: 'mode_state', healTarget: true }
|
|
347
|
+
: resolved
|
|
348
|
+
))
|
|
349
|
+
: preferredPaneTarget
|
|
350
|
+
? await resolvePaneTarget({ type: 'pane', value: preferredPaneTarget }, cwd, '', cwd, payload)
|
|
351
|
+
: await resolvePaneTarget(config.target, cwd, modePane, cwd, payload);
|
|
352
|
+
if (!resolution.paneTarget && preferredPaneTarget) {
|
|
353
|
+
resolution = await resolvePaneTarget(config.target, cwd, modePane, cwd, payload);
|
|
354
|
+
}
|
|
285
355
|
if (!resolution.paneTarget) {
|
|
286
356
|
state.last_reason = resolution.reason;
|
|
287
357
|
state.last_event_at = nowIso;
|
|
@@ -319,7 +389,7 @@ export async function handleTmuxInjection({
|
|
|
319
389
|
}
|
|
320
390
|
|
|
321
391
|
// 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)) {
|
|
392
|
+
if (resolution.healTarget && config.target && (config.target.type !== 'pane' || safeString(config.target.value).trim() !== paneTarget)) {
|
|
323
393
|
try {
|
|
324
394
|
const healed = {
|
|
325
395
|
...(rawConfig && typeof rawConfig === 'object' ? rawConfig : {}),
|