oh-my-codex 0.10.2 → 0.10.4
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 +2 -2
- package/Cargo.toml +1 -1
- package/README.de.md +4 -4
- package/README.es.md +4 -4
- package/README.fr.md +4 -4
- package/README.it.md +4 -4
- package/README.ja.md +4 -4
- package/README.ko.md +4 -4
- package/README.md +13 -7
- package/README.pt.md +4 -4
- package/README.ru.md +4 -4
- package/README.tr.md +4 -4
- package/README.vi.md +4 -4
- package/README.zh-TW.md +4 -4
- package/README.zh.md +4 -4
- package/dist/agents/__tests__/native-config.test.js +37 -33
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/native-config.d.ts +18 -6
- package/dist/agents/native-config.d.ts.map +1 -1
- package/dist/agents/native-config.js +109 -92
- package/dist/agents/native-config.js.map +1 -1
- package/dist/autoresearch/__tests__/contracts.test.js +37 -1
- package/dist/autoresearch/__tests__/contracts.test.js.map +1 -1
- package/dist/autoresearch/__tests__/runtime-parity-extra.test.js +10 -10
- package/dist/autoresearch/__tests__/runtime-parity-extra.test.js.map +1 -1
- package/dist/autoresearch/__tests__/runtime.test.js +2 -2
- package/dist/autoresearch/__tests__/runtime.test.js.map +1 -1
- package/dist/autoresearch/contracts.d.ts.map +1 -1
- package/dist/autoresearch/contracts.js +17 -10
- package/dist/autoresearch/contracts.js.map +1 -1
- package/dist/autoresearch/runtime.d.ts.map +1 -1
- package/dist/autoresearch/runtime.js +84 -96
- package/dist/autoresearch/runtime.js.map +1 -1
- package/dist/cli/__tests__/agents-init.test.js +2 -0
- package/dist/cli/__tests__/agents-init.test.js.map +1 -1
- package/dist/cli/__tests__/agents.test.d.ts +2 -0
- package/dist/cli/__tests__/agents.test.d.ts.map +1 -0
- package/dist/cli/__tests__/agents.test.js +114 -0
- package/dist/cli/__tests__/agents.test.js.map +1 -0
- package/dist/cli/__tests__/autoresearch-guided.test.js +156 -1
- package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch.test.js +483 -25
- package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
- package/dist/cli/__tests__/cleanup.test.d.ts +2 -0
- package/dist/cli/__tests__/cleanup.test.d.ts.map +1 -0
- package/dist/cli/__tests__/cleanup.test.js +213 -0
- package/dist/cli/__tests__/cleanup.test.js.map +1 -0
- package/dist/cli/__tests__/error-handling-warnings.test.js +1 -1
- package/dist/cli/__tests__/error-handling-warnings.test.js.map +1 -1
- package/dist/cli/__tests__/explore.test.js +3 -3
- package/dist/cli/__tests__/explore.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +530 -401
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/native-assets.test.js +72 -9
- package/dist/cli/__tests__/native-assets.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-deslop-contract.test.d.ts +2 -0
- package/dist/cli/__tests__/ralph-deslop-contract.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ralph-deslop-contract.test.js +28 -0
- package/dist/cli/__tests__/ralph-deslop-contract.test.js.map +1 -0
- package/dist/cli/__tests__/ralph-prd-deep-interview.test.js +4 -0
- package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
- package/dist/cli/__tests__/ralphthon.test.d.ts +2 -0
- package/dist/cli/__tests__/ralphthon.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ralphthon.test.js +28 -0
- package/dist/cli/__tests__/ralphthon.test.js.map +1 -0
- package/dist/cli/__tests__/setup-agents-overwrite.test.js +36 -1
- package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/setup-prompts-overwrite.test.js +35 -5
- package/dist/cli/__tests__/setup-prompts-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +2 -2
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +131 -161
- package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
- package/dist/cli/__tests__/setup-skills-overwrite.test.js +10 -10
- package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-cli.test.js +28 -2
- package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +1 -112
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/uninstall.test.js +7 -20
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/agents-init.d.ts.map +1 -1
- package/dist/cli/agents-init.js +99 -95
- package/dist/cli/agents-init.js.map +1 -1
- package/dist/cli/agents.d.ts +14 -0
- package/dist/cli/agents.d.ts.map +1 -0
- package/dist/cli/agents.js +261 -0
- package/dist/cli/agents.js.map +1 -0
- package/dist/cli/autoresearch-guided.d.ts +8 -0
- package/dist/cli/autoresearch-guided.d.ts.map +1 -1
- package/dist/cli/autoresearch-guided.js +104 -37
- package/dist/cli/autoresearch-guided.js.map +1 -1
- package/dist/cli/autoresearch-intake.d.ts +62 -0
- package/dist/cli/autoresearch-intake.d.ts.map +1 -0
- package/dist/cli/autoresearch-intake.js +336 -0
- package/dist/cli/autoresearch-intake.js.map +1 -0
- package/dist/cli/autoresearch.d.ts +4 -1
- package/dist/cli/autoresearch.d.ts.map +1 -1
- package/dist/cli/autoresearch.js +181 -32
- package/dist/cli/autoresearch.js.map +1 -1
- package/dist/cli/cleanup.d.ts +52 -0
- package/dist/cli/cleanup.d.ts.map +1 -0
- package/dist/cli/cleanup.js +302 -0
- package/dist/cli/cleanup.js.map +1 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +9 -37
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/explore.d.ts.map +1 -1
- package/dist/cli/explore.js +5 -4
- package/dist/cli/explore.js.map +1 -1
- package/dist/cli/index.d.ts +5 -7
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +623 -451
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/native-assets.d.ts +15 -1
- package/dist/cli/native-assets.d.ts.map +1 -1
- package/dist/cli/native-assets.js +134 -32
- package/dist/cli/native-assets.js.map +1 -1
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +38 -1
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/ralphthon.d.ts +14 -0
- package/dist/cli/ralphthon.d.ts.map +1 -0
- package/dist/cli/ralphthon.js +234 -0
- package/dist/cli/ralphthon.js.map +1 -0
- package/dist/cli/setup.d.ts +1 -4
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +111 -76
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/sparkshell.d.ts +3 -1
- package/dist/cli/sparkshell.d.ts.map +1 -1
- package/dist/cli/sparkshell.js +35 -16
- package/dist/cli/sparkshell.js.map +1 -1
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +1 -0
- package/dist/cli/team.js.map +1 -1
- package/dist/cli/uninstall.d.ts +1 -1
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +82 -64
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +10 -10
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +15 -0
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/generator.d.ts +0 -1
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +53 -42
- package/dist/config/generator.js.map +1 -1
- package/dist/hooks/__tests__/agents-overlay.test.js +295 -230
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/anti-slop-workflow.test.js +3 -0
- package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js +49 -24
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher-ralphthon.test.d.ts +2 -0
- package/dist/hooks/__tests__/notify-fallback-watcher-ralphthon.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/notify-fallback-watcher-ralphthon.test.js +193 -0
- package/dist/hooks/__tests__/notify-fallback-watcher-ralphthon.test.js.map +1 -0
- package/dist/hooks/agents-overlay.d.ts +1 -1
- package/dist/hooks/agents-overlay.d.ts.map +1 -1
- package/dist/hooks/agents-overlay.js +109 -106
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hud/constants.d.ts +1 -1
- package/dist/hud/constants.js +1 -1
- package/dist/modes/base.d.ts +1 -1
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +1 -1
- package/dist/modes/base.js.map +1 -1
- package/dist/notifications/__tests__/formatter.test.js +36 -2
- package/dist/notifications/__tests__/formatter.test.js.map +1 -1
- package/dist/notifications/formatter.d.ts +3 -2
- package/dist/notifications/formatter.d.ts.map +1 -1
- package/dist/notifications/formatter.js +33 -9
- package/dist/notifications/formatter.js.map +1 -1
- package/dist/ralphthon/__tests__/bootstrap.test.d.ts +2 -0
- package/dist/ralphthon/__tests__/bootstrap.test.d.ts.map +1 -0
- package/dist/ralphthon/__tests__/bootstrap.test.js +23 -0
- package/dist/ralphthon/__tests__/bootstrap.test.js.map +1 -0
- package/dist/ralphthon/__tests__/orchestrator.test.d.ts +2 -0
- package/dist/ralphthon/__tests__/orchestrator.test.d.ts.map +1 -0
- package/dist/ralphthon/__tests__/orchestrator.test.js +309 -0
- package/dist/ralphthon/__tests__/orchestrator.test.js.map +1 -0
- package/dist/ralphthon/__tests__/prd.test.d.ts +2 -0
- package/dist/ralphthon/__tests__/prd.test.d.ts.map +1 -0
- package/dist/ralphthon/__tests__/prd.test.js +133 -0
- package/dist/ralphthon/__tests__/prd.test.js.map +1 -0
- package/dist/ralphthon/bootstrap.d.ts +3 -0
- package/dist/ralphthon/bootstrap.d.ts.map +1 -0
- package/dist/ralphthon/bootstrap.js +84 -0
- package/dist/ralphthon/bootstrap.js.map +1 -0
- package/dist/ralphthon/orchestrator.d.ts +50 -0
- package/dist/ralphthon/orchestrator.d.ts.map +1 -0
- package/dist/ralphthon/orchestrator.js +362 -0
- package/dist/ralphthon/orchestrator.js.map +1 -0
- package/dist/ralphthon/prd.d.ts +191 -0
- package/dist/ralphthon/prd.d.ts.map +1 -0
- package/dist/ralphthon/prd.js +359 -0
- package/dist/ralphthon/prd.js.map +1 -0
- package/dist/ralphthon/runtime.d.ts +31 -0
- package/dist/ralphthon/runtime.d.ts.map +1 -0
- package/dist/ralphthon/runtime.js +108 -0
- package/dist/ralphthon/runtime.js.map +1 -0
- package/dist/ralphthon/tmux.d.ts +3 -0
- package/dist/ralphthon/tmux.d.ts.map +1 -0
- package/dist/ralphthon/tmux.js +39 -0
- package/dist/ralphthon/tmux.js.map +1 -0
- package/dist/subagents/__tests__/tracker.test.d.ts +2 -0
- package/dist/subagents/__tests__/tracker.test.d.ts.map +1 -0
- package/dist/subagents/__tests__/tracker.test.js +47 -0
- package/dist/subagents/__tests__/tracker.test.js.map +1 -0
- package/dist/subagents/tracker.d.ts +52 -0
- package/dist/subagents/tracker.d.ts.map +1 -0
- package/dist/subagents/tracker.js +175 -0
- package/dist/subagents/tracker.js.map +1 -0
- package/dist/team/__tests__/runtime.test.js +2 -2
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +34 -0
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +189 -163
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/__tests__/worktree.test.js +1 -1
- package/dist/team/__tests__/worktree.test.js.map +1 -1
- package/dist/team/tmux-session.d.ts +4 -4
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +48 -15
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts +1 -1
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +58 -63
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/team/worktree.js +1 -1
- package/dist/team/worktree.js.map +1 -1
- package/dist/utils/__tests__/agents-md.test.d.ts +2 -0
- package/dist/utils/__tests__/agents-md.test.d.ts.map +1 -0
- package/dist/utils/__tests__/agents-md.test.js +32 -0
- package/dist/utils/__tests__/agents-md.test.js.map +1 -0
- package/dist/utils/__tests__/agents-model-table.test.d.ts +2 -0
- package/dist/utils/__tests__/agents-model-table.test.d.ts.map +1 -0
- package/dist/utils/__tests__/agents-model-table.test.js +84 -0
- package/dist/utils/__tests__/agents-model-table.test.js.map +1 -0
- package/dist/utils/__tests__/paths.test.js +78 -83
- package/dist/utils/__tests__/paths.test.js.map +1 -1
- package/dist/utils/agents-md.d.ts.map +1 -1
- package/dist/utils/agents-md.js +10 -0
- package/dist/utils/agents-md.js.map +1 -1
- package/dist/utils/agents-model-table.d.ts +16 -0
- package/dist/utils/agents-model-table.d.ts.map +1 -0
- package/dist/utils/agents-model-table.js +83 -0
- package/dist/utils/agents-model-table.js.map +1 -0
- package/dist/utils/paths.d.ts +6 -6
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +31 -31
- package/dist/utils/paths.js.map +1 -1
- package/dist/verification/__tests__/explore-harness-release-workflow.test.js +21 -3
- package/dist/verification/__tests__/explore-harness-release-workflow.test.js.map +1 -1
- package/dist/verification/__tests__/native-release-manifest.test.d.ts +2 -0
- package/dist/verification/__tests__/native-release-manifest.test.d.ts.map +1 -0
- package/dist/verification/__tests__/native-release-manifest.test.js +80 -0
- package/dist/verification/__tests__/native-release-manifest.test.js.map +1 -0
- package/package.json +1 -1
- package/prompts/executor.md +15 -0
- package/scripts/__tests__/smoke-packed-install.test.mjs +137 -8
- package/scripts/eval-adaptive-sort-optimization.py +24 -0
- package/scripts/eval-in-action-cat-shellout-demo.js +31 -0
- package/scripts/eval-ml-kaggle-model-optimization.py +29 -0
- package/scripts/eval-noisy-bayesopt-highdim.py +44 -0
- package/scripts/eval-noisy-latent-subspace-discovery.py +44 -0
- package/scripts/generate-native-release-manifest.mjs +14 -3
- package/scripts/notify-fallback-watcher.js +308 -6
- package/scripts/notify-hook.js +20 -0
- package/scripts/run-autoresearch-showcase.sh +75 -0
- package/scripts/smoke-packed-install.mjs +142 -10
- package/skills/ai-slop-cleaner/SKILL.md +7 -0
- package/skills/deep-interview/SKILL.md +30 -1
- package/skills/omx-setup/SKILL.md +2 -2
- package/skills/ralph/SKILL.md +15 -0
- package/skills/skill/SKILL.md +32 -32
- package/skills/team/SKILL.md +6 -0
- package/skills/worker/SKILL.md +2 -2
- package/templates/AGENTS.md +97 -16
- package/dist/cli/__tests__/runtime-native-resolution.test.d.ts +0 -2
- package/dist/cli/__tests__/runtime-native-resolution.test.d.ts.map +0 -1
- package/dist/cli/__tests__/runtime-native-resolution.test.js.map +0 -1
- package/dist/cli/__tests__/runtime-native.test.d.ts +0 -2
- package/dist/cli/__tests__/runtime-native.test.d.ts.map +0 -1
- package/dist/cli/__tests__/runtime-native.test.js.map +0 -1
- package/dist/cli/runtime-native.d.ts +0 -23
- package/dist/cli/runtime-native.d.ts.map +0 -1
- package/dist/cli/runtime-native.js +0 -86
- package/dist/cli/runtime-native.js.map +0 -1
- package/dist/mcp/__tests__/runtime-run-native-cutover.test.d.ts +0 -2
- package/dist/mcp/__tests__/runtime-run-native-cutover.test.d.ts.map +0 -1
- package/dist/mcp/__tests__/runtime-run-native-cutover.test.js.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -2,41 +2,45 @@
|
|
|
2
2
|
* oh-my-codex CLI
|
|
3
3
|
* Multi-agent orchestration for OpenAI Codex CLI
|
|
4
4
|
*/
|
|
5
|
-
import { execFileSync, spawn } from
|
|
6
|
-
import { basename, dirname, join } from
|
|
7
|
-
import { existsSync, readFileSync } from
|
|
8
|
-
import { constants as osConstants } from
|
|
9
|
-
import { setup, SETUP_SCOPES
|
|
10
|
-
import { uninstall } from
|
|
11
|
-
import { version } from
|
|
12
|
-
import { tmuxHookCommand } from
|
|
13
|
-
import { hooksCommand } from
|
|
14
|
-
import { hudCommand } from
|
|
15
|
-
import { teamCommand } from
|
|
16
|
-
import { ralphCommand } from
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
5
|
+
import { execFileSync, spawn } from "child_process";
|
|
6
|
+
import { basename, dirname, join } from "path";
|
|
7
|
+
import { existsSync, readFileSync } from "fs";
|
|
8
|
+
import { constants as osConstants } from "os";
|
|
9
|
+
import { setup, SETUP_SCOPES } from "./setup.js";
|
|
10
|
+
import { uninstall } from "./uninstall.js";
|
|
11
|
+
import { version } from "./version.js";
|
|
12
|
+
import { tmuxHookCommand } from "./tmux-hook.js";
|
|
13
|
+
import { hooksCommand } from "./hooks.js";
|
|
14
|
+
import { hudCommand } from "../hud/index.js";
|
|
15
|
+
import { teamCommand } from "./team.js";
|
|
16
|
+
import { ralphCommand } from "./ralph.js";
|
|
17
|
+
import { ralphthonCommand } from "./ralphthon.js";
|
|
18
|
+
import { askCommand } from "./ask.js";
|
|
19
|
+
import { cleanupCommand } from "./cleanup.js";
|
|
20
|
+
import { exploreCommand } from "./explore.js";
|
|
21
|
+
import { sparkshellCommand } from "./sparkshell.js";
|
|
22
|
+
import { agentsInitCommand } from "./agents-init.js";
|
|
23
|
+
import { agentsCommand } from "./agents.js";
|
|
24
|
+
import { sessionCommand } from "./session-search.js";
|
|
25
|
+
import { autoresearchCommand } from "./autoresearch.js";
|
|
26
|
+
import { MADMAX_FLAG, CODEX_BYPASS_FLAG, HIGH_REASONING_FLAG, XHIGH_REASONING_FLAG, SPARK_FLAG, MADMAX_SPARK_FLAG, CONFIG_FLAG, LONG_CONFIG_FLAG, } from "./constants.js";
|
|
27
|
+
import { getBaseStateDir, getStateDir, listModeStateFilesWithScopePreference, } from "../mcp/state-paths.js";
|
|
28
|
+
import { maybeCheckAndPromptUpdate } from "./update.js";
|
|
29
|
+
import { maybePromptGithubStar } from "./star-prompt.js";
|
|
30
|
+
import { generateOverlay, removeSessionModelInstructionsFile, resolveSessionOrchestrationMode, sessionModelInstructionsPath, writeSessionModelInstructionsFile, } from "../hooks/agents-overlay.js";
|
|
31
|
+
import { readModeState, updateModeState } from "../modes/base.js";
|
|
32
|
+
import { readSessionState, writeSessionStart, writeSessionEnd, resetSessionMetrics, } from "../hooks/session.js";
|
|
33
|
+
import { buildClientAttachedReconcileHookName, buildReconcileHudResizeArgs, buildRegisterClientAttachedReconcileArgs, buildRegisterResizeHookArgs, buildResizeHookName, buildResizeHookTarget, buildScheduleDelayedHudResizeArgs, buildUnregisterClientAttachedReconcileArgs, buildUnregisterResizeHookArgs, enableMouseScrolling, isNativeWindows, isTmuxAvailable, } from "../team/tmux-session.js";
|
|
34
|
+
import { getPackageRoot } from "../utils/package.js";
|
|
35
|
+
import { codexConfigPath } from "../utils/paths.js";
|
|
36
|
+
import { repairConfigIfNeeded } from "../config/generator.js";
|
|
37
|
+
import { HUD_TMUX_HEIGHT_LINES } from "../hud/constants.js";
|
|
38
|
+
import { classifySpawnError, spawnPlatformCommandSync, } from "../utils/platform-command.js";
|
|
39
|
+
import { buildHookEvent } from "../hooks/extensibility/events.js";
|
|
40
|
+
import { dispatchHookEvent } from "../hooks/extensibility/dispatcher.js";
|
|
41
|
+
import { collectInheritableTeamWorkerArgs as collectInheritableTeamWorkerArgsShared, resolveTeamWorkerLaunchArgs, resolveTeamLowComplexityDefaultModel, } from "../team/model-contract.js";
|
|
42
|
+
import { parseWorktreeMode, planWorktreeTarget, ensureWorktree, } from "../team/worktree.js";
|
|
43
|
+
import { OMX_NOTIFY_TEMP_CONTRACT_ENV, parseNotifyTempContractFromArgs, serializeNotifyTempContract, } from "../notifications/temp-contract.js";
|
|
40
44
|
const HELP = `
|
|
41
45
|
oh-my-codex (omx) - Multi-agent orchestration for Codex CLI
|
|
42
46
|
|
|
@@ -46,6 +50,7 @@ Usage:
|
|
|
46
50
|
omx setup Install skills, prompts, MCP servers, and scope-specific AGENTS.md
|
|
47
51
|
omx uninstall Remove OMX configuration and clean up installed artifacts
|
|
48
52
|
omx doctor Check installation health
|
|
53
|
+
omx cleanup Kill orphaned OMX MCP server processes and remove stale OMX /tmp directories
|
|
49
54
|
omx doctor --team Check team/swarm runtime health diagnostics
|
|
50
55
|
omx ask Ask local provider CLI (claude|gemini) and write artifact output
|
|
51
56
|
omx resume Resume a previous interactive Codex session
|
|
@@ -53,10 +58,12 @@ Usage:
|
|
|
53
58
|
omx session Search prior local session transcripts and history artifacts
|
|
54
59
|
omx agents-init [path]
|
|
55
60
|
Bootstrap lightweight AGENTS.md files for a repo/subtree
|
|
61
|
+
omx agents Manage Codex native agent TOML files
|
|
56
62
|
omx deepinit [path]
|
|
57
63
|
Alias for agents-init (lightweight AGENTS bootstrap only)
|
|
58
64
|
omx team Spawn parallel worker panes in tmux and bootstrap inbox/task state
|
|
59
65
|
omx ralph Launch Codex with ralph persistence mode active
|
|
66
|
+
omx ralphthon Launch Codex with autonomous hackathon lifecycle mode active
|
|
60
67
|
omx autoresearch Launch thin-supervisor autoresearch with keep/discard/reset parity
|
|
61
68
|
omx version Show version information
|
|
62
69
|
omx tmux-hook Manage tmux prompt injection workaround (init|status|validate|test)
|
|
@@ -100,39 +107,55 @@ Options:
|
|
|
100
107
|
user | project
|
|
101
108
|
--skill-target
|
|
102
109
|
User-scope skills target for "omx setup" only:
|
|
103
|
-
codex-home
|
|
110
|
+
codex-home
|
|
104
111
|
`;
|
|
105
|
-
const REASONING_KEY =
|
|
106
|
-
const MODEL_INSTRUCTIONS_FILE_KEY =
|
|
107
|
-
const TEAM_WORKER_LAUNCH_ARGS_ENV =
|
|
108
|
-
const TEAM_INHERIT_LEADER_FLAGS_ENV =
|
|
109
|
-
const OMX_BYPASS_DEFAULT_SYSTEM_PROMPT_ENV =
|
|
110
|
-
const OMX_MODEL_INSTRUCTIONS_FILE_ENV =
|
|
111
|
-
const
|
|
112
|
-
const
|
|
112
|
+
const REASONING_KEY = "model_reasoning_effort";
|
|
113
|
+
const MODEL_INSTRUCTIONS_FILE_KEY = "model_instructions_file";
|
|
114
|
+
const TEAM_WORKER_LAUNCH_ARGS_ENV = "OMX_TEAM_WORKER_LAUNCH_ARGS";
|
|
115
|
+
const TEAM_INHERIT_LEADER_FLAGS_ENV = "OMX_TEAM_INHERIT_LEADER_FLAGS";
|
|
116
|
+
const OMX_BYPASS_DEFAULT_SYSTEM_PROMPT_ENV = "OMX_BYPASS_DEFAULT_SYSTEM_PROMPT";
|
|
117
|
+
const OMX_MODEL_INSTRUCTIONS_FILE_ENV = "OMX_MODEL_INSTRUCTIONS_FILE";
|
|
118
|
+
const OMX_RALPH_APPEND_INSTRUCTIONS_FILE_ENV = "OMX_RALPH_APPEND_INSTRUCTIONS_FILE";
|
|
119
|
+
const OMX_AUTORESEARCH_APPEND_INSTRUCTIONS_FILE_ENV = "OMX_AUTORESEARCH_APPEND_INSTRUCTIONS_FILE";
|
|
120
|
+
const OMX_RALPHTHON_APPEND_INSTRUCTIONS_FILE_ENV = "OMX_RALPHTHON_APPEND_INSTRUCTIONS_FILE";
|
|
121
|
+
const REASONING_MODES = ["low", "medium", "high", "xhigh"];
|
|
113
122
|
const REASONING_MODE_SET = new Set(REASONING_MODES);
|
|
114
|
-
const REASONING_USAGE =
|
|
123
|
+
const REASONING_USAGE = "Usage: omx reasoning <low|medium|high|xhigh>";
|
|
115
124
|
const ALLOWED_SHELLS = new Set([
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
125
|
+
"/bin/sh",
|
|
126
|
+
"/bin/bash",
|
|
127
|
+
"/bin/zsh",
|
|
128
|
+
"/bin/dash",
|
|
129
|
+
"/bin/fish",
|
|
130
|
+
"/usr/bin/sh",
|
|
131
|
+
"/usr/bin/bash",
|
|
132
|
+
"/usr/bin/zsh",
|
|
133
|
+
"/usr/bin/dash",
|
|
134
|
+
"/usr/bin/fish",
|
|
135
|
+
"/usr/local/bin/bash",
|
|
136
|
+
"/usr/local/bin/zsh",
|
|
137
|
+
"/usr/local/bin/fish",
|
|
119
138
|
]);
|
|
120
139
|
const WINDOWS_DETACHED_BOOTSTRAP_DELAY_MS = 2500;
|
|
121
|
-
const CODEX_VERSION_FLAGS = new Set([
|
|
140
|
+
const CODEX_VERSION_FLAGS = new Set(["--version", "-V"]);
|
|
122
141
|
const NESTED_HELP_COMMANDS = new Set([
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
142
|
+
"ask",
|
|
143
|
+
"cleanup",
|
|
144
|
+
"autoresearch",
|
|
145
|
+
"agents",
|
|
146
|
+
"agents-init",
|
|
147
|
+
"deepinit",
|
|
148
|
+
"exec",
|
|
149
|
+
"hooks",
|
|
150
|
+
"hud",
|
|
151
|
+
"ralph",
|
|
152
|
+
"ralphthon",
|
|
153
|
+
"ralphthon",
|
|
154
|
+
"resume",
|
|
155
|
+
"session",
|
|
156
|
+
"sparkshell",
|
|
157
|
+
"team",
|
|
158
|
+
"tmux-hook",
|
|
136
159
|
]);
|
|
137
160
|
/**
|
|
138
161
|
* Legacy scope values that may appear in persisted setup-scope.json files.
|
|
@@ -140,19 +163,19 @@ const NESTED_HELP_COMMANDS = new Set([
|
|
|
140
163
|
* migrated to the current 'project' scope on read.
|
|
141
164
|
*/
|
|
142
165
|
const LEGACY_SCOPE_MIGRATION_SYNC = {
|
|
143
|
-
|
|
166
|
+
"project-local": "project",
|
|
144
167
|
};
|
|
145
168
|
export function readPersistedSetupScope(cwd) {
|
|
146
169
|
return readPersistedSetupPreferences(cwd)?.scope;
|
|
147
170
|
}
|
|
148
171
|
export function readPersistedSetupPreferences(cwd) {
|
|
149
|
-
const scopePath = join(cwd,
|
|
172
|
+
const scopePath = join(cwd, ".omx", "setup-scope.json");
|
|
150
173
|
if (!existsSync(scopePath))
|
|
151
174
|
return undefined;
|
|
152
175
|
try {
|
|
153
|
-
const parsed = JSON.parse(readFileSync(scopePath,
|
|
176
|
+
const parsed = JSON.parse(readFileSync(scopePath, "utf-8"));
|
|
154
177
|
const persisted = {};
|
|
155
|
-
if (typeof parsed.scope ===
|
|
178
|
+
if (typeof parsed.scope === "string") {
|
|
156
179
|
if (SETUP_SCOPES.includes(parsed.scope)) {
|
|
157
180
|
persisted.scope = parsed.scope;
|
|
158
181
|
}
|
|
@@ -160,10 +183,6 @@ export function readPersistedSetupPreferences(cwd) {
|
|
|
160
183
|
if (migrated)
|
|
161
184
|
persisted.scope = migrated;
|
|
162
185
|
}
|
|
163
|
-
if (typeof parsed.skillTarget === 'string' &&
|
|
164
|
-
SETUP_SKILL_TARGETS.includes(parsed.skillTarget)) {
|
|
165
|
-
persisted.skillTarget = parsed.skillTarget;
|
|
166
|
-
}
|
|
167
186
|
return Object.keys(persisted).length > 0 ? persisted : undefined;
|
|
168
187
|
}
|
|
169
188
|
catch (err) {
|
|
@@ -173,11 +192,11 @@ export function readPersistedSetupPreferences(cwd) {
|
|
|
173
192
|
return undefined;
|
|
174
193
|
}
|
|
175
194
|
export function resolveCodexHomeForLaunch(cwd, env = process.env) {
|
|
176
|
-
if (env.CODEX_HOME && env.CODEX_HOME.trim() !==
|
|
195
|
+
if (env.CODEX_HOME && env.CODEX_HOME.trim() !== "")
|
|
177
196
|
return env.CODEX_HOME;
|
|
178
197
|
const persistedScope = readPersistedSetupScope(cwd);
|
|
179
|
-
if (persistedScope ===
|
|
180
|
-
return join(cwd,
|
|
198
|
+
if (persistedScope === "project") {
|
|
199
|
+
return join(cwd, ".codex");
|
|
181
200
|
}
|
|
182
201
|
return undefined;
|
|
183
202
|
}
|
|
@@ -185,17 +204,17 @@ export function resolveSetupScopeArg(args) {
|
|
|
185
204
|
let value;
|
|
186
205
|
for (let index = 0; index < args.length; index += 1) {
|
|
187
206
|
const arg = args[index];
|
|
188
|
-
if (arg ===
|
|
207
|
+
if (arg === "--scope") {
|
|
189
208
|
const next = args[index + 1];
|
|
190
|
-
if (!next || next.startsWith(
|
|
191
|
-
throw new Error(`Missing setup scope value after --scope. Expected one of: ${SETUP_SCOPES.join(
|
|
209
|
+
if (!next || next.startsWith("-")) {
|
|
210
|
+
throw new Error(`Missing setup scope value after --scope. Expected one of: ${SETUP_SCOPES.join(", ")}`);
|
|
192
211
|
}
|
|
193
212
|
value = next;
|
|
194
213
|
index += 1;
|
|
195
214
|
continue;
|
|
196
215
|
}
|
|
197
|
-
if (arg.startsWith(
|
|
198
|
-
value = arg.slice(
|
|
216
|
+
if (arg.startsWith("--scope=")) {
|
|
217
|
+
value = arg.slice("--scope=".length);
|
|
199
218
|
}
|
|
200
219
|
}
|
|
201
220
|
if (!value)
|
|
@@ -203,51 +222,27 @@ export function resolveSetupScopeArg(args) {
|
|
|
203
222
|
if (SETUP_SCOPES.includes(value)) {
|
|
204
223
|
return value;
|
|
205
224
|
}
|
|
206
|
-
throw new Error(`Invalid setup scope: ${value}. Expected one of: ${SETUP_SCOPES.join(
|
|
207
|
-
}
|
|
208
|
-
export function resolveSetupSkillTargetArg(args) {
|
|
209
|
-
let value;
|
|
210
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
211
|
-
const arg = args[index];
|
|
212
|
-
if (arg === '--skill-target') {
|
|
213
|
-
const next = args[index + 1];
|
|
214
|
-
if (!next || next.startsWith('-')) {
|
|
215
|
-
throw new Error(`Missing setup skill target value after --skill-target. Expected one of: ${SETUP_SKILL_TARGETS.join(', ')}`);
|
|
216
|
-
}
|
|
217
|
-
value = next;
|
|
218
|
-
index += 1;
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
|
-
if (arg.startsWith('--skill-target=')) {
|
|
222
|
-
value = arg.slice('--skill-target='.length);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
if (!value)
|
|
226
|
-
return undefined;
|
|
227
|
-
if (SETUP_SKILL_TARGETS.includes(value)) {
|
|
228
|
-
return value;
|
|
229
|
-
}
|
|
230
|
-
throw new Error(`Invalid setup skill target: ${value}. Expected one of: ${SETUP_SKILL_TARGETS.join(', ')}`);
|
|
225
|
+
throw new Error(`Invalid setup scope: ${value}. Expected one of: ${SETUP_SCOPES.join(", ")}`);
|
|
231
226
|
}
|
|
232
227
|
export function resolveCliInvocation(args) {
|
|
233
228
|
const firstArg = args[0];
|
|
234
|
-
if (firstArg ===
|
|
235
|
-
return { command:
|
|
229
|
+
if (firstArg === "--help" || firstArg === "-h") {
|
|
230
|
+
return { command: "help", launchArgs: [] };
|
|
236
231
|
}
|
|
237
|
-
if (firstArg ===
|
|
238
|
-
return { command:
|
|
232
|
+
if (firstArg === "--version" || firstArg === "-v") {
|
|
233
|
+
return { command: "version", launchArgs: [] };
|
|
239
234
|
}
|
|
240
|
-
if (!firstArg || firstArg.startsWith(
|
|
241
|
-
return { command:
|
|
235
|
+
if (!firstArg || firstArg.startsWith("--")) {
|
|
236
|
+
return { command: "launch", launchArgs: firstArg ? args : [] };
|
|
242
237
|
}
|
|
243
|
-
if (firstArg ===
|
|
244
|
-
return { command:
|
|
238
|
+
if (firstArg === "launch") {
|
|
239
|
+
return { command: "launch", launchArgs: args.slice(1) };
|
|
245
240
|
}
|
|
246
|
-
if (firstArg ===
|
|
247
|
-
return { command:
|
|
241
|
+
if (firstArg === "exec") {
|
|
242
|
+
return { command: "exec", launchArgs: args.slice(1) };
|
|
248
243
|
}
|
|
249
|
-
if (firstArg ===
|
|
250
|
-
return { command:
|
|
244
|
+
if (firstArg === "resume") {
|
|
245
|
+
return { command: "resume", launchArgs: args.slice(1) };
|
|
251
246
|
}
|
|
252
247
|
return { command: firstArg, launchArgs: [] };
|
|
253
248
|
}
|
|
@@ -259,65 +254,70 @@ export function commandOwnsLocalHelp(command) {
|
|
|
259
254
|
}
|
|
260
255
|
export function resolveCodexLaunchPolicy(env = process.env, _platform = process.platform, tmuxAvailable = isTmuxAvailable()) {
|
|
261
256
|
if (env.TMUX)
|
|
262
|
-
return
|
|
263
|
-
return tmuxAvailable ?
|
|
257
|
+
return "inside-tmux";
|
|
258
|
+
return tmuxAvailable ? "detached-tmux" : "direct";
|
|
264
259
|
}
|
|
265
260
|
function hasErrnoCode(error, code) {
|
|
266
|
-
return Boolean(error &&
|
|
261
|
+
return Boolean(error &&
|
|
262
|
+
typeof error === "object" &&
|
|
263
|
+
"code" in error &&
|
|
264
|
+
error.code === code);
|
|
267
265
|
}
|
|
268
266
|
export function resolveSignalExitCode(signal) {
|
|
269
267
|
if (!signal)
|
|
270
268
|
return 1;
|
|
271
269
|
const signalNumber = osConstants.signals[signal];
|
|
272
|
-
if (typeof signalNumber ===
|
|
270
|
+
if (typeof signalNumber === "number" && Number.isFinite(signalNumber)) {
|
|
273
271
|
return 128 + signalNumber;
|
|
274
272
|
}
|
|
275
273
|
return 1;
|
|
276
274
|
}
|
|
277
275
|
export function classifyCodexExecFailure(error) {
|
|
278
|
-
if (!error || typeof error !==
|
|
276
|
+
if (!error || typeof error !== "object") {
|
|
279
277
|
return {
|
|
280
|
-
kind:
|
|
278
|
+
kind: "launch-error",
|
|
281
279
|
message: String(error),
|
|
282
280
|
};
|
|
283
281
|
}
|
|
284
282
|
const err = error;
|
|
285
|
-
const code = typeof err.code ===
|
|
286
|
-
const message = typeof err.message ===
|
|
283
|
+
const code = typeof err.code === "string" ? err.code : undefined;
|
|
284
|
+
const message = typeof err.message === "string" && err.message.length > 0
|
|
287
285
|
? err.message
|
|
288
|
-
:
|
|
289
|
-
const hasExitStatus = typeof err.status ===
|
|
290
|
-
const hasSignal = typeof err.signal ===
|
|
286
|
+
: "unknown codex launch failure";
|
|
287
|
+
const hasExitStatus = typeof err.status === "number";
|
|
288
|
+
const hasSignal = typeof err.signal === "string" && err.signal.length > 0;
|
|
291
289
|
if (hasExitStatus || hasSignal) {
|
|
292
290
|
return {
|
|
293
|
-
kind:
|
|
291
|
+
kind: "exit",
|
|
294
292
|
code,
|
|
295
293
|
message,
|
|
296
|
-
exitCode: hasExitStatus
|
|
294
|
+
exitCode: hasExitStatus
|
|
295
|
+
? err.status
|
|
296
|
+
: resolveSignalExitCode(err.signal),
|
|
297
297
|
signal: hasSignal ? err.signal : undefined,
|
|
298
298
|
};
|
|
299
299
|
}
|
|
300
300
|
return {
|
|
301
|
-
kind:
|
|
301
|
+
kind: "launch-error",
|
|
302
302
|
code,
|
|
303
303
|
message,
|
|
304
304
|
};
|
|
305
305
|
}
|
|
306
306
|
function runCodexBlocking(cwd, launchArgs, codexEnv) {
|
|
307
|
-
const { result } = spawnPlatformCommandSync(
|
|
307
|
+
const { result } = spawnPlatformCommandSync("codex", launchArgs, {
|
|
308
308
|
cwd,
|
|
309
|
-
stdio:
|
|
309
|
+
stdio: "inherit",
|
|
310
310
|
env: codexEnv,
|
|
311
|
-
encoding:
|
|
311
|
+
encoding: "utf-8",
|
|
312
312
|
});
|
|
313
313
|
if (result.error) {
|
|
314
314
|
const errno = result.error;
|
|
315
315
|
const kind = classifySpawnError(errno);
|
|
316
|
-
if (kind ===
|
|
317
|
-
console.error(
|
|
316
|
+
if (kind === "missing") {
|
|
317
|
+
console.error("[omx] failed to launch codex: executable not found in PATH");
|
|
318
318
|
}
|
|
319
|
-
else if (kind ===
|
|
320
|
-
console.error(`[omx] failed to launch codex: executable is present but blocked in the current environment (${errno.code ||
|
|
319
|
+
else if (kind === "blocked") {
|
|
320
|
+
console.error(`[omx] failed to launch codex: executable is present but blocked in the current environment (${errno.code || "blocked"})`);
|
|
321
321
|
}
|
|
322
322
|
else {
|
|
323
323
|
console.error(`[omx] failed to launch codex: ${errno.message}`);
|
|
@@ -325,9 +325,10 @@ function runCodexBlocking(cwd, launchArgs, codexEnv) {
|
|
|
325
325
|
throw result.error;
|
|
326
326
|
}
|
|
327
327
|
if (result.status !== 0) {
|
|
328
|
-
process.exitCode =
|
|
329
|
-
|
|
330
|
-
|
|
328
|
+
process.exitCode =
|
|
329
|
+
typeof result.status === "number"
|
|
330
|
+
? result.status
|
|
331
|
+
: resolveSignalExitCode(result.signal);
|
|
331
332
|
if (result.signal) {
|
|
332
333
|
console.error(`[omx] codex exited due to signal ${result.signal}`);
|
|
333
334
|
}
|
|
@@ -335,24 +336,24 @@ function runCodexBlocking(cwd, launchArgs, codexEnv) {
|
|
|
335
336
|
}
|
|
336
337
|
export function parseTmuxPaneSnapshot(output) {
|
|
337
338
|
return output
|
|
338
|
-
.split(
|
|
339
|
+
.split("\n")
|
|
339
340
|
.map((line) => line.trim())
|
|
340
341
|
.filter(Boolean)
|
|
341
342
|
.map((line) => {
|
|
342
|
-
const [paneId =
|
|
343
|
+
const [paneId = "", currentCommand = "", ...startCommandParts] = line.split("\t");
|
|
343
344
|
return {
|
|
344
345
|
paneId: paneId.trim(),
|
|
345
346
|
currentCommand: currentCommand.trim(),
|
|
346
|
-
startCommand: startCommandParts.join(
|
|
347
|
+
startCommand: startCommandParts.join("\t").trim(),
|
|
347
348
|
};
|
|
348
349
|
})
|
|
349
|
-
.filter((pane) => pane.paneId.startsWith(
|
|
350
|
+
.filter((pane) => pane.paneId.startsWith("%"));
|
|
350
351
|
}
|
|
351
352
|
export function isHudWatchPane(pane) {
|
|
352
353
|
const command = `${pane.startCommand} ${pane.currentCommand}`.toLowerCase();
|
|
353
|
-
return /\bhud\b/.test(command)
|
|
354
|
-
|
|
355
|
-
|
|
354
|
+
return (/\bhud\b/.test(command) &&
|
|
355
|
+
/--watch\b/.test(command) &&
|
|
356
|
+
(/\bomx(?:\.js)?\b/.test(command) || /\bnode\b/.test(command)));
|
|
356
357
|
}
|
|
357
358
|
export function findHudWatchPaneIds(panes, currentPaneId) {
|
|
358
359
|
return panes
|
|
@@ -361,122 +362,158 @@ export function findHudWatchPaneIds(panes, currentPaneId) {
|
|
|
361
362
|
.map((pane) => pane.paneId);
|
|
362
363
|
}
|
|
363
364
|
export function buildHudPaneCleanupTargets(existingPaneIds, createdPaneId, leaderPaneId) {
|
|
364
|
-
const targets = new Set(existingPaneIds.filter((id) => id.startsWith(
|
|
365
|
-
if (createdPaneId && createdPaneId.startsWith(
|
|
365
|
+
const targets = new Set(existingPaneIds.filter((id) => id.startsWith("%")));
|
|
366
|
+
if (createdPaneId && createdPaneId.startsWith("%")) {
|
|
366
367
|
targets.add(createdPaneId);
|
|
367
368
|
}
|
|
368
369
|
// Guard: never kill the leader's own pane under any circumstances.
|
|
369
|
-
if (leaderPaneId && leaderPaneId.startsWith(
|
|
370
|
+
if (leaderPaneId && leaderPaneId.startsWith("%")) {
|
|
370
371
|
targets.delete(leaderPaneId);
|
|
371
372
|
}
|
|
372
373
|
return [...targets];
|
|
373
374
|
}
|
|
374
375
|
export async function main(args) {
|
|
375
376
|
const knownCommands = new Set([
|
|
376
|
-
|
|
377
|
+
"launch",
|
|
378
|
+
"exec",
|
|
379
|
+
"setup",
|
|
380
|
+
"agents",
|
|
381
|
+
"agents-init",
|
|
382
|
+
"deepinit",
|
|
383
|
+
"uninstall",
|
|
384
|
+
"doctor",
|
|
385
|
+
"cleanup",
|
|
386
|
+
"ask",
|
|
387
|
+
"autoresearch",
|
|
388
|
+
"explore",
|
|
389
|
+
"sparkshell",
|
|
390
|
+
"team",
|
|
391
|
+
"ralph",
|
|
392
|
+
"ralphthon",
|
|
393
|
+
"session",
|
|
394
|
+
"resume",
|
|
395
|
+
"version",
|
|
396
|
+
"tmux-hook",
|
|
397
|
+
"hooks",
|
|
398
|
+
"hud",
|
|
399
|
+
"status",
|
|
400
|
+
"cancel",
|
|
401
|
+
"help",
|
|
402
|
+
"--help",
|
|
403
|
+
"-h",
|
|
377
404
|
]);
|
|
378
405
|
const firstArg = args[0];
|
|
379
406
|
const { command, launchArgs } = resolveCliInvocation(args);
|
|
380
|
-
const flags = new Set(args.filter(a => a.startsWith(
|
|
407
|
+
const flags = new Set(args.filter((a) => a.startsWith("--")));
|
|
381
408
|
const options = {
|
|
382
|
-
force: flags.has(
|
|
383
|
-
dryRun: flags.has(
|
|
384
|
-
verbose: flags.has(
|
|
385
|
-
team: flags.has(
|
|
409
|
+
force: flags.has("--force"),
|
|
410
|
+
dryRun: flags.has("--dry-run"),
|
|
411
|
+
verbose: flags.has("--verbose"),
|
|
412
|
+
team: flags.has("--team"),
|
|
386
413
|
};
|
|
387
|
-
if (flags.has(
|
|
414
|
+
if (flags.has("--help") && !commandOwnsLocalHelp(command)) {
|
|
388
415
|
console.log(HELP);
|
|
389
416
|
return;
|
|
390
417
|
}
|
|
391
418
|
try {
|
|
392
419
|
switch (command) {
|
|
393
|
-
case
|
|
420
|
+
case "launch":
|
|
394
421
|
await launchWithHud(launchArgs);
|
|
395
422
|
break;
|
|
396
|
-
case
|
|
397
|
-
await launchWithHud([
|
|
423
|
+
case "resume":
|
|
424
|
+
await launchWithHud(["resume", ...launchArgs]);
|
|
398
425
|
break;
|
|
399
|
-
case
|
|
426
|
+
case "setup":
|
|
400
427
|
await setup({
|
|
401
428
|
force: options.force,
|
|
402
429
|
dryRun: options.dryRun,
|
|
403
430
|
verbose: options.verbose,
|
|
404
431
|
scope: resolveSetupScopeArg(args.slice(1)),
|
|
405
|
-
skillTarget: resolveSetupSkillTargetArg(args.slice(1)),
|
|
406
432
|
});
|
|
407
433
|
break;
|
|
408
|
-
case
|
|
434
|
+
case "agents":
|
|
435
|
+
await agentsCommand(args.slice(1));
|
|
436
|
+
break;
|
|
437
|
+
case "agents-init":
|
|
409
438
|
await agentsInitCommand(args.slice(1));
|
|
410
439
|
break;
|
|
411
|
-
case
|
|
440
|
+
case "deepinit":
|
|
412
441
|
await agentsInitCommand(args.slice(1));
|
|
413
442
|
break;
|
|
414
|
-
case
|
|
443
|
+
case "uninstall":
|
|
415
444
|
await uninstall({
|
|
416
445
|
dryRun: options.dryRun,
|
|
417
|
-
keepConfig: flags.has(
|
|
446
|
+
keepConfig: flags.has("--keep-config"),
|
|
418
447
|
verbose: options.verbose,
|
|
419
|
-
purge: flags.has(
|
|
448
|
+
purge: flags.has("--purge"),
|
|
420
449
|
scope: resolveSetupScopeArg(args.slice(1)),
|
|
421
450
|
});
|
|
422
451
|
break;
|
|
423
|
-
case
|
|
424
|
-
const { doctor } = await import(
|
|
452
|
+
case "doctor": {
|
|
453
|
+
const { doctor } = await import("./doctor.js");
|
|
425
454
|
await doctor(options);
|
|
426
455
|
break;
|
|
427
456
|
}
|
|
428
|
-
case
|
|
457
|
+
case "ask":
|
|
429
458
|
await askCommand(args.slice(1));
|
|
430
459
|
break;
|
|
431
|
-
case
|
|
460
|
+
case "cleanup":
|
|
461
|
+
await cleanupCommand(args.slice(1));
|
|
462
|
+
break;
|
|
463
|
+
case "autoresearch":
|
|
432
464
|
await autoresearchCommand(args.slice(1));
|
|
433
465
|
break;
|
|
434
|
-
case
|
|
466
|
+
case "explore":
|
|
435
467
|
await exploreCommand(args.slice(1));
|
|
436
468
|
break;
|
|
437
|
-
case
|
|
469
|
+
case "exec":
|
|
438
470
|
await execWithOverlay(launchArgs);
|
|
439
471
|
break;
|
|
440
|
-
case
|
|
472
|
+
case "sparkshell":
|
|
441
473
|
await sparkshellCommand(args.slice(1));
|
|
442
474
|
break;
|
|
443
|
-
case
|
|
475
|
+
case "team":
|
|
444
476
|
await teamCommand(args.slice(1), options);
|
|
445
477
|
break;
|
|
446
|
-
case
|
|
478
|
+
case "session":
|
|
447
479
|
await sessionCommand(args.slice(1));
|
|
448
480
|
break;
|
|
449
|
-
case
|
|
481
|
+
case "ralph":
|
|
450
482
|
await ralphCommand(args.slice(1));
|
|
451
483
|
break;
|
|
452
|
-
case
|
|
484
|
+
case "ralphthon":
|
|
485
|
+
await ralphthonCommand(args.slice(1));
|
|
486
|
+
break;
|
|
487
|
+
case "version":
|
|
453
488
|
version();
|
|
454
489
|
break;
|
|
455
|
-
case
|
|
490
|
+
case "hud":
|
|
456
491
|
await hudCommand(args.slice(1));
|
|
457
492
|
break;
|
|
458
|
-
case
|
|
493
|
+
case "tmux-hook":
|
|
459
494
|
await tmuxHookCommand(args.slice(1));
|
|
460
495
|
break;
|
|
461
|
-
case
|
|
496
|
+
case "hooks":
|
|
462
497
|
await hooksCommand(args.slice(1));
|
|
463
498
|
break;
|
|
464
|
-
case
|
|
499
|
+
case "status":
|
|
465
500
|
await showStatus();
|
|
466
501
|
break;
|
|
467
|
-
case
|
|
502
|
+
case "cancel":
|
|
468
503
|
await cancelModes();
|
|
469
504
|
break;
|
|
470
|
-
case
|
|
505
|
+
case "reasoning":
|
|
471
506
|
await reasoningCommand(args.slice(1));
|
|
472
507
|
break;
|
|
473
|
-
case
|
|
474
|
-
case
|
|
475
|
-
case
|
|
508
|
+
case "help":
|
|
509
|
+
case "--help":
|
|
510
|
+
case "-h":
|
|
476
511
|
console.log(HELP);
|
|
477
512
|
break;
|
|
478
513
|
default:
|
|
479
|
-
if (firstArg &&
|
|
514
|
+
if (firstArg &&
|
|
515
|
+
firstArg.startsWith("-") &&
|
|
516
|
+
!knownCommands.has(firstArg)) {
|
|
480
517
|
await launchWithHud(args);
|
|
481
518
|
break;
|
|
482
519
|
}
|
|
@@ -491,17 +528,17 @@ export async function main(args) {
|
|
|
491
528
|
}
|
|
492
529
|
}
|
|
493
530
|
async function showStatus() {
|
|
494
|
-
const { readFile } = await import(
|
|
531
|
+
const { readFile } = await import("fs/promises");
|
|
495
532
|
const cwd = process.cwd();
|
|
496
533
|
try {
|
|
497
534
|
const refs = await listModeStateFilesWithScopePreference(cwd);
|
|
498
535
|
const states = refs.map((ref) => ref.path);
|
|
499
536
|
if (states.length === 0) {
|
|
500
|
-
console.log(
|
|
537
|
+
console.log("No active modes.");
|
|
501
538
|
return;
|
|
502
539
|
}
|
|
503
540
|
for (const path of states) {
|
|
504
|
-
const content = await readFile(path,
|
|
541
|
+
const content = await readFile(path, "utf-8");
|
|
505
542
|
let state;
|
|
506
543
|
try {
|
|
507
544
|
state = JSON.parse(content);
|
|
@@ -511,13 +548,13 @@ async function showStatus() {
|
|
|
511
548
|
continue;
|
|
512
549
|
}
|
|
513
550
|
const file = basename(path);
|
|
514
|
-
const mode = file.replace(
|
|
515
|
-
console.log(`${mode}: ${state.active === true ?
|
|
551
|
+
const mode = file.replace("-state.json", "");
|
|
552
|
+
console.log(`${mode}: ${state.active === true ? "ACTIVE" : "inactive"} (phase: ${String(state.current_phase || "n/a")})`);
|
|
516
553
|
}
|
|
517
554
|
}
|
|
518
555
|
catch (err) {
|
|
519
556
|
process.stderr.write(`[cli/index] operation failed: ${err}\n`);
|
|
520
|
-
console.log(
|
|
557
|
+
console.log("No active modes.");
|
|
521
558
|
}
|
|
522
559
|
}
|
|
523
560
|
async function reasoningCommand(args) {
|
|
@@ -529,8 +566,8 @@ async function reasoningCommand(args) {
|
|
|
529
566
|
console.log(REASONING_USAGE);
|
|
530
567
|
return;
|
|
531
568
|
}
|
|
532
|
-
const { readFile } = await import(
|
|
533
|
-
const content = await readFile(configPath,
|
|
569
|
+
const { readFile } = await import("fs/promises");
|
|
570
|
+
const content = await readFile(configPath, "utf-8");
|
|
534
571
|
const current = readTopLevelTomlString(content, REASONING_KEY);
|
|
535
572
|
if (current) {
|
|
536
573
|
console.log(`Current ${REASONING_KEY}: ${current}`);
|
|
@@ -541,37 +578,39 @@ async function reasoningCommand(args) {
|
|
|
541
578
|
return;
|
|
542
579
|
}
|
|
543
580
|
if (!REASONING_MODE_SET.has(mode)) {
|
|
544
|
-
throw new Error(`Invalid reasoning mode "${mode}". Expected one of: ${REASONING_MODES.join(
|
|
581
|
+
throw new Error(`Invalid reasoning mode "${mode}". Expected one of: ${REASONING_MODES.join(", ")}.\n${REASONING_USAGE}`);
|
|
545
582
|
}
|
|
546
|
-
const { mkdir, readFile, writeFile } = await import(
|
|
583
|
+
const { mkdir, readFile, writeFile } = await import("fs/promises");
|
|
547
584
|
await mkdir(dirname(configPath), { recursive: true });
|
|
548
|
-
const existing = existsSync(configPath)
|
|
585
|
+
const existing = existsSync(configPath)
|
|
586
|
+
? await readFile(configPath, "utf-8")
|
|
587
|
+
: "";
|
|
549
588
|
const updated = upsertTopLevelTomlString(existing, REASONING_KEY, mode);
|
|
550
589
|
await writeFile(configPath, updated);
|
|
551
590
|
console.log(`Set ${REASONING_KEY}="${mode}" in ${configPath}`);
|
|
552
591
|
}
|
|
553
592
|
export async function launchWithHud(args) {
|
|
554
593
|
if (isNativeWindows()) {
|
|
555
|
-
const { result } = spawnPlatformCommandSync(
|
|
556
|
-
encoding:
|
|
557
|
-
stdio: [
|
|
594
|
+
const { result } = spawnPlatformCommandSync("tmux", ["-V"], {
|
|
595
|
+
encoding: "utf-8",
|
|
596
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
558
597
|
});
|
|
559
598
|
if (result.error) {
|
|
560
599
|
const errno = result.error;
|
|
561
600
|
const kind = classifySpawnError(errno);
|
|
562
|
-
if (kind ===
|
|
563
|
-
console.warn(
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
601
|
+
if (kind === "missing") {
|
|
602
|
+
console.warn("[omx] warning: tmux was not found on native Windows. Continuing without tmux/HUD.\n" +
|
|
603
|
+
"[omx] To enable tmux-backed features, install psmux:\n" +
|
|
604
|
+
"[omx] winget install psmux\n" +
|
|
605
|
+
"[omx] See: https://github.com/marlocarlo/psmux");
|
|
567
606
|
}
|
|
568
607
|
else {
|
|
569
608
|
console.warn(`[omx] warning: tmux probe failed on native Windows (${errno.code || errno.message}). Continuing without tmux/HUD.`);
|
|
570
609
|
}
|
|
571
610
|
}
|
|
572
611
|
else if (result.status !== 0 && !isTmuxAvailable()) {
|
|
573
|
-
const stderr = (result.stderr ||
|
|
574
|
-
console.warn(`[omx] warning: tmux reported an error on native Windows${stderr ? ` (${stderr})` :
|
|
612
|
+
const stderr = (result.stderr || "").trim();
|
|
613
|
+
console.warn(`[omx] warning: tmux reported an error on native Windows${stderr ? ` (${stderr})` : ""}. Continuing without tmux/HUD.`);
|
|
575
614
|
}
|
|
576
615
|
}
|
|
577
616
|
const launchCwd = process.cwd();
|
|
@@ -584,7 +623,7 @@ export async function launchWithHud(args) {
|
|
|
584
623
|
if (parsedWorktree.mode.enabled) {
|
|
585
624
|
const planned = planWorktreeTarget({
|
|
586
625
|
cwd: launchCwd,
|
|
587
|
-
scope:
|
|
626
|
+
scope: "launch",
|
|
588
627
|
mode: parsedWorktree.mode,
|
|
589
628
|
});
|
|
590
629
|
const ensured = ensureWorktree(planned);
|
|
@@ -614,7 +653,7 @@ export async function launchWithHud(args) {
|
|
|
614
653
|
try {
|
|
615
654
|
const repaired = await repairConfigIfNeeded(codexConfigPath(), getPackageRoot());
|
|
616
655
|
if (repaired) {
|
|
617
|
-
console.log(
|
|
656
|
+
console.log("[omx] Repaired duplicate [tui] section in config.toml.");
|
|
618
657
|
}
|
|
619
658
|
}
|
|
620
659
|
catch {
|
|
@@ -650,7 +689,7 @@ export async function execWithOverlay(args) {
|
|
|
650
689
|
if (parsedWorktree.mode.enabled) {
|
|
651
690
|
const planned = planWorktreeTarget({
|
|
652
691
|
cwd: launchCwd,
|
|
653
|
-
scope:
|
|
692
|
+
scope: "launch",
|
|
654
693
|
mode: parsedWorktree.mode,
|
|
655
694
|
});
|
|
656
695
|
const ensured = ensureWorktree(planned);
|
|
@@ -674,7 +713,7 @@ export async function execWithOverlay(args) {
|
|
|
674
713
|
try {
|
|
675
714
|
const repaired = await repairConfigIfNeeded(codexConfigPath(), getPackageRoot());
|
|
676
715
|
if (repaired) {
|
|
677
|
-
console.log(
|
|
716
|
+
console.log("[omx] Repaired duplicate [tui] section in config.toml.");
|
|
678
717
|
}
|
|
679
718
|
}
|
|
680
719
|
catch {
|
|
@@ -690,12 +729,15 @@ export async function execWithOverlay(args) {
|
|
|
690
729
|
const notifyTempContractRaw = notifyTempResult.contract.active
|
|
691
730
|
? serializeNotifyTempContract(notifyTempResult.contract)
|
|
692
731
|
: null;
|
|
693
|
-
const codexArgs = injectModelInstructionsBypassArgs(cwd, [
|
|
732
|
+
const codexArgs = injectModelInstructionsBypassArgs(cwd, ["exec", ...normalizedArgs], process.env, sessionModelInstructionsPath(cwd, sessionId));
|
|
694
733
|
const codexEnvBase = codexHomeOverride
|
|
695
734
|
? { ...process.env, CODEX_HOME: codexHomeOverride }
|
|
696
735
|
: process.env;
|
|
697
736
|
const codexEnv = notifyTempContractRaw
|
|
698
|
-
? {
|
|
737
|
+
? {
|
|
738
|
+
...codexEnvBase,
|
|
739
|
+
[OMX_NOTIFY_TEMP_CONTRACT_ENV]: notifyTempContractRaw,
|
|
740
|
+
}
|
|
699
741
|
: codexEnvBase;
|
|
700
742
|
runCodexBlocking(cwd, codexArgs, codexEnv);
|
|
701
743
|
}
|
|
@@ -723,11 +765,11 @@ export function normalizeCodexLaunchArgs(args) {
|
|
|
723
765
|
continue;
|
|
724
766
|
}
|
|
725
767
|
if (arg === HIGH_REASONING_FLAG) {
|
|
726
|
-
reasoningMode =
|
|
768
|
+
reasoningMode = "high";
|
|
727
769
|
continue;
|
|
728
770
|
}
|
|
729
771
|
if (arg === XHIGH_REASONING_FLAG) {
|
|
730
|
-
reasoningMode =
|
|
772
|
+
reasoningMode = "xhigh";
|
|
731
773
|
continue;
|
|
732
774
|
}
|
|
733
775
|
if (arg === SPARK_FLAG) {
|
|
@@ -770,7 +812,8 @@ function hasModelInstructionsOverride(args) {
|
|
|
770
812
|
const arg = args[i];
|
|
771
813
|
if (arg === CONFIG_FLAG || arg === LONG_CONFIG_FLAG) {
|
|
772
814
|
const maybeValue = args[i + 1];
|
|
773
|
-
if (typeof maybeValue ===
|
|
815
|
+
if (typeof maybeValue === "string" &&
|
|
816
|
+
isModelInstructionsOverride(maybeValue)) {
|
|
774
817
|
return true;
|
|
775
818
|
}
|
|
776
819
|
continue;
|
|
@@ -784,18 +827,20 @@ function hasModelInstructionsOverride(args) {
|
|
|
784
827
|
return false;
|
|
785
828
|
}
|
|
786
829
|
function shouldBypassDefaultSystemPrompt(env) {
|
|
787
|
-
return env[OMX_BYPASS_DEFAULT_SYSTEM_PROMPT_ENV] !==
|
|
830
|
+
return env[OMX_BYPASS_DEFAULT_SYSTEM_PROMPT_ENV] !== "0";
|
|
788
831
|
}
|
|
789
832
|
function buildModelInstructionsOverride(cwd, env, defaultFilePath) {
|
|
790
|
-
const filePath = env[OMX_MODEL_INSTRUCTIONS_FILE_ENV] ||
|
|
833
|
+
const filePath = env[OMX_MODEL_INSTRUCTIONS_FILE_ENV] ||
|
|
834
|
+
defaultFilePath ||
|
|
835
|
+
join(cwd, "AGENTS.md");
|
|
791
836
|
return `${MODEL_INSTRUCTIONS_FILE_KEY}="${escapeTomlString(filePath)}"`;
|
|
792
837
|
}
|
|
793
838
|
function tryReadGitValue(cwd, args) {
|
|
794
839
|
try {
|
|
795
|
-
const value = execFileSync(
|
|
840
|
+
const value = execFileSync("git", args, {
|
|
796
841
|
cwd,
|
|
797
|
-
encoding:
|
|
798
|
-
stdio: [
|
|
842
|
+
encoding: "utf-8",
|
|
843
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
799
844
|
timeout: 2000,
|
|
800
845
|
}).trim();
|
|
801
846
|
return value || undefined;
|
|
@@ -814,9 +859,9 @@ function extractIssueNumber(text) {
|
|
|
814
859
|
function resolveNativeSessionName(cwd, sessionId) {
|
|
815
860
|
if (process.env.TMUX) {
|
|
816
861
|
try {
|
|
817
|
-
const tmuxSession = execFileSync(
|
|
818
|
-
encoding:
|
|
819
|
-
stdio: [
|
|
862
|
+
const tmuxSession = execFileSync("tmux", ["display-message", "-p", "#S"], {
|
|
863
|
+
encoding: "utf-8",
|
|
864
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
820
865
|
timeout: 2000,
|
|
821
866
|
}).trim();
|
|
822
867
|
if (tmuxSession)
|
|
@@ -829,9 +874,9 @@ function resolveNativeSessionName(cwd, sessionId) {
|
|
|
829
874
|
return buildTmuxSessionName(cwd, sessionId);
|
|
830
875
|
}
|
|
831
876
|
function buildNativeHookBaseContext(cwd, sessionId, normalizedEvent, extra = {}) {
|
|
832
|
-
const repoPath = tryReadGitValue(cwd, [
|
|
833
|
-
const branch = tryReadGitValue(cwd, [
|
|
834
|
-
const issueNumber = extractIssueNumber([branch, basename(cwd)].filter(Boolean).join(
|
|
877
|
+
const repoPath = tryReadGitValue(cwd, ["rev-parse", "--show-toplevel"]) || cwd;
|
|
878
|
+
const branch = tryReadGitValue(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
879
|
+
const issueNumber = extractIssueNumber([branch, basename(cwd)].filter(Boolean).join(" "));
|
|
835
880
|
return {
|
|
836
881
|
normalized_event: normalizedEvent,
|
|
837
882
|
session_name: resolveNativeSessionName(cwd, sessionId),
|
|
@@ -848,13 +893,19 @@ export function injectModelInstructionsBypassArgs(cwd, args, env = process.env,
|
|
|
848
893
|
return [...args];
|
|
849
894
|
if (hasModelInstructionsOverride(args))
|
|
850
895
|
return [...args];
|
|
851
|
-
return [
|
|
896
|
+
return [
|
|
897
|
+
...args,
|
|
898
|
+
CONFIG_FLAG,
|
|
899
|
+
buildModelInstructionsOverride(cwd, env, defaultFilePath),
|
|
900
|
+
];
|
|
852
901
|
}
|
|
853
902
|
export function collectInheritableTeamWorkerArgs(codexArgs) {
|
|
854
903
|
return collectInheritableTeamWorkerArgsShared(codexArgs);
|
|
855
904
|
}
|
|
856
905
|
export function resolveTeamWorkerLaunchArgsEnv(existingRaw, codexArgs, inheritLeaderFlags = true, defaultModel) {
|
|
857
|
-
const inheritedArgs = inheritLeaderFlags
|
|
906
|
+
const inheritedArgs = inheritLeaderFlags
|
|
907
|
+
? collectInheritableTeamWorkerArgs(codexArgs)
|
|
908
|
+
: [];
|
|
858
909
|
const normalized = resolveTeamWorkerLaunchArgs({
|
|
859
910
|
existingRaw,
|
|
860
911
|
inheritedArgs,
|
|
@@ -862,14 +913,14 @@ export function resolveTeamWorkerLaunchArgsEnv(existingRaw, codexArgs, inheritLe
|
|
|
862
913
|
});
|
|
863
914
|
if (normalized.length === 0)
|
|
864
915
|
return null;
|
|
865
|
-
return normalized.join(
|
|
916
|
+
return normalized.join(" ");
|
|
866
917
|
}
|
|
867
918
|
export function readTopLevelTomlString(content, key) {
|
|
868
919
|
let inTopLevel = true;
|
|
869
920
|
const lines = content.split(/\r?\n/);
|
|
870
921
|
for (const line of lines) {
|
|
871
922
|
const trimmed = line.trim();
|
|
872
|
-
if (!trimmed || trimmed.startsWith(
|
|
923
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
873
924
|
continue;
|
|
874
925
|
if (/^\[[^[\]]+\]\s*(#.*)?$/.test(trimmed)) {
|
|
875
926
|
inTopLevel = false;
|
|
@@ -885,7 +936,7 @@ export function readTopLevelTomlString(content, key) {
|
|
|
885
936
|
return null;
|
|
886
937
|
}
|
|
887
938
|
export function upsertTopLevelTomlString(content, key, value) {
|
|
888
|
-
const eol = content.includes(
|
|
939
|
+
const eol = content.includes("\r\n") ? "\r\n" : "\n";
|
|
889
940
|
const assignment = `${key} = "${escapeTomlString(value)}"`;
|
|
890
941
|
if (!content.trim()) {
|
|
891
942
|
return assignment + eol;
|
|
@@ -896,7 +947,7 @@ export function upsertTopLevelTomlString(content, key, value) {
|
|
|
896
947
|
for (let i = 0; i < lines.length; i++) {
|
|
897
948
|
const line = lines[i];
|
|
898
949
|
const trimmed = line.trim();
|
|
899
|
-
if (!trimmed || trimmed.startsWith(
|
|
950
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
900
951
|
continue;
|
|
901
952
|
if (/^\[[^[\]]+\]\s*(#.*)?$/.test(trimmed)) {
|
|
902
953
|
inTopLevel = false;
|
|
@@ -912,7 +963,7 @@ export function upsertTopLevelTomlString(content, key, value) {
|
|
|
912
963
|
}
|
|
913
964
|
}
|
|
914
965
|
if (!replaced) {
|
|
915
|
-
const firstTableIndex = lines.findIndex(line => /^\s*\[[^[\]]+\]\s*(#.*)?$/.test(line.trim()));
|
|
966
|
+
const firstTableIndex = lines.findIndex((line) => /^\s*\[[^[\]]+\]\s*(#.*)?$/.test(line.trim()));
|
|
916
967
|
if (firstTableIndex >= 0) {
|
|
917
968
|
lines.splice(firstTableIndex, 0, assignment);
|
|
918
969
|
}
|
|
@@ -930,43 +981,54 @@ function parseTomlStringValue(value) {
|
|
|
930
981
|
if (trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length >= 2) {
|
|
931
982
|
return trimmed.slice(1, -1);
|
|
932
983
|
}
|
|
933
|
-
if (trimmed.startsWith('
|
|
984
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'") && trimmed.length >= 2) {
|
|
934
985
|
return trimmed.slice(1, -1);
|
|
935
986
|
}
|
|
936
987
|
return trimmed;
|
|
937
988
|
}
|
|
938
989
|
function escapeTomlString(value) {
|
|
939
|
-
return value.replace(/\\/g,
|
|
990
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
940
991
|
}
|
|
941
992
|
function sanitizeTmuxToken(value) {
|
|
942
|
-
const cleaned = value
|
|
943
|
-
|
|
993
|
+
const cleaned = value
|
|
994
|
+
.toLowerCase()
|
|
995
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
996
|
+
.replace(/^-+|-+$/g, "");
|
|
997
|
+
return cleaned || "unknown";
|
|
944
998
|
}
|
|
945
999
|
export function buildTmuxSessionName(cwd, sessionId) {
|
|
946
|
-
const
|
|
1000
|
+
const parentPath = dirname(cwd);
|
|
1001
|
+
const parentDir = basename(parentPath);
|
|
947
1002
|
const dirName = basename(cwd);
|
|
948
|
-
const
|
|
949
|
-
|
|
1003
|
+
const grandparentPath = dirname(parentPath);
|
|
1004
|
+
const grandparentDir = basename(grandparentPath);
|
|
1005
|
+
const repoDir = parentDir.endsWith(".omx-worktrees")
|
|
1006
|
+
? parentDir.slice(0, -".omx-worktrees".length)
|
|
1007
|
+
: parentDir === "worktrees" && grandparentDir === ".omx"
|
|
1008
|
+
? basename(dirname(grandparentPath))
|
|
1009
|
+
: null;
|
|
1010
|
+
const dirToken = repoDir
|
|
1011
|
+
? sanitizeTmuxToken(`${repoDir}-${dirName}`)
|
|
950
1012
|
: sanitizeTmuxToken(dirName);
|
|
951
|
-
let branchToken =
|
|
952
|
-
const branch = tryReadGitValue(cwd, [
|
|
1013
|
+
let branchToken = "detached";
|
|
1014
|
+
const branch = tryReadGitValue(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
953
1015
|
if (branch)
|
|
954
1016
|
branchToken = sanitizeTmuxToken(branch);
|
|
955
|
-
const sessionToken = sanitizeTmuxToken(sessionId.replace(/^omx-/,
|
|
1017
|
+
const sessionToken = sanitizeTmuxToken(sessionId.replace(/^omx-/, ""));
|
|
956
1018
|
const name = `omx-${dirToken}-${branchToken}-${sessionToken}`;
|
|
957
1019
|
return name.length > 120 ? name.slice(0, 120) : name;
|
|
958
1020
|
}
|
|
959
1021
|
function parsePaneIdFromTmuxOutput(rawOutput) {
|
|
960
|
-
const paneId = rawOutput.split(
|
|
961
|
-
return paneId.startsWith(
|
|
1022
|
+
const paneId = rawOutput.split("\n")[0]?.trim() || "";
|
|
1023
|
+
return paneId.startsWith("%") ? paneId : null;
|
|
962
1024
|
}
|
|
963
1025
|
function parseWindowIndexFromTmuxOutput(rawOutput) {
|
|
964
|
-
const windowIndex = rawOutput.split(
|
|
1026
|
+
const windowIndex = rawOutput.split("\n")[0]?.trim() || "";
|
|
965
1027
|
return /^[0-9]+$/.test(windowIndex) ? windowIndex : null;
|
|
966
1028
|
}
|
|
967
1029
|
function detectDetachedSessionWindowIndex(sessionName) {
|
|
968
1030
|
try {
|
|
969
|
-
const output = execFileSync(
|
|
1031
|
+
const output = execFileSync("tmux", ["display-message", "-p", "-t", sessionName, "#{window_index}"], { encoding: "utf-8" });
|
|
970
1032
|
return parseWindowIndexFromTmuxOutput(output);
|
|
971
1033
|
}
|
|
972
1034
|
catch (err) {
|
|
@@ -975,129 +1037,177 @@ function detectDetachedSessionWindowIndex(sessionName) {
|
|
|
975
1037
|
}
|
|
976
1038
|
}
|
|
977
1039
|
export function buildDetachedSessionBootstrapSteps(sessionName, cwd, codexCmd, hudCmd, workerLaunchArgs, codexHomeOverride, notifyTempContractRaw, nativeWindows = false) {
|
|
1040
|
+
const detachedLeaderCmd = nativeWindows
|
|
1041
|
+
? "powershell.exe"
|
|
1042
|
+
: `/bin/sh -lc ${quoteShellArg(`${codexCmd}; status=$?; tmux kill-session -t ${quoteShellArg(sessionName)} >/dev/null 2>&1 || true; exit $status`)}`;
|
|
978
1043
|
const newSessionArgs = [
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1044
|
+
"new-session",
|
|
1045
|
+
"-d",
|
|
1046
|
+
"-P",
|
|
1047
|
+
"-F",
|
|
1048
|
+
"#{pane_id}",
|
|
1049
|
+
"-s",
|
|
1050
|
+
sessionName,
|
|
1051
|
+
"-c",
|
|
1052
|
+
cwd,
|
|
1053
|
+
...(workerLaunchArgs
|
|
1054
|
+
? ["-e", `${TEAM_WORKER_LAUNCH_ARGS_ENV}=${workerLaunchArgs}`]
|
|
1055
|
+
: []),
|
|
1056
|
+
...(codexHomeOverride ? ["-e", `CODEX_HOME=${codexHomeOverride}`] : []),
|
|
1057
|
+
...(notifyTempContractRaw
|
|
1058
|
+
? ["-e", `${OMX_NOTIFY_TEMP_CONTRACT_ENV}=${notifyTempContractRaw}`]
|
|
1059
|
+
: []),
|
|
1060
|
+
detachedLeaderCmd,
|
|
984
1061
|
];
|
|
985
1062
|
const splitCaptureArgs = [
|
|
986
|
-
|
|
987
|
-
|
|
1063
|
+
"split-window",
|
|
1064
|
+
"-v",
|
|
1065
|
+
"-l",
|
|
1066
|
+
String(HUD_TMUX_HEIGHT_LINES),
|
|
1067
|
+
"-d",
|
|
1068
|
+
"-t",
|
|
1069
|
+
sessionName,
|
|
1070
|
+
"-c",
|
|
1071
|
+
cwd,
|
|
1072
|
+
"-P",
|
|
1073
|
+
"-F",
|
|
1074
|
+
"#{pane_id}",
|
|
1075
|
+
hudCmd,
|
|
988
1076
|
];
|
|
989
1077
|
return [
|
|
990
|
-
{ name:
|
|
991
|
-
{ name:
|
|
1078
|
+
{ name: "new-session", args: newSessionArgs },
|
|
1079
|
+
{ name: "split-and-capture-hud-pane", args: splitCaptureArgs },
|
|
992
1080
|
];
|
|
993
1081
|
}
|
|
1082
|
+
async function updateActiveRalphthonLaunchTarget(cwd, patch) {
|
|
1083
|
+
const state = await readModeState("ralphthon", cwd).catch(() => null);
|
|
1084
|
+
if (!state || state.active !== true)
|
|
1085
|
+
return;
|
|
1086
|
+
const next = {};
|
|
1087
|
+
if (typeof patch.leader_pane_id === "string" &&
|
|
1088
|
+
patch.leader_pane_id.trim().startsWith("%")) {
|
|
1089
|
+
next.leader_pane_id = patch.leader_pane_id.trim();
|
|
1090
|
+
next.tmux_pane_id = patch.leader_pane_id.trim();
|
|
1091
|
+
}
|
|
1092
|
+
else if (typeof patch.tmux_pane_id === "string" &&
|
|
1093
|
+
patch.tmux_pane_id.trim().startsWith("%")) {
|
|
1094
|
+
next.tmux_pane_id = patch.tmux_pane_id.trim();
|
|
1095
|
+
}
|
|
1096
|
+
if (typeof patch.tmux_session === "string" &&
|
|
1097
|
+
patch.tmux_session.trim() !== "") {
|
|
1098
|
+
next.tmux_session = patch.tmux_session.trim();
|
|
1099
|
+
}
|
|
1100
|
+
if (Object.keys(next).length === 0)
|
|
1101
|
+
return;
|
|
1102
|
+
await updateModeState("ralphthon", next, cwd).catch(() => { });
|
|
1103
|
+
}
|
|
1104
|
+
async function readLaunchAppendInstructions() {
|
|
1105
|
+
const appendixCandidates = [
|
|
1106
|
+
process.env[OMX_RALPH_APPEND_INSTRUCTIONS_FILE_ENV]?.trim(),
|
|
1107
|
+
process.env[OMX_RALPHTHON_APPEND_INSTRUCTIONS_FILE_ENV]?.trim(),
|
|
1108
|
+
process.env[OMX_AUTORESEARCH_APPEND_INSTRUCTIONS_FILE_ENV]?.trim(),
|
|
1109
|
+
].filter((value) => typeof value === "string" && value.length > 0);
|
|
1110
|
+
if (appendixCandidates.length === 0)
|
|
1111
|
+
return "";
|
|
1112
|
+
const appendixPath = appendixCandidates[0];
|
|
1113
|
+
if (!existsSync(appendixPath)) {
|
|
1114
|
+
throw new Error(`launch instructions file not found: ${appendixPath}`);
|
|
1115
|
+
}
|
|
1116
|
+
const { readFile } = await import("fs/promises");
|
|
1117
|
+
return (await readFile(appendixPath, "utf-8")).trim();
|
|
1118
|
+
}
|
|
994
1119
|
export function buildDetachedSessionFinalizeSteps(sessionName, hudPaneId, hookWindowIndex, enableMouse, nativeWindows = false) {
|
|
995
1120
|
const steps = [];
|
|
996
1121
|
if (!nativeWindows && hudPaneId && hookWindowIndex) {
|
|
997
1122
|
const hookTarget = buildResizeHookTarget(sessionName, hookWindowIndex);
|
|
998
|
-
const hookName = buildResizeHookName(
|
|
999
|
-
const clientAttachedHookName = buildClientAttachedReconcileHookName(
|
|
1123
|
+
const hookName = buildResizeHookName("launch", sessionName, hookWindowIndex, hudPaneId);
|
|
1124
|
+
const clientAttachedHookName = buildClientAttachedReconcileHookName("launch", sessionName, hookWindowIndex, hudPaneId);
|
|
1000
1125
|
steps.push({
|
|
1001
|
-
name:
|
|
1126
|
+
name: "register-resize-hook",
|
|
1002
1127
|
args: buildRegisterResizeHookArgs(hookTarget, hookName, hudPaneId, HUD_TMUX_HEIGHT_LINES),
|
|
1003
1128
|
});
|
|
1004
1129
|
steps.push({
|
|
1005
|
-
name:
|
|
1130
|
+
name: "register-client-attached-reconcile",
|
|
1006
1131
|
args: buildRegisterClientAttachedReconcileArgs(hookTarget, clientAttachedHookName, hudPaneId, HUD_TMUX_HEIGHT_LINES),
|
|
1007
1132
|
});
|
|
1008
1133
|
steps.push({
|
|
1009
|
-
name:
|
|
1134
|
+
name: "schedule-delayed-resize",
|
|
1010
1135
|
args: buildScheduleDelayedHudResizeArgs(hudPaneId, undefined, HUD_TMUX_HEIGHT_LINES),
|
|
1011
1136
|
});
|
|
1012
1137
|
steps.push({
|
|
1013
|
-
name:
|
|
1138
|
+
name: "reconcile-hud-resize",
|
|
1014
1139
|
args: buildReconcileHudResizeArgs(hudPaneId, HUD_TMUX_HEIGHT_LINES),
|
|
1015
1140
|
});
|
|
1016
1141
|
}
|
|
1017
1142
|
if (enableMouse) {
|
|
1018
|
-
steps.push({
|
|
1143
|
+
steps.push({
|
|
1144
|
+
name: "set-mouse",
|
|
1145
|
+
args: ["set-option", "-t", sessionName, "mouse", "on"],
|
|
1146
|
+
});
|
|
1019
1147
|
}
|
|
1020
|
-
steps.push({
|
|
1148
|
+
steps.push({
|
|
1149
|
+
name: "attach-session",
|
|
1150
|
+
args: ["attach-session", "-t", sessionName],
|
|
1151
|
+
});
|
|
1021
1152
|
return steps;
|
|
1022
1153
|
}
|
|
1023
1154
|
export function buildDetachedSessionRollbackSteps(sessionName, hookTarget, hookName, clientAttachedHookName) {
|
|
1024
1155
|
const steps = [];
|
|
1025
1156
|
if (hookTarget && clientAttachedHookName) {
|
|
1026
1157
|
steps.push({
|
|
1027
|
-
name:
|
|
1158
|
+
name: "unregister-client-attached-reconcile",
|
|
1028
1159
|
args: buildUnregisterClientAttachedReconcileArgs(hookTarget, clientAttachedHookName),
|
|
1029
1160
|
});
|
|
1030
1161
|
}
|
|
1031
1162
|
if (hookTarget && hookName) {
|
|
1032
1163
|
steps.push({
|
|
1033
|
-
name:
|
|
1164
|
+
name: "unregister-resize-hook",
|
|
1034
1165
|
args: buildUnregisterResizeHookArgs(hookTarget, hookName),
|
|
1035
1166
|
});
|
|
1036
1167
|
}
|
|
1037
|
-
steps.push({
|
|
1168
|
+
steps.push({
|
|
1169
|
+
name: "kill-session",
|
|
1170
|
+
args: ["kill-session", "-t", sessionName],
|
|
1171
|
+
});
|
|
1038
1172
|
return steps;
|
|
1039
1173
|
}
|
|
1040
|
-
async function readAutoresearchAppendInstructions() {
|
|
1041
|
-
const appendixPath = process.env[OMX_AUTORESEARCH_APPEND_INSTRUCTIONS_FILE_ENV]?.trim();
|
|
1042
|
-
if (!appendixPath)
|
|
1043
|
-
return '';
|
|
1044
|
-
if (!existsSync(appendixPath)) {
|
|
1045
|
-
throw new Error(`autoresearch instructions file not found: ${appendixPath}`);
|
|
1046
|
-
}
|
|
1047
|
-
const { readFile } = await import('fs/promises');
|
|
1048
|
-
return (await readFile(appendixPath, 'utf-8')).trim();
|
|
1049
|
-
}
|
|
1050
1174
|
export function buildNotifyTempStartupMessages(contract, hasValidProviders) {
|
|
1051
1175
|
const providers = contract.canonicalSelectors.length > 0
|
|
1052
|
-
? contract.canonicalSelectors.join(
|
|
1053
|
-
:
|
|
1176
|
+
? contract.canonicalSelectors.join(",")
|
|
1177
|
+
: "none";
|
|
1054
1178
|
const infoLines = [
|
|
1055
1179
|
`notify temp: active | providers=${providers} | persistent-routing=bypassed`,
|
|
1056
1180
|
];
|
|
1057
1181
|
const warningLines = [...contract.warnings];
|
|
1058
1182
|
if (!hasValidProviders) {
|
|
1059
|
-
warningLines.push(
|
|
1183
|
+
warningLines.push("notify temp: no valid providers resolved; notifications skipped");
|
|
1060
1184
|
}
|
|
1061
1185
|
return { infoLines, warningLines };
|
|
1062
1186
|
}
|
|
1063
1187
|
/**
|
|
1064
1188
|
* preLaunch: Prepare environment before Codex starts.
|
|
1065
|
-
* 1.
|
|
1066
|
-
* 2.
|
|
1067
|
-
*
|
|
1189
|
+
* 1. Generate runtime overlay + write session-scoped model instructions file
|
|
1190
|
+
* 2. Write session.json
|
|
1191
|
+
*
|
|
1192
|
+
* Automatic stale-session cleanup is intentionally disabled here. Destructive
|
|
1193
|
+
* cleanup must be explicit via `omx cleanup` so normal launches never reap
|
|
1194
|
+
* files or processes from other OMX sessions.
|
|
1068
1195
|
*/
|
|
1069
1196
|
async function preLaunch(cwd, sessionId, notifyTempContract) {
|
|
1070
|
-
// 1.
|
|
1071
|
-
const existingSession = await readSessionState(cwd);
|
|
1072
|
-
if (existingSession && isSessionStale(existingSession)) {
|
|
1073
|
-
try {
|
|
1074
|
-
await removeSessionModelInstructionsFile(cwd, existingSession.session_id);
|
|
1075
|
-
}
|
|
1076
|
-
catch (err) {
|
|
1077
|
-
process.stderr.write(`[cli/index] operation failed: ${err}\n`);
|
|
1078
|
-
}
|
|
1079
|
-
const { unlink } = await import('fs/promises');
|
|
1080
|
-
try {
|
|
1081
|
-
await unlink(join(cwd, '.omx', 'state', 'session.json'));
|
|
1082
|
-
}
|
|
1083
|
-
catch (err) {
|
|
1084
|
-
process.stderr.write(`[cli/index] operation failed: ${err}\n`);
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
// 2. Generate runtime overlay + write session-scoped model instructions file
|
|
1197
|
+
// 1. Generate runtime overlay + write session-scoped model instructions file
|
|
1088
1198
|
const orchestrationMode = await resolveSessionOrchestrationMode(cwd, sessionId);
|
|
1089
1199
|
const overlay = await generateOverlay(cwd, sessionId, { orchestrationMode });
|
|
1090
|
-
const
|
|
1091
|
-
const sessionInstructions =
|
|
1200
|
+
const launchAppendix = await readLaunchAppendInstructions();
|
|
1201
|
+
const sessionInstructions = launchAppendix.trim().length > 0
|
|
1092
1202
|
? `${overlay}
|
|
1093
1203
|
|
|
1094
|
-
${
|
|
1204
|
+
${launchAppendix}`
|
|
1095
1205
|
: overlay;
|
|
1096
1206
|
await writeSessionModelInstructionsFile(cwd, sessionId, sessionInstructions);
|
|
1097
|
-
//
|
|
1207
|
+
// 2. Write session state
|
|
1098
1208
|
await resetSessionMetrics(cwd);
|
|
1099
1209
|
await writeSessionStart(cwd, sessionId);
|
|
1100
|
-
//
|
|
1210
|
+
// 3. Start notify fallback watcher (best effort)
|
|
1101
1211
|
try {
|
|
1102
1212
|
await startNotifyFallbackWatcher(cwd);
|
|
1103
1213
|
}
|
|
@@ -1105,7 +1215,7 @@ ${autoresearchAppendix}`
|
|
|
1105
1215
|
process.stderr.write(`[cli/index] operation failed: ${err}\n`);
|
|
1106
1216
|
// Non-fatal
|
|
1107
1217
|
}
|
|
1108
|
-
//
|
|
1218
|
+
// 4. Start derived watcher (best effort, opt-in)
|
|
1109
1219
|
try {
|
|
1110
1220
|
await startHookDerivedWatcher(cwd);
|
|
1111
1221
|
}
|
|
@@ -1113,11 +1223,12 @@ ${autoresearchAppendix}`
|
|
|
1113
1223
|
process.stderr.write(`[cli/index] operation failed: ${err}\n`);
|
|
1114
1224
|
// Non-fatal
|
|
1115
1225
|
}
|
|
1116
|
-
//
|
|
1226
|
+
// 5. Emit temp notification startup summary + warnings, then send session-start lifecycle notification (best effort)
|
|
1117
1227
|
try {
|
|
1118
1228
|
if (notifyTempContract?.active) {
|
|
1119
|
-
process.env[OMX_NOTIFY_TEMP_CONTRACT_ENV] =
|
|
1120
|
-
|
|
1229
|
+
process.env[OMX_NOTIFY_TEMP_CONTRACT_ENV] =
|
|
1230
|
+
serializeNotifyTempContract(notifyTempContract);
|
|
1231
|
+
const { getNotificationConfig } = await import("../notifications/config.js");
|
|
1121
1232
|
const resolved = getNotificationConfig();
|
|
1122
1233
|
const startup = buildNotifyTempStartupMessages(notifyTempContract, Boolean(resolved?.enabled));
|
|
1123
1234
|
for (const info of startup.infoLines) {
|
|
@@ -1130,8 +1241,8 @@ ${autoresearchAppendix}`
|
|
|
1130
1241
|
else {
|
|
1131
1242
|
delete process.env[OMX_NOTIFY_TEMP_CONTRACT_ENV];
|
|
1132
1243
|
}
|
|
1133
|
-
const { notifyLifecycle } = await import(
|
|
1134
|
-
await notifyLifecycle(
|
|
1244
|
+
const { notifyLifecycle } = await import("../notifications/index.js");
|
|
1245
|
+
await notifyLifecycle("session-start", {
|
|
1135
1246
|
sessionId,
|
|
1136
1247
|
projectPath: cwd,
|
|
1137
1248
|
projectName: basename(cwd),
|
|
@@ -1141,14 +1252,14 @@ ${autoresearchAppendix}`
|
|
|
1141
1252
|
process.stderr.write(`[cli/index] operation failed: ${err}\n`);
|
|
1142
1253
|
// Non-fatal: notification failures must never block launch
|
|
1143
1254
|
}
|
|
1144
|
-
//
|
|
1255
|
+
// 6. Dispatch native hook event (best effort)
|
|
1145
1256
|
try {
|
|
1146
|
-
await emitNativeHookEvent(cwd,
|
|
1257
|
+
await emitNativeHookEvent(cwd, "session-start", {
|
|
1147
1258
|
session_id: sessionId,
|
|
1148
|
-
context: buildNativeHookBaseContext(cwd, sessionId,
|
|
1259
|
+
context: buildNativeHookBaseContext(cwd, sessionId, "started", {
|
|
1149
1260
|
project_path: cwd,
|
|
1150
1261
|
project_name: basename(cwd),
|
|
1151
|
-
status:
|
|
1262
|
+
status: "started",
|
|
1152
1263
|
}),
|
|
1153
1264
|
});
|
|
1154
1265
|
}
|
|
@@ -1166,9 +1277,9 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
|
|
|
1166
1277
|
const nativeWindows = isNativeWindows();
|
|
1167
1278
|
const omxBin = process.argv[1];
|
|
1168
1279
|
const hudCmd = nativeWindows
|
|
1169
|
-
? buildWindowsPromptCommand(
|
|
1170
|
-
: buildTmuxPaneCommand(
|
|
1171
|
-
const inheritLeaderFlags = process.env[TEAM_INHERIT_LEADER_FLAGS_ENV] !==
|
|
1280
|
+
? buildWindowsPromptCommand("node", [omxBin, "hud", "--watch"])
|
|
1281
|
+
: buildTmuxPaneCommand("node", [omxBin, "hud", "--watch"]);
|
|
1282
|
+
const inheritLeaderFlags = process.env[TEAM_INHERIT_LEADER_FLAGS_ENV] !== "0";
|
|
1172
1283
|
const workerLaunchArgs = resolveTeamWorkerLaunchArgsEnv(process.env[TEAM_WORKER_LAUNCH_ARGS_ENV], launchArgs, inheritLeaderFlags, workerDefaultModel);
|
|
1173
1284
|
const codexBaseEnv = codexHomeOverride
|
|
1174
1285
|
? { ...process.env, CODEX_HOME: codexHomeOverride }
|
|
@@ -1184,7 +1295,7 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
|
|
|
1184
1295
|
runCodexBlocking(cwd, launchArgs, codexEnvWithNotify);
|
|
1185
1296
|
return;
|
|
1186
1297
|
}
|
|
1187
|
-
if (launchPolicy ===
|
|
1298
|
+
if (launchPolicy === "inside-tmux") {
|
|
1188
1299
|
// Already in tmux: launch codex in current pane, HUD in bottom split
|
|
1189
1300
|
const currentPaneId = process.env.TMUX_PANE;
|
|
1190
1301
|
const staleHudPaneIds = listHudWatchPaneIdsInCurrentWindow(currentPaneId);
|
|
@@ -1202,13 +1313,15 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
|
|
|
1202
1313
|
// Enable mouse scrolling at session start so scroll works before team
|
|
1203
1314
|
// expansion. Previously this was only called from createTeamSession().
|
|
1204
1315
|
// Opt-out: set OMX_MOUSE=0. (closes #128)
|
|
1205
|
-
if (process.env.OMX_MOUSE !==
|
|
1316
|
+
if (process.env.OMX_MOUSE !== "0") {
|
|
1206
1317
|
try {
|
|
1207
1318
|
const tmuxPaneTarget = process.env.TMUX_PANE;
|
|
1208
1319
|
const displayArgs = tmuxPaneTarget
|
|
1209
|
-
? [
|
|
1210
|
-
: [
|
|
1211
|
-
const tmuxSession = execFileSync(
|
|
1320
|
+
? ["display-message", "-p", "-t", tmuxPaneTarget, "#S"]
|
|
1321
|
+
: ["display-message", "-p", "#S"];
|
|
1322
|
+
const tmuxSession = execFileSync("tmux", displayArgs, {
|
|
1323
|
+
encoding: "utf-8",
|
|
1324
|
+
}).trim();
|
|
1212
1325
|
if (tmuxSession)
|
|
1213
1326
|
enableMouseScrolling(tmuxSession);
|
|
1214
1327
|
}
|
|
@@ -1217,6 +1330,22 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
|
|
|
1217
1330
|
// Non-fatal: mouse scrolling is a convenience feature
|
|
1218
1331
|
}
|
|
1219
1332
|
}
|
|
1333
|
+
const activePaneId = process.env.TMUX_PANE?.trim();
|
|
1334
|
+
if (activePaneId) {
|
|
1335
|
+
let tmuxSessionName;
|
|
1336
|
+
try {
|
|
1337
|
+
const displayArgs = ["display-message", "-p", "-t", activePaneId, "#S"];
|
|
1338
|
+
tmuxSessionName =
|
|
1339
|
+
execFileSync("tmux", displayArgs, { encoding: "utf-8" }).trim() ||
|
|
1340
|
+
undefined;
|
|
1341
|
+
}
|
|
1342
|
+
catch { }
|
|
1343
|
+
void updateActiveRalphthonLaunchTarget(cwd, {
|
|
1344
|
+
leader_pane_id: activePaneId,
|
|
1345
|
+
tmux_pane_id: activePaneId,
|
|
1346
|
+
tmux_session: tmuxSessionName ?? null,
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1220
1349
|
try {
|
|
1221
1350
|
runCodexBlocking(cwd, launchArgs, codexEnvWithNotify);
|
|
1222
1351
|
}
|
|
@@ -1227,62 +1356,77 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
|
|
|
1227
1356
|
}
|
|
1228
1357
|
}
|
|
1229
1358
|
}
|
|
1230
|
-
else if (launchPolicy ===
|
|
1359
|
+
else if (launchPolicy === "direct") {
|
|
1231
1360
|
// Detached HUD sessions require tmux. Skip the bootstrap entirely when the
|
|
1232
1361
|
// binary is unavailable so direct launches do not emit noisy ENOENT logs.
|
|
1233
1362
|
runCodexBlocking(cwd, launchArgs, codexEnvWithNotify);
|
|
1234
1363
|
}
|
|
1235
1364
|
else {
|
|
1236
1365
|
// Not in tmux: create a new tmux session with codex + HUD pane
|
|
1237
|
-
const codexCmd = buildTmuxPaneCommand(
|
|
1366
|
+
const codexCmd = buildTmuxPaneCommand("codex", launchArgs);
|
|
1238
1367
|
const detachedWindowsCodexCmd = nativeWindows
|
|
1239
|
-
? buildWindowsPromptCommand(
|
|
1368
|
+
? buildWindowsPromptCommand("codex", launchArgs)
|
|
1240
1369
|
: null;
|
|
1241
1370
|
const tmuxSessionId = `omx-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1242
1371
|
const sessionName = buildTmuxSessionName(cwd, tmuxSessionId);
|
|
1243
1372
|
let createdDetachedSession = false;
|
|
1373
|
+
let detachedLeaderPaneId = null;
|
|
1244
1374
|
let registeredHookTarget = null;
|
|
1245
1375
|
let registeredHookName = null;
|
|
1246
1376
|
let registeredClientAttachedHookName = null;
|
|
1247
1377
|
try {
|
|
1248
1378
|
const bootstrapSteps = buildDetachedSessionBootstrapSteps(sessionName, cwd, codexCmd, hudCmd, workerLaunchArgs, codexHomeOverride, notifyTempContractRaw, nativeWindows);
|
|
1249
1379
|
for (const step of bootstrapSteps) {
|
|
1250
|
-
const output = execFileSync(
|
|
1251
|
-
|
|
1380
|
+
const output = execFileSync("tmux", step.args, {
|
|
1381
|
+
stdio: "pipe",
|
|
1382
|
+
encoding: "utf-8",
|
|
1383
|
+
});
|
|
1384
|
+
if (step.name === "new-session") {
|
|
1252
1385
|
createdDetachedSession = true;
|
|
1386
|
+
detachedLeaderPaneId = parsePaneIdFromTmuxOutput(output || "");
|
|
1387
|
+
void updateActiveRalphthonLaunchTarget(cwd, {
|
|
1388
|
+
leader_pane_id: detachedLeaderPaneId,
|
|
1389
|
+
tmux_pane_id: detachedLeaderPaneId,
|
|
1390
|
+
tmux_session: sessionName,
|
|
1391
|
+
});
|
|
1253
1392
|
}
|
|
1254
|
-
if (step.name ===
|
|
1255
|
-
const hudPaneId = parsePaneIdFromTmuxOutput(output ||
|
|
1256
|
-
const hookWindowIndex = hudPaneId
|
|
1393
|
+
if (step.name === "split-and-capture-hud-pane") {
|
|
1394
|
+
const hudPaneId = parsePaneIdFromTmuxOutput(output || "");
|
|
1395
|
+
const hookWindowIndex = hudPaneId
|
|
1396
|
+
? detectDetachedSessionWindowIndex(sessionName)
|
|
1397
|
+
: null;
|
|
1257
1398
|
const hookTarget = hudPaneId && hookWindowIndex
|
|
1258
1399
|
? buildResizeHookTarget(sessionName, hookWindowIndex)
|
|
1259
1400
|
: null;
|
|
1260
1401
|
const hookName = hudPaneId && hookWindowIndex
|
|
1261
|
-
? buildResizeHookName(
|
|
1402
|
+
? buildResizeHookName("launch", sessionName, hookWindowIndex, hudPaneId)
|
|
1262
1403
|
: null;
|
|
1263
1404
|
const clientAttachedHookName = hudPaneId && hookWindowIndex
|
|
1264
|
-
? buildClientAttachedReconcileHookName(
|
|
1405
|
+
? buildClientAttachedReconcileHookName("launch", sessionName, hookWindowIndex, hudPaneId)
|
|
1265
1406
|
: null;
|
|
1266
|
-
const finalizeSteps = buildDetachedSessionFinalizeSteps(sessionName, hudPaneId, hookWindowIndex, process.env.OMX_MOUSE !==
|
|
1407
|
+
const finalizeSteps = buildDetachedSessionFinalizeSteps(sessionName, hudPaneId, hookWindowIndex, process.env.OMX_MOUSE !== "0", nativeWindows);
|
|
1267
1408
|
if (nativeWindows && detachedWindowsCodexCmd) {
|
|
1268
1409
|
scheduleDetachedWindowsCodexLaunch(sessionName, detachedWindowsCodexCmd);
|
|
1269
1410
|
}
|
|
1270
1411
|
for (const finalizeStep of finalizeSteps) {
|
|
1271
|
-
const stdio = finalizeStep.name ===
|
|
1412
|
+
const stdio = finalizeStep.name === "attach-session" ? "inherit" : "ignore";
|
|
1272
1413
|
try {
|
|
1273
|
-
execFileSync(
|
|
1414
|
+
execFileSync("tmux", finalizeStep.args, { stdio });
|
|
1274
1415
|
}
|
|
1275
1416
|
catch (err) {
|
|
1276
1417
|
process.stderr.write(`[cli/index] operation failed: ${err}\n`);
|
|
1277
|
-
if (finalizeStep.name ===
|
|
1278
|
-
throw new Error(
|
|
1418
|
+
if (finalizeStep.name === "attach-session")
|
|
1419
|
+
throw new Error("failed to attach detached tmux session");
|
|
1279
1420
|
continue;
|
|
1280
1421
|
}
|
|
1281
|
-
if (finalizeStep.name ===
|
|
1422
|
+
if (finalizeStep.name === "register-resize-hook" &&
|
|
1423
|
+
hookTarget &&
|
|
1424
|
+
hookName) {
|
|
1282
1425
|
registeredHookTarget = hookTarget;
|
|
1283
1426
|
registeredHookName = hookName;
|
|
1284
1427
|
}
|
|
1285
|
-
if (finalizeStep.name ===
|
|
1428
|
+
if (finalizeStep.name === "register-client-attached-reconcile" &&
|
|
1429
|
+
clientAttachedHookName) {
|
|
1286
1430
|
registeredClientAttachedHookName = clientAttachedHookName;
|
|
1287
1431
|
}
|
|
1288
1432
|
}
|
|
@@ -1295,7 +1439,7 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
|
|
|
1295
1439
|
const rollbackSteps = buildDetachedSessionRollbackSteps(sessionName, registeredHookTarget, registeredHookName, registeredClientAttachedHookName);
|
|
1296
1440
|
for (const rollbackStep of rollbackSteps) {
|
|
1297
1441
|
try {
|
|
1298
|
-
execFileSync(
|
|
1442
|
+
execFileSync("tmux", rollbackStep.args, { stdio: "ignore" });
|
|
1299
1443
|
}
|
|
1300
1444
|
catch (err) {
|
|
1301
1445
|
process.stderr.write(`[cli/index] operation failed: ${err}\n`);
|
|
@@ -1310,7 +1454,11 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
|
|
|
1310
1454
|
}
|
|
1311
1455
|
function listHudWatchPaneIdsInCurrentWindow(currentPaneId) {
|
|
1312
1456
|
try {
|
|
1313
|
-
const output = execFileSync(
|
|
1457
|
+
const output = execFileSync("tmux", [
|
|
1458
|
+
"list-panes",
|
|
1459
|
+
"-F",
|
|
1460
|
+
"#{pane_id}\t#{pane_current_command}\t#{pane_start_command}",
|
|
1461
|
+
], { encoding: "utf-8" });
|
|
1314
1462
|
return findHudWatchPaneIds(parseTmuxPaneSnapshot(output), currentPaneId);
|
|
1315
1463
|
}
|
|
1316
1464
|
catch (err) {
|
|
@@ -1319,14 +1467,26 @@ function listHudWatchPaneIdsInCurrentWindow(currentPaneId) {
|
|
|
1319
1467
|
}
|
|
1320
1468
|
}
|
|
1321
1469
|
function createHudWatchPane(cwd, hudCmd) {
|
|
1322
|
-
const output = execFileSync(
|
|
1470
|
+
const output = execFileSync("tmux", [
|
|
1471
|
+
"split-window",
|
|
1472
|
+
"-v",
|
|
1473
|
+
"-l",
|
|
1474
|
+
String(HUD_TMUX_HEIGHT_LINES),
|
|
1475
|
+
"-d",
|
|
1476
|
+
"-c",
|
|
1477
|
+
cwd,
|
|
1478
|
+
"-P",
|
|
1479
|
+
"-F",
|
|
1480
|
+
"#{pane_id}",
|
|
1481
|
+
hudCmd,
|
|
1482
|
+
], { encoding: "utf-8" });
|
|
1323
1483
|
return parsePaneIdFromTmuxOutput(output);
|
|
1324
1484
|
}
|
|
1325
1485
|
function killTmuxPane(paneId) {
|
|
1326
|
-
if (!paneId.startsWith(
|
|
1486
|
+
if (!paneId.startsWith("%"))
|
|
1327
1487
|
return;
|
|
1328
1488
|
try {
|
|
1329
|
-
execFileSync(
|
|
1489
|
+
execFileSync("tmux", ["kill-pane", "-t", paneId], { stdio: "ignore" });
|
|
1330
1490
|
}
|
|
1331
1491
|
catch (err) {
|
|
1332
1492
|
process.stderr.write(`[cli/index] operation failed: ${err}\n`);
|
|
@@ -1334,20 +1494,24 @@ function killTmuxPane(paneId) {
|
|
|
1334
1494
|
}
|
|
1335
1495
|
}
|
|
1336
1496
|
export function buildTmuxShellCommand(command, args) {
|
|
1337
|
-
return [quoteShellArg(command), ...args.map(quoteShellArg)].join(
|
|
1497
|
+
return [quoteShellArg(command), ...args.map(quoteShellArg)].join(" ");
|
|
1338
1498
|
}
|
|
1339
1499
|
function encodePowerShellCommand(commandText) {
|
|
1340
|
-
return Buffer.from(commandText,
|
|
1500
|
+
return Buffer.from(commandText, "utf16le").toString("base64");
|
|
1341
1501
|
}
|
|
1342
1502
|
function isCodexVersionRequest(args) {
|
|
1343
1503
|
return args.some((arg) => CODEX_VERSION_FLAGS.has(arg));
|
|
1344
1504
|
}
|
|
1345
1505
|
export function buildWindowsPromptCommand(command, args) {
|
|
1346
|
-
const invocation = [
|
|
1506
|
+
const invocation = [
|
|
1507
|
+
"&",
|
|
1508
|
+
quotePowerShellArg(command),
|
|
1509
|
+
...args.map(quotePowerShellArg),
|
|
1510
|
+
].join(" ");
|
|
1347
1511
|
const wrappedCommand = [
|
|
1348
1512
|
"$ErrorActionPreference = 'Stop'",
|
|
1349
1513
|
`& { ${invocation} }`,
|
|
1350
|
-
].join(
|
|
1514
|
+
].join("; ");
|
|
1351
1515
|
return `powershell.exe -NoLogo -NoExit -EncodedCommand ${encodePowerShellCommand(wrappedCommand)}`;
|
|
1352
1516
|
}
|
|
1353
1517
|
/**
|
|
@@ -1357,15 +1521,15 @@ export function buildWindowsPromptCommand(command, args) {
|
|
|
1357
1521
|
*/
|
|
1358
1522
|
export function buildTmuxPaneCommand(command, args, shellPath = process.env.SHELL) {
|
|
1359
1523
|
const bareCmd = buildTmuxShellCommand(command, args);
|
|
1360
|
-
let rcSource =
|
|
1524
|
+
let rcSource = "";
|
|
1361
1525
|
if (shellPath && /\/zsh$/i.test(shellPath)) {
|
|
1362
|
-
rcSource =
|
|
1526
|
+
rcSource = "if [ -f ~/.zshrc ]; then source ~/.zshrc; fi; ";
|
|
1363
1527
|
}
|
|
1364
1528
|
else if (shellPath && /\/bash$/i.test(shellPath)) {
|
|
1365
|
-
rcSource =
|
|
1529
|
+
rcSource = "if [ -f ~/.bashrc ]; then source ~/.bashrc; fi; ";
|
|
1366
1530
|
}
|
|
1367
|
-
const rawShell = shellPath && shellPath.trim() !==
|
|
1368
|
-
const shellBin = ALLOWED_SHELLS.has(rawShell) ? rawShell :
|
|
1531
|
+
const rawShell = shellPath && shellPath.trim() !== "" ? shellPath.trim() : "/bin/sh";
|
|
1532
|
+
const shellBin = ALLOWED_SHELLS.has(rawShell) ? rawShell : "/bin/sh";
|
|
1369
1533
|
const inner = `${rcSource}exec ${bareCmd}`;
|
|
1370
1534
|
return `${quoteShellArg(shellBin)} -lc ${quoteShellArg(inner)}`;
|
|
1371
1535
|
}
|
|
@@ -1387,12 +1551,12 @@ function buildDetachedWindowsBootstrapScript(sessionName, commandText, delayMs =
|
|
|
1387
1551
|
`try { execFileSync('tmux', ['send-keys', '-t', ${targetLiteral}, '-l', '--', ${commandLiteral}], { stdio: 'ignore' }); } catch {}`,
|
|
1388
1552
|
`try { execFileSync('tmux', ['send-keys', '-t', ${targetLiteral}, 'C-m'], { stdio: 'ignore' }); } catch {}`,
|
|
1389
1553
|
`}, ${delay});`,
|
|
1390
|
-
].join(
|
|
1554
|
+
].join("");
|
|
1391
1555
|
}
|
|
1392
1556
|
function scheduleDetachedWindowsCodexLaunch(sessionName, commandText) {
|
|
1393
|
-
const child = spawn(process.execPath, [
|
|
1557
|
+
const child = spawn(process.execPath, ["-e", buildDetachedWindowsBootstrapScript(sessionName, commandText)], {
|
|
1394
1558
|
detached: true,
|
|
1395
|
-
stdio:
|
|
1559
|
+
stdio: "ignore",
|
|
1396
1560
|
windowsHide: true,
|
|
1397
1561
|
});
|
|
1398
1562
|
child.unref();
|
|
@@ -1460,15 +1624,15 @@ async function postLaunch(cwd, sessionId) {
|
|
|
1460
1624
|
}
|
|
1461
1625
|
// 3. Cancel any still-active modes
|
|
1462
1626
|
try {
|
|
1463
|
-
const { readdir, writeFile, readFile } = await import(
|
|
1627
|
+
const { readdir, writeFile, readFile } = await import("fs/promises");
|
|
1464
1628
|
const scopedDirs = [getBaseStateDir(cwd), getStateDir(cwd, sessionId)];
|
|
1465
1629
|
for (const stateDir of scopedDirs) {
|
|
1466
1630
|
const files = await readdir(stateDir).catch(() => []);
|
|
1467
1631
|
for (const file of files) {
|
|
1468
|
-
if (!file.endsWith(
|
|
1632
|
+
if (!file.endsWith("-state.json") || file === "session.json")
|
|
1469
1633
|
continue;
|
|
1470
1634
|
const path = join(stateDir, file);
|
|
1471
|
-
const content = await readFile(path,
|
|
1635
|
+
const content = await readFile(path, "utf-8");
|
|
1472
1636
|
const state = JSON.parse(content);
|
|
1473
1637
|
if (state.active) {
|
|
1474
1638
|
state.active = false;
|
|
@@ -1483,16 +1647,16 @@ async function postLaunch(cwd, sessionId) {
|
|
|
1483
1647
|
}
|
|
1484
1648
|
// 4. Send session-end lifecycle notification (best effort)
|
|
1485
1649
|
try {
|
|
1486
|
-
const { notifyLifecycle } = await import(
|
|
1650
|
+
const { notifyLifecycle } = await import("../notifications/index.js");
|
|
1487
1651
|
const durationMs = sessionStartedAt
|
|
1488
1652
|
? Date.now() - new Date(sessionStartedAt).getTime()
|
|
1489
1653
|
: undefined;
|
|
1490
|
-
await notifyLifecycle(
|
|
1654
|
+
await notifyLifecycle("session-end", {
|
|
1491
1655
|
sessionId,
|
|
1492
1656
|
projectPath: cwd,
|
|
1493
1657
|
projectName: basename(cwd),
|
|
1494
1658
|
durationMs,
|
|
1495
|
-
reason:
|
|
1659
|
+
reason: "session_exit",
|
|
1496
1660
|
});
|
|
1497
1661
|
}
|
|
1498
1662
|
catch (err) {
|
|
@@ -1504,19 +1668,21 @@ async function postLaunch(cwd, sessionId) {
|
|
|
1504
1668
|
const durationMs = sessionStartedAt
|
|
1505
1669
|
? Date.now() - new Date(sessionStartedAt).getTime()
|
|
1506
1670
|
: undefined;
|
|
1507
|
-
const normalizedEvent = process.exitCode && process.exitCode !== 0 ?
|
|
1508
|
-
const errorSummary = normalizedEvent ===
|
|
1671
|
+
const normalizedEvent = process.exitCode && process.exitCode !== 0 ? "failed" : "finished";
|
|
1672
|
+
const errorSummary = normalizedEvent === "failed"
|
|
1509
1673
|
? `codex exited with code ${process.exitCode}`
|
|
1510
1674
|
: undefined;
|
|
1511
|
-
await emitNativeHookEvent(cwd,
|
|
1675
|
+
await emitNativeHookEvent(cwd, "session-end", {
|
|
1512
1676
|
session_id: sessionId,
|
|
1513
1677
|
context: buildNativeHookBaseContext(cwd, sessionId, normalizedEvent, {
|
|
1514
1678
|
project_path: cwd,
|
|
1515
1679
|
project_name: basename(cwd),
|
|
1516
1680
|
duration_ms: durationMs,
|
|
1517
|
-
reason:
|
|
1518
|
-
status: normalizedEvent ===
|
|
1519
|
-
...(process.exitCode !== undefined
|
|
1681
|
+
reason: "session_exit",
|
|
1682
|
+
status: normalizedEvent === "failed" ? "failed" : "finished",
|
|
1683
|
+
...(process.exitCode !== undefined
|
|
1684
|
+
? { exit_code: process.exitCode }
|
|
1685
|
+
: {}),
|
|
1520
1686
|
...(errorSummary ? { error_summary: errorSummary } : {}),
|
|
1521
1687
|
}),
|
|
1522
1688
|
});
|
|
@@ -1528,7 +1694,7 @@ async function postLaunch(cwd, sessionId) {
|
|
|
1528
1694
|
}
|
|
1529
1695
|
async function emitNativeHookEvent(cwd, event, opts = {}) {
|
|
1530
1696
|
const payload = buildHookEvent(event, {
|
|
1531
|
-
source:
|
|
1697
|
+
source: "native",
|
|
1532
1698
|
context: opts.context || {},
|
|
1533
1699
|
session_id: opts.session_id,
|
|
1534
1700
|
thread_id: opts.thread_id,
|
|
@@ -1540,10 +1706,10 @@ async function emitNativeHookEvent(cwd, event, opts = {}) {
|
|
|
1540
1706
|
});
|
|
1541
1707
|
}
|
|
1542
1708
|
function notifyFallbackPidPath(cwd) {
|
|
1543
|
-
return join(cwd,
|
|
1709
|
+
return join(cwd, ".omx", "state", "notify-fallback.pid");
|
|
1544
1710
|
}
|
|
1545
1711
|
function hookDerivedWatcherPidPath(cwd) {
|
|
1546
|
-
return join(cwd,
|
|
1712
|
+
return join(cwd, ".omx", "state", "hook-derived-watcher.pid");
|
|
1547
1713
|
}
|
|
1548
1714
|
function parseWatcherPidFile(content) {
|
|
1549
1715
|
const trimmed = content.trim();
|
|
@@ -1551,217 +1717,221 @@ function parseWatcherPidFile(content) {
|
|
|
1551
1717
|
return null;
|
|
1552
1718
|
try {
|
|
1553
1719
|
const parsed = JSON.parse(trimmed);
|
|
1554
|
-
return typeof parsed.pid ===
|
|
1720
|
+
return typeof parsed.pid === "number" &&
|
|
1721
|
+
Number.isFinite(parsed.pid) &&
|
|
1722
|
+
parsed.pid > 0
|
|
1723
|
+
? parsed.pid
|
|
1724
|
+
: null;
|
|
1555
1725
|
}
|
|
1556
1726
|
catch {
|
|
1557
1727
|
const pid = Number.parseInt(trimmed, 10);
|
|
1558
1728
|
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
1559
1729
|
}
|
|
1560
1730
|
}
|
|
1561
|
-
function tryKillPid(pid, signal =
|
|
1731
|
+
function tryKillPid(pid, signal = "SIGTERM") {
|
|
1562
1732
|
try {
|
|
1563
1733
|
process.kill(pid, signal);
|
|
1564
1734
|
return true;
|
|
1565
1735
|
}
|
|
1566
1736
|
catch (error) {
|
|
1567
1737
|
const code = error.code;
|
|
1568
|
-
if (code ===
|
|
1738
|
+
if (code === "ESRCH")
|
|
1569
1739
|
return false;
|
|
1570
1740
|
throw error;
|
|
1571
1741
|
}
|
|
1572
1742
|
}
|
|
1573
1743
|
async function startNotifyFallbackWatcher(cwd) {
|
|
1574
|
-
if (process.env.OMX_NOTIFY_FALLBACK ===
|
|
1744
|
+
if (process.env.OMX_NOTIFY_FALLBACK === "0")
|
|
1575
1745
|
return;
|
|
1576
|
-
const { mkdir, writeFile, readFile } = await import(
|
|
1746
|
+
const { mkdir, writeFile, readFile } = await import("fs/promises");
|
|
1577
1747
|
const pidPath = notifyFallbackPidPath(cwd);
|
|
1578
1748
|
const pkgRoot = getPackageRoot();
|
|
1579
|
-
const watcherScript = join(pkgRoot,
|
|
1580
|
-
const notifyScript = join(pkgRoot,
|
|
1749
|
+
const watcherScript = join(pkgRoot, "scripts", "notify-fallback-watcher.js");
|
|
1750
|
+
const notifyScript = join(pkgRoot, "scripts", "notify-hook.js");
|
|
1581
1751
|
if (!existsSync(watcherScript) || !existsSync(notifyScript))
|
|
1582
1752
|
return;
|
|
1583
1753
|
// Stop stale watcher from a previous run.
|
|
1584
1754
|
if (existsSync(pidPath)) {
|
|
1585
1755
|
try {
|
|
1586
|
-
const prevPid = parseWatcherPidFile(await readFile(pidPath,
|
|
1756
|
+
const prevPid = parseWatcherPidFile(await readFile(pidPath, "utf-8"));
|
|
1587
1757
|
if (prevPid) {
|
|
1588
|
-
tryKillPid(prevPid,
|
|
1758
|
+
tryKillPid(prevPid, "SIGTERM");
|
|
1589
1759
|
}
|
|
1590
1760
|
}
|
|
1591
1761
|
catch (error) {
|
|
1592
|
-
if (!hasErrnoCode(error,
|
|
1593
|
-
console.warn(
|
|
1762
|
+
if (!hasErrnoCode(error, "ESRCH")) {
|
|
1763
|
+
console.warn("[omx] warning: failed to stop stale notify fallback watcher", {
|
|
1594
1764
|
path: pidPath,
|
|
1595
1765
|
error: error instanceof Error ? error.message : String(error),
|
|
1596
1766
|
});
|
|
1597
1767
|
}
|
|
1598
1768
|
}
|
|
1599
1769
|
}
|
|
1600
|
-
await mkdir(join(cwd,
|
|
1601
|
-
console.warn(
|
|
1770
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true }).catch((error) => {
|
|
1771
|
+
console.warn("[omx] warning: failed to create notify fallback watcher state directory", {
|
|
1602
1772
|
cwd,
|
|
1603
1773
|
error: error instanceof Error ? error.message : String(error),
|
|
1604
1774
|
});
|
|
1605
1775
|
});
|
|
1606
1776
|
const child = spawn(process.execPath, [
|
|
1607
1777
|
watcherScript,
|
|
1608
|
-
|
|
1778
|
+
"--cwd",
|
|
1609
1779
|
cwd,
|
|
1610
|
-
|
|
1780
|
+
"--notify-script",
|
|
1611
1781
|
notifyScript,
|
|
1612
|
-
|
|
1782
|
+
"--pid-file",
|
|
1613
1783
|
pidPath,
|
|
1614
|
-
|
|
1784
|
+
"--parent-pid",
|
|
1615
1785
|
String(process.pid),
|
|
1616
1786
|
...(process.env.OMX_NOTIFY_FALLBACK_MAX_LIFETIME_MS
|
|
1617
|
-
? [
|
|
1787
|
+
? ["--max-lifetime-ms", process.env.OMX_NOTIFY_FALLBACK_MAX_LIFETIME_MS]
|
|
1618
1788
|
: []),
|
|
1619
1789
|
], {
|
|
1620
1790
|
cwd,
|
|
1621
1791
|
detached: true,
|
|
1622
|
-
stdio:
|
|
1792
|
+
stdio: "ignore",
|
|
1623
1793
|
});
|
|
1624
1794
|
child.unref();
|
|
1625
1795
|
await writeFile(pidPath, JSON.stringify({ pid: child.pid, started_at: new Date().toISOString() }, null, 2)).catch((error) => {
|
|
1626
|
-
console.warn(
|
|
1796
|
+
console.warn("[omx] warning: failed to write notify fallback watcher pid file", {
|
|
1627
1797
|
path: pidPath,
|
|
1628
1798
|
error: error instanceof Error ? error.message : String(error),
|
|
1629
1799
|
});
|
|
1630
1800
|
});
|
|
1631
1801
|
}
|
|
1632
1802
|
async function startHookDerivedWatcher(cwd) {
|
|
1633
|
-
if (process.env.OMX_HOOK_DERIVED_SIGNALS !==
|
|
1803
|
+
if (process.env.OMX_HOOK_DERIVED_SIGNALS !== "1")
|
|
1634
1804
|
return;
|
|
1635
|
-
const { mkdir, writeFile, readFile } = await import(
|
|
1805
|
+
const { mkdir, writeFile, readFile } = await import("fs/promises");
|
|
1636
1806
|
const pidPath = hookDerivedWatcherPidPath(cwd);
|
|
1637
1807
|
const pkgRoot = getPackageRoot();
|
|
1638
|
-
const watcherScript = join(pkgRoot,
|
|
1808
|
+
const watcherScript = join(pkgRoot, "scripts", "hook-derived-watcher.js");
|
|
1639
1809
|
if (!existsSync(watcherScript))
|
|
1640
1810
|
return;
|
|
1641
1811
|
if (existsSync(pidPath)) {
|
|
1642
1812
|
try {
|
|
1643
|
-
const prev = JSON.parse(await readFile(pidPath,
|
|
1644
|
-
if (prev && typeof prev.pid ===
|
|
1645
|
-
process.kill(prev.pid,
|
|
1813
|
+
const prev = JSON.parse(await readFile(pidPath, "utf-8"));
|
|
1814
|
+
if (prev && typeof prev.pid === "number") {
|
|
1815
|
+
process.kill(prev.pid, "SIGTERM");
|
|
1646
1816
|
}
|
|
1647
1817
|
}
|
|
1648
1818
|
catch (error) {
|
|
1649
|
-
console.warn(
|
|
1819
|
+
console.warn("[omx] warning: failed to stop stale hook-derived watcher", {
|
|
1650
1820
|
path: pidPath,
|
|
1651
1821
|
error: error instanceof Error ? error.message : String(error),
|
|
1652
1822
|
});
|
|
1653
1823
|
}
|
|
1654
1824
|
}
|
|
1655
|
-
await mkdir(join(cwd,
|
|
1656
|
-
console.warn(
|
|
1825
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true }).catch((error) => {
|
|
1826
|
+
console.warn("[omx] warning: failed to create hook-derived watcher state directory", {
|
|
1657
1827
|
cwd,
|
|
1658
1828
|
error: error instanceof Error ? error.message : String(error),
|
|
1659
1829
|
});
|
|
1660
1830
|
});
|
|
1661
|
-
const child = spawn(process.execPath, [watcherScript,
|
|
1831
|
+
const child = spawn(process.execPath, [watcherScript, "--cwd", cwd], {
|
|
1662
1832
|
cwd,
|
|
1663
1833
|
detached: true,
|
|
1664
|
-
stdio:
|
|
1834
|
+
stdio: "ignore",
|
|
1665
1835
|
env: process.env,
|
|
1666
1836
|
});
|
|
1667
1837
|
child.unref();
|
|
1668
1838
|
await writeFile(pidPath, JSON.stringify({ pid: child.pid, started_at: new Date().toISOString() }, null, 2)).catch((error) => {
|
|
1669
|
-
console.warn(
|
|
1839
|
+
console.warn("[omx] warning: failed to write hook-derived watcher pid file", {
|
|
1670
1840
|
path: pidPath,
|
|
1671
1841
|
error: error instanceof Error ? error.message : String(error),
|
|
1672
1842
|
});
|
|
1673
1843
|
});
|
|
1674
1844
|
}
|
|
1675
1845
|
async function stopNotifyFallbackWatcher(cwd) {
|
|
1676
|
-
const { readFile, unlink } = await import(
|
|
1846
|
+
const { readFile, unlink } = await import("fs/promises");
|
|
1677
1847
|
const pidPath = notifyFallbackPidPath(cwd);
|
|
1678
1848
|
if (!existsSync(pidPath))
|
|
1679
1849
|
return;
|
|
1680
1850
|
try {
|
|
1681
|
-
const pid = parseWatcherPidFile(await readFile(pidPath,
|
|
1851
|
+
const pid = parseWatcherPidFile(await readFile(pidPath, "utf-8"));
|
|
1682
1852
|
if (pid) {
|
|
1683
|
-
tryKillPid(pid,
|
|
1853
|
+
tryKillPid(pid, "SIGTERM");
|
|
1684
1854
|
}
|
|
1685
1855
|
}
|
|
1686
1856
|
catch (error) {
|
|
1687
|
-
if (!hasErrnoCode(error,
|
|
1688
|
-
console.warn(
|
|
1857
|
+
if (!hasErrnoCode(error, "ESRCH")) {
|
|
1858
|
+
console.warn("[omx] warning: failed to stop notify fallback watcher process", {
|
|
1689
1859
|
path: pidPath,
|
|
1690
1860
|
error: error instanceof Error ? error.message : String(error),
|
|
1691
1861
|
});
|
|
1692
1862
|
}
|
|
1693
1863
|
}
|
|
1694
1864
|
await unlink(pidPath).catch((error) => {
|
|
1695
|
-
console.warn(
|
|
1865
|
+
console.warn("[omx] warning: failed to remove notify fallback watcher pid file", {
|
|
1696
1866
|
path: pidPath,
|
|
1697
1867
|
error: error instanceof Error ? error.message : String(error),
|
|
1698
1868
|
});
|
|
1699
1869
|
});
|
|
1700
1870
|
}
|
|
1701
1871
|
async function stopHookDerivedWatcher(cwd) {
|
|
1702
|
-
const { readFile, unlink } = await import(
|
|
1872
|
+
const { readFile, unlink } = await import("fs/promises");
|
|
1703
1873
|
const pidPath = hookDerivedWatcherPidPath(cwd);
|
|
1704
1874
|
if (!existsSync(pidPath))
|
|
1705
1875
|
return;
|
|
1706
1876
|
try {
|
|
1707
|
-
const parsed = JSON.parse(await readFile(pidPath,
|
|
1708
|
-
if (parsed && typeof parsed.pid ===
|
|
1709
|
-
process.kill(parsed.pid,
|
|
1877
|
+
const parsed = JSON.parse(await readFile(pidPath, "utf-8"));
|
|
1878
|
+
if (parsed && typeof parsed.pid === "number") {
|
|
1879
|
+
process.kill(parsed.pid, "SIGTERM");
|
|
1710
1880
|
}
|
|
1711
1881
|
}
|
|
1712
1882
|
catch (error) {
|
|
1713
|
-
console.warn(
|
|
1883
|
+
console.warn("[omx] warning: failed to stop hook-derived watcher process", {
|
|
1714
1884
|
path: pidPath,
|
|
1715
1885
|
error: error instanceof Error ? error.message : String(error),
|
|
1716
1886
|
});
|
|
1717
1887
|
}
|
|
1718
1888
|
await unlink(pidPath).catch((error) => {
|
|
1719
|
-
console.warn(
|
|
1889
|
+
console.warn("[omx] warning: failed to remove hook-derived watcher pid file", {
|
|
1720
1890
|
path: pidPath,
|
|
1721
1891
|
error: error instanceof Error ? error.message : String(error),
|
|
1722
1892
|
});
|
|
1723
1893
|
});
|
|
1724
1894
|
}
|
|
1725
1895
|
async function flushNotifyFallbackOnce(cwd) {
|
|
1726
|
-
const { spawnSync } = await import(
|
|
1896
|
+
const { spawnSync } = await import("child_process");
|
|
1727
1897
|
const pkgRoot = getPackageRoot();
|
|
1728
|
-
const watcherScript = join(pkgRoot,
|
|
1729
|
-
const notifyScript = join(pkgRoot,
|
|
1898
|
+
const watcherScript = join(pkgRoot, "scripts", "notify-fallback-watcher.js");
|
|
1899
|
+
const notifyScript = join(pkgRoot, "scripts", "notify-hook.js");
|
|
1730
1900
|
if (!existsSync(watcherScript) || !existsSync(notifyScript))
|
|
1731
1901
|
return;
|
|
1732
|
-
spawnSync(process.execPath, [watcherScript,
|
|
1902
|
+
spawnSync(process.execPath, [watcherScript, "--once", "--cwd", cwd, "--notify-script", notifyScript], {
|
|
1733
1903
|
cwd,
|
|
1734
|
-
stdio:
|
|
1904
|
+
stdio: "ignore",
|
|
1735
1905
|
timeout: 3000,
|
|
1736
1906
|
});
|
|
1737
1907
|
}
|
|
1738
1908
|
async function flushHookDerivedWatcherOnce(cwd) {
|
|
1739
|
-
if (process.env.OMX_HOOK_DERIVED_SIGNALS !==
|
|
1909
|
+
if (process.env.OMX_HOOK_DERIVED_SIGNALS !== "1")
|
|
1740
1910
|
return;
|
|
1741
|
-
const { spawnSync } = await import(
|
|
1911
|
+
const { spawnSync } = await import("child_process");
|
|
1742
1912
|
const pkgRoot = getPackageRoot();
|
|
1743
|
-
const watcherScript = join(pkgRoot,
|
|
1913
|
+
const watcherScript = join(pkgRoot, "scripts", "hook-derived-watcher.js");
|
|
1744
1914
|
if (!existsSync(watcherScript))
|
|
1745
1915
|
return;
|
|
1746
|
-
spawnSync(process.execPath, [watcherScript,
|
|
1916
|
+
spawnSync(process.execPath, [watcherScript, "--once", "--cwd", cwd], {
|
|
1747
1917
|
cwd,
|
|
1748
|
-
stdio:
|
|
1918
|
+
stdio: "ignore",
|
|
1749
1919
|
timeout: 3000,
|
|
1750
1920
|
env: {
|
|
1751
1921
|
...process.env,
|
|
1752
|
-
OMX_HOOK_DERIVED_SIGNALS:
|
|
1922
|
+
OMX_HOOK_DERIVED_SIGNALS: "1",
|
|
1753
1923
|
},
|
|
1754
1924
|
});
|
|
1755
1925
|
}
|
|
1756
1926
|
async function cancelModes() {
|
|
1757
|
-
const { writeFile, readFile } = await import(
|
|
1927
|
+
const { writeFile, readFile } = await import("fs/promises");
|
|
1758
1928
|
const cwd = process.cwd();
|
|
1759
1929
|
const nowIso = new Date().toISOString();
|
|
1760
1930
|
try {
|
|
1761
1931
|
const refs = await listModeStateFilesWithScopePreference(cwd);
|
|
1762
1932
|
const states = new Map();
|
|
1763
1933
|
for (const ref of refs) {
|
|
1764
|
-
const content = await readFile(ref.path,
|
|
1934
|
+
const content = await readFile(ref.path, "utf-8");
|
|
1765
1935
|
let parsedState;
|
|
1766
1936
|
try {
|
|
1767
1937
|
parsedState = JSON.parse(content);
|
|
@@ -1778,15 +1948,15 @@ async function cancelModes() {
|
|
|
1778
1948
|
}
|
|
1779
1949
|
const changed = new Set();
|
|
1780
1950
|
const reported = new Set();
|
|
1781
|
-
const cancelMode = (mode, phase =
|
|
1951
|
+
const cancelMode = (mode, phase = "cancelled", reportIfWasActive = true) => {
|
|
1782
1952
|
const entry = states.get(mode);
|
|
1783
1953
|
if (!entry)
|
|
1784
1954
|
return;
|
|
1785
1955
|
const wasActive = entry.state.active === true;
|
|
1786
|
-
const needsChange = entry.state.active !== false
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1956
|
+
const needsChange = entry.state.active !== false ||
|
|
1957
|
+
entry.state.current_phase !== phase ||
|
|
1958
|
+
typeof entry.state.completed_at !== "string" ||
|
|
1959
|
+
String(entry.state.completed_at).trim() === "";
|
|
1790
1960
|
if (!needsChange)
|
|
1791
1961
|
return;
|
|
1792
1962
|
entry.state.active = false;
|
|
@@ -1797,30 +1967,32 @@ async function cancelModes() {
|
|
|
1797
1967
|
if (reportIfWasActive && wasActive)
|
|
1798
1968
|
reported.add(mode);
|
|
1799
1969
|
};
|
|
1800
|
-
const ralphLinksUltrawork = (state) => state.linked_ultrawork === true || state.linked_mode ===
|
|
1801
|
-
const team = states.get(
|
|
1802
|
-
const ralph = states.get(
|
|
1970
|
+
const ralphLinksUltrawork = (state) => state.linked_ultrawork === true || state.linked_mode === "ultrawork";
|
|
1971
|
+
const team = states.get("team");
|
|
1972
|
+
const ralph = states.get("ralph");
|
|
1803
1973
|
const hadActiveRalph = !!(ralph && ralph.state.active === true);
|
|
1804
|
-
if (team &&
|
|
1805
|
-
|
|
1974
|
+
if (team &&
|
|
1975
|
+
team.state.active === true &&
|
|
1976
|
+
team.state.linked_ralph === true) {
|
|
1977
|
+
cancelMode("team", "cancelled", true);
|
|
1806
1978
|
if (ralph && ralph.state.linked_team === true) {
|
|
1807
|
-
cancelMode(
|
|
1808
|
-
ralph.state.linked_team_terminal_phase =
|
|
1979
|
+
cancelMode("ralph", "cancelled", true);
|
|
1980
|
+
ralph.state.linked_team_terminal_phase = "cancelled";
|
|
1809
1981
|
ralph.state.linked_team_terminal_at = nowIso;
|
|
1810
|
-
changed.add(
|
|
1982
|
+
changed.add("ralph");
|
|
1811
1983
|
if (ralphLinksUltrawork(ralph.state))
|
|
1812
|
-
cancelMode(
|
|
1984
|
+
cancelMode("ultrawork", "cancelled", true);
|
|
1813
1985
|
}
|
|
1814
1986
|
}
|
|
1815
1987
|
if (ralph && ralph.state.active === true) {
|
|
1816
|
-
cancelMode(
|
|
1988
|
+
cancelMode("ralph", "cancelled", true);
|
|
1817
1989
|
if (ralphLinksUltrawork(ralph.state))
|
|
1818
|
-
cancelMode(
|
|
1990
|
+
cancelMode("ultrawork", "cancelled", true);
|
|
1819
1991
|
}
|
|
1820
1992
|
if (!hadActiveRalph) {
|
|
1821
1993
|
for (const [mode, entry] of states.entries()) {
|
|
1822
1994
|
if (entry.state.active === true)
|
|
1823
|
-
cancelMode(mode,
|
|
1995
|
+
cancelMode(mode, "cancelled", true);
|
|
1824
1996
|
}
|
|
1825
1997
|
}
|
|
1826
1998
|
for (const [mode, entry] of states.entries()) {
|
|
@@ -1832,12 +2004,12 @@ async function cancelModes() {
|
|
|
1832
2004
|
console.log(`Cancelled: ${mode}`);
|
|
1833
2005
|
}
|
|
1834
2006
|
if (reported.size === 0) {
|
|
1835
|
-
console.log(
|
|
2007
|
+
console.log("No active modes to cancel.");
|
|
1836
2008
|
}
|
|
1837
2009
|
}
|
|
1838
2010
|
catch (err) {
|
|
1839
2011
|
process.stderr.write(`[cli/index] operation failed: ${err}\n`);
|
|
1840
|
-
console.log(
|
|
2012
|
+
console.log("No active modes to cancel.");
|
|
1841
2013
|
}
|
|
1842
2014
|
}
|
|
1843
2015
|
//# sourceMappingURL=index.js.map
|