commandmate 0.14.0 → 0.14.1
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 +279 -279
- package/.next/app-path-routes-manifest.json +30 -30
- package/.next/build-manifest.json +5 -5
- package/.next/prerender-manifest.json +9 -9
- package/.next/react-loadable-manifest.json +4 -4
- 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/app/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update-check/route.js +1 -1
- package/.next/server/app/api/app/update-check/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/conversation/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/current-output/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/messages/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/session/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/start/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/terminal/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/tools/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/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/hooks/claude-done/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/subscriptions/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/push/vapid/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/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/[id]/todos/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/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/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/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/todos/route_client-reference-manifest.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]/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_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]/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]/prompt-response/route_client-reference-manifest.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]/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/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route.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]/start-polling/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/terminal/route.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]/viewed/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
- package/.next/server/app/chat/page_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_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/[skillId]/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_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/page.js +10 -10
- 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/2119.js +1 -1
- package/.next/server/chunks/2404.js +1 -1
- package/.next/server/chunks/2445.js +1 -1
- package/.next/server/chunks/5959.js +3 -3
- package/.next/server/chunks/6130.js +1 -1
- package/.next/server/chunks/6283.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-manifest.json +5 -5
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/{4027-0c529b322be7d4d8.js → 4027-000402aee1e3520b.js} +1 -1
- package/.next/static/chunks/{6488-1a37ccee1d444c10.js → 6488-74d2f443e43c07b7.js} +1 -1
- package/.next/static/chunks/{6497-dc26c6c6b0b15adf.js → 6497-ea466b22a6c1ae89.js} +1 -1
- package/.next/static/chunks/{8566-b8bbcfd837cc338b.js → 8566-9da32e46c9d3fd54.js} +1 -1
- package/.next/static/chunks/app/chat/{page-9cf4ba62e21d9074.js → page-623f5cb9040de639.js} +1 -1
- package/.next/static/chunks/app/worktrees/[id]/files/[...path]/{page-9ff4d065483cdfe9.js → page-6a45f3744da50d95.js} +1 -1
- package/.next/static/chunks/app/worktrees/[id]/page-983a80ece6289026.js +1 -0
- package/.next/static/chunks/webpack-e655374fed175528.js +1 -0
- package/.next/trace +11 -11
- package/dist/server/src/lib/cli-tools/submit-verified-sender.js +120 -34
- package/dist/server/src/lib/detection/cli-patterns.js +27 -3
- package/dist/server/src/lib/detection/prompt-detect-multiple-choice.js +19 -0
- package/dist/server/src/lib/session/current-output-builder.js +10 -1
- package/dist/server/src/lib/tmux/tmux.js +26 -0
- package/package.json +1 -1
- package/.next/static/chunks/app/worktrees/[id]/page-f2801662a721faab.js +0 -1
- package/.next/static/chunks/webpack-0f9275e279c058eb.js +0 -1
- /package/.next/static/{Etea8LidpB8eWOTac-R8b → 1WyelLMRgPnhvA2HmhZmm}/_buildManifest.js +0 -0
- /package/.next/static/{Etea8LidpB8eWOTac-R8b → 1WyelLMRgPnhvA2HmhZmm}/_ssgManifest.js +0 -0
|
@@ -29,8 +29,18 @@
|
|
|
29
29
|
* `[Pasted text #\d+` placeholder. That placeholder is used only as one
|
|
30
30
|
* "still pending" positive signal (broadened to be version-resilient); the
|
|
31
31
|
* primary decision is "is the message still sitting on the input line?".
|
|
32
|
+
*
|
|
33
|
+
* Issue #1501 hardens the "still pending" branch. A TUI completion popup can
|
|
34
|
+
* REPLACE the typed body with a different command (`/status` -> `/statusline`,
|
|
35
|
+
* `/review` -> `/teamwork-preview`) when Enter selects a highlighted suggestion.
|
|
36
|
+
* The old substring check misread the replacement as "still typed" and resent
|
|
37
|
+
* Enter (executing the wrong command), or as "submitted" and left the residual
|
|
38
|
+
* behind (detonating on the next send). The decision is now three-valued —
|
|
39
|
+
* submitted / pending / replaced — and a `replaced` verdict clears the input
|
|
40
|
+
* line and THROWS instead of resending Enter (see classifySubmit).
|
|
32
41
|
*/
|
|
33
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.classifySubmit = classifySubmit;
|
|
34
44
|
exports.isSubmitted = isSubmitted;
|
|
35
45
|
exports.sendMessageWithSubmitVerification = sendMessageWithSubmitVerification;
|
|
36
46
|
const tmux_1 = require("../tmux/tmux");
|
|
@@ -49,6 +59,18 @@ const logger = (0, logger_1.createLogger)('cli-tools/submit-verified-sender');
|
|
|
49
59
|
* a tool that never renders it simply never matches.
|
|
50
60
|
*/
|
|
51
61
|
const PASTE_PLACEHOLDER_PATTERN = /\[Pasted text[\s#]/;
|
|
62
|
+
/**
|
|
63
|
+
* A slash-command sitting on the input line (Issue #1501).
|
|
64
|
+
*
|
|
65
|
+
* TUI completion popups replace a typed slash command with a highlighted menu
|
|
66
|
+
* item (`/status` -> `/statusline`, `/review` -> `/teamwork-preview`); the
|
|
67
|
+
* result is always another slash command. Scoping the "replaced" verdict to
|
|
68
|
+
* `/…` text keeps idle-prompt placeholders that some TUIs paint on an empty
|
|
69
|
+
* composer (gemini's "Type your message or @path", claude's hints, "? for
|
|
70
|
+
* shortcuts") — none of which start with `/` — from being mistaken for a
|
|
71
|
+
* substitution, so a genuinely-submitted message never fails spuriously.
|
|
72
|
+
*/
|
|
73
|
+
const REPLACEMENT_COMMAND_PATTERN = /^\/[A-Za-z]/;
|
|
52
74
|
/**
|
|
53
75
|
* Prompt input-line markers across the supported TUIs.
|
|
54
76
|
* claude/gemini/copilot: `>` or `❯`; codex: `›`; antigravity: `>`;
|
|
@@ -59,22 +81,18 @@ const INPUT_LINE_MARKER = /^\s*(?:ctx:\d+%\s*)?[>❯›]/;
|
|
|
59
81
|
const VERIFY_WINDOW_LINES = 12;
|
|
60
82
|
/** Default bounded read-back attempts before giving up (throwing). */
|
|
61
83
|
const DEFAULT_VERIFY_ATTEMPTS = 4;
|
|
62
|
-
/** Minimum fragment length used to decide the body is still on the input line. */
|
|
63
|
-
const MIN_FRAGMENT_LENGTH = 3;
|
|
64
|
-
const MAX_FRAGMENT_LENGTH = 24;
|
|
65
84
|
/**
|
|
66
|
-
* First non-blank
|
|
67
|
-
*
|
|
68
|
-
*
|
|
85
|
+
* First non-blank line of the message, trimmed. This is what a TUI shows on the
|
|
86
|
+
* prompt line before the body folds into a paste placeholder. NOT truncated:
|
|
87
|
+
* the replacement check (inputMatchesBody) needs the full first line so that a
|
|
88
|
+
* completion suffix (`/status` -> `/statusline`) is not mistaken for the body.
|
|
69
89
|
*/
|
|
70
|
-
function
|
|
71
|
-
const
|
|
90
|
+
function firstNonBlankLine(message) {
|
|
91
|
+
const line = message
|
|
72
92
|
.split('\n')
|
|
73
93
|
.map((l) => l.trim())
|
|
74
94
|
.find((l) => l.length > 0);
|
|
75
|
-
|
|
76
|
-
return '';
|
|
77
|
-
return firstLine.slice(0, MAX_FRAGMENT_LENGTH);
|
|
95
|
+
return line ?? '';
|
|
78
96
|
}
|
|
79
97
|
/**
|
|
80
98
|
* Locate the active input line within the pane tail: the last line that begins
|
|
@@ -90,40 +108,87 @@ function findInputLine(windowLines) {
|
|
|
90
108
|
}
|
|
91
109
|
return null;
|
|
92
110
|
}
|
|
111
|
+
/** Input line text with the prompt marker stripped and surrounding space trimmed. */
|
|
112
|
+
function stripInputMarker(inputLine) {
|
|
113
|
+
return inputLine.replace(INPUT_LINE_MARKER, '').trim();
|
|
114
|
+
}
|
|
93
115
|
/**
|
|
94
|
-
*
|
|
116
|
+
* Whether the (marker-stripped, non-empty) input-line text is still our
|
|
117
|
+
* unsent body rather than a TUI-substituted command.
|
|
118
|
+
*
|
|
119
|
+
* The still-unsent body appears verbatim on the input line. Line wrapping can
|
|
120
|
+
* visually truncate it to a PREFIX of the first line, but a completion popup
|
|
121
|
+
* always produces a DIFFERENT string — the body plus a completion suffix
|
|
122
|
+
* (`/status` -> `/statusline`) or an unrelated command (`/review` ->
|
|
123
|
+
* `/teamwork-preview`) — which is never a prefix of the body. So the body is
|
|
124
|
+
* "still there" iff the input text is a prefix of (or equals) the body's first
|
|
125
|
+
* line. This deliberately rejects `/statusline` for a `/status` body: the safe
|
|
126
|
+
* rule is "input text that is not the body (or a prefix of it) is NOT resent".
|
|
127
|
+
*/
|
|
128
|
+
function inputMatchesBody(strippedInput, message) {
|
|
129
|
+
const bodyFirstLine = firstNonBlankLine(message);
|
|
130
|
+
if (bodyFirstLine.length === 0)
|
|
131
|
+
return false;
|
|
132
|
+
return bodyFirstLine.startsWith(strippedInput);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Classify a captured pane into submitted / pending / replaced (Issue #1501).
|
|
95
136
|
*
|
|
96
137
|
* Version-independent by design — does NOT require the paste placeholder:
|
|
97
|
-
* A. The tool is generating a response
|
|
98
|
-
* B.
|
|
99
|
-
* C.
|
|
100
|
-
* D.
|
|
138
|
+
* A. The tool is generating a response -> submitted.
|
|
139
|
+
* B. No input line, or the input line is empty -> submitted.
|
|
140
|
+
* C. A paste placeholder is folded on the input line -> pending (Enter eaten).
|
|
141
|
+
* D. The body is still verbatim on the input line -> pending (resend Enter).
|
|
142
|
+
* E. A DIFFERENT slash command is on the input line -> replaced (TUI popup
|
|
143
|
+
* autocompleted the command; clear the line and throw, never resend Enter).
|
|
144
|
+
* F. Any other non-empty text (idle placeholder/hint) -> submitted (unchanged
|
|
145
|
+
* pre-#1501 permissive default, so normal sends never spuriously fail).
|
|
101
146
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
147
|
+
* C–F are scoped to the input line only, so the user-message echo that a TUI
|
|
148
|
+
* prints into its history above the prompt never causes a false verdict.
|
|
104
149
|
*/
|
|
105
|
-
function
|
|
150
|
+
function classifySubmit(output, cliToolId, message) {
|
|
106
151
|
const clean = (0, cli_patterns_1.stripAnsi)(output);
|
|
107
152
|
const windowLines = clean.split('\n').slice(-VERIFY_WINDOW_LINES);
|
|
108
153
|
const windowStr = windowLines.join('\n');
|
|
109
154
|
// A. Actively generating a response => the message was accepted.
|
|
110
155
|
if ((0, cli_patterns_1.detectThinking)(cliToolId, windowStr)) {
|
|
111
|
-
return
|
|
156
|
+
return 'submitted';
|
|
112
157
|
}
|
|
113
158
|
const inputLine = findInputLine(windowLines);
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
159
|
+
// B. No input line visible => the prompt scrolled off / moved on => submitted.
|
|
160
|
+
if (!inputLine) {
|
|
161
|
+
return 'submitted';
|
|
162
|
+
}
|
|
163
|
+
const strippedInput = stripInputMarker(inputLine);
|
|
164
|
+
// B. Empty input line => the message left the box => submitted.
|
|
165
|
+
if (strippedInput.length === 0) {
|
|
166
|
+
return 'submitted';
|
|
167
|
+
}
|
|
168
|
+
// C. A paste placeholder is still folded on the input line => body is there.
|
|
169
|
+
if (PASTE_PLACEHOLDER_PATTERN.test(inputLine)) {
|
|
170
|
+
return 'pending';
|
|
124
171
|
}
|
|
125
|
-
// D.
|
|
126
|
-
|
|
172
|
+
// D. The typed body is still sitting on the input line => resend Enter.
|
|
173
|
+
if (inputMatchesBody(strippedInput, message)) {
|
|
174
|
+
return 'pending';
|
|
175
|
+
}
|
|
176
|
+
// E. A different slash command is on the input line => a completion popup
|
|
177
|
+
// replaced what we typed. (Scoped to `/…` so idle-prompt placeholders that
|
|
178
|
+
// some TUIs paint on an empty composer are never mistaken for this.)
|
|
179
|
+
if (REPLACEMENT_COMMAND_PATTERN.test(strippedInput)) {
|
|
180
|
+
return 'replaced';
|
|
181
|
+
}
|
|
182
|
+
// F. Non-empty, non-command steady-state text (idle placeholder / hint) =>
|
|
183
|
+
// submitted, preserving the pre-#1501 permissive default.
|
|
184
|
+
return 'submitted';
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Backward-compatible boolean view of {@link classifySubmit}: submitted vs not.
|
|
188
|
+
* A `replaced` verdict is NOT "submitted", so this returns false for it too.
|
|
189
|
+
*/
|
|
190
|
+
function isSubmitted(output, cliToolId, message) {
|
|
191
|
+
return classifySubmit(output, cliToolId, message) === 'submitted';
|
|
127
192
|
}
|
|
128
193
|
/**
|
|
129
194
|
* Type a message body and submit it, then verify the submit actually happened.
|
|
@@ -156,11 +221,32 @@ async function sendMessageWithSubmitVerification(params) {
|
|
|
156
221
|
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
157
222
|
await new Promise((resolve) => setTimeout(resolve, verifyDelayMs));
|
|
158
223
|
const output = await (0, tmux_1.capturePane)(sessionName, { startLine: -VERIFY_WINDOW_LINES });
|
|
159
|
-
|
|
224
|
+
const state = classifySubmit(output, cliToolId, message);
|
|
225
|
+
if (state === 'submitted') {
|
|
160
226
|
(0, tmux_capture_cache_1.invalidateCache)(sessionName);
|
|
161
227
|
return;
|
|
162
228
|
}
|
|
163
|
-
|
|
229
|
+
if (state === 'replaced') {
|
|
230
|
+
// A TUI completion popup replaced our command with a different one.
|
|
231
|
+
// Resending Enter would EXECUTE that command (Issue #1501 flavor A);
|
|
232
|
+
// leaving it in place lets the residual detonate on the next send
|
|
233
|
+
// (flavor B). Clear the input line (best-effort) and surface the failure
|
|
234
|
+
// to the caller instead of ever resending Enter.
|
|
235
|
+
try {
|
|
236
|
+
await (0, tmux_1.clearInputLine)(sessionName);
|
|
237
|
+
}
|
|
238
|
+
catch (clearError) {
|
|
239
|
+
logger.error('submit-clear-input-failed', {
|
|
240
|
+
sessionName,
|
|
241
|
+
cliToolId,
|
|
242
|
+
error: clearError instanceof Error ? clearError.message : String(clearError),
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
(0, tmux_capture_cache_1.invalidateCache)(sessionName);
|
|
246
|
+
logger.error('submit-replaced-by-tui-completion', { sessionName, cliToolId, attempt });
|
|
247
|
+
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.`);
|
|
248
|
+
}
|
|
249
|
+
// state === 'pending' — still typed-but-unsent, resend a single Enter and re-check.
|
|
164
250
|
logger.warn('submit-not-confirmed:resending-enter', {
|
|
165
251
|
sessionName,
|
|
166
252
|
cliToolId,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Shared between response-poller.ts and API routes
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.CLAUDE_SESSION_ERROR_REGEX_PATTERNS = exports.CLAUDE_SESSION_ERROR_PATTERNS = exports.extractAnsiSequences = exports.stripAnsi = exports.ANTIGRAVITY_SKIP_PATTERNS = exports.ANTIGRAVITY_SELECTION_LIST_PATTERN = exports.ANTIGRAVITY_SEPARATOR_PATTERN = exports.ANTIGRAVITY_THINKING_PATTERN = exports.ANTIGRAVITY_PROMPT_PATTERN = exports.VIBE_LOCAL_THINKING_PATTERN = exports.VIBE_LOCAL_PROMPT_PATTERN = exports.COPILOT_SKIP_PATTERNS = exports.COPILOT_SELECTION_LIST_PATTERN = exports.COPILOT_SEPARATOR_PATTERN = exports.COPILOT_THINKING_PATTERN = exports.COPILOT_PROMPT_PATTERN = exports.OPENCODE_SKIP_PATTERNS = exports.OPENCODE_SEPARATOR_PATTERN = exports.CLAUDE_SELECTION_LIST_FOOTER = exports.OPENCODE_SELECTION_LIST_PATTERN = exports.OPENCODE_PROCESSING_INDICATOR = exports.OPENCODE_RESPONSE_COMPLETE = exports.OPENCODE_LOADING_PATTERN = exports.OPENCODE_THINKING_PATTERN = exports.OPENCODE_PROMPT_AFTER_RESPONSE = exports.OPENCODE_PROMPT_PATTERN = exports.GEMINI_THINKING_PATTERN = exports.GEMINI_PROMPT_PATTERN = exports.MAX_PASTED_TEXT_RETRIES = exports.PASTED_TEXT_DETECT_DELAY = exports.PASTED_TEXT_PATTERN = exports.CODEX_STATUS_BAR_PATTERN = exports.CODEX_PAGER_FOOTER_PATTERN = exports.CODEX_SELECTION_LIST_PATTERN = exports.CODEX_SEPARATOR_PATTERN = exports.CODEX_DIALOG_PATTERN = exports.CODEX_PROMPT_PATTERN = exports.CLAUDE_TRUST_DIALOG_PATTERN = exports.CLAUDE_SEPARATOR_PATTERN = exports.CLAUDE_PROMPT_PATTERN = exports.CODEX_THINKING_PATTERN = exports.CLAUDE_INTERRUPT_HINT_PATTERN = exports.CLAUDE_THINKING_PATTERN = exports.CLAUDE_SPINNER_CHARS = void 0;
|
|
7
|
+
exports.CLAUDE_SESSION_ERROR_REGEX_PATTERNS = exports.CLAUDE_SESSION_ERROR_PATTERNS = exports.extractAnsiSequences = exports.stripAnsi = exports.ANTIGRAVITY_SKIP_PATTERNS = exports.ANTIGRAVITY_SELECTION_LIST_PATTERN = exports.ANTIGRAVITY_SEPARATOR_PATTERN = exports.ANTIGRAVITY_THINKING_PATTERN = exports.ANTIGRAVITY_PROMPT_PATTERN = exports.VIBE_LOCAL_THINKING_PATTERN = exports.VIBE_LOCAL_PROMPT_PATTERN = exports.COPILOT_SKIP_PATTERNS = exports.COPILOT_SELECTION_LIST_PATTERN = exports.COPILOT_SEPARATOR_PATTERN = exports.COPILOT_THINKING_PATTERN = exports.COPILOT_PROMPT_PATTERN = exports.OPENCODE_SKIP_PATTERNS = exports.OPENCODE_SEPARATOR_PATTERN = exports.CLAUDE_SELECTION_LIST_FOOTER = exports.CLAUDE_MODEL_OVERLAY_FOOTER_PATTERN = exports.OPENCODE_SELECTION_LIST_PATTERN = exports.OPENCODE_PROCESSING_INDICATOR = exports.OPENCODE_RESPONSE_COMPLETE = exports.OPENCODE_LOADING_PATTERN = exports.OPENCODE_THINKING_PATTERN = exports.OPENCODE_PROMPT_AFTER_RESPONSE = exports.OPENCODE_PROMPT_PATTERN = exports.GEMINI_THINKING_PATTERN = exports.GEMINI_PROMPT_PATTERN = exports.MAX_PASTED_TEXT_RETRIES = exports.PASTED_TEXT_DETECT_DELAY = exports.PASTED_TEXT_PATTERN = exports.CODEX_STATUS_BAR_PATTERN = exports.CODEX_PAGER_FOOTER_PATTERN = exports.CODEX_SELECTION_LIST_PATTERN = exports.CODEX_SEPARATOR_PATTERN = exports.CODEX_DIALOG_PATTERN = exports.CODEX_PROMPT_PATTERN = exports.CLAUDE_TRUST_DIALOG_PATTERN = exports.CLAUDE_SEPARATOR_PATTERN = exports.CLAUDE_PROMPT_PATTERN = exports.CODEX_THINKING_PATTERN = exports.CLAUDE_INTERRUPT_HINT_PATTERN = exports.CLAUDE_THINKING_PATTERN = exports.CLAUDE_SPINNER_CHARS = void 0;
|
|
8
8
|
exports.findClaudeChromeStart = findClaudeChromeStart;
|
|
9
9
|
exports.isCodexPromptReady = isCodexPromptReady;
|
|
10
10
|
exports.getCodexActiveDialog = getCodexActiveDialog;
|
|
@@ -464,6 +464,23 @@ exports.OPENCODE_PROCESSING_INDICATOR = /esc interrupt/;
|
|
|
464
464
|
* " Connect a provider esc"
|
|
465
465
|
*/
|
|
466
466
|
exports.OPENCODE_SELECTION_LIST_PATTERN = /^\s*(Select\s+(model|provider)|Connect\s+a\s+provider)/m;
|
|
467
|
+
/**
|
|
468
|
+
* [Issue #1495] Footer signature of Claude Code's `/model` local-settings overlay.
|
|
469
|
+
* Verified against a real Claude Code v2.1.218 capture, the footer reads:
|
|
470
|
+
* "Enter to set as default · s to use this session only · Esc to cancel"
|
|
471
|
+
*
|
|
472
|
+
* The overlay renders a ❯-marked numbered model list ("1. Default … 5. Haiku")
|
|
473
|
+
* under a "Select model" header that detectMultipleChoicePrompt() otherwise
|
|
474
|
+
* matches as a genuine multiple_choice prompt — which let Auto-Yes Enter-confirm
|
|
475
|
+
* a selection and silently change the user's default model. This "set as default"
|
|
476
|
+
* phrasing is unique to the model picker: genuine confirmation prompts never
|
|
477
|
+
* contain it (the trust dialog uses "Enter to confirm · Esc to cancel", Bash-tool
|
|
478
|
+
* approvals use "Esc to cancel · Tab to amend", AskUserQuestion uses
|
|
479
|
+
* "Enter to select · … to navigate"), so it is a safe exclusion signal.
|
|
480
|
+
*
|
|
481
|
+
* Linear pattern, no nested quantifiers — ReDoS safe (S4-001).
|
|
482
|
+
*/
|
|
483
|
+
exports.CLAUDE_MODEL_OVERLAY_FOOTER_PATTERN = /Enter\s+to\s+set\s+as\s+default\b/i;
|
|
467
484
|
/**
|
|
468
485
|
* Claude CLI selection list footer pattern
|
|
469
486
|
* Detects Claude CLI's interactive selection prompts that require
|
|
@@ -472,9 +489,16 @@ exports.OPENCODE_SELECTION_LIST_PATTERN = /^\s*(Select\s+(model|provider)|Connec
|
|
|
472
489
|
* Matches footer instruction lines (known variants):
|
|
473
490
|
* "Enter to select · Tab/Arrow keys to navigate · Esc to cancel"
|
|
474
491
|
* "Enter to select · ↑/↓ to navigate · n to add notes · Esc to cancel"
|
|
475
|
-
* "Enter to confirm · Esc to exit" (/model command)
|
|
492
|
+
* "Enter to confirm · Esc to exit" (legacy /model command footer)
|
|
493
|
+
* "Enter to set as default · s to use this session only · Esc to cancel"
|
|
494
|
+
* (/model command footer as of Claude Code v2.1.218 — Issue #1495)
|
|
495
|
+
*
|
|
496
|
+
* The "set as default" branch lets status-detector classify the `/model` overlay
|
|
497
|
+
* as a Claude selection list (NavigationButtons + ESC hatch, hasActivePrompt=false)
|
|
498
|
+
* once detectPrompt() no longer reports it as a prompt (see
|
|
499
|
+
* CLAUDE_MODEL_OVERLAY_FOOTER_PATTERN).
|
|
476
500
|
*/
|
|
477
|
-
exports.CLAUDE_SELECTION_LIST_FOOTER = /Enter\s+to\s+(?:select\s+.*to\s+navigate|confirm\s+·\s+Esc)/;
|
|
501
|
+
exports.CLAUDE_SELECTION_LIST_FOOTER = /Enter\s+to\s+(?:select\s+.*to\s+navigate|confirm\s+·\s+Esc|set\s+as\s+default)/;
|
|
478
502
|
/**
|
|
479
503
|
* OpenCode TUI separator pattern (Issue #379)
|
|
480
504
|
* Matches lines composed entirely of box-drawing / TUI decoration characters.
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.buildMultipleChoiceResult = buildMultipleChoiceResult;
|
|
5
5
|
exports.detectMultipleChoicePrompt = detectMultipleChoicePrompt;
|
|
6
6
|
const tui_detection_frame_1 = require("./tui-detection-frame");
|
|
7
|
+
const cli_patterns_1 = require("./cli-patterns");
|
|
7
8
|
// ============================================================================
|
|
8
9
|
// Constants
|
|
9
10
|
// ============================================================================
|
|
@@ -590,6 +591,24 @@ function detectMultipleChoicePrompt(output, options, truncateRawContentFn) {
|
|
|
590
591
|
}
|
|
591
592
|
}
|
|
592
593
|
const scanWindow = lines.slice(scanStart, effectiveEnd);
|
|
594
|
+
// [Issue #1495] Claude `/model` local-settings overlay guard.
|
|
595
|
+
// The `/model` overlay renders a ❯-marked numbered model list ("1. Default …
|
|
596
|
+
// 5. Haiku") under a "Select model" header, which the option-collection logic
|
|
597
|
+
// below would otherwise return as a genuine multiple_choice prompt. Auto-Yes
|
|
598
|
+
// (detectAndRespondToPrompt) calls detectPrompt() directly and would resolve +
|
|
599
|
+
// Enter-confirm the default option — silently changing the user's default model.
|
|
600
|
+
// The overlay-unique "Enter to set as default …" footer never appears in real
|
|
601
|
+
// confirmation prompts (permission dialogs, the trust dialog, AskUserQuestion),
|
|
602
|
+
// so bailing out here stops Auto-Yes without touching any real-prompt path. The
|
|
603
|
+
// overlay is modal — no genuine prompt can be active at the same time, and the
|
|
604
|
+
// Claude TUI repaints the overlay away on close, so a stale footer cannot linger
|
|
605
|
+
// in the captured pane. status-detector then reclassifies it as a Claude
|
|
606
|
+
// selection list via CLAUDE_SELECTION_LIST_FOOTER (NavigationButtons + ESC hatch).
|
|
607
|
+
for (const rawLine of scanWindow) {
|
|
608
|
+
if (cli_patterns_1.CLAUDE_MODEL_OVERLAY_FOOTER_PATTERN.test(rawLine)) {
|
|
609
|
+
return noPromptResult(output);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
593
612
|
// Single-pass footer detection: identify both confirmation footer presence and
|
|
594
613
|
// specific "press number to confirm" variant in one iteration (Issue #616).
|
|
595
614
|
let hasConfirmationFooter = false;
|
|
@@ -53,7 +53,16 @@ async function buildCurrentOutput(db, worktreeId, cliToolId, instanceId) {
|
|
|
53
53
|
const isPromptWaiting = statusResult.hasActivePrompt;
|
|
54
54
|
const isSelectionListActive = statusResult.status === 'waiting' && status_detector_1.SELECTION_LIST_REASONS.has(statusResult.reason);
|
|
55
55
|
const isPagerActive = statusResult.reason === status_detector_1.STATUS_REASON.CODEX_PAGER;
|
|
56
|
-
|
|
56
|
+
// Issue #1497: the detection-independent nav hatch (#1017/#1494) is gated on
|
|
57
|
+
// isUnclassifiedActive. A static, unrecognized TUI overlay (e.g. Claude `/help`)
|
|
58
|
+
// whose frame stops changing degrades from `running`/`default` to `ready`/
|
|
59
|
+
// `no_recent_output` once the Auto-Yes poller has stamped lastOutputTimestamp
|
|
60
|
+
// (its sole writer, auto-yes-poller.ts). That is still an interactive-but-
|
|
61
|
+
// unclassified frame — a real idle prompt (`❯`) is classified earlier as
|
|
62
|
+
// `input_prompt`, never as `no_recent_output` — so treat the timed-out fallback
|
|
63
|
+
// as unclassified too and keep the hatch open instead of stranding the user.
|
|
64
|
+
const isUnclassifiedActive = (statusResult.status === 'running' && statusResult.reason === status_detector_1.STATUS_REASON.DEFAULT) ||
|
|
65
|
+
(statusResult.status === 'ready' && statusResult.reason === status_detector_1.STATUS_REASON.NO_RECENT_OUTPUT);
|
|
57
66
|
const realtimeSnippet = lines.slice(-100).join('\n');
|
|
58
67
|
const autoYesState = (0, auto_yes_manager_1.getAutoYesState)(worktreeId, cliToolId, instanceId);
|
|
59
68
|
return {
|
|
@@ -17,6 +17,7 @@ exports.capturePane = capturePane;
|
|
|
17
17
|
exports.killSession = killSession;
|
|
18
18
|
exports.ensureSession = ensureSession;
|
|
19
19
|
exports.sendSpecialKey = sendSpecialKey;
|
|
20
|
+
exports.clearInputLine = clearInputLine;
|
|
20
21
|
exports.isAllowedSpecialKey = isAllowedSpecialKey;
|
|
21
22
|
exports.sendSpecialKeysAndInvalidate = sendSpecialKeysAndInvalidate;
|
|
22
23
|
const child_process_1 = require("child_process");
|
|
@@ -477,6 +478,31 @@ async function sendSpecialKey(sessionName, key) {
|
|
|
477
478
|
throw new Error(`Failed to send special key: ${errorMessage}`);
|
|
478
479
|
}
|
|
479
480
|
}
|
|
481
|
+
/**
|
|
482
|
+
* Clear whatever text is currently sitting on the TUI input line (Issue #1501).
|
|
483
|
+
*
|
|
484
|
+
* Sends a single `C-u` (kill-line-before-cursor) so the prompt is wiped. Used by
|
|
485
|
+
* the submit-verified sender when it detects that a TUI popup autocompleted the
|
|
486
|
+
* typed command into a DIFFERENT one (e.g. `/status` -> `/statusline`): the
|
|
487
|
+
* residual text must be removed so it can neither be executed by a stray Enter
|
|
488
|
+
* nor detonate on the next send.
|
|
489
|
+
*
|
|
490
|
+
* `C-u` is a fixed literal passed via execFile (no shell, no injection) and is
|
|
491
|
+
* intentionally NOT added to ALLOWED_SPECIAL_KEYS / NAVIGATION_KEY_VALUES — it is
|
|
492
|
+
* an internal recovery primitive, never exposed through the special-keys API.
|
|
493
|
+
*
|
|
494
|
+
* @param sessionName - Target session name
|
|
495
|
+
* @throws {Error} If the tmux command fails
|
|
496
|
+
*/
|
|
497
|
+
async function clearInputLine(sessionName) {
|
|
498
|
+
try {
|
|
499
|
+
await execFileAsync('tmux', ['send-keys', '-t', exactTarget(sessionName), 'C-u'], { timeout: DEFAULT_TIMEOUT });
|
|
500
|
+
}
|
|
501
|
+
catch (error) {
|
|
502
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
503
|
+
throw new Error(`Failed to clear tmux input line: ${errorMessage}`);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
480
506
|
/**
|
|
481
507
|
* Allowed navigation key names for special-keys API validation.
|
|
482
508
|
* Used for TUI navigation sequences (e.g., Up/Down cursor, Enter/Escape selection).
|
package/package.json
CHANGED