cascade-ai 0.15.2 → 0.17.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/cli.cjs +509 -12
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +508 -12
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +483 -74
- package/dist/index.cjs +480 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +59 -4
- package/dist/index.d.ts +59 -4
- package/dist/index.js +480 -71
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/desktop-core.cjs
CHANGED
|
@@ -222595,7 +222595,7 @@ Anthropic.Models = Models2;
|
|
|
222595
222595
|
Anthropic.Beta = Beta;
|
|
222596
222596
|
|
|
222597
222597
|
// src/constants.ts
|
|
222598
|
-
var CASCADE_VERSION = "0.
|
|
222598
|
+
var CASCADE_VERSION = "0.17.0";
|
|
222599
222599
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
222600
222600
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
222601
222601
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -253816,6 +253816,10 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
253816
253816
|
const worker = new T3Worker(this.router, this.toolRegistry, this.id);
|
|
253817
253817
|
if (this.store) worker.setStore(this.store, taskId);
|
|
253818
253818
|
worker.setPeerBus(this.t3PeerBus);
|
|
253819
|
+
if (this.permissionEscalator) worker.setPermissionEscalator(this.permissionEscalator);
|
|
253820
|
+
if (this.toolCreator) worker.setToolCreator(this.toolCreator);
|
|
253821
|
+
worker.on("log", (e3) => this.emit("log", e3));
|
|
253822
|
+
worker.on("tier:status", (e3) => this.emit("tier:status", e3));
|
|
253819
253823
|
worker.on("stream:token", (e3) => this.emit("stream:token", e3));
|
|
253820
253824
|
worker.on("tool:approval-request", (e3) => this.emit("tool:approval-request", {
|
|
253821
253825
|
...e3,
|
|
@@ -254164,12 +254168,14 @@ Board guidance (must be followed in the plan): ${decision.note}`,
|
|
|
254164
254168
|
status: "IN_PROGRESS"
|
|
254165
254169
|
});
|
|
254166
254170
|
const okBefore = okCount(allT2Results);
|
|
254171
|
+
const completedSummary = this.summarizeCompletedSections(allT2Results);
|
|
254172
|
+
const correctionContext = [systemContext, completedSummary].filter(Boolean).join("\n\n") || void 0;
|
|
254167
254173
|
const correctionPlan = await this.decomposeTask(`The previous execution plan failed to fully satisfy the original goal or encountered errors.
|
|
254168
254174
|
Review reason: ${reviewResult.reason}
|
|
254169
254175
|
|
|
254170
254176
|
Original goal: ${enrichedPrompt}
|
|
254171
254177
|
|
|
254172
|
-
Create a CORRECTION PLAN that contains only the new sections needed to fix the issues. Do not repeat successful sections
|
|
254178
|
+
Create a CORRECTION PLAN that contains only the new sections needed to fix the issues. Do not repeat successful sections.`, correctionContext);
|
|
254173
254179
|
const correctionResults = await this.dispatchT2Managers(correctionPlan.sections);
|
|
254174
254180
|
allT2Results = [...allT2Results, ...correctionResults];
|
|
254175
254181
|
if (okCount(allT2Results) <= okBefore) {
|
|
@@ -254272,6 +254278,15 @@ In 3-5 terse bullets, flag the most important RISKS, GAPS, or over-/under-decomp
|
|
|
254272
254278
|
return null;
|
|
254273
254279
|
}
|
|
254274
254280
|
}
|
|
254281
|
+
/** Structured, grounded summary of what's already done — used to keep
|
|
254282
|
+
* corrective replan passes from re-emitting completed sections. */
|
|
254283
|
+
summarizeCompletedSections(results) {
|
|
254284
|
+
const done = results.filter((r4) => r4.status === "COMPLETED" || r4.status === "PARTIAL");
|
|
254285
|
+
if (done.length === 0) return "";
|
|
254286
|
+
const lines = done.map((r4) => `- "${r4.sectionTitle}" [${r4.status}]: ${(r4.sectionSummary || "(no summary)").slice(0, 300)}`);
|
|
254287
|
+
return `ALREADY COMPLETED SECTIONS (do not redo these \u2014 plan only what's still missing):
|
|
254288
|
+
${lines.join("\n")}`;
|
|
254289
|
+
}
|
|
254275
254290
|
async decomposeTask(prompt, systemContext) {
|
|
254276
254291
|
const db = this.router.getWorldStateDB?.();
|
|
254277
254292
|
let worldStateContext = "";
|
|
@@ -254539,7 +254554,7 @@ Leave dependsOn empty for sections that can run immediately in parallel.`;
|
|
|
254539
254554
|
}
|
|
254540
254555
|
}
|
|
254541
254556
|
if (readyIds.length === 0) return;
|
|
254542
|
-
|
|
254557
|
+
const runOne = async (id) => {
|
|
254543
254558
|
resultMap.set(id, null);
|
|
254544
254559
|
const index = sections.findIndex((s3) => s3.sectionId === id);
|
|
254545
254560
|
const section = sections[index];
|
|
@@ -254579,7 +254594,14 @@ Leave dependsOn empty for sections that can run immediately in parallel.`;
|
|
|
254579
254594
|
for (const dependentId of adj.get(id) ?? /* @__PURE__ */ new Set()) {
|
|
254580
254595
|
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
254581
254596
|
}
|
|
254582
|
-
}
|
|
254597
|
+
};
|
|
254598
|
+
if (this.router.getT3ExecutionMode?.() === "sequential") {
|
|
254599
|
+
for (const id of readyIds) {
|
|
254600
|
+
await runOne(id);
|
|
254601
|
+
}
|
|
254602
|
+
} else {
|
|
254603
|
+
await Promise.all(readyIds.map(runOne));
|
|
254604
|
+
}
|
|
254583
254605
|
if (Array.from(inDegree.values()).some((deg) => deg === 0) && resultMap.size < totalSections) {
|
|
254584
254606
|
await executeWave();
|
|
254585
254607
|
}
|
|
@@ -294549,6 +294571,13 @@ var PermissionEscalator = class extends EventEmitter__default.default {
|
|
|
294549
294571
|
* All T3 workers under the same T2 share cached decisions for the same tool.
|
|
294550
294572
|
*/
|
|
294551
294573
|
sessionCache = /* @__PURE__ */ new Map();
|
|
294574
|
+
/**
|
|
294575
|
+
* Task-wide cache keyed by `toolName` alone, for USER- and T1-level
|
|
294576
|
+
* "always" decisions — these are meant to cover every sibling T2/T3 in the
|
|
294577
|
+
* run, not just the one that happened to ask first (see PermissionDecision
|
|
294578
|
+
* doc comment: "task-wide for T1").
|
|
294579
|
+
*/
|
|
294580
|
+
taskWideCache = /* @__PURE__ */ new Map();
|
|
294552
294581
|
t2Evaluator;
|
|
294553
294582
|
t1Evaluator;
|
|
294554
294583
|
/** Pending user-decision resolvers keyed by request ID */
|
|
@@ -294577,6 +294606,15 @@ var PermissionEscalator = class extends EventEmitter__default.default {
|
|
|
294577
294606
|
* Returns a PermissionDecision from whichever tier was able to decide.
|
|
294578
294607
|
*/
|
|
294579
294608
|
async requestPermission(req) {
|
|
294609
|
+
if (!req.forceReprompt && this.taskWideCache.has(req.toolName)) {
|
|
294610
|
+
return {
|
|
294611
|
+
requestId: req.id,
|
|
294612
|
+
approved: this.taskWideCache.get(req.toolName),
|
|
294613
|
+
always: true,
|
|
294614
|
+
decidedBy: "T1",
|
|
294615
|
+
reasoning: "Cached from a previous task-wide decision in this session"
|
|
294616
|
+
};
|
|
294617
|
+
}
|
|
294580
294618
|
const cacheKey = `${req.parentT2Id}:${req.toolName}`;
|
|
294581
294619
|
if (!req.forceReprompt && this.sessionCache.has(cacheKey)) {
|
|
294582
294620
|
return {
|
|
@@ -294621,7 +294659,7 @@ var PermissionEscalator = class extends EventEmitter__default.default {
|
|
|
294621
294659
|
try {
|
|
294622
294660
|
const t1Decision = await this.t1Evaluator(req);
|
|
294623
294661
|
if (t1Decision !== null) {
|
|
294624
|
-
if (t1Decision.always) this.
|
|
294662
|
+
if (t1Decision.always) this.taskWideCache.set(req.toolName, t1Decision.approved);
|
|
294625
294663
|
return t1Decision;
|
|
294626
294664
|
}
|
|
294627
294665
|
} catch {
|
|
@@ -294651,7 +294689,7 @@ var PermissionEscalator = class extends EventEmitter__default.default {
|
|
|
294651
294689
|
const wrappedResolver = (decision) => {
|
|
294652
294690
|
if (timer) clearTimeout(timer);
|
|
294653
294691
|
if (decision.always) {
|
|
294654
|
-
this.
|
|
294692
|
+
this.taskWideCache.set(req.toolName, decision.approved);
|
|
294655
294693
|
}
|
|
294656
294694
|
resolve(decision);
|
|
294657
294695
|
};
|
|
@@ -299132,6 +299170,25 @@ var DashboardSocket = class {
|
|
|
299132
299170
|
});
|
|
299133
299171
|
});
|
|
299134
299172
|
}
|
|
299173
|
+
/**
|
|
299174
|
+
* Boardroom plan decisions from a connected client. The desktop shows a
|
|
299175
|
+
* plan-review modal on `plan:approval-required` and answers here; the
|
|
299176
|
+
* server routes the decision into the paused run via resolvePlanApproval.
|
|
299177
|
+
*/
|
|
299178
|
+
onPlanDecision(callback) {
|
|
299179
|
+
this.io.on("connection", (socket) => {
|
|
299180
|
+
socket.on("plan:decision", (payload) => {
|
|
299181
|
+
if (typeof payload?.sessionId === "string" && typeof payload?.approved === "boolean") {
|
|
299182
|
+
callback({
|
|
299183
|
+
sessionId: payload.sessionId,
|
|
299184
|
+
approved: payload.approved,
|
|
299185
|
+
note: typeof payload.note === "string" && payload.note.trim() ? payload.note.trim() : void 0,
|
|
299186
|
+
editedPlan: payload.editedPlan
|
|
299187
|
+
});
|
|
299188
|
+
}
|
|
299189
|
+
});
|
|
299190
|
+
});
|
|
299191
|
+
}
|
|
299135
299192
|
onSessionSteer(callback) {
|
|
299136
299193
|
this.io.on("connection", (socket) => {
|
|
299137
299194
|
socket.on("session:steer", (payload) => {
|
|
@@ -299173,6 +299230,125 @@ var DashboardSocket = class {
|
|
|
299173
299230
|
this.io.close();
|
|
299174
299231
|
}
|
|
299175
299232
|
};
|
|
299233
|
+
|
|
299234
|
+
// src/scheduler/index.ts
|
|
299235
|
+
var import_node_cron = __toESM(require_node_cron(), 1);
|
|
299236
|
+
var TaskScheduler = class {
|
|
299237
|
+
cronJobs = /* @__PURE__ */ new Map();
|
|
299238
|
+
store;
|
|
299239
|
+
runner;
|
|
299240
|
+
constructor(store, runner) {
|
|
299241
|
+
this.store = store;
|
|
299242
|
+
this.runner = runner;
|
|
299243
|
+
}
|
|
299244
|
+
start() {
|
|
299245
|
+
const tasks = this.store.listScheduledTasks();
|
|
299246
|
+
for (const task of tasks) {
|
|
299247
|
+
if (task.enabled) this.schedule(task);
|
|
299248
|
+
}
|
|
299249
|
+
}
|
|
299250
|
+
stop() {
|
|
299251
|
+
for (const job of this.cronJobs.values()) job.stop();
|
|
299252
|
+
this.cronJobs.clear();
|
|
299253
|
+
}
|
|
299254
|
+
schedule(task) {
|
|
299255
|
+
if (!import_node_cron.default.validate(task.cronExpression)) {
|
|
299256
|
+
throw new Error(`Invalid cron expression: ${task.cronExpression}`);
|
|
299257
|
+
}
|
|
299258
|
+
const existing = this.cronJobs.get(task.id);
|
|
299259
|
+
if (existing) {
|
|
299260
|
+
try {
|
|
299261
|
+
existing.stop();
|
|
299262
|
+
} catch {
|
|
299263
|
+
}
|
|
299264
|
+
}
|
|
299265
|
+
const job = import_node_cron.default.schedule(task.cronExpression, async () => {
|
|
299266
|
+
try {
|
|
299267
|
+
task.lastRun = (/* @__PURE__ */ new Date()).toISOString();
|
|
299268
|
+
this.store.saveScheduledTask(task);
|
|
299269
|
+
await this.runner(task);
|
|
299270
|
+
} catch (err) {
|
|
299271
|
+
console.error(`[scheduler] Task "${task.id}" (${task.cronExpression}) failed:`, err);
|
|
299272
|
+
}
|
|
299273
|
+
}, { timezone: "UTC" });
|
|
299274
|
+
this.cronJobs.set(task.id, job);
|
|
299275
|
+
}
|
|
299276
|
+
unschedule(taskId) {
|
|
299277
|
+
this.cronJobs.get(taskId)?.stop();
|
|
299278
|
+
this.cronJobs.delete(taskId);
|
|
299279
|
+
}
|
|
299280
|
+
add(task) {
|
|
299281
|
+
this.store.saveScheduledTask(task);
|
|
299282
|
+
if (task.enabled) this.schedule(task);
|
|
299283
|
+
}
|
|
299284
|
+
remove(taskId) {
|
|
299285
|
+
this.unschedule(taskId);
|
|
299286
|
+
this.store.deleteScheduledTask(taskId);
|
|
299287
|
+
}
|
|
299288
|
+
list() {
|
|
299289
|
+
return this.store.listScheduledTasks();
|
|
299290
|
+
}
|
|
299291
|
+
isRunning(taskId) {
|
|
299292
|
+
return this.cronJobs.has(taskId);
|
|
299293
|
+
}
|
|
299294
|
+
static validateCron(expression) {
|
|
299295
|
+
return import_node_cron.default.validate(expression);
|
|
299296
|
+
}
|
|
299297
|
+
};
|
|
299298
|
+
|
|
299299
|
+
// src/dashboard/cost-stats.ts
|
|
299300
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
299301
|
+
function utcDateKey(d2) {
|
|
299302
|
+
return d2.toISOString().slice(0, 10);
|
|
299303
|
+
}
|
|
299304
|
+
function aggregateCostStats(sessions, opts = {}) {
|
|
299305
|
+
const days = Math.max(1, opts.days ?? 30);
|
|
299306
|
+
const topN = Math.max(1, opts.topN ?? 8);
|
|
299307
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
299308
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
299309
|
+
for (let i4 = days - 1; i4 >= 0; i4--) {
|
|
299310
|
+
const key = utcDateKey(new Date(now.getTime() - i4 * DAY_MS));
|
|
299311
|
+
buckets.set(key, { date: key, costUsd: 0, tokens: 0, runs: 0 });
|
|
299312
|
+
}
|
|
299313
|
+
let totalCostUsd = 0;
|
|
299314
|
+
let totalTokens = 0;
|
|
299315
|
+
let totalRuns = 0;
|
|
299316
|
+
for (const s3 of sessions) {
|
|
299317
|
+
const cost = s3.metadata?.totalCostUsd ?? 0;
|
|
299318
|
+
const tokens = s3.metadata?.totalTokens ?? 0;
|
|
299319
|
+
const runs = s3.metadata?.taskCount ?? 0;
|
|
299320
|
+
totalCostUsd += cost;
|
|
299321
|
+
totalTokens += tokens;
|
|
299322
|
+
totalRuns += runs;
|
|
299323
|
+
const when = new Date(s3.updatedAt || s3.createdAt);
|
|
299324
|
+
if (!Number.isNaN(when.getTime())) {
|
|
299325
|
+
const bucket = buckets.get(utcDateKey(when));
|
|
299326
|
+
if (bucket) {
|
|
299327
|
+
bucket.costUsd += cost;
|
|
299328
|
+
bucket.tokens += tokens;
|
|
299329
|
+
bucket.runs += runs;
|
|
299330
|
+
}
|
|
299331
|
+
}
|
|
299332
|
+
}
|
|
299333
|
+
const topSessions = [...sessions].filter((s3) => (s3.metadata?.totalCostUsd ?? 0) > 0).sort((a2, b3) => (b3.metadata?.totalCostUsd ?? 0) - (a2.metadata?.totalCostUsd ?? 0)).slice(0, topN).map((s3) => ({
|
|
299334
|
+
sessionId: s3.id,
|
|
299335
|
+
title: s3.title,
|
|
299336
|
+
costUsd: s3.metadata?.totalCostUsd ?? 0,
|
|
299337
|
+
tokens: s3.metadata?.totalTokens ?? 0,
|
|
299338
|
+
runs: s3.metadata?.taskCount ?? 0,
|
|
299339
|
+
updatedAt: s3.updatedAt
|
|
299340
|
+
}));
|
|
299341
|
+
return {
|
|
299342
|
+
totalCostUsd,
|
|
299343
|
+
totalTokens,
|
|
299344
|
+
totalSessions: sessions.length,
|
|
299345
|
+
totalRuns,
|
|
299346
|
+
perDay: [...buckets.values()],
|
|
299347
|
+
topSessions
|
|
299348
|
+
};
|
|
299349
|
+
}
|
|
299350
|
+
|
|
299351
|
+
// src/dashboard/server.ts
|
|
299176
299352
|
var __dirname2 = path22__namespace.default.dirname(Url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('desktop-core.cjs', document.baseURI).href))));
|
|
299177
299353
|
var DashboardServer = class {
|
|
299178
299354
|
app;
|
|
@@ -299198,6 +299374,14 @@ var DashboardServer = class {
|
|
|
299198
299374
|
* map is how that answer reaches the run that's blocked on it.
|
|
299199
299375
|
*/
|
|
299200
299376
|
pendingApprovals = /* @__PURE__ */ new Map();
|
|
299377
|
+
/**
|
|
299378
|
+
* The orchestration decision trail ("why") of each session's most recent
|
|
299379
|
+
* run — captured when the run ends so the desktop's Why panel can show it
|
|
299380
|
+
* after the fact. Bounded: oldest entry evicted past 50 sessions.
|
|
299381
|
+
*/
|
|
299382
|
+
whyBySession = /* @__PURE__ */ new Map();
|
|
299383
|
+
/** Cron scheduler for the Schedules UI — runs tasks while this server is up. */
|
|
299384
|
+
scheduler;
|
|
299201
299385
|
port;
|
|
299202
299386
|
host;
|
|
299203
299387
|
workspacePath;
|
|
@@ -299215,6 +299399,7 @@ var DashboardServer = class {
|
|
|
299215
299399
|
secret: this.dashboardSecret,
|
|
299216
299400
|
corsOrigin: config2.dashboard.auth ? [`http://localhost:${this.port}`, `http://127.0.0.1:${this.port}`] : "*"
|
|
299217
299401
|
});
|
|
299402
|
+
this.scheduler = new TaskScheduler(store, (task) => this.runScheduledTask(task));
|
|
299218
299403
|
this.setupMiddleware();
|
|
299219
299404
|
this.setupRoutes();
|
|
299220
299405
|
this.socket.onSessionRate((sessionId, rating) => {
|
|
@@ -299278,6 +299463,9 @@ var DashboardServer = class {
|
|
|
299278
299463
|
cascade.on("peer:message", (e3) => {
|
|
299279
299464
|
this.socket.emitPeerMessage(e3);
|
|
299280
299465
|
});
|
|
299466
|
+
cascade.on("plan:approval-required", (e3) => {
|
|
299467
|
+
this.socket.emitToSocket(socketId, "plan:approval-required", { sessionId, ...e3 });
|
|
299468
|
+
});
|
|
299281
299469
|
try {
|
|
299282
299470
|
const result = await cascade.run({
|
|
299283
299471
|
prompt: runPrompt,
|
|
@@ -299285,7 +299473,8 @@ var DashboardServer = class {
|
|
|
299285
299473
|
approvalCallback: this.makeApprovalCallback(sessionId)
|
|
299286
299474
|
});
|
|
299287
299475
|
this.recordSessionTask(sessionId, result.taskId);
|
|
299288
|
-
this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
|
|
299476
|
+
this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED", result);
|
|
299477
|
+
this.captureWhy(sessionId, cascade, result);
|
|
299289
299478
|
this.socket.emitToSocket(socketId, "session:complete", { sessionId, result });
|
|
299290
299479
|
this.socket.broadcast("cost:update", {
|
|
299291
299480
|
sessionId,
|
|
@@ -299295,6 +299484,7 @@ var DashboardServer = class {
|
|
|
299295
299484
|
this.throttledBroadcast("workspace");
|
|
299296
299485
|
} catch (err) {
|
|
299297
299486
|
this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
|
|
299487
|
+
this.captureWhy(sessionId, cascade);
|
|
299298
299488
|
this.socket.emitToSocket(socketId, "session:error", {
|
|
299299
299489
|
sessionId,
|
|
299300
299490
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -299305,9 +299495,17 @@ var DashboardServer = class {
|
|
|
299305
299495
|
this.denyPendingApprovals(sessionId);
|
|
299306
299496
|
}
|
|
299307
299497
|
});
|
|
299498
|
+
this.socket.onPlanDecision(({ sessionId, approved, note, editedPlan }) => {
|
|
299499
|
+
this.activeSessions.get(sessionId)?.resolvePlanApproval(
|
|
299500
|
+
approved,
|
|
299501
|
+
note,
|
|
299502
|
+
editedPlan
|
|
299503
|
+
);
|
|
299504
|
+
});
|
|
299308
299505
|
this.socket.onSessionHalt((sessionId) => {
|
|
299309
299506
|
this.activeControllers.get(sessionId)?.abort();
|
|
299310
299507
|
this.denyPendingApprovals(sessionId);
|
|
299508
|
+
this.activeSessions.get(sessionId)?.resolvePlanApproval(false);
|
|
299311
299509
|
});
|
|
299312
299510
|
this.socket.onApprovalResponse(({ requestId, approved, always }) => {
|
|
299313
299511
|
const pending = this.pendingApprovals.get(requestId);
|
|
@@ -299340,12 +299538,21 @@ var DashboardServer = class {
|
|
|
299340
299538
|
resolve();
|
|
299341
299539
|
});
|
|
299342
299540
|
});
|
|
299541
|
+
try {
|
|
299542
|
+
this.scheduler.start();
|
|
299543
|
+
} catch (err) {
|
|
299544
|
+
console.warn("[dashboard] failed to start task scheduler:", err);
|
|
299545
|
+
}
|
|
299343
299546
|
}
|
|
299344
299547
|
async stop() {
|
|
299345
299548
|
if (this.broadcastTimer) {
|
|
299346
299549
|
clearTimeout(this.broadcastTimer);
|
|
299347
299550
|
this.broadcastTimer = null;
|
|
299348
299551
|
}
|
|
299552
|
+
try {
|
|
299553
|
+
this.scheduler.stop();
|
|
299554
|
+
} catch {
|
|
299555
|
+
}
|
|
299349
299556
|
this.socket.close();
|
|
299350
299557
|
try {
|
|
299351
299558
|
this.globalStore?.close();
|
|
@@ -299359,6 +299566,16 @@ var DashboardServer = class {
|
|
|
299359
299566
|
getSocket() {
|
|
299360
299567
|
return this.socket;
|
|
299361
299568
|
}
|
|
299569
|
+
/**
|
|
299570
|
+
* Rebind the workspace tasks execute in — e.g. the desktop app's Code view
|
|
299571
|
+
* opening a different project folder — without tearing down the socket
|
|
299572
|
+
* server (which would drop the port/auth token/connection mid-session).
|
|
299573
|
+
* The next `cascade:run` picks this up immediately since `this.workspacePath`
|
|
299574
|
+
* is read live per-run (see onCascadeRun below).
|
|
299575
|
+
*/
|
|
299576
|
+
setWorkspacePath(workspacePath) {
|
|
299577
|
+
this.workspacePath = workspacePath;
|
|
299578
|
+
}
|
|
299362
299579
|
/**
|
|
299363
299580
|
* Write the in-memory config back to the workspace config file so mutations
|
|
299364
299581
|
* made over the socket (Settings → Save) persist across restarts. Best-effort:
|
|
@@ -299456,16 +299673,60 @@ var DashboardServer = class {
|
|
|
299456
299673
|
return title;
|
|
299457
299674
|
}
|
|
299458
299675
|
/** Record run end: the assistant reply (when there is one) and the runtime row's final status. */
|
|
299459
|
-
persistRunEnd(sessionId, title, latestPrompt, reply, status) {
|
|
299676
|
+
persistRunEnd(sessionId, title, latestPrompt, reply, status, result) {
|
|
299460
299677
|
try {
|
|
299461
299678
|
if (reply && reply.trim()) {
|
|
299462
299679
|
this.store.addMessage({ id: crypto4.randomUUID(), sessionId, role: "assistant", content: reply, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
299463
299680
|
}
|
|
299681
|
+
if (result) {
|
|
299682
|
+
const session = this.store.getSession(sessionId);
|
|
299683
|
+
if (session) {
|
|
299684
|
+
this.store.updateSession(sessionId, {
|
|
299685
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
299686
|
+
metadata: {
|
|
299687
|
+
...session.metadata,
|
|
299688
|
+
totalTokens: session.metadata.totalTokens + result.usage.totalTokens,
|
|
299689
|
+
totalCostUsd: session.metadata.totalCostUsd + result.usage.estimatedCostUsd,
|
|
299690
|
+
taskCount: session.metadata.taskCount + 1
|
|
299691
|
+
}
|
|
299692
|
+
});
|
|
299693
|
+
}
|
|
299694
|
+
}
|
|
299464
299695
|
} catch (err) {
|
|
299465
299696
|
console.warn("[dashboard] failed to persist run end:", err);
|
|
299466
299697
|
}
|
|
299467
299698
|
this.persistRuntimeRow(sessionId, title, status, latestPrompt);
|
|
299468
299699
|
}
|
|
299700
|
+
/**
|
|
299701
|
+
* Capture the run's decision trail + router economics ("why") and broadcast
|
|
299702
|
+
* it so the desktop's Why panel updates live; kept per-session for the
|
|
299703
|
+
* GET /api/sessions/:id/why fallback (panel opened after the run).
|
|
299704
|
+
*/
|
|
299705
|
+
captureWhy(sessionId, cascade, result) {
|
|
299706
|
+
try {
|
|
299707
|
+
const stats = cascade.getRouter().getStats();
|
|
299708
|
+
const savings = cascade.getRouter().getDelegationSavings();
|
|
299709
|
+
const report = {
|
|
299710
|
+
sessionId,
|
|
299711
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
299712
|
+
decisions: cascade.getDecisionLog(),
|
|
299713
|
+
savedUsd: savings.savedUsd,
|
|
299714
|
+
savedPct: savings.savedPct,
|
|
299715
|
+
totalCostUsd: stats.totalCostUsd,
|
|
299716
|
+
totalTokens: stats.totalTokens,
|
|
299717
|
+
costByTier: stats.costByTier,
|
|
299718
|
+
durationMs: result?.durationMs
|
|
299719
|
+
};
|
|
299720
|
+
this.whyBySession.set(sessionId, report);
|
|
299721
|
+
if (this.whyBySession.size > 50) {
|
|
299722
|
+
const oldest = this.whyBySession.keys().next().value;
|
|
299723
|
+
if (oldest) this.whyBySession.delete(oldest);
|
|
299724
|
+
}
|
|
299725
|
+
this.socket.broadcast("run:why", report);
|
|
299726
|
+
} catch (err) {
|
|
299727
|
+
console.warn("[dashboard] failed to capture decision trail:", err);
|
|
299728
|
+
}
|
|
299729
|
+
}
|
|
299469
299730
|
/**
|
|
299470
299731
|
* Route steering text into running Cascade instances. Targets the given
|
|
299471
299732
|
* session, or every active session when none is specified (the desktop
|
|
@@ -299495,6 +299756,52 @@ var DashboardServer = class {
|
|
|
299495
299756
|
this.pendingApprovals.set(request.id, { resolve, sessionId });
|
|
299496
299757
|
});
|
|
299497
299758
|
}
|
|
299759
|
+
/**
|
|
299760
|
+
* Execute one scheduled task firing. Runs headless (like `cascade run -p`):
|
|
299761
|
+
* tool approvals are auto-granted since nobody may be watching when the cron
|
|
299762
|
+
* fires — the Schedules UI states this. Events broadcast to every connected
|
|
299763
|
+
* client so an open desktop sees the run appear live.
|
|
299764
|
+
*/
|
|
299765
|
+
async runScheduledTask(task) {
|
|
299766
|
+
const sessionId = crypto4.randomUUID();
|
|
299767
|
+
const prompt = task.prompt;
|
|
299768
|
+
const title = this.persistRunStart(sessionId, `[${task.name}] ${prompt}`);
|
|
299769
|
+
const cascade = new Cascade(this.config, task.workspacePath ?? this.workspacePath, this.store);
|
|
299770
|
+
this.activeSessions.set(sessionId, cascade);
|
|
299771
|
+
cascade.on("tier:status", (e3) => {
|
|
299772
|
+
this.socket.broadcast("tier:status", { sessionId, ...e3 });
|
|
299773
|
+
});
|
|
299774
|
+
cascade.on("peer:message", (e3) => {
|
|
299775
|
+
this.socket.emitPeerMessage(e3);
|
|
299776
|
+
});
|
|
299777
|
+
try {
|
|
299778
|
+
const result = await cascade.run({
|
|
299779
|
+
prompt,
|
|
299780
|
+
identityId: task.identityId,
|
|
299781
|
+
approvalCallback: async () => ({ approved: true, always: false })
|
|
299782
|
+
});
|
|
299783
|
+
this.recordSessionTask(sessionId, result.taskId);
|
|
299784
|
+
this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED", result);
|
|
299785
|
+
this.captureWhy(sessionId, cascade, result);
|
|
299786
|
+
this.socket.broadcast("session:complete", { sessionId, result });
|
|
299787
|
+
this.socket.broadcast("cost:update", {
|
|
299788
|
+
sessionId,
|
|
299789
|
+
totalTokens: result.usage.totalTokens,
|
|
299790
|
+
totalCostUsd: result.usage.estimatedCostUsd
|
|
299791
|
+
});
|
|
299792
|
+
this.throttledBroadcast("workspace");
|
|
299793
|
+
} catch (err) {
|
|
299794
|
+
this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
|
|
299795
|
+
this.captureWhy(sessionId, cascade);
|
|
299796
|
+
this.socket.broadcast("session:error", {
|
|
299797
|
+
sessionId,
|
|
299798
|
+
error: err instanceof Error ? err.message : String(err)
|
|
299799
|
+
});
|
|
299800
|
+
throw err;
|
|
299801
|
+
} finally {
|
|
299802
|
+
this.activeSessions.delete(sessionId);
|
|
299803
|
+
}
|
|
299804
|
+
}
|
|
299498
299805
|
/** Deny + clear any approvals still pending for a session (run end / abort). */
|
|
299499
299806
|
denyPendingApprovals(sessionId) {
|
|
299500
299807
|
for (const [id, pending] of this.pendingApprovals) {
|
|
@@ -300007,6 +300314,9 @@ ${prompt}`;
|
|
|
300007
300314
|
cascade.on("peer:message", (e3) => {
|
|
300008
300315
|
this.socket.emitPeerMessage(e3);
|
|
300009
300316
|
});
|
|
300317
|
+
cascade.on("plan:approval-required", (e3) => {
|
|
300318
|
+
this.socket.broadcastToRoom(`session:${sessionId}`, "plan:approval-required", { sessionId, ...e3 });
|
|
300319
|
+
});
|
|
300010
300320
|
try {
|
|
300011
300321
|
const result = await cascade.run({
|
|
300012
300322
|
prompt: runPrompt,
|
|
@@ -300014,7 +300324,8 @@ ${prompt}`;
|
|
|
300014
300324
|
approvalCallback: this.makeApprovalCallback(sessionId)
|
|
300015
300325
|
});
|
|
300016
300326
|
this.recordSessionTask(sessionId, result.taskId);
|
|
300017
|
-
this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
|
|
300327
|
+
this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED", result);
|
|
300328
|
+
this.captureWhy(sessionId, cascade, result);
|
|
300018
300329
|
this.socket.broadcast("cost:update", {
|
|
300019
300330
|
sessionId,
|
|
300020
300331
|
totalTokens: result.usage.totalTokens,
|
|
@@ -300024,6 +300335,7 @@ ${prompt}`;
|
|
|
300024
300335
|
this.throttledBroadcast("workspace");
|
|
300025
300336
|
} catch (err) {
|
|
300026
300337
|
this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
|
|
300338
|
+
this.captureWhy(sessionId, cascade);
|
|
300027
300339
|
this.socket.broadcastToRoom(`session:${sessionId}`, "session:error", {
|
|
300028
300340
|
sessionId,
|
|
300029
300341
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -300045,6 +300357,168 @@ ${prompt}`;
|
|
|
300045
300357
|
}))
|
|
300046
300358
|
});
|
|
300047
300359
|
});
|
|
300360
|
+
this.app.get("/api/sessions/:id/why", auth, (req, res) => {
|
|
300361
|
+
const report = this.whyBySession.get(req.params.id);
|
|
300362
|
+
if (!report) {
|
|
300363
|
+
res.status(404).json({ error: "No decision trail recorded for this session in the current app run." });
|
|
300364
|
+
return;
|
|
300365
|
+
}
|
|
300366
|
+
res.json(report);
|
|
300367
|
+
});
|
|
300368
|
+
this.app.get("/api/sessions/:id/changes", auth, async (req, res) => {
|
|
300369
|
+
const sessionId = req.params.id;
|
|
300370
|
+
const taskIds = this.sessionTaskIds.get(sessionId) ?? [];
|
|
300371
|
+
const before = /* @__PURE__ */ new Map();
|
|
300372
|
+
for (const taskId of taskIds) {
|
|
300373
|
+
for (const { filePath, content } of this.store.getLatestFileSnapshots(taskId)) {
|
|
300374
|
+
if (!before.has(filePath)) before.set(filePath, content);
|
|
300375
|
+
}
|
|
300376
|
+
}
|
|
300377
|
+
const { readFile } = await import('fs/promises');
|
|
300378
|
+
const MAX_DIFF_BYTES = 2 * 1024 * 1024;
|
|
300379
|
+
const changes = await Promise.all([...before.entries()].map(async ([filePath, beforeContent]) => {
|
|
300380
|
+
let after = "";
|
|
300381
|
+
let missing = false;
|
|
300382
|
+
try {
|
|
300383
|
+
const buf = await readFile(filePath);
|
|
300384
|
+
after = buf.length > MAX_DIFF_BYTES ? `[file too large to diff \u2014 ${buf.length} bytes]` : buf.toString("utf-8");
|
|
300385
|
+
} catch {
|
|
300386
|
+
missing = true;
|
|
300387
|
+
}
|
|
300388
|
+
return { filePath, before: beforeContent, after, missing, changed: missing || after !== beforeContent };
|
|
300389
|
+
}));
|
|
300390
|
+
res.json({ sessionId, changes: changes.filter((c4) => c4.changed) });
|
|
300391
|
+
});
|
|
300392
|
+
this.app.post("/api/sessions/:id/revert-file", auth, mutationLimiter, async (req, res) => {
|
|
300393
|
+
const sessionId = req.params.id;
|
|
300394
|
+
const body = req.body;
|
|
300395
|
+
if (!body.filePath || typeof body.filePath !== "string") {
|
|
300396
|
+
res.status(400).json({ error: "filePath is required" });
|
|
300397
|
+
return;
|
|
300398
|
+
}
|
|
300399
|
+
const taskIds = this.sessionTaskIds.get(sessionId) ?? [];
|
|
300400
|
+
let content;
|
|
300401
|
+
for (const taskId of taskIds) {
|
|
300402
|
+
const snap = this.store.getLatestFileSnapshots(taskId).find((s3) => s3.filePath === body.filePath);
|
|
300403
|
+
if (snap) {
|
|
300404
|
+
content = snap.content;
|
|
300405
|
+
break;
|
|
300406
|
+
}
|
|
300407
|
+
}
|
|
300408
|
+
if (content === void 0) {
|
|
300409
|
+
res.status(404).json({ error: "No snapshot recorded for that file in this session." });
|
|
300410
|
+
return;
|
|
300411
|
+
}
|
|
300412
|
+
try {
|
|
300413
|
+
const { writeFile: writeFile2 } = await import('fs/promises');
|
|
300414
|
+
await writeFile2(body.filePath, content, "utf-8");
|
|
300415
|
+
res.json({ ok: true, filePath: body.filePath });
|
|
300416
|
+
} catch (err) {
|
|
300417
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
300418
|
+
}
|
|
300419
|
+
});
|
|
300420
|
+
this.app.get("/api/costs", auth, (_req, res) => {
|
|
300421
|
+
try {
|
|
300422
|
+
const sessions = this.store.listSessions(void 0, 1e3);
|
|
300423
|
+
const budget = this.config.budget ?? { warnAtPct: 80 };
|
|
300424
|
+
res.json({
|
|
300425
|
+
...aggregateCostStats(sessions, { days: 30, topN: 8 }),
|
|
300426
|
+
budget: {
|
|
300427
|
+
dailyBudgetUsd: budget.dailyBudgetUsd,
|
|
300428
|
+
sessionBudgetUsd: budget.sessionBudgetUsd,
|
|
300429
|
+
maxCostPerRunUsd: budget.maxCostPerRunUsd
|
|
300430
|
+
}
|
|
300431
|
+
});
|
|
300432
|
+
} catch (err) {
|
|
300433
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
300434
|
+
}
|
|
300435
|
+
});
|
|
300436
|
+
this.app.get("/api/schedules", auth, (_req, res) => {
|
|
300437
|
+
try {
|
|
300438
|
+
res.json(this.scheduler.list().map((t4) => ({ ...t4, armed: this.scheduler.isRunning(t4.id) })));
|
|
300439
|
+
} catch (err) {
|
|
300440
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
300441
|
+
}
|
|
300442
|
+
});
|
|
300443
|
+
this.app.post("/api/schedules", auth, mutationLimiter, (req, res) => {
|
|
300444
|
+
const body = req.body;
|
|
300445
|
+
if (!body.name?.trim() || !body.cronExpression?.trim() || !body.prompt?.trim()) {
|
|
300446
|
+
res.status(400).json({ error: "name, cronExpression, and prompt are required" });
|
|
300447
|
+
return;
|
|
300448
|
+
}
|
|
300449
|
+
if (!TaskScheduler.validateCron(body.cronExpression.trim())) {
|
|
300450
|
+
res.status(400).json({ error: `Invalid cron expression: ${body.cronExpression}` });
|
|
300451
|
+
return;
|
|
300452
|
+
}
|
|
300453
|
+
const task = {
|
|
300454
|
+
id: crypto4.randomUUID(),
|
|
300455
|
+
name: body.name.trim(),
|
|
300456
|
+
cronExpression: body.cronExpression.trim(),
|
|
300457
|
+
prompt: body.prompt.trim(),
|
|
300458
|
+
workspacePath: this.workspacePath,
|
|
300459
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
300460
|
+
enabled: body.enabled !== false
|
|
300461
|
+
};
|
|
300462
|
+
try {
|
|
300463
|
+
this.scheduler.add(task);
|
|
300464
|
+
res.json(task);
|
|
300465
|
+
} catch (err) {
|
|
300466
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
300467
|
+
}
|
|
300468
|
+
});
|
|
300469
|
+
this.app.put("/api/schedules/:id", auth, mutationLimiter, (req, res) => {
|
|
300470
|
+
const id = req.params.id;
|
|
300471
|
+
const existing = this.scheduler.list().find((t4) => t4.id === id);
|
|
300472
|
+
if (!existing) {
|
|
300473
|
+
res.status(404).json({ error: "Not found" });
|
|
300474
|
+
return;
|
|
300475
|
+
}
|
|
300476
|
+
const body = req.body;
|
|
300477
|
+
if (body.cronExpression !== void 0 && !TaskScheduler.validateCron(body.cronExpression)) {
|
|
300478
|
+
res.status(400).json({ error: `Invalid cron expression: ${body.cronExpression}` });
|
|
300479
|
+
return;
|
|
300480
|
+
}
|
|
300481
|
+
const updated = {
|
|
300482
|
+
...existing,
|
|
300483
|
+
name: body.name?.trim() || existing.name,
|
|
300484
|
+
cronExpression: body.cronExpression?.trim() || existing.cronExpression,
|
|
300485
|
+
prompt: body.prompt?.trim() || existing.prompt,
|
|
300486
|
+
enabled: body.enabled ?? existing.enabled
|
|
300487
|
+
};
|
|
300488
|
+
try {
|
|
300489
|
+
this.scheduler.add(updated);
|
|
300490
|
+
if (!updated.enabled) this.scheduler.unschedule(id);
|
|
300491
|
+
res.json(updated);
|
|
300492
|
+
} catch (err) {
|
|
300493
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
300494
|
+
}
|
|
300495
|
+
});
|
|
300496
|
+
this.app.delete("/api/schedules/:id", auth, mutationLimiter, (req, res) => {
|
|
300497
|
+
try {
|
|
300498
|
+
this.scheduler.remove(req.params.id);
|
|
300499
|
+
res.json({ ok: true });
|
|
300500
|
+
} catch (err) {
|
|
300501
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
300502
|
+
}
|
|
300503
|
+
});
|
|
300504
|
+
this.app.get("/api/audit-chain", auth, async (req, res) => {
|
|
300505
|
+
try {
|
|
300506
|
+
const limit2 = Math.min(500, Math.max(1, parseInt(req.query["limit"] || "200", 10) || 200));
|
|
300507
|
+
const offset = Math.max(0, parseInt(req.query["offset"] || "0", 10) || 0);
|
|
300508
|
+
const { AuditLogger: AuditLogger3 } = await Promise.resolve().then(() => (init_audit_logger(), audit_logger_exports));
|
|
300509
|
+
const logger = new AuditLogger3(this.workspacePath);
|
|
300510
|
+
try {
|
|
300511
|
+
const all = logger.getAllLogs();
|
|
300512
|
+
const total = all.length;
|
|
300513
|
+
const entries = all.reverse().slice(offset, offset + limit2);
|
|
300514
|
+
res.json({ total, offset, entries });
|
|
300515
|
+
} finally {
|
|
300516
|
+
logger.close();
|
|
300517
|
+
}
|
|
300518
|
+
} catch (err) {
|
|
300519
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
300520
|
+
}
|
|
300521
|
+
});
|
|
300048
300522
|
const prodPath = path22__namespace.default.resolve(__dirname2, "../web/dist");
|
|
300049
300523
|
const devPath = path22__namespace.default.resolve(__dirname2, "../../web/dist");
|
|
300050
300524
|
const webDistPath = fs11__namespace.default.existsSync(prodPath) ? prodPath : devPath;
|
|
@@ -300066,71 +300540,6 @@ ${prompt}`;
|
|
|
300066
300540
|
}
|
|
300067
300541
|
}
|
|
300068
300542
|
};
|
|
300069
|
-
|
|
300070
|
-
// src/scheduler/index.ts
|
|
300071
|
-
var import_node_cron = __toESM(require_node_cron(), 1);
|
|
300072
|
-
var TaskScheduler = class {
|
|
300073
|
-
cronJobs = /* @__PURE__ */ new Map();
|
|
300074
|
-
store;
|
|
300075
|
-
runner;
|
|
300076
|
-
constructor(store, runner) {
|
|
300077
|
-
this.store = store;
|
|
300078
|
-
this.runner = runner;
|
|
300079
|
-
}
|
|
300080
|
-
start() {
|
|
300081
|
-
const tasks = this.store.listScheduledTasks();
|
|
300082
|
-
for (const task of tasks) {
|
|
300083
|
-
if (task.enabled) this.schedule(task);
|
|
300084
|
-
}
|
|
300085
|
-
}
|
|
300086
|
-
stop() {
|
|
300087
|
-
for (const job of this.cronJobs.values()) job.stop();
|
|
300088
|
-
this.cronJobs.clear();
|
|
300089
|
-
}
|
|
300090
|
-
schedule(task) {
|
|
300091
|
-
if (!import_node_cron.default.validate(task.cronExpression)) {
|
|
300092
|
-
throw new Error(`Invalid cron expression: ${task.cronExpression}`);
|
|
300093
|
-
}
|
|
300094
|
-
const existing = this.cronJobs.get(task.id);
|
|
300095
|
-
if (existing) {
|
|
300096
|
-
try {
|
|
300097
|
-
existing.stop();
|
|
300098
|
-
} catch {
|
|
300099
|
-
}
|
|
300100
|
-
}
|
|
300101
|
-
const job = import_node_cron.default.schedule(task.cronExpression, async () => {
|
|
300102
|
-
try {
|
|
300103
|
-
task.lastRun = (/* @__PURE__ */ new Date()).toISOString();
|
|
300104
|
-
this.store.saveScheduledTask(task);
|
|
300105
|
-
await this.runner(task);
|
|
300106
|
-
} catch (err) {
|
|
300107
|
-
console.error(`[scheduler] Task "${task.id}" (${task.cronExpression}) failed:`, err);
|
|
300108
|
-
}
|
|
300109
|
-
}, { timezone: "UTC" });
|
|
300110
|
-
this.cronJobs.set(task.id, job);
|
|
300111
|
-
}
|
|
300112
|
-
unschedule(taskId) {
|
|
300113
|
-
this.cronJobs.get(taskId)?.stop();
|
|
300114
|
-
this.cronJobs.delete(taskId);
|
|
300115
|
-
}
|
|
300116
|
-
add(task) {
|
|
300117
|
-
this.store.saveScheduledTask(task);
|
|
300118
|
-
if (task.enabled) this.schedule(task);
|
|
300119
|
-
}
|
|
300120
|
-
remove(taskId) {
|
|
300121
|
-
this.unschedule(taskId);
|
|
300122
|
-
this.store.deleteScheduledTask(taskId);
|
|
300123
|
-
}
|
|
300124
|
-
list() {
|
|
300125
|
-
return this.store.listScheduledTasks();
|
|
300126
|
-
}
|
|
300127
|
-
isRunning(taskId) {
|
|
300128
|
-
return this.cronJobs.has(taskId);
|
|
300129
|
-
}
|
|
300130
|
-
static validateCron(expression) {
|
|
300131
|
-
return import_node_cron.default.validate(expression);
|
|
300132
|
-
}
|
|
300133
|
-
};
|
|
300134
300543
|
var execFileAsync2 = util$1.promisify(child_process.execFile);
|
|
300135
300544
|
var SAFE_ENV_NAME = /^[A-Z][A-Z0-9_]*$/;
|
|
300136
300545
|
function sanitizeEnvValue(v6) {
|