oh-my-codex 0.18.7 → 0.18.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +12 -12
- package/Cargo.toml +1 -1
- package/README.md +5 -5
- package/crates/omx-sparkshell/tests/execution.rs +1 -1
- package/dist/agents/__tests__/native-config.test.js +42 -1
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/definitions.d.ts +8 -0
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +1 -0
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/native-config.d.ts +5 -1
- package/dist/agents/native-config.d.ts.map +1 -1
- package/dist/agents/native-config.js +17 -2
- package/dist/agents/native-config.js.map +1 -1
- package/dist/autopilot/__tests__/fsm.test.js +3 -0
- package/dist/autopilot/__tests__/fsm.test.js.map +1 -1
- package/dist/autopilot/fsm.js +2 -2
- package/dist/autopilot/fsm.js.map +1 -1
- package/dist/cli/__tests__/auth.test.js +4 -2
- package/dist/cli/__tests__/auth.test.js.map +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js +512 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +39 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +98 -6
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +28 -8
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.js +26 -9
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +13 -0
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
- package/dist/cli/__tests__/ralph.test.js +14 -0
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/resume.test.js +50 -1
- package/dist/cli/__tests__/resume.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +89 -0
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +65 -0
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/state.test.js +21 -0
- package/dist/cli/__tests__/state.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +2 -2
- package/dist/cli/__tests__/update.test.js +323 -18
- package/dist/cli/__tests__/update.test.js.map +1 -1
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +8 -1
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +21 -4
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +143 -28
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/plugin-marketplace.d.ts +14 -2
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +62 -15
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +3 -1
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/setup-preferences.d.ts +2 -0
- package/dist/cli/setup-preferences.d.ts.map +1 -1
- package/dist/cli/setup-preferences.js +4 -0
- package/dist/cli/setup-preferences.js.map +1 -1
- package/dist/cli/setup.d.ts +3 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +166 -27
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/state.d.ts.map +1 -1
- package/dist/cli/state.js +8 -1
- package/dist/cli/state.js.map +1 -1
- package/dist/cli/tmux-hook.d.ts.map +1 -1
- package/dist/cli/tmux-hook.js +16 -0
- package/dist/cli/tmux-hook.js.map +1 -1
- package/dist/cli/update.d.ts +22 -3
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +312 -26
- package/dist/cli/update.js.map +1 -1
- package/dist/cli/version.d.ts.map +1 -1
- package/dist/cli/version.js +5 -9
- package/dist/cli/version.js.map +1 -1
- package/dist/compat/__tests__/doctor-contract.test.js +12 -1
- package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +1 -0
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/generator.d.ts +2 -2
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +2 -2
- package/dist/config/generator.js.map +1 -1
- package/dist/config/team-mode.d.ts +12 -0
- package/dist/config/team-mode.d.ts.map +1 -0
- package/dist/config/team-mode.js +91 -0
- package/dist/config/team-mode.js.map +1 -0
- package/dist/hooks/__tests__/agents-overlay.test.js +88 -0
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/code-review-skill-contract.test.js +12 -0
- package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js +30 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +423 -3
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +189 -0
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +35 -2
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +3 -3
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/skill-guidance-contract.test.js +21 -0
- package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
- package/dist/hooks/agents-overlay.d.ts.map +1 -1
- package/dist/hooks/agents-overlay.js +36 -50
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/extensibility/__tests__/plugin-runner.test.js +31 -0
- package/dist/hooks/extensibility/__tests__/plugin-runner.test.js.map +1 -1
- package/dist/hooks/extensibility/plugin-runner.js +17 -21
- package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +258 -12
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
- package/dist/hooks/prompt-guidance-contract.js +6 -0
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hooks/session.d.ts +1 -0
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js.map +1 -1
- package/dist/hud/__tests__/authority.test.js +435 -32
- package/dist/hud/__tests__/authority.test.js.map +1 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js +2 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
- package/dist/hud/__tests__/index.test.js +42 -0
- package/dist/hud/__tests__/index.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +642 -15
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/render.test.js +61 -0
- package/dist/hud/__tests__/render.test.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +160 -4
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +180 -21
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/authority.d.ts +5 -0
- package/dist/hud/authority.d.ts.map +1 -1
- package/dist/hud/authority.js +324 -28
- package/dist/hud/authority.js.map +1 -1
- package/dist/hud/index.d.ts +3 -2
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +42 -19
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/reconcile.d.ts +3 -3
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +128 -19
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +35 -0
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +65 -80
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/tmux.d.ts +24 -6
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +136 -38
- package/dist/hud/tmux.js.map +1 -1
- package/dist/hud/types.d.ts +11 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js.map +1 -1
- package/dist/mcp/__tests__/state-paths.test.js +71 -1
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/state-paths.d.ts +32 -0
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +113 -17
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/mcp/state-server.d.ts +4 -4
- package/dist/question/__tests__/renderer.test.js +566 -1
- package/dist/question/__tests__/renderer.test.js.map +1 -1
- package/dist/question/renderer.d.ts +9 -1
- package/dist/question/renderer.d.ts.map +1 -1
- package/dist/question/renderer.js +246 -70
- package/dist/question/renderer.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +837 -101
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-state-io.test.js +72 -1
- package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts +2 -0
- package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/notify-tmux-injection.test.js +57 -0
- package/dist/scripts/__tests__/notify-tmux-injection.test.js.map +1 -0
- package/dist/scripts/__tests__/run-test-files.test.js +74 -0
- package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
- package/dist/scripts/__tests__/verify-native-agents.test.js +65 -0
- package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +107 -39
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/eval/eval-parity-smoke.js +1 -1
- package/dist/scripts/eval/eval-parity-smoke.js.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.js +3 -1
- package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.js +3 -10
- package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
- package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
- package/dist/scripts/notify-hook/state-io.js +62 -38
- package/dist/scripts/notify-hook/state-io.js.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.js +7 -0
- package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts +7 -0
- package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js +24 -18
- package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
- package/dist/scripts/notify-hook.js +75 -11
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/run-test-files.js +193 -22
- package/dist/scripts/run-test-files.js.map +1 -1
- package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
- package/dist/scripts/sync-plugin-mirror.js +61 -3
- package/dist/scripts/sync-plugin-mirror.js.map +1 -1
- package/dist/scripts/verify-native-agents.d.ts.map +1 -1
- package/dist/scripts/verify-native-agents.js +58 -1
- package/dist/scripts/verify-native-agents.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +113 -0
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +3 -16
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +25 -0
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +57 -2
- package/dist/state/operations.js.map +1 -1
- package/dist/state/skill-active.d.ts.map +1 -1
- package/dist/state/skill-active.js +7 -39
- package/dist/state/skill-active.js.map +1 -1
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +10 -14
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +1 -1
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +9 -4
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +195 -2
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-runtime-identity.test.js +4 -2
- package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +3 -2
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/tmux-session.d.ts +2 -0
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +142 -12
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/utils/__tests__/platform-command.test.js +16 -1
- package/dist/utils/__tests__/platform-command.test.js.map +1 -1
- package/dist/utils/__tests__/version.test.d.ts +2 -0
- package/dist/utils/__tests__/version.test.d.ts.map +1 -0
- package/dist/utils/__tests__/version.test.js +51 -0
- package/dist/utils/__tests__/version.test.js.map +1 -0
- package/dist/utils/paths.d.ts +8 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +16 -4
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/platform-command.d.ts +9 -0
- package/dist/utils/platform-command.d.ts.map +1 -1
- package/dist/utils/platform-command.js +15 -0
- package/dist/utils/platform-command.js.map +1 -1
- package/dist/utils/version.d.ts +7 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +67 -0
- package/dist/utils/version.js.map +1 -0
- package/dist/verification/__tests__/ci-rust-gates.test.js +89 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js +16 -2
- package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
- package/package.json +11 -10
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +334 -21
- package/plugins/oh-my-codex/hooks/hooks.json +1 -2
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +3 -1
- package/plugins/oh-my-codex/skills/code-review/SKILL.md +7 -7
- package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +51 -11
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +22 -22
- package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +9 -0
- package/skills/autopilot/SKILL.md +3 -1
- package/skills/code-review/SKILL.md +7 -7
- package/skills/deep-interview/SKILL.md +51 -11
- package/skills/ralph/SKILL.md +22 -22
- package/skills/ultraqa/SKILL.md +9 -0
- package/src/scripts/__tests__/codex-native-hook.test.ts +946 -98
- package/src/scripts/__tests__/notify-state-io.test.ts +95 -0
- package/src/scripts/__tests__/notify-tmux-injection.test.ts +82 -0
- package/src/scripts/__tests__/run-test-files.test.ts +102 -0
- package/src/scripts/__tests__/verify-native-agents.test.ts +75 -0
- package/src/scripts/codex-native-hook.ts +123 -34
- package/src/scripts/demo-team-e2e.sh +10 -7
- package/src/scripts/eval/eval-parity-smoke.ts +1 -1
- package/src/scripts/notify-hook/auto-nudge.ts +3 -1
- package/src/scripts/notify-hook/ralph-session-resume.ts +2 -8
- package/src/scripts/notify-hook/state-io.ts +75 -37
- package/src/scripts/notify-hook/team-leader-nudge.ts +7 -0
- package/src/scripts/notify-hook/tmux-injection.ts +35 -19
- package/src/scripts/notify-hook.ts +91 -4
- package/src/scripts/prepare-build.js +83 -0
- package/src/scripts/run-test-files.ts +192 -22
- package/src/scripts/sync-plugin-mirror.ts +98 -9
- package/src/scripts/verify-native-agents.ts +65 -1
- package/src/scripts/postinstall-bootstrap.js +0 -23
|
@@ -3,7 +3,7 @@ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'nod
|
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { describe, it } from 'node:test';
|
|
6
|
-
import { closeQuestionRenderer, computeAdaptiveQuestionPaneHeight, formatQuestionAnswerForInjection, formatQuestionAnswersForInjection, injectQuestionAnswerToPane, findLiveQuestionsForSession, injectQuestionAnswersToPane, launchQuestionRenderer, resolveQuestionRendererStrategy, supersedeLiveQuestionsForSession, } from '../renderer.js';
|
|
6
|
+
import { buildQuestionUiTmuxArgs, closeQuestionRenderer, computeAdaptiveQuestionPaneHeight, formatQuestionAnswerForInjection, formatQuestionAnswersForInjection, injectQuestionAnswerToPane, findLiveQuestionsForSession, injectQuestionAnswersToPane, launchQuestionRenderer, resolveQuestionRendererStrategy, estimateQuestionRenderFootprint, shouldOpenQuestionInNewWindow, supersedeLiveQuestionsForSession, } from '../renderer.js';
|
|
7
7
|
import { buildSendPaneArgvs } from '../../notifications/tmux-detector.js';
|
|
8
8
|
describe('resolveQuestionRendererStrategy', () => {
|
|
9
9
|
it('prefers inside-tmux when TMUX is present', () => {
|
|
@@ -90,6 +90,185 @@ describe('adaptive question pane sizing', () => {
|
|
|
90
90
|
assert.equal(computeAdaptiveQuestionPaneHeight(9, 20), 7);
|
|
91
91
|
});
|
|
92
92
|
});
|
|
93
|
+
describe('question window topology selection', () => {
|
|
94
|
+
it('switches to a new tmux window only after the split height budget is exceeded', () => {
|
|
95
|
+
assert.equal(shouldOpenQuestionInNewWindow(20, 18), false);
|
|
96
|
+
assert.equal(shouldOpenQuestionInNewWindow(20, 19), true);
|
|
97
|
+
assert.equal(shouldOpenQuestionInNewWindow(5, 3), false);
|
|
98
|
+
assert.equal(shouldOpenQuestionInNewWindow(5, 4), true);
|
|
99
|
+
});
|
|
100
|
+
it('counts wrapped question text more conservatively in narrow panes', () => {
|
|
101
|
+
const record = {
|
|
102
|
+
kind: 'omx.question/v1',
|
|
103
|
+
question_id: 'question-1',
|
|
104
|
+
created_at: '2026-05-01T10:08:52.523Z',
|
|
105
|
+
updated_at: '2026-05-01T10:08:52.523Z',
|
|
106
|
+
status: 'pending',
|
|
107
|
+
question: 'x'.repeat(180),
|
|
108
|
+
options: [{
|
|
109
|
+
label: 'Only option',
|
|
110
|
+
value: 'only',
|
|
111
|
+
description: 'y'.repeat(140),
|
|
112
|
+
}],
|
|
113
|
+
allow_other: false,
|
|
114
|
+
multi_select: false,
|
|
115
|
+
type: 'single-answerable',
|
|
116
|
+
source: 'deep-interview',
|
|
117
|
+
questions: [{
|
|
118
|
+
id: 'q-1',
|
|
119
|
+
question: 'x'.repeat(180),
|
|
120
|
+
options: [{
|
|
121
|
+
label: 'Only option',
|
|
122
|
+
value: 'only',
|
|
123
|
+
description: 'y'.repeat(140),
|
|
124
|
+
}],
|
|
125
|
+
allow_other: false,
|
|
126
|
+
multi_select: false,
|
|
127
|
+
type: 'single-answerable',
|
|
128
|
+
}],
|
|
129
|
+
};
|
|
130
|
+
assert.ok(estimateQuestionRenderFootprint(record, 20) > estimateQuestionRenderFootprint(record, 80));
|
|
131
|
+
});
|
|
132
|
+
it('sizes multi-question records from the largest visible screen rather than summing every question', () => {
|
|
133
|
+
const longQuestion = {
|
|
134
|
+
id: 'q-2',
|
|
135
|
+
question: 'x'.repeat(220),
|
|
136
|
+
options: [{
|
|
137
|
+
label: 'Only option',
|
|
138
|
+
value: 'only',
|
|
139
|
+
description: 'y'.repeat(180),
|
|
140
|
+
}],
|
|
141
|
+
allow_other: false,
|
|
142
|
+
multi_select: false,
|
|
143
|
+
type: 'single-answerable',
|
|
144
|
+
};
|
|
145
|
+
const shortQuestion = {
|
|
146
|
+
id: 'q-1',
|
|
147
|
+
question: 'Short question?',
|
|
148
|
+
options: [{
|
|
149
|
+
label: 'Only option',
|
|
150
|
+
value: 'only',
|
|
151
|
+
description: 'Short description.',
|
|
152
|
+
}],
|
|
153
|
+
allow_other: false,
|
|
154
|
+
multi_select: false,
|
|
155
|
+
type: 'single-answerable',
|
|
156
|
+
};
|
|
157
|
+
const shared = {
|
|
158
|
+
kind: 'omx.question/v1',
|
|
159
|
+
question_id: 'question-1',
|
|
160
|
+
created_at: '2026-05-01T10:08:52.523Z',
|
|
161
|
+
updated_at: '2026-05-01T10:08:52.523Z',
|
|
162
|
+
status: 'pending',
|
|
163
|
+
allow_other: false,
|
|
164
|
+
multi_select: false,
|
|
165
|
+
type: 'single-answerable',
|
|
166
|
+
source: 'deep-interview',
|
|
167
|
+
};
|
|
168
|
+
const multiQuestionRecord = {
|
|
169
|
+
...shared,
|
|
170
|
+
questions: [shortQuestion, longQuestion],
|
|
171
|
+
};
|
|
172
|
+
const shortRecord = {
|
|
173
|
+
...shared,
|
|
174
|
+
questions: [shortQuestion],
|
|
175
|
+
};
|
|
176
|
+
const longRecord = {
|
|
177
|
+
...shared,
|
|
178
|
+
questions: [longQuestion],
|
|
179
|
+
};
|
|
180
|
+
const multiFootprint = estimateQuestionRenderFootprint(multiQuestionRecord, 20);
|
|
181
|
+
const shortFootprint = estimateQuestionRenderFootprint(shortRecord, 20);
|
|
182
|
+
const longFootprint = estimateQuestionRenderFootprint(longRecord, 20);
|
|
183
|
+
assert.ok(multiFootprint >= longFootprint);
|
|
184
|
+
assert.ok(multiFootprint < shortFootprint + longFootprint);
|
|
185
|
+
});
|
|
186
|
+
it('includes the review screen when sizing multi-question records', () => {
|
|
187
|
+
const shortQuestion = {
|
|
188
|
+
id: 'q-1',
|
|
189
|
+
question: 'First short question?',
|
|
190
|
+
options: [{
|
|
191
|
+
label: 'Only option',
|
|
192
|
+
value: 'only',
|
|
193
|
+
description: 'Short description.',
|
|
194
|
+
}],
|
|
195
|
+
allow_other: false,
|
|
196
|
+
multi_select: false,
|
|
197
|
+
type: 'single-answerable',
|
|
198
|
+
};
|
|
199
|
+
const shortSecondQuestion = {
|
|
200
|
+
id: 'q-2',
|
|
201
|
+
question: 'Second short question?',
|
|
202
|
+
options: [{
|
|
203
|
+
label: 'Only option',
|
|
204
|
+
value: 'only',
|
|
205
|
+
description: 'Short description.',
|
|
206
|
+
}],
|
|
207
|
+
allow_other: false,
|
|
208
|
+
multi_select: false,
|
|
209
|
+
type: 'single-answerable',
|
|
210
|
+
};
|
|
211
|
+
const shortThirdQuestion = {
|
|
212
|
+
id: 'q-3',
|
|
213
|
+
question: 'Third short question?',
|
|
214
|
+
options: [{
|
|
215
|
+
label: 'Only option',
|
|
216
|
+
value: 'only',
|
|
217
|
+
description: 'Short description.',
|
|
218
|
+
}],
|
|
219
|
+
allow_other: false,
|
|
220
|
+
multi_select: false,
|
|
221
|
+
type: 'single-answerable',
|
|
222
|
+
};
|
|
223
|
+
const shared = {
|
|
224
|
+
kind: 'omx.question/v1',
|
|
225
|
+
question_id: 'question-1',
|
|
226
|
+
created_at: '2026-05-01T10:08:52.523Z',
|
|
227
|
+
updated_at: '2026-05-01T10:08:52.523Z',
|
|
228
|
+
status: 'pending',
|
|
229
|
+
allow_other: false,
|
|
230
|
+
multi_select: false,
|
|
231
|
+
type: 'single-answerable',
|
|
232
|
+
source: 'deep-interview',
|
|
233
|
+
};
|
|
234
|
+
const singleScreenRecord = {
|
|
235
|
+
...shared,
|
|
236
|
+
questions: [shortQuestion],
|
|
237
|
+
};
|
|
238
|
+
const reviewScreenRecord = {
|
|
239
|
+
...shared,
|
|
240
|
+
questions: [shortQuestion, shortSecondQuestion, shortThirdQuestion],
|
|
241
|
+
};
|
|
242
|
+
assert.ok(estimateQuestionRenderFootprint(reviewScreenRecord, 80) > estimateQuestionRenderFootprint(singleScreenRecord, 80));
|
|
243
|
+
});
|
|
244
|
+
it('sizes review screens from selected answers when multi-select summaries wrap', () => {
|
|
245
|
+
const questions = Array.from({ length: 5 }, (_, index) => ({
|
|
246
|
+
id: `q-${index + 1}`,
|
|
247
|
+
question: `Question ${index + 1}?`,
|
|
248
|
+
options: [
|
|
249
|
+
{ label: 'Alpha option', value: 'alpha' },
|
|
250
|
+
{ label: 'Beta option', value: 'beta' },
|
|
251
|
+
{ label: 'Gamma option', value: 'gamma' },
|
|
252
|
+
],
|
|
253
|
+
allow_other: false,
|
|
254
|
+
multi_select: true,
|
|
255
|
+
type: 'multi-answerable',
|
|
256
|
+
}));
|
|
257
|
+
const record = {
|
|
258
|
+
kind: 'omx.question/v1',
|
|
259
|
+
question_id: 'question-1',
|
|
260
|
+
created_at: '2026-05-01T10:08:52.523Z',
|
|
261
|
+
updated_at: '2026-05-01T10:08:52.523Z',
|
|
262
|
+
status: 'pending',
|
|
263
|
+
allow_other: false,
|
|
264
|
+
multi_select: true,
|
|
265
|
+
type: 'multi-answerable',
|
|
266
|
+
source: 'deep-interview',
|
|
267
|
+
questions,
|
|
268
|
+
};
|
|
269
|
+
assert.equal(shouldOpenQuestionInNewWindow(20, estimateQuestionRenderFootprint(record, 20)), true);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
93
272
|
describe('launchQuestionRenderer', () => {
|
|
94
273
|
it('fails before building UI argv or invoking tmux when no visible renderer is available', () => {
|
|
95
274
|
const calls = [];
|
|
@@ -133,6 +312,8 @@ describe('launchQuestionRenderer', () => {
|
|
|
133
312
|
strategy: 'inside-tmux',
|
|
134
313
|
execTmux: (args) => {
|
|
135
314
|
calls.push(args);
|
|
315
|
+
if (args[0] === 'display-message' && args.includes('#{pane_height}'))
|
|
316
|
+
return '40\n';
|
|
136
317
|
if (args[0] === 'display-message')
|
|
137
318
|
return '1\n';
|
|
138
319
|
if (args[0] === 'split-window')
|
|
@@ -168,6 +349,239 @@ describe('launchQuestionRenderer', () => {
|
|
|
168
349
|
assert.ok(splitCall.includes('OMX_QUESTION_RETURN_TRANSPORT=tmux-send-keys'));
|
|
169
350
|
assert.ok(calls.some((call) => call.join(' ') === 'list-panes -t %42 -F #{pane_dead}\t#{pane_id}'));
|
|
170
351
|
});
|
|
352
|
+
it('opens a new tmux window when the current pane is too short for the question frame', () => {
|
|
353
|
+
const cwd = mkdtempSync(join(tmpdir(), 'omx-question-renderer-new-window-'));
|
|
354
|
+
try {
|
|
355
|
+
const stateDir = join(cwd, '.omx', 'state', 'sessions', 's1', 'questions');
|
|
356
|
+
mkdirSync(stateDir, { recursive: true });
|
|
357
|
+
const recordPath = join(stateDir, 'question-1.json');
|
|
358
|
+
writeFileSync(recordPath, JSON.stringify({
|
|
359
|
+
kind: 'omx.question/v1',
|
|
360
|
+
question_id: 'question-1',
|
|
361
|
+
created_at: '2026-05-01T10:08:52.523Z',
|
|
362
|
+
updated_at: '2026-05-01T10:08:52.523Z',
|
|
363
|
+
status: 'pending',
|
|
364
|
+
question: 'Round 1 | Target: definition-boundary | Ambiguity: 42%\n\nСамая важная неоднозначность: что именно считать системной метрикой скорости для TTS / Image / Voice, чтобы прогресс-бар и карточки не врали оператору?',
|
|
365
|
+
options: [
|
|
366
|
+
{ label: 'Stage timings', value: 'stage-timings', description: 'Считать отдельно реальные этапы: TTS synthesis, image/still generation, voice/video final generation; для каждого нужны stage timestamps/metadata.' },
|
|
367
|
+
{ label: 'Wall-clock by artifact', value: 'wall-clock-by-artifact', description: 'Брать общий wall-clock от createdAt до completed и нормализовать по типу результата: 1 image / N sec, 1s video / N sec.' },
|
|
368
|
+
{ label: 'Hybrid recommended', value: 'hybrid', description: 'Сначала использовать wall-clock fallback, но добавлять stage timings для новых генераций, когда этапы можно инструментировать.' },
|
|
369
|
+
],
|
|
370
|
+
allow_other: true,
|
|
371
|
+
other_label: 'Other',
|
|
372
|
+
multi_select: false,
|
|
373
|
+
type: 'single-answerable',
|
|
374
|
+
questions: [{
|
|
375
|
+
id: 'q-1',
|
|
376
|
+
question: 'Round 1 | Target: definition-boundary | Ambiguity: 42%\n\nСамая важная неоднозначность: что именно считать системной метрикой скорости для TTS / Image / Voice, чтобы прогресс-бар и карточки не врали оператору?',
|
|
377
|
+
options: [
|
|
378
|
+
{ label: 'Stage timings', value: 'stage-timings', description: 'Считать отдельно реальные этапы: TTS synthesis, image/still generation, voice/video final generation; для каждого нужны stage timestamps/metadata.' },
|
|
379
|
+
{ label: 'Wall-clock by artifact', value: 'wall-clock-by-artifact', description: 'Брать общий wall-clock от createdAt до completed и нормализовать по типу результата: 1 image / N sec, 1s video / N sec.' },
|
|
380
|
+
{ label: 'Hybrid recommended', value: 'hybrid', description: 'Сначала использовать wall-clock fallback, но добавлять stage timings для новых генераций, когда этапы можно инструментировать.' },
|
|
381
|
+
],
|
|
382
|
+
allow_other: true,
|
|
383
|
+
other_label: 'Other',
|
|
384
|
+
multi_select: false,
|
|
385
|
+
type: 'single-answerable',
|
|
386
|
+
}],
|
|
387
|
+
source: 'deep-interview',
|
|
388
|
+
}, null, 2));
|
|
389
|
+
const calls = [];
|
|
390
|
+
const result = launchQuestionRenderer({
|
|
391
|
+
cwd,
|
|
392
|
+
recordPath,
|
|
393
|
+
sessionId: 's1',
|
|
394
|
+
env: { TMUX: '/tmp/tmux-demo', TMUX_PANE: '%11' },
|
|
395
|
+
}, {
|
|
396
|
+
strategy: 'inside-tmux',
|
|
397
|
+
execTmux: (args) => {
|
|
398
|
+
calls.push(args);
|
|
399
|
+
if (args[0] === 'display-message' && args.includes('#{session_attached}'))
|
|
400
|
+
return '1\n';
|
|
401
|
+
if (args[0] === 'display-message' && args.includes('#{pane_height}'))
|
|
402
|
+
return '5\n';
|
|
403
|
+
if (args[0] === 'display-message' && args.includes('#{session_id}'))
|
|
404
|
+
return '$1\n';
|
|
405
|
+
if (args[0] === 'new-window')
|
|
406
|
+
return '%42\n';
|
|
407
|
+
if (args[0] === 'list-panes' && args[2] === '%42')
|
|
408
|
+
return '0\t%42\n';
|
|
409
|
+
return '';
|
|
410
|
+
},
|
|
411
|
+
sleepSync: () => { },
|
|
412
|
+
});
|
|
413
|
+
assert.equal(result.renderer, 'tmux-pane');
|
|
414
|
+
assert.equal(result.target, '%42');
|
|
415
|
+
assert.equal(result.return_target, '%11');
|
|
416
|
+
assert.equal(result.return_transport, 'tmux-send-keys');
|
|
417
|
+
const newWindowCall = calls.find((call) => call[0] === 'new-window');
|
|
418
|
+
assert.ok(newWindowCall);
|
|
419
|
+
const targetIndex = newWindowCall.indexOf('-t');
|
|
420
|
+
assert.notEqual(targetIndex, -1);
|
|
421
|
+
assert.deepEqual(newWindowCall.slice(targetIndex, targetIndex + 2), ['-t', '$1']);
|
|
422
|
+
assert.equal(calls.some((call) => call[0] === 'split-window'), false);
|
|
423
|
+
assert.equal(calls.some((call) => call[0] === 'display-message' && call.includes('#{session_id}')), true);
|
|
424
|
+
}
|
|
425
|
+
finally {
|
|
426
|
+
rmSync(cwd, { recursive: true });
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
it('opens a new tmux window when wrapped content would exceed the split budget in a narrow pane', () => {
|
|
430
|
+
const cwd = mkdtempSync(join(tmpdir(), 'omx-question-renderer-wrapped-window-'));
|
|
431
|
+
try {
|
|
432
|
+
const stateDir = join(cwd, '.omx', 'state', 'sessions', 's1', 'questions');
|
|
433
|
+
mkdirSync(stateDir, { recursive: true });
|
|
434
|
+
const recordPath = join(stateDir, 'question-1.json');
|
|
435
|
+
writeFileSync(recordPath, JSON.stringify({
|
|
436
|
+
kind: 'omx.question/v1',
|
|
437
|
+
question_id: 'question-1',
|
|
438
|
+
created_at: '2026-05-01T10:08:52.523Z',
|
|
439
|
+
updated_at: '2026-05-01T10:08:52.523Z',
|
|
440
|
+
status: 'pending',
|
|
441
|
+
question: 'x'.repeat(220),
|
|
442
|
+
options: [{
|
|
443
|
+
label: 'Only option',
|
|
444
|
+
value: 'only',
|
|
445
|
+
description: 'y'.repeat(180),
|
|
446
|
+
}],
|
|
447
|
+
allow_other: false,
|
|
448
|
+
multi_select: false,
|
|
449
|
+
type: 'single-answerable',
|
|
450
|
+
source: 'deep-interview',
|
|
451
|
+
questions: [{
|
|
452
|
+
id: 'q-1',
|
|
453
|
+
question: 'x'.repeat(220),
|
|
454
|
+
options: [{
|
|
455
|
+
label: 'Only option',
|
|
456
|
+
value: 'only',
|
|
457
|
+
description: 'y'.repeat(180),
|
|
458
|
+
}],
|
|
459
|
+
allow_other: false,
|
|
460
|
+
multi_select: false,
|
|
461
|
+
type: 'single-answerable',
|
|
462
|
+
}],
|
|
463
|
+
}, null, 2));
|
|
464
|
+
const calls = [];
|
|
465
|
+
const result = launchQuestionRenderer({
|
|
466
|
+
cwd,
|
|
467
|
+
recordPath,
|
|
468
|
+
sessionId: 's1',
|
|
469
|
+
env: { TMUX: '/tmp/tmux-demo', TMUX_PANE: '%11' },
|
|
470
|
+
}, {
|
|
471
|
+
strategy: 'inside-tmux',
|
|
472
|
+
execTmux: (args) => {
|
|
473
|
+
calls.push(args);
|
|
474
|
+
if (args[0] === 'display-message' && args.includes('#{session_attached}'))
|
|
475
|
+
return '1\n';
|
|
476
|
+
if (args[0] === 'display-message' && args.includes('#{pane_height}'))
|
|
477
|
+
return '20\n';
|
|
478
|
+
if (args[0] === 'display-message' && args.includes('#{pane_width}'))
|
|
479
|
+
return '20\n';
|
|
480
|
+
if (args[0] === 'display-message' && args.includes('#{session_id}'))
|
|
481
|
+
return '$1\n';
|
|
482
|
+
if (args[0] === 'new-window')
|
|
483
|
+
return '%99\n';
|
|
484
|
+
if (args[0] === 'list-panes' && args[2] === '%99')
|
|
485
|
+
return '0\t%99\n';
|
|
486
|
+
return '';
|
|
487
|
+
},
|
|
488
|
+
sleepSync: () => { },
|
|
489
|
+
});
|
|
490
|
+
assert.equal(result.renderer, 'tmux-pane');
|
|
491
|
+
assert.equal(result.target, '%99');
|
|
492
|
+
assert.equal(result.return_target, '%11');
|
|
493
|
+
assert.equal(result.return_transport, 'tmux-send-keys');
|
|
494
|
+
const newWindowCall = calls.find((call) => call[0] === 'new-window');
|
|
495
|
+
assert.ok(newWindowCall);
|
|
496
|
+
const targetIndex = newWindowCall.indexOf('-t');
|
|
497
|
+
assert.notEqual(targetIndex, -1);
|
|
498
|
+
assert.deepEqual(newWindowCall.slice(targetIndex, targetIndex + 2), ['-t', '$1']);
|
|
499
|
+
assert.equal(calls.some((call) => call[0] === 'split-window'), false);
|
|
500
|
+
}
|
|
501
|
+
finally {
|
|
502
|
+
rmSync(cwd, { recursive: true });
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
it('falls back to the default tmux width when the width probe fails', () => {
|
|
506
|
+
const cwd = mkdtempSync(join(tmpdir(), 'omx-question-renderer-width-fallback-'));
|
|
507
|
+
try {
|
|
508
|
+
const stateDir = join(cwd, '.omx', 'state', 'sessions', 's1', 'questions');
|
|
509
|
+
mkdirSync(stateDir, { recursive: true });
|
|
510
|
+
const recordPath = join(stateDir, 'question-1.json');
|
|
511
|
+
writeFileSync(recordPath, JSON.stringify({
|
|
512
|
+
kind: 'omx.question/v1',
|
|
513
|
+
question_id: 'question-1',
|
|
514
|
+
created_at: '2026-05-01T10:08:52.523Z',
|
|
515
|
+
updated_at: '2026-05-01T10:08:52.523Z',
|
|
516
|
+
status: 'pending',
|
|
517
|
+
question: 'x'.repeat(2000),
|
|
518
|
+
options: [{
|
|
519
|
+
label: 'Only option',
|
|
520
|
+
value: 'only',
|
|
521
|
+
description: 'y'.repeat(1000),
|
|
522
|
+
}],
|
|
523
|
+
allow_other: false,
|
|
524
|
+
multi_select: false,
|
|
525
|
+
type: 'single-answerable',
|
|
526
|
+
source: 'deep-interview',
|
|
527
|
+
questions: [{
|
|
528
|
+
id: 'q-1',
|
|
529
|
+
question: 'x'.repeat(2000),
|
|
530
|
+
options: [{
|
|
531
|
+
label: 'Only option',
|
|
532
|
+
value: 'only',
|
|
533
|
+
description: 'y'.repeat(1000),
|
|
534
|
+
}],
|
|
535
|
+
allow_other: false,
|
|
536
|
+
multi_select: false,
|
|
537
|
+
type: 'single-answerable',
|
|
538
|
+
}],
|
|
539
|
+
}, null, 2));
|
|
540
|
+
const calls = [];
|
|
541
|
+
const result = launchQuestionRenderer({
|
|
542
|
+
cwd,
|
|
543
|
+
recordPath,
|
|
544
|
+
sessionId: 's1',
|
|
545
|
+
env: { TMUX: '/tmp/tmux-demo', TMUX_PANE: '%11' },
|
|
546
|
+
}, {
|
|
547
|
+
strategy: 'inside-tmux',
|
|
548
|
+
execTmux: (args) => {
|
|
549
|
+
calls.push(args);
|
|
550
|
+
if (args[0] === 'display-message' && args.includes('#{session_attached}'))
|
|
551
|
+
return '1\n';
|
|
552
|
+
if (args[0] === 'display-message' && args.includes('#{pane_height}'))
|
|
553
|
+
return '20\n';
|
|
554
|
+
if (args[0] === 'display-message' && args.includes('#{pane_width}') && args.includes('-t'))
|
|
555
|
+
throw new Error('width query failed');
|
|
556
|
+
if (args[0] === 'display-message' && args.includes('#{pane_width}') && !args.includes('-t'))
|
|
557
|
+
return '3\n';
|
|
558
|
+
if (args[0] === 'display-message' && args.includes('#{session_id}'))
|
|
559
|
+
return '$1\n';
|
|
560
|
+
if (args[0] === 'new-window')
|
|
561
|
+
return '%88\n';
|
|
562
|
+
if (args[0] === 'list-panes' && args[2] === '%88')
|
|
563
|
+
return '0\t%88\n';
|
|
564
|
+
return '';
|
|
565
|
+
},
|
|
566
|
+
sleepSync: () => { },
|
|
567
|
+
});
|
|
568
|
+
assert.equal(result.renderer, 'tmux-pane');
|
|
569
|
+
assert.equal(result.target, '%88');
|
|
570
|
+
assert.equal(result.return_target, '%11');
|
|
571
|
+
assert.equal(result.return_transport, 'tmux-send-keys');
|
|
572
|
+
const newWindowCall = calls.find((call) => call[0] === 'new-window');
|
|
573
|
+
assert.ok(newWindowCall);
|
|
574
|
+
const targetIndex = newWindowCall.indexOf('-t');
|
|
575
|
+
assert.notEqual(targetIndex, -1);
|
|
576
|
+
assert.deepEqual(newWindowCall.slice(targetIndex, targetIndex + 2), ['-t', '$1']);
|
|
577
|
+
assert.equal(calls.some((call) => call[0] === 'display-message' && call.includes('#{pane_height}')), false);
|
|
578
|
+
assert.equal(calls.some((call) => call[0] === 'display-message' && call.includes('#{pane_width}') && !call.includes('-t')), false);
|
|
579
|
+
assert.equal(calls.some((call) => call[0] === 'split-window'), false);
|
|
580
|
+
}
|
|
581
|
+
finally {
|
|
582
|
+
rmSync(cwd, { recursive: true });
|
|
583
|
+
}
|
|
584
|
+
});
|
|
171
585
|
it('targets the explicit leader pane even when the caller is already inside tmux', () => {
|
|
172
586
|
const calls = [];
|
|
173
587
|
const result = launchQuestionRenderer({
|
|
@@ -239,6 +653,10 @@ describe('launchQuestionRenderer', () => {
|
|
|
239
653
|
}, {
|
|
240
654
|
execTmux: (args) => {
|
|
241
655
|
calls.push(args);
|
|
656
|
+
if (args[0] === 'display-message' && args.includes('#{pane_height}'))
|
|
657
|
+
return '40\n';
|
|
658
|
+
if (args[0] === 'display-message' && args.includes('#{pane_width}'))
|
|
659
|
+
return '80\n';
|
|
242
660
|
if (args[0] === 'split-window')
|
|
243
661
|
return '%78\n';
|
|
244
662
|
if (args[0] === 'list-panes')
|
|
@@ -321,6 +739,10 @@ describe('launchQuestionRenderer', () => {
|
|
|
321
739
|
}, {
|
|
322
740
|
execTmux: (args) => {
|
|
323
741
|
calls.push(args);
|
|
742
|
+
if (args[0] === 'display-message' && args.includes('#{pane_height}'))
|
|
743
|
+
return '40\n';
|
|
744
|
+
if (args[0] === 'display-message' && args.includes('#{pane_width}'))
|
|
745
|
+
return '80\n';
|
|
324
746
|
if (args[0] === 'split-window')
|
|
325
747
|
return '%92\n';
|
|
326
748
|
if (args[0] === 'list-panes')
|
|
@@ -355,6 +777,8 @@ describe('launchQuestionRenderer', () => {
|
|
|
355
777
|
strategy: 'inside-tmux',
|
|
356
778
|
execTmux: (args) => {
|
|
357
779
|
calls.push(args);
|
|
780
|
+
if (args[0] === 'display-message' && args.includes('#{pane_height}'))
|
|
781
|
+
return '40\n';
|
|
358
782
|
if (args[0] === 'display-message')
|
|
359
783
|
return '1\n';
|
|
360
784
|
if (args[0] === 'split-window')
|
|
@@ -419,6 +843,10 @@ describe('launchQuestionRenderer', () => {
|
|
|
419
843
|
strategy: 'inside-tmux',
|
|
420
844
|
execTmux: (args) => {
|
|
421
845
|
calls.push(args);
|
|
846
|
+
if (args[0] === 'display-message' && args.includes('#{pane_height}'))
|
|
847
|
+
return '40\n';
|
|
848
|
+
if (args[0] === 'display-message' && args.includes('#{pane_width}'))
|
|
849
|
+
return '80\n';
|
|
422
850
|
if (args[0] === 'display-message')
|
|
423
851
|
return '1\n';
|
|
424
852
|
if (args[0] === 'split-window')
|
|
@@ -464,6 +892,10 @@ describe('launchQuestionRenderer', () => {
|
|
|
464
892
|
}, {
|
|
465
893
|
strategy: 'inside-tmux',
|
|
466
894
|
execTmux: (args) => {
|
|
895
|
+
if (args[0] === 'display-message' && args.includes('#{pane_height}'))
|
|
896
|
+
return '40\n';
|
|
897
|
+
if (args[0] === 'display-message' && args.includes('#{pane_width}'))
|
|
898
|
+
return '80\n';
|
|
467
899
|
if (args[0] === 'display-message')
|
|
468
900
|
return '1\n';
|
|
469
901
|
if (args[0] === 'split-window')
|
|
@@ -491,6 +923,8 @@ describe('launchQuestionRenderer', () => {
|
|
|
491
923
|
strategy: 'inside-tmux',
|
|
492
924
|
execTmux: (args) => {
|
|
493
925
|
calls.push(args);
|
|
926
|
+
if (args[0] === 'display-message' && args.includes('#{pane_height}'))
|
|
927
|
+
return '40\n';
|
|
494
928
|
if (args[0] === 'display-message')
|
|
495
929
|
return '1\n';
|
|
496
930
|
if (args[0] === 'split-window')
|
|
@@ -531,6 +965,8 @@ describe('launchQuestionRenderer', () => {
|
|
|
531
965
|
strategy: 'inside-tmux',
|
|
532
966
|
execTmux: (args) => {
|
|
533
967
|
calls.push(args);
|
|
968
|
+
if (args[0] === 'display-message' && args.includes('#{pane_height}'))
|
|
969
|
+
return '40\n';
|
|
534
970
|
if (args[0] === 'display-message')
|
|
535
971
|
return '1\n';
|
|
536
972
|
if (args[0] === 'split-window') {
|
|
@@ -628,6 +1064,8 @@ describe('launchQuestionRenderer', () => {
|
|
|
628
1064
|
strategy: 'inside-tmux',
|
|
629
1065
|
execTmux: (args) => {
|
|
630
1066
|
calls.push(args);
|
|
1067
|
+
if (args[0] === 'display-message' && args.includes('#{pane_height}'))
|
|
1068
|
+
return '40\n';
|
|
631
1069
|
if (args[0] === 'display-message')
|
|
632
1070
|
return '1\n';
|
|
633
1071
|
if (args[0] === 'split-window')
|
|
@@ -838,6 +1276,8 @@ describe('question renderer in-flight dedupe', () => {
|
|
|
838
1276
|
return '1\n';
|
|
839
1277
|
if (args[0] === 'display-message' && args.includes('#{pane_height}'))
|
|
840
1278
|
return '40\n';
|
|
1279
|
+
if (args[0] === 'display-message' && args.includes('#{pane_width}'))
|
|
1280
|
+
return '80\n';
|
|
841
1281
|
if (args[0] === 'list-panes' && args[2] === '%41')
|
|
842
1282
|
return '0\t%41\n';
|
|
843
1283
|
if (args[0] === 'kill-pane')
|
|
@@ -893,4 +1333,129 @@ describe('question renderer in-flight dedupe', () => {
|
|
|
893
1333
|
}
|
|
894
1334
|
});
|
|
895
1335
|
});
|
|
1336
|
+
describe('buildQuestionUiTmuxArgs', () => {
|
|
1337
|
+
const recordPath = '/repo/.omx/state/sessions/s1/questions/question-1.json';
|
|
1338
|
+
it('passes env via tmux -e flags on real tmux (no cmux)', () => {
|
|
1339
|
+
const args = buildQuestionUiTmuxArgs(recordPath, {
|
|
1340
|
+
cwd: '/repo',
|
|
1341
|
+
sessionId: 's1',
|
|
1342
|
+
returnTarget: '%11',
|
|
1343
|
+
underCmux: false,
|
|
1344
|
+
});
|
|
1345
|
+
assert.ok(args.includes('-e'));
|
|
1346
|
+
assert.ok(args.includes('OMX_SESSION_ID=s1'));
|
|
1347
|
+
assert.ok(args.includes('OMX_QUESTION_RETURN_TARGET=%11'));
|
|
1348
|
+
assert.ok(args.includes('OMX_QUESTION_RETURN_TRANSPORT=tmux-send-keys'));
|
|
1349
|
+
// tmux execs the command argv directly, so command tokens stay raw/unquoted.
|
|
1350
|
+
assert.ok(args.includes(process.execPath));
|
|
1351
|
+
assert.equal(args.includes('export'), false);
|
|
1352
|
+
assert.equal(args.includes('&&'), false);
|
|
1353
|
+
assert.equal(args.some((token) => /^'.*'$/.test(token)), false);
|
|
1354
|
+
});
|
|
1355
|
+
it('delivers env via a single shell-neutral env-prefixed command and never emits a bare -e under cmux', () => {
|
|
1356
|
+
const args = buildQuestionUiTmuxArgs(recordPath, {
|
|
1357
|
+
cwd: '/repo',
|
|
1358
|
+
sessionId: 's1',
|
|
1359
|
+
returnTarget: '%11',
|
|
1360
|
+
underCmux: true,
|
|
1361
|
+
});
|
|
1362
|
+
// A single shell-command argument: stays correct on both the cmux shim and a
|
|
1363
|
+
// real tmux that inherits cmux env vars (single arg -> run via the shell).
|
|
1364
|
+
assert.equal(args.length, 1);
|
|
1365
|
+
const command = args[0];
|
|
1366
|
+
// `env` keeps it shell-neutral (works in fish/zsh/sh); no POSIX-only `export`/`&&`.
|
|
1367
|
+
assert.match(command, /^env /);
|
|
1368
|
+
assert.equal(command.includes('export'), false);
|
|
1369
|
+
assert.equal(command.includes('&&'), false);
|
|
1370
|
+
assert.equal(command.startsWith('-e'), false);
|
|
1371
|
+
assert.equal(command.includes(' -e '), false);
|
|
1372
|
+
assert.ok(command.includes("OMX_SESSION_ID='s1'"));
|
|
1373
|
+
assert.ok(command.includes("OMX_QUESTION_RETURN_TARGET='%11'"));
|
|
1374
|
+
assert.ok(command.includes("OMX_QUESTION_RETURN_TRANSPORT='tmux-send-keys'"));
|
|
1375
|
+
// The executable env runs is the real command (quoted node), never `-e`.
|
|
1376
|
+
assert.ok(command.includes(`'tmux-send-keys' '${process.execPath}' `));
|
|
1377
|
+
assert.ok(command.endsWith(`'${recordPath}'`));
|
|
1378
|
+
});
|
|
1379
|
+
it('single-quotes values containing =, % and spaces so they survive the cmux shell command', () => {
|
|
1380
|
+
const trickyReturnTarget = 'pane=%5 with spaces';
|
|
1381
|
+
const trickySessionId = 'sess=a%b c';
|
|
1382
|
+
const args = buildQuestionUiTmuxArgs(recordPath, {
|
|
1383
|
+
cwd: '/repo',
|
|
1384
|
+
sessionId: trickySessionId,
|
|
1385
|
+
returnTarget: trickyReturnTarget,
|
|
1386
|
+
underCmux: true,
|
|
1387
|
+
});
|
|
1388
|
+
const command = args[0];
|
|
1389
|
+
// `=`, `%`, and spaces round-trip intact inside single quotes.
|
|
1390
|
+
assert.match(command, /^env /);
|
|
1391
|
+
assert.ok(command.includes(`OMX_SESSION_ID='${trickySessionId}'`));
|
|
1392
|
+
assert.ok(command.includes(`OMX_QUESTION_RETURN_TARGET='${trickyReturnTarget}'`));
|
|
1393
|
+
assert.equal(command.includes(' -e '), false);
|
|
1394
|
+
});
|
|
1395
|
+
it('escapes embedded single quotes in env values under cmux', () => {
|
|
1396
|
+
const args = buildQuestionUiTmuxArgs(recordPath, {
|
|
1397
|
+
cwd: '/repo',
|
|
1398
|
+
sessionId: "a'b=c",
|
|
1399
|
+
underCmux: true,
|
|
1400
|
+
});
|
|
1401
|
+
// POSIX single-quote escaping: a'b=c -> 'a'\''b=c'
|
|
1402
|
+
assert.ok(args[0].includes("OMX_SESSION_ID='a'\\''b=c'"));
|
|
1403
|
+
});
|
|
1404
|
+
it('omits the env prefix entirely when there are no env vars under cmux', () => {
|
|
1405
|
+
const args = buildQuestionUiTmuxArgs(recordPath, { cwd: '/repo', underCmux: true });
|
|
1406
|
+
assert.equal(args.length, 1);
|
|
1407
|
+
assert.equal(args[0].startsWith('env '), false);
|
|
1408
|
+
assert.equal(args[0].includes('export'), false);
|
|
1409
|
+
assert.equal(args[0].includes('&&'), false);
|
|
1410
|
+
assert.ok(args[0].startsWith(`'${process.execPath}' `));
|
|
1411
|
+
});
|
|
1412
|
+
});
|
|
1413
|
+
describe('launchQuestionRenderer under cmux', () => {
|
|
1414
|
+
it('drops bare -e and exports env so the cmux split pane runs the real command', () => {
|
|
1415
|
+
const calls = [];
|
|
1416
|
+
const result = launchQuestionRenderer({
|
|
1417
|
+
cwd: '/repo',
|
|
1418
|
+
recordPath: '/repo/.omx/state/sessions/s1/questions/question-cmux.json',
|
|
1419
|
+
sessionId: 's1',
|
|
1420
|
+
env: {
|
|
1421
|
+
TMUX: '/tmp/tmux-demo',
|
|
1422
|
+
TMUX_PANE: '%11',
|
|
1423
|
+
CMUX_SOCKET_PATH: '/tmp/cmux.sock',
|
|
1424
|
+
},
|
|
1425
|
+
}, {
|
|
1426
|
+
strategy: 'inside-tmux',
|
|
1427
|
+
execTmux: (args) => {
|
|
1428
|
+
calls.push(args);
|
|
1429
|
+
if (args[0] === 'display-message' && args.includes('#{pane_height}'))
|
|
1430
|
+
return '40\n';
|
|
1431
|
+
if (args[0] === 'display-message')
|
|
1432
|
+
return '1\n';
|
|
1433
|
+
if (args[0] === 'split-window')
|
|
1434
|
+
return '%55\n';
|
|
1435
|
+
if (args[0] === 'list-panes')
|
|
1436
|
+
return '0\t%55\n';
|
|
1437
|
+
return '';
|
|
1438
|
+
},
|
|
1439
|
+
sleepSync: () => { },
|
|
1440
|
+
});
|
|
1441
|
+
assert.equal(result.target, '%55');
|
|
1442
|
+
const splitCall = calls.find((call) => call[0] === 'split-window');
|
|
1443
|
+
assert.ok(splitCall);
|
|
1444
|
+
// cwd (-c) and pane flags are preserved exactly as on real tmux.
|
|
1445
|
+
assert.ok(splitCall.includes('-c'));
|
|
1446
|
+
assert.ok(splitCall.includes('/repo'));
|
|
1447
|
+
assert.ok(splitCall.includes('-P'));
|
|
1448
|
+
// No bare `-e` leaks into the cmux pane command (the original bug).
|
|
1449
|
+
assert.equal(splitCall.includes('-e'), false);
|
|
1450
|
+
// The pane command is a single shell-neutral env-prefixed shell-command argument.
|
|
1451
|
+
const paneCommand = splitCall[splitCall.length - 1];
|
|
1452
|
+
assert.match(paneCommand, /^env /);
|
|
1453
|
+
assert.equal(paneCommand.includes(' -e '), false);
|
|
1454
|
+
assert.equal(paneCommand.includes('export'), false);
|
|
1455
|
+
assert.ok(paneCommand.includes("OMX_SESSION_ID='s1'"));
|
|
1456
|
+
assert.ok(paneCommand.includes("OMX_QUESTION_RETURN_TARGET='%11'"));
|
|
1457
|
+
// env runs the real command (quoted node), never `-e`.
|
|
1458
|
+
assert.ok(paneCommand.includes(`'${process.execPath}' `));
|
|
1459
|
+
});
|
|
1460
|
+
});
|
|
896
1461
|
//# sourceMappingURL=renderer.test.js.map
|