aicodeman 1.29.0 → 1.30.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 +57 -30
- package/README.zh-CN.md +207 -51
- package/dist/reboot-restore.d.ts +146 -0
- package/dist/reboot-restore.d.ts.map +1 -0
- package/dist/reboot-restore.js +205 -0
- package/dist/reboot-restore.js.map +1 -0
- package/dist/session-auto-name.d.ts +74 -0
- package/dist/session-auto-name.d.ts.map +1 -0
- package/dist/session-auto-name.js +339 -0
- package/dist/session-auto-name.js.map +1 -0
- package/dist/session-env-clamp.d.ts +80 -0
- package/dist/session-env-clamp.d.ts.map +1 -0
- package/dist/session-env-clamp.js +96 -0
- package/dist/session-env-clamp.js.map +1 -0
- package/dist/session.d.ts +54 -4
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +86 -7
- package/dist/session.js.map +1 -1
- package/dist/types/session.d.ts +18 -0
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js.map +1 -1
- package/dist/web/ports/session-port.d.ts +31 -0
- package/dist/web/ports/session-port.d.ts.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/{app.556be563.js → app.6d2dc4e8.js} +2 -2
- package/dist/web/public/app.6d2dc4e8.js.br +0 -0
- package/dist/web/public/app.6d2dc4e8.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/constants.258b140f.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/i18n.5f897ed5.js +1 -0
- package/dist/web/public/i18n.5f897ed5.js.br +0 -0
- package/dist/web/public/i18n.5f897ed5.js.gz +0 -0
- package/dist/web/public/image-input.cd4b97c4.js.gz +0 -0
- package/dist/web/public/index.html +34 -8
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.2cf04f17.js.gz +0 -0
- package/dist/web/public/mobile-handlers.6f354a87.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/{mobile.e9d0b53e.css → mobile.6caaa28a.css} +1 -1
- package/dist/web/public/mobile.6caaa28a.css.br +0 -0
- package/dist/web/public/mobile.6caaa28a.css.gz +0 -0
- package/dist/web/public/notification-manager.36ea4624.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/panels-ui.5b07ad14.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/reboot-restore-ui.js +142 -0
- package/dist/web/public/reboot-restore-ui.js.br +0 -0
- package/dist/web/public/reboot-restore-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/session-ui.42b81477.js.gz +0 -0
- package/dist/web/public/settings-ui.58f1d756.js +67 -0
- package/dist/web/public/settings-ui.58f1d756.js.br +0 -0
- package/dist/web/public/settings-ui.58f1d756.js.gz +0 -0
- package/dist/web/public/styles.e6edfb0b.css +1 -0
- package/dist/web/public/styles.e6edfb0b.css.br +0 -0
- package/dist/web/public/styles.e6edfb0b.css.gz +0 -0
- package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/tab-rail-resize.42c24949.js.gz +0 -0
- package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js +1 -0
- package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js.br +0 -0
- package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js.gz +0 -0
- package/dist/web/public/{terminal-ui.38c49244.js → terminal-ui.4f8d5820.js} +2 -2
- package/dist/web/public/terminal-ui.4f8d5820.js.br +0 -0
- package/dist/web/public/terminal-ui.4f8d5820.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.bd6882b8.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.c4b51eb6.js.gz +0 -0
- package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/reboot-restore-registry.d.ts +107 -0
- package/dist/web/reboot-restore-registry.d.ts.map +1 -0
- package/dist/web/reboot-restore-registry.js +189 -0
- package/dist/web/reboot-restore-registry.js.map +1 -0
- package/dist/web/routes/index.d.ts +1 -0
- package/dist/web/routes/index.d.ts.map +1 -1
- package/dist/web/routes/index.js +1 -0
- package/dist/web/routes/index.js.map +1 -1
- package/dist/web/routes/reboot-restore-routes.d.ts +30 -0
- package/dist/web/routes/reboot-restore-routes.d.ts.map +1 -0
- package/dist/web/routes/reboot-restore-routes.js +273 -0
- package/dist/web/routes/reboot-restore-routes.js.map +1 -0
- package/dist/web/routes/session-routes.d.ts +1 -18
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +13 -71
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/ws-routes.d.ts.map +1 -1
- package/dist/web/routes/ws-routes.js +2 -1
- package/dist/web/routes/ws-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +12 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +20 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +74 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +243 -2
- package/dist/web/server.js.map +1 -1
- package/dist/web/session-listener-wiring.d.ts +6 -2
- package/dist/web/session-listener-wiring.d.ts.map +1 -1
- package/dist/web/session-listener-wiring.js +33 -2
- package/dist/web/session-listener-wiring.js.map +1 -1
- package/package.json +1 -1
- package/dist/web/public/app.556be563.js.br +0 -0
- package/dist/web/public/app.556be563.js.gz +0 -0
- package/dist/web/public/i18n.561ca09c.js +0 -1
- package/dist/web/public/i18n.561ca09c.js.br +0 -0
- package/dist/web/public/i18n.561ca09c.js.gz +0 -0
- package/dist/web/public/mobile.e9d0b53e.css.br +0 -0
- package/dist/web/public/mobile.e9d0b53e.css.gz +0 -0
- package/dist/web/public/settings-ui.e0c7f6b4.js +0 -67
- package/dist/web/public/settings-ui.e0c7f6b4.js.br +0 -0
- package/dist/web/public/settings-ui.e0c7f6b4.js.gz +0 -0
- package/dist/web/public/styles.6add175d.css +0 -1
- package/dist/web/public/styles.6add175d.css.br +0 -0
- package/dist/web/public/styles.6add175d.css.gz +0 -0
- package/dist/web/public/terminal-keycode229-recovery.6ea8fe37.js +0 -1
- package/dist/web/public/terminal-keycode229-recovery.6ea8fe37.js.br +0 -0
- package/dist/web/public/terminal-keycode229-recovery.6ea8fe37.js.gz +0 -0
- package/dist/web/public/terminal-ui.38c49244.js.br +0 -0
- package/dist/web/public/terminal-ui.38c49244.js.gz +0 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Automatic session names from the first prompt.
|
|
3
|
+
*
|
|
4
|
+
* A new tab is born as `w3-myapp`, which says where it runs and nothing about
|
|
5
|
+
* what it is doing. Once the user submits a real prompt the tab can carry a
|
|
6
|
+
* title derived from it (`w3-myapp: fix the login redirect`), and this module
|
|
7
|
+
* holds the three pure pieces of that: a tracker that reconstructs the composer
|
|
8
|
+
* text from the keystrokes Codeman forwards, the title heuristic, and the
|
|
9
|
+
* prefix-preserving composition.
|
|
10
|
+
*
|
|
11
|
+
* Deliberately no LLM: the prompt already passes through the input boundary,
|
|
12
|
+
* so a local title is private, deterministic and identical for every CLI.
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ The tracker sits on the raw keystroke stream, which carries far more than
|
|
15
|
+
* the prompt: cursor keys, mouse reports Codeman forwards to the CLI, bracketed
|
|
16
|
+
* pastes, Alt chords, the bare Esc that interrupts a turn. Every one of those
|
|
17
|
+
* once named a tab something wrong (a lone Esc ate the next prompt's first
|
|
18
|
+
* character; a wheel tick mid-word dropped the first half of the prompt), so
|
|
19
|
+
* the rules below are explicit per key. The model is a best-effort transcript:
|
|
20
|
+
* keys whose effect on the composer is knowable are mirrored, keys that leave
|
|
21
|
+
* the text alone are ignored, and keys that replace it with something the
|
|
22
|
+
* tracker cannot see (history recall) TAINT the draft so that Enter submits
|
|
23
|
+
* nothing rather than a fragment. A prompt that yields no title leaves the
|
|
24
|
+
* session eligible for the next one.
|
|
25
|
+
*
|
|
26
|
+
* Only user-originated input is fed here; the Session decides that. Ralph
|
|
27
|
+
* kick-starts, respawn `/clear`s, cron launches and approval answers all go
|
|
28
|
+
* through the same write paths and must never become a tab title.
|
|
29
|
+
*
|
|
30
|
+
* @module session-auto-name
|
|
31
|
+
*/
|
|
32
|
+
import { MAX_SESSION_NAME_LENGTH } from './config/terminal-limits.js';
|
|
33
|
+
/**
|
|
34
|
+
* Longest composer draft kept, in code points. The title is cut from the HEAD
|
|
35
|
+
* of the prompt, so once the cap is reached further text is counted rather
|
|
36
|
+
* than kept (backspaces consume that count first). Keeping the tail instead
|
|
37
|
+
* would turn a long paste into a title made of its last line.
|
|
38
|
+
*/
|
|
39
|
+
const MAX_PROMPT_BUFFER_CODE_POINTS = 8_192;
|
|
40
|
+
/** Longest escape sequence collected before the tracker gives up on it. */
|
|
41
|
+
const MAX_ESCAPE_SEQUENCE_LENGTH = 64;
|
|
42
|
+
/** Longest title, in code points, before it is cut with an ellipsis. */
|
|
43
|
+
const MAX_AUTO_NAME_CODE_POINTS = 72;
|
|
44
|
+
/**
|
|
45
|
+
* A sentence boundary is only honoured this far into the prompt, or "e.g. fix
|
|
46
|
+
* this now" becomes "e.g." and "Ok. Fix the bug" becomes "Ok". Short enough
|
|
47
|
+
* that a CJK sentence (a dozen code points is a full request) still cuts.
|
|
48
|
+
*/
|
|
49
|
+
const MIN_SENTENCE_CODE_POINTS = 8;
|
|
50
|
+
/** A CSI sequence ends at its first byte in this range. */
|
|
51
|
+
const CSI_FINAL_BYTE = /[\x40-\x7e]/;
|
|
52
|
+
/** CSI parameter and intermediate bytes; anything else mid-sequence is malformed. */
|
|
53
|
+
const CSI_BODY_BYTE = /[\x20-\x3f]/;
|
|
54
|
+
/**
|
|
55
|
+
* `/clear`, `/model opus`, `/ralph-loop:ralph-loop`: a slash followed by a
|
|
56
|
+
* command word and then whitespace or the end. A path (`/home/me/notes.txt
|
|
57
|
+
* what is this`) has a second slash where the whitespace should be and so is a
|
|
58
|
+
* prompt.
|
|
59
|
+
*/
|
|
60
|
+
const SLASH_COMMAND_PATTERN = /^\/[a-z][a-z0-9_:-]*(?:\s|$)/i;
|
|
61
|
+
// eslint-disable-next-line no-control-regex
|
|
62
|
+
const CSI_SEQUENCE_PATTERN = /\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g;
|
|
63
|
+
// eslint-disable-next-line no-control-regex
|
|
64
|
+
const CONTROL_CHAR_PATTERN = /[\x00-\x1f\x7f]/g;
|
|
65
|
+
const SENTENCE_TERMINATORS = new Set(['.', '!', '?', '。', '!', '?']);
|
|
66
|
+
/**
|
|
67
|
+
* Reconstructs the composer draft from forwarded keystrokes and reports each
|
|
68
|
+
* submitted prompt. Input arrives in arbitrary chunks (one keystroke, a paste,
|
|
69
|
+
* an agent's whole prompt plus Enter), so all state lives across calls.
|
|
70
|
+
*/
|
|
71
|
+
export class SubmittedPromptTracker {
|
|
72
|
+
buffer = '';
|
|
73
|
+
bufferCodePoints = 0;
|
|
74
|
+
/** Code points typed past the cap; backspaces eat these before real text. */
|
|
75
|
+
overflow = 0;
|
|
76
|
+
/** Escape sequence in progress; a lone ESC means "just saw ESC". */
|
|
77
|
+
sequence = '';
|
|
78
|
+
inPaste = false;
|
|
79
|
+
/** The composer holds text the tracker never saw (history recall); Enter submits nothing. */
|
|
80
|
+
tainted = false;
|
|
81
|
+
feed(data) {
|
|
82
|
+
const submitted = [];
|
|
83
|
+
for (const ch of data) {
|
|
84
|
+
if (this.sequence) {
|
|
85
|
+
this.continueSequence(ch);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (ch === '\x1b') {
|
|
89
|
+
this.sequence = ch;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
this.handleKey(ch, submitted);
|
|
93
|
+
}
|
|
94
|
+
// A chunk that ENDS in a lone ESC is the Esc key, not the start of a
|
|
95
|
+
// sequence: xterm hands each key's whole sequence to one write, and the
|
|
96
|
+
// programmatic senders (an approval deny sends exactly `\x1b`) send it
|
|
97
|
+
// alone. Leaving it pending would make the next prompt's first character
|
|
98
|
+
// look like an Alt chord and swallow it.
|
|
99
|
+
if (this.sequence === '\x1b')
|
|
100
|
+
this.sequence = '';
|
|
101
|
+
return submitted;
|
|
102
|
+
}
|
|
103
|
+
continueSequence(ch) {
|
|
104
|
+
if (this.sequence === '\x1b') {
|
|
105
|
+
if (ch === '[' || ch === 'O' || ch === ']' || ch === 'P') {
|
|
106
|
+
this.sequence += ch;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
this.sequence = ch === '\x1b' ? ch : '';
|
|
110
|
+
// Alt+Enter inserts a newline in the composer; every other Alt chord
|
|
111
|
+
// (word movement, Alt+B/F) leaves the text alone.
|
|
112
|
+
if (ch === '\r' || ch === '\n')
|
|
113
|
+
this.appendSeparator();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
this.sequence += ch;
|
|
117
|
+
if (this.sequence.length > MAX_ESCAPE_SEQUENCE_LENGTH) {
|
|
118
|
+
// Not a sequence any terminal sends; what follows is unknowable, so the
|
|
119
|
+
// draft is tainted rather than titled after the tail of the garbage.
|
|
120
|
+
this.sequence = '';
|
|
121
|
+
this.tainted = true;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const kind = this.sequence[1];
|
|
125
|
+
if (kind === '[') {
|
|
126
|
+
if (CSI_FINAL_BYTE.test(ch)) {
|
|
127
|
+
const sequence = this.sequence;
|
|
128
|
+
this.sequence = '';
|
|
129
|
+
this.handleCsi(sequence);
|
|
130
|
+
}
|
|
131
|
+
else if (!CSI_BODY_BYTE.test(ch)) {
|
|
132
|
+
// Malformed (an ESC [ followed by text): drop the sequence and let the
|
|
133
|
+
// character count as typed rather than swallowing up to 64 of them.
|
|
134
|
+
this.sequence = '';
|
|
135
|
+
this.handleKeyOrEscape(ch);
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (kind === 'O') {
|
|
140
|
+
// SS3 carries exactly one byte (application-mode cursor keys).
|
|
141
|
+
this.sequence = '';
|
|
142
|
+
if (ch === 'A' || ch === 'B')
|
|
143
|
+
this.tainted = true;
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
// OSC / DCS run to BEL or ST (ESC \).
|
|
147
|
+
if (ch === '\x07' || this.sequence.endsWith('\x1b\\'))
|
|
148
|
+
this.sequence = '';
|
|
149
|
+
}
|
|
150
|
+
handleKeyOrEscape(ch) {
|
|
151
|
+
if (ch === '\x1b') {
|
|
152
|
+
this.sequence = ch;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// Only reached mid-chunk from a malformed sequence, where no submission can
|
|
156
|
+
// be reported; a stray Enter there resets the draft like any other Enter.
|
|
157
|
+
this.handleKey(ch, []);
|
|
158
|
+
}
|
|
159
|
+
handleCsi(sequence) {
|
|
160
|
+
if (sequence === '\x1b[200~') {
|
|
161
|
+
this.inPaste = true;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (sequence === '\x1b[201~') {
|
|
165
|
+
this.inPaste = false;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const final = sequence[sequence.length - 1];
|
|
169
|
+
// Up/Down (with or without modifiers) recall history: the composer now
|
|
170
|
+
// holds a line this tracker never saw. Everything else leaves the text as
|
|
171
|
+
// it is: Left/Right/Home/End, Delete (`3~`), Shift+Tab (`Z`), SGR mouse
|
|
172
|
+
// reports (`<…M`/`m`, forwarded on every wheel tick), focus reports.
|
|
173
|
+
if (final === 'A' || final === 'B')
|
|
174
|
+
this.tainted = true;
|
|
175
|
+
}
|
|
176
|
+
handleKey(ch, submitted) {
|
|
177
|
+
const codePoint = ch.codePointAt(0) ?? 0;
|
|
178
|
+
if (this.inPaste) {
|
|
179
|
+
// Pasted newlines are newlines IN the composer, never Enter; they and
|
|
180
|
+
// the other controls (tabs) become a single separator.
|
|
181
|
+
if (codePoint < 0x20 || codePoint === 0x7f)
|
|
182
|
+
this.appendSeparator();
|
|
183
|
+
else
|
|
184
|
+
this.append(ch);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
switch (ch) {
|
|
188
|
+
case '\r': {
|
|
189
|
+
const prompt = this.tainted ? '' : this.buffer.trim();
|
|
190
|
+
if (prompt)
|
|
191
|
+
submitted.push(prompt);
|
|
192
|
+
this.reset();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
case '\n':
|
|
196
|
+
// Ctrl+J, and the line feed the send-key route injects for Shift+Enter:
|
|
197
|
+
// a newline inside the composer, so the lines join with a separator.
|
|
198
|
+
this.appendSeparator();
|
|
199
|
+
return;
|
|
200
|
+
case '\x7f':
|
|
201
|
+
case '\x08':
|
|
202
|
+
this.backspace();
|
|
203
|
+
return;
|
|
204
|
+
case '\x17': // Ctrl+W: word rubout
|
|
205
|
+
this.killWord();
|
|
206
|
+
return;
|
|
207
|
+
case '\x15': // Ctrl+U: line discard
|
|
208
|
+
case '\x03': // Ctrl+C: clears the composer (or, empty, arms an exit)
|
|
209
|
+
this.reset();
|
|
210
|
+
return;
|
|
211
|
+
case '\x10': // Ctrl+P
|
|
212
|
+
case '\x0e': // Ctrl+N
|
|
213
|
+
case '\x12': // Ctrl+R: history search
|
|
214
|
+
case '\x1f': // Ctrl+_: undo
|
|
215
|
+
this.tainted = true;
|
|
216
|
+
return;
|
|
217
|
+
default:
|
|
218
|
+
// Tab (the @-mention completer, which only ever extends the token),
|
|
219
|
+
// cursor chords (Ctrl+A/E/B/F) and the rest of C0 leave the text alone.
|
|
220
|
+
if (codePoint < 0x20 || codePoint === 0x7f)
|
|
221
|
+
return;
|
|
222
|
+
this.append(ch);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/** One space between lines, never a run of them, and none at the start. */
|
|
226
|
+
appendSeparator() {
|
|
227
|
+
if (this.overflow > 0)
|
|
228
|
+
return;
|
|
229
|
+
if (!this.buffer || /\s$/.test(this.buffer))
|
|
230
|
+
return;
|
|
231
|
+
this.append(' ');
|
|
232
|
+
}
|
|
233
|
+
append(ch) {
|
|
234
|
+
if (this.bufferCodePoints >= MAX_PROMPT_BUFFER_CODE_POINTS) {
|
|
235
|
+
this.overflow += 1;
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
this.buffer += ch;
|
|
239
|
+
this.bufferCodePoints += 1;
|
|
240
|
+
}
|
|
241
|
+
backspace() {
|
|
242
|
+
if (this.overflow > 0) {
|
|
243
|
+
this.overflow -= 1;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (!this.buffer)
|
|
247
|
+
return;
|
|
248
|
+
const last = this.buffer.charCodeAt(this.buffer.length - 1);
|
|
249
|
+
const units = last >= 0xdc00 && last <= 0xdfff && this.buffer.length >= 2 ? 2 : 1;
|
|
250
|
+
this.buffer = this.buffer.slice(0, -units);
|
|
251
|
+
this.bufferCodePoints -= 1;
|
|
252
|
+
}
|
|
253
|
+
killWord() {
|
|
254
|
+
this.overflow = 0;
|
|
255
|
+
this.buffer = this.buffer.replace(/\S+\s*$/u, '');
|
|
256
|
+
this.bufferCodePoints = Array.from(this.buffer).length;
|
|
257
|
+
}
|
|
258
|
+
reset() {
|
|
259
|
+
this.buffer = '';
|
|
260
|
+
this.bufferCodePoints = 0;
|
|
261
|
+
this.overflow = 0;
|
|
262
|
+
this.tainted = false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Turns a submitted prompt into a title, or null when the prompt is not a task:
|
|
267
|
+
* empty, a slash command (`/clear`, `/model`), or a `!` shell escape.
|
|
268
|
+
*/
|
|
269
|
+
export function deriveAutoSessionName(prompt) {
|
|
270
|
+
const text = prompt.replace(CSI_SEQUENCE_PATTERN, '').replace(CONTROL_CHAR_PATTERN, ' ').replace(/\s+/g, ' ').trim();
|
|
271
|
+
if (!text || text.startsWith('!') || SLASH_COMMAND_PATTERN.test(text))
|
|
272
|
+
return null;
|
|
273
|
+
return truncateCodePoints(firstSentence(text), MAX_AUTO_NAME_CODE_POINTS);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* The first sentence, provided it is long enough to be one; a trailing full
|
|
277
|
+
* stop is dropped because a tab title is not a sentence.
|
|
278
|
+
*/
|
|
279
|
+
function firstSentence(text) {
|
|
280
|
+
const codePoints = Array.from(text);
|
|
281
|
+
for (let i = MIN_SENTENCE_CODE_POINTS - 1; i < codePoints.length; i++) {
|
|
282
|
+
if (!SENTENCE_TERMINATORS.has(codePoints[i]))
|
|
283
|
+
continue;
|
|
284
|
+
const next = codePoints[i + 1];
|
|
285
|
+
if (next !== undefined && !/\s/.test(next))
|
|
286
|
+
continue;
|
|
287
|
+
return codePoints
|
|
288
|
+
.slice(0, i + 1)
|
|
289
|
+
.join('')
|
|
290
|
+
.replace(/[.。]+$/, '');
|
|
291
|
+
}
|
|
292
|
+
return text.replace(/[.。]+$/, '');
|
|
293
|
+
}
|
|
294
|
+
/** Cuts to `max` code points with an ellipsis, on a word boundary when one is near the end. */
|
|
295
|
+
function truncateCodePoints(text, max) {
|
|
296
|
+
const codePoints = Array.from(text);
|
|
297
|
+
if (codePoints.length <= max)
|
|
298
|
+
return text;
|
|
299
|
+
let cut = codePoints.slice(0, max - 1).join('');
|
|
300
|
+
const lastSpace = cut.lastIndexOf(' ');
|
|
301
|
+
if (lastSpace >= Math.floor(cut.length / 2))
|
|
302
|
+
cut = cut.slice(0, lastSpace);
|
|
303
|
+
return `${cut.trimEnd()}…`;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* The name a placeholder becomes: `<prefix>: <title>`, so the tab keeps its
|
|
307
|
+
* case identity and its `w<n>` counter (the tab strip already renders that
|
|
308
|
+
* form as the title alone, prefix in the tooltip, and the next-session counter
|
|
309
|
+
* still matches it). A session with no name at all just takes the title. The
|
|
310
|
+
* result honours `maxLength` in UTF-16 units, the unit the rename route caps.
|
|
311
|
+
*/
|
|
312
|
+
export function composeAutoSessionName(currentName, title, maxLength = MAX_SESSION_NAME_LENGTH) {
|
|
313
|
+
const prefix = currentName.trim();
|
|
314
|
+
if (!prefix)
|
|
315
|
+
return fitTitle(title, maxLength);
|
|
316
|
+
const room = maxLength - prefix.length - 2;
|
|
317
|
+
if (room <= 0)
|
|
318
|
+
return prefix;
|
|
319
|
+
return `${prefix}: ${fitTitle(title, room)}`;
|
|
320
|
+
}
|
|
321
|
+
/** Fits a title into `maxUnits` UTF-16 units, ellipsis included. */
|
|
322
|
+
function fitTitle(title, maxUnits) {
|
|
323
|
+
if (title.length <= maxUnits)
|
|
324
|
+
return title;
|
|
325
|
+
let units = 0;
|
|
326
|
+
let keep = 0;
|
|
327
|
+
for (const codePoint of Array.from(title)) {
|
|
328
|
+
if (units + codePoint.length > maxUnits - 1)
|
|
329
|
+
break;
|
|
330
|
+
units += codePoint.length;
|
|
331
|
+
keep += 1;
|
|
332
|
+
}
|
|
333
|
+
return truncateCodePoints(title, keep + 1);
|
|
334
|
+
}
|
|
335
|
+
/** Codeman's own `w<n>-<case>` / `s<n>-<case>` placeholders, the only names auto-naming replaces. */
|
|
336
|
+
export function isGeneratedSessionName(name) {
|
|
337
|
+
return /^[ws]\d+-[a-zA-Z0-9_-]+$/.test(name);
|
|
338
|
+
}
|
|
339
|
+
//# sourceMappingURL=session-auto-name.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-auto-name.js","sourceRoot":"","sources":["../src/session-auto-name.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The env-var half of the multi-user privilege clamp.
|
|
3
|
+
*
|
|
4
|
+
* A session's `envOverrides` can hand back privilege that the per-CLI config
|
|
5
|
+
* clamp removed, so a non-granted owner's overrides get the privileged keys
|
|
6
|
+
* stripped before the session is built. The create and resume routes are what
|
|
7
|
+
* this bites on: they clamp what a request asked for.
|
|
8
|
+
*
|
|
9
|
+
* The reboot-restore route calls it as defence in depth, and today it can strip
|
|
10
|
+
* nothing. `Session.getEnvOverridesForPersist()` keeps only `CLAUDE_CODE_*` and
|
|
11
|
+
* `CLAUDE_CONFIG_DIR` out of a session's overrides, claude's `privilegedEnvKeys`
|
|
12
|
+
* are the five `ANTHROPIC_*` names, and that pass admits claude alone — so a
|
|
13
|
+
* persisted record cannot carry a clamped key. The call is there for the day the
|
|
14
|
+
* persisted set widens. The grant re-resolution that does bite on that path is
|
|
15
|
+
* `resolveClaudeModeForUsername`, which recomputes the permission mode.
|
|
16
|
+
*
|
|
17
|
+
* This lives outside `web/routes` on purpose. The question it answers is about
|
|
18
|
+
* session privilege rather than about HTTP, and `cron/cron-service.ts` sets the
|
|
19
|
+
* precedent by importing `canUsernameRunPrivilegedCommands` from `user-store.ts`
|
|
20
|
+
* directly and re-resolving the owner's grant when a job fires. Every caller here
|
|
21
|
+
* re-resolves the grant at the moment it builds a session, for the same reason.
|
|
22
|
+
*
|
|
23
|
+
* @dependencies user-store (canUsernameRunPrivilegedCommands), config/cli-registry
|
|
24
|
+
* @consumedby web/routes/session-routes, web/routes/reboot-restore-routes
|
|
25
|
+
*
|
|
26
|
+
* @module session-env-clamp
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Env-var keys a non-granted owner must not be able to set, because each one
|
|
30
|
+
* hands back privilege `clampExternalCliBypassForOwner()` just removed, or redirects a
|
|
31
|
+
* credential-resolution endpoint.
|
|
32
|
+
*
|
|
33
|
+
* The DeepSeek three are reachable because `DSH_*` and `DEEPSEEK_*` are
|
|
34
|
+
* allowlisted `envOverrides` prefixes (schemas.ts) — which they have to be, since
|
|
35
|
+
* that is also how a user configures the harness's non-privileged knobs.
|
|
36
|
+
*
|
|
37
|
+
* - `DSH_PERMISSION_MODE` IS the harness's permission switch. Every other CLI's
|
|
38
|
+
* bypass is a command-line FLAG, reachable only through the per-CLI config the
|
|
39
|
+
* clamp already owns; this one is an env var, so the config clamp alone is
|
|
40
|
+
* half a gate.
|
|
41
|
+
* - `DSH_HOME` points the launcher at a profile tree, and a profile's plugin code
|
|
42
|
+
* executes at BOOT, before any approval row can apply. A user who can write a
|
|
43
|
+
* workspace can put a profile in it, so this is the wider of the two.
|
|
44
|
+
* - `DEEPSEEK_BASE_URL` aims the provider endpoint, and `_configureCliEnv()`
|
|
45
|
+
* forwards the SERVER's own `DEEPSEEK_API_KEY` into every dsh pane before
|
|
46
|
+
* `applyEnvOverrides()` runs — so a non-granted owner who could set the base
|
|
47
|
+
* URL would have the operator's API key sent as a bearer credential to a host
|
|
48
|
+
* of their choosing. (`DEEPSEEK_API_KEY` itself stays overridable: supplying
|
|
49
|
+
* your OWN key removes privilege rather than granting it.)
|
|
50
|
+
* - `OMP_AUTH_BROKER_URL`/`OMP_AUTH_BROKER_TOKEN` are where omp resolves
|
|
51
|
+
* credentials from — the same shape as `DEEPSEEK_BASE_URL` above, reachable
|
|
52
|
+
* because `OMP_*` is an allowlisted prefix. Unlike DeepSeek, Codeman does not
|
|
53
|
+
* forward any operator-held key into an omp pane today (omp's provider
|
|
54
|
+
* credentials live in `~/.omp` config files, not env vars), so there is no
|
|
55
|
+
* known concrete exfiltration path yet — clamped defensively anyway, since a
|
|
56
|
+
* non-granted owner redirecting where a shared multi-tenant deployment
|
|
57
|
+
* resolves auth from is not something to allow silently (found in
|
|
58
|
+
* Ark0N/Codeman#353 review; omp's own knobs are otherwise mostly `PI_*`,
|
|
59
|
+
* already allowlisted for pi and not addressed here — see resolveOmpHome()).
|
|
60
|
+
*/
|
|
61
|
+
export declare function ownerClampedEnvKeys(): string[];
|
|
62
|
+
/**
|
|
63
|
+
* Env-var half of the multi-user bypass clamp.
|
|
64
|
+
*
|
|
65
|
+
* `clampExternalCliBypassForOwner()` in `web/routes/session-routes.ts` clamps the
|
|
66
|
+
* per-CLI CONFIG, and for every CLI
|
|
67
|
+
* but DeepSeek that is the whole story. Here it is not: `applyEnvOverrides()` runs
|
|
68
|
+
* AFTER `_configureCliEnv()` in tmux-manager, so an override sent on the SAME
|
|
69
|
+
* request lands last and wins, and a non-granted owner could restore
|
|
70
|
+
* `danger-full-access` on the very request the config clamp downgraded.
|
|
71
|
+
*
|
|
72
|
+
* Keys are DROPPED rather than rewritten: dropping falls through to what
|
|
73
|
+
* `_configureCliEnv()` exports, which is the clamped config and the server's own
|
|
74
|
+
* `DSH_HOME`, i.e. exactly the intended state. No-op in single-user mode and for a
|
|
75
|
+
* granted owner, like every other clamp here
|
|
76
|
+
* (`canUsernameRunPrivilegedCommands()` returns true when `!isMultiUserMode()`),
|
|
77
|
+
* and it returns the caller's own object untouched when there is nothing to strip.
|
|
78
|
+
*/
|
|
79
|
+
export declare function clampEnvOverridesForOwner(owner: string | undefined, envOverrides: Record<string, string> | undefined): Promise<Record<string, string> | undefined>;
|
|
80
|
+
//# sourceMappingURL=session-env-clamp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-env-clamp.d.ts","sourceRoot":"","sources":["../src/session-env-clamp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,yBAAyB,CAC7C,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,GAC/C,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC,CAQ7C"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The env-var half of the multi-user privilege clamp.
|
|
3
|
+
*
|
|
4
|
+
* A session's `envOverrides` can hand back privilege that the per-CLI config
|
|
5
|
+
* clamp removed, so a non-granted owner's overrides get the privileged keys
|
|
6
|
+
* stripped before the session is built. The create and resume routes are what
|
|
7
|
+
* this bites on: they clamp what a request asked for.
|
|
8
|
+
*
|
|
9
|
+
* The reboot-restore route calls it as defence in depth, and today it can strip
|
|
10
|
+
* nothing. `Session.getEnvOverridesForPersist()` keeps only `CLAUDE_CODE_*` and
|
|
11
|
+
* `CLAUDE_CONFIG_DIR` out of a session's overrides, claude's `privilegedEnvKeys`
|
|
12
|
+
* are the five `ANTHROPIC_*` names, and that pass admits claude alone — so a
|
|
13
|
+
* persisted record cannot carry a clamped key. The call is there for the day the
|
|
14
|
+
* persisted set widens. The grant re-resolution that does bite on that path is
|
|
15
|
+
* `resolveClaudeModeForUsername`, which recomputes the permission mode.
|
|
16
|
+
*
|
|
17
|
+
* This lives outside `web/routes` on purpose. The question it answers is about
|
|
18
|
+
* session privilege rather than about HTTP, and `cron/cron-service.ts` sets the
|
|
19
|
+
* precedent by importing `canUsernameRunPrivilegedCommands` from `user-store.ts`
|
|
20
|
+
* directly and re-resolving the owner's grant when a job fires. Every caller here
|
|
21
|
+
* re-resolves the grant at the moment it builds a session, for the same reason.
|
|
22
|
+
*
|
|
23
|
+
* @dependencies user-store (canUsernameRunPrivilegedCommands), config/cli-registry
|
|
24
|
+
* @consumedby web/routes/session-routes, web/routes/reboot-restore-routes
|
|
25
|
+
*
|
|
26
|
+
* @module session-env-clamp
|
|
27
|
+
*/
|
|
28
|
+
import { canUsernameRunPrivilegedCommands } from './user-store.js';
|
|
29
|
+
import { enabledClis } from './config/cli-registry/registry.js';
|
|
30
|
+
/**
|
|
31
|
+
* Env-var keys a non-granted owner must not be able to set, because each one
|
|
32
|
+
* hands back privilege `clampExternalCliBypassForOwner()` just removed, or redirects a
|
|
33
|
+
* credential-resolution endpoint.
|
|
34
|
+
*
|
|
35
|
+
* The DeepSeek three are reachable because `DSH_*` and `DEEPSEEK_*` are
|
|
36
|
+
* allowlisted `envOverrides` prefixes (schemas.ts) — which they have to be, since
|
|
37
|
+
* that is also how a user configures the harness's non-privileged knobs.
|
|
38
|
+
*
|
|
39
|
+
* - `DSH_PERMISSION_MODE` IS the harness's permission switch. Every other CLI's
|
|
40
|
+
* bypass is a command-line FLAG, reachable only through the per-CLI config the
|
|
41
|
+
* clamp already owns; this one is an env var, so the config clamp alone is
|
|
42
|
+
* half a gate.
|
|
43
|
+
* - `DSH_HOME` points the launcher at a profile tree, and a profile's plugin code
|
|
44
|
+
* executes at BOOT, before any approval row can apply. A user who can write a
|
|
45
|
+
* workspace can put a profile in it, so this is the wider of the two.
|
|
46
|
+
* - `DEEPSEEK_BASE_URL` aims the provider endpoint, and `_configureCliEnv()`
|
|
47
|
+
* forwards the SERVER's own `DEEPSEEK_API_KEY` into every dsh pane before
|
|
48
|
+
* `applyEnvOverrides()` runs — so a non-granted owner who could set the base
|
|
49
|
+
* URL would have the operator's API key sent as a bearer credential to a host
|
|
50
|
+
* of their choosing. (`DEEPSEEK_API_KEY` itself stays overridable: supplying
|
|
51
|
+
* your OWN key removes privilege rather than granting it.)
|
|
52
|
+
* - `OMP_AUTH_BROKER_URL`/`OMP_AUTH_BROKER_TOKEN` are where omp resolves
|
|
53
|
+
* credentials from — the same shape as `DEEPSEEK_BASE_URL` above, reachable
|
|
54
|
+
* because `OMP_*` is an allowlisted prefix. Unlike DeepSeek, Codeman does not
|
|
55
|
+
* forward any operator-held key into an omp pane today (omp's provider
|
|
56
|
+
* credentials live in `~/.omp` config files, not env vars), so there is no
|
|
57
|
+
* known concrete exfiltration path yet — clamped defensively anyway, since a
|
|
58
|
+
* non-granted owner redirecting where a shared multi-tenant deployment
|
|
59
|
+
* resolves auth from is not something to allow silently (found in
|
|
60
|
+
* Ark0N/Codeman#353 review; omp's own knobs are otherwise mostly `PI_*`,
|
|
61
|
+
* already allowlisted for pi and not addressed here — see resolveOmpHome()).
|
|
62
|
+
*/
|
|
63
|
+
export function ownerClampedEnvKeys() {
|
|
64
|
+
return enabledClis().flatMap((entry) => entry.capabilities.privilegedEnvKeys);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Env-var half of the multi-user bypass clamp.
|
|
68
|
+
*
|
|
69
|
+
* `clampExternalCliBypassForOwner()` in `web/routes/session-routes.ts` clamps the
|
|
70
|
+
* per-CLI CONFIG, and for every CLI
|
|
71
|
+
* but DeepSeek that is the whole story. Here it is not: `applyEnvOverrides()` runs
|
|
72
|
+
* AFTER `_configureCliEnv()` in tmux-manager, so an override sent on the SAME
|
|
73
|
+
* request lands last and wins, and a non-granted owner could restore
|
|
74
|
+
* `danger-full-access` on the very request the config clamp downgraded.
|
|
75
|
+
*
|
|
76
|
+
* Keys are DROPPED rather than rewritten: dropping falls through to what
|
|
77
|
+
* `_configureCliEnv()` exports, which is the clamped config and the server's own
|
|
78
|
+
* `DSH_HOME`, i.e. exactly the intended state. No-op in single-user mode and for a
|
|
79
|
+
* granted owner, like every other clamp here
|
|
80
|
+
* (`canUsernameRunPrivilegedCommands()` returns true when `!isMultiUserMode()`),
|
|
81
|
+
* and it returns the caller's own object untouched when there is nothing to strip.
|
|
82
|
+
*/
|
|
83
|
+
export async function clampEnvOverridesForOwner(owner, envOverrides) {
|
|
84
|
+
if (!envOverrides)
|
|
85
|
+
return envOverrides;
|
|
86
|
+
const keys = ownerClampedEnvKeys();
|
|
87
|
+
if (!keys.some((key) => key in envOverrides))
|
|
88
|
+
return envOverrides;
|
|
89
|
+
if (await canUsernameRunPrivilegedCommands(owner))
|
|
90
|
+
return envOverrides;
|
|
91
|
+
const clamped = { ...envOverrides };
|
|
92
|
+
for (const key of keys)
|
|
93
|
+
delete clamped[key];
|
|
94
|
+
return clamped;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=session-env-clamp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-env-clamp.js","sourceRoot":"","sources":["../src/session-env-clamp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,gCAAgC,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO,WAAW,EAAE,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC;AAChF,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,KAAyB,EACzB,YAAgD;IAEhD,IAAI,CAAC,YAAY;QAAE,OAAO,YAAY,CAAC;IACvC,MAAM,IAAI,GAAG,mBAAmB,EAAE,CAAC;IACnC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,YAAY,CAAC;QAAE,OAAO,YAAY,CAAC;IAClE,IAAI,MAAM,gCAAgC,CAAC,KAAK,CAAC;QAAE,OAAO,YAAY,CAAC;IACvE,MAAM,OAAO,GAAG,EAAE,GAAG,YAAY,EAAE,CAAC;IACpC,KAAK,MAAM,GAAG,IAAI,IAAI;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;IAC5C,OAAO,OAAO,CAAC;AACjB,CAAC"}
|