lody 0.45.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1114 -463
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -45,13 +45,13 @@ import require$$1$6 from "string_decoder";
|
|
|
45
45
|
import * as http$2 from "http";
|
|
46
46
|
import http__default from "http";
|
|
47
47
|
import require$$1$7 from "https";
|
|
48
|
-
import require$$0$a, { execSync, exec, execFile as execFile$1 } from "child_process";
|
|
48
|
+
import require$$0$a, { execSync, exec, execFileSync, execFile as execFile$1 } from "child_process";
|
|
49
49
|
import { randomFillSync, randomUUID as randomUUID$1, createHash as createHash$1 } from "node:crypto";
|
|
50
50
|
import require$$0$b from "net";
|
|
51
51
|
import require$$4$3 from "tls";
|
|
52
52
|
import { i as imports, _ as __wbg_set_wasm$1, r as rawWasm, L as LoroDoc, E as EphemeralStoreWasm, U as UndoManager, c as callPendingEvents$3, a as LoroTree, b as LoroText, d as LoroMovableList, e as LoroList, f as LoroMap, g as __vite__initWasm, V as VersionVector, h as decodeImportBlobMeta, __tla as __tla_0 } from "./chunks/loro_wasm_bg-DgxHrrrp.js";
|
|
53
53
|
import * as fs$5 from "fs/promises";
|
|
54
|
-
import fs__default$1, { stat as stat$1, readFile as readFile$1 } from "fs/promises";
|
|
54
|
+
import fs__default$1, { stat as stat$1, readFile as readFile$1, statfs } from "fs/promises";
|
|
55
55
|
import fsPromises, { stat, open } from "node:fs/promises";
|
|
56
56
|
import { fileURLToPath } from "node:url";
|
|
57
57
|
import require$$2$7 from "assert";
|
|
@@ -36820,7 +36820,7 @@ Mongoose Error Code: ${error2.code}` : ""}`
|
|
|
36820
36820
|
return client;
|
|
36821
36821
|
}
|
|
36822
36822
|
const name = "lody";
|
|
36823
|
-
const version$4 = "0.
|
|
36823
|
+
const version$4 = "0.46.0";
|
|
36824
36824
|
const description = "Lody Agent CLI tool for managing remote command execution";
|
|
36825
36825
|
const type = "module";
|
|
36826
36826
|
const main$3 = "dist/index.js";
|
|
@@ -36864,7 +36864,7 @@ Mongoose Error Code: ${error2.code}` : ""}`
|
|
|
36864
36864
|
};
|
|
36865
36865
|
const optionalDependencies = {
|
|
36866
36866
|
"acp-extension-claude": "0.30.0",
|
|
36867
|
-
"acp-extension-codex": "0.
|
|
36867
|
+
"acp-extension-codex": "0.12.0"
|
|
36868
36868
|
};
|
|
36869
36869
|
const devDependencies = {
|
|
36870
36870
|
"@agentclientprotocol/sdk": "catalog:",
|
|
@@ -78436,6 +78436,7 @@ Task description:
|
|
|
78436
78436
|
"session_init_failed",
|
|
78437
78437
|
"session_restore_failed",
|
|
78438
78438
|
"session_not_found",
|
|
78439
|
+
"memory_pressure",
|
|
78439
78440
|
"acp_not_ready",
|
|
78440
78441
|
"agent_disconnected",
|
|
78441
78442
|
"turn_pre_prompt_failed",
|
|
@@ -81990,6 +81991,59 @@ ${tailedOutput}` : null;
|
|
|
81990
81991
|
gatewayBaseUrl: string$2().url().optional()
|
|
81991
81992
|
});
|
|
81992
81993
|
const LORO_STREAMS_TOKEN_REFRESH_SKEW_MS = 3e4;
|
|
81994
|
+
const LORO_STREAMS_TOKEN_STORAGE_KEY_PREFIX = "lody:loroStreamsToken";
|
|
81995
|
+
function getTokenStorageKey(workspaceId) {
|
|
81996
|
+
return `${LORO_STREAMS_TOKEN_STORAGE_KEY_PREFIX}:${workspaceId}`;
|
|
81997
|
+
}
|
|
81998
|
+
function getLocalStorage() {
|
|
81999
|
+
try {
|
|
82000
|
+
const storage = globalThis.localStorage;
|
|
82001
|
+
if (storage && typeof storage.getItem === "function" && typeof storage.setItem === "function" && typeof storage.removeItem === "function") {
|
|
82002
|
+
return storage;
|
|
82003
|
+
}
|
|
82004
|
+
} catch {
|
|
82005
|
+
}
|
|
82006
|
+
return void 0;
|
|
82007
|
+
}
|
|
82008
|
+
function readCachedTokenFromStorage(workspaceId) {
|
|
82009
|
+
const storage = getLocalStorage();
|
|
82010
|
+
if (!storage) return null;
|
|
82011
|
+
try {
|
|
82012
|
+
const raw = storage.getItem(getTokenStorageKey(workspaceId));
|
|
82013
|
+
if (!raw) return null;
|
|
82014
|
+
const parsed = JSON.parse(raw);
|
|
82015
|
+
if (typeof parsed === "object" && parsed !== null && "token" in parsed && typeof parsed.token === "string" && "expiresAtMs" in parsed && typeof parsed.expiresAtMs === "number") {
|
|
82016
|
+
const result = {
|
|
82017
|
+
token: parsed.token,
|
|
82018
|
+
expiresAtMs: parsed.expiresAtMs
|
|
82019
|
+
};
|
|
82020
|
+
const gatewayBaseUrl = parsed.gatewayBaseUrl;
|
|
82021
|
+
if (typeof gatewayBaseUrl === "string") {
|
|
82022
|
+
result.gatewayBaseUrl = gatewayBaseUrl;
|
|
82023
|
+
}
|
|
82024
|
+
return result;
|
|
82025
|
+
}
|
|
82026
|
+
return null;
|
|
82027
|
+
} catch {
|
|
82028
|
+
return null;
|
|
82029
|
+
}
|
|
82030
|
+
}
|
|
82031
|
+
function writeCachedTokenToStorage(workspaceId, token2) {
|
|
82032
|
+
const storage = getLocalStorage();
|
|
82033
|
+
if (!storage) return;
|
|
82034
|
+
try {
|
|
82035
|
+
storage.setItem(getTokenStorageKey(workspaceId), JSON.stringify(token2));
|
|
82036
|
+
} catch {
|
|
82037
|
+
}
|
|
82038
|
+
}
|
|
82039
|
+
function clearCachedTokenFromStorage(workspaceId) {
|
|
82040
|
+
const storage = getLocalStorage();
|
|
82041
|
+
if (!storage) return;
|
|
82042
|
+
try {
|
|
82043
|
+
storage.removeItem(getTokenStorageKey(workspaceId));
|
|
82044
|
+
} catch {
|
|
82045
|
+
}
|
|
82046
|
+
}
|
|
81993
82047
|
const buildLoroStreamsTokenEndpoint = (baseUrl) => `${baseUrl.replace(/\/+$/g, "")}/api/loro-streams/token`;
|
|
81994
82048
|
class LoroStreamsTokenAuthError extends Error {
|
|
81995
82049
|
constructor(message, status, detail) {
|
|
@@ -82002,7 +82056,7 @@ ${tailedOutput}` : null;
|
|
|
82002
82056
|
function createLoroStreamsTokenProvider(options) {
|
|
82003
82057
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
82004
82058
|
const refreshSkewMs = options.refreshSkewMs ?? LORO_STREAMS_TOKEN_REFRESH_SKEW_MS;
|
|
82005
|
-
let cached2 =
|
|
82059
|
+
let cached2 = readCachedTokenFromStorage(options.workspaceId);
|
|
82006
82060
|
let inFlight = null;
|
|
82007
82061
|
let generation = 0;
|
|
82008
82062
|
const resolveAuthToken = async () => {
|
|
@@ -82049,6 +82103,7 @@ ${tailedOutput}` : null;
|
|
|
82049
82103
|
inFlight = fetchToken().then((nextToken) => {
|
|
82050
82104
|
if (generation === fetchGeneration) {
|
|
82051
82105
|
cached2 = nextToken;
|
|
82106
|
+
writeCachedTokenToStorage(options.workspaceId, nextToken);
|
|
82052
82107
|
}
|
|
82053
82108
|
return nextToken;
|
|
82054
82109
|
}).finally(() => {
|
|
@@ -82063,6 +82118,7 @@ ${tailedOutput}` : null;
|
|
|
82063
82118
|
cached2 = null;
|
|
82064
82119
|
inFlight = null;
|
|
82065
82120
|
generation++;
|
|
82121
|
+
clearCachedTokenFromStorage(options.workspaceId);
|
|
82066
82122
|
};
|
|
82067
82123
|
return {
|
|
82068
82124
|
getToken: async () => {
|
|
@@ -89037,8 +89093,20 @@ ${val.stack}`;
|
|
|
89037
89093
|
const patchObject = patch2;
|
|
89038
89094
|
for (const key2 of Object.keys(patchObject)) {
|
|
89039
89095
|
const rawValue = patchObject[key2];
|
|
89040
|
-
|
|
89041
|
-
if (
|
|
89096
|
+
const hadKey = base ? key2 in base : false;
|
|
89097
|
+
if (rawValue === void 0) {
|
|
89098
|
+
if (!hadKey) continue;
|
|
89099
|
+
this.metaFlock.delete([
|
|
89100
|
+
"m",
|
|
89101
|
+
docId,
|
|
89102
|
+
key2
|
|
89103
|
+
]);
|
|
89104
|
+
delete next[key2];
|
|
89105
|
+
outPatch[key2] = null;
|
|
89106
|
+
changed = true;
|
|
89107
|
+
continue;
|
|
89108
|
+
}
|
|
89109
|
+
if (jsonEquals(hadKey ? base[key2] : void 0, rawValue)) continue;
|
|
89042
89110
|
this.metaFlock.put([
|
|
89043
89111
|
"m",
|
|
89044
89112
|
docId,
|
|
@@ -91144,7 +91212,7 @@ ${val.stack}`;
|
|
|
91144
91212
|
live: "sse"
|
|
91145
91213
|
});
|
|
91146
91214
|
const controller = new AbortController();
|
|
91147
|
-
const signal = mergeAbortSignals(input2.signal, controller.signal);
|
|
91215
|
+
const signal = mergeAbortSignals$2(input2.signal, controller.signal);
|
|
91148
91216
|
try {
|
|
91149
91217
|
const response = await this.http.fetchAuthorized(requestUrl, {
|
|
91150
91218
|
method: "GET",
|
|
@@ -91647,7 +91715,7 @@ ${val.stack}`;
|
|
|
91647
91715
|
}
|
|
91648
91716
|
};
|
|
91649
91717
|
}
|
|
91650
|
-
function mergeAbortSignals(...signals2) {
|
|
91718
|
+
function mergeAbortSignals$2(...signals2) {
|
|
91651
91719
|
const active2 = signals2.filter((signal) => signal != null);
|
|
91652
91720
|
if (active2.length === 0) return;
|
|
91653
91721
|
if (active2.length === 1) return active2[0];
|
|
@@ -91666,84 +91734,6 @@ ${val.stack}`;
|
|
|
91666
91734
|
}
|
|
91667
91735
|
return controller.signal;
|
|
91668
91736
|
}
|
|
91669
|
-
function composeBeforeRemoteCursorSaveHooks(...hooks2) {
|
|
91670
|
-
const active2 = hooks2.filter((hook) => hook != null);
|
|
91671
|
-
if (active2.length === 0) return;
|
|
91672
|
-
return async (context2) => {
|
|
91673
|
-
for (const hook of active2) await hook(context2);
|
|
91674
|
-
};
|
|
91675
|
-
}
|
|
91676
|
-
async function persistRemoteCursor(input2) {
|
|
91677
|
-
const next = {
|
|
91678
|
-
...input2.cursor,
|
|
91679
|
-
updatedAtMs: (input2.now ?? Date.now)()
|
|
91680
|
-
};
|
|
91681
|
-
if (input2.beforeRemoteCursorSave != null) await input2.beforeRemoteCursorSave({
|
|
91682
|
-
cursor: next,
|
|
91683
|
-
source: input2.source
|
|
91684
|
-
});
|
|
91685
|
-
await input2.remoteCursorStore.save(next);
|
|
91686
|
-
return next;
|
|
91687
|
-
}
|
|
91688
|
-
function key(streamUrl) {
|
|
91689
|
-
return streamUrl;
|
|
91690
|
-
}
|
|
91691
|
-
function cloneJsonValue(value) {
|
|
91692
|
-
return structuredClone(value);
|
|
91693
|
-
}
|
|
91694
|
-
function cloneRemoteCursor(cursor) {
|
|
91695
|
-
return {
|
|
91696
|
-
...cursor,
|
|
91697
|
-
serverLowerBoundVersion: cloneJsonValue(cursor.serverLowerBoundVersion)
|
|
91698
|
-
};
|
|
91699
|
-
}
|
|
91700
|
-
var InMemoryRemoteCursorStore = class {
|
|
91701
|
-
data = /* @__PURE__ */ new Map();
|
|
91702
|
-
async load(streamUrl) {
|
|
91703
|
-
const value = this.data.get(key(streamUrl));
|
|
91704
|
-
return value == null ? null : cloneRemoteCursor(value);
|
|
91705
|
-
}
|
|
91706
|
-
async save(cursor) {
|
|
91707
|
-
this.data.set(key(cursor.streamUrl), cloneRemoteCursor(cursor));
|
|
91708
|
-
}
|
|
91709
|
-
async delete(streamUrl) {
|
|
91710
|
-
this.data.delete(key(streamUrl));
|
|
91711
|
-
}
|
|
91712
|
-
};
|
|
91713
|
-
function createInitialRemoteCursor(input2) {
|
|
91714
|
-
return {
|
|
91715
|
-
streamUrl: input2.streamUrl,
|
|
91716
|
-
nextOffset: input2.nextOffset,
|
|
91717
|
-
serverLowerBoundVersion: cloneJsonValue(input2.serverLowerBoundVersion),
|
|
91718
|
-
updatedAtMs: Date.now()
|
|
91719
|
-
};
|
|
91720
|
-
}
|
|
91721
|
-
const DEFAULT_RECONNECT_CONFIG = {
|
|
91722
|
-
delays: [
|
|
91723
|
-
0,
|
|
91724
|
-
500,
|
|
91725
|
-
1e3,
|
|
91726
|
-
2e3,
|
|
91727
|
-
4e3,
|
|
91728
|
-
8e3,
|
|
91729
|
-
15e3
|
|
91730
|
-
],
|
|
91731
|
-
jitterFraction: 0.2,
|
|
91732
|
-
maxAttempts: Infinity,
|
|
91733
|
-
connectTimeoutMs: 1e4,
|
|
91734
|
-
pollTimeoutMs: 3e4,
|
|
91735
|
-
liveInactivityTimeoutMs: 45e3
|
|
91736
|
-
};
|
|
91737
|
-
function applyJitter(baseMs, fraction) {
|
|
91738
|
-
if (baseMs === 0) return 0;
|
|
91739
|
-
const jitter = baseMs * fraction * (2 * Math.random() - 1);
|
|
91740
|
-
return Math.max(0, Math.round(baseMs + jitter));
|
|
91741
|
-
}
|
|
91742
|
-
function computeRetryDelay(attempt, config2 = DEFAULT_RECONNECT_CONFIG) {
|
|
91743
|
-
const { delays, jitterFraction } = config2;
|
|
91744
|
-
const base = delays[Math.min(attempt, delays.length - 1)];
|
|
91745
|
-
return applyJitter(base, jitterFraction);
|
|
91746
|
-
}
|
|
91747
91737
|
const DEFAULT_COMPACT_THRESHOLD = 64;
|
|
91748
91738
|
const DEFAULT_MIN_CAPACITY = 16;
|
|
91749
91739
|
var Deque = class {
|
|
@@ -91863,6 +91853,32 @@ ${val.stack}`;
|
|
|
91863
91853
|
this.tail = length2;
|
|
91864
91854
|
}
|
|
91865
91855
|
};
|
|
91856
|
+
const DEFAULT_RECONNECT_CONFIG = {
|
|
91857
|
+
delays: [
|
|
91858
|
+
0,
|
|
91859
|
+
500,
|
|
91860
|
+
1e3,
|
|
91861
|
+
2e3,
|
|
91862
|
+
4e3,
|
|
91863
|
+
8e3,
|
|
91864
|
+
15e3
|
|
91865
|
+
],
|
|
91866
|
+
jitterFraction: 0.2,
|
|
91867
|
+
maxAttempts: Infinity,
|
|
91868
|
+
connectTimeoutMs: 1e4,
|
|
91869
|
+
pollTimeoutMs: 3e4,
|
|
91870
|
+
liveInactivityTimeoutMs: 45e3
|
|
91871
|
+
};
|
|
91872
|
+
function applyJitter(baseMs, fraction) {
|
|
91873
|
+
if (baseMs === 0) return 0;
|
|
91874
|
+
const jitter = baseMs * fraction * (2 * Math.random() - 1);
|
|
91875
|
+
return Math.max(0, Math.round(baseMs + jitter));
|
|
91876
|
+
}
|
|
91877
|
+
function computeRetryDelay(attempt, config2 = DEFAULT_RECONNECT_CONFIG) {
|
|
91878
|
+
const { delays, jitterFraction } = config2;
|
|
91879
|
+
const base = delays[Math.min(attempt, delays.length - 1)];
|
|
91880
|
+
return applyJitter(base, jitterFraction);
|
|
91881
|
+
}
|
|
91866
91882
|
const BINARY_CONTENT_TYPE = "application/octet-stream";
|
|
91867
91883
|
function encodeItems(items) {
|
|
91868
91884
|
const total = items.reduce((sum, item) => sum + 4 + item.byteLength, 0);
|
|
@@ -92305,6 +92321,25 @@ stream:${scope2.streamId}`;
|
|
|
92305
92321
|
});
|
|
92306
92322
|
});
|
|
92307
92323
|
}
|
|
92324
|
+
function mergeAbortSignals(...signals2) {
|
|
92325
|
+
const active2 = signals2.filter((signal) => signal != null);
|
|
92326
|
+
if (active2.length === 0) return;
|
|
92327
|
+
if (active2.length === 1) return active2[0];
|
|
92328
|
+
if (typeof AbortSignal.any === "function") return AbortSignal.any(active2);
|
|
92329
|
+
const controller = new AbortController();
|
|
92330
|
+
for (const signal of active2) {
|
|
92331
|
+
if (signal.aborted) {
|
|
92332
|
+
controller.abort(signal.reason);
|
|
92333
|
+
break;
|
|
92334
|
+
}
|
|
92335
|
+
signal.addEventListener("abort", () => {
|
|
92336
|
+
controller.abort(signal.reason);
|
|
92337
|
+
}, {
|
|
92338
|
+
once: true
|
|
92339
|
+
});
|
|
92340
|
+
}
|
|
92341
|
+
return controller.signal;
|
|
92342
|
+
}
|
|
92308
92343
|
function classifyFetchError(err2) {
|
|
92309
92344
|
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
92310
92345
|
const status = err2 instanceof HttpError ? err2.status : void 0;
|
|
@@ -92508,7 +92543,10 @@ stream:${scope2.streamId}`;
|
|
|
92508
92543
|
streamUrl;
|
|
92509
92544
|
adapter;
|
|
92510
92545
|
client;
|
|
92546
|
+
fetchImpl;
|
|
92547
|
+
authProvider;
|
|
92511
92548
|
reconnectConfig;
|
|
92549
|
+
authRefreshInFlight;
|
|
92512
92550
|
constructor(options) {
|
|
92513
92551
|
this.streamUrl = options.streamUrl;
|
|
92514
92552
|
this.adapter = options.adapter;
|
|
@@ -92524,6 +92562,8 @@ stream:${scope2.streamId}`;
|
|
|
92524
92562
|
pollTimeoutMs: options.reconnectConfig.pollTimeoutMs
|
|
92525
92563
|
}
|
|
92526
92564
|
});
|
|
92565
|
+
this.fetchImpl = options.fetchImpl;
|
|
92566
|
+
this.authProvider = options.authProvider;
|
|
92527
92567
|
this.reconnectConfig = options.reconnectConfig;
|
|
92528
92568
|
}
|
|
92529
92569
|
async createStreamRequest() {
|
|
@@ -92625,6 +92665,166 @@ stream:${scope2.streamId}`;
|
|
|
92625
92665
|
};
|
|
92626
92666
|
throw this.toTransportFailure(error2);
|
|
92627
92667
|
}
|
|
92668
|
+
async appendItems(updates) {
|
|
92669
|
+
const result = await this.client.append({
|
|
92670
|
+
part: {
|
|
92671
|
+
contentType: BINARY_CONTENT_TYPE,
|
|
92672
|
+
body: encodeItems(updates)
|
|
92673
|
+
}
|
|
92674
|
+
});
|
|
92675
|
+
if (result.ok) return result.result.nextOffset;
|
|
92676
|
+
throw this.toTransportFailure(result.result);
|
|
92677
|
+
}
|
|
92678
|
+
async appendEphemeralUpdate(update2, signal) {
|
|
92679
|
+
if (update2.byteLength === 0) return;
|
|
92680
|
+
const response = await this.fetchAuthorized(this.streamUrl, {
|
|
92681
|
+
method: "POST",
|
|
92682
|
+
headers: {
|
|
92683
|
+
"Content-Type": BINARY_CONTENT_TYPE
|
|
92684
|
+
},
|
|
92685
|
+
body: update2,
|
|
92686
|
+
signal
|
|
92687
|
+
}, this.reconnectConfig.connectTimeoutMs, "connect");
|
|
92688
|
+
if (response.status === 200 || response.status === 204) return;
|
|
92689
|
+
throw await this.responseToEphemeralFailure(response, "ephemeral append");
|
|
92690
|
+
}
|
|
92691
|
+
async readEphemeralSse(onBootstrap, onData, signal) {
|
|
92692
|
+
const requestUrl = this.ephemeralReadEndpoint();
|
|
92693
|
+
const response = await this.fetchAuthorized(requestUrl, {
|
|
92694
|
+
method: "GET",
|
|
92695
|
+
headers: {
|
|
92696
|
+
Accept: "text/event-stream"
|
|
92697
|
+
},
|
|
92698
|
+
signal
|
|
92699
|
+
}, this.reconnectConfig.connectTimeoutMs, "connect");
|
|
92700
|
+
if (response.status === 404) return {
|
|
92701
|
+
kind: "not-found"
|
|
92702
|
+
};
|
|
92703
|
+
if (!response.ok) {
|
|
92704
|
+
if (response.status === 400) {
|
|
92705
|
+
const message = await response.text();
|
|
92706
|
+
if (isSseUnsupportedErrorText(message)) return {
|
|
92707
|
+
kind: "unsupported"
|
|
92708
|
+
};
|
|
92709
|
+
throw new HttpError(message.length > 0 ? `ephemeral live read failed: ${message}` : "ephemeral live read failed", response.status);
|
|
92710
|
+
}
|
|
92711
|
+
throw await this.responseToEphemeralFailure(response, "ephemeral live read");
|
|
92712
|
+
}
|
|
92713
|
+
if (response.body == null) throw new ProtocolError("ephemeral sse response missing body");
|
|
92714
|
+
if (!(response.headers.get("Content-Type") ?? "").toLowerCase().includes("text/event-stream")) throw new ProtocolError("ephemeral sse response must use text/event-stream content type");
|
|
92715
|
+
const dataEncoding = response.headers.get("Stream-SSE-Data-Encoding") ?? void 0;
|
|
92716
|
+
const consume = async () => {
|
|
92717
|
+
let sawBootstrap = false;
|
|
92718
|
+
for await (const event of readSseEvents(response.body)) {
|
|
92719
|
+
if (!sawBootstrap) {
|
|
92720
|
+
if (event.event !== "bootstrap") throw new ProtocolError(`ephemeral sse must start with bootstrap, got '${event.event}'`);
|
|
92721
|
+
await onBootstrap(decodeSsePayload(event.data, dataEncoding));
|
|
92722
|
+
sawBootstrap = true;
|
|
92723
|
+
continue;
|
|
92724
|
+
}
|
|
92725
|
+
if (event.event === "data") {
|
|
92726
|
+
await onData(decodeSsePayload(event.data, dataEncoding));
|
|
92727
|
+
continue;
|
|
92728
|
+
}
|
|
92729
|
+
throw new ProtocolError(`unexpected ephemeral sse event '${event.event}'`);
|
|
92730
|
+
}
|
|
92731
|
+
if (!sawBootstrap) throw new ProtocolError("ephemeral sse ended before bootstrap");
|
|
92732
|
+
return {
|
|
92733
|
+
kind: "ok"
|
|
92734
|
+
};
|
|
92735
|
+
};
|
|
92736
|
+
let abortHandler;
|
|
92737
|
+
const abortRace = signal != null && !signal.aborted ? new Promise((_2, reject) => {
|
|
92738
|
+
abortHandler = () => {
|
|
92739
|
+
response.body?.cancel().catch(() => {
|
|
92740
|
+
});
|
|
92741
|
+
reject(signal.reason ?? new DOMException("The operation was aborted.", "AbortError"));
|
|
92742
|
+
};
|
|
92743
|
+
signal.addEventListener("abort", abortHandler, {
|
|
92744
|
+
once: true
|
|
92745
|
+
});
|
|
92746
|
+
}) : null;
|
|
92747
|
+
try {
|
|
92748
|
+
if (signal?.aborted) {
|
|
92749
|
+
await response.body.cancel().catch(() => {
|
|
92750
|
+
});
|
|
92751
|
+
throw signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
|
|
92752
|
+
}
|
|
92753
|
+
return abortRace == null ? await consume() : await Promise.race([
|
|
92754
|
+
consume(),
|
|
92755
|
+
abortRace
|
|
92756
|
+
]);
|
|
92757
|
+
} finally {
|
|
92758
|
+
if (abortHandler != null && signal != null) signal.removeEventListener("abort", abortHandler);
|
|
92759
|
+
}
|
|
92760
|
+
}
|
|
92761
|
+
async fetchAuthorized(input2, init2, timeoutMs, phase) {
|
|
92762
|
+
const firstToken = await this.resolveToken({
|
|
92763
|
+
reason: "request"
|
|
92764
|
+
});
|
|
92765
|
+
const firstResponse = await this.fetchWithTimeout(input2, {
|
|
92766
|
+
...init2,
|
|
92767
|
+
headers: this.attachAuthorization(init2.headers, firstToken)
|
|
92768
|
+
}, timeoutMs, phase);
|
|
92769
|
+
if (this.authProvider == null || firstResponse.status !== 401 && firstResponse.status !== 403) return firstResponse;
|
|
92770
|
+
await firstResponse.body?.cancel().catch(() => {
|
|
92771
|
+
});
|
|
92772
|
+
const refreshedToken = await this.refreshTokenAfterFailure(firstResponse.status, firstToken);
|
|
92773
|
+
return this.fetchWithTimeout(input2, {
|
|
92774
|
+
...init2,
|
|
92775
|
+
headers: this.attachAuthorization(init2.headers, refreshedToken)
|
|
92776
|
+
}, timeoutMs, phase);
|
|
92777
|
+
}
|
|
92778
|
+
async fetchWithTimeout(input2, init2, timeoutMs, phase) {
|
|
92779
|
+
const controller = new AbortController();
|
|
92780
|
+
const signal = mergeAbortSignals(init2.signal, controller.signal);
|
|
92781
|
+
const timer2 = setTimeout(() => {
|
|
92782
|
+
controller.abort(new TimeoutError(phase, timeoutMs));
|
|
92783
|
+
}, timeoutMs);
|
|
92784
|
+
try {
|
|
92785
|
+
return await this.fetchImpl(input2, {
|
|
92786
|
+
...init2,
|
|
92787
|
+
signal
|
|
92788
|
+
});
|
|
92789
|
+
} catch (error2) {
|
|
92790
|
+
if (controller.signal.aborted && controller.signal.reason instanceof TimeoutError) throw controller.signal.reason;
|
|
92791
|
+
throw error2;
|
|
92792
|
+
} finally {
|
|
92793
|
+
clearTimeout(timer2);
|
|
92794
|
+
}
|
|
92795
|
+
}
|
|
92796
|
+
async resolveToken(context2) {
|
|
92797
|
+
return await this.authProvider?.(context2);
|
|
92798
|
+
}
|
|
92799
|
+
async refreshTokenAfterFailure(status, previousToken) {
|
|
92800
|
+
if (this.authProvider == null) return;
|
|
92801
|
+
if (this.authRefreshInFlight == null) this.authRefreshInFlight = this.resolveToken({
|
|
92802
|
+
reason: "unauthorized",
|
|
92803
|
+
status,
|
|
92804
|
+
previousToken
|
|
92805
|
+
}).finally(() => {
|
|
92806
|
+
this.authRefreshInFlight = void 0;
|
|
92807
|
+
});
|
|
92808
|
+
return await this.authRefreshInFlight;
|
|
92809
|
+
}
|
|
92810
|
+
attachAuthorization(base, token2) {
|
|
92811
|
+
const headers = new Headers(base);
|
|
92812
|
+
if (token2 == null || token2.length === 0) {
|
|
92813
|
+
headers.delete("Authorization");
|
|
92814
|
+
return headers;
|
|
92815
|
+
}
|
|
92816
|
+
headers.set("Authorization", `Bearer ${token2}`);
|
|
92817
|
+
return headers;
|
|
92818
|
+
}
|
|
92819
|
+
async responseToEphemeralFailure(response, context2) {
|
|
92820
|
+
const message = await response.text().catch(() => "");
|
|
92821
|
+
return new HttpError(message.length > 0 ? `${context2} failed: ${message}` : `${context2} failed with status ${response.status}`, response.status);
|
|
92822
|
+
}
|
|
92823
|
+
ephemeralReadEndpoint() {
|
|
92824
|
+
const target = new URL(this.streamUrl);
|
|
92825
|
+
target.searchParams.set("live", "sse");
|
|
92826
|
+
return target.toString();
|
|
92827
|
+
}
|
|
92628
92828
|
toTransportFailure(error2) {
|
|
92629
92829
|
switch (error2.code) {
|
|
92630
92830
|
case "bad_request":
|
|
@@ -92766,6 +92966,58 @@ stream:${scope2.streamId}`;
|
|
|
92766
92966
|
};
|
|
92767
92967
|
}
|
|
92768
92968
|
};
|
|
92969
|
+
function composeBeforeRemoteCursorSaveHooks(...hooks2) {
|
|
92970
|
+
const active2 = hooks2.filter((hook) => hook != null);
|
|
92971
|
+
if (active2.length === 0) return;
|
|
92972
|
+
return async (context2) => {
|
|
92973
|
+
for (const hook of active2) await hook(context2);
|
|
92974
|
+
};
|
|
92975
|
+
}
|
|
92976
|
+
async function persistRemoteCursor(input2) {
|
|
92977
|
+
const next = {
|
|
92978
|
+
...input2.cursor,
|
|
92979
|
+
updatedAtMs: (input2.now ?? Date.now)()
|
|
92980
|
+
};
|
|
92981
|
+
if (input2.beforeRemoteCursorSave != null) await input2.beforeRemoteCursorSave({
|
|
92982
|
+
cursor: next,
|
|
92983
|
+
source: input2.source
|
|
92984
|
+
});
|
|
92985
|
+
await input2.remoteCursorStore.save(next);
|
|
92986
|
+
return next;
|
|
92987
|
+
}
|
|
92988
|
+
function key(streamUrl) {
|
|
92989
|
+
return streamUrl;
|
|
92990
|
+
}
|
|
92991
|
+
function cloneJsonValue(value) {
|
|
92992
|
+
return structuredClone(value);
|
|
92993
|
+
}
|
|
92994
|
+
function cloneRemoteCursor(cursor) {
|
|
92995
|
+
return {
|
|
92996
|
+
...cursor,
|
|
92997
|
+
serverLowerBoundVersion: cloneJsonValue(cursor.serverLowerBoundVersion)
|
|
92998
|
+
};
|
|
92999
|
+
}
|
|
93000
|
+
var InMemoryRemoteCursorStore = class {
|
|
93001
|
+
data = /* @__PURE__ */ new Map();
|
|
93002
|
+
async load(streamUrl) {
|
|
93003
|
+
const value = this.data.get(key(streamUrl));
|
|
93004
|
+
return value == null ? null : cloneRemoteCursor(value);
|
|
93005
|
+
}
|
|
93006
|
+
async save(cursor) {
|
|
93007
|
+
this.data.set(key(cursor.streamUrl), cloneRemoteCursor(cursor));
|
|
93008
|
+
}
|
|
93009
|
+
async delete(streamUrl) {
|
|
93010
|
+
this.data.delete(key(streamUrl));
|
|
93011
|
+
}
|
|
93012
|
+
};
|
|
93013
|
+
function createInitialRemoteCursor(input2) {
|
|
93014
|
+
return {
|
|
93015
|
+
streamUrl: input2.streamUrl,
|
|
93016
|
+
nextOffset: input2.nextOffset,
|
|
93017
|
+
serverLowerBoundVersion: cloneJsonValue(input2.serverLowerBoundVersion),
|
|
93018
|
+
updatedAtMs: Date.now()
|
|
93019
|
+
};
|
|
93020
|
+
}
|
|
92769
93021
|
var TransportCursorManager = class {
|
|
92770
93022
|
adapter;
|
|
92771
93023
|
decodeSnapshot;
|
|
@@ -93023,6 +93275,8 @@ stream:${scope2.streamId}`;
|
|
|
93023
93275
|
producerId = createProducerId();
|
|
93024
93276
|
producerEpoch = 0;
|
|
93025
93277
|
nextProducerSeq = 0;
|
|
93278
|
+
frozenLocalAppend;
|
|
93279
|
+
committedPendingCount;
|
|
93026
93280
|
pendingWriteOnly = new Deque();
|
|
93027
93281
|
writeOnlyCursor;
|
|
93028
93282
|
unsubscribeWriteOnlyLocal;
|
|
@@ -93202,6 +93456,8 @@ stream:${scope2.streamId}`;
|
|
|
93202
93456
|
await this.remoteCursorStore.delete?.(this.streamUrl);
|
|
93203
93457
|
this.producerEpoch = 0;
|
|
93204
93458
|
this.nextProducerSeq = 0;
|
|
93459
|
+
this.frozenLocalAppend = void 0;
|
|
93460
|
+
this.committedPendingCount = void 0;
|
|
93205
93461
|
return deleted;
|
|
93206
93462
|
})
|
|
93207
93463
|
}
|
|
@@ -93242,15 +93498,17 @@ stream:${scope2.streamId}`;
|
|
|
93242
93498
|
localVersion: this.writeOnlyCursor.serverLowerBoundVersion
|
|
93243
93499
|
}
|
|
93244
93500
|
};
|
|
93245
|
-
const
|
|
93246
|
-
|
|
93247
|
-
|
|
93501
|
+
const appended = await this.appendLocalBatchRemote(this.writeOnlyCursor, drained.batch, drained.count, "write-only");
|
|
93502
|
+
const committedCount = appended.pendingCount ?? drained.count;
|
|
93503
|
+
for (let i2 = 0; i2 < committedCount; i2 += 1) this.pendingWriteOnly.popFront();
|
|
93504
|
+
this.writeOnlyCursor = appended.cursor;
|
|
93505
|
+
this.commitProducerAck(appended.producerAck);
|
|
93248
93506
|
return {
|
|
93249
93507
|
ok: true,
|
|
93250
93508
|
value: {
|
|
93251
93509
|
appended: true,
|
|
93252
|
-
nextOffset:
|
|
93253
|
-
localVersion:
|
|
93510
|
+
nextOffset: appended.cursor.nextOffset,
|
|
93511
|
+
localVersion: appended.cursor.serverLowerBoundVersion
|
|
93254
93512
|
}
|
|
93255
93513
|
};
|
|
93256
93514
|
} catch (error2) {
|
|
@@ -93425,7 +93683,7 @@ stream:${scope2.streamId}`;
|
|
|
93425
93683
|
}
|
|
93426
93684
|
}
|
|
93427
93685
|
async syncOnce() {
|
|
93428
|
-
let cursor = (await this.resolveInitialRemoteState()).cursor;
|
|
93686
|
+
let cursor = (await this.finalizeDirectLocalAppendIfNeeded(await this.resolveInitialRemoteState())).cursor;
|
|
93429
93687
|
const localBatch = this.exportUpdates(cursor.serverLowerBoundVersion);
|
|
93430
93688
|
if (localBatch.batch != null) {
|
|
93431
93689
|
cursor = await this.appendLocalBatch(cursor, localBatch.batch);
|
|
@@ -93434,7 +93692,7 @@ stream:${scope2.streamId}`;
|
|
|
93434
93692
|
return cursor;
|
|
93435
93693
|
}
|
|
93436
93694
|
async performInitialJoinSync(preferredLiveMode) {
|
|
93437
|
-
const initial = await this.resolveInitialRemoteState();
|
|
93695
|
+
const initial = await this.finalizeDirectLocalAppendIfNeeded(await this.resolveInitialRemoteState());
|
|
93438
93696
|
let cursor = initial.cursor;
|
|
93439
93697
|
let localExportRefVersion = cursor.serverLowerBoundVersion;
|
|
93440
93698
|
let streamCursor = initial.streamCursor;
|
|
@@ -93534,17 +93792,52 @@ stream:${scope2.streamId}`;
|
|
|
93534
93792
|
};
|
|
93535
93793
|
}
|
|
93536
93794
|
}
|
|
93537
|
-
async appendLocalBatch(cursor, batch,
|
|
93538
|
-
|
|
93539
|
-
|
|
93540
|
-
|
|
93541
|
-
|
|
93795
|
+
async appendLocalBatch(cursor, batch, pendingCount) {
|
|
93796
|
+
const source = pendingCount == null ? "direct" : "pending";
|
|
93797
|
+
const committed = await this.appendLocalBatchDurably(cursor, batch, pendingCount, source);
|
|
93798
|
+
this.committedPendingCount = committed.pendingCount;
|
|
93799
|
+
return committed.cursor;
|
|
93800
|
+
}
|
|
93801
|
+
async appendLocalBatchDurably(cursor, batch, pendingCount, source = pendingCount == null ? "direct" : "pending") {
|
|
93802
|
+
const frozen = await this.getOrCreateFrozenLocalAppend(cursor, batch, pendingCount, source);
|
|
93803
|
+
const appended = await this.appendFrozenLocalBatchRemote(frozen);
|
|
93804
|
+
const saved = await this.saveRemoteCursor(appended.cursor, "local");
|
|
93805
|
+
this.commitProducerAck(appended.producerAck);
|
|
93806
|
+
return {
|
|
93807
|
+
cursor: saved,
|
|
93808
|
+
pendingCount: frozen.pendingCount
|
|
93542
93809
|
};
|
|
93810
|
+
}
|
|
93811
|
+
async appendLocalBatchRemote(cursor, batch, pendingCount, source = pendingCount == null ? "direct" : "pending") {
|
|
93812
|
+
const frozen = await this.getOrCreateFrozenLocalAppend(cursor, batch, pendingCount, source);
|
|
93813
|
+
return {
|
|
93814
|
+
...await this.appendFrozenLocalBatchRemote(frozen),
|
|
93815
|
+
pendingCount: frozen.pendingCount
|
|
93816
|
+
};
|
|
93817
|
+
}
|
|
93818
|
+
async getOrCreateFrozenLocalAppend(cursor, batch, pendingCount, source = pendingCount == null ? "direct" : "pending") {
|
|
93819
|
+
const existing = this.frozenLocalAppend;
|
|
93820
|
+
if (existing != null) {
|
|
93821
|
+
if (existing.source !== source) throw new Error(`${existing.source} local append must be finalized before ${source} append`);
|
|
93822
|
+
return existing;
|
|
93823
|
+
}
|
|
93824
|
+
const frozen = {
|
|
93825
|
+
source,
|
|
93826
|
+
baseCursor: cursor,
|
|
93827
|
+
batch,
|
|
93828
|
+
pendingCount,
|
|
93829
|
+
wireUpdates: this.encodeUpdateItems == null ? batch.updates : await this.encodeUpdateItems(batch.updates),
|
|
93830
|
+
producerSession: this.createCurrentProducerSession()
|
|
93831
|
+
};
|
|
93832
|
+
this.frozenLocalAppend = frozen;
|
|
93833
|
+
return frozen;
|
|
93834
|
+
}
|
|
93835
|
+
async appendFrozenLocalBatchRemote(frozen) {
|
|
93836
|
+
if (frozen.acknowledged != null) return frozen.acknowledged;
|
|
93543
93837
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
93544
93838
|
let result;
|
|
93545
93839
|
try {
|
|
93546
|
-
|
|
93547
|
-
result = await this.client.appendUpdates(wireUpdates, producerSession);
|
|
93840
|
+
result = await this.client.appendUpdates(frozen.wireUpdates, frozen.producerSession);
|
|
93548
93841
|
} catch (error2) {
|
|
93549
93842
|
if (isStreamNotFoundError(error2) && this.createStreamIfMissing) {
|
|
93550
93843
|
await this.ensureStreamExists();
|
|
@@ -93553,30 +93846,66 @@ stream:${scope2.streamId}`;
|
|
|
93553
93846
|
throw error2;
|
|
93554
93847
|
}
|
|
93555
93848
|
if (result.kind === "ok") {
|
|
93556
|
-
|
|
93557
|
-
|
|
93558
|
-
|
|
93559
|
-
|
|
93560
|
-
|
|
93561
|
-
|
|
93849
|
+
const appended = {
|
|
93850
|
+
cursor: {
|
|
93851
|
+
...frozen.baseCursor,
|
|
93852
|
+
nextOffset: result.nextOffset,
|
|
93853
|
+
serverLowerBoundVersion: this.adapter.mergeVersions(frozen.baseCursor.serverLowerBoundVersion, frozen.batch.version)
|
|
93854
|
+
},
|
|
93855
|
+
producerAck: {
|
|
93856
|
+
epoch: result.ackEpoch,
|
|
93857
|
+
seq: result.ackSeq
|
|
93858
|
+
}
|
|
93562
93859
|
};
|
|
93563
|
-
|
|
93860
|
+
frozen.acknowledged = appended;
|
|
93861
|
+
return appended;
|
|
93564
93862
|
}
|
|
93565
93863
|
if (result.kind === "seq-gap") {
|
|
93566
|
-
producerSession = {
|
|
93567
|
-
...producerSession,
|
|
93864
|
+
frozen.producerSession = {
|
|
93865
|
+
...frozen.producerSession,
|
|
93568
93866
|
nextProducerSeq: result.expectedSeq
|
|
93569
93867
|
};
|
|
93570
93868
|
continue;
|
|
93571
93869
|
}
|
|
93572
|
-
producerSession = {
|
|
93573
|
-
...producerSession,
|
|
93870
|
+
frozen.producerSession = {
|
|
93871
|
+
...frozen.producerSession,
|
|
93574
93872
|
producerEpoch: result.serverEpoch + 1,
|
|
93575
93873
|
nextProducerSeq: 0
|
|
93576
93874
|
};
|
|
93577
93875
|
}
|
|
93578
93876
|
throw new Error("append retry budget exhausted");
|
|
93579
93877
|
}
|
|
93878
|
+
async finalizeDirectLocalAppendIfNeeded(state2) {
|
|
93879
|
+
const frozen = this.frozenLocalAppend;
|
|
93880
|
+
if (frozen == null || frozen.pendingCount != null) return state2;
|
|
93881
|
+
const appended = await this.appendFrozenLocalBatchRemote(frozen);
|
|
93882
|
+
const saved = await this.saveRemoteCursor(appended.cursor, "local");
|
|
93883
|
+
this.commitProducerAck(appended.producerAck);
|
|
93884
|
+
return await this.catchup(saved, void 0, state2.streamCursor);
|
|
93885
|
+
}
|
|
93886
|
+
createCurrentProducerSession() {
|
|
93887
|
+
return {
|
|
93888
|
+
producerId: this.producerId,
|
|
93889
|
+
producerEpoch: this.producerEpoch,
|
|
93890
|
+
nextProducerSeq: this.nextProducerSeq
|
|
93891
|
+
};
|
|
93892
|
+
}
|
|
93893
|
+
commitProducerAck(ack) {
|
|
93894
|
+
this.producerEpoch = ack.epoch;
|
|
93895
|
+
this.nextProducerSeq = ack.seq + 1;
|
|
93896
|
+
this.frozenLocalAppend = void 0;
|
|
93897
|
+
}
|
|
93898
|
+
abandonFrozenLocalAppend(frozen) {
|
|
93899
|
+
if (this.frozenLocalAppend !== frozen) return;
|
|
93900
|
+
this.frozenLocalAppend = void 0;
|
|
93901
|
+
this.producerEpoch = Math.max(this.producerEpoch, frozen.producerSession.producerEpoch + 1);
|
|
93902
|
+
this.nextProducerSeq = 0;
|
|
93903
|
+
}
|
|
93904
|
+
consumeCommittedPendingCount() {
|
|
93905
|
+
const count2 = this.committedPendingCount;
|
|
93906
|
+
this.committedPendingCount = void 0;
|
|
93907
|
+
return count2;
|
|
93908
|
+
}
|
|
93580
93909
|
async ensureStreamExists() {
|
|
93581
93910
|
try {
|
|
93582
93911
|
await this.client.createStreamRequest();
|
|
@@ -93621,9 +93950,17 @@ stream:${scope2.streamId}`;
|
|
|
93621
93950
|
const inferredRemote = await this.bootstrapState(bootstrapCursor, {
|
|
93622
93951
|
cursorManager
|
|
93623
93952
|
});
|
|
93624
|
-
|
|
93625
|
-
if (
|
|
93626
|
-
|
|
93953
|
+
let pendingAppend;
|
|
93954
|
+
if (this.frozenLocalAppend != null) {
|
|
93955
|
+
if (this.frozenLocalAppend.pendingCount != null) throw new Error("pending local append must be finalized by pending flush");
|
|
93956
|
+
pendingAppend = await this.appendFrozenLocalBatchRemote(this.frozenLocalAppend);
|
|
93957
|
+
} else {
|
|
93958
|
+
const localBatch = this.exportUpdates(inferredRemote.cursor.serverLowerBoundVersion);
|
|
93959
|
+
if (localBatch.batch != null) pendingAppend = await this.appendLocalBatchRemote(inferredRemote.cursor, localBatch.batch);
|
|
93960
|
+
}
|
|
93961
|
+
const result = await this.bootstrapState(bootstrapCursor);
|
|
93962
|
+
if (pendingAppend != null) this.commitProducerAck(pendingAppend.producerAck);
|
|
93963
|
+
return result;
|
|
93627
93964
|
}
|
|
93628
93965
|
async recoverFromGoneWithLocalBootstrap(cursor) {
|
|
93629
93966
|
const isolatedCursorManager = await this.createIsolatedCursorManager();
|
|
@@ -93641,9 +93978,17 @@ stream:${scope2.streamId}`;
|
|
|
93641
93978
|
});
|
|
93642
93979
|
await this.adapter.applySnapshot(preservedLocalSnapshot);
|
|
93643
93980
|
restoredLocalSnapshot = true;
|
|
93644
|
-
|
|
93645
|
-
if (
|
|
93646
|
-
|
|
93981
|
+
let pendingAppend;
|
|
93982
|
+
if (this.frozenLocalAppend != null) {
|
|
93983
|
+
if (this.frozenLocalAppend.pendingCount != null) throw new Error("pending local append must be finalized by pending flush");
|
|
93984
|
+
pendingAppend = await this.appendFrozenLocalBatchRemote(this.frozenLocalAppend);
|
|
93985
|
+
} else {
|
|
93986
|
+
const localBatch = this.exportUpdates(inferredRemote.cursor.serverLowerBoundVersion);
|
|
93987
|
+
if (localBatch.batch != null) pendingAppend = await this.appendLocalBatchRemote(inferredRemote.cursor, localBatch.batch);
|
|
93988
|
+
}
|
|
93989
|
+
const result = await this.bootstrapState(bootstrapCursor);
|
|
93990
|
+
if (pendingAppend != null) this.commitProducerAck(pendingAppend.producerAck);
|
|
93991
|
+
return result;
|
|
93647
93992
|
} catch (error2) {
|
|
93648
93993
|
if (!restoredLocalSnapshot) await this.adapter.applySnapshot(preservedLocalSnapshot);
|
|
93649
93994
|
throw error2;
|
|
@@ -93855,7 +94200,6 @@ stream:${scope2.streamId}`;
|
|
|
93855
94200
|
let mergedVersion = first2.version;
|
|
93856
94201
|
const allUpdates = Array.from(first2.updates);
|
|
93857
94202
|
let count2 = 1;
|
|
93858
|
-
await this.measureAppendBodyByteLength(allUpdates);
|
|
93859
94203
|
while (count2 < pendingLocal.length) {
|
|
93860
94204
|
const next = pendingLocal.at(count2);
|
|
93861
94205
|
const candidateUpdates = [
|
|
@@ -93894,9 +94238,10 @@ stream:${scope2.streamId}`;
|
|
|
93894
94238
|
if (drained == null) return;
|
|
93895
94239
|
let attempts = 0;
|
|
93896
94240
|
while (!state2.closed) try {
|
|
93897
|
-
state2.cursor = await this.enqueueExclusive(async () => this.appendLocalBatch(state2.cursor, drained.batch));
|
|
93898
|
-
|
|
93899
|
-
|
|
94241
|
+
state2.cursor = await this.enqueueExclusive(async () => this.appendLocalBatch(state2.cursor, drained.batch, drained.count));
|
|
94242
|
+
const committedCount = this.consumeCommittedPendingCount() ?? drained.count;
|
|
94243
|
+
for (let i2 = 0; i2 < committedCount; i2 += 1) state2.pendingLocal.popFront();
|
|
94244
|
+
this.notifySyncWaiters(state2, committedCount);
|
|
93900
94245
|
this.setWriteSubStatus(state2, "ok");
|
|
93901
94246
|
this.snapshotManager.schedule(state2);
|
|
93902
94247
|
break;
|
|
@@ -93986,6 +94331,8 @@ stream:${scope2.streamId}`;
|
|
|
93986
94331
|
return updates.reduce((sum, update2) => sum + 4 + update2.byteLength, 0);
|
|
93987
94332
|
}
|
|
93988
94333
|
disableWriteOnlyMode(reason) {
|
|
94334
|
+
const frozen = this.frozenLocalAppend;
|
|
94335
|
+
if (frozen?.source === "write-only") this.abandonFrozenLocalAppend(frozen);
|
|
93989
94336
|
if (this.writeOnlyDisabledReason != null) return;
|
|
93990
94337
|
this.writeOnlyDisabledReason = reason;
|
|
93991
94338
|
this.pendingWriteOnly.clear();
|
|
@@ -112230,6 +112577,9 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
|
|
|
112230
112577
|
isTransportConnected() {
|
|
112231
112578
|
return this.transportStatus === "connected";
|
|
112232
112579
|
}
|
|
112580
|
+
isRecovering() {
|
|
112581
|
+
return !this.isCleanedUp && !this.isStreamsHealthy();
|
|
112582
|
+
}
|
|
112233
112583
|
setTransportStatus(status) {
|
|
112234
112584
|
this.transportStatus = status;
|
|
112235
112585
|
if (status === "disconnected") {
|
|
@@ -113429,6 +113779,9 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
|
|
|
113429
113779
|
isTransportConnected() {
|
|
113430
113780
|
return this.connectionRecovery.isTransportConnected();
|
|
113431
113781
|
}
|
|
113782
|
+
isTransportRecovering() {
|
|
113783
|
+
return this.connectionRecovery.isRecovering();
|
|
113784
|
+
}
|
|
113432
113785
|
getConnectedRoomCount() {
|
|
113433
113786
|
return this.sessions.size + this.codeSessions.size;
|
|
113434
113787
|
}
|
|
@@ -113879,6 +114232,34 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
|
|
|
113879
114232
|
async waitForRemoteSync() {
|
|
113880
114233
|
await this.remoteSyncReady;
|
|
113881
114234
|
}
|
|
114235
|
+
async ensureDocRoomJoined() {
|
|
114236
|
+
if (this.destroyed) {
|
|
114237
|
+
return;
|
|
114238
|
+
}
|
|
114239
|
+
if (!this.docSub) {
|
|
114240
|
+
this.remoteSyncReady = this.startDocRoomSync();
|
|
114241
|
+
}
|
|
114242
|
+
await this.remoteSyncReady;
|
|
114243
|
+
}
|
|
114244
|
+
getDocRoomStatus() {
|
|
114245
|
+
return this.docSub?.status;
|
|
114246
|
+
}
|
|
114247
|
+
onDocRoomStatusChange(listener) {
|
|
114248
|
+
return this.docSub?.onStatusChange(listener) ?? (() => {
|
|
114249
|
+
});
|
|
114250
|
+
}
|
|
114251
|
+
async rejoinDocRoom() {
|
|
114252
|
+
if (this.destroyed) {
|
|
114253
|
+
return;
|
|
114254
|
+
}
|
|
114255
|
+
if (!this.docSub) {
|
|
114256
|
+
await this.ensureDocRoomJoined();
|
|
114257
|
+
return;
|
|
114258
|
+
}
|
|
114259
|
+
if (this.docSub.rejoin) {
|
|
114260
|
+
await this.docSub.rejoin();
|
|
114261
|
+
}
|
|
114262
|
+
}
|
|
113882
114263
|
async initOffline(initialState) {
|
|
113883
114264
|
this.destroyed = false;
|
|
113884
114265
|
this.handle = await this.repo.openPersistedDoc(this.roomId);
|
|
@@ -116891,7 +117272,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
|
|
|
116891
117272
|
},
|
|
116892
117273
|
codex: {
|
|
116893
117274
|
packageName: "acp-extension-codex",
|
|
116894
|
-
version: "0.
|
|
117275
|
+
version: "0.12.0",
|
|
116895
117276
|
binName: "acp-extension-codex",
|
|
116896
117277
|
args: [
|
|
116897
117278
|
"-c",
|
|
@@ -120182,6 +120563,221 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
120182
120563
|
}
|
|
120183
120564
|
return resolveAgentConfigEnvForSessionResume(repo, options.agentConfigId);
|
|
120184
120565
|
};
|
|
120566
|
+
function getMemoryPressureSnapshot() {
|
|
120567
|
+
const windowsStatus = getWindowsMemoryStatus();
|
|
120568
|
+
const systemAvailable = getSystemAvailableMemoryBytes(windowsStatus);
|
|
120569
|
+
const cgroupAvailable = getCgroupAvailableMemoryBytes();
|
|
120570
|
+
const availableMemoryBytes = cgroupAvailable !== null ? Math.min(systemAvailable, cgroupAvailable) : systemAvailable;
|
|
120571
|
+
const effectiveMemoryLimitBytes = getEffectiveMemoryLimitBytes();
|
|
120572
|
+
return {
|
|
120573
|
+
availableMemoryBytes,
|
|
120574
|
+
effectiveMemoryLimitBytes,
|
|
120575
|
+
...windowsStatus ? {
|
|
120576
|
+
availableCommitBytes: windowsStatus.availableCommitBytes,
|
|
120577
|
+
commitLimitBytes: windowsStatus.commitLimitBytes,
|
|
120578
|
+
committedBytes: windowsStatus.committedBytes
|
|
120579
|
+
} : {}
|
|
120580
|
+
};
|
|
120581
|
+
}
|
|
120582
|
+
function getEffectiveMemoryLimitBytes() {
|
|
120583
|
+
const totalMem = os__default.totalmem();
|
|
120584
|
+
const cgroupMax = getCgroupMemoryMaxBytes();
|
|
120585
|
+
if (cgroupMax !== null) {
|
|
120586
|
+
return Math.min(totalMem, cgroupMax);
|
|
120587
|
+
}
|
|
120588
|
+
return totalMem;
|
|
120589
|
+
}
|
|
120590
|
+
function getSystemAvailableMemoryBytes(windowsStatus) {
|
|
120591
|
+
if (windowsStatus) {
|
|
120592
|
+
return Math.max(windowsStatus.availableBytes, os__default.freemem());
|
|
120593
|
+
}
|
|
120594
|
+
const darwinAvailable = getDarwinAvailableMemoryBytes();
|
|
120595
|
+
if (darwinAvailable !== null) {
|
|
120596
|
+
return darwinAvailable;
|
|
120597
|
+
}
|
|
120598
|
+
try {
|
|
120599
|
+
const meminfo = readFileSync("/proc/meminfo", "utf8");
|
|
120600
|
+
const match5 = meminfo.match(/MemAvailable:\s+(\d+)/);
|
|
120601
|
+
if (match5?.[1]) {
|
|
120602
|
+
return parseInt(match5[1], 10) * 1024;
|
|
120603
|
+
}
|
|
120604
|
+
} catch {
|
|
120605
|
+
}
|
|
120606
|
+
return os__default.freemem();
|
|
120607
|
+
}
|
|
120608
|
+
function getWindowsMemoryStatus() {
|
|
120609
|
+
if (process.platform !== "win32") {
|
|
120610
|
+
return null;
|
|
120611
|
+
}
|
|
120612
|
+
try {
|
|
120613
|
+
const script = `
|
|
120614
|
+
$mem = Get-CimInstance -ClassName Win32_PerfFormattedData_PerfOS_Memory |
|
|
120615
|
+
Select-Object AvailableBytes, CommitLimit, CommittedBytes
|
|
120616
|
+
$mem | ConvertTo-Json -Compress
|
|
120617
|
+
`;
|
|
120618
|
+
const output = execFileSync("powershell.exe", [
|
|
120619
|
+
"-NoProfile",
|
|
120620
|
+
"-NonInteractive",
|
|
120621
|
+
"-ExecutionPolicy",
|
|
120622
|
+
"Bypass",
|
|
120623
|
+
"-Command",
|
|
120624
|
+
script
|
|
120625
|
+
], {
|
|
120626
|
+
encoding: "utf8"
|
|
120627
|
+
});
|
|
120628
|
+
return parseWindowsMemoryStatus(output);
|
|
120629
|
+
} catch {
|
|
120630
|
+
return null;
|
|
120631
|
+
}
|
|
120632
|
+
}
|
|
120633
|
+
function parseWindowsMemoryStatus(rawJson) {
|
|
120634
|
+
try {
|
|
120635
|
+
const parsed = JSON.parse(rawJson);
|
|
120636
|
+
const availableBytes = Number(parsed.AvailableBytes);
|
|
120637
|
+
const commitLimitBytes = Number(parsed.CommitLimit);
|
|
120638
|
+
const committedBytes = Number(parsed.CommittedBytes);
|
|
120639
|
+
if (!Number.isFinite(availableBytes) || availableBytes < 0 || !Number.isFinite(commitLimitBytes) || commitLimitBytes <= 0 || !Number.isFinite(committedBytes) || committedBytes < 0) {
|
|
120640
|
+
return null;
|
|
120641
|
+
}
|
|
120642
|
+
return {
|
|
120643
|
+
availableBytes,
|
|
120644
|
+
commitLimitBytes,
|
|
120645
|
+
committedBytes,
|
|
120646
|
+
availableCommitBytes: Math.max(0, commitLimitBytes - committedBytes)
|
|
120647
|
+
};
|
|
120648
|
+
} catch {
|
|
120649
|
+
return null;
|
|
120650
|
+
}
|
|
120651
|
+
}
|
|
120652
|
+
function getDarwinAvailableMemoryBytes() {
|
|
120653
|
+
if (process.platform !== "darwin") {
|
|
120654
|
+
return null;
|
|
120655
|
+
}
|
|
120656
|
+
try {
|
|
120657
|
+
const vmStatOutput = execFileSync("vm_stat", {
|
|
120658
|
+
encoding: "utf8"
|
|
120659
|
+
});
|
|
120660
|
+
const parsed = parseDarwinAvailableMemoryBytes(vmStatOutput);
|
|
120661
|
+
if (parsed !== null) {
|
|
120662
|
+
return Math.max(parsed, os__default.freemem());
|
|
120663
|
+
}
|
|
120664
|
+
} catch {
|
|
120665
|
+
}
|
|
120666
|
+
return null;
|
|
120667
|
+
}
|
|
120668
|
+
function parseDarwinAvailableMemoryBytes(vmStatOutput) {
|
|
120669
|
+
const pageSizeMatch = vmStatOutput.match(/page size of\s+(\d+)\s+bytes/i);
|
|
120670
|
+
if (!pageSizeMatch?.[1]) {
|
|
120671
|
+
return null;
|
|
120672
|
+
}
|
|
120673
|
+
const pageSize = parseInt(pageSizeMatch[1], 10);
|
|
120674
|
+
if (!Number.isFinite(pageSize) || pageSize <= 0) {
|
|
120675
|
+
return null;
|
|
120676
|
+
}
|
|
120677
|
+
const counters = /* @__PURE__ */ new Map();
|
|
120678
|
+
for (const rawLine of vmStatOutput.split("\n")) {
|
|
120679
|
+
const line3 = rawLine.trim();
|
|
120680
|
+
const match5 = line3.match(/^"?([^":]+?)"?:\s+(\d+)\.?$/);
|
|
120681
|
+
if (!match5?.[1] || !match5[2]) {
|
|
120682
|
+
continue;
|
|
120683
|
+
}
|
|
120684
|
+
counters.set(match5[1].toLowerCase(), parseInt(match5[2], 10));
|
|
120685
|
+
}
|
|
120686
|
+
const freePages = counters.get("pages free") ?? 0;
|
|
120687
|
+
const speculativePages = counters.get("pages speculative") ?? 0;
|
|
120688
|
+
const purgeablePages = counters.get("pages purgeable") ?? 0;
|
|
120689
|
+
const inactivePages = counters.get("pages inactive") ?? 0;
|
|
120690
|
+
const fileBackedPages = counters.get("file-backed pages");
|
|
120691
|
+
if (freePages === 0 && speculativePages === 0 && purgeablePages === 0 && inactivePages === 0) {
|
|
120692
|
+
return null;
|
|
120693
|
+
}
|
|
120694
|
+
const reclaimableCachedPages = fileBackedPages !== void 0 ? Math.min(fileBackedPages, inactivePages) : inactivePages;
|
|
120695
|
+
const availablePages = freePages + speculativePages + purgeablePages + reclaimableCachedPages;
|
|
120696
|
+
return availablePages * pageSize;
|
|
120697
|
+
}
|
|
120698
|
+
function getCgroupMemoryMaxBytes() {
|
|
120699
|
+
try {
|
|
120700
|
+
const cgroupPath = readSelfCgroupPath();
|
|
120701
|
+
if (cgroupPath === null) return null;
|
|
120702
|
+
let tightest = null;
|
|
120703
|
+
let current2 = cgroupPath;
|
|
120704
|
+
for (let depth = 0; depth < 20; depth++) {
|
|
120705
|
+
const memMaxPath = `/sys/fs/cgroup${current2 === "/" ? "" : current2}/memory.max`;
|
|
120706
|
+
const raw = readFileSafe(memMaxPath);
|
|
120707
|
+
if (raw !== null) {
|
|
120708
|
+
const trimmed = raw.trim();
|
|
120709
|
+
if (trimmed !== "max") {
|
|
120710
|
+
const value = parseInt(trimmed, 10);
|
|
120711
|
+
if (Number.isFinite(value) && value > 0) {
|
|
120712
|
+
tightest = tightest === null ? value : Math.min(tightest, value);
|
|
120713
|
+
}
|
|
120714
|
+
}
|
|
120715
|
+
}
|
|
120716
|
+
if (current2 === "/" || current2 === "") break;
|
|
120717
|
+
const parent = current2.substring(0, current2.lastIndexOf("/")) || "/";
|
|
120718
|
+
if (parent === current2) break;
|
|
120719
|
+
current2 = parent;
|
|
120720
|
+
}
|
|
120721
|
+
return tightest;
|
|
120722
|
+
} catch {
|
|
120723
|
+
return null;
|
|
120724
|
+
}
|
|
120725
|
+
}
|
|
120726
|
+
function getCgroupAvailableMemoryBytes() {
|
|
120727
|
+
try {
|
|
120728
|
+
const cgroupPath = readSelfCgroupPath();
|
|
120729
|
+
if (cgroupPath === null) return null;
|
|
120730
|
+
let tightestMax = null;
|
|
120731
|
+
let tightestPath = null;
|
|
120732
|
+
let current2 = cgroupPath;
|
|
120733
|
+
for (let depth = 0; depth < 20; depth++) {
|
|
120734
|
+
const prefix = `/sys/fs/cgroup${current2 === "/" ? "" : current2}`;
|
|
120735
|
+
const raw = readFileSafe(`${prefix}/memory.max`);
|
|
120736
|
+
if (raw !== null) {
|
|
120737
|
+
const trimmed = raw.trim();
|
|
120738
|
+
if (trimmed !== "max") {
|
|
120739
|
+
const value = parseInt(trimmed, 10);
|
|
120740
|
+
if (Number.isFinite(value) && value > 0) {
|
|
120741
|
+
if (tightestMax === null || value < tightestMax) {
|
|
120742
|
+
tightestMax = value;
|
|
120743
|
+
tightestPath = prefix;
|
|
120744
|
+
}
|
|
120745
|
+
}
|
|
120746
|
+
}
|
|
120747
|
+
}
|
|
120748
|
+
if (current2 === "/" || current2 === "") break;
|
|
120749
|
+
const parent = current2.substring(0, current2.lastIndexOf("/")) || "/";
|
|
120750
|
+
if (parent === current2) break;
|
|
120751
|
+
current2 = parent;
|
|
120752
|
+
}
|
|
120753
|
+
if (tightestMax === null || tightestPath === null) return null;
|
|
120754
|
+
const currentRaw = readFileSafe(`${tightestPath}/memory.current`);
|
|
120755
|
+
if (currentRaw === null) return null;
|
|
120756
|
+
const currentUsage = parseInt(currentRaw.trim(), 10);
|
|
120757
|
+
if (!Number.isFinite(currentUsage)) return null;
|
|
120758
|
+
return Math.max(0, tightestMax - currentUsage);
|
|
120759
|
+
} catch {
|
|
120760
|
+
return null;
|
|
120761
|
+
}
|
|
120762
|
+
}
|
|
120763
|
+
function readSelfCgroupPath() {
|
|
120764
|
+
try {
|
|
120765
|
+
const content = readFileSync("/proc/self/cgroup", "utf8");
|
|
120766
|
+
const line3 = content.split("\n").map((l) => l.trim()).find((l) => l.startsWith("0::"));
|
|
120767
|
+
if (!line3) return null;
|
|
120768
|
+
const cgroupPath = line3.slice(3).trim();
|
|
120769
|
+
return cgroupPath || "/";
|
|
120770
|
+
} catch {
|
|
120771
|
+
return null;
|
|
120772
|
+
}
|
|
120773
|
+
}
|
|
120774
|
+
function readFileSafe(filePath) {
|
|
120775
|
+
try {
|
|
120776
|
+
return readFileSync(filePath, "utf8");
|
|
120777
|
+
} catch {
|
|
120778
|
+
return null;
|
|
120779
|
+
}
|
|
120780
|
+
}
|
|
120185
120781
|
class SessionTurnCancelled extends TaggedError("SessionTurnCancelled") {
|
|
120186
120782
|
}
|
|
120187
120783
|
class SessionTurnHalted extends TaggedError("SessionTurnHalted") {
|
|
@@ -120202,6 +120798,7 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
120202
120798
|
AUTH_REQUIRED: -32e3,
|
|
120203
120799
|
RESOURCE_NOT_FOUND: -32002
|
|
120204
120800
|
};
|
|
120801
|
+
const BYTES_PER_GIB = 1024 * 1024 * 1024;
|
|
120205
120802
|
const shouldRedactEnvKey = (key2) => /token|secret|password|passwd|key/i.test(key2);
|
|
120206
120803
|
const redactEnvForLog = (env2) => {
|
|
120207
120804
|
if (!env2) {
|
|
@@ -120237,6 +120834,46 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
120237
120834
|
canceledTurnBySession = /* @__PURE__ */ new Map();
|
|
120238
120835
|
currentTurnBySession = /* @__PURE__ */ new Map();
|
|
120239
120836
|
turnRuntimeBySession = /* @__PURE__ */ new Map();
|
|
120837
|
+
formatGiB(bytes) {
|
|
120838
|
+
if (bytes === null || !Number.isFinite(bytes) || bytes < 0) {
|
|
120839
|
+
return "unavailable";
|
|
120840
|
+
}
|
|
120841
|
+
return `${(bytes / BYTES_PER_GIB).toFixed(1)}GB`;
|
|
120842
|
+
}
|
|
120843
|
+
formatPercent(value) {
|
|
120844
|
+
if (value === null || !Number.isFinite(value) || value < 0) {
|
|
120845
|
+
return "unavailable";
|
|
120846
|
+
}
|
|
120847
|
+
return `${value.toFixed(1)}%`;
|
|
120848
|
+
}
|
|
120849
|
+
async getFreeDiskBytes() {
|
|
120850
|
+
try {
|
|
120851
|
+
const stats = await statfs(os__default.homedir());
|
|
120852
|
+
const bsize = typeof stats.bsize === "bigint" ? Number(stats.bsize) : stats.bsize;
|
|
120853
|
+
const bavail = typeof stats.bavail === "bigint" ? Number(stats.bavail) : stats.bavail;
|
|
120854
|
+
if (!Number.isFinite(bsize) || !Number.isFinite(bavail) || bsize <= 0 || bavail < 0) {
|
|
120855
|
+
return null;
|
|
120856
|
+
}
|
|
120857
|
+
return bsize * bavail;
|
|
120858
|
+
} catch {
|
|
120859
|
+
return null;
|
|
120860
|
+
}
|
|
120861
|
+
}
|
|
120862
|
+
async logTurnStartResources(sessionId, mode2) {
|
|
120863
|
+
try {
|
|
120864
|
+
const memorySnapshot = getMemoryPressureSnapshot();
|
|
120865
|
+
const availableMemoryBytes = memorySnapshot.availableMemoryBytes;
|
|
120866
|
+
const memoryFreePercent = memorySnapshot.effectiveMemoryLimitBytes > 0 ? availableMemoryBytes / memorySnapshot.effectiveMemoryLimitBytes * 100 : null;
|
|
120867
|
+
const [resources, freeDiskBytes] = await Promise.all([
|
|
120868
|
+
this.deps.collectMachineResources().catch(() => null),
|
|
120869
|
+
this.getFreeDiskBytes()
|
|
120870
|
+
]);
|
|
120871
|
+
const cpuUsagePercent = resources?.cpuUsagePercent ?? null;
|
|
120872
|
+
this.deps.logger.info(`[${sessionId}] Turn start resources (mode=${mode2}): memoryAvailable=${this.formatGiB(availableMemoryBytes)} memoryAvailablePercent=${this.formatPercent(memoryFreePercent)} cpuUsage=${this.formatPercent(cpuUsagePercent)} diskFree=${this.formatGiB(freeDiskBytes)}`);
|
|
120873
|
+
} catch (error2) {
|
|
120874
|
+
this.deps.logger.debug(`[${sessionId}] Failed to log turn start resources: ${formatErrorMessage(error2)}`);
|
|
120875
|
+
}
|
|
120876
|
+
}
|
|
120240
120877
|
createTurnRuntime(sessionId, turnId, userTurnId, session) {
|
|
120241
120878
|
return {
|
|
120242
120879
|
sessionId,
|
|
@@ -120419,10 +121056,19 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
120419
121056
|
async recordKnownChatFailure(options) {
|
|
120420
121057
|
await this.deps.recordChatFailure(options.sessionDoc, options.reason, options.message);
|
|
120421
121058
|
if (options.userTurnId) {
|
|
120422
|
-
await this.
|
|
121059
|
+
await this.markTurnFailed(options.sessionId, options.sessionDoc, options.userTurnId);
|
|
120423
121060
|
}
|
|
120424
121061
|
await options.sessionDoc.setStatus(SessionStatusFactory.idle());
|
|
120425
121062
|
}
|
|
121063
|
+
formatMemoryPressureWarningMessage(result) {
|
|
121064
|
+
const availableMb = Math.round(result.availableMemoryBytes / 1024 / 1024);
|
|
121065
|
+
const thresholdMb = Math.round(result.thresholdBytes / 1024 / 1024);
|
|
121066
|
+
const commitText = result.availableCommitBytes !== void 0 && (result.pressureReason === "commit" || result.pressureReason === "physical_and_commit") && result.commitThresholdBytes !== void 0 ? ` Commit headroom is ${Math.round(result.availableCommitBytes / 1024 / 1024)}MB (threshold: ${Math.round(result.commitThresholdBytes / 1024 / 1024)}MB).` : "";
|
|
121067
|
+
return `The machine is under memory pressure (${availableMb}MB available, ${thresholdMb}MB required to start a turn). Proceeding anyway because the new turn may be used to free resources.${commitText}`;
|
|
121068
|
+
}
|
|
121069
|
+
warnOnMemoryPressure(sessionId, result) {
|
|
121070
|
+
this.deps.logger.warn(`[${sessionId}] ${this.formatMemoryPressureWarningMessage(result)}`);
|
|
121071
|
+
}
|
|
120426
121072
|
recordKnownChatFailureAndHaltEffect(options) {
|
|
120427
121073
|
return this.tryPromise(() => this.recordKnownChatFailure(options)).pipe(flatMap$1(() => fail(new SessionTurnHalted({
|
|
120428
121074
|
sessionId: options.sessionId,
|
|
@@ -120446,7 +121092,7 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
120446
121092
|
}
|
|
120447
121093
|
this.deps.logger.error(options.describe(options.error), options.error);
|
|
120448
121094
|
if (options.userTurnId) {
|
|
120449
|
-
await this.
|
|
121095
|
+
await this.markTurnFailed(options.sessionId, options.sessionDoc, options.userTurnId);
|
|
120450
121096
|
}
|
|
120451
121097
|
await this.handleTurnError(options.sessionId, options.sessionDoc, options.error);
|
|
120452
121098
|
await options.onUnhandledError?.(options.error);
|
|
@@ -120782,8 +121428,7 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
120782
121428
|
await this.setUserTurnStatus(sessionDoc, userTurnId, "processing");
|
|
120783
121429
|
await this.upsertSessionMeta(sessionId, {
|
|
120784
121430
|
latestUserMsgId: userTurnId,
|
|
120785
|
-
processingUserMsgId: userTurnId
|
|
120786
|
-
dispatchError: void 0
|
|
121431
|
+
processingUserMsgId: userTurnId
|
|
120787
121432
|
});
|
|
120788
121433
|
}
|
|
120789
121434
|
async clearDispatchProcessing(sessionId) {
|
|
@@ -120832,34 +121477,31 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
120832
121477
|
await this.upsertSessionMeta(sessionId, {
|
|
120833
121478
|
latestUserMsgId: existingMeta?.latestUserMsgId ?? cancelledUserMsgId,
|
|
120834
121479
|
lastHandledUserMsgId: cancelledUserMsgId,
|
|
120835
|
-
processingUserMsgId: void 0
|
|
120836
|
-
dispatchError: void 0
|
|
121480
|
+
processingUserMsgId: void 0
|
|
120837
121481
|
});
|
|
120838
121482
|
return;
|
|
120839
121483
|
}
|
|
120840
121484
|
await this.clearDispatchProcessing(sessionId);
|
|
120841
121485
|
}
|
|
121486
|
+
resolveLatestUserMsgIdForTerminalTurn(meta, terminalUserTurnId) {
|
|
121487
|
+
return meta?.latestUserMsgId && meta.latestUserMsgId !== terminalUserTurnId ? meta.latestUserMsgId : terminalUserTurnId;
|
|
121488
|
+
}
|
|
120842
121489
|
async setDispatchHandled(sessionId, sessionDoc, userTurnId) {
|
|
121490
|
+
const existingMeta = await this.getSessionMeta(sessionId);
|
|
120843
121491
|
await this.setUserTurnStatus(sessionDoc, userTurnId, "handled");
|
|
120844
121492
|
await this.upsertSessionMeta(sessionId, {
|
|
120845
|
-
latestUserMsgId: userTurnId,
|
|
121493
|
+
latestUserMsgId: this.resolveLatestUserMsgIdForTerminalTurn(existingMeta, userTurnId),
|
|
120846
121494
|
lastHandledUserMsgId: userTurnId,
|
|
120847
|
-
processingUserMsgId: void 0
|
|
120848
|
-
dispatchError: void 0
|
|
121495
|
+
processingUserMsgId: void 0
|
|
120849
121496
|
});
|
|
120850
121497
|
}
|
|
120851
|
-
async
|
|
121498
|
+
async markTurnFailed(sessionId, sessionDoc, userTurnId) {
|
|
121499
|
+
const existingMeta = await this.getSessionMeta(sessionId);
|
|
120852
121500
|
await this.setUserTurnStatus(sessionDoc, userTurnId, "failed");
|
|
120853
121501
|
await this.upsertSessionMeta(sessionId, {
|
|
120854
|
-
latestUserMsgId: userTurnId,
|
|
120855
|
-
|
|
120856
|
-
|
|
120857
|
-
code: code2,
|
|
120858
|
-
...message ? {
|
|
120859
|
-
message
|
|
120860
|
-
} : {},
|
|
120861
|
-
at: getServerNow()
|
|
120862
|
-
}
|
|
121502
|
+
latestUserMsgId: this.resolveLatestUserMsgIdForTerminalTurn(existingMeta, userTurnId),
|
|
121503
|
+
lastHandledUserMsgId: userTurnId,
|
|
121504
|
+
processingUserMsgId: void 0
|
|
120863
121505
|
});
|
|
120864
121506
|
}
|
|
120865
121507
|
resolveGitHubProjectBranch(meta, preferredBranch) {
|
|
@@ -120953,15 +121595,18 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
120953
121595
|
}
|
|
120954
121596
|
async continueSession(message) {
|
|
120955
121597
|
const { sessionId, acpSessionConfig, userId, userName, userEmail, userTurnId } = message;
|
|
120956
|
-
await this.deps.evictForMemoryPressure(sessionId);
|
|
121598
|
+
const memoryPressureResult = await this.deps.evictForMemoryPressure(sessionId);
|
|
121599
|
+
if (memoryPressureResult.stillUnderPressure) {
|
|
121600
|
+
this.warnOnMemoryPressure(sessionId, memoryPressureResult);
|
|
121601
|
+
}
|
|
121602
|
+
await this.logTurnStartResources(sessionId, "continue");
|
|
121603
|
+
const sessionDoc = await this.deps.workspaceDocument.getOrCreateSessionDoc(sessionId);
|
|
120957
121604
|
this.deps.touchSession(sessionId);
|
|
120958
121605
|
this.deps.logger.info(`Session chat received: ${sessionId}`);
|
|
120959
121606
|
this.deps.logger.debug(`[${sessionId}] Received chat request (userTurnId=${userTurnId})`);
|
|
120960
121607
|
await this.upsertSessionMeta(sessionId, {
|
|
120961
|
-
latestUserMsgId: userTurnId
|
|
120962
|
-
dispatchError: void 0
|
|
121608
|
+
latestUserMsgId: userTurnId
|
|
120963
121609
|
});
|
|
120964
|
-
const sessionDoc = await this.deps.workspaceDocument.getOrCreateSessionDoc(sessionId);
|
|
120965
121610
|
const incomingProjectBranch = message.project?.branch?.trim();
|
|
120966
121611
|
if (incomingProjectBranch) {
|
|
120967
121612
|
await sessionDoc.setBaseBranch(incomingProjectBranch);
|
|
@@ -121298,7 +121943,7 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
121298
121943
|
}));
|
|
121299
121944
|
}
|
|
121300
121945
|
async startSession(message) {
|
|
121301
|
-
await this.deps.evictForMemoryPressure(message.sessionId);
|
|
121946
|
+
const memoryPressureResult = await this.deps.evictForMemoryPressure(message.sessionId);
|
|
121302
121947
|
const { sessionId, acpSessionConfig, workspaceId, env: env2 } = message;
|
|
121303
121948
|
const userTurnId = typeof message.userTurnId === "string" && message.userTurnId.trim() ? message.userTurnId.trim() : void 0;
|
|
121304
121949
|
const project = message.project;
|
|
@@ -121311,6 +121956,10 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
121311
121956
|
const promptText = agentConfig.prompt ?? "";
|
|
121312
121957
|
const promptBytes = Buffer.byteLength(promptText, "utf8");
|
|
121313
121958
|
const promptPreview = promptText.length > 200 ? `${promptText.slice(0, 200)}\u2026` : promptText;
|
|
121959
|
+
if (memoryPressureResult.stillUnderPressure) {
|
|
121960
|
+
this.warnOnMemoryPressure(sessionId, memoryPressureResult);
|
|
121961
|
+
}
|
|
121962
|
+
await this.logTurnStartResources(sessionId, "start");
|
|
121314
121963
|
const sessionDoc = await this.deps.workspaceDocument.getOrCreateSessionDoc(sessionId);
|
|
121315
121964
|
const existingMeta = await sessionDoc.getMetaState();
|
|
121316
121965
|
const fromFeedbackPostId = message.meta?.fromFeedbackPostId?.trim() || existingMeta?.fromFeedbackPostId?.trim() || void 0;
|
|
@@ -121334,12 +121983,6 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
121334
121983
|
fromFeedbackPostId
|
|
121335
121984
|
};
|
|
121336
121985
|
this.deps.logger.debug(`[${sessionId}] session/create summary`, configForLog);
|
|
121337
|
-
if (userTurnId) {
|
|
121338
|
-
await this.upsertSessionMeta(sessionId, {
|
|
121339
|
-
latestUserMsgId: userTurnId,
|
|
121340
|
-
dispatchError: void 0
|
|
121341
|
-
});
|
|
121342
|
-
}
|
|
121343
121986
|
const sessionConfig = {
|
|
121344
121987
|
sessionId,
|
|
121345
121988
|
workspaceId,
|
|
@@ -121360,6 +122003,11 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
121360
122003
|
if (project) {
|
|
121361
122004
|
await sessionDoc.setProject(project);
|
|
121362
122005
|
}
|
|
122006
|
+
if (userTurnId) {
|
|
122007
|
+
await this.upsertSessionMeta(sessionId, {
|
|
122008
|
+
latestUserMsgId: userTurnId
|
|
122009
|
+
});
|
|
122010
|
+
}
|
|
121363
122011
|
if (branch) {
|
|
121364
122012
|
await sessionDoc.setBaseBranch(branch);
|
|
121365
122013
|
}
|
|
@@ -121853,9 +122501,6 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
121853
122501
|
watchedSessions = /* @__PURE__ */ new Map();
|
|
121854
122502
|
sessionCheckChains = /* @__PURE__ */ new Map();
|
|
121855
122503
|
cancelCheckChains = /* @__PURE__ */ new Map();
|
|
121856
|
-
dispatchRetryTimers = /* @__PURE__ */ new Map();
|
|
121857
|
-
dispatchRetryAttempts = /* @__PURE__ */ new Map();
|
|
121858
|
-
dispatchRetryStartedAt = /* @__PURE__ */ new Map();
|
|
121859
122504
|
cancelSeenTurn = /* @__PURE__ */ new Map();
|
|
121860
122505
|
metadataWatchHandle = null;
|
|
121861
122506
|
userResolver;
|
|
@@ -121899,15 +122544,9 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
121899
122544
|
for (const watched of this.watchedSessions.values()) {
|
|
121900
122545
|
watched.unsubscribe();
|
|
121901
122546
|
}
|
|
121902
|
-
for (const timer2 of this.dispatchRetryTimers.values()) {
|
|
121903
|
-
clearTimeout(timer2);
|
|
121904
|
-
}
|
|
121905
122547
|
this.watchedSessions.clear();
|
|
121906
122548
|
this.sessionCheckChains.clear();
|
|
121907
122549
|
this.cancelCheckChains.clear();
|
|
121908
|
-
this.dispatchRetryTimers.clear();
|
|
121909
|
-
this.dispatchRetryAttempts.clear();
|
|
121910
|
-
this.dispatchRetryStartedAt.clear();
|
|
121911
122550
|
this.cancelSeenTurn.clear();
|
|
121912
122551
|
this.userResolver.clear();
|
|
121913
122552
|
this.started = false;
|
|
@@ -121983,14 +122622,15 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
121983
122622
|
}));
|
|
121984
122623
|
}
|
|
121985
122624
|
async reconcileSessionWatch(sessionId) {
|
|
121986
|
-
const
|
|
122625
|
+
const roomId = getSessionRoomId(sessionId);
|
|
122626
|
+
const record2 = await this.deps.workspaceDocument.repo.getDocMeta(roomId);
|
|
122627
|
+
const meta = isLoroRepoDocDeleted(record2) ? void 0 : record2?.meta;
|
|
121987
122628
|
const isOwned = meta?.machineId === this.deps.machineId && !meta?.isArchived;
|
|
121988
122629
|
if (!isOwned) {
|
|
121989
122630
|
const watched = this.watchedSessions.get(sessionId);
|
|
121990
122631
|
watched?.unsubscribe();
|
|
121991
122632
|
this.watchedSessions.delete(sessionId);
|
|
121992
122633
|
this.cancelCheckChains.delete(sessionId);
|
|
121993
|
-
this.clearDispatchRetry(sessionId);
|
|
121994
122634
|
this.cancelSeenTurn.delete(sessionId);
|
|
121995
122635
|
return;
|
|
121996
122636
|
}
|
|
@@ -122001,7 +122641,6 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
122001
122641
|
this.watchedSessions.delete(sessionId);
|
|
122002
122642
|
this.deps.logger.debug(`[${sessionId}] Unwatching idle session (no pending work)`);
|
|
122003
122643
|
}
|
|
122004
|
-
this.clearDispatchRetry(sessionId);
|
|
122005
122644
|
return;
|
|
122006
122645
|
}
|
|
122007
122646
|
if (!this.watchedSessions.has(sessionId)) {
|
|
@@ -122018,11 +122657,6 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
122018
122657
|
this.enqueueCancelCheck(sessionId);
|
|
122019
122658
|
this.enqueueSessionCheck(sessionId);
|
|
122020
122659
|
}
|
|
122021
|
-
async readSessionMeta(sessionId) {
|
|
122022
|
-
const roomId = getSessionRoomId(sessionId);
|
|
122023
|
-
const record2 = await this.deps.workspaceDocument.repo.getDocMeta(roomId);
|
|
122024
|
-
return isLoroRepoDocDeleted(record2) ? void 0 : record2?.meta;
|
|
122025
|
-
}
|
|
122026
122660
|
sessionNeedsActiveWatch(meta) {
|
|
122027
122661
|
const statusType = meta.status?.type;
|
|
122028
122662
|
if (statusType === "running" || statusType === "initializing" || statusType === "requestPermission") {
|
|
@@ -122037,155 +122671,11 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
122037
122671
|
if (meta.lastCanceledTurn && meta.lastCanceledTurn !== this.cancelSeenTurn.get(meta.id)) {
|
|
122038
122672
|
return true;
|
|
122039
122673
|
}
|
|
122040
|
-
if (this.isDispatchRecoveryUnhealthy(meta)) {
|
|
122041
|
-
return false;
|
|
122042
|
-
}
|
|
122043
122674
|
if (!meta.lastHandledUserMsgId) {
|
|
122044
122675
|
return true;
|
|
122045
122676
|
}
|
|
122046
122677
|
return false;
|
|
122047
122678
|
}
|
|
122048
|
-
static RECOVERABLE_DISPATCH_RETRY_INITIAL_DELAY_MS = 1e3;
|
|
122049
|
-
static RECOVERABLE_DISPATCH_RETRY_MAX_DELAY_MS = 6e4;
|
|
122050
|
-
static RECOVERABLE_DISPATCH_RETRY_MAX_ELAPSED_MS = 5 * 6e4;
|
|
122051
|
-
static DISPATCH_RECOVERY_UNHEALTHY_CODE = "dispatch_recovery_unhealthy";
|
|
122052
|
-
hasRecoverableDispatchSignal(meta) {
|
|
122053
|
-
const statusType = meta.status?.type;
|
|
122054
|
-
if (statusType === "running" || statusType === "initializing" || statusType === "requestPermission") {
|
|
122055
|
-
return true;
|
|
122056
|
-
}
|
|
122057
|
-
if (meta.processingUserMsgId) {
|
|
122058
|
-
return true;
|
|
122059
|
-
}
|
|
122060
|
-
return !!meta.latestUserMsgId && meta.latestUserMsgId !== meta.lastHandledUserMsgId;
|
|
122061
|
-
}
|
|
122062
|
-
isDispatchRecoveryUnhealthy(meta) {
|
|
122063
|
-
return meta.dispatchError?.code === SessionDispatchWatcher.DISPATCH_RECOVERY_UNHEALTHY_CODE;
|
|
122064
|
-
}
|
|
122065
|
-
clearDispatchRetry(sessionId) {
|
|
122066
|
-
const timer2 = this.dispatchRetryTimers.get(sessionId);
|
|
122067
|
-
if (timer2) {
|
|
122068
|
-
clearTimeout(timer2);
|
|
122069
|
-
this.dispatchRetryTimers.delete(sessionId);
|
|
122070
|
-
}
|
|
122071
|
-
this.dispatchRetryAttempts.delete(sessionId);
|
|
122072
|
-
this.dispatchRetryStartedAt.delete(sessionId);
|
|
122073
|
-
}
|
|
122074
|
-
scheduleRecoverableDispatchRetry(sessionId, meta) {
|
|
122075
|
-
if (!this.hasRecoverableDispatchSignal(meta)) {
|
|
122076
|
-
this.clearDispatchRetry(sessionId);
|
|
122077
|
-
return;
|
|
122078
|
-
}
|
|
122079
|
-
if (this.dispatchRetryTimers.has(sessionId)) {
|
|
122080
|
-
return;
|
|
122081
|
-
}
|
|
122082
|
-
const startedAt = this.dispatchRetryStartedAt.get(sessionId) ?? Date.now();
|
|
122083
|
-
this.dispatchRetryStartedAt.set(sessionId, startedAt);
|
|
122084
|
-
const elapsedMs = Date.now() - startedAt;
|
|
122085
|
-
if (elapsedMs >= SessionDispatchWatcher.RECOVERABLE_DISPATCH_RETRY_MAX_ELAPSED_MS) {
|
|
122086
|
-
void this.markDispatchRecoveryUnhealthy(sessionId, meta, elapsedMs).catch((error2) => {
|
|
122087
|
-
this.deps.logger.error(`[${sessionId}] Failed to mark dispatch recovery unhealthy: ${formatErrorMessage(error2)}`);
|
|
122088
|
-
});
|
|
122089
|
-
return;
|
|
122090
|
-
}
|
|
122091
|
-
const attempt = this.dispatchRetryAttempts.get(sessionId) ?? 0;
|
|
122092
|
-
const delayMs = this.calculateRecoverableDispatchRetryDelay(attempt);
|
|
122093
|
-
const attemptNumber = attempt + 1;
|
|
122094
|
-
this.dispatchRetryAttempts.set(sessionId, attemptNumber);
|
|
122095
|
-
this.deps.logger.debug(`[${sessionId}] Dispatch metadata is present but no user turn is visible; rebuilding session doc watch in ${delayMs}ms (attempt=${attemptNumber}, elapsedMs=${elapsedMs})`);
|
|
122096
|
-
const timer2 = setTimeout(() => {
|
|
122097
|
-
this.dispatchRetryTimers.delete(sessionId);
|
|
122098
|
-
if (!this.started) {
|
|
122099
|
-
return;
|
|
122100
|
-
}
|
|
122101
|
-
this.refreshSessionDocWatch(sessionId, `recoverable-dispatch-missing-turn:${attemptNumber}`).catch((error2) => {
|
|
122102
|
-
void this.handleDispatchRetryRefreshFailure(sessionId, meta, error2);
|
|
122103
|
-
});
|
|
122104
|
-
}, delayMs);
|
|
122105
|
-
const maybeUnrefTimer = timer2;
|
|
122106
|
-
maybeUnrefTimer.unref?.();
|
|
122107
|
-
this.dispatchRetryTimers.set(sessionId, timer2);
|
|
122108
|
-
}
|
|
122109
|
-
calculateRecoverableDispatchRetryDelay(attempt) {
|
|
122110
|
-
const exponentialDelayMs = Math.min(SessionDispatchWatcher.RECOVERABLE_DISPATCH_RETRY_INITIAL_DELAY_MS * 2 ** attempt, SessionDispatchWatcher.RECOVERABLE_DISPATCH_RETRY_MAX_DELAY_MS);
|
|
122111
|
-
const halfDelayMs = exponentialDelayMs / 2;
|
|
122112
|
-
return Math.floor(halfDelayMs + Math.random() * halfDelayMs);
|
|
122113
|
-
}
|
|
122114
|
-
async handleDispatchRetryRefreshFailure(sessionId, previousMeta, error2) {
|
|
122115
|
-
this.deps.logger.error(`[${sessionId}] Failed to refresh session doc watch for dispatch retry: ${formatErrorMessage(error2)}`);
|
|
122116
|
-
if (!this.started) {
|
|
122117
|
-
return;
|
|
122118
|
-
}
|
|
122119
|
-
let meta = previousMeta;
|
|
122120
|
-
try {
|
|
122121
|
-
const freshMeta = await this.readSessionMeta(sessionId);
|
|
122122
|
-
if (!freshMeta) {
|
|
122123
|
-
this.clearDispatchRetry(sessionId);
|
|
122124
|
-
return;
|
|
122125
|
-
}
|
|
122126
|
-
meta = freshMeta;
|
|
122127
|
-
} catch (metaError) {
|
|
122128
|
-
this.deps.logger.debug(`[${sessionId}] Failed to read fresh metadata after dispatch retry refresh failure; using previous retry signal: ${formatErrorMessage(metaError)}`);
|
|
122129
|
-
}
|
|
122130
|
-
if (!this.started) {
|
|
122131
|
-
return;
|
|
122132
|
-
}
|
|
122133
|
-
if (meta.machineId !== this.deps.machineId || meta.isArchived) {
|
|
122134
|
-
this.clearDispatchRetry(sessionId);
|
|
122135
|
-
return;
|
|
122136
|
-
}
|
|
122137
|
-
this.scheduleRecoverableDispatchRetry(sessionId, meta);
|
|
122138
|
-
}
|
|
122139
|
-
async markDispatchRecoveryUnhealthy(sessionId, previousMeta, elapsedMs) {
|
|
122140
|
-
this.clearDispatchRetry(sessionId);
|
|
122141
|
-
if (!this.started) {
|
|
122142
|
-
return;
|
|
122143
|
-
}
|
|
122144
|
-
let meta = previousMeta;
|
|
122145
|
-
try {
|
|
122146
|
-
const freshMeta = await this.readSessionMeta(sessionId);
|
|
122147
|
-
if (!freshMeta) {
|
|
122148
|
-
return;
|
|
122149
|
-
}
|
|
122150
|
-
meta = freshMeta;
|
|
122151
|
-
} catch (error2) {
|
|
122152
|
-
this.deps.logger.debug(`[${sessionId}] Failed to read fresh metadata before marking dispatch recovery unhealthy; using previous retry signal: ${formatErrorMessage(error2)}`);
|
|
122153
|
-
}
|
|
122154
|
-
if (!this.hasRecoverableDispatchSignal(meta) || meta.machineId !== this.deps.machineId || meta.isArchived) {
|
|
122155
|
-
return;
|
|
122156
|
-
}
|
|
122157
|
-
this.deps.logger.warn(`[${sessionId}] Dispatch recovery could not find a user turn after ${elapsedMs}ms; marking recovery unhealthy`);
|
|
122158
|
-
await this.deps.workspaceDocument.repo.upsertDocMeta?.(getSessionRoomId(sessionId), {
|
|
122159
|
-
status: SessionStatusFactory.idle(),
|
|
122160
|
-
latestUserMsgId: void 0,
|
|
122161
|
-
processingUserMsgId: void 0,
|
|
122162
|
-
dispatchError: {
|
|
122163
|
-
code: SessionDispatchWatcher.DISPATCH_RECOVERY_UNHEALTHY_CODE,
|
|
122164
|
-
message: "Dispatch recovery could not reconnect to this session after 5 minutes. Send a new message to retry.",
|
|
122165
|
-
at: getServerNow()
|
|
122166
|
-
}
|
|
122167
|
-
});
|
|
122168
|
-
const watched = this.watchedSessions.get(sessionId);
|
|
122169
|
-
watched?.unsubscribe();
|
|
122170
|
-
this.watchedSessions.delete(sessionId);
|
|
122171
|
-
this.sessionCheckChains.delete(sessionId);
|
|
122172
|
-
await this.deps.workspaceDocument.cleanSessionDoc(sessionId, {
|
|
122173
|
-
preserveStatus: true
|
|
122174
|
-
});
|
|
122175
|
-
}
|
|
122176
|
-
async refreshSessionDocWatch(sessionId, reason) {
|
|
122177
|
-
const watched = this.watchedSessions.get(sessionId);
|
|
122178
|
-
watched?.unsubscribe();
|
|
122179
|
-
this.watchedSessions.delete(sessionId);
|
|
122180
|
-
this.deps.logger.debug(`[${sessionId}] Refreshing session doc watch (${reason})`);
|
|
122181
|
-
await this.deps.workspaceDocument.cleanSessionDoc(sessionId, {
|
|
122182
|
-
preserveStatus: true
|
|
122183
|
-
});
|
|
122184
|
-
if (!this.started) {
|
|
122185
|
-
return;
|
|
122186
|
-
}
|
|
122187
|
-
await this.reconcileSessionWatch(sessionId);
|
|
122188
|
-
}
|
|
122189
122679
|
async maybeHandleSession(sessionId) {
|
|
122190
122680
|
const sessionDoc = await this.deps.workspaceDocument.getOrCreateSessionDoc(sessionId);
|
|
122191
122681
|
const meta = await sessionDoc.getMetaState();
|
|
@@ -122208,10 +122698,11 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
122208
122698
|
}
|
|
122209
122699
|
const nextUserTurn = await this.findOrAwaitDispatchableTurn(sessionId, sessionDoc, meta);
|
|
122210
122700
|
if (!nextUserTurn) {
|
|
122211
|
-
this.
|
|
122701
|
+
if (this.hasPendingUserTurnSignal(meta)) {
|
|
122702
|
+
await this.markMissingUserTurnRecovery(sessionId, meta);
|
|
122703
|
+
}
|
|
122212
122704
|
return;
|
|
122213
122705
|
}
|
|
122214
|
-
this.clearDispatchRetry(sessionId);
|
|
122215
122706
|
const freshMeta = await sessionDoc.getMetaState();
|
|
122216
122707
|
if (!freshMeta) {
|
|
122217
122708
|
return;
|
|
@@ -122275,12 +122766,7 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
122275
122766
|
await this.deps.workspaceDocument.repo.upsertDocMeta?.(getSessionRoomId(sessionId), {
|
|
122276
122767
|
latestUserMsgId: userTurnId,
|
|
122277
122768
|
lastHandledUserMsgId: userTurnId,
|
|
122278
|
-
processingUserMsgId: void 0
|
|
122279
|
-
dispatchError: {
|
|
122280
|
-
code: reason === "cli_token_invalid" ? "cli_token_invalid" : "machine_access_denied",
|
|
122281
|
-
message,
|
|
122282
|
-
at: getServerNow()
|
|
122283
|
-
}
|
|
122769
|
+
processingUserMsgId: void 0
|
|
122284
122770
|
});
|
|
122285
122771
|
await sessionDoc.setStatus(SessionStatusFactory.idle());
|
|
122286
122772
|
}
|
|
@@ -122407,6 +122893,26 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
122407
122893
|
}
|
|
122408
122894
|
static REALTIME_WAIT_TIMEOUT_MS = 3e4;
|
|
122409
122895
|
static REMOTE_SYNC_TIMEOUT_MS = 15e3;
|
|
122896
|
+
static HISTORY_SYNC_WAIT_TIMEOUT_MS = 5 * 6e4;
|
|
122897
|
+
static HISTORY_RECONNECT_JITTER_MIN_MS = 500;
|
|
122898
|
+
static HISTORY_RECONNECT_JITTER_MAX_MS = 1500;
|
|
122899
|
+
static setUnrefTimeout(callback, delayMs) {
|
|
122900
|
+
const timer2 = setTimeout(callback, delayMs);
|
|
122901
|
+
if (typeof timer2 === "object" && "unref" in timer2) {
|
|
122902
|
+
timer2.unref();
|
|
122903
|
+
}
|
|
122904
|
+
return timer2;
|
|
122905
|
+
}
|
|
122906
|
+
static sleep(delayMs) {
|
|
122907
|
+
return new Promise((resolve2) => {
|
|
122908
|
+
SessionDispatchWatcher.setUnrefTimeout(resolve2, delayMs);
|
|
122909
|
+
});
|
|
122910
|
+
}
|
|
122911
|
+
static getReconnectJitterMs() {
|
|
122912
|
+
const min2 = SessionDispatchWatcher.HISTORY_RECONNECT_JITTER_MIN_MS;
|
|
122913
|
+
const max2 = SessionDispatchWatcher.HISTORY_RECONNECT_JITTER_MAX_MS;
|
|
122914
|
+
return min2 + Math.floor(Math.random() * (max2 - min2 + 1));
|
|
122915
|
+
}
|
|
122410
122916
|
async findOrAwaitDispatchableTurn(sessionId, sessionDoc, meta) {
|
|
122411
122917
|
const history = await sessionDoc.getHistory();
|
|
122412
122918
|
let turn = findNextDispatchableUserTurn(history, meta);
|
|
@@ -122417,9 +122923,12 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
122417
122923
|
if (turn) {
|
|
122418
122924
|
return turn;
|
|
122419
122925
|
}
|
|
122926
|
+
if (this.hasPendingUserTurnSignal(meta)) {
|
|
122927
|
+
return await this.waitForPendingUserTurnHistorySync(sessionId, sessionDoc, meta);
|
|
122928
|
+
}
|
|
122420
122929
|
await Promise.race([
|
|
122421
122930
|
sessionDoc.waitForRemoteSync(),
|
|
122422
|
-
new Promise((resolve2) =>
|
|
122931
|
+
new Promise((resolve2) => SessionDispatchWatcher.setUnrefTimeout(resolve2, SessionDispatchWatcher.REMOTE_SYNC_TIMEOUT_MS))
|
|
122423
122932
|
]);
|
|
122424
122933
|
turn = await this.checkHistoryAndQueue(sessionDoc, meta);
|
|
122425
122934
|
if (turn) {
|
|
@@ -122429,6 +122938,138 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
122429
122938
|
turn = await this.waitForDispatchableTurnFromMirror(sessionId, sessionDoc, meta);
|
|
122430
122939
|
return turn;
|
|
122431
122940
|
}
|
|
122941
|
+
hasPendingUserTurnSignal(meta) {
|
|
122942
|
+
if (meta.processingUserMsgId) {
|
|
122943
|
+
return true;
|
|
122944
|
+
}
|
|
122945
|
+
return Boolean(meta.latestUserMsgId && meta.latestUserMsgId !== meta.lastHandledUserMsgId);
|
|
122946
|
+
}
|
|
122947
|
+
async waitForPendingUserTurnHistorySync(sessionId, sessionDoc, meta) {
|
|
122948
|
+
this.deps.logger.debug(`[${sessionId}] Pending user turn metadata is visible but history is missing it; waiting up to ${SessionDispatchWatcher.HISTORY_SYNC_WAIT_TIMEOUT_MS / 1e3}s for history CRDT sync`);
|
|
122949
|
+
try {
|
|
122950
|
+
await sessionDoc.ensureDocRoomJoined();
|
|
122951
|
+
} catch (error2) {
|
|
122952
|
+
this.deps.logger.debug(`[${sessionId}] Failed to ensure session history room is joined before waiting: ${formatErrorMessage(error2)}`);
|
|
122953
|
+
}
|
|
122954
|
+
await sessionDoc.waitUntilSynced();
|
|
122955
|
+
const freshMeta = await sessionDoc.getMetaState() ?? meta;
|
|
122956
|
+
if (!this.hasPendingUserTurnSignal(freshMeta)) {
|
|
122957
|
+
this.deps.logger.debug(`[${sessionId}] Pending user turn pointer cleared during pre-wait sync; exiting wait`);
|
|
122958
|
+
return null;
|
|
122959
|
+
}
|
|
122960
|
+
const turnAfterJoin = await this.checkHistoryAndQueue(sessionDoc, freshMeta);
|
|
122961
|
+
if (turnAfterJoin) {
|
|
122962
|
+
return turnAfterJoin;
|
|
122963
|
+
}
|
|
122964
|
+
return await new Promise((resolve2) => {
|
|
122965
|
+
let settled = false;
|
|
122966
|
+
let reconnectAttempted = false;
|
|
122967
|
+
let unsubscribeMirror;
|
|
122968
|
+
let unsubscribeStatus;
|
|
122969
|
+
const cleanup = () => {
|
|
122970
|
+
if (settled) {
|
|
122971
|
+
return;
|
|
122972
|
+
}
|
|
122973
|
+
settled = true;
|
|
122974
|
+
clearTimeout(timer2);
|
|
122975
|
+
unsubscribeMirror?.();
|
|
122976
|
+
unsubscribeStatus?.();
|
|
122977
|
+
};
|
|
122978
|
+
const checkForTurn = () => {
|
|
122979
|
+
if (settled) {
|
|
122980
|
+
return;
|
|
122981
|
+
}
|
|
122982
|
+
void this.checkHistoryAndQueue(sessionDoc, freshMeta).then((turn) => {
|
|
122983
|
+
if (settled || !turn) {
|
|
122984
|
+
return;
|
|
122985
|
+
}
|
|
122986
|
+
cleanup();
|
|
122987
|
+
this.deps.logger.debug(`[${sessionId}] Found dispatchable turn after waiting for history CRDT sync`);
|
|
122988
|
+
resolve2(turn);
|
|
122989
|
+
}).catch((error2) => {
|
|
122990
|
+
if (settled) {
|
|
122991
|
+
return;
|
|
122992
|
+
}
|
|
122993
|
+
this.deps.logger.debug(`[${sessionId}] Error checking history during history sync wait: ${formatErrorMessage(error2)}`);
|
|
122994
|
+
});
|
|
122995
|
+
};
|
|
122996
|
+
const attemptReconnectOnce = (reason) => {
|
|
122997
|
+
if (settled || reconnectAttempted) {
|
|
122998
|
+
return;
|
|
122999
|
+
}
|
|
123000
|
+
reconnectAttempted = true;
|
|
123001
|
+
void (async () => {
|
|
123002
|
+
const jitterMs = SessionDispatchWatcher.getReconnectJitterMs();
|
|
123003
|
+
this.deps.logger.debug(`[${sessionId}] Session history room ${reason}; attempting one rejoin in ${jitterMs}ms`);
|
|
123004
|
+
await SessionDispatchWatcher.sleep(jitterMs);
|
|
123005
|
+
if (settled) {
|
|
123006
|
+
return;
|
|
123007
|
+
}
|
|
123008
|
+
try {
|
|
123009
|
+
await sessionDoc.rejoinDocRoom();
|
|
123010
|
+
} catch (error2) {
|
|
123011
|
+
this.deps.logger.debug(`[${sessionId}] Session history room rejoin failed: ${formatErrorMessage(error2)}`);
|
|
123012
|
+
}
|
|
123013
|
+
checkForTurn();
|
|
123014
|
+
})();
|
|
123015
|
+
};
|
|
123016
|
+
const handleRoomStatus = (status) => {
|
|
123017
|
+
if (settled || !status) {
|
|
123018
|
+
return;
|
|
123019
|
+
}
|
|
123020
|
+
if (status === "disconnected" || status === "error") {
|
|
123021
|
+
attemptReconnectOnce(status);
|
|
123022
|
+
}
|
|
123023
|
+
};
|
|
123024
|
+
const timer2 = SessionDispatchWatcher.setUnrefTimeout(() => {
|
|
123025
|
+
cleanup();
|
|
123026
|
+
this.deps.logger.warn(`[${sessionId}] User turn did not arrive in history after ${SessionDispatchWatcher.HISTORY_SYNC_WAIT_TIMEOUT_MS / 1e3}s; entering dispatch recovery`);
|
|
123027
|
+
resolve2(null);
|
|
123028
|
+
}, SessionDispatchWatcher.HISTORY_SYNC_WAIT_TIMEOUT_MS);
|
|
123029
|
+
unsubscribeMirror = sessionDoc.mirror?.subscribe(checkForTurn);
|
|
123030
|
+
if (!unsubscribeMirror) {
|
|
123031
|
+
this.deps.logger.debug(`[${sessionId}] Session mirror is unavailable during history sync wait`);
|
|
123032
|
+
}
|
|
123033
|
+
unsubscribeStatus = sessionDoc.onDocRoomStatusChange(handleRoomStatus);
|
|
123034
|
+
const currentStatus = sessionDoc.getDocRoomStatus();
|
|
123035
|
+
if (currentStatus) {
|
|
123036
|
+
handleRoomStatus(currentStatus);
|
|
123037
|
+
} else {
|
|
123038
|
+
attemptReconnectOnce("has no active subscription");
|
|
123039
|
+
}
|
|
123040
|
+
});
|
|
123041
|
+
}
|
|
123042
|
+
async markMissingUserTurnRecovery(sessionId, previousMeta) {
|
|
123043
|
+
const roomId = getSessionRoomId(sessionId);
|
|
123044
|
+
let meta = previousMeta;
|
|
123045
|
+
try {
|
|
123046
|
+
const record2 = await this.deps.workspaceDocument.repo.getDocMeta(roomId);
|
|
123047
|
+
if (!isLoroRepoDocDeleted(record2) && record2?.meta) {
|
|
123048
|
+
meta = record2.meta;
|
|
123049
|
+
}
|
|
123050
|
+
} catch (error2) {
|
|
123051
|
+
this.deps.logger.debug(`[${sessionId}] Failed to refresh metadata before dispatch recovery: ${formatErrorMessage(error2)}`);
|
|
123052
|
+
}
|
|
123053
|
+
if (meta.machineId !== this.deps.machineId || meta.isArchived || !this.hasPendingUserTurnSignal(meta)) {
|
|
123054
|
+
return;
|
|
123055
|
+
}
|
|
123056
|
+
const pendingUserMsgId = meta.processingUserMsgId ?? meta.latestUserMsgId ?? meta.lastHandledUserMsgId;
|
|
123057
|
+
const recoveryPatch = {
|
|
123058
|
+
status: SessionStatusFactory.idle(),
|
|
123059
|
+
processingUserMsgId: void 0
|
|
123060
|
+
};
|
|
123061
|
+
if (pendingUserMsgId) {
|
|
123062
|
+
recoveryPatch.lastHandledUserMsgId = pendingUserMsgId;
|
|
123063
|
+
recoveryPatch.latestUserMsgId = pendingUserMsgId;
|
|
123064
|
+
}
|
|
123065
|
+
await this.deps.workspaceDocument.repo.upsertDocMeta?.(roomId, recoveryPatch);
|
|
123066
|
+
const watched = this.watchedSessions.get(sessionId);
|
|
123067
|
+
watched?.unsubscribe();
|
|
123068
|
+
this.watchedSessions.delete(sessionId);
|
|
123069
|
+
await this.deps.workspaceDocument.cleanSessionDoc(sessionId, {
|
|
123070
|
+
preserveStatus: true
|
|
123071
|
+
});
|
|
123072
|
+
}
|
|
122432
123073
|
async waitForDispatchableTurnFromMirror(sessionId, sessionDoc, meta) {
|
|
122433
123074
|
return new Promise((resolve2) => {
|
|
122434
123075
|
let settled = false;
|
|
@@ -122437,7 +123078,7 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
122437
123078
|
clearTimeout(timer2);
|
|
122438
123079
|
unsubscribe2?.();
|
|
122439
123080
|
};
|
|
122440
|
-
const timer2 =
|
|
123081
|
+
const timer2 = SessionDispatchWatcher.setUnrefTimeout(() => {
|
|
122441
123082
|
if (settled) return;
|
|
122442
123083
|
cleanup();
|
|
122443
123084
|
this.deps.logger.debug(`[${sessionId}] No dispatchable turn found after ${SessionDispatchWatcher.REALTIME_WAIT_TIMEOUT_MS / 1e3}s timeout`);
|
|
@@ -123168,8 +123809,14 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
123168
123809
|
permissionRequestStartTimes = /* @__PURE__ */ new Map();
|
|
123169
123810
|
machineHeartbeatTimer = null;
|
|
123170
123811
|
static MACHINE_HEARTBEAT_INTERVAL_MS = 2e4;
|
|
123171
|
-
evictForMemoryPressureFn = async () => {
|
|
123172
|
-
|
|
123812
|
+
evictForMemoryPressureFn = async () => ({
|
|
123813
|
+
availableMemoryBytes: 0,
|
|
123814
|
+
thresholdBytes: 0,
|
|
123815
|
+
hadMemoryPressure: false,
|
|
123816
|
+
stillUnderPressure: false,
|
|
123817
|
+
evictedSessionIds: [],
|
|
123818
|
+
pressureReason: null
|
|
123819
|
+
});
|
|
123173
123820
|
executionService;
|
|
123174
123821
|
sessionDispatchWatcher;
|
|
123175
123822
|
autoPromptRunner;
|
|
@@ -125181,6 +125828,16 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
125181
125828
|
hasPendingUpdates(sessionId) {
|
|
125182
125829
|
return this.store.has(sessionId) && this.store.get(sessionId).acpUpdateBuffer.length > 0;
|
|
125183
125830
|
}
|
|
125831
|
+
async hasPendingUserWork(sessionId) {
|
|
125832
|
+
const meta = (await this.workspaceDocument.repo.getDocMeta(getSessionRoomId(sessionId)))?.meta;
|
|
125833
|
+
if (!meta) {
|
|
125834
|
+
return false;
|
|
125835
|
+
}
|
|
125836
|
+
if (meta.processingUserMsgId) {
|
|
125837
|
+
return true;
|
|
125838
|
+
}
|
|
125839
|
+
return Boolean(meta.latestUserMsgId && meta.latestUserMsgId !== meta.lastHandledUserMsgId);
|
|
125840
|
+
}
|
|
125184
125841
|
isArchiveInFlight(sessionId) {
|
|
125185
125842
|
return this.archiveInFlight.has(sessionId);
|
|
125186
125843
|
}
|
|
@@ -125537,116 +126194,6 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
125537
126194
|
}
|
|
125538
126195
|
}
|
|
125539
126196
|
}
|
|
125540
|
-
function getAvailableMemoryBytes() {
|
|
125541
|
-
const systemAvailable = getSystemAvailableMemoryBytes();
|
|
125542
|
-
const cgroupAvailable = getCgroupAvailableMemoryBytes();
|
|
125543
|
-
if (cgroupAvailable !== null) {
|
|
125544
|
-
return Math.min(systemAvailable, cgroupAvailable);
|
|
125545
|
-
}
|
|
125546
|
-
return systemAvailable;
|
|
125547
|
-
}
|
|
125548
|
-
function getEffectiveMemoryLimitBytes() {
|
|
125549
|
-
const totalMem = os__default.totalmem();
|
|
125550
|
-
const cgroupMax = getCgroupMemoryMaxBytes();
|
|
125551
|
-
if (cgroupMax !== null) {
|
|
125552
|
-
return Math.min(totalMem, cgroupMax);
|
|
125553
|
-
}
|
|
125554
|
-
return totalMem;
|
|
125555
|
-
}
|
|
125556
|
-
function getSystemAvailableMemoryBytes() {
|
|
125557
|
-
try {
|
|
125558
|
-
const meminfo = readFileSync("/proc/meminfo", "utf8");
|
|
125559
|
-
const match5 = meminfo.match(/MemAvailable:\s+(\d+)/);
|
|
125560
|
-
if (match5?.[1]) {
|
|
125561
|
-
return parseInt(match5[1], 10) * 1024;
|
|
125562
|
-
}
|
|
125563
|
-
} catch {
|
|
125564
|
-
}
|
|
125565
|
-
return os__default.freemem();
|
|
125566
|
-
}
|
|
125567
|
-
function getCgroupMemoryMaxBytes() {
|
|
125568
|
-
try {
|
|
125569
|
-
const cgroupPath = readSelfCgroupPath();
|
|
125570
|
-
if (cgroupPath === null) return null;
|
|
125571
|
-
let tightest = null;
|
|
125572
|
-
let current2 = cgroupPath;
|
|
125573
|
-
for (let depth = 0; depth < 20; depth++) {
|
|
125574
|
-
const memMaxPath = `/sys/fs/cgroup${current2 === "/" ? "" : current2}/memory.max`;
|
|
125575
|
-
const raw = readFileSafe(memMaxPath);
|
|
125576
|
-
if (raw !== null) {
|
|
125577
|
-
const trimmed = raw.trim();
|
|
125578
|
-
if (trimmed !== "max") {
|
|
125579
|
-
const value = parseInt(trimmed, 10);
|
|
125580
|
-
if (Number.isFinite(value) && value > 0) {
|
|
125581
|
-
tightest = tightest === null ? value : Math.min(tightest, value);
|
|
125582
|
-
}
|
|
125583
|
-
}
|
|
125584
|
-
}
|
|
125585
|
-
if (current2 === "/" || current2 === "") break;
|
|
125586
|
-
const parent = current2.substring(0, current2.lastIndexOf("/")) || "/";
|
|
125587
|
-
if (parent === current2) break;
|
|
125588
|
-
current2 = parent;
|
|
125589
|
-
}
|
|
125590
|
-
return tightest;
|
|
125591
|
-
} catch {
|
|
125592
|
-
return null;
|
|
125593
|
-
}
|
|
125594
|
-
}
|
|
125595
|
-
function getCgroupAvailableMemoryBytes() {
|
|
125596
|
-
try {
|
|
125597
|
-
const cgroupPath = readSelfCgroupPath();
|
|
125598
|
-
if (cgroupPath === null) return null;
|
|
125599
|
-
let tightestMax = null;
|
|
125600
|
-
let tightestPath = null;
|
|
125601
|
-
let current2 = cgroupPath;
|
|
125602
|
-
for (let depth = 0; depth < 20; depth++) {
|
|
125603
|
-
const prefix = `/sys/fs/cgroup${current2 === "/" ? "" : current2}`;
|
|
125604
|
-
const raw = readFileSafe(`${prefix}/memory.max`);
|
|
125605
|
-
if (raw !== null) {
|
|
125606
|
-
const trimmed = raw.trim();
|
|
125607
|
-
if (trimmed !== "max") {
|
|
125608
|
-
const value = parseInt(trimmed, 10);
|
|
125609
|
-
if (Number.isFinite(value) && value > 0) {
|
|
125610
|
-
if (tightestMax === null || value < tightestMax) {
|
|
125611
|
-
tightestMax = value;
|
|
125612
|
-
tightestPath = prefix;
|
|
125613
|
-
}
|
|
125614
|
-
}
|
|
125615
|
-
}
|
|
125616
|
-
}
|
|
125617
|
-
if (current2 === "/" || current2 === "") break;
|
|
125618
|
-
const parent = current2.substring(0, current2.lastIndexOf("/")) || "/";
|
|
125619
|
-
if (parent === current2) break;
|
|
125620
|
-
current2 = parent;
|
|
125621
|
-
}
|
|
125622
|
-
if (tightestMax === null || tightestPath === null) return null;
|
|
125623
|
-
const currentRaw = readFileSafe(`${tightestPath}/memory.current`);
|
|
125624
|
-
if (currentRaw === null) return null;
|
|
125625
|
-
const currentUsage = parseInt(currentRaw.trim(), 10);
|
|
125626
|
-
if (!Number.isFinite(currentUsage)) return null;
|
|
125627
|
-
return Math.max(0, tightestMax - currentUsage);
|
|
125628
|
-
} catch {
|
|
125629
|
-
return null;
|
|
125630
|
-
}
|
|
125631
|
-
}
|
|
125632
|
-
function readSelfCgroupPath() {
|
|
125633
|
-
try {
|
|
125634
|
-
const content = readFileSync("/proc/self/cgroup", "utf8");
|
|
125635
|
-
const line3 = content.split("\n").map((l) => l.trim()).find((l) => l.startsWith("0::"));
|
|
125636
|
-
if (!line3) return null;
|
|
125637
|
-
const cgroupPath = line3.slice(3).trim();
|
|
125638
|
-
return cgroupPath || "/";
|
|
125639
|
-
} catch {
|
|
125640
|
-
return null;
|
|
125641
|
-
}
|
|
125642
|
-
}
|
|
125643
|
-
function readFileSafe(filePath) {
|
|
125644
|
-
try {
|
|
125645
|
-
return readFileSync(filePath, "utf8");
|
|
125646
|
-
} catch {
|
|
125647
|
-
return null;
|
|
125648
|
-
}
|
|
125649
|
-
}
|
|
125650
126197
|
function readEnvNumber(key2, fallback2) {
|
|
125651
126198
|
const raw = process.env[key2];
|
|
125652
126199
|
if (!raw) return fallback2;
|
|
@@ -125654,6 +126201,8 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
125654
126201
|
return Number.isFinite(parsed) ? parsed : fallback2;
|
|
125655
126202
|
}
|
|
125656
126203
|
const GIB = 1024 * 1024 * 1024;
|
|
126204
|
+
const WINDOWS_COMMIT_THRESHOLD_FLOOR_BYTES = 512 * 1024 * 1024;
|
|
126205
|
+
const WINDOWS_COMMIT_THRESHOLD_CEILING_BYTES = 2 * GIB;
|
|
125657
126206
|
function defaultMemoryThresholdBytes() {
|
|
125658
126207
|
const tenPercent = Math.floor(getEffectiveMemoryLimitBytes() * 0.1);
|
|
125659
126208
|
return Math.max(GIB, Math.min(4 * GIB, tenPercent));
|
|
@@ -125666,6 +126215,9 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
125666
126215
|
memoryThresholdBytes: readEnvNumber("LODY_SESSION_GC_MEMORY_THRESHOLD_BYTES", defaultMemoryThresholdBytes())
|
|
125667
126216
|
};
|
|
125668
126217
|
}
|
|
126218
|
+
function getWindowsCommitThresholdBytes(memoryThresholdBytes) {
|
|
126219
|
+
return Math.max(WINDOWS_COMMIT_THRESHOLD_FLOOR_BYTES, Math.min(WINDOWS_COMMIT_THRESHOLD_CEILING_BYTES, memoryThresholdBytes));
|
|
126220
|
+
}
|
|
125669
126221
|
class SessionGCManager {
|
|
125670
126222
|
constructor(config2, deps) {
|
|
125671
126223
|
this.config = config2;
|
|
@@ -125689,7 +126241,7 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
125689
126241
|
}
|
|
125690
126242
|
async sweep() {
|
|
125691
126243
|
const sweepStart = Date.now();
|
|
125692
|
-
const candidates = this.getIdleCandidates();
|
|
126244
|
+
const candidates = await this.getIdleCandidates();
|
|
125693
126245
|
if (candidates.length === 0) {
|
|
125694
126246
|
return;
|
|
125695
126247
|
}
|
|
@@ -125697,7 +126249,7 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
125697
126249
|
let cleaned = 0;
|
|
125698
126250
|
let skipped = 0;
|
|
125699
126251
|
for (const { sessionId } of candidates) {
|
|
125700
|
-
if (!this.isStillEligibleForGC(sessionId)) {
|
|
126252
|
+
if (!await this.isStillEligibleForGC(sessionId)) {
|
|
125701
126253
|
skipped++;
|
|
125702
126254
|
continue;
|
|
125703
126255
|
}
|
|
@@ -125715,19 +126267,50 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
125715
126267
|
this.deps.logger.debug(`[GC] Sweep completed: cleaned ${cleaned}/${candidates.length} sessions in ${sweepDuration}ms`);
|
|
125716
126268
|
}
|
|
125717
126269
|
async evictForMemoryPressure(excludeSessionId) {
|
|
126270
|
+
const thresholdBytes = this.config.memoryThresholdBytes;
|
|
126271
|
+
const commitThresholdBytes = getWindowsCommitThresholdBytes(thresholdBytes);
|
|
126272
|
+
let memorySnapshot = getMemoryPressureSnapshot();
|
|
126273
|
+
let availableMemory = memorySnapshot.availableMemoryBytes;
|
|
126274
|
+
let pressureReason = this.getPressureReason(memorySnapshot, thresholdBytes, commitThresholdBytes);
|
|
125718
126275
|
if (!this.config.enabled) {
|
|
125719
|
-
return
|
|
126276
|
+
return {
|
|
126277
|
+
availableMemoryBytes: availableMemory,
|
|
126278
|
+
thresholdBytes,
|
|
126279
|
+
hadMemoryPressure: false,
|
|
126280
|
+
stillUnderPressure: false,
|
|
126281
|
+
evictedSessionIds: [],
|
|
126282
|
+
pressureReason: null,
|
|
126283
|
+
...memorySnapshot.availableCommitBytes !== void 0 ? {
|
|
126284
|
+
availableCommitBytes: memorySnapshot.availableCommitBytes,
|
|
126285
|
+
commitThresholdBytes,
|
|
126286
|
+
commitLimitBytes: memorySnapshot.commitLimitBytes,
|
|
126287
|
+
committedBytes: memorySnapshot.committedBytes
|
|
126288
|
+
} : {}
|
|
126289
|
+
};
|
|
125720
126290
|
}
|
|
125721
|
-
|
|
125722
|
-
|
|
125723
|
-
|
|
126291
|
+
if (pressureReason === null) {
|
|
126292
|
+
return {
|
|
126293
|
+
availableMemoryBytes: availableMemory,
|
|
126294
|
+
thresholdBytes,
|
|
126295
|
+
hadMemoryPressure: false,
|
|
126296
|
+
stillUnderPressure: false,
|
|
126297
|
+
evictedSessionIds: [],
|
|
126298
|
+
pressureReason: null,
|
|
126299
|
+
...memorySnapshot.availableCommitBytes !== void 0 ? {
|
|
126300
|
+
availableCommitBytes: memorySnapshot.availableCommitBytes,
|
|
126301
|
+
commitThresholdBytes,
|
|
126302
|
+
commitLimitBytes: memorySnapshot.commitLimitBytes,
|
|
126303
|
+
committedBytes: memorySnapshot.committedBytes
|
|
126304
|
+
} : {}
|
|
126305
|
+
};
|
|
125724
126306
|
}
|
|
125725
|
-
|
|
126307
|
+
const commitText = memorySnapshot.availableCommitBytes !== void 0 ? `, commit headroom ${Math.round(memorySnapshot.availableCommitBytes / 1024 / 1024)}MB (threshold: ${Math.round(commitThresholdBytes / 1024 / 1024)}MB)` : "";
|
|
126308
|
+
this.deps.logger.debug(`[GC] Memory pressure detected: ${Math.round(availableMemory / 1024 / 1024)}MB available (threshold: ${Math.round(thresholdBytes / 1024 / 1024)}MB)${commitText}`);
|
|
125726
126309
|
const sessions = this.getSessionsWithIdleTime();
|
|
125727
126310
|
sessions.sort((a, b) => b.idleMs - a.idleMs);
|
|
125728
|
-
|
|
126311
|
+
const evictedSessionIds = [];
|
|
125729
126312
|
for (const { sessionId, idleMs } of sessions) {
|
|
125730
|
-
if (
|
|
126313
|
+
if (pressureReason === null) {
|
|
125731
126314
|
break;
|
|
125732
126315
|
}
|
|
125733
126316
|
if (excludeSessionId && sessionId === excludeSessionId) {
|
|
@@ -125736,30 +126319,63 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
125736
126319
|
if (idleMs === 0) {
|
|
125737
126320
|
continue;
|
|
125738
126321
|
}
|
|
125739
|
-
if (!this.isEligibleForCleanup(sessionId)) {
|
|
126322
|
+
if (!await this.isEligibleForCleanup(sessionId)) {
|
|
125740
126323
|
continue;
|
|
125741
126324
|
}
|
|
125742
126325
|
try {
|
|
125743
126326
|
this.deps.logger.debug(`[GC] Evicting session ${sessionId} (idle ${Math.round(idleMs / 1e3)}s) due to memory pressure`);
|
|
125744
126327
|
await this.deps.cleanSession(sessionId);
|
|
125745
|
-
|
|
125746
|
-
|
|
126328
|
+
evictedSessionIds.push(sessionId);
|
|
126329
|
+
memorySnapshot = getMemoryPressureSnapshot();
|
|
126330
|
+
availableMemory = memorySnapshot.availableMemoryBytes;
|
|
126331
|
+
pressureReason = this.getPressureReason(memorySnapshot, thresholdBytes, commitThresholdBytes);
|
|
125747
126332
|
} catch (error2) {
|
|
125748
126333
|
this.deps.logger.error(`[GC] Failed to evict session ${sessionId}: ${formatErrorMessage(error2)}`);
|
|
125749
126334
|
}
|
|
125750
126335
|
}
|
|
125751
|
-
|
|
125752
|
-
|
|
126336
|
+
const stillUnderPressure = pressureReason !== null;
|
|
126337
|
+
if (evictedSessionIds.length > 0) {
|
|
126338
|
+
this.deps.logger.debug(`[GC] Memory pressure eviction complete: evicted ${evictedSessionIds.length} sessions, available memory now ${Math.round(availableMemory / 1024 / 1024)}MB`);
|
|
126339
|
+
} else if (stillUnderPressure) {
|
|
126340
|
+
this.deps.logger.debug("[GC] Memory pressure persists but no idle sessions were eligible for eviction");
|
|
126341
|
+
}
|
|
126342
|
+
return {
|
|
126343
|
+
availableMemoryBytes: availableMemory,
|
|
126344
|
+
thresholdBytes,
|
|
126345
|
+
hadMemoryPressure: true,
|
|
126346
|
+
stillUnderPressure,
|
|
126347
|
+
evictedSessionIds,
|
|
126348
|
+
pressureReason,
|
|
126349
|
+
...memorySnapshot.availableCommitBytes !== void 0 ? {
|
|
126350
|
+
availableCommitBytes: memorySnapshot.availableCommitBytes,
|
|
126351
|
+
commitThresholdBytes,
|
|
126352
|
+
commitLimitBytes: memorySnapshot.commitLimitBytes,
|
|
126353
|
+
committedBytes: memorySnapshot.committedBytes
|
|
126354
|
+
} : {}
|
|
126355
|
+
};
|
|
126356
|
+
}
|
|
126357
|
+
getPressureReason(snapshot, thresholdBytes, commitThresholdBytes) {
|
|
126358
|
+
const physicalPressure = snapshot.availableMemoryBytes < thresholdBytes;
|
|
126359
|
+
const commitPressure = snapshot.availableCommitBytes !== void 0 && snapshot.availableCommitBytes < commitThresholdBytes;
|
|
126360
|
+
if (physicalPressure && commitPressure) {
|
|
126361
|
+
return "physical_and_commit";
|
|
126362
|
+
}
|
|
126363
|
+
if (physicalPressure) {
|
|
126364
|
+
return "physical";
|
|
126365
|
+
}
|
|
126366
|
+
if (commitPressure) {
|
|
126367
|
+
return "commit";
|
|
125753
126368
|
}
|
|
126369
|
+
return null;
|
|
125754
126370
|
}
|
|
125755
|
-
getIdleCandidates() {
|
|
126371
|
+
async getIdleCandidates() {
|
|
125756
126372
|
const sessions = this.getSessionsWithIdleTime();
|
|
125757
126373
|
const candidates = [];
|
|
125758
126374
|
for (const session of sessions) {
|
|
125759
126375
|
if (session.idleMs < this.config.idleTimeoutMs) {
|
|
125760
126376
|
continue;
|
|
125761
126377
|
}
|
|
125762
|
-
if (!this.isEligibleForCleanup(session.sessionId)) {
|
|
126378
|
+
if (!await this.isEligibleForCleanup(session.sessionId)) {
|
|
125763
126379
|
continue;
|
|
125764
126380
|
}
|
|
125765
126381
|
candidates.push(session);
|
|
@@ -125787,20 +126403,23 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
125787
126403
|
}
|
|
125788
126404
|
return result;
|
|
125789
126405
|
}
|
|
125790
|
-
isEligibleForCleanup(sessionId) {
|
|
126406
|
+
async isEligibleForCleanup(sessionId) {
|
|
125791
126407
|
if (this.deps.hasActiveTurn(sessionId)) {
|
|
125792
126408
|
return false;
|
|
125793
126409
|
}
|
|
125794
126410
|
if (this.deps.hasPendingUpdates(sessionId)) {
|
|
125795
126411
|
return false;
|
|
125796
126412
|
}
|
|
126413
|
+
if (await this.deps.hasPendingUserWork(sessionId)) {
|
|
126414
|
+
return false;
|
|
126415
|
+
}
|
|
125797
126416
|
if (this.deps.isArchiveInFlight(sessionId)) {
|
|
125798
126417
|
return false;
|
|
125799
126418
|
}
|
|
125800
126419
|
return true;
|
|
125801
126420
|
}
|
|
125802
|
-
isStillEligibleForGC(sessionId) {
|
|
125803
|
-
if (!this.isEligibleForCleanup(sessionId)) {
|
|
126421
|
+
async isStillEligibleForGC(sessionId) {
|
|
126422
|
+
if (!await this.isEligibleForCleanup(sessionId)) {
|
|
125804
126423
|
return false;
|
|
125805
126424
|
}
|
|
125806
126425
|
const lastActivity = this.deps.getSessionLastActivity(sessionId);
|
|
@@ -125928,6 +126547,7 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
125928
126547
|
getSessionLastActivity: (sessionId) => handler.getLastActivity(sessionId),
|
|
125929
126548
|
hasActiveTurn: (sessionId) => handler.hasActiveTurn(sessionId),
|
|
125930
126549
|
hasPendingUpdates: (sessionId) => handler.hasPendingUpdates(sessionId),
|
|
126550
|
+
hasPendingUserWork: async (sessionId) => await handler.hasPendingUserWork(sessionId),
|
|
125931
126551
|
isArchiveInFlight: (sessionId) => handler.isArchiveInFlight(sessionId),
|
|
125932
126552
|
cleanSession: (sessionId) => handler.cleanSessionForGC(sessionId),
|
|
125933
126553
|
getSessionIds: () => handler.getTrackedSessionIds(),
|
|
@@ -125936,8 +126556,16 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
125936
126556
|
this.gcManager.start();
|
|
125937
126557
|
handler.setEvictForMemoryPressure(async (excludeSessionId) => {
|
|
125938
126558
|
if (this.gcManager) {
|
|
125939
|
-
await this.gcManager.evictForMemoryPressure(excludeSessionId);
|
|
126559
|
+
return await this.gcManager.evictForMemoryPressure(excludeSessionId);
|
|
125940
126560
|
}
|
|
126561
|
+
return {
|
|
126562
|
+
availableMemoryBytes: 0,
|
|
126563
|
+
thresholdBytes: 0,
|
|
126564
|
+
hadMemoryPressure: false,
|
|
126565
|
+
stillUnderPressure: false,
|
|
126566
|
+
evictedSessionIds: [],
|
|
126567
|
+
pressureReason: null
|
|
126568
|
+
};
|
|
125941
126569
|
});
|
|
125942
126570
|
}
|
|
125943
126571
|
requireSessionManager() {
|
|
@@ -133135,6 +133763,9 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
|
|
|
133135
133763
|
isControlPlaneReady() {
|
|
133136
133764
|
return this.documentManager.isTransportConnected();
|
|
133137
133765
|
}
|
|
133766
|
+
isControlPlaneRecovering() {
|
|
133767
|
+
return this.documentManager.isTransportRecovering();
|
|
133768
|
+
}
|
|
133138
133769
|
getActiveSessionCount() {
|
|
133139
133770
|
return this.runtime.getActiveSessionCount();
|
|
133140
133771
|
}
|
|
@@ -134964,23 +135595,26 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
|
|
|
134964
135595
|
refreshRuntimeState() {
|
|
134965
135596
|
const desiredCount = this.desiredWorkspaces.size;
|
|
134966
135597
|
let connectedCount = 0;
|
|
135598
|
+
let reconnectingCount = 0;
|
|
134967
135599
|
let totalActiveSessions = 0;
|
|
134968
135600
|
let totalConnectedRooms = 0;
|
|
134969
135601
|
for (const runtime of this.runtimes.values()) {
|
|
134970
135602
|
if (runtime.lody.isControlPlaneReady()) {
|
|
134971
135603
|
connectedCount += 1;
|
|
135604
|
+
} else if (runtime.lody.isControlPlaneRecovering()) {
|
|
135605
|
+
reconnectingCount += 1;
|
|
134972
135606
|
}
|
|
134973
135607
|
totalActiveSessions += runtime.lody.getActiveSessionCount();
|
|
134974
135608
|
totalConnectedRooms += runtime.lody.getConnectedRoomCount();
|
|
134975
135609
|
}
|
|
134976
135610
|
this.runtimeStateReporter.setActiveSessionCount(totalActiveSessions);
|
|
134977
135611
|
this.runtimeStateReporter.setConnectedRoomCount(totalConnectedRooms);
|
|
134978
|
-
const
|
|
135612
|
+
const hasWorkspaceRetry = this.retryTimers.size > 0 || this.startInFlight.size > 0;
|
|
135613
|
+
const nextConnectivity = desiredCount === 0 ? "online" : connectedCount === desiredCount ? "online" : reconnectingCount > 0 || hasWorkspaceRetry ? "reconnecting" : "offline";
|
|
134979
135614
|
if (this.lastConnectivity !== nextConnectivity) {
|
|
134980
135615
|
this.lastConnectivity = nextConnectivity;
|
|
134981
135616
|
this.runtimeStateReporter.setConnectivity(nextConnectivity);
|
|
134982
135617
|
}
|
|
134983
|
-
const hasWorkspaceRetry = this.retryTimers.size > 0 || this.startInFlight.size > 0;
|
|
134984
135618
|
if (hasWorkspaceRetry && !this.hasWorkspaceRetryIssue) {
|
|
134985
135619
|
this.hasWorkspaceRetryIssue = true;
|
|
134986
135620
|
this.runtimeStateReporter.upsertIssue({
|
|
@@ -167263,10 +167897,18 @@ ${entry2.text}`).join("\n\n");
|
|
|
167263
167897
|
}
|
|
167264
167898
|
const DEFAULT_MIN_RETRY_MS$1 = 1e3;
|
|
167265
167899
|
const DEFAULT_MAX_RETRY_MS$1 = 3e4;
|
|
167266
|
-
|
|
167900
|
+
const DEFAULT_JITTER_FRACTION = 0.2;
|
|
167901
|
+
function buildRetryDelay(attempt, minMs = DEFAULT_MIN_RETRY_MS$1, maxMs = DEFAULT_MAX_RETRY_MS$1, options = {}) {
|
|
167267
167902
|
const exponent = Math.max(0, attempt);
|
|
167268
|
-
const
|
|
167269
|
-
|
|
167903
|
+
const baseDelay = Math.min(minMs * 2 ** exponent, maxMs);
|
|
167904
|
+
const jitterFraction = Math.max(0, options.jitterFraction ?? DEFAULT_JITTER_FRACTION);
|
|
167905
|
+
if (jitterFraction === 0) {
|
|
167906
|
+
return baseDelay;
|
|
167907
|
+
}
|
|
167908
|
+
const random2 = options.random ?? Math.random;
|
|
167909
|
+
const randomValue = Math.min(1, Math.max(0, random2()));
|
|
167910
|
+
const jitterMultiplier = 1 + (randomValue * 2 - 1) * jitterFraction;
|
|
167911
|
+
return Math.min(Math.max(0, Math.round(baseDelay * jitterMultiplier)), maxMs);
|
|
167270
167912
|
}
|
|
167271
167913
|
class FailureWindow {
|
|
167272
167914
|
historyMs = [];
|
|
@@ -167327,6 +167969,7 @@ ${result.stderr}`;
|
|
|
167327
167969
|
probeFailureThreshold;
|
|
167328
167970
|
minRetryMs;
|
|
167329
167971
|
maxRetryMs;
|
|
167972
|
+
retryRandom;
|
|
167330
167973
|
failureWindow;
|
|
167331
167974
|
triggered = false;
|
|
167332
167975
|
inFlight = false;
|
|
@@ -167358,6 +168001,7 @@ ${result.stderr}`;
|
|
|
167358
168001
|
this.probeFailureThreshold = options.probeFailureThreshold ?? DEFAULT_PROBE_FAILURE_THRESHOLD;
|
|
167359
168002
|
this.minRetryMs = options.minRetryMs ?? DEFAULT_MIN_RETRY_MS;
|
|
167360
168003
|
this.maxRetryMs = options.maxRetryMs ?? DEFAULT_MAX_RETRY_MS;
|
|
168004
|
+
this.retryRandom = options.retryRandom ?? Math.random;
|
|
167361
168005
|
this.failureWindow = new FailureWindow(options.fatalFailureWindowMs ?? DEFAULT_FATAL_FAILURE_WINDOW_MS, options.fatalFailureThreshold ?? DEFAULT_FATAL_FAILURE_THRESHOLD);
|
|
167362
168006
|
}
|
|
167363
168007
|
getState() {
|
|
@@ -167437,6 +168081,9 @@ ${result.stderr}`;
|
|
|
167437
168081
|
this.latestRuntimeState = runtimeState;
|
|
167438
168082
|
this.probeFailureCount = 0;
|
|
167439
168083
|
this.probeUnavailable = false;
|
|
168084
|
+
this.clearRetryTimer();
|
|
168085
|
+
this.attempt = 0;
|
|
168086
|
+
this.failureWindow.reset();
|
|
167440
168087
|
this.lastStateMessage = void 0;
|
|
167441
168088
|
} else {
|
|
167442
168089
|
this.probeFailureCount += 1;
|
|
@@ -167457,7 +168104,9 @@ ${result.stderr}`;
|
|
|
167457
168104
|
} else if (this.fatalReason) {
|
|
167458
168105
|
phase = "fatal";
|
|
167459
168106
|
} else if (runtime) {
|
|
167460
|
-
phase = runtime.phase === "fatal" ? "fatal" : runtime.phase;
|
|
168107
|
+
phase = runtime.phase === "fatal" ? "fatal" : runtime.connectivity === "reconnecting" ? "reconnecting" : runtime.phase;
|
|
168108
|
+
} else if (this.retryTimer) {
|
|
168109
|
+
phase = "reconnecting";
|
|
167461
168110
|
} else if (childRunning) {
|
|
167462
168111
|
phase = this.probeUnavailable ? "offline" : "starting";
|
|
167463
168112
|
} else if (this.probeUnavailable && !this.inFlight && !this.retryTimer) {
|
|
@@ -167514,7 +168163,9 @@ ${result.stderr}`;
|
|
|
167514
168163
|
scheduleRetry(reason, recordFailure = true) {
|
|
167515
168164
|
if (this.fatalReason) return;
|
|
167516
168165
|
if (recordFailure && this.recordFailure(reason)) return;
|
|
167517
|
-
const delay2 = buildRetryDelay(this.attempt, this.minRetryMs, this.maxRetryMs
|
|
168166
|
+
const delay2 = buildRetryDelay(this.attempt, this.minRetryMs, this.maxRetryMs, {
|
|
168167
|
+
random: this.retryRandom
|
|
168168
|
+
});
|
|
167518
168169
|
this.attempt += 1;
|
|
167519
168170
|
this.clearRetryTimer();
|
|
167520
168171
|
this.pendingRetryInMs = delay2;
|