lody 0.72.0 → 0.73.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/chunks/{directory-handle-D57OdGy1.js → directory-handle-QE16b3Ol.js} +10 -10
- package/dist/chunks/{index-Cbwwpq6s.js → index-yJXq3BkB.js} +12 -1
- package/dist/chunks/{review-viewer-CkoswOhv.js → review-viewer-BbX0BXrV.js} +1 -1
- package/dist/codex-acp.js +1 -1
- package/dist/file-index-scan-worker.js +1 -1
- package/dist/index.js +832 -409
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -58,7 +58,7 @@ import require$$1$5 from "node:worker_threads";
|
|
|
58
58
|
import require$$1$7, { fileURLToPath } from "node:url";
|
|
59
59
|
import require$$1$8 from "node:console";
|
|
60
60
|
import require$$1$9 from "node:dns";
|
|
61
|
-
import { C as CodeCollabV2OpenTextRequestSchema, a as CodeCollabV2RefreshTextRequestSchema, b as CodeCollabV2SaveTextRequestSchema, c as CodeCollabV2OpenCurrentDiffRequestSchema, d as CodeCollabV2OpenAllChangesDiffRequestSchema, e as CodeCollabV2OpenTurnDiffRequestSchema, f as CodeCollabV2InitDirectoryRequestSchema, g as CodeCollabV2OpenTextOkSchema, h as CodeCollabV2RefreshTextResponseSchema, i as CodeCollabV2SaveTextResponseSchema, j as CodeCollabV2OpenCurrentDiffResponseSchema, k as CodeCollabV2OpenAllChangesDiffResponseSchema, l as CodeCollabV2OpenTurnDiffResponseSchema, m as CodeCollabV2InitDirectoryOkSchema, n as CodeCollabV2LspUnsupportedSchema, o as CodeCollabV2ErrorSchema, p as CodeCollabV2RpcContentEnvelopeSchema, q as deriveCodeCollabV2ContentKeyId, r as CodeCollabV2ErrorCodeSchema, s as CodeCollabV2RpcResponseSchema, t as deriveCodeCollabV2ContentKeyBytes, u as
|
|
61
|
+
import { C as CodeCollabV2OpenTextRequestSchema, a as CodeCollabV2RefreshTextRequestSchema, b as CodeCollabV2SaveTextRequestSchema, c as CodeCollabV2OpenCurrentDiffRequestSchema, d as CodeCollabV2OpenAllChangesDiffRequestSchema, e as CodeCollabV2OpenTurnDiffRequestSchema, f as CodeCollabV2InitDirectoryRequestSchema, g as CodeCollabV2OpenTextOkSchema, h as CodeCollabV2RefreshTextResponseSchema, i as CodeCollabV2SaveTextResponseSchema, j as CodeCollabV2OpenCurrentDiffResponseSchema, k as CodeCollabV2OpenAllChangesDiffResponseSchema, l as CodeCollabV2OpenTurnDiffResponseSchema, m as CodeCollabV2InitDirectoryOkSchema, n as CodeCollabV2LspUnsupportedSchema, o as CodeCollabV2ErrorSchema, p as CodeCollabV2RpcContentEnvelopeSchema, q as deriveCodeCollabV2ContentKeyId, r as CodeCollabV2ErrorCodeSchema, s as CodeCollabV2RpcResponseSchema, t as deriveCodeCollabV2ContentKeyBytes, u as readCodeCollabFileIndexSignalFromFlock, w as writeCodeCollabFileIndexSignalToFlock, v as writeCodeCollabFileIndexToFlock, x as buildCodeCollabFileIndexState, y as CODE_COLLAB_V2_TEXT_LIMITS, z as CODE_COLLAB_V2_ALL_CHANGES_DIFF_LIMITS, A as computeAllChanges, B as codeCollabFileIndexStatesEqual, D as scanGitDirectoryEntries, E as closeDirectoryQuietly } from "./chunks/directory-handle-QE16b3Ol.js";
|
|
62
62
|
import * as fs$2 from "node:fs/promises";
|
|
63
63
|
import fs__default$3, { mkdir as mkdir$1, mkdtemp, chmod, writeFile, rm, rename, stat, lstat, symlink, readdir, unlink, readFile, open, realpath, opendir } from "node:fs/promises";
|
|
64
64
|
import { pipeline } from "node:stream/promises";
|
|
@@ -4190,7 +4190,7 @@ let __tla = Promise.all([
|
|
|
4190
4190
|
}
|
|
4191
4191
|
}
|
|
4192
4192
|
const name$1 = "lody";
|
|
4193
|
-
const version$7 = "0.
|
|
4193
|
+
const version$7 = "0.73.0";
|
|
4194
4194
|
const description$1 = "Lody Agent CLI tool for managing remote command execution";
|
|
4195
4195
|
const type$2 = "module";
|
|
4196
4196
|
const main$4 = "dist/index.js";
|
|
@@ -4617,22 +4617,22 @@ let __tla = Promise.all([
|
|
|
4617
4617
|
const CODEX_REASONING_OPTIONS = [
|
|
4618
4618
|
{
|
|
4619
4619
|
value: "low",
|
|
4620
|
-
name: "
|
|
4620
|
+
name: "Low",
|
|
4621
4621
|
description: "Fastest responses"
|
|
4622
4622
|
},
|
|
4623
4623
|
{
|
|
4624
4624
|
value: "medium",
|
|
4625
|
-
name: "
|
|
4625
|
+
name: "Medium",
|
|
4626
4626
|
description: "Balanced reasoning"
|
|
4627
4627
|
},
|
|
4628
4628
|
{
|
|
4629
4629
|
value: "high",
|
|
4630
|
-
name: "
|
|
4630
|
+
name: "High",
|
|
4631
4631
|
description: "More reasoning for difficult tasks"
|
|
4632
4632
|
},
|
|
4633
4633
|
{
|
|
4634
4634
|
value: "xhigh",
|
|
4635
|
-
name: "
|
|
4635
|
+
name: "XHigh",
|
|
4636
4636
|
description: "Extra reasoning for complex tasks"
|
|
4637
4637
|
}
|
|
4638
4638
|
];
|
|
@@ -74726,6 +74726,7 @@ ${val.stack}`;
|
|
|
74726
74726
|
return new Promise((resolve2) => setTimeout(resolve2, 0));
|
|
74727
74727
|
}
|
|
74728
74728
|
const YIELD_BUDGET_MS = 5;
|
|
74729
|
+
const DEFAULT_COMPACTION_BYTE_THRESHOLD = 100 * 1024;
|
|
74729
74730
|
const nowMs = typeof globalThis.performance?.now === "function" ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
|
|
74730
74731
|
function stableVersion(vector) {
|
|
74731
74732
|
const sorted = {};
|
|
@@ -74752,6 +74753,7 @@ ${val.stack}`;
|
|
|
74752
74753
|
ensureFlockDocExists;
|
|
74753
74754
|
retentionMs;
|
|
74754
74755
|
persistDebounceMs;
|
|
74756
|
+
compactionByteThreshold;
|
|
74755
74757
|
flocks = /* @__PURE__ */ new Map();
|
|
74756
74758
|
subscriptions = /* @__PURE__ */ new Map();
|
|
74757
74759
|
persistedVersions = /* @__PURE__ */ new Map();
|
|
@@ -74759,6 +74761,8 @@ ${val.stack}`;
|
|
|
74759
74761
|
dirtyFlockDocs = /* @__PURE__ */ new Set();
|
|
74760
74762
|
existenceMaterialized = /* @__PURE__ */ new Set();
|
|
74761
74763
|
docsWithVisibleContent = /* @__PURE__ */ new Set();
|
|
74764
|
+
updateBytesSinceCompaction = /* @__PURE__ */ new Map();
|
|
74765
|
+
queuedCompactions = /* @__PURE__ */ new Set();
|
|
74762
74766
|
debounceTimer;
|
|
74763
74767
|
flushScheduled = false;
|
|
74764
74768
|
operationPromise = Promise.resolve();
|
|
@@ -74769,6 +74773,8 @@ ${val.stack}`;
|
|
|
74769
74773
|
this.retentionMs = options.retentionMs;
|
|
74770
74774
|
const configured = options.persistDebounceMs;
|
|
74771
74775
|
this.persistDebounceMs = typeof configured === "number" && Number.isFinite(configured) && configured >= 0 ? configured : 0;
|
|
74776
|
+
const configuredCompactionThreshold = options.compactionByteThreshold;
|
|
74777
|
+
this.compactionByteThreshold = typeof configuredCompactionThreshold === "number" && Number.isFinite(configuredCompactionThreshold) && configuredCompactionThreshold >= 0 ? Math.floor(configuredCompactionThreshold) : DEFAULT_COMPACTION_BYTE_THRESHOLD;
|
|
74772
74778
|
}
|
|
74773
74779
|
async openFlockDoc(flockDocId) {
|
|
74774
74780
|
return this.ensureFlockDoc(flockDocId);
|
|
@@ -74822,16 +74828,26 @@ ${val.stack}`;
|
|
|
74822
74828
|
throw error2;
|
|
74823
74829
|
}
|
|
74824
74830
|
}
|
|
74831
|
+
async persistFlockDocChanges(flockDocId, flock) {
|
|
74832
|
+
await this.enqueueOperation(async () => {
|
|
74833
|
+
if (this.flocks.get(flockDocId) !== flock) return;
|
|
74834
|
+
await this.persistFlockDocUpdate(flockDocId, flock);
|
|
74835
|
+
});
|
|
74836
|
+
}
|
|
74825
74837
|
async unloadFlockDoc(flockDocId) {
|
|
74826
|
-
|
|
74827
|
-
|
|
74828
|
-
|
|
74829
|
-
|
|
74830
|
-
|
|
74831
|
-
|
|
74832
|
-
|
|
74833
|
-
|
|
74834
|
-
|
|
74838
|
+
await this.enqueueOperation(async () => {
|
|
74839
|
+
const flock = this.flocks.get(flockDocId);
|
|
74840
|
+
if (!flock) return;
|
|
74841
|
+
await this.persistFlockDocUpdate(flockDocId, flock);
|
|
74842
|
+
this.subscriptions.get(flockDocId)?.();
|
|
74843
|
+
this.subscriptions.delete(flockDocId);
|
|
74844
|
+
this.flocks.delete(flockDocId);
|
|
74845
|
+
this.persistedVersions.delete(flockDocId);
|
|
74846
|
+
this.dirtyFlockDocs.delete(flockDocId);
|
|
74847
|
+
this.existenceMaterialized.delete(flockDocId);
|
|
74848
|
+
this.docsWithVisibleContent.delete(flockDocId);
|
|
74849
|
+
this.updateBytesSinceCompaction.delete(flockDocId);
|
|
74850
|
+
});
|
|
74835
74851
|
}
|
|
74836
74852
|
async dropFlockDoc(flockDocId) {
|
|
74837
74853
|
await this.enqueueOperation(async () => {
|
|
@@ -74842,6 +74858,7 @@ ${val.stack}`;
|
|
|
74842
74858
|
this.dirtyFlockDocs.delete(flockDocId);
|
|
74843
74859
|
this.existenceMaterialized.delete(flockDocId);
|
|
74844
74860
|
this.docsWithVisibleContent.delete(flockDocId);
|
|
74861
|
+
this.updateBytesSinceCompaction.delete(flockDocId);
|
|
74845
74862
|
await this.storage?.deleteFlockDoc?.(flockDocId);
|
|
74846
74863
|
});
|
|
74847
74864
|
}
|
|
@@ -74850,6 +74867,7 @@ ${val.stack}`;
|
|
|
74850
74867
|
this.flushScheduled = false;
|
|
74851
74868
|
while (true) {
|
|
74852
74869
|
await this.enqueueOperation(() => this.drainDirtyFlockDocs());
|
|
74870
|
+
await this.operationPromise;
|
|
74853
74871
|
if (this.dirtyFlockDocs.size === 0 && !this.flushScheduled) break;
|
|
74854
74872
|
this.flushScheduled = false;
|
|
74855
74873
|
}
|
|
@@ -74871,6 +74889,8 @@ ${val.stack}`;
|
|
|
74871
74889
|
this.dirtyFlockDocs.clear();
|
|
74872
74890
|
this.existenceMaterialized.clear();
|
|
74873
74891
|
this.docsWithVisibleContent.clear();
|
|
74892
|
+
this.updateBytesSinceCompaction.clear();
|
|
74893
|
+
this.queuedCompactions.clear();
|
|
74874
74894
|
}
|
|
74875
74895
|
}
|
|
74876
74896
|
async loadOrCreateFlockDoc(flockDocId) {
|
|
@@ -74919,22 +74939,23 @@ ${val.stack}`;
|
|
|
74919
74939
|
}
|
|
74920
74940
|
if (versionEquals(previousVersion, nextVersion)) {
|
|
74921
74941
|
await this.materializeExistence(flockDocId, flock);
|
|
74922
|
-
this.
|
|
74942
|
+
this.clearDirtyIfStable(flockDocId, nextVersion, flock);
|
|
74923
74943
|
return;
|
|
74924
74944
|
}
|
|
74925
74945
|
const bundle = await exportJsonUpdate(flock, previousVersion);
|
|
74926
74946
|
if (!hasBundleEntries(bundle)) {
|
|
74927
74947
|
await this.materializeExistence(flockDocId, flock);
|
|
74928
74948
|
this.persistedVersions.set(flockDocId, nextVersion);
|
|
74929
|
-
this.
|
|
74949
|
+
this.clearDirtyIfStable(flockDocId, nextVersion, flock);
|
|
74930
74950
|
return;
|
|
74931
74951
|
}
|
|
74952
|
+
const update2 = encodeMetaJsonUpdate(bundle);
|
|
74932
74953
|
this.persistedVersions.set(flockDocId, nextVersion);
|
|
74933
74954
|
try {
|
|
74934
74955
|
await this.storage.save({
|
|
74935
74956
|
type: "flock-doc-update",
|
|
74936
74957
|
flockDocId,
|
|
74937
|
-
update:
|
|
74958
|
+
update: update2
|
|
74938
74959
|
});
|
|
74939
74960
|
await this.materializeExistence(flockDocId, flock);
|
|
74940
74961
|
this.clearDirtyIfStable(flockDocId, nextVersion, flock);
|
|
@@ -74942,6 +74963,31 @@ ${val.stack}`;
|
|
|
74942
74963
|
this.persistedVersions.set(flockDocId, previousVersion);
|
|
74943
74964
|
throw error2;
|
|
74944
74965
|
}
|
|
74966
|
+
this.scheduleFlockDocCompaction(flockDocId, flock, update2.byteLength);
|
|
74967
|
+
}
|
|
74968
|
+
scheduleFlockDocCompaction(flockDocId, flock, updateBytes) {
|
|
74969
|
+
if (this.compactionByteThreshold === 0 || !this.storage?.compactFlockDoc) return;
|
|
74970
|
+
const total = (this.updateBytesSinceCompaction.get(flockDocId) ?? 0) + updateBytes;
|
|
74971
|
+
this.updateBytesSinceCompaction.set(flockDocId, total);
|
|
74972
|
+
if (total < this.compactionByteThreshold) return;
|
|
74973
|
+
this.queueFlockDocCompaction(flockDocId, flock);
|
|
74974
|
+
}
|
|
74975
|
+
queueFlockDocCompaction(flockDocId, flock) {
|
|
74976
|
+
if (this.queuedCompactions.has(flockDocId)) return;
|
|
74977
|
+
const storage = this.storage;
|
|
74978
|
+
if (!storage?.compactFlockDoc) return;
|
|
74979
|
+
this.queuedCompactions.add(flockDocId);
|
|
74980
|
+
this.enqueueOperation(async () => {
|
|
74981
|
+
try {
|
|
74982
|
+
await yieldToMacroTask();
|
|
74983
|
+
if (this.flocks.get(flockDocId) !== flock) return;
|
|
74984
|
+
await storage.compactFlockDoc?.(flockDocId);
|
|
74985
|
+
if (this.flocks.has(flockDocId)) this.updateBytesSinceCompaction.set(flockDocId, 0);
|
|
74986
|
+
else this.updateBytesSinceCompaction.delete(flockDocId);
|
|
74987
|
+
} finally {
|
|
74988
|
+
this.queuedCompactions.delete(flockDocId);
|
|
74989
|
+
}
|
|
74990
|
+
}).catch(logAsyncError(`Flock document "${flockDocId}" compaction`));
|
|
74945
74991
|
}
|
|
74946
74992
|
async materializeExistence(flockDocId, flock) {
|
|
74947
74993
|
if (this.existenceMaterialized.has(flockDocId)) return;
|
|
@@ -75940,7 +75986,7 @@ ${val.stack}`;
|
|
|
75940
75986
|
} finally {
|
|
75941
75987
|
this.eventBus.popEventBy();
|
|
75942
75988
|
}
|
|
75943
|
-
await this.flockDocManager.
|
|
75989
|
+
await this.flockDocManager.persistFlockDocChanges(flockDocId, flock);
|
|
75944
75990
|
}
|
|
75945
75991
|
}
|
|
75946
75992
|
async buildSyncPlan(options, buildOptions = {}) {
|
|
@@ -76423,7 +76469,8 @@ ${val.stack}`;
|
|
|
76423
76469
|
storage: this.storage,
|
|
76424
76470
|
ensureFlockDocExists: (flockDocId, expiresAtMs) => this.flockDocRegistry.ensureExists(flockDocId, expiresAtMs),
|
|
76425
76471
|
retentionMs: getFlockDocRetentionMs,
|
|
76426
|
-
persistDebounceMs: options.docPersistDebounceMs
|
|
76472
|
+
persistDebounceMs: options.docPersistDebounceMs,
|
|
76473
|
+
compactionByteThreshold: options.flockDocCompactionByteThreshold
|
|
76427
76474
|
});
|
|
76428
76475
|
if (this.storage) this.metaPersister = new MetaPersister({
|
|
76429
76476
|
getMetaFlock,
|
|
@@ -85100,6 +85147,7 @@ stream:${scope2.streamId}`;
|
|
|
85100
85147
|
return {
|
|
85101
85148
|
save: (payload) => this.saveStorage(payload),
|
|
85102
85149
|
compactMeta: () => this.compactMeta(),
|
|
85150
|
+
compactFlockDoc: (flockDocId) => this.compactFlockDoc(flockDocId),
|
|
85103
85151
|
loadDoc: (docId) => this.loadDoc(docId),
|
|
85104
85152
|
loadFlockDoc: (flockDocId) => this.loadFlockDoc(flockDocId),
|
|
85105
85153
|
deleteDoc: (docId) => this.deleteDoc(docId),
|
|
@@ -85148,6 +85196,9 @@ stream:${scope2.streamId}`;
|
|
|
85148
85196
|
if (state2.needsCompaction) this.finalizeFlockDocCompaction(flockDocId, state2, await exportFlockFileSnapshot(state2.flock));
|
|
85149
85197
|
return state2.flock;
|
|
85150
85198
|
}
|
|
85199
|
+
async compactFlockDoc(flockDocId) {
|
|
85200
|
+
await this.loadFlockDoc(flockDocId);
|
|
85201
|
+
}
|
|
85151
85202
|
loadDocBody(docId) {
|
|
85152
85203
|
const snapshotRow = this.stmts.selectDocSnapshot.get(docId);
|
|
85153
85204
|
const updateRows = this.stmts.selectDocUpdates.all(docId);
|
|
@@ -107450,16 +107501,23 @@ ${value}`;
|
|
|
107450
107501
|
},
|
|
107451
107502
|
readJsonLive: async (streamId, state2, onBatch, liveOptions) => {
|
|
107452
107503
|
const { client: streamClient, context: context2 } = getStreamClient(streamId, "live");
|
|
107504
|
+
const modePolicy = liveOptions?.modePolicy;
|
|
107505
|
+
const requestedMode = modePolicy?.selectRequestMode() ?? options.liveMode ?? "sse";
|
|
107506
|
+
let observedTransport;
|
|
107507
|
+
let deliveredBatch = false;
|
|
107508
|
+
let readError;
|
|
107453
107509
|
const watchdog = createLiveIdleWatchdog(options.liveIdleTimeoutMs ?? DEFAULT_LIVE_IDLE_TIMEOUT_MS, liveOptions?.signal);
|
|
107454
107510
|
try {
|
|
107455
107511
|
for await (const event of streamClient.live({
|
|
107456
107512
|
offset: state2.nextOffset ?? "now",
|
|
107457
|
-
mode:
|
|
107513
|
+
mode: requestedMode,
|
|
107458
107514
|
signal: watchdog.signal
|
|
107459
107515
|
})) {
|
|
107460
107516
|
watchdog.reset();
|
|
107517
|
+
observedTransport = event.mode;
|
|
107461
107518
|
switch (event.type) {
|
|
107462
107519
|
case "data":
|
|
107520
|
+
deliveredBatch = true;
|
|
107463
107521
|
state2.nextOffset = event.nextOffset;
|
|
107464
107522
|
state2.cursor = event.cursor;
|
|
107465
107523
|
await onBatch({
|
|
@@ -107470,6 +107528,7 @@ ${value}`;
|
|
|
107470
107528
|
});
|
|
107471
107529
|
break;
|
|
107472
107530
|
case "up_to_date": {
|
|
107531
|
+
deliveredBatch = true;
|
|
107473
107532
|
state2.nextOffset = event.nextOffset;
|
|
107474
107533
|
state2.cursor = event.cursor;
|
|
107475
107534
|
await onBatch({
|
|
@@ -107481,6 +107540,7 @@ ${value}`;
|
|
|
107481
107540
|
break;
|
|
107482
107541
|
}
|
|
107483
107542
|
case "eof": {
|
|
107543
|
+
deliveredBatch = true;
|
|
107484
107544
|
state2.nextOffset = event.nextOffset;
|
|
107485
107545
|
state2.cursor = void 0;
|
|
107486
107546
|
await onBatch({
|
|
@@ -107498,8 +107558,19 @@ ${value}`;
|
|
|
107498
107558
|
throw toLoroGatewayError("read live updates from", streamId, event.error, context2);
|
|
107499
107559
|
}
|
|
107500
107560
|
}
|
|
107561
|
+
} catch (error2) {
|
|
107562
|
+
readError = error2;
|
|
107563
|
+
throw error2;
|
|
107501
107564
|
} finally {
|
|
107502
107565
|
watchdog.dispose();
|
|
107566
|
+
if (modePolicy && liveOptions?.signal?.aborted !== true) {
|
|
107567
|
+
modePolicy.noteReadOutcome({
|
|
107568
|
+
requestedMode,
|
|
107569
|
+
observedTransport,
|
|
107570
|
+
deliveredBatch,
|
|
107571
|
+
error: readError
|
|
107572
|
+
});
|
|
107573
|
+
}
|
|
107503
107574
|
}
|
|
107504
107575
|
}
|
|
107505
107576
|
};
|
|
@@ -107962,6 +108033,8 @@ ${value}`;
|
|
|
107962
108033
|
constructor(options) {
|
|
107963
108034
|
this.options = options;
|
|
107964
108035
|
this.responseStreamId = options.responseStreamId ?? `${getLoroWorkspaceRpcResponseStreamId(options.workspaceId)}:${crypto.randomUUID()}`;
|
|
108036
|
+
this.liveModePolicy = options.liveModePolicy;
|
|
108037
|
+
this.lastReportedTransport = options.liveModePolicy?.getDiagnostics().transport;
|
|
107965
108038
|
}
|
|
107966
108039
|
responseStreamId;
|
|
107967
108040
|
responseState = {
|
|
@@ -107969,8 +108042,14 @@ ${value}`;
|
|
|
107969
108042
|
};
|
|
107970
108043
|
pending = /* @__PURE__ */ new Map();
|
|
107971
108044
|
stopController = new AbortController();
|
|
108045
|
+
liveModePolicy;
|
|
108046
|
+
currentReadController = null;
|
|
108047
|
+
lastReportedTransport;
|
|
107972
108048
|
startedPromise = null;
|
|
107973
108049
|
stopped = false;
|
|
108050
|
+
getLiveModeDiagnostics() {
|
|
108051
|
+
return this.liveModePolicy?.getDiagnostics() ?? null;
|
|
108052
|
+
}
|
|
107974
108053
|
getResponseStreamId() {
|
|
107975
108054
|
return this.responseStreamId;
|
|
107976
108055
|
}
|
|
@@ -107987,6 +108066,7 @@ ${value}`;
|
|
|
107987
108066
|
return new Promise((resolve2) => {
|
|
107988
108067
|
const timeoutId = setTimeout(() => {
|
|
107989
108068
|
this.pending.delete(requestId);
|
|
108069
|
+
this.noteLiveModeResponseTimeout();
|
|
107990
108070
|
resolve2(null);
|
|
107991
108071
|
}, registration.timeoutMs);
|
|
107992
108072
|
this.pending.set(requestId, {
|
|
@@ -108033,8 +108113,39 @@ ${value}`;
|
|
|
108033
108113
|
this.pending.delete(requestId);
|
|
108034
108114
|
}
|
|
108035
108115
|
}
|
|
108116
|
+
noteLiveModeResponseTimeout() {
|
|
108117
|
+
if (!this.liveModePolicy) {
|
|
108118
|
+
return;
|
|
108119
|
+
}
|
|
108120
|
+
this.liveModePolicy.noteResponseTimeout();
|
|
108121
|
+
if (!this.reportLiveModeChange()) {
|
|
108122
|
+
return;
|
|
108123
|
+
}
|
|
108124
|
+
this.currentReadController?.abort();
|
|
108125
|
+
}
|
|
108126
|
+
reportLiveModeChange() {
|
|
108127
|
+
const diagnostics2 = this.liveModePolicy?.getDiagnostics();
|
|
108128
|
+
if (!diagnostics2 || diagnostics2.transport === this.lastReportedTransport) {
|
|
108129
|
+
return false;
|
|
108130
|
+
}
|
|
108131
|
+
this.lastReportedTransport = diagnostics2.transport;
|
|
108132
|
+
const label2 = this.options.logLabel ?? this.options.workspaceId;
|
|
108133
|
+
this.options.trace?.("machine rpc live transport changed", {
|
|
108134
|
+
workspaceId: this.options.workspaceId,
|
|
108135
|
+
responseStreamId: this.responseStreamId,
|
|
108136
|
+
...diagnostics2
|
|
108137
|
+
});
|
|
108138
|
+
this.options.logger?.warn?.(`[rpc-client:${label2}] live transport -> ${diagnostics2.transport} (${diagnostics2.reason})`);
|
|
108139
|
+
return true;
|
|
108140
|
+
}
|
|
108036
108141
|
async runResponseLoop() {
|
|
108037
108142
|
while (!this.stopped) {
|
|
108143
|
+
const readController = new AbortController();
|
|
108144
|
+
const onStop = () => readController.abort();
|
|
108145
|
+
this.stopController.signal.addEventListener("abort", onStop, {
|
|
108146
|
+
once: true
|
|
108147
|
+
});
|
|
108148
|
+
this.currentReadController = readController;
|
|
108038
108149
|
try {
|
|
108039
108150
|
await this.options.streamClient.readJsonLive(this.responseStreamId, this.responseState, async (batch) => {
|
|
108040
108151
|
this.responseState.nextOffset = batch.nextOffset ?? this.responseState.nextOffset;
|
|
@@ -108043,7 +108154,8 @@ ${value}`;
|
|
|
108043
108154
|
await this.handleRawResponse(raw2);
|
|
108044
108155
|
}
|
|
108045
108156
|
}, {
|
|
108046
|
-
signal:
|
|
108157
|
+
signal: readController.signal,
|
|
108158
|
+
modePolicy: this.liveModePolicy
|
|
108047
108159
|
});
|
|
108048
108160
|
} catch (error2) {
|
|
108049
108161
|
if (this.stopped) {
|
|
@@ -108065,6 +108177,10 @@ ${value}`;
|
|
|
108065
108177
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
108066
108178
|
this.options.logger?.warn?.(`[rpc-client:${this.options.logLabel ?? this.options.workspaceId}] response loop error: ${message}`);
|
|
108067
108179
|
await delay$4(1e3);
|
|
108180
|
+
} finally {
|
|
108181
|
+
this.stopController.signal.removeEventListener("abort", onStop);
|
|
108182
|
+
this.currentReadController = null;
|
|
108183
|
+
this.reportLiveModeChange();
|
|
108068
108184
|
}
|
|
108069
108185
|
}
|
|
108070
108186
|
}
|
|
@@ -108078,6 +108194,7 @@ ${value}`;
|
|
|
108078
108194
|
if (!pending2) {
|
|
108079
108195
|
return;
|
|
108080
108196
|
}
|
|
108197
|
+
this.liveModePolicy?.noteResponseReceived();
|
|
108081
108198
|
const receivedAtMs = Date.now();
|
|
108082
108199
|
this.options.trace?.("machine rpc transport response received", {
|
|
108083
108200
|
workspaceId: this.options.workspaceId,
|
|
@@ -109840,6 +109957,50 @@ ${value}`;
|
|
|
109840
109957
|
}
|
|
109841
109958
|
}
|
|
109842
109959
|
}
|
|
109960
|
+
class CodeCollabFileIndexChangedPublishError extends Error {
|
|
109961
|
+
constructor(cause) {
|
|
109962
|
+
super("Code Collab file-index publication failed after changing local Flock state.", {
|
|
109963
|
+
cause
|
|
109964
|
+
});
|
|
109965
|
+
this.name = "CodeCollabFileIndexChangedPublishError";
|
|
109966
|
+
}
|
|
109967
|
+
}
|
|
109968
|
+
async function publishCodeCollabFileIndexFlock(args2) {
|
|
109969
|
+
const handle = await args2.repo.openFlockDoc(args2.flockDocId);
|
|
109970
|
+
if (args2.reconcileRemote) {
|
|
109971
|
+
await handle.syncOnce();
|
|
109972
|
+
}
|
|
109973
|
+
const changed = writeCodeCollabFileIndexToFlock(handle.flock, args2.fileIndex, args2.updatedAtMs);
|
|
109974
|
+
if (!changed) {
|
|
109975
|
+
return {
|
|
109976
|
+
changed: false
|
|
109977
|
+
};
|
|
109978
|
+
}
|
|
109979
|
+
try {
|
|
109980
|
+
await args2.repo.flush();
|
|
109981
|
+
await handle.syncOnce();
|
|
109982
|
+
} catch (error2) {
|
|
109983
|
+
throw new CodeCollabFileIndexChangedPublishError(error2);
|
|
109984
|
+
}
|
|
109985
|
+
return {
|
|
109986
|
+
changed: true
|
|
109987
|
+
};
|
|
109988
|
+
}
|
|
109989
|
+
async function publishCodeCollabFileIndexSignalFlock(args2) {
|
|
109990
|
+
const handle = await args2.repo.openFlockDoc(args2.flockDocId);
|
|
109991
|
+
await handle.syncOnce();
|
|
109992
|
+
const previous = readCodeCollabFileIndexSignalFromFlock(handle.flock);
|
|
109993
|
+
const revision = (previous?.r ?? 0) + 1;
|
|
109994
|
+
const changed = writeCodeCollabFileIndexSignalToFlock(handle.flock, revision, args2.updatedAtMs);
|
|
109995
|
+
if (changed) {
|
|
109996
|
+
await args2.repo.flush();
|
|
109997
|
+
await handle.syncOnce();
|
|
109998
|
+
}
|
|
109999
|
+
return {
|
|
110000
|
+
changed,
|
|
110001
|
+
revision
|
|
110002
|
+
};
|
|
110003
|
+
}
|
|
109843
110004
|
const WorkspaceSummarySchema = object$1({
|
|
109844
110005
|
id: string$1(),
|
|
109845
110006
|
name: string$1(),
|
|
@@ -135149,6 +135310,30 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
135149
135310
|
const localProjects = await readMachineLocalProjects(repo, workspaceId, machineId);
|
|
135150
135311
|
return localProjects[localProjectId] ?? null;
|
|
135151
135312
|
}
|
|
135313
|
+
const defaultSleep$1 = (ms2) => new Promise((resolve2) => setTimeout(resolve2, ms2));
|
|
135314
|
+
async function resolveWorkspaceLocalProjectRootPathWithRetry(repo, workspaceId, machineId, localProjectId, options = {}) {
|
|
135315
|
+
const maxAttempts = Math.max(1, options.maxAttempts ?? readTimeoutEnv("LODY_LOCAL_PROJECT_RESOLVE_MAX_ATTEMPTS", 4));
|
|
135316
|
+
const retryDelayMs = options.retryDelayMs ?? readTimeoutEnv("LODY_LOCAL_PROJECT_RESOLVE_RETRY_DELAY_MS", 400);
|
|
135317
|
+
const syncTimeoutMs = options.syncTimeoutMs ?? readTimeoutEnv("LODY_LOCAL_PROJECT_RESOLVE_SYNC_TIMEOUT_MS", 1500);
|
|
135318
|
+
const sleep2 = options.sleep ?? defaultSleep$1;
|
|
135319
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
135320
|
+
const rootPath = await resolveWorkspaceLocalProjectRootPath(repo, workspaceId, machineId, localProjectId);
|
|
135321
|
+
if (rootPath !== null) {
|
|
135322
|
+
return rootPath;
|
|
135323
|
+
}
|
|
135324
|
+
if (attempt >= maxAttempts) {
|
|
135325
|
+
return null;
|
|
135326
|
+
}
|
|
135327
|
+
options.onRetry?.(attempt, maxAttempts);
|
|
135328
|
+
if (options.requestSync) {
|
|
135329
|
+
try {
|
|
135330
|
+
await withTimeout$3(options.requestSync(), syncTimeoutMs, `Timeout waiting for machine Flock sync (localProject=${localProjectId})`);
|
|
135331
|
+
} catch {
|
|
135332
|
+
}
|
|
135333
|
+
}
|
|
135334
|
+
await sleep2(retryDelayMs);
|
|
135335
|
+
}
|
|
135336
|
+
}
|
|
135152
135337
|
class ConcurrentQueue {
|
|
135153
135338
|
constructor(maxConcurrency = 30) {
|
|
135154
135339
|
this.maxConcurrency = maxConcurrency;
|
|
@@ -136455,6 +136640,15 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
136455
136640
|
}
|
|
136456
136641
|
return (await checkoutLocalProjectBranchAtRootPath(workdir, branch)).currentBranch;
|
|
136457
136642
|
}
|
|
136643
|
+
resolveLocalProjectWorkdirForTurn(localProjectId) {
|
|
136644
|
+
return resolveWorkspaceLocalProjectRootPathWithRetry(this.deps.workspaceDocument.repo, this.deps.workspaceId, this.deps.machineId, localProjectId, {
|
|
136645
|
+
requestSync: () => this.deps.workspaceDocument.syncMachineFlockDoc(this.deps.machineId, {
|
|
136646
|
+
reason: "session-local-project-resolve",
|
|
136647
|
+
timeoutMs: readTimeoutEnv("LODY_LOCAL_PROJECT_RESOLVE_SYNC_TIMEOUT_MS", 1500)
|
|
136648
|
+
}),
|
|
136649
|
+
onRetry: (attempt, maxAttempts) => this.deps.logger.debug(`Local project ${localProjectId} not visible in machine Flock yet; pulling machine Flock and retrying (attempt ${attempt}/${maxAttempts})`)
|
|
136650
|
+
});
|
|
136651
|
+
}
|
|
136458
136652
|
async handleTurnError(sessionId, sessionDoc, error2) {
|
|
136459
136653
|
await this.deps.turnFinalization.finalizeACPState(sessionId);
|
|
136460
136654
|
await this.persistCodeCollabTurnDiffsAfterACPFinalization(sessionId, this.currentTurnBySession.get(sessionId));
|
|
@@ -137171,7 +137365,7 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
137171
137365
|
const meta = yield* self2.tryPromise(() => sessionDoc.getMetaState());
|
|
137172
137366
|
project = project ?? self2.resolveProjectFromMeta(meta, message.project?.branch);
|
|
137173
137367
|
const localProjectId = project?.kind === "local" ? project.localProjectId : void 0;
|
|
137174
|
-
const restoreWorkdir = localProjectId ? (yield* self2.tryPromise(() =>
|
|
137368
|
+
const restoreWorkdir = localProjectId ? (yield* self2.tryPromise(() => self2.resolveLocalProjectWorkdirForTurn(localProjectId))) ?? void 0 : void 0;
|
|
137175
137369
|
if (project?.kind === "local" && !restoreWorkdir) {
|
|
137176
137370
|
const missingMessage = `Local project not found in workspace: ${project.localProjectId}`;
|
|
137177
137371
|
self2.deps.logger.warn(`[${sessionId}] ${missingMessage}`);
|
|
@@ -137649,7 +137843,7 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
137649
137843
|
const githubRepoFullName = resolveProjectGitHubRepo(project);
|
|
137650
137844
|
const shouldPrepareWorktree = project?.kind === "github" && !!githubRepoFullName || project?.kind === "local" && project.useWorktree === true;
|
|
137651
137845
|
let branch = project?.branch?.trim() || void 0;
|
|
137652
|
-
const workdir = project?.kind === "local" ? await
|
|
137846
|
+
const workdir = project?.kind === "local" ? await this.resolveLocalProjectWorkdirForTurn(project.localProjectId) ?? void 0 : void 0;
|
|
137653
137847
|
this.deps.logger.info(`Session create received: ${sessionId}`);
|
|
137654
137848
|
const agentConfig = acpSessionConfig;
|
|
137655
137849
|
const promptText = agentConfig.prompt ?? "";
|
|
@@ -138617,67 +138811,6 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
138617
138811
|
this.releaseWaiters();
|
|
138618
138812
|
}
|
|
138619
138813
|
}
|
|
138620
|
-
class SessionUserResolver {
|
|
138621
|
-
constructor(logger2, workspaceId) {
|
|
138622
|
-
this.logger = logger2;
|
|
138623
|
-
this.workspaceId = workspaceId;
|
|
138624
|
-
this.client = LODY_AUTH_URL ? new ConvexHttpClient(LODY_AUTH_URL) : null;
|
|
138625
|
-
}
|
|
138626
|
-
client;
|
|
138627
|
-
cache = /* @__PURE__ */ new Map();
|
|
138628
|
-
async resolve(userId) {
|
|
138629
|
-
const normalizedUserId = userId.trim();
|
|
138630
|
-
if (!normalizedUserId) {
|
|
138631
|
-
return {
|
|
138632
|
-
id: userId,
|
|
138633
|
-
name: userId,
|
|
138634
|
-
email: buildMissingEmail("lody", userId)
|
|
138635
|
-
};
|
|
138636
|
-
}
|
|
138637
|
-
const existing = this.cache.get(normalizedUserId);
|
|
138638
|
-
if (existing) {
|
|
138639
|
-
return await existing;
|
|
138640
|
-
}
|
|
138641
|
-
const request2 = this.fetchUser(normalizedUserId).catch((error2) => {
|
|
138642
|
-
this.cache.delete(normalizedUserId);
|
|
138643
|
-
throw error2;
|
|
138644
|
-
});
|
|
138645
|
-
this.cache.set(normalizedUserId, request2);
|
|
138646
|
-
return await request2;
|
|
138647
|
-
}
|
|
138648
|
-
clear() {
|
|
138649
|
-
this.cache.clear();
|
|
138650
|
-
}
|
|
138651
|
-
async fetchUser(userId) {
|
|
138652
|
-
if (!this.client) {
|
|
138653
|
-
return this.fallbackUser(userId);
|
|
138654
|
-
}
|
|
138655
|
-
try {
|
|
138656
|
-
const user = await this.client.query(api$1.auth.getUserById, {
|
|
138657
|
-
userId,
|
|
138658
|
-
workspaceId: this.workspaceId
|
|
138659
|
-
});
|
|
138660
|
-
const email = typeof user?.email === "string" && user.email.trim() ? user.email.trim() : buildMissingEmail("lody", userId);
|
|
138661
|
-
const name2 = typeof user?.name === "string" && user.name.trim() ? user.name.trim() : email;
|
|
138662
|
-
return {
|
|
138663
|
-
id: userId,
|
|
138664
|
-
name: name2,
|
|
138665
|
-
email
|
|
138666
|
-
};
|
|
138667
|
-
} catch (error2) {
|
|
138668
|
-
this.logger.debug(`[session-user-resolver] Failed to resolve user ${userId} in workspace ${this.workspaceId}: ${formatErrorMessage(error2)}`);
|
|
138669
|
-
return this.fallbackUser(userId);
|
|
138670
|
-
}
|
|
138671
|
-
}
|
|
138672
|
-
fallbackUser(userId) {
|
|
138673
|
-
const email = buildMissingEmail("lody", userId);
|
|
138674
|
-
return {
|
|
138675
|
-
id: userId,
|
|
138676
|
-
name: email,
|
|
138677
|
-
email
|
|
138678
|
-
};
|
|
138679
|
-
}
|
|
138680
|
-
}
|
|
138681
138814
|
class AccessDenied extends TaggedError("AccessDenied") {
|
|
138682
138815
|
}
|
|
138683
138816
|
class AccessIndeterminate extends TaggedError("AccessIndeterminate") {
|
|
@@ -138720,6 +138853,26 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
138720
138853
|
const policy = whileInput(jittered$12, (error2) => error2._tag === "AccessIndeterminate");
|
|
138721
138854
|
yield* retry$2(attempt, policy).pipe(catchTag("AccessIndeterminate", () => _void));
|
|
138722
138855
|
});
|
|
138856
|
+
async function mapWithConcurrency$1(items2, concurrency, worker) {
|
|
138857
|
+
if (items2.length === 0) {
|
|
138858
|
+
return [];
|
|
138859
|
+
}
|
|
138860
|
+
const limit2 = Math.max(1, Math.floor(concurrency));
|
|
138861
|
+
const results = new Array(items2.length);
|
|
138862
|
+
let nextIndex = 0;
|
|
138863
|
+
const runWorker = async () => {
|
|
138864
|
+
while (nextIndex < items2.length) {
|
|
138865
|
+
const currentIndex = nextIndex;
|
|
138866
|
+
nextIndex += 1;
|
|
138867
|
+
results[currentIndex] = await worker(items2[currentIndex], currentIndex);
|
|
138868
|
+
}
|
|
138869
|
+
};
|
|
138870
|
+
await Promise.all(Array.from({
|
|
138871
|
+
length: Math.min(limit2, items2.length)
|
|
138872
|
+
}, runWorker));
|
|
138873
|
+
return results;
|
|
138874
|
+
}
|
|
138875
|
+
const SESSION_BOOTSTRAP_RECONCILE_CONCURRENCY = 4;
|
|
138723
138876
|
const isConfigOptionValueRecord = (value) => {
|
|
138724
138877
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
138725
138878
|
return false;
|
|
@@ -138729,7 +138882,7 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
138729
138882
|
class SessionDispatchWatcher {
|
|
138730
138883
|
constructor(deps) {
|
|
138731
138884
|
this.deps = deps;
|
|
138732
|
-
this.userResolver = deps.userResolver
|
|
138885
|
+
this.userResolver = deps.userResolver;
|
|
138733
138886
|
}
|
|
138734
138887
|
watchedSessions = /* @__PURE__ */ new Map();
|
|
138735
138888
|
sessionCheckChains = /* @__PURE__ */ new Map();
|
|
@@ -139030,9 +139183,9 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
139030
139183
|
if (!this.started) {
|
|
139031
139184
|
return;
|
|
139032
139185
|
}
|
|
139033
|
-
const reconcileFailures = await
|
|
139186
|
+
const reconcileFailures = await mapWithConcurrency$1([
|
|
139034
139187
|
...sessionRoomIds
|
|
139035
|
-
]
|
|
139188
|
+
], SESSION_BOOTSTRAP_RECONCILE_CONCURRENCY, async (roomId) => {
|
|
139036
139189
|
if (!this.started) {
|
|
139037
139190
|
return false;
|
|
139038
139191
|
}
|
|
@@ -139043,7 +139196,7 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
139043
139196
|
} catch {
|
|
139044
139197
|
return true;
|
|
139045
139198
|
}
|
|
139046
|
-
})
|
|
139199
|
+
});
|
|
139047
139200
|
const failedCount = reconcileFailures.filter(Boolean).length;
|
|
139048
139201
|
if (failedCount > 0) {
|
|
139049
139202
|
this.deps.logger.warn(`[dispatch] Owned-session bootstrap completed with ${failedCount}/${sessionRoomIds.size} session reconcile failure(s) (reason=${reason})`);
|
|
@@ -139966,6 +140119,148 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
139966
140119
|
return true;
|
|
139967
140120
|
}
|
|
139968
140121
|
}
|
|
140122
|
+
const DEFAULT_AI_GIT_AUTHOR_NAME = "LodyAI";
|
|
140123
|
+
const DEFAULT_AI_GIT_AUTHOR_EMAIL = "agent@lody.ai";
|
|
140124
|
+
const trimNonEmpty$1 = (value) => {
|
|
140125
|
+
const trimmed2 = value?.trim();
|
|
140126
|
+
return trimmed2 !== void 0 && trimmed2.length > 0 ? trimmed2 : void 0;
|
|
140127
|
+
};
|
|
140128
|
+
const isUsableEmail = (email) => {
|
|
140129
|
+
const trimmed2 = trimNonEmpty$1(email);
|
|
140130
|
+
return trimmed2 !== void 0 && !isMissingEmail(trimmed2);
|
|
140131
|
+
};
|
|
140132
|
+
const buildGitHubNoreplyEmail = (githubAccountId, githubLogin) => {
|
|
140133
|
+
const accountId = trimNonEmpty$1(githubAccountId);
|
|
140134
|
+
const login = trimNonEmpty$1(githubLogin);
|
|
140135
|
+
if (!accountId || !login || !/^\d+$/.test(accountId)) {
|
|
140136
|
+
return void 0;
|
|
140137
|
+
}
|
|
140138
|
+
return `${accountId}+${login}@users.noreply.github.com`;
|
|
140139
|
+
};
|
|
140140
|
+
const normalizeName = (name2, email) => {
|
|
140141
|
+
if (name2 !== void 0 && !isMissingEmail(name2)) {
|
|
140142
|
+
return name2;
|
|
140143
|
+
}
|
|
140144
|
+
return email;
|
|
140145
|
+
};
|
|
140146
|
+
const readGitConfig = (key2, cwd) => {
|
|
140147
|
+
try {
|
|
140148
|
+
const output = execFileSync("git", [
|
|
140149
|
+
"config",
|
|
140150
|
+
key2
|
|
140151
|
+
], {
|
|
140152
|
+
cwd,
|
|
140153
|
+
encoding: "utf8",
|
|
140154
|
+
stdio: [
|
|
140155
|
+
"ignore",
|
|
140156
|
+
"pipe",
|
|
140157
|
+
"ignore"
|
|
140158
|
+
]
|
|
140159
|
+
});
|
|
140160
|
+
return trimNonEmpty$1(output);
|
|
140161
|
+
} catch {
|
|
140162
|
+
return void 0;
|
|
140163
|
+
}
|
|
140164
|
+
};
|
|
140165
|
+
const readHostDefaultGitIdentity = (cwd) => ({
|
|
140166
|
+
name: trimNonEmpty$1(process.env.GIT_AUTHOR_NAME) ?? trimNonEmpty$1(process.env.GIT_COMMITTER_NAME) ?? readGitConfig("user.name", cwd),
|
|
140167
|
+
email: trimNonEmpty$1(process.env.GIT_AUTHOR_EMAIL) ?? trimNonEmpty$1(process.env.GIT_COMMITTER_EMAIL) ?? readGitConfig("user.email", cwd)
|
|
140168
|
+
});
|
|
140169
|
+
const resolveSessionGitIdentity = (requested, defaultIdentity, cwd) => {
|
|
140170
|
+
const resolvedDefaultIdentity = readHostDefaultGitIdentity(cwd);
|
|
140171
|
+
const requestedEmail = trimNonEmpty$1(requested.email);
|
|
140172
|
+
if (isUsableEmail(requestedEmail)) {
|
|
140173
|
+
return {
|
|
140174
|
+
name: normalizeName(trimNonEmpty$1(requested.name), requestedEmail),
|
|
140175
|
+
email: requestedEmail
|
|
140176
|
+
};
|
|
140177
|
+
}
|
|
140178
|
+
const defaultEmail = trimNonEmpty$1(resolvedDefaultIdentity.email);
|
|
140179
|
+
if (isUsableEmail(defaultEmail)) {
|
|
140180
|
+
return {
|
|
140181
|
+
name: normalizeName(trimNonEmpty$1(resolvedDefaultIdentity.name), defaultEmail),
|
|
140182
|
+
email: defaultEmail
|
|
140183
|
+
};
|
|
140184
|
+
}
|
|
140185
|
+
return {
|
|
140186
|
+
name: DEFAULT_AI_GIT_AUTHOR_NAME,
|
|
140187
|
+
email: DEFAULT_AI_GIT_AUTHOR_EMAIL
|
|
140188
|
+
};
|
|
140189
|
+
};
|
|
140190
|
+
const trimNonEmpty = (value) => {
|
|
140191
|
+
const trimmed2 = value?.trim();
|
|
140192
|
+
return trimmed2 !== void 0 && trimmed2.length > 0 ? trimmed2 : void 0;
|
|
140193
|
+
};
|
|
140194
|
+
class SessionUserResolver {
|
|
140195
|
+
constructor(logger2, workspaceId, cliToken, options) {
|
|
140196
|
+
this.logger = logger2;
|
|
140197
|
+
this.workspaceId = workspaceId;
|
|
140198
|
+
this.cliToken = cliToken;
|
|
140199
|
+
this.client = LODY_AUTH_URL ? new ConvexHttpClient(LODY_AUTH_URL) : null;
|
|
140200
|
+
this.queryProfile = options?.queryProfile ?? (async (args2) => await this.client?.query(api$1.auth.getWorkspaceUserProfileForCliToken, args2) ?? null);
|
|
140201
|
+
}
|
|
140202
|
+
client;
|
|
140203
|
+
queryProfile;
|
|
140204
|
+
cache = /* @__PURE__ */ new Map();
|
|
140205
|
+
async resolve(userId) {
|
|
140206
|
+
const normalizedUserId = userId.trim();
|
|
140207
|
+
if (!normalizedUserId) {
|
|
140208
|
+
return {
|
|
140209
|
+
id: userId,
|
|
140210
|
+
name: userId,
|
|
140211
|
+
email: buildMissingEmail("lody", userId)
|
|
140212
|
+
};
|
|
140213
|
+
}
|
|
140214
|
+
const existing = this.cache.get(normalizedUserId);
|
|
140215
|
+
if (existing) {
|
|
140216
|
+
return await existing;
|
|
140217
|
+
}
|
|
140218
|
+
const request2 = this.fetchUser(normalizedUserId).catch((error2) => {
|
|
140219
|
+
this.cache.delete(normalizedUserId);
|
|
140220
|
+
throw error2;
|
|
140221
|
+
});
|
|
140222
|
+
this.cache.set(normalizedUserId, request2);
|
|
140223
|
+
return await request2;
|
|
140224
|
+
}
|
|
140225
|
+
clear() {
|
|
140226
|
+
this.cache.clear();
|
|
140227
|
+
}
|
|
140228
|
+
async fetchUser(userId) {
|
|
140229
|
+
try {
|
|
140230
|
+
const profile = await this.queryProfile({
|
|
140231
|
+
cliToken: this.cliToken,
|
|
140232
|
+
workspaceId: this.workspaceId,
|
|
140233
|
+
userId
|
|
140234
|
+
});
|
|
140235
|
+
if (!profile) {
|
|
140236
|
+
this.logger.debug(`[session-user-resolver] User ${userId} not resolvable in workspace ${this.workspaceId}; using placeholder identity`);
|
|
140237
|
+
return this.fallbackUser(userId);
|
|
140238
|
+
}
|
|
140239
|
+
return this.toSessionUserProfile(userId, profile);
|
|
140240
|
+
} catch (error2) {
|
|
140241
|
+
this.logger.debug(`[session-user-resolver] Failed to resolve user ${userId} in workspace ${this.workspaceId}: ${formatErrorMessage(error2)}`);
|
|
140242
|
+
return this.fallbackUser(userId);
|
|
140243
|
+
}
|
|
140244
|
+
}
|
|
140245
|
+
toSessionUserProfile(userId, profile) {
|
|
140246
|
+
const accountEmail = trimNonEmpty(profile.email);
|
|
140247
|
+
const email = (accountEmail && !isMissingEmail(accountEmail) ? accountEmail : void 0) ?? buildGitHubNoreplyEmail(profile.githubAccountId, profile.githubLogin) ?? accountEmail ?? buildMissingEmail("lody", userId);
|
|
140248
|
+
const name2 = trimNonEmpty(profile.name) ?? trimNonEmpty(profile.githubLogin) ?? email;
|
|
140249
|
+
return {
|
|
140250
|
+
id: userId,
|
|
140251
|
+
name: name2,
|
|
140252
|
+
email
|
|
140253
|
+
};
|
|
140254
|
+
}
|
|
140255
|
+
fallbackUser(userId) {
|
|
140256
|
+
const email = buildMissingEmail("lody", userId);
|
|
140257
|
+
return {
|
|
140258
|
+
id: userId,
|
|
140259
|
+
name: email,
|
|
140260
|
+
email
|
|
140261
|
+
};
|
|
140262
|
+
}
|
|
140263
|
+
}
|
|
139969
140264
|
const DAY_MS$1 = 24 * 60 * 60 * 1e3;
|
|
139970
140265
|
const TERMINAL_RETENTION_MS = 7 * DAY_MS$1;
|
|
139971
140266
|
const MATERIALIZATION_CLAIM_MS = 6e4;
|
|
@@ -141043,6 +141338,7 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
141043
141338
|
return;
|
|
141044
141339
|
}
|
|
141045
141340
|
const frozen = operation.frozenContinuationConfig.inputConfig;
|
|
141341
|
+
const requester = await this.resolveRequesterIdentity(operation.requesterUserId);
|
|
141046
141342
|
await this.options.executionService.continueSession({
|
|
141047
141343
|
type: "session/chat",
|
|
141048
141344
|
sessionId: delivery.requesterSessionId,
|
|
@@ -141074,8 +141370,8 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
141074
141370
|
},
|
|
141075
141371
|
userTurnId: delivery.systemTurnId,
|
|
141076
141372
|
userId: operation.requesterUserId,
|
|
141077
|
-
userName:
|
|
141078
|
-
userEmail:
|
|
141373
|
+
userName: requester.name,
|
|
141374
|
+
userEmail: requester.email
|
|
141079
141375
|
}, {
|
|
141080
141376
|
dispatchSource: "delivery",
|
|
141081
141377
|
onTurnClaimed: async () => {
|
|
@@ -141087,6 +141383,26 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
141087
141383
|
this.withStore((store) => store.consumeDelivery(delivery.requesterSessionId, delivery.operationId));
|
|
141088
141384
|
}
|
|
141089
141385
|
}
|
|
141386
|
+
async resolveRequesterIdentity(requesterUserId) {
|
|
141387
|
+
const fallback2 = {
|
|
141388
|
+
name: requesterUserId,
|
|
141389
|
+
email: buildMissingEmail("lody", requesterUserId)
|
|
141390
|
+
};
|
|
141391
|
+
const resolver = this.options.userResolver;
|
|
141392
|
+
if (!resolver) {
|
|
141393
|
+
return fallback2;
|
|
141394
|
+
}
|
|
141395
|
+
try {
|
|
141396
|
+
const profile = await resolver.resolve(requesterUserId);
|
|
141397
|
+
return {
|
|
141398
|
+
name: profile.name,
|
|
141399
|
+
email: profile.email
|
|
141400
|
+
};
|
|
141401
|
+
} catch (error2) {
|
|
141402
|
+
this.options.logger.debug(`[operation-coordinator] Failed to resolve requester identity ${requesterUserId}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
141403
|
+
return fallback2;
|
|
141404
|
+
}
|
|
141405
|
+
}
|
|
141090
141406
|
async resolveFrozenConfiguration(operation) {
|
|
141091
141407
|
const agentConfigId = operation.frozenContinuationConfig.agentConfigId;
|
|
141092
141408
|
if (!agentConfigId) return "available";
|
|
@@ -147784,6 +148100,7 @@ ${entry.text}`).join("\n\n");
|
|
|
147784
148100
|
function isCodeCollabV2ServiceError(error2) {
|
|
147785
148101
|
return error2 instanceof CodeCollabV2ServiceError;
|
|
147786
148102
|
}
|
|
148103
|
+
const DEFAULT_CODE_COLLAB_REPUBLISH_CONCURRENCY = 4;
|
|
147787
148104
|
class CodeCollabV2Service {
|
|
147788
148105
|
constructor(deps) {
|
|
147789
148106
|
this.deps = deps;
|
|
@@ -147792,9 +148109,16 @@ ${entry.text}`).join("\n\n");
|
|
|
147792
148109
|
publishedStateByOwnerSessionId = /* @__PURE__ */ new Map();
|
|
147793
148110
|
watchByOwnerSessionId = /* @__PURE__ */ new Map();
|
|
147794
148111
|
refreshQueueByOwnerSessionId = /* @__PURE__ */ new Map();
|
|
148112
|
+
publishChainByOwnerSessionId = /* @__PURE__ */ new Map();
|
|
148113
|
+
pendingFileIndexSignalByOwnerSessionId = /* @__PURE__ */ new Set();
|
|
148114
|
+
republishRequestedGeneration = 0;
|
|
148115
|
+
republishCompletedGeneration = 0;
|
|
148116
|
+
republishDrainPromise = null;
|
|
148117
|
+
disposed = false;
|
|
147795
148118
|
logger = getLogger("code-collab");
|
|
147796
148119
|
writeChainByAbsolutePath = /* @__PURE__ */ new Map();
|
|
147797
148120
|
dispose() {
|
|
148121
|
+
this.disposed = true;
|
|
147798
148122
|
for (const ownerSessionId of this.watchByOwnerSessionId.keys()) {
|
|
147799
148123
|
this.releaseWorkspaceWatch(ownerSessionId);
|
|
147800
148124
|
}
|
|
@@ -148063,25 +148387,79 @@ ${entry.text}`).join("\n\n");
|
|
|
148063
148387
|
...this.stateByOwnerSessionId.keys(),
|
|
148064
148388
|
...this.watchByOwnerSessionId.keys()
|
|
148065
148389
|
]);
|
|
148066
|
-
await
|
|
148390
|
+
await mapWithConcurrency$1([
|
|
148067
148391
|
...ownerSessionIds
|
|
148068
|
-
]
|
|
148069
|
-
|
|
148070
|
-
|
|
148071
|
-
|
|
148072
|
-
|
|
148073
|
-
|
|
148074
|
-
|
|
148392
|
+
], DEFAULT_CODE_COLLAB_REPUBLISH_CONCURRENCY, async (sessionId) => {
|
|
148393
|
+
try {
|
|
148394
|
+
await this.refreshSharedState({
|
|
148395
|
+
sessionId
|
|
148396
|
+
}, {
|
|
148397
|
+
forcePublish: true
|
|
148398
|
+
});
|
|
148399
|
+
} catch (error2) {
|
|
148400
|
+
this.logger.debug(`[code-collab] known file-index refresh failed ownerSessionId=${sessionId}: ${formatErrorMessage(error2)}`);
|
|
148401
|
+
}
|
|
148402
|
+
});
|
|
148075
148403
|
}
|
|
148076
148404
|
async republishKnownSharedStates() {
|
|
148077
|
-
|
|
148078
|
-
|
|
148079
|
-
|
|
148080
|
-
|
|
148081
|
-
|
|
148082
|
-
|
|
148405
|
+
if (this.disposed) {
|
|
148406
|
+
return;
|
|
148407
|
+
}
|
|
148408
|
+
const requestedGeneration = this.republishRequestedGeneration + 1;
|
|
148409
|
+
this.republishRequestedGeneration = requestedGeneration;
|
|
148410
|
+
while (!this.disposed && this.republishCompletedGeneration < requestedGeneration) {
|
|
148411
|
+
this.ensureRepublishDrain();
|
|
148412
|
+
await this.republishDrainPromise;
|
|
148413
|
+
}
|
|
148414
|
+
}
|
|
148415
|
+
ensureRepublishDrain() {
|
|
148416
|
+
if (this.republishDrainPromise || this.disposed) {
|
|
148417
|
+
return;
|
|
148418
|
+
}
|
|
148419
|
+
const drain = Promise.resolve().then(async () => await this.drainRepublishRequests());
|
|
148420
|
+
const beforeCleanup = this.deps.testOnlyBeforeRepublishDrainCleanup;
|
|
148421
|
+
const drainAfterCleanupBoundary = beforeCleanup ? drain.then(beforeCleanup, async (error2) => {
|
|
148422
|
+
await beforeCleanup();
|
|
148423
|
+
throw error2;
|
|
148424
|
+
}) : drain;
|
|
148425
|
+
const managedDrain = drainAfterCleanupBoundary.finally(() => {
|
|
148426
|
+
if (this.republishDrainPromise === managedDrain) {
|
|
148427
|
+
this.republishDrainPromise = null;
|
|
148428
|
+
if (!this.disposed && this.republishCompletedGeneration < this.republishRequestedGeneration) {
|
|
148429
|
+
this.ensureRepublishDrain();
|
|
148430
|
+
}
|
|
148431
|
+
}
|
|
148432
|
+
});
|
|
148433
|
+
this.republishDrainPromise = managedDrain;
|
|
148434
|
+
}
|
|
148435
|
+
async drainRepublishRequests() {
|
|
148436
|
+
while (!this.disposed) {
|
|
148437
|
+
const targetGeneration = this.republishRequestedGeneration;
|
|
148438
|
+
if (this.republishCompletedGeneration >= targetGeneration) {
|
|
148439
|
+
return;
|
|
148440
|
+
}
|
|
148441
|
+
const ownerSessionIds = [
|
|
148442
|
+
...this.stateByOwnerSessionId.keys()
|
|
148443
|
+
];
|
|
148444
|
+
await mapWithConcurrency$1(ownerSessionIds, DEFAULT_CODE_COLLAB_REPUBLISH_CONCURRENCY, async (ownerSessionId) => {
|
|
148445
|
+
if (this.disposed) {
|
|
148446
|
+
return;
|
|
148447
|
+
}
|
|
148448
|
+
const state2 = this.stateByOwnerSessionId.get(ownerSessionId);
|
|
148449
|
+
if (!state2) {
|
|
148450
|
+
return;
|
|
148451
|
+
}
|
|
148452
|
+
try {
|
|
148453
|
+
await this.publishOwnerState(ownerSessionId, state2, {
|
|
148454
|
+
forcePublish: true,
|
|
148455
|
+
persistAllChangesDiffStats: true
|
|
148456
|
+
});
|
|
148457
|
+
} catch (error2) {
|
|
148458
|
+
this.logger.debug(`[code-collab] file-index republish failed ownerSessionId=${ownerSessionId}: ${formatErrorMessage(error2)}`);
|
|
148459
|
+
}
|
|
148083
148460
|
});
|
|
148084
|
-
|
|
148461
|
+
this.republishCompletedGeneration = targetGeneration;
|
|
148462
|
+
}
|
|
148085
148463
|
}
|
|
148086
148464
|
enqueueSharedStateRefresh(ownerSessionId, refresh) {
|
|
148087
148465
|
let queue2 = this.refreshQueueByOwnerSessionId.get(ownerSessionId);
|
|
@@ -148660,7 +149038,7 @@ ${entry.text}`).join("\n\n");
|
|
|
148660
149038
|
if (missingWorkspacePaths.length === 0) {
|
|
148661
149039
|
return;
|
|
148662
149040
|
}
|
|
148663
|
-
await
|
|
149041
|
+
await mapWithConcurrency$1(missingWorkspacePaths, DEFAULT_CODE_COLLAB_REPUBLISH_CONCURRENCY, async (workspacePath) => {
|
|
148664
149042
|
const absolutePath = path__default$1.resolve(resolved.workspaceRoot, workspacePath);
|
|
148665
149043
|
const value = await classifyPathForFileTree(absolutePath).catch((error2) => {
|
|
148666
149044
|
const code2 = errorCode$1(error2);
|
|
@@ -148679,7 +149057,7 @@ ${entry.text}`).join("\n\n");
|
|
|
148679
149057
|
if (!isLazyDirectoryValue(value)) {
|
|
148680
149058
|
deleteDescendantFileTreeEntries(state2.fileTree, workspacePath, fileTreeIndex);
|
|
148681
149059
|
}
|
|
148682
|
-
})
|
|
149060
|
+
});
|
|
148683
149061
|
}
|
|
148684
149062
|
async computeAllChangesFromDiffStore(resolved) {
|
|
148685
149063
|
const store = this.deps.diffStore;
|
|
@@ -148893,24 +149271,34 @@ ${entry.text}`).join("\n\n");
|
|
|
148893
149271
|
throw new Error(`Unsupported Code Collab v2 diff snapshot: ${String(exhaustive)}`);
|
|
148894
149272
|
}
|
|
148895
149273
|
async publishOwnerState(ownerSessionId, state2, options = {}) {
|
|
148896
|
-
|
|
148897
|
-
|
|
148898
|
-
|
|
148899
|
-
|
|
148900
|
-
|
|
148901
|
-
|
|
148902
|
-
|
|
148903
|
-
|
|
148904
|
-
|
|
148905
|
-
|
|
148906
|
-
|
|
148907
|
-
|
|
148908
|
-
|
|
148909
|
-
|
|
148910
|
-
|
|
149274
|
+
await this.enqueueOwnerPublication(ownerSessionId, async () => {
|
|
149275
|
+
const startedAtMs = Date.now();
|
|
149276
|
+
if (!this.deps.publishFileIndex || this.disposed) {
|
|
149277
|
+
return;
|
|
149278
|
+
}
|
|
149279
|
+
const cloneStartedAtMs = Date.now();
|
|
149280
|
+
const fileTree = cloneFileTreeState(state2.fileTree);
|
|
149281
|
+
const allChanges = cloneAllChangesState(state2.allChanges);
|
|
149282
|
+
const cloneMs = Date.now() - cloneStartedAtMs;
|
|
149283
|
+
const buildStartedAtMs = Date.now();
|
|
149284
|
+
const fileIndex = buildCodeCollabFileIndexState(fileTree, allChanges);
|
|
149285
|
+
const buildMs = Date.now() - buildStartedAtMs;
|
|
149286
|
+
await this.publishPreparedOwnerFileIndexNow(ownerSessionId, fileIndex, options, {
|
|
149287
|
+
startedAtMs,
|
|
149288
|
+
cloneMs,
|
|
149289
|
+
buildMs
|
|
149290
|
+
});
|
|
148911
149291
|
});
|
|
148912
149292
|
}
|
|
148913
149293
|
async publishPreparedOwnerFileIndex(ownerSessionId, fileIndex, options = {}, metrics = {}) {
|
|
149294
|
+
await this.enqueueOwnerPublication(ownerSessionId, async () => {
|
|
149295
|
+
if (this.disposed) {
|
|
149296
|
+
return;
|
|
149297
|
+
}
|
|
149298
|
+
await this.publishPreparedOwnerFileIndexNow(ownerSessionId, fileIndex, options, metrics);
|
|
149299
|
+
});
|
|
149300
|
+
}
|
|
149301
|
+
async publishPreparedOwnerFileIndexNow(ownerSessionId, fileIndex, options, metrics) {
|
|
148914
149302
|
const startedAtMs = metrics.startedAtMs ?? Date.now();
|
|
148915
149303
|
const publishFileIndex = this.deps.publishFileIndex;
|
|
148916
149304
|
const publishFileIndexSignal = this.deps.publishFileIndexSignal;
|
|
@@ -148935,18 +149323,30 @@ ${entry.text}`).join("\n\n");
|
|
|
148935
149323
|
this.publishedStateByOwnerSessionId.set(ownerSessionId, nextPublishedState);
|
|
148936
149324
|
const publishStartedAtMs = Date.now();
|
|
148937
149325
|
try {
|
|
148938
|
-
|
|
148939
|
-
|
|
148940
|
-
|
|
148941
|
-
|
|
148942
|
-
|
|
148943
|
-
|
|
148944
|
-
|
|
148945
|
-
|
|
149326
|
+
let publishResult;
|
|
149327
|
+
try {
|
|
149328
|
+
publishResult = await publishFileIndex({
|
|
149329
|
+
ownerSessionId,
|
|
149330
|
+
fileIndex,
|
|
149331
|
+
allChangesDiffStats: summarizeFileIndexAllChanges(fileIndex),
|
|
149332
|
+
persistAllChangesDiffStats: options.persistAllChangesDiffStats === true,
|
|
149333
|
+
updatedAtMs,
|
|
149334
|
+
reconcileRemote: options.forcePublish === true
|
|
149335
|
+
});
|
|
149336
|
+
} catch (error2) {
|
|
149337
|
+
if (error2 instanceof CodeCollabFileIndexChangedPublishError) {
|
|
149338
|
+
this.pendingFileIndexSignalByOwnerSessionId.add(ownerSessionId);
|
|
149339
|
+
}
|
|
149340
|
+
throw error2;
|
|
149341
|
+
}
|
|
149342
|
+
const shouldPublishSignal = publishResult?.changed !== false || sameFileIndex !== true || this.pendingFileIndexSignalByOwnerSessionId.has(ownerSessionId);
|
|
149343
|
+
if (publishFileIndexSignal && shouldPublishSignal) {
|
|
149344
|
+
this.pendingFileIndexSignalByOwnerSessionId.add(ownerSessionId);
|
|
148946
149345
|
await publishFileIndexSignal({
|
|
148947
149346
|
ownerSessionId,
|
|
148948
149347
|
updatedAtMs
|
|
148949
149348
|
});
|
|
149349
|
+
this.pendingFileIndexSignalByOwnerSessionId.delete(ownerSessionId);
|
|
148950
149350
|
}
|
|
148951
149351
|
const publishMs = Date.now() - publishStartedAtMs;
|
|
148952
149352
|
this.logger.info(`[code-collab] file-index publish completed ownerSessionId=${ownerSessionId} paths=${pathCount} durationMs=${Date.now() - startedAtMs} cloneMs=${metrics.cloneMs ?? 0} buildMs=${metrics.buildMs ?? 0} equalityMs=${equalityMs} publishMs=${publishMs} force=${options.forcePublish === true}${metrics.workerMs === void 0 ? "" : ` workerMs=${metrics.workerMs}`}`);
|
|
@@ -148961,6 +149361,18 @@ ${entry.text}`).join("\n\n");
|
|
|
148961
149361
|
throw error2;
|
|
148962
149362
|
}
|
|
148963
149363
|
}
|
|
149364
|
+
enqueueOwnerPublication(ownerSessionId, task) {
|
|
149365
|
+
const previous = this.publishChainByOwnerSessionId.get(ownerSessionId) ?? Promise.resolve();
|
|
149366
|
+
const result = previous.catch(() => void 0).then(task);
|
|
149367
|
+
const settled = result.then(() => void 0, () => void 0);
|
|
149368
|
+
this.publishChainByOwnerSessionId.set(ownerSessionId, settled);
|
|
149369
|
+
void settled.finally(() => {
|
|
149370
|
+
if (this.publishChainByOwnerSessionId.get(ownerSessionId) === settled) {
|
|
149371
|
+
this.publishChainByOwnerSessionId.delete(ownerSessionId);
|
|
149372
|
+
}
|
|
149373
|
+
});
|
|
149374
|
+
return result;
|
|
149375
|
+
}
|
|
148964
149376
|
}
|
|
148965
149377
|
function cloneFileTreeValue(value) {
|
|
148966
149378
|
return value === true ? true : {
|
|
@@ -156716,18 +157128,13 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
|
|
|
156716
157128
|
publishFileIndex: async (state2) => {
|
|
156717
157129
|
const publishStartedAtMs = Date.now();
|
|
156718
157130
|
const pathCount = Object.keys(state2.fileIndex).length;
|
|
156719
|
-
const
|
|
156720
|
-
|
|
156721
|
-
|
|
156722
|
-
|
|
156723
|
-
|
|
156724
|
-
|
|
156725
|
-
|
|
156726
|
-
await this.workspaceDocument.repo.flush();
|
|
156727
|
-
const flushMs = Date.now() - flushStartedAtMs;
|
|
156728
|
-
const syncStartedAtMs = Date.now();
|
|
156729
|
-
await handle.syncOnce();
|
|
156730
|
-
const syncMs = Date.now() - syncStartedAtMs;
|
|
157131
|
+
const result = await publishCodeCollabFileIndexFlock({
|
|
157132
|
+
repo: this.workspaceDocument.repo,
|
|
157133
|
+
flockDocId: getCodeCollabFileIndexFlockDocId(this.workspaceId, state2.ownerSessionId),
|
|
157134
|
+
fileIndex: state2.fileIndex,
|
|
157135
|
+
updatedAtMs: state2.updatedAtMs,
|
|
157136
|
+
reconcileRemote: state2.reconcileRemote
|
|
157137
|
+
});
|
|
156731
157138
|
if (state2.persistAllChangesDiffStats && state2.allChangesDiffStats) {
|
|
156732
157139
|
try {
|
|
156733
157140
|
await this.persistCodeCollabAllChangesDiffStats(state2.ownerSessionId, state2.allChangesDiffStats);
|
|
@@ -156735,28 +157142,17 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
|
|
|
156735
157142
|
this.logger.debug(`[code-collab-v2] Failed to persist All Changes diffStats ownerSessionId=${state2.ownerSessionId}: ${formatErrorMessage(error2)}`);
|
|
156736
157143
|
}
|
|
156737
157144
|
}
|
|
156738
|
-
this.logger.info(`[code-collab-v2] file-index flock publish completed ownerSessionId=${state2.ownerSessionId} paths=${pathCount} changed=${changed} durationMs=${Date.now() - publishStartedAtMs}
|
|
157145
|
+
this.logger.info(`[code-collab-v2] file-index flock publish completed ownerSessionId=${state2.ownerSessionId} paths=${pathCount} changed=${result.changed} reconcileRemote=${state2.reconcileRemote} durationMs=${Date.now() - publishStartedAtMs}`);
|
|
157146
|
+
return result;
|
|
156739
157147
|
},
|
|
156740
157148
|
publishFileIndexSignal: async (state2) => {
|
|
156741
157149
|
const publishStartedAtMs = Date.now();
|
|
156742
|
-
const
|
|
156743
|
-
|
|
156744
|
-
|
|
156745
|
-
|
|
156746
|
-
|
|
156747
|
-
|
|
156748
|
-
const writeStartedAtMs = Date.now();
|
|
156749
|
-
const previous = readCodeCollabFileIndexSignalFromFlock(handle.flock);
|
|
156750
|
-
const revision = (previous?.r ?? 0) + 1;
|
|
156751
|
-
const changed = writeCodeCollabFileIndexSignalToFlock(handle.flock, revision, state2.updatedAtMs);
|
|
156752
|
-
const writeMs = Date.now() - writeStartedAtMs;
|
|
156753
|
-
const flushStartedAtMs = Date.now();
|
|
156754
|
-
await this.workspaceDocument.repo.flush();
|
|
156755
|
-
const flushMs = Date.now() - flushStartedAtMs;
|
|
156756
|
-
const syncStartedAtMs = Date.now();
|
|
156757
|
-
await handle.syncOnce();
|
|
156758
|
-
const syncMs = Date.now() - syncStartedAtMs;
|
|
156759
|
-
this.logger.info(`[code-collab-v2] file-index signal publish completed ownerSessionId=${state2.ownerSessionId} revision=${revision} changed=${changed} durationMs=${Date.now() - publishStartedAtMs} openMs=${openMs} preSyncMs=${preSyncMs} writeMs=${writeMs} flushMs=${flushMs} syncMs=${syncMs}`);
|
|
157150
|
+
const result = await publishCodeCollabFileIndexSignalFlock({
|
|
157151
|
+
repo: this.workspaceDocument.repo,
|
|
157152
|
+
flockDocId: getCodeCollabFileIndexSignalFlockDocId(this.workspaceId, state2.ownerSessionId),
|
|
157153
|
+
updatedAtMs: state2.updatedAtMs
|
|
157154
|
+
});
|
|
157155
|
+
this.logger.info(`[code-collab-v2] file-index signal publish completed ownerSessionId=${state2.ownerSessionId} revision=${result.revision} changed=${result.changed} durationMs=${Date.now() - publishStartedAtMs}`);
|
|
156760
157156
|
}
|
|
156761
157157
|
});
|
|
156762
157158
|
if (typeof this.workspaceDocument.onMetaRoomSynced === "function") {
|
|
@@ -157047,11 +157443,13 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
|
|
|
157047
157443
|
} else {
|
|
157048
157444
|
this.logger.debug("Streams RPC disabled: missing LODY_AUTH_URL / LODY_AUTH_SITE_URL");
|
|
157049
157445
|
}
|
|
157446
|
+
this.sessionUserResolver = new SessionUserResolver(this.logger, this.workspaceId, this.token);
|
|
157050
157447
|
this.sessionDispatchWatcher = new SessionDispatchWatcher({
|
|
157051
157448
|
logger: this.logger,
|
|
157052
157449
|
machineId: this.machineId,
|
|
157053
157450
|
workspaceId: this.workspaceId,
|
|
157054
157451
|
currentUserId: this.userId,
|
|
157452
|
+
userResolver: this.sessionUserResolver,
|
|
157055
157453
|
workspaceDocument: this.workspaceDocument,
|
|
157056
157454
|
executionService: this.executionService,
|
|
157057
157455
|
getPreparedSessionLaunchConfig: (input2) => this.sessionManager.getPreparedSessionLaunchConfig(input2),
|
|
@@ -157081,6 +157479,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
|
|
|
157081
157479
|
workspaceDocument: this.workspaceDocument,
|
|
157082
157480
|
executionService: this.executionService,
|
|
157083
157481
|
dispatchWatcher: this.sessionDispatchWatcher,
|
|
157482
|
+
userResolver: this.sessionUserResolver,
|
|
157084
157483
|
logger: this.logger,
|
|
157085
157484
|
materializeTarget: async (operation, item, index) => await this.materializeOperationTarget(operation, item, index)
|
|
157086
157485
|
});
|
|
@@ -157151,6 +157550,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
|
|
|
157151
157550
|
providerSetupManager;
|
|
157152
157551
|
previewService;
|
|
157153
157552
|
sessionDispatchWatcher;
|
|
157553
|
+
sessionUserResolver;
|
|
157154
157554
|
operationCoordinator;
|
|
157155
157555
|
autoPromptRunner;
|
|
157156
157556
|
turnPostProcessingService;
|
|
@@ -171153,66 +171553,6 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
|
|
|
171153
171553
|
}
|
|
171154
171554
|
return child.pid;
|
|
171155
171555
|
}
|
|
171156
|
-
const DEFAULT_AI_GIT_AUTHOR_NAME = "LodyAI";
|
|
171157
|
-
const DEFAULT_AI_GIT_AUTHOR_EMAIL = "agent@lody.ai";
|
|
171158
|
-
const trimNonEmpty = (value) => {
|
|
171159
|
-
const trimmed2 = value?.trim();
|
|
171160
|
-
return trimmed2 !== void 0 && trimmed2.length > 0 ? trimmed2 : void 0;
|
|
171161
|
-
};
|
|
171162
|
-
const isUsableEmail = (email) => {
|
|
171163
|
-
const trimmed2 = trimNonEmpty(email);
|
|
171164
|
-
return trimmed2 !== void 0 && !isMissingEmail(trimmed2);
|
|
171165
|
-
};
|
|
171166
|
-
const normalizeName = (name2, email) => {
|
|
171167
|
-
if (name2 !== void 0 && !isMissingEmail(name2)) {
|
|
171168
|
-
return name2;
|
|
171169
|
-
}
|
|
171170
|
-
return email;
|
|
171171
|
-
};
|
|
171172
|
-
const readGitConfig = (key2, cwd) => {
|
|
171173
|
-
try {
|
|
171174
|
-
const output = execFileSync("git", [
|
|
171175
|
-
"config",
|
|
171176
|
-
key2
|
|
171177
|
-
], {
|
|
171178
|
-
cwd,
|
|
171179
|
-
encoding: "utf8",
|
|
171180
|
-
stdio: [
|
|
171181
|
-
"ignore",
|
|
171182
|
-
"pipe",
|
|
171183
|
-
"ignore"
|
|
171184
|
-
]
|
|
171185
|
-
});
|
|
171186
|
-
return trimNonEmpty(output);
|
|
171187
|
-
} catch {
|
|
171188
|
-
return void 0;
|
|
171189
|
-
}
|
|
171190
|
-
};
|
|
171191
|
-
const readHostDefaultGitIdentity = (cwd) => ({
|
|
171192
|
-
name: trimNonEmpty(process.env.GIT_AUTHOR_NAME) ?? trimNonEmpty(process.env.GIT_COMMITTER_NAME) ?? readGitConfig("user.name", cwd),
|
|
171193
|
-
email: trimNonEmpty(process.env.GIT_AUTHOR_EMAIL) ?? trimNonEmpty(process.env.GIT_COMMITTER_EMAIL) ?? readGitConfig("user.email", cwd)
|
|
171194
|
-
});
|
|
171195
|
-
const resolveSessionGitIdentity = (requested, defaultIdentity, cwd) => {
|
|
171196
|
-
const resolvedDefaultIdentity = readHostDefaultGitIdentity(cwd);
|
|
171197
|
-
const requestedEmail = trimNonEmpty(requested.email);
|
|
171198
|
-
if (isUsableEmail(requestedEmail)) {
|
|
171199
|
-
return {
|
|
171200
|
-
name: normalizeName(trimNonEmpty(requested.name), requestedEmail),
|
|
171201
|
-
email: requestedEmail
|
|
171202
|
-
};
|
|
171203
|
-
}
|
|
171204
|
-
const defaultEmail = trimNonEmpty(resolvedDefaultIdentity.email);
|
|
171205
|
-
if (isUsableEmail(defaultEmail)) {
|
|
171206
|
-
return {
|
|
171207
|
-
name: normalizeName(trimNonEmpty(resolvedDefaultIdentity.name), defaultEmail),
|
|
171208
|
-
email: defaultEmail
|
|
171209
|
-
};
|
|
171210
|
-
}
|
|
171211
|
-
return {
|
|
171212
|
-
name: DEFAULT_AI_GIT_AUTHOR_NAME,
|
|
171213
|
-
email: DEFAULT_AI_GIT_AUTHOR_EMAIL
|
|
171214
|
-
};
|
|
171215
|
-
};
|
|
171216
171556
|
const getDefaultSessionWorkdir = (sessionId) => path__default.join(os__default.homedir(), ".lody", "chats", sessionId);
|
|
171217
171557
|
const ensureDefaultSessionWorkdir = (sessionId) => {
|
|
171218
171558
|
const dir = getDefaultSessionWorkdir(sessionId);
|
|
@@ -172036,7 +172376,7 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
|
|
|
172036
172376
|
this.sessionSandboxFactory = options?.sessionSandboxFactory ?? createSessionSandboxFactory({
|
|
172037
172377
|
logger: this.logger
|
|
172038
172378
|
});
|
|
172039
|
-
this.preparationUserResolver = new SessionUserResolver(this.logger, this.workspaceId);
|
|
172379
|
+
this.preparationUserResolver = new SessionUserResolver(this.logger, this.workspaceId, this.token);
|
|
172040
172380
|
this.preparationService = new SessionPreparationService(this.logger, {
|
|
172041
172381
|
hardTtlMs: SESSION_PREPARATION_HARD_TTL_MS,
|
|
172042
172382
|
maxConcurrent: MAX_CONCURRENT_SESSION_PREPARATIONS
|
|
@@ -180020,152 +180360,17 @@ Shutting down gracefully${reason}...`);
|
|
|
180020
180360
|
throw error2;
|
|
180021
180361
|
}
|
|
180022
180362
|
}
|
|
180023
|
-
|
|
180024
|
-
|
|
180025
|
-
|
|
180026
|
-
|
|
180027
|
-
const loginCommand = new Command("login").description("Login to Lody using device authorization flow or an API key").option("-d, --debug", "enable debug output").option("--machine-name <name>", "Machine name to register (defaults to hostname)").option("--auth <api-key>", "Use a CLI API key for non-interactive login").action(async (options) => {
|
|
180028
|
-
if (options.debug) {
|
|
180029
|
-
rootLogger.setDebug(true);
|
|
180030
|
-
}
|
|
180031
|
-
const logger2 = getLogger("login");
|
|
180032
|
-
const authClient = new AuthClient(logger2);
|
|
180033
|
-
const providedAuth = options.auth?.trim();
|
|
180034
|
-
const authMethod = providedAuth ? "api_key" : "device_auth";
|
|
180035
|
-
try {
|
|
180036
|
-
if (options.auth !== void 0 && !providedAuth) {
|
|
180037
|
-
captureAuthEvent("login_failed", {
|
|
180038
|
-
auth_method: "api_key",
|
|
180039
|
-
reason_code: "missing_api_key"
|
|
180040
|
-
});
|
|
180041
|
-
logger2.error("Missing API key for --auth.");
|
|
180042
|
-
await exitLogin(1);
|
|
180043
|
-
}
|
|
180044
|
-
captureAuthEvent("login_started", {
|
|
180045
|
-
auth_method: authMethod
|
|
180046
|
-
});
|
|
180047
|
-
if (providedAuth) {
|
|
180048
|
-
const result2 = await performLoginWithApiKey(authClient, logger2, {
|
|
180049
|
-
apiKey: providedAuth,
|
|
180050
|
-
machineName: options.machineName
|
|
180051
|
-
});
|
|
180052
|
-
if (result2.success) {
|
|
180053
|
-
captureAuthEvent("login_succeeded", {
|
|
180054
|
-
auth_method: "api_key"
|
|
180055
|
-
});
|
|
180056
|
-
logger2.success("\n" + chalk.green("\u2713") + " Successfully logged in!");
|
|
180057
|
-
logger2.info(" User: " + chalk.cyan(result2.user.name || result2.user.email));
|
|
180058
|
-
logger2.info(" Email: " + chalk.cyan(result2.user.email));
|
|
180059
|
-
} else {
|
|
180060
|
-
captureAuthEvent("login_failed", {
|
|
180061
|
-
auth_method: "api_key"
|
|
180062
|
-
});
|
|
180063
|
-
await reportError("login", result2.error, {
|
|
180064
|
-
message: "Login failed using --auth",
|
|
180065
|
-
logger: logger2
|
|
180066
|
-
});
|
|
180067
|
-
await exitLogin(1);
|
|
180068
|
-
}
|
|
180069
|
-
await flushTelemetry();
|
|
180070
|
-
return;
|
|
180071
|
-
}
|
|
180072
|
-
const existingAuth = authClient.getAuthInfo();
|
|
180073
|
-
if (existingAuth) {
|
|
180074
|
-
logger2.debug("Found existing authentication, checking validity...");
|
|
180075
|
-
const validation2 = await authClient.validateToken(existingAuth.token);
|
|
180076
|
-
if (validation2.valid && validation2.user) {
|
|
180077
|
-
captureAuthEvent("login_succeeded", {
|
|
180078
|
-
auth_method: "device_auth",
|
|
180079
|
-
already_logged_in: true
|
|
180080
|
-
});
|
|
180081
|
-
logger2.success("\n" + chalk.green("\u2713") + " You are already logged in!");
|
|
180082
|
-
logger2.info(" User: " + chalk.cyan(validation2.user.name || validation2.user.email));
|
|
180083
|
-
logger2.info(" Email: " + chalk.cyan(validation2.user.email));
|
|
180084
|
-
await flushTelemetry();
|
|
180085
|
-
return;
|
|
180086
|
-
} else if (isRetryableTokenValidationFailure(validation2)) {
|
|
180087
|
-
captureAuthEvent("login_failed", {
|
|
180088
|
-
auth_method: "device_auth",
|
|
180089
|
-
reason_code: "auth_service_unreachable"
|
|
180090
|
-
});
|
|
180091
|
-
logger2.error(`Unable to validate existing authentication because the auth service is unreachable: ${validation2.error ?? "network error"}`);
|
|
180092
|
-
await exitLogin(1);
|
|
180093
|
-
} else if (isTokenValidationUnavailable(validation2)) {
|
|
180094
|
-
captureAuthEvent("login_failed", {
|
|
180095
|
-
auth_method: "device_auth",
|
|
180096
|
-
reason_code: "auth_validation_unavailable"
|
|
180097
|
-
});
|
|
180098
|
-
logger2.error(`Unable to validate existing authentication: ${validation2.error ?? validation2.reason}`);
|
|
180099
|
-
await exitLogin(1);
|
|
180100
|
-
} else {
|
|
180101
|
-
logger2.debug("Existing authentication is invalid, proceeding with new login...");
|
|
180102
|
-
}
|
|
180103
|
-
}
|
|
180104
|
-
logger2.info("Initializing device authorization...");
|
|
180105
|
-
const result = await performLogin(authClient, logger2, {
|
|
180106
|
-
machineName: options.machineName
|
|
180107
|
-
});
|
|
180108
|
-
if (result.success) {
|
|
180109
|
-
captureAuthEvent("login_succeeded", {
|
|
180110
|
-
auth_method: "device_auth"
|
|
180111
|
-
});
|
|
180112
|
-
logger2.success("\n" + chalk.green("\u2713") + " Successfully logged in!");
|
|
180113
|
-
logger2.info(" User: " + chalk.cyan(result.user.name || result.user.email));
|
|
180114
|
-
logger2.info(" Email: " + chalk.cyan(result.user.email));
|
|
180115
|
-
} else {
|
|
180116
|
-
captureAuthEvent("login_failed", {
|
|
180117
|
-
auth_method: "device_auth"
|
|
180118
|
-
});
|
|
180119
|
-
await reportError("login", result.error, {
|
|
180120
|
-
message: "Login failed during device authorization",
|
|
180121
|
-
logger: logger2
|
|
180122
|
-
});
|
|
180123
|
-
await exitLogin(1);
|
|
180124
|
-
}
|
|
180125
|
-
await flushTelemetry();
|
|
180126
|
-
} catch (error2) {
|
|
180127
|
-
captureAuthEvent("login_failed", {
|
|
180128
|
-
auth_method: authMethod,
|
|
180129
|
-
reason_code: "exception"
|
|
180130
|
-
});
|
|
180131
|
-
await reportError("login", error2, {
|
|
180132
|
-
message: "Login command failed",
|
|
180133
|
-
logger: logger2
|
|
180134
|
-
});
|
|
180135
|
-
await exitLogin(1);
|
|
180136
|
-
}
|
|
180137
|
-
});
|
|
180138
|
-
const logoutCommand = new Command("logout").description("Logout from Lody and clear saved authentication").option("-d, --debug", "enable debug output").action(async (options) => {
|
|
180139
|
-
if (options.debug) {
|
|
180140
|
-
rootLogger.setDebug(true);
|
|
180141
|
-
}
|
|
180142
|
-
const logger2 = getLogger("logout");
|
|
180143
|
-
const authClient = new AuthClient(logger2);
|
|
180144
|
-
const logoutResult = authClient.logout();
|
|
180145
|
-
if (!logoutResult.success) {
|
|
180146
|
-
captureAuthEvent("logout_failed");
|
|
180147
|
-
await reportError("logout", logoutResult.error, {
|
|
180148
|
-
message: "Logout failed",
|
|
180149
|
-
logger: logger2
|
|
180150
|
-
});
|
|
180151
|
-
await flushTelemetry();
|
|
180152
|
-
process.exit(1);
|
|
180153
|
-
}
|
|
180154
|
-
if (!logoutResult.user) {
|
|
180155
|
-
captureAuthEvent("logout_succeeded", {
|
|
180156
|
-
was_logged_in: false
|
|
180157
|
-
});
|
|
180158
|
-
logger2.info("You are not logged in.");
|
|
180159
|
-
await flushTelemetry();
|
|
180160
|
-
return;
|
|
180161
|
-
}
|
|
180162
|
-
captureAuthEvent("logout_succeeded", {
|
|
180163
|
-
was_logged_in: true
|
|
180363
|
+
function buildOpenLocalProjectDeepLink(input2) {
|
|
180364
|
+
const search2 = new URLSearchParams({
|
|
180365
|
+
machine: input2.machineId,
|
|
180366
|
+
project: input2.localProjectId
|
|
180164
180367
|
});
|
|
180165
|
-
|
|
180166
|
-
|
|
180167
|
-
|
|
180168
|
-
|
|
180368
|
+
const workspaceSlug = input2.workspaceSlug?.trim();
|
|
180369
|
+
if (workspaceSlug) {
|
|
180370
|
+
search2.set("workspaceSlug", workspaceSlug);
|
|
180371
|
+
}
|
|
180372
|
+
return `lody://chat/new?${search2.toString()}`;
|
|
180373
|
+
}
|
|
180169
180374
|
const isUpKey = (key2) => key2.name === "up" || key2.name === "k" || key2.ctrl && key2.name === "p";
|
|
180170
180375
|
const isDownKey = (key2) => key2.name === "down" || key2.name === "j" || key2.ctrl && key2.name === "n";
|
|
180171
180376
|
const isSpaceKey = (key2) => key2.name === "space";
|
|
@@ -205610,21 +205815,6 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
|
|
|
205610
205815
|
Separator
|
|
205611
205816
|
};
|
|
205612
205817
|
const LOCAL_CONTROL_TIMEOUT_MS = 15e3;
|
|
205613
|
-
function setDebugIfEnabled(options) {
|
|
205614
|
-
if (options.debug) {
|
|
205615
|
-
rootLogger.setDebug(true);
|
|
205616
|
-
}
|
|
205617
|
-
}
|
|
205618
|
-
function resolveMachineIdOrExit() {
|
|
205619
|
-
const logger2 = getLogger("project");
|
|
205620
|
-
const authClient = new AuthClient(logger2);
|
|
205621
|
-
const authInfo = authClient.getAuthInfo();
|
|
205622
|
-
if (!authInfo) {
|
|
205623
|
-
logger2.error("Not logged in. Run `lody login` first.");
|
|
205624
|
-
process.exit(1);
|
|
205625
|
-
}
|
|
205626
|
-
return authInfo.machine.machineId;
|
|
205627
|
-
}
|
|
205628
205818
|
async function sendLocalProjectControl(message) {
|
|
205629
205819
|
try {
|
|
205630
205820
|
await ensureDaemonReachable();
|
|
@@ -205688,8 +205878,8 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
|
|
|
205688
205878
|
}
|
|
205689
205879
|
return candidates.length > 0 ? candidates : null;
|
|
205690
205880
|
}
|
|
205691
|
-
function printWorkspaceCandidates(candidates) {
|
|
205692
|
-
const logger2 = getLogger(
|
|
205881
|
+
function printWorkspaceCandidates(candidates, loggerName) {
|
|
205882
|
+
const logger2 = getLogger(loggerName);
|
|
205693
205883
|
logger2.info("Available workspaces:");
|
|
205694
205884
|
for (const item of candidates) {
|
|
205695
205885
|
const slug = item.slug ? ` (${item.slug})` : "";
|
|
@@ -205697,8 +205887,8 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
|
|
|
205697
205887
|
}
|
|
205698
205888
|
logger2.info("Use --workspace <id|slug|name>.");
|
|
205699
205889
|
}
|
|
205700
|
-
async function promptWorkspaceSelection(candidates) {
|
|
205701
|
-
const logger2 = getLogger(
|
|
205890
|
+
async function promptWorkspaceSelection(candidates, loggerName) {
|
|
205891
|
+
const logger2 = getLogger(loggerName);
|
|
205702
205892
|
const choices = candidates.map((candidate) => {
|
|
205703
205893
|
const slug = candidate.slug ? ` (${candidate.slug})` : "";
|
|
205704
205894
|
return {
|
|
@@ -205722,13 +205912,264 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
|
|
|
205722
205912
|
return null;
|
|
205723
205913
|
}
|
|
205724
205914
|
}
|
|
205915
|
+
async function resolveLocalProjectForApp(args2) {
|
|
205916
|
+
const buildRequest = (workspace) => ({
|
|
205917
|
+
type: "local-project/add",
|
|
205918
|
+
machineId: args2.machineId,
|
|
205919
|
+
rootPath: args2.rootPath,
|
|
205920
|
+
...workspace ? {
|
|
205921
|
+
workspace
|
|
205922
|
+
} : {}
|
|
205923
|
+
});
|
|
205924
|
+
let response2 = await args2.send(buildRequest(args2.workspaceSelector));
|
|
205925
|
+
let workspaceSlug = null;
|
|
205926
|
+
if (!response2.ok && response2.error === "workspace_required" && args2.canPrompt) {
|
|
205927
|
+
const candidates = extractWorkspaceCandidates(response2);
|
|
205928
|
+
if (candidates) {
|
|
205929
|
+
const selectedWorkspaceId = await args2.promptWorkspace(candidates);
|
|
205930
|
+
if (!selectedWorkspaceId) {
|
|
205931
|
+
throw new Error("Workspace selection is required to open a local project.");
|
|
205932
|
+
}
|
|
205933
|
+
workspaceSlug = candidates.find((c) => c.id === selectedWorkspaceId)?.slug ?? null;
|
|
205934
|
+
response2 = await args2.send(buildRequest(selectedWorkspaceId));
|
|
205935
|
+
}
|
|
205936
|
+
}
|
|
205937
|
+
if (!response2.ok) {
|
|
205938
|
+
if (response2.error === "daemon_unavailable") {
|
|
205939
|
+
return {
|
|
205940
|
+
localProjectId: createLocalProjectId(args2.rootPath),
|
|
205941
|
+
name: getLocalProjectNameFromRootPath(args2.rootPath),
|
|
205942
|
+
workspaceSlug: null,
|
|
205943
|
+
registered: false
|
|
205944
|
+
};
|
|
205945
|
+
}
|
|
205946
|
+
const candidates = extractWorkspaceCandidates(response2);
|
|
205947
|
+
if (candidates) {
|
|
205948
|
+
throw new Error(`${response2.message} Pass --workspace <id|slug|name>. Candidates: ${candidates.map(formatWorkspaceCandidate).join(", ")}`);
|
|
205949
|
+
}
|
|
205950
|
+
throw new Error(response2.message);
|
|
205951
|
+
}
|
|
205952
|
+
if (response2.type !== "local-project/add") {
|
|
205953
|
+
throw new Error(`Unexpected response type: ${response2.type}`);
|
|
205954
|
+
}
|
|
205955
|
+
return {
|
|
205956
|
+
localProjectId: response2.result.localProjectId,
|
|
205957
|
+
name: response2.result.name,
|
|
205958
|
+
workspaceSlug,
|
|
205959
|
+
registered: true
|
|
205960
|
+
};
|
|
205961
|
+
}
|
|
205962
|
+
const appCommand = new Command("app").description("Open the Lody desktop app on a new chat with a local directory selected (registers it if needed)").argument("[path]", "Local project directory to select", ".").option("--workspace <selector>", "Target workspace id, slug, or name").option("--json", "Print JSON output").option("-d, --debug", "Enable debug output").action(async (projectPath, options) => {
|
|
205963
|
+
await runOneShotCommand("app", options, async () => {
|
|
205964
|
+
const logger2 = getLogger("app");
|
|
205965
|
+
const auth = getAuthContextOrThrow$1("app");
|
|
205966
|
+
const rootPath = ensureLocalProjectRootPath(path__default$1.resolve(projectPath ?? "."));
|
|
205967
|
+
const target = await resolveLocalProjectForApp({
|
|
205968
|
+
machineId: auth.machineId,
|
|
205969
|
+
rootPath,
|
|
205970
|
+
workspaceSelector: options.workspace,
|
|
205971
|
+
canPrompt: !options.json && !options.workspace && !!process.stdin.isTTY && !!process.stdout.isTTY,
|
|
205972
|
+
send: sendLocalProjectControl,
|
|
205973
|
+
promptWorkspace: (candidates) => promptWorkspaceSelection(candidates, "app")
|
|
205974
|
+
});
|
|
205975
|
+
const deepLink = buildOpenLocalProjectDeepLink({
|
|
205976
|
+
machineId: auth.machineId,
|
|
205977
|
+
localProjectId: target.localProjectId,
|
|
205978
|
+
workspaceSlug: target.workspaceSlug
|
|
205979
|
+
});
|
|
205980
|
+
try {
|
|
205981
|
+
await openBrowser(deepLink);
|
|
205982
|
+
} catch (error2) {
|
|
205983
|
+
throw new Error(`Could not hand the link to the operating system. Is the Lody desktop app installed? Open it manually: ${deepLink}`, {
|
|
205984
|
+
cause: error2
|
|
205985
|
+
});
|
|
205986
|
+
}
|
|
205987
|
+
if (options.json) {
|
|
205988
|
+
printJson({
|
|
205989
|
+
ok: true,
|
|
205990
|
+
machineId: auth.machineId,
|
|
205991
|
+
localProjectId: target.localProjectId,
|
|
205992
|
+
name: target.name,
|
|
205993
|
+
rootPath,
|
|
205994
|
+
registered: target.registered,
|
|
205995
|
+
deepLink
|
|
205996
|
+
});
|
|
205997
|
+
return;
|
|
205998
|
+
}
|
|
205999
|
+
if (!target.registered) {
|
|
206000
|
+
logger2.warn("\u26A0\uFE0F Local CLI daemon is not running, so this directory could not be registered. If it is new, add it from the desktop app after it starts.");
|
|
206001
|
+
}
|
|
206002
|
+
logger2.success(`\u2705 Opening Lody desktop: ${target.name} (${rootPath})`);
|
|
206003
|
+
});
|
|
206004
|
+
});
|
|
206005
|
+
async function exitLogin(code2) {
|
|
206006
|
+
await flushTelemetry();
|
|
206007
|
+
process.exit(code2);
|
|
206008
|
+
}
|
|
206009
|
+
const loginCommand = new Command("login").description("Login to Lody using device authorization flow or an API key").option("-d, --debug", "enable debug output").option("--machine-name <name>", "Machine name to register (defaults to hostname)").option("--auth <api-key>", "Use a CLI API key for non-interactive login").action(async (options) => {
|
|
206010
|
+
if (options.debug) {
|
|
206011
|
+
rootLogger.setDebug(true);
|
|
206012
|
+
}
|
|
206013
|
+
const logger2 = getLogger("login");
|
|
206014
|
+
const authClient = new AuthClient(logger2);
|
|
206015
|
+
const providedAuth = options.auth?.trim();
|
|
206016
|
+
const authMethod = providedAuth ? "api_key" : "device_auth";
|
|
206017
|
+
try {
|
|
206018
|
+
if (options.auth !== void 0 && !providedAuth) {
|
|
206019
|
+
captureAuthEvent("login_failed", {
|
|
206020
|
+
auth_method: "api_key",
|
|
206021
|
+
reason_code: "missing_api_key"
|
|
206022
|
+
});
|
|
206023
|
+
logger2.error("Missing API key for --auth.");
|
|
206024
|
+
await exitLogin(1);
|
|
206025
|
+
}
|
|
206026
|
+
captureAuthEvent("login_started", {
|
|
206027
|
+
auth_method: authMethod
|
|
206028
|
+
});
|
|
206029
|
+
if (providedAuth) {
|
|
206030
|
+
const result2 = await performLoginWithApiKey(authClient, logger2, {
|
|
206031
|
+
apiKey: providedAuth,
|
|
206032
|
+
machineName: options.machineName
|
|
206033
|
+
});
|
|
206034
|
+
if (result2.success) {
|
|
206035
|
+
captureAuthEvent("login_succeeded", {
|
|
206036
|
+
auth_method: "api_key"
|
|
206037
|
+
});
|
|
206038
|
+
logger2.success("\n" + chalk.green("\u2713") + " Successfully logged in!");
|
|
206039
|
+
logger2.info(" User: " + chalk.cyan(result2.user.name || result2.user.email));
|
|
206040
|
+
logger2.info(" Email: " + chalk.cyan(result2.user.email));
|
|
206041
|
+
} else {
|
|
206042
|
+
captureAuthEvent("login_failed", {
|
|
206043
|
+
auth_method: "api_key"
|
|
206044
|
+
});
|
|
206045
|
+
await reportError("login", result2.error, {
|
|
206046
|
+
message: "Login failed using --auth",
|
|
206047
|
+
logger: logger2
|
|
206048
|
+
});
|
|
206049
|
+
await exitLogin(1);
|
|
206050
|
+
}
|
|
206051
|
+
await flushTelemetry();
|
|
206052
|
+
return;
|
|
206053
|
+
}
|
|
206054
|
+
const existingAuth = authClient.getAuthInfo();
|
|
206055
|
+
if (existingAuth) {
|
|
206056
|
+
logger2.debug("Found existing authentication, checking validity...");
|
|
206057
|
+
const validation2 = await authClient.validateToken(existingAuth.token);
|
|
206058
|
+
if (validation2.valid && validation2.user) {
|
|
206059
|
+
captureAuthEvent("login_succeeded", {
|
|
206060
|
+
auth_method: "device_auth",
|
|
206061
|
+
already_logged_in: true
|
|
206062
|
+
});
|
|
206063
|
+
logger2.success("\n" + chalk.green("\u2713") + " You are already logged in!");
|
|
206064
|
+
logger2.info(" User: " + chalk.cyan(validation2.user.name || validation2.user.email));
|
|
206065
|
+
logger2.info(" Email: " + chalk.cyan(validation2.user.email));
|
|
206066
|
+
await flushTelemetry();
|
|
206067
|
+
return;
|
|
206068
|
+
} else if (isRetryableTokenValidationFailure(validation2)) {
|
|
206069
|
+
captureAuthEvent("login_failed", {
|
|
206070
|
+
auth_method: "device_auth",
|
|
206071
|
+
reason_code: "auth_service_unreachable"
|
|
206072
|
+
});
|
|
206073
|
+
logger2.error(`Unable to validate existing authentication because the auth service is unreachable: ${validation2.error ?? "network error"}`);
|
|
206074
|
+
await exitLogin(1);
|
|
206075
|
+
} else if (isTokenValidationUnavailable(validation2)) {
|
|
206076
|
+
captureAuthEvent("login_failed", {
|
|
206077
|
+
auth_method: "device_auth",
|
|
206078
|
+
reason_code: "auth_validation_unavailable"
|
|
206079
|
+
});
|
|
206080
|
+
logger2.error(`Unable to validate existing authentication: ${validation2.error ?? validation2.reason}`);
|
|
206081
|
+
await exitLogin(1);
|
|
206082
|
+
} else {
|
|
206083
|
+
logger2.debug("Existing authentication is invalid, proceeding with new login...");
|
|
206084
|
+
}
|
|
206085
|
+
}
|
|
206086
|
+
logger2.info("Initializing device authorization...");
|
|
206087
|
+
const result = await performLogin(authClient, logger2, {
|
|
206088
|
+
machineName: options.machineName
|
|
206089
|
+
});
|
|
206090
|
+
if (result.success) {
|
|
206091
|
+
captureAuthEvent("login_succeeded", {
|
|
206092
|
+
auth_method: "device_auth"
|
|
206093
|
+
});
|
|
206094
|
+
logger2.success("\n" + chalk.green("\u2713") + " Successfully logged in!");
|
|
206095
|
+
logger2.info(" User: " + chalk.cyan(result.user.name || result.user.email));
|
|
206096
|
+
logger2.info(" Email: " + chalk.cyan(result.user.email));
|
|
206097
|
+
} else {
|
|
206098
|
+
captureAuthEvent("login_failed", {
|
|
206099
|
+
auth_method: "device_auth"
|
|
206100
|
+
});
|
|
206101
|
+
await reportError("login", result.error, {
|
|
206102
|
+
message: "Login failed during device authorization",
|
|
206103
|
+
logger: logger2
|
|
206104
|
+
});
|
|
206105
|
+
await exitLogin(1);
|
|
206106
|
+
}
|
|
206107
|
+
await flushTelemetry();
|
|
206108
|
+
} catch (error2) {
|
|
206109
|
+
captureAuthEvent("login_failed", {
|
|
206110
|
+
auth_method: authMethod,
|
|
206111
|
+
reason_code: "exception"
|
|
206112
|
+
});
|
|
206113
|
+
await reportError("login", error2, {
|
|
206114
|
+
message: "Login command failed",
|
|
206115
|
+
logger: logger2
|
|
206116
|
+
});
|
|
206117
|
+
await exitLogin(1);
|
|
206118
|
+
}
|
|
206119
|
+
});
|
|
206120
|
+
const logoutCommand = new Command("logout").description("Logout from Lody and clear saved authentication").option("-d, --debug", "enable debug output").action(async (options) => {
|
|
206121
|
+
if (options.debug) {
|
|
206122
|
+
rootLogger.setDebug(true);
|
|
206123
|
+
}
|
|
206124
|
+
const logger2 = getLogger("logout");
|
|
206125
|
+
const authClient = new AuthClient(logger2);
|
|
206126
|
+
const logoutResult = authClient.logout();
|
|
206127
|
+
if (!logoutResult.success) {
|
|
206128
|
+
captureAuthEvent("logout_failed");
|
|
206129
|
+
await reportError("logout", logoutResult.error, {
|
|
206130
|
+
message: "Logout failed",
|
|
206131
|
+
logger: logger2
|
|
206132
|
+
});
|
|
206133
|
+
await flushTelemetry();
|
|
206134
|
+
process.exit(1);
|
|
206135
|
+
}
|
|
206136
|
+
if (!logoutResult.user) {
|
|
206137
|
+
captureAuthEvent("logout_succeeded", {
|
|
206138
|
+
was_logged_in: false
|
|
206139
|
+
});
|
|
206140
|
+
logger2.info("You are not logged in.");
|
|
206141
|
+
await flushTelemetry();
|
|
206142
|
+
return;
|
|
206143
|
+
}
|
|
206144
|
+
captureAuthEvent("logout_succeeded", {
|
|
206145
|
+
was_logged_in: true
|
|
206146
|
+
});
|
|
206147
|
+
logger2.info(`Logging out user: ${chalk.cyan(logoutResult.user.email)}`);
|
|
206148
|
+
logger2.success("\n" + chalk.green("\u2713") + " Successfully logged out!");
|
|
206149
|
+
await flushTelemetry();
|
|
206150
|
+
});
|
|
206151
|
+
function setDebugIfEnabled(options) {
|
|
206152
|
+
if (options.debug) {
|
|
206153
|
+
rootLogger.setDebug(true);
|
|
206154
|
+
}
|
|
206155
|
+
}
|
|
206156
|
+
function resolveMachineIdOrExit() {
|
|
206157
|
+
const logger2 = getLogger("project");
|
|
206158
|
+
const authClient = new AuthClient(logger2);
|
|
206159
|
+
const authInfo = authClient.getAuthInfo();
|
|
206160
|
+
if (!authInfo) {
|
|
206161
|
+
logger2.error("Not logged in. Run `lody login` first.");
|
|
206162
|
+
process.exit(1);
|
|
206163
|
+
}
|
|
206164
|
+
return authInfo.machine.machineId;
|
|
206165
|
+
}
|
|
205725
206166
|
function printProjectControlError(action, response2) {
|
|
205726
206167
|
const logger2 = getLogger("project");
|
|
205727
206168
|
logger2.error(`Failed to ${action}: ${response2.message}`);
|
|
205728
206169
|
if (response2.error === "workspace_required") {
|
|
205729
206170
|
const candidates = extractWorkspaceCandidates(response2);
|
|
205730
206171
|
if (candidates && candidates.length > 0) {
|
|
205731
|
-
printWorkspaceCandidates(candidates);
|
|
206172
|
+
printWorkspaceCandidates(candidates, "project");
|
|
205732
206173
|
}
|
|
205733
206174
|
}
|
|
205734
206175
|
}
|
|
@@ -205826,7 +206267,7 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
|
|
|
205826
206267
|
if (!response2.ok && response2.error === "workspace_required" && canPromptForWorkspace) {
|
|
205827
206268
|
const candidates = extractWorkspaceCandidates(response2);
|
|
205828
206269
|
if (candidates && candidates.length > 0) {
|
|
205829
|
-
const selectedWorkspace = await promptWorkspaceSelection(candidates);
|
|
206270
|
+
const selectedWorkspace = await promptWorkspaceSelection(candidates, "project");
|
|
205830
206271
|
if (!selectedWorkspace) {
|
|
205831
206272
|
process.exit(1);
|
|
205832
206273
|
}
|
|
@@ -205989,25 +206430,6 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
|
|
|
205989
206430
|
], rows));
|
|
205990
206431
|
});
|
|
205991
206432
|
const projectCommand = new Command("project").description("Manage local projects").addCommand(projectAddCommand).addCommand(projectDeleteCommand).addCommand(projectListCommand);
|
|
205992
|
-
async function mapWithConcurrency$1(items2, concurrency, worker) {
|
|
205993
|
-
if (items2.length === 0) {
|
|
205994
|
-
return [];
|
|
205995
|
-
}
|
|
205996
|
-
const limit2 = Math.max(1, Math.floor(concurrency));
|
|
205997
|
-
const results = new Array(items2.length);
|
|
205998
|
-
let nextIndex = 0;
|
|
205999
|
-
const runWorker = async () => {
|
|
206000
|
-
while (nextIndex < items2.length) {
|
|
206001
|
-
const currentIndex = nextIndex;
|
|
206002
|
-
nextIndex += 1;
|
|
206003
|
-
results[currentIndex] = await worker(items2[currentIndex], currentIndex);
|
|
206004
|
-
}
|
|
206005
|
-
};
|
|
206006
|
-
await Promise.all(Array.from({
|
|
206007
|
-
length: Math.min(limit2, items2.length)
|
|
206008
|
-
}, () => runWorker()));
|
|
206009
|
-
return results;
|
|
206010
|
-
}
|
|
206011
206433
|
const DEFAULT_SYNC_CONCURRENCY = 4;
|
|
206012
206434
|
function parsePositiveIntOption(value) {
|
|
206013
206435
|
const parsed = Number.parseInt(value, 10);
|
|
@@ -207258,7 +207680,7 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
|
|
|
207258
207680
|
data = createReviewBundleSnapshot(bundle);
|
|
207259
207681
|
}
|
|
207260
207682
|
const { injectReviewSnapshot } = await import("./chunks/index-VoI6Ds2-.js");
|
|
207261
|
-
const { resolveReviewViewerTemplate } = await import("./chunks/review-viewer-
|
|
207683
|
+
const { resolveReviewViewerTemplate } = await import("./chunks/review-viewer-BbX0BXrV.js");
|
|
207262
207684
|
const template = await resolveReviewViewerTemplate();
|
|
207263
207685
|
const html = injectReviewSnapshot(template, data);
|
|
207264
207686
|
const outputPath = options.output ? path__default$1.resolve(options.output) : defaultHtmlOutputPath(inputPath);
|
|
@@ -227337,6 +227759,7 @@ ${lines2.join("\n")}` : ""}${suffix}`);
|
|
|
227337
227759
|
program.addCommand(loginCommand);
|
|
227338
227760
|
program.addCommand(logoutCommand);
|
|
227339
227761
|
program.addCommand(startCommand);
|
|
227762
|
+
program.addCommand(appCommand);
|
|
227340
227763
|
program.addCommand(projectCommand);
|
|
227341
227764
|
program.addCommand(sessionCommand);
|
|
227342
227765
|
program.addCommand(syncCommand);
|