@dev-anywhere/proxy 0.1.9 → 0.2.1
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/dist/{chunk-BMVYMCKF.js → chunk-3ZUZ22V6.js} +2 -2
- package/dist/{chunk-7XMJMVIL.js → chunk-4YQ2JUM7.js} +41 -6
- package/dist/chunk-4YQ2JUM7.js.map +1 -0
- package/dist/chunk-7UOPAMX7.js +220 -0
- package/dist/chunk-7UOPAMX7.js.map +1 -0
- package/dist/chunk-NBRBO5GS.js +1032 -0
- package/dist/chunk-NBRBO5GS.js.map +1 -0
- package/dist/chunk-NQDJ6QAM.js +18 -0
- package/dist/chunk-NQDJ6QAM.js.map +1 -0
- package/dist/chunk-OBYEKZWC.js +104 -0
- package/dist/chunk-OBYEKZWC.js.map +1 -0
- package/dist/chunk-PWG6K5QB.js +204 -0
- package/dist/chunk-PWG6K5QB.js.map +1 -0
- package/dist/{chunk-DCDXAM76.js → chunk-RIQ6OL7X.js} +9 -6
- package/dist/chunk-RIQ6OL7X.js.map +1 -0
- package/dist/{chunk-6O6JTF24.js → chunk-WUBRUO3G.js} +1 -1
- package/dist/index.js +5 -5
- package/dist/{relay-token-Z4JZFPQ5.js → relay-token-RKAVVQHE.js} +5 -4
- package/dist/{relay-token-Z4JZFPQ5.js.map → relay-token-RKAVVQHE.js.map} +1 -1
- package/dist/serve.js +538 -431
- package/dist/serve.js.map +1 -1
- package/dist/session-worker.js +99 -32
- package/dist/session-worker.js.map +1 -1
- package/dist/{terminal-FJAIRC73.js → terminal-YO2D2OJU.js} +194 -151
- package/dist/terminal-YO2D2OJU.js.map +1 -0
- package/package.json +3 -3
- package/dist/chunk-2JUB4LDU.js +0 -84
- package/dist/chunk-2JUB4LDU.js.map +0 -1
- package/dist/chunk-7XMJMVIL.js.map +0 -1
- package/dist/chunk-DCDXAM76.js.map +0 -1
- package/dist/chunk-ORZTFYXR.js +0 -123
- package/dist/chunk-ORZTFYXR.js.map +0 -1
- package/dist/chunk-QFYI6AMN.js +0 -870
- package/dist/chunk-QFYI6AMN.js.map +0 -1
- package/dist/chunk-U5T7ZYXT.js +0 -346
- package/dist/chunk-U5T7ZYXT.js.map +0 -1
- package/dist/terminal-FJAIRC73.js.map +0 -1
- /package/dist/{chunk-BMVYMCKF.js.map → chunk-3ZUZ22V6.js.map} +0 -0
- /package/dist/{chunk-6O6JTF24.js.map → chunk-WUBRUO3G.js.map} +0 -0
package/dist/serve.js
CHANGED
|
@@ -3,17 +3,15 @@ import {
|
|
|
3
3
|
ContentBlockDeltaSchema,
|
|
4
4
|
IGNORED_EVENT_TYPES,
|
|
5
5
|
KnownContentBlockSchema,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
StreamJsonEventSchema,
|
|
7
|
+
disposeSeqCounter,
|
|
8
|
+
getSeqCounterFor
|
|
9
|
+
} from "./chunk-4YQ2JUM7.js";
|
|
9
10
|
import {
|
|
10
|
-
|
|
11
|
-
defineFSM,
|
|
11
|
+
decidePtySemanticTransition,
|
|
12
12
|
extractOscSequences,
|
|
13
|
-
extractOscSignals
|
|
14
|
-
|
|
15
|
-
stateAfterApprovalRelease
|
|
16
|
-
} from "./chunk-ORZTFYXR.js";
|
|
13
|
+
extractOscSignals
|
|
14
|
+
} from "./chunk-OBYEKZWC.js";
|
|
17
15
|
import {
|
|
18
16
|
spawnScript
|
|
19
17
|
} from "./chunk-ZUWAB67J.js";
|
|
@@ -21,47 +19,56 @@ import {
|
|
|
21
19
|
CLAUDE_PROVIDER,
|
|
22
20
|
CODEX_PROVIDER,
|
|
23
21
|
detectAgentCliStatus
|
|
24
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-WUBRUO3G.js";
|
|
25
23
|
import {
|
|
24
|
+
ControlErrorCode,
|
|
25
|
+
MessageEnvelopeSchema,
|
|
26
|
+
RelayControlSchema,
|
|
27
|
+
SessionState,
|
|
28
|
+
buildMessage,
|
|
29
|
+
createFSM,
|
|
26
30
|
createIpcReader,
|
|
27
31
|
createWorkerReader,
|
|
32
|
+
defineFSM,
|
|
33
|
+
encodeBinaryFrame,
|
|
34
|
+
providerValues,
|
|
35
|
+
serializeControl,
|
|
28
36
|
serializeIpc,
|
|
29
37
|
serializeWorkerMsg
|
|
30
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-NBRBO5GS.js";
|
|
31
39
|
import {
|
|
32
40
|
buildProviderEnv,
|
|
33
41
|
loadConfig,
|
|
34
42
|
saveAgentCliPath
|
|
35
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-RIQ6OL7X.js";
|
|
44
|
+
import {
|
|
45
|
+
atomicWriteFileSync
|
|
46
|
+
} from "./chunk-NQDJ6QAM.js";
|
|
36
47
|
import {
|
|
48
|
+
flushLogger,
|
|
37
49
|
serviceLogger
|
|
38
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-7UOPAMX7.js";
|
|
39
51
|
import {
|
|
40
|
-
ControlErrorCode,
|
|
41
52
|
DATA_DIR,
|
|
42
53
|
DEFAULT_PROXY_PROFILE,
|
|
43
54
|
HOOK_REGISTRY_PATH,
|
|
44
|
-
MessageEnvelopeSchema,
|
|
45
55
|
PID_PATH,
|
|
46
56
|
PROFILE_NAME,
|
|
47
57
|
PROXY_ID_PATH,
|
|
48
58
|
SESSIONS_PATH,
|
|
49
59
|
SOCK_PATH,
|
|
50
60
|
STOPPED_PATH,
|
|
51
|
-
SessionState,
|
|
52
|
-
buildMessage,
|
|
53
61
|
ensureProfileWorkspace,
|
|
54
62
|
sessionPaths,
|
|
55
63
|
tildify
|
|
56
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-PWG6K5QB.js";
|
|
57
65
|
|
|
58
66
|
// src/serve.ts
|
|
59
67
|
import { createServer as createServer2 } from "net";
|
|
60
|
-
import { unlinkSync as
|
|
68
|
+
import { unlinkSync as unlinkSync4, writeFileSync as writeFileSync3, chmodSync, rmSync as rmSync2 } from "fs";
|
|
61
69
|
|
|
62
70
|
// src/serve/session-manager.ts
|
|
63
|
-
import {
|
|
64
|
-
import { dirname } from "path";
|
|
71
|
+
import { readFileSync, existsSync } from "fs";
|
|
65
72
|
import { nanoid } from "nanoid";
|
|
66
73
|
var PTY_TRANSITIONS = {
|
|
67
74
|
[SessionState.IDLE]: [
|
|
@@ -278,8 +285,6 @@ var SessionManager = class {
|
|
|
278
285
|
}
|
|
279
286
|
}
|
|
280
287
|
save() {
|
|
281
|
-
const dir = dirname(this.persistPath);
|
|
282
|
-
mkdirSync(dir, { recursive: true });
|
|
283
288
|
const persisted = Array.from(this.sessions.values()).map((s) => ({
|
|
284
289
|
id: s.id,
|
|
285
290
|
mode: s.mode,
|
|
@@ -292,9 +297,7 @@ var SessionManager = class {
|
|
|
292
297
|
...s.claudeSessionId !== void 0 ? { claudeSessionId: s.claudeSessionId } : {}
|
|
293
298
|
}));
|
|
294
299
|
const data = JSON.stringify(persisted, null, 2);
|
|
295
|
-
|
|
296
|
-
writeFileSync(tmpPath, data, "utf-8");
|
|
297
|
-
renameSync(tmpPath, this.persistPath);
|
|
300
|
+
atomicWriteFileSync(this.persistPath, data, { ensureDir: true });
|
|
298
301
|
}
|
|
299
302
|
load() {
|
|
300
303
|
if (!existsSync(this.persistPath)) {
|
|
@@ -305,22 +308,28 @@ var SessionManager = class {
|
|
|
305
308
|
try {
|
|
306
309
|
parsed = JSON.parse(raw);
|
|
307
310
|
} catch (err) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
+
serviceLogger.warn(
|
|
312
|
+
{ path: this.persistPath, error: String(err) },
|
|
313
|
+
"Session persistence file unparseable, starting with empty state"
|
|
314
|
+
);
|
|
315
|
+
return;
|
|
311
316
|
}
|
|
312
317
|
if (!Array.isArray(parsed)) {
|
|
313
|
-
|
|
314
|
-
|
|
318
|
+
serviceLogger.warn(
|
|
319
|
+
{ path: this.persistPath },
|
|
320
|
+
"Session persistence file has unexpected format (not array), starting with empty state"
|
|
315
321
|
);
|
|
322
|
+
return;
|
|
316
323
|
}
|
|
317
324
|
for (const item of parsed) {
|
|
318
325
|
if (item && typeof item === "object" && "state" in item) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
326
|
+
const sessionId = String(item.id);
|
|
327
|
+
serviceLogger.warn(
|
|
328
|
+
{ sessionId },
|
|
329
|
+
"Session persistence record has unexpected state field, skipping"
|
|
323
330
|
);
|
|
331
|
+
this.onSessionRemoved?.(sessionId);
|
|
332
|
+
continue;
|
|
324
333
|
}
|
|
325
334
|
const info = item;
|
|
326
335
|
if (!isProviderId(info.provider)) {
|
|
@@ -366,9 +375,9 @@ var SessionManager = class {
|
|
|
366
375
|
|
|
367
376
|
// src/serve/relay-connection.ts
|
|
368
377
|
import WebSocket from "ws";
|
|
369
|
-
import { readFileSync as readFileSync2,
|
|
378
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
370
379
|
import { homedir } from "os";
|
|
371
|
-
import {
|
|
380
|
+
import { join } from "path";
|
|
372
381
|
import { nanoid as nanoid2 } from "nanoid";
|
|
373
382
|
import { EventEmitter } from "events";
|
|
374
383
|
|
|
@@ -400,6 +409,7 @@ var DEFAULT_PROXY_ID_PATH = join(homedir(), ".dev-anywhere", "proxy-id");
|
|
|
400
409
|
var MAX_BACKOFF_MS = 3e4;
|
|
401
410
|
var BASE_BACKOFF_MS = 1e3;
|
|
402
411
|
var MAX_QUEUE_SIZE = 1e4;
|
|
412
|
+
var MAX_JSON_MESSAGE_SIZE = 1 * 1024 * 1024;
|
|
403
413
|
var RelayConnectionState = {
|
|
404
414
|
DISCONNECTED: "disconnected",
|
|
405
415
|
CONNECTING: "connecting",
|
|
@@ -467,11 +477,7 @@ var RelayConnection = class extends EventEmitter {
|
|
|
467
477
|
}
|
|
468
478
|
}
|
|
469
479
|
const id = nanoid2(21);
|
|
470
|
-
|
|
471
|
-
if (!existsSync2(dir)) {
|
|
472
|
-
mkdirSync2(dir, { recursive: true });
|
|
473
|
-
}
|
|
474
|
-
writeFileSync2(idPath, id, "utf-8");
|
|
480
|
+
atomicWriteFileSync(idPath, id, { ensureDir: true });
|
|
475
481
|
return id;
|
|
476
482
|
}
|
|
477
483
|
// 连接到 relay server
|
|
@@ -492,7 +498,7 @@ var RelayConnection = class extends EventEmitter {
|
|
|
492
498
|
"Connected to relay server"
|
|
493
499
|
);
|
|
494
500
|
this.ws.send(
|
|
495
|
-
|
|
501
|
+
serializeControl({
|
|
496
502
|
type: "proxy_register",
|
|
497
503
|
proxyId: this.proxyId,
|
|
498
504
|
...this.name ? { name: this.name } : {}
|
|
@@ -500,7 +506,15 @@ var RelayConnection = class extends EventEmitter {
|
|
|
500
506
|
);
|
|
501
507
|
});
|
|
502
508
|
this.ws.on("message", (data) => {
|
|
503
|
-
const
|
|
509
|
+
const buf = data;
|
|
510
|
+
if (buf.length > MAX_JSON_MESSAGE_SIZE) {
|
|
511
|
+
serviceLogger.warn(
|
|
512
|
+
{ size: buf.length },
|
|
513
|
+
"JSON message from relay rejected: exceeds max size"
|
|
514
|
+
);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const raw = buf.toString();
|
|
504
518
|
let msg;
|
|
505
519
|
try {
|
|
506
520
|
msg = JSON.parse(raw);
|
|
@@ -518,15 +532,16 @@ var RelayConnection = class extends EventEmitter {
|
|
|
518
532
|
}
|
|
519
533
|
this.emit("message", msg);
|
|
520
534
|
});
|
|
521
|
-
this.ws.on("close", () => {
|
|
535
|
+
this.ws.on("close", (code, reason) => {
|
|
522
536
|
this.ws = null;
|
|
537
|
+
const closeMeta = { code, reason: reason.toString() || void 0 };
|
|
523
538
|
if (this.fsm.current() !== RelayConnectionState.CLOSED) {
|
|
524
539
|
this.fsm.tryTransitionTo(RelayConnectionState.WAITING_RECONNECT);
|
|
525
|
-
serviceLogger.info("Relay connection closed unexpectedly");
|
|
540
|
+
serviceLogger.info(closeMeta, "Relay connection closed unexpectedly");
|
|
526
541
|
this.emit("disconnected");
|
|
527
542
|
this.scheduleReconnect();
|
|
528
543
|
} else {
|
|
529
|
-
serviceLogger.info("Relay connection closed");
|
|
544
|
+
serviceLogger.info(closeMeta, "Relay connection closed");
|
|
530
545
|
}
|
|
531
546
|
});
|
|
532
547
|
this.ws.on("error", (err) => {
|
|
@@ -565,6 +580,8 @@ var RelayConnection = class extends EventEmitter {
|
|
|
565
580
|
this.sendRaw(raw);
|
|
566
581
|
}
|
|
567
582
|
// 发送 binary PTY 帧到 relay,断线时直接丢弃不入队
|
|
583
|
+
// 接受 Uint8Array 而非强制 Buffer:encodeBinaryFrame 在 shared 包返回 Uint8Array,
|
|
584
|
+
// ws.send 在底层同样支持 Uint8Array,无需额外 Buffer.from 拷贝。
|
|
568
585
|
sendBinary(data) {
|
|
569
586
|
if (this.fsm.current() === RelayConnectionState.SYNCED && this.ws?.readyState === WebSocket.OPEN) {
|
|
570
587
|
this.ws.send(data);
|
|
@@ -599,7 +616,7 @@ var RelayConnection = class extends EventEmitter {
|
|
|
599
616
|
}
|
|
600
617
|
if (this.ws) {
|
|
601
618
|
if (this.ws.readyState === WebSocket.OPEN) {
|
|
602
|
-
this.ws.send(
|
|
619
|
+
this.ws.send(serializeControl({ type: "proxy_disconnect", proxyId: this.proxyId }));
|
|
603
620
|
}
|
|
604
621
|
this.ws.close();
|
|
605
622
|
this.ws = null;
|
|
@@ -744,7 +761,9 @@ function decodeHistoryCursor(cursor, fileSize) {
|
|
|
744
761
|
if (!Number.isInteger(parsed) || parsed < 0) return fileSize;
|
|
745
762
|
return Math.min(parsed, fileSize);
|
|
746
763
|
}
|
|
764
|
+
var SAFE_SESSION_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
747
765
|
async function findClaudeSessionFile(claudeSessionId) {
|
|
766
|
+
if (!SAFE_SESSION_ID_PATTERN.test(claudeSessionId)) return null;
|
|
748
767
|
let projectDirs;
|
|
749
768
|
try {
|
|
750
769
|
projectDirs = await readdir(claudeProjectsDir());
|
|
@@ -1270,7 +1289,7 @@ function createControlMessageHandlers(send, sessionManager) {
|
|
|
1270
1289
|
try {
|
|
1271
1290
|
const commands = await discoverCommands(workDir);
|
|
1272
1291
|
send(
|
|
1273
|
-
|
|
1292
|
+
serializeControl({
|
|
1274
1293
|
type: "command_list_push",
|
|
1275
1294
|
commands
|
|
1276
1295
|
})
|
|
@@ -1285,7 +1304,7 @@ function createControlMessageHandlers(send, sessionManager) {
|
|
|
1285
1304
|
async handleDirListRequest(msg) {
|
|
1286
1305
|
if (!isPathSafe(msg.path)) {
|
|
1287
1306
|
send(
|
|
1288
|
-
|
|
1307
|
+
serializeControl({
|
|
1289
1308
|
type: "dir_list_response",
|
|
1290
1309
|
requestId: msg.requestId,
|
|
1291
1310
|
path: msg.path,
|
|
@@ -1300,7 +1319,7 @@ function createControlMessageHandlers(send, sessionManager) {
|
|
|
1300
1319
|
try {
|
|
1301
1320
|
const entries = await scanDir(msg.path);
|
|
1302
1321
|
send(
|
|
1303
|
-
|
|
1322
|
+
serializeControl({
|
|
1304
1323
|
type: "dir_list_response",
|
|
1305
1324
|
requestId: msg.requestId,
|
|
1306
1325
|
path: msg.path,
|
|
@@ -1310,7 +1329,7 @@ function createControlMessageHandlers(send, sessionManager) {
|
|
|
1310
1329
|
serviceLogger.debug({ path: msg.path, count: entries.length }, "Dir list response sent");
|
|
1311
1330
|
} catch (err) {
|
|
1312
1331
|
send(
|
|
1313
|
-
|
|
1332
|
+
serializeControl({
|
|
1314
1333
|
type: "dir_list_response",
|
|
1315
1334
|
requestId: msg.requestId,
|
|
1316
1335
|
path: msg.path,
|
|
@@ -1325,7 +1344,7 @@ function createControlMessageHandlers(send, sessionManager) {
|
|
|
1325
1344
|
async handleDirCreateRequest(msg) {
|
|
1326
1345
|
if (!isPathSafe(msg.path)) {
|
|
1327
1346
|
send(
|
|
1328
|
-
|
|
1347
|
+
serializeControl({
|
|
1329
1348
|
type: "dir_create_response",
|
|
1330
1349
|
requestId: msg.requestId,
|
|
1331
1350
|
path: msg.path,
|
|
@@ -1340,7 +1359,7 @@ function createControlMessageHandlers(send, sessionManager) {
|
|
|
1340
1359
|
try {
|
|
1341
1360
|
await mkdir(msg.path, { recursive: true });
|
|
1342
1361
|
send(
|
|
1343
|
-
|
|
1362
|
+
serializeControl({
|
|
1344
1363
|
type: "dir_create_response",
|
|
1345
1364
|
requestId: msg.requestId,
|
|
1346
1365
|
path: msg.path,
|
|
@@ -1350,7 +1369,7 @@ function createControlMessageHandlers(send, sessionManager) {
|
|
|
1350
1369
|
serviceLogger.info({ path: msg.path }, "Directory created");
|
|
1351
1370
|
} catch (err) {
|
|
1352
1371
|
send(
|
|
1353
|
-
|
|
1372
|
+
serializeControl({
|
|
1354
1373
|
type: "dir_create_response",
|
|
1355
1374
|
requestId: msg.requestId,
|
|
1356
1375
|
path: msg.path,
|
|
@@ -1366,7 +1385,7 @@ function createControlMessageHandlers(send, sessionManager) {
|
|
|
1366
1385
|
try {
|
|
1367
1386
|
const sessions = await scanSessionHistory();
|
|
1368
1387
|
send(
|
|
1369
|
-
|
|
1388
|
+
serializeControl({
|
|
1370
1389
|
type: "session_history_response",
|
|
1371
1390
|
requestId: msg.requestId,
|
|
1372
1391
|
sessions
|
|
@@ -1375,7 +1394,7 @@ function createControlMessageHandlers(send, sessionManager) {
|
|
|
1375
1394
|
serviceLogger.debug({ count: sessions.length }, "Session history response sent");
|
|
1376
1395
|
} catch (err) {
|
|
1377
1396
|
send(
|
|
1378
|
-
|
|
1397
|
+
serializeControl({
|
|
1379
1398
|
type: "session_history_response",
|
|
1380
1399
|
requestId: msg.requestId,
|
|
1381
1400
|
sessions: []
|
|
@@ -1395,7 +1414,7 @@ function createControlMessageHandlers(send, sessionManager) {
|
|
|
1395
1414
|
const groups = groupsResult.status === "fulfilled" ? groupsResult.value : [];
|
|
1396
1415
|
const failedReason = commandsResult.status === "rejected" ? commandsResult.reason : groupsResult.status === "rejected" ? groupsResult.reason : void 0;
|
|
1397
1416
|
send(
|
|
1398
|
-
|
|
1417
|
+
serializeControl({
|
|
1399
1418
|
type: "session_resources_response",
|
|
1400
1419
|
requestId: msg.requestId,
|
|
1401
1420
|
sessionId: msg.sessionId,
|
|
@@ -1416,7 +1435,7 @@ function createControlMessageHandlers(send, sessionManager) {
|
|
|
1416
1435
|
try {
|
|
1417
1436
|
const commands = await discoverCommands(workDir);
|
|
1418
1437
|
send(
|
|
1419
|
-
|
|
1438
|
+
serializeControl({
|
|
1420
1439
|
type: "command_list_push",
|
|
1421
1440
|
commands
|
|
1422
1441
|
})
|
|
@@ -1433,7 +1452,7 @@ function createControlMessageHandlers(send, sessionManager) {
|
|
|
1433
1452
|
try {
|
|
1434
1453
|
const groups = await getFileTree(workDir);
|
|
1435
1454
|
send(
|
|
1436
|
-
|
|
1455
|
+
serializeControl({
|
|
1437
1456
|
type: "file_tree_push",
|
|
1438
1457
|
groups
|
|
1439
1458
|
})
|
|
@@ -1451,7 +1470,7 @@ function createControlMessageHandlers(send, sessionManager) {
|
|
|
1451
1470
|
const activeSessions = sessionManager.listSessions().filter((s) => s.state !== "terminated");
|
|
1452
1471
|
if (activeSessions.length > 0) {
|
|
1453
1472
|
send(
|
|
1454
|
-
|
|
1473
|
+
serializeControl({
|
|
1455
1474
|
type: "session_sync",
|
|
1456
1475
|
sessions: activeSessions.map((s) => ({
|
|
1457
1476
|
id: s.id,
|
|
@@ -1471,14 +1490,14 @@ function createControlMessageHandlers(send, sessionManager) {
|
|
|
1471
1490
|
try {
|
|
1472
1491
|
const commands = await discoverCommands(workDir);
|
|
1473
1492
|
send(
|
|
1474
|
-
|
|
1493
|
+
serializeControl({
|
|
1475
1494
|
type: "command_list_push",
|
|
1476
1495
|
commands
|
|
1477
1496
|
})
|
|
1478
1497
|
);
|
|
1479
1498
|
const groups = await getFileTree(workDir);
|
|
1480
1499
|
send(
|
|
1481
|
-
|
|
1500
|
+
serializeControl({
|
|
1482
1501
|
type: "file_tree_push",
|
|
1483
1502
|
groups
|
|
1484
1503
|
})
|
|
@@ -1585,7 +1604,16 @@ var WorkerRegistry = class {
|
|
|
1585
1604
|
const sock = connect(sockPath);
|
|
1586
1605
|
sock.on("connect", () => {
|
|
1587
1606
|
this.sockets.set(sessionId, sock);
|
|
1588
|
-
createWorkerReader(
|
|
1607
|
+
createWorkerReader(
|
|
1608
|
+
sock,
|
|
1609
|
+
(msg) => this.handleWorkerMessage(sessionId, msg),
|
|
1610
|
+
(err, line) => {
|
|
1611
|
+
serviceLogger.warn(
|
|
1612
|
+
{ sessionId, err: err.message, lineLen: line.length },
|
|
1613
|
+
"Worker IPC message dropped (parse/schema error)"
|
|
1614
|
+
);
|
|
1615
|
+
}
|
|
1616
|
+
);
|
|
1589
1617
|
sock.on("close", () => this.onDisconnect(sessionId));
|
|
1590
1618
|
sock.on("error", () => this.onDisconnect(sessionId));
|
|
1591
1619
|
resolve3(sock);
|
|
@@ -1690,6 +1718,9 @@ var WorkerRegistry = class {
|
|
|
1690
1718
|
onDisconnect(sessionId) {
|
|
1691
1719
|
this.sockets.delete(sessionId);
|
|
1692
1720
|
this.deps.permissionBroker.cleanupSession(sessionId, "Worker disconnected");
|
|
1721
|
+
if (this.deps.sessionManager.getSession(sessionId)) {
|
|
1722
|
+
this.deps.jsonObserver.onChannelBroken(sessionId);
|
|
1723
|
+
}
|
|
1693
1724
|
}
|
|
1694
1725
|
// 对齐 Claude CLI stream-json 输出,按 type 分发:
|
|
1695
1726
|
// stream_event.content_block_delta → 增量 text/thinking envelope(仅 streamDelta 会话产生)
|
|
@@ -1802,7 +1833,7 @@ var WorkerRegistry = class {
|
|
|
1802
1833
|
if (ev.type === "result") {
|
|
1803
1834
|
const resultText = typeof ev.result === "string" ? ev.result : void 0;
|
|
1804
1835
|
relay.sendRaw(
|
|
1805
|
-
|
|
1836
|
+
serializeControl({
|
|
1806
1837
|
type: "turn_result",
|
|
1807
1838
|
sessionId,
|
|
1808
1839
|
success: ev.subtype === "success",
|
|
@@ -1820,7 +1851,7 @@ var WorkerRegistry = class {
|
|
|
1820
1851
|
);
|
|
1821
1852
|
this.deps.jsonObserver.onApprovalRequested(sessionId);
|
|
1822
1853
|
try {
|
|
1823
|
-
const approvalSeq = this.deps.nextSeq?.(sessionId) ??
|
|
1854
|
+
const approvalSeq = this.deps.nextSeq?.(sessionId) ?? getSeqCounterFor(sessionId).next();
|
|
1824
1855
|
const envelope = buildMessage(
|
|
1825
1856
|
"tool_use_request",
|
|
1826
1857
|
sessionId,
|
|
@@ -1887,6 +1918,7 @@ function terminateSessionByOwnership(deps, sessionId) {
|
|
|
1887
1918
|
});
|
|
1888
1919
|
deps.controlHandlers.cleanup(sessionId);
|
|
1889
1920
|
deps.agentStatusRegistry.delete(sessionId);
|
|
1921
|
+
deps.broadcastSessionList();
|
|
1890
1922
|
serviceLogger.info(
|
|
1891
1923
|
{ sessionId, success: result.success },
|
|
1892
1924
|
"Local terminal session detached from remote view"
|
|
@@ -1904,6 +1936,7 @@ function terminateSessionByOwnership(deps, sessionId) {
|
|
|
1904
1936
|
const result = deps.sessionManager.terminateSession(sessionId);
|
|
1905
1937
|
deps.controlHandlers.cleanup(sessionId);
|
|
1906
1938
|
deps.agentStatusRegistry.delete(sessionId);
|
|
1939
|
+
deps.broadcastSessionList();
|
|
1907
1940
|
serviceLogger.info({ sessionId, success: result.success }, "JSON worker session terminated");
|
|
1908
1941
|
return { success: result.success, action: "terminate_json_worker" };
|
|
1909
1942
|
}
|
|
@@ -1915,7 +1948,7 @@ function terminateSessionByOwnership(deps, sessionId) {
|
|
|
1915
1948
|
}
|
|
1916
1949
|
|
|
1917
1950
|
// src/serve/clipboard-image-upload.ts
|
|
1918
|
-
import { existsSync as existsSync4, mkdirSync
|
|
1951
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, statSync, writeFileSync } from "fs";
|
|
1919
1952
|
import { isAbsolute as isAbsolute2, join as join5, relative, resolve } from "path";
|
|
1920
1953
|
import { nanoid as nanoid3 } from "nanoid";
|
|
1921
1954
|
var MAX_CLIPBOARD_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
@@ -1971,7 +2004,7 @@ function ensureProjectClipboardIgnored(cwd) {
|
|
|
1971
2004
|
const alreadyIgnored = current.split(/\r?\n/).some((line) => normalizeGitignoreLine(line) === ".dev-anywhere");
|
|
1972
2005
|
if (alreadyIgnored) return;
|
|
1973
2006
|
const separator = current.length > 0 && !current.endsWith("\n") ? "\n" : "";
|
|
1974
|
-
|
|
2007
|
+
writeFileSync(gitignorePath, `${current}${separator}.dev-anywhere/
|
|
1975
2008
|
`);
|
|
1976
2009
|
} catch {
|
|
1977
2010
|
}
|
|
@@ -1984,11 +2017,15 @@ function trySaveProjectClipboardImage(options) {
|
|
|
1984
2017
|
const clipboardRoot = resolve(cwd, ".dev-anywhere", "clipboard");
|
|
1985
2018
|
const uploadDir = resolveChildDir(clipboardRoot, options.sessionId);
|
|
1986
2019
|
const path = join5(uploadDir, options.fileName);
|
|
1987
|
-
|
|
1988
|
-
|
|
2020
|
+
mkdirSync(uploadDir, { recursive: true });
|
|
2021
|
+
writeFileSync(path, options.buffer, { mode: 384 });
|
|
1989
2022
|
ensureProjectClipboardIgnored(cwd);
|
|
1990
2023
|
return { success: true, path: relative(cwd, path) };
|
|
1991
|
-
} catch {
|
|
2024
|
+
} catch (err) {
|
|
2025
|
+
serviceLogger.warn(
|
|
2026
|
+
{ sessionId: options.sessionId, cwd: options.cwd, error: String(err) },
|
|
2027
|
+
"Project clipboard image write failed; falling back to data dir"
|
|
2028
|
+
);
|
|
1992
2029
|
return null;
|
|
1993
2030
|
}
|
|
1994
2031
|
}
|
|
@@ -2017,8 +2054,8 @@ function saveClipboardImageUpload(request, options = {}) {
|
|
|
2017
2054
|
const dataDir = options.dataDir ?? DATA_DIR;
|
|
2018
2055
|
const uploadDir = resolveSessionClipboardDir(dataDir, request.sessionId);
|
|
2019
2056
|
const path = join5(uploadDir, fileName);
|
|
2020
|
-
|
|
2021
|
-
|
|
2057
|
+
mkdirSync(uploadDir, { recursive: true });
|
|
2058
|
+
writeFileSync(path, buffer, { mode: 384 });
|
|
2022
2059
|
return { success: true, path };
|
|
2023
2060
|
} catch (err) {
|
|
2024
2061
|
return {
|
|
@@ -2144,17 +2181,15 @@ var RelayInputHandlers = class {
|
|
|
2144
2181
|
}
|
|
2145
2182
|
deps;
|
|
2146
2183
|
onUserInput(msg) {
|
|
2147
|
-
const sessionId = msg
|
|
2184
|
+
const { sessionId } = msg;
|
|
2148
2185
|
if (!sessionId) return;
|
|
2149
2186
|
const session = this.deps.sessionManager.getSession(sessionId);
|
|
2150
2187
|
if (!session) {
|
|
2151
2188
|
serviceLogger.warn({ sessionId }, "Remote input dropped: session not found");
|
|
2152
2189
|
return;
|
|
2153
2190
|
}
|
|
2154
|
-
const
|
|
2155
|
-
const text = payload?.text ?? "";
|
|
2191
|
+
const text = msg.payload.text;
|
|
2156
2192
|
if (session.mode === "json") {
|
|
2157
|
-
this.deps.jsonObserver.onTurnStart(sessionId);
|
|
2158
2193
|
const sent = this.deps.workerRegistry.send(sessionId, {
|
|
2159
2194
|
type: "worker_input",
|
|
2160
2195
|
content: text
|
|
@@ -2163,18 +2198,16 @@ var RelayInputHandlers = class {
|
|
|
2163
2198
|
serviceLogger.warn({ sessionId }, "Remote input dropped: JSON worker socket not available");
|
|
2164
2199
|
return;
|
|
2165
2200
|
}
|
|
2166
|
-
|
|
2167
|
-
const
|
|
2168
|
-
const version = typeof msg.version === "string" ? msg.version : "1";
|
|
2169
|
-
const messageId = typeof payload?.messageId === "string" && payload.messageId.length > 0 ? payload.messageId : `${sessionId}-user-${timestamp}`;
|
|
2201
|
+
this.deps.jsonObserver.onTurnStart(sessionId);
|
|
2202
|
+
const messageId = msg.payload.messageId && msg.payload.messageId.length > 0 ? msg.payload.messageId : `${sessionId}-user-${msg.timestamp}`;
|
|
2170
2203
|
this.deps.relayConnection.sendEnvelope(
|
|
2171
2204
|
MessageEnvelopeSchema.parse({
|
|
2172
2205
|
type: "user_input",
|
|
2173
2206
|
sessionId,
|
|
2174
|
-
seq,
|
|
2175
|
-
timestamp,
|
|
2207
|
+
seq: msg.seq,
|
|
2208
|
+
timestamp: msg.timestamp,
|
|
2176
2209
|
source: "proxy",
|
|
2177
|
-
version,
|
|
2210
|
+
version: msg.version,
|
|
2178
2211
|
payload: { text, messageId }
|
|
2179
2212
|
})
|
|
2180
2213
|
);
|
|
@@ -2187,8 +2220,7 @@ var RelayInputHandlers = class {
|
|
|
2187
2220
|
);
|
|
2188
2221
|
}
|
|
2189
2222
|
onRemoteInputRaw(msg) {
|
|
2190
|
-
const sessionId = msg
|
|
2191
|
-
const data = msg.data;
|
|
2223
|
+
const { sessionId, data } = msg;
|
|
2192
2224
|
if (!sessionId || data === void 0) return;
|
|
2193
2225
|
const ts = this.deps.terminalSockets.get(sessionId);
|
|
2194
2226
|
if (!ts?.writable && this.deps.hostedPtyRegistry.write(sessionId, data)) {
|
|
@@ -2206,13 +2238,12 @@ var RelayInputHandlers = class {
|
|
|
2206
2238
|
serviceLogger.info({ sessionId, bytes: data.length }, "Raw PTY input forwarded");
|
|
2207
2239
|
}
|
|
2208
2240
|
onClipboardImageUpload(msg) {
|
|
2209
|
-
const sessionId = msg
|
|
2210
|
-
const requestId = msg.requestId;
|
|
2241
|
+
const { sessionId, requestId } = msg;
|
|
2211
2242
|
if (!sessionId) return;
|
|
2212
2243
|
const session = this.deps.sessionManager.getSession(sessionId);
|
|
2213
2244
|
if (!session) {
|
|
2214
2245
|
this.deps.relayConnection.sendRaw(
|
|
2215
|
-
|
|
2246
|
+
serializeControl({
|
|
2216
2247
|
type: "clipboard_image_upload_response",
|
|
2217
2248
|
requestId,
|
|
2218
2249
|
sessionId,
|
|
@@ -2228,16 +2259,16 @@ var RelayInputHandlers = class {
|
|
|
2228
2259
|
const result = saveClipboardImageUpload(
|
|
2229
2260
|
{
|
|
2230
2261
|
sessionId,
|
|
2231
|
-
mimeType:
|
|
2232
|
-
dataBase64:
|
|
2233
|
-
fileName:
|
|
2262
|
+
mimeType: msg.mimeType,
|
|
2263
|
+
dataBase64: msg.dataBase64,
|
|
2264
|
+
fileName: msg.fileName
|
|
2234
2265
|
},
|
|
2235
2266
|
{
|
|
2236
2267
|
cwd: session.cwd
|
|
2237
2268
|
}
|
|
2238
2269
|
);
|
|
2239
2270
|
this.deps.relayConnection.sendRaw(
|
|
2240
|
-
|
|
2271
|
+
serializeControl({
|
|
2241
2272
|
type: "clipboard_image_upload_response",
|
|
2242
2273
|
requestId,
|
|
2243
2274
|
sessionId,
|
|
@@ -2247,14 +2278,12 @@ var RelayInputHandlers = class {
|
|
|
2247
2278
|
serviceLogger.info({ sessionId, success: result.success }, "Clipboard image upload handled");
|
|
2248
2279
|
}
|
|
2249
2280
|
onImagePreviewRequest(msg) {
|
|
2250
|
-
const sessionId = msg
|
|
2251
|
-
const requestId = msg.requestId;
|
|
2252
|
-
const path = msg.path;
|
|
2281
|
+
const { sessionId, requestId, path } = msg;
|
|
2253
2282
|
if (!sessionId || !path) return;
|
|
2254
2283
|
const session = this.deps.sessionManager.getSession(sessionId);
|
|
2255
2284
|
if (!session) {
|
|
2256
2285
|
this.deps.relayConnection.sendRaw(
|
|
2257
|
-
|
|
2286
|
+
serializeControl({
|
|
2258
2287
|
type: "image_preview_response",
|
|
2259
2288
|
requestId,
|
|
2260
2289
|
sessionId,
|
|
@@ -2275,7 +2304,7 @@ var RelayInputHandlers = class {
|
|
|
2275
2304
|
}
|
|
2276
2305
|
);
|
|
2277
2306
|
this.deps.relayConnection.sendRaw(
|
|
2278
|
-
|
|
2307
|
+
serializeControl({
|
|
2279
2308
|
type: "image_preview_response",
|
|
2280
2309
|
requestId,
|
|
2281
2310
|
...result
|
|
@@ -2292,16 +2321,13 @@ var RelayHistoryHandlers = class {
|
|
|
2292
2321
|
}
|
|
2293
2322
|
deps;
|
|
2294
2323
|
onSessionMessagesRequest(msg) {
|
|
2295
|
-
const sid = msg
|
|
2324
|
+
const { sessionId: sid, requestId, before, limit } = msg;
|
|
2296
2325
|
if (!sid) return;
|
|
2297
|
-
const requestId = msg.requestId;
|
|
2298
|
-
const before = msg.before;
|
|
2299
|
-
const limit = msg.limit;
|
|
2300
2326
|
const session = this.deps.sessionManager.getSession(sid);
|
|
2301
2327
|
if (session?.claudeSessionId) {
|
|
2302
2328
|
readSessionMessagesPage(session.claudeSessionId, { before, limit }).then((page) => {
|
|
2303
2329
|
this.deps.relaySend(
|
|
2304
|
-
|
|
2330
|
+
serializeControl({
|
|
2305
2331
|
type: "session_history_messages",
|
|
2306
2332
|
requestId,
|
|
2307
2333
|
sessionId: sid,
|
|
@@ -2327,7 +2353,7 @@ var RelayHistoryHandlers = class {
|
|
|
2327
2353
|
"Failed to read session history page on request"
|
|
2328
2354
|
);
|
|
2329
2355
|
this.deps.relaySend(
|
|
2330
|
-
|
|
2356
|
+
serializeControl({
|
|
2331
2357
|
type: "session_history_messages",
|
|
2332
2358
|
requestId,
|
|
2333
2359
|
sessionId: sid,
|
|
@@ -2339,7 +2365,7 @@ var RelayHistoryHandlers = class {
|
|
|
2339
2365
|
});
|
|
2340
2366
|
} else {
|
|
2341
2367
|
this.deps.relaySend(
|
|
2342
|
-
|
|
2368
|
+
serializeControl({
|
|
2343
2369
|
type: "session_history_messages",
|
|
2344
2370
|
requestId,
|
|
2345
2371
|
sessionId: sid,
|
|
@@ -2355,7 +2381,7 @@ var RelayHistoryHandlers = class {
|
|
|
2355
2381
|
input: approval.input
|
|
2356
2382
|
}));
|
|
2357
2383
|
this.deps.relaySend(
|
|
2358
|
-
|
|
2384
|
+
serializeControl({ type: "pending_approvals_push", sessionId: sid, approvals })
|
|
2359
2385
|
);
|
|
2360
2386
|
serviceLogger.info({ sessionId: sid, count: approvals.length }, "Pending approvals pushed");
|
|
2361
2387
|
}
|
|
@@ -2368,8 +2394,7 @@ var RelayPermissionHandlers = class {
|
|
|
2368
2394
|
}
|
|
2369
2395
|
deps;
|
|
2370
2396
|
onToolApprove(msg) {
|
|
2371
|
-
const sessionId = msg
|
|
2372
|
-
const payload = msg.payload;
|
|
2397
|
+
const { sessionId, payload } = msg;
|
|
2373
2398
|
if (!sessionId || !payload?.toolId) return;
|
|
2374
2399
|
const pending = this.deps.permissionBroker.get(payload.toolId);
|
|
2375
2400
|
if (!pending) {
|
|
@@ -2421,8 +2446,7 @@ var RelayPermissionHandlers = class {
|
|
|
2421
2446
|
);
|
|
2422
2447
|
}
|
|
2423
2448
|
onToolDeny(msg) {
|
|
2424
|
-
const sessionId = msg
|
|
2425
|
-
const payload = msg.payload;
|
|
2449
|
+
const { sessionId, payload } = msg;
|
|
2426
2450
|
if (!sessionId || !payload?.toolId) return;
|
|
2427
2451
|
const reason = payload.reason ?? "Denied by remote user";
|
|
2428
2452
|
const pending = this.deps.permissionBroker.get(payload.toolId);
|
|
@@ -2460,15 +2484,14 @@ var RelayPermissionHandlers = class {
|
|
|
2460
2484
|
serviceLogger.info({ sessionId, toolId: payload.toolId }, "Tool denied via relay");
|
|
2461
2485
|
}
|
|
2462
2486
|
onPermissionRequestDelivered(msg) {
|
|
2463
|
-
const sid = msg
|
|
2464
|
-
const requestId = msg.requestId;
|
|
2487
|
+
const { sessionId: sid, requestId } = msg;
|
|
2465
2488
|
if (!sid || !requestId) return;
|
|
2466
2489
|
const marked = this.deps.permissionBroker.markDelivered(requestId);
|
|
2467
2490
|
serviceLogger.info({ sessionId: sid, requestId, marked }, "Permission request delivered");
|
|
2468
2491
|
}
|
|
2469
2492
|
pushPermissionDecisionResult(sessionId, requestId, outcome, delivered, message) {
|
|
2470
2493
|
this.deps.relaySend(
|
|
2471
|
-
|
|
2494
|
+
serializeControl({
|
|
2472
2495
|
type: "permission_decision_result",
|
|
2473
2496
|
sessionId,
|
|
2474
2497
|
requestId,
|
|
@@ -2501,7 +2524,7 @@ var RelayResourceHandlers = class {
|
|
|
2501
2524
|
deps;
|
|
2502
2525
|
onProxyInfoRequest(msg) {
|
|
2503
2526
|
this.deps.relaySend(
|
|
2504
|
-
|
|
2527
|
+
serializeControl({
|
|
2505
2528
|
type: "proxy_info",
|
|
2506
2529
|
requestId: msg.requestId,
|
|
2507
2530
|
homePath: homedir4() || "/",
|
|
@@ -2512,12 +2535,11 @@ var RelayResourceHandlers = class {
|
|
|
2512
2535
|
);
|
|
2513
2536
|
}
|
|
2514
2537
|
onAgentCliConfigUpdate(msg) {
|
|
2515
|
-
const requestId = msg
|
|
2516
|
-
const provider = msg.provider;
|
|
2538
|
+
const { requestId, provider } = msg;
|
|
2517
2539
|
const rawPath = msg.path;
|
|
2518
2540
|
if (provider !== "claude" && provider !== "codex") {
|
|
2519
2541
|
this.deps.relaySend(
|
|
2520
|
-
|
|
2542
|
+
serializeControl({
|
|
2521
2543
|
type: "agent_cli_config_update_response",
|
|
2522
2544
|
requestId,
|
|
2523
2545
|
provider: "claude",
|
|
@@ -2535,7 +2557,7 @@ var RelayResourceHandlers = class {
|
|
|
2535
2557
|
suggestions: this.deps.getAgentCliSuggestions()
|
|
2536
2558
|
});
|
|
2537
2559
|
this.deps.relaySend(
|
|
2538
|
-
|
|
2560
|
+
serializeControl({
|
|
2539
2561
|
type: "agent_cli_config_update_response",
|
|
2540
2562
|
requestId,
|
|
2541
2563
|
provider,
|
|
@@ -2546,7 +2568,7 @@ var RelayResourceHandlers = class {
|
|
|
2546
2568
|
} catch (err) {
|
|
2547
2569
|
const error = errorMessage(err);
|
|
2548
2570
|
this.deps.relaySend(
|
|
2549
|
-
|
|
2571
|
+
serializeControl({
|
|
2550
2572
|
type: "agent_cli_config_update_response",
|
|
2551
2573
|
requestId,
|
|
2552
2574
|
provider,
|
|
@@ -2576,7 +2598,7 @@ var RelayResourceHandlers = class {
|
|
|
2576
2598
|
if (!session?.cwd) {
|
|
2577
2599
|
serviceLogger.warn({ sessionId: sid }, "Session resources request: no cwd available");
|
|
2578
2600
|
this.deps.relaySend(
|
|
2579
|
-
|
|
2601
|
+
serializeControl({
|
|
2580
2602
|
type: "session_resources_response",
|
|
2581
2603
|
requestId: msg.requestId,
|
|
2582
2604
|
sessionId: sid,
|
|
@@ -2712,7 +2734,7 @@ var HostedPtyRegistry = class {
|
|
|
2712
2734
|
hosted.child.resize(cols, rows);
|
|
2713
2735
|
hosted.terminal.resize(cols, rows);
|
|
2714
2736
|
this.deps.relayConnection.sendRaw(
|
|
2715
|
-
|
|
2737
|
+
serializeControl({ type: "terminal_resize", sessionId, cols, rows })
|
|
2716
2738
|
);
|
|
2717
2739
|
serviceLogger.info({ sessionId, cols, rows }, "Hosted PTY resized");
|
|
2718
2740
|
return true;
|
|
@@ -2722,14 +2744,14 @@ var HostedPtyRegistry = class {
|
|
|
2722
2744
|
if (!hosted) return false;
|
|
2723
2745
|
const data = hosted.serializeAddon.serialize();
|
|
2724
2746
|
this.deps.relayConnection.sendRaw(
|
|
2725
|
-
|
|
2747
|
+
serializeControl({
|
|
2726
2748
|
type: "session_snapshot",
|
|
2727
2749
|
sessionId,
|
|
2728
2750
|
cols: hosted.terminal.cols,
|
|
2729
2751
|
rows: hosted.terminal.rows,
|
|
2730
2752
|
data,
|
|
2731
2753
|
outputSeq: hosted.outputSeq,
|
|
2732
|
-
requestId
|
|
2754
|
+
...requestId !== void 0 ? { requestId } : {}
|
|
2733
2755
|
})
|
|
2734
2756
|
);
|
|
2735
2757
|
serviceLogger.info(
|
|
@@ -2770,46 +2792,15 @@ var HostedPtyRegistry = class {
|
|
|
2770
2792
|
if (signal?.title) {
|
|
2771
2793
|
this.sendTerminalTitle(sessionId, signal.title);
|
|
2772
2794
|
}
|
|
2773
|
-
|
|
2774
|
-
hosted.currentState = "approval_wait";
|
|
2775
|
-
this.deps.changeSessionState(sessionId, SessionState.WAITING_APPROVAL);
|
|
2776
|
-
this.sendPtyState(sessionId, "approval_wait", { title: signal?.title, tool: signal?.tool });
|
|
2777
|
-
return;
|
|
2778
|
-
}
|
|
2779
|
-
if (shouldReleaseApprovalWait({
|
|
2795
|
+
const decision = decidePtySemanticTransition({
|
|
2780
2796
|
currentState: hosted.currentState,
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
} else {
|
|
2789
|
-
this.deps.changeSessionState(sessionId, SessionState.WORKING);
|
|
2790
|
-
}
|
|
2791
|
-
this.sendPtyState(sessionId, nextState, { title: signal?.title, tool: signal?.tool });
|
|
2792
|
-
return;
|
|
2793
|
-
}
|
|
2794
|
-
if ((session?.state === SessionState.WAITING_APPROVAL || hosted.currentState === "approval_wait") && signal?.state !== "turn_complete") {
|
|
2795
|
-
hosted.currentState = "approval_wait";
|
|
2796
|
-
this.sendPtyState(sessionId, "approval_wait", { title: signal?.title, tool: signal?.tool });
|
|
2797
|
-
return;
|
|
2798
|
-
}
|
|
2799
|
-
if (signal && signal.state !== "working") {
|
|
2800
|
-
hosted.currentState = signal.state;
|
|
2801
|
-
if (signal.state === "turn_complete") {
|
|
2802
|
-
this.deps.onTurnComplete(sessionId);
|
|
2803
|
-
this.deps.changeSessionState(sessionId, SessionState.IDLE);
|
|
2804
|
-
}
|
|
2805
|
-
this.sendPtyState(sessionId, signal.state, { title: signal.title, tool: signal.tool });
|
|
2806
|
-
return;
|
|
2807
|
-
}
|
|
2808
|
-
if (hosted.currentState !== "working") {
|
|
2809
|
-
hosted.currentState = "working";
|
|
2810
|
-
this.deps.changeSessionState(sessionId, SessionState.WORKING);
|
|
2811
|
-
this.sendPtyState(sessionId, "working");
|
|
2812
|
-
}
|
|
2797
|
+
signal: signal ?? null,
|
|
2798
|
+
sessionStateIsWaitingApproval: session?.state === SessionState.WAITING_APPROVAL
|
|
2799
|
+
});
|
|
2800
|
+
hosted.currentState = decision.nextState;
|
|
2801
|
+
if (!decision.emit) return;
|
|
2802
|
+
this.sendPtyState(sessionId, decision.nextState, decision.meta);
|
|
2803
|
+
this.deps.applyPtyStateToSession(sessionId, decision.nextState);
|
|
2813
2804
|
}
|
|
2814
2805
|
checkIdle(sessionId) {
|
|
2815
2806
|
const hosted = this.sessions.get(sessionId);
|
|
@@ -2820,9 +2811,8 @@ var HostedPtyRegistry = class {
|
|
|
2820
2811
|
hosted.lastOutputTime = 0;
|
|
2821
2812
|
if (hosted.currentState !== "working") return;
|
|
2822
2813
|
hosted.currentState = "turn_complete";
|
|
2823
|
-
this.deps.onTurnComplete(sessionId);
|
|
2824
|
-
this.deps.changeSessionState(sessionId, SessionState.IDLE);
|
|
2825
2814
|
this.sendPtyState(sessionId, "turn_complete");
|
|
2815
|
+
this.deps.applyPtyStateToSession(sessionId, "turn_complete");
|
|
2826
2816
|
}
|
|
2827
2817
|
sendPtyState(sessionId, state, meta) {
|
|
2828
2818
|
const payload = {
|
|
@@ -2831,7 +2821,7 @@ var HostedPtyRegistry = class {
|
|
|
2831
2821
|
...meta?.tool !== void 0 ? { tool: meta.tool } : {}
|
|
2832
2822
|
};
|
|
2833
2823
|
this.deps.relayConnection.sendRaw(
|
|
2834
|
-
|
|
2824
|
+
serializeControl({
|
|
2835
2825
|
type: "pty_state",
|
|
2836
2826
|
sessionId,
|
|
2837
2827
|
payload
|
|
@@ -2846,7 +2836,7 @@ var HostedPtyRegistry = class {
|
|
|
2846
2836
|
}
|
|
2847
2837
|
sendTerminalTitle(sessionId, title) {
|
|
2848
2838
|
this.deps.relayConnection.sendRaw(
|
|
2849
|
-
|
|
2839
|
+
serializeControl({
|
|
2850
2840
|
type: "terminal_title",
|
|
2851
2841
|
sessionId,
|
|
2852
2842
|
title
|
|
@@ -2854,13 +2844,7 @@ var HostedPtyRegistry = class {
|
|
|
2854
2844
|
);
|
|
2855
2845
|
}
|
|
2856
2846
|
sendBinary(sessionId, data, outputSeq) {
|
|
2857
|
-
|
|
2858
|
-
const frame = Buffer.alloc(1 + sessionIdBuf.length + 4 + data.length);
|
|
2859
|
-
frame[0] = sessionIdBuf.length;
|
|
2860
|
-
sessionIdBuf.copy(frame, 1);
|
|
2861
|
-
frame.writeUInt32LE(outputSeq, 1 + sessionIdBuf.length);
|
|
2862
|
-
data.copy(frame, 1 + sessionIdBuf.length + 4);
|
|
2863
|
-
this.deps.relayConnection.sendBinary(frame);
|
|
2847
|
+
this.deps.relayConnection.sendBinary(encodeBinaryFrame(sessionId, outputSeq, data));
|
|
2864
2848
|
}
|
|
2865
2849
|
close(sessionId, options) {
|
|
2866
2850
|
const hosted = this.sessions.get(sessionId);
|
|
@@ -2907,16 +2891,25 @@ var RelaySessionCreateHandler = class {
|
|
|
2907
2891
|
this.deps = deps;
|
|
2908
2892
|
}
|
|
2909
2893
|
deps;
|
|
2894
|
+
// 跟踪每个 pendingId 当前挂起的 retry timer。SIGTERM 抵达时 destroy() 会 clear
|
|
2895
|
+
// 这些 timer 并执行 cleanupPendingJsonSession,否则 worker 子进程在窗口期内可能成为孤儿
|
|
2896
|
+
// (setTimeout 回调命中时 workerRegistry 已经 destroyAll,但 worker 进程并未被 kill)。
|
|
2897
|
+
pendingTimers = /* @__PURE__ */ new Map();
|
|
2898
|
+
destroy() {
|
|
2899
|
+
for (const [pendingId, timer] of this.pendingTimers) {
|
|
2900
|
+
clearTimeout(timer);
|
|
2901
|
+
this.cleanupPendingJsonSession(pendingId);
|
|
2902
|
+
}
|
|
2903
|
+
this.pendingTimers.clear();
|
|
2904
|
+
}
|
|
2910
2905
|
onSessionCreate(msg) {
|
|
2911
|
-
const requestId = msg
|
|
2912
|
-
const cwd = msg.cwd;
|
|
2906
|
+
const { requestId, cwd } = msg;
|
|
2913
2907
|
const cwdError = validateSessionCwd(cwd);
|
|
2914
2908
|
if (cwdError) {
|
|
2915
2909
|
this.deps.relaySend(
|
|
2916
|
-
|
|
2910
|
+
serializeControl({
|
|
2917
2911
|
type: "session_create_response",
|
|
2918
2912
|
requestId,
|
|
2919
|
-
sessionId: "",
|
|
2920
2913
|
error: cwdError.message,
|
|
2921
2914
|
errorCode: cwdError.code
|
|
2922
2915
|
})
|
|
@@ -2934,10 +2927,9 @@ var RelaySessionCreateHandler = class {
|
|
|
2934
2927
|
}
|
|
2935
2928
|
if (provider !== "claude") {
|
|
2936
2929
|
this.deps.relaySend(
|
|
2937
|
-
|
|
2930
|
+
serializeControl({
|
|
2938
2931
|
type: "session_create_response",
|
|
2939
2932
|
requestId,
|
|
2940
|
-
sessionId: "",
|
|
2941
2933
|
errorCode: ControlErrorCode.PROVIDER_UNSUPPORTED,
|
|
2942
2934
|
error: provider === "codex" ? "Codex chat sessions are not supported yet; start a Codex terminal session instead." : "Unsupported provider for JSON session."
|
|
2943
2935
|
})
|
|
@@ -2946,7 +2938,7 @@ var RelaySessionCreateHandler = class {
|
|
|
2946
2938
|
return;
|
|
2947
2939
|
}
|
|
2948
2940
|
const resumeSessionId = msg.resumeSessionId;
|
|
2949
|
-
const streamDelta =
|
|
2941
|
+
const streamDelta = false;
|
|
2950
2942
|
const name = tildify(sessionCwd);
|
|
2951
2943
|
const pendingId = nanoid4();
|
|
2952
2944
|
const hook = this.deps.createHookContext(pendingId, provider);
|
|
@@ -2960,7 +2952,12 @@ var RelaySessionCreateHandler = class {
|
|
|
2960
2952
|
const paths = sessionPaths(pendingId);
|
|
2961
2953
|
let attempt = 0;
|
|
2962
2954
|
const maxRetries = 20;
|
|
2955
|
+
const scheduleAttempt = (delayMs) => {
|
|
2956
|
+
const timer = setTimeout(tryConnect, delayMs);
|
|
2957
|
+
this.pendingTimers.set(pendingId, timer);
|
|
2958
|
+
};
|
|
2963
2959
|
const tryConnect = () => {
|
|
2960
|
+
this.pendingTimers.delete(pendingId);
|
|
2964
2961
|
attempt++;
|
|
2965
2962
|
this.deps.workerRegistry.connect(pendingId, paths.workerSock).then((sock) => {
|
|
2966
2963
|
if (sock) {
|
|
@@ -2976,7 +2973,11 @@ var RelaySessionCreateHandler = class {
|
|
|
2976
2973
|
this.deps.sessionManager.setClaudeSessionId(session.id, resumeSessionId);
|
|
2977
2974
|
}
|
|
2978
2975
|
this.deps.relaySend(
|
|
2979
|
-
|
|
2976
|
+
serializeControl({
|
|
2977
|
+
type: "session_create_response",
|
|
2978
|
+
requestId,
|
|
2979
|
+
sessionId: session.id
|
|
2980
|
+
})
|
|
2980
2981
|
);
|
|
2981
2982
|
if (resumeSessionId) {
|
|
2982
2983
|
this.pushHistoryMessages(session.id, resumeSessionId);
|
|
@@ -2989,11 +2990,11 @@ var RelaySessionCreateHandler = class {
|
|
|
2989
2990
|
this.deps.broadcastSessionSync(session);
|
|
2990
2991
|
this.deps.broadcastSessionList();
|
|
2991
2992
|
} else if (attempt < maxRetries) {
|
|
2992
|
-
|
|
2993
|
+
scheduleAttempt(Math.min(100 * attempt, 2e3));
|
|
2993
2994
|
} else {
|
|
2994
2995
|
this.cleanupPendingJsonSession(pendingId);
|
|
2995
2996
|
this.deps.relaySend(
|
|
2996
|
-
|
|
2997
|
+
serializeControl({
|
|
2997
2998
|
type: "session_create_response",
|
|
2998
2999
|
requestId,
|
|
2999
3000
|
sessionId: pendingId,
|
|
@@ -3005,7 +3006,7 @@ var RelaySessionCreateHandler = class {
|
|
|
3005
3006
|
}
|
|
3006
3007
|
});
|
|
3007
3008
|
};
|
|
3008
|
-
|
|
3009
|
+
scheduleAttempt(100);
|
|
3009
3010
|
}
|
|
3010
3011
|
cleanupPendingJsonSession(sessionId) {
|
|
3011
3012
|
const killed = this.deps.workerRegistry.terminateProcess(sessionId);
|
|
@@ -3022,10 +3023,9 @@ var RelaySessionCreateHandler = class {
|
|
|
3022
3023
|
createHostedPtySession(msg, cwd, provider, permissionMode) {
|
|
3023
3024
|
if (provider !== "claude" && provider !== "codex") {
|
|
3024
3025
|
this.deps.relaySend(
|
|
3025
|
-
|
|
3026
|
+
serializeControl({
|
|
3026
3027
|
type: "session_create_response",
|
|
3027
3028
|
requestId: msg.requestId,
|
|
3028
|
-
sessionId: "",
|
|
3029
3029
|
errorCode: ControlErrorCode.PROVIDER_UNSUPPORTED,
|
|
3030
3030
|
error: "Unsupported provider for PTY session."
|
|
3031
3031
|
})
|
|
@@ -3058,7 +3058,7 @@ var RelaySessionCreateHandler = class {
|
|
|
3058
3058
|
this.deps.sessionManager.setClaudeSessionId(session.id, resumeSessionId);
|
|
3059
3059
|
}
|
|
3060
3060
|
this.deps.relaySend(
|
|
3061
|
-
|
|
3061
|
+
serializeControl({
|
|
3062
3062
|
type: "session_create_response",
|
|
3063
3063
|
requestId: msg.requestId,
|
|
3064
3064
|
sessionId: session.id,
|
|
@@ -3075,10 +3075,9 @@ var RelaySessionCreateHandler = class {
|
|
|
3075
3075
|
} catch (err) {
|
|
3076
3076
|
const error = err instanceof Error ? err.message : String(err);
|
|
3077
3077
|
this.deps.relaySend(
|
|
3078
|
-
|
|
3078
|
+
serializeControl({
|
|
3079
3079
|
type: "session_create_response",
|
|
3080
3080
|
requestId: msg.requestId,
|
|
3081
|
-
sessionId: "",
|
|
3082
3081
|
errorCode: ControlErrorCode.PROCESS_START_FAILED,
|
|
3083
3082
|
error
|
|
3084
3083
|
})
|
|
@@ -3101,7 +3100,7 @@ var RelaySessionCreateHandler = class {
|
|
|
3101
3100
|
readSessionMessagesPage(resumeSessionId).then((page) => {
|
|
3102
3101
|
if (page.messages.length === 0) return;
|
|
3103
3102
|
this.deps.relaySend(
|
|
3104
|
-
|
|
3103
|
+
serializeControl({
|
|
3105
3104
|
type: "session_history_messages",
|
|
3106
3105
|
sessionId,
|
|
3107
3106
|
messages: page.messages,
|
|
@@ -3180,56 +3179,120 @@ var RelayRouter = class {
|
|
|
3180
3179
|
permissionHandlers;
|
|
3181
3180
|
resourceHandlers;
|
|
3182
3181
|
sessionCreateHandler;
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3182
|
+
// shutdown 链路上提供单一 destroy 入口:把 sessionCreateHandler 内部 pending retry timer 清掉
|
|
3183
|
+
// 并 cleanup 已 spawn 但未 connect 的 worker 子进程,避免在 SIGTERM 之后变成孤儿。
|
|
3184
|
+
destroy() {
|
|
3185
|
+
this.sessionCreateHandler.destroy();
|
|
3186
|
+
}
|
|
3187
|
+
// 入站消息统一入口:proxy 收两类消息——relay control 与 envelope(user_input 这一种)。
|
|
3188
|
+
// 先按 envelope 试解析(discriminated union),失败再按 control 解析;各 handler 拿到
|
|
3189
|
+
// 强类型窄化后的消息,不再需要 `as string | undefined` / `as { ... }` 裸 cast。
|
|
3190
|
+
handle(rawMsg) {
|
|
3191
|
+
const asEnvelope = MessageEnvelopeSchema.safeParse(rawMsg);
|
|
3192
|
+
if (asEnvelope.success && asEnvelope.data.type === "user_input") {
|
|
3193
|
+
try {
|
|
3194
|
+
this.inputHandlers.onUserInput(asEnvelope.data);
|
|
3195
|
+
} catch (err) {
|
|
3196
|
+
serviceLogger.warn({ type: "user_input", error: String(err) }, "Relay handler threw");
|
|
3197
|
+
}
|
|
3187
3198
|
return;
|
|
3188
3199
|
}
|
|
3189
|
-
const
|
|
3190
|
-
if (!
|
|
3191
|
-
serviceLogger.warn(
|
|
3200
|
+
const asControl = RelayControlSchema.safeParse(rawMsg);
|
|
3201
|
+
if (!asControl.success) {
|
|
3202
|
+
serviceLogger.warn(
|
|
3203
|
+
{
|
|
3204
|
+
type: typeof rawMsg.type === "string" ? rawMsg.type : "<missing>",
|
|
3205
|
+
controlIssues: asControl.error.issues.slice(0, 3)
|
|
3206
|
+
},
|
|
3207
|
+
"Relay message rejected by both envelope and control schemas"
|
|
3208
|
+
);
|
|
3192
3209
|
return;
|
|
3193
3210
|
}
|
|
3211
|
+
const msg = asControl.data;
|
|
3194
3212
|
try {
|
|
3195
|
-
|
|
3213
|
+
this.dispatch(msg);
|
|
3196
3214
|
} catch (err) {
|
|
3197
|
-
serviceLogger.warn({ type, error: String(err) }, "Relay handler threw");
|
|
3198
|
-
}
|
|
3199
|
-
}
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3215
|
+
serviceLogger.warn({ type: msg.type, error: String(err) }, "Relay handler threw");
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
dispatch(msg) {
|
|
3219
|
+
switch (msg.type) {
|
|
3220
|
+
case "remote_input_raw":
|
|
3221
|
+
this.inputHandlers.onRemoteInputRaw(msg);
|
|
3222
|
+
return;
|
|
3223
|
+
case "clipboard_image_upload":
|
|
3224
|
+
this.inputHandlers.onClipboardImageUpload(msg);
|
|
3225
|
+
return;
|
|
3226
|
+
case "image_preview_request":
|
|
3227
|
+
this.inputHandlers.onImagePreviewRequest(msg);
|
|
3228
|
+
return;
|
|
3229
|
+
case "tool_approve":
|
|
3230
|
+
this.permissionHandlers.onToolApprove(msg);
|
|
3231
|
+
return;
|
|
3232
|
+
case "tool_deny":
|
|
3233
|
+
this.permissionHandlers.onToolDeny(msg);
|
|
3234
|
+
return;
|
|
3235
|
+
case "proxy_info_request":
|
|
3236
|
+
this.resourceHandlers.onProxyInfoRequest(msg);
|
|
3237
|
+
return;
|
|
3238
|
+
case "agent_cli_config_update":
|
|
3239
|
+
this.resourceHandlers.onAgentCliConfigUpdate(msg);
|
|
3240
|
+
return;
|
|
3241
|
+
case "dir_list_request":
|
|
3242
|
+
this.resourceHandlers.onDirListRequest(msg);
|
|
3243
|
+
return;
|
|
3244
|
+
case "dir_create_request":
|
|
3245
|
+
this.resourceHandlers.onDirCreateRequest(msg);
|
|
3246
|
+
return;
|
|
3247
|
+
case "session_create":
|
|
3248
|
+
this.sessionCreateHandler.onSessionCreate(msg);
|
|
3249
|
+
return;
|
|
3250
|
+
case "session_messages_request":
|
|
3251
|
+
this.historyHandlers.onSessionMessagesRequest(msg);
|
|
3252
|
+
return;
|
|
3253
|
+
case "session_resources_request":
|
|
3254
|
+
this.resourceHandlers.onSessionResourcesRequest(msg);
|
|
3255
|
+
return;
|
|
3256
|
+
case "agent_status_request":
|
|
3257
|
+
this.onAgentStatusRequest(msg);
|
|
3258
|
+
return;
|
|
3259
|
+
case "permission_request_delivered":
|
|
3260
|
+
this.permissionHandlers.onPermissionRequestDelivered(msg);
|
|
3261
|
+
return;
|
|
3262
|
+
case "session_terminate":
|
|
3263
|
+
this.onSessionTerminate(msg);
|
|
3264
|
+
return;
|
|
3265
|
+
case "session_worker_abort":
|
|
3266
|
+
this.onSessionWorkerAbort(msg);
|
|
3267
|
+
return;
|
|
3268
|
+
case "session_history_request":
|
|
3269
|
+
this.deps.controlHandlers.handleSessionHistoryRequest({ requestId: msg.requestId });
|
|
3270
|
+
return;
|
|
3271
|
+
case "session_list":
|
|
3272
|
+
this.onSessionList();
|
|
3273
|
+
return;
|
|
3274
|
+
case "permission_mode_change":
|
|
3275
|
+
this.onPermissionModeChange(msg);
|
|
3276
|
+
return;
|
|
3277
|
+
case "session_subscribe":
|
|
3278
|
+
this.onSessionSubscribe(msg);
|
|
3279
|
+
return;
|
|
3280
|
+
case "terminal_resize_request":
|
|
3281
|
+
this.onTerminalResizeRequest(msg);
|
|
3282
|
+
return;
|
|
3283
|
+
default:
|
|
3284
|
+
return;
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3226
3287
|
onAgentStatusRequest(msg) {
|
|
3227
3288
|
const sid = msg.sessionId;
|
|
3228
3289
|
const requestId = msg.requestId;
|
|
3229
3290
|
if (sid) {
|
|
3230
3291
|
const status = this.deps.agentStatusRegistry.get(sid);
|
|
3231
3292
|
const statuses2 = status && this.deps.sessionManager.getSession(sid) ? [{ sessionId: sid, payload: status }] : [];
|
|
3232
|
-
this.deps.relaySend(
|
|
3293
|
+
this.deps.relaySend(
|
|
3294
|
+
serializeControl({ type: "agent_status_response", requestId, statuses: statuses2 })
|
|
3295
|
+
);
|
|
3233
3296
|
serviceLogger.info({ sessionId: sid, count: statuses2.length }, "Agent status snapshot sent");
|
|
3234
3297
|
return;
|
|
3235
3298
|
}
|
|
@@ -3238,7 +3301,9 @@ var RelayRouter = class {
|
|
|
3238
3301
|
if (!this.deps.sessionManager.getSession(sessionId)) continue;
|
|
3239
3302
|
statuses.push({ sessionId, payload: status });
|
|
3240
3303
|
}
|
|
3241
|
-
this.deps.relaySend(
|
|
3304
|
+
this.deps.relaySend(
|
|
3305
|
+
serializeControl({ type: "agent_status_response", requestId, statuses })
|
|
3306
|
+
);
|
|
3242
3307
|
serviceLogger.info({ count: statuses.length }, "Agent status snapshot sent");
|
|
3243
3308
|
}
|
|
3244
3309
|
onSessionTerminate(msg) {
|
|
@@ -3249,7 +3314,6 @@ var RelayRouter = class {
|
|
|
3249
3314
|
{ sessionId: sid, success: result.success, action: result.action },
|
|
3250
3315
|
"Session termination handled via relay"
|
|
3251
3316
|
);
|
|
3252
|
-
if (result.action !== "terminate_hosted_pty") this.deps.broadcastSessionList();
|
|
3253
3317
|
}
|
|
3254
3318
|
onSessionWorkerAbort(msg) {
|
|
3255
3319
|
const sid = msg.sessionId;
|
|
@@ -3382,15 +3446,27 @@ var JsonObserver = class {
|
|
|
3382
3446
|
};
|
|
3383
3447
|
|
|
3384
3448
|
// src/serve/permission-broker.ts
|
|
3449
|
+
var DUPLICATE_DECISION = {
|
|
3450
|
+
behavior: "deny",
|
|
3451
|
+
message: "Duplicate permission request id."
|
|
3452
|
+
};
|
|
3453
|
+
function snapshot(pending) {
|
|
3454
|
+
return {
|
|
3455
|
+
requestId: pending.requestId,
|
|
3456
|
+
sessionId: pending.sessionId,
|
|
3457
|
+
provider: pending.provider,
|
|
3458
|
+
source: pending.source,
|
|
3459
|
+
toolName: pending.toolName,
|
|
3460
|
+
input: pending.input,
|
|
3461
|
+
createdAt: pending.createdAt,
|
|
3462
|
+
...pending.deliveredAt !== void 0 ? { deliveredAt: pending.deliveredAt } : {}
|
|
3463
|
+
};
|
|
3464
|
+
}
|
|
3385
3465
|
var PermissionBroker = class {
|
|
3386
3466
|
pending = /* @__PURE__ */ new Map();
|
|
3387
3467
|
request(request) {
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
return Promise.resolve({
|
|
3391
|
-
behavior: "deny",
|
|
3392
|
-
message: "Duplicate permission request id."
|
|
3393
|
-
});
|
|
3468
|
+
if (this.pending.has(request.requestId)) {
|
|
3469
|
+
return Promise.resolve(DUPLICATE_DECISION);
|
|
3394
3470
|
}
|
|
3395
3471
|
return new Promise((resolve3) => {
|
|
3396
3472
|
this.pending.set(request.requestId, {
|
|
@@ -3402,12 +3478,8 @@ var PermissionBroker = class {
|
|
|
3402
3478
|
});
|
|
3403
3479
|
}
|
|
3404
3480
|
registerWorkerRequest(request, onDecision) {
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
onDecision({
|
|
3408
|
-
behavior: "deny",
|
|
3409
|
-
message: "Duplicate permission request id."
|
|
3410
|
-
});
|
|
3481
|
+
if (this.pending.has(request.requestId)) {
|
|
3482
|
+
onDecision(DUPLICATE_DECISION);
|
|
3411
3483
|
return false;
|
|
3412
3484
|
}
|
|
3413
3485
|
this.pending.set(request.requestId, {
|
|
@@ -3433,17 +3505,7 @@ var PermissionBroker = class {
|
|
|
3433
3505
|
}
|
|
3434
3506
|
get(requestId) {
|
|
3435
3507
|
const pending = this.pending.get(requestId);
|
|
3436
|
-
|
|
3437
|
-
return {
|
|
3438
|
-
requestId: pending.requestId,
|
|
3439
|
-
sessionId: pending.sessionId,
|
|
3440
|
-
provider: pending.provider,
|
|
3441
|
-
source: pending.source,
|
|
3442
|
-
toolName: pending.toolName,
|
|
3443
|
-
input: pending.input,
|
|
3444
|
-
createdAt: pending.createdAt,
|
|
3445
|
-
...pending.deliveredAt !== void 0 ? { deliveredAt: pending.deliveredAt } : {}
|
|
3446
|
-
};
|
|
3508
|
+
return pending ? snapshot(pending) : null;
|
|
3447
3509
|
}
|
|
3448
3510
|
cleanupSession(sessionId, reason) {
|
|
3449
3511
|
for (const [requestId, pending] of this.pending) {
|
|
@@ -3457,31 +3519,27 @@ var PermissionBroker = class {
|
|
|
3457
3519
|
const out = [];
|
|
3458
3520
|
for (const pending of this.pending.values()) {
|
|
3459
3521
|
if (pending.sessionId !== sessionId) continue;
|
|
3460
|
-
out.push(
|
|
3461
|
-
requestId: pending.requestId,
|
|
3462
|
-
sessionId: pending.sessionId,
|
|
3463
|
-
provider: pending.provider,
|
|
3464
|
-
source: pending.source,
|
|
3465
|
-
toolName: pending.toolName,
|
|
3466
|
-
input: pending.input,
|
|
3467
|
-
createdAt: pending.createdAt,
|
|
3468
|
-
...pending.deliveredAt !== void 0 ? { deliveredAt: pending.deliveredAt } : {}
|
|
3469
|
-
});
|
|
3522
|
+
out.push(snapshot(pending));
|
|
3470
3523
|
}
|
|
3471
3524
|
return out;
|
|
3472
3525
|
}
|
|
3473
3526
|
};
|
|
3474
3527
|
|
|
3475
|
-
// src/serve/hook-
|
|
3476
|
-
function
|
|
3528
|
+
// src/serve/hook-payload-helpers.ts
|
|
3529
|
+
function asRecord(value) {
|
|
3477
3530
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
3478
3531
|
}
|
|
3532
|
+
function asProvider(value) {
|
|
3533
|
+
return providerValues.includes(value) ? value : null;
|
|
3534
|
+
}
|
|
3479
3535
|
function toolNameFromPayload(payload) {
|
|
3480
3536
|
return typeof payload.toolName === "string" ? payload.toolName : typeof payload.tool_name === "string" ? payload.tool_name : "unknown";
|
|
3481
3537
|
}
|
|
3482
3538
|
function toolInputFromPayload(payload) {
|
|
3483
|
-
return
|
|
3539
|
+
return asRecord(payload.input ?? payload.tool_input);
|
|
3484
3540
|
}
|
|
3541
|
+
|
|
3542
|
+
// src/serve/hook-event-router.ts
|
|
3485
3543
|
var HookEventRouter = class {
|
|
3486
3544
|
constructor(deps) {
|
|
3487
3545
|
this.deps = deps;
|
|
@@ -3521,9 +3579,13 @@ var HookEventRouter = class {
|
|
|
3521
3579
|
}
|
|
3522
3580
|
}
|
|
3523
3581
|
onPermissionResolved(sessionId, provider, requestId, outcome, context) {
|
|
3524
|
-
this.deps.changeSessionState(sessionId, SessionState.WORKING);
|
|
3525
3582
|
if (outcome === "deny") {
|
|
3526
3583
|
this.deps.changeSessionState(sessionId, SessionState.IDLE);
|
|
3584
|
+
} else {
|
|
3585
|
+
const mode = this.deps.getSessionMode?.(sessionId);
|
|
3586
|
+
if (mode === "pty") {
|
|
3587
|
+
this.deps.changeSessionState(sessionId, SessionState.WORKING);
|
|
3588
|
+
}
|
|
3527
3589
|
}
|
|
3528
3590
|
this.forwardAgentStatus(
|
|
3529
3591
|
{
|
|
@@ -3556,7 +3618,7 @@ var HookEventRouter = class {
|
|
|
3556
3618
|
input
|
|
3557
3619
|
}
|
|
3558
3620
|
});
|
|
3559
|
-
const seq = this.deps.nextSeq?.(event.sessionId) ??
|
|
3621
|
+
const seq = this.deps.nextSeq?.(event.sessionId) ?? getSeqCounterFor(event.sessionId).next();
|
|
3560
3622
|
const envelope = buildMessage(
|
|
3561
3623
|
"tool_use_request",
|
|
3562
3624
|
event.sessionId,
|
|
@@ -3588,7 +3650,7 @@ var HookEventRouter = class {
|
|
|
3588
3650
|
};
|
|
3589
3651
|
this.deps.agentStatusRegistry.set(event.sessionId, payload);
|
|
3590
3652
|
this.deps.relayConnection.sendRaw(
|
|
3591
|
-
|
|
3653
|
+
serializeControl({
|
|
3592
3654
|
type: "agent_status",
|
|
3593
3655
|
sessionId: event.sessionId,
|
|
3594
3656
|
payload
|
|
@@ -3596,7 +3658,7 @@ var HookEventRouter = class {
|
|
|
3596
3658
|
);
|
|
3597
3659
|
}
|
|
3598
3660
|
nextSeq(sessionId) {
|
|
3599
|
-
return this.deps.nextSeq?.(sessionId) ??
|
|
3661
|
+
return this.deps.nextSeq?.(sessionId) ?? getSeqCounterFor(sessionId).next();
|
|
3600
3662
|
}
|
|
3601
3663
|
};
|
|
3602
3664
|
|
|
@@ -3619,6 +3681,52 @@ var AgentStatusRegistry = class {
|
|
|
3619
3681
|
}
|
|
3620
3682
|
};
|
|
3621
3683
|
|
|
3684
|
+
// src/serve/pty-state-guard.ts
|
|
3685
|
+
function shouldPromotePtyActivityToWorking(session, pendingApprovalCount) {
|
|
3686
|
+
if (!session || session.mode !== "pty") return false;
|
|
3687
|
+
if (pendingApprovalCount > 0) return false;
|
|
3688
|
+
return session.state === SessionState.IDLE || session.state === SessionState.WAITING_APPROVAL;
|
|
3689
|
+
}
|
|
3690
|
+
|
|
3691
|
+
// src/serve/pty-semantic-lifecycle.ts
|
|
3692
|
+
function resolvePtySemanticSessionTransitions(currentState, semanticState) {
|
|
3693
|
+
if (semanticState !== "turn_complete") return [];
|
|
3694
|
+
if (currentState === SessionState.WAITING_APPROVAL) {
|
|
3695
|
+
return [SessionState.IDLE];
|
|
3696
|
+
}
|
|
3697
|
+
if (currentState === SessionState.WORKING) {
|
|
3698
|
+
return [SessionState.IDLE];
|
|
3699
|
+
}
|
|
3700
|
+
return [];
|
|
3701
|
+
}
|
|
3702
|
+
|
|
3703
|
+
// src/serve/pty-session-bridge.ts
|
|
3704
|
+
function applyPtyStateToSession(deps, sessionId, ptyState) {
|
|
3705
|
+
const session = deps.getSession(sessionId);
|
|
3706
|
+
if (!session || session.state === SessionState.TERMINATED) return;
|
|
3707
|
+
switch (ptyState) {
|
|
3708
|
+
case "approval_wait":
|
|
3709
|
+
deps.changeSessionState(sessionId, SessionState.WAITING_APPROVAL);
|
|
3710
|
+
break;
|
|
3711
|
+
case "working": {
|
|
3712
|
+
const pending = deps.getPendingApprovalCount(sessionId);
|
|
3713
|
+
if (shouldPromotePtyActivityToWorking(session, pending)) {
|
|
3714
|
+
deps.changeSessionState(sessionId, SessionState.WORKING);
|
|
3715
|
+
}
|
|
3716
|
+
break;
|
|
3717
|
+
}
|
|
3718
|
+
case "turn_complete": {
|
|
3719
|
+
deps.resolveInterruptedApprovals(sessionId);
|
|
3720
|
+
const transitions = resolvePtySemanticSessionTransitions(session.state, ptyState);
|
|
3721
|
+
for (const next of transitions) {
|
|
3722
|
+
deps.changeSessionState(sessionId, next);
|
|
3723
|
+
}
|
|
3724
|
+
deps.emitAgentStatus(sessionId, "idle");
|
|
3725
|
+
break;
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3622
3730
|
// src/serve/session-broadcast.ts
|
|
3623
3731
|
var ACTIVITY_STATUS_PUSH_INTERVAL_MS = 15e3;
|
|
3624
3732
|
function toSessionListPayload(s) {
|
|
@@ -3649,21 +3757,18 @@ function pushSessionStatus(relay, sessionManager, sessionId) {
|
|
|
3649
3757
|
}
|
|
3650
3758
|
}
|
|
3651
3759
|
function broadcastSessionList(relay, sessionManager) {
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
source: "proxy",
|
|
3659
|
-
version: "1",
|
|
3660
|
-
payload: { sessions: sessionManager.listSessions().map(toSessionListPayload) }
|
|
3661
|
-
})
|
|
3760
|
+
const envelope = buildMessage(
|
|
3761
|
+
"session_list",
|
|
3762
|
+
null,
|
|
3763
|
+
0,
|
|
3764
|
+
{ sessions: sessionManager.listSessions().map(toSessionListPayload) },
|
|
3765
|
+
"proxy"
|
|
3662
3766
|
);
|
|
3767
|
+
relay.sendEnvelope(envelope);
|
|
3663
3768
|
}
|
|
3664
3769
|
function broadcastSessionSync(relay, session) {
|
|
3665
3770
|
relay.sendRaw(
|
|
3666
|
-
|
|
3771
|
+
serializeControl({
|
|
3667
3772
|
type: "session_sync",
|
|
3668
3773
|
sessions: [
|
|
3669
3774
|
{
|
|
@@ -3689,6 +3794,37 @@ function touchSessionActivity(sessionManager, relay, sessionId, now = Date.now()
|
|
|
3689
3794
|
return touched;
|
|
3690
3795
|
}
|
|
3691
3796
|
|
|
3797
|
+
// src/serve/event-bridge.ts
|
|
3798
|
+
function createEventBridge(deps) {
|
|
3799
|
+
const changeState = (sessionId, next) => changeSessionState(deps.sessionManager, deps.relayConnection, sessionId, next);
|
|
3800
|
+
const touchActivity = (sessionId) => touchSessionActivity(deps.sessionManager, deps.relayConnection, sessionId);
|
|
3801
|
+
const emitAgentStatus = (sessionId, phase) => {
|
|
3802
|
+
const session = deps.sessionManager.getSession(sessionId);
|
|
3803
|
+
if (!session) return;
|
|
3804
|
+
const payload = {
|
|
3805
|
+
provider: session.provider,
|
|
3806
|
+
phase,
|
|
3807
|
+
seq: getSeqCounterFor(sessionId).next(),
|
|
3808
|
+
updatedAt: Date.now()
|
|
3809
|
+
};
|
|
3810
|
+
deps.agentStatusRegistry.set(sessionId, payload);
|
|
3811
|
+
deps.relayConnection.sendRaw(serializeControl({ type: "agent_status", sessionId, payload }));
|
|
3812
|
+
};
|
|
3813
|
+
const cleanupSessionResources = (sessionId) => {
|
|
3814
|
+
deps.controlHandlers.cleanup(sessionId);
|
|
3815
|
+
deps.agentStatusRegistry.delete(sessionId);
|
|
3816
|
+
disposeSeqCounter(sessionId);
|
|
3817
|
+
deps.permissionBroker.cleanupSession(sessionId, "Session closed");
|
|
3818
|
+
broadcastSessionList(deps.relayConnection, deps.sessionManager);
|
|
3819
|
+
};
|
|
3820
|
+
return {
|
|
3821
|
+
changeSessionState: changeState,
|
|
3822
|
+
touchSessionActivity: touchActivity,
|
|
3823
|
+
emitAgentStatus,
|
|
3824
|
+
cleanupSessionResources
|
|
3825
|
+
};
|
|
3826
|
+
}
|
|
3827
|
+
|
|
3692
3828
|
// src/serve/service-files.ts
|
|
3693
3829
|
import { execSync } from "child_process";
|
|
3694
3830
|
import { existsSync as existsSync5, readFileSync as readFileSync6, unlinkSync as unlinkSync2 } from "fs";
|
|
@@ -3717,6 +3853,7 @@ async function cleanupStaleResources() {
|
|
|
3717
3853
|
const msg = `Another service is already running on ${SOCK_PATH}`;
|
|
3718
3854
|
serviceLogger.error(msg);
|
|
3719
3855
|
console.error(msg);
|
|
3856
|
+
await flushLogger(serviceLogger);
|
|
3720
3857
|
process.exit(1);
|
|
3721
3858
|
}
|
|
3722
3859
|
unlinkSync2(SOCK_PATH);
|
|
@@ -3729,6 +3866,7 @@ async function cleanupStaleResources() {
|
|
|
3729
3866
|
const msg = `Another service is already running with PID ${pid}`;
|
|
3730
3867
|
serviceLogger.error(msg);
|
|
3731
3868
|
console.error(msg);
|
|
3869
|
+
await flushLogger(serviceLogger);
|
|
3732
3870
|
process.exit(1);
|
|
3733
3871
|
}
|
|
3734
3872
|
unlinkSync2(PID_PATH);
|
|
@@ -3751,25 +3889,6 @@ function getComputerName() {
|
|
|
3751
3889
|
}
|
|
3752
3890
|
}
|
|
3753
3891
|
|
|
3754
|
-
// src/serve/pty-state-guard.ts
|
|
3755
|
-
function shouldPromotePtyActivityToWorking(session, pendingApprovalCount) {
|
|
3756
|
-
if (!session || session.mode !== "pty") return false;
|
|
3757
|
-
if (pendingApprovalCount > 0) return false;
|
|
3758
|
-
return session.state === SessionState.IDLE || session.state === SessionState.WAITING_APPROVAL;
|
|
3759
|
-
}
|
|
3760
|
-
|
|
3761
|
-
// src/serve/pty-semantic-lifecycle.ts
|
|
3762
|
-
function resolvePtySemanticSessionTransitions(currentState, semanticState) {
|
|
3763
|
-
if (semanticState !== "turn_complete") return [];
|
|
3764
|
-
if (currentState === SessionState.WAITING_APPROVAL) {
|
|
3765
|
-
return [SessionState.IDLE];
|
|
3766
|
-
}
|
|
3767
|
-
if (currentState === SessionState.WORKING) {
|
|
3768
|
-
return [SessionState.IDLE];
|
|
3769
|
-
}
|
|
3770
|
-
return [];
|
|
3771
|
-
}
|
|
3772
|
-
|
|
3773
3892
|
// src/serve/terminal-ipc.ts
|
|
3774
3893
|
function handleTerminalConnection(socket, deps) {
|
|
3775
3894
|
const {
|
|
@@ -3784,8 +3903,16 @@ function handleTerminalConnection(socket, deps) {
|
|
|
3784
3903
|
createHookContext,
|
|
3785
3904
|
emitAgentStatus,
|
|
3786
3905
|
resolveInterruptedApprovals: resolveInterruptedApprovals2,
|
|
3906
|
+
cleanupSessionResources,
|
|
3787
3907
|
config
|
|
3788
3908
|
} = deps;
|
|
3909
|
+
const bridgeDeps = {
|
|
3910
|
+
changeSessionState: (sessionId, next) => changeSessionState(sessionManager, relayConnection, sessionId, next),
|
|
3911
|
+
getSession: (sessionId) => sessionManager.getSession(sessionId),
|
|
3912
|
+
getPendingApprovalCount: (sessionId) => permissionBroker.listSession(sessionId).length,
|
|
3913
|
+
resolveInterruptedApprovals: resolveInterruptedApprovals2,
|
|
3914
|
+
emitAgentStatus
|
|
3915
|
+
};
|
|
3789
3916
|
createIpcReader(
|
|
3790
3917
|
socket,
|
|
3791
3918
|
(msg) => {
|
|
@@ -3852,7 +3979,7 @@ function handleTerminalConnection(socket, deps) {
|
|
|
3852
3979
|
case "pty_title_change": {
|
|
3853
3980
|
if (!sessionManager.getSession(msg.sessionId)) break;
|
|
3854
3981
|
relayConnection.sendRaw(
|
|
3855
|
-
|
|
3982
|
+
serializeControl({
|
|
3856
3983
|
type: "terminal_title",
|
|
3857
3984
|
sessionId: msg.sessionId,
|
|
3858
3985
|
title: msg.title
|
|
@@ -3873,45 +4000,9 @@ function handleTerminalConnection(socket, deps) {
|
|
|
3873
4000
|
} else {
|
|
3874
4001
|
serviceLogger.debug(logPayload, "PTY semantic event received");
|
|
3875
4002
|
}
|
|
3876
|
-
|
|
3877
|
-
changeSessionState(
|
|
3878
|
-
sessionManager,
|
|
3879
|
-
relayConnection,
|
|
3880
|
-
msg.sessionId,
|
|
3881
|
-
SessionState.WAITING_APPROVAL
|
|
3882
|
-
);
|
|
3883
|
-
} else if (msg.state === "working" || msg.state === "mid_pause") {
|
|
3884
|
-
const session = sessionManager.getSession(msg.sessionId);
|
|
3885
|
-
const pendingApprovals = permissionBroker.listSession(msg.sessionId);
|
|
3886
|
-
if (shouldPromotePtyActivityToWorking(session, pendingApprovals.length)) {
|
|
3887
|
-
changeSessionState(
|
|
3888
|
-
sessionManager,
|
|
3889
|
-
relayConnection,
|
|
3890
|
-
msg.sessionId,
|
|
3891
|
-
SessionState.WORKING
|
|
3892
|
-
);
|
|
3893
|
-
} else if (session?.state === SessionState.WAITING_APPROVAL) {
|
|
3894
|
-
serviceLogger.debug(
|
|
3895
|
-
{
|
|
3896
|
-
sessionId: msg.sessionId,
|
|
3897
|
-
ptyState: msg.state,
|
|
3898
|
-
pendingApprovals: pendingApprovals.length
|
|
3899
|
-
},
|
|
3900
|
-
"PTY working signal ignored while permission approval is pending"
|
|
3901
|
-
);
|
|
3902
|
-
}
|
|
3903
|
-
}
|
|
3904
|
-
if (msg.state === "turn_complete") {
|
|
3905
|
-
resolveInterruptedApprovals2(msg.sessionId);
|
|
3906
|
-
const session = sessionManager.getSession(msg.sessionId);
|
|
3907
|
-
const transitions = resolvePtySemanticSessionTransitions(session?.state, msg.state);
|
|
3908
|
-
for (const next of transitions) {
|
|
3909
|
-
changeSessionState(sessionManager, relayConnection, msg.sessionId, next);
|
|
3910
|
-
}
|
|
3911
|
-
emitAgentStatus(msg.sessionId, "idle");
|
|
3912
|
-
}
|
|
4003
|
+
applyPtyStateToSession(bridgeDeps, msg.sessionId, msg.state);
|
|
3913
4004
|
relayConnection.sendRaw(
|
|
3914
|
-
|
|
4005
|
+
serializeControl({
|
|
3915
4006
|
type: "pty_state",
|
|
3916
4007
|
sessionId: msg.sessionId,
|
|
3917
4008
|
payload: {
|
|
@@ -3926,7 +4017,7 @@ function handleTerminalConnection(socket, deps) {
|
|
|
3926
4017
|
case "pty_resize": {
|
|
3927
4018
|
if (!sessionManager.getSession(msg.sessionId)) break;
|
|
3928
4019
|
relayConnection.sendRaw(
|
|
3929
|
-
|
|
4020
|
+
serializeControl({
|
|
3930
4021
|
type: "terminal_resize",
|
|
3931
4022
|
sessionId: msg.sessionId,
|
|
3932
4023
|
cols: msg.cols,
|
|
@@ -3943,7 +4034,8 @@ function handleTerminalConnection(socket, deps) {
|
|
|
3943
4034
|
controlHandlers,
|
|
3944
4035
|
terminalSockets,
|
|
3945
4036
|
hostedPtyRegistry,
|
|
3946
|
-
agentStatusRegistry
|
|
4037
|
+
agentStatusRegistry,
|
|
4038
|
+
broadcastSessionList: () => broadcastSessionList(relayConnection, sessionManager)
|
|
3947
4039
|
},
|
|
3948
4040
|
msg.sessionId
|
|
3949
4041
|
);
|
|
@@ -3986,7 +4078,7 @@ function handleTerminalConnection(socket, deps) {
|
|
|
3986
4078
|
}
|
|
3987
4079
|
case "pty_deregister": {
|
|
3988
4080
|
relayConnection.sendRaw(
|
|
3989
|
-
|
|
4081
|
+
serializeControl({
|
|
3990
4082
|
type: "pty_state",
|
|
3991
4083
|
sessionId: msg.sessionId,
|
|
3992
4084
|
payload: { state: "turn_complete" }
|
|
@@ -3994,9 +4086,7 @@ function handleTerminalConnection(socket, deps) {
|
|
|
3994
4086
|
);
|
|
3995
4087
|
sessionManager.terminateSession(msg.sessionId);
|
|
3996
4088
|
terminalSockets.delete(msg.sessionId);
|
|
3997
|
-
|
|
3998
|
-
agentStatusRegistry.delete(msg.sessionId);
|
|
3999
|
-
broadcastSessionList(relayConnection, sessionManager);
|
|
4089
|
+
cleanupSessionResources(msg.sessionId);
|
|
4000
4090
|
serviceLogger.info({ sessionId: msg.sessionId }, "PTY session deregistered");
|
|
4001
4091
|
break;
|
|
4002
4092
|
}
|
|
@@ -4024,14 +4114,14 @@ function handleTerminalConnection(socket, deps) {
|
|
|
4024
4114
|
case "pty_snapshot": {
|
|
4025
4115
|
if (!sessionManager.getSession(msg.sessionId)) break;
|
|
4026
4116
|
relayConnection.sendRaw(
|
|
4027
|
-
|
|
4117
|
+
serializeControl({
|
|
4028
4118
|
type: "session_snapshot",
|
|
4029
4119
|
sessionId: msg.sessionId,
|
|
4030
4120
|
cols: msg.cols,
|
|
4031
4121
|
rows: msg.rows,
|
|
4032
4122
|
data: msg.data,
|
|
4033
4123
|
outputSeq: msg.outputSeq,
|
|
4034
|
-
requestId: msg.requestId
|
|
4124
|
+
...msg.requestId !== void 0 ? { requestId: msg.requestId } : {}
|
|
4035
4125
|
})
|
|
4036
4126
|
);
|
|
4037
4127
|
serviceLogger.info(
|
|
@@ -4048,13 +4138,13 @@ function handleTerminalConnection(socket, deps) {
|
|
|
4048
4138
|
(sessionId, data, outputSeq) => {
|
|
4049
4139
|
if (!sessionManager.getSession(sessionId)) return;
|
|
4050
4140
|
touchSessionActivity(sessionManager, relayConnection, sessionId);
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4141
|
+
relayConnection.sendBinary(encodeBinaryFrame(sessionId, outputSeq, data));
|
|
4142
|
+
},
|
|
4143
|
+
(err, line) => {
|
|
4144
|
+
serviceLogger.warn(
|
|
4145
|
+
{ err: err.message, lineLen: line.length },
|
|
4146
|
+
"Terminal IPC message dropped (parse/schema error)"
|
|
4147
|
+
);
|
|
4058
4148
|
}
|
|
4059
4149
|
);
|
|
4060
4150
|
socket.on("close", () => {
|
|
@@ -4074,16 +4164,14 @@ function handleTerminalConnection(socket, deps) {
|
|
|
4074
4164
|
continue;
|
|
4075
4165
|
}
|
|
4076
4166
|
relayConnection.sendRaw(
|
|
4077
|
-
|
|
4167
|
+
serializeControl({
|
|
4078
4168
|
type: "pty_state",
|
|
4079
4169
|
sessionId,
|
|
4080
4170
|
payload: { state: "turn_complete" }
|
|
4081
4171
|
})
|
|
4082
4172
|
);
|
|
4083
4173
|
sessionManager.terminateSession(sessionId);
|
|
4084
|
-
|
|
4085
|
-
agentStatusRegistry.delete(sessionId);
|
|
4086
|
-
broadcastSessionList(relayConnection, sessionManager);
|
|
4174
|
+
cleanupSessionResources(sessionId);
|
|
4087
4175
|
serviceLogger.info(
|
|
4088
4176
|
{ sessionId },
|
|
4089
4177
|
"PTY session cleaned up on socket close (crash fallback)"
|
|
@@ -4098,8 +4186,8 @@ function handleTerminalConnection(socket, deps) {
|
|
|
4098
4186
|
|
|
4099
4187
|
// src/serve/hook-registry.ts
|
|
4100
4188
|
import { createHash, randomBytes } from "crypto";
|
|
4101
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
4102
|
-
import { dirname
|
|
4189
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync7, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
4190
|
+
import { dirname } from "path";
|
|
4103
4191
|
import { z } from "zod";
|
|
4104
4192
|
var PersistedHookSessionBindingSchema = z.object({
|
|
4105
4193
|
sessionId: z.string(),
|
|
@@ -4178,9 +4266,9 @@ var HookRegistry = class {
|
|
|
4178
4266
|
save() {
|
|
4179
4267
|
if (!this.persistPath) return;
|
|
4180
4268
|
try {
|
|
4181
|
-
|
|
4269
|
+
mkdirSync2(dirname(this.persistPath), { recursive: true });
|
|
4182
4270
|
const tmpPath = `${this.persistPath}.${process.pid}.${Date.now()}.tmp`;
|
|
4183
|
-
|
|
4271
|
+
writeFileSync2(
|
|
4184
4272
|
tmpPath,
|
|
4185
4273
|
JSON.stringify(
|
|
4186
4274
|
{
|
|
@@ -4191,7 +4279,7 @@ var HookRegistry = class {
|
|
|
4191
4279
|
2
|
|
4192
4280
|
)
|
|
4193
4281
|
);
|
|
4194
|
-
|
|
4282
|
+
renameSync(tmpPath, this.persistPath);
|
|
4195
4283
|
} catch (err) {
|
|
4196
4284
|
serviceLogger.warn(
|
|
4197
4285
|
{ path: this.persistPath, error: String(err) },
|
|
@@ -4208,12 +4296,6 @@ function getBearerToken(req) {
|
|
|
4208
4296
|
if (!header?.startsWith("Bearer ")) return null;
|
|
4209
4297
|
return header.slice("Bearer ".length).trim() || null;
|
|
4210
4298
|
}
|
|
4211
|
-
function asProvider(value) {
|
|
4212
|
-
return value === "claude" || value === "codex" ? value : null;
|
|
4213
|
-
}
|
|
4214
|
-
function asRecord(value) {
|
|
4215
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
4216
|
-
}
|
|
4217
4299
|
var HookServer = class {
|
|
4218
4300
|
constructor(options) {
|
|
4219
4301
|
this.options = options;
|
|
@@ -4313,7 +4395,7 @@ var HookServer = class {
|
|
|
4313
4395
|
}
|
|
4314
4396
|
async handlePermissionRequest(event, res) {
|
|
4315
4397
|
const requestId = event.requestId ?? (typeof event.payload.tool_use_id === "string" ? event.payload.tool_use_id : void 0) ?? `${event.sessionId}:${Date.now()}`;
|
|
4316
|
-
const toolName =
|
|
4398
|
+
const toolName = toolNameFromPayload(event.payload);
|
|
4317
4399
|
const input = asRecord(event.payload.input ?? event.payload.tool_input);
|
|
4318
4400
|
this.options.onEvent?.({ ...event, requestId });
|
|
4319
4401
|
const decision = await this.options.permissionBroker.request({
|
|
@@ -4396,7 +4478,8 @@ async function createProviderHookRuntime(options) {
|
|
|
4396
4478
|
const hookEventRouter = new HookEventRouter({
|
|
4397
4479
|
relayConnection: options.relayConnection,
|
|
4398
4480
|
agentStatusRegistry: options.agentStatusRegistry,
|
|
4399
|
-
changeSessionState: options.changeSessionState
|
|
4481
|
+
changeSessionState: options.changeSessionState,
|
|
4482
|
+
getSessionMode: (sessionId) => options.sessionManager.getSession(sessionId)?.mode
|
|
4400
4483
|
});
|
|
4401
4484
|
const port = options.hookPort ?? 17654;
|
|
4402
4485
|
const hookServer = new HookServer({
|
|
@@ -4423,6 +4506,7 @@ async function createProviderHookRuntime(options) {
|
|
|
4423
4506
|
const msg = `Failed to start hook server on 127.0.0.1:${port}: ${String(err)}`;
|
|
4424
4507
|
serviceLogger.error(msg);
|
|
4425
4508
|
console.error(msg);
|
|
4509
|
+
await flushLogger(serviceLogger);
|
|
4426
4510
|
process.exit(1);
|
|
4427
4511
|
}
|
|
4428
4512
|
const hookUrl = `http://127.0.0.1:${hookServer.getListeningPort() ?? port}/hook`;
|
|
@@ -4444,6 +4528,35 @@ async function createProviderHookRuntime(options) {
|
|
|
4444
4528
|
};
|
|
4445
4529
|
}
|
|
4446
4530
|
|
|
4531
|
+
// src/serve/shutdown.ts
|
|
4532
|
+
import { unlinkSync as unlinkSync3 } from "fs";
|
|
4533
|
+
function createServeShutdown(deps) {
|
|
4534
|
+
const exit = deps.exit ?? ((code) => process.exit(code));
|
|
4535
|
+
let shuttingDown = false;
|
|
4536
|
+
return async () => {
|
|
4537
|
+
if (shuttingDown) return;
|
|
4538
|
+
shuttingDown = true;
|
|
4539
|
+
deps.logger.info("Shutting down service");
|
|
4540
|
+
deps.sessionManagerStopReaper();
|
|
4541
|
+
deps.relayRouterDestroy();
|
|
4542
|
+
await deps.hookServerClose();
|
|
4543
|
+
deps.relayConnectionClose();
|
|
4544
|
+
deps.workerRegistryDestroyAll();
|
|
4545
|
+
deps.hostedPtyRegistryDestroyAll();
|
|
4546
|
+
deps.ipcServerClose();
|
|
4547
|
+
try {
|
|
4548
|
+
unlinkSync3(deps.sockPath);
|
|
4549
|
+
} catch {
|
|
4550
|
+
}
|
|
4551
|
+
try {
|
|
4552
|
+
unlinkSync3(deps.pidPath);
|
|
4553
|
+
} catch {
|
|
4554
|
+
}
|
|
4555
|
+
await flushLogger(deps.logger);
|
|
4556
|
+
exit(0);
|
|
4557
|
+
};
|
|
4558
|
+
}
|
|
4559
|
+
|
|
4447
4560
|
// src/serve.ts
|
|
4448
4561
|
function resolveInterruptedApprovals(permissionBroker, hookEventRouter, relay, sessionId) {
|
|
4449
4562
|
const approvals = permissionBroker.listSession(sessionId);
|
|
@@ -4459,7 +4572,7 @@ function resolveInterruptedApprovals(permissionBroker, hookEventRouter, relay, s
|
|
|
4459
4572
|
{ toolName: approval.toolName, toolInput: approval.input }
|
|
4460
4573
|
);
|
|
4461
4574
|
relay.sendRaw(
|
|
4462
|
-
|
|
4575
|
+
serializeControl({
|
|
4463
4576
|
type: "permission_decision_result",
|
|
4464
4577
|
sessionId: approval.sessionId,
|
|
4465
4578
|
requestId: approval.requestId,
|
|
@@ -4499,7 +4612,7 @@ async function startService(options) {
|
|
|
4499
4612
|
ensureProfileWorkspace();
|
|
4500
4613
|
await cleanupStaleResources();
|
|
4501
4614
|
try {
|
|
4502
|
-
|
|
4615
|
+
unlinkSync4(STOPPED_PATH);
|
|
4503
4616
|
} catch {
|
|
4504
4617
|
}
|
|
4505
4618
|
const permissionBroker = new PermissionBroker();
|
|
@@ -4560,6 +4673,7 @@ async function startService(options) {
|
|
|
4560
4673
|
const msg = `Relay URL is required. Set relays.${proxyConfig.relayName}.url in ~/.dev-anywhere/config.json or pass --relay <name>.`;
|
|
4561
4674
|
serviceLogger.error(msg);
|
|
4562
4675
|
console.error(msg);
|
|
4676
|
+
await flushLogger(serviceLogger);
|
|
4563
4677
|
process.exit(1);
|
|
4564
4678
|
}
|
|
4565
4679
|
const relayConnection = new RelayConnection(relayUrl, {
|
|
@@ -4569,23 +4683,16 @@ async function startService(options) {
|
|
|
4569
4683
|
});
|
|
4570
4684
|
const relaySend = (data) => relayConnection.sendRaw(data);
|
|
4571
4685
|
const controlHandlers = createControlMessageHandlers(relaySend, sessionManager);
|
|
4572
|
-
const
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
phase,
|
|
4580
|
-
seq: new SeqCounter(sessionId).next(),
|
|
4581
|
-
updatedAt: Date.now()
|
|
4582
|
-
};
|
|
4583
|
-
agentStatusRegistry.set(sessionId, payload);
|
|
4584
|
-
relayConnection.sendRaw(JSON.stringify({ type: "agent_status", sessionId, payload }));
|
|
4585
|
-
};
|
|
4686
|
+
const eventBridge = createEventBridge({
|
|
4687
|
+
sessionManager,
|
|
4688
|
+
relayConnection,
|
|
4689
|
+
agentStatusRegistry,
|
|
4690
|
+
controlHandlers,
|
|
4691
|
+
permissionBroker
|
|
4692
|
+
});
|
|
4586
4693
|
const jsonObserver = new JsonObserver({
|
|
4587
|
-
changeSessionState:
|
|
4588
|
-
emitAgentStatus
|
|
4694
|
+
changeSessionState: eventBridge.changeSessionState,
|
|
4695
|
+
emitAgentStatus: eventBridge.emitAgentStatus
|
|
4589
4696
|
});
|
|
4590
4697
|
const hookRuntime = await createProviderHookRuntime({
|
|
4591
4698
|
hookPort: proxyConfig.hookPort,
|
|
@@ -4593,7 +4700,7 @@ async function startService(options) {
|
|
|
4593
4700
|
sessionManager,
|
|
4594
4701
|
relayConnection,
|
|
4595
4702
|
agentStatusRegistry,
|
|
4596
|
-
changeSessionState:
|
|
4703
|
+
changeSessionState: eventBridge.changeSessionState
|
|
4597
4704
|
});
|
|
4598
4705
|
unregisterHookSession = (sessionId) => hookRuntime.hookRegistry.unregisterSession(sessionId);
|
|
4599
4706
|
const workerRegistry = new WorkerRegistry({
|
|
@@ -4601,29 +4708,28 @@ async function startService(options) {
|
|
|
4601
4708
|
permissionBroker,
|
|
4602
4709
|
relayConnection,
|
|
4603
4710
|
jsonObserver,
|
|
4604
|
-
touchSessionActivity:
|
|
4711
|
+
touchSessionActivity: eventBridge.touchSessionActivity,
|
|
4605
4712
|
getProviderEnv
|
|
4606
4713
|
});
|
|
4714
|
+
const ptyBridgeDeps = {
|
|
4715
|
+
changeSessionState: eventBridge.changeSessionState,
|
|
4716
|
+
getSession: (sessionId) => sessionManager.getSession(sessionId),
|
|
4717
|
+
getPendingApprovalCount: (sessionId) => permissionBroker.listSession(sessionId).length,
|
|
4718
|
+
resolveInterruptedApprovals: (sessionId) => resolveInterruptedApprovals(
|
|
4719
|
+
permissionBroker,
|
|
4720
|
+
hookRuntime.hookEventRouter,
|
|
4721
|
+
relayConnection,
|
|
4722
|
+
sessionId
|
|
4723
|
+
),
|
|
4724
|
+
emitAgentStatus: eventBridge.emitAgentStatus
|
|
4725
|
+
};
|
|
4607
4726
|
const hostedPtyRegistry = new HostedPtyRegistry({
|
|
4608
4727
|
sessionManager,
|
|
4609
4728
|
relayConnection,
|
|
4610
4729
|
getProviderEnv,
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
resolveInterruptedApprovals(
|
|
4615
|
-
permissionBroker,
|
|
4616
|
-
hookRuntime.hookEventRouter,
|
|
4617
|
-
relayConnection,
|
|
4618
|
-
sessionId
|
|
4619
|
-
);
|
|
4620
|
-
emitAgentStatus(sessionId, "idle");
|
|
4621
|
-
},
|
|
4622
|
-
onSessionClosed: (sessionId) => {
|
|
4623
|
-
controlHandlers.cleanup(sessionId);
|
|
4624
|
-
agentStatusRegistry.delete(sessionId);
|
|
4625
|
-
broadcastSessionList(relayConnection, sessionManager);
|
|
4626
|
-
}
|
|
4730
|
+
touchSessionActivity: eventBridge.touchSessionActivity,
|
|
4731
|
+
applyPtyStateToSession: (sessionId, ptyState) => applyPtyStateToSession(ptyBridgeDeps, sessionId, ptyState),
|
|
4732
|
+
onSessionClosed: eventBridge.cleanupSessionResources
|
|
4627
4733
|
});
|
|
4628
4734
|
relayConnection.connect();
|
|
4629
4735
|
serviceLogger.info(
|
|
@@ -4660,7 +4766,12 @@ async function startService(options) {
|
|
|
4660
4766
|
});
|
|
4661
4767
|
relayConnection.on("message", (msg) => relayRouter.handle(msg));
|
|
4662
4768
|
relayConnection.on("connected", () => {
|
|
4663
|
-
controlHandlers.reinitializeOnReconnect()
|
|
4769
|
+
void controlHandlers.reinitializeOnReconnect().catch((err) => {
|
|
4770
|
+
serviceLogger.error(
|
|
4771
|
+
{ error: err instanceof Error ? err.message : String(err), stack: err instanceof Error ? err.stack : void 0 },
|
|
4772
|
+
"reinitializeOnReconnect failed: client may see stale state until next manual sync"
|
|
4773
|
+
);
|
|
4774
|
+
});
|
|
4664
4775
|
broadcastBridgeStatus(true);
|
|
4665
4776
|
});
|
|
4666
4777
|
relayConnection.on("disconnected", () => {
|
|
@@ -4685,7 +4796,8 @@ async function startService(options) {
|
|
|
4685
4796
|
permissionBroker,
|
|
4686
4797
|
hookEventRouter: hookRuntime.hookEventRouter,
|
|
4687
4798
|
createHookContext: hookRuntime.createHookContext,
|
|
4688
|
-
emitAgentStatus,
|
|
4799
|
+
emitAgentStatus: eventBridge.emitAgentStatus,
|
|
4800
|
+
cleanupSessionResources: eventBridge.cleanupSessionResources,
|
|
4689
4801
|
config: statusConfig,
|
|
4690
4802
|
resolveInterruptedApprovals: (sessionId) => resolveInterruptedApprovals(
|
|
4691
4803
|
permissionBroker,
|
|
@@ -4696,41 +4808,36 @@ async function startService(options) {
|
|
|
4696
4808
|
});
|
|
4697
4809
|
});
|
|
4698
4810
|
server.listen(SOCK_PATH, () => {
|
|
4699
|
-
|
|
4811
|
+
writeFileSync3(PID_PATH, String(process.pid));
|
|
4700
4812
|
chmodSync(SOCK_PATH, 384);
|
|
4701
4813
|
serviceLogger.info({ pid: process.pid, sock: SOCK_PATH }, "Service started");
|
|
4702
4814
|
});
|
|
4703
|
-
|
|
4704
|
-
serviceLogger
|
|
4705
|
-
sessionManager.stopReaper()
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
try {
|
|
4716
|
-
unlinkSync3(PID_PATH);
|
|
4717
|
-
} catch {
|
|
4718
|
-
}
|
|
4719
|
-
process.exit(0);
|
|
4720
|
-
}
|
|
4815
|
+
const shutdown = createServeShutdown({
|
|
4816
|
+
logger: serviceLogger,
|
|
4817
|
+
sessionManagerStopReaper: () => sessionManager.stopReaper(),
|
|
4818
|
+
relayRouterDestroy: () => relayRouter.destroy(),
|
|
4819
|
+
hookServerClose: () => hookRuntime.hookServer.close(),
|
|
4820
|
+
relayConnectionClose: () => relayConnection.close(),
|
|
4821
|
+
workerRegistryDestroyAll: () => workerRegistry.destroyAll(),
|
|
4822
|
+
hostedPtyRegistryDestroyAll: () => hostedPtyRegistry.destroyAll(),
|
|
4823
|
+
ipcServerClose: () => server.close(),
|
|
4824
|
+
sockPath: SOCK_PATH,
|
|
4825
|
+
pidPath: PID_PATH
|
|
4826
|
+
});
|
|
4721
4827
|
process.on("SIGTERM", () => {
|
|
4722
|
-
shutdown();
|
|
4828
|
+
void shutdown();
|
|
4723
4829
|
});
|
|
4724
4830
|
process.on("SIGINT", () => {
|
|
4725
|
-
shutdown();
|
|
4831
|
+
void shutdown();
|
|
4726
4832
|
});
|
|
4727
4833
|
}
|
|
4728
4834
|
var isMainModule = process.argv[1] && (process.argv[1].endsWith("serve.js") || process.argv[1].endsWith("serve.ts"));
|
|
4729
4835
|
if (isMainModule) {
|
|
4730
|
-
startService(parseServiceOptions(process.argv.slice(2))).catch((err) => {
|
|
4836
|
+
startService(parseServiceOptions(process.argv.slice(2))).catch(async (err) => {
|
|
4731
4837
|
const message = err instanceof Error ? err.message : String(err);
|
|
4732
4838
|
serviceLogger.error({ err: message }, "Service failed to start");
|
|
4733
4839
|
console.error(message);
|
|
4840
|
+
await flushLogger(serviceLogger);
|
|
4734
4841
|
process.exit(1);
|
|
4735
4842
|
});
|
|
4736
4843
|
}
|