@vibedeckx/darwin-arm64 0.3.14 → 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 +513 -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",
|
|
@@ -231310,10 +231362,59 @@ var AgentSessionManager = class {
|
|
|
231310
231362
|
await this.storage.agentSessions.updateStatusPreservingTimestamp(dbSession.id, "stopped");
|
|
231311
231363
|
restoredCount++;
|
|
231312
231364
|
}
|
|
231365
|
+
await this.repairOrphanedRunningRows(allSessions);
|
|
231313
231366
|
if (restoredCount > 0) {
|
|
231314
231367
|
console.log(`[AgentSession] Restored ${restoredCount} dormant session(s) from database`);
|
|
231315
231368
|
}
|
|
231316
231369
|
}
|
|
231370
|
+
/**
|
|
231371
|
+
* Reconcile rows the database still calls "running" against what this
|
|
231372
|
+
* process actually owns.
|
|
231373
|
+
*
|
|
231374
|
+
* `create` writes `status='running'` BEFORE the process is spawned, so any
|
|
231375
|
+
* path that dies between the INSERT and the first entry leaves a row
|
|
231376
|
+
* claiming to run forever. The restore loop above is supposed to be the
|
|
231377
|
+
* backstop, but it skips zero-entry rows before it resets crashed statuses
|
|
231378
|
+
* — so precisely the sessions that died earliest are the ones nothing ever
|
|
231379
|
+
* repairs. Measured on a real worker (2026-08-10): 63 such rows, the oldest
|
|
231380
|
+
* four months old, every one of them permanently exempt from session
|
|
231381
|
+
* retention (`status <> 'running'`) and inflating the project dashboard's
|
|
231382
|
+
* running count (46 reported, 1 real).
|
|
231383
|
+
*
|
|
231384
|
+
* Reconciliation rather than prevention, deliberately: a DB row and an OS
|
|
231385
|
+
* process are two resources with no atomic commit between them, so a kill
|
|
231386
|
+
* landing between the INSERT and the spawn leaves the same row no matter
|
|
231387
|
+
* how careful `create` becomes. Prevention narrows the window; only
|
|
231388
|
+
* reconciliation closes it. Same argument the retention plan's §3.1 makes
|
|
231389
|
+
* for snapshot reconciliation over delete events.
|
|
231390
|
+
*
|
|
231391
|
+
* The ownership test is "not in `this.sessions`", NOT `process === null`: a
|
|
231392
|
+
* session that is spawning or waking sits in the map with a null process,
|
|
231393
|
+
* and resetting it would be the same class of bug as the wake/retention
|
|
231394
|
+
* race in `deleteDormantSessionIfExpired`.
|
|
231395
|
+
*
|
|
231396
|
+
* STARTUP ONLY. `createNewSession` INSERTs the row before it puts the
|
|
231397
|
+
* session in the map, so a few milliseconds exist in which a perfectly
|
|
231398
|
+
* healthy session looks orphaned. That window is unreachable from here (no
|
|
231399
|
+
* requests are served yet); a periodic caller would first have to add an
|
|
231400
|
+
* age threshold.
|
|
231401
|
+
*
|
|
231402
|
+
* @param snapshot the row list read at the top of `restoreSessionsFromDb`.
|
|
231403
|
+
* Rows the loop already reset read as stale "running" here — the map check
|
|
231404
|
+
* is what excludes them, which is why it must come after the loop.
|
|
231405
|
+
*/
|
|
231406
|
+
async repairOrphanedRunningRows(snapshot) {
|
|
231407
|
+
let repaired = 0;
|
|
231408
|
+
for (const row of snapshot) {
|
|
231409
|
+
if (row.status !== "running") continue;
|
|
231410
|
+
if (this.sessions.has(row.id)) continue;
|
|
231411
|
+
await this.storage.agentSessions.updateStatusPreservingTimestamp(row.id, "stopped");
|
|
231412
|
+
repaired++;
|
|
231413
|
+
}
|
|
231414
|
+
if (repaired > 0) {
|
|
231415
|
+
console.log(`[AgentSession] Reset ${repaired} orphaned session row(s) left as "running"`);
|
|
231416
|
+
}
|
|
231417
|
+
}
|
|
231317
231418
|
/**
|
|
231318
231419
|
* Create a new dormant session that copies another session's conversation
|
|
231319
231420
|
* history ("branch"). The new session gets its own DB row, copied entry
|
|
@@ -231431,6 +231532,7 @@ var AgentSessionManager = class {
|
|
|
231431
231532
|
process: null,
|
|
231432
231533
|
dormant: true,
|
|
231433
231534
|
processStartsInFlight: 0,
|
|
231535
|
+
historyEpoch: 0,
|
|
231434
231536
|
store,
|
|
231435
231537
|
subscribers: /* @__PURE__ */ new Set(),
|
|
231436
231538
|
status: "stopped",
|
|
@@ -232742,7 +232844,7 @@ async function persistRemoteSessionActivityFrame(storage, sessionId, remoteInfo,
|
|
|
232742
232844
|
}
|
|
232743
232845
|
return false;
|
|
232744
232846
|
}
|
|
232745
|
-
function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnectManager, eventBus, agentSessionManager, storage) {
|
|
232847
|
+
function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnectManager, eventBus, agentSessionManager, storage, requestedHistory) {
|
|
232746
232848
|
const hasCachedData = cache2.hasData(sessionId);
|
|
232747
232849
|
console.log(`[AgentWS] Opening persistent remote WS for ${sessionId} (cached=${hasCachedData})`);
|
|
232748
232850
|
if (!reverseConnectManager || !reverseConnectManager.isConnected(remoteInfo.remoteServerId)) {
|
|
@@ -232752,7 +232854,16 @@ function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnect
|
|
|
232752
232854
|
return;
|
|
232753
232855
|
}
|
|
232754
232856
|
const channelId = randomUUID2();
|
|
232755
|
-
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}` : ""}`;
|
|
232756
232867
|
const adapter = new VirtualWsAdapter(
|
|
232757
232868
|
(data) => reverseConnectManager.sendChannelData(remoteInfo.remoteServerId, channelId, data),
|
|
232758
232869
|
() => reverseConnectManager.closeChannel(remoteInfo.remoteServerId, channelId)
|
|
@@ -232786,6 +232897,7 @@ function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnect
|
|
|
232786
232897
|
cache2.broadcast(sessionId, raw);
|
|
232787
232898
|
const statusEvent = statusEventFromRemotePatch(parsed, sessionId, remoteInfo);
|
|
232788
232899
|
if (statusEvent) {
|
|
232900
|
+
cache2.setSessionStatus(sessionId, statusEvent.status);
|
|
232789
232901
|
let activityReady = true;
|
|
232790
232902
|
if (storage) {
|
|
232791
232903
|
activityReady = await persistRemoteSessionActivityFrame(storage, sessionId, remoteInfo, parsed).catch((error48) => {
|
|
@@ -232802,6 +232914,11 @@ function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnect
|
|
|
232802
232914
|
cache2.setFinished(sessionId);
|
|
232803
232915
|
cache2.broadcast(sessionId, raw);
|
|
232804
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
|
+
}
|
|
232805
232922
|
cache2.appendMessage(sessionId, raw, false);
|
|
232806
232923
|
cache2.broadcast(sessionId, raw);
|
|
232807
232924
|
let activityReady = true;
|
|
@@ -232857,6 +232974,7 @@ function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnect
|
|
|
232857
232974
|
}
|
|
232858
232975
|
}
|
|
232859
232976
|
} else if ("error" in parsed) {
|
|
232977
|
+
cache2.setSessionStatus(sessionId, "error");
|
|
232860
232978
|
cache2.appendMessage(sessionId, raw, false);
|
|
232861
232979
|
cache2.broadcast(sessionId, raw);
|
|
232862
232980
|
let activityReady = true;
|
|
@@ -232879,6 +232997,15 @@ function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnect
|
|
|
232879
232997
|
cache2.setFinished(sessionId);
|
|
232880
232998
|
}
|
|
232881
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
|
+
}
|
|
232882
233009
|
cache2.broadcast(sessionId, raw);
|
|
232883
233010
|
}
|
|
232884
233011
|
};
|
|
@@ -232917,8 +233044,12 @@ function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnect
|
|
|
232917
233044
|
syncing = false;
|
|
232918
233045
|
const currentEntry = cache2.get(sessionId);
|
|
232919
233046
|
const cachedSeq = entryPatchFrames(currentEntry.messages);
|
|
232920
|
-
const extendsCache = isSequencePrefix(cachedSeq, replayBuffer);
|
|
232921
|
-
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) {
|
|
232922
233053
|
const delta = replayBuffer.slice(cachedSeq.length);
|
|
232923
233054
|
console.log(`[AgentWS] Sync delta: ${delta.length} new entry patches for ${sessionId} (remote=${replayBuffer.length}, cached=${cachedSeq.length})`);
|
|
232924
233055
|
for (const msg of delta) {
|
|
@@ -239313,6 +239444,24 @@ var EventBus = class {
|
|
|
239313
239444
|
init_proxy_manager();
|
|
239314
239445
|
|
|
239315
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
|
+
}
|
|
239316
239465
|
var RemotePatchCache = class {
|
|
239317
239466
|
cache = /* @__PURE__ */ new Map();
|
|
239318
239467
|
getOrCreate(sessionId) {
|
|
@@ -239326,7 +239475,11 @@ var RemotePatchCache = class {
|
|
|
239326
239475
|
subscribers: /* @__PURE__ */ new Set(),
|
|
239327
239476
|
reconnecting: false,
|
|
239328
239477
|
reconnectTimer: null,
|
|
239329
|
-
reconnectAttempt: 0
|
|
239478
|
+
reconnectAttempt: 0,
|
|
239479
|
+
historyEpoch: null,
|
|
239480
|
+
latestEntryIndex: null,
|
|
239481
|
+
lastTurnEndEntryIndex: null,
|
|
239482
|
+
sessionStatus: null
|
|
239330
239483
|
};
|
|
239331
239484
|
this.cache.set(sessionId, entry);
|
|
239332
239485
|
}
|
|
@@ -239349,6 +239502,11 @@ var RemotePatchCache = class {
|
|
|
239349
239502
|
entry.messages.push(raw);
|
|
239350
239503
|
if (isJsonPatch) {
|
|
239351
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
|
+
}
|
|
239352
239510
|
}
|
|
239353
239511
|
}
|
|
239354
239512
|
/** Full cache replacement (used when cache is detected as stale). */
|
|
@@ -239359,6 +239517,15 @@ var RemotePatchCache = class {
|
|
|
239359
239517
|
const reconnecting = existing?.reconnecting ?? false;
|
|
239360
239518
|
const reconnectTimer = existing?.reconnectTimer ?? null;
|
|
239361
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;
|
|
239362
239529
|
this.cache.set(sessionId, {
|
|
239363
239530
|
messages,
|
|
239364
239531
|
patchCount,
|
|
@@ -239367,8 +239534,43 @@ var RemotePatchCache = class {
|
|
|
239367
239534
|
subscribers,
|
|
239368
239535
|
reconnecting,
|
|
239369
239536
|
reconnectTimer,
|
|
239370
|
-
reconnectAttempt
|
|
239537
|
+
reconnectAttempt,
|
|
239538
|
+
historyEpoch,
|
|
239539
|
+
latestEntryIndex,
|
|
239540
|
+
lastTurnEndEntryIndex,
|
|
239541
|
+
sessionStatus
|
|
239542
|
+
});
|
|
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
|
+
}
|
|
239371
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);
|
|
239372
239574
|
}
|
|
239373
239575
|
setFinished(sessionId) {
|
|
239374
239576
|
const entry = this.cache.get(sessionId);
|
|
@@ -239376,6 +239578,25 @@ var RemotePatchCache = class {
|
|
|
239376
239578
|
entry.finished = true;
|
|
239377
239579
|
}
|
|
239378
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
|
+
}
|
|
239379
239600
|
/** Store a persistent remote WebSocket connection. */
|
|
239380
239601
|
setRemoteWs(sessionId, ws) {
|
|
239381
239602
|
const entry = this.getOrCreate(sessionId);
|
|
@@ -239953,18 +240174,36 @@ var RemoteExecutorMonitor = class {
|
|
|
239953
240174
|
this.eventBus = eventBus;
|
|
239954
240175
|
this.storage = storage;
|
|
239955
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
|
+
});
|
|
239956
240185
|
}
|
|
239957
240186
|
reverseConnectManager;
|
|
239958
240187
|
eventBus;
|
|
239959
240188
|
storage;
|
|
239960
240189
|
remoteExecutorMap;
|
|
239961
|
-
/**
|
|
239962
|
-
|
|
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();
|
|
239963
240198
|
watch(localProcessId, remoteInfo) {
|
|
239964
|
-
|
|
240199
|
+
this.watched.set(localProcessId, remoteInfo);
|
|
240200
|
+
this.attach(localProcessId, remoteInfo);
|
|
240201
|
+
}
|
|
240202
|
+
attach(localProcessId, remoteInfo) {
|
|
240203
|
+
if (this.activeMonitors.has(localProcessId)) return;
|
|
239965
240204
|
const rcm = this.reverseConnectManager;
|
|
239966
240205
|
if (!rcm.isConnected(remoteInfo.remoteServerId)) {
|
|
239967
|
-
console.log(`[RemoteExecutorMonitor] remote ${remoteInfo.remoteServerId} not connected for ${localProcessId},
|
|
240206
|
+
console.log(`[RemoteExecutorMonitor] remote ${remoteInfo.remoteServerId} not connected for ${localProcessId}, deferring`);
|
|
239968
240207
|
return;
|
|
239969
240208
|
}
|
|
239970
240209
|
const channelId = randomUUID8();
|
|
@@ -239977,11 +240216,16 @@ var RemoteExecutorMonitor = class {
|
|
|
239977
240216
|
rcm.openVirtualChannel(remoteInfo.remoteServerId, channelId, wsPath);
|
|
239978
240217
|
const remoteWs = adapter;
|
|
239979
240218
|
setTimeout(() => adapter.emit("open"), 0);
|
|
239980
|
-
|
|
239981
|
-
|
|
239982
|
-
|
|
239983
|
-
|
|
239984
|
-
|
|
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
|
+
}
|
|
239985
240229
|
}
|
|
239986
240230
|
};
|
|
239987
240231
|
const outputChunks = [];
|
|
@@ -239992,7 +240236,10 @@ var RemoteExecutorMonitor = class {
|
|
|
239992
240236
|
outputChunks.push(parsed.data);
|
|
239993
240237
|
}
|
|
239994
240238
|
if (parsed.type === "finished") {
|
|
240239
|
+
this.watched.delete(localProcessId);
|
|
239995
240240
|
const info = this.remoteExecutorMap.get(localProcessId);
|
|
240241
|
+
const hasKnownExitCode = typeof parsed.exitCode === "number";
|
|
240242
|
+
const eventExitCode = hasKnownExitCode ? parsed.exitCode : 1;
|
|
239996
240243
|
if (info && !info.stoppedEmitted) {
|
|
239997
240244
|
info.stoppedEmitted = true;
|
|
239998
240245
|
let raw = outputChunks.join("");
|
|
@@ -240003,7 +240250,7 @@ var RemoteExecutorMonitor = class {
|
|
|
240003
240250
|
projectId: info.projectId ?? "",
|
|
240004
240251
|
executorId: info.executorId,
|
|
240005
240252
|
processId: localProcessId,
|
|
240006
|
-
exitCode:
|
|
240253
|
+
exitCode: eventExitCode,
|
|
240007
240254
|
target: info.remoteServerId,
|
|
240008
240255
|
tailOutput,
|
|
240009
240256
|
// Structured final message forwarded by the remote's finished
|
|
@@ -240012,33 +240259,33 @@ var RemoteExecutorMonitor = class {
|
|
|
240012
240259
|
});
|
|
240013
240260
|
}
|
|
240014
240261
|
this.remoteExecutorMap.delete(localProcessId);
|
|
240015
|
-
this.storage.remoteExecutorProcesses.markFinished(
|
|
240016
|
-
|
|
240017
|
-
typeof parsed.exitCode === "number" ? parsed.exitCode : 0
|
|
240018
|
-
).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) => {
|
|
240019
240264
|
console.error(`[RemoteExecutorMonitor] Failed to mark process ${localProcessId} finished:`, err);
|
|
240020
240265
|
});
|
|
240021
|
-
|
|
240266
|
+
cleanupActive(true);
|
|
240022
240267
|
}
|
|
240023
240268
|
} catch {
|
|
240024
240269
|
}
|
|
240025
240270
|
});
|
|
240026
240271
|
remoteWs.on("close", () => {
|
|
240027
|
-
|
|
240272
|
+
cleanupActive(false);
|
|
240028
240273
|
});
|
|
240029
240274
|
remoteWs.on("error", (error48) => {
|
|
240030
240275
|
console.error(`[RemoteExecutorMonitor] error for ${localProcessId}:`, error48);
|
|
240031
|
-
|
|
240276
|
+
cleanupActive(true);
|
|
240032
240277
|
});
|
|
240033
|
-
this.
|
|
240278
|
+
this.activeMonitors.set(localProcessId, () => cleanupActive(true));
|
|
240034
240279
|
console.log(`[RemoteExecutorMonitor] watching ${localProcessId}`);
|
|
240035
240280
|
}
|
|
240036
240281
|
unwatch(localProcessId) {
|
|
240037
|
-
this.
|
|
240282
|
+
this.watched.delete(localProcessId);
|
|
240283
|
+
this.activeMonitors.get(localProcessId)?.();
|
|
240038
240284
|
}
|
|
240039
240285
|
shutdown() {
|
|
240040
|
-
|
|
240041
|
-
this.
|
|
240286
|
+
this.watched.clear();
|
|
240287
|
+
for (const cleanup of [...this.activeMonitors.values()]) cleanup();
|
|
240288
|
+
this.activeMonitors.clear();
|
|
240042
240289
|
}
|
|
240043
240290
|
};
|
|
240044
240291
|
|
|
@@ -245367,6 +245614,45 @@ var MODEL_SUGGESTIONS = {
|
|
|
245367
245614
|
codex: ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-5.5"]
|
|
245368
245615
|
};
|
|
245369
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
|
+
|
|
245370
245656
|
// src/routes/agent-session-routes.ts
|
|
245371
245657
|
async function resolveProjectPath(projectId, storage) {
|
|
245372
245658
|
if (projectId.startsWith("path:")) {
|
|
@@ -245517,7 +245803,7 @@ var routes11 = async (fastify2) => {
|
|
|
245517
245803
|
return reply.code(200).send({ providers: providers2 });
|
|
245518
245804
|
});
|
|
245519
245805
|
fastify2.post("/api/path/agent-sessions", async (req, reply) => {
|
|
245520
|
-
const { path: projectPath, branch, permissionMode, agentType, force, model } = req.body;
|
|
245806
|
+
const { path: projectPath, branch, permissionMode, agentType, force, model, historyTurns } = req.body;
|
|
245521
245807
|
if (!projectPath) {
|
|
245522
245808
|
return reply.code(400).send({ error: "Path is required" });
|
|
245523
245809
|
}
|
|
@@ -245555,6 +245841,8 @@ var routes11 = async (fastify2) => {
|
|
|
245555
245841
|
}
|
|
245556
245842
|
const session = fastify2.agentSessionManager.getSession(sessionId);
|
|
245557
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;
|
|
245558
245846
|
const projection = await fastify2.storage.agentSessions.getActivityById(sessionId, "session-detail");
|
|
245559
245847
|
if (session?.workspaceCheckoutId && !projection) {
|
|
245560
245848
|
return reply.code(409).send({
|
|
@@ -245579,7 +245867,8 @@ var routes11 = async (fastify2) => {
|
|
|
245579
245867
|
checkoutDeletedAt: projection?.checkoutDeletedAt ?? null,
|
|
245580
245868
|
processAlive: session ? fastify2.agentSessionManager.getSessionProcessAlive(sessionId) : false
|
|
245581
245869
|
},
|
|
245582
|
-
messages
|
|
245870
|
+
messages: historyWindow ? historyWindow.entries.map((entry) => entry.message) : messages,
|
|
245871
|
+
...historyWindow ? { historyWindow } : {}
|
|
245583
245872
|
});
|
|
245584
245873
|
} catch (error48) {
|
|
245585
245874
|
console.error("[API] Failed to load path-based agent session:", error48);
|
|
@@ -245866,7 +246155,7 @@ var routes11 = async (fastify2) => {
|
|
|
245866
246155
|
if (!project) {
|
|
245867
246156
|
return reply.code(404).send({ error: "Project not found" });
|
|
245868
246157
|
}
|
|
245869
|
-
const { branch, permissionMode, agentType, model } = req.body;
|
|
246158
|
+
const { branch, permissionMode, agentType, model, historyTurns } = req.body;
|
|
245870
246159
|
let agentMode = project.agent_mode;
|
|
245871
246160
|
let useRemoteAgent = agentMode !== "local";
|
|
245872
246161
|
if (useRemoteAgent && agentMode === "remote") {
|
|
@@ -245899,7 +246188,7 @@ var routes11 = async (fastify2) => {
|
|
|
245899
246188
|
agentMode,
|
|
245900
246189
|
"POST",
|
|
245901
246190
|
`/api/path/agent-sessions`,
|
|
245902
|
-
{ path: remoteConfig.remote_path, branch, permissionMode, agentType, model }
|
|
246191
|
+
{ path: remoteConfig.remote_path, branch, permissionMode, agentType, model, historyTurns }
|
|
245903
246192
|
);
|
|
245904
246193
|
console.log(`[API] Remote proxy result: ok=${result.ok}, status=${result.status}, data=${JSON.stringify(result.data).substring(0, 500)}`);
|
|
245905
246194
|
if (result.ok) {
|
|
@@ -245926,10 +246215,21 @@ var routes11 = async (fastify2) => {
|
|
|
245926
246215
|
if (remoteData.messages && remoteData.messages.length > 0) {
|
|
245927
246216
|
const cacheEntry = fastify2.remotePatchCache.getOrCreate(localSessionId);
|
|
245928
246217
|
if (cacheEntry.messages.length === 0) {
|
|
245929
|
-
|
|
245930
|
-
|
|
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);
|
|
245931
246221
|
fastify2.remotePatchCache.appendMessage(localSessionId, JSON.stringify({ JsonPatch: patch }), true);
|
|
245932
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
|
+
}
|
|
245933
246233
|
console.log(`[API] findExisting proxy: seeded cache with ${remoteData.messages.length} msgs for ${localSessionId}`);
|
|
245934
246234
|
} else {
|
|
245935
246235
|
console.log(`[API] findExisting proxy: cache already has ${cacheEntry.messages.length} msgs for ${localSessionId} (remote returned ${remoteData.messages.length}), skipping seed`);
|
|
@@ -245943,7 +246243,8 @@ var routes11 = async (fastify2) => {
|
|
|
245943
246243
|
id: localSessionId,
|
|
245944
246244
|
projectId: req.params.projectId
|
|
245945
246245
|
},
|
|
245946
|
-
messages: remoteData.messages
|
|
246246
|
+
messages: remoteData.messages,
|
|
246247
|
+
...remoteData.historyWindow ? { historyWindow: remoteData.historyWindow } : {}
|
|
245947
246248
|
});
|
|
245948
246249
|
}
|
|
245949
246250
|
return reply.code(proxyStatus(result)).send(result.data);
|
|
@@ -245968,6 +246269,8 @@ var routes11 = async (fastify2) => {
|
|
|
245968
246269
|
}
|
|
245969
246270
|
const session = fastify2.agentSessionManager.getSession(sessionId);
|
|
245970
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;
|
|
245971
246274
|
const effectiveStatus = session?.status || "stopped";
|
|
245972
246275
|
return reply.code(200).send({
|
|
245973
246276
|
session: {
|
|
@@ -245980,7 +246283,8 @@ var routes11 = async (fastify2) => {
|
|
|
245980
246283
|
model: session?.model ?? null,
|
|
245981
246284
|
processAlive: session ? fastify2.agentSessionManager.getSessionProcessAlive(sessionId) : false
|
|
245982
246285
|
},
|
|
245983
|
-
messages
|
|
246286
|
+
messages: historyWindow ? historyWindow.entries.map((entry) => entry.message) : messages,
|
|
246287
|
+
...historyWindow ? { historyWindow } : {}
|
|
245984
246288
|
});
|
|
245985
246289
|
} catch (error48) {
|
|
245986
246290
|
console.error("[API] Failed to load agent session:", error48);
|
|
@@ -246165,6 +246469,121 @@ var routes11 = async (fastify2) => {
|
|
|
246165
246469
|
});
|
|
246166
246470
|
}
|
|
246167
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
|
+
);
|
|
246168
246587
|
fastify2.get(
|
|
246169
246588
|
"/api/agent-sessions/:sessionId/brief-source",
|
|
246170
246589
|
async (req, reply) => {
|
|
@@ -249434,6 +249853,13 @@ function attachWsHeartbeat(socket, { label, intervalMs = DEFAULT_INTERVAL_MS, ke
|
|
|
249434
249853
|
}
|
|
249435
249854
|
|
|
249436
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
|
+
}
|
|
249437
249863
|
var routes23 = async (fastify2) => {
|
|
249438
249864
|
fastify2.reverseConnectManager.setStatusChangeHandler((remoteServerId, status) => {
|
|
249439
249865
|
if (status !== "online") return;
|
|
@@ -249645,6 +250071,8 @@ var routes23 = async (fastify2) => {
|
|
|
249645
250071
|
{ websocket: true },
|
|
249646
250072
|
async (socket, req) => {
|
|
249647
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;
|
|
249648
250076
|
console.log(`[AgentWS] Connection attempt for session ${sessionId} (auth=${fastify2.authEnabled})`);
|
|
249649
250077
|
let principalUserId = fastify2.authEnabled ? null : "local";
|
|
249650
250078
|
if (fastify2.authEnabled) {
|
|
@@ -249694,15 +250122,42 @@ var routes23 = async (fastify2) => {
|
|
|
249694
250122
|
const cacheEntry = cache2.getOrCreate(sessionId);
|
|
249695
250123
|
console.log(`[AgentWS] WS connect: cacheEntry for ${sessionId} has messages.length=${cacheEntry.messages.length} finished=${cacheEntry.finished} remoteWsOpen=${!!cache2.getRemoteWs(sessionId)}`);
|
|
249696
250124
|
if (cacheEntry.messages.length > 0) {
|
|
249697
|
-
|
|
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
|
+
}
|
|
249698
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
|
+
}
|
|
249699
250154
|
try {
|
|
249700
250155
|
socket.send(raw);
|
|
249701
250156
|
} catch {
|
|
249702
250157
|
}
|
|
249703
250158
|
}
|
|
249704
250159
|
try {
|
|
249705
|
-
socket.send(JSON.stringify({ Ready: true }));
|
|
250160
|
+
socket.send(JSON.stringify({ Ready: true, historyEpoch: cacheEntry.historyEpoch ?? void 0 }));
|
|
249706
250161
|
} catch {
|
|
249707
250162
|
}
|
|
249708
250163
|
if (cacheEntry.finished) {
|
|
@@ -249728,7 +250183,8 @@ var routes23 = async (fastify2) => {
|
|
|
249728
250183
|
fastify2.reverseConnectManager,
|
|
249729
250184
|
fastify2.eventBus,
|
|
249730
250185
|
fastify2.agentSessionManager,
|
|
249731
|
-
fastify2.storage
|
|
250186
|
+
fastify2.storage,
|
|
250187
|
+
{ afterEntryIndex, historyEpoch }
|
|
249732
250188
|
);
|
|
249733
250189
|
}
|
|
249734
250190
|
if (cache2.getRemoteWs(sessionId)) {
|
|
@@ -249758,7 +250214,7 @@ var routes23 = async (fastify2) => {
|
|
|
249758
250214
|
});
|
|
249759
250215
|
return;
|
|
249760
250216
|
}
|
|
249761
|
-
const unsubscribe = fastify2.agentSessionManager.subscribe(sessionId, socket);
|
|
250217
|
+
const unsubscribe = fastify2.agentSessionManager.subscribe(sessionId, socket, { afterEntryIndex, historyEpoch });
|
|
249762
250218
|
if (!unsubscribe) {
|
|
249763
250219
|
console.log(`[AgentWS] Session ${sessionId} not found`);
|
|
249764
250220
|
stopHeartbeat();
|
|
@@ -253437,6 +253893,8 @@ var WORKER_CAPABILITIES = {
|
|
|
253437
253893
|
"http:POST /api/path/agent-sessions": { since: "0.2.0", summary: "\u521B\u5EFA\u4F1A\u8BDD" },
|
|
253438
253894
|
"http:POST /api/path/agent-sessions/new": { since: "0.2.0", summary: "\u521B\u5EFA\u4F1A\u8BDD(\u6307\u5B9A ID)" },
|
|
253439
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" },
|
|
253440
253898
|
// Additive: a worker below 0.3.6 404s it and the hub's intent-brief
|
|
253441
253899
|
// distillation degrades to the deterministic excerpt (tier 2).
|
|
253442
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)" },
|