@vibedeckx/linux-x64 0.3.15 → 0.3.17
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/bin.js +464 -55
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -186724,7 +186724,8 @@ var mapAgentSession = (row) => ({
|
|
|
186724
186724
|
last_user_message_at: row.last_user_message_at,
|
|
186725
186725
|
last_completed_at: row.last_completed_at,
|
|
186726
186726
|
favorited_at: row.favorited_at,
|
|
186727
|
-
native_session_id: row.native_session_id
|
|
186727
|
+
native_session_id: row.native_session_id,
|
|
186728
|
+
history_epoch: row.history_epoch
|
|
186728
186729
|
});
|
|
186729
186730
|
var parseActivityTimestamp = (value) => {
|
|
186730
186731
|
const explicitZone = /(?:Z|[+-]\d\d:\d\d)$/i.test(value);
|
|
@@ -187123,6 +187124,11 @@ var createAgentSessionRepos = (kdb, h) => ({
|
|
|
187123
187124
|
markCompleted: async (id, timestampMs) => {
|
|
187124
187125
|
await kdb.updateTable("agent_sessions").set({ last_completed_at: timestampMs, activity_at: sql`max(activity_at, ${timestampMs})` }).where("id", "=", id).execute();
|
|
187125
187126
|
},
|
|
187127
|
+
incrementHistoryEpoch: async (id) => {
|
|
187128
|
+
await kdb.updateTable("agent_sessions").set({ history_epoch: sql`history_epoch + 1` }).where("id", "=", id).execute();
|
|
187129
|
+
const row = await kdb.selectFrom("agent_sessions").select("history_epoch").where("id", "=", id).executeTakeFirstOrThrow();
|
|
187130
|
+
return row.history_epoch;
|
|
187131
|
+
},
|
|
187126
187132
|
delete: async (id) => {
|
|
187127
187133
|
await kdb.deleteFrom("agent_sessions").where("id", "=", id).execute();
|
|
187128
187134
|
},
|
|
@@ -203632,6 +203638,7 @@ var tightenWorkspaceCheckoutForeignKeys = (db) => {
|
|
|
203632
203638
|
last_completed_at INTEGER DEFAULT NULL,
|
|
203633
203639
|
favorited_at INTEGER DEFAULT NULL,
|
|
203634
203640
|
native_session_id TEXT DEFAULT NULL,
|
|
203641
|
+
history_epoch INTEGER NOT NULL DEFAULT 0,
|
|
203635
203642
|
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
|
|
203636
203643
|
FOREIGN KEY (workspace_checkout_id) REFERENCES workspace_checkouts(id)
|
|
203637
203644
|
DEFERRABLE INITIALLY DEFERRED
|
|
@@ -203639,10 +203646,10 @@ var tightenWorkspaceCheckoutForeignKeys = (db) => {
|
|
|
203639
203646
|
INSERT INTO agent_sessions_fk_new
|
|
203640
203647
|
(id, project_id, branch, workspace_checkout_id, status, permission_mode, agent_type,
|
|
203641
203648
|
title, model, created_at, updated_at, activity_at, last_user_message_at,
|
|
203642
|
-
last_completed_at, favorited_at, native_session_id)
|
|
203649
|
+
last_completed_at, favorited_at, native_session_id, history_epoch)
|
|
203643
203650
|
SELECT id, project_id, branch, workspace_checkout_id, status, permission_mode, agent_type,
|
|
203644
203651
|
title, model, created_at, updated_at, activity_at, last_user_message_at,
|
|
203645
|
-
last_completed_at, favorited_at, native_session_id
|
|
203652
|
+
last_completed_at, favorited_at, native_session_id, history_epoch
|
|
203646
203653
|
FROM agent_sessions;
|
|
203647
203654
|
DROP TABLE agent_sessions;
|
|
203648
203655
|
ALTER TABLE agent_sessions_fk_new RENAME TO agent_sessions;
|
|
@@ -203916,6 +203923,7 @@ var initializeSchema = (db) => {
|
|
|
203916
203923
|
-- Drives the workspace-status derivation; see plans/branch-activity-refactor.md.
|
|
203917
203924
|
last_user_message_at INTEGER DEFAULT NULL,
|
|
203918
203925
|
last_completed_at INTEGER DEFAULT NULL,
|
|
203926
|
+
history_epoch INTEGER NOT NULL DEFAULT 0,
|
|
203919
203927
|
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
|
203920
203928
|
);
|
|
203921
203929
|
|
|
@@ -204450,6 +204458,10 @@ var initializeSchema = (db) => {
|
|
|
204450
204458
|
if (!sessionInfoNativeId.some((col) => col.name === "native_session_id")) {
|
|
204451
204459
|
db.exec("ALTER TABLE agent_sessions ADD COLUMN native_session_id TEXT DEFAULT NULL");
|
|
204452
204460
|
}
|
|
204461
|
+
const sessionHistoryEpochInfo = db.prepare("PRAGMA table_info(agent_sessions)").all();
|
|
204462
|
+
if (!sessionHistoryEpochInfo.some((col) => col.name === "history_epoch")) {
|
|
204463
|
+
db.exec("ALTER TABLE agent_sessions ADD COLUMN history_epoch INTEGER NOT NULL DEFAULT 0");
|
|
204464
|
+
}
|
|
204453
204465
|
const sessionCheckoutInfo = db.prepare("PRAGMA table_info(agent_sessions)").all();
|
|
204454
204466
|
if (!sessionCheckoutInfo.some((col) => col.name === "workspace_checkout_id")) {
|
|
204455
204467
|
db.exec("ALTER TABLE agent_sessions ADD COLUMN workspace_checkout_id TEXT DEFAULT NULL");
|
|
@@ -207067,6 +207079,7 @@ function buildApprovalResponse(requestId, decision) {
|
|
|
207067
207079
|
|
|
207068
207080
|
// src/providers/codex-provider.ts
|
|
207069
207081
|
var ITEM_PAYLOAD_SUMMARY_LIMIT = 500;
|
|
207082
|
+
var MAX_COMPLETED_TURN_IDS = 32;
|
|
207070
207083
|
function firstText(...candidates) {
|
|
207071
207084
|
for (const c of candidates) {
|
|
207072
207085
|
if (typeof c === "string" && c.trim()) return c.trim();
|
|
@@ -207200,14 +207213,23 @@ var CodexProvider = class _CodexProvider {
|
|
|
207200
207213
|
handleItemCompleted(params, sessionId) {
|
|
207201
207214
|
const item = params?.item;
|
|
207202
207215
|
if (!item?.type) return [];
|
|
207203
|
-
|
|
207204
|
-
|
|
207205
|
-
|
|
207216
|
+
const state = this.getSessionState(sessionId);
|
|
207217
|
+
const itemTurnId = params?.turnId != null ? String(params.turnId) : null;
|
|
207218
|
+
const outOfTurn = itemTurnId !== null && state.completedTurnIds.has(itemTurnId);
|
|
207219
|
+
if (itemTurnId !== null && !outOfTurn) {
|
|
207220
|
+
state.currentTurnId = itemTurnId;
|
|
207221
|
+
}
|
|
207222
|
+
const events = this.itemEvents(item, itemTurnId, outOfTurn, state);
|
|
207223
|
+
if (!outOfTurn) return events;
|
|
207224
|
+
return events.map(
|
|
207225
|
+
(e) => e.type === "tool_use" || e.type === "tool_result" ? { ...e, outOfTurn: true } : e
|
|
207226
|
+
);
|
|
207227
|
+
}
|
|
207228
|
+
itemEvents(item, itemTurnId, outOfTurn, state) {
|
|
207206
207229
|
switch (item.type) {
|
|
207207
207230
|
case "agentMessage": {
|
|
207208
|
-
if (
|
|
207209
|
-
|
|
207210
|
-
state.turnsWithFinalMessage.add(String(params.turnId));
|
|
207231
|
+
if (itemTurnId && !outOfTurn && (item.phase === "final_answer" || item.text)) {
|
|
207232
|
+
state.turnsWithFinalMessage.add(itemTurnId);
|
|
207211
207233
|
}
|
|
207212
207234
|
return [{ type: "text", content: item.text ?? "" }];
|
|
207213
207235
|
}
|
|
@@ -207300,6 +207322,10 @@ var CodexProvider = class _CodexProvider {
|
|
|
207300
207322
|
const hadFinalMessage = turnId != null && state.turnsWithFinalMessage.has(turnId);
|
|
207301
207323
|
if (turnId != null) {
|
|
207302
207324
|
state.turnsWithFinalMessage.delete(turnId);
|
|
207325
|
+
state.completedTurnIds.add(turnId);
|
|
207326
|
+
while (state.completedTurnIds.size > MAX_COMPLETED_TURN_IDS) {
|
|
207327
|
+
state.completedTurnIds.delete(state.completedTurnIds.values().next().value);
|
|
207328
|
+
}
|
|
207303
207329
|
}
|
|
207304
207330
|
state.currentTurnId = null;
|
|
207305
207331
|
if (turn.status === "completed" && !hadFinalMessage) {
|
|
@@ -207424,7 +207450,8 @@ var CodexProvider = class _CodexProvider {
|
|
|
207424
207450
|
pendingTurnContent: null,
|
|
207425
207451
|
lastTokenUsage: {},
|
|
207426
207452
|
turnsWithFinalMessage: /* @__PURE__ */ new Set(),
|
|
207427
|
-
currentTurnId: null
|
|
207453
|
+
currentTurnId: null,
|
|
207454
|
+
completedTurnIds: /* @__PURE__ */ new Set()
|
|
207428
207455
|
});
|
|
207429
207456
|
}
|
|
207430
207457
|
onSessionDestroyed(sessionId) {
|
|
@@ -207443,7 +207470,8 @@ var CodexProvider = class _CodexProvider {
|
|
|
207443
207470
|
pendingTurnContent: null,
|
|
207444
207471
|
lastTokenUsage: {},
|
|
207445
207472
|
turnsWithFinalMessage: /* @__PURE__ */ new Set(),
|
|
207446
|
-
currentTurnId: null
|
|
207473
|
+
currentTurnId: null,
|
|
207474
|
+
completedTurnIds: /* @__PURE__ */ new Set()
|
|
207447
207475
|
};
|
|
207448
207476
|
this.sessions.set(sessionId, state);
|
|
207449
207477
|
}
|
|
@@ -229656,6 +229684,7 @@ var AgentSessionManager = class {
|
|
|
229656
229684
|
process: null,
|
|
229657
229685
|
dormant: false,
|
|
229658
229686
|
processStartsInFlight: 0,
|
|
229687
|
+
historyEpoch: stored?.history_epoch ?? 0,
|
|
229659
229688
|
store,
|
|
229660
229689
|
subscribers: /* @__PURE__ */ new Set(),
|
|
229661
229690
|
status: "running",
|
|
@@ -230001,14 +230030,15 @@ var AgentSessionManager = class {
|
|
|
230001
230030
|
if (!session) return;
|
|
230002
230031
|
const timestamp = Date.now();
|
|
230003
230032
|
session.lastActiveAt = timestamp;
|
|
230004
|
-
|
|
230033
|
+
const outOfTurn = (event.type === "tool_use" || event.type === "tool_result") && event.outOfTurn === true;
|
|
230034
|
+
if (!outOfTurn && (event.type === "turn_started" || event.type === "text" || event.type === "thinking" || event.type === "tool_use" || event.type === "tool_result" || event.type === "approval_request")) {
|
|
230005
230035
|
this.applyCompletionTimerAction(session, session.completion.noteTurnActivity());
|
|
230006
230036
|
}
|
|
230007
|
-
if (session.turnOpenSince === null && (event.type === "turn_started" || event.type === "text" || event.type === "thinking" || event.type === "tool_use" || event.type === "tool_result" || event.type === "approval_request")) {
|
|
230037
|
+
if (session.turnOpenSince === null && !outOfTurn && (event.type === "turn_started" || event.type === "text" || event.type === "thinking" || event.type === "tool_use" || event.type === "tool_result" || event.type === "approval_request")) {
|
|
230008
230038
|
session.turnOpenSince = timestamp;
|
|
230009
230039
|
session.turnDisposition = resolveNotificationDisposition(findLatestUserEntry(session.store.entries));
|
|
230010
230040
|
}
|
|
230011
|
-
if (session.status !== "running" && (event.type === "text" || event.type === "thinking" || event.type === "tool_use" || event.type === "tool_result" || event.type === "approval_request")) {
|
|
230041
|
+
if (session.status !== "running" && !outOfTurn && (event.type === "text" || event.type === "thinking" || event.type === "tool_use" || event.type === "tool_result" || event.type === "approval_request")) {
|
|
230012
230042
|
session.status = "running";
|
|
230013
230043
|
if (!session.skipDb) await this.storage.agentSessions.updateStatus(sessionId, "running");
|
|
230014
230044
|
this.broadcastPatch(sessionId, ConversationPatch.updateStatus("running"));
|
|
@@ -230497,17 +230527,29 @@ var AgentSessionManager = class {
|
|
|
230497
230527
|
/**
|
|
230498
230528
|
* Subscribe to session updates (WebSocket connection)
|
|
230499
230529
|
*/
|
|
230500
|
-
subscribe(sessionId, ws) {
|
|
230530
|
+
subscribe(sessionId, ws, opts = {}) {
|
|
230501
230531
|
const session = this.sessions.get(sessionId);
|
|
230502
230532
|
if (!session) {
|
|
230503
230533
|
return null;
|
|
230504
230534
|
}
|
|
230505
230535
|
session.subscribers.add(ws);
|
|
230536
|
+
const after = opts.historyEpoch === void 0 || opts.historyEpoch === session.historyEpoch ? opts.afterEntryIndex ?? -1 : -1;
|
|
230537
|
+
ws.send(JSON.stringify({
|
|
230538
|
+
HistorySync: {
|
|
230539
|
+
historyEpoch: session.historyEpoch,
|
|
230540
|
+
reset: opts.historyEpoch !== void 0 && opts.historyEpoch !== session.historyEpoch
|
|
230541
|
+
}
|
|
230542
|
+
}));
|
|
230506
230543
|
for (const patch of session.store.patches) {
|
|
230544
|
+
const entryIndices = patch.flatMap((op) => {
|
|
230545
|
+
const match2 = op.path.match(/^\/entries\/(\d+)$/);
|
|
230546
|
+
return match2 ? [Number(match2[1])] : [];
|
|
230547
|
+
});
|
|
230548
|
+
if (entryIndices.length > 0 && entryIndices.every((index) => index <= after)) continue;
|
|
230507
230549
|
const msg = { JsonPatch: patch };
|
|
230508
230550
|
ws.send(JSON.stringify(msg));
|
|
230509
230551
|
}
|
|
230510
|
-
ws.send(JSON.stringify({ Ready: true }));
|
|
230552
|
+
ws.send(JSON.stringify({ Ready: true, historyEpoch: session.historyEpoch }));
|
|
230511
230553
|
const statusPatch = ConversationPatch.updateStatus(session.status);
|
|
230512
230554
|
ws.send(JSON.stringify({ JsonPatch: statusPatch }));
|
|
230513
230555
|
return () => {
|
|
@@ -230525,6 +230567,9 @@ var AgentSessionManager = class {
|
|
|
230525
230567
|
getRawMessages(sessionId) {
|
|
230526
230568
|
return this.sessions.get(sessionId)?.store.entries ?? [];
|
|
230527
230569
|
}
|
|
230570
|
+
getHistoryEpoch(sessionId) {
|
|
230571
|
+
return this.sessions.get(sessionId)?.historyEpoch;
|
|
230572
|
+
}
|
|
230528
230573
|
/**
|
|
230529
230574
|
* Public wrapper over broadcastRaw for the WorkflowEngine: mirror a raw WS
|
|
230530
230575
|
* frame to a session's stream subscribers (a front server subscribed to
|
|
@@ -230796,6 +230841,9 @@ var AgentSessionManager = class {
|
|
|
230796
230841
|
this.emitProcessAlive(session, false);
|
|
230797
230842
|
if (!session.skipDb) {
|
|
230798
230843
|
await this.storage.agentSessions.deleteEntries(sessionId);
|
|
230844
|
+
session.historyEpoch = await this.storage.agentSessions.incrementHistoryEpoch(sessionId);
|
|
230845
|
+
} else {
|
|
230846
|
+
session.historyEpoch += 1;
|
|
230799
230847
|
}
|
|
230800
230848
|
session.store.patches = [];
|
|
230801
230849
|
session.store.entries = [];
|
|
@@ -230806,6 +230854,9 @@ var AgentSessionManager = class {
|
|
|
230806
230854
|
session.dormant = false;
|
|
230807
230855
|
session.turnOpenSince = null;
|
|
230808
230856
|
this.touchSession(session);
|
|
230857
|
+
this.broadcastRaw(sessionId, {
|
|
230858
|
+
HistorySync: { historyEpoch: session.historyEpoch, reset: true }
|
|
230859
|
+
});
|
|
230809
230860
|
const clearPatch = ConversationPatch.clearAll();
|
|
230810
230861
|
this.broadcastPatch(sessionId, clearPatch);
|
|
230811
230862
|
session.status = "running";
|
|
@@ -231289,6 +231340,7 @@ var AgentSessionManager = class {
|
|
|
231289
231340
|
process: null,
|
|
231290
231341
|
dormant: true,
|
|
231291
231342
|
processStartsInFlight: 0,
|
|
231343
|
+
historyEpoch: dbSession.history_epoch ?? 0,
|
|
231292
231344
|
store,
|
|
231293
231345
|
subscribers: /* @__PURE__ */ new Set(),
|
|
231294
231346
|
status: "stopped",
|
|
@@ -231480,6 +231532,7 @@ var AgentSessionManager = class {
|
|
|
231480
231532
|
process: null,
|
|
231481
231533
|
dormant: true,
|
|
231482
231534
|
processStartsInFlight: 0,
|
|
231535
|
+
historyEpoch: 0,
|
|
231483
231536
|
store,
|
|
231484
231537
|
subscribers: /* @__PURE__ */ new Set(),
|
|
231485
231538
|
status: "stopped",
|
|
@@ -232791,7 +232844,7 @@ async function persistRemoteSessionActivityFrame(storage, sessionId, remoteInfo,
|
|
|
232791
232844
|
}
|
|
232792
232845
|
return false;
|
|
232793
232846
|
}
|
|
232794
|
-
function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnectManager, eventBus, agentSessionManager, storage) {
|
|
232847
|
+
function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnectManager, eventBus, agentSessionManager, storage, requestedHistory) {
|
|
232795
232848
|
const hasCachedData = cache2.hasData(sessionId);
|
|
232796
232849
|
console.log(`[AgentWS] Opening persistent remote WS for ${sessionId} (cached=${hasCachedData})`);
|
|
232797
232850
|
if (!reverseConnectManager || !reverseConnectManager.isConnected(remoteInfo.remoteServerId)) {
|
|
@@ -232801,7 +232854,16 @@ function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnect
|
|
|
232801
232854
|
return;
|
|
232802
232855
|
}
|
|
232803
232856
|
const channelId = randomUUID2();
|
|
232804
|
-
const
|
|
232857
|
+
const cachedHead = cache2.get(sessionId);
|
|
232858
|
+
const upstreamEpoch = requestedHistory?.historyEpoch ?? cachedHead?.historyEpoch ?? void 0;
|
|
232859
|
+
const upstreamAfter = requestedHistory?.afterEntryIndex ?? (upstreamEpoch !== void 0 ? cachedHead?.lastTurnEndEntryIndex ?? void 0 : void 0);
|
|
232860
|
+
const upstreamParams = new URLSearchParams();
|
|
232861
|
+
if (upstreamEpoch !== void 0 && upstreamAfter !== void 0) {
|
|
232862
|
+
upstreamParams.set("after", String(upstreamAfter));
|
|
232863
|
+
upstreamParams.set("epoch", String(upstreamEpoch));
|
|
232864
|
+
}
|
|
232865
|
+
const usesBoundedReplay = upstreamParams.size > 0;
|
|
232866
|
+
const wsPath = `/api/agent-sessions/${encodeURIComponent(remoteInfo.remoteSessionId)}/stream${usesBoundedReplay ? `?${upstreamParams}` : ""}`;
|
|
232805
232867
|
const adapter = new VirtualWsAdapter(
|
|
232806
232868
|
(data) => reverseConnectManager.sendChannelData(remoteInfo.remoteServerId, channelId, data),
|
|
232807
232869
|
() => reverseConnectManager.closeChannel(remoteInfo.remoteServerId, channelId)
|
|
@@ -232835,6 +232897,7 @@ function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnect
|
|
|
232835
232897
|
cache2.broadcast(sessionId, raw);
|
|
232836
232898
|
const statusEvent = statusEventFromRemotePatch(parsed, sessionId, remoteInfo);
|
|
232837
232899
|
if (statusEvent) {
|
|
232900
|
+
cache2.setSessionStatus(sessionId, statusEvent.status);
|
|
232838
232901
|
let activityReady = true;
|
|
232839
232902
|
if (storage) {
|
|
232840
232903
|
activityReady = await persistRemoteSessionActivityFrame(storage, sessionId, remoteInfo, parsed).catch((error48) => {
|
|
@@ -232851,6 +232914,11 @@ function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnect
|
|
|
232851
232914
|
cache2.setFinished(sessionId);
|
|
232852
232915
|
cache2.broadcast(sessionId, raw);
|
|
232853
232916
|
} else if ("taskCompleted" in parsed) {
|
|
232917
|
+
cache2.setSessionStatus(sessionId, "stopped");
|
|
232918
|
+
const turnEndIndex = parsed.taskCompleted.turnEndEntryIndex;
|
|
232919
|
+
if (typeof turnEndIndex === "number" && Number.isInteger(turnEndIndex)) {
|
|
232920
|
+
cache2.setLastTurnEndEntryIndex(sessionId, turnEndIndex);
|
|
232921
|
+
}
|
|
232854
232922
|
cache2.appendMessage(sessionId, raw, false);
|
|
232855
232923
|
cache2.broadcast(sessionId, raw);
|
|
232856
232924
|
let activityReady = true;
|
|
@@ -232906,6 +232974,7 @@ function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnect
|
|
|
232906
232974
|
}
|
|
232907
232975
|
}
|
|
232908
232976
|
} else if ("error" in parsed) {
|
|
232977
|
+
cache2.setSessionStatus(sessionId, "error");
|
|
232909
232978
|
cache2.appendMessage(sessionId, raw, false);
|
|
232910
232979
|
cache2.broadcast(sessionId, raw);
|
|
232911
232980
|
let activityReady = true;
|
|
@@ -232928,6 +232997,15 @@ function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnect
|
|
|
232928
232997
|
cache2.setFinished(sessionId);
|
|
232929
232998
|
}
|
|
232930
232999
|
} else if ("Ready" in parsed) {
|
|
233000
|
+
const epoch = parsed.historyEpoch;
|
|
233001
|
+
if (typeof epoch === "number" && Number.isInteger(epoch)) cache2.setHistoryEpoch(sessionId, epoch);
|
|
233002
|
+
cache2.broadcast(sessionId, raw);
|
|
233003
|
+
} else if ("HistorySync" in parsed) {
|
|
233004
|
+
const sync = parsed.HistorySync;
|
|
233005
|
+
if (typeof sync.historyEpoch === "number" && Number.isInteger(sync.historyEpoch)) {
|
|
233006
|
+
if (sync.reset === true) cache2.resetHistory(sessionId, sync.historyEpoch);
|
|
233007
|
+
else cache2.setHistoryEpoch(sessionId, sync.historyEpoch);
|
|
233008
|
+
}
|
|
232931
233009
|
cache2.broadcast(sessionId, raw);
|
|
232932
233010
|
}
|
|
232933
233011
|
};
|
|
@@ -232966,8 +233044,12 @@ function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnect
|
|
|
232966
233044
|
syncing = false;
|
|
232967
233045
|
const currentEntry = cache2.get(sessionId);
|
|
232968
233046
|
const cachedSeq = entryPatchFrames(currentEntry.messages);
|
|
232969
|
-
const extendsCache = isSequencePrefix(cachedSeq, replayBuffer);
|
|
232970
|
-
if (
|
|
233047
|
+
const extendsCache = !usesBoundedReplay && isSequencePrefix(cachedSeq, replayBuffer);
|
|
233048
|
+
if (usesBoundedReplay) {
|
|
233049
|
+
cache2.replaceEntryTail(sessionId, upstreamAfter, replayBuffer);
|
|
233050
|
+
for (const msg of replayBuffer) cache2.broadcast(sessionId, msg);
|
|
233051
|
+
cache2.broadcast(sessionId, raw);
|
|
233052
|
+
} else if (extendsCache && replayBuffer.length > cachedSeq.length) {
|
|
232971
233053
|
const delta = replayBuffer.slice(cachedSeq.length);
|
|
232972
233054
|
console.log(`[AgentWS] Sync delta: ${delta.length} new entry patches for ${sessionId} (remote=${replayBuffer.length}, cached=${cachedSeq.length})`);
|
|
232973
233055
|
for (const msg of delta) {
|
|
@@ -239362,6 +239444,24 @@ var EventBus = class {
|
|
|
239362
239444
|
init_proxy_manager();
|
|
239363
239445
|
|
|
239364
239446
|
// src/remote-patch-cache.ts
|
|
239447
|
+
function patchEntryMetadata(raw) {
|
|
239448
|
+
let latest = null;
|
|
239449
|
+
let lastTurnEnd = null;
|
|
239450
|
+
try {
|
|
239451
|
+
const parsed = JSON.parse(raw);
|
|
239452
|
+
for (const op of parsed.JsonPatch ?? []) {
|
|
239453
|
+
const match2 = op.path?.match(/^\/entries\/(\d+)$/);
|
|
239454
|
+
if (!match2) continue;
|
|
239455
|
+
const index = Number(match2[1]);
|
|
239456
|
+
latest = Math.max(latest ?? -1, index);
|
|
239457
|
+
if (op.value?.type === "ENTRY" && op.value.content?.type === "turn_end") {
|
|
239458
|
+
lastTurnEnd = Math.max(lastTurnEnd ?? -1, index);
|
|
239459
|
+
}
|
|
239460
|
+
}
|
|
239461
|
+
} catch {
|
|
239462
|
+
}
|
|
239463
|
+
return { latest, lastTurnEnd };
|
|
239464
|
+
}
|
|
239365
239465
|
var RemotePatchCache = class {
|
|
239366
239466
|
cache = /* @__PURE__ */ new Map();
|
|
239367
239467
|
getOrCreate(sessionId) {
|
|
@@ -239375,7 +239475,11 @@ var RemotePatchCache = class {
|
|
|
239375
239475
|
subscribers: /* @__PURE__ */ new Set(),
|
|
239376
239476
|
reconnecting: false,
|
|
239377
239477
|
reconnectTimer: null,
|
|
239378
|
-
reconnectAttempt: 0
|
|
239478
|
+
reconnectAttempt: 0,
|
|
239479
|
+
historyEpoch: null,
|
|
239480
|
+
latestEntryIndex: null,
|
|
239481
|
+
lastTurnEndEntryIndex: null,
|
|
239482
|
+
sessionStatus: null
|
|
239379
239483
|
};
|
|
239380
239484
|
this.cache.set(sessionId, entry);
|
|
239381
239485
|
}
|
|
@@ -239398,6 +239502,11 @@ var RemotePatchCache = class {
|
|
|
239398
239502
|
entry.messages.push(raw);
|
|
239399
239503
|
if (isJsonPatch) {
|
|
239400
239504
|
entry.patchCount++;
|
|
239505
|
+
const metadata = patchEntryMetadata(raw);
|
|
239506
|
+
if (metadata.latest !== null) entry.latestEntryIndex = Math.max(entry.latestEntryIndex ?? -1, metadata.latest);
|
|
239507
|
+
if (metadata.lastTurnEnd !== null) {
|
|
239508
|
+
entry.lastTurnEndEntryIndex = Math.max(entry.lastTurnEndEntryIndex ?? -1, metadata.lastTurnEnd);
|
|
239509
|
+
}
|
|
239401
239510
|
}
|
|
239402
239511
|
}
|
|
239403
239512
|
/** Full cache replacement (used when cache is detected as stale). */
|
|
@@ -239408,6 +239517,15 @@ var RemotePatchCache = class {
|
|
|
239408
239517
|
const reconnecting = existing?.reconnecting ?? false;
|
|
239409
239518
|
const reconnectTimer = existing?.reconnectTimer ?? null;
|
|
239410
239519
|
const reconnectAttempt = existing?.reconnectAttempt ?? 0;
|
|
239520
|
+
const historyEpoch = existing?.historyEpoch ?? null;
|
|
239521
|
+
let latestEntryIndex = null;
|
|
239522
|
+
let lastTurnEndEntryIndex = null;
|
|
239523
|
+
for (const raw of messages) {
|
|
239524
|
+
const metadata = patchEntryMetadata(raw);
|
|
239525
|
+
if (metadata.latest !== null) latestEntryIndex = Math.max(latestEntryIndex ?? -1, metadata.latest);
|
|
239526
|
+
if (metadata.lastTurnEnd !== null) lastTurnEndEntryIndex = Math.max(lastTurnEndEntryIndex ?? -1, metadata.lastTurnEnd);
|
|
239527
|
+
}
|
|
239528
|
+
const sessionStatus = existing?.sessionStatus ?? null;
|
|
239411
239529
|
this.cache.set(sessionId, {
|
|
239412
239530
|
messages,
|
|
239413
239531
|
patchCount,
|
|
@@ -239416,15 +239534,69 @@ var RemotePatchCache = class {
|
|
|
239416
239534
|
subscribers,
|
|
239417
239535
|
reconnecting,
|
|
239418
239536
|
reconnectTimer,
|
|
239419
|
-
reconnectAttempt
|
|
239537
|
+
reconnectAttempt,
|
|
239538
|
+
historyEpoch,
|
|
239539
|
+
latestEntryIndex,
|
|
239540
|
+
lastTurnEndEntryIndex,
|
|
239541
|
+
sessionStatus
|
|
239420
239542
|
});
|
|
239421
239543
|
}
|
|
239544
|
+
/**
|
|
239545
|
+
* Replace only the unsealed entry tail. Completed entries at or before the
|
|
239546
|
+
* cursor and non-entry lifecycle frames stay cached; replayed tail frames
|
|
239547
|
+
* become the authoritative copy after a tunnel reconnect.
|
|
239548
|
+
*/
|
|
239549
|
+
replaceEntryTail(sessionId, afterEntryIndex, tail) {
|
|
239550
|
+
const entry = this.getOrCreate(sessionId);
|
|
239551
|
+
const kept = entry.messages.filter((raw) => {
|
|
239552
|
+
try {
|
|
239553
|
+
const parsed = JSON.parse(raw);
|
|
239554
|
+
if (!Array.isArray(parsed.JsonPatch)) return true;
|
|
239555
|
+
const indices = parsed.JsonPatch.flatMap((op) => {
|
|
239556
|
+
const match2 = op.path?.match(/^\/entries\/(\d+)$/);
|
|
239557
|
+
return match2 ? [Number(match2[1])] : [];
|
|
239558
|
+
});
|
|
239559
|
+
return indices.length === 0 || indices.some((index) => index <= afterEntryIndex);
|
|
239560
|
+
} catch {
|
|
239561
|
+
return true;
|
|
239562
|
+
}
|
|
239563
|
+
});
|
|
239564
|
+
const messages = [...kept, ...tail];
|
|
239565
|
+
const patchCount = messages.reduce((count, raw) => {
|
|
239566
|
+
try {
|
|
239567
|
+
const parsed = JSON.parse(raw);
|
|
239568
|
+
return count + (Array.isArray(parsed.JsonPatch) ? 1 : 0);
|
|
239569
|
+
} catch {
|
|
239570
|
+
return count;
|
|
239571
|
+
}
|
|
239572
|
+
}, 0);
|
|
239573
|
+
this.replaceAll(sessionId, messages, patchCount);
|
|
239574
|
+
}
|
|
239422
239575
|
setFinished(sessionId) {
|
|
239423
239576
|
const entry = this.cache.get(sessionId);
|
|
239424
239577
|
if (entry) {
|
|
239425
239578
|
entry.finished = true;
|
|
239426
239579
|
}
|
|
239427
239580
|
}
|
|
239581
|
+
setHistoryEpoch(sessionId, epoch) {
|
|
239582
|
+
this.getOrCreate(sessionId).historyEpoch = epoch;
|
|
239583
|
+
}
|
|
239584
|
+
/** Start a fresh entry-index namespace without dropping live connections. */
|
|
239585
|
+
resetHistory(sessionId, epoch) {
|
|
239586
|
+
const entry = this.getOrCreate(sessionId);
|
|
239587
|
+
entry.messages = [];
|
|
239588
|
+
entry.patchCount = 0;
|
|
239589
|
+
entry.finished = false;
|
|
239590
|
+
entry.historyEpoch = epoch;
|
|
239591
|
+
entry.latestEntryIndex = null;
|
|
239592
|
+
entry.lastTurnEndEntryIndex = null;
|
|
239593
|
+
}
|
|
239594
|
+
setLastTurnEndEntryIndex(sessionId, index) {
|
|
239595
|
+
this.getOrCreate(sessionId).lastTurnEndEntryIndex = index;
|
|
239596
|
+
}
|
|
239597
|
+
setSessionStatus(sessionId, status) {
|
|
239598
|
+
this.getOrCreate(sessionId).sessionStatus = status;
|
|
239599
|
+
}
|
|
239428
239600
|
/** Store a persistent remote WebSocket connection. */
|
|
239429
239601
|
setRemoteWs(sessionId, ws) {
|
|
239430
239602
|
const entry = this.getOrCreate(sessionId);
|
|
@@ -240002,18 +240174,36 @@ var RemoteExecutorMonitor = class {
|
|
|
240002
240174
|
this.eventBus = eventBus;
|
|
240003
240175
|
this.storage = storage;
|
|
240004
240176
|
this.remoteExecutorMap = remoteExecutorMap;
|
|
240177
|
+
this.reverseConnectManager.setStatusChangeHandler((_remoteServerId, status) => {
|
|
240178
|
+
if (status !== "online") return;
|
|
240179
|
+
for (const [localProcessId, info] of this.watched) {
|
|
240180
|
+
if (this.reverseConnectManager.isConnected(info.remoteServerId)) {
|
|
240181
|
+
this.attach(localProcessId, info);
|
|
240182
|
+
}
|
|
240183
|
+
}
|
|
240184
|
+
});
|
|
240005
240185
|
}
|
|
240006
240186
|
reverseConnectManager;
|
|
240007
240187
|
eventBus;
|
|
240008
240188
|
storage;
|
|
240009
240189
|
remoteExecutorMap;
|
|
240010
|
-
/**
|
|
240011
|
-
|
|
240190
|
+
/**
|
|
240191
|
+
* Processes that still need a terminal observation. This registry is
|
|
240192
|
+
* deliberately separate from the currently-open virtual channels: a control
|
|
240193
|
+
* connection can disappear while the remote process keeps running.
|
|
240194
|
+
*/
|
|
240195
|
+
watched = /* @__PURE__ */ new Map();
|
|
240196
|
+
/** localProcessId → cleanup for the currently-open virtual channel */
|
|
240197
|
+
activeMonitors = /* @__PURE__ */ new Map();
|
|
240012
240198
|
watch(localProcessId, remoteInfo) {
|
|
240013
|
-
|
|
240199
|
+
this.watched.set(localProcessId, remoteInfo);
|
|
240200
|
+
this.attach(localProcessId, remoteInfo);
|
|
240201
|
+
}
|
|
240202
|
+
attach(localProcessId, remoteInfo) {
|
|
240203
|
+
if (this.activeMonitors.has(localProcessId)) return;
|
|
240014
240204
|
const rcm = this.reverseConnectManager;
|
|
240015
240205
|
if (!rcm.isConnected(remoteInfo.remoteServerId)) {
|
|
240016
|
-
console.log(`[RemoteExecutorMonitor] remote ${remoteInfo.remoteServerId} not connected for ${localProcessId},
|
|
240206
|
+
console.log(`[RemoteExecutorMonitor] remote ${remoteInfo.remoteServerId} not connected for ${localProcessId}, deferring`);
|
|
240017
240207
|
return;
|
|
240018
240208
|
}
|
|
240019
240209
|
const channelId = randomUUID8();
|
|
@@ -240026,11 +240216,16 @@ var RemoteExecutorMonitor = class {
|
|
|
240026
240216
|
rcm.openVirtualChannel(remoteInfo.remoteServerId, channelId, wsPath);
|
|
240027
240217
|
const remoteWs = adapter;
|
|
240028
240218
|
setTimeout(() => adapter.emit("open"), 0);
|
|
240029
|
-
|
|
240030
|
-
|
|
240031
|
-
|
|
240032
|
-
|
|
240033
|
-
|
|
240219
|
+
let cleanedUp = false;
|
|
240220
|
+
const cleanupActive = (closeChannel) => {
|
|
240221
|
+
if (cleanedUp) return;
|
|
240222
|
+
cleanedUp = true;
|
|
240223
|
+
this.activeMonitors.delete(localProcessId);
|
|
240224
|
+
if (closeChannel) {
|
|
240225
|
+
try {
|
|
240226
|
+
remoteWs.close();
|
|
240227
|
+
} catch {
|
|
240228
|
+
}
|
|
240034
240229
|
}
|
|
240035
240230
|
};
|
|
240036
240231
|
const outputChunks = [];
|
|
@@ -240041,7 +240236,10 @@ var RemoteExecutorMonitor = class {
|
|
|
240041
240236
|
outputChunks.push(parsed.data);
|
|
240042
240237
|
}
|
|
240043
240238
|
if (parsed.type === "finished") {
|
|
240239
|
+
this.watched.delete(localProcessId);
|
|
240044
240240
|
const info = this.remoteExecutorMap.get(localProcessId);
|
|
240241
|
+
const hasKnownExitCode = typeof parsed.exitCode === "number";
|
|
240242
|
+
const eventExitCode = hasKnownExitCode ? parsed.exitCode : 1;
|
|
240045
240243
|
if (info && !info.stoppedEmitted) {
|
|
240046
240244
|
info.stoppedEmitted = true;
|
|
240047
240245
|
let raw = outputChunks.join("");
|
|
@@ -240052,7 +240250,7 @@ var RemoteExecutorMonitor = class {
|
|
|
240052
240250
|
projectId: info.projectId ?? "",
|
|
240053
240251
|
executorId: info.executorId,
|
|
240054
240252
|
processId: localProcessId,
|
|
240055
|
-
exitCode:
|
|
240253
|
+
exitCode: eventExitCode,
|
|
240056
240254
|
target: info.remoteServerId,
|
|
240057
240255
|
tailOutput,
|
|
240058
240256
|
// Structured final message forwarded by the remote's finished
|
|
@@ -240061,33 +240259,33 @@ var RemoteExecutorMonitor = class {
|
|
|
240061
240259
|
});
|
|
240062
240260
|
}
|
|
240063
240261
|
this.remoteExecutorMap.delete(localProcessId);
|
|
240064
|
-
this.storage.remoteExecutorProcesses.markFinished(
|
|
240065
|
-
|
|
240066
|
-
typeof parsed.exitCode === "number" ? parsed.exitCode : 0
|
|
240067
|
-
).catch((err) => {
|
|
240262
|
+
const markFinished = hasKnownExitCode ? this.storage.remoteExecutorProcesses.markFinished(localProcessId, parsed.exitCode) : this.storage.remoteExecutorProcesses.markFinished(localProcessId, void 0, "killed");
|
|
240263
|
+
markFinished.catch((err) => {
|
|
240068
240264
|
console.error(`[RemoteExecutorMonitor] Failed to mark process ${localProcessId} finished:`, err);
|
|
240069
240265
|
});
|
|
240070
|
-
|
|
240266
|
+
cleanupActive(true);
|
|
240071
240267
|
}
|
|
240072
240268
|
} catch {
|
|
240073
240269
|
}
|
|
240074
240270
|
});
|
|
240075
240271
|
remoteWs.on("close", () => {
|
|
240076
|
-
|
|
240272
|
+
cleanupActive(false);
|
|
240077
240273
|
});
|
|
240078
240274
|
remoteWs.on("error", (error48) => {
|
|
240079
240275
|
console.error(`[RemoteExecutorMonitor] error for ${localProcessId}:`, error48);
|
|
240080
|
-
|
|
240276
|
+
cleanupActive(true);
|
|
240081
240277
|
});
|
|
240082
|
-
this.
|
|
240278
|
+
this.activeMonitors.set(localProcessId, () => cleanupActive(true));
|
|
240083
240279
|
console.log(`[RemoteExecutorMonitor] watching ${localProcessId}`);
|
|
240084
240280
|
}
|
|
240085
240281
|
unwatch(localProcessId) {
|
|
240086
|
-
this.
|
|
240282
|
+
this.watched.delete(localProcessId);
|
|
240283
|
+
this.activeMonitors.get(localProcessId)?.();
|
|
240087
240284
|
}
|
|
240088
240285
|
shutdown() {
|
|
240089
|
-
|
|
240090
|
-
this.
|
|
240286
|
+
this.watched.clear();
|
|
240287
|
+
for (const cleanup of [...this.activeMonitors.values()]) cleanup();
|
|
240288
|
+
this.activeMonitors.clear();
|
|
240091
240289
|
}
|
|
240092
240290
|
};
|
|
240093
240291
|
|
|
@@ -245416,6 +245614,45 @@ var MODEL_SUGGESTIONS = {
|
|
|
245416
245614
|
codex: ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-5.5"]
|
|
245417
245615
|
};
|
|
245418
245616
|
|
|
245617
|
+
// src/session-history-window.ts
|
|
245618
|
+
function historyHead(entries, historyEpoch) {
|
|
245619
|
+
let latestEntryIndex = null;
|
|
245620
|
+
let lastTurnEndEntryIndex = null;
|
|
245621
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
245622
|
+
const message = entries[index];
|
|
245623
|
+
if (!message) continue;
|
|
245624
|
+
latestEntryIndex ??= index;
|
|
245625
|
+
if (lastTurnEndEntryIndex === null && message.type === "turn_end") {
|
|
245626
|
+
lastTurnEndEntryIndex = index;
|
|
245627
|
+
}
|
|
245628
|
+
if (latestEntryIndex !== null && lastTurnEndEntryIndex !== null) break;
|
|
245629
|
+
}
|
|
245630
|
+
return { historyEpoch, latestEntryIndex, lastTurnEndEntryIndex };
|
|
245631
|
+
}
|
|
245632
|
+
function buildHistoryWindow(entries, historyEpoch, opts = {}) {
|
|
245633
|
+
const head = historyHead(entries, historyEpoch);
|
|
245634
|
+
const endExclusive = Math.max(0, Math.min(opts.before ?? entries.length, entries.length));
|
|
245635
|
+
const requestedTurns = Math.max(1, Math.min(opts.turns ?? 5, 20));
|
|
245636
|
+
const boundaries = [];
|
|
245637
|
+
for (let index = endExclusive - 1; index >= 0; index--) {
|
|
245638
|
+
if (entries[index]?.type === "turn_end") boundaries.push(index);
|
|
245639
|
+
if (boundaries.length >= requestedTurns + 2) break;
|
|
245640
|
+
}
|
|
245641
|
+
const startIndex = boundaries.length > requestedTurns + 1 ? boundaries[requestedTurns + 1] + 1 : 0;
|
|
245642
|
+
const dense = [];
|
|
245643
|
+
for (let index = startIndex; index < endExclusive; index++) {
|
|
245644
|
+
const message = entries[index];
|
|
245645
|
+
if (message) dense.push({ entryIndex: index, message });
|
|
245646
|
+
}
|
|
245647
|
+
const hasMore = startIndex > 0;
|
|
245648
|
+
return {
|
|
245649
|
+
...head,
|
|
245650
|
+
entries: dense,
|
|
245651
|
+
previousCursor: hasMore ? startIndex : null,
|
|
245652
|
+
hasMore
|
|
245653
|
+
};
|
|
245654
|
+
}
|
|
245655
|
+
|
|
245419
245656
|
// src/routes/agent-session-routes.ts
|
|
245420
245657
|
async function resolveProjectPath(projectId, storage) {
|
|
245421
245658
|
if (projectId.startsWith("path:")) {
|
|
@@ -245566,7 +245803,7 @@ var routes11 = async (fastify2) => {
|
|
|
245566
245803
|
return reply.code(200).send({ providers: providers2 });
|
|
245567
245804
|
});
|
|
245568
245805
|
fastify2.post("/api/path/agent-sessions", async (req, reply) => {
|
|
245569
|
-
const { path: projectPath, branch, permissionMode, agentType, force, model } = req.body;
|
|
245806
|
+
const { path: projectPath, branch, permissionMode, agentType, force, model, historyTurns } = req.body;
|
|
245570
245807
|
if (!projectPath) {
|
|
245571
245808
|
return reply.code(400).send({ error: "Path is required" });
|
|
245572
245809
|
}
|
|
@@ -245604,6 +245841,8 @@ var routes11 = async (fastify2) => {
|
|
|
245604
245841
|
}
|
|
245605
245842
|
const session = fastify2.agentSessionManager.getSession(sessionId);
|
|
245606
245843
|
const messages = fastify2.agentSessionManager.getMessages(sessionId);
|
|
245844
|
+
const epoch = fastify2.agentSessionManager.getHistoryEpoch(sessionId) ?? 0;
|
|
245845
|
+
const historyWindow = historyTurns ? buildHistoryWindow(fastify2.agentSessionManager.getRawMessages(sessionId), epoch, { turns: historyTurns }) : void 0;
|
|
245607
245846
|
const projection = await fastify2.storage.agentSessions.getActivityById(sessionId, "session-detail");
|
|
245608
245847
|
if (session?.workspaceCheckoutId && !projection) {
|
|
245609
245848
|
return reply.code(409).send({
|
|
@@ -245628,7 +245867,8 @@ var routes11 = async (fastify2) => {
|
|
|
245628
245867
|
checkoutDeletedAt: projection?.checkoutDeletedAt ?? null,
|
|
245629
245868
|
processAlive: session ? fastify2.agentSessionManager.getSessionProcessAlive(sessionId) : false
|
|
245630
245869
|
},
|
|
245631
|
-
messages
|
|
245870
|
+
messages: historyWindow ? historyWindow.entries.map((entry) => entry.message) : messages,
|
|
245871
|
+
...historyWindow ? { historyWindow } : {}
|
|
245632
245872
|
});
|
|
245633
245873
|
} catch (error48) {
|
|
245634
245874
|
console.error("[API] Failed to load path-based agent session:", error48);
|
|
@@ -245915,7 +246155,7 @@ var routes11 = async (fastify2) => {
|
|
|
245915
246155
|
if (!project) {
|
|
245916
246156
|
return reply.code(404).send({ error: "Project not found" });
|
|
245917
246157
|
}
|
|
245918
|
-
const { branch, permissionMode, agentType, model } = req.body;
|
|
246158
|
+
const { branch, permissionMode, agentType, model, historyTurns } = req.body;
|
|
245919
246159
|
let agentMode = project.agent_mode;
|
|
245920
246160
|
let useRemoteAgent = agentMode !== "local";
|
|
245921
246161
|
if (useRemoteAgent && agentMode === "remote") {
|
|
@@ -245948,7 +246188,7 @@ var routes11 = async (fastify2) => {
|
|
|
245948
246188
|
agentMode,
|
|
245949
246189
|
"POST",
|
|
245950
246190
|
`/api/path/agent-sessions`,
|
|
245951
|
-
{ path: remoteConfig.remote_path, branch, permissionMode, agentType, model }
|
|
246191
|
+
{ path: remoteConfig.remote_path, branch, permissionMode, agentType, model, historyTurns }
|
|
245952
246192
|
);
|
|
245953
246193
|
console.log(`[API] Remote proxy result: ok=${result.ok}, status=${result.status}, data=${JSON.stringify(result.data).substring(0, 500)}`);
|
|
245954
246194
|
if (result.ok) {
|
|
@@ -245975,10 +246215,21 @@ var routes11 = async (fastify2) => {
|
|
|
245975
246215
|
if (remoteData.messages && remoteData.messages.length > 0) {
|
|
245976
246216
|
const cacheEntry = fastify2.remotePatchCache.getOrCreate(localSessionId);
|
|
245977
246217
|
if (cacheEntry.messages.length === 0) {
|
|
245978
|
-
|
|
245979
|
-
|
|
246218
|
+
const seededEntries = remoteData.historyWindow?.entries ?? remoteData.messages.map((message, entryIndex) => ({ entryIndex, message }));
|
|
246219
|
+
for (const { entryIndex, message } of seededEntries) {
|
|
246220
|
+
const patch = ConversationPatch.addEntry(entryIndex, message);
|
|
245980
246221
|
fastify2.remotePatchCache.appendMessage(localSessionId, JSON.stringify({ JsonPatch: patch }), true);
|
|
245981
246222
|
}
|
|
246223
|
+
if (remoteData.historyWindow) {
|
|
246224
|
+
fastify2.remotePatchCache.setHistoryEpoch(localSessionId, remoteData.historyWindow.historyEpoch);
|
|
246225
|
+
if (remoteData.historyWindow.lastTurnEndEntryIndex !== null) {
|
|
246226
|
+
fastify2.remotePatchCache.setLastTurnEndEntryIndex(localSessionId, remoteData.historyWindow.lastTurnEndEntryIndex);
|
|
246227
|
+
}
|
|
246228
|
+
}
|
|
246229
|
+
const seededStatus = remoteData.session.status;
|
|
246230
|
+
if (seededStatus === "running" || seededStatus === "stopped" || seededStatus === "error") {
|
|
246231
|
+
fastify2.remotePatchCache.setSessionStatus(localSessionId, seededStatus);
|
|
246232
|
+
}
|
|
245982
246233
|
console.log(`[API] findExisting proxy: seeded cache with ${remoteData.messages.length} msgs for ${localSessionId}`);
|
|
245983
246234
|
} else {
|
|
245984
246235
|
console.log(`[API] findExisting proxy: cache already has ${cacheEntry.messages.length} msgs for ${localSessionId} (remote returned ${remoteData.messages.length}), skipping seed`);
|
|
@@ -245992,7 +246243,8 @@ var routes11 = async (fastify2) => {
|
|
|
245992
246243
|
id: localSessionId,
|
|
245993
246244
|
projectId: req.params.projectId
|
|
245994
246245
|
},
|
|
245995
|
-
messages: remoteData.messages
|
|
246246
|
+
messages: remoteData.messages,
|
|
246247
|
+
...remoteData.historyWindow ? { historyWindow: remoteData.historyWindow } : {}
|
|
245996
246248
|
});
|
|
245997
246249
|
}
|
|
245998
246250
|
return reply.code(proxyStatus(result)).send(result.data);
|
|
@@ -246017,6 +246269,8 @@ var routes11 = async (fastify2) => {
|
|
|
246017
246269
|
}
|
|
246018
246270
|
const session = fastify2.agentSessionManager.getSession(sessionId);
|
|
246019
246271
|
const messages = fastify2.agentSessionManager.getMessages(sessionId);
|
|
246272
|
+
const epoch = fastify2.agentSessionManager.getHistoryEpoch(sessionId) ?? 0;
|
|
246273
|
+
const historyWindow = historyTurns ? buildHistoryWindow(fastify2.agentSessionManager.getRawMessages(sessionId), epoch, { turns: historyTurns }) : void 0;
|
|
246020
246274
|
const effectiveStatus = session?.status || "stopped";
|
|
246021
246275
|
return reply.code(200).send({
|
|
246022
246276
|
session: {
|
|
@@ -246029,7 +246283,8 @@ var routes11 = async (fastify2) => {
|
|
|
246029
246283
|
model: session?.model ?? null,
|
|
246030
246284
|
processAlive: session ? fastify2.agentSessionManager.getSessionProcessAlive(sessionId) : false
|
|
246031
246285
|
},
|
|
246032
|
-
messages
|
|
246286
|
+
messages: historyWindow ? historyWindow.entries.map((entry) => entry.message) : messages,
|
|
246287
|
+
...historyWindow ? { historyWindow } : {}
|
|
246033
246288
|
});
|
|
246034
246289
|
} catch (error48) {
|
|
246035
246290
|
console.error("[API] Failed to load agent session:", error48);
|
|
@@ -246214,6 +246469,121 @@ var routes11 = async (fastify2) => {
|
|
|
246214
246469
|
});
|
|
246215
246470
|
}
|
|
246216
246471
|
);
|
|
246472
|
+
fastify2.get("/api/agent-sessions/:sessionId/history-window", async (req, reply) => {
|
|
246473
|
+
const beforeRaw = req.query.before === void 0 ? void 0 : Number(req.query.before);
|
|
246474
|
+
const turnsRaw = req.query.turns === void 0 ? void 0 : Number(req.query.turns);
|
|
246475
|
+
const before = Number.isInteger(beforeRaw) && beforeRaw >= 0 ? beforeRaw : void 0;
|
|
246476
|
+
const turns = Number.isInteger(turnsRaw) ? Math.max(1, Math.min(turnsRaw, 20)) : 5;
|
|
246477
|
+
if (req.params.sessionId.startsWith("remote-")) {
|
|
246478
|
+
const userId = requireUserFacingUserId(req, reply);
|
|
246479
|
+
if (userId === null) return;
|
|
246480
|
+
const remoteInfo = await getAuthorizedRemoteSessionInfo(req.params.sessionId, userId);
|
|
246481
|
+
if (!remoteInfo) return reply.code(404).send({ error: "Remote session not found" });
|
|
246482
|
+
const params = new URLSearchParams({ turns: String(turns) });
|
|
246483
|
+
if (before !== void 0) params.set("before", String(before));
|
|
246484
|
+
const result = await proxyAuto(
|
|
246485
|
+
remoteInfo.remoteServerId,
|
|
246486
|
+
"GET",
|
|
246487
|
+
`/api/agent-sessions/${encodeURIComponent(remoteInfo.remoteSessionId)}/history-window?${params}`
|
|
246488
|
+
);
|
|
246489
|
+
if (result.ok) {
|
|
246490
|
+
const data = result.data;
|
|
246491
|
+
return reply.code(200).send({
|
|
246492
|
+
...data,
|
|
246493
|
+
session: data.session ? {
|
|
246494
|
+
...data.session,
|
|
246495
|
+
id: req.params.sessionId,
|
|
246496
|
+
projectId: projectIdFromRemoteSessionId(req.params.sessionId, remoteInfo),
|
|
246497
|
+
branch: remoteInfo.branch ?? null
|
|
246498
|
+
} : void 0
|
|
246499
|
+
});
|
|
246500
|
+
}
|
|
246501
|
+
if (result.status === 404) {
|
|
246502
|
+
const legacy = await proxyAuto(
|
|
246503
|
+
remoteInfo.remoteServerId,
|
|
246504
|
+
"GET",
|
|
246505
|
+
`/api/agent-sessions/${encodeURIComponent(remoteInfo.remoteSessionId)}`
|
|
246506
|
+
);
|
|
246507
|
+
if (!legacy.ok) return reply.code(proxyStatus(legacy)).send(legacy.data);
|
|
246508
|
+
const data = legacy.data;
|
|
246509
|
+
const messages = data.messages ?? [];
|
|
246510
|
+
return reply.code(200).send({
|
|
246511
|
+
...buildHistoryWindow(messages, 0, { before, turns }),
|
|
246512
|
+
status: data.session?.status ?? "stopped",
|
|
246513
|
+
legacyFallback: true
|
|
246514
|
+
});
|
|
246515
|
+
}
|
|
246516
|
+
return reply.code(proxyStatus(result)).send(result.data);
|
|
246517
|
+
}
|
|
246518
|
+
const session = fastify2.agentSessionManager.getSession(req.params.sessionId);
|
|
246519
|
+
if (!session) return reply.code(404).send({ error: "Session not found" });
|
|
246520
|
+
const epoch = fastify2.agentSessionManager.getHistoryEpoch(req.params.sessionId) ?? 0;
|
|
246521
|
+
return reply.code(200).send({
|
|
246522
|
+
...buildHistoryWindow(fastify2.agentSessionManager.getRawMessages(req.params.sessionId), epoch, { before, turns }),
|
|
246523
|
+
status: session.status,
|
|
246524
|
+
session: {
|
|
246525
|
+
id: session.id,
|
|
246526
|
+
projectId: session.projectId,
|
|
246527
|
+
branch: session.branch,
|
|
246528
|
+
status: session.status,
|
|
246529
|
+
permissionMode: session.permissionMode,
|
|
246530
|
+
agentType: session.agentType,
|
|
246531
|
+
model: session.model,
|
|
246532
|
+
processAlive: fastify2.agentSessionManager.getSessionProcessAlive(session.id)
|
|
246533
|
+
}
|
|
246534
|
+
});
|
|
246535
|
+
});
|
|
246536
|
+
fastify2.get(
|
|
246537
|
+
"/api/agent-sessions/:sessionId/history-head",
|
|
246538
|
+
async (req, reply) => {
|
|
246539
|
+
if (req.params.sessionId.startsWith("remote-")) {
|
|
246540
|
+
const userId = requireUserFacingUserId(req, reply);
|
|
246541
|
+
if (userId === null) return;
|
|
246542
|
+
const remoteInfo = await getAuthorizedRemoteSessionInfo(req.params.sessionId, userId);
|
|
246543
|
+
if (!remoteInfo) return reply.code(404).send({ error: "Remote session not found" });
|
|
246544
|
+
const cached2 = fastify2.remotePatchCache.get(req.params.sessionId);
|
|
246545
|
+
if (cached2?.historyEpoch !== null && cached2?.lastTurnEndEntryIndex !== null && cached2?.sessionStatus) {
|
|
246546
|
+
return reply.code(200).send({
|
|
246547
|
+
historyEpoch: cached2.historyEpoch,
|
|
246548
|
+
latestEntryIndex: cached2.latestEntryIndex,
|
|
246549
|
+
lastTurnEndEntryIndex: cached2.lastTurnEndEntryIndex,
|
|
246550
|
+
status: cached2.sessionStatus
|
|
246551
|
+
});
|
|
246552
|
+
}
|
|
246553
|
+
const result = await proxyAuto(
|
|
246554
|
+
remoteInfo.remoteServerId,
|
|
246555
|
+
"GET",
|
|
246556
|
+
`/api/agent-sessions/${encodeURIComponent(remoteInfo.remoteSessionId)}/history-head`
|
|
246557
|
+
);
|
|
246558
|
+
if (result.ok) return reply.code(200).send(result.data);
|
|
246559
|
+
if (result.status !== 404) return reply.code(proxyStatus(result)).send(result.data);
|
|
246560
|
+
const legacy = await proxyAuto(
|
|
246561
|
+
remoteInfo.remoteServerId,
|
|
246562
|
+
"GET",
|
|
246563
|
+
`/api/agent-sessions/${encodeURIComponent(remoteInfo.remoteSessionId)}`
|
|
246564
|
+
);
|
|
246565
|
+
if (!legacy.ok) return reply.code(proxyStatus(legacy)).send(legacy.data);
|
|
246566
|
+
const data = legacy.data;
|
|
246567
|
+
const head2 = buildHistoryWindow(data.messages ?? [], 0, { turns: 1 });
|
|
246568
|
+
return reply.code(200).send({
|
|
246569
|
+
historyEpoch: head2.historyEpoch,
|
|
246570
|
+
latestEntryIndex: head2.latestEntryIndex,
|
|
246571
|
+
lastTurnEndEntryIndex: head2.lastTurnEndEntryIndex,
|
|
246572
|
+
status: data.session?.status ?? "stopped"
|
|
246573
|
+
});
|
|
246574
|
+
}
|
|
246575
|
+
const session = fastify2.agentSessionManager.getSession(req.params.sessionId);
|
|
246576
|
+
if (!session) return reply.code(404).send({ error: "Session not found" });
|
|
246577
|
+
const epoch = fastify2.agentSessionManager.getHistoryEpoch(req.params.sessionId) ?? 0;
|
|
246578
|
+
const head = buildHistoryWindow(fastify2.agentSessionManager.getRawMessages(req.params.sessionId), epoch, { turns: 1 });
|
|
246579
|
+
return reply.code(200).send({
|
|
246580
|
+
historyEpoch: head.historyEpoch,
|
|
246581
|
+
latestEntryIndex: head.latestEntryIndex,
|
|
246582
|
+
lastTurnEndEntryIndex: head.lastTurnEndEntryIndex,
|
|
246583
|
+
status: session.status
|
|
246584
|
+
});
|
|
246585
|
+
}
|
|
246586
|
+
);
|
|
246217
246587
|
fastify2.get(
|
|
246218
246588
|
"/api/agent-sessions/:sessionId/brief-source",
|
|
246219
246589
|
async (req, reply) => {
|
|
@@ -249483,6 +249853,13 @@ function attachWsHeartbeat(socket, { label, intervalMs = DEFAULT_INTERVAL_MS, ke
|
|
|
249483
249853
|
}
|
|
249484
249854
|
|
|
249485
249855
|
// src/routes/websocket-routes.ts
|
|
249856
|
+
function resolveRemoteReplayCursor(clientEpoch, cachedEpoch, afterEntryIndex) {
|
|
249857
|
+
const epochMatches = clientEpoch === void 0 || cachedEpoch !== null && clientEpoch === cachedEpoch;
|
|
249858
|
+
return {
|
|
249859
|
+
epochMatches,
|
|
249860
|
+
replayAfter: epochMatches ? afterEntryIndex ?? -1 : -1
|
|
249861
|
+
};
|
|
249862
|
+
}
|
|
249486
249863
|
var routes23 = async (fastify2) => {
|
|
249487
249864
|
fastify2.reverseConnectManager.setStatusChangeHandler((remoteServerId, status) => {
|
|
249488
249865
|
if (status !== "online") return;
|
|
@@ -249694,6 +250071,8 @@ var routes23 = async (fastify2) => {
|
|
|
249694
250071
|
{ websocket: true },
|
|
249695
250072
|
async (socket, req) => {
|
|
249696
250073
|
const { sessionId } = req.params;
|
|
250074
|
+
const afterEntryIndex = Number.isInteger(Number(req.query.after)) ? Number(req.query.after) : void 0;
|
|
250075
|
+
const historyEpoch = Number.isInteger(Number(req.query.epoch)) ? Number(req.query.epoch) : void 0;
|
|
249697
250076
|
console.log(`[AgentWS] Connection attempt for session ${sessionId} (auth=${fastify2.authEnabled})`);
|
|
249698
250077
|
let principalUserId = fastify2.authEnabled ? null : "local";
|
|
249699
250078
|
if (fastify2.authEnabled) {
|
|
@@ -249743,15 +250122,42 @@ var routes23 = async (fastify2) => {
|
|
|
249743
250122
|
const cacheEntry = cache2.getOrCreate(sessionId);
|
|
249744
250123
|
console.log(`[AgentWS] WS connect: cacheEntry for ${sessionId} has messages.length=${cacheEntry.messages.length} finished=${cacheEntry.finished} remoteWsOpen=${!!cache2.getRemoteWs(sessionId)}`);
|
|
249745
250124
|
if (cacheEntry.messages.length > 0) {
|
|
249746
|
-
|
|
250125
|
+
const { epochMatches, replayAfter } = resolveRemoteReplayCursor(
|
|
250126
|
+
historyEpoch,
|
|
250127
|
+
cacheEntry.historyEpoch,
|
|
250128
|
+
afterEntryIndex
|
|
250129
|
+
);
|
|
250130
|
+
console.log(`[AgentWS] Replaying cached msgs for ${sessionId} after=${replayAfter}`);
|
|
250131
|
+
try {
|
|
250132
|
+
socket.send(JSON.stringify({
|
|
250133
|
+
HistorySync: {
|
|
250134
|
+
historyEpoch: cacheEntry.historyEpoch ?? historyEpoch ?? 0,
|
|
250135
|
+
reset: !epochMatches
|
|
250136
|
+
}
|
|
250137
|
+
}));
|
|
250138
|
+
} catch {
|
|
250139
|
+
}
|
|
249747
250140
|
for (const raw of cacheEntry.messages) {
|
|
250141
|
+
if (replayAfter >= 0) {
|
|
250142
|
+
try {
|
|
250143
|
+
const parsed = JSON.parse(raw);
|
|
250144
|
+
if (Array.isArray(parsed.JsonPatch)) {
|
|
250145
|
+
const indices = parsed.JsonPatch.flatMap((op) => {
|
|
250146
|
+
const match2 = op.path?.match(/^\/entries\/(\d+)$/);
|
|
250147
|
+
return match2 ? [Number(match2[1])] : [];
|
|
250148
|
+
});
|
|
250149
|
+
if (indices.length > 0 && indices.every((index) => index <= replayAfter)) continue;
|
|
250150
|
+
}
|
|
250151
|
+
} catch {
|
|
250152
|
+
}
|
|
250153
|
+
}
|
|
249748
250154
|
try {
|
|
249749
250155
|
socket.send(raw);
|
|
249750
250156
|
} catch {
|
|
249751
250157
|
}
|
|
249752
250158
|
}
|
|
249753
250159
|
try {
|
|
249754
|
-
socket.send(JSON.stringify({ Ready: true }));
|
|
250160
|
+
socket.send(JSON.stringify({ Ready: true, historyEpoch: cacheEntry.historyEpoch ?? void 0 }));
|
|
249755
250161
|
} catch {
|
|
249756
250162
|
}
|
|
249757
250163
|
if (cacheEntry.finished) {
|
|
@@ -249777,7 +250183,8 @@ var routes23 = async (fastify2) => {
|
|
|
249777
250183
|
fastify2.reverseConnectManager,
|
|
249778
250184
|
fastify2.eventBus,
|
|
249779
250185
|
fastify2.agentSessionManager,
|
|
249780
|
-
fastify2.storage
|
|
250186
|
+
fastify2.storage,
|
|
250187
|
+
{ afterEntryIndex, historyEpoch }
|
|
249781
250188
|
);
|
|
249782
250189
|
}
|
|
249783
250190
|
if (cache2.getRemoteWs(sessionId)) {
|
|
@@ -249807,7 +250214,7 @@ var routes23 = async (fastify2) => {
|
|
|
249807
250214
|
});
|
|
249808
250215
|
return;
|
|
249809
250216
|
}
|
|
249810
|
-
const unsubscribe = fastify2.agentSessionManager.subscribe(sessionId, socket);
|
|
250217
|
+
const unsubscribe = fastify2.agentSessionManager.subscribe(sessionId, socket, { afterEntryIndex, historyEpoch });
|
|
249811
250218
|
if (!unsubscribe) {
|
|
249812
250219
|
console.log(`[AgentWS] Session ${sessionId} not found`);
|
|
249813
250220
|
stopHeartbeat();
|
|
@@ -253486,6 +253893,8 @@ var WORKER_CAPABILITIES = {
|
|
|
253486
253893
|
"http:POST /api/path/agent-sessions": { since: "0.2.0", summary: "\u521B\u5EFA\u4F1A\u8BDD" },
|
|
253487
253894
|
"http:POST /api/path/agent-sessions/new": { since: "0.2.0", summary: "\u521B\u5EFA\u4F1A\u8BDD(\u6307\u5B9A ID)" },
|
|
253488
253895
|
"http:GET /api/agent-sessions/:param": { since: "0.2.0", summary: "\u8BFB\u4F1A\u8BDD\u8BE6\u60C5/\u5BF9\u8BDD" },
|
|
253896
|
+
"http:GET /api/agent-sessions/:param/history-window": { since: "0.4.0", summary: "\u6309 turn \u8FB9\u754C\u8BFB\u53D6\u4F1A\u8BDD\u7A97\u53E3" },
|
|
253897
|
+
"http:GET /api/agent-sessions/:param/history-head": { since: "0.4.0", summary: "\u8BFB\u53D6\u4F1A\u8BDD\u5386\u53F2\u6E38\u6807" },
|
|
253489
253898
|
// Additive: a worker below 0.3.6 404s it and the hub's intent-brief
|
|
253490
253899
|
// distillation degrades to the deterministic excerpt (tier 2).
|
|
253491
253900
|
"http:GET /api/agent-sessions/:param/brief-source": { since: "0.3.6", summary: "\u8BFB\u4F1A\u8BDD\u5BF9\u8BDD(\u4EC5\u84B8\u998F\u7528\u6587\u672C)" },
|