aegis-bridge 2.5.2 → 2.5.4
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/dashboard/dist/assets/{index-DxAes2EQ.js → index-DIyuyrlO.js} +47 -47
- package/dashboard/dist/index.html +1 -1
- package/dist/auth.d.ts +3 -2
- package/dist/auth.js +42 -29
- package/dist/channels/manager.d.ts +8 -0
- package/dist/channels/manager.js +15 -0
- package/dist/channels/webhook.d.ts +2 -0
- package/dist/channels/webhook.js +57 -15
- package/dist/dashboard/assets/{index-DxAes2EQ.js → index-DIyuyrlO.js} +47 -47
- package/dist/dashboard/index.html +1 -1
- package/dist/events.d.ts +2 -0
- package/dist/events.js +16 -1
- package/dist/hook-settings.js +31 -11
- package/dist/hooks.js +2 -2
- package/dist/jsonl-watcher.js +9 -2
- package/dist/pipeline.js +10 -0
- package/dist/server.js +32 -7
- package/dist/session.d.ts +7 -1
- package/dist/session.js +93 -22
- package/dist/sse-writer.js +1 -1
- package/dist/ssrf.d.ts +19 -2
- package/dist/ssrf.js +38 -6
- package/dist/tmux.d.ts +8 -7
- package/dist/tmux.js +51 -36
- package/dist/transcript.js +12 -8
- package/dist/validation.d.ts +4 -0
- package/dist/validation.js +3 -2
- package/package.json +1 -1
package/dist/tmux.js
CHANGED
|
@@ -217,6 +217,15 @@ export class TmuxManager {
|
|
|
217
217
|
if (lastError) {
|
|
218
218
|
throw new Error(`Failed to create tmux window after ${MAX_RETRIES} attempts: ${name} — ${lastError.message}`);
|
|
219
219
|
}
|
|
220
|
+
// #837: Set env vars INSIDE the serialize block to prevent race.
|
|
221
|
+
// Previously setEnvSecure ran after serialize() returned, so concurrent
|
|
222
|
+
// createWindow calls could interleave send-keys between window creation
|
|
223
|
+
// and env injection, corrupting the environment.
|
|
224
|
+
// Uses setEnvSecureDirect (sendKeysDirectInternal) to avoid re-entering
|
|
225
|
+
// serialize from within an active serialize callback.
|
|
226
|
+
if (opts.env && Object.keys(opts.env).length > 0) {
|
|
227
|
+
await this.setEnvSecureDirect(id, opts.env);
|
|
228
|
+
}
|
|
220
229
|
return { windowId: id, windowName: name };
|
|
221
230
|
});
|
|
222
231
|
windowId = creationResult.windowId;
|
|
@@ -225,31 +234,6 @@ export class TmuxManager {
|
|
|
225
234
|
finally {
|
|
226
235
|
this._creatingCount--;
|
|
227
236
|
}
|
|
228
|
-
// Set env vars if provided.
|
|
229
|
-
// Issue #89 L29: Recommended Claude Code environment variables.
|
|
230
|
-
// These are merged in SessionManager.createSession() from config.defaultSessionEnv
|
|
231
|
-
// and per-session opts.env (user vars override defaults).
|
|
232
|
-
//
|
|
233
|
-
// Key env vars CC recognizes:
|
|
234
|
-
// ANTHROPIC_API_KEY — Required for Anthropic API direct access
|
|
235
|
-
// ANTHROPIC_BASE_URL — Custom API endpoint (e.g. proxy/bedrock)
|
|
236
|
-
// ANTHROPIC_MODEL — Override default model selection
|
|
237
|
-
// DISABLE_AUTOUPDATER=1 — Suppress CC's auto-update check
|
|
238
|
-
// CLAUDE_CODE_SKIP_EULA=1 — Skip EULA prompt on first launch
|
|
239
|
-
// CLAUDE_CODE_USE_BEDROCK=1 — Use AWS Bedrock backend
|
|
240
|
-
// MCP_TIMEOUT_MS — Timeout for MCP server communication
|
|
241
|
-
// NO_COLOR=1 — Disable color output (useful for parsing)
|
|
242
|
-
// HOME — User home directory (usually inherited)
|
|
243
|
-
// PATH — Binary search path (usually inherited)
|
|
244
|
-
//
|
|
245
|
-
// Note: The --settings flag already handles proxy config (z.ai) and
|
|
246
|
-
// workspace trust, so ANTHROPIC_BASE_URL via env is a fallback.
|
|
247
|
-
//
|
|
248
|
-
// Issue #23: Use temp file + source instead of send-keys export to prevent
|
|
249
|
-
// env var values (tokens, secrets) from appearing in tmux pane history.
|
|
250
|
-
if (opts.env && Object.keys(opts.env).length > 0) {
|
|
251
|
-
await this.setEnvSecure(windowId, opts.env);
|
|
252
|
-
}
|
|
253
237
|
// Ensure Claude starts a fresh session.
|
|
254
238
|
// Two-layer defense against CC auto-resuming stale sessions:
|
|
255
239
|
//
|
|
@@ -417,6 +401,38 @@ export class TmuxManager {
|
|
|
417
401
|
}
|
|
418
402
|
catch { /* already deleted by shell */ }
|
|
419
403
|
}
|
|
404
|
+
/** #837: Direct variant of setEnvSecure that uses sendKeysDirectInternal instead of
|
|
405
|
+
* sendKeys, safe to call from inside a serialize() callback without deadlocking.
|
|
406
|
+
* Identical logic otherwise. */
|
|
407
|
+
async setEnvSecureDirect(windowId, env) {
|
|
408
|
+
const fs = await import('node:fs/promises');
|
|
409
|
+
const path = await import('node:path');
|
|
410
|
+
for (const key of Object.keys(env)) {
|
|
411
|
+
if (!ENV_KEY_RE.test(key)) {
|
|
412
|
+
throw new Error(`Invalid env var key: '${key}' — must match ${ENV_KEY_RE.source}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const tmpFile = path.join(tmpdir(), `.aegis-env-${randomBytes(16).toString('hex')}`);
|
|
416
|
+
const lines = Object.entries(env).map(([key, val]) => {
|
|
417
|
+
const escaped = val.replace(/'/g, "'\\''");
|
|
418
|
+
return `export ${key}='${escaped}'`;
|
|
419
|
+
});
|
|
420
|
+
await fs.writeFile(tmpFile, lines.join('\n') + '\n', { mode: 0o600 });
|
|
421
|
+
// Use sendKeysDirectInternal to avoid re-entering serialize()
|
|
422
|
+
const cmd = `source ${shellEscape(tmpFile)} && rm -f ${shellEscape(tmpFile)}`;
|
|
423
|
+
await this.sendKeysDirectInternal(windowId, cmd, true);
|
|
424
|
+
await this.pollUntil(async () => { try {
|
|
425
|
+
await stat(tmpFile);
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
catch {
|
|
429
|
+
return true;
|
|
430
|
+
} }, 50, 500);
|
|
431
|
+
try {
|
|
432
|
+
await fs.unlink(tmpFile);
|
|
433
|
+
}
|
|
434
|
+
catch { /* already deleted by shell */ }
|
|
435
|
+
}
|
|
420
436
|
/** P1 fix: Check if a window exists. Returns true if window is in the session.
|
|
421
437
|
* #357: Uses a short-lived cache to avoid repeated tmux CLI calls. */
|
|
422
438
|
async windowExists(windowId) {
|
|
@@ -620,19 +636,13 @@ export class TmuxManager {
|
|
|
620
636
|
const raw = await this.tmux('capture-pane', '-t', target, '-p');
|
|
621
637
|
return raw.replace(/\x1bP[\s\S]*?\x1b\\/g, '');
|
|
622
638
|
}
|
|
623
|
-
/** Capture pane content
|
|
624
|
-
*
|
|
625
|
-
*
|
|
626
|
-
*
|
|
627
|
-
* session creation time.
|
|
628
|
-
* #403: During window creation (_creatingCount > 0), queues behind serialize
|
|
629
|
-
* to avoid racing with the creation sequence.
|
|
639
|
+
/** Capture pane content through the serialize queue.
|
|
640
|
+
* #824: Always serialize to prevent race conditions with concurrent reads
|
|
641
|
+
* from monitor polls and ! command mode. The previous _creatingCount guard
|
|
642
|
+
* only queued during window creation, leaving a race window at other times.
|
|
630
643
|
*/
|
|
631
644
|
async capturePaneDirect(windowId) {
|
|
632
|
-
|
|
633
|
-
return this.serialize(() => this.capturePaneDirectInternal(windowId));
|
|
634
|
-
}
|
|
635
|
-
return this.capturePaneDirectInternal(windowId);
|
|
645
|
+
return this.serialize(() => this.capturePaneDirectInternal(windowId));
|
|
636
646
|
}
|
|
637
647
|
async capturePaneDirectInternal(windowId) {
|
|
638
648
|
const target = `${this.sessionName}:${windowId}`;
|
|
@@ -647,6 +657,11 @@ export class TmuxManager {
|
|
|
647
657
|
if (e && typeof e === 'object' && 'killed' in e && e.killed) {
|
|
648
658
|
throw new TmuxTimeoutError(['capture-pane', '-t', target, '-p'], TMUX_DEFAULT_TIMEOUT_MS);
|
|
649
659
|
}
|
|
660
|
+
// Issue #845: Handle tmux server crash (ECONNREFUSED) gracefully.
|
|
661
|
+
// Return empty string instead of crashing the request handler.
|
|
662
|
+
if (e && typeof e === 'object' && 'code' in e && e.code === 'ECONNREFUSED') {
|
|
663
|
+
return '';
|
|
664
|
+
}
|
|
650
665
|
throw e;
|
|
651
666
|
}
|
|
652
667
|
}
|
package/dist/transcript.js
CHANGED
|
@@ -12,15 +12,20 @@ import { readdir } from 'node:fs/promises';
|
|
|
12
12
|
import { sessionsIndexSchema } from './validation.js';
|
|
13
13
|
/** Default Claude projects directory */
|
|
14
14
|
const DEFAULT_CLAUDE_PROJECTS_DIR = join(homedir(), '.claude', 'projects');
|
|
15
|
-
/** Parse a single JSONL line. Returns null if not parseable.
|
|
15
|
+
/** Parse a single JSONL line. Returns null if not parseable.
|
|
16
|
+
* Issue #823: Logs at error level when a non-empty line is dropped. */
|
|
16
17
|
function parseLine(line) {
|
|
17
18
|
const trimmed = line.trim();
|
|
18
|
-
if (!trimmed || trimmed[0] !== '{')
|
|
19
|
+
if (!trimmed || trimmed[0] !== '{') {
|
|
20
|
+
// Lines that are blank or don't start with '{' are expected (separators, comments)
|
|
19
21
|
return null;
|
|
22
|
+
}
|
|
20
23
|
try {
|
|
21
24
|
return JSON.parse(trimmed);
|
|
22
25
|
}
|
|
23
|
-
catch {
|
|
26
|
+
catch (err) {
|
|
27
|
+
// Issue #823: Log malformed JSON lines so data loss is visible
|
|
28
|
+
console.error(`parseLine: dropping malformed JSONL line (${err.message}): ${trimmed.slice(0, 200)}`);
|
|
24
29
|
return null;
|
|
25
30
|
}
|
|
26
31
|
}
|
|
@@ -184,11 +189,10 @@ export async function readNewEntries(filePath, fromOffset) {
|
|
|
184
189
|
break;
|
|
185
190
|
}
|
|
186
191
|
}
|
|
187
|
-
// Issue #
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
+
// Issue #836: If no newline found in the scan window, the line is
|
|
193
|
+
// longer than scanSize. Keep effectiveOffset as-is (fromOffset) —
|
|
194
|
+
// starting mid-line is handled by JSON.parse rejecting partial lines.
|
|
195
|
+
// Never fall back to offset 0, which causes O(n) re-reads.
|
|
192
196
|
}
|
|
193
197
|
const slicedContent = await new Promise((resolve, reject) => {
|
|
194
198
|
const chunks = [];
|
package/dist/validation.d.ts
CHANGED
|
@@ -128,10 +128,14 @@ export declare const persistedStateSchema: z.ZodRecord<z.ZodString, z.ZodObject<
|
|
|
128
128
|
byteOffset: z.ZodNumber;
|
|
129
129
|
monitorOffset: z.ZodNumber;
|
|
130
130
|
status: z.ZodEnum<{
|
|
131
|
+
error: "error";
|
|
131
132
|
unknown: "unknown";
|
|
132
133
|
permission_prompt: "permission_prompt";
|
|
133
134
|
idle: "idle";
|
|
134
135
|
working: "working";
|
|
136
|
+
compacting: "compacting";
|
|
137
|
+
context_warning: "context_warning";
|
|
138
|
+
waiting_for_input: "waiting_for_input";
|
|
135
139
|
bash_approval: "bash_approval";
|
|
136
140
|
plan_mode: "plan_mode";
|
|
137
141
|
ask_question: "ask_question";
|
package/dist/validation.js
CHANGED
|
@@ -121,8 +121,9 @@ export function isValidUUID(id) {
|
|
|
121
121
|
}
|
|
122
122
|
// ── JSON.parse boundary validation (Issue #410) ──────────────────
|
|
123
123
|
const UIStateEnum = z.enum([
|
|
124
|
-
'idle', 'working', '
|
|
125
|
-
'
|
|
124
|
+
'idle', 'working', 'compacting', 'context_warning', 'waiting_for_input',
|
|
125
|
+
'permission_prompt', 'bash_approval', 'plan_mode', 'ask_question',
|
|
126
|
+
'settings', 'error', 'unknown',
|
|
126
127
|
]);
|
|
127
128
|
/** Schema for persisted SessionState (sessions: { [id]: SessionInfo }). */
|
|
128
129
|
export const persistedStateSchema = z.record(z.string(), z.object({
|