aegiscode 6.3.1 → 6.4.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/src/secret.js ADDED
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Read a secret from the terminal without echoing it.
5
+ *
6
+ * Extracted from app.js so the non-interactive entry point can use the same
7
+ * prompt as the in-session commands: `aegiscode login` asks for the account key
8
+ * on the same terms `/key` does, and a second copy of this loop would be a
9
+ * second place for a key to end up echoed to the screen or in a shell history.
10
+ *
11
+ * Raw mode when the stream supports it; a stream that cannot mask (no TTY, no
12
+ * setRawMode) resolves to '' rather than silently reading an echoed secret —
13
+ * every caller treats '' as "nothing given" and says so.
14
+ */
15
+
16
+ const MASK = '\u2022'; // •
17
+
18
+ function readSecret(promptText, o = {}) {
19
+ const stdin = o.stdin || process.stdin;
20
+ const stdout = o.stdout || process.stdout;
21
+ const canMask = !!stdin.isTTY && typeof stdin.setRawMode === 'function';
22
+ if (!canMask) return Promise.resolve('');
23
+ return new Promise((resolve) => {
24
+ stdout.write(promptText);
25
+ let buf = '';
26
+ const done = (value) => {
27
+ try {
28
+ stdin.setRawMode(false);
29
+ } catch {}
30
+ stdin.removeListener('data', onData);
31
+ stdout.write('\n');
32
+ resolve(value);
33
+ };
34
+ const onData = (chunk) => {
35
+ // Drop terminal escape sequences whole: skipping only the ESC leaves the
36
+ // CSI tail ([A, [3~, …) to be appended to the key.
37
+ const text = String(chunk).replace(/\x1b\[[0-9;?]*[A-Za-z~]/g, '').replace(/\x1b./g, '');
38
+ for (const ch of text) {
39
+ if (ch === '\r' || ch === '\n') return done(buf);
40
+ if (ch === '\x03' || ch === '\x04') return done(''); // ctrl+c / ctrl+d
41
+ if (ch === '\u007f' || ch === '\b') {
42
+ buf = buf.slice(0, -1);
43
+ stdout.write('\b \b');
44
+ continue;
45
+ }
46
+ buf += ch;
47
+ stdout.write(MASK);
48
+ }
49
+ };
50
+ stdin.setRawMode(true);
51
+ stdin.resume();
52
+ stdin.on('data', onData);
53
+ });
54
+ }
55
+
56
+ module.exports = { readSecret, MASK };
@@ -278,6 +278,13 @@ function createClient(opts = {}) {
278
278
  * so a reasoning-only stream still counts as unanswered.
279
279
  * - `idleTimeoutMs` override the stalled-stream watchdog for this call
280
280
  * (a worker fan-out is legitimately silent between passes)
281
+ * - `maxTokens` output ceiling. Omitted from the body when absent so the
282
+ * server's own budget applies — on a pooled request that is
283
+ * the `effort` ladder, and a number sent from here caps it.
284
+ * - `effort` / `workers` pooled-brain budget rung and fan-out size.
285
+ * Forwarded only inside `extra`, as the server reads them off
286
+ * the body (aegis1 services/pool_brain.py parse_brain_request)
287
+ * and sizes the budget from them.
281
288
  */
282
289
  async function chatCompletion({
283
290
  prompt,
@@ -298,9 +305,17 @@ function createClient(opts = {}) {
298
305
  // model the server picks its default (no client-invented tier id). `mode`
299
306
  // is a legacy server-side shorthand — forwarded verbatim only when the
300
307
  // caller supplies it, never defaulted, never built into a model id.
308
+ //
309
+ // `max_tokens` is omitted entirely when the caller states none, rather than
310
+ // defaulted here. The server has its own budget ladder (`mode`/`effort` on
311
+ // the pooled path) and treats a body max_tokens as a *ceiling* over it, so
312
+ // an invented 4096 was not a harmless default: it capped every pass of a
313
+ // pooled-brain fan-out at 4096 and overrode the ladder the caller's effort
314
+ // selected. Omitting it is the documented way to say "server default" (see
315
+ // mcp/tools.js's max_tokens description).
301
316
  const body = {
302
317
  messages: buildMessages(messages, system, prompt),
303
- max_tokens: maxTokens || 4096,
318
+ ...(maxTokens ? { max_tokens: maxTokens } : {}),
304
319
  ...(extra || {}),
305
320
  };
306
321
  if (model) body.model = model;
@@ -584,13 +584,18 @@ function createLocalEngine({ aegis, settings, ollama, providers, tools, promptBu
584
584
  // model as the fan-out, one sample instead of four — otherwise
585
585
  // dropping the fan-out would have quietly changed the model too.
586
586
  ...(brainFlag === false ? { mode: 'brain' } : {}),
587
- // Only meaningful (and only sent) alongside a running fan-out — aegis1
588
- // services/pool_brain.py parse_brain_request reads `effort`/
589
- // `workers` straight off the body and clamps them itself
590
- // (EFFORT_LEVELS / MAX_WORKERS), so no client-side validation here.
591
- // Keyed on the effective brain flag, not on `autonomous`: an opted-out
592
- // single pass carries no fan-out tuning it cannot use.
593
- ...(brainFlag === true && opts.effort ? { effort: opts.effort } : {}),
587
+ // `effort` is the budget rung, and it is sent whenever the caller has
588
+ // one — not only alongside a fan-out. The fan-out is triggered by the
589
+ // model id (this class sends ``nexus-brain``), so a caller that never
590
+ // ticked "work autonomously" still ran a pooled call and had no way to
591
+ // say how big it should be: the server fell through to its own default
592
+ // rung. That is how a one-word prompt came to reserve the top of the
593
+ // ladder. A single-pass pass carries it too inert on that path, but
594
+ // it keeps the request honest about what it asked for.
595
+ ...(opts.effort ? { effort: opts.effort } : {}),
596
+ // Only meaningful with a running fan-out — aegis1 services/pool_brain.py
597
+ // parse_brain_request reads `workers` straight off the body and clamps
598
+ // it itself (MAX_WORKERS), so no client-side validation here.
594
599
  ...(brainFlag === true && opts.workers ? { workers: opts.workers } : {}),
595
600
  // The pool forwards `tools` to the provider and returns tool_calls
596
601
  // (aegis1 app.py:7765 → provider, pool_brain synthesis keeps them).