lody 0.43.1 → 0.44.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 +553 -126
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -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.44.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";
|
|
@@ -36863,7 +36863,7 @@ Mongoose Error Code: ${error2.code}` : ""}`
|
|
|
36863
36863
|
"node": ">=18.0.0"
|
|
36864
36864
|
};
|
|
36865
36865
|
const optionalDependencies = {
|
|
36866
|
-
"
|
|
36866
|
+
"@agentclientprotocol/claude-agent-acp": "0.29.0",
|
|
36867
36867
|
"acp-extension-codex": "0.11.1"
|
|
36868
36868
|
};
|
|
36869
36869
|
const devDependencies = {
|
|
@@ -64620,34 +64620,78 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
64620
64620
|
SITE_URL = "https://lody.ai";
|
|
64621
64621
|
SITE_APP_BASE_PATH = process.env["SITE_APP_BASE_PATH"] ?? "";
|
|
64622
64622
|
};
|
|
64623
|
+
const MACHINE_ID_FILE_NAME = "machine-id";
|
|
64624
|
+
function getMachineIdFilePath() {
|
|
64625
|
+
return path__default.join(os__default.homedir(), ".lody", MACHINE_ID_FILE_NAME);
|
|
64626
|
+
}
|
|
64627
|
+
function isRunningInDocker() {
|
|
64628
|
+
try {
|
|
64629
|
+
if (fs__default.existsSync("/.dockerenv")) {
|
|
64630
|
+
return true;
|
|
64631
|
+
}
|
|
64632
|
+
const cgroup = fs__default.readFileSync("/proc/self/cgroup", "utf-8");
|
|
64633
|
+
return cgroup.includes("docker") || /[0-9a-f]{64}/.test(cgroup);
|
|
64634
|
+
} catch {
|
|
64635
|
+
return false;
|
|
64636
|
+
}
|
|
64637
|
+
}
|
|
64623
64638
|
function getSystemMachineId() {
|
|
64624
64639
|
try {
|
|
64640
|
+
const inDocker = isRunningInDocker();
|
|
64641
|
+
if (inDocker) {
|
|
64642
|
+
const machineIdPath = getMachineIdFilePath();
|
|
64643
|
+
if (fs__default.existsSync(machineIdPath)) {
|
|
64644
|
+
const id = fs__default.readFileSync(machineIdPath, "utf-8").trim();
|
|
64645
|
+
if (id) return id;
|
|
64646
|
+
}
|
|
64647
|
+
}
|
|
64648
|
+
let baseId = null;
|
|
64625
64649
|
if (process.platform === "linux") {
|
|
64626
64650
|
if (fs__default.existsSync("/etc/machine-id")) {
|
|
64627
|
-
|
|
64628
|
-
}
|
|
64629
|
-
|
|
64630
|
-
return fs__default.readFileSync("/var/lib/dbus/machine-id", "utf-8").trim();
|
|
64651
|
+
baseId = fs__default.readFileSync("/etc/machine-id", "utf-8").trim();
|
|
64652
|
+
} else if (fs__default.existsSync("/var/lib/dbus/machine-id")) {
|
|
64653
|
+
baseId = fs__default.readFileSync("/var/lib/dbus/machine-id", "utf-8").trim();
|
|
64631
64654
|
}
|
|
64632
64655
|
} else if (process.platform === "darwin") {
|
|
64633
64656
|
try {
|
|
64634
64657
|
const uuid2 = execSync("ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID", {
|
|
64635
64658
|
encoding: "utf-8"
|
|
64636
64659
|
}).match(/"IOPlatformUUID" = "(.+)"/)?.[1];
|
|
64637
|
-
|
|
64660
|
+
baseId = uuid2 ?? null;
|
|
64638
64661
|
} catch {
|
|
64639
|
-
|
|
64662
|
+
baseId = null;
|
|
64640
64663
|
}
|
|
64641
64664
|
} else if (process.platform === "win32") {
|
|
64642
64665
|
try {
|
|
64643
64666
|
const uuid2 = execSync("wmic csproduct get UUID", {
|
|
64644
64667
|
encoding: "utf-8"
|
|
64645
64668
|
}).split("\n")[1]?.trim();
|
|
64646
|
-
|
|
64669
|
+
baseId = uuid2 ?? null;
|
|
64670
|
+
} catch {
|
|
64671
|
+
baseId = null;
|
|
64672
|
+
}
|
|
64673
|
+
}
|
|
64674
|
+
if (!baseId) return null;
|
|
64675
|
+
if (inDocker) {
|
|
64676
|
+
const suffix = require$$3$4.randomUUID().replace(/-/g, "").slice(0, 12);
|
|
64677
|
+
const dockerMachineId = `${baseId}-docker-${suffix}`;
|
|
64678
|
+
try {
|
|
64679
|
+
const machineIdPath = getMachineIdFilePath();
|
|
64680
|
+
fs__default.mkdirSync(path__default.dirname(machineIdPath), {
|
|
64681
|
+
recursive: true
|
|
64682
|
+
});
|
|
64683
|
+
fs__default.writeFileSync(machineIdPath, dockerMachineId, "utf-8");
|
|
64684
|
+
if (process.platform !== "win32") {
|
|
64685
|
+
try {
|
|
64686
|
+
fs__default.chmodSync(machineIdPath, 384);
|
|
64687
|
+
} catch {
|
|
64688
|
+
}
|
|
64689
|
+
}
|
|
64647
64690
|
} catch {
|
|
64648
|
-
return null;
|
|
64649
64691
|
}
|
|
64692
|
+
return dockerMachineId;
|
|
64650
64693
|
}
|
|
64694
|
+
return baseId;
|
|
64651
64695
|
} catch {
|
|
64652
64696
|
}
|
|
64653
64697
|
return null;
|
|
@@ -66603,6 +66647,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
66603
66647
|
if (!normalizedSegment) return "";
|
|
66604
66648
|
return `/${normalizedSegment}`;
|
|
66605
66649
|
}
|
|
66650
|
+
const LODY_FULL_ACCESS_MODE_ID = "lodyFullAccess";
|
|
66651
|
+
const isFullAccessModeId = (modeId) => modeId === LODY_FULL_ACCESS_MODE_ID || modeId === "bypassPermissions" || modeId === "full-access";
|
|
66606
66652
|
const ACP_CAPABILITY_CACHE_VERSION = 1;
|
|
66607
66653
|
const getAcpCapabilityCacheKey = (cliType, agentType) => `${cliType}:${agentType}`;
|
|
66608
66654
|
const getAcpCapabilityCacheStaleReason = (entry2, expectedSourceVersion) => {
|
|
@@ -66618,6 +66664,37 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
66618
66664
|
return void 0;
|
|
66619
66665
|
};
|
|
66620
66666
|
const isBuiltinAgentType = (agentType) => agentType === "claude" || agentType === "codex";
|
|
66667
|
+
const isAllowPermissionKind = (kind) => typeof kind === "string" && kind.startsWith("allow");
|
|
66668
|
+
const isDenyOrRejectPermissionKind = (kind) => typeof kind === "string" && (kind.startsWith("deny") || kind.startsWith("reject"));
|
|
66669
|
+
const getPermissionOptionKind = (option2) => option2.kind;
|
|
66670
|
+
function selectAutoApprovePermissionDecision(options) {
|
|
66671
|
+
const selected = options.find((option2) => getPermissionOptionKind(option2) === "allow_always") ?? options.find((option2) => getPermissionOptionKind(option2) === "allow_once") ?? options.find((option2) => isAllowPermissionKind(getPermissionOptionKind(option2)));
|
|
66672
|
+
if (selected) {
|
|
66673
|
+
return {
|
|
66674
|
+
outcome: {
|
|
66675
|
+
outcome: "selected",
|
|
66676
|
+
optionId: selected.optionId
|
|
66677
|
+
},
|
|
66678
|
+
option: selected,
|
|
66679
|
+
reason: "allow"
|
|
66680
|
+
};
|
|
66681
|
+
}
|
|
66682
|
+
const fallback2 = options.find((option2) => {
|
|
66683
|
+
const kind = getPermissionOptionKind(option2);
|
|
66684
|
+
return typeof kind === "string" && !isDenyOrRejectPermissionKind(kind);
|
|
66685
|
+
});
|
|
66686
|
+
if (fallback2) {
|
|
66687
|
+
return {
|
|
66688
|
+
outcome: {
|
|
66689
|
+
outcome: "selected",
|
|
66690
|
+
optionId: fallback2.optionId
|
|
66691
|
+
},
|
|
66692
|
+
option: fallback2,
|
|
66693
|
+
reason: "fallback"
|
|
66694
|
+
};
|
|
66695
|
+
}
|
|
66696
|
+
return null;
|
|
66697
|
+
}
|
|
66621
66698
|
function getBuiltinTitleGenerationDefaults(agentType) {
|
|
66622
66699
|
if (agentType === "claude") {
|
|
66623
66700
|
return {
|
|
@@ -76319,6 +76396,37 @@ ${tailedOutput}` : null;
|
|
|
76319
76396
|
machineId
|
|
76320
76397
|
});
|
|
76321
76398
|
}
|
|
76399
|
+
async loginWithApiKey(apiKey, machineName) {
|
|
76400
|
+
const accessToken = apiKey.trim();
|
|
76401
|
+
if (!accessToken) {
|
|
76402
|
+
return {
|
|
76403
|
+
success: false,
|
|
76404
|
+
error: "Missing API key for --auth login"
|
|
76405
|
+
};
|
|
76406
|
+
}
|
|
76407
|
+
const validation2 = await this.validateToken(accessToken);
|
|
76408
|
+
if (!validation2.valid || !validation2.user || !validation2.userId) {
|
|
76409
|
+
return {
|
|
76410
|
+
success: false,
|
|
76411
|
+
error: "Invalid API key. Generate a new key and retry."
|
|
76412
|
+
};
|
|
76413
|
+
}
|
|
76414
|
+
const machineId = getSystemMachineId() || v4();
|
|
76415
|
+
const authInfo = {
|
|
76416
|
+
token: accessToken,
|
|
76417
|
+
user: validation2.user,
|
|
76418
|
+
machine: {
|
|
76419
|
+
machineName,
|
|
76420
|
+
machineId
|
|
76421
|
+
}
|
|
76422
|
+
};
|
|
76423
|
+
await saveAuthInfo(accessToken, authInfo.user, authInfo.machine);
|
|
76424
|
+
setSentryUser(authInfo.user);
|
|
76425
|
+
return {
|
|
76426
|
+
success: true,
|
|
76427
|
+
...authInfo
|
|
76428
|
+
};
|
|
76429
|
+
}
|
|
76322
76430
|
async login(machineName) {
|
|
76323
76431
|
const machineId = getSystemMachineId() || v4();
|
|
76324
76432
|
this.logger.debug(`[device-auth] siteUrl=${this.siteUrl} serverUrl=${this.serverUrl}`);
|
|
@@ -76629,6 +76737,25 @@ ${tailedOutput}` : null;
|
|
|
76629
76737
|
machine: loginResult.machine
|
|
76630
76738
|
};
|
|
76631
76739
|
}
|
|
76740
|
+
async function performLoginWithApiKey(authClient, logger2, options) {
|
|
76741
|
+
const machineNameOverride = options.machineName?.trim();
|
|
76742
|
+
const machineName = machineNameOverride || os__default.hostname();
|
|
76743
|
+
logger2.info("Using provided API key for non-interactive login.");
|
|
76744
|
+
logger2.info(" Machine Name: " + chalk.cyan(machineName));
|
|
76745
|
+
const loginResult = await authClient.loginWithApiKey(options.apiKey, machineName);
|
|
76746
|
+
if (!loginResult.success) {
|
|
76747
|
+
return {
|
|
76748
|
+
success: false,
|
|
76749
|
+
error: loginResult.error
|
|
76750
|
+
};
|
|
76751
|
+
}
|
|
76752
|
+
return {
|
|
76753
|
+
success: true,
|
|
76754
|
+
token: loginResult.token,
|
|
76755
|
+
user: loginResult.user,
|
|
76756
|
+
machine: loginResult.machine
|
|
76757
|
+
};
|
|
76758
|
+
}
|
|
76632
76759
|
const version$1 = "1.25.4";
|
|
76633
76760
|
var lookup = [];
|
|
76634
76761
|
var revLookup = [];
|
|
@@ -88969,12 +89096,11 @@ ${val.stack}`;
|
|
|
88969
89096
|
const flock = this.metaFlock;
|
|
88970
89097
|
const currentVersion = flock.version();
|
|
88971
89098
|
if (this.lastPersistedVersion && this.versionsEqual(currentVersion, this.lastPersistedVersion)) return;
|
|
88972
|
-
const fullBundle = flock.exportJson();
|
|
88973
|
-
const encoded = Flock.fromJson(fullBundle, flock.peerId()).exportFile();
|
|
88974
89099
|
if (!this.storage) {
|
|
88975
89100
|
this.lastPersistedVersion = currentVersion;
|
|
88976
89101
|
return;
|
|
88977
89102
|
}
|
|
89103
|
+
const encoded = flock.exportFile();
|
|
88978
89104
|
await this.storage.save({
|
|
88979
89105
|
type: "meta",
|
|
88980
89106
|
update: encoded
|
|
@@ -104038,7 +104164,7 @@ stream:${scope2.streamId}`;
|
|
|
104038
104164
|
const DEFAULT_GATEWAY_BASE_URL = "https://streams-api.loro.dev";
|
|
104039
104165
|
const JSON_RPC_VERSION$1 = "2.0";
|
|
104040
104166
|
const LORO_STREAMS_RPC_VERSION = "1";
|
|
104041
|
-
const LORO_STREAMS_RPC_RETENTION_SECONDS =
|
|
104167
|
+
const LORO_STREAMS_RPC_RETENTION_SECONDS = 86400;
|
|
104042
104168
|
const LORO_RPC_REQUEST_STREAM_SEGMENT = "rpc:req";
|
|
104043
104169
|
const getLoroMachineRpcRequestStreamId = (workspaceId, machineId) => `${workspaceId}:${LORO_RPC_REQUEST_STREAM_SEGMENT}:${machineId}`;
|
|
104044
104170
|
const normalizeLoroGatewayBaseUrl = (baseUrl) => {
|
|
@@ -105181,8 +105307,9 @@ stream:${scope2.streamId}`;
|
|
|
105181
105307
|
const filtered = stripToolCallContentForHistory(kind ?? null, content);
|
|
105182
105308
|
return filtered.length ? filtered : void 0;
|
|
105183
105309
|
};
|
|
105184
|
-
const ensurePermissionRequestOnToolCall = async (doc, requestId, request,
|
|
105310
|
+
const ensurePermissionRequestOnToolCall = async (doc, requestId, request, _model) => {
|
|
105185
105311
|
const toolCallId = request.toolCall.toolCallId;
|
|
105312
|
+
let persisted = false;
|
|
105186
105313
|
await doc.updateHistory((history) => {
|
|
105187
105314
|
let updated = false;
|
|
105188
105315
|
history.forEach((entry2) => {
|
|
@@ -105197,25 +105324,23 @@ stream:${scope2.streamId}`;
|
|
|
105197
105324
|
return content;
|
|
105198
105325
|
});
|
|
105199
105326
|
if (entryUpdated) {
|
|
105327
|
+
persisted = true;
|
|
105200
105328
|
writeEntryItems(entry2, nextContents);
|
|
105201
105329
|
}
|
|
105202
105330
|
});
|
|
105203
105331
|
if (!updated) {
|
|
105204
|
-
history.
|
|
105205
|
-
|
|
105206
|
-
|
|
105207
|
-
|
|
105332
|
+
const latestEntry = history[history.length - 1];
|
|
105333
|
+
if (latestEntry?.role === "assistant" && latestEntry.finished !== true && typeof latestEntry.endedAt !== "number") {
|
|
105334
|
+
persisted = true;
|
|
105335
|
+
writeEntryItems(latestEntry, [
|
|
105336
|
+
...readEntryItems(latestEntry),
|
|
105208
105337
|
buildToolCallFromPermissionRequest(requestId, request)
|
|
105209
|
-
]
|
|
105210
|
-
|
|
105211
|
-
read: void 0,
|
|
105212
|
-
userId: void 0,
|
|
105213
|
-
modelInfo: model,
|
|
105214
|
-
fileDiff: []
|
|
105215
|
-
});
|
|
105338
|
+
]);
|
|
105339
|
+
}
|
|
105216
105340
|
}
|
|
105217
105341
|
return history;
|
|
105218
105342
|
});
|
|
105343
|
+
return persisted;
|
|
105219
105344
|
};
|
|
105220
105345
|
const updatePermissionOutcomeInHistory = async (doc, requestId, outcome, _logger) => {
|
|
105221
105346
|
await doc.updateHistory((history) => {
|
|
@@ -106300,9 +106425,9 @@ stream:${scope2.streamId}`;
|
|
|
106300
106425
|
};
|
|
106301
106426
|
const BuiltinACPSetting = {
|
|
106302
106427
|
claude: {
|
|
106303
|
-
packageName: "
|
|
106304
|
-
version: "0.
|
|
106305
|
-
binName: "
|
|
106428
|
+
packageName: "@agentclientprotocol/claude-agent-acp",
|
|
106429
|
+
version: "0.29.0",
|
|
106430
|
+
binName: "claude-agent-acp"
|
|
106306
106431
|
},
|
|
106307
106432
|
codex: {
|
|
106308
106433
|
packageName: "acp-extension-codex",
|
|
@@ -106454,7 +106579,10 @@ stream:${scope2.streamId}`;
|
|
|
106454
106579
|
if (fs__default.existsSync(path__default.join(installScopedNodeModules, "package.json"))) {
|
|
106455
106580
|
return true;
|
|
106456
106581
|
}
|
|
106457
|
-
|
|
106582
|
+
let installNodeModulesRoot = path__default.dirname(packageRoot);
|
|
106583
|
+
if (path__default.basename(installNodeModulesRoot).startsWith("@")) {
|
|
106584
|
+
installNodeModulesRoot = path__default.dirname(installNodeModulesRoot);
|
|
106585
|
+
}
|
|
106458
106586
|
const hoistedDependencyPath = path__default.join(installNodeModulesRoot, ...depSegments);
|
|
106459
106587
|
return fs__default.existsSync(path__default.join(hoistedDependencyPath, "package.json"));
|
|
106460
106588
|
}
|
|
@@ -107830,6 +107958,19 @@ main().catch(() => {});
|
|
|
107830
107958
|
return `!node "${helperPath}"`;
|
|
107831
107959
|
};
|
|
107832
107960
|
const SAFE_SESSION_ID_RE$1 = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
107961
|
+
const COMMON_BASE_BRANCH_NAMES = /* @__PURE__ */ new Set([
|
|
107962
|
+
"main",
|
|
107963
|
+
"master",
|
|
107964
|
+
"dev",
|
|
107965
|
+
"develop",
|
|
107966
|
+
"development",
|
|
107967
|
+
"trunk",
|
|
107968
|
+
"release",
|
|
107969
|
+
"staging",
|
|
107970
|
+
"stage",
|
|
107971
|
+
"prod",
|
|
107972
|
+
"production"
|
|
107973
|
+
]);
|
|
107833
107974
|
function assertSafeSessionId$1(sessionId) {
|
|
107834
107975
|
if (!SAFE_SESSION_ID_RE$1.test(sessionId)) {
|
|
107835
107976
|
throw new Error(`Invalid sessionId ${JSON.stringify(sessionId)}: expected /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/`);
|
|
@@ -108517,6 +108658,23 @@ path=/${options.repoFullName}.git
|
|
|
108517
108658
|
getDefaultSessionBranchName(sessionId) {
|
|
108518
108659
|
return `session/${sessionId.slice(0, 8)}`;
|
|
108519
108660
|
}
|
|
108661
|
+
normalizeBranchName(branchName) {
|
|
108662
|
+
return branchName.trim().replace(/^refs\/heads\//, "").replace(/^origin\//, "");
|
|
108663
|
+
}
|
|
108664
|
+
isCommonBaseBranchName(branchName) {
|
|
108665
|
+
const normalized = this.normalizeBranchName(branchName).toLowerCase();
|
|
108666
|
+
if (!normalized) {
|
|
108667
|
+
return false;
|
|
108668
|
+
}
|
|
108669
|
+
if (COMMON_BASE_BRANCH_NAMES.has(normalized)) {
|
|
108670
|
+
return true;
|
|
108671
|
+
}
|
|
108672
|
+
return normalized.startsWith("release/") || normalized.startsWith("releases/");
|
|
108673
|
+
}
|
|
108674
|
+
shouldReuseExistingBaseBranch(branchName) {
|
|
108675
|
+
const trimmed = branchName?.trim();
|
|
108676
|
+
return !!trimmed && !this.isCommonBaseBranchName(trimmed);
|
|
108677
|
+
}
|
|
108520
108678
|
async hasLocalBranch(branchName) {
|
|
108521
108679
|
const sanitized = branchName.trim();
|
|
108522
108680
|
if (!sanitized) {
|
|
@@ -108544,6 +108702,33 @@ path=/${options.repoFullName}.git
|
|
|
108544
108702
|
}
|
|
108545
108703
|
return null;
|
|
108546
108704
|
}
|
|
108705
|
+
async resolveReusableBaseBranch(baseBranch) {
|
|
108706
|
+
const trimmed = baseBranch?.trim();
|
|
108707
|
+
if (!this.shouldReuseExistingBaseBranch(trimmed)) {
|
|
108708
|
+
return null;
|
|
108709
|
+
}
|
|
108710
|
+
if (await this.hasLocalBranch(trimmed)) {
|
|
108711
|
+
return {
|
|
108712
|
+
branchName: trimmed
|
|
108713
|
+
};
|
|
108714
|
+
}
|
|
108715
|
+
const remoteBranch = `origin/${trimmed}`;
|
|
108716
|
+
if (this.repoUrl && await this.hasCommitish(remoteBranch)) {
|
|
108717
|
+
return {
|
|
108718
|
+
branchName: trimmed,
|
|
108719
|
+
startPoint: remoteBranch
|
|
108720
|
+
};
|
|
108721
|
+
}
|
|
108722
|
+
return null;
|
|
108723
|
+
}
|
|
108724
|
+
shouldPreserveRemovedBranch(branchName, options) {
|
|
108725
|
+
const baseBranchName = options?.baseBranchName?.trim();
|
|
108726
|
+
return !!baseBranchName && branchName === baseBranchName && this.shouldReuseExistingBaseBranch(baseBranchName);
|
|
108727
|
+
}
|
|
108728
|
+
isBranchAlreadyCheckedOutError(error2) {
|
|
108729
|
+
const message = formatErrorMessage(error2).toLowerCase();
|
|
108730
|
+
return message.includes("is already checked out at");
|
|
108731
|
+
}
|
|
108547
108732
|
async createWorktree(sessionId, baseBranch, restoreBranchName) {
|
|
108548
108733
|
return withRepoLock(this.repoId, async () => {
|
|
108549
108734
|
assertSafeSessionId$1(sessionId);
|
|
@@ -108551,6 +108736,7 @@ path=/${options.repoFullName}.git
|
|
|
108551
108736
|
const worktreePath = this.getWorktreeHostPath(sessionId);
|
|
108552
108737
|
const branchName = this.getDefaultSessionBranchName(sessionId);
|
|
108553
108738
|
const resolvedBase = await this.resolveBaseRef(baseBranch);
|
|
108739
|
+
const reusableBaseBranch = await this.resolveReusableBaseBranch(baseBranch);
|
|
108554
108740
|
if (fs$4.existsSync(worktreePath)) {
|
|
108555
108741
|
this.ensureWorktreeGitdirIsRelative(sessionId);
|
|
108556
108742
|
const info2 = await this.getWorktreeInfo(sessionId);
|
|
@@ -108566,6 +108752,40 @@ path=/${options.repoFullName}.git
|
|
|
108566
108752
|
worktreePath,
|
|
108567
108753
|
existingBranchName
|
|
108568
108754
|
], this.bareGitDir);
|
|
108755
|
+
} else if (reusableBaseBranch) {
|
|
108756
|
+
this.logger.debug(`[${this.repoId}] Creating worktree from selected branch: ${reusableBaseBranch.branchName}`);
|
|
108757
|
+
try {
|
|
108758
|
+
if (reusableBaseBranch.startPoint) {
|
|
108759
|
+
await this.runGit([
|
|
108760
|
+
"worktree",
|
|
108761
|
+
"add",
|
|
108762
|
+
"-b",
|
|
108763
|
+
reusableBaseBranch.branchName,
|
|
108764
|
+
worktreePath,
|
|
108765
|
+
reusableBaseBranch.startPoint
|
|
108766
|
+
], this.bareGitDir);
|
|
108767
|
+
} else {
|
|
108768
|
+
await this.runGit([
|
|
108769
|
+
"worktree",
|
|
108770
|
+
"add",
|
|
108771
|
+
worktreePath,
|
|
108772
|
+
reusableBaseBranch.branchName
|
|
108773
|
+
], this.bareGitDir);
|
|
108774
|
+
}
|
|
108775
|
+
} catch (error2) {
|
|
108776
|
+
if (!this.isBranchAlreadyCheckedOutError(error2)) {
|
|
108777
|
+
throw error2;
|
|
108778
|
+
}
|
|
108779
|
+
this.logger.debug(`[${this.repoId}] Selected branch already checked out; creating session branch instead (branch=${branchName} base=${resolvedBase})`);
|
|
108780
|
+
await this.runGit([
|
|
108781
|
+
"worktree",
|
|
108782
|
+
"add",
|
|
108783
|
+
"-b",
|
|
108784
|
+
branchName,
|
|
108785
|
+
worktreePath,
|
|
108786
|
+
resolvedBase
|
|
108787
|
+
], this.bareGitDir);
|
|
108788
|
+
}
|
|
108569
108789
|
} else {
|
|
108570
108790
|
this.logger.debug(`[${this.repoId}] Creating new worktree (sessionId=${sessionId} branch=${branchName} base=${resolvedBase}): ${worktreePath}`);
|
|
108571
108791
|
await this.runGit([
|
|
@@ -108614,7 +108834,7 @@ path=/${options.repoFullName}.git
|
|
|
108614
108834
|
isClean
|
|
108615
108835
|
};
|
|
108616
108836
|
}
|
|
108617
|
-
async removeWorktree(sessionId, force = false, branchName) {
|
|
108837
|
+
async removeWorktree(sessionId, force = false, branchName, options) {
|
|
108618
108838
|
return withRepoLock(this.repoId, async () => {
|
|
108619
108839
|
const resolvedBranchName = await this.removeWorktreeInternal(sessionId, {
|
|
108620
108840
|
force,
|
|
@@ -108622,6 +108842,10 @@ path=/${options.repoFullName}.git
|
|
|
108622
108842
|
branchName
|
|
108623
108843
|
});
|
|
108624
108844
|
if (resolvedBranchName) {
|
|
108845
|
+
if (this.shouldPreserveRemovedBranch(resolvedBranchName, options)) {
|
|
108846
|
+
this.logger.debug(`[${this.repoId}] Preserving reused base branch after worktree removal: ${resolvedBranchName}`);
|
|
108847
|
+
return;
|
|
108848
|
+
}
|
|
108625
108849
|
await this.cleanupBranch(resolvedBranchName);
|
|
108626
108850
|
}
|
|
108627
108851
|
});
|
|
@@ -108897,6 +109121,7 @@ path=/${options.repoFullName}.git
|
|
|
108897
109121
|
pendingContextWindowHandlers: /* @__PURE__ */ new Set(),
|
|
108898
109122
|
pendingUsageHandlers: /* @__PURE__ */ new Set(),
|
|
108899
109123
|
permissionWaitMs: 0,
|
|
109124
|
+
autoApprovePermissions: false,
|
|
108900
109125
|
pendingUnread: false,
|
|
108901
109126
|
heartbeat: null,
|
|
108902
109127
|
lastActivityMs: Date.now(),
|
|
@@ -120291,6 +120516,15 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
|
|
|
120291
120516
|
if (dispatchAction.type !== "dispatch") {
|
|
120292
120517
|
return;
|
|
120293
120518
|
}
|
|
120519
|
+
const requesterUserId = nextUserTurn.userId ?? freshMeta.userId;
|
|
120520
|
+
const access = await this.deps.canUseMachine({
|
|
120521
|
+
sessionId,
|
|
120522
|
+
requesterUserId
|
|
120523
|
+
});
|
|
120524
|
+
if (!access.allowed) {
|
|
120525
|
+
await this.markDispatchAccessDenied(sessionId, sessionDoc, nextUserTurn.id, access.reason);
|
|
120526
|
+
return;
|
|
120527
|
+
}
|
|
120294
120528
|
if (dispatchAction.mode === "create") {
|
|
120295
120529
|
const createRequest = await this.buildCreateRequestFromHistoryEntry(freshMeta, nextUserTurn);
|
|
120296
120530
|
await this.deps.executionService.startSession(createRequest);
|
|
@@ -120299,6 +120533,38 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
|
|
|
120299
120533
|
await this.deps.executionService.continueSession(chatRequest);
|
|
120300
120534
|
}
|
|
120301
120535
|
}
|
|
120536
|
+
getAccessDeniedMessage(reason) {
|
|
120537
|
+
switch (reason) {
|
|
120538
|
+
case "requester_not_member":
|
|
120539
|
+
return "The requester is not a member of this workspace.";
|
|
120540
|
+
case "machine_not_registered":
|
|
120541
|
+
return "This machine is not registered for workspace access.";
|
|
120542
|
+
case "not_visible":
|
|
120543
|
+
return "This machine is private to its owner.";
|
|
120544
|
+
case "access_unavailable":
|
|
120545
|
+
return "Machine access could not be verified.";
|
|
120546
|
+
}
|
|
120547
|
+
}
|
|
120548
|
+
async markDispatchAccessDenied(sessionId, sessionDoc, userTurnId, reason) {
|
|
120549
|
+
const message = this.getAccessDeniedMessage(reason);
|
|
120550
|
+
this.deps.logger.warn(`[${sessionId}] Refusing dispatch: ${message}`);
|
|
120551
|
+
await sessionDoc.updateHistory((history) => history.map((entry2) => entry2.id === userTurnId && entry2.role === "user" ? {
|
|
120552
|
+
...entry2,
|
|
120553
|
+
status: "failed",
|
|
120554
|
+
read: getLegacyReadForSessionHistoryStatus("failed")
|
|
120555
|
+
} : entry2));
|
|
120556
|
+
await this.deps.workspaceDocument.repo.upsertDocMeta?.(getSessionRoomId(sessionId), {
|
|
120557
|
+
latestUserMsgId: userTurnId,
|
|
120558
|
+
lastHandledUserMsgId: userTurnId,
|
|
120559
|
+
processingUserMsgId: void 0,
|
|
120560
|
+
dispatchError: {
|
|
120561
|
+
code: "machine_access_denied",
|
|
120562
|
+
message,
|
|
120563
|
+
at: getServerNow()
|
|
120564
|
+
}
|
|
120565
|
+
});
|
|
120566
|
+
await sessionDoc.setStatus(SessionStatusFactory.idle());
|
|
120567
|
+
}
|
|
120302
120568
|
async maybeHandleCancelRequest(sessionId) {
|
|
120303
120569
|
const sessionDoc = await this.deps.workspaceDocument.getOrCreateSessionDoc(sessionId);
|
|
120304
120570
|
const meta = await sessionDoc.getMetaState();
|
|
@@ -120869,6 +121135,121 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
|
|
|
120869
121135
|
}
|
|
120870
121136
|
}
|
|
120871
121137
|
}
|
|
121138
|
+
const WorkspaceSummarySchema = object({
|
|
121139
|
+
id: string$2(),
|
|
121140
|
+
name: string$2(),
|
|
121141
|
+
slug: string$2().nullable(),
|
|
121142
|
+
role: string$2()
|
|
121143
|
+
});
|
|
121144
|
+
const WorkspaceGitHubRepositorySchema = object({
|
|
121145
|
+
id: number$3(),
|
|
121146
|
+
name: string$2(),
|
|
121147
|
+
fullName: string$2(),
|
|
121148
|
+
private: boolean()
|
|
121149
|
+
});
|
|
121150
|
+
const WorkspaceListResultSchema = discriminatedUnion("valid", [
|
|
121151
|
+
object({
|
|
121152
|
+
valid: literal(false),
|
|
121153
|
+
userId: _null(),
|
|
121154
|
+
workspaces: array$3(WorkspaceSummarySchema)
|
|
121155
|
+
}),
|
|
121156
|
+
object({
|
|
121157
|
+
valid: literal(true),
|
|
121158
|
+
userId: string$2(),
|
|
121159
|
+
workspaces: array$3(WorkspaceSummarySchema)
|
|
121160
|
+
})
|
|
121161
|
+
]);
|
|
121162
|
+
const RegisterSessionOwnerResultSchema = object({
|
|
121163
|
+
success: literal(true),
|
|
121164
|
+
existing: boolean()
|
|
121165
|
+
});
|
|
121166
|
+
const RegisterMachineAccessResultSchema = object({
|
|
121167
|
+
success: literal(true),
|
|
121168
|
+
existing: boolean(),
|
|
121169
|
+
sharedWithTeam: boolean()
|
|
121170
|
+
});
|
|
121171
|
+
const MachineAccessCheckResultSchema = discriminatedUnion("allowed", [
|
|
121172
|
+
object({
|
|
121173
|
+
allowed: literal(true)
|
|
121174
|
+
}),
|
|
121175
|
+
object({
|
|
121176
|
+
allowed: literal(false),
|
|
121177
|
+
reason: _enum([
|
|
121178
|
+
"requester_not_member",
|
|
121179
|
+
"machine_not_registered",
|
|
121180
|
+
"not_visible"
|
|
121181
|
+
])
|
|
121182
|
+
})
|
|
121183
|
+
]);
|
|
121184
|
+
const WorkspaceGitHubRepositoryListResultSchema = discriminatedUnion("valid", [
|
|
121185
|
+
object({
|
|
121186
|
+
valid: literal(false),
|
|
121187
|
+
repositories: array$3(WorkspaceGitHubRepositorySchema)
|
|
121188
|
+
}),
|
|
121189
|
+
object({
|
|
121190
|
+
valid: literal(true),
|
|
121191
|
+
repositories: array$3(WorkspaceGitHubRepositorySchema)
|
|
121192
|
+
})
|
|
121193
|
+
]);
|
|
121194
|
+
function createAuthConvexClient() {
|
|
121195
|
+
if (!LODY_AUTH_URL) {
|
|
121196
|
+
throw new Error("LODY_AUTH_URL is not defined.");
|
|
121197
|
+
}
|
|
121198
|
+
return new ConvexHttpClient(LODY_AUTH_URL);
|
|
121199
|
+
}
|
|
121200
|
+
async function listWorkspacesForToken(token2) {
|
|
121201
|
+
const client = createAuthConvexClient();
|
|
121202
|
+
const raw = await client.query(api.deviceAuth.listMyWorkspacesForCliToken, {
|
|
121203
|
+
token: token2
|
|
121204
|
+
});
|
|
121205
|
+
const parsed = WorkspaceListResultSchema.parse(raw);
|
|
121206
|
+
if (!parsed.valid) {
|
|
121207
|
+
throw new Error("CLI token is invalid or expired. Run `lody login` again.");
|
|
121208
|
+
}
|
|
121209
|
+
return parsed.workspaces;
|
|
121210
|
+
}
|
|
121211
|
+
async function listWorkspaceGitHubRepositoriesForCliToken(input2) {
|
|
121212
|
+
const client = createAuthConvexClient();
|
|
121213
|
+
const raw = await client.query(api.github.listWorkspaceRepositoriesForCliToken, {
|
|
121214
|
+
cliToken: input2.token,
|
|
121215
|
+
workspaceId: input2.workspaceId
|
|
121216
|
+
});
|
|
121217
|
+
const parsed = WorkspaceGitHubRepositoryListResultSchema.parse(raw);
|
|
121218
|
+
if (!parsed.valid) {
|
|
121219
|
+
throw new Error("CLI token is invalid or expired. Run `lody login` again.");
|
|
121220
|
+
}
|
|
121221
|
+
return parsed.repositories;
|
|
121222
|
+
}
|
|
121223
|
+
async function registerSessionOwnerForCliToken(input2) {
|
|
121224
|
+
const client = createAuthConvexClient();
|
|
121225
|
+
const raw = await client.mutation(api.sessions.registerSessionOwnerFromCliToken, {
|
|
121226
|
+
cliToken: input2.token,
|
|
121227
|
+
workspaceId: input2.workspaceId,
|
|
121228
|
+
sessionId: input2.sessionId,
|
|
121229
|
+
cliType: input2.cliType,
|
|
121230
|
+
repoFullName: input2.repoFullName
|
|
121231
|
+
});
|
|
121232
|
+
return RegisterSessionOwnerResultSchema.parse(raw);
|
|
121233
|
+
}
|
|
121234
|
+
async function registerMachineAccessForCliToken(input2) {
|
|
121235
|
+
const client = createAuthConvexClient();
|
|
121236
|
+
const raw = await client.mutation(api.machines.upsertMachineRegistrationFromCliToken, {
|
|
121237
|
+
cliToken: input2.token,
|
|
121238
|
+
workspaceId: input2.workspaceId,
|
|
121239
|
+
machineId: input2.machineId
|
|
121240
|
+
});
|
|
121241
|
+
return RegisterMachineAccessResultSchema.parse(raw);
|
|
121242
|
+
}
|
|
121243
|
+
async function canUseMachineForCliToken(input2) {
|
|
121244
|
+
const client = createAuthConvexClient();
|
|
121245
|
+
const raw = await client.query(api.machines.canUseMachineFromCliToken, {
|
|
121246
|
+
cliToken: input2.token,
|
|
121247
|
+
workspaceId: input2.workspaceId,
|
|
121248
|
+
machineId: input2.machineId,
|
|
121249
|
+
requesterUserId: input2.requesterUserId
|
|
121250
|
+
});
|
|
121251
|
+
return MachineAccessCheckResultSchema.parse(raw);
|
|
121252
|
+
}
|
|
120872
121253
|
const SESSION_IMAGE_MIME_TYPE_BY_EXTENSION = {
|
|
120873
121254
|
png: "image/png",
|
|
120874
121255
|
jpg: "image/jpeg",
|
|
@@ -121013,7 +121394,23 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
|
|
|
121013
121394
|
workspaceId: this.workspaceId,
|
|
121014
121395
|
workspaceDocument: this.workspaceDocument,
|
|
121015
121396
|
sessionManager: this.sessionManager,
|
|
121016
|
-
executionService: this.executionService
|
|
121397
|
+
executionService: this.executionService,
|
|
121398
|
+
canUseMachine: async ({ requesterUserId, sessionId }) => {
|
|
121399
|
+
try {
|
|
121400
|
+
return await canUseMachineForCliToken({
|
|
121401
|
+
token: this.token,
|
|
121402
|
+
workspaceId: this.workspaceId,
|
|
121403
|
+
machineId: this.machineId,
|
|
121404
|
+
requesterUserId
|
|
121405
|
+
});
|
|
121406
|
+
} catch (error2) {
|
|
121407
|
+
this.logger.error(`[${sessionId}] Failed to verify machine access: ${formatErrorMessage(error2)}`);
|
|
121408
|
+
return {
|
|
121409
|
+
allowed: false,
|
|
121410
|
+
reason: "access_unavailable"
|
|
121411
|
+
};
|
|
121412
|
+
}
|
|
121413
|
+
}
|
|
121017
121414
|
});
|
|
121018
121415
|
this.setupSessionEventHandlers();
|
|
121019
121416
|
this.setupArchiveWatcher();
|
|
@@ -121313,13 +121710,25 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
|
|
|
121313
121710
|
}
|
|
121314
121711
|
const modeId = config2.modeId;
|
|
121315
121712
|
const modelId = config2.modelId;
|
|
121713
|
+
const agentConfigOptions = session.agentClient.getConfigOptions?.() ?? [];
|
|
121714
|
+
const modeConfigId = agentConfigOptions.find((o) => o.category === "mode")?.id ?? "mode";
|
|
121715
|
+
const modelConfigId = agentConfigOptions.find((o) => o.category === "model")?.id ?? "model";
|
|
121716
|
+
const configModeId = config2.configOptionValues?.[modeConfigId];
|
|
121717
|
+
const effectiveModeId = modeId ?? configModeId;
|
|
121718
|
+
const autoApprovePermissions = isFullAccessModeId(effectiveModeId);
|
|
121719
|
+
this.store.get(sessionId).autoApprovePermissions = autoApprovePermissions;
|
|
121720
|
+
this.logger.debug(`[${sessionId}] applyAcpModeAndModel: autoApprovePermissions=${autoApprovePermissions}`);
|
|
121316
121721
|
if (modeId) {
|
|
121317
|
-
|
|
121318
|
-
|
|
121319
|
-
|
|
121320
|
-
this.logger.debug(`[${sessionId}] applyAcpModeAndModel: mode
|
|
121321
|
-
|
|
121322
|
-
|
|
121722
|
+
if (modeId === LODY_FULL_ACCESS_MODE_ID) {
|
|
121723
|
+
this.logger.debug(`[${sessionId}] applyAcpModeAndModel: using Lody Full Access mode without sending setSessionMode to agent`);
|
|
121724
|
+
} else {
|
|
121725
|
+
this.logger.debug(`[${sessionId}] applyAcpModeAndModel: setting mode (modeId=${modeId})`);
|
|
121726
|
+
try {
|
|
121727
|
+
await session.agentClient.setSessionMode?.(acpSessionId, modeId);
|
|
121728
|
+
this.logger.debug(`[${sessionId}] applyAcpModeAndModel: mode set complete`);
|
|
121729
|
+
} catch (err2) {
|
|
121730
|
+
this.logger.debug(`[${sessionId}] applyAcpModeAndModel: setSessionMode failed for "${modeId}", skipping (mode may not be supported by agent): ${err2}`);
|
|
121731
|
+
}
|
|
121323
121732
|
}
|
|
121324
121733
|
}
|
|
121325
121734
|
if (modelId) {
|
|
@@ -121332,12 +121741,9 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
|
|
|
121332
121741
|
}
|
|
121333
121742
|
}
|
|
121334
121743
|
if (config2.configOptionValues && session.agentClient) {
|
|
121335
|
-
const agentConfigOptions = session.agentClient.getConfigOptions();
|
|
121336
|
-
const modeConfigId = agentConfigOptions.find((o) => o.category === "mode")?.id;
|
|
121337
|
-
const modelConfigId = agentConfigOptions.find((o) => o.category === "model")?.id;
|
|
121338
121744
|
for (const [configId, value] of Object.entries(config2.configOptionValues)) {
|
|
121339
121745
|
if (configId === modeConfigId) {
|
|
121340
|
-
if (!modeId) {
|
|
121746
|
+
if (!modeId && value !== LODY_FULL_ACCESS_MODE_ID) {
|
|
121341
121747
|
this.logger.debug(`[${sessionId}] applyAcpModeAndModel: setting mode via setSessionMode from configOption (${configId}=${value})`);
|
|
121342
121748
|
try {
|
|
121343
121749
|
await session.agentClient.setSessionMode?.(acpSessionId, value);
|
|
@@ -121650,6 +122056,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
|
|
|
121650
122056
|
needToArchiveSessions: {},
|
|
121651
122057
|
raceLimits: {}
|
|
121652
122058
|
});
|
|
122059
|
+
await this.registerMachineAccess();
|
|
121653
122060
|
this.logger.debug("Machine re-registered in machine meta");
|
|
121654
122061
|
} catch (error2) {
|
|
121655
122062
|
this.logger.error(`Failed to ensure machine registration after reconnect: ${error2}`);
|
|
@@ -121927,6 +122334,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
|
|
|
121927
122334
|
}
|
|
121928
122335
|
const repoFullName = sessionMeta?.project?.kind === "local" ? void 0 : sessionMeta?.repoFullName ?? (request && typeof request === "object" && "repoFullName" in request ? request.repoFullName : void 0);
|
|
121929
122336
|
const branchName = sessionMeta?.branchName ?? (request && typeof request === "object" && "branchName" in request ? request.branchName : void 0);
|
|
122337
|
+
const baseBranchName = sessionMeta?.baseBranch?.trim() || void 0;
|
|
121930
122338
|
if (repoFullName) {
|
|
121931
122339
|
try {
|
|
121932
122340
|
const repoId = deriveRepoIdFromGitHubRepo$1(repoFullName);
|
|
@@ -121934,7 +122342,9 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
|
|
|
121934
122342
|
repoId,
|
|
121935
122343
|
logger: this.logger
|
|
121936
122344
|
});
|
|
121937
|
-
await worktreeManager.removeWorktree(sessionId, true, branchName
|
|
122345
|
+
await worktreeManager.removeWorktree(sessionId, true, branchName, {
|
|
122346
|
+
baseBranchName
|
|
122347
|
+
});
|
|
121938
122348
|
} catch (error2) {
|
|
121939
122349
|
this.logger.debug(`[${sessionId}] Failed to remove worktree: ${formatErrorMessage(error2)}`);
|
|
121940
122350
|
}
|
|
@@ -122286,6 +122696,17 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
|
|
|
122286
122696
|
this.store.beginTurn(sessionId, turnId);
|
|
122287
122697
|
return turnId;
|
|
122288
122698
|
}
|
|
122699
|
+
async registerMachineAccess() {
|
|
122700
|
+
try {
|
|
122701
|
+
await registerMachineAccessForCliToken({
|
|
122702
|
+
token: this.token,
|
|
122703
|
+
workspaceId: this.workspaceId,
|
|
122704
|
+
machineId: this.machineId
|
|
122705
|
+
});
|
|
122706
|
+
} catch (error2) {
|
|
122707
|
+
this.logger.error(`Failed to register machine access: ${formatErrorMessage(error2)}`);
|
|
122708
|
+
}
|
|
122709
|
+
}
|
|
122289
122710
|
registerMachine() {
|
|
122290
122711
|
void (async () => {
|
|
122291
122712
|
const supportsStreamsRpc = !!this.machineRpcServer;
|
|
@@ -122310,6 +122731,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
|
|
|
122310
122731
|
lastSeen: machineMeta?.lastSeen,
|
|
122311
122732
|
raceLimits: machineMeta?.raceLimits ?? {}
|
|
122312
122733
|
});
|
|
122734
|
+
await this.registerMachineAccess();
|
|
122313
122735
|
this.startMachineHeartbeat();
|
|
122314
122736
|
this.logger.debug(`Machine registered with name: ${this.machineName}`);
|
|
122315
122737
|
})().catch((error2) => {
|
|
@@ -122686,6 +123108,22 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
|
|
|
122686
123108
|
cpuUsagePercent
|
|
122687
123109
|
};
|
|
122688
123110
|
}
|
|
123111
|
+
tryAutoApprovePermission(sessionId, request) {
|
|
123112
|
+
const state2 = this.store.get(sessionId);
|
|
123113
|
+
if (!state2.autoApprovePermissions) {
|
|
123114
|
+
return null;
|
|
123115
|
+
}
|
|
123116
|
+
const decision = selectAutoApprovePermissionDecision(request.options);
|
|
123117
|
+
if (!decision) {
|
|
123118
|
+
this.logger.debug(`[${sessionId}] Full Access auto-approval skipped because no allow or fallback-safe option was available`);
|
|
123119
|
+
return null;
|
|
123120
|
+
}
|
|
123121
|
+
if (decision.reason === "fallback") {
|
|
123122
|
+
const kind = decision.option.kind;
|
|
123123
|
+
this.logger.warn(`[${sessionId}] Full Access auto-approval selected a non-standard permission option (optionId=${decision.option.optionId}, kind=${String(kind)})`);
|
|
123124
|
+
}
|
|
123125
|
+
return decision.outcome;
|
|
123126
|
+
}
|
|
122689
123127
|
async handleAgentPermissionRequest(sessionId, requestId, request, model) {
|
|
122690
123128
|
const toolTitle = request.toolCall.title?.trim();
|
|
122691
123129
|
const toolKind = typeof request.toolCall.kind === "string" ? request.toolCall.kind.trim() : void 0;
|
|
@@ -122696,10 +123134,10 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
|
|
|
122696
123134
|
let sessionTitle;
|
|
122697
123135
|
let metaUserId;
|
|
122698
123136
|
let historyUserId;
|
|
123137
|
+
let permissionRequestPersisted = false;
|
|
122699
123138
|
try {
|
|
122700
|
-
await ensurePermissionRequestOnToolCall(doc, requestId, request, model);
|
|
123139
|
+
permissionRequestPersisted = await ensurePermissionRequestOnToolCall(doc, requestId, request, model);
|
|
122701
123140
|
await doc.setLastMessageAt();
|
|
122702
|
-
await doc.setStatus(SessionStatusFactory.requestPermission(requestId, request.toolCall.toolCallId, request.toolCall.title ?? void 0));
|
|
122703
123141
|
const meta = await doc.getMetaState();
|
|
122704
123142
|
sessionTitle = meta?.title;
|
|
122705
123143
|
metaUserId = meta?.userId;
|
|
@@ -122716,6 +123154,31 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
|
|
|
122716
123154
|
} catch (error2) {
|
|
122717
123155
|
this.logger.error(`[${sessionId}] Failed to append permission request to history: ${formatErrorMessage(error2)}`);
|
|
122718
123156
|
}
|
|
123157
|
+
const autoApproveOutcome = this.tryAutoApprovePermission(sessionId, request);
|
|
123158
|
+
if (autoApproveOutcome) {
|
|
123159
|
+
try {
|
|
123160
|
+
await updatePermissionOutcomeInHistory(doc, requestId, autoApproveOutcome, this.logger);
|
|
123161
|
+
} catch (error2) {
|
|
123162
|
+
this.logger.error(`[${sessionId}] Failed to persist auto-approved permission outcome: ${formatErrorMessage(error2)}`);
|
|
123163
|
+
}
|
|
123164
|
+
this.logger.info(`[${sessionId}] Permission auto-approved by Full Access for ${permissionLabel}: optionId=${autoApproveOutcome.outcome === "selected" ? autoApproveOutcome.optionId : "cancelled"}`);
|
|
123165
|
+
return {
|
|
123166
|
+
outcome: autoApproveOutcome
|
|
123167
|
+
};
|
|
123168
|
+
}
|
|
123169
|
+
if (!permissionRequestPersisted) {
|
|
123170
|
+
this.logger.warn(`[${sessionId}] Permission request ${requestId} for tool call ${request.toolCall.toolCallId} could not be attached to an active assistant entry; cancelling to avoid waiting for an unobservable permission outcome`);
|
|
123171
|
+
return {
|
|
123172
|
+
outcome: {
|
|
123173
|
+
outcome: "cancelled"
|
|
123174
|
+
}
|
|
123175
|
+
};
|
|
123176
|
+
}
|
|
123177
|
+
try {
|
|
123178
|
+
await doc.setStatus(SessionStatusFactory.requestPermission(requestId, request.toolCall.toolCallId, request.toolCall.title ?? void 0));
|
|
123179
|
+
} catch (error2) {
|
|
123180
|
+
this.logger.error(`[${sessionId}] Failed to mark session as waiting for permission: ${formatErrorMessage(error2)}`);
|
|
123181
|
+
}
|
|
122719
123182
|
if (this.notificationService) {
|
|
122720
123183
|
const workspaceSlug = this.workspaceSlug?.trim() || this.workspaceId;
|
|
122721
123184
|
const userId = historyUserId ?? metaUserId ?? this.userId;
|
|
@@ -133469,7 +133932,7 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
|
|
|
133469
133932
|
];
|
|
133470
133933
|
}
|
|
133471
133934
|
return previous.concat(value);
|
|
133472
|
-
}).option("--debug", "enable debug output").option("--heartbeat-log", "output current timestamp every 5 seconds").action(async (options) => {
|
|
133935
|
+
}).option("--debug", "enable debug output").option("--heartbeat-log", "output current timestamp every 5 seconds").option("--auth <api-key>", "Use a CLI API key for non-interactive authentication").action(async (options) => {
|
|
133473
133936
|
createHybridLogger({
|
|
133474
133937
|
level: options.debug ? "debug" : "info"
|
|
133475
133938
|
});
|
|
@@ -133497,6 +133960,7 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
|
|
|
133497
133960
|
runtimeStateReporter.setStartupStage("bootstrap");
|
|
133498
133961
|
const electronBootstrapEnabled = process.env[ELECTRON_BOOTSTRAP_ENV] === "1";
|
|
133499
133962
|
const electronSessionToken = process.env[ELECTRON_SESSION_TOKEN_ENV]?.trim();
|
|
133963
|
+
const providedAuth = options.auth?.trim();
|
|
133500
133964
|
const cliAvailability = {
|
|
133501
133965
|
claude: checkClaude(),
|
|
133502
133966
|
codex: checkCodex()
|
|
@@ -133543,7 +134007,25 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
|
|
|
133543
134007
|
let machineId;
|
|
133544
134008
|
let machineName;
|
|
133545
134009
|
const existingAuth = authClient.getAuthInfo();
|
|
133546
|
-
if (
|
|
134010
|
+
if (options.auth !== void 0 && !providedAuth) {
|
|
134011
|
+
logger2.error("Missing API key for --auth.");
|
|
134012
|
+
process.exit(1);
|
|
134013
|
+
}
|
|
134014
|
+
if (providedAuth) {
|
|
134015
|
+
const loginResult = await performLoginWithApiKey(authClient, logger2, {
|
|
134016
|
+
apiKey: providedAuth,
|
|
134017
|
+
machineName: defaultMachineName
|
|
134018
|
+
});
|
|
134019
|
+
if (!loginResult.success) {
|
|
134020
|
+
logger2.error(`Login failed: ${loginResult.error}`);
|
|
134021
|
+
process.exit(1);
|
|
134022
|
+
}
|
|
134023
|
+
token2 = loginResult.token;
|
|
134024
|
+
userId = loginResult.user.id;
|
|
134025
|
+
machineId = loginResult.machine.machineId;
|
|
134026
|
+
machineName = loginResult.machine.machineName;
|
|
134027
|
+
logger2.success("Login successful via --auth.");
|
|
134028
|
+
} else if (existingAuth) {
|
|
133547
134029
|
logger2.debug("Found existing authentication, checking validity...");
|
|
133548
134030
|
const validation2 = await authClient.validateToken(existingAuth.token);
|
|
133549
134031
|
if (validation2.valid) {
|
|
@@ -133703,13 +134185,36 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
|
|
|
133703
134185
|
throw error2;
|
|
133704
134186
|
}
|
|
133705
134187
|
}
|
|
133706
|
-
const loginCommand = new Command("login").description("Login to Lody using device authorization flow").option("-d, --debug", "enable debug output").option("--machine-name <name>", "Machine name to register (defaults to hostname)").action(async (options) => {
|
|
134188
|
+
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) => {
|
|
133707
134189
|
if (options.debug) {
|
|
133708
134190
|
rootLogger.setDebug(true);
|
|
133709
134191
|
}
|
|
133710
134192
|
const logger2 = getLogger("login");
|
|
133711
134193
|
const authClient = new AuthClient(logger2);
|
|
134194
|
+
const providedAuth = options.auth?.trim();
|
|
133712
134195
|
try {
|
|
134196
|
+
if (options.auth !== void 0 && !providedAuth) {
|
|
134197
|
+
logger2.error("Missing API key for --auth.");
|
|
134198
|
+
process.exit(1);
|
|
134199
|
+
}
|
|
134200
|
+
if (providedAuth) {
|
|
134201
|
+
const result2 = await performLoginWithApiKey(authClient, logger2, {
|
|
134202
|
+
apiKey: providedAuth,
|
|
134203
|
+
machineName: options.machineName
|
|
134204
|
+
});
|
|
134205
|
+
if (result2.success) {
|
|
134206
|
+
logger2.success("\n" + chalk.green("\u2713") + " Successfully logged in!");
|
|
134207
|
+
logger2.info(" User: " + chalk.cyan(result2.user.name || result2.user.email));
|
|
134208
|
+
logger2.info(" Email: " + chalk.cyan(result2.user.email));
|
|
134209
|
+
} else {
|
|
134210
|
+
await reportError("login", result2.error, {
|
|
134211
|
+
message: "Login failed using --auth",
|
|
134212
|
+
logger: logger2
|
|
134213
|
+
});
|
|
134214
|
+
process.exit(1);
|
|
134215
|
+
}
|
|
134216
|
+
return;
|
|
134217
|
+
}
|
|
133713
134218
|
const existingAuth = authClient.getAuthInfo();
|
|
133714
134219
|
if (existingAuth) {
|
|
133715
134220
|
logger2.debug("Found existing authentication, checking validity...");
|
|
@@ -159300,84 +159805,6 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
|
|
|
159300
159805
|
restoreDefaultPrompts,
|
|
159301
159806
|
Separator
|
|
159302
159807
|
};
|
|
159303
|
-
const WorkspaceSummarySchema = object({
|
|
159304
|
-
id: string$2(),
|
|
159305
|
-
name: string$2(),
|
|
159306
|
-
slug: string$2().nullable(),
|
|
159307
|
-
role: string$2()
|
|
159308
|
-
});
|
|
159309
|
-
const WorkspaceGitHubRepositorySchema = object({
|
|
159310
|
-
id: number$3(),
|
|
159311
|
-
name: string$2(),
|
|
159312
|
-
fullName: string$2(),
|
|
159313
|
-
private: boolean()
|
|
159314
|
-
});
|
|
159315
|
-
const WorkspaceListResultSchema = discriminatedUnion("valid", [
|
|
159316
|
-
object({
|
|
159317
|
-
valid: literal(false),
|
|
159318
|
-
userId: _null(),
|
|
159319
|
-
workspaces: array$3(WorkspaceSummarySchema)
|
|
159320
|
-
}),
|
|
159321
|
-
object({
|
|
159322
|
-
valid: literal(true),
|
|
159323
|
-
userId: string$2(),
|
|
159324
|
-
workspaces: array$3(WorkspaceSummarySchema)
|
|
159325
|
-
})
|
|
159326
|
-
]);
|
|
159327
|
-
const RegisterSessionOwnerResultSchema = object({
|
|
159328
|
-
success: literal(true),
|
|
159329
|
-
existing: boolean()
|
|
159330
|
-
});
|
|
159331
|
-
const WorkspaceGitHubRepositoryListResultSchema = discriminatedUnion("valid", [
|
|
159332
|
-
object({
|
|
159333
|
-
valid: literal(false),
|
|
159334
|
-
repositories: array$3(WorkspaceGitHubRepositorySchema)
|
|
159335
|
-
}),
|
|
159336
|
-
object({
|
|
159337
|
-
valid: literal(true),
|
|
159338
|
-
repositories: array$3(WorkspaceGitHubRepositorySchema)
|
|
159339
|
-
})
|
|
159340
|
-
]);
|
|
159341
|
-
function createAuthConvexClient() {
|
|
159342
|
-
if (!LODY_AUTH_URL) {
|
|
159343
|
-
throw new Error("LODY_AUTH_URL is not defined.");
|
|
159344
|
-
}
|
|
159345
|
-
return new ConvexHttpClient(LODY_AUTH_URL);
|
|
159346
|
-
}
|
|
159347
|
-
async function listWorkspacesForToken(token2) {
|
|
159348
|
-
const client = createAuthConvexClient();
|
|
159349
|
-
const raw = await client.query(api.deviceAuth.listMyWorkspacesForCliToken, {
|
|
159350
|
-
token: token2
|
|
159351
|
-
});
|
|
159352
|
-
const parsed = WorkspaceListResultSchema.parse(raw);
|
|
159353
|
-
if (!parsed.valid) {
|
|
159354
|
-
throw new Error("CLI token is invalid or expired. Run `lody login` again.");
|
|
159355
|
-
}
|
|
159356
|
-
return parsed.workspaces;
|
|
159357
|
-
}
|
|
159358
|
-
async function listWorkspaceGitHubRepositoriesForCliToken(input2) {
|
|
159359
|
-
const client = createAuthConvexClient();
|
|
159360
|
-
const raw = await client.query(api.github.listWorkspaceRepositoriesForCliToken, {
|
|
159361
|
-
cliToken: input2.token,
|
|
159362
|
-
workspaceId: input2.workspaceId
|
|
159363
|
-
});
|
|
159364
|
-
const parsed = WorkspaceGitHubRepositoryListResultSchema.parse(raw);
|
|
159365
|
-
if (!parsed.valid) {
|
|
159366
|
-
throw new Error("CLI token is invalid or expired. Run `lody login` again.");
|
|
159367
|
-
}
|
|
159368
|
-
return parsed.repositories;
|
|
159369
|
-
}
|
|
159370
|
-
async function registerSessionOwnerForCliToken(input2) {
|
|
159371
|
-
const client = createAuthConvexClient();
|
|
159372
|
-
const raw = await client.mutation(api.sessions.registerSessionOwnerFromCliToken, {
|
|
159373
|
-
cliToken: input2.token,
|
|
159374
|
-
workspaceId: input2.workspaceId,
|
|
159375
|
-
sessionId: input2.sessionId,
|
|
159376
|
-
cliType: input2.cliType,
|
|
159377
|
-
repoFullName: input2.repoFullName
|
|
159378
|
-
});
|
|
159379
|
-
return RegisterSessionOwnerResultSchema.parse(raw);
|
|
159380
|
-
}
|
|
159381
159808
|
const SESSION_CONTROL_PATH = "/session-control";
|
|
159382
159809
|
const LOCAL_CONTROL_HEADER = "x-lody-local-control";
|
|
159383
159810
|
const DEFAULT_LOCAL_CONTROL_TIMEOUT_MS = 3e4;
|
|
@@ -163250,7 +163677,7 @@ ${entry2.text}`).join("\n\n");
|
|
|
163250
163677
|
}
|
|
163251
163678
|
function resolveBuiltinFullAccessModeId(cliType, agentType) {
|
|
163252
163679
|
if (cliType !== "builtin") {
|
|
163253
|
-
return
|
|
163680
|
+
return LODY_FULL_ACCESS_MODE_ID;
|
|
163254
163681
|
}
|
|
163255
163682
|
if (agentType === "claude") {
|
|
163256
163683
|
return "bypassPermissions";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lody",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.0",
|
|
4
4
|
"description": "Lody Agent CLI tool for managing remote command execution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"node": ">=18.0.0"
|
|
21
21
|
},
|
|
22
22
|
"optionalDependencies": {
|
|
23
|
-
"
|
|
23
|
+
"@agentclientprotocol/claude-agent-acp": "0.29.0",
|
|
24
24
|
"acp-extension-codex": "0.11.1"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"jiti": "^2.6.1",
|
|
57
57
|
"loro-crdt": "^1.11.0",
|
|
58
58
|
"loro-mirror": "1.2.1",
|
|
59
|
-
"loro-repo": "^0.16.
|
|
59
|
+
"loro-repo": "^0.16.5",
|
|
60
60
|
"ora": "^8.2.0",
|
|
61
61
|
"prettier": "^3.6.2",
|
|
62
62
|
"proxy-from-env": "^1.1.0",
|
|
@@ -72,11 +72,11 @@
|
|
|
72
72
|
"winston-transport": "^4.7.1",
|
|
73
73
|
"ws": "^8.18.3",
|
|
74
74
|
"zod": "^4.1.5",
|
|
75
|
+
"@lody/cli-supervisor": "0.0.1",
|
|
75
76
|
"@lody/convex": "0.0.1",
|
|
76
77
|
"@lody/loro-streams-rpc": "0.0.1",
|
|
77
78
|
"@lody/shared": "0.0.1",
|
|
78
|
-
"loro-code": "0.0.1"
|
|
79
|
-
"@lody/cli-supervisor": "0.0.1"
|
|
79
|
+
"loro-code": "0.0.1"
|
|
80
80
|
},
|
|
81
81
|
"files": [
|
|
82
82
|
"dist",
|