oh-my-codex 0.16.0 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +5 -5
- package/Cargo.toml +1 -1
- package/README.md +2 -2
- package/crates/omx-explore/src/main.rs +434 -28
- package/dist/agents/__tests__/native-config.test.js +50 -0
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/native-config.d.ts.map +1 -1
- package/dist/agents/native-config.js +3 -2
- package/dist/agents/native-config.js.map +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js +1 -0
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/explore.test.js +120 -3
- package/dist/cli/__tests__/explore.test.js.map +1 -1
- package/dist/cli/__tests__/imagegen-continuation.test.d.ts +2 -0
- package/dist/cli/__tests__/imagegen-continuation.test.d.ts.map +1 -0
- package/dist/cli/__tests__/imagegen-continuation.test.js +135 -0
- package/dist/cli/__tests__/imagegen-continuation.test.js.map +1 -0
- package/dist/cli/__tests__/index.test.js +182 -18
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +88 -2
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/ralph.test.js +62 -0
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +48 -0
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +12 -0
- package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +465 -12
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +50 -5
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/__tests__/uninstall.test.js +6 -2
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/explore.d.ts.map +1 -1
- package/dist/cli/explore.js +211 -12
- package/dist/cli/explore.js.map +1 -1
- package/dist/cli/index.d.ts +11 -3
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +124 -18
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +37 -3
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +100 -9
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/team.d.ts +1 -0
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +42 -7
- package/dist/cli/team.js.map +1 -1
- package/dist/cli/ultragoal.d.ts +1 -1
- package/dist/cli/ultragoal.d.ts.map +1 -1
- package/dist/cli/ultragoal.js +29 -8
- package/dist/cli/ultragoal.js.map +1 -1
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +2 -1
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/config/__tests__/codex-hooks.test.js +97 -2
- package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +22 -0
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/__tests__/models.test.js +18 -1
- package/dist/config/__tests__/models.test.js.map +1 -1
- package/dist/config/__tests__/wiki-config-contract.test.js +2 -1
- package/dist/config/__tests__/wiki-config-contract.test.js.map +1 -1
- package/dist/config/codex-hooks.d.ts +17 -3
- package/dist/config/codex-hooks.d.ts.map +1 -1
- package/dist/config/codex-hooks.js +102 -2
- package/dist/config/codex-hooks.js.map +1 -1
- package/dist/config/generator.d.ts +4 -1
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +69 -12
- package/dist/config/generator.js.map +1 -1
- package/dist/config/models.d.ts +6 -0
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +37 -0
- package/dist/config/models.js.map +1 -1
- package/dist/exec/followup.d.ts +1 -0
- package/dist/exec/followup.d.ts.map +1 -1
- package/dist/exec/followup.js +9 -3
- package/dist/exec/followup.js.map +1 -1
- package/dist/hooks/__tests__/anti-slop-workflow.test.js +19 -0
- package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
- package/dist/hooks/__tests__/consensus-execution-handoff.test.js +19 -2
- package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js +40 -0
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/foreground-isolation-contract.test.d.ts +2 -0
- package/dist/hooks/__tests__/foreground-isolation-contract.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/foreground-isolation-contract.test.js +28 -0
- package/dist/hooks/__tests__/foreground-isolation-contract.test.js.map +1 -0
- package/dist/hooks/__tests__/keyword-detector.test.js +37 -25
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +10 -4
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/session.test.js +32 -0
- package/dist/hooks/__tests__/session.test.js.map +1 -1
- package/dist/hooks/__tests__/wiki-docs-contract.test.js +6 -4
- package/dist/hooks/__tests__/wiki-docs-contract.test.js.map +1 -1
- package/dist/hooks/codebase-map.d.ts.map +1 -1
- package/dist/hooks/codebase-map.js +3 -2
- package/dist/hooks/codebase-map.js.map +1 -1
- package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
- package/dist/hooks/extensibility/dispatcher.js +6 -4
- package/dist/hooks/extensibility/dispatcher.js.map +1 -1
- package/dist/hooks/extensibility/logging.d.ts.map +1 -1
- package/dist/hooks/extensibility/logging.js +3 -2
- package/dist/hooks/extensibility/logging.js.map +1 -1
- package/dist/hooks/extensibility/sdk/paths.d.ts.map +1 -1
- package/dist/hooks/extensibility/sdk/paths.js +4 -3
- package/dist/hooks/extensibility/sdk/paths.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +2 -4
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +22 -12
- package/dist/hooks/session.js.map +1 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js +8 -7
- package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +1 -1
- package/dist/hud/__tests__/state.test.js +24 -0
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/index.js +1 -1
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +22 -8
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/tmux.js +1 -1
- package/dist/hud/tmux.js.map +1 -1
- package/dist/imagegen/continuation.d.ts +44 -0
- package/dist/imagegen/continuation.d.ts.map +1 -0
- package/dist/imagegen/continuation.js +220 -0
- package/dist/imagegen/continuation.js.map +1 -0
- package/dist/mcp/__tests__/bootstrap.test.js +47 -2
- package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
- package/dist/mcp/__tests__/server-lifecycle.test.js +49 -1
- package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server.test.js +145 -6
- package/dist/mcp/__tests__/state-server.test.js.map +1 -1
- package/dist/mcp/__tests__/wiki-server.test.js +97 -1
- package/dist/mcp/__tests__/wiki-server.test.js.map +1 -1
- package/dist/mcp/bootstrap.d.ts +2 -0
- package/dist/mcp/bootstrap.d.ts.map +1 -1
- package/dist/mcp/bootstrap.js +95 -15
- package/dist/mcp/bootstrap.js.map +1 -1
- package/dist/mcp/lifecycle-telemetry.d.ts +16 -0
- package/dist/mcp/lifecycle-telemetry.d.ts.map +1 -0
- package/dist/mcp/lifecycle-telemetry.js +95 -0
- package/dist/mcp/lifecycle-telemetry.js.map +1 -0
- package/dist/mcp/wiki-server.d.ts.map +1 -1
- package/dist/mcp/wiki-server.js +11 -2
- package/dist/mcp/wiki-server.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +274 -5
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/stages/team-exec.d.ts +2 -0
- package/dist/pipeline/stages/team-exec.d.ts.map +1 -1
- package/dist/pipeline/stages/team-exec.js +51 -26
- package/dist/pipeline/stages/team-exec.js.map +1 -1
- package/dist/planning/__tests__/artifacts.test.js +138 -3
- package/dist/planning/__tests__/artifacts.test.js.map +1 -1
- package/dist/planning/__tests__/context-pack-status.test.d.ts +2 -0
- package/dist/planning/__tests__/context-pack-status.test.d.ts.map +1 -0
- package/dist/planning/__tests__/context-pack-status.test.js +271 -0
- package/dist/planning/__tests__/context-pack-status.test.js.map +1 -0
- package/dist/planning/artifacts.d.ts +12 -1
- package/dist/planning/artifacts.d.ts.map +1 -1
- package/dist/planning/artifacts.js +32 -9
- package/dist/planning/artifacts.js.map +1 -1
- package/dist/planning/context-pack-status.d.ts +42 -0
- package/dist/planning/context-pack-status.d.ts.map +1 -0
- package/dist/planning/context-pack-status.js +479 -0
- package/dist/planning/context-pack-status.js.map +1 -0
- package/dist/runtime/__tests__/process-tree.test.d.ts +2 -0
- package/dist/runtime/__tests__/process-tree.test.d.ts.map +1 -0
- package/dist/runtime/__tests__/process-tree.test.js +107 -0
- package/dist/runtime/__tests__/process-tree.test.js.map +1 -0
- package/dist/runtime/process-tree.d.ts +28 -0
- package/dist/runtime/process-tree.d.ts.map +1 -0
- package/dist/runtime/process-tree.js +230 -0
- package/dist/runtime/process-tree.js.map +1 -0
- package/dist/scripts/__tests__/codex-native-hook.test.js +267 -2
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-state-io.test.d.ts +2 -0
- package/dist/scripts/__tests__/notify-state-io.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/notify-state-io.test.js +40 -0
- package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -0
- package/dist/scripts/codex-execution-surface.d.ts +1 -1
- package/dist/scripts/codex-execution-surface.d.ts.map +1 -1
- package/dist/scripts/codex-execution-surface.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +141 -9
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
- package/dist/scripts/notify-hook/managed-tmux.js +6 -9
- package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
- package/dist/scripts/notify-hook/process-runner.d.ts.map +1 -1
- package/dist/scripts/notify-hook/process-runner.js +4 -1
- package/dist/scripts/notify-hook/process-runner.js.map +1 -1
- package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
- package/dist/scripts/notify-hook/state-io.js +4 -7
- package/dist/scripts/notify-hook/state-io.js.map +1 -1
- package/dist/scripts/notify-hook.js +25 -3
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/verify-native-agents.d.ts.map +1 -1
- package/dist/scripts/verify-native-agents.js +3 -1
- package/dist/scripts/verify-native-agents.js.map +1 -1
- package/dist/sidecar/__tests__/tmux.test.js +1 -1
- package/dist/sidecar/__tests__/tmux.test.js.map +1 -1
- package/dist/sidecar/tmux.js +1 -1
- package/dist/sidecar/tmux.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +79 -0
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +10 -18
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +45 -1
- 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 +1 -20
- package/dist/state/operations.js.map +1 -1
- package/dist/state/skill-active.d.ts +1 -0
- package/dist/state/skill-active.d.ts.map +1 -1
- package/dist/state/skill-active.js +28 -18
- 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 +3 -2
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/state/workflow-transition.js +2 -2
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/team/__tests__/approved-execution.test.js +96 -0
- package/dist/team/__tests__/approved-execution.test.js.map +1 -1
- package/dist/team/__tests__/followup-planner.test.js +16 -0
- package/dist/team/__tests__/followup-planner.test.js.map +1 -1
- package/dist/team/__tests__/model-contract.test.js +16 -0
- package/dist/team/__tests__/model-contract.test.js.map +1 -1
- package/dist/team/__tests__/repo-aware-decomposition.test.js +20 -0
- package/dist/team/__tests__/repo-aware-decomposition.test.js.map +1 -1
- package/dist/team/__tests__/runtime-cli.test.js +16 -0
- package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +209 -11
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +110 -0
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +9 -0
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-runtime-identity.test.js +6 -0
- package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
- package/dist/team/approved-execution.d.ts +13 -0
- package/dist/team/approved-execution.d.ts.map +1 -1
- package/dist/team/approved-execution.js +40 -22
- package/dist/team/approved-execution.js.map +1 -1
- package/dist/team/followup-planner.d.ts +1 -0
- package/dist/team/followup-planner.d.ts.map +1 -1
- package/dist/team/followup-planner.js +9 -9
- package/dist/team/followup-planner.js.map +1 -1
- package/dist/team/model-contract.d.ts +1 -1
- package/dist/team/model-contract.d.ts.map +1 -1
- package/dist/team/model-contract.js +4 -3
- package/dist/team/model-contract.js.map +1 -1
- package/dist/team/repo-aware-decomposition.d.ts +1 -0
- package/dist/team/repo-aware-decomposition.d.ts.map +1 -1
- package/dist/team/repo-aware-decomposition.js +5 -1
- package/dist/team/repo-aware-decomposition.js.map +1 -1
- package/dist/team/runtime-cli.d.ts +4 -0
- package/dist/team/runtime-cli.d.ts.map +1 -1
- package/dist/team/runtime-cli.js +14 -1
- package/dist/team/runtime-cli.js.map +1 -1
- package/dist/team/runtime.d.ts +1 -0
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +46 -16
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +13 -6
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +7 -0
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js +129 -4
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/__tests__/docs-contract.test.d.ts +2 -0
- package/dist/ultragoal/__tests__/docs-contract.test.d.ts.map +1 -0
- package/dist/ultragoal/__tests__/docs-contract.test.js +31 -0
- package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -0
- package/dist/ultragoal/artifacts.d.ts +6 -2
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +108 -4
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/utils/paths.d.ts +3 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +6 -2
- package/dist/utils/paths.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +44 -14
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/dist/wiki/__tests__/ingest.test.js +35 -1
- package/dist/wiki/__tests__/ingest.test.js.map +1 -1
- package/dist/wiki/__tests__/lint.test.js +14 -1
- package/dist/wiki/__tests__/lint.test.js.map +1 -1
- package/dist/wiki/__tests__/query.test.js +28 -3
- package/dist/wiki/__tests__/query.test.js.map +1 -1
- package/dist/wiki/__tests__/session-hooks.test.js +30 -2
- package/dist/wiki/__tests__/session-hooks.test.js.map +1 -1
- package/dist/wiki/__tests__/storage.test.js +62 -22
- package/dist/wiki/__tests__/storage.test.js.map +1 -1
- package/dist/wiki/index.d.ts +2 -2
- package/dist/wiki/index.d.ts.map +1 -1
- package/dist/wiki/index.js +2 -2
- package/dist/wiki/index.js.map +1 -1
- package/dist/wiki/ingest.js +2 -2
- package/dist/wiki/ingest.js.map +1 -1
- package/dist/wiki/lifecycle.d.ts +5 -0
- package/dist/wiki/lifecycle.d.ts.map +1 -1
- package/dist/wiki/lifecycle.js +31 -4
- package/dist/wiki/lifecycle.js.map +1 -1
- package/dist/wiki/lint.d.ts.map +1 -1
- package/dist/wiki/lint.js +12 -8
- package/dist/wiki/lint.js.map +1 -1
- package/dist/wiki/query.d.ts.map +1 -1
- package/dist/wiki/query.js +3 -2
- package/dist/wiki/query.js.map +1 -1
- package/dist/wiki/storage.d.ts +4 -0
- package/dist/wiki/storage.d.ts.map +1 -1
- package/dist/wiki/storage.js +54 -18
- package/dist/wiki/storage.js.map +1 -1
- package/package.json +1 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/skills/ai-slop-cleaner/SKILL.md +9 -0
- package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +25 -2
- package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/plan/SKILL.md +7 -4
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +13 -3
- package/plugins/oh-my-codex/skills/team/SKILL.md +2 -2
- package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +11 -7
- package/plugins/oh-my-codex/skills/visual-ralph/SKILL.md +8 -0
- package/plugins/oh-my-codex/skills/wiki/SKILL.md +5 -5
- package/prompts/planner.md +1 -1
- package/skills/ai-slop-cleaner/SKILL.md +9 -0
- package/skills/deep-interview/SKILL.md +25 -2
- package/skills/omx-setup/SKILL.md +1 -1
- package/skills/plan/SKILL.md +7 -4
- package/skills/ralplan/SKILL.md +13 -3
- package/skills/team/SKILL.md +2 -2
- package/skills/ultragoal/SKILL.md +11 -7
- package/skills/visual-ralph/SKILL.md +8 -0
- package/skills/wiki/SKILL.md +5 -5
- package/src/scripts/__tests__/codex-native-hook.test.ts +302 -2
- package/src/scripts/__tests__/notify-state-io.test.ts +73 -0
- package/src/scripts/codex-execution-surface.ts +2 -0
- package/src/scripts/codex-native-hook.ts +163 -16
- package/src/scripts/notify-hook/managed-tmux.ts +6 -7
- package/src/scripts/notify-hook/process-runner.ts +4 -1
- package/src/scripts/notify-hook/state-io.ts +5 -7
- package/src/scripts/notify-hook.ts +26 -3
- package/src/scripts/verify-native-agents.ts +3 -1
package/Cargo.lock
CHANGED
|
@@ -32,14 +32,14 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
|
|
32
32
|
|
|
33
33
|
[[package]]
|
|
34
34
|
name = "omx-explore-harness"
|
|
35
|
-
version = "0.16.
|
|
35
|
+
version = "0.16.2"
|
|
36
36
|
dependencies = [
|
|
37
37
|
"libc",
|
|
38
38
|
]
|
|
39
39
|
|
|
40
40
|
[[package]]
|
|
41
41
|
name = "omx-mux"
|
|
42
|
-
version = "0.16.
|
|
42
|
+
version = "0.16.2"
|
|
43
43
|
dependencies = [
|
|
44
44
|
"serde",
|
|
45
45
|
"serde_json",
|
|
@@ -47,7 +47,7 @@ dependencies = [
|
|
|
47
47
|
|
|
48
48
|
[[package]]
|
|
49
49
|
name = "omx-runtime"
|
|
50
|
-
version = "0.16.
|
|
50
|
+
version = "0.16.2"
|
|
51
51
|
dependencies = [
|
|
52
52
|
"omx-mux",
|
|
53
53
|
"omx-runtime-core",
|
|
@@ -56,7 +56,7 @@ dependencies = [
|
|
|
56
56
|
|
|
57
57
|
[[package]]
|
|
58
58
|
name = "omx-runtime-core"
|
|
59
|
-
version = "0.16.
|
|
59
|
+
version = "0.16.2"
|
|
60
60
|
dependencies = [
|
|
61
61
|
"fs2",
|
|
62
62
|
"serde",
|
|
@@ -65,7 +65,7 @@ dependencies = [
|
|
|
65
65
|
|
|
66
66
|
[[package]]
|
|
67
67
|
name = "omx-sparkshell"
|
|
68
|
-
version = "0.16.
|
|
68
|
+
version = "0.16.2"
|
|
69
69
|
dependencies = [
|
|
70
70
|
"omx-mux",
|
|
71
71
|
]
|
package/Cargo.toml
CHANGED
package/README.md
CHANGED
|
@@ -265,7 +265,7 @@ If `Shift+Enter` still submits instead of inserting a newline inside an OMX-mana
|
|
|
265
265
|
|
|
266
266
|
- `omx explore --prompt "..."` is for read-only repository lookup
|
|
267
267
|
- `omx sparkshell <command>` is for shell-native inspection and bounded verification
|
|
268
|
-
- when
|
|
268
|
+
- when `omx_wiki/` exists, `omx explore` can inject wiki-first context before falling back to broader repository search
|
|
269
269
|
- fallback boundaries are explicit: sparkshell-backend fallback is reported on stderr, and spark-model fallback emits stderr metadata plus an `## OMX Explore fallback` notice in stdout so users can see when cost/behavior may differ from the low-cost path
|
|
270
270
|
|
|
271
271
|
Examples:
|
|
@@ -279,7 +279,7 @@ omx sparkshell --tmux-pane %12 --tail-lines 400
|
|
|
279
279
|
### Wiki
|
|
280
280
|
|
|
281
281
|
- `omx wiki` is the CLI parity surface for the OMX wiki MCP server
|
|
282
|
-
- wiki data lives
|
|
282
|
+
- wiki data lives as repository project knowledge under `omx_wiki/`
|
|
283
283
|
- the wiki is markdown-first and search-first, not vector-first
|
|
284
284
|
|
|
285
285
|
Examples:
|
|
@@ -6,22 +6,35 @@ use std::fs::{
|
|
|
6
6
|
use std::io::{self, BufRead, BufReader, Read};
|
|
7
7
|
use std::path::{Path, PathBuf};
|
|
8
8
|
use std::process::{Child, Command, Output, Stdio};
|
|
9
|
-
use std::sync::mpsc::{self, Receiver, RecvTimeoutError};
|
|
9
|
+
use std::sync::mpsc::{self, Receiver, RecvTimeoutError, TryRecvError};
|
|
10
10
|
use std::thread;
|
|
11
11
|
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
|
12
12
|
|
|
13
13
|
const CODEX_BIN_ENV: &str = "OMX_EXPLORE_CODEX_BIN";
|
|
14
14
|
const HARNESS_ROOT_ENV: &str = "OMX_EXPLORE_ROOT";
|
|
15
15
|
const CODEX_TIMEOUT_MS_ENV: &str = "OMX_EXPLORE_CODEX_TIMEOUT_MS";
|
|
16
|
+
const PROCESS_LIMIT_ENV: &str = "OMX_EXPLORE_PROCESS_LIMIT";
|
|
17
|
+
const CODEX_OUTPUT_LIMIT_BYTES_ENV: &str = "OMX_EXPLORE_CODEX_OUTPUT_LIMIT_BYTES";
|
|
16
18
|
const INTERNAL_DIRECT_WRAPPER_FLAG: &str = "--internal-allowlist-direct";
|
|
17
19
|
const INTERNAL_SHELL_WRAPPER_FLAG: &str = "--internal-allowlist-shell";
|
|
18
20
|
const TEMP_ALLOWLIST_DIR_PREFIX: &str = "omx-explore-allowlist-";
|
|
19
21
|
const DEFAULT_CODEX_TIMEOUT_MS: u64 = 180_000;
|
|
22
|
+
const DEFAULT_PROCESS_LIMIT: usize = 96;
|
|
23
|
+
const DEFAULT_CODEX_OUTPUT_LIMIT_BYTES: usize = 8 * 1024 * 1024;
|
|
24
|
+
const PROCESS_LIMIT_POLL_MS: u64 = 100;
|
|
20
25
|
const PROCESS_TERMINATION_GRACE_MS: u64 = 500;
|
|
21
26
|
const PIPE_READER_READY_GRACE_MS: u64 = 25;
|
|
22
27
|
const PIPE_READER_JOIN_GRACE_MS: u64 = 500;
|
|
23
|
-
const EXPLORE_SUBPROCESS_ENV_VARS_TO_SCRUB: &[&str] =
|
|
24
|
-
|
|
28
|
+
const EXPLORE_SUBPROCESS_ENV_VARS_TO_SCRUB: &[&str] = &[
|
|
29
|
+
"BASH_ENV",
|
|
30
|
+
"ENV",
|
|
31
|
+
"PROMPT_COMMAND",
|
|
32
|
+
"NODE_OPTIONS",
|
|
33
|
+
"SHELLOPTS",
|
|
34
|
+
"BASHOPTS",
|
|
35
|
+
"GREP_OPTIONS",
|
|
36
|
+
"GREP_COLORS",
|
|
37
|
+
];
|
|
25
38
|
const WINDOWS_UNSUPPORTED_ALLOWLIST_MESSAGE: &str =
|
|
26
39
|
"omx explore built-in harness is not ready on Windows because its allowlist runtime relies on POSIX sh/bash wrappers. Set OMX_EXPLORE_BIN to a compatible custom harness, prefer `omx sparkshell` for shell-native read-only lookups, or run `omx doctor` for readiness details.";
|
|
27
40
|
|
|
@@ -334,13 +347,59 @@ fn invoke_codex(args: &Args, model: &str, prompt_contract: &str) -> io::Result<A
|
|
|
334
347
|
),
|
|
335
348
|
output_markdown: None,
|
|
336
349
|
}),
|
|
350
|
+
TimedCommandOutput::ProcessLimitExceeded {
|
|
351
|
+
stderr,
|
|
352
|
+
process_count,
|
|
353
|
+
process_limit,
|
|
354
|
+
} => Ok(AttemptResult {
|
|
355
|
+
status_code: 125,
|
|
356
|
+
stderr: format!(
|
|
357
|
+
"[omx explore] codex exec exceeded per-run process limit ({process_count}>{process_limit}); terminated process tree to avoid runaway shell storms{}{}",
|
|
358
|
+
if stderr.trim().is_empty() {
|
|
359
|
+
""
|
|
360
|
+
} else {
|
|
361
|
+
". stderr before termination: "
|
|
362
|
+
},
|
|
363
|
+
stderr.trim()
|
|
364
|
+
),
|
|
365
|
+
output_markdown: None,
|
|
366
|
+
}),
|
|
367
|
+
TimedCommandOutput::OutputLimitExceeded {
|
|
368
|
+
stderr,
|
|
369
|
+
output_limit,
|
|
370
|
+
stream,
|
|
371
|
+
} => Ok(AttemptResult {
|
|
372
|
+
status_code: 126,
|
|
373
|
+
stderr: format!(
|
|
374
|
+
"[omx explore] codex exec exceeded subprocess {stream} output limit ({output_limit} bytes); terminated process tree to avoid unbounded memory growth{}{}",
|
|
375
|
+
if stderr.trim().is_empty() {
|
|
376
|
+
""
|
|
377
|
+
} else {
|
|
378
|
+
". stderr before termination: "
|
|
379
|
+
},
|
|
380
|
+
stderr.trim()
|
|
381
|
+
),
|
|
382
|
+
output_markdown: None,
|
|
383
|
+
}),
|
|
337
384
|
}
|
|
338
385
|
}
|
|
339
386
|
|
|
340
387
|
#[derive(Debug)]
|
|
341
388
|
enum TimedCommandOutput {
|
|
342
389
|
Completed(Output),
|
|
343
|
-
TimedOut {
|
|
390
|
+
TimedOut {
|
|
391
|
+
stderr: String,
|
|
392
|
+
},
|
|
393
|
+
ProcessLimitExceeded {
|
|
394
|
+
stderr: String,
|
|
395
|
+
process_count: usize,
|
|
396
|
+
process_limit: usize,
|
|
397
|
+
},
|
|
398
|
+
OutputLimitExceeded {
|
|
399
|
+
stderr: String,
|
|
400
|
+
output_limit: usize,
|
|
401
|
+
stream: &'static str,
|
|
402
|
+
},
|
|
344
403
|
}
|
|
345
404
|
|
|
346
405
|
fn codex_timeout() -> Duration {
|
|
@@ -352,6 +411,22 @@ fn codex_timeout() -> Duration {
|
|
|
352
411
|
Duration::from_millis(timeout_ms)
|
|
353
412
|
}
|
|
354
413
|
|
|
414
|
+
fn codex_output_limit_bytes() -> usize {
|
|
415
|
+
env::var(CODEX_OUTPUT_LIMIT_BYTES_ENV)
|
|
416
|
+
.ok()
|
|
417
|
+
.and_then(|value| value.trim().parse::<usize>().ok())
|
|
418
|
+
.filter(|value| *value > 0)
|
|
419
|
+
.unwrap_or(DEFAULT_CODEX_OUTPUT_LIMIT_BYTES)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
fn process_limit() -> usize {
|
|
423
|
+
env::var(PROCESS_LIMIT_ENV)
|
|
424
|
+
.ok()
|
|
425
|
+
.and_then(|value| value.trim().parse::<usize>().ok())
|
|
426
|
+
.filter(|value| *value > 0)
|
|
427
|
+
.unwrap_or(DEFAULT_PROCESS_LIMIT)
|
|
428
|
+
}
|
|
429
|
+
|
|
355
430
|
fn run_command_with_timeout(
|
|
356
431
|
mut command: Command,
|
|
357
432
|
timeout: Duration,
|
|
@@ -360,19 +435,37 @@ fn run_command_with_timeout(
|
|
|
360
435
|
configure_process_group(&mut command);
|
|
361
436
|
let mut child = command.spawn()?;
|
|
362
437
|
|
|
363
|
-
let
|
|
364
|
-
let
|
|
438
|
+
let output_limit = codex_output_limit_bytes();
|
|
439
|
+
let mut stdout_reader = spawn_pipe_reader("stdout", child.stdout.take(), output_limit);
|
|
440
|
+
let mut stderr_reader = spawn_pipe_reader("stderr", child.stderr.take(), output_limit);
|
|
365
441
|
|
|
366
442
|
let deadline = Instant::now() + timeout;
|
|
443
|
+
let process_limit = process_limit();
|
|
444
|
+
let mut next_process_limit_poll = Instant::now() + Duration::from_millis(PROCESS_LIMIT_POLL_MS);
|
|
367
445
|
loop {
|
|
368
446
|
if let Some(status) = child.try_wait()? {
|
|
369
|
-
|
|
447
|
+
// The wrapper may exit while grandchildren keep the process group
|
|
448
|
+
// alive. Sweep it before collecting pipes so completed harness
|
|
449
|
+
// runs cannot leave detached shells behind.
|
|
450
|
+
terminate_child_process_tree(&mut child);
|
|
451
|
+
let output = collect_completed_output(
|
|
370
452
|
&mut child,
|
|
371
|
-
&stdout_reader,
|
|
372
|
-
&stderr_reader,
|
|
453
|
+
&mut stdout_reader,
|
|
454
|
+
&mut stderr_reader,
|
|
373
455
|
Duration::from_millis(PIPE_READER_READY_GRACE_MS),
|
|
374
456
|
Duration::from_millis(PIPE_READER_JOIN_GRACE_MS),
|
|
375
|
-
)
|
|
457
|
+
);
|
|
458
|
+
let (stdout, stderr) = match output {
|
|
459
|
+
Ok(output) => output,
|
|
460
|
+
Err(err) if is_output_limit_error(&err) => {
|
|
461
|
+
return Ok(TimedCommandOutput::OutputLimitExceeded {
|
|
462
|
+
stderr: String::new(),
|
|
463
|
+
output_limit,
|
|
464
|
+
stream: output_limit_stream(&err),
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
Err(err) => return Err(err),
|
|
468
|
+
};
|
|
376
469
|
return Ok(TimedCommandOutput::Completed(Output {
|
|
377
470
|
status,
|
|
378
471
|
stdout,
|
|
@@ -384,37 +477,192 @@ fn run_command_with_timeout(
|
|
|
384
477
|
terminate_child_process_tree(&mut child);
|
|
385
478
|
let _ = child.wait();
|
|
386
479
|
let reader_timeout = Duration::from_millis(PIPE_READER_JOIN_GRACE_MS);
|
|
387
|
-
let _ = receive_pipe_reader(&stdout_reader, reader_timeout);
|
|
388
|
-
let stderr =
|
|
480
|
+
let _ = receive_pipe_reader(&mut stdout_reader, reader_timeout);
|
|
481
|
+
let stderr =
|
|
482
|
+
receive_pipe_reader(&mut stderr_reader, reader_timeout).unwrap_or_default();
|
|
389
483
|
return Ok(TimedCommandOutput::TimedOut {
|
|
390
484
|
stderr: String::from_utf8_lossy(&stderr).into_owned(),
|
|
391
485
|
});
|
|
392
486
|
}
|
|
393
487
|
|
|
488
|
+
if Instant::now() >= next_process_limit_poll {
|
|
489
|
+
next_process_limit_poll = Instant::now() + Duration::from_millis(PROCESS_LIMIT_POLL_MS);
|
|
490
|
+
if let Some(process_count) = count_process_tree(child.id()) {
|
|
491
|
+
if process_count > process_limit {
|
|
492
|
+
terminate_child_process_tree(&mut child);
|
|
493
|
+
let _ = child.wait();
|
|
494
|
+
let reader_timeout = Duration::from_millis(PIPE_READER_JOIN_GRACE_MS);
|
|
495
|
+
let _ = receive_pipe_reader(&mut stdout_reader, reader_timeout);
|
|
496
|
+
let stderr =
|
|
497
|
+
receive_pipe_reader(&mut stderr_reader, reader_timeout).unwrap_or_default();
|
|
498
|
+
return Ok(TimedCommandOutput::ProcessLimitExceeded {
|
|
499
|
+
stderr: String::from_utf8_lossy(&stderr).into_owned(),
|
|
500
|
+
process_count,
|
|
501
|
+
process_limit,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if let Some(stream) = poll_output_limit(&mut stdout_reader, &mut stderr_reader)? {
|
|
508
|
+
terminate_child_process_tree(&mut child);
|
|
509
|
+
let _ = child.wait();
|
|
510
|
+
let reader_timeout = Duration::from_millis(PIPE_READER_JOIN_GRACE_MS);
|
|
511
|
+
let stderr = if stream == "stderr" {
|
|
512
|
+
Vec::new()
|
|
513
|
+
} else {
|
|
514
|
+
receive_pipe_reader(&mut stderr_reader, reader_timeout).unwrap_or_default()
|
|
515
|
+
};
|
|
516
|
+
return Ok(TimedCommandOutput::OutputLimitExceeded {
|
|
517
|
+
stderr: String::from_utf8_lossy(&stderr).into_owned(),
|
|
518
|
+
output_limit,
|
|
519
|
+
stream,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
394
523
|
thread::sleep(Duration::from_millis(25));
|
|
395
524
|
}
|
|
396
525
|
}
|
|
397
526
|
|
|
398
|
-
|
|
527
|
+
#[cfg(target_os = "linux")]
|
|
528
|
+
fn count_process_tree(root_pid: u32) -> Option<usize> {
|
|
529
|
+
use std::collections::HashMap;
|
|
530
|
+
let entries = std::fs::read_dir("/proc").ok()?;
|
|
531
|
+
let mut children: HashMap<u32, Vec<u32>> = HashMap::new();
|
|
532
|
+
for entry in entries.flatten() {
|
|
533
|
+
let name = entry.file_name();
|
|
534
|
+
let Some(name) = name.to_str() else {
|
|
535
|
+
continue;
|
|
536
|
+
};
|
|
537
|
+
let Ok(pid) = name.parse::<u32>() else {
|
|
538
|
+
continue;
|
|
539
|
+
};
|
|
540
|
+
let Ok(stat) = std::fs::read_to_string(format!("/proc/{pid}/stat")) else {
|
|
541
|
+
continue;
|
|
542
|
+
};
|
|
543
|
+
let Some(close_paren) = stat.rfind(')') else {
|
|
544
|
+
continue;
|
|
545
|
+
};
|
|
546
|
+
let fields: Vec<&str> = stat[close_paren + 2..].split(' ').collect();
|
|
547
|
+
let Some(ppid) = fields.get(1).and_then(|field| field.parse::<u32>().ok()) else {
|
|
548
|
+
continue;
|
|
549
|
+
};
|
|
550
|
+
children.entry(ppid).or_default().push(pid);
|
|
551
|
+
}
|
|
552
|
+
let mut count = 1;
|
|
553
|
+
let mut stack = children.remove(&root_pid).unwrap_or_default();
|
|
554
|
+
while let Some(pid) = stack.pop() {
|
|
555
|
+
count += 1;
|
|
556
|
+
if let Some(mut nested) = children.remove(&pid) {
|
|
557
|
+
stack.append(&mut nested);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
Some(count)
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
#[cfg(not(target_os = "linux"))]
|
|
564
|
+
fn count_process_tree(_root_pid: u32) -> Option<usize> {
|
|
565
|
+
None
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
struct PipeReader {
|
|
569
|
+
receiver: Receiver<io::Result<Vec<u8>>>,
|
|
570
|
+
cached: Option<io::Result<Vec<u8>>>,
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
fn spawn_pipe_reader<R: Read + Send + 'static>(
|
|
574
|
+
stream: &'static str,
|
|
575
|
+
pipe: Option<R>,
|
|
576
|
+
output_limit: usize,
|
|
577
|
+
) -> PipeReader {
|
|
399
578
|
let (sender, receiver) = mpsc::channel();
|
|
400
579
|
thread::spawn(move || {
|
|
401
|
-
let _ = sender.send(
|
|
580
|
+
let _ = sender.send(read_pipe_bounded(pipe, stream, output_limit));
|
|
402
581
|
});
|
|
403
|
-
|
|
582
|
+
PipeReader {
|
|
583
|
+
receiver,
|
|
584
|
+
cached: None,
|
|
585
|
+
}
|
|
404
586
|
}
|
|
405
587
|
|
|
406
|
-
fn
|
|
588
|
+
fn read_pipe_bounded<R: Read + Send + 'static>(
|
|
589
|
+
pipe: Option<R>,
|
|
590
|
+
stream: &'static str,
|
|
591
|
+
output_limit: usize,
|
|
592
|
+
) -> io::Result<Vec<u8>> {
|
|
407
593
|
let mut bytes = Vec::new();
|
|
408
|
-
|
|
409
|
-
|
|
594
|
+
let Some(pipe) = pipe else {
|
|
595
|
+
return Ok(bytes);
|
|
596
|
+
};
|
|
597
|
+
let mut reader = BufReader::new(pipe);
|
|
598
|
+
let mut chunk = [0_u8; 8192];
|
|
599
|
+
loop {
|
|
600
|
+
let read = reader.read(&mut chunk)?;
|
|
601
|
+
if read == 0 {
|
|
602
|
+
return Ok(bytes);
|
|
603
|
+
}
|
|
604
|
+
if bytes.len().saturating_add(read) > output_limit {
|
|
605
|
+
return Err(output_limit_error(stream, output_limit));
|
|
606
|
+
}
|
|
607
|
+
bytes.extend_from_slice(&chunk[..read]);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
fn output_limit_error(stream: &'static str, output_limit: usize) -> io::Error {
|
|
612
|
+
io::Error::new(
|
|
613
|
+
io::ErrorKind::Other,
|
|
614
|
+
format!("subprocess {stream} exceeded output limit of {output_limit} bytes"),
|
|
615
|
+
)
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
fn is_output_limit_error(err: &io::Error) -> bool {
|
|
619
|
+
err.to_string().contains("exceeded output limit")
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
fn output_limit_stream(err: &io::Error) -> &'static str {
|
|
623
|
+
if err.to_string().contains("stderr") {
|
|
624
|
+
"stderr"
|
|
625
|
+
} else {
|
|
626
|
+
"stdout"
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
fn poll_output_limit(
|
|
631
|
+
stdout_reader: &mut PipeReader,
|
|
632
|
+
stderr_reader: &mut PipeReader,
|
|
633
|
+
) -> io::Result<Option<&'static str>> {
|
|
634
|
+
if let Some(stream) = poll_one_output_limit("stdout", stdout_reader)? {
|
|
635
|
+
return Ok(Some(stream));
|
|
636
|
+
}
|
|
637
|
+
poll_one_output_limit("stderr", stderr_reader)
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
fn poll_one_output_limit(
|
|
641
|
+
stream: &'static str,
|
|
642
|
+
reader: &mut PipeReader,
|
|
643
|
+
) -> io::Result<Option<&'static str>> {
|
|
644
|
+
if reader.cached.is_some() {
|
|
645
|
+
return Ok(None);
|
|
646
|
+
}
|
|
647
|
+
match reader.receiver.try_recv() {
|
|
648
|
+
Ok(Ok(bytes)) => {
|
|
649
|
+
reader.cached = Some(Ok(bytes));
|
|
650
|
+
Ok(None)
|
|
651
|
+
}
|
|
652
|
+
Ok(Err(err)) if is_output_limit_error(&err) => Ok(Some(stream)),
|
|
653
|
+
Ok(Err(err)) => Err(err),
|
|
654
|
+
Err(TryRecvError::Empty) => Ok(None),
|
|
655
|
+
Err(TryRecvError::Disconnected) => Err(io::Error::new(
|
|
656
|
+
io::ErrorKind::Other,
|
|
657
|
+
"subprocess output reader disconnected",
|
|
658
|
+
)),
|
|
410
659
|
}
|
|
411
|
-
Ok(bytes)
|
|
412
660
|
}
|
|
413
661
|
|
|
414
662
|
fn collect_completed_output(
|
|
415
663
|
child: &mut Child,
|
|
416
|
-
stdout_reader: &
|
|
417
|
-
stderr_reader: &
|
|
664
|
+
stdout_reader: &mut PipeReader,
|
|
665
|
+
stderr_reader: &mut PipeReader,
|
|
418
666
|
ready_timeout: Duration,
|
|
419
667
|
cleanup_timeout: Duration,
|
|
420
668
|
) -> io::Result<(Vec<u8>, Vec<u8>)> {
|
|
@@ -438,21 +686,21 @@ fn collect_completed_output(
|
|
|
438
686
|
}
|
|
439
687
|
|
|
440
688
|
fn receive_pipe_reader_if_ready(
|
|
441
|
-
|
|
689
|
+
reader: &mut PipeReader,
|
|
442
690
|
timeout: Duration,
|
|
443
691
|
) -> io::Result<Option<Vec<u8>>> {
|
|
444
|
-
match receive_pipe_reader(
|
|
692
|
+
match receive_pipe_reader(reader, timeout) {
|
|
445
693
|
Ok(bytes) => Ok(Some(bytes)),
|
|
446
694
|
Err(err) if err.kind() == io::ErrorKind::TimedOut => Ok(None),
|
|
447
695
|
Err(err) => Err(err),
|
|
448
696
|
}
|
|
449
697
|
}
|
|
450
698
|
|
|
451
|
-
fn receive_pipe_reader(
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
match receiver.recv_timeout(timeout) {
|
|
699
|
+
fn receive_pipe_reader(reader: &mut PipeReader, timeout: Duration) -> io::Result<Vec<u8>> {
|
|
700
|
+
if let Some(result) = reader.cached.take() {
|
|
701
|
+
return result;
|
|
702
|
+
}
|
|
703
|
+
match reader.receiver.recv_timeout(timeout) {
|
|
456
704
|
Ok(result) => result,
|
|
457
705
|
Err(RecvTimeoutError::Timeout) => Err(io::Error::new(
|
|
458
706
|
io::ErrorKind::TimedOut,
|
|
@@ -2476,6 +2724,126 @@ sleep 30
|
|
|
2476
2724
|
assert_eq!(read_to_string(&term_file).unwrap_or_default(), "term");
|
|
2477
2725
|
}
|
|
2478
2726
|
|
|
2727
|
+
#[cfg(target_os = "linux")]
|
|
2728
|
+
#[test]
|
|
2729
|
+
fn run_command_with_timeout_aborts_suspicious_process_storm() {
|
|
2730
|
+
let _env_guard = env_lock();
|
|
2731
|
+
let _process_guard = process_tree_lock();
|
|
2732
|
+
let root = temp_allowlist_dir().expect("temp root");
|
|
2733
|
+
let script = root.path.join("storm.sh");
|
|
2734
|
+
write_executable(
|
|
2735
|
+
&script,
|
|
2736
|
+
r#"#!/bin/sh
|
|
2737
|
+
while :; do
|
|
2738
|
+
sleep 30 &
|
|
2739
|
+
sleep 0.01
|
|
2740
|
+
done
|
|
2741
|
+
"#,
|
|
2742
|
+
)
|
|
2743
|
+
.expect("write script");
|
|
2744
|
+
|
|
2745
|
+
unsafe {
|
|
2746
|
+
env::set_var(PROCESS_LIMIT_ENV, "12");
|
|
2747
|
+
}
|
|
2748
|
+
let started = Instant::now();
|
|
2749
|
+
let result = run_command_with_timeout(Command::new(&script), Duration::from_secs(10))
|
|
2750
|
+
.expect("run with process storm");
|
|
2751
|
+
unsafe {
|
|
2752
|
+
env::remove_var(PROCESS_LIMIT_ENV);
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
let TimedCommandOutput::ProcessLimitExceeded {
|
|
2756
|
+
process_count,
|
|
2757
|
+
process_limit,
|
|
2758
|
+
..
|
|
2759
|
+
} = result
|
|
2760
|
+
else {
|
|
2761
|
+
panic!("expected process limit failure");
|
|
2762
|
+
};
|
|
2763
|
+
assert!(process_count > process_limit);
|
|
2764
|
+
assert!(started.elapsed() < Duration::from_secs(5));
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
#[cfg(unix)]
|
|
2768
|
+
#[test]
|
|
2769
|
+
fn run_command_with_timeout_fails_closed_on_large_stdout() {
|
|
2770
|
+
let _env_guard = env_lock();
|
|
2771
|
+
let _process_guard = process_tree_lock();
|
|
2772
|
+
let root = temp_allowlist_dir().expect("temp root");
|
|
2773
|
+
let script = root.path.join("large-stdout.sh");
|
|
2774
|
+
write_executable(
|
|
2775
|
+
&script,
|
|
2776
|
+
r#"#!/bin/sh
|
|
2777
|
+
while :; do
|
|
2778
|
+
printf 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
|
2779
|
+
done
|
|
2780
|
+
"#,
|
|
2781
|
+
)
|
|
2782
|
+
.expect("write script");
|
|
2783
|
+
|
|
2784
|
+
unsafe {
|
|
2785
|
+
env::set_var(CODEX_OUTPUT_LIMIT_BYTES_ENV, "4096");
|
|
2786
|
+
}
|
|
2787
|
+
let started = Instant::now();
|
|
2788
|
+
let result = run_command_with_timeout(Command::new(&script), Duration::from_secs(10))
|
|
2789
|
+
.expect("run with large stdout");
|
|
2790
|
+
unsafe {
|
|
2791
|
+
env::remove_var(CODEX_OUTPUT_LIMIT_BYTES_ENV);
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
let TimedCommandOutput::OutputLimitExceeded {
|
|
2795
|
+
stream,
|
|
2796
|
+
output_limit,
|
|
2797
|
+
..
|
|
2798
|
+
} = result
|
|
2799
|
+
else {
|
|
2800
|
+
panic!("expected stdout output limit failure");
|
|
2801
|
+
};
|
|
2802
|
+
assert_eq!(stream, "stdout");
|
|
2803
|
+
assert_eq!(output_limit, 4096);
|
|
2804
|
+
assert!(started.elapsed() < Duration::from_secs(3));
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
#[cfg(unix)]
|
|
2808
|
+
#[test]
|
|
2809
|
+
fn run_command_with_timeout_fails_closed_on_large_stderr() {
|
|
2810
|
+
let _env_guard = env_lock();
|
|
2811
|
+
let _process_guard = process_tree_lock();
|
|
2812
|
+
let root = temp_allowlist_dir().expect("temp root");
|
|
2813
|
+
let script = root.path.join("large-stderr.sh");
|
|
2814
|
+
write_executable(
|
|
2815
|
+
&script,
|
|
2816
|
+
r#"#!/bin/sh
|
|
2817
|
+
while :; do
|
|
2818
|
+
printf 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' >&2
|
|
2819
|
+
done
|
|
2820
|
+
"#,
|
|
2821
|
+
)
|
|
2822
|
+
.expect("write script");
|
|
2823
|
+
|
|
2824
|
+
unsafe {
|
|
2825
|
+
env::set_var(CODEX_OUTPUT_LIMIT_BYTES_ENV, "4096");
|
|
2826
|
+
}
|
|
2827
|
+
let started = Instant::now();
|
|
2828
|
+
let result = run_command_with_timeout(Command::new(&script), Duration::from_secs(10))
|
|
2829
|
+
.expect("run with large stderr");
|
|
2830
|
+
unsafe {
|
|
2831
|
+
env::remove_var(CODEX_OUTPUT_LIMIT_BYTES_ENV);
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
let TimedCommandOutput::OutputLimitExceeded {
|
|
2835
|
+
stream,
|
|
2836
|
+
output_limit,
|
|
2837
|
+
..
|
|
2838
|
+
} = result
|
|
2839
|
+
else {
|
|
2840
|
+
panic!("expected stderr output limit failure");
|
|
2841
|
+
};
|
|
2842
|
+
assert_eq!(stream, "stderr");
|
|
2843
|
+
assert_eq!(output_limit, 4096);
|
|
2844
|
+
assert!(started.elapsed() < Duration::from_secs(3));
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2479
2847
|
#[cfg(unix)]
|
|
2480
2848
|
#[test]
|
|
2481
2849
|
fn run_command_with_timeout_closes_inherited_stdio_after_parent_exit() {
|
|
@@ -2510,6 +2878,44 @@ exit 0
|
|
|
2510
2878
|
assert_eq!(String::from_utf8_lossy(&output.stderr), "parent stderr\n");
|
|
2511
2879
|
}
|
|
2512
2880
|
|
|
2881
|
+
#[cfg(unix)]
|
|
2882
|
+
#[test]
|
|
2883
|
+
fn run_command_with_timeout_sweeps_detached_grandchildren_after_parent_exit() {
|
|
2884
|
+
let _env_guard = env_lock();
|
|
2885
|
+
let _process_guard = process_tree_lock();
|
|
2886
|
+
let root = temp_allowlist_dir().expect("temp root");
|
|
2887
|
+
let term_file = root.path.join("orphan.term");
|
|
2888
|
+
let ready_file = root.path.join("orphan.ready");
|
|
2889
|
+
let script = root.path.join("spawn-detached-grandchild.sh");
|
|
2890
|
+
write_executable(
|
|
2891
|
+
&script,
|
|
2892
|
+
&format!(
|
|
2893
|
+
r#"#!/bin/sh
|
|
2894
|
+
(trap 'printf term > {}; exit 0' TERM; printf ready > {}; sleep 30) >/dev/null 2>&1 &
|
|
2895
|
+
while [ ! -f {} ]; do
|
|
2896
|
+
sleep 0.01
|
|
2897
|
+
done
|
|
2898
|
+
printf 'parent done\n'
|
|
2899
|
+
exit 0
|
|
2900
|
+
"#,
|
|
2901
|
+
shell_quote(&term_file.display().to_string()),
|
|
2902
|
+
shell_quote(&ready_file.display().to_string()),
|
|
2903
|
+
shell_quote(&ready_file.display().to_string()),
|
|
2904
|
+
),
|
|
2905
|
+
)
|
|
2906
|
+
.expect("write script");
|
|
2907
|
+
|
|
2908
|
+
let result = run_command_with_timeout(Command::new(&script), Duration::from_secs(10))
|
|
2909
|
+
.expect("run with detached grandchild");
|
|
2910
|
+
|
|
2911
|
+
let TimedCommandOutput::Completed(output) = result else {
|
|
2912
|
+
panic!("expected parent completion");
|
|
2913
|
+
};
|
|
2914
|
+
assert!(output.status.success());
|
|
2915
|
+
assert_eq!(String::from_utf8_lossy(&output.stdout), "parent done\n");
|
|
2916
|
+
assert_eq!(read_to_string(&term_file).unwrap_or_default(), "term");
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2513
2919
|
fn fallback_test_event() -> FallbackEvent {
|
|
2514
2920
|
FallbackEvent {
|
|
2515
2921
|
from_model: "spark-model".to_string(),
|