oh-my-codex 0.10.2 → 0.10.3
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/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/__tests__/skill-bridge.test.d.ts +2 -0
- package/dist/agents/__tests__/skill-bridge.test.d.ts.map +1 -0
- package/dist/agents/__tests__/skill-bridge.test.js +71 -0
- package/dist/agents/__tests__/skill-bridge.test.js.map +1 -0
- 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/agents/skill-bridge.d.ts +20 -0
- package/dist/agents/skill-bridge.d.ts.map +1 -0
- package/dist/agents/skill-bridge.js +150 -0
- package/dist/agents/skill-bridge.js.map +1 -0
- 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 +71 -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 +195 -24
- 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 +521 -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__/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 +60 -0
- package/dist/cli/autoresearch-intake.d.ts.map +1 -0
- package/dist/cli/autoresearch-intake.js +318 -0
- package/dist/cli/autoresearch-intake.js.map +1 -0
- package/dist/cli/autoresearch.d.ts +3 -1
- package/dist/cli/autoresearch.d.ts.map +1 -1
- package/dist/cli/autoresearch.js +64 -10
- 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 +610 -427
- 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__/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/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/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 +355 -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 +104 -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__/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/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/deep-interview/SKILL.md +30 -1
- package/skills/omx-setup/SKILL.md +2 -2
- 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/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, isSessionStale, 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) {
|
|
@@ -976,87 +1038,146 @@ function detectDetachedSessionWindowIndex(sessionName) {
|
|
|
976
1038
|
}
|
|
977
1039
|
export function buildDetachedSessionBootstrapSteps(sessionName, cwd, codexCmd, hudCmd, workerLaunchArgs, codexHomeOverride, notifyTempContractRaw, nativeWindows = false) {
|
|
978
1040
|
const newSessionArgs = [
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1041
|
+
"new-session",
|
|
1042
|
+
"-d",
|
|
1043
|
+
"-P",
|
|
1044
|
+
"-F",
|
|
1045
|
+
"#{pane_id}",
|
|
1046
|
+
"-s",
|
|
1047
|
+
sessionName,
|
|
1048
|
+
"-c",
|
|
1049
|
+
cwd,
|
|
1050
|
+
...(workerLaunchArgs
|
|
1051
|
+
? ["-e", `${TEAM_WORKER_LAUNCH_ARGS_ENV}=${workerLaunchArgs}`]
|
|
1052
|
+
: []),
|
|
1053
|
+
...(codexHomeOverride ? ["-e", `CODEX_HOME=${codexHomeOverride}`] : []),
|
|
1054
|
+
...(notifyTempContractRaw
|
|
1055
|
+
? ["-e", `${OMX_NOTIFY_TEMP_CONTRACT_ENV}=${notifyTempContractRaw}`]
|
|
1056
|
+
: []),
|
|
1057
|
+
nativeWindows ? "powershell.exe" : codexCmd,
|
|
984
1058
|
];
|
|
985
1059
|
const splitCaptureArgs = [
|
|
986
|
-
|
|
987
|
-
|
|
1060
|
+
"split-window",
|
|
1061
|
+
"-v",
|
|
1062
|
+
"-l",
|
|
1063
|
+
String(HUD_TMUX_HEIGHT_LINES),
|
|
1064
|
+
"-d",
|
|
1065
|
+
"-t",
|
|
1066
|
+
sessionName,
|
|
1067
|
+
"-c",
|
|
1068
|
+
cwd,
|
|
1069
|
+
"-P",
|
|
1070
|
+
"-F",
|
|
1071
|
+
"#{pane_id}",
|
|
1072
|
+
hudCmd,
|
|
988
1073
|
];
|
|
989
1074
|
return [
|
|
990
|
-
{ name:
|
|
991
|
-
{ name:
|
|
1075
|
+
{ name: "new-session", args: newSessionArgs },
|
|
1076
|
+
{ name: "split-and-capture-hud-pane", args: splitCaptureArgs },
|
|
992
1077
|
];
|
|
993
1078
|
}
|
|
1079
|
+
async function updateActiveRalphthonLaunchTarget(cwd, patch) {
|
|
1080
|
+
const state = await readModeState("ralphthon", cwd).catch(() => null);
|
|
1081
|
+
if (!state || state.active !== true)
|
|
1082
|
+
return;
|
|
1083
|
+
const next = {};
|
|
1084
|
+
if (typeof patch.leader_pane_id === "string" &&
|
|
1085
|
+
patch.leader_pane_id.trim().startsWith("%")) {
|
|
1086
|
+
next.leader_pane_id = patch.leader_pane_id.trim();
|
|
1087
|
+
next.tmux_pane_id = patch.leader_pane_id.trim();
|
|
1088
|
+
}
|
|
1089
|
+
else if (typeof patch.tmux_pane_id === "string" &&
|
|
1090
|
+
patch.tmux_pane_id.trim().startsWith("%")) {
|
|
1091
|
+
next.tmux_pane_id = patch.tmux_pane_id.trim();
|
|
1092
|
+
}
|
|
1093
|
+
if (typeof patch.tmux_session === "string" &&
|
|
1094
|
+
patch.tmux_session.trim() !== "") {
|
|
1095
|
+
next.tmux_session = patch.tmux_session.trim();
|
|
1096
|
+
}
|
|
1097
|
+
if (Object.keys(next).length === 0)
|
|
1098
|
+
return;
|
|
1099
|
+
await updateModeState("ralphthon", next, cwd).catch(() => { });
|
|
1100
|
+
}
|
|
1101
|
+
async function readLaunchAppendInstructions() {
|
|
1102
|
+
const appendixCandidates = [
|
|
1103
|
+
process.env[OMX_RALPH_APPEND_INSTRUCTIONS_FILE_ENV]?.trim(),
|
|
1104
|
+
process.env[OMX_RALPHTHON_APPEND_INSTRUCTIONS_FILE_ENV]?.trim(),
|
|
1105
|
+
process.env[OMX_AUTORESEARCH_APPEND_INSTRUCTIONS_FILE_ENV]?.trim(),
|
|
1106
|
+
].filter((value) => typeof value === "string" && value.length > 0);
|
|
1107
|
+
if (appendixCandidates.length === 0)
|
|
1108
|
+
return "";
|
|
1109
|
+
const appendixPath = appendixCandidates[0];
|
|
1110
|
+
if (!existsSync(appendixPath)) {
|
|
1111
|
+
throw new Error(`launch instructions file not found: ${appendixPath}`);
|
|
1112
|
+
}
|
|
1113
|
+
const { readFile } = await import("fs/promises");
|
|
1114
|
+
return (await readFile(appendixPath, "utf-8")).trim();
|
|
1115
|
+
}
|
|
994
1116
|
export function buildDetachedSessionFinalizeSteps(sessionName, hudPaneId, hookWindowIndex, enableMouse, nativeWindows = false) {
|
|
995
1117
|
const steps = [];
|
|
996
1118
|
if (!nativeWindows && hudPaneId && hookWindowIndex) {
|
|
997
1119
|
const hookTarget = buildResizeHookTarget(sessionName, hookWindowIndex);
|
|
998
|
-
const hookName = buildResizeHookName(
|
|
999
|
-
const clientAttachedHookName = buildClientAttachedReconcileHookName(
|
|
1120
|
+
const hookName = buildResizeHookName("launch", sessionName, hookWindowIndex, hudPaneId);
|
|
1121
|
+
const clientAttachedHookName = buildClientAttachedReconcileHookName("launch", sessionName, hookWindowIndex, hudPaneId);
|
|
1000
1122
|
steps.push({
|
|
1001
|
-
name:
|
|
1123
|
+
name: "register-resize-hook",
|
|
1002
1124
|
args: buildRegisterResizeHookArgs(hookTarget, hookName, hudPaneId, HUD_TMUX_HEIGHT_LINES),
|
|
1003
1125
|
});
|
|
1004
1126
|
steps.push({
|
|
1005
|
-
name:
|
|
1127
|
+
name: "register-client-attached-reconcile",
|
|
1006
1128
|
args: buildRegisterClientAttachedReconcileArgs(hookTarget, clientAttachedHookName, hudPaneId, HUD_TMUX_HEIGHT_LINES),
|
|
1007
1129
|
});
|
|
1008
1130
|
steps.push({
|
|
1009
|
-
name:
|
|
1131
|
+
name: "schedule-delayed-resize",
|
|
1010
1132
|
args: buildScheduleDelayedHudResizeArgs(hudPaneId, undefined, HUD_TMUX_HEIGHT_LINES),
|
|
1011
1133
|
});
|
|
1012
1134
|
steps.push({
|
|
1013
|
-
name:
|
|
1135
|
+
name: "reconcile-hud-resize",
|
|
1014
1136
|
args: buildReconcileHudResizeArgs(hudPaneId, HUD_TMUX_HEIGHT_LINES),
|
|
1015
1137
|
});
|
|
1016
1138
|
}
|
|
1017
1139
|
if (enableMouse) {
|
|
1018
|
-
steps.push({
|
|
1140
|
+
steps.push({
|
|
1141
|
+
name: "set-mouse",
|
|
1142
|
+
args: ["set-option", "-t", sessionName, "mouse", "on"],
|
|
1143
|
+
});
|
|
1019
1144
|
}
|
|
1020
|
-
steps.push({
|
|
1145
|
+
steps.push({
|
|
1146
|
+
name: "attach-session",
|
|
1147
|
+
args: ["attach-session", "-t", sessionName],
|
|
1148
|
+
});
|
|
1021
1149
|
return steps;
|
|
1022
1150
|
}
|
|
1023
1151
|
export function buildDetachedSessionRollbackSteps(sessionName, hookTarget, hookName, clientAttachedHookName) {
|
|
1024
1152
|
const steps = [];
|
|
1025
1153
|
if (hookTarget && clientAttachedHookName) {
|
|
1026
1154
|
steps.push({
|
|
1027
|
-
name:
|
|
1155
|
+
name: "unregister-client-attached-reconcile",
|
|
1028
1156
|
args: buildUnregisterClientAttachedReconcileArgs(hookTarget, clientAttachedHookName),
|
|
1029
1157
|
});
|
|
1030
1158
|
}
|
|
1031
1159
|
if (hookTarget && hookName) {
|
|
1032
1160
|
steps.push({
|
|
1033
|
-
name:
|
|
1161
|
+
name: "unregister-resize-hook",
|
|
1034
1162
|
args: buildUnregisterResizeHookArgs(hookTarget, hookName),
|
|
1035
1163
|
});
|
|
1036
1164
|
}
|
|
1037
|
-
steps.push({
|
|
1165
|
+
steps.push({
|
|
1166
|
+
name: "kill-session",
|
|
1167
|
+
args: ["kill-session", "-t", sessionName],
|
|
1168
|
+
});
|
|
1038
1169
|
return steps;
|
|
1039
1170
|
}
|
|
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
1171
|
export function buildNotifyTempStartupMessages(contract, hasValidProviders) {
|
|
1051
1172
|
const providers = contract.canonicalSelectors.length > 0
|
|
1052
|
-
? contract.canonicalSelectors.join(
|
|
1053
|
-
:
|
|
1173
|
+
? contract.canonicalSelectors.join(",")
|
|
1174
|
+
: "none";
|
|
1054
1175
|
const infoLines = [
|
|
1055
1176
|
`notify temp: active | providers=${providers} | persistent-routing=bypassed`,
|
|
1056
1177
|
];
|
|
1057
1178
|
const warningLines = [...contract.warnings];
|
|
1058
1179
|
if (!hasValidProviders) {
|
|
1059
|
-
warningLines.push(
|
|
1180
|
+
warningLines.push("notify temp: no valid providers resolved; notifications skipped");
|
|
1060
1181
|
}
|
|
1061
1182
|
return { infoLines, warningLines };
|
|
1062
1183
|
}
|
|
@@ -1076,9 +1197,9 @@ async function preLaunch(cwd, sessionId, notifyTempContract) {
|
|
|
1076
1197
|
catch (err) {
|
|
1077
1198
|
process.stderr.write(`[cli/index] operation failed: ${err}\n`);
|
|
1078
1199
|
}
|
|
1079
|
-
const { unlink } = await import(
|
|
1200
|
+
const { unlink } = await import("fs/promises");
|
|
1080
1201
|
try {
|
|
1081
|
-
await unlink(join(cwd,
|
|
1202
|
+
await unlink(join(cwd, ".omx", "state", "session.json"));
|
|
1082
1203
|
}
|
|
1083
1204
|
catch (err) {
|
|
1084
1205
|
process.stderr.write(`[cli/index] operation failed: ${err}\n`);
|
|
@@ -1087,11 +1208,11 @@ async function preLaunch(cwd, sessionId, notifyTempContract) {
|
|
|
1087
1208
|
// 2. Generate runtime overlay + write session-scoped model instructions file
|
|
1088
1209
|
const orchestrationMode = await resolveSessionOrchestrationMode(cwd, sessionId);
|
|
1089
1210
|
const overlay = await generateOverlay(cwd, sessionId, { orchestrationMode });
|
|
1090
|
-
const
|
|
1091
|
-
const sessionInstructions =
|
|
1211
|
+
const launchAppendix = await readLaunchAppendInstructions();
|
|
1212
|
+
const sessionInstructions = launchAppendix.trim().length > 0
|
|
1092
1213
|
? `${overlay}
|
|
1093
1214
|
|
|
1094
|
-
${
|
|
1215
|
+
${launchAppendix}`
|
|
1095
1216
|
: overlay;
|
|
1096
1217
|
await writeSessionModelInstructionsFile(cwd, sessionId, sessionInstructions);
|
|
1097
1218
|
// 3. Write session state
|
|
@@ -1116,8 +1237,9 @@ ${autoresearchAppendix}`
|
|
|
1116
1237
|
// 6. Emit temp notification startup summary + warnings, then send session-start lifecycle notification (best effort)
|
|
1117
1238
|
try {
|
|
1118
1239
|
if (notifyTempContract?.active) {
|
|
1119
|
-
process.env[OMX_NOTIFY_TEMP_CONTRACT_ENV] =
|
|
1120
|
-
|
|
1240
|
+
process.env[OMX_NOTIFY_TEMP_CONTRACT_ENV] =
|
|
1241
|
+
serializeNotifyTempContract(notifyTempContract);
|
|
1242
|
+
const { getNotificationConfig } = await import("../notifications/config.js");
|
|
1121
1243
|
const resolved = getNotificationConfig();
|
|
1122
1244
|
const startup = buildNotifyTempStartupMessages(notifyTempContract, Boolean(resolved?.enabled));
|
|
1123
1245
|
for (const info of startup.infoLines) {
|
|
@@ -1130,8 +1252,8 @@ ${autoresearchAppendix}`
|
|
|
1130
1252
|
else {
|
|
1131
1253
|
delete process.env[OMX_NOTIFY_TEMP_CONTRACT_ENV];
|
|
1132
1254
|
}
|
|
1133
|
-
const { notifyLifecycle } = await import(
|
|
1134
|
-
await notifyLifecycle(
|
|
1255
|
+
const { notifyLifecycle } = await import("../notifications/index.js");
|
|
1256
|
+
await notifyLifecycle("session-start", {
|
|
1135
1257
|
sessionId,
|
|
1136
1258
|
projectPath: cwd,
|
|
1137
1259
|
projectName: basename(cwd),
|
|
@@ -1143,12 +1265,12 @@ ${autoresearchAppendix}`
|
|
|
1143
1265
|
}
|
|
1144
1266
|
// 7. Dispatch native hook event (best effort)
|
|
1145
1267
|
try {
|
|
1146
|
-
await emitNativeHookEvent(cwd,
|
|
1268
|
+
await emitNativeHookEvent(cwd, "session-start", {
|
|
1147
1269
|
session_id: sessionId,
|
|
1148
|
-
context: buildNativeHookBaseContext(cwd, sessionId,
|
|
1270
|
+
context: buildNativeHookBaseContext(cwd, sessionId, "started", {
|
|
1149
1271
|
project_path: cwd,
|
|
1150
1272
|
project_name: basename(cwd),
|
|
1151
|
-
status:
|
|
1273
|
+
status: "started",
|
|
1152
1274
|
}),
|
|
1153
1275
|
});
|
|
1154
1276
|
}
|
|
@@ -1166,9 +1288,9 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
|
|
|
1166
1288
|
const nativeWindows = isNativeWindows();
|
|
1167
1289
|
const omxBin = process.argv[1];
|
|
1168
1290
|
const hudCmd = nativeWindows
|
|
1169
|
-
? buildWindowsPromptCommand(
|
|
1170
|
-
: buildTmuxPaneCommand(
|
|
1171
|
-
const inheritLeaderFlags = process.env[TEAM_INHERIT_LEADER_FLAGS_ENV] !==
|
|
1291
|
+
? buildWindowsPromptCommand("node", [omxBin, "hud", "--watch"])
|
|
1292
|
+
: buildTmuxPaneCommand("node", [omxBin, "hud", "--watch"]);
|
|
1293
|
+
const inheritLeaderFlags = process.env[TEAM_INHERIT_LEADER_FLAGS_ENV] !== "0";
|
|
1172
1294
|
const workerLaunchArgs = resolveTeamWorkerLaunchArgsEnv(process.env[TEAM_WORKER_LAUNCH_ARGS_ENV], launchArgs, inheritLeaderFlags, workerDefaultModel);
|
|
1173
1295
|
const codexBaseEnv = codexHomeOverride
|
|
1174
1296
|
? { ...process.env, CODEX_HOME: codexHomeOverride }
|
|
@@ -1184,7 +1306,7 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
|
|
|
1184
1306
|
runCodexBlocking(cwd, launchArgs, codexEnvWithNotify);
|
|
1185
1307
|
return;
|
|
1186
1308
|
}
|
|
1187
|
-
if (launchPolicy ===
|
|
1309
|
+
if (launchPolicy === "inside-tmux") {
|
|
1188
1310
|
// Already in tmux: launch codex in current pane, HUD in bottom split
|
|
1189
1311
|
const currentPaneId = process.env.TMUX_PANE;
|
|
1190
1312
|
const staleHudPaneIds = listHudWatchPaneIdsInCurrentWindow(currentPaneId);
|
|
@@ -1202,13 +1324,15 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
|
|
|
1202
1324
|
// Enable mouse scrolling at session start so scroll works before team
|
|
1203
1325
|
// expansion. Previously this was only called from createTeamSession().
|
|
1204
1326
|
// Opt-out: set OMX_MOUSE=0. (closes #128)
|
|
1205
|
-
if (process.env.OMX_MOUSE !==
|
|
1327
|
+
if (process.env.OMX_MOUSE !== "0") {
|
|
1206
1328
|
try {
|
|
1207
1329
|
const tmuxPaneTarget = process.env.TMUX_PANE;
|
|
1208
1330
|
const displayArgs = tmuxPaneTarget
|
|
1209
|
-
? [
|
|
1210
|
-
: [
|
|
1211
|
-
const tmuxSession = execFileSync(
|
|
1331
|
+
? ["display-message", "-p", "-t", tmuxPaneTarget, "#S"]
|
|
1332
|
+
: ["display-message", "-p", "#S"];
|
|
1333
|
+
const tmuxSession = execFileSync("tmux", displayArgs, {
|
|
1334
|
+
encoding: "utf-8",
|
|
1335
|
+
}).trim();
|
|
1212
1336
|
if (tmuxSession)
|
|
1213
1337
|
enableMouseScrolling(tmuxSession);
|
|
1214
1338
|
}
|
|
@@ -1217,6 +1341,22 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
|
|
|
1217
1341
|
// Non-fatal: mouse scrolling is a convenience feature
|
|
1218
1342
|
}
|
|
1219
1343
|
}
|
|
1344
|
+
const activePaneId = process.env.TMUX_PANE?.trim();
|
|
1345
|
+
if (activePaneId) {
|
|
1346
|
+
let tmuxSessionName;
|
|
1347
|
+
try {
|
|
1348
|
+
const displayArgs = ["display-message", "-p", "-t", activePaneId, "#S"];
|
|
1349
|
+
tmuxSessionName =
|
|
1350
|
+
execFileSync("tmux", displayArgs, { encoding: "utf-8" }).trim() ||
|
|
1351
|
+
undefined;
|
|
1352
|
+
}
|
|
1353
|
+
catch { }
|
|
1354
|
+
void updateActiveRalphthonLaunchTarget(cwd, {
|
|
1355
|
+
leader_pane_id: activePaneId,
|
|
1356
|
+
tmux_pane_id: activePaneId,
|
|
1357
|
+
tmux_session: tmuxSessionName ?? null,
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1220
1360
|
try {
|
|
1221
1361
|
runCodexBlocking(cwd, launchArgs, codexEnvWithNotify);
|
|
1222
1362
|
}
|
|
@@ -1227,62 +1367,77 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
|
|
|
1227
1367
|
}
|
|
1228
1368
|
}
|
|
1229
1369
|
}
|
|
1230
|
-
else if (launchPolicy ===
|
|
1370
|
+
else if (launchPolicy === "direct") {
|
|
1231
1371
|
// Detached HUD sessions require tmux. Skip the bootstrap entirely when the
|
|
1232
1372
|
// binary is unavailable so direct launches do not emit noisy ENOENT logs.
|
|
1233
1373
|
runCodexBlocking(cwd, launchArgs, codexEnvWithNotify);
|
|
1234
1374
|
}
|
|
1235
1375
|
else {
|
|
1236
1376
|
// Not in tmux: create a new tmux session with codex + HUD pane
|
|
1237
|
-
const codexCmd = buildTmuxPaneCommand(
|
|
1377
|
+
const codexCmd = buildTmuxPaneCommand("codex", launchArgs);
|
|
1238
1378
|
const detachedWindowsCodexCmd = nativeWindows
|
|
1239
|
-
? buildWindowsPromptCommand(
|
|
1379
|
+
? buildWindowsPromptCommand("codex", launchArgs)
|
|
1240
1380
|
: null;
|
|
1241
1381
|
const tmuxSessionId = `omx-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1242
1382
|
const sessionName = buildTmuxSessionName(cwd, tmuxSessionId);
|
|
1243
1383
|
let createdDetachedSession = false;
|
|
1384
|
+
let detachedLeaderPaneId = null;
|
|
1244
1385
|
let registeredHookTarget = null;
|
|
1245
1386
|
let registeredHookName = null;
|
|
1246
1387
|
let registeredClientAttachedHookName = null;
|
|
1247
1388
|
try {
|
|
1248
1389
|
const bootstrapSteps = buildDetachedSessionBootstrapSteps(sessionName, cwd, codexCmd, hudCmd, workerLaunchArgs, codexHomeOverride, notifyTempContractRaw, nativeWindows);
|
|
1249
1390
|
for (const step of bootstrapSteps) {
|
|
1250
|
-
const output = execFileSync(
|
|
1251
|
-
|
|
1391
|
+
const output = execFileSync("tmux", step.args, {
|
|
1392
|
+
stdio: "pipe",
|
|
1393
|
+
encoding: "utf-8",
|
|
1394
|
+
});
|
|
1395
|
+
if (step.name === "new-session") {
|
|
1252
1396
|
createdDetachedSession = true;
|
|
1397
|
+
detachedLeaderPaneId = parsePaneIdFromTmuxOutput(output || "");
|
|
1398
|
+
void updateActiveRalphthonLaunchTarget(cwd, {
|
|
1399
|
+
leader_pane_id: detachedLeaderPaneId,
|
|
1400
|
+
tmux_pane_id: detachedLeaderPaneId,
|
|
1401
|
+
tmux_session: sessionName,
|
|
1402
|
+
});
|
|
1253
1403
|
}
|
|
1254
|
-
if (step.name ===
|
|
1255
|
-
const hudPaneId = parsePaneIdFromTmuxOutput(output ||
|
|
1256
|
-
const hookWindowIndex = hudPaneId
|
|
1404
|
+
if (step.name === "split-and-capture-hud-pane") {
|
|
1405
|
+
const hudPaneId = parsePaneIdFromTmuxOutput(output || "");
|
|
1406
|
+
const hookWindowIndex = hudPaneId
|
|
1407
|
+
? detectDetachedSessionWindowIndex(sessionName)
|
|
1408
|
+
: null;
|
|
1257
1409
|
const hookTarget = hudPaneId && hookWindowIndex
|
|
1258
1410
|
? buildResizeHookTarget(sessionName, hookWindowIndex)
|
|
1259
1411
|
: null;
|
|
1260
1412
|
const hookName = hudPaneId && hookWindowIndex
|
|
1261
|
-
? buildResizeHookName(
|
|
1413
|
+
? buildResizeHookName("launch", sessionName, hookWindowIndex, hudPaneId)
|
|
1262
1414
|
: null;
|
|
1263
1415
|
const clientAttachedHookName = hudPaneId && hookWindowIndex
|
|
1264
|
-
? buildClientAttachedReconcileHookName(
|
|
1416
|
+
? buildClientAttachedReconcileHookName("launch", sessionName, hookWindowIndex, hudPaneId)
|
|
1265
1417
|
: null;
|
|
1266
|
-
const finalizeSteps = buildDetachedSessionFinalizeSteps(sessionName, hudPaneId, hookWindowIndex, process.env.OMX_MOUSE !==
|
|
1418
|
+
const finalizeSteps = buildDetachedSessionFinalizeSteps(sessionName, hudPaneId, hookWindowIndex, process.env.OMX_MOUSE !== "0", nativeWindows);
|
|
1267
1419
|
if (nativeWindows && detachedWindowsCodexCmd) {
|
|
1268
1420
|
scheduleDetachedWindowsCodexLaunch(sessionName, detachedWindowsCodexCmd);
|
|
1269
1421
|
}
|
|
1270
1422
|
for (const finalizeStep of finalizeSteps) {
|
|
1271
|
-
const stdio = finalizeStep.name ===
|
|
1423
|
+
const stdio = finalizeStep.name === "attach-session" ? "inherit" : "ignore";
|
|
1272
1424
|
try {
|
|
1273
|
-
execFileSync(
|
|
1425
|
+
execFileSync("tmux", finalizeStep.args, { stdio });
|
|
1274
1426
|
}
|
|
1275
1427
|
catch (err) {
|
|
1276
1428
|
process.stderr.write(`[cli/index] operation failed: ${err}\n`);
|
|
1277
|
-
if (finalizeStep.name ===
|
|
1278
|
-
throw new Error(
|
|
1429
|
+
if (finalizeStep.name === "attach-session")
|
|
1430
|
+
throw new Error("failed to attach detached tmux session");
|
|
1279
1431
|
continue;
|
|
1280
1432
|
}
|
|
1281
|
-
if (finalizeStep.name ===
|
|
1433
|
+
if (finalizeStep.name === "register-resize-hook" &&
|
|
1434
|
+
hookTarget &&
|
|
1435
|
+
hookName) {
|
|
1282
1436
|
registeredHookTarget = hookTarget;
|
|
1283
1437
|
registeredHookName = hookName;
|
|
1284
1438
|
}
|
|
1285
|
-
if (finalizeStep.name ===
|
|
1439
|
+
if (finalizeStep.name === "register-client-attached-reconcile" &&
|
|
1440
|
+
clientAttachedHookName) {
|
|
1286
1441
|
registeredClientAttachedHookName = clientAttachedHookName;
|
|
1287
1442
|
}
|
|
1288
1443
|
}
|
|
@@ -1295,7 +1450,7 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
|
|
|
1295
1450
|
const rollbackSteps = buildDetachedSessionRollbackSteps(sessionName, registeredHookTarget, registeredHookName, registeredClientAttachedHookName);
|
|
1296
1451
|
for (const rollbackStep of rollbackSteps) {
|
|
1297
1452
|
try {
|
|
1298
|
-
execFileSync(
|
|
1453
|
+
execFileSync("tmux", rollbackStep.args, { stdio: "ignore" });
|
|
1299
1454
|
}
|
|
1300
1455
|
catch (err) {
|
|
1301
1456
|
process.stderr.write(`[cli/index] operation failed: ${err}\n`);
|
|
@@ -1310,7 +1465,11 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
|
|
|
1310
1465
|
}
|
|
1311
1466
|
function listHudWatchPaneIdsInCurrentWindow(currentPaneId) {
|
|
1312
1467
|
try {
|
|
1313
|
-
const output = execFileSync(
|
|
1468
|
+
const output = execFileSync("tmux", [
|
|
1469
|
+
"list-panes",
|
|
1470
|
+
"-F",
|
|
1471
|
+
"#{pane_id}\t#{pane_current_command}\t#{pane_start_command}",
|
|
1472
|
+
], { encoding: "utf-8" });
|
|
1314
1473
|
return findHudWatchPaneIds(parseTmuxPaneSnapshot(output), currentPaneId);
|
|
1315
1474
|
}
|
|
1316
1475
|
catch (err) {
|
|
@@ -1319,14 +1478,26 @@ function listHudWatchPaneIdsInCurrentWindow(currentPaneId) {
|
|
|
1319
1478
|
}
|
|
1320
1479
|
}
|
|
1321
1480
|
function createHudWatchPane(cwd, hudCmd) {
|
|
1322
|
-
const output = execFileSync(
|
|
1481
|
+
const output = execFileSync("tmux", [
|
|
1482
|
+
"split-window",
|
|
1483
|
+
"-v",
|
|
1484
|
+
"-l",
|
|
1485
|
+
String(HUD_TMUX_HEIGHT_LINES),
|
|
1486
|
+
"-d",
|
|
1487
|
+
"-c",
|
|
1488
|
+
cwd,
|
|
1489
|
+
"-P",
|
|
1490
|
+
"-F",
|
|
1491
|
+
"#{pane_id}",
|
|
1492
|
+
hudCmd,
|
|
1493
|
+
], { encoding: "utf-8" });
|
|
1323
1494
|
return parsePaneIdFromTmuxOutput(output);
|
|
1324
1495
|
}
|
|
1325
1496
|
function killTmuxPane(paneId) {
|
|
1326
|
-
if (!paneId.startsWith(
|
|
1497
|
+
if (!paneId.startsWith("%"))
|
|
1327
1498
|
return;
|
|
1328
1499
|
try {
|
|
1329
|
-
execFileSync(
|
|
1500
|
+
execFileSync("tmux", ["kill-pane", "-t", paneId], { stdio: "ignore" });
|
|
1330
1501
|
}
|
|
1331
1502
|
catch (err) {
|
|
1332
1503
|
process.stderr.write(`[cli/index] operation failed: ${err}\n`);
|
|
@@ -1334,20 +1505,24 @@ function killTmuxPane(paneId) {
|
|
|
1334
1505
|
}
|
|
1335
1506
|
}
|
|
1336
1507
|
export function buildTmuxShellCommand(command, args) {
|
|
1337
|
-
return [quoteShellArg(command), ...args.map(quoteShellArg)].join(
|
|
1508
|
+
return [quoteShellArg(command), ...args.map(quoteShellArg)].join(" ");
|
|
1338
1509
|
}
|
|
1339
1510
|
function encodePowerShellCommand(commandText) {
|
|
1340
|
-
return Buffer.from(commandText,
|
|
1511
|
+
return Buffer.from(commandText, "utf16le").toString("base64");
|
|
1341
1512
|
}
|
|
1342
1513
|
function isCodexVersionRequest(args) {
|
|
1343
1514
|
return args.some((arg) => CODEX_VERSION_FLAGS.has(arg));
|
|
1344
1515
|
}
|
|
1345
1516
|
export function buildWindowsPromptCommand(command, args) {
|
|
1346
|
-
const invocation = [
|
|
1517
|
+
const invocation = [
|
|
1518
|
+
"&",
|
|
1519
|
+
quotePowerShellArg(command),
|
|
1520
|
+
...args.map(quotePowerShellArg),
|
|
1521
|
+
].join(" ");
|
|
1347
1522
|
const wrappedCommand = [
|
|
1348
1523
|
"$ErrorActionPreference = 'Stop'",
|
|
1349
1524
|
`& { ${invocation} }`,
|
|
1350
|
-
].join(
|
|
1525
|
+
].join("; ");
|
|
1351
1526
|
return `powershell.exe -NoLogo -NoExit -EncodedCommand ${encodePowerShellCommand(wrappedCommand)}`;
|
|
1352
1527
|
}
|
|
1353
1528
|
/**
|
|
@@ -1357,15 +1532,15 @@ export function buildWindowsPromptCommand(command, args) {
|
|
|
1357
1532
|
*/
|
|
1358
1533
|
export function buildTmuxPaneCommand(command, args, shellPath = process.env.SHELL) {
|
|
1359
1534
|
const bareCmd = buildTmuxShellCommand(command, args);
|
|
1360
|
-
let rcSource =
|
|
1535
|
+
let rcSource = "";
|
|
1361
1536
|
if (shellPath && /\/zsh$/i.test(shellPath)) {
|
|
1362
|
-
rcSource =
|
|
1537
|
+
rcSource = "if [ -f ~/.zshrc ]; then source ~/.zshrc; fi; ";
|
|
1363
1538
|
}
|
|
1364
1539
|
else if (shellPath && /\/bash$/i.test(shellPath)) {
|
|
1365
|
-
rcSource =
|
|
1540
|
+
rcSource = "if [ -f ~/.bashrc ]; then source ~/.bashrc; fi; ";
|
|
1366
1541
|
}
|
|
1367
|
-
const rawShell = shellPath && shellPath.trim() !==
|
|
1368
|
-
const shellBin = ALLOWED_SHELLS.has(rawShell) ? rawShell :
|
|
1542
|
+
const rawShell = shellPath && shellPath.trim() !== "" ? shellPath.trim() : "/bin/sh";
|
|
1543
|
+
const shellBin = ALLOWED_SHELLS.has(rawShell) ? rawShell : "/bin/sh";
|
|
1369
1544
|
const inner = `${rcSource}exec ${bareCmd}`;
|
|
1370
1545
|
return `${quoteShellArg(shellBin)} -lc ${quoteShellArg(inner)}`;
|
|
1371
1546
|
}
|
|
@@ -1387,12 +1562,12 @@ function buildDetachedWindowsBootstrapScript(sessionName, commandText, delayMs =
|
|
|
1387
1562
|
`try { execFileSync('tmux', ['send-keys', '-t', ${targetLiteral}, '-l', '--', ${commandLiteral}], { stdio: 'ignore' }); } catch {}`,
|
|
1388
1563
|
`try { execFileSync('tmux', ['send-keys', '-t', ${targetLiteral}, 'C-m'], { stdio: 'ignore' }); } catch {}`,
|
|
1389
1564
|
`}, ${delay});`,
|
|
1390
|
-
].join(
|
|
1565
|
+
].join("");
|
|
1391
1566
|
}
|
|
1392
1567
|
function scheduleDetachedWindowsCodexLaunch(sessionName, commandText) {
|
|
1393
|
-
const child = spawn(process.execPath, [
|
|
1568
|
+
const child = spawn(process.execPath, ["-e", buildDetachedWindowsBootstrapScript(sessionName, commandText)], {
|
|
1394
1569
|
detached: true,
|
|
1395
|
-
stdio:
|
|
1570
|
+
stdio: "ignore",
|
|
1396
1571
|
windowsHide: true,
|
|
1397
1572
|
});
|
|
1398
1573
|
child.unref();
|
|
@@ -1460,15 +1635,15 @@ async function postLaunch(cwd, sessionId) {
|
|
|
1460
1635
|
}
|
|
1461
1636
|
// 3. Cancel any still-active modes
|
|
1462
1637
|
try {
|
|
1463
|
-
const { readdir, writeFile, readFile } = await import(
|
|
1638
|
+
const { readdir, writeFile, readFile } = await import("fs/promises");
|
|
1464
1639
|
const scopedDirs = [getBaseStateDir(cwd), getStateDir(cwd, sessionId)];
|
|
1465
1640
|
for (const stateDir of scopedDirs) {
|
|
1466
1641
|
const files = await readdir(stateDir).catch(() => []);
|
|
1467
1642
|
for (const file of files) {
|
|
1468
|
-
if (!file.endsWith(
|
|
1643
|
+
if (!file.endsWith("-state.json") || file === "session.json")
|
|
1469
1644
|
continue;
|
|
1470
1645
|
const path = join(stateDir, file);
|
|
1471
|
-
const content = await readFile(path,
|
|
1646
|
+
const content = await readFile(path, "utf-8");
|
|
1472
1647
|
const state = JSON.parse(content);
|
|
1473
1648
|
if (state.active) {
|
|
1474
1649
|
state.active = false;
|
|
@@ -1483,16 +1658,16 @@ async function postLaunch(cwd, sessionId) {
|
|
|
1483
1658
|
}
|
|
1484
1659
|
// 4. Send session-end lifecycle notification (best effort)
|
|
1485
1660
|
try {
|
|
1486
|
-
const { notifyLifecycle } = await import(
|
|
1661
|
+
const { notifyLifecycle } = await import("../notifications/index.js");
|
|
1487
1662
|
const durationMs = sessionStartedAt
|
|
1488
1663
|
? Date.now() - new Date(sessionStartedAt).getTime()
|
|
1489
1664
|
: undefined;
|
|
1490
|
-
await notifyLifecycle(
|
|
1665
|
+
await notifyLifecycle("session-end", {
|
|
1491
1666
|
sessionId,
|
|
1492
1667
|
projectPath: cwd,
|
|
1493
1668
|
projectName: basename(cwd),
|
|
1494
1669
|
durationMs,
|
|
1495
|
-
reason:
|
|
1670
|
+
reason: "session_exit",
|
|
1496
1671
|
});
|
|
1497
1672
|
}
|
|
1498
1673
|
catch (err) {
|
|
@@ -1504,19 +1679,21 @@ async function postLaunch(cwd, sessionId) {
|
|
|
1504
1679
|
const durationMs = sessionStartedAt
|
|
1505
1680
|
? Date.now() - new Date(sessionStartedAt).getTime()
|
|
1506
1681
|
: undefined;
|
|
1507
|
-
const normalizedEvent = process.exitCode && process.exitCode !== 0 ?
|
|
1508
|
-
const errorSummary = normalizedEvent ===
|
|
1682
|
+
const normalizedEvent = process.exitCode && process.exitCode !== 0 ? "failed" : "finished";
|
|
1683
|
+
const errorSummary = normalizedEvent === "failed"
|
|
1509
1684
|
? `codex exited with code ${process.exitCode}`
|
|
1510
1685
|
: undefined;
|
|
1511
|
-
await emitNativeHookEvent(cwd,
|
|
1686
|
+
await emitNativeHookEvent(cwd, "session-end", {
|
|
1512
1687
|
session_id: sessionId,
|
|
1513
1688
|
context: buildNativeHookBaseContext(cwd, sessionId, normalizedEvent, {
|
|
1514
1689
|
project_path: cwd,
|
|
1515
1690
|
project_name: basename(cwd),
|
|
1516
1691
|
duration_ms: durationMs,
|
|
1517
|
-
reason:
|
|
1518
|
-
status: normalizedEvent ===
|
|
1519
|
-
...(process.exitCode !== undefined
|
|
1692
|
+
reason: "session_exit",
|
|
1693
|
+
status: normalizedEvent === "failed" ? "failed" : "finished",
|
|
1694
|
+
...(process.exitCode !== undefined
|
|
1695
|
+
? { exit_code: process.exitCode }
|
|
1696
|
+
: {}),
|
|
1520
1697
|
...(errorSummary ? { error_summary: errorSummary } : {}),
|
|
1521
1698
|
}),
|
|
1522
1699
|
});
|
|
@@ -1528,7 +1705,7 @@ async function postLaunch(cwd, sessionId) {
|
|
|
1528
1705
|
}
|
|
1529
1706
|
async function emitNativeHookEvent(cwd, event, opts = {}) {
|
|
1530
1707
|
const payload = buildHookEvent(event, {
|
|
1531
|
-
source:
|
|
1708
|
+
source: "native",
|
|
1532
1709
|
context: opts.context || {},
|
|
1533
1710
|
session_id: opts.session_id,
|
|
1534
1711
|
thread_id: opts.thread_id,
|
|
@@ -1540,10 +1717,10 @@ async function emitNativeHookEvent(cwd, event, opts = {}) {
|
|
|
1540
1717
|
});
|
|
1541
1718
|
}
|
|
1542
1719
|
function notifyFallbackPidPath(cwd) {
|
|
1543
|
-
return join(cwd,
|
|
1720
|
+
return join(cwd, ".omx", "state", "notify-fallback.pid");
|
|
1544
1721
|
}
|
|
1545
1722
|
function hookDerivedWatcherPidPath(cwd) {
|
|
1546
|
-
return join(cwd,
|
|
1723
|
+
return join(cwd, ".omx", "state", "hook-derived-watcher.pid");
|
|
1547
1724
|
}
|
|
1548
1725
|
function parseWatcherPidFile(content) {
|
|
1549
1726
|
const trimmed = content.trim();
|
|
@@ -1551,217 +1728,221 @@ function parseWatcherPidFile(content) {
|
|
|
1551
1728
|
return null;
|
|
1552
1729
|
try {
|
|
1553
1730
|
const parsed = JSON.parse(trimmed);
|
|
1554
|
-
return typeof parsed.pid ===
|
|
1731
|
+
return typeof parsed.pid === "number" &&
|
|
1732
|
+
Number.isFinite(parsed.pid) &&
|
|
1733
|
+
parsed.pid > 0
|
|
1734
|
+
? parsed.pid
|
|
1735
|
+
: null;
|
|
1555
1736
|
}
|
|
1556
1737
|
catch {
|
|
1557
1738
|
const pid = Number.parseInt(trimmed, 10);
|
|
1558
1739
|
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
1559
1740
|
}
|
|
1560
1741
|
}
|
|
1561
|
-
function tryKillPid(pid, signal =
|
|
1742
|
+
function tryKillPid(pid, signal = "SIGTERM") {
|
|
1562
1743
|
try {
|
|
1563
1744
|
process.kill(pid, signal);
|
|
1564
1745
|
return true;
|
|
1565
1746
|
}
|
|
1566
1747
|
catch (error) {
|
|
1567
1748
|
const code = error.code;
|
|
1568
|
-
if (code ===
|
|
1749
|
+
if (code === "ESRCH")
|
|
1569
1750
|
return false;
|
|
1570
1751
|
throw error;
|
|
1571
1752
|
}
|
|
1572
1753
|
}
|
|
1573
1754
|
async function startNotifyFallbackWatcher(cwd) {
|
|
1574
|
-
if (process.env.OMX_NOTIFY_FALLBACK ===
|
|
1755
|
+
if (process.env.OMX_NOTIFY_FALLBACK === "0")
|
|
1575
1756
|
return;
|
|
1576
|
-
const { mkdir, writeFile, readFile } = await import(
|
|
1757
|
+
const { mkdir, writeFile, readFile } = await import("fs/promises");
|
|
1577
1758
|
const pidPath = notifyFallbackPidPath(cwd);
|
|
1578
1759
|
const pkgRoot = getPackageRoot();
|
|
1579
|
-
const watcherScript = join(pkgRoot,
|
|
1580
|
-
const notifyScript = join(pkgRoot,
|
|
1760
|
+
const watcherScript = join(pkgRoot, "scripts", "notify-fallback-watcher.js");
|
|
1761
|
+
const notifyScript = join(pkgRoot, "scripts", "notify-hook.js");
|
|
1581
1762
|
if (!existsSync(watcherScript) || !existsSync(notifyScript))
|
|
1582
1763
|
return;
|
|
1583
1764
|
// Stop stale watcher from a previous run.
|
|
1584
1765
|
if (existsSync(pidPath)) {
|
|
1585
1766
|
try {
|
|
1586
|
-
const prevPid = parseWatcherPidFile(await readFile(pidPath,
|
|
1767
|
+
const prevPid = parseWatcherPidFile(await readFile(pidPath, "utf-8"));
|
|
1587
1768
|
if (prevPid) {
|
|
1588
|
-
tryKillPid(prevPid,
|
|
1769
|
+
tryKillPid(prevPid, "SIGTERM");
|
|
1589
1770
|
}
|
|
1590
1771
|
}
|
|
1591
1772
|
catch (error) {
|
|
1592
|
-
if (!hasErrnoCode(error,
|
|
1593
|
-
console.warn(
|
|
1773
|
+
if (!hasErrnoCode(error, "ESRCH")) {
|
|
1774
|
+
console.warn("[omx] warning: failed to stop stale notify fallback watcher", {
|
|
1594
1775
|
path: pidPath,
|
|
1595
1776
|
error: error instanceof Error ? error.message : String(error),
|
|
1596
1777
|
});
|
|
1597
1778
|
}
|
|
1598
1779
|
}
|
|
1599
1780
|
}
|
|
1600
|
-
await mkdir(join(cwd,
|
|
1601
|
-
console.warn(
|
|
1781
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true }).catch((error) => {
|
|
1782
|
+
console.warn("[omx] warning: failed to create notify fallback watcher state directory", {
|
|
1602
1783
|
cwd,
|
|
1603
1784
|
error: error instanceof Error ? error.message : String(error),
|
|
1604
1785
|
});
|
|
1605
1786
|
});
|
|
1606
1787
|
const child = spawn(process.execPath, [
|
|
1607
1788
|
watcherScript,
|
|
1608
|
-
|
|
1789
|
+
"--cwd",
|
|
1609
1790
|
cwd,
|
|
1610
|
-
|
|
1791
|
+
"--notify-script",
|
|
1611
1792
|
notifyScript,
|
|
1612
|
-
|
|
1793
|
+
"--pid-file",
|
|
1613
1794
|
pidPath,
|
|
1614
|
-
|
|
1795
|
+
"--parent-pid",
|
|
1615
1796
|
String(process.pid),
|
|
1616
1797
|
...(process.env.OMX_NOTIFY_FALLBACK_MAX_LIFETIME_MS
|
|
1617
|
-
? [
|
|
1798
|
+
? ["--max-lifetime-ms", process.env.OMX_NOTIFY_FALLBACK_MAX_LIFETIME_MS]
|
|
1618
1799
|
: []),
|
|
1619
1800
|
], {
|
|
1620
1801
|
cwd,
|
|
1621
1802
|
detached: true,
|
|
1622
|
-
stdio:
|
|
1803
|
+
stdio: "ignore",
|
|
1623
1804
|
});
|
|
1624
1805
|
child.unref();
|
|
1625
1806
|
await writeFile(pidPath, JSON.stringify({ pid: child.pid, started_at: new Date().toISOString() }, null, 2)).catch((error) => {
|
|
1626
|
-
console.warn(
|
|
1807
|
+
console.warn("[omx] warning: failed to write notify fallback watcher pid file", {
|
|
1627
1808
|
path: pidPath,
|
|
1628
1809
|
error: error instanceof Error ? error.message : String(error),
|
|
1629
1810
|
});
|
|
1630
1811
|
});
|
|
1631
1812
|
}
|
|
1632
1813
|
async function startHookDerivedWatcher(cwd) {
|
|
1633
|
-
if (process.env.OMX_HOOK_DERIVED_SIGNALS !==
|
|
1814
|
+
if (process.env.OMX_HOOK_DERIVED_SIGNALS !== "1")
|
|
1634
1815
|
return;
|
|
1635
|
-
const { mkdir, writeFile, readFile } = await import(
|
|
1816
|
+
const { mkdir, writeFile, readFile } = await import("fs/promises");
|
|
1636
1817
|
const pidPath = hookDerivedWatcherPidPath(cwd);
|
|
1637
1818
|
const pkgRoot = getPackageRoot();
|
|
1638
|
-
const watcherScript = join(pkgRoot,
|
|
1819
|
+
const watcherScript = join(pkgRoot, "scripts", "hook-derived-watcher.js");
|
|
1639
1820
|
if (!existsSync(watcherScript))
|
|
1640
1821
|
return;
|
|
1641
1822
|
if (existsSync(pidPath)) {
|
|
1642
1823
|
try {
|
|
1643
|
-
const prev = JSON.parse(await readFile(pidPath,
|
|
1644
|
-
if (prev && typeof prev.pid ===
|
|
1645
|
-
process.kill(prev.pid,
|
|
1824
|
+
const prev = JSON.parse(await readFile(pidPath, "utf-8"));
|
|
1825
|
+
if (prev && typeof prev.pid === "number") {
|
|
1826
|
+
process.kill(prev.pid, "SIGTERM");
|
|
1646
1827
|
}
|
|
1647
1828
|
}
|
|
1648
1829
|
catch (error) {
|
|
1649
|
-
console.warn(
|
|
1830
|
+
console.warn("[omx] warning: failed to stop stale hook-derived watcher", {
|
|
1650
1831
|
path: pidPath,
|
|
1651
1832
|
error: error instanceof Error ? error.message : String(error),
|
|
1652
1833
|
});
|
|
1653
1834
|
}
|
|
1654
1835
|
}
|
|
1655
|
-
await mkdir(join(cwd,
|
|
1656
|
-
console.warn(
|
|
1836
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true }).catch((error) => {
|
|
1837
|
+
console.warn("[omx] warning: failed to create hook-derived watcher state directory", {
|
|
1657
1838
|
cwd,
|
|
1658
1839
|
error: error instanceof Error ? error.message : String(error),
|
|
1659
1840
|
});
|
|
1660
1841
|
});
|
|
1661
|
-
const child = spawn(process.execPath, [watcherScript,
|
|
1842
|
+
const child = spawn(process.execPath, [watcherScript, "--cwd", cwd], {
|
|
1662
1843
|
cwd,
|
|
1663
1844
|
detached: true,
|
|
1664
|
-
stdio:
|
|
1845
|
+
stdio: "ignore",
|
|
1665
1846
|
env: process.env,
|
|
1666
1847
|
});
|
|
1667
1848
|
child.unref();
|
|
1668
1849
|
await writeFile(pidPath, JSON.stringify({ pid: child.pid, started_at: new Date().toISOString() }, null, 2)).catch((error) => {
|
|
1669
|
-
console.warn(
|
|
1850
|
+
console.warn("[omx] warning: failed to write hook-derived watcher pid file", {
|
|
1670
1851
|
path: pidPath,
|
|
1671
1852
|
error: error instanceof Error ? error.message : String(error),
|
|
1672
1853
|
});
|
|
1673
1854
|
});
|
|
1674
1855
|
}
|
|
1675
1856
|
async function stopNotifyFallbackWatcher(cwd) {
|
|
1676
|
-
const { readFile, unlink } = await import(
|
|
1857
|
+
const { readFile, unlink } = await import("fs/promises");
|
|
1677
1858
|
const pidPath = notifyFallbackPidPath(cwd);
|
|
1678
1859
|
if (!existsSync(pidPath))
|
|
1679
1860
|
return;
|
|
1680
1861
|
try {
|
|
1681
|
-
const pid = parseWatcherPidFile(await readFile(pidPath,
|
|
1862
|
+
const pid = parseWatcherPidFile(await readFile(pidPath, "utf-8"));
|
|
1682
1863
|
if (pid) {
|
|
1683
|
-
tryKillPid(pid,
|
|
1864
|
+
tryKillPid(pid, "SIGTERM");
|
|
1684
1865
|
}
|
|
1685
1866
|
}
|
|
1686
1867
|
catch (error) {
|
|
1687
|
-
if (!hasErrnoCode(error,
|
|
1688
|
-
console.warn(
|
|
1868
|
+
if (!hasErrnoCode(error, "ESRCH")) {
|
|
1869
|
+
console.warn("[omx] warning: failed to stop notify fallback watcher process", {
|
|
1689
1870
|
path: pidPath,
|
|
1690
1871
|
error: error instanceof Error ? error.message : String(error),
|
|
1691
1872
|
});
|
|
1692
1873
|
}
|
|
1693
1874
|
}
|
|
1694
1875
|
await unlink(pidPath).catch((error) => {
|
|
1695
|
-
console.warn(
|
|
1876
|
+
console.warn("[omx] warning: failed to remove notify fallback watcher pid file", {
|
|
1696
1877
|
path: pidPath,
|
|
1697
1878
|
error: error instanceof Error ? error.message : String(error),
|
|
1698
1879
|
});
|
|
1699
1880
|
});
|
|
1700
1881
|
}
|
|
1701
1882
|
async function stopHookDerivedWatcher(cwd) {
|
|
1702
|
-
const { readFile, unlink } = await import(
|
|
1883
|
+
const { readFile, unlink } = await import("fs/promises");
|
|
1703
1884
|
const pidPath = hookDerivedWatcherPidPath(cwd);
|
|
1704
1885
|
if (!existsSync(pidPath))
|
|
1705
1886
|
return;
|
|
1706
1887
|
try {
|
|
1707
|
-
const parsed = JSON.parse(await readFile(pidPath,
|
|
1708
|
-
if (parsed && typeof parsed.pid ===
|
|
1709
|
-
process.kill(parsed.pid,
|
|
1888
|
+
const parsed = JSON.parse(await readFile(pidPath, "utf-8"));
|
|
1889
|
+
if (parsed && typeof parsed.pid === "number") {
|
|
1890
|
+
process.kill(parsed.pid, "SIGTERM");
|
|
1710
1891
|
}
|
|
1711
1892
|
}
|
|
1712
1893
|
catch (error) {
|
|
1713
|
-
console.warn(
|
|
1894
|
+
console.warn("[omx] warning: failed to stop hook-derived watcher process", {
|
|
1714
1895
|
path: pidPath,
|
|
1715
1896
|
error: error instanceof Error ? error.message : String(error),
|
|
1716
1897
|
});
|
|
1717
1898
|
}
|
|
1718
1899
|
await unlink(pidPath).catch((error) => {
|
|
1719
|
-
console.warn(
|
|
1900
|
+
console.warn("[omx] warning: failed to remove hook-derived watcher pid file", {
|
|
1720
1901
|
path: pidPath,
|
|
1721
1902
|
error: error instanceof Error ? error.message : String(error),
|
|
1722
1903
|
});
|
|
1723
1904
|
});
|
|
1724
1905
|
}
|
|
1725
1906
|
async function flushNotifyFallbackOnce(cwd) {
|
|
1726
|
-
const { spawnSync } = await import(
|
|
1907
|
+
const { spawnSync } = await import("child_process");
|
|
1727
1908
|
const pkgRoot = getPackageRoot();
|
|
1728
|
-
const watcherScript = join(pkgRoot,
|
|
1729
|
-
const notifyScript = join(pkgRoot,
|
|
1909
|
+
const watcherScript = join(pkgRoot, "scripts", "notify-fallback-watcher.js");
|
|
1910
|
+
const notifyScript = join(pkgRoot, "scripts", "notify-hook.js");
|
|
1730
1911
|
if (!existsSync(watcherScript) || !existsSync(notifyScript))
|
|
1731
1912
|
return;
|
|
1732
|
-
spawnSync(process.execPath, [watcherScript,
|
|
1913
|
+
spawnSync(process.execPath, [watcherScript, "--once", "--cwd", cwd, "--notify-script", notifyScript], {
|
|
1733
1914
|
cwd,
|
|
1734
|
-
stdio:
|
|
1915
|
+
stdio: "ignore",
|
|
1735
1916
|
timeout: 3000,
|
|
1736
1917
|
});
|
|
1737
1918
|
}
|
|
1738
1919
|
async function flushHookDerivedWatcherOnce(cwd) {
|
|
1739
|
-
if (process.env.OMX_HOOK_DERIVED_SIGNALS !==
|
|
1920
|
+
if (process.env.OMX_HOOK_DERIVED_SIGNALS !== "1")
|
|
1740
1921
|
return;
|
|
1741
|
-
const { spawnSync } = await import(
|
|
1922
|
+
const { spawnSync } = await import("child_process");
|
|
1742
1923
|
const pkgRoot = getPackageRoot();
|
|
1743
|
-
const watcherScript = join(pkgRoot,
|
|
1924
|
+
const watcherScript = join(pkgRoot, "scripts", "hook-derived-watcher.js");
|
|
1744
1925
|
if (!existsSync(watcherScript))
|
|
1745
1926
|
return;
|
|
1746
|
-
spawnSync(process.execPath, [watcherScript,
|
|
1927
|
+
spawnSync(process.execPath, [watcherScript, "--once", "--cwd", cwd], {
|
|
1747
1928
|
cwd,
|
|
1748
|
-
stdio:
|
|
1929
|
+
stdio: "ignore",
|
|
1749
1930
|
timeout: 3000,
|
|
1750
1931
|
env: {
|
|
1751
1932
|
...process.env,
|
|
1752
|
-
OMX_HOOK_DERIVED_SIGNALS:
|
|
1933
|
+
OMX_HOOK_DERIVED_SIGNALS: "1",
|
|
1753
1934
|
},
|
|
1754
1935
|
});
|
|
1755
1936
|
}
|
|
1756
1937
|
async function cancelModes() {
|
|
1757
|
-
const { writeFile, readFile } = await import(
|
|
1938
|
+
const { writeFile, readFile } = await import("fs/promises");
|
|
1758
1939
|
const cwd = process.cwd();
|
|
1759
1940
|
const nowIso = new Date().toISOString();
|
|
1760
1941
|
try {
|
|
1761
1942
|
const refs = await listModeStateFilesWithScopePreference(cwd);
|
|
1762
1943
|
const states = new Map();
|
|
1763
1944
|
for (const ref of refs) {
|
|
1764
|
-
const content = await readFile(ref.path,
|
|
1945
|
+
const content = await readFile(ref.path, "utf-8");
|
|
1765
1946
|
let parsedState;
|
|
1766
1947
|
try {
|
|
1767
1948
|
parsedState = JSON.parse(content);
|
|
@@ -1778,15 +1959,15 @@ async function cancelModes() {
|
|
|
1778
1959
|
}
|
|
1779
1960
|
const changed = new Set();
|
|
1780
1961
|
const reported = new Set();
|
|
1781
|
-
const cancelMode = (mode, phase =
|
|
1962
|
+
const cancelMode = (mode, phase = "cancelled", reportIfWasActive = true) => {
|
|
1782
1963
|
const entry = states.get(mode);
|
|
1783
1964
|
if (!entry)
|
|
1784
1965
|
return;
|
|
1785
1966
|
const wasActive = entry.state.active === true;
|
|
1786
|
-
const needsChange = entry.state.active !== false
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1967
|
+
const needsChange = entry.state.active !== false ||
|
|
1968
|
+
entry.state.current_phase !== phase ||
|
|
1969
|
+
typeof entry.state.completed_at !== "string" ||
|
|
1970
|
+
String(entry.state.completed_at).trim() === "";
|
|
1790
1971
|
if (!needsChange)
|
|
1791
1972
|
return;
|
|
1792
1973
|
entry.state.active = false;
|
|
@@ -1797,30 +1978,32 @@ async function cancelModes() {
|
|
|
1797
1978
|
if (reportIfWasActive && wasActive)
|
|
1798
1979
|
reported.add(mode);
|
|
1799
1980
|
};
|
|
1800
|
-
const ralphLinksUltrawork = (state) => state.linked_ultrawork === true || state.linked_mode ===
|
|
1801
|
-
const team = states.get(
|
|
1802
|
-
const ralph = states.get(
|
|
1981
|
+
const ralphLinksUltrawork = (state) => state.linked_ultrawork === true || state.linked_mode === "ultrawork";
|
|
1982
|
+
const team = states.get("team");
|
|
1983
|
+
const ralph = states.get("ralph");
|
|
1803
1984
|
const hadActiveRalph = !!(ralph && ralph.state.active === true);
|
|
1804
|
-
if (team &&
|
|
1805
|
-
|
|
1985
|
+
if (team &&
|
|
1986
|
+
team.state.active === true &&
|
|
1987
|
+
team.state.linked_ralph === true) {
|
|
1988
|
+
cancelMode("team", "cancelled", true);
|
|
1806
1989
|
if (ralph && ralph.state.linked_team === true) {
|
|
1807
|
-
cancelMode(
|
|
1808
|
-
ralph.state.linked_team_terminal_phase =
|
|
1990
|
+
cancelMode("ralph", "cancelled", true);
|
|
1991
|
+
ralph.state.linked_team_terminal_phase = "cancelled";
|
|
1809
1992
|
ralph.state.linked_team_terminal_at = nowIso;
|
|
1810
|
-
changed.add(
|
|
1993
|
+
changed.add("ralph");
|
|
1811
1994
|
if (ralphLinksUltrawork(ralph.state))
|
|
1812
|
-
cancelMode(
|
|
1995
|
+
cancelMode("ultrawork", "cancelled", true);
|
|
1813
1996
|
}
|
|
1814
1997
|
}
|
|
1815
1998
|
if (ralph && ralph.state.active === true) {
|
|
1816
|
-
cancelMode(
|
|
1999
|
+
cancelMode("ralph", "cancelled", true);
|
|
1817
2000
|
if (ralphLinksUltrawork(ralph.state))
|
|
1818
|
-
cancelMode(
|
|
2001
|
+
cancelMode("ultrawork", "cancelled", true);
|
|
1819
2002
|
}
|
|
1820
2003
|
if (!hadActiveRalph) {
|
|
1821
2004
|
for (const [mode, entry] of states.entries()) {
|
|
1822
2005
|
if (entry.state.active === true)
|
|
1823
|
-
cancelMode(mode,
|
|
2006
|
+
cancelMode(mode, "cancelled", true);
|
|
1824
2007
|
}
|
|
1825
2008
|
}
|
|
1826
2009
|
for (const [mode, entry] of states.entries()) {
|
|
@@ -1832,12 +2015,12 @@ async function cancelModes() {
|
|
|
1832
2015
|
console.log(`Cancelled: ${mode}`);
|
|
1833
2016
|
}
|
|
1834
2017
|
if (reported.size === 0) {
|
|
1835
|
-
console.log(
|
|
2018
|
+
console.log("No active modes to cancel.");
|
|
1836
2019
|
}
|
|
1837
2020
|
}
|
|
1838
2021
|
catch (err) {
|
|
1839
2022
|
process.stderr.write(`[cli/index] operation failed: ${err}\n`);
|
|
1840
|
-
console.log(
|
|
2023
|
+
console.log("No active modes to cancel.");
|
|
1841
2024
|
}
|
|
1842
2025
|
}
|
|
1843
2026
|
//# sourceMappingURL=index.js.map
|