hammoc 1.6.0 → 1.7.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/README.md +6 -4
- package/package.json +2 -2
- package/packages/client/dist/assets/{agentExampleHighlight-ltj9ce0U.js → agentExampleHighlight-7Olvqdpf.js} +1 -1
- package/packages/client/dist/assets/{commandTokenHighlight-ji_ViMb4.js → commandTokenHighlight-DtKwh4lx.js} +1 -1
- package/packages/client/dist/assets/{index-BT4RIi0U.js → index-Bee52CKG.js} +556 -510
- package/packages/client/dist/assets/index-CVy582lU.css +32 -0
- package/packages/client/dist/assets/{index-B-DiRGuz.js → index-DLHBUw8c.js} +1 -1
- package/packages/client/dist/assets/{snippetTokenHighlight-CP3v4o2g.js → snippetTokenHighlight-C8O688f4.js} +1 -1
- package/packages/client/dist/index.html +2 -2
- package/packages/client/dist/sw.js +2 -2
- package/packages/server/dist/_dbg_agent_rows.d.ts +2 -0
- package/packages/server/dist/_dbg_agent_rows.d.ts.map +1 -0
- package/packages/server/dist/_dbg_agent_rows.js +60 -0
- package/packages/server/dist/_dbg_agent_rows.js.map +1 -0
- package/packages/server/dist/_dbg_flicker.d.ts +2 -0
- package/packages/server/dist/_dbg_flicker.d.ts.map +1 -0
- package/packages/server/dist/_dbg_flicker.js +38 -0
- package/packages/server/dist/_dbg_flicker.js.map +1 -0
- package/packages/server/dist/_harness_cli.d.ts +2 -0
- package/packages/server/dist/_harness_cli.d.ts.map +1 -0
- package/packages/server/dist/_harness_cli.js +81 -0
- package/packages/server/dist/_harness_cli.js.map +1 -0
- package/packages/server/dist/_harness_provcard.d.ts +2 -0
- package/packages/server/dist/_harness_provcard.d.ts.map +1 -0
- package/packages/server/dist/_harness_provcard.js +67 -0
- package/packages/server/dist/_harness_provcard.js.map +1 -0
- package/packages/server/dist/_replay_dump.d.ts +2 -0
- package/packages/server/dist/_replay_dump.d.ts.map +1 -0
- package/packages/server/dist/_replay_dump.js +62 -0
- package/packages/server/dist/_replay_dump.js.map +1 -0
- package/packages/server/dist/app.d.ts.map +1 -1
- package/packages/server/dist/app.js +6 -0
- package/packages/server/dist/app.js.map +1 -1
- package/packages/server/dist/config/index.d.ts.map +1 -1
- package/packages/server/dist/config/index.js.map +1 -1
- package/packages/server/dist/controllers/projectController.d.ts.map +1 -1
- package/packages/server/dist/controllers/projectController.js +7 -1
- package/packages/server/dist/controllers/projectController.js.map +1 -1
- package/packages/server/dist/controllers/sessionController.d.ts.map +1 -1
- package/packages/server/dist/controllers/sessionController.js +16 -4
- package/packages/server/dist/controllers/sessionController.js.map +1 -1
- package/packages/server/dist/controllers/systemBrowseController.d.ts +33 -0
- package/packages/server/dist/controllers/systemBrowseController.d.ts.map +1 -0
- package/packages/server/dist/controllers/systemBrowseController.js +105 -0
- package/packages/server/dist/controllers/systemBrowseController.js.map +1 -0
- package/packages/server/dist/handlers/streamCallbacks.d.ts +2 -0
- package/packages/server/dist/handlers/streamCallbacks.d.ts.map +1 -1
- package/packages/server/dist/handlers/streamCallbacks.js +17 -4
- package/packages/server/dist/handlers/streamCallbacks.js.map +1 -1
- package/packages/server/dist/handlers/websocket.d.ts +43 -3
- package/packages/server/dist/handlers/websocket.d.ts.map +1 -1
- package/packages/server/dist/handlers/websocket.js +362 -92
- package/packages/server/dist/handlers/websocket.js.map +1 -1
- package/packages/server/dist/index.js +2 -0
- package/packages/server/dist/index.js.map +1 -1
- package/packages/server/dist/locales/en/server.json +17 -0
- package/packages/server/dist/locales/es/server.json +17 -0
- package/packages/server/dist/locales/ja/server.json +17 -0
- package/packages/server/dist/locales/ko/server.json +17 -0
- package/packages/server/dist/locales/pt/server.json +17 -0
- package/packages/server/dist/locales/zh-CN/server.json +17 -0
- package/packages/server/dist/routes/preferences.d.ts.map +1 -1
- package/packages/server/dist/routes/preferences.js +12 -2
- package/packages/server/dist/routes/preferences.js.map +1 -1
- package/packages/server/dist/routes/systemBrowse.d.ts +17 -0
- package/packages/server/dist/routes/systemBrowse.d.ts.map +1 -0
- package/packages/server/dist/routes/systemBrowse.js +22 -0
- package/packages/server/dist/routes/systemBrowse.js.map +1 -0
- package/packages/server/dist/services/bmadStatusService.d.ts.map +1 -1
- package/packages/server/dist/services/bmadStatusService.js +18 -7
- package/packages/server/dist/services/bmadStatusService.js.map +1 -1
- package/packages/server/dist/services/chatEngine.d.ts +81 -0
- package/packages/server/dist/services/chatEngine.d.ts.map +1 -0
- package/packages/server/dist/services/chatEngine.js +2 -0
- package/packages/server/dist/services/chatEngine.js.map +1 -0
- package/packages/server/dist/services/chatEngineFactory.d.ts +15 -0
- package/packages/server/dist/services/chatEngineFactory.d.ts.map +1 -0
- package/packages/server/dist/services/chatEngineFactory.js +28 -0
- package/packages/server/dist/services/chatEngineFactory.js.map +1 -0
- package/packages/server/dist/services/chatService.d.ts +34 -26
- package/packages/server/dist/services/chatService.d.ts.map +1 -1
- package/packages/server/dist/services/chatService.js +43 -103
- package/packages/server/dist/services/chatService.js.map +1 -1
- package/packages/server/dist/services/cliChatEngine.d.ts +198 -0
- package/packages/server/dist/services/cliChatEngine.d.ts.map +1 -0
- package/packages/server/dist/services/cliChatEngine.js +2977 -0
- package/packages/server/dist/services/cliChatEngine.js.map +1 -0
- package/packages/server/dist/services/cliGridCards.d.ts +87 -0
- package/packages/server/dist/services/cliGridCards.d.ts.map +1 -0
- package/packages/server/dist/services/cliGridCards.js +298 -0
- package/packages/server/dist/services/cliGridCards.js.map +1 -0
- package/packages/server/dist/services/cliGridRegion.d.ts +44 -0
- package/packages/server/dist/services/cliGridRegion.d.ts.map +1 -0
- package/packages/server/dist/services/cliGridRegion.js +94 -0
- package/packages/server/dist/services/cliGridRegion.js.map +1 -0
- package/packages/server/dist/services/cliModalDetect.d.ts +299 -0
- package/packages/server/dist/services/cliModalDetect.d.ts.map +1 -0
- package/packages/server/dist/services/cliModalDetect.js +583 -0
- package/packages/server/dist/services/cliModalDetect.js.map +1 -0
- package/packages/server/dist/services/cliScreenCache.d.ts +33 -0
- package/packages/server/dist/services/cliScreenCache.d.ts.map +1 -0
- package/packages/server/dist/services/cliScreenCache.js +48 -0
- package/packages/server/dist/services/cliScreenCache.js.map +1 -0
- package/packages/server/dist/services/cliScreenModel.d.ts +141 -0
- package/packages/server/dist/services/cliScreenModel.d.ts.map +1 -0
- package/packages/server/dist/services/cliScreenModel.js +196 -0
- package/packages/server/dist/services/cliScreenModel.js.map +1 -0
- package/packages/server/dist/services/cliScreenStallWatchdog.d.ts +41 -0
- package/packages/server/dist/services/cliScreenStallWatchdog.d.ts.map +1 -0
- package/packages/server/dist/services/cliScreenStallWatchdog.js +45 -0
- package/packages/server/dist/services/cliScreenStallWatchdog.js.map +1 -0
- package/packages/server/dist/services/cliSessionPool.d.ts +97 -0
- package/packages/server/dist/services/cliSessionPool.d.ts.map +1 -0
- package/packages/server/dist/services/cliSessionPool.js +278 -0
- package/packages/server/dist/services/cliSessionPool.js.map +1 -0
- package/packages/server/dist/services/cliSpinnerProgress.d.ts +41 -0
- package/packages/server/dist/services/cliSpinnerProgress.d.ts.map +1 -0
- package/packages/server/dist/services/cliSpinnerProgress.js +131 -0
- package/packages/server/dist/services/cliSpinnerProgress.js.map +1 -0
- package/packages/server/dist/services/fileRewind.d.ts +46 -0
- package/packages/server/dist/services/fileRewind.d.ts.map +1 -0
- package/packages/server/dist/services/fileRewind.js +77 -0
- package/packages/server/dist/services/fileRewind.js.map +1 -0
- package/packages/server/dist/services/historyParser.d.ts +11 -0
- package/packages/server/dist/services/historyParser.d.ts.map +1 -1
- package/packages/server/dist/services/historyParser.js +10 -8
- package/packages/server/dist/services/historyParser.js.map +1 -1
- package/packages/server/dist/services/imageStorageService.d.ts +12 -0
- package/packages/server/dist/services/imageStorageService.d.ts.map +1 -1
- package/packages/server/dist/services/imageStorageService.js +22 -0
- package/packages/server/dist/services/imageStorageService.js.map +1 -1
- package/packages/server/dist/services/projectService.d.ts +13 -1
- package/packages/server/dist/services/projectService.d.ts.map +1 -1
- package/packages/server/dist/services/projectService.js +32 -0
- package/packages/server/dist/services/projectService.js.map +1 -1
- package/packages/server/dist/services/queueService.d.ts.map +1 -1
- package/packages/server/dist/services/queueService.js +9 -3
- package/packages/server/dist/services/queueService.js.map +1 -1
- package/packages/server/dist/services/rateLimitProbeService.d.ts +15 -0
- package/packages/server/dist/services/rateLimitProbeService.d.ts.map +1 -1
- package/packages/server/dist/services/rateLimitProbeService.js +48 -19
- package/packages/server/dist/services/rateLimitProbeService.js.map +1 -1
- package/packages/server/dist/services/streamHandler.d.ts +8 -1
- package/packages/server/dist/services/streamHandler.d.ts.map +1 -1
- package/packages/server/dist/services/streamHandler.js +12 -5
- package/packages/server/dist/services/streamHandler.js.map +1 -1
- package/packages/server/dist/services/systemBrowseService.d.ts +91 -0
- package/packages/server/dist/services/systemBrowseService.d.ts.map +1 -0
- package/packages/server/dist/services/systemBrowseService.js +372 -0
- package/packages/server/dist/services/systemBrowseService.js.map +1 -0
- package/packages/server/dist/services/workspaceContext.d.ts +24 -0
- package/packages/server/dist/services/workspaceContext.d.ts.map +1 -0
- package/packages/server/dist/services/workspaceContext.js +109 -0
- package/packages/server/dist/services/workspaceContext.js.map +1 -0
- package/packages/server/dist/snippets/qa-review +2 -0
- package/packages/server/dist/utils/backgroundBlock.d.ts +29 -0
- package/packages/server/dist/utils/backgroundBlock.d.ts.map +1 -0
- package/packages/server/dist/utils/backgroundBlock.js +33 -0
- package/packages/server/dist/utils/backgroundBlock.js.map +1 -0
- package/packages/server/dist/utils/backgroundTaskTracker.d.ts +20 -0
- package/packages/server/dist/utils/backgroundTaskTracker.d.ts.map +1 -0
- package/packages/server/dist/utils/backgroundTaskTracker.js +40 -0
- package/packages/server/dist/utils/backgroundTaskTracker.js.map +1 -0
- package/packages/server/dist/utils/bundledBinaryModelSupport.d.ts +7 -0
- package/packages/server/dist/utils/bundledBinaryModelSupport.d.ts.map +1 -1
- package/packages/server/dist/utils/bundledBinaryModelSupport.js +9 -4
- package/packages/server/dist/utils/bundledBinaryModelSupport.js.map +1 -1
- package/packages/server/dist/utils/cliDebugLog.d.ts +38 -0
- package/packages/server/dist/utils/cliDebugLog.d.ts.map +1 -0
- package/packages/server/dist/utils/cliDebugLog.js +65 -0
- package/packages/server/dist/utils/cliDebugLog.js.map +1 -0
- package/packages/server/dist/utils/cliEngineUtils.d.ts +35 -0
- package/packages/server/dist/utils/cliEngineUtils.d.ts.map +1 -0
- package/packages/server/dist/utils/cliEngineUtils.js +38 -0
- package/packages/server/dist/utils/cliEngineUtils.js.map +1 -0
- package/packages/server/dist/utils/effortUtils.d.ts.map +1 -1
- package/packages/server/dist/utils/effortUtils.js +5 -3
- package/packages/server/dist/utils/effortUtils.js.map +1 -1
- package/packages/server/dist/utils/trailingThrottle.d.ts +21 -0
- package/packages/server/dist/utils/trailingThrottle.d.ts.map +1 -0
- package/packages/server/dist/utils/trailingThrottle.js +36 -0
- package/packages/server/dist/utils/trailingThrottle.js.map +1 -0
- package/packages/server/package.json +61 -59
- package/packages/server/resources/internals/image-storage.md +9 -0
- package/packages/server/resources/manual/02-chat.md +22 -3
- package/packages/server/resources/manual/03-sessions.md +1 -0
- package/packages/server/resources/manual/05-projects.md +12 -1
- package/packages/server/resources/manual/11-bmad-method-integration.md +2 -0
- package/packages/server/resources/manual/13-settings.md +57 -5
- package/packages/shared/dist/index.d.ts +3 -0
- package/packages/shared/dist/index.d.ts.map +1 -1
- package/packages/shared/dist/index.js +6 -0
- package/packages/shared/dist/index.js.map +1 -1
- package/packages/shared/dist/types/bmadStatus.d.ts +16 -0
- package/packages/shared/dist/types/bmadStatus.d.ts.map +1 -1
- package/packages/shared/dist/types/bmadStatus.js.map +1 -1
- package/packages/shared/dist/types/preferences.d.ts +14 -2
- package/packages/shared/dist/types/preferences.d.ts.map +1 -1
- package/packages/shared/dist/types/preferences.js +1 -0
- package/packages/shared/dist/types/preferences.js.map +1 -1
- package/packages/shared/dist/types/project.d.ts +7 -1
- package/packages/shared/dist/types/project.d.ts.map +1 -1
- package/packages/shared/dist/types/project.js.map +1 -1
- package/packages/shared/dist/types/sdk.d.ts +123 -11
- package/packages/shared/dist/types/sdk.d.ts.map +1 -1
- package/packages/shared/dist/types/sdk.js +64 -19
- package/packages/shared/dist/types/sdk.js.map +1 -1
- package/packages/shared/dist/types/streaming.d.ts +2 -2
- package/packages/shared/dist/types/streaming.d.ts.map +1 -1
- package/packages/shared/dist/types/streaming.js.map +1 -1
- package/packages/shared/dist/types/systemBrowse.d.ts +120 -0
- package/packages/shared/dist/types/systemBrowse.d.ts.map +1 -0
- package/packages/shared/dist/types/systemBrowse.js +57 -0
- package/packages/shared/dist/types/systemBrowse.js.map +1 -0
- package/packages/shared/dist/types/websocket.d.ts +38 -0
- package/packages/shared/dist/types/websocket.d.ts.map +1 -1
- package/packages/shared/dist/utils/interruptFiller.d.ts +30 -0
- package/packages/shared/dist/utils/interruptFiller.d.ts.map +1 -0
- package/packages/shared/dist/utils/interruptFiller.js +38 -0
- package/packages/shared/dist/utils/interruptFiller.js.map +1 -0
- package/packages/shared/dist/utils/toolResultContent.d.ts +22 -0
- package/packages/shared/dist/utils/toolResultContent.d.ts.map +1 -0
- package/packages/shared/dist/utils/toolResultContent.js +29 -0
- package/packages/shared/dist/utils/toolResultContent.js.map +1 -0
- package/scripts/run-integration-test.mjs +362 -362
- package/packages/client/dist/assets/index-DyNJ5jEW.css +0 -32
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI modal/usage-limit grid detectors (Epic 37 — Story 37.4)
|
|
3
|
+
*
|
|
4
|
+
* Pure readers for the permission dialog (Story 32.6), the AskUserQuestion selection
|
|
5
|
+
* modal (Story 32.8), and the subscription usage-limit notice — moved here, *input
|
|
6
|
+
* source switched from a linear ANSI-stripped buffer to the settled screen grid*
|
|
7
|
+
* produced by the headless screen model (Story 37.1). This is the final extraction
|
|
8
|
+
* surface to leave the linear path: Stories 37.2/37.3 already moved the spinner
|
|
9
|
+
* progress reader (`cliSpinnerProgress`), and once these modal/limit consumers read
|
|
10
|
+
* the grid, the linear `stripAnsiForDetect` + rolling-buffer skeleton is deleted (37.4).
|
|
11
|
+
*
|
|
12
|
+
* Why the grid matters here (the 32.8 box-chrome fix): claude paints the modal with
|
|
13
|
+
* *box-drawing borders* (┌─┐ │ └─┘) and *in-place overwrites*. A linear buffer has no
|
|
14
|
+
* 2D coordinates, so box glyphs fused into option labels ("│"-laden / "──────"-stretched
|
|
15
|
+
* rows) and same-cell redraws concatenated. The screen grid *applies* claude's cursor
|
|
16
|
+
* moves, so **each option lands on its own row** — a per-row read keeps every label
|
|
17
|
+
* intact and fusion is structurally impossible. The per-row `stripBoxChrome` still trims
|
|
18
|
+
* the left/right border cells, but rows no longer fuse, so the label body survives.
|
|
19
|
+
*
|
|
20
|
+
* Two input shapes, same settled grid:
|
|
21
|
+
* - *Existence* detectors (`detectPermissionDialog` / `detectUsageLimit` /
|
|
22
|
+
* `detectQuestionModal`) and the verb/sentence scrapes (`extractToolName` /
|
|
23
|
+
* `extractPromptSentence`) run line-spanning regex → they take `readScreenText()`
|
|
24
|
+
* (the grid joined with '\n').
|
|
25
|
+
* - *Row-structure* parsers (`parseQuestionModal` / `parsePrecedingText`) read the
|
|
26
|
+
* grid ROWS (`readGrid()` → string[]) directly — reading rows is the whole point of
|
|
27
|
+
* the box-chrome fix.
|
|
28
|
+
*
|
|
29
|
+
* Pure by construction (no node-pty / no engine state), so the screen-less unit tests
|
|
30
|
+
* exercise them with hand-built grid rows — the pattern Stories 37.2/37.3 established
|
|
31
|
+
* with `readSpinnerProgress`. Still version-fragile (a TUI revision could reword the
|
|
32
|
+
* footers/phrases or change the box layout); the grid removes the *fusion / box-chrome*
|
|
33
|
+
* failure mode, not the wording fragility.
|
|
34
|
+
*
|
|
35
|
+
* @see docs/stories/37.4.story.md
|
|
36
|
+
* [Source: docs/prd/epic-37-cli-terminal-emulation.md#story-374]
|
|
37
|
+
*/
|
|
38
|
+
import { liveFooterRows, liveFooterText } from './cliGridRegion.js';
|
|
39
|
+
import { parseGridCards } from './cliGridCards.js';
|
|
40
|
+
/** "Chat about this" is auto-appended to every AskUserQuestion modal — a marker unique to
|
|
41
|
+
* it (absent from the permission dialog and other selection menus), used to disambiguate. */
|
|
42
|
+
const CLI_QUESTION_AFFORDANCE_RE = /Chat about this/i;
|
|
43
|
+
/** The terminal box-drawing / block range (U+2500–U+259F: ─ │ ┌ ▏ …). */
|
|
44
|
+
const BOX_CHROME_RE = /[─-▟]/g;
|
|
45
|
+
/**
|
|
46
|
+
* Conservative permission-dialog matcher. Requires a permission-specific phrase
|
|
47
|
+
* AND the fully-rendered footer ("Esc to cancel" / "Tab to amend") so a half-drawn
|
|
48
|
+
* dialog, a spinner, or the echoed prompt can never match — a false positive would
|
|
49
|
+
* inject a stray Enter/Esc and corrupt the session. On the grid a half-drawn dialog
|
|
50
|
+
* simply has no footer row yet, so the AND-of-footer naturally withholds detection
|
|
51
|
+
* until the modal is fully painted. The dialog chrome renders in English regardless of
|
|
52
|
+
* the model's reply language (observed); a future TUI revision could change these
|
|
53
|
+
* strings (documented version-fragility).
|
|
54
|
+
*/
|
|
55
|
+
export function detectPermissionDialog(text) {
|
|
56
|
+
const hasPermPhrase = /Yes,\s*allow all edits/i.test(text) ||
|
|
57
|
+
/Do you want to (?:create|make|write|edit|update|apply|run|execute|read|proceed|allow)/i.test(text);
|
|
58
|
+
const hasFooter = /Esc\b[^\n]{0,16}\bcancel\b/i.test(text) || /Tab\b[^\n]{0,16}\bamend\b/i.test(text);
|
|
59
|
+
return hasPermPhrase && hasFooter;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Detect the subscription **usage-limit exhaustion** notice on the screen and return its
|
|
63
|
+
* scraped sentence (else null). Why this exists: the interactive TUI prints "You've hit your
|
|
64
|
+
* weekly limit · resets 1am (Asia/Seoul)" ONLY on screen — it is NEVER written to the session
|
|
65
|
+
* JSONL — so the JSONL watch (how every other turn-completion is detected) never sees it and the
|
|
66
|
+
* turn would otherwise hang forever waiting for an `end_turn` that never comes. Detecting it lets
|
|
67
|
+
* the engine fail fast with the exact message claude showed (including the reset time).
|
|
68
|
+
*
|
|
69
|
+
* Conservative by construction so it can never stop a healthy turn:
|
|
70
|
+
* - requires an exhaustion verb (hit/reached/exceeded) OR "<window> limit reached",
|
|
71
|
+
* - requires an explicit window qualifier (weekly / 5-hour / daily / usage / session),
|
|
72
|
+
* - requires a nearby "reset" clause (the real notice always has one),
|
|
73
|
+
* - and explicitly EXCLUDES the still-usable percentage warning ("used 97% of your weekly
|
|
74
|
+
* limit") — at 97% generation continues, so that must NOT stop the turn.
|
|
75
|
+
* The call site adds the OAuth-usage corroboration + POST-INJECTION gating (preserved in the
|
|
76
|
+
* engine dispatch). Version-fragile (a TUI revision could reword these) — the same documented
|
|
77
|
+
* constraint as the permission/question detectors, which also read screen *state*.
|
|
78
|
+
*/
|
|
79
|
+
export function detectUsageLimit(text) {
|
|
80
|
+
const m = text.match(/(?:hit|reached|exceeded)\s+your\s+(?:weekly|5-hour|daily|usage|session)\s+limit\b[^\n]{0,60}|(?:weekly|usage|5-hour|daily|session)\s+limit\s+(?:reached|exceeded)\b[^\n]{0,60}/i);
|
|
81
|
+
if (!m)
|
|
82
|
+
return null;
|
|
83
|
+
if (/\bused\s+\d+\s*%/i.test(m[0]))
|
|
84
|
+
return null; // percentage warning — still usable, don't stop
|
|
85
|
+
if (!/\breset/i.test(m[0]))
|
|
86
|
+
return null; // require the reset clause for confidence
|
|
87
|
+
return m[0].replace(/\s{2,}/g, ' ').trim().slice(0, 160);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Detect the transient server-side **rate-limit** error on screen — DISTINCT from the weekly usage cap.
|
|
91
|
+
* claude prints "API Error: Server is temporarily limiting requests (not your usage limit) · Rate limited"
|
|
92
|
+
* ONLY on the PTY (never the JSONL), so without detection the turn hangs on the spinner waiting for an
|
|
93
|
+
* end_turn that never comes. Returns the scraped sentence (else null). No OAuth corroboration needed (this
|
|
94
|
+
* is an explicit error string, not the ambiguous usage-cap prose) — but require BOTH the distinctive phrase
|
|
95
|
+
* AND the "Rate limited" tag so a stray mention can't trip it. Version-fragile like the other screen-state
|
|
96
|
+
* detectors. The caller gates it POST-INJECTION (so a pre-injection repaint can't false-fire).
|
|
97
|
+
*/
|
|
98
|
+
export function detectRateLimit(text) {
|
|
99
|
+
if (!/Server is temporarily limiting requests/i.test(text))
|
|
100
|
+
return null;
|
|
101
|
+
if (!/Rate limited/i.test(text))
|
|
102
|
+
return null;
|
|
103
|
+
const m = text.match(/(?:API Error:\s*)?Server is temporarily limiting requests[^\n]{0,80}/i);
|
|
104
|
+
return m ? m[0].replace(/\s{2,}/g, ' ').trim().slice(0, 160) : null;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Best-effort tool name from the dialog's question verb (screen scrape — low
|
|
108
|
+
* fidelity; the structured tool name is not in the JSONL until after approval).
|
|
109
|
+
*/
|
|
110
|
+
export function extractToolName(text) {
|
|
111
|
+
const verb = (text.match(/Do you want to (\w+)/i)?.[1] ?? '').toLowerCase();
|
|
112
|
+
if (/^(create|write|make)$/.test(verb))
|
|
113
|
+
return 'Write';
|
|
114
|
+
if (/^(edit|update|apply|modify|change)$/.test(verb))
|
|
115
|
+
return 'Edit';
|
|
116
|
+
if (/^(run|execute)$/.test(verb))
|
|
117
|
+
return 'Bash';
|
|
118
|
+
if (/^(read|view)$/.test(verb))
|
|
119
|
+
return 'Read';
|
|
120
|
+
if (/^(fetch|access)$/.test(verb))
|
|
121
|
+
return 'WebFetch';
|
|
122
|
+
// Secondary hint: the tool header line "● Write(…)".
|
|
123
|
+
return text.match(/[●·]\s*([A-Z][a-zA-Z]+)\s*\(/)?.[1] ?? 'Tool';
|
|
124
|
+
}
|
|
125
|
+
/** Best-effort human-readable prompt sentence (the dialog's own words). */
|
|
126
|
+
export function extractPromptSentence(text) {
|
|
127
|
+
return (text.match(/Do you want to [^?\n]{1,160}\?/i)?.[0] ?? 'Claude is requesting tool permission').trim();
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Conservative AskUserQuestion-modal matcher (Story 32.8). Requires the selection footer
|
|
131
|
+
* ("Enter to select" + "↑/↓ to navigate") AND the auto-appended "Chat about this"
|
|
132
|
+
* affordance — together unique to the question modal and absent from a permission dialog
|
|
133
|
+
* ("Do you want to…" / "Yes, allow all edits", which has no list navigation) and from
|
|
134
|
+
* ordinary output, so neither can false-trigger a stray keypress. `detectPermissionDialog`
|
|
135
|
+
* is checked first and is mutually exclusive (it needs a permission phrase this modal lacks;
|
|
136
|
+
* this needs "to navigate" the dialog lacks), so the two never cross-fire. Version-fragile
|
|
137
|
+
* (a TUI revision could reword these) — the documented constrained surface.
|
|
138
|
+
*/
|
|
139
|
+
export function detectQuestionModal(text) {
|
|
140
|
+
const hasNavFooter = /Enter\b[^\n]{0,12}\bselect\b/i.test(text) && /to\s+navigate/i.test(text);
|
|
141
|
+
return hasNavFooter && CLI_QUESTION_AFFORDANCE_RE.test(text);
|
|
142
|
+
}
|
|
143
|
+
/** Index of the LAST grid row matching `re`, or -1. (The footer sits at the bottom of the modal.) */
|
|
144
|
+
function lastRowMatching(rows, re) {
|
|
145
|
+
for (let i = rows.length - 1; i >= 0; i--)
|
|
146
|
+
if (re.test(rows[i]))
|
|
147
|
+
return i;
|
|
148
|
+
return -1;
|
|
149
|
+
}
|
|
150
|
+
/** Strip box-drawing / block chrome from a row, collapse runs to one space, trim (a chrome-only
|
|
151
|
+
* row collapses to ''). Each option is its OWN grid row, so this only removes the border cells —
|
|
152
|
+
* the label body no longer fuses with a neighbour the way the linear buffer fused them (32.8). */
|
|
153
|
+
function stripBoxChrome(s) {
|
|
154
|
+
return s.replace(BOX_CHROME_RE, ' ').replace(/\s{2,}/g, ' ').trim();
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The modal region (header ballot-box row down to just above the footer) and its header row, or
|
|
158
|
+
* null when no selection footer is on the grid. Shared by every question reader so they bound the
|
|
159
|
+
* scan to the rendered box identically — lead-in prose far above the box cannot leak into a scrape.
|
|
160
|
+
*/
|
|
161
|
+
function questionModalRegion(rows) {
|
|
162
|
+
const footerIdx = lastRowMatching(rows, /to\s+navigate/i);
|
|
163
|
+
if (footerIdx < 0)
|
|
164
|
+
return null;
|
|
165
|
+
const region = rows.slice(0, footerIdx);
|
|
166
|
+
const headerIdx = region.findIndex((r) => /[☐☒]/.test(r));
|
|
167
|
+
const modalRows = headerIdx >= 0 ? region.slice(headerIdx) : region;
|
|
168
|
+
return { region, headerIdx, modalRows };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Scrape the question text + multiSelect flag + options from the modal region rows — the body
|
|
172
|
+
* common to BOTH the single-question modal (Story 32.8) and one tab of a multi-question modal
|
|
173
|
+
* (ISSUE-99). NO single-question guard here (a tabbed modal legitimately has >1 header ballot box);
|
|
174
|
+
* the guard lives in `parseQuestionModal`. `header` (when known) is excluded from the question-text
|
|
175
|
+
* candidates so the short tab label never masquerades as the full question.
|
|
176
|
+
*
|
|
177
|
+
* The grid renders **each option on its own row**, so the box-chrome that used to fuse into labels
|
|
178
|
+
* is confined to each row's border cells and `stripBoxChrome` removes it cleanly. Crucially the
|
|
179
|
+
* scrape order equals the grid row order (top-to-bottom), which equals the ↓-count used to drive
|
|
180
|
+
* the answer, so the option↔index mapping is self-consistent (row order IS the navigation index).
|
|
181
|
+
*/
|
|
182
|
+
function scrapeQuestionBody(modalRows, header) {
|
|
183
|
+
const multiSelect = modalRows.some((r) => /\[\s*[✔x ]?\s*\]/.test(r)); // [ ] / [✔] ⇒ multiSelect
|
|
184
|
+
// A numbered row opens an option; the indented prose row(s) claude paints directly BELOW the label,
|
|
185
|
+
// up to the next numbered row, are that option's DESCRIPTION (실측 2026-06-17: the TUI renders
|
|
186
|
+
// "1. 빨강 / <indent>빨간색을 선호합니다." for EVERY option, not just the highlighted one). The earlier
|
|
187
|
+
// scrape read only the label row and silently dropped these description rows, so the web card showed
|
|
188
|
+
// bare labels while the richer text sat right there on screen — the "선택지가 단순" gap. Last
|
|
189
|
+
// occurrence wins per number, then ordered by number; one label per row so a per-row match suffices.
|
|
190
|
+
const byNum = new Map();
|
|
191
|
+
let currentNum = null;
|
|
192
|
+
for (const r of modalRows) {
|
|
193
|
+
const m = r.match(/(\d{1,2})\.\s+(?:\[[✔x ]?\s*\]\s*)?(.*\S)/);
|
|
194
|
+
if (m) {
|
|
195
|
+
currentNum = parseInt(m[1], 10);
|
|
196
|
+
byNum.set(currentNum, { label: stripBoxChrome(m[2]), desc: [] });
|
|
197
|
+
}
|
|
198
|
+
else if (currentNum !== null) {
|
|
199
|
+
// Continuation row under the open option → its description. Drop chrome-only rows (empty after
|
|
200
|
+
// stripBoxChrome) and the auto-appended affordance rows so neither leaks into the description.
|
|
201
|
+
const extra = stripBoxChrome(r);
|
|
202
|
+
if (extra && !/^(?:Type something|Chat about this)\.?$/i.test(extra))
|
|
203
|
+
byNum.get(currentNum).desc.push(extra);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const options = [...byNum.entries()]
|
|
207
|
+
.sort((a, b) => a[0] - b[0])
|
|
208
|
+
.map(([, v]) => ({ label: v.label, description: v.desc.join(' ').trim().slice(0, 500) }))
|
|
209
|
+
// Drop rows that were ONLY box-drawing chrome (empty label) and the auto-appended affordance rows
|
|
210
|
+
// — none of these are real answer options. Omit an empty description so the option stays `{label}`.
|
|
211
|
+
.filter((o) => o.label.length > 0)
|
|
212
|
+
.filter((o) => !/^Type something\.?$/i.test(o.label) && !/^Chat about this\.?$/i.test(o.label))
|
|
213
|
+
.map((o) => ({ label: o.label, ...(o.description ? { description: o.description } : {}) }));
|
|
214
|
+
if (options.length === 0)
|
|
215
|
+
return null;
|
|
216
|
+
// Question = the last meaningful row between the header tab and the first numbered option
|
|
217
|
+
// (best-effort; it may not end in '?' — e.g. "Which pets? Choose any."). Strip tab/cursor glyphs
|
|
218
|
+
// and drop the header line itself.
|
|
219
|
+
const firstOptOffset = modalRows.findIndex((r) => /\d{1,2}\.\s/.test(r));
|
|
220
|
+
const preOptionRows = firstOptOffset > 0 ? modalRows.slice(0, firstOptOffset) : [];
|
|
221
|
+
const question = (preOptionRows
|
|
222
|
+
.map((l) => stripBoxChrome(l.replace(/[←→✔☐☒❯]/g, '')))
|
|
223
|
+
.filter((l) => l && l !== header)
|
|
224
|
+
.pop() ??
|
|
225
|
+
header ??
|
|
226
|
+
'Claude is asking a question').trim();
|
|
227
|
+
return { question, multiSelect, options };
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Scrape one question + its options from a SINGLE-question modal's screen ROWS (Story 32.8 — low
|
|
231
|
+
* fidelity, the documented constraint; Story 37.4 — reads the settled grid rows instead of a linear
|
|
232
|
+
* buffer).
|
|
233
|
+
*
|
|
234
|
+
* Returns null when it cannot build a usable *single*-question structure — no options, or a
|
|
235
|
+
* **multi-question** tabbed modal (more than one header ballot-box tab) which this single-round-trip
|
|
236
|
+
* reader does not drive. ISSUE-99 added a separate per-tab path (`countQuestionTabs` +
|
|
237
|
+
* `parseQuestionTabBody`) for the multi-question case; THIS function and its guard are unchanged so
|
|
238
|
+
* the single-question path stays byte-for-byte identical.
|
|
239
|
+
*/
|
|
240
|
+
export function parseQuestionModal(rows) {
|
|
241
|
+
const r = questionModalRegion(rows);
|
|
242
|
+
if (!r)
|
|
243
|
+
return null;
|
|
244
|
+
const { region, headerIdx, modalRows } = r;
|
|
245
|
+
// Multi-question modals show >1 ballot-box tab in the header row (☐ Q1 ☐ Q2 ✔ Submit) — not a
|
|
246
|
+
// single round-trip, so guard rather than half-answer (the multi-question driver handles those).
|
|
247
|
+
if ((modalRows.join('').match(/[☐☒]/g) || []).length > 1)
|
|
248
|
+
return null;
|
|
249
|
+
const headerRow = headerIdx >= 0 ? region[headerIdx] : undefined;
|
|
250
|
+
const headerMatch = headerRow?.match(/[☐☒]\s*([^✔→\n]+?)(?:\s{2,}|$)/);
|
|
251
|
+
const header = headerMatch ? headerMatch[1].trim() : undefined;
|
|
252
|
+
const body = scrapeQuestionBody(modalRows, header);
|
|
253
|
+
if (!body)
|
|
254
|
+
return null;
|
|
255
|
+
return { question: body.question, header, multiSelect: body.multiSelect, options: body.options };
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Count the question tabs (header ballot boxes ☐/☒) in a detected AskUserQuestion modal (ISSUE-99).
|
|
259
|
+
* claude renders a single question as one ballot box (`☐ Color`) and a **multi-question** modal as a
|
|
260
|
+
* tab bar with one box per question plus a Submit tab (`← ☐ Color ☐ Size ✔ Submit →`). So the
|
|
261
|
+
* count is the branch signal: 1 ⇒ the single-round-trip `parseQuestionModal` path; >1 ⇒ the tabbed
|
|
262
|
+
* multi-question driver. 0 ⇒ no header (a confirm-style menu / not a question modal). Pure — counts
|
|
263
|
+
* the ballot glyphs on the header row only (option checkboxes are `[ ]`, a distinct glyph). */
|
|
264
|
+
export function countQuestionTabs(rows) {
|
|
265
|
+
const r = questionModalRegion(rows);
|
|
266
|
+
if (!r || r.headerIdx < 0)
|
|
267
|
+
return 0;
|
|
268
|
+
return (r.region[r.headerIdx].match(/[☐☒]/g) || []).length;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* The ordered question header labels from a multi-question tab bar (ISSUE-99). For
|
|
272
|
+
* `← ☐ Color ☐ Size ✔ Submit →` this returns `['Color', 'Size']` — each label is the text after
|
|
273
|
+
* a ballot box up to the next box / `✔` Submit / arrow. The Submit tab is excluded (it is not a
|
|
274
|
+
* question). The labels are positional: the i-th label is the i-th tab the driver visits in order,
|
|
275
|
+
* so the engine attaches `headers[i]` to the question it scrapes on tab i. Pure. */
|
|
276
|
+
export function parseQuestionTabHeaders(rows) {
|
|
277
|
+
const r = questionModalRegion(rows);
|
|
278
|
+
if (!r || r.headerIdx < 0)
|
|
279
|
+
return [];
|
|
280
|
+
const headerRow = r.region[r.headerIdx];
|
|
281
|
+
const labels = [];
|
|
282
|
+
const re = /[☐☒]\s*([^☐☒✔→←\n]+)/g;
|
|
283
|
+
let m;
|
|
284
|
+
while ((m = re.exec(headerRow)) !== null) {
|
|
285
|
+
const label = stripBoxChrome(m[1]).trim();
|
|
286
|
+
if (label && !/^Submit$/i.test(label))
|
|
287
|
+
labels.push(label);
|
|
288
|
+
}
|
|
289
|
+
return labels;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Scrape ONE tab of a multi-question modal — the active question's text + multiSelect + options
|
|
293
|
+
* (ISSUE-99). The tabbed modal shows only the active question's body below the tab bar, so this is
|
|
294
|
+
* the same body scrape as the single-question path but WITHOUT the single-question guard (the tab
|
|
295
|
+
* bar legitimately carries >1 ballot box). The header label is NOT read here (it is supplied
|
|
296
|
+
* positionally by `parseQuestionTabHeaders`), so a multi-box tab bar row is harmless — it is not a
|
|
297
|
+
* numbered option and is never the last pre-option row (the question text is). Returns null when the
|
|
298
|
+
* tab has no real options (e.g. a half-painted frame). Pure. */
|
|
299
|
+
export function parseQuestionTabBody(rows) {
|
|
300
|
+
const r = questionModalRegion(rows);
|
|
301
|
+
if (!r)
|
|
302
|
+
return null;
|
|
303
|
+
return scrapeQuestionBody(r.modalRows, undefined);
|
|
304
|
+
}
|
|
305
|
+
/** A confirm-style choice menu — e.g. claude's "resume full session vs summary" prompt shown when
|
|
306
|
+
* resuming a large/old session. It differs from the AskUserQuestion modal in two ways: the footer
|
|
307
|
+
* reads "Enter to confirm · Esc to cancel" (not "↑↓ to navigate · Enter to select") and there is
|
|
308
|
+
* no ballot-box header tab. It is still a single-select numbered list, so it returns the SAME
|
|
309
|
+
* `ParsedQuestion` shape (multiSelect:false) and the existing web card round-trip + the
|
|
310
|
+
* `buildQuestionKeys` driver (↓×index + Enter) handle it verbatim — the boot-stage gate hands a
|
|
311
|
+
* detected one to the same card instead of Esc-cancelling it (Story 37.6 follow-up). Kept as a
|
|
312
|
+
* SEPARATE parser so the AskUserQuestion path stays byte-for-byte untouched (regression-0). */
|
|
313
|
+
const CLI_CONFIRM_FOOTER_RE = /Enter\b[^\n]{0,16}\bconfirm\b/i;
|
|
314
|
+
export function parseConfirmChoiceMenu(rows) {
|
|
315
|
+
// AND-gate: require the live confirm footer alongside the numbered rows (same conservative spirit
|
|
316
|
+
// as the other detectors — a quoted "Enter to confirm" in scrollback prose, with no numbered
|
|
317
|
+
// options below it, must not read as a live menu).
|
|
318
|
+
const footerIdx = lastRowMatching(rows, CLI_CONFIRM_FOOTER_RE);
|
|
319
|
+
if (footerIdx < 0)
|
|
320
|
+
return null;
|
|
321
|
+
// Live-menu gate (false-positive guard, 실측 2026-06-12): a REAL confirm menu occupies the input
|
|
322
|
+
// area at the bottom of the screen, so its footer is the LAST meaningful row — nothing renders
|
|
323
|
+
// below it. If ANY non-blank row follows the footer, the numbered rows are quoted SCROLLBACK:
|
|
324
|
+
// transcript prose/lists that merely contain a "1." "2." sequence and the literal "Enter to
|
|
325
|
+
// confirm" phrase (e.g. THIS feature discussed in the very session, repainted on resume). A
|
|
326
|
+
// footer-phrase match alone is far too weak — ordinary chat ("press Enter to confirm", numbered
|
|
327
|
+
// steps) trips it; requiring the footer to be the last painted row is what isolates a live menu.
|
|
328
|
+
const below = rows.slice(footerIdx + 1);
|
|
329
|
+
if (below.some((r) => r.trim().length > 0))
|
|
330
|
+
return null;
|
|
331
|
+
const region = rows.slice(0, footerIdx);
|
|
332
|
+
// Numbered option rows ("❯ 1. …" / " 2. …"), one per row; last label wins per number, ordered by
|
|
333
|
+
// number. The optional leading "❯" is the highlight cursor on the current row.
|
|
334
|
+
const byNum = new Map();
|
|
335
|
+
for (const r of region) {
|
|
336
|
+
const m = r.match(/^\s*[❯>]?\s*(\d{1,2})\.\s+(.*\S)/);
|
|
337
|
+
if (m)
|
|
338
|
+
byNum.set(parseInt(m[1], 10), stripBoxChrome(m[2]));
|
|
339
|
+
}
|
|
340
|
+
const options = [...byNum.entries()]
|
|
341
|
+
.sort((a, b) => a[0] - b[0])
|
|
342
|
+
.map((e) => e[1])
|
|
343
|
+
.filter((l) => l.length > 0)
|
|
344
|
+
.map((label) => ({ label }));
|
|
345
|
+
// A real choice needs at least two options (a lone "1." quoted in scrollback is not a live menu).
|
|
346
|
+
if (options.length < 2)
|
|
347
|
+
return null;
|
|
348
|
+
// Question = the trailing prose row above the first numbered option (best-effort, like
|
|
349
|
+
// parseQuestionModal). The card shows every option regardless, so a weak question is non-fatal.
|
|
350
|
+
const firstOptIdx = region.findIndex((r) => /^\s*[❯>]?\s*\d{1,2}\.\s/.test(r));
|
|
351
|
+
const preOption = firstOptIdx > 0 ? region.slice(0, firstOptIdx) : [];
|
|
352
|
+
const question = (preOption.map((l) => stripBoxChrome(l)).filter((l) => l).pop() ?? 'Claude is asking a question').trim();
|
|
353
|
+
return { question, header: undefined, multiSelect: false, options };
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Scrape the assistant prose the TUI rendered ABOVE the question modal (the explanation that
|
|
357
|
+
* leads into the choices), else null. Why this exists (ordering fix): that prose and the
|
|
358
|
+
* AskUserQuestion are flushed to the session JSONL only AFTER the user answers (the whole
|
|
359
|
+
* assistant message lands post-selection — verified), while the question CARD is shown the moment
|
|
360
|
+
* the modal is detected on screen. So reading the prose from the file always puts it AFTER the
|
|
361
|
+
* card ("선택지 누른 뒤에 설명이 나온다"). The screen is the ONLY pre-answer source, so we scrape
|
|
362
|
+
* it and emit it first. Best-effort and lossy (collapsed to one line, may catch nothing) — the
|
|
363
|
+
* turn-end reload replaces it with the authoritative JSONL copy; the caller dedups the matching
|
|
364
|
+
* JSONL block's live re-emit.
|
|
365
|
+
*
|
|
366
|
+
* Reads the same grid rows as parseQuestionModal: it finds where the modal begins (the header
|
|
367
|
+
* ballot-box tab ☐/☒, or the row above the first numbered option) and returns the trailing
|
|
368
|
+
* contiguous block of prose rows above it (box chrome stripped, option/footer/affordance rows
|
|
369
|
+
* excluded). Returns null for a trivial fragment so a bare modal with no lead-in prose never
|
|
370
|
+
* emits scrape noise.
|
|
371
|
+
*/
|
|
372
|
+
export function parsePrecedingText(rows) {
|
|
373
|
+
const footerIdx = lastRowMatching(rows, /to\s+navigate/i);
|
|
374
|
+
if (footerIdx < 0)
|
|
375
|
+
return null;
|
|
376
|
+
const region = rows.slice(0, footerIdx);
|
|
377
|
+
let modalStart = region.findIndex((r) => /[☐☒]/.test(r));
|
|
378
|
+
if (modalStart < 0)
|
|
379
|
+
modalStart = region.findIndex((r) => /^\s*1\.\s/.test(r));
|
|
380
|
+
if (modalStart <= 0)
|
|
381
|
+
return null;
|
|
382
|
+
// Use the card classifier so only recognized blocks (● text, ● tool, ⎿ result, ∴/Thought
|
|
383
|
+
// thinking) are considered — spinners, UI chrome, and any other non-block rows are
|
|
384
|
+
// automatically excluded. Only trailing contiguous TEXT cards are lead-in prose.
|
|
385
|
+
const cards = parseGridCards(region.slice(0, modalStart));
|
|
386
|
+
const prose = [];
|
|
387
|
+
for (let i = cards.length - 1; i >= 0; i--) {
|
|
388
|
+
if (cards[i].kind === 'text')
|
|
389
|
+
prose.unshift(cards[i].text);
|
|
390
|
+
else
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
const text = prose.join(' ').trim();
|
|
394
|
+
return text.length < 4 ? null : text.slice(0, 2000);
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Scrape the assistant prose rendered ABOVE a *permission* dialog (Story 32.6) — the sibling of
|
|
398
|
+
* `parsePrecedingText` for the permission path (Story 37.9, the same "[본문, 선택지]" ordering fix:
|
|
399
|
+
* claude writes the gated tool's whole assistant message — explanatory prose included — to the JSONL
|
|
400
|
+
* only AFTER the decision, so the screen is the only pre-decision source).
|
|
401
|
+
*
|
|
402
|
+
* The permission dialog has no "to navigate" footer; its gated tool renders as a `● Tool(…)` card
|
|
403
|
+
* with the "Create file / Do you want to… / 1. Yes / 2. … / Esc to cancel" chrome below it, and the
|
|
404
|
+
* explanatory prose sits ABOVE that tool card. So the region above the dialog footer is classified
|
|
405
|
+
* with the single-source `parseGridCards`, and the prose = the trailing contiguous run of TEXT cards
|
|
406
|
+
* immediately before the gated (last) tool card. Returns null when there is no lead-in prose.
|
|
407
|
+
* Best-effort / lossy like its sibling — the turn-end reload replaces it with the canonical block.
|
|
408
|
+
*/
|
|
409
|
+
export function parsePrecedingPermissionText(rows) {
|
|
410
|
+
const footerIdx = lastRowMatching(rows, /Esc\b[^\n]{0,16}\bcancel\b|Tab\b[^\n]{0,16}\bamend\b/i);
|
|
411
|
+
const region = footerIdx >= 0 ? rows.slice(0, footerIdx) : rows;
|
|
412
|
+
const cards = parseGridCards(region);
|
|
413
|
+
// The gated tool is the LAST tool card (the dialog paints it just above the prompt). The prose is
|
|
414
|
+
// the contiguous TEXT cards right before it; with no tool card at all, consider every card.
|
|
415
|
+
let lastToolIdx = -1;
|
|
416
|
+
for (let i = cards.length - 1; i >= 0; i--) {
|
|
417
|
+
if (cards[i].kind === 'tool') {
|
|
418
|
+
lastToolIdx = i;
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const before = lastToolIdx >= 0 ? cards.slice(0, lastToolIdx) : cards;
|
|
423
|
+
const prose = [];
|
|
424
|
+
for (let i = before.length - 1; i >= 0; i--) {
|
|
425
|
+
if (before[i].kind === 'text')
|
|
426
|
+
prose.unshift(before[i].text);
|
|
427
|
+
else
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
const text = prose.join(' ').trim();
|
|
431
|
+
// Sub-word floor: a real short lead-in must survive; only a stray glyph is dropped.
|
|
432
|
+
return text.length < 4 ? null : text.slice(0, 2000);
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Story 37.5 — permission-mode control (the *write* application of the same settled grid).
|
|
436
|
+
*
|
|
437
|
+
* CLI permission modes that map 1:1 onto claude's Shift+Tab (`CSI Z`) cycle, **in cycle order**:
|
|
438
|
+
* a normal session cycles `default → accept edits on → plan mode on → auto mode on → (wrap)
|
|
439
|
+
* default` (empirically captured, claude v2.1.162). The forward step count from current→target
|
|
440
|
+
* is `(targetIdx - curIdx + N) % N`.
|
|
441
|
+
*
|
|
442
|
+
* `auto` is claude's classifier mode ("auto mode on"), a DISTINCT mode from `acceptEdits`
|
|
443
|
+
* ("accept edits on") — they are separate `--permission-mode` values. Earlier code wrongly read
|
|
444
|
+
* "auto mode on" as `bypassPermissions`; that conflation is fixed here.
|
|
445
|
+
*
|
|
446
|
+
* Two values sit OFF this cycle, so `permissionModeCycleIndex` returns -1 and the engine routes
|
|
447
|
+
* them to the store-only / next-spawn `--permission-mode` path instead of a live closed loop:
|
|
448
|
+
* - `bypassPermissions` — claude deliberately keeps it out of a normal session's cycle (it
|
|
449
|
+
* surfaces in the cycle only once a session is explicitly STARTED in it). We never drive to it
|
|
450
|
+
* live; selecting Bypass applies on the NEXT spawn via `--permission-mode bypassPermissions`.
|
|
451
|
+
* - `dontAsk` — headless-only, never on the cycle.
|
|
452
|
+
*
|
|
453
|
+
* version-fragile: label wording / cycle order can shift across claude versions (live-verify item).
|
|
454
|
+
*/
|
|
455
|
+
export const CLI_PERMISSION_MODE_CYCLE = ['default', 'acceptEdits', 'plan', 'auto'];
|
|
456
|
+
/** A session STARTED in bypass renders `bypassPermissions` INSIDE its live Shift+Tab cycle —
|
|
457
|
+
* inserted between `plan` and `auto` (empirically captured: `bypass → auto → default → accept
|
|
458
|
+
* edits → plan → bypass`, claude v2.1.177). So for such a session bypass IS a live-drivable
|
|
459
|
+
* target; a session NOT started in bypass keeps it off the cycle (`CLI_PERMISSION_MODE_CYCLE`
|
|
460
|
+
* above) and bypass stays store-only / next-spawn. */
|
|
461
|
+
export const CLI_PERMISSION_MODE_CYCLE_WITH_BYPASS = ['default', 'acceptEdits', 'plan', 'bypassPermissions', 'auto'];
|
|
462
|
+
/** Cycle position of a mode, or -1 when off the Shift+Tab cycle. `includeBypass` selects the
|
|
463
|
+
* bypass-started session's cycle (where `bypassPermissions` is reachable live); the default
|
|
464
|
+
* (false) keeps `bypassPermissions`/`dontAsk` off-cycle → next-spawn `--permission-mode` path. */
|
|
465
|
+
export function permissionModeCycleIndex(mode, includeBypass = false) {
|
|
466
|
+
return (includeBypass ? CLI_PERMISSION_MODE_CYCLE_WITH_BYPASS : CLI_PERMISSION_MODE_CYCLE).indexOf(mode);
|
|
467
|
+
}
|
|
468
|
+
/** Status-row label → Hammoc mode. Each is anchored by its claude label phrase. `default`
|
|
469
|
+
* (normal) has NO label row, so it is read as the *absence* of any of these (see below). */
|
|
470
|
+
const CLI_MODE_LABELS = [
|
|
471
|
+
{ re: /accept edits on/i, mode: 'acceptEdits' },
|
|
472
|
+
{ re: /plan mode on/i, mode: 'plan' },
|
|
473
|
+
{ re: /auto mode on/i, mode: 'auto' },
|
|
474
|
+
// bypassPermissions is off the normal cycle, but a session STARTED in bypass renders this row,
|
|
475
|
+
// so the reader must still recognize it (it just isn't a live-drivable target — see the cycle).
|
|
476
|
+
{ re: /bypass permissions on/i, mode: 'bypassPermissions' },
|
|
477
|
+
];
|
|
478
|
+
/** The mode status row renders the label together with this footer ("… (shift+tab to cycle) ·
|
|
479
|
+
* ← for agents"). Requiring the footer on the SAME row is the AND-gate that keeps a half-drawn
|
|
480
|
+
* frame or a quoted "plan mode on" in conversation prose from being read as the live mode (the
|
|
481
|
+
* same conservative AND-of-footer spirit the permission / question detectors use). */
|
|
482
|
+
const CLI_MODE_CYCLE_FOOTER_RE = /shift\s*\+?\s*tab\s+to\s+cycle/i;
|
|
483
|
+
/**
|
|
484
|
+
* Read claude's current permission mode from the settled screen grid's status row (Story 37.5).
|
|
485
|
+
* Pure: input is the grid rows, output is the Hammoc `PermissionMode`. A row is accepted as the
|
|
486
|
+
* mode status row ONLY when it carries the `shift+tab to cycle` footer (AND-gate); among such
|
|
487
|
+
* rows the bottom-most (freshest rendered) wins, matching `readSpinnerProgress`. When NO mode
|
|
488
|
+
* status row is present the mode is `default` (normal) — but note this is a *weak* signal (it is
|
|
489
|
+
* the absence of a row), so the caller MUST read it from a `flush()`-settled grid: a half-drawn
|
|
490
|
+
* frame whose label has not painted yet would otherwise read transiently as `default`.
|
|
491
|
+
*/
|
|
492
|
+
export function readPermissionMode(grid) {
|
|
493
|
+
// Live region only: the mode status row renders in the bottom UI cluster, so a full mode row
|
|
494
|
+
// ("⏸ plan mode on (shift+tab to cycle) …") QUOTED far up in resume-repaint scrollback cannot be
|
|
495
|
+
// read as the live mode when the live screen is `default` (no row). Bottom-most (freshest) wins.
|
|
496
|
+
const region = liveFooterRows(grid);
|
|
497
|
+
for (let y = region.length - 1; y >= 0; y--) {
|
|
498
|
+
const row = region[y];
|
|
499
|
+
if (!CLI_MODE_CYCLE_FOOTER_RE.test(row))
|
|
500
|
+
continue; // AND-gate: only a real mode status row
|
|
501
|
+
for (const { re, mode } of CLI_MODE_LABELS) {
|
|
502
|
+
if (re.test(row))
|
|
503
|
+
return mode;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// No mode status row (or a footer with no recognized label) = normal = default.
|
|
507
|
+
return 'default';
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Is the settled grid mid-GENERATION? The live footer carries claude's active-generation footer
|
|
511
|
+
* ("esc to interrupt") or a spinner token counter ("↓ N tokens"). Scanned over the LIVE FOOTER
|
|
512
|
+
* REGION only (`liveFooterText`) so scrollback prose that quotes these phrases can't poison the
|
|
513
|
+
* verdict (same discipline as `isIdleInputGrid`).
|
|
514
|
+
*
|
|
515
|
+
* Story 37.5 follow-up (owner-confirmed 2026-06-13): the mode status row renders at the very bottom
|
|
516
|
+
* of the reconstructed grid in BOTH idle and generating states (spinner above, input box + mode row
|
|
517
|
+
* below), and Shift+Tab cycles the permission mode live in either. So the permission-mode closed
|
|
518
|
+
* loop drives on this POSITIVE generation signal too — it distinguishes a real generating frame
|
|
519
|
+
* (safe to read the mode + cycle) from an UNKNOWN boot/loading screen (where blind keys stay
|
|
520
|
+
* forbidden). Version-fragile like the rest of this module.
|
|
521
|
+
*/
|
|
522
|
+
export function isGeneratingGrid(grid) {
|
|
523
|
+
const footer = liveFooterText(grid);
|
|
524
|
+
// Either arrow direction (`[↑↓]`): claude renders the spinner counter as "↓ N tokens" or
|
|
525
|
+
// "↑ N tokens" depending on phase (실측 2026-06-14). The ↓-only test let a long "↑ N tokens"
|
|
526
|
+
// generation read as NOT generating, which (a) suppressed progress and (b) would let the
|
|
527
|
+
// post-injection modal guard fire mid-generation — both fixed by accepting both arrows.
|
|
528
|
+
return /esc to interrupt/i.test(footer) || /[↑↓]\s*[\d.,]+k?\s*tokens/i.test(footer);
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Is the settled grid showing claude's idle INPUT BOX (ready to accept a keypress), as opposed to a
|
|
532
|
+
* mid-generation spinner frame? (Story 37.5 — distinguishes idle from generating; both render the
|
|
533
|
+
* mode status row, but the live Shift+Tab gate treats them separately where it needs to.)
|
|
534
|
+
*
|
|
535
|
+
* Heuristic on a settled grid (half-drawn frames are excluded upstream by `flush()`), scanned over
|
|
536
|
+
* the LIVE FOOTER REGION only (so scrollback prose that quotes these phrases can't poison it):
|
|
537
|
+
* - a generation footer/counter (`isGeneratingGrid`) ⇒ NOT idle;
|
|
538
|
+
* - else the input-box prompt glyph (❯) present ⇒ idle.
|
|
539
|
+
*
|
|
540
|
+
* Left as a shared named helper so Story 37.6's "pre-injection screen classification" (which draws
|
|
541
|
+
* the same input-box-vs-spinner line, and likewise treats `❯` alone as insufficient) can reuse it
|
|
542
|
+
* rather than re-deriving it. This story is self-sufficient and does NOT depend on 37.6.
|
|
543
|
+
*/
|
|
544
|
+
export function isIdleInputGrid(grid) {
|
|
545
|
+
if (isGeneratingGrid(grid))
|
|
546
|
+
return false; // active generation footer / spinner counter ⇒ generating
|
|
547
|
+
return /❯/.test(liveFooterText(grid)); // idle input-box marker (live region only — not scrollback)
|
|
548
|
+
}
|
|
549
|
+
/** A LIVE selection footer (bottom-of-box nav/cancel affordance) — the AND-gate partner that keeps
|
|
550
|
+
* quoted scrollback ("❯ 1. Yes" in resume-repaint prose) from reading as a live menu. */
|
|
551
|
+
const CLI_SELECTION_FOOTER_RE = /to\s+navigate|Esc\b[^\n]{0,16}\bcancel\b|↑\s*\/?\s*↓/i;
|
|
552
|
+
/** A numbered option row ("1. …" / " 2. …"), anchored at the row start (one option per grid row). */
|
|
553
|
+
const CLI_NUMBERED_OPTION_RE = /^\s*\d{1,2}\.\s/;
|
|
554
|
+
/**
|
|
555
|
+
* Classify a SETTLED screen grid as input box / selection menu / unknown for the pre-injection
|
|
556
|
+
* readiness gate (Story 37.6). MUST be called on a `flush()`-settled grid (see the absence-signal
|
|
557
|
+
* note above). Pure — no node-pty / no engine state.
|
|
558
|
+
*/
|
|
559
|
+
export function classifyPreInjectScreen(grid) {
|
|
560
|
+
// (1) Recognized selection menus/modals, OR a numbered list WITH a live footer (AND-gate). EVERY
|
|
561
|
+
// selection signal here is matched over the LIVE FOOTER region only (`liveFooterText` /
|
|
562
|
+
// `liveFooterRows`), NEVER the whole screen. A resume-repaint can QUOTE a modal's text — a
|
|
563
|
+
// nav/cancel footer, a permission phrase, an AskUserQuestion fixture in a transcript that merely
|
|
564
|
+
// *discusses* these menus — in the scrollback BODY; a whole-screen scan then mistook that for a
|
|
565
|
+
// live modal and WITHHELD injection (boot abort, surfaced to the user as a generic "timeout"). A
|
|
566
|
+
// genuinely live modal renders at the BOTTOM of the screen, so its signature lands in the live
|
|
567
|
+
// region; a quoted one sits up in the body with the idle input box below it. (Until ISSUE-99 only
|
|
568
|
+
// the generic numbered-list path was footer-anchored; the named permission/question detectors
|
|
569
|
+
// still scanned the whole screen — exactly the hole ISSUE-99's modal test fixtures fell through.
|
|
570
|
+
// Now all three are footer-anchored, the same discipline the idle/generating/mode readers use.)
|
|
571
|
+
const footer = liveFooterText(grid);
|
|
572
|
+
const hasNumberedOption = liveFooterRows(grid).some((row) => CLI_NUMBERED_OPTION_RE.test(row));
|
|
573
|
+
const hasSelectionFooter = CLI_SELECTION_FOOTER_RE.test(footer);
|
|
574
|
+
if (detectPermissionDialog(footer) || detectQuestionModal(footer) || (hasNumberedOption && hasSelectionFooter)) {
|
|
575
|
+
return 'selection';
|
|
576
|
+
}
|
|
577
|
+
// (2) A verified idle input box (no selection signature + `❯` present + not mid-generation).
|
|
578
|
+
if (isIdleInputGrid(grid))
|
|
579
|
+
return 'input-box';
|
|
580
|
+
// (3) Neither — do not press blind keys (AC3).
|
|
581
|
+
return 'unknown';
|
|
582
|
+
}
|
|
583
|
+
//# sourceMappingURL=cliModalDetect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cliModalDetect.js","sourceRoot":"","sources":["../../src/services/cliModalDetect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI screen cache (Story 37.7, reworked in 37.8) — a session-lifetime snapshot of the
|
|
3
|
+
* current claude TUI screen, used to restore late-joining / refreshed / collapse-expanded
|
|
4
|
+
* browsers in CLI mode.
|
|
5
|
+
*
|
|
6
|
+
* The Story 37.1 headless emulator is per-TURN (spawned with the PTY, disposed at teardown).
|
|
7
|
+
* But the CLI engine is turn-per-process, so between turns there is no emulator and no live
|
|
8
|
+
* PTY. This cache lives at the SESSION scope: while a turn streams, the engine refreshes it
|
|
9
|
+
* with the CURRENT serialized screen on the same ~100ms throttle as the live `cli:screen-frame`
|
|
10
|
+
* (so a late-join mid-turn restores the live screen, not a stale one), and hands off the FINAL
|
|
11
|
+
* serialized screen at teardown. A `session:join` or `cli:request-screen-frame` arriving
|
|
12
|
+
* between turns then has a "current screen" to send.
|
|
13
|
+
*
|
|
14
|
+
* Story 37.8: the entry is now a single SERIALIZED frame string (ANSI/color intact, produced
|
|
15
|
+
* by the screen model's serialize addon), not a plain-text grid — the client restores it via
|
|
16
|
+
* reset()+write(frame), identical to a live frame, so restore and live share one render path.
|
|
17
|
+
*
|
|
18
|
+
* Pure module-level Map. CLI-only; SDK mode never writes it (no PTY / screen model), so `get`
|
|
19
|
+
* returns undefined for SDK sessions and the frame push is naturally a no-op.
|
|
20
|
+
*/
|
|
21
|
+
/** Store (or replace) the current serialized screen frame for a session. `ts` records recency.
|
|
22
|
+
* Preserves the existing `stalled` flag — only the watchdog (setCliScreenStall) mutates that. */
|
|
23
|
+
export declare function setCliScreen(sessionId: string, frame: string): void;
|
|
24
|
+
/** Return the cached serialized screen frame for a session, or undefined on a cache miss. */
|
|
25
|
+
export declare function getCliScreen(sessionId: string): string | undefined;
|
|
26
|
+
/** Update the soft screen-stall flag on an EXISTING screen entry. No-op on a cache miss, so a
|
|
27
|
+
* frame-less (leak-prone) entry is never created — a cached screen frame is what defines a session. */
|
|
28
|
+
export declare function setCliScreenStall(sessionId: string, stalled: boolean): void;
|
|
29
|
+
/** Current cached stall flag for a session (false on a miss). Used to resync late joiners. */
|
|
30
|
+
export declare function getCliScreenStall(sessionId: string): boolean;
|
|
31
|
+
/** Drop a session's cached screen (called when its socket room empties — leak guard). */
|
|
32
|
+
export declare function deleteCliScreen(sessionId: string): void;
|
|
33
|
+
//# sourceMappingURL=cliScreenCache.d.ts.map
|