commandmate 0.48.0 → 0.49.0
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/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +164 -164
- package/.next/app-path-routes-manifest.json +30 -30
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +3 -3
- package/.next/required-server-files.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agents/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agents/versions/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/release-notes/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update-check/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/capabilities/route.js +1 -1
- package/.next/server/app/api/capabilities/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/daily-summary/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/daily-summary/status/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/external-apps/[id]/health/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/external-apps/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/external-apps/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/fs/browse/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/fs/recent-paths/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/agent-event/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/claude-done/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/permission-request/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/metrics/vibe/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/ollama/models/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/push/escalation/route.js +1 -1
- package/.next/server/app/api/push/escalation/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/push/subscriptions/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/push/vapid/route.js +1 -1
- package/.next/server/app/api/push/vapid/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/relays/[relayId]/cancel/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/relays/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/remote/pair/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/clone/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/excluded/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/restore/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/scan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/sync/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/validate-path/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/settings/default-agents/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/settings/default-surface-mode/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sidebar/group-order/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/installations/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/operations/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/reindex/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/slash-commands/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/templates/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/templates/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/verification/runs/[runId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/verification/runs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/capture/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/clear-composer/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-reference/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-tool/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cmate/schedules/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/direct-input/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/env/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/files/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branch/create/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branch/delete/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branches/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/checkout/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/commit/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/diff/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/fetch/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/log/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/pull/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/push/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/reset/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/revert/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stage/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/staged/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/[index]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/apply/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/pop/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/push/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/status/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/unstage/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/working-diff/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/instances/notes/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/instances/opencode/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/[filename]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/marp-render/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/messages/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/opencode/diff/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/opencode/session/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/opencode/share/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/strict/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/strict/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/resolve-target/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/active/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/search/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/install/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/plan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update-plan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/special-keys/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tasks/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/terminal/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/history/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/todos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/upload/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/config/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/cancel/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/viewed/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/apply-default-agents/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
- package/.next/server/app/more/page_client-reference-manifest.js +1 -1
- package/.next/server/app/offline/page_client-reference-manifest.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/proxy/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/repositories/page_client-reference-manifest.js +1 -1
- package/.next/server/app/review/page_client-reference-manifest.js +1 -1
- package/.next/server/app/sessions/page.js +1 -1
- package/.next/server/app/sessions/page.js.nft.json +1 -1
- package/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/[skillId]/page.js +1 -1
- package/.next/server/app/skills/[skillId]/page.js.nft.json +1 -1
- package/.next/server/app/skills/[skillId]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/installed/page_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/files/[...path]/page.js +2 -2
- package/.next/server/app/worktrees/[id]/files/[...path]/page.js.nft.json +1 -1
- package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/page.js +3 -3
- package/.next/server/app/worktrees/[id]/page.js.nft.json +1 -1
- package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +30 -30
- package/.next/server/chunks/1601.js +2 -2
- package/.next/server/chunks/1656.js +2 -2
- package/.next/server/chunks/1945.js +3 -3
- package/.next/server/chunks/2069.js +1 -0
- package/.next/server/chunks/2422.js +1 -1
- package/.next/server/chunks/2534.js +1 -1
- package/.next/server/chunks/3241.js +1 -1
- package/.next/server/chunks/4243.js +1 -1
- package/.next/server/chunks/4248.js +1 -1
- package/.next/server/chunks/4280.js +1 -1
- package/.next/server/chunks/6119.js +1 -1
- package/.next/server/chunks/6130.js +1 -1
- package/.next/server/chunks/6401.js +1 -1
- package/.next/server/chunks/6486.js +1 -1
- package/.next/server/chunks/720.js +2 -2
- package/.next/server/chunks/7604.js +2 -2
- package/.next/server/chunks/7905.js +1 -1
- package/.next/server/chunks/8581.js +3 -3
- package/.next/server/chunks/8728.js +3 -3
- package/.next/server/chunks/8791.js +1 -1
- package/.next/server/chunks/9390.js +1 -1
- package/.next/server/chunks/9627.js +3 -3
- package/.next/server/middleware-manifest.json +5 -5
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/1128-914c559913546bbc.js +1 -0
- package/.next/static/chunks/{2407-26139a8a2f1adbe8.js → 2407-ab92ae9837c39dce.js} +1 -1
- package/.next/static/chunks/{2619-a305ad4810f1393a.js → 2619-04bc32f026a0d946.js} +1 -1
- package/.next/static/chunks/{2863-f0121af1cbbedb3b.js → 2863-7173dc305dffc4f6.js} +1 -1
- package/.next/static/chunks/6288-6fadbba6e84393b2.js +1 -0
- package/.next/static/chunks/7665-e9ef6e0a85b9fa49.js +1 -0
- package/.next/static/chunks/{8165-c68b3dc844210e6a.js → 8165-263172c17ae8e8c3.js} +1 -1
- package/.next/static/chunks/9079-987939d231704218.js +1 -0
- package/.next/static/chunks/app/{layout-46b4cbf07520d9b6.js → layout-aa8e66164a4367af.js} +1 -1
- package/.next/static/chunks/app/sessions/{page-fb241bed579c67aa.js → page-cfd645305e25fd33.js} +1 -1
- package/.next/static/chunks/app/skills/[skillId]/{page-52ba58712faf3378.js → page-cb6e74fd9a6a571d.js} +1 -1
- package/.next/static/chunks/app/worktrees/[id]/files/[...path]/page-704086656fb45699.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/{page-cbc7749a33450cc5.js → page-a8d7886703ffc7b4.js} +1 -1
- package/.next/static/chunks/app/worktrees/[id]/terminal/{page-c601f3b3ece6557e.js → page-373ea37b16d930c7.js} +1 -1
- package/.next/static/css/{743f79ff19ab91e9.css → db8959555c3d9c96.css} +1 -1
- package/.next/trace +13 -13
- package/dist/cli/commands/auto-yes.d.ts.map +1 -1
- package/dist/cli/commands/auto-yes.js +3 -5
- package/dist/cli/commands/capture.d.ts.map +1 -1
- package/dist/cli/commands/capture.js +5 -0
- package/dist/cli/commands/send.d.ts.map +1 -1
- package/dist/cli/commands/send.js +4 -6
- package/dist/cli/commands/verify.d.ts.map +1 -1
- package/dist/cli/commands/verify.js +8 -1
- package/dist/cli/config/duration-constants.d.ts +16 -13
- package/dist/cli/config/duration-constants.d.ts.map +1 -1
- package/dist/cli/config/duration-constants.js +21 -21
- package/dist/cli/docs/agent-operations.d.ts +1 -1
- package/dist/cli/docs/agent-operations.d.ts.map +1 -1
- package/dist/cli/docs/agent-operations.js +2 -1
- package/dist/cli/types/api-responses.d.ts +29 -1
- package/dist/cli/types/api-responses.d.ts.map +1 -1
- package/dist/cli/utils/verify-runner.d.ts +24 -1
- package/dist/cli/utils/verify-runner.d.ts.map +1 -1
- package/dist/cli/utils/verify-runner.js +54 -1
- package/dist/config/auto-yes-duration.d.ts +56 -0
- package/dist/config/auto-yes-duration.d.ts.map +1 -0
- package/dist/config/auto-yes-duration.js +76 -0
- package/dist/server/src/config/auto-yes-config.js +14 -19
- package/dist/server/src/config/auto-yes-duration.js +76 -0
- package/dist/server/src/lib/auto-yes-state.js +9 -2
- package/dist/server/src/lib/cli-tools/submit-verified-sender.js +137 -14
- package/dist/server/src/lib/detection/composer-text.js +215 -11
- package/dist/server/src/lib/detection/prompt-detect-multiple-choice-chrome.js +193 -0
- package/dist/server/src/lib/detection/prompt-detect-multiple-choice.js +8 -191
- package/dist/server/src/lib/detection/upstream-faults.js +19 -2
- package/dist/server/src/lib/hooks/structured-decision-response.js +1 -1
- package/dist/server/src/lib/polling/auto-yes-manual-answer.js +4 -2
- package/dist/server/src/lib/polling/response-checker.js +12 -15
- package/dist/server/src/lib/session/composer-clear.js +56 -4
- package/dist/server/src/lib/session/current-output-builder.js +2 -1
- package/dist/server/src/lib/session/send-attempt-state.js +2 -1
- package/dist/server/src/lib/session/waiting-episode-state-ops.js +152 -0
- package/dist/server/src/lib/session/waiting-episode-state-store.js +202 -0
- package/dist/server/src/lib/session/waiting-episode-state-types.js +6 -0
- package/dist/server/src/lib/session/waiting-episode-state.js +26 -339
- package/dist/server/src/lib/tmux/tmux.js +25 -0
- package/dist/server/src/lib/verification/gate-runner.js +11 -4
- package/docs/en/user-guide/webapp-guide.md +15 -10
- package/package.json +1 -1
- package/release-notes/0.49.0.json +45 -0
- package/.next/server/chunks/6722.js +0 -1
- package/.next/static/chunks/2099-c3e0ab70a1b95dd3.js +0 -1
- package/.next/static/chunks/5371-a549a0d2c2044d41.js +0 -1
- package/.next/static/chunks/6288-09feba367cc2c65e.js +0 -1
- package/.next/static/chunks/9079-406b84d3cc891a87.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/files/[...path]/page-9cf1ecc59e36a004.js +0 -1
- /package/.next/static/{PTlAswXLq8PAe2vUJ8dVP → 3_JtAZaKMJEDFIIfkHv4n}/_buildManifest.js +0 -0
- /package/.next/static/{PTlAswXLq8PAe2vUJ8dVP → 3_JtAZaKMJEDFIIfkHv4n}/_ssgManifest.js +0 -0
|
@@ -456,6 +456,15 @@ function codexComposerRegion(output) {
|
|
|
456
456
|
const box = (0, composer_text_1.findCodexInputBox)(raw);
|
|
457
457
|
if (box === null)
|
|
458
458
|
return null;
|
|
459
|
+
return codexRegionFrom(raw, box.firstRow, box.lastRow);
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* {@link codexComposerRegion} from a known `›` row (Issue #3906): the rows from
|
|
463
|
+
* `firstRow` down to the frame's last block (the footer), ANSI and glyph
|
|
464
|
+
* stripped. `lastRow` is the end of the `›` row's own block, so a composer that
|
|
465
|
+
* is itself the frame's last block is read to the end.
|
|
466
|
+
*/
|
|
467
|
+
function codexRegionFrom(raw, firstRow, lastRow) {
|
|
459
468
|
const isBlank = (row) => (0, cli_patterns_1.stripAnsi)(row).trim() === '';
|
|
460
469
|
let end = raw.length - 1;
|
|
461
470
|
while (end >= 0 && isBlank(raw[end]))
|
|
@@ -463,18 +472,57 @@ function codexComposerRegion(output) {
|
|
|
463
472
|
let lastBlockStart = end;
|
|
464
473
|
while (lastBlockStart > 0 && !isBlank(raw[lastBlockStart - 1]))
|
|
465
474
|
lastBlockStart--;
|
|
466
|
-
const stop = lastBlockStart >
|
|
467
|
-
const rows = raw.slice(
|
|
475
|
+
const stop = lastBlockStart > lastRow ? lastBlockStart - 1 : end;
|
|
476
|
+
const rows = raw.slice(firstRow, stop + 1).map((row) => (0, cli_patterns_1.stripAnsi)(row));
|
|
468
477
|
rows[0] = rows[0].replace(/^\s*\u203A ?/, '');
|
|
469
478
|
return rows.join('\n');
|
|
470
479
|
}
|
|
471
|
-
|
|
480
|
+
/** End (inclusive) of the blank-row-delimited block that starts at `row`. */
|
|
481
|
+
function blockEndFrom(raw, row) {
|
|
482
|
+
let end = row;
|
|
483
|
+
while (end + 1 < raw.length && (0, cli_patterns_1.stripAnsi)(raw[end + 1]).trim() !== '')
|
|
484
|
+
end++;
|
|
485
|
+
return end;
|
|
486
|
+
}
|
|
487
|
+
/** The bottom-most row of `windowLines` the marker reader takes as the input line, as a frame index. */
|
|
488
|
+
function markerRowIndex(windowLines, windowStart) {
|
|
489
|
+
for (let i = windowLines.length - 1; i >= 0; i--) {
|
|
490
|
+
if (INPUT_LINE_MARKER.test(windowLines[i]))
|
|
491
|
+
return windowStart + i;
|
|
492
|
+
}
|
|
493
|
+
return -1;
|
|
494
|
+
}
|
|
495
|
+
function readSubmitComposer(output, cliToolId, reader, lines, windowLines, message) {
|
|
472
496
|
if (cliToolId === 'codex' && reader === 'input-line-marker') {
|
|
497
|
+
const raw = output.split('\n');
|
|
498
|
+
const windowStart = lines.length - windowLines.length;
|
|
473
499
|
const read = (0, composer_text_1.extractComposerText)(output, cliToolId);
|
|
474
500
|
if (read.state === 'content') {
|
|
501
|
+
const box = (0, composer_text_1.findCodexInputBox)(raw);
|
|
502
|
+
// Issue #3906: a dialog option drawn below the composer the extractor
|
|
503
|
+
// located means the dialog is what the pane shows now, on every branch
|
|
504
|
+
// below — that composer is one left from before, not this send's.
|
|
505
|
+
if (box !== null && (0, composer_text_1.codexDialogBelow)(raw, box.firstRow))
|
|
506
|
+
return { kind: 'absent' };
|
|
507
|
+
const outsideWindow = box !== null && box.firstRow < windowStart;
|
|
475
508
|
const region = codexComposerRegion(output) ?? read.text;
|
|
476
509
|
if (read.text.includes('\n')) {
|
|
477
|
-
|
|
510
|
+
// Issue #3906: a composer of many rows with no blank line (20 lines) has
|
|
511
|
+
// its `›` row above the window too, and is marked so like the others.
|
|
512
|
+
return {
|
|
513
|
+
kind: 'text',
|
|
514
|
+
text: firstNonBlankLine(read.text),
|
|
515
|
+
rows: read.text.split('\n'),
|
|
516
|
+
region,
|
|
517
|
+
...(outsideWindow ? { outsideWindow } : {}),
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
// Issue #3906 (1): the `›` row sits above the twelve-row window (a one-line
|
|
521
|
+
// paragraph, a blank line, then a tall one). The marker reader would see no
|
|
522
|
+
// input line there — `unseen` before Enter, `submitted` after it — so the
|
|
523
|
+
// composer `extractComposerText` located is read instead, whole.
|
|
524
|
+
if (outsideWindow && (0, composer_text_1.findCodexLiveComposerRow)(raw) === box.firstRow) {
|
|
525
|
+
return { kind: 'text', text: read.text.trim(), rows: [raw[box.firstRow]], region, outsideWindow: true };
|
|
478
526
|
}
|
|
479
527
|
// Issue #3880 (N1): a one-row excerpt is read as before for its verdict
|
|
480
528
|
// after Enter, but still carries the whole region — `head` with the rest of
|
|
@@ -483,6 +531,45 @@ function readSubmitComposer(output, cliToolId, reader, lines, windowLines) {
|
|
|
483
531
|
const single = readComposer(reader, lines, windowLines);
|
|
484
532
|
return single.kind === 'text' ? { ...single, region } : single;
|
|
485
533
|
}
|
|
534
|
+
if (read.state === 'no_composer') {
|
|
535
|
+
// Issue #3906 (2): more paragraphs than the four blocks the extractor
|
|
536
|
+
// searches. Whatever the marker reader takes must carry the whole region,
|
|
537
|
+
// so the composer's first line alone never passes for the body. The row is
|
|
538
|
+
// the live composer only when no dialog option is drawn below it
|
|
539
|
+
// ({@link findCodexLiveComposerRow}, the order `findCodexBottomGlyphRow`
|
|
540
|
+
// reads a frame in): a composer row left above an approval dialog is
|
|
541
|
+
// never this send's composer, however its text reads.
|
|
542
|
+
const composerRow = (0, composer_text_1.findCodexLiveComposerRow)(raw);
|
|
543
|
+
if (composerRow >= 0) {
|
|
544
|
+
const region = codexRegionFrom(raw, composerRow, blockEndFrom(raw, composerRow));
|
|
545
|
+
const text = stripInputMarker((0, cli_patterns_1.stripAnsi)(raw[composerRow]));
|
|
546
|
+
if (composerRow >= windowStart) {
|
|
547
|
+
const single = readComposer(reader, lines, windowLines);
|
|
548
|
+
if (single.kind === 'text' && markerRowIndex(windowLines, windowStart) === composerRow) {
|
|
549
|
+
return { ...single, region };
|
|
550
|
+
}
|
|
551
|
+
return single;
|
|
552
|
+
}
|
|
553
|
+
// Above the window as well, with no dialog below it.
|
|
554
|
+
if (message !== undefined && text.length > 0 && inputMatchesBody(text, message)) {
|
|
555
|
+
return { kind: 'text', text, rows: [raw[composerRow]], region, outsideWindow: true };
|
|
556
|
+
}
|
|
557
|
+
// Not the body's first line. A composer whose own block is empty or
|
|
558
|
+
// placeholder-only is empty; real text is read too — a `/…` command
|
|
559
|
+
// there is `replaced` as it is inside the window, and anything else is
|
|
560
|
+
// not proof that the message left ({@link classifySubmit}: `indeterminate`).
|
|
561
|
+
const held = (0, composer_text_1.readCodexComposerFrom)(raw.slice(0, blockEndFrom(raw, composerRow) + 1), composerRow);
|
|
562
|
+
if (held.state !== 'content')
|
|
563
|
+
return { kind: 'empty' };
|
|
564
|
+
return {
|
|
565
|
+
kind: 'text',
|
|
566
|
+
text: firstNonBlankLine(held.text),
|
|
567
|
+
rows: [raw[composerRow]],
|
|
568
|
+
region,
|
|
569
|
+
outsideWindow: true,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
}
|
|
486
573
|
}
|
|
487
574
|
return readComposer(reader, lines, windowLines);
|
|
488
575
|
}
|
|
@@ -497,7 +584,9 @@ function readSubmitComposer(output, cliToolId, reader, lines, windowLines) {
|
|
|
497
584
|
* E. A DIFFERENT slash command is in the composer -> replaced (TUI popup
|
|
498
585
|
* autocompleted the command; clear the line and throw, never resend Enter).
|
|
499
586
|
* F. Any other non-empty text (idle placeholder/hint) -> submitted (unchanged
|
|
500
|
-
* pre-#1501 permissive default, so normal sends never spuriously fail)
|
|
587
|
+
* pre-#1501 permissive default, so normal sends never spuriously fail) —
|
|
588
|
+
* except in a codex composer above the read window -> indeterminate
|
|
589
|
+
* (Issue #3906).
|
|
501
590
|
*
|
|
502
591
|
* C–F are scoped to the composer only, so the user-message echo that a TUI
|
|
503
592
|
* prints into its history above the prompt never causes a false verdict.
|
|
@@ -530,7 +619,7 @@ function classifySubmit(output, cliToolId, message, composerSpec = (0, composer_
|
|
|
530
619
|
if ((0, cli_patterns_1.detectThinking)(cliToolId, windowStr)) {
|
|
531
620
|
return 'submitted';
|
|
532
621
|
}
|
|
533
|
-
const composer = readSubmitComposer(output, cliToolId, composerSpec.reader, lines, windowLines);
|
|
622
|
+
const composer = readSubmitComposer(output, cliToolId, composerSpec.reader, lines, windowLines, message);
|
|
534
623
|
// B. No composer visible (scrolled off / dialog / still starting), or the
|
|
535
624
|
// composer is empty => the message left the box => submitted.
|
|
536
625
|
if (composer.kind !== 'text') {
|
|
@@ -551,8 +640,11 @@ function classifySubmit(output, cliToolId, message, composerSpec = (0, composer_
|
|
|
551
640
|
return 'replaced';
|
|
552
641
|
}
|
|
553
642
|
// F. Non-empty, non-command steady-state text (idle placeholder / hint) =>
|
|
554
|
-
// submitted, preserving the pre-#1501 permissive default.
|
|
555
|
-
|
|
643
|
+
// submitted, preserving the pre-#1501 permissive default. Not for a codex
|
|
644
|
+
// composer above the window (Issue #3906): before #3906 that frame was
|
|
645
|
+
// read as no composer at all; text there that is not the body says
|
|
646
|
+
// neither that the message left nor that Enter would send it.
|
|
647
|
+
return composer.outsideWindow ? 'indeterminate' : 'submitted';
|
|
556
648
|
}
|
|
557
649
|
/**
|
|
558
650
|
* Backward-compatible boolean view of {@link classifySubmit}: submitted vs not.
|
|
@@ -590,10 +682,15 @@ function isSubmitted(output, cliToolId, message, composerSpec = (0, composer_spe
|
|
|
590
682
|
* is checked before placeholders, so a body that itself quotes
|
|
591
683
|
* `[Pasted Content 600 chars]` is its own text, not codex's placeholder; a real
|
|
592
684
|
* placeholder is only ever the composer's first row.
|
|
685
|
+
*
|
|
686
|
+
* Issue #3906: a codex composer outside the twelve-row window, or split into
|
|
687
|
+
* more blocks than `extractComposerText` searches, is still read with its whole
|
|
688
|
+
* region ({@link readSubmitComposer}), so neither `unseen` for a body that is
|
|
689
|
+
* there nor `landed` on its first line alone.
|
|
593
690
|
*/
|
|
594
691
|
function classifyPasteLanded(output, cliToolId, message, composerSpec = (0, composer_spec_1.resolveComposerSpec)(cliToolId)) {
|
|
595
692
|
const lines = withoutTrailingBlankRows((0, cli_patterns_1.stripAnsi)(output).split('\n'));
|
|
596
|
-
const composer = readSubmitComposer(output, cliToolId, composerSpec.reader, lines, lines.slice(-VERIFY_WINDOW_LINES));
|
|
693
|
+
const composer = readSubmitComposer(output, cliToolId, composerSpec.reader, lines, lines.slice(-VERIFY_WINDOW_LINES), message);
|
|
597
694
|
if (composer.kind !== 'text')
|
|
598
695
|
return 'unseen';
|
|
599
696
|
if (composer.region !== undefined) {
|
|
@@ -674,9 +771,9 @@ async function pasteBody(sessionName, message) {
|
|
|
674
771
|
}
|
|
675
772
|
}
|
|
676
773
|
/** The composer's first row, for an error message a person has to act on. */
|
|
677
|
-
function describeComposer(output, cliToolId, composerSpec) {
|
|
774
|
+
function describeComposer(output, cliToolId, composerSpec, message) {
|
|
678
775
|
const lines = withoutTrailingBlankRows((0, cli_patterns_1.stripAnsi)(output).split('\n'));
|
|
679
|
-
const composer = readSubmitComposer(output, cliToolId, composerSpec.reader, lines, lines.slice(-VERIFY_WINDOW_LINES));
|
|
776
|
+
const composer = readSubmitComposer(output, cliToolId, composerSpec.reader, lines, lines.slice(-VERIFY_WINDOW_LINES), message);
|
|
680
777
|
if (composer.kind === 'absent')
|
|
681
778
|
return 'no composer on screen';
|
|
682
779
|
if (composer.kind === 'empty')
|
|
@@ -749,7 +846,7 @@ async function confirmPasteLanded(params) {
|
|
|
749
846
|
}
|
|
750
847
|
const bytes = Buffer.byteLength(message, 'utf8');
|
|
751
848
|
const lineCount = message.split('\n').length;
|
|
752
|
-
const shown = describeComposer(output, cliToolId, composer);
|
|
849
|
+
const shown = describeComposer(output, cliToolId, composer, message);
|
|
753
850
|
await withdrawUnsentBody(sessionName, cliToolId, composer);
|
|
754
851
|
(0, tmux_capture_cache_1.invalidateCache)(sessionName);
|
|
755
852
|
logger.error('paste-not-landed', { sessionName, cliToolId, state, bytes, lines: lineCount, shown });
|
|
@@ -781,6 +878,12 @@ function visibleTypedBody(message) {
|
|
|
781
878
|
const blank = lines.findIndex((line, index) => index > first && line.trim() === '');
|
|
782
879
|
return lines.slice(0, blank < 0 ? lines.length : blank).join('\n');
|
|
783
880
|
}
|
|
881
|
+
/** Whether the whole codex composer, read past the bounded readers (Issue #3906), is exactly `message`. */
|
|
882
|
+
function codexComposerShowsWholeBody(output, message, composerSpec) {
|
|
883
|
+
const lines = withoutTrailingBlankRows((0, cli_patterns_1.stripAnsi)(output).split('\n'));
|
|
884
|
+
const composer = readSubmitComposer(output, 'codex', composerSpec.reader, lines, lines.slice(-VERIFY_WINDOW_LINES), message);
|
|
885
|
+
return composer.kind === 'text' && composer.region !== undefined && compactText(composer.region) === compactText(message);
|
|
886
|
+
}
|
|
784
887
|
/**
|
|
785
888
|
* Wait until the composer shows the typed body; only then may Enter be pressed
|
|
786
889
|
* (Issue #3366).
|
|
@@ -794,6 +897,11 @@ function visibleTypedBody(message) {
|
|
|
794
897
|
* a prefix is not enough, because codex can draw the first keystrokes before it
|
|
795
898
|
* decides the rest are a burst.
|
|
796
899
|
*
|
|
900
|
+
* On codex the verdict is the whole composer against the whole body
|
|
901
|
+
* ({@link codexComposerShowsWholeBody}, Issue #3906): the extractor's text is
|
|
902
|
+
* the body's first paragraph at most, so on its own it would let Enter go on a
|
|
903
|
+
* body whose later paragraphs had not been drawn.
|
|
904
|
+
*
|
|
797
905
|
* A body that never appears is taken back out and the send THROWS without
|
|
798
906
|
* Enter — nothing was submitted, so the caller must not print `Message sent.`.
|
|
799
907
|
*
|
|
@@ -808,12 +916,21 @@ async function confirmTypedBodyLanded(params) {
|
|
|
808
916
|
for (let attempt = 0; attempt < Math.max(1, attempts); attempt++) {
|
|
809
917
|
await new Promise((resolve) => setTimeout(resolve, TYPED_BODY_POLL_MS));
|
|
810
918
|
output = await (0, tmux_1.capturePane)(sessionName, { startLine: -composer.verifyCaptureLines });
|
|
919
|
+
// Issue #3906: on codex the whole composer must show the whole body. The
|
|
920
|
+
// extractor's excerpt stops at the first blank row, so its match is only
|
|
921
|
+
// the first paragraph — a five-paragraph body with one paragraph drawn
|
|
922
|
+
// would pass on it — and it cannot reach a body of more than four blocks.
|
|
923
|
+
if (cliToolId === 'codex') {
|
|
924
|
+
if (codexComposerShowsWholeBody(output, message, composer))
|
|
925
|
+
return;
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
811
928
|
const read = (0, composer_text_1.extractComposerText)(output, cliToolId);
|
|
812
929
|
if (read.state === 'content' && compactText(read.text) === expected)
|
|
813
930
|
return;
|
|
814
931
|
}
|
|
815
932
|
const bytes = Buffer.byteLength(message, 'utf8');
|
|
816
|
-
const shown = describeComposer(output, cliToolId, composer);
|
|
933
|
+
const shown = describeComposer(output, cliToolId, composer, message);
|
|
817
934
|
await withdrawUnsentBody(sessionName, cliToolId, composer);
|
|
818
935
|
(0, tmux_capture_cache_1.invalidateCache)(sessionName);
|
|
819
936
|
logger.error('typed-body-not-landed', { sessionName, cliToolId, bytes, attempts, shown });
|
|
@@ -1003,9 +1120,15 @@ async function sendMessageWithSubmitVerification(params) {
|
|
|
1003
1120
|
logger.error('submit-replaced-by-tui-completion', { sessionName, cliToolId, attempt });
|
|
1004
1121
|
throw new Error(`Message was replaced by a TUI autocompletion for session ${sessionName}; the input no longer matches the sent text. Cleared the input line without submitting to avoid executing a different command.`);
|
|
1005
1122
|
}
|
|
1006
|
-
// state === 'pending' after the final
|
|
1123
|
+
// state === 'pending' / 'indeterminate' after the final read-back: give up below.
|
|
1007
1124
|
if (attempt === attempts)
|
|
1008
1125
|
break;
|
|
1126
|
+
// state === 'indeterminate' (Issue #3906) — no Enter: the composer does not
|
|
1127
|
+
// show the body, so another Enter could submit whatever it does show.
|
|
1128
|
+
if (state === 'indeterminate') {
|
|
1129
|
+
logger.warn('submit-not-confirmed:composer-unreadable', { sessionName, cliToolId, attempt });
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1009
1132
|
// state === 'pending' — still typed-but-unsent, resend a single Enter and re-check.
|
|
1010
1133
|
logger.warn('submit-not-confirmed:resending-enter', {
|
|
1011
1134
|
sessionName,
|
|
@@ -93,12 +93,25 @@
|
|
|
93
93
|
* server does. That is deliberate — `useTerminalPanePolling` derives the
|
|
94
94
|
* composer text client-side from the frame it already has, which keeps the
|
|
95
95
|
* WebSocket push path and the HTTP poll path from disagreeing.
|
|
96
|
+
*
|
|
97
|
+
* That is also why the "a dialog below the composer means no composer" rule
|
|
98
|
+
* (Issue #3927, {@link codexDialogBelow}) lives here rather than next to the
|
|
99
|
+
* clear in `session/composer-clear.ts`: the clear, the published
|
|
100
|
+
* `composerText` and the browser's own reading all go through
|
|
101
|
+
* {@link extractLiveComposerText}, and `tools/codex/cli-patterns.ts`, whose
|
|
102
|
+
* `readCodexGlyphRowKind` the rule was first written on, imports the logger.
|
|
96
103
|
*/
|
|
97
104
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
98
105
|
exports.SUPPORTED_COMPOSER_TOOLS = exports.COMPOSER_TEXT_MAX_CHARS = void 0;
|
|
99
106
|
exports.findClaudeInputBox = findClaudeInputBox;
|
|
107
|
+
exports.isCodexComposerRow = isCodexComposerRow;
|
|
108
|
+
exports.isCodexBoldGutterRow = isCodexBoldGutterRow;
|
|
109
|
+
exports.findCodexLiveComposerRow = findCodexLiveComposerRow;
|
|
110
|
+
exports.codexDialogBelow = codexDialogBelow;
|
|
100
111
|
exports.findCodexInputBox = findCodexInputBox;
|
|
101
112
|
exports.extractComposerText = extractComposerText;
|
|
113
|
+
exports.extractLiveComposerText = extractLiveComposerText;
|
|
114
|
+
exports.readCodexComposerFrom = readCodexComposerFrom;
|
|
102
115
|
const ansi_1 = require("./ansi");
|
|
103
116
|
/**
|
|
104
117
|
* Upper bound on the published composer text.
|
|
@@ -208,6 +221,10 @@ function isBlankRow(line) {
|
|
|
208
221
|
* A bare `\u203A` with nothing after it is accepted: `capture-pane` trims trailing
|
|
209
222
|
* whitespace, so that is what a composer with neither text nor placeholder looks
|
|
210
223
|
* like, and calling it `no_composer` would be a worse answer than `empty`.
|
|
224
|
+
*
|
|
225
|
+
* Exported for the send path (Issue #3906): when {@link findCodexInputBox}'s
|
|
226
|
+
* block bound cannot reach a composer split by many blank rows, the sender
|
|
227
|
+
* still has to tell the real `\u203A` row from these look-alikes.
|
|
211
228
|
*/
|
|
212
229
|
function isCodexComposerRow(line) {
|
|
213
230
|
if (line === undefined)
|
|
@@ -226,6 +243,120 @@ function isCodexComposerRow(line) {
|
|
|
226
243
|
const firstText = chars.slice(2).find(c => c.ch !== ' ');
|
|
227
244
|
return firstText === undefined || !firstText.bold;
|
|
228
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Whether a codex `\u203A` row has its gutter space inside the glyph's bold span
|
|
248
|
+
* (Issue #3906).
|
|
249
|
+
*
|
|
250
|
+
* codex also draws the transcript echo of a sent message with a bold glyph that
|
|
251
|
+
* is NOT dim — `ESC[1m\u203A ESC[0m` + text (measured on 0.153.2 and 0.160.0:
|
|
252
|
+
* `tests/fixtures/codex-live-2310/turn-submitted-no-status.txt:36`,
|
|
253
|
+
* `codex-multiline-composer-3205/tc104-raw-pane.txt:20`,
|
|
254
|
+
* `codex-dialogs-0157/approval.txt:24`) — which {@link isCodexComposerRow}
|
|
255
|
+
* accepts. Every measured composer resets right after its glyph
|
|
256
|
+
* (`ESC[1m\u203AESC[0m `), and every echo carries the space in the glyph's span,
|
|
257
|
+
* dim or not. {@link findCodexInputBox} does not need this — it only looks at
|
|
258
|
+
* the frame's last blocks, where the live composer is — but a search that climbs
|
|
259
|
+
* past them into the transcript does.
|
|
260
|
+
*/
|
|
261
|
+
function isCodexBoldGutterRow(line) {
|
|
262
|
+
if (line === undefined)
|
|
263
|
+
return false;
|
|
264
|
+
const chars = scanAttributedChars(line);
|
|
265
|
+
return chars.length > 1 && chars[0].ch === CODEX_PROMPT_GLYPH && chars[1].ch === ' ' && chars[1].bold;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Classify one raw codex row whose first rendered glyph is `\u203A` (Issue #3927).
|
|
269
|
+
*
|
|
270
|
+
* The same reading as `readCodexGlyphRowKind` in `tools/codex/cli-patterns.ts`,
|
|
271
|
+
* re-stated on this module's SGR scan because that module imports the logger
|
|
272
|
+
* and this one is read by the browser bundle. In order: no attributes at all →
|
|
273
|
+
* null (a stripped capture, where every `\u203A` use reads the same); a dim
|
|
274
|
+
* glyph → the transcript echo; a bold label → a dialog option; a dim label →
|
|
275
|
+
* the composer's placeholder; a coloured glyph whose label is drawn in the
|
|
276
|
+
* same colour → a dialog option (from 0.155.1 the composer's glyph is
|
|
277
|
+
* coloured too, but it resets before its label); anything else → the composer.
|
|
278
|
+
* `tests/unit/lib/detection/composer-text-dialog-below-3927.test.ts` holds the
|
|
279
|
+
* two readings equal over the codex fixtures.
|
|
280
|
+
*
|
|
281
|
+
* @param rawRow - One row of the capture, ANSI intact
|
|
282
|
+
*/
|
|
283
|
+
function readCodexGlyphRow(rawRow) {
|
|
284
|
+
if (!rawRow.includes('\x1b'))
|
|
285
|
+
return null;
|
|
286
|
+
const chars = scanAttributedChars(rawRow);
|
|
287
|
+
const glyphIndex = chars.findIndex(c => c.ch.trim() !== '');
|
|
288
|
+
if (glyphIndex < 0 || chars[glyphIndex].ch !== CODEX_PROMPT_GLYPH)
|
|
289
|
+
return null;
|
|
290
|
+
const glyph = chars[glyphIndex];
|
|
291
|
+
if (glyph.dim)
|
|
292
|
+
return 'transcript-echo';
|
|
293
|
+
const label = chars.slice(glyphIndex + 1).find(c => c.ch.trim() !== '');
|
|
294
|
+
if (label?.bold)
|
|
295
|
+
return 'option';
|
|
296
|
+
if (label?.dim)
|
|
297
|
+
return 'composer';
|
|
298
|
+
if (glyph.fg !== null && label?.fg === glyph.fg)
|
|
299
|
+
return 'option';
|
|
300
|
+
return 'composer';
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* A composer row by every reading: {@link isCodexComposerRow}, and not the
|
|
304
|
+
* bold-not-dim echo ({@link isCodexBoldGutterRow}). The caller has already had
|
|
305
|
+
* {@link readCodexGlyphRow} call it `'composer'`, which also requires ANSI
|
|
306
|
+
* attributes: an attribute-free `\u203A` row (a stripped capture, where a dialog
|
|
307
|
+
* option reads the same as a composer) is never taken (Issue #3906).
|
|
308
|
+
*/
|
|
309
|
+
function isLiveComposerRow(row) {
|
|
310
|
+
return isCodexComposerRow(row) && !isCodexBoldGutterRow(row);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* The live codex composer's `\u203A` row, wherever it is in the frame (Issue #3906).
|
|
314
|
+
*
|
|
315
|
+
* Read from the bottom, in the order `findCodexBottomGlyphRow` reads a frame:
|
|
316
|
+
* a dialog's highlighted option reached before any composer row means the
|
|
317
|
+
* dialog is what the pane shows now, and a composer row above it is one left
|
|
318
|
+
* there from before (codex-live-1671) — so the answer is -1, not that row. The
|
|
319
|
+
* transcript echo of a sent message is skipped, dim ({@link isCodexComposerRow}
|
|
320
|
+
* rejects it) or not ({@link isCodexBoldGutterRow}).
|
|
321
|
+
*
|
|
322
|
+
* Moved here from `session/composer-clear.ts` by Issue #3927, with
|
|
323
|
+
* {@link codexDialogBelow}, so the browser can read the same rule.
|
|
324
|
+
*
|
|
325
|
+
* @param rows - The capture split into rows, ANSI intact
|
|
326
|
+
* @returns The row's index, or -1 when no live composer row is drawn
|
|
327
|
+
*/
|
|
328
|
+
function findCodexLiveComposerRow(rows) {
|
|
329
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
330
|
+
const kind = readCodexGlyphRow(rows[i]);
|
|
331
|
+
if (kind === 'option')
|
|
332
|
+
return -1;
|
|
333
|
+
if (kind === 'composer' && isLiveComposerRow(rows[i]))
|
|
334
|
+
return i;
|
|
335
|
+
}
|
|
336
|
+
return -1;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Whether a dialog, not a composer, is what the pane shows below `row` (Issue #3906).
|
|
340
|
+
*
|
|
341
|
+
* The same bottom-up reading as {@link findCodexLiveComposerRow}, stopped at
|
|
342
|
+
* `row`: a dialog's highlighted option reached first means the composer at
|
|
343
|
+
* `row` — even one {@link findCodexInputBox} located among the frame's last
|
|
344
|
+
* blocks — was left there from before the dialog opened. A live composer row
|
|
345
|
+
* reached first means it was not.
|
|
346
|
+
*
|
|
347
|
+
* @param rows - The capture split into rows, ANSI intact
|
|
348
|
+
* @param row - Index of the composer's `\u203A` row
|
|
349
|
+
*/
|
|
350
|
+
function codexDialogBelow(rows, row) {
|
|
351
|
+
for (let i = rows.length - 1; i > row; i--) {
|
|
352
|
+
const kind = readCodexGlyphRow(rows[i]);
|
|
353
|
+
if (kind === 'option')
|
|
354
|
+
return true;
|
|
355
|
+
if (kind === 'composer' && isLiveComposerRow(rows[i]))
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
229
360
|
/**
|
|
230
361
|
* Locate codex's composer in a captured pane (Issue #1890).
|
|
231
362
|
*
|
|
@@ -282,7 +413,7 @@ function findCodexInputBox(lines) {
|
|
|
282
413
|
*/
|
|
283
414
|
function applySgr(params, state) {
|
|
284
415
|
const parts = params === '' ? ['0'] : params.split(';');
|
|
285
|
-
let { dim, bold } = state;
|
|
416
|
+
let { dim, bold, fg } = state;
|
|
286
417
|
for (let i = 0; i < parts.length; i++) {
|
|
287
418
|
const code = parts[i] === '' ? 0 : Number(parts[i]);
|
|
288
419
|
if (Number.isNaN(code))
|
|
@@ -290,15 +421,18 @@ function applySgr(params, state) {
|
|
|
290
421
|
if (code === 38 || code === 48 || code === 58) {
|
|
291
422
|
const mode = parts[i + 1] === '' || parts[i + 1] === undefined ? -1 : Number(parts[i + 1]);
|
|
292
423
|
// 5 = 256-colour (one argument), 2 = 24-bit RGB (three arguments).
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
else
|
|
298
|
-
i += 1;
|
|
424
|
+
const argCount = mode === 5 ? 2 : mode === 2 ? 4 : 1;
|
|
425
|
+
if (code === 38)
|
|
426
|
+
fg = parts.slice(i + 1, i + 1 + argCount).join(';');
|
|
427
|
+
i += argCount;
|
|
299
428
|
continue;
|
|
300
429
|
}
|
|
301
|
-
if (code === 0
|
|
430
|
+
if (code === 0) {
|
|
431
|
+
dim = false;
|
|
432
|
+
bold = false;
|
|
433
|
+
fg = null;
|
|
434
|
+
}
|
|
435
|
+
else if (code === 22) {
|
|
302
436
|
dim = false;
|
|
303
437
|
bold = false;
|
|
304
438
|
}
|
|
@@ -306,8 +440,12 @@ function applySgr(params, state) {
|
|
|
306
440
|
bold = true;
|
|
307
441
|
else if (code === 2)
|
|
308
442
|
dim = true;
|
|
443
|
+
else if (code === 39)
|
|
444
|
+
fg = null;
|
|
445
|
+
else if ((code >= 30 && code <= 37) || (code >= 90 && code <= 97))
|
|
446
|
+
fg = String(code);
|
|
309
447
|
}
|
|
310
|
-
return { dim, bold };
|
|
448
|
+
return { dim, bold, fg };
|
|
311
449
|
}
|
|
312
450
|
/**
|
|
313
451
|
* Split one raw pane row into rendered characters tagged with their dim state.
|
|
@@ -319,12 +457,12 @@ function applySgr(params, state) {
|
|
|
319
457
|
*/
|
|
320
458
|
function scanAttributedChars(line) {
|
|
321
459
|
const out = [];
|
|
322
|
-
let state = { dim: false, bold: false };
|
|
460
|
+
let state = { dim: false, bold: false, fg: null };
|
|
323
461
|
let i = 0;
|
|
324
462
|
while (i < line.length) {
|
|
325
463
|
const ch = line[i];
|
|
326
464
|
if (ch !== '\x1b') {
|
|
327
|
-
out.push({ ch, dim: state.dim, bold: state.bold });
|
|
465
|
+
out.push({ ch, dim: state.dim, bold: state.bold, fg: state.fg });
|
|
328
466
|
i++;
|
|
329
467
|
continue;
|
|
330
468
|
}
|
|
@@ -430,6 +568,72 @@ function extractComposerText(rawCapture, cliToolId) {
|
|
|
430
568
|
const region = locateComposer(lines, cliToolId);
|
|
431
569
|
if (region === null)
|
|
432
570
|
return { text: '', state: 'no_composer' };
|
|
571
|
+
return readComposerRegion(lines, region);
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* The composer the pane shows NOW — {@link extractComposerText}, less a codex
|
|
575
|
+
* composer that a dialog has opened below (Issue #3927).
|
|
576
|
+
*
|
|
577
|
+
* codex draws on the normal screen, so a composer row from before a dialog
|
|
578
|
+
* opened can still sit among the frame's last blocks, above the dialog's
|
|
579
|
+
* highlighted option. {@link extractComposerText} locates it and reports its
|
|
580
|
+
* old text as `content`; this reports `no_composer`, the answer the clear
|
|
581
|
+
* (`session/composer-clear.ts`) has given that frame since #3906. The two
|
|
582
|
+
* display paths — the published `composerText`
|
|
583
|
+
* (`session/current-output-builder.ts`) and the browser's own reading
|
|
584
|
+
* (`hooks/useTerminalPanePolling.ts`) — read through this, so the unsent bar
|
|
585
|
+
* neither shows text its [Clear] cannot remove nor offers a [Run] whose Enter
|
|
586
|
+
* would land in the dialog.
|
|
587
|
+
*
|
|
588
|
+
* {@link extractComposerText} itself keeps its reading. The sender
|
|
589
|
+
* (`cli-tools/submit-verified-sender.ts`) applies {@link codexDialogBelow} to
|
|
590
|
+
* the box it locates on its own branches, and Auto-Yes's Enter fallback and
|
|
591
|
+
* the send-residue check are left reading the box as before.
|
|
592
|
+
*
|
|
593
|
+
* @param rawCapture - A pane capture with ANSI attributes intact
|
|
594
|
+
* @param cliToolId - The CLI whose session was captured
|
|
595
|
+
*/
|
|
596
|
+
function extractLiveComposerText(rawCapture, cliToolId) {
|
|
597
|
+
const read = extractComposerText(rawCapture, cliToolId);
|
|
598
|
+
if (cliToolId !== 'codex' || read.state === 'no_composer' || read.state === 'unsupported_tool')
|
|
599
|
+
return read;
|
|
600
|
+
const lines = rawCapture.split('\n');
|
|
601
|
+
const box = findCodexInputBox(lines);
|
|
602
|
+
if (box !== null && codexDialogBelow(lines, box.firstRow))
|
|
603
|
+
return { text: '', state: 'no_composer' };
|
|
604
|
+
return read;
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Read a codex composer from a known `\u203A` row (Issue #3906).
|
|
608
|
+
*
|
|
609
|
+
* {@link findCodexInputBox} only searches the frame's last four blocks, and
|
|
610
|
+
* takes the composer's own block alone: a body of several paragraphs puts its
|
|
611
|
+
* `\u203A` row higher up, and its text stops at the first blank row. This reads
|
|
612
|
+
* from `firstRow` down to the frame's last block — codex's model / cwd footer —
|
|
613
|
+
* blank rows included, under the same dim rule as {@link extractComposerText}.
|
|
614
|
+
* When the composer's own block is the frame's last block it is read to the end.
|
|
615
|
+
*
|
|
616
|
+
* Whether `firstRow` is the live composer — not a row left above a dialog — is
|
|
617
|
+
* the caller's question; this only reads it.
|
|
618
|
+
*
|
|
619
|
+
* @param lines - Captured pane lines, ANSI intact
|
|
620
|
+
* @param firstRow - Index of a row {@link isCodexComposerRow} accepts
|
|
621
|
+
*/
|
|
622
|
+
function readCodexComposerFrom(lines, firstRow) {
|
|
623
|
+
let end = lines.length - 1;
|
|
624
|
+
while (end >= 0 && isBlankRow(lines[end]))
|
|
625
|
+
end--;
|
|
626
|
+
let lastBlockStart = end;
|
|
627
|
+
while (lastBlockStart > 0 && !isBlankRow(lines[lastBlockStart - 1]))
|
|
628
|
+
lastBlockStart--;
|
|
629
|
+
let ownBlockEnd = firstRow;
|
|
630
|
+
while (ownBlockEnd + 1 < lines.length && !isBlankRow(lines[ownBlockEnd + 1]))
|
|
631
|
+
ownBlockEnd++;
|
|
632
|
+
const lastRow = lastBlockStart > ownBlockEnd ? lastBlockStart - 1 : end;
|
|
633
|
+
return readComposerRegion(lines, { firstRow, lastRow, glyph: CODEX_PROMPT_GLYPH_PATTERN });
|
|
634
|
+
}
|
|
635
|
+
/** The text of a located composer region, under the dim rule. */
|
|
636
|
+
function readComposerRegion(lines, region) {
|
|
433
637
|
const realRows = [];
|
|
434
638
|
const renderedRows = [];
|
|
435
639
|
for (let i = region.firstRow; i <= region.lastRow; i++) {
|