@nexrall/code-core 1.4.76 → 1.4.78

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.
Files changed (143) hide show
  1. package/dist/agent/effortCap.d.ts +24 -0
  2. package/dist/agent/effortCap.d.ts.map +1 -0
  3. package/dist/agent/effortCap.js +66 -0
  4. package/dist/agent/goal.d.ts +55 -0
  5. package/dist/agent/goal.d.ts.map +1 -0
  6. package/dist/agent/goal.js +120 -0
  7. package/dist/agent/handoffBrief.d.ts +82 -0
  8. package/dist/agent/handoffBrief.d.ts.map +1 -0
  9. package/dist/agent/handoffBrief.js +121 -0
  10. package/dist/agent/hooks.d.ts +10 -2
  11. package/dist/agent/hooks.d.ts.map +1 -1
  12. package/dist/agent/hooks.js +9 -3
  13. package/dist/agent/modelCatalogue.d.ts +23 -0
  14. package/dist/agent/modelCatalogue.d.ts.map +1 -1
  15. package/dist/agent/modelCatalogue.js +28 -0
  16. package/dist/agent/outputStyles.d.ts +27 -0
  17. package/dist/agent/outputStyles.d.ts.map +1 -0
  18. package/dist/agent/outputStyles.js +174 -0
  19. package/dist/agent/skills.d.ts.map +1 -1
  20. package/dist/agent/skills.js +21 -1
  21. package/dist/api/billingLines.d.ts +11 -0
  22. package/dist/api/billingLines.d.ts.map +1 -0
  23. package/dist/api/billingLines.js +56 -0
  24. package/dist/api/client.d.ts +2 -0
  25. package/dist/api/client.d.ts.map +1 -1
  26. package/dist/api/scheduleLines.d.ts +53 -0
  27. package/dist/api/scheduleLines.d.ts.map +1 -0
  28. package/dist/api/scheduleLines.js +150 -0
  29. package/dist/context/contextFiles.d.ts +81 -0
  30. package/dist/context/contextFiles.d.ts.map +1 -0
  31. package/dist/context/contextFiles.js +310 -0
  32. package/dist/diff/collect.d.ts +25 -0
  33. package/dist/diff/collect.d.ts.map +1 -0
  34. package/dist/diff/collect.js +169 -0
  35. package/dist/diff/diffView.d.ts +57 -0
  36. package/dist/diff/diffView.d.ts.map +1 -0
  37. package/dist/diff/diffView.js +231 -0
  38. package/dist/diff/reviewQueue.d.ts +53 -0
  39. package/dist/diff/reviewQueue.d.ts.map +1 -0
  40. package/dist/diff/reviewQueue.js +349 -0
  41. package/dist/events/recap.d.ts +54 -0
  42. package/dist/events/recap.d.ts.map +1 -0
  43. package/dist/events/recap.js +161 -0
  44. package/dist/events/sessionEvents.d.ts +54 -0
  45. package/dist/events/sessionEvents.d.ts.map +1 -0
  46. package/dist/events/sessionEvents.js +324 -0
  47. package/dist/events/sessionStats.d.ts +89 -0
  48. package/dist/events/sessionStats.d.ts.map +1 -0
  49. package/dist/events/sessionStats.js +264 -0
  50. package/dist/import/claudeImport.d.ts +44 -0
  51. package/dist/import/claudeImport.d.ts.map +1 -0
  52. package/dist/import/claudeImport.js +337 -0
  53. package/dist/index.d.ts +38 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +38 -0
  56. package/dist/mcp/manager.d.ts +8 -0
  57. package/dist/mcp/manager.d.ts.map +1 -1
  58. package/dist/mcp/manager.js +64 -57
  59. package/dist/permissions/destructiveTokens.d.ts +1 -0
  60. package/dist/permissions/destructiveTokens.d.ts.map +1 -1
  61. package/dist/permissions/destructiveTokens.js +93 -0
  62. package/dist/permissions/rules.d.ts +12 -0
  63. package/dist/permissions/rules.d.ts.map +1 -1
  64. package/dist/permissions/rules.js +89 -7
  65. package/dist/plugins/configStore.d.ts +43 -0
  66. package/dist/plugins/configStore.d.ts.map +1 -0
  67. package/dist/plugins/configStore.js +253 -0
  68. package/dist/plugins/files.d.ts +10 -0
  69. package/dist/plugins/files.d.ts.map +1 -0
  70. package/dist/plugins/files.js +85 -0
  71. package/dist/plugins/index.d.ts +3 -22
  72. package/dist/plugins/index.d.ts.map +1 -1
  73. package/dist/plugins/index.js +30 -40
  74. package/dist/plugins/resolve.d.ts +18 -0
  75. package/dist/plugins/resolve.d.ts.map +1 -0
  76. package/dist/plugins/resolve.js +152 -0
  77. package/dist/plugins/userConfig.d.ts +90 -0
  78. package/dist/plugins/userConfig.d.ts.map +1 -0
  79. package/dist/plugins/userConfig.js +332 -0
  80. package/dist/plugins/validate.d.ts +14 -0
  81. package/dist/plugins/validate.d.ts.map +1 -0
  82. package/dist/plugins/validate.js +222 -0
  83. package/dist/project/projectCommands.d.ts +33 -0
  84. package/dist/project/projectCommands.d.ts.map +1 -0
  85. package/dist/project/projectCommands.js +246 -0
  86. package/dist/search/conversationTurns.d.ts +53 -0
  87. package/dist/search/conversationTurns.d.ts.map +1 -0
  88. package/dist/search/conversationTurns.js +170 -0
  89. package/dist/search/globalSearch.d.ts +144 -0
  90. package/dist/search/globalSearch.d.ts.map +1 -0
  91. package/dist/search/globalSearch.js +350 -0
  92. package/dist/share/sessionChannel.d.ts +136 -0
  93. package/dist/share/sessionChannel.d.ts.map +1 -0
  94. package/dist/share/sessionChannel.js +373 -0
  95. package/dist/share/sessionShare.d.ts +104 -0
  96. package/dist/share/sessionShare.d.ts.map +1 -0
  97. package/dist/share/sessionShare.js +336 -0
  98. package/dist/ui/configCmd.d.ts +48 -0
  99. package/dist/ui/configCmd.d.ts.map +1 -0
  100. package/dist/ui/configCmd.js +146 -0
  101. package/dist/ui/deployPanel.d.ts +91 -0
  102. package/dist/ui/deployPanel.d.ts.map +1 -0
  103. package/dist/ui/deployPanel.js +442 -0
  104. package/dist/ui/mergeAssistant.d.ts +120 -0
  105. package/dist/ui/mergeAssistant.d.ts.map +1 -0
  106. package/dist/ui/mergeAssistant.js +391 -0
  107. package/dist/ui/notify.d.ts +114 -0
  108. package/dist/ui/notify.d.ts.map +1 -0
  109. package/dist/ui/notify.js +235 -0
  110. package/dist/ui/promptSuggest.d.ts +56 -0
  111. package/dist/ui/promptSuggest.d.ts.map +1 -0
  112. package/dist/ui/promptSuggest.js +160 -0
  113. package/dist/ui/taskGraph.d.ts +44 -0
  114. package/dist/ui/taskGraph.d.ts.map +1 -0
  115. package/dist/ui/taskGraph.js +289 -0
  116. package/dist/util/addDir.d.ts +31 -0
  117. package/dist/util/addDir.d.ts.map +1 -0
  118. package/dist/util/addDir.js +124 -0
  119. package/dist/util/batch.d.ts +16 -0
  120. package/dist/util/batch.d.ts.map +1 -0
  121. package/dist/util/batch.js +92 -0
  122. package/dist/util/cellWidth.d.ts +12 -0
  123. package/dist/util/cellWidth.d.ts.map +1 -0
  124. package/dist/util/cellWidth.js +161 -0
  125. package/dist/util/duration.d.ts +8 -0
  126. package/dist/util/duration.d.ts.map +1 -0
  127. package/dist/util/duration.js +50 -0
  128. package/dist/util/loopJobs.d.ts +67 -0
  129. package/dist/util/loopJobs.d.ts.map +1 -0
  130. package/dist/util/loopJobs.js +169 -0
  131. package/dist/util/settingsFile.d.ts +14 -0
  132. package/dist/util/settingsFile.d.ts.map +1 -0
  133. package/dist/util/settingsFile.js +99 -0
  134. package/dist/util/toolLabel.d.ts +2 -0
  135. package/dist/util/toolLabel.d.ts.map +1 -0
  136. package/dist/util/toolLabel.js +17 -0
  137. package/dist/verify/failureTimeline.d.ts +100 -0
  138. package/dist/verify/failureTimeline.d.ts.map +1 -0
  139. package/dist/verify/failureTimeline.js +253 -0
  140. package/dist/verify/verification.d.ts +75 -0
  141. package/dist/verify/verification.d.ts.map +1 -0
  142. package/dist/verify/verification.js +315 -0
  143. package/package.json +5 -4
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ // Lives in core (moved from the CLI's ui/notify.ts, 2026-10-04) so the VS Code
3
+ // panel reads the SAME `notifications` setting and composes the same notes with
4
+ // its editor-native delivery; the CLI module is a re-export shim.
5
+ // §25 Task notifications — "tell me when it needs me", as a terminal can.
6
+ //
7
+ // A terminal CLI cannot raise a Toast: it prints. What it CAN do is hand the message to
8
+ // the emulator through an escape sequence, and every capability list here is grounded in
9
+ // what the emulators actually document rather than in a guess:
10
+ //
11
+ // OSC 9 ; <text> BEL iTerm2 (the originator), WezTerm, kitty, Ghostty, Windows
12
+ // Terminal, and the VS Code extension that adds it to the built-in
13
+ // terminal. OSC 777 ; notify ; ; <text> is the urxvt/Ghostty sibling;
14
+ // OSC 9 is the one every one of those five also accepts.
15
+ // BEL the fallback that has worked since VT100: no text, just attention.
16
+ //
17
+ // Two honesty rules the code below enforces rather than documents:
18
+ // 1. NOTHING is written when stdout is not a TTY. A CI log or a `| jq` consumer must not
19
+ // receive control bytes because a background job happened to finish.
20
+ // 2. A sequence is only emitted for a terminal that is KNOWN to read it. `auto` on
21
+ // Apple_Terminal or the VS Code terminal degrades to the bell instead of printing
22
+ // "9;done" into the middle of the screen — the failure mode that makes people turn
23
+ // notifications off forever.
24
+ //
25
+ // Configurable via the `notifications` setting and `/notify` (§25 "Notification should be
26
+ // configurable"). Pure string/decision logic lives here so it is all unit-testable; the one
27
+ // side effect (a stdout write) is injected.
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.Notifier = exports.SLOW_TURN_MS = exports.MAX_NOTE = exports.NOTIFY_USAGE = exports.NOTIFY_SETTING = exports.NOTIFY_MODES = void 0;
30
+ exports.parseNotifyMode = parseNotifyMode;
31
+ exports.readNotifySetting = readNotifySetting;
32
+ exports.detectOscSupport = detectOscSupport;
33
+ exports.sanitizeNote = sanitizeNote;
34
+ exports.oscNotification = oscNotification;
35
+ exports.passthrough = passthrough;
36
+ exports.notificationBytes = notificationBytes;
37
+ exports.bgShellNote = bgShellNote;
38
+ exports.bgAgentNote = bgAgentNote;
39
+ exports.needsYouNote = needsYouNote;
40
+ exports.turnDoneNote = turnDoneNote;
41
+ const duration_1 = require("../util/duration");
42
+ exports.NOTIFY_MODES = ['auto', 'bell', 'osc', 'off'];
43
+ exports.NOTIFY_SETTING = 'notifications';
44
+ exports.NOTIFY_USAGE = '/notify [auto|bell|osc|off] [--project]';
45
+ /** Longest notification body kept. Emulators truncate anyway; ours should be predictable. */
46
+ exports.MAX_NOTE = 120;
47
+ /**
48
+ * A turn only *notifies* when it ran long enough that the user plausibly walked away.
49
+ * Below this, the completion block is already on screen in front of them.
50
+ */
51
+ exports.SLOW_TURN_MS = 30000;
52
+ // ─── Parsing / settings ────────────────────────────────────────────────────────
53
+ /**
54
+ * A mode string from the settings file or `/notify`. `null` for anything that is not a
55
+ * mode — the caller decides whether that is "unset, keep the default" (settings) or
56
+ * "invalid, tell the user" (`/notify`), which are different answers to the same input.
57
+ */
58
+ function parseNotifyMode(raw) {
59
+ if (typeof raw !== 'string')
60
+ return null;
61
+ const v = raw.trim().toLowerCase();
62
+ return exports.NOTIFY_MODES.includes(v) ? v : null;
63
+ }
64
+ /**
65
+ * The `notifications` setting, defaulting to `auto`: on, using the best technique this
66
+ * terminal supports. A hand-written `false`/`"off"` means off, matching `/livediff`'s
67
+ * convention (a settings value a person typed must not be silently overruled).
68
+ */
69
+ function readNotifySetting(raw) {
70
+ const v = (raw ?? {})[exports.NOTIFY_SETTING];
71
+ if (v === undefined)
72
+ return 'auto';
73
+ if (v === false)
74
+ return 'off';
75
+ return parseNotifyMode(v) ?? 'auto';
76
+ }
77
+ // ─── Capability ────────────────────────────────────────────────────────────────
78
+ /**
79
+ * Does this terminal show OSC 9 notifications? Only the ones that document it get a yes;
80
+ * an unknown TERM_PROGRAM (VS Code's `vscode`, Apple's `Apple_Terminal`, a bare xterm)
81
+ * gets a `false` and therefore the bell.
82
+ */
83
+ function detectOscSupport(env) {
84
+ const prog = (env.TERM_PROGRAM ?? '').toLowerCase();
85
+ if (prog === 'iterm.app' || prog === 'wezterm' || prog === 'ghostty' || prog === 'hyper')
86
+ return true;
87
+ if (prog === 'vscode' || prog === 'apple_terminal')
88
+ return false;
89
+ if (env.KITTY_WINDOW_ID !== undefined && env.KITTY_WINDOW_ID !== '')
90
+ return true;
91
+ if ((env.TERM ?? '').includes('kitty'))
92
+ return true;
93
+ if (env.WT_SESSION !== undefined && env.WT_SESSION !== '')
94
+ return true; // Windows Terminal
95
+ return false;
96
+ }
97
+ // ─── Building the bytes ────────────────────────────────────────────────────────
98
+ /**
99
+ * A notification body safe to embed in an OSC sequence: no escape sequences, no control
100
+ * characters (a stray BEL would terminate the sequence early and print the rest as text),
101
+ * one line, bounded. Returns '' when nothing printable is left, which callers treat as
102
+ * "send nothing".
103
+ *
104
+ * The ANSI strip is not cosmetic. The permission prompts hand us strings that are already
105
+ * coloured (`paint.boldRed('Type "yes" …')`), and replacing the ESC byte alone leaves the
106
+ * parameters behind as visible text — measured on a real prompt:
107
+ * `waiting for you — [1m [31mType "yes" to run this destructive command:`.
108
+ */
109
+ function sanitizeNote(text, max = exports.MAX_NOTE) {
110
+ const flat = text
111
+ // Whole ANSI sequences first: CSI (colours/cursor), OSC (titles/hyperlinks), and the
112
+ // two-character escapes (ESC 7/8 are the save/restore cursor pair readline emits).
113
+ // The ESC must go with its parameters, not on its own.
114
+ // eslint-disable-next-line no-control-regex
115
+ .replace(/\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)|\u001b[@-Z\\-_78]/g, '')
116
+ // eslint-disable-next-line no-control-regex
117
+ .replace(/[\u0000-\u001f\u007f-\u009f]/g, ' ')
118
+ .replace(/\s+/g, ' ')
119
+ .trim();
120
+ if (!flat)
121
+ return '';
122
+ if (flat.length <= max)
123
+ return flat;
124
+ return flat.slice(0, max - 1).trimEnd() + '…';
125
+ }
126
+ /** The raw OSC 9 sequence. BEL-terminated: the form iTerm2 documents and every other
127
+ * implementation of it inherits. */
128
+ function oscNotification(text) {
129
+ return `\x1b]9;${text}\x07`;
130
+ }
131
+ /**
132
+ * tmux and screen do not forward an OSC sequence they do not recognise, so it has to be
133
+ * handed to the OUTER terminal wrapped in a DCS passthrough — with every ESC inside the
134
+ * payload doubled, which is what tmux's own parser un-escapes. Without a multiplexer the
135
+ * sequence passes through unchanged.
136
+ *
137
+ * If the user has not enabled `allow-passthrough` (tmux ≥ 3.3) the wrapper is discarded
138
+ * silently rather than printed — the reason to wrap even when unsure.
139
+ */
140
+ function passthrough(seq, env) {
141
+ if (env.TMUX)
142
+ return '\x1bPtmux;' + seq.split('\x1b').join('\x1b\x1b') + '\x1b\\';
143
+ if (env.STY || (env.TERM ?? '').startsWith('screen'))
144
+ return '\x1bP' + seq + '\x1b\\';
145
+ return seq;
146
+ }
147
+ /**
148
+ * The bytes for one notification, or null for "say nothing".
149
+ *
150
+ * `bell` and `osc` are explicit requests and are honoured; `auto` picks OSC only where it
151
+ * is known to be read, and the bell otherwise.
152
+ */
153
+ function notificationBytes(mode, env, text) {
154
+ if (mode === 'off')
155
+ return null;
156
+ const note = sanitizeNote(text);
157
+ if (mode === 'bell')
158
+ return '\x07';
159
+ if (mode === 'osc')
160
+ return note ? passthrough(oscNotification(note), env) : null;
161
+ // auto
162
+ if (detectOscSupport(env))
163
+ return note ? passthrough(oscNotification(note), env) : null;
164
+ return '\x07';
165
+ }
166
+ class Notifier {
167
+ constructor(opts = {}) {
168
+ this.mode = opts.mode ?? 'auto';
169
+ this.env = opts.env ?? {};
170
+ this.isTTY = opts.isTTY ?? false;
171
+ this.write =
172
+ opts.write ??
173
+ ((b) => {
174
+ process.stdout.write(b);
175
+ });
176
+ }
177
+ setMode(mode) {
178
+ this.mode = mode;
179
+ }
180
+ getMode() {
181
+ return this.mode;
182
+ }
183
+ /** What a notification would do right now — the honest answer `/notify` prints. */
184
+ effective() {
185
+ if (this.mode === 'off' || !this.isTTY)
186
+ return { technique: 'off', unconfirmed: false };
187
+ if (this.mode === 'bell')
188
+ return { technique: 'bell', unconfirmed: false };
189
+ const recognised = detectOscSupport(this.env);
190
+ if (this.mode === 'osc')
191
+ return { technique: 'osc', unconfirmed: !recognised };
192
+ return { technique: recognised ? 'osc' : 'bell', unconfirmed: false };
193
+ }
194
+ /** Send one. False when nothing was written — not a TTY, mode off, or an empty note. */
195
+ notify(text) {
196
+ if (!this.isTTY || this.mode === 'off')
197
+ return false;
198
+ const bytes = notificationBytes(this.mode, this.env, text);
199
+ if (!bytes)
200
+ return false;
201
+ this.write(bytes);
202
+ return true;
203
+ }
204
+ }
205
+ exports.Notifier = Notifier;
206
+ // ─── Event texts ───────────────────────────────────────────────────────────────
207
+ /** One line naming what finished, for a `!command` that was moved to the background. */
208
+ function bgShellNote(cmd, id, code, signal) {
209
+ const how = code === 0 ? 'exit 0' : `exit ${code ?? signal ?? '?'}`;
210
+ const short = cmd.length > 60 ? cmd.slice(0, 59) + '…' : cmd;
211
+ return sanitizeNote(`background #${id} finished (${how}): ${short}`);
212
+ }
213
+ /** A background sub-agent came back. */
214
+ function bgAgentNote(descriptions) {
215
+ const one = descriptions.length === 1;
216
+ return sanitizeNote(`background agent${one ? '' : 's'} ${descriptions.map((d) => `"${d}"`).join(', ')} finished`);
217
+ }
218
+ /** The agent is blocked on a human: a permission box, a destructive confirm, a question. */
219
+ function needsYouNote(what) {
220
+ return sanitizeNote(`waiting for you — ${what}`);
221
+ }
222
+ /**
223
+ * A turn finished. Null unless it ran long enough to have lost the user AND they did not
224
+ * touch the keyboard since it started — a notification for work they were watching is
225
+ * just noise, and "long enough" is measured, not guessed.
226
+ */
227
+ function turnDoneNote(durationMs, userActive, sessionId) {
228
+ if (userActive || durationMs < exports.SLOW_TURN_MS)
229
+ return null;
230
+ // §94: this note is read on a phone or another window — exactly when "which session,
231
+ // and how do I get back to it" is the missing half. Carry the resume handle with it.
232
+ const where = sessionId ? ` · nex --resume ${sessionId.slice(0, 8)}` : '';
233
+ return sanitizeNote(`turn finished after ${(0, duration_1.formatElapsed)(durationMs)}${where}`);
234
+ }
235
+ //# sourceMappingURL=notify.js.map
@@ -0,0 +1,56 @@
1
+ import { SUGGESTIONS_SETTING } from './configCmd';
2
+ export { SUGGESTIONS_SETTING };
3
+ /** The suggestion must fit an input box line; longer output is cut at a word. */
4
+ export declare const SUGGESTION_MAX_CHARS = 200;
5
+ /** Claude Code's number: "appear less often after 20 in a row go unused". */
6
+ export declare const SUGGESTION_DAMP_AFTER = 20;
7
+ /** While damped, one offer in this many opportunities (first one still shows). */
8
+ export declare const SUGGESTION_DAMPED_EVERY = 4;
9
+ /**
10
+ * On/off resolution: env (`NEXRALL_PROMPT_SUGGESTIONS`) beats the setting, the
11
+ * setting (`promptSuggestions`) beats the default, and the default is ON —
12
+ * matching CC, where the toggle only exists for sessions whose feature flags
13
+ * resolved. `0`/`false`/`off` turn it off in both places.
14
+ */
15
+ export declare function resolvePromptSuggestions(f: {
16
+ env?: Record<string, string | undefined>;
17
+ setting?: unknown;
18
+ }): boolean;
19
+ /**
20
+ * The background request. Deliberately tiny: one short instruction plus the
21
+ * tail of the conversation, clipped — the suggestion is a guess at the next
22
+ * LINE, not a summary, and the call must stay cheap.
23
+ */
24
+ export declare function buildSuggestionPrompt(lastUser: string, lastAssistant: string): string;
25
+ /**
26
+ * Turn a raw model reply into something safe to place in the input box, or
27
+ * null when there is nothing usable. Rules, pinned by the tests:
28
+ * · first non-empty line only (a multi-line reply is not a prompt line);
29
+ * · a leading `> `/list marker and wrapping quotes/backticks are stripped;
30
+ * · meta/evaluative text ("Sure!", "Here's a suggestion:") is rejected —
31
+ * CC drops the same class;
32
+ * · must carry at least one letter or digit (punctuation-only is noise);
33
+ * · over-long output is cut at the last space with NO ellipsis — whatever
34
+ * is placed in the box gets sent verbatim, so decoration would be typed.
35
+ */
36
+ export declare function sanitizeSuggestion(raw: string | null | undefined): string | null;
37
+ /**
38
+ * The ignore damping counter (CC: "after 20 in a row go unused" suggestions
39
+ * appear less often; using one brings them back). A suggestion counts as
40
+ * ignored when it was showing and the user typed something else; used when
41
+ * accepted. While damped only every `SUGGESTION_DAMPED_EVERY`th opportunity is
42
+ * offered — except the first, so the notice has something to accompany.
43
+ */
44
+ export declare class SuggestionDamping {
45
+ private ignored;
46
+ private whileDamped;
47
+ private noticePending;
48
+ /** Consecutive ignored suggestions (0 = full frequency). */
49
+ get ignoredCount(): number;
50
+ noteIgnored(): void;
51
+ noteUsed(): void;
52
+ shouldOffer(): boolean;
53
+ /** True exactly once after damping begins (until a use re-arms it). */
54
+ takeNotice(): boolean;
55
+ }
56
+ //# sourceMappingURL=promptSuggest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promptSuggest.d.ts","sourceRoot":"","sources":["../../src/ui/promptSuggest.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAE/B,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,MAAM,CAAC;AACxC,6EAA6E;AAC7E,eAAO,MAAM,qBAAqB,KAAK,CAAC;AACxC,kFAAkF;AAClF,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAMzC;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAQpH;AAOD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAQrF;AAiBD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAoBhF;AAED;;;;;;GAMG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,aAAa,CAAS;IAE9B,4DAA4D;IAC5D,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,WAAW,IAAI,IAAI;IAOnB,QAAQ,IAAI,IAAI;IAKhB,WAAW,IAAI,OAAO;IAOtB,uEAAuE;IACvE,UAAU,IAAI,OAAO;CAKtB"}
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ // Lives in core (moved from the CLI's ui/promptSuggest.ts, 2026-10-05) so the VS
3
+ // Code panel asks the same background suggestion question; the CLI module is a
4
+ // re-export shim.
5
+ // S19d — prompt suggestions (Claude Code parity): after a turn ends, a short
6
+ // background request proposes the user's next prompt; the input box shows it as
7
+ // ghost text; Tab (or →) places it, typing dismisses it.
8
+ //
9
+ // Pure half — parsing the raw model reply, building the request, the on/off
10
+ // resolution and the ignore-damping counter. The caller owns the I/O (the
11
+ // one-shot model call, the Ink ghost text) — see chat.ts and inkTerminal.tsx.
12
+ //
13
+ // Cost honesty: each shown suggestion is one small background request billed
14
+ // like any turn. The context handed to the model is clipped hard (last prompt +
15
+ // last reply), the reply must survive sanitizeSuggestion, and the feature can
16
+ // be turned off three ways (env > setting > /suggestions).
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.SuggestionDamping = exports.SUGGESTION_DAMPED_EVERY = exports.SUGGESTION_DAMP_AFTER = exports.SUGGESTION_MAX_CHARS = exports.SUGGESTIONS_SETTING = void 0;
19
+ exports.resolvePromptSuggestions = resolvePromptSuggestions;
20
+ exports.buildSuggestionPrompt = buildSuggestionPrompt;
21
+ exports.sanitizeSuggestion = sanitizeSuggestion;
22
+ const configCmd_1 = require("./configCmd");
23
+ Object.defineProperty(exports, "SUGGESTIONS_SETTING", { enumerable: true, get: function () { return configCmd_1.SUGGESTIONS_SETTING; } });
24
+ /** The suggestion must fit an input box line; longer output is cut at a word. */
25
+ exports.SUGGESTION_MAX_CHARS = 200;
26
+ /** Claude Code's number: "appear less often after 20 in a row go unused". */
27
+ exports.SUGGESTION_DAMP_AFTER = 20;
28
+ /** While damped, one offer in this many opportunities (first one still shows). */
29
+ exports.SUGGESTION_DAMPED_EVERY = 4;
30
+ /** How much of the conversation rides in the background request. */
31
+ const CLIP_USER = 400;
32
+ const CLIP_ASSISTANT = 600;
33
+ /**
34
+ * On/off resolution: env (`NEXRALL_PROMPT_SUGGESTIONS`) beats the setting, the
35
+ * setting (`promptSuggestions`) beats the default, and the default is ON —
36
+ * matching CC, where the toggle only exists for sessions whose feature flags
37
+ * resolved. `0`/`false`/`off` turn it off in both places.
38
+ */
39
+ function resolvePromptSuggestions(f) {
40
+ const raw = (f.env ?? {}).NEXRALL_PROMPT_SUGGESTIONS;
41
+ const v = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
42
+ if (v === '0' || v === 'false' || v === 'off')
43
+ return false;
44
+ if (v === '1' || v === 'true')
45
+ return true;
46
+ const s = f.setting;
47
+ if (s === false || s === 'false' || s === '0' || s === 'off')
48
+ return false;
49
+ return true;
50
+ }
51
+ function clip(text, max) {
52
+ const t = text.replace(/\s+/g, ' ').trim();
53
+ return t.length > max ? t.slice(0, max - 1) + '…' : t;
54
+ }
55
+ /**
56
+ * The background request. Deliberately tiny: one short instruction plus the
57
+ * tail of the conversation, clipped — the suggestion is a guess at the next
58
+ * LINE, not a summary, and the call must stay cheap.
59
+ */
60
+ function buildSuggestionPrompt(lastUser, lastAssistant) {
61
+ return ("You suggest the user's NEXT prompt in an AI coding terminal session.\n" +
62
+ 'Reply with EXACTLY ONE short line (max 12 words): the literal text the user might type next.\n' +
63
+ 'No quotes, no explanation, no preamble. Write it in the same language as the user.\n\n' +
64
+ `The user last said:\n${clip(lastUser, CLIP_USER)}\n\n` +
65
+ `The assistant replied:\n${clip(lastAssistant, CLIP_ASSISTANT)}`);
66
+ }
67
+ /** Prefixes that mean the model explained the suggestion instead of giving it. */
68
+ const META_PREFIXES = [
69
+ /^(sure|okay|ok)[,.!:\s]/i,
70
+ /^here'?s\b/i,
71
+ /^i (suggest|recommend|would|could)\b/i,
72
+ /^you (could|can|might|should)\b/i,
73
+ /^the user\b/i,
74
+ /^(prompt|suggestion)\s*:/i,
75
+ /^let me\b/i,
76
+ ];
77
+ function hasLetterOrDigit(s) {
78
+ return /[\p{L}\p{N}]/u.test(s);
79
+ }
80
+ /**
81
+ * Turn a raw model reply into something safe to place in the input box, or
82
+ * null when there is nothing usable. Rules, pinned by the tests:
83
+ * · first non-empty line only (a multi-line reply is not a prompt line);
84
+ * · a leading `> `/list marker and wrapping quotes/backticks are stripped;
85
+ * · meta/evaluative text ("Sure!", "Here's a suggestion:") is rejected —
86
+ * CC drops the same class;
87
+ * · must carry at least one letter or digit (punctuation-only is noise);
88
+ * · over-long output is cut at the last space with NO ellipsis — whatever
89
+ * is placed in the box gets sent verbatim, so decoration would be typed.
90
+ */
91
+ function sanitizeSuggestion(raw) {
92
+ if (typeof raw !== 'string')
93
+ return null;
94
+ const firstLine = raw.split('\n').find((l) => l.trim().length > 0);
95
+ if (!firstLine)
96
+ return null;
97
+ let s = firstLine.replace(/\s+/g, ' ').trim();
98
+ s = s.replace(/^>\s*/, '').trim();
99
+ // Strip one matching pair of wrappers the model likes to add.
100
+ for (const q of ['"', "'", '`']) {
101
+ if (s.length >= 2 && s.startsWith(q) && s.endsWith(q) && s.indexOf(q, 1) === s.length - 1) {
102
+ s = s.slice(1, -1).trim();
103
+ }
104
+ }
105
+ if (s.length < 2 || !hasLetterOrDigit(s))
106
+ return null;
107
+ if (META_PREFIXES.some((re) => re.test(s)))
108
+ return null;
109
+ if (s.length > exports.SUGGESTION_MAX_CHARS) {
110
+ const cut = s.slice(0, exports.SUGGESTION_MAX_CHARS);
111
+ const sp = cut.lastIndexOf(' ');
112
+ s = (sp > 0 ? cut.slice(0, sp) : cut).replace(/[\s,;:]+$/, '');
113
+ }
114
+ return s.length >= 2 && hasLetterOrDigit(s) ? s : null;
115
+ }
116
+ /**
117
+ * The ignore damping counter (CC: "after 20 in a row go unused" suggestions
118
+ * appear less often; using one brings them back). A suggestion counts as
119
+ * ignored when it was showing and the user typed something else; used when
120
+ * accepted. While damped only every `SUGGESTION_DAMPED_EVERY`th opportunity is
121
+ * offered — except the first, so the notice has something to accompany.
122
+ */
123
+ class SuggestionDamping {
124
+ constructor() {
125
+ this.ignored = 0;
126
+ this.whileDamped = 0;
127
+ this.noticePending = false;
128
+ }
129
+ /** Consecutive ignored suggestions (0 = full frequency). */
130
+ get ignoredCount() {
131
+ return this.ignored;
132
+ }
133
+ noteIgnored() {
134
+ if (this.ignored < exports.SUGGESTION_DAMP_AFTER) {
135
+ this.ignored += 1;
136
+ if (this.ignored === exports.SUGGESTION_DAMP_AFTER)
137
+ this.noticePending = true;
138
+ }
139
+ }
140
+ noteUsed() {
141
+ this.ignored = 0;
142
+ this.whileDamped = 0;
143
+ }
144
+ shouldOffer() {
145
+ if (this.ignored < exports.SUGGESTION_DAMP_AFTER)
146
+ return true;
147
+ const k = this.whileDamped % exports.SUGGESTION_DAMPED_EVERY;
148
+ this.whileDamped += 1;
149
+ return k === 0;
150
+ }
151
+ /** True exactly once after damping begins (until a use re-arms it). */
152
+ takeNotice() {
153
+ if (!this.noticePending)
154
+ return false;
155
+ this.noticePending = false;
156
+ return true;
157
+ }
158
+ }
159
+ exports.SuggestionDamping = SuggestionDamping;
160
+ //# sourceMappingURL=promptSuggest.js.map
@@ -0,0 +1,44 @@
1
+ /** Shape of the fields this module reads from core's SharedTask (kept structural). */
2
+ export interface GraphTask {
3
+ id: string;
4
+ content: string;
5
+ status: 'pending' | 'in_progress' | 'completed' | 'blocked';
6
+ blockedBy?: string[];
7
+ claimedBy?: string;
8
+ claimedByPid?: number;
9
+ claimedAt?: number;
10
+ updatedAt?: number;
11
+ }
12
+ export interface GraphInput {
13
+ tasks: GraphTask[];
14
+ now: number;
15
+ cols?: number;
16
+ /** Max rows to print; the tail is summarised. A big board must not bury the prompt. */
17
+ maxRows?: number;
18
+ }
19
+ export declare const STATUS_MARK: Record<GraphTask['status'], string>;
20
+ /** Tasks that no one has to wait for: no blockers, or blockers we cannot see. */
21
+ export declare function roots(tasks: GraphTask[]): GraphTask[];
22
+ export declare function blockersOf(tasks: GraphTask[], t: GraphTask): string[];
23
+ /** Blockers that are named but do not exist — a broken edge the user should see. */
24
+ export declare function missingBlockers(tasks: GraphTask[], t: GraphTask): string[];
25
+ /**
26
+ * Tasks that can be claimed this instant: nothing blocking, and nobody holding them.
27
+ *
28
+ * A task blocked by an in-progress or pending dependency is NOT ready even though it is
29
+ * itself `pending` — the whole point of the graph. Claimed-but-abandoned tasks are left
30
+ * out too: core's claim-liveness rules are the authority on whether a claim is dead, and
31
+ * second-guessing them here would print "ready" for something core would refuse.
32
+ */
33
+ export declare function readyTasks(tasks: GraphTask[]): GraphTask[];
34
+ /** Task ids in a dependency cycle (mutual or longer). Empty when the graph is a DAG. */
35
+ export declare function findCycle(tasks: GraphTask[]): string[];
36
+ /**
37
+ * The whole graph, as lines for the caller to print.
38
+ *
39
+ * Empty input is a normal state, not an error — most projects never create shared tasks.
40
+ * The caller pairs this with an explanation of how one gets created, which is why the
41
+ * empty case returns no lines at all rather than a placeholder.
42
+ */
43
+ export declare function formatTaskGraph(input: GraphInput): string[];
44
+ //# sourceMappingURL=taskGraph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"taskGraph.d.ts","sourceRoot":"","sources":["../../src/ui/taskGraph.ts"],"names":[],"mappings":"AA+BA,sFAAsF;AACtF,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,WAAW,GAAG,SAAS,CAAC;IAC5D,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uFAAuF;IACvF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAKD,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,MAAM,CAK3D,CAAC;AAEF,iFAAiF;AACjF,wBAAgB,KAAK,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CAGrD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC,EAAE,SAAS,GAAG,MAAM,EAAE,CAGrE;AAED,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC,EAAE,SAAS,GAAG,MAAM,EAAE,CAG1E;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CAO1D;AAED,wFAAwF;AACxF,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAwBtD;AAuED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,EAAE,CA4H3D"}