@mobvibe/cli 0.1.4 → 0.1.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/dist/index.js +507 -194
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -379,12 +379,13 @@ var getCliConfig = async () => {
|
|
|
379
379
|
|
|
380
380
|
// src/daemon/daemon.ts
|
|
381
381
|
import { spawn as spawn2 } from "child_process";
|
|
382
|
-
import
|
|
383
|
-
import
|
|
382
|
+
import fs5 from "fs/promises";
|
|
383
|
+
import path6 from "path";
|
|
384
384
|
|
|
385
385
|
// src/acp/session-manager.ts
|
|
386
386
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
387
387
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
388
|
+
import fs3 from "fs/promises";
|
|
388
389
|
|
|
389
390
|
// ../../packages/shared/dist/types/errors.js
|
|
390
391
|
var createErrorDetail = (input) => ({
|
|
@@ -559,8 +560,7 @@ var AcpConnection = class {
|
|
|
559
560
|
getSessionCapabilities() {
|
|
560
561
|
return {
|
|
561
562
|
list: this.agentCapabilities?.sessionCapabilities?.list != null,
|
|
562
|
-
load: this.agentCapabilities?.loadSession === true
|
|
563
|
-
resume: this.agentCapabilities?.sessionCapabilities?.resume != null
|
|
563
|
+
load: this.agentCapabilities?.loadSession === true
|
|
564
564
|
};
|
|
565
565
|
}
|
|
566
566
|
/**
|
|
@@ -575,12 +575,6 @@ var AcpConnection = class {
|
|
|
575
575
|
supportsSessionLoad() {
|
|
576
576
|
return this.agentCapabilities?.loadSession === true;
|
|
577
577
|
}
|
|
578
|
-
/**
|
|
579
|
-
* Check if the agent supports session/resume.
|
|
580
|
-
*/
|
|
581
|
-
supportsSessionResume() {
|
|
582
|
-
return this.agentCapabilities?.sessionCapabilities?.resume != null;
|
|
583
|
-
}
|
|
584
578
|
/**
|
|
585
579
|
* List sessions from the agent (session/list).
|
|
586
580
|
* @param params Optional filter parameters
|
|
@@ -588,14 +582,17 @@ var AcpConnection = class {
|
|
|
588
582
|
*/
|
|
589
583
|
async listSessions(params) {
|
|
590
584
|
if (!this.supportsSessionList()) {
|
|
591
|
-
return [];
|
|
585
|
+
return { sessions: [] };
|
|
592
586
|
}
|
|
593
587
|
const connection = await this.ensureReady();
|
|
594
588
|
const response = await connection.unstable_listSessions({
|
|
595
589
|
cursor: params?.cursor ?? void 0,
|
|
596
590
|
cwd: params?.cwd ?? void 0
|
|
597
591
|
});
|
|
598
|
-
return
|
|
592
|
+
return {
|
|
593
|
+
sessions: response.sessions,
|
|
594
|
+
nextCursor: response.nextCursor ?? void 0
|
|
595
|
+
};
|
|
599
596
|
}
|
|
600
597
|
/**
|
|
601
598
|
* Load a historical session with message history replay (session/load).
|
|
@@ -616,25 +613,6 @@ var AcpConnection = class {
|
|
|
616
613
|
this.sessionId = sessionId;
|
|
617
614
|
return response;
|
|
618
615
|
}
|
|
619
|
-
/**
|
|
620
|
-
* Resume an active session without message history replay (session/resume).
|
|
621
|
-
* @param sessionId The session ID to resume
|
|
622
|
-
* @param cwd The working directory
|
|
623
|
-
* @returns Resume session response
|
|
624
|
-
*/
|
|
625
|
-
async resumeSession(sessionId, cwd) {
|
|
626
|
-
if (!this.supportsSessionResume()) {
|
|
627
|
-
throw new Error("Agent does not support session/resume capability");
|
|
628
|
-
}
|
|
629
|
-
const connection = await this.ensureReady();
|
|
630
|
-
const response = await connection.unstable_resumeSession({
|
|
631
|
-
sessionId,
|
|
632
|
-
cwd,
|
|
633
|
-
mcpServers: []
|
|
634
|
-
});
|
|
635
|
-
this.sessionId = sessionId;
|
|
636
|
-
return response;
|
|
637
|
-
}
|
|
638
616
|
setPermissionHandler(handler) {
|
|
639
617
|
this.permissionHandler = handler;
|
|
640
618
|
}
|
|
@@ -979,6 +957,14 @@ var createCapabilityNotSupportedError = (message) => new AppError(
|
|
|
979
957
|
}),
|
|
980
958
|
409
|
|
981
959
|
);
|
|
960
|
+
var isValidWorkspacePath = async (cwd) => {
|
|
961
|
+
try {
|
|
962
|
+
const stats = await fs3.stat(cwd);
|
|
963
|
+
return stats.isDirectory();
|
|
964
|
+
} catch {
|
|
965
|
+
return false;
|
|
966
|
+
}
|
|
967
|
+
};
|
|
982
968
|
var SessionManager = class {
|
|
983
969
|
constructor(config) {
|
|
984
970
|
this.config = config;
|
|
@@ -988,6 +974,7 @@ var SessionManager = class {
|
|
|
988
974
|
this.defaultBackendId = config.defaultAcpBackendId;
|
|
989
975
|
}
|
|
990
976
|
sessions = /* @__PURE__ */ new Map();
|
|
977
|
+
discoveredSessions = /* @__PURE__ */ new Map();
|
|
991
978
|
backendById;
|
|
992
979
|
defaultBackendId;
|
|
993
980
|
permissionRequests = /* @__PURE__ */ new Map();
|
|
@@ -997,6 +984,8 @@ var SessionManager = class {
|
|
|
997
984
|
permissionResultEmitter = new EventEmitter2();
|
|
998
985
|
terminalOutputEmitter = new EventEmitter2();
|
|
999
986
|
sessionsChangedEmitter = new EventEmitter2();
|
|
987
|
+
sessionAttachedEmitter = new EventEmitter2();
|
|
988
|
+
sessionDetachedEmitter = new EventEmitter2();
|
|
1000
989
|
listSessions() {
|
|
1001
990
|
return Array.from(this.sessions.values()).map(
|
|
1002
991
|
(record) => this.buildSummary(record)
|
|
@@ -1041,9 +1030,54 @@ var SessionManager = class {
|
|
|
1041
1030
|
this.sessionsChangedEmitter.off("changed", listener);
|
|
1042
1031
|
};
|
|
1043
1032
|
}
|
|
1033
|
+
onSessionAttached(listener) {
|
|
1034
|
+
this.sessionAttachedEmitter.on("attached", listener);
|
|
1035
|
+
return () => {
|
|
1036
|
+
this.sessionAttachedEmitter.off("attached", listener);
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
onSessionDetached(listener) {
|
|
1040
|
+
this.sessionDetachedEmitter.on("detached", listener);
|
|
1041
|
+
return () => {
|
|
1042
|
+
this.sessionDetachedEmitter.off("detached", listener);
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1044
1045
|
emitSessionsChanged(payload) {
|
|
1045
1046
|
this.sessionsChangedEmitter.emit("changed", payload);
|
|
1046
1047
|
}
|
|
1048
|
+
emitSessionAttached(sessionId, force = false) {
|
|
1049
|
+
const record = this.sessions.get(sessionId);
|
|
1050
|
+
if (!record) {
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
if (record.isAttached && !force) {
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
const attachedAt = /* @__PURE__ */ new Date();
|
|
1057
|
+
record.isAttached = true;
|
|
1058
|
+
record.attachedAt = attachedAt;
|
|
1059
|
+
this.sessionAttachedEmitter.emit("attached", {
|
|
1060
|
+
sessionId,
|
|
1061
|
+
machineId: this.config.machineId,
|
|
1062
|
+
attachedAt: attachedAt.toISOString()
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
emitSessionDetached(sessionId, reason) {
|
|
1066
|
+
const record = this.sessions.get(sessionId);
|
|
1067
|
+
if (!record) {
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
if (!record.isAttached) {
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
record.isAttached = false;
|
|
1074
|
+
this.sessionDetachedEmitter.emit("detached", {
|
|
1075
|
+
sessionId,
|
|
1076
|
+
machineId: this.config.machineId,
|
|
1077
|
+
detachedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1078
|
+
reason
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1047
1081
|
listPendingPermissions(sessionId) {
|
|
1048
1082
|
return Array.from(this.permissionRequests.values()).filter((record) => record.sessionId === sessionId).map((record) => this.buildPermissionRequestPayload(record));
|
|
1049
1083
|
}
|
|
@@ -1132,29 +1166,9 @@ var SessionManager = class {
|
|
|
1132
1166
|
};
|
|
1133
1167
|
record.unsubscribe = connection.onSessionUpdate(
|
|
1134
1168
|
(notification) => {
|
|
1135
|
-
|
|
1169
|
+
record.updatedAt = /* @__PURE__ */ new Date();
|
|
1136
1170
|
this.sessionUpdateEmitter.emit("update", notification);
|
|
1137
|
-
|
|
1138
|
-
if (update.sessionUpdate === "current_mode_update") {
|
|
1139
|
-
record.modeId = update.currentModeId;
|
|
1140
|
-
record.modeName = record.availableModes?.find(
|
|
1141
|
-
(mode) => mode.id === update.currentModeId
|
|
1142
|
-
)?.name ?? record.modeName;
|
|
1143
|
-
return;
|
|
1144
|
-
}
|
|
1145
|
-
if (update.sessionUpdate === "session_info_update") {
|
|
1146
|
-
if (typeof update.title === "string") {
|
|
1147
|
-
record.title = update.title;
|
|
1148
|
-
}
|
|
1149
|
-
if (typeof update.updatedAt === "string") {
|
|
1150
|
-
record.updatedAt = new Date(update.updatedAt);
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
if (update.sessionUpdate === "available_commands_update") {
|
|
1154
|
-
if (update.availableCommands) {
|
|
1155
|
-
record.availableCommands = update.availableCommands;
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1171
|
+
this.applySessionUpdateToRecord(record, notification);
|
|
1158
1172
|
}
|
|
1159
1173
|
);
|
|
1160
1174
|
record.unsubscribeTerminal = connection.onTerminalOutput((event) => {
|
|
@@ -1166,6 +1180,7 @@ var SessionManager = class {
|
|
|
1166
1180
|
sessionId: session.sessionId,
|
|
1167
1181
|
error: status.error
|
|
1168
1182
|
});
|
|
1183
|
+
this.emitSessionDetached(session.sessionId, "agent_exit");
|
|
1169
1184
|
}
|
|
1170
1185
|
});
|
|
1171
1186
|
this.sessions.set(session.sessionId, record);
|
|
@@ -1175,6 +1190,7 @@ var SessionManager = class {
|
|
|
1175
1190
|
updated: [],
|
|
1176
1191
|
removed: []
|
|
1177
1192
|
});
|
|
1193
|
+
this.emitSessionAttached(session.sessionId);
|
|
1178
1194
|
return summary;
|
|
1179
1195
|
} catch (error) {
|
|
1180
1196
|
const status = connection.getStatus();
|
|
@@ -1391,6 +1407,7 @@ var SessionManager = class {
|
|
|
1391
1407
|
} catch (error) {
|
|
1392
1408
|
logger.error({ err: error, sessionId }, "session_disconnect_failed");
|
|
1393
1409
|
}
|
|
1410
|
+
this.emitSessionDetached(sessionId, "unknown");
|
|
1394
1411
|
this.sessions.delete(sessionId);
|
|
1395
1412
|
this.emitSessionsChanged({
|
|
1396
1413
|
added: [],
|
|
@@ -1424,11 +1441,30 @@ var SessionManager = class {
|
|
|
1424
1441
|
await connection.connect();
|
|
1425
1442
|
const capabilities = connection.getSessionCapabilities();
|
|
1426
1443
|
const sessions = [];
|
|
1444
|
+
let nextCursor;
|
|
1427
1445
|
if (capabilities.list) {
|
|
1428
|
-
const
|
|
1429
|
-
cwd: options?.cwd
|
|
1446
|
+
const response = await connection.listSessions({
|
|
1447
|
+
cwd: options?.cwd,
|
|
1448
|
+
cursor: options?.cursor
|
|
1430
1449
|
});
|
|
1431
|
-
|
|
1450
|
+
nextCursor = response.nextCursor;
|
|
1451
|
+
const validity = await Promise.all(
|
|
1452
|
+
response.sessions.map(async (session) => ({
|
|
1453
|
+
session,
|
|
1454
|
+
isValid: session.cwd ? await isValidWorkspacePath(session.cwd) : false
|
|
1455
|
+
}))
|
|
1456
|
+
);
|
|
1457
|
+
for (const { session, isValid } of validity) {
|
|
1458
|
+
if (!isValid) {
|
|
1459
|
+
this.discoveredSessions.delete(session.sessionId);
|
|
1460
|
+
continue;
|
|
1461
|
+
}
|
|
1462
|
+
this.discoveredSessions.set(session.sessionId, {
|
|
1463
|
+
sessionId: session.sessionId,
|
|
1464
|
+
cwd: session.cwd,
|
|
1465
|
+
title: session.title ?? void 0,
|
|
1466
|
+
updatedAt: session.updatedAt ?? void 0
|
|
1467
|
+
});
|
|
1432
1468
|
sessions.push({
|
|
1433
1469
|
sessionId: session.sessionId,
|
|
1434
1470
|
cwd: session.cwd,
|
|
@@ -1445,7 +1481,7 @@ var SessionManager = class {
|
|
|
1445
1481
|
},
|
|
1446
1482
|
"sessions_discovered"
|
|
1447
1483
|
);
|
|
1448
|
-
return { sessions, capabilities };
|
|
1484
|
+
return { sessions, capabilities, nextCursor };
|
|
1449
1485
|
} finally {
|
|
1450
1486
|
await connection.disconnect();
|
|
1451
1487
|
}
|
|
@@ -1461,6 +1497,7 @@ var SessionManager = class {
|
|
|
1461
1497
|
async loadSession(sessionId, cwd, backendId) {
|
|
1462
1498
|
const existing = this.sessions.get(sessionId);
|
|
1463
1499
|
if (existing) {
|
|
1500
|
+
this.emitSessionAttached(sessionId, true);
|
|
1464
1501
|
return this.buildSummary(existing);
|
|
1465
1502
|
}
|
|
1466
1503
|
const backend = this.resolveBackend(backendId);
|
|
@@ -1478,6 +1515,19 @@ var SessionManager = class {
|
|
|
1478
1515
|
"Agent does not support session loading"
|
|
1479
1516
|
);
|
|
1480
1517
|
}
|
|
1518
|
+
const bufferedUpdates = [];
|
|
1519
|
+
let recordRef;
|
|
1520
|
+
const unsubscribe = connection.onSessionUpdate(
|
|
1521
|
+
(notification) => {
|
|
1522
|
+
this.sessionUpdateEmitter.emit("update", notification);
|
|
1523
|
+
if (recordRef) {
|
|
1524
|
+
recordRef.updatedAt = /* @__PURE__ */ new Date();
|
|
1525
|
+
this.applySessionUpdateToRecord(recordRef, notification);
|
|
1526
|
+
} else {
|
|
1527
|
+
bufferedUpdates.push(notification);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
);
|
|
1481
1531
|
const response = await connection.loadSession(sessionId, cwd);
|
|
1482
1532
|
connection.setPermissionHandler(
|
|
1483
1533
|
(params) => this.handlePermissionRequest(sessionId, params)
|
|
@@ -1490,9 +1540,10 @@ var SessionManager = class {
|
|
|
1490
1540
|
const { modeId, modeName, availableModes } = resolveModeState(
|
|
1491
1541
|
response.modes
|
|
1492
1542
|
);
|
|
1543
|
+
const discovered = this.discoveredSessions.get(sessionId);
|
|
1493
1544
|
const record = {
|
|
1494
1545
|
sessionId,
|
|
1495
|
-
title:
|
|
1546
|
+
title: discovered?.title ?? sessionId,
|
|
1496
1547
|
backendId: backend.id,
|
|
1497
1548
|
backendLabel: backend.label,
|
|
1498
1549
|
connection,
|
|
@@ -1508,7 +1559,12 @@ var SessionManager = class {
|
|
|
1508
1559
|
availableModels,
|
|
1509
1560
|
availableCommands: void 0
|
|
1510
1561
|
};
|
|
1511
|
-
|
|
1562
|
+
recordRef = record;
|
|
1563
|
+
record.unsubscribe = unsubscribe;
|
|
1564
|
+
for (const notification of bufferedUpdates) {
|
|
1565
|
+
this.applySessionUpdateToRecord(record, notification);
|
|
1566
|
+
}
|
|
1567
|
+
this.setupSessionSubscriptions(record, { skipSessionUpdates: true });
|
|
1512
1568
|
this.sessions.set(sessionId, record);
|
|
1513
1569
|
const summary = this.buildSummary(record);
|
|
1514
1570
|
this.emitSessionsChanged({
|
|
@@ -1516,6 +1572,7 @@ var SessionManager = class {
|
|
|
1516
1572
|
updated: [],
|
|
1517
1573
|
removed: []
|
|
1518
1574
|
});
|
|
1575
|
+
this.emitSessionAttached(sessionId);
|
|
1519
1576
|
logger.info({ sessionId, backendId: backend.id }, "session_loaded");
|
|
1520
1577
|
return summary;
|
|
1521
1578
|
} catch (error) {
|
|
@@ -1524,110 +1581,81 @@ var SessionManager = class {
|
|
|
1524
1581
|
}
|
|
1525
1582
|
}
|
|
1526
1583
|
/**
|
|
1527
|
-
*
|
|
1528
|
-
*
|
|
1529
|
-
* @param sessionId The session ID to resume
|
|
1530
|
-
* @param cwd The working directory
|
|
1531
|
-
* @param backendId Optional backend ID
|
|
1532
|
-
* @returns The resumed session summary
|
|
1584
|
+
* Reload a historical session from the ACP agent.
|
|
1585
|
+
* Replays session history even if the session is already loaded.
|
|
1533
1586
|
*/
|
|
1534
|
-
async
|
|
1587
|
+
async reloadSession(sessionId, cwd, backendId) {
|
|
1535
1588
|
const existing = this.sessions.get(sessionId);
|
|
1536
|
-
if (existing) {
|
|
1537
|
-
return this.
|
|
1589
|
+
if (!existing) {
|
|
1590
|
+
return this.loadSession(sessionId, cwd, backendId);
|
|
1538
1591
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1592
|
+
if (!existing.connection.supportsSessionLoad()) {
|
|
1593
|
+
throw createCapabilityNotSupportedError(
|
|
1594
|
+
"Agent does not support session loading"
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
const response = await existing.connection.loadSession(sessionId, cwd);
|
|
1598
|
+
const { modelId, modelName, availableModels } = resolveModelState(
|
|
1599
|
+
response.models
|
|
1600
|
+
);
|
|
1601
|
+
const { modeId, modeName, availableModes } = resolveModeState(
|
|
1602
|
+
response.modes
|
|
1603
|
+
);
|
|
1604
|
+
const agentInfo = existing.connection.getAgentInfo();
|
|
1605
|
+
existing.cwd = cwd;
|
|
1606
|
+
existing.agentName = agentInfo?.title ?? agentInfo?.name ?? existing.agentName;
|
|
1607
|
+
existing.modelId = modelId;
|
|
1608
|
+
existing.modelName = modelName;
|
|
1609
|
+
existing.availableModels = availableModels;
|
|
1610
|
+
existing.modeId = modeId;
|
|
1611
|
+
existing.modeName = modeName;
|
|
1612
|
+
existing.availableModes = availableModes;
|
|
1613
|
+
existing.updatedAt = /* @__PURE__ */ new Date();
|
|
1614
|
+
const summary = this.buildSummary(existing);
|
|
1615
|
+
this.emitSessionsChanged({
|
|
1616
|
+
added: [],
|
|
1617
|
+
updated: [summary],
|
|
1618
|
+
removed: []
|
|
1546
1619
|
});
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1620
|
+
this.emitSessionAttached(sessionId, true);
|
|
1621
|
+
logger.info({ sessionId, backendId }, "session_reloaded");
|
|
1622
|
+
return summary;
|
|
1623
|
+
}
|
|
1624
|
+
applySessionUpdateToRecord(record, notification) {
|
|
1625
|
+
const update = notification.update;
|
|
1626
|
+
if (update.sessionUpdate === "current_mode_update") {
|
|
1627
|
+
record.modeId = update.currentModeId;
|
|
1628
|
+
record.modeName = record.availableModes?.find((mode) => mode.id === update.currentModeId)?.name ?? record.modeName;
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
if (update.sessionUpdate === "session_info_update") {
|
|
1632
|
+
if (typeof update.title === "string") {
|
|
1633
|
+
record.title = update.title;
|
|
1634
|
+
}
|
|
1635
|
+
if (typeof update.updatedAt === "string") {
|
|
1636
|
+
record.updatedAt = new Date(update.updatedAt);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
if (update.sessionUpdate === "available_commands_update") {
|
|
1640
|
+
if (update.availableCommands) {
|
|
1641
|
+
record.availableCommands = update.availableCommands;
|
|
1553
1642
|
}
|
|
1554
|
-
const response = await connection.resumeSession(sessionId, cwd);
|
|
1555
|
-
connection.setPermissionHandler(
|
|
1556
|
-
(params) => this.handlePermissionRequest(sessionId, params)
|
|
1557
|
-
);
|
|
1558
|
-
const now = /* @__PURE__ */ new Date();
|
|
1559
|
-
const agentInfo = connection.getAgentInfo();
|
|
1560
|
-
const { modelId, modelName, availableModels } = resolveModelState(
|
|
1561
|
-
response.models
|
|
1562
|
-
);
|
|
1563
|
-
const { modeId, modeName, availableModes } = resolveModeState(
|
|
1564
|
-
response.modes
|
|
1565
|
-
);
|
|
1566
|
-
const record = {
|
|
1567
|
-
sessionId,
|
|
1568
|
-
title: `Resumed Session`,
|
|
1569
|
-
backendId: backend.id,
|
|
1570
|
-
backendLabel: backend.label,
|
|
1571
|
-
connection,
|
|
1572
|
-
createdAt: now,
|
|
1573
|
-
updatedAt: now,
|
|
1574
|
-
cwd,
|
|
1575
|
-
agentName: agentInfo?.title ?? agentInfo?.name,
|
|
1576
|
-
modelId,
|
|
1577
|
-
modelName,
|
|
1578
|
-
modeId,
|
|
1579
|
-
modeName,
|
|
1580
|
-
availableModes,
|
|
1581
|
-
availableModels,
|
|
1582
|
-
availableCommands: void 0
|
|
1583
|
-
};
|
|
1584
|
-
this.setupSessionSubscriptions(record);
|
|
1585
|
-
this.sessions.set(sessionId, record);
|
|
1586
|
-
const summary = this.buildSummary(record);
|
|
1587
|
-
this.emitSessionsChanged({
|
|
1588
|
-
added: [summary],
|
|
1589
|
-
updated: [],
|
|
1590
|
-
removed: []
|
|
1591
|
-
});
|
|
1592
|
-
logger.info({ sessionId, backendId: backend.id }, "session_resumed");
|
|
1593
|
-
return summary;
|
|
1594
|
-
} catch (error) {
|
|
1595
|
-
await connection.disconnect();
|
|
1596
|
-
throw error;
|
|
1597
1643
|
}
|
|
1598
1644
|
}
|
|
1599
1645
|
/**
|
|
1600
1646
|
* Set up event subscriptions for a session record.
|
|
1601
1647
|
*/
|
|
1602
|
-
setupSessionSubscriptions(record) {
|
|
1648
|
+
setupSessionSubscriptions(record, options) {
|
|
1603
1649
|
const { sessionId, connection } = record;
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
record.modeId = update.currentModeId;
|
|
1611
|
-
record.modeName = record.availableModes?.find(
|
|
1612
|
-
(mode) => mode.id === update.currentModeId
|
|
1613
|
-
)?.name ?? record.modeName;
|
|
1614
|
-
return;
|
|
1615
|
-
}
|
|
1616
|
-
if (update.sessionUpdate === "session_info_update") {
|
|
1617
|
-
if (typeof update.title === "string") {
|
|
1618
|
-
record.title = update.title;
|
|
1619
|
-
}
|
|
1620
|
-
if (typeof update.updatedAt === "string") {
|
|
1621
|
-
record.updatedAt = new Date(update.updatedAt);
|
|
1622
|
-
}
|
|
1623
|
-
}
|
|
1624
|
-
if (update.sessionUpdate === "available_commands_update") {
|
|
1625
|
-
if (update.availableCommands) {
|
|
1626
|
-
record.availableCommands = update.availableCommands;
|
|
1627
|
-
}
|
|
1650
|
+
if (!options?.skipSessionUpdates) {
|
|
1651
|
+
record.unsubscribe = connection.onSessionUpdate(
|
|
1652
|
+
(notification) => {
|
|
1653
|
+
record.updatedAt = /* @__PURE__ */ new Date();
|
|
1654
|
+
this.sessionUpdateEmitter.emit("update", notification);
|
|
1655
|
+
this.applySessionUpdateToRecord(record, notification);
|
|
1628
1656
|
}
|
|
1629
|
-
|
|
1630
|
-
|
|
1657
|
+
);
|
|
1658
|
+
}
|
|
1631
1659
|
record.unsubscribeTerminal = connection.onTerminalOutput((event) => {
|
|
1632
1660
|
this.terminalOutputEmitter.emit("output", event);
|
|
1633
1661
|
});
|
|
@@ -1637,6 +1665,7 @@ var SessionManager = class {
|
|
|
1637
1665
|
sessionId,
|
|
1638
1666
|
error: status.error
|
|
1639
1667
|
});
|
|
1668
|
+
this.emitSessionDetached(sessionId, "agent_exit");
|
|
1640
1669
|
}
|
|
1641
1670
|
});
|
|
1642
1671
|
}
|
|
@@ -1647,7 +1676,6 @@ var SessionManager = class {
|
|
|
1647
1676
|
title: record.title,
|
|
1648
1677
|
backendId: record.backendId,
|
|
1649
1678
|
backendLabel: record.backendLabel,
|
|
1650
|
-
state: status.state,
|
|
1651
1679
|
error: status.error,
|
|
1652
1680
|
pid: status.pid,
|
|
1653
1681
|
createdAt: record.createdAt.toISOString(),
|
|
@@ -1667,11 +1695,189 @@ var SessionManager = class {
|
|
|
1667
1695
|
|
|
1668
1696
|
// src/daemon/socket-client.ts
|
|
1669
1697
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
1670
|
-
import
|
|
1698
|
+
import fs4 from "fs/promises";
|
|
1671
1699
|
import { homedir } from "os";
|
|
1672
|
-
import
|
|
1700
|
+
import path5 from "path";
|
|
1673
1701
|
import ignore from "ignore";
|
|
1674
1702
|
import { io } from "socket.io-client";
|
|
1703
|
+
|
|
1704
|
+
// src/lib/git-utils.ts
|
|
1705
|
+
import { exec } from "child_process";
|
|
1706
|
+
import path4 from "path";
|
|
1707
|
+
import { promisify } from "util";
|
|
1708
|
+
var execAsync = promisify(exec);
|
|
1709
|
+
var MAX_BUFFER = 10 * 1024 * 1024;
|
|
1710
|
+
async function isGitRepo(cwd) {
|
|
1711
|
+
try {
|
|
1712
|
+
await execAsync("git rev-parse --is-inside-work-tree", {
|
|
1713
|
+
cwd,
|
|
1714
|
+
maxBuffer: MAX_BUFFER
|
|
1715
|
+
});
|
|
1716
|
+
return true;
|
|
1717
|
+
} catch {
|
|
1718
|
+
return false;
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
async function getGitBranch(cwd) {
|
|
1722
|
+
try {
|
|
1723
|
+
const { stdout } = await execAsync("git branch --show-current", {
|
|
1724
|
+
cwd,
|
|
1725
|
+
maxBuffer: MAX_BUFFER
|
|
1726
|
+
});
|
|
1727
|
+
const branch = stdout.trim();
|
|
1728
|
+
if (branch) {
|
|
1729
|
+
return branch;
|
|
1730
|
+
}
|
|
1731
|
+
const { stdout: hashOut } = await execAsync("git rev-parse --short HEAD", {
|
|
1732
|
+
cwd,
|
|
1733
|
+
maxBuffer: MAX_BUFFER
|
|
1734
|
+
});
|
|
1735
|
+
return hashOut.trim() || void 0;
|
|
1736
|
+
} catch {
|
|
1737
|
+
return void 0;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
function parseGitStatus(output) {
|
|
1741
|
+
const files = [];
|
|
1742
|
+
const lines = output.split("\n").filter((line) => line.length > 0);
|
|
1743
|
+
for (const line of lines) {
|
|
1744
|
+
const indexStatus = line[0];
|
|
1745
|
+
const workTreeStatus = line[1];
|
|
1746
|
+
const filePath = line.slice(3).split(" -> ").pop()?.trim();
|
|
1747
|
+
if (!filePath) {
|
|
1748
|
+
continue;
|
|
1749
|
+
}
|
|
1750
|
+
let status;
|
|
1751
|
+
if (indexStatus === "?" || workTreeStatus === "?") {
|
|
1752
|
+
status = "?";
|
|
1753
|
+
} else if (indexStatus === "!" || workTreeStatus === "!") {
|
|
1754
|
+
status = "!";
|
|
1755
|
+
} else if (indexStatus === "A" || workTreeStatus === "A") {
|
|
1756
|
+
status = "A";
|
|
1757
|
+
} else if (indexStatus === "D" || workTreeStatus === "D") {
|
|
1758
|
+
status = "D";
|
|
1759
|
+
} else if (indexStatus === "R" || workTreeStatus === "R") {
|
|
1760
|
+
status = "R";
|
|
1761
|
+
} else if (indexStatus === "C" || workTreeStatus === "C") {
|
|
1762
|
+
status = "C";
|
|
1763
|
+
} else if (indexStatus === "U" || workTreeStatus === "U") {
|
|
1764
|
+
status = "U";
|
|
1765
|
+
} else if (indexStatus === "M" || workTreeStatus === "M" || indexStatus !== " " || workTreeStatus !== " ") {
|
|
1766
|
+
status = "M";
|
|
1767
|
+
} else {
|
|
1768
|
+
continue;
|
|
1769
|
+
}
|
|
1770
|
+
files.push({ path: filePath, status });
|
|
1771
|
+
}
|
|
1772
|
+
return files;
|
|
1773
|
+
}
|
|
1774
|
+
async function getGitStatus(cwd) {
|
|
1775
|
+
try {
|
|
1776
|
+
const { stdout } = await execAsync("git status --porcelain=v1", {
|
|
1777
|
+
cwd,
|
|
1778
|
+
maxBuffer: MAX_BUFFER
|
|
1779
|
+
});
|
|
1780
|
+
return parseGitStatus(stdout);
|
|
1781
|
+
} catch {
|
|
1782
|
+
return [];
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
function aggregateDirStatus(files) {
|
|
1786
|
+
const dirStatus = {};
|
|
1787
|
+
const statusPriority = {
|
|
1788
|
+
A: 7,
|
|
1789
|
+
D: 6,
|
|
1790
|
+
M: 5,
|
|
1791
|
+
R: 4,
|
|
1792
|
+
C: 3,
|
|
1793
|
+
U: 2,
|
|
1794
|
+
"?": 1,
|
|
1795
|
+
"!": 0
|
|
1796
|
+
};
|
|
1797
|
+
for (const file of files) {
|
|
1798
|
+
const parts = file.path.split("/");
|
|
1799
|
+
let currentPath = "";
|
|
1800
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
1801
|
+
currentPath = currentPath ? `${currentPath}/${parts[i]}` : parts[i];
|
|
1802
|
+
const existing = dirStatus[currentPath];
|
|
1803
|
+
if (!existing || statusPriority[file.status] > statusPriority[existing]) {
|
|
1804
|
+
dirStatus[currentPath] = file.status;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
return dirStatus;
|
|
1809
|
+
}
|
|
1810
|
+
function parseDiffOutput(diffOutput) {
|
|
1811
|
+
const addedLines = [];
|
|
1812
|
+
const modifiedLines = [];
|
|
1813
|
+
const deletedLines = [];
|
|
1814
|
+
const lines = diffOutput.split("\n");
|
|
1815
|
+
let currentLine = 0;
|
|
1816
|
+
let inHunk = false;
|
|
1817
|
+
let pendingDeletionLine = 0;
|
|
1818
|
+
for (const line of lines) {
|
|
1819
|
+
const hunkMatch = line.match(/^@@\s+-\d+(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@/);
|
|
1820
|
+
if (hunkMatch) {
|
|
1821
|
+
currentLine = Number.parseInt(hunkMatch[1], 10);
|
|
1822
|
+
inHunk = true;
|
|
1823
|
+
pendingDeletionLine = 0;
|
|
1824
|
+
continue;
|
|
1825
|
+
}
|
|
1826
|
+
if (!inHunk) {
|
|
1827
|
+
continue;
|
|
1828
|
+
}
|
|
1829
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
1830
|
+
addedLines.push(currentLine);
|
|
1831
|
+
currentLine++;
|
|
1832
|
+
pendingDeletionLine = 0;
|
|
1833
|
+
} else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
1834
|
+
if (pendingDeletionLine === 0) {
|
|
1835
|
+
pendingDeletionLine = currentLine;
|
|
1836
|
+
}
|
|
1837
|
+
const deletionPos = Math.max(1, currentLine);
|
|
1838
|
+
if (!deletedLines.includes(deletionPos)) {
|
|
1839
|
+
deletedLines.push(deletionPos);
|
|
1840
|
+
}
|
|
1841
|
+
} else if (!line.startsWith("\\")) {
|
|
1842
|
+
currentLine++;
|
|
1843
|
+
pendingDeletionLine = 0;
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
return { addedLines, modifiedLines, deletedLines };
|
|
1847
|
+
}
|
|
1848
|
+
async function getFileDiff(cwd, filePath) {
|
|
1849
|
+
try {
|
|
1850
|
+
const relativePath = path4.isAbsolute(filePath) ? path4.relative(cwd, filePath) : filePath;
|
|
1851
|
+
const { stdout } = await execAsync(`git diff HEAD -- "${relativePath}"`, {
|
|
1852
|
+
cwd,
|
|
1853
|
+
maxBuffer: MAX_BUFFER
|
|
1854
|
+
});
|
|
1855
|
+
if (!stdout.trim()) {
|
|
1856
|
+
const { stdout: statusOut } = await execAsync(
|
|
1857
|
+
`git status --porcelain=v1 -- "${relativePath}"`,
|
|
1858
|
+
{ cwd, maxBuffer: MAX_BUFFER }
|
|
1859
|
+
);
|
|
1860
|
+
if (statusOut.startsWith("?") || statusOut.startsWith("A")) {
|
|
1861
|
+
const { stdout: wcOut } = await execAsync(`wc -l < "${relativePath}"`, {
|
|
1862
|
+
cwd,
|
|
1863
|
+
maxBuffer: MAX_BUFFER
|
|
1864
|
+
});
|
|
1865
|
+
const lineCount = Number.parseInt(wcOut.trim(), 10) || 0;
|
|
1866
|
+
return {
|
|
1867
|
+
addedLines: Array.from({ length: lineCount }, (_, i) => i + 1),
|
|
1868
|
+
modifiedLines: [],
|
|
1869
|
+
deletedLines: []
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
return { addedLines: [], modifiedLines: [], deletedLines: [] };
|
|
1873
|
+
}
|
|
1874
|
+
return parseDiffOutput(stdout);
|
|
1875
|
+
} catch {
|
|
1876
|
+
return { addedLines: [], modifiedLines: [], deletedLines: [] };
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
// src/daemon/socket-client.ts
|
|
1675
1881
|
var SESSION_ROOT_NAME = "Working Directory";
|
|
1676
1882
|
var MAX_RESOURCE_FILES = 2e3;
|
|
1677
1883
|
var DEFAULT_IGNORES = [
|
|
@@ -1691,15 +1897,15 @@ var DEFAULT_IGNORES = [
|
|
|
1691
1897
|
var loadGitignore = async (rootPath) => {
|
|
1692
1898
|
const ig = ignore().add(DEFAULT_IGNORES);
|
|
1693
1899
|
try {
|
|
1694
|
-
const gitignorePath =
|
|
1695
|
-
const content = await
|
|
1900
|
+
const gitignorePath = path5.join(rootPath, ".gitignore");
|
|
1901
|
+
const content = await fs4.readFile(gitignorePath, "utf8");
|
|
1696
1902
|
ig.add(content);
|
|
1697
1903
|
} catch {
|
|
1698
1904
|
}
|
|
1699
1905
|
return ig;
|
|
1700
1906
|
};
|
|
1701
1907
|
var resolveImageMimeType = (filePath) => {
|
|
1702
|
-
const extension =
|
|
1908
|
+
const extension = path5.extname(filePath).toLowerCase();
|
|
1703
1909
|
switch (extension) {
|
|
1704
1910
|
case ".apng":
|
|
1705
1911
|
return "image/apng";
|
|
@@ -1722,14 +1928,14 @@ var resolveImageMimeType = (filePath) => {
|
|
|
1722
1928
|
}
|
|
1723
1929
|
};
|
|
1724
1930
|
var readDirectoryEntries = async (dirPath) => {
|
|
1725
|
-
const entries = await
|
|
1931
|
+
const entries = await fs4.readdir(dirPath, { withFileTypes: true });
|
|
1726
1932
|
const resolvedEntries = await Promise.all(
|
|
1727
1933
|
entries.map(async (entry) => {
|
|
1728
|
-
const entryPath =
|
|
1934
|
+
const entryPath = path5.join(dirPath, entry.name);
|
|
1729
1935
|
let isDirectory = entry.isDirectory();
|
|
1730
1936
|
if (!isDirectory && entry.isSymbolicLink()) {
|
|
1731
1937
|
try {
|
|
1732
|
-
const stats = await
|
|
1938
|
+
const stats = await fs4.stat(entryPath);
|
|
1733
1939
|
isDirectory = stats.isDirectory();
|
|
1734
1940
|
} catch {
|
|
1735
1941
|
}
|
|
@@ -1813,14 +2019,24 @@ var SocketClient = class extends EventEmitter3 {
|
|
|
1813
2019
|
this.socket.on("cli:registered", async (info) => {
|
|
1814
2020
|
logger.info({ machineId: info.machineId }, "gateway_registered");
|
|
1815
2021
|
try {
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
2022
|
+
let cursor;
|
|
2023
|
+
let page = 0;
|
|
2024
|
+
do {
|
|
2025
|
+
const { sessions, capabilities, nextCursor } = await this.options.sessionManager.discoverSessions({ cursor });
|
|
2026
|
+
cursor = nextCursor;
|
|
2027
|
+
if (sessions.length > 0) {
|
|
2028
|
+
this.socket.emit("sessions:discovered", {
|
|
2029
|
+
sessions,
|
|
2030
|
+
capabilities,
|
|
2031
|
+
nextCursor
|
|
2032
|
+
});
|
|
2033
|
+
logger.info(
|
|
2034
|
+
{ count: sessions.length, capabilities, page },
|
|
2035
|
+
"historical_sessions_discovered"
|
|
2036
|
+
);
|
|
2037
|
+
}
|
|
2038
|
+
page += 1;
|
|
2039
|
+
} while (cursor);
|
|
1824
2040
|
} catch (error) {
|
|
1825
2041
|
logger.warn({ err: error }, "session_discovery_failed");
|
|
1826
2042
|
}
|
|
@@ -2115,7 +2331,7 @@ var SocketClient = class extends EventEmitter3 {
|
|
|
2115
2331
|
if (!record || !record.cwd) {
|
|
2116
2332
|
throw new Error("Session not found or no working directory");
|
|
2117
2333
|
}
|
|
2118
|
-
const resolved = requestPath ?
|
|
2334
|
+
const resolved = requestPath ? path5.isAbsolute(requestPath) ? requestPath : path5.join(record.cwd, requestPath) : record.cwd;
|
|
2119
2335
|
const entries = await readDirectoryEntries(resolved);
|
|
2120
2336
|
this.sendRpcResponse(request.requestId, { path: resolved, entries });
|
|
2121
2337
|
} catch (error) {
|
|
@@ -2141,10 +2357,10 @@ var SocketClient = class extends EventEmitter3 {
|
|
|
2141
2357
|
if (!record || !record.cwd) {
|
|
2142
2358
|
throw new Error("Session not found or no working directory");
|
|
2143
2359
|
}
|
|
2144
|
-
const resolved =
|
|
2360
|
+
const resolved = path5.isAbsolute(requestPath) ? requestPath : path5.join(record.cwd, requestPath);
|
|
2145
2361
|
const mimeType = resolveImageMimeType(resolved);
|
|
2146
2362
|
if (mimeType) {
|
|
2147
|
-
const buffer = await
|
|
2363
|
+
const buffer = await fs4.readFile(resolved);
|
|
2148
2364
|
const preview2 = {
|
|
2149
2365
|
path: resolved,
|
|
2150
2366
|
previewType: "image",
|
|
@@ -2154,7 +2370,7 @@ var SocketClient = class extends EventEmitter3 {
|
|
|
2154
2370
|
this.sendRpcResponse(request.requestId, preview2);
|
|
2155
2371
|
return;
|
|
2156
2372
|
}
|
|
2157
|
-
const content = await
|
|
2373
|
+
const content = await fs4.readFile(resolved, "utf8");
|
|
2158
2374
|
const preview = {
|
|
2159
2375
|
path: resolved,
|
|
2160
2376
|
previewType: "code",
|
|
@@ -2203,14 +2419,15 @@ var SocketClient = class extends EventEmitter3 {
|
|
|
2203
2419
|
});
|
|
2204
2420
|
this.socket.on("rpc:sessions:discover", async (request) => {
|
|
2205
2421
|
try {
|
|
2206
|
-
const { cwd, backendId } = request.params;
|
|
2422
|
+
const { cwd, backendId, cursor } = request.params;
|
|
2207
2423
|
logger.info(
|
|
2208
|
-
{ requestId: request.requestId, cwd, backendId },
|
|
2424
|
+
{ requestId: request.requestId, cwd, backendId, cursor },
|
|
2209
2425
|
"rpc_sessions_discover"
|
|
2210
2426
|
);
|
|
2211
2427
|
const result = await sessionManager.discoverSessions({
|
|
2212
2428
|
cwd,
|
|
2213
|
-
backendId
|
|
2429
|
+
backendId,
|
|
2430
|
+
cursor
|
|
2214
2431
|
});
|
|
2215
2432
|
this.sendRpcResponse(request.requestId, result);
|
|
2216
2433
|
} catch (error) {
|
|
@@ -2245,14 +2462,14 @@ var SocketClient = class extends EventEmitter3 {
|
|
|
2245
2462
|
this.sendRpcError(request.requestId, error);
|
|
2246
2463
|
}
|
|
2247
2464
|
});
|
|
2248
|
-
this.socket.on("rpc:session:
|
|
2465
|
+
this.socket.on("rpc:session:reload", async (request) => {
|
|
2249
2466
|
try {
|
|
2250
2467
|
const { sessionId, cwd } = request.params;
|
|
2251
2468
|
logger.info(
|
|
2252
2469
|
{ requestId: request.requestId, sessionId, cwd },
|
|
2253
|
-
"
|
|
2470
|
+
"rpc_session_reload"
|
|
2254
2471
|
);
|
|
2255
|
-
const session = await sessionManager.
|
|
2472
|
+
const session = await sessionManager.reloadSession(sessionId, cwd);
|
|
2256
2473
|
this.sendRpcResponse(request.requestId, session);
|
|
2257
2474
|
} catch (error) {
|
|
2258
2475
|
logger.error(
|
|
@@ -2261,7 +2478,93 @@ var SocketClient = class extends EventEmitter3 {
|
|
|
2261
2478
|
requestId: request.requestId,
|
|
2262
2479
|
sessionId: request.params.sessionId
|
|
2263
2480
|
},
|
|
2264
|
-
"
|
|
2481
|
+
"rpc_session_reload_error"
|
|
2482
|
+
);
|
|
2483
|
+
this.sendRpcError(request.requestId, error);
|
|
2484
|
+
}
|
|
2485
|
+
});
|
|
2486
|
+
this.socket.on("rpc:git:status", async (request) => {
|
|
2487
|
+
try {
|
|
2488
|
+
const { sessionId } = request.params;
|
|
2489
|
+
logger.debug(
|
|
2490
|
+
{ requestId: request.requestId, sessionId },
|
|
2491
|
+
"rpc_git_status"
|
|
2492
|
+
);
|
|
2493
|
+
const record = sessionManager.getSession(sessionId);
|
|
2494
|
+
if (!record || !record.cwd) {
|
|
2495
|
+
throw new Error("Session not found or no working directory");
|
|
2496
|
+
}
|
|
2497
|
+
const isRepo = await isGitRepo(record.cwd);
|
|
2498
|
+
if (!isRepo) {
|
|
2499
|
+
this.sendRpcResponse(request.requestId, {
|
|
2500
|
+
isGitRepo: false,
|
|
2501
|
+
files: [],
|
|
2502
|
+
dirStatus: {}
|
|
2503
|
+
});
|
|
2504
|
+
return;
|
|
2505
|
+
}
|
|
2506
|
+
const [branch, files] = await Promise.all([
|
|
2507
|
+
getGitBranch(record.cwd),
|
|
2508
|
+
getGitStatus(record.cwd)
|
|
2509
|
+
]);
|
|
2510
|
+
const dirStatus = aggregateDirStatus(files);
|
|
2511
|
+
this.sendRpcResponse(request.requestId, {
|
|
2512
|
+
isGitRepo: true,
|
|
2513
|
+
branch,
|
|
2514
|
+
files,
|
|
2515
|
+
dirStatus
|
|
2516
|
+
});
|
|
2517
|
+
} catch (error) {
|
|
2518
|
+
logger.error(
|
|
2519
|
+
{
|
|
2520
|
+
err: error,
|
|
2521
|
+
requestId: request.requestId,
|
|
2522
|
+
sessionId: request.params.sessionId
|
|
2523
|
+
},
|
|
2524
|
+
"rpc_git_status_error"
|
|
2525
|
+
);
|
|
2526
|
+
this.sendRpcError(request.requestId, error);
|
|
2527
|
+
}
|
|
2528
|
+
});
|
|
2529
|
+
this.socket.on("rpc:git:fileDiff", async (request) => {
|
|
2530
|
+
try {
|
|
2531
|
+
const { sessionId, path: filePath } = request.params;
|
|
2532
|
+
logger.debug(
|
|
2533
|
+
{ requestId: request.requestId, sessionId, path: filePath },
|
|
2534
|
+
"rpc_git_file_diff"
|
|
2535
|
+
);
|
|
2536
|
+
const record = sessionManager.getSession(sessionId);
|
|
2537
|
+
if (!record || !record.cwd) {
|
|
2538
|
+
throw new Error("Session not found or no working directory");
|
|
2539
|
+
}
|
|
2540
|
+
const isRepo = await isGitRepo(record.cwd);
|
|
2541
|
+
if (!isRepo) {
|
|
2542
|
+
this.sendRpcResponse(request.requestId, {
|
|
2543
|
+
isGitRepo: false,
|
|
2544
|
+
path: filePath,
|
|
2545
|
+
addedLines: [],
|
|
2546
|
+
modifiedLines: []
|
|
2547
|
+
});
|
|
2548
|
+
return;
|
|
2549
|
+
}
|
|
2550
|
+
const { addedLines, modifiedLines } = await getFileDiff(
|
|
2551
|
+
record.cwd,
|
|
2552
|
+
filePath
|
|
2553
|
+
);
|
|
2554
|
+
this.sendRpcResponse(request.requestId, {
|
|
2555
|
+
isGitRepo: true,
|
|
2556
|
+
path: filePath,
|
|
2557
|
+
addedLines,
|
|
2558
|
+
modifiedLines
|
|
2559
|
+
});
|
|
2560
|
+
} catch (error) {
|
|
2561
|
+
logger.error(
|
|
2562
|
+
{
|
|
2563
|
+
err: error,
|
|
2564
|
+
requestId: request.requestId,
|
|
2565
|
+
sessionId: request.params.sessionId
|
|
2566
|
+
},
|
|
2567
|
+
"rpc_git_file_diff_error"
|
|
2265
2568
|
);
|
|
2266
2569
|
this.sendRpcError(request.requestId, error);
|
|
2267
2570
|
}
|
|
@@ -2310,13 +2613,23 @@ var SocketClient = class extends EventEmitter3 {
|
|
|
2310
2613
|
this.socket.emit("sessions:changed", payload);
|
|
2311
2614
|
}
|
|
2312
2615
|
});
|
|
2616
|
+
sessionManager.onSessionAttached((payload) => {
|
|
2617
|
+
if (this.connected) {
|
|
2618
|
+
this.socket.emit("session:attached", payload);
|
|
2619
|
+
}
|
|
2620
|
+
});
|
|
2621
|
+
sessionManager.onSessionDetached((payload) => {
|
|
2622
|
+
if (this.connected) {
|
|
2623
|
+
this.socket.emit("session:detached", payload);
|
|
2624
|
+
}
|
|
2625
|
+
});
|
|
2313
2626
|
}
|
|
2314
2627
|
async listSessionResources(rootPath) {
|
|
2315
2628
|
const ig = await loadGitignore(rootPath);
|
|
2316
2629
|
const allFiles = await this.listAllFiles(rootPath, ig, rootPath, []);
|
|
2317
2630
|
return allFiles.map((filePath) => ({
|
|
2318
|
-
name:
|
|
2319
|
-
relativePath:
|
|
2631
|
+
name: path5.basename(filePath),
|
|
2632
|
+
relativePath: path5.relative(rootPath, filePath),
|
|
2320
2633
|
path: filePath
|
|
2321
2634
|
}));
|
|
2322
2635
|
}
|
|
@@ -2324,13 +2637,13 @@ var SocketClient = class extends EventEmitter3 {
|
|
|
2324
2637
|
if (collected.length >= MAX_RESOURCE_FILES) {
|
|
2325
2638
|
return collected;
|
|
2326
2639
|
}
|
|
2327
|
-
const entries = await
|
|
2640
|
+
const entries = await fs4.readdir(rootPath, { withFileTypes: true });
|
|
2328
2641
|
for (const entry of entries) {
|
|
2329
2642
|
if (collected.length >= MAX_RESOURCE_FILES) {
|
|
2330
2643
|
break;
|
|
2331
2644
|
}
|
|
2332
|
-
const entryPath =
|
|
2333
|
-
const relativePath =
|
|
2645
|
+
const entryPath = path5.join(rootPath, entry.name);
|
|
2646
|
+
const relativePath = path5.relative(baseDir, entryPath);
|
|
2334
2647
|
const checkPath = entry.isDirectory() ? `${relativePath}/` : relativePath;
|
|
2335
2648
|
if (ig.ignores(checkPath)) {
|
|
2336
2649
|
continue;
|
|
@@ -2424,12 +2737,12 @@ var DaemonManager = class {
|
|
|
2424
2737
|
this.config = config;
|
|
2425
2738
|
}
|
|
2426
2739
|
async ensureHomeDirectory() {
|
|
2427
|
-
await
|
|
2428
|
-
await
|
|
2740
|
+
await fs5.mkdir(this.config.homePath, { recursive: true });
|
|
2741
|
+
await fs5.mkdir(this.config.logPath, { recursive: true });
|
|
2429
2742
|
}
|
|
2430
2743
|
async getPid() {
|
|
2431
2744
|
try {
|
|
2432
|
-
const content = await
|
|
2745
|
+
const content = await fs5.readFile(this.config.pidFile, "utf8");
|
|
2433
2746
|
const pid = Number.parseInt(content.trim(), 10);
|
|
2434
2747
|
if (Number.isNaN(pid)) {
|
|
2435
2748
|
return null;
|
|
@@ -2446,11 +2759,11 @@ var DaemonManager = class {
|
|
|
2446
2759
|
}
|
|
2447
2760
|
}
|
|
2448
2761
|
async writePidFile(pid) {
|
|
2449
|
-
await
|
|
2762
|
+
await fs5.writeFile(this.config.pidFile, String(pid), "utf8");
|
|
2450
2763
|
}
|
|
2451
2764
|
async removePidFile() {
|
|
2452
2765
|
try {
|
|
2453
|
-
await
|
|
2766
|
+
await fs5.unlink(this.config.pidFile);
|
|
2454
2767
|
} catch {
|
|
2455
2768
|
}
|
|
2456
2769
|
}
|
|
@@ -2517,7 +2830,7 @@ var DaemonManager = class {
|
|
|
2517
2830
|
}
|
|
2518
2831
|
}
|
|
2519
2832
|
async spawnBackground() {
|
|
2520
|
-
const logFile =
|
|
2833
|
+
const logFile = path6.join(
|
|
2521
2834
|
this.config.logPath,
|
|
2522
2835
|
`${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-daemon.log`
|
|
2523
2836
|
);
|
|
@@ -2535,7 +2848,7 @@ var DaemonManager = class {
|
|
|
2535
2848
|
logger.error("daemon_spawn_failed");
|
|
2536
2849
|
throw new Error("Failed to spawn daemon process");
|
|
2537
2850
|
}
|
|
2538
|
-
const logStream = await
|
|
2851
|
+
const logStream = await fs5.open(logFile, "a");
|
|
2539
2852
|
const fileHandle = logStream;
|
|
2540
2853
|
child.stdout?.on("data", (data) => {
|
|
2541
2854
|
fileHandle.write(`[stdout] ${data.toString()}`).catch(() => {
|
|
@@ -2611,14 +2924,14 @@ var DaemonManager = class {
|
|
|
2611
2924
|
});
|
|
2612
2925
|
}
|
|
2613
2926
|
async logs(options) {
|
|
2614
|
-
const files = await
|
|
2927
|
+
const files = await fs5.readdir(this.config.logPath);
|
|
2615
2928
|
const logFiles = files.filter((f) => f.endsWith("-daemon.log")).sort().reverse();
|
|
2616
2929
|
if (logFiles.length === 0) {
|
|
2617
2930
|
logger.warn("daemon_logs_empty");
|
|
2618
2931
|
console.log("No log files found");
|
|
2619
2932
|
return;
|
|
2620
2933
|
}
|
|
2621
|
-
const latestLog =
|
|
2934
|
+
const latestLog = path6.join(this.config.logPath, logFiles[0]);
|
|
2622
2935
|
logger.info({ logFile: latestLog }, "daemon_logs_latest");
|
|
2623
2936
|
console.log(`Log file: ${latestLog}
|
|
2624
2937
|
`);
|
|
@@ -2630,7 +2943,7 @@ var DaemonManager = class {
|
|
|
2630
2943
|
tail.on("close", () => resolve());
|
|
2631
2944
|
});
|
|
2632
2945
|
} else {
|
|
2633
|
-
const content = await
|
|
2946
|
+
const content = await fs5.readFile(latestLog, "utf8");
|
|
2634
2947
|
const lines = content.split("\n");
|
|
2635
2948
|
const count = options?.lines ?? 50;
|
|
2636
2949
|
console.log(lines.slice(-count).join("\n"));
|