cascade-ai 0.77.0 → 0.78.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 +268 -4
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +268 -4
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +269 -4
- package/dist/index.cjs +269 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +252 -1
- package/dist/index.d.ts +252 -1
- package/dist/index.js +269 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11623,7 +11623,7 @@ function reconcilePrice(model, live) {
|
|
|
11623
11623
|
}
|
|
11624
11624
|
|
|
11625
11625
|
// src/constants.ts
|
|
11626
|
-
var CASCADE_VERSION = "0.
|
|
11626
|
+
var CASCADE_VERSION = "0.78.0";
|
|
11627
11627
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
11628
11628
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
11629
11629
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -16084,6 +16084,20 @@ var ToolsConfigSchema = z.object({
|
|
|
16084
16084
|
shellBlocklist: z.array(z.string()).default(["rm -rf", "sudo rm", "format", "mkfs"]),
|
|
16085
16085
|
requireApprovalFor: z.array(z.string()).default([]),
|
|
16086
16086
|
browserEnabled: z.boolean().default(false),
|
|
16087
|
+
/**
|
|
16088
|
+
* Let a run ACT on the page open in the desktop's built-in browser — click,
|
|
16089
|
+
* type, submit — not just read it.
|
|
16090
|
+
*
|
|
16091
|
+
* Distinct from `browserEnabled`, which turns on the headless Playwright
|
|
16092
|
+
* tool. That one drives a throwaway browser with an empty cookie jar; this
|
|
16093
|
+
* one drives the session the user is signed into, which is a different order
|
|
16094
|
+
* of consequence and so gets its own switch rather than riding on that one.
|
|
16095
|
+
*
|
|
16096
|
+
* Off by default. Turning it on does not hand anything over silently: the
|
|
16097
|
+
* tool still goes through the approval chain, and the browser panel carries a
|
|
16098
|
+
* stop control for the run that is acting.
|
|
16099
|
+
*/
|
|
16100
|
+
agentBrowserControl: z.boolean().default(false),
|
|
16087
16101
|
mcpServers: z.array(McpServerConfigSchema).optional(),
|
|
16088
16102
|
mcpTrusted: z.array(z.string()).optional(),
|
|
16089
16103
|
/** Web search backends — at least one should be configured for best results */
|
|
@@ -20868,6 +20882,23 @@ Now execute your subtask using this context where relevant.`
|
|
|
20868
20882
|
this.setStatus("FAILED", finalOutput);
|
|
20869
20883
|
this.peerBus?.publish(this.id, assignment.subtaskId, finalOutput, "FAILED");
|
|
20870
20884
|
return this.buildResult("ESCALATED", finalOutput, { checksRun, passed, failed }, issues, correctionAttempts);
|
|
20885
|
+
} finally {
|
|
20886
|
+
this.releaseBrowserControl();
|
|
20887
|
+
}
|
|
20888
|
+
}
|
|
20889
|
+
/**
|
|
20890
|
+
* End this worker's hold on the shared browser.
|
|
20891
|
+
*
|
|
20892
|
+
* Duck-typed rather than imported: `browser_control` is registered only by a
|
|
20893
|
+
* host that HAS a browser (the desktop), so in the CLI and hosted runs there
|
|
20894
|
+
* is no such tool and this is a no-op. Importing the class to instanceof-check
|
|
20895
|
+
* it would couple the worker to a tool that usually is not there.
|
|
20896
|
+
*/
|
|
20897
|
+
releaseBrowserControl() {
|
|
20898
|
+
try {
|
|
20899
|
+
const tool = this.toolRegistry.getTool?.("browser_control");
|
|
20900
|
+
tool?.releaseActor?.(this.id);
|
|
20901
|
+
} catch {
|
|
20871
20902
|
}
|
|
20872
20903
|
}
|
|
20873
20904
|
sendToPeer(toId, content) {
|
|
@@ -30684,6 +30715,127 @@ var CurrentPageTool = class extends BaseTool {
|
|
|
30684
30715
|
}
|
|
30685
30716
|
};
|
|
30686
30717
|
|
|
30718
|
+
// src/tools/browser-control.ts
|
|
30719
|
+
var BrowserControlTool = class extends BaseTool {
|
|
30720
|
+
name = "browser_control";
|
|
30721
|
+
description = "Act on the web page the user has open in the built-in browser: navigate, click an element, type into a field, press a key, or wait for something to appear. This drives the REAL browser the user is signed into and can see, so actions have real consequences \u2014 prefer read_current_page when you only need to look. CSS selectors are matched against the live page; call extract_text first if you need to find one. There is one browser: if another part of this run is using it, this call waits its turn, so a slow reply means queued, not stuck.";
|
|
30722
|
+
inputSchema = {
|
|
30723
|
+
type: "object",
|
|
30724
|
+
properties: {
|
|
30725
|
+
action: {
|
|
30726
|
+
type: "string",
|
|
30727
|
+
enum: ["navigate", "click", "fill", "press", "wait_for", "extract_text"],
|
|
30728
|
+
description: "What to do."
|
|
30729
|
+
},
|
|
30730
|
+
url: { type: "string", description: "For navigate: the http(s) address to open." },
|
|
30731
|
+
selector: {
|
|
30732
|
+
type: "string",
|
|
30733
|
+
description: "CSS selector for click, fill and wait_for. Optional for extract_text (defaults to the whole page)."
|
|
30734
|
+
},
|
|
30735
|
+
value: { type: "string", description: "For fill: the text to type." },
|
|
30736
|
+
key: { type: "string", description: 'For press: a key name such as "Enter", "Tab" or "Escape".' },
|
|
30737
|
+
timeoutMs: { type: "number", description: "For wait_for: how long to wait before giving up (default 10000, max 30000)." }
|
|
30738
|
+
},
|
|
30739
|
+
required: ["action"]
|
|
30740
|
+
};
|
|
30741
|
+
controller;
|
|
30742
|
+
release;
|
|
30743
|
+
/** One-way. See `revoke`. */
|
|
30744
|
+
revoked = false;
|
|
30745
|
+
constructor(controller, release) {
|
|
30746
|
+
super();
|
|
30747
|
+
this.controller = controller;
|
|
30748
|
+
this.release = release;
|
|
30749
|
+
}
|
|
30750
|
+
/**
|
|
30751
|
+
* A worker has finished. Ends its hold on the browser.
|
|
30752
|
+
*
|
|
30753
|
+
* Called from the tier's terminal path rather than by the model, because a
|
|
30754
|
+
* model cannot be relied on to announce that it is done — which is why the
|
|
30755
|
+
* host cannot use idle time as the boundary instead.
|
|
30756
|
+
*/
|
|
30757
|
+
releaseActor(actorId) {
|
|
30758
|
+
this.release?.(actorId);
|
|
30759
|
+
}
|
|
30760
|
+
/**
|
|
30761
|
+
* Permanently disable this tool, whatever the registry still holds.
|
|
30762
|
+
*
|
|
30763
|
+
* Registration-time gates cannot express "this run turned out to be
|
|
30764
|
+
* unattended": a host that wires the controller and only then declares the
|
|
30765
|
+
* run unattended has already registered the tool, and a check that ran at
|
|
30766
|
+
* registration has no second chance. Enforcing at EXECUTION time makes the
|
|
30767
|
+
* guarantee independent of call order, which is the whole point of stating
|
|
30768
|
+
* the invariant rather than relying on the scheduler's current sequence.
|
|
30769
|
+
*
|
|
30770
|
+
* One-way on purpose. A safety gate that can be re-opened by a later call is
|
|
30771
|
+
* a gate that a refactor re-opens by accident.
|
|
30772
|
+
*/
|
|
30773
|
+
revoke() {
|
|
30774
|
+
this.revoked = true;
|
|
30775
|
+
}
|
|
30776
|
+
// Acting on a session the user is signed into is the highest-consequence
|
|
30777
|
+
// thing in the tool set — higher than shell, arguably, because the blast
|
|
30778
|
+
// radius is someone else's server rather than this machine.
|
|
30779
|
+
isDangerous() {
|
|
30780
|
+
return true;
|
|
30781
|
+
}
|
|
30782
|
+
async execute(input, options) {
|
|
30783
|
+
if (this.revoked) {
|
|
30784
|
+
return "Error: browser control is not available in this run. It requires a person watching who can stop it, and nobody is.";
|
|
30785
|
+
}
|
|
30786
|
+
const kind = input["action"];
|
|
30787
|
+
if (!kind) return "Error: action is required.";
|
|
30788
|
+
if (options?.signal?.aborted) return "Error: the run was cancelled before this action ran.";
|
|
30789
|
+
const missing = requiredFieldFor(kind, input);
|
|
30790
|
+
if (missing) return `Error: "${kind}" needs ${missing}.`;
|
|
30791
|
+
const action = {
|
|
30792
|
+
kind,
|
|
30793
|
+
...typeof input["url"] === "string" ? { url: input["url"] } : {},
|
|
30794
|
+
...typeof input["selector"] === "string" ? { selector: input["selector"] } : {},
|
|
30795
|
+
...typeof input["value"] === "string" ? { value: input["value"] } : {},
|
|
30796
|
+
...typeof input["key"] === "string" ? { key: input["key"] } : {},
|
|
30797
|
+
...typeof input["timeoutMs"] === "number" ? { timeoutMs: Math.min(Math.max(input["timeoutMs"], 0), 3e4) } : {}
|
|
30798
|
+
};
|
|
30799
|
+
let outcome;
|
|
30800
|
+
try {
|
|
30801
|
+
outcome = await this.controller(action, {
|
|
30802
|
+
// `sessionId` is the run. The host scopes both revocation and its
|
|
30803
|
+
// single-owner lease on it, so passing it is not telemetry — drop it
|
|
30804
|
+
// and a Stop in one run silently stops every later one.
|
|
30805
|
+
sessionId: options?.sessionId ?? "",
|
|
30806
|
+
actorId: options?.tierId ?? options?.sessionId ?? "",
|
|
30807
|
+
...options?.signal ? { signal: options.signal } : {}
|
|
30808
|
+
});
|
|
30809
|
+
} catch (err) {
|
|
30810
|
+
return `Error: the browser could not perform "${kind}" \u2014 ${err instanceof Error ? err.message : String(err)}`;
|
|
30811
|
+
}
|
|
30812
|
+
const where = outcome.url ? `
|
|
30813
|
+
Page: ${outcome.title ? `${outcome.title} \u2014 ` : ""}${outcome.url}` : "";
|
|
30814
|
+
return `${outcome.ok ? "" : "Failed: "}${outcome.detail}${where}`;
|
|
30815
|
+
}
|
|
30816
|
+
};
|
|
30817
|
+
function requiredFieldFor(kind, input) {
|
|
30818
|
+
const has = (k) => typeof input[k] === "string" && input[k].length > 0;
|
|
30819
|
+
const given = (k) => typeof input[k] === "string";
|
|
30820
|
+
switch (kind) {
|
|
30821
|
+
case "navigate":
|
|
30822
|
+
return has("url") ? null : "a url";
|
|
30823
|
+
case "click":
|
|
30824
|
+
return has("selector") ? null : "a selector";
|
|
30825
|
+
case "fill":
|
|
30826
|
+
return has("selector") ? given("value") ? null : "a value" : "a selector";
|
|
30827
|
+
case "press":
|
|
30828
|
+
return has("key") ? null : "a key";
|
|
30829
|
+
case "wait_for":
|
|
30830
|
+
return has("selector") ? null : "a selector";
|
|
30831
|
+
// extract_text works with or without a selector.
|
|
30832
|
+
case "extract_text":
|
|
30833
|
+
return null;
|
|
30834
|
+
default:
|
|
30835
|
+
return null;
|
|
30836
|
+
}
|
|
30837
|
+
}
|
|
30838
|
+
|
|
30687
30839
|
// src/core/cascade.ts
|
|
30688
30840
|
var ESCALATION_DECISION_TIMEOUT_MS = 5 * 6e4;
|
|
30689
30841
|
function buildContextualPrompt(prompt, history = []) {
|
|
@@ -31176,6 +31328,49 @@ ${input}` }],
|
|
|
31176
31328
|
if ((this.config.tools?.disabledTools ?? []).includes("read_current_page")) return;
|
|
31177
31329
|
this.toolRegistry.register(new CurrentPageTool(provider));
|
|
31178
31330
|
}
|
|
31331
|
+
/**
|
|
31332
|
+
* True when nobody is watching this run — a scheduled/cron task, which
|
|
31333
|
+
* auto-approves every tool because there is no one to ask.
|
|
31334
|
+
*
|
|
31335
|
+
* That auto-approval is fine for ordinary tools and disqualifying for this
|
|
31336
|
+
* one: `browser_control` drives the signed-in browser, and "every run asks
|
|
31337
|
+
* first" is the promise the Settings copy makes. The property used to hold
|
|
31338
|
+
* only because `runScheduledTask` happens not to call `setBrowserController`
|
|
31339
|
+
* — a guarantee resting on an ABSENT call, which is exactly what a later
|
|
31340
|
+
* refactor adds without noticing. Stated and enforced instead.
|
|
31341
|
+
*/
|
|
31342
|
+
unattended = false;
|
|
31343
|
+
/**
|
|
31344
|
+
* Declare that no human is watching this run, so capabilities that depend on
|
|
31345
|
+
* a person being able to see and stop them are refused outright.
|
|
31346
|
+
*/
|
|
31347
|
+
setUnattended(on) {
|
|
31348
|
+
this.unattended = on;
|
|
31349
|
+
if (!on) return;
|
|
31350
|
+
const tool = this.toolRegistry.getTool("browser_control");
|
|
31351
|
+
tool?.revoke?.();
|
|
31352
|
+
}
|
|
31353
|
+
/**
|
|
31354
|
+
* Wire the host's browser so a run can ACT on the open page, not just read it.
|
|
31355
|
+
*
|
|
31356
|
+
* Two gates before this registers anything, and both matter:
|
|
31357
|
+
*
|
|
31358
|
+
* - `tools.agentBrowserControl` must be on. Unlike read_current_page, which
|
|
31359
|
+
* reaches nothing the user has not already opened themselves, this drives
|
|
31360
|
+
* the session they are signed into. That is opt-in, not a default.
|
|
31361
|
+
* - `disabledTools` still removes it, the same as every other tool.
|
|
31362
|
+
* - The run must not be UNATTENDED. See `setUnattended`.
|
|
31363
|
+
*
|
|
31364
|
+
* Host-supplied like the page provider: the tool exists only where there is a
|
|
31365
|
+
* real browser to drive, so it never has to be refused in the CLI or a hosted
|
|
31366
|
+
* run — it is simply not there.
|
|
31367
|
+
*/
|
|
31368
|
+
setBrowserController(controller, release) {
|
|
31369
|
+
if (this.unattended) return;
|
|
31370
|
+
if (!this.config.tools?.agentBrowserControl) return;
|
|
31371
|
+
if ((this.config.tools?.disabledTools ?? []).includes("browser_control")) return;
|
|
31372
|
+
this.toolRegistry.register(new BrowserControlTool(controller, release));
|
|
31373
|
+
}
|
|
31179
31374
|
registerMediaTools(workspacePath) {
|
|
31180
31375
|
const denied = new Set(this.config.tools?.disabledTools ?? []);
|
|
31181
31376
|
const permitted = (name) => !denied.has(name);
|
|
@@ -31781,6 +31976,7 @@ ${prompt}` : prompt;
|
|
|
31781
31976
|
this.router.setRunSignal(options.signal);
|
|
31782
31977
|
const startMs = Date.now();
|
|
31783
31978
|
const taskId = randomUUID();
|
|
31979
|
+
this.emit("run:started", { taskId });
|
|
31784
31980
|
this.decisionLog = [];
|
|
31785
31981
|
this.currentRunCompleted = [];
|
|
31786
31982
|
this.wroteCheckpointThisRun = false;
|
|
@@ -32546,6 +32742,7 @@ function applySettingsPayload(config, data) {
|
|
|
32546
32742
|
if (["auto", "T1", "T2", "T3"].includes(a["forceTier"])) config.routing = { ...config.routing ?? {}, forceTier: a["forceTier"] };
|
|
32547
32743
|
if (typeof a["benchmarksLive"] === "boolean") config.benchmarks = { ...config.benchmarks ?? {}, live: a["benchmarksLive"] };
|
|
32548
32744
|
if (["isolate", "worker", "auto"].includes(a["dynamicToolSandbox"])) config.tools = { ...config.tools ?? {}, dynamicToolSandbox: a["dynamicToolSandbox"] };
|
|
32745
|
+
if (typeof a["agentBrowserControl"] === "boolean") config.tools = { ...config.tools ?? {}, agentBrowserControl: a["agentBrowserControl"] };
|
|
32549
32746
|
if (typeof a["factsExtraction"] === "boolean") config.knowledge = { ...config.knowledge ?? {}, factsExtraction: a["factsExtraction"] };
|
|
32550
32747
|
if (typeof a["rememberSessions"] === "boolean") config.memory = { ...config.memory ?? {}, rememberSessions: a["rememberSessions"] };
|
|
32551
32748
|
if (typeof a["enableToolCreation"] === "boolean") config.enableToolCreation = a["enableToolCreation"];
|
|
@@ -32603,6 +32800,7 @@ function settingsSnapshot(config) {
|
|
|
32603
32800
|
forceTier: config.routing?.forceTier,
|
|
32604
32801
|
benchmarksLive: config.benchmarks?.live,
|
|
32605
32802
|
dynamicToolSandbox: config.tools?.dynamicToolSandbox,
|
|
32803
|
+
agentBrowserControl: config.tools?.agentBrowserControl,
|
|
32606
32804
|
factsExtraction: config.knowledge?.factsExtraction,
|
|
32607
32805
|
rememberSessions: config.memory?.rememberSessions,
|
|
32608
32806
|
enableToolCreation: config.enableToolCreation,
|
|
@@ -33107,6 +33305,14 @@ var DashboardServer = class {
|
|
|
33107
33305
|
* always failing.
|
|
33108
33306
|
*/
|
|
33109
33307
|
currentPageProvider;
|
|
33308
|
+
/**
|
|
33309
|
+
* Supplied by the desktop shell so a run can ACT on that page — click, type,
|
|
33310
|
+
* submit — not just read it. Same reasoning as the provider above: absent in
|
|
33311
|
+
* the CLI, so the tool is not registered rather than present and failing.
|
|
33312
|
+
* Cascade applies the `tools.agentBrowserControl` gate on top of this.
|
|
33313
|
+
*/
|
|
33314
|
+
browserController;
|
|
33315
|
+
browserActorRelease;
|
|
33110
33316
|
activeControllers = /* @__PURE__ */ new Map();
|
|
33111
33317
|
/**
|
|
33112
33318
|
* Run taskIds per chat session — file snapshots are keyed by the run's
|
|
@@ -33168,6 +33374,7 @@ var DashboardServer = class {
|
|
|
33168
33374
|
});
|
|
33169
33375
|
this.socket.onConfigUpdate(async (data) => {
|
|
33170
33376
|
const result = await commitSettings(this.config, data, (committed) => this.persistConfig(committed));
|
|
33377
|
+
if (result.ok) this.settingsChangeHook?.(this.config);
|
|
33171
33378
|
const refused = result.refused.map((r) => ({
|
|
33172
33379
|
type: r.type,
|
|
33173
33380
|
reason: r.reason,
|
|
@@ -33191,6 +33398,7 @@ var DashboardServer = class {
|
|
|
33191
33398
|
const cascade = new Cascade(cfg, this.workspacePath, this.store);
|
|
33192
33399
|
this.activeSessions.set(sessionId, cascade);
|
|
33193
33400
|
if (this.currentPageProvider) cascade.setCurrentPageProvider(this.currentPageProvider);
|
|
33401
|
+
if (this.browserController) cascade.setBrowserController(this.browserController, this.browserActorRelease);
|
|
33194
33402
|
cascade.on("stream:token", (e) => {
|
|
33195
33403
|
this.emitToSessionClients(socketId, sessionId, "stream:token", { sessionId, tierId: e.tierId, text: e.text, primary: e.primary });
|
|
33196
33404
|
});
|
|
@@ -33214,13 +33422,16 @@ var DashboardServer = class {
|
|
|
33214
33422
|
if (ev.requestId) this.clearPendingEscalation(ev.requestId);
|
|
33215
33423
|
this.emitToSessionClients(socketId, sessionId, "escalation:timeout", { sessionId, ...e });
|
|
33216
33424
|
});
|
|
33425
|
+
cascade.on("run:started", (e) => {
|
|
33426
|
+
const id = e.taskId;
|
|
33427
|
+
if (id) this.recordSessionTask(sessionId, id);
|
|
33428
|
+
});
|
|
33217
33429
|
try {
|
|
33218
33430
|
const result = await cascade.run({
|
|
33219
33431
|
prompt: runPrompt,
|
|
33220
33432
|
signal: abortController.signal,
|
|
33221
33433
|
approvalCallback: this.makeApprovalCallback(sessionId)
|
|
33222
33434
|
});
|
|
33223
|
-
this.recordSessionTask(sessionId, result.taskId);
|
|
33224
33435
|
this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED", result);
|
|
33225
33436
|
this.captureWhy(sessionId, cascade, result);
|
|
33226
33437
|
this.emitToSessionClients(socketId, sessionId, "session:complete", { sessionId, result });
|
|
@@ -33286,6 +33497,49 @@ var DashboardServer = class {
|
|
|
33286
33497
|
setCurrentPageProvider(provider) {
|
|
33287
33498
|
this.currentPageProvider = provider;
|
|
33288
33499
|
}
|
|
33500
|
+
/** Let runs act on that view, not just read it (desktop only). */
|
|
33501
|
+
setBrowserController(controller, release) {
|
|
33502
|
+
this.browserController = controller;
|
|
33503
|
+
this.browserActorRelease = release;
|
|
33504
|
+
}
|
|
33505
|
+
/**
|
|
33506
|
+
* Told whenever settings are committed through THIS server.
|
|
33507
|
+
*
|
|
33508
|
+
* Settings reach the live config by two routes — the desktop's Electron IPC
|
|
33509
|
+
* handler and this server's `config:update` socket — and both run the same
|
|
33510
|
+
* `commitSettings`. The desktop mirrors `tools.agentBrowserControl` into its
|
|
33511
|
+
* browser module after its own route, but had no way to learn about the
|
|
33512
|
+
* other, so a socket write left the module's copy stale: an enable the module
|
|
33513
|
+
* still refused, or worse a disable the module still honoured for a run
|
|
33514
|
+
* already holding the tool. This is the same sibling-writer drift
|
|
33515
|
+
* `settings-payload.ts` exists to prevent, one layer up.
|
|
33516
|
+
*/
|
|
33517
|
+
settingsChangeHook;
|
|
33518
|
+
onSettingsChanged(hook) {
|
|
33519
|
+
this.settingsChangeHook = hook;
|
|
33520
|
+
}
|
|
33521
|
+
/**
|
|
33522
|
+
* Told when a run ends, so a host holding run-scoped state can release it.
|
|
33523
|
+
*
|
|
33524
|
+
* The desktop uses this to retire a run's browser Stop control: that has to
|
|
33525
|
+
* outlive each individual action (the gap between two actions is when the
|
|
33526
|
+
* user most wants it) but must not outlive the run.
|
|
33527
|
+
*/
|
|
33528
|
+
/**
|
|
33529
|
+
* Fired when a run ends, carrying BOTH identifiers explicitly.
|
|
33530
|
+
*
|
|
33531
|
+
* They are different things and conflating them was a live bug: a chat
|
|
33532
|
+
* `sessionId` can contain many runs (hence `sessionTaskIds`), while
|
|
33533
|
+
* `Cascade.run()` mints a fresh random `taskId` per run — and it is the
|
|
33534
|
+
* taskId that tools see as `ToolExecuteOptions.sessionId`. Passing only the
|
|
33535
|
+
* chat id meant browser cleanup keyed on the task id matched nothing, so a
|
|
33536
|
+
* finished run kept its Stop banner armed and held its browser lease until
|
|
33537
|
+
* the deadlock ceiling.
|
|
33538
|
+
*/
|
|
33539
|
+
runEndedHook;
|
|
33540
|
+
onRunEnded(hook) {
|
|
33541
|
+
this.runEndedHook = hook;
|
|
33542
|
+
}
|
|
33289
33543
|
async start() {
|
|
33290
33544
|
const isLoopback = this.host === "127.0.0.1" || this.host === "::1" || this.host === "localhost";
|
|
33291
33545
|
if (!isLoopback) {
|
|
@@ -33476,6 +33730,8 @@ var DashboardServer = class {
|
|
|
33476
33730
|
}
|
|
33477
33731
|
/** Record run end: the assistant reply (when there is one) and the runtime row's final status. */
|
|
33478
33732
|
persistRunEnd(sessionId, title, latestPrompt, reply, status, result) {
|
|
33733
|
+
const endedTaskId = result?.taskId ?? this.sessionTaskIds.get(sessionId)?.at(-1);
|
|
33734
|
+
this.runEndedHook?.({ sessionId, ...endedTaskId ? { taskId: endedTaskId } : {} });
|
|
33479
33735
|
try {
|
|
33480
33736
|
if (reply && reply.trim()) {
|
|
33481
33737
|
this.store.addMessage({ id: randomUUID(), sessionId, role: "assistant", content: reply, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
@@ -33569,6 +33825,7 @@ var DashboardServer = class {
|
|
|
33569
33825
|
const prompt = task.prompt;
|
|
33570
33826
|
const title = this.persistRunStart(sessionId, `[${task.name}] ${prompt}`);
|
|
33571
33827
|
const cascade = new Cascade(this.config, task.workspacePath ?? this.workspacePath, this.store);
|
|
33828
|
+
cascade.setUnattended(true);
|
|
33572
33829
|
this.activeSessions.set(sessionId, cascade);
|
|
33573
33830
|
cascade.on("tier:status", (e) => {
|
|
33574
33831
|
this.socket.broadcast("tier:status", { sessionId, ...e });
|
|
@@ -33576,13 +33833,16 @@ var DashboardServer = class {
|
|
|
33576
33833
|
cascade.on("peer:message", (e) => {
|
|
33577
33834
|
this.socket.emitPeerMessage(e);
|
|
33578
33835
|
});
|
|
33836
|
+
cascade.on("run:started", (e) => {
|
|
33837
|
+
const id = e.taskId;
|
|
33838
|
+
if (id) this.recordSessionTask(sessionId, id);
|
|
33839
|
+
});
|
|
33579
33840
|
try {
|
|
33580
33841
|
const result = await cascade.run({
|
|
33581
33842
|
prompt,
|
|
33582
33843
|
identityId: task.identityId,
|
|
33583
33844
|
approvalCallback: async () => ({ approved: true, always: false })
|
|
33584
33845
|
});
|
|
33585
|
-
this.recordSessionTask(sessionId, result.taskId);
|
|
33586
33846
|
this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED", result);
|
|
33587
33847
|
this.captureWhy(sessionId, cascade, result);
|
|
33588
33848
|
this.socket.broadcast("session:complete", { sessionId, result });
|
|
@@ -34215,6 +34475,7 @@ ${prompt}`;
|
|
|
34215
34475
|
const cascade = new Cascade(this.config, this.workspacePath, this.store);
|
|
34216
34476
|
this.activeSessions.set(sessionId, cascade);
|
|
34217
34477
|
if (this.currentPageProvider) cascade.setCurrentPageProvider(this.currentPageProvider);
|
|
34478
|
+
if (this.browserController) cascade.setBrowserController(this.browserController, this.browserActorRelease);
|
|
34218
34479
|
cascade.on("stream:token", (e) => {
|
|
34219
34480
|
this.socket.broadcastToRoom(`session:${sessionId}`, "stream:token", { sessionId, tierId: e.tierId, text: e.text, primary: e.primary });
|
|
34220
34481
|
});
|
|
@@ -34238,13 +34499,16 @@ ${prompt}`;
|
|
|
34238
34499
|
if (ev.requestId) this.clearPendingEscalation(ev.requestId);
|
|
34239
34500
|
this.socket.broadcastToRoom(`session:${sessionId}`, "escalation:timeout", { sessionId, ...e });
|
|
34240
34501
|
});
|
|
34502
|
+
cascade.on("run:started", (e) => {
|
|
34503
|
+
const id = e.taskId;
|
|
34504
|
+
if (id) this.recordSessionTask(sessionId, id);
|
|
34505
|
+
});
|
|
34241
34506
|
try {
|
|
34242
34507
|
const result = await cascade.run({
|
|
34243
34508
|
prompt: runPrompt,
|
|
34244
34509
|
identityId: body.identityId,
|
|
34245
34510
|
approvalCallback: this.makeApprovalCallback(sessionId)
|
|
34246
34511
|
});
|
|
34247
|
-
this.recordSessionTask(sessionId, result.taskId);
|
|
34248
34512
|
this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED", result);
|
|
34249
34513
|
this.captureWhy(sessionId, cascade, result);
|
|
34250
34514
|
this.socket.broadcast("cost:update", {
|
|
@@ -35284,6 +35548,6 @@ function applySyncBundle(bundle, config, cleanup) {
|
|
|
35284
35548
|
return next;
|
|
35285
35549
|
}
|
|
35286
35550
|
|
|
35287
|
-
export { AZURE_BASE_MODELS, AZURE_BASE_URL_TEMPLATE, AuditLogger, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, CHARS_PER_TOKEN2 as CHARS_PER_TOKEN, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, CascadeConfigSchema, CascadeIgnore, CascadeRouter, CascadeToolError, CloudClient, CodeSearchTool, ConfigManager, CurrentPageTool, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CLOUD_URL, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DOCUMENT_MIME, DashboardServer, DependencyScheduler, FileMcpOAuthStore, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, GenerateDocumentTool, GraphRetriever, GraphSearchTool, HooksRunner, Keystore, LLMReranker, LM_STUDIO_BASE_URL, MAX_PAGE_TEXT_CHARS, MCP_TOOL_PREFIX, MODELS, McpClient, McpOAuthProvider, MemoryStore, OLLAMA_BASE_URL, OpenAICompatibleEmbedder, PROVIDER_DISPLAY_NAMES, RESUME_SCHEMA_VERSION, ResumeStore, Retriever, SqliteVectorStore, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskGraphValidationError, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, WorkspaceIndex, applyEndpointEdit, applyProviderApiKey, applyProviderCredential, applySettingsCredentials, applySettingsPayload, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildDocumentTools, buildManifest, buildSessionTranscript, bytesToBase64, cagCharBudget, chartKind, chartToTableRows, chatCompleterFromProviders, chunkCode, chunkText2 as chunkText, commitSettings, compileSectionGraph, compileSubtaskGraph, compileTaskGraph, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, credentialDispositionForEdit, credentialEndpointIdentity, decryptJSON as decryptSyncBlob, diffManifest, disambiguateMcpServerNames, discoverMcpAuthServer, discoverMcpTools, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, endpointFromSettingsPayload, evaluateAcceptance, explainRefusal, extractCharts, failures, fileExt, fileOAuthProvider, gatherSyncBundle, hasDefaultEndpoint, hasProviderCredential, hasUsableProvider, hashContent, heuristicCodeChunker, inferAzureBaseModel, inlineRuns, isDocumentFormat, isMcpToolName, matchImageLine, mcpServerPrefix, mcpToolName, nodeHttpFetch, normalizeEndpoint, parseBlocks, parseChartSpec, parseDelimited, parseRankOrder, parseSlide, planRetrieval, preferIpv4Host, priorAzureRow, reciprocalRankFusion, refreshMcpToken, removeMcpServerDenials, renderDocument, renderDocx, renderPptx, renderXlsx, runCascade, sameAzureEndpoint, sameCredentialEndpoint, sameEndpoint, sessionWorthRemembering, settingsSnapshot, sniffImage, splitSlides, streamCascade, stripInline, summarizeCompleted, undecided, uniqueMcpServerName };
|
|
35551
|
+
export { AZURE_BASE_MODELS, AZURE_BASE_URL_TEMPLATE, AuditLogger, BrowserControlTool, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, CHARS_PER_TOKEN2 as CHARS_PER_TOKEN, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, CascadeConfigSchema, CascadeIgnore, CascadeRouter, CascadeToolError, CloudClient, CodeSearchTool, ConfigManager, CurrentPageTool, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CLOUD_URL, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DOCUMENT_MIME, DashboardServer, DependencyScheduler, FileMcpOAuthStore, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, GenerateDocumentTool, GraphRetriever, GraphSearchTool, HooksRunner, Keystore, LLMReranker, LM_STUDIO_BASE_URL, MAX_PAGE_TEXT_CHARS, MCP_TOOL_PREFIX, MODELS, McpClient, McpOAuthProvider, MemoryStore, OLLAMA_BASE_URL, OpenAICompatibleEmbedder, PROVIDER_DISPLAY_NAMES, RESUME_SCHEMA_VERSION, ResumeStore, Retriever, SqliteVectorStore, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskGraphValidationError, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, WorkspaceIndex, applyEndpointEdit, applyProviderApiKey, applyProviderCredential, applySettingsCredentials, applySettingsPayload, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildDocumentTools, buildManifest, buildSessionTranscript, bytesToBase64, cagCharBudget, chartKind, chartToTableRows, chatCompleterFromProviders, chunkCode, chunkText2 as chunkText, commitSettings, compileSectionGraph, compileSubtaskGraph, compileTaskGraph, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, credentialDispositionForEdit, credentialEndpointIdentity, decryptJSON as decryptSyncBlob, diffManifest, disambiguateMcpServerNames, discoverMcpAuthServer, discoverMcpTools, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, endpointFromSettingsPayload, evaluateAcceptance, explainRefusal, extractCharts, failures, fileExt, fileOAuthProvider, gatherSyncBundle, hasDefaultEndpoint, hasProviderCredential, hasUsableProvider, hashContent, heuristicCodeChunker, inferAzureBaseModel, inlineRuns, isDocumentFormat, isMcpToolName, matchImageLine, mcpServerPrefix, mcpToolName, nodeHttpFetch, normalizeEndpoint, parseBlocks, parseChartSpec, parseDelimited, parseRankOrder, parseSlide, planRetrieval, preferIpv4Host, priorAzureRow, reciprocalRankFusion, refreshMcpToken, removeMcpServerDenials, renderDocument, renderDocx, renderPptx, renderXlsx, runCascade, sameAzureEndpoint, sameCredentialEndpoint, sameEndpoint, sessionWorthRemembering, settingsSnapshot, sniffImage, splitSlides, streamCascade, stripInline, summarizeCompleted, undecided, uniqueMcpServerName };
|
|
35288
35552
|
//# sourceMappingURL=index.js.map
|
|
35289
35553
|
//# sourceMappingURL=index.js.map
|