@elizaos/plugin-agent-orchestrator 0.4.1 → 0.4.3
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/actions/coding-task-helpers.d.ts +8 -2
- package/dist/actions/coding-task-helpers.d.ts.map +1 -1
- package/dist/index.js +162 -48
- package/dist/index.js.map +10 -9
- package/dist/services/config-env.d.ts +11 -0
- package/dist/services/config-env.d.ts.map +1 -0
- package/dist/services/swarm-coordinator.d.ts +8 -0
- package/dist/services/swarm-coordinator.d.ts.map +1 -1
- package/dist/services/swarm-history.d.ts.map +1 -1
- package/dist/services/workspace-lifecycle.d.ts +1 -1
- package/dist/services/workspace-lifecycle.d.ts.map +1 -1
- package/dist/services/workspace-service.d.ts +10 -1
- package/dist/services/workspace-service.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -9,8 +9,14 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { type HandlerCallback, type IAgentRuntime } from "@elizaos/core";
|
|
11
11
|
import type { PTYService } from "../services/pty-service.js";
|
|
12
|
-
/**
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Create a scratch sandbox directory for non-repo tasks.
|
|
14
|
+
*
|
|
15
|
+
* When `PARALLAX_CODING_DIRECTORY` is set (e.g. `~/Projects`), creates a
|
|
16
|
+
* named subdir like `~/Projects/todo-app/` derived from the task label.
|
|
17
|
+
* Otherwise falls back to `~/.milady/workspaces/{uuid}`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createScratchDir(runtime?: IAgentRuntime, label?: string): string;
|
|
14
20
|
/**
|
|
15
21
|
* Generate a short semantic label from repo URL and/or task description.
|
|
16
22
|
* e.g. "git-workspace-service-testbed/hello-mima" or "scratch/react-research"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coding-task-helpers.d.ts","sourceRoot":"","sources":["../../src/actions/coding-task-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,aAAa,EAEnB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"coding-task-helpers.d.ts","sourceRoot":"","sources":["../../src/actions/coding-task-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,aAAa,EAEnB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AA+B7D;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,CAAC,EAAE,aAAa,EACvB,KAAK,CAAC,EAAE,MAAM,GACb,MAAM,CA0BR;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB,MAAM,CA4BR;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,QAAQ,CAAC,EAAE,eAAe,EAC1B,iBAAiB,UAAQ,GACxB,IAAI,CAsEN"}
|
package/dist/index.js
CHANGED
|
@@ -3241,6 +3241,7 @@ import * as os from "os";
|
|
|
3241
3241
|
import * as path2 from "path";
|
|
3242
3242
|
var MAX_ENTRIES = 150;
|
|
3243
3243
|
var TRUNCATE_TO = 100;
|
|
3244
|
+
var MAX_FILE_SIZE_BYTES = 1048576;
|
|
3244
3245
|
|
|
3245
3246
|
class WriteMutex {
|
|
3246
3247
|
queue = [];
|
|
@@ -3280,6 +3281,13 @@ class SwarmHistory {
|
|
|
3280
3281
|
await fs.appendFile(this.filePath, `${JSON.stringify(entry)}
|
|
3281
3282
|
`, "utf-8");
|
|
3282
3283
|
this.appendCount++;
|
|
3284
|
+
try {
|
|
3285
|
+
const stat2 = await fs.stat(this.filePath);
|
|
3286
|
+
if (stat2.size > MAX_FILE_SIZE_BYTES) {
|
|
3287
|
+
await this.truncateInner(TRUNCATE_TO);
|
|
3288
|
+
return;
|
|
3289
|
+
}
|
|
3290
|
+
} catch {}
|
|
3283
3291
|
if (this.appendCount >= MAX_ENTRIES - TRUNCATE_TO) {
|
|
3284
3292
|
const content = await fs.readFile(this.filePath, "utf-8");
|
|
3285
3293
|
const lineCount = content.split(`
|
|
@@ -3339,10 +3347,16 @@ class SwarmHistory {
|
|
|
3339
3347
|
return;
|
|
3340
3348
|
}
|
|
3341
3349
|
}
|
|
3342
|
-
|
|
3343
|
-
|
|
3350
|
+
let kept = entries.slice(-maxEntries);
|
|
3351
|
+
let content = kept.map((e) => JSON.stringify(e)).join(`
|
|
3352
|
+
`) + `
|
|
3353
|
+
`;
|
|
3354
|
+
while (Buffer.byteLength(content, "utf-8") > MAX_FILE_SIZE_BYTES && kept.length > 1) {
|
|
3355
|
+
kept = kept.slice(Math.max(1, Math.floor(kept.length * 0.2)));
|
|
3356
|
+
content = kept.map((e) => JSON.stringify(e)).join(`
|
|
3344
3357
|
`) + `
|
|
3345
3358
|
`;
|
|
3359
|
+
}
|
|
3346
3360
|
await fs.writeFile(this.filePath, content, "utf-8");
|
|
3347
3361
|
this.appendCount = 0;
|
|
3348
3362
|
}
|
|
@@ -3586,9 +3600,30 @@ class SwarmCoordinator {
|
|
|
3586
3600
|
constructor(runtime) {
|
|
3587
3601
|
this.runtime = runtime;
|
|
3588
3602
|
}
|
|
3603
|
+
scratchDecisionWired = false;
|
|
3589
3604
|
setChatCallback(cb) {
|
|
3590
3605
|
this.chatCallback = cb;
|
|
3591
3606
|
this.log("Chat callback wired");
|
|
3607
|
+
this.wireScratchDecisionCallback();
|
|
3608
|
+
}
|
|
3609
|
+
wireScratchDecisionCallback() {
|
|
3610
|
+
if (this.scratchDecisionWired || !this.chatCallback)
|
|
3611
|
+
return;
|
|
3612
|
+
const wsService = this.runtime.getService("CODING_WORKSPACE_SERVICE");
|
|
3613
|
+
if (wsService?.setScratchDecisionCallback) {
|
|
3614
|
+
const chatCb = this.chatCallback;
|
|
3615
|
+
wsService.setScratchDecisionCallback(async (record) => {
|
|
3616
|
+
const ttlNote = record.expiresAt ? (() => {
|
|
3617
|
+
const remainMs = record.expiresAt - Date.now();
|
|
3618
|
+
const hours = Math.round(remainMs / (60 * 60 * 1000));
|
|
3619
|
+
return hours >= 1 ? `It will be automatically cleaned up in ~${hours} hour${hours === 1 ? "" : "s"}.` : `It will be automatically cleaned up shortly.`;
|
|
3620
|
+
})() : "It will be automatically cleaned up after the configured retention period.";
|
|
3621
|
+
await chatCb(`Task "${record.label}" finished. Code is at \`${record.path}\`.
|
|
3622
|
+
` + `${ttlNote} To keep it, say "keep the workspace" or manage it in Settings → Coding Agents.`, "coding-agent");
|
|
3623
|
+
});
|
|
3624
|
+
this.scratchDecisionWired = true;
|
|
3625
|
+
this.log("Scratch decision callback wired");
|
|
3626
|
+
}
|
|
3592
3627
|
}
|
|
3593
3628
|
setWsBroadcast(cb) {
|
|
3594
3629
|
this.wsBroadcast = cb;
|
|
@@ -3897,6 +3932,9 @@ class SwarmCoordinator {
|
|
|
3897
3932
|
} catch {}
|
|
3898
3933
|
}
|
|
3899
3934
|
async handleSessionEvent(sessionId, event, data) {
|
|
3935
|
+
if (!this.scratchDecisionWired) {
|
|
3936
|
+
this.wireScratchDecisionCallback();
|
|
3937
|
+
}
|
|
3900
3938
|
const tsMatch = sessionId.match(/^pty-(\d+)-/);
|
|
3901
3939
|
if (tsMatch) {
|
|
3902
3940
|
const sessionCreatedAt = Number(tsMatch[1]);
|
|
@@ -5270,15 +5308,55 @@ function formatAge(timestamp) {
|
|
|
5270
5308
|
// src/actions/coding-task-helpers.ts
|
|
5271
5309
|
import { randomUUID } from "node:crypto";
|
|
5272
5310
|
import * as fs2 from "node:fs";
|
|
5273
|
-
import * as
|
|
5274
|
-
import * as
|
|
5311
|
+
import * as os4 from "node:os";
|
|
5312
|
+
import * as path5 from "node:path";
|
|
5275
5313
|
import {
|
|
5276
5314
|
logger as logger5
|
|
5277
5315
|
} from "@elizaos/core";
|
|
5278
|
-
|
|
5279
|
-
|
|
5316
|
+
|
|
5317
|
+
// src/services/config-env.ts
|
|
5318
|
+
import { readFileSync } from "node:fs";
|
|
5319
|
+
import * as os3 from "node:os";
|
|
5320
|
+
import * as path4 from "node:path";
|
|
5321
|
+
function readConfigEnvKey(key) {
|
|
5322
|
+
try {
|
|
5323
|
+
const configPath = path4.join(process.env.MILADY_STATE_DIR ?? process.env.ELIZA_STATE_DIR ?? path4.join(os3.homedir(), ".milady"), process.env.ELIZA_NAMESPACE === "milady" || !process.env.ELIZA_NAMESPACE ? "milady.json" : `${process.env.ELIZA_NAMESPACE}.json`);
|
|
5324
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
5325
|
+
const config = JSON.parse(raw);
|
|
5326
|
+
const val = config?.env?.[key];
|
|
5327
|
+
return typeof val === "string" ? val : undefined;
|
|
5328
|
+
} catch {
|
|
5329
|
+
return;
|
|
5330
|
+
}
|
|
5331
|
+
}
|
|
5332
|
+
|
|
5333
|
+
// src/actions/coding-task-helpers.ts
|
|
5334
|
+
function sanitizeDirName(label) {
|
|
5335
|
+
return label.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "").slice(0, 60) || "scratch";
|
|
5336
|
+
}
|
|
5337
|
+
function resolveNonColliding(baseDir, name) {
|
|
5338
|
+
let candidate = path5.join(baseDir, name);
|
|
5339
|
+
if (!fs2.existsSync(candidate))
|
|
5340
|
+
return candidate;
|
|
5341
|
+
for (let i = 2;i < 100; i++) {
|
|
5342
|
+
candidate = path5.join(baseDir, `${name}-${i}`);
|
|
5343
|
+
if (!fs2.existsSync(candidate))
|
|
5344
|
+
return candidate;
|
|
5345
|
+
}
|
|
5346
|
+
return path5.join(baseDir, `${name}-${randomUUID().slice(0, 8)}`);
|
|
5347
|
+
}
|
|
5348
|
+
function createScratchDir(runtime, label) {
|
|
5349
|
+
const codingDir = runtime?.getSetting("PARALLAX_CODING_DIRECTORY") ?? readConfigEnvKey("PARALLAX_CODING_DIRECTORY") ?? process.env.PARALLAX_CODING_DIRECTORY;
|
|
5350
|
+
if (codingDir?.trim()) {
|
|
5351
|
+
const resolved = codingDir.startsWith("~") ? path5.join(os4.homedir(), codingDir.slice(1)) : path5.resolve(codingDir);
|
|
5352
|
+
const dirName = label ? sanitizeDirName(label) : `scratch-${randomUUID().slice(0, 8)}`;
|
|
5353
|
+
const scratchDir2 = resolveNonColliding(resolved, dirName);
|
|
5354
|
+
fs2.mkdirSync(scratchDir2, { recursive: true });
|
|
5355
|
+
return scratchDir2;
|
|
5356
|
+
}
|
|
5357
|
+
const baseDir = path5.join(os4.homedir(), ".milady", "workspaces");
|
|
5280
5358
|
const scratchId = randomUUID();
|
|
5281
|
-
const scratchDir =
|
|
5359
|
+
const scratchDir = path5.join(baseDir, scratchId);
|
|
5282
5360
|
fs2.mkdirSync(scratchDir, { recursive: true });
|
|
5283
5361
|
return scratchDir;
|
|
5284
5362
|
}
|
|
@@ -5298,6 +5376,7 @@ function generateLabel(repo, task) {
|
|
|
5298
5376
|
return parts.join("/");
|
|
5299
5377
|
}
|
|
5300
5378
|
function registerSessionEvents(ptyService, runtime, sessionId, label, scratchDir, callback, coordinatorActive = false) {
|
|
5379
|
+
let scratchRegistered = false;
|
|
5301
5380
|
ptyService.onSessionEvent((sid, event, data) => {
|
|
5302
5381
|
if (sid !== sessionId)
|
|
5303
5382
|
return;
|
|
@@ -5327,10 +5406,15 @@ ${preview}` : `Agent "${label}" completed the task.`
|
|
|
5327
5406
|
});
|
|
5328
5407
|
}
|
|
5329
5408
|
}
|
|
5330
|
-
if ((event === "stopped" || event === "task_complete" || event === "error") && scratchDir) {
|
|
5409
|
+
if ((event === "stopped" || event === "task_complete" || event === "error") && scratchDir && !scratchRegistered) {
|
|
5410
|
+
logger5.info(`[scratch-lifecycle] Terminal event "${event}" for "${label}" — registering scratch workspace at ${scratchDir}`);
|
|
5331
5411
|
const wsService = runtime.getService("CODING_WORKSPACE_SERVICE");
|
|
5332
|
-
if (wsService) {
|
|
5333
|
-
|
|
5412
|
+
if (!wsService) {
|
|
5413
|
+
logger5.warn(`[scratch-lifecycle] CODING_WORKSPACE_SERVICE not found — cannot register scratch workspace`);
|
|
5414
|
+
} else {
|
|
5415
|
+
wsService.registerScratchWorkspace(sessionId, scratchDir, label, event).then(() => {
|
|
5416
|
+
scratchRegistered = true;
|
|
5417
|
+
}).catch((err) => {
|
|
5334
5418
|
logger5.warn(`[START_CODING_TASK] Failed to register scratch workspace for "${label}": ${err}`);
|
|
5335
5419
|
});
|
|
5336
5420
|
}
|
|
@@ -5509,7 +5593,7 @@ async function handleMultiAgent(ctx, agentsParam) {
|
|
|
5509
5593
|
branch = workspace.branch;
|
|
5510
5594
|
wsService.setLabel(workspace.id, specLabel);
|
|
5511
5595
|
} else {
|
|
5512
|
-
workdir = createScratchDir();
|
|
5596
|
+
workdir = createScratchDir(runtime, specLabel);
|
|
5513
5597
|
}
|
|
5514
5598
|
if (specAgentType !== "shell" && specAgentType !== "pi") {
|
|
5515
5599
|
const [preflight] = await ptyService.checkAvailableAgents([
|
|
@@ -6177,7 +6261,7 @@ var activeWorkspaceContextProvider = {
|
|
|
6177
6261
|
try {
|
|
6178
6262
|
sessions = await Promise.race([
|
|
6179
6263
|
ptyService.listSessions(),
|
|
6180
|
-
new Promise((
|
|
6264
|
+
new Promise((resolve4) => setTimeout(() => resolve4([]), 2000))
|
|
6181
6265
|
]);
|
|
6182
6266
|
} catch {
|
|
6183
6267
|
sessions = [];
|
|
@@ -6261,8 +6345,8 @@ var activeWorkspaceContextProvider = {
|
|
|
6261
6345
|
};
|
|
6262
6346
|
|
|
6263
6347
|
// src/services/workspace-service.ts
|
|
6264
|
-
import * as
|
|
6265
|
-
import * as
|
|
6348
|
+
import * as os6 from "node:os";
|
|
6349
|
+
import * as path7 from "node:path";
|
|
6266
6350
|
import * as fs4 from "node:fs/promises";
|
|
6267
6351
|
import {
|
|
6268
6352
|
CredentialService,
|
|
@@ -6477,12 +6561,18 @@ async function createPR(workspaceService, workspace, workspaceId, options, log)
|
|
|
6477
6561
|
|
|
6478
6562
|
// src/services/workspace-lifecycle.ts
|
|
6479
6563
|
import * as fs3 from "node:fs";
|
|
6480
|
-
import * as
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
const
|
|
6484
|
-
|
|
6485
|
-
|
|
6564
|
+
import * as os5 from "node:os";
|
|
6565
|
+
import * as path6 from "node:path";
|
|
6566
|
+
async function removeScratchDir(dirPath, baseDir, log, allowedDirs) {
|
|
6567
|
+
const resolved = path6.resolve(dirPath);
|
|
6568
|
+
const expandTilde = (p) => p.startsWith("~") ? path6.join(os5.homedir(), p.slice(1)) : p;
|
|
6569
|
+
const allAllowed = [baseDir, ...allowedDirs ?? []];
|
|
6570
|
+
const isAllowed = allAllowed.some((dir) => {
|
|
6571
|
+
const resolvedDir = path6.resolve(expandTilde(dir)) + path6.sep;
|
|
6572
|
+
return resolved.startsWith(resolvedDir) || resolved === path6.resolve(expandTilde(dir));
|
|
6573
|
+
});
|
|
6574
|
+
if (!isAllowed) {
|
|
6575
|
+
console.warn(`[CodingWorkspaceService] Refusing to remove dir outside allowed paths: ${resolved}`);
|
|
6486
6576
|
return;
|
|
6487
6577
|
}
|
|
6488
6578
|
try {
|
|
@@ -6513,7 +6603,7 @@ async function gcOrphanedWorkspaces(baseDir, workspaceTtlMs, trackedWorkspaceIds
|
|
|
6513
6603
|
skipped++;
|
|
6514
6604
|
continue;
|
|
6515
6605
|
}
|
|
6516
|
-
const dirPath =
|
|
6606
|
+
const dirPath = path6.join(baseDir, entry.name);
|
|
6517
6607
|
try {
|
|
6518
6608
|
const stat2 = await fs3.promises.stat(dirPath);
|
|
6519
6609
|
const age = now - stat2.mtimeMs;
|
|
@@ -6549,10 +6639,11 @@ class CodingWorkspaceService {
|
|
|
6549
6639
|
scratchCleanupTimers = new Map;
|
|
6550
6640
|
eventCallbacks = [];
|
|
6551
6641
|
authPromptCallback = null;
|
|
6642
|
+
scratchDecisionCallback = null;
|
|
6552
6643
|
constructor(runtime, config = {}) {
|
|
6553
6644
|
this.runtime = runtime;
|
|
6554
6645
|
this.serviceConfig = {
|
|
6555
|
-
baseDir: config.baseDir ??
|
|
6646
|
+
baseDir: config.baseDir ?? path7.join(os6.homedir(), ".milady", "workspaces"),
|
|
6556
6647
|
branchPrefix: config.branchPrefix ?? "milady",
|
|
6557
6648
|
debug: config.debug ?? false,
|
|
6558
6649
|
workspaceTtlMs: config.workspaceTtlMs ?? 24 * 60 * 60 * 1000
|
|
@@ -6747,6 +6838,9 @@ class CodingWorkspaceService {
|
|
|
6747
6838
|
setAuthPromptCallback(callback) {
|
|
6748
6839
|
this.authPromptCallback = callback;
|
|
6749
6840
|
}
|
|
6841
|
+
setScratchDecisionCallback(callback) {
|
|
6842
|
+
this.scratchDecisionCallback = callback;
|
|
6843
|
+
}
|
|
6750
6844
|
async createIssue(repo, options) {
|
|
6751
6845
|
return createIssue(this.getGitHubContext(), repo, options);
|
|
6752
6846
|
}
|
|
@@ -6805,7 +6899,10 @@ class CodingWorkspaceService {
|
|
|
6805
6899
|
}
|
|
6806
6900
|
}
|
|
6807
6901
|
async removeScratchDir(dirPath) {
|
|
6808
|
-
|
|
6902
|
+
const rawCodingDir = this.runtime.getSetting("PARALLAX_CODING_DIRECTORY") ?? this.readConfigEnvKey("PARALLAX_CODING_DIRECTORY") ?? process.env.PARALLAX_CODING_DIRECTORY;
|
|
6903
|
+
const codingDir = rawCodingDir?.trim() ? rawCodingDir.trim().startsWith("~") ? path7.join(os6.homedir(), rawCodingDir.trim().slice(1)) : path7.resolve(rawCodingDir.trim()) : undefined;
|
|
6904
|
+
const allowedDirs = codingDir ? [codingDir] : undefined;
|
|
6905
|
+
return removeScratchDir(dirPath, this.serviceConfig.baseDir, (msg) => this.log(msg), allowedDirs);
|
|
6809
6906
|
}
|
|
6810
6907
|
listScratchWorkspaces() {
|
|
6811
6908
|
return Array.from(this.scratchBySession.values()).sort((a, b) => b.terminalAt - a.terminalAt);
|
|
@@ -6823,6 +6920,7 @@ class CodingWorkspaceService {
|
|
|
6823
6920
|
status: "pending_decision"
|
|
6824
6921
|
};
|
|
6825
6922
|
const policy = this.getScratchRetentionPolicy();
|
|
6923
|
+
this.log(`Scratch retention policy: "${policy}" for "${label}"`);
|
|
6826
6924
|
if (policy === "ephemeral") {
|
|
6827
6925
|
await this.removeScratchDir(dirPath);
|
|
6828
6926
|
this.scratchBySession.delete(sessionId);
|
|
@@ -6843,6 +6941,14 @@ class CodingWorkspaceService {
|
|
|
6843
6941
|
const ttlMs = this.getScratchDecisionTtlMs();
|
|
6844
6942
|
record.expiresAt = now + ttlMs;
|
|
6845
6943
|
this.scheduleScratchCleanup(sessionId, ttlMs);
|
|
6944
|
+
if (this.scratchDecisionCallback) {
|
|
6945
|
+
this.log(`Firing scratch decision prompt for "${label}" at ${dirPath}`);
|
|
6946
|
+
this.scratchDecisionCallback(record).catch((err) => {
|
|
6947
|
+
console.warn(`[workspace] Failed to send scratch decision prompt: ${err}`);
|
|
6948
|
+
});
|
|
6949
|
+
} else {
|
|
6950
|
+
this.log(`No scratch decision callback wired — skipping prompt for "${label}"`);
|
|
6951
|
+
}
|
|
6846
6952
|
} else {
|
|
6847
6953
|
this.clearScratchCleanupTimer(sessionId);
|
|
6848
6954
|
}
|
|
@@ -6898,14 +7004,22 @@ class CodingWorkspaceService {
|
|
|
6898
7004
|
console.log(`[CodingWorkspaceService] ${message}`);
|
|
6899
7005
|
}
|
|
6900
7006
|
}
|
|
7007
|
+
readConfigEnvKey(key) {
|
|
7008
|
+
return readConfigEnvKey(key);
|
|
7009
|
+
}
|
|
6901
7010
|
getScratchRetentionPolicy() {
|
|
6902
|
-
const setting = this.runtime.getSetting("PARALLAX_SCRATCH_RETENTION") ?? process.env.PARALLAX_SCRATCH_RETENTION;
|
|
7011
|
+
const setting = this.runtime.getSetting("PARALLAX_SCRATCH_RETENTION") ?? this.readConfigEnvKey("PARALLAX_SCRATCH_RETENTION") ?? process.env.PARALLAX_SCRATCH_RETENTION;
|
|
6903
7012
|
const normalized = setting?.trim().toLowerCase();
|
|
6904
7013
|
if (normalized === "ephemeral")
|
|
6905
7014
|
return "ephemeral";
|
|
6906
7015
|
if (normalized === "persistent" || normalized === "keep") {
|
|
6907
7016
|
return "persistent";
|
|
6908
7017
|
}
|
|
7018
|
+
if (!normalized) {
|
|
7019
|
+
const codingDir = this.runtime.getSetting("PARALLAX_CODING_DIRECTORY") ?? this.readConfigEnvKey("PARALLAX_CODING_DIRECTORY") ?? process.env.PARALLAX_CODING_DIRECTORY;
|
|
7020
|
+
if (codingDir?.trim())
|
|
7021
|
+
return "persistent";
|
|
7022
|
+
}
|
|
6909
7023
|
return "pending_decision";
|
|
6910
7024
|
}
|
|
6911
7025
|
getScratchDecisionTtlMs() {
|
|
@@ -6951,11 +7065,11 @@ class CodingWorkspaceService {
|
|
|
6951
7065
|
return compact || `scratch-${Date.now().toString(36)}`;
|
|
6952
7066
|
}
|
|
6953
7067
|
async allocatePromotedPath(baseDir, baseName) {
|
|
6954
|
-
const baseResolved =
|
|
7068
|
+
const baseResolved = path7.resolve(baseDir);
|
|
6955
7069
|
for (let i = 0;i < 1000; i++) {
|
|
6956
7070
|
const candidateName = i === 0 ? baseName : `${baseName}-${i}`;
|
|
6957
|
-
const candidate =
|
|
6958
|
-
if (candidate !== baseResolved && !candidate.startsWith(`${baseResolved}${
|
|
7071
|
+
const candidate = path7.resolve(baseResolved, candidateName);
|
|
7072
|
+
if (candidate !== baseResolved && !candidate.startsWith(`${baseResolved}${path7.sep}`)) {
|
|
6959
7073
|
continue;
|
|
6960
7074
|
}
|
|
6961
7075
|
try {
|
|
@@ -6970,8 +7084,8 @@ class CodingWorkspaceService {
|
|
|
6970
7084
|
// src/api/agent-routes.ts
|
|
6971
7085
|
import { access as access2, readFile as readFile4, realpath, rm as rm2 } from "node:fs/promises";
|
|
6972
7086
|
import { createHash } from "node:crypto";
|
|
6973
|
-
import * as
|
|
6974
|
-
import * as
|
|
7087
|
+
import * as os7 from "node:os";
|
|
7088
|
+
import * as path8 from "node:path";
|
|
6975
7089
|
import { execFile } from "node:child_process";
|
|
6976
7090
|
import { promisify } from "node:util";
|
|
6977
7091
|
var execFileAsync = promisify(execFile);
|
|
@@ -6983,23 +7097,23 @@ function shouldAutoPreflight() {
|
|
|
6983
7097
|
return false;
|
|
6984
7098
|
}
|
|
6985
7099
|
function isPathInside(parent, candidate) {
|
|
6986
|
-
return candidate === parent || candidate.startsWith(`${parent}${
|
|
7100
|
+
return candidate === parent || candidate.startsWith(`${parent}${path8.sep}`);
|
|
6987
7101
|
}
|
|
6988
7102
|
async function resolveSafeVenvPath(workdir, venvDirRaw) {
|
|
6989
7103
|
const venvDir = venvDirRaw.trim();
|
|
6990
7104
|
if (!venvDir) {
|
|
6991
7105
|
throw new Error("PARALLAX_BENCHMARK_PREFLIGHT_VENV must be non-empty");
|
|
6992
7106
|
}
|
|
6993
|
-
if (
|
|
7107
|
+
if (path8.isAbsolute(venvDir)) {
|
|
6994
7108
|
throw new Error("PARALLAX_BENCHMARK_PREFLIGHT_VENV must be relative to workdir");
|
|
6995
7109
|
}
|
|
6996
|
-
const normalized =
|
|
6997
|
-
if (normalized === "." || normalized === ".." || normalized.startsWith(`..${
|
|
7110
|
+
const normalized = path8.normalize(venvDir);
|
|
7111
|
+
if (normalized === "." || normalized === ".." || normalized.startsWith(`..${path8.sep}`)) {
|
|
6998
7112
|
throw new Error("PARALLAX_BENCHMARK_PREFLIGHT_VENV must stay within workdir");
|
|
6999
7113
|
}
|
|
7000
|
-
const workdirResolved =
|
|
7114
|
+
const workdirResolved = path8.resolve(workdir);
|
|
7001
7115
|
const workdirReal = await realpath(workdirResolved);
|
|
7002
|
-
const resolved =
|
|
7116
|
+
const resolved = path8.resolve(workdirReal, normalized);
|
|
7003
7117
|
if (!isPathInside(workdirReal, resolved)) {
|
|
7004
7118
|
throw new Error("PARALLAX_BENCHMARK_PREFLIGHT_VENV resolves outside workdir");
|
|
7005
7119
|
}
|
|
@@ -7015,7 +7129,7 @@ async function resolveSafeVenvPath(workdir, venvDirRaw) {
|
|
|
7015
7129
|
const maybeErr = err;
|
|
7016
7130
|
if (maybeErr?.code !== "ENOENT")
|
|
7017
7131
|
throw err;
|
|
7018
|
-
const parentReal = await realpath(
|
|
7132
|
+
const parentReal = await realpath(path8.dirname(resolved));
|
|
7019
7133
|
if (!isPathInside(workdirReal, parentReal)) {
|
|
7020
7134
|
throw new Error("PARALLAX_BENCHMARK_PREFLIGHT_VENV parent resolves outside workdir");
|
|
7021
7135
|
}
|
|
@@ -7031,10 +7145,10 @@ async function fileExists(filePath) {
|
|
|
7031
7145
|
}
|
|
7032
7146
|
}
|
|
7033
7147
|
async function resolveRequirementsPath(workdir) {
|
|
7034
|
-
const workdirReal = await realpath(
|
|
7148
|
+
const workdirReal = await realpath(path8.resolve(workdir));
|
|
7035
7149
|
const candidates = [
|
|
7036
|
-
|
|
7037
|
-
|
|
7150
|
+
path8.join(workdir, "apps", "api", "requirements.txt"),
|
|
7151
|
+
path8.join(workdir, "requirements.txt")
|
|
7038
7152
|
];
|
|
7039
7153
|
for (const candidate of candidates) {
|
|
7040
7154
|
if (!await fileExists(candidate))
|
|
@@ -7061,7 +7175,7 @@ async function runBenchmarkPreflight(workdir) {
|
|
|
7061
7175
|
const mode = process.env.PARALLAX_BENCHMARK_PREFLIGHT_MODE?.toLowerCase() === "warm" ? "warm" : "cold";
|
|
7062
7176
|
const venvDir = process.env.PARALLAX_BENCHMARK_PREFLIGHT_VENV || ".benchmark-venv";
|
|
7063
7177
|
const venvPath = await resolveSafeVenvPath(workdir, venvDir);
|
|
7064
|
-
const pythonInVenv =
|
|
7178
|
+
const pythonInVenv = path8.join(venvPath, process.platform === "win32" ? "Scripts" : "bin", process.platform === "win32" ? "python.exe" : "python");
|
|
7065
7179
|
const key = `${workdir}::${mode}::${venvPath}::${requirementsFingerprint}`;
|
|
7066
7180
|
if (PREFLIGHT_DONE.has(key)) {
|
|
7067
7181
|
if (await fileExists(pythonInVenv))
|
|
@@ -7269,21 +7383,21 @@ async function handleAgentRoutes(req, res, pathname, ctx) {
|
|
|
7269
7383
|
customCredentials,
|
|
7270
7384
|
metadata
|
|
7271
7385
|
} = body;
|
|
7272
|
-
const workspaceBaseDir =
|
|
7273
|
-
const workspaceBaseDirResolved =
|
|
7274
|
-
const cwdResolved =
|
|
7386
|
+
const workspaceBaseDir = path8.join(os7.homedir(), ".milady", "workspaces");
|
|
7387
|
+
const workspaceBaseDirResolved = path8.resolve(workspaceBaseDir);
|
|
7388
|
+
const cwdResolved = path8.resolve(process.cwd());
|
|
7275
7389
|
const workspaceBaseDirReal = await realpath(workspaceBaseDirResolved).catch(() => workspaceBaseDirResolved);
|
|
7276
7390
|
const cwdReal = await realpath(cwdResolved).catch(() => cwdResolved);
|
|
7277
7391
|
const allowedPrefixes = [workspaceBaseDirReal, cwdReal];
|
|
7278
7392
|
let workdir = rawWorkdir;
|
|
7279
7393
|
if (workdir) {
|
|
7280
|
-
const resolved =
|
|
7394
|
+
const resolved = path8.resolve(workdir);
|
|
7281
7395
|
const resolvedReal = await realpath(resolved).catch(() => null);
|
|
7282
7396
|
if (!resolvedReal) {
|
|
7283
7397
|
sendError(res, "workdir must exist", 403);
|
|
7284
7398
|
return true;
|
|
7285
7399
|
}
|
|
7286
|
-
const isAllowed = allowedPrefixes.some((prefix2) => resolvedReal === prefix2 || resolvedReal.startsWith(prefix2 +
|
|
7400
|
+
const isAllowed = allowedPrefixes.some((prefix2) => resolvedReal === prefix2 || resolvedReal.startsWith(prefix2 + path8.sep));
|
|
7287
7401
|
if (!isAllowed) {
|
|
7288
7402
|
sendError(res, "workdir must be within workspace base directory or cwd", 403);
|
|
7289
7403
|
return true;
|
|
@@ -7927,7 +8041,7 @@ async function handleWorkspaceRoutes(req, res, pathname, ctx) {
|
|
|
7927
8041
|
// src/api/routes.ts
|
|
7928
8042
|
var MAX_BODY_SIZE = 1024 * 1024;
|
|
7929
8043
|
async function parseBody(req) {
|
|
7930
|
-
return new Promise((
|
|
8044
|
+
return new Promise((resolve7, reject) => {
|
|
7931
8045
|
let body = "";
|
|
7932
8046
|
let size = 0;
|
|
7933
8047
|
req.on("data", (chunk) => {
|
|
@@ -7941,7 +8055,7 @@ async function parseBody(req) {
|
|
|
7941
8055
|
});
|
|
7942
8056
|
req.on("end", () => {
|
|
7943
8057
|
try {
|
|
7944
|
-
|
|
8058
|
+
resolve7(body ? JSON.parse(body) : {});
|
|
7945
8059
|
} catch {
|
|
7946
8060
|
reject(new Error("Invalid JSON body"));
|
|
7947
8061
|
}
|
|
@@ -8029,5 +8143,5 @@ export {
|
|
|
8029
8143
|
CodingWorkspaceService
|
|
8030
8144
|
};
|
|
8031
8145
|
|
|
8032
|
-
//# debugId=
|
|
8146
|
+
//# debugId=CC31B3CD95C1813A64756E2164756E21
|
|
8033
8147
|
//# sourceMappingURL=index.js.map
|