aegis-bridge 2.3.5 → 2.3.7
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 +32 -3
- package/dist/auth.js +2 -1
- package/dist/hooks.js +5 -0
- package/dist/server.js +8 -5
- package/dist/session.js +3 -2
- package/dist/ws-terminal.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -103,7 +103,7 @@ All endpoints under `/v1/`.
|
|
|
103
103
|
| Method | Endpoint | Description |
|
|
104
104
|
|--------|----------|-------------|
|
|
105
105
|
| `GET` | `/v1/health` | Server health & uptime |
|
|
106
|
-
| `POST` | `/v1/sessions` | Create a session |
|
|
106
|
+
| `POST` | `/v1/sessions` | Create (or reuse) a session |
|
|
107
107
|
| `GET` | `/v1/sessions` | List sessions |
|
|
108
108
|
| `GET` | `/v1/sessions/:id` | Session details |
|
|
109
109
|
| `GET` | `/v1/sessions/:id/read` | Parsed transcript |
|
|
@@ -144,9 +144,38 @@ All endpoints under `/v1/`.
|
|
|
144
144
|
|
|
145
145
|
</details>
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
<details>
|
|
148
|
+
<summary>Session Reuse</summary>
|
|
149
|
+
|
|
150
|
+
When you `POST /v1/sessions` (or `POST /sessions`) with a `workDir` that already has an **idle** session, Aegis reuses that session instead of creating a duplicate. The existing session's prompt is delivered and you get the same session object back.
|
|
151
|
+
|
|
152
|
+
**Response differences:**
|
|
153
|
+
|
|
154
|
+
| | New Session | Reused Session |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| Status | `201 Created` | `200 OK` |
|
|
157
|
+
| `reused` | `false` | `true` |
|
|
158
|
+
| `promptDelivery` | `{ delivered, attempts }` | `{ delivered, attempts }` |
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
# First call → creates session (201)
|
|
162
|
+
curl -s -o /dev/null -w "%{http_code}" -X POST http://localhost:9100/v1/sessions \
|
|
163
|
+
-H "Content-Type: application/json" \
|
|
164
|
+
-d '{"workDir": "/home/user/project", "prompt": "Fix the tests"}'
|
|
165
|
+
# → 201
|
|
148
166
|
|
|
149
|
-
|
|
167
|
+
# Same workDir while idle → reuses session (200)
|
|
168
|
+
curl -s -o /dev/null -w "%{http_code}" -X POST http://localhost:9100/v1/sessions \
|
|
169
|
+
-H "Content-Type: application/json" \
|
|
170
|
+
-d '{"workDir": "/home/user/project", "prompt": "Now add error handling"}'
|
|
171
|
+
# → 200, body includes "reused": true
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Only **idle** sessions are reused. Working, stalled, or permission-prompt sessions are ignored — a new one is created.
|
|
175
|
+
|
|
176
|
+
</details>
|
|
177
|
+
|
|
178
|
+
---
|
|
150
179
|
|
|
151
180
|
### Telegram
|
|
152
181
|
|
package/dist/auth.js
CHANGED
|
@@ -155,8 +155,9 @@ export class AuthManager {
|
|
|
155
155
|
const previous = this.sseMutex;
|
|
156
156
|
this.sseMutex = lock;
|
|
157
157
|
// #509: await + try/finally together so release() fires even if previous rejects
|
|
158
|
+
// #573: catch prior rejection so it doesn't propagate and block subsequent callers
|
|
158
159
|
try {
|
|
159
|
-
await previous;
|
|
160
|
+
await previous.catch(() => { });
|
|
160
161
|
// Cleanup expired tokens first
|
|
161
162
|
this.cleanExpiredSSETokens();
|
|
162
163
|
// Enforce per-key limit
|
package/dist/hooks.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* Issue #169: Phase 1 — HTTP hooks infrastructure.
|
|
15
15
|
* Issue #169: Phase 3 — Hook-driven status detection.
|
|
16
16
|
*/
|
|
17
|
+
import { isValidUUID } from './validation.js';
|
|
17
18
|
/** CC hook events that require a decision response. */
|
|
18
19
|
const DECISION_EVENTS = new Set(['PreToolUse', 'PermissionRequest']);
|
|
19
20
|
/** Permission modes that should be auto-approved via hook response. */
|
|
@@ -105,6 +106,10 @@ export function registerHookRoutes(app, deps) {
|
|
|
105
106
|
if (!sessionId) {
|
|
106
107
|
return reply.status(400).send({ error: 'Missing session ID — provide X-Session-Id header or sessionId query param' });
|
|
107
108
|
}
|
|
109
|
+
// Issue #580: Reject non-UUID session IDs before getSession lookup.
|
|
110
|
+
if (!isValidUUID(sessionId)) {
|
|
111
|
+
return reply.status(400).send({ error: 'Invalid session ID — must be a UUID' });
|
|
112
|
+
}
|
|
108
113
|
const session = deps.sessions.getSession(sessionId);
|
|
109
114
|
if (!session) {
|
|
110
115
|
return reply.status(404).send({ error: `Session ${sessionId} not found` });
|
package/dist/server.js
CHANGED
|
@@ -34,7 +34,7 @@ import { MetricsCollector } from './metrics.js';
|
|
|
34
34
|
import { registerHookRoutes } from './hooks.js';
|
|
35
35
|
import { registerWsTerminalRoute } from './ws-terminal.js';
|
|
36
36
|
import { SwarmMonitor } from './swarm-monitor.js';
|
|
37
|
-
import {
|
|
37
|
+
import { execFileSync } from 'node:child_process';
|
|
38
38
|
import { authKeySchema, sendMessageSchema, commandSchema, bashSchema, screenshotSchema, permissionHookSchema, stopHookSchema, batchSessionSchema, pipelineSchema, parseIntSafe, isValidUUID, } from './validation.js';
|
|
39
39
|
const __filename = fileURLToPath(import.meta.url);
|
|
40
40
|
const __dirname = path.dirname(__filename);
|
|
@@ -155,11 +155,15 @@ function setupAuth(authManager) {
|
|
|
155
155
|
return;
|
|
156
156
|
// Hook routes — exact match: /v1/hooks/{eventName} (alpha only, no path traversal)
|
|
157
157
|
// Issue #394: Require valid X-Session-Id for known sessions instead of blanket bypass.
|
|
158
|
+
// Issue #580: Validate UUID format before getSession lookup.
|
|
158
159
|
// CC hooks run from localhost and always include the session ID they were started with.
|
|
159
160
|
const hookMatch = /^\/v1\/hooks\/[A-Za-z]+$/.exec(urlPath);
|
|
160
161
|
if (hookMatch) {
|
|
161
162
|
const hookSessionId = req.headers['x-session-id']
|
|
162
163
|
|| req.query?.sessionId;
|
|
164
|
+
if (hookSessionId && !isValidUUID(hookSessionId)) {
|
|
165
|
+
return reply.status(400).send({ error: 'Invalid session ID — must be a UUID' });
|
|
166
|
+
}
|
|
163
167
|
if (hookSessionId && sessions.getSession(hookSessionId)) {
|
|
164
168
|
return; // valid session — allow
|
|
165
169
|
}
|
|
@@ -437,11 +441,10 @@ app.get('/v1/sessions', async (req) => {
|
|
|
437
441
|
const total = all.length;
|
|
438
442
|
const start = (page - 1) * limit;
|
|
439
443
|
const items = all.slice(start, start + limit);
|
|
444
|
+
const totalPages = Math.ceil(total / limit);
|
|
440
445
|
return {
|
|
441
446
|
sessions: items,
|
|
442
|
-
total,
|
|
443
|
-
page,
|
|
444
|
-
limit,
|
|
447
|
+
pagination: { page, limit, total, totalPages },
|
|
445
448
|
};
|
|
446
449
|
});
|
|
447
450
|
// Backwards compat: /sessions (no prefix) returns raw array
|
|
@@ -1344,7 +1347,7 @@ async function killStalePortHolder(port) {
|
|
|
1344
1347
|
// Small random delay to reduce race window with systemd restarts
|
|
1345
1348
|
await new Promise(resolve => setTimeout(resolve, 100 + Math.random() * 400));
|
|
1346
1349
|
try {
|
|
1347
|
-
const output =
|
|
1350
|
+
const output = execFileSync('lsof', ['-ti', `tcp:${port}`], { encoding: 'utf-8', timeout: 5_000 }).trim();
|
|
1348
1351
|
if (!output)
|
|
1349
1352
|
return false;
|
|
1350
1353
|
const pids = output.split('\n').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n));
|
package/dist/session.js
CHANGED
|
@@ -489,12 +489,13 @@ export class SessionManager {
|
|
|
489
489
|
await this.save();
|
|
490
490
|
// Issue #353: Fetch CC process PID for swarm parent matching.
|
|
491
491
|
// Fire-and-forget — PID is not needed synchronously.
|
|
492
|
+
// Issue #574: Add .catch() to prevent unhandled rejection if tmux fails mid-lookup.
|
|
492
493
|
void this.tmux.listPanePid(windowId).then(pid => {
|
|
493
494
|
if (pid !== null) {
|
|
494
495
|
session.ccPid = pid;
|
|
495
|
-
void this.save();
|
|
496
|
+
void this.save().catch(e => console.error(`Session: failed to save PID for ${id}:`, e));
|
|
496
497
|
}
|
|
497
|
-
});
|
|
498
|
+
}).catch(e => console.error(`Session: failed to list pane PID for ${id}:`, e));
|
|
498
499
|
// Start BOTH discovery methods in parallel:
|
|
499
500
|
// 1. Hook-based: fast, relies on SessionStart hook writing session_map.json
|
|
500
501
|
// 2. Filesystem-based: slower, scans for new .jsonl files — works when hooks fail
|
package/dist/ws-terminal.js
CHANGED
|
@@ -182,8 +182,8 @@ export function registerWsTerminalRoute(app, sessions, tmux, auth) {
|
|
|
182
182
|
await sessions.sendMessage(sessionId, msg.text);
|
|
183
183
|
}
|
|
184
184
|
else if (msg.type === 'resize') {
|
|
185
|
-
const cols = clamp(msg.cols ?? 80,
|
|
186
|
-
const rows = clamp(msg.rows ?? 24,
|
|
185
|
+
const cols = clamp(msg.cols ?? 80, 10, 500, 80);
|
|
186
|
+
const rows = clamp(msg.rows ?? 24, 5, 200, 24);
|
|
187
187
|
await tmux.resizePane(session.windowId, cols, rows);
|
|
188
188
|
}
|
|
189
189
|
}
|