cascade-ai 0.13.1 → 0.13.2
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/README.md +6 -0
- package/dist/cli.cjs +117 -36
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +117 -36
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +117 -36
- package/dist/index.cjs +117 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +59 -2
- package/dist/index.d.ts +59 -2
- package/dist/index.js +117 -36
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -429,6 +429,14 @@ interface CascadeConfig {
|
|
|
429
429
|
policy: 'local-only';
|
|
430
430
|
}>;
|
|
431
431
|
};
|
|
432
|
+
/**
|
|
433
|
+
* Routing controls. `forceTier` pins the run's root tier — 'T1' (full
|
|
434
|
+
* hierarchy), 'T2' (manager + workers), 'T3' (single worker) — bypassing the
|
|
435
|
+
* complexity classifier. 'auto' (default) uses classification.
|
|
436
|
+
*/
|
|
437
|
+
routing?: {
|
|
438
|
+
forceTier?: 'auto' | 'T1' | 'T2' | 'T3';
|
|
439
|
+
};
|
|
432
440
|
/** Render the TUI in the alternate screen buffer (vim-style). Default: false. */
|
|
433
441
|
altScreen?: boolean;
|
|
434
442
|
}
|
|
@@ -595,6 +603,19 @@ interface PermissionRequest {
|
|
|
595
603
|
* auto-approve a later dangerous action.
|
|
596
604
|
*/
|
|
597
605
|
forceReprompt?: boolean;
|
|
606
|
+
/**
|
|
607
|
+
* Escalation trail — each engaged tier's non-binding recommendation as the
|
|
608
|
+
* request rose toward the user. Dangerous tools never get a terminal
|
|
609
|
+
* decision from a tier; the tier records its advice here instead, so the
|
|
610
|
+
* user sees who asked and what T2/T1 thought.
|
|
611
|
+
*/
|
|
612
|
+
trail?: PermissionTrailEntry[];
|
|
613
|
+
}
|
|
614
|
+
/** One tier's advisory verdict recorded while a request escalated upward. */
|
|
615
|
+
interface PermissionTrailEntry {
|
|
616
|
+
tier: 'T2' | 'T1';
|
|
617
|
+
verdict: 'approve' | 'deny' | 'unsure';
|
|
618
|
+
reason?: string;
|
|
598
619
|
}
|
|
599
620
|
/**
|
|
600
621
|
* A decision made at any tier (T2, T1, or USER) about a PermissionRequest.
|
|
@@ -1374,7 +1395,16 @@ declare abstract class BaseTier extends EventEmitter {
|
|
|
1374
1395
|
protected hierarchyContext: string;
|
|
1375
1396
|
/** Propagated AbortSignal — set by the tier's `execute()` before work begins. */
|
|
1376
1397
|
protected signal?: AbortSignal;
|
|
1398
|
+
/**
|
|
1399
|
+
* True for the run's ROOT tier (T3 for Simple, T2 for Moderate, T1 for
|
|
1400
|
+
* Complex). Its own synthesis stream is the user-facing answer and is
|
|
1401
|
+
* tagged `primary` so the desktop renders it live — background workers,
|
|
1402
|
+
* which would interleave, are not tagged.
|
|
1403
|
+
*/
|
|
1404
|
+
protected isPresenter: boolean;
|
|
1377
1405
|
constructor(role: TierRole, id?: string, parentId?: string);
|
|
1406
|
+
/** Mark this tier as the run's presenter (root tier). */
|
|
1407
|
+
setPresenter(on?: boolean): void;
|
|
1378
1408
|
getStatus(): TierStatus;
|
|
1379
1409
|
protected setStatus(status: TierStatus, output?: string): void;
|
|
1380
1410
|
protected setLabel(label: string): void;
|
|
@@ -1698,6 +1728,14 @@ declare class Cascade extends EventEmitter {
|
|
|
1698
1728
|
* do?" requests from being mis-routed into a multi-agent, multi-thousand-token run.
|
|
1699
1729
|
*/
|
|
1700
1730
|
private looksLikeReadOnlyInquiry;
|
|
1731
|
+
/**
|
|
1732
|
+
* Strong, explicit signals that a task needs the full hierarchy (planning +
|
|
1733
|
+
* multiple sections/workers). Deliberately conservative: requires a
|
|
1734
|
+
* build/implementation verb AND either an app/system-scale noun or an
|
|
1735
|
+
* explicit multi-part structure, so ordinary single-file asks (handled as
|
|
1736
|
+
* Simple/Moderate) don't get over-escalated.
|
|
1737
|
+
*/
|
|
1738
|
+
private looksClearlyComplex;
|
|
1701
1739
|
private static globCache;
|
|
1702
1740
|
private countWorkspaceFiles;
|
|
1703
1741
|
private determineComplexity;
|
|
@@ -2161,7 +2199,7 @@ declare class DashboardSocket {
|
|
|
2161
2199
|
}) => void): void;
|
|
2162
2200
|
emitToSocket(socketId: string, event: string, data: unknown): void;
|
|
2163
2201
|
onConfigGet(callback: (socketId: string) => void): void;
|
|
2164
|
-
onCascadeRun(callback: (prompt: string, model: string, socketId: string, sessionId?: string) => void): void;
|
|
2202
|
+
onCascadeRun(callback: (prompt: string, model: string, socketId: string, sessionId?: string, forceTier?: string) => void): void;
|
|
2165
2203
|
close(): void;
|
|
2166
2204
|
}
|
|
2167
2205
|
|
|
@@ -2182,6 +2220,13 @@ declare class DashboardServer {
|
|
|
2182
2220
|
* of runs the session performed in this server's lifetime.
|
|
2183
2221
|
*/
|
|
2184
2222
|
private sessionTaskIds;
|
|
2223
|
+
/**
|
|
2224
|
+
* Tool-approval requests awaiting a user decision from a connected client,
|
|
2225
|
+
* keyed by the request's uuid. The desktop shows a modal on
|
|
2226
|
+
* `permission:user-required` and answers with `permission:decision`; this
|
|
2227
|
+
* map is how that answer reaches the run that's blocked on it.
|
|
2228
|
+
*/
|
|
2229
|
+
private pendingApprovals;
|
|
2185
2230
|
private port;
|
|
2186
2231
|
private host;
|
|
2187
2232
|
private workspacePath;
|
|
@@ -2221,6 +2266,18 @@ declare class DashboardServer {
|
|
|
2221
2266
|
*/
|
|
2222
2267
|
private steerSessions;
|
|
2223
2268
|
private recordSessionTask;
|
|
2269
|
+
/**
|
|
2270
|
+
* Approval bridge: cascade calls this when a dangerous tool escalates to the
|
|
2271
|
+
* user. The request itself was already pushed to the client via the
|
|
2272
|
+
* `permission:user-required` forward; here we just park a resolver keyed by
|
|
2273
|
+
* the request id and wait for the client's `permission:decision` (handled in
|
|
2274
|
+
* onApprovalResponse). Never auto-approves — an unanswered request stays
|
|
2275
|
+
* pending until the client answers, the run ends, or the escalator's own
|
|
2276
|
+
* timeout denies it.
|
|
2277
|
+
*/
|
|
2278
|
+
private makeApprovalCallback;
|
|
2279
|
+
/** Deny + clear any approvals still pending for a session (run end / abort). */
|
|
2280
|
+
private denyPendingApprovals;
|
|
2224
2281
|
private persistRuntimeRow;
|
|
2225
2282
|
/**
|
|
2226
2283
|
* Continuing an existing session: the Cascade orchestrator is per-run and
|
|
@@ -2462,4 +2519,4 @@ declare class CascadeToolError extends Error {
|
|
|
2462
2519
|
declare function preferIpv4Host(url: string | undefined): string | undefined;
|
|
2463
2520
|
declare function nodeHttpFetch(input: string | URL | Request, init?: RequestInit, redirectCount?: number): Promise<Response>;
|
|
2464
2521
|
|
|
2465
|
-
export { AZURE_BASE_URL_TEMPLATE, type ApprovalRequest, type ApprovalResponse, type AuditEntry, AuditLogger, type BenchmarksConfig, type BudgetConfig, 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, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, type CascadeConfig, type CascadeEvent, type CascadeEventType, CascadeIgnore, type CascadeMessage, CascadeRouter, type CascadeRunOptions, type CascadeRunResult, type CascadeThemeName, CascadeToolError, ConfigManager, type ConversationMessage, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, type DashboardConfig, DashboardServer, type EscalationPayload, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, type GenerateOptions, type GenerateResult, type HookDefinition, type HooksConfig, HooksRunner, type Identity, type ImageAttachment, Keystore, LM_STUDIO_BASE_URL, type LegacyThemeName, MODELS, McpClient, type McpServerConfig$1 as McpServerConfig, type MemoryConfig, MemoryStore, type Message, type MessageContent, type MessagePayload, type MessageStatus, type MessageType, type ModelInfo, type ModelOverrides, OLLAMA_BASE_URL, PROVIDER_DISPLAY_NAMES, type PeerMessage, type PeerMessageEvent, type PeerSyncPayload, type PeerSyncType, type PermissionDecision, type PermissionDecisionPayload, type PermissionRequest, type PlanReviewConfig, type ProviderConfig, type ProviderType, type RuntimeNode, type RuntimeNodeLog, type RuntimeRefreshPayload, type RuntimeScope, type RuntimeSession, type RuntimeSnapshotPayload, type ScheduledTask, type Session, type SessionCheckpoint, type SessionMetadata, type SessionSubscriptionPayload, type StatusUpdate, type StoredMessage, type StreamChunk, T1Administrator, type T1ToT2Assignment, T1_MODEL_PRIORITY, T2Manager, type T2Result, type T2ToT3Assignment, T2_MODEL_PRIORITY, type T3Result, type T3ResultPayload, type T3SubtaskSpec, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, type TaskComplexity, TaskScheduler, Telemetry, type TelemetryConfig, type Theme, type ThemeColors, type ThemeName, type TierConfig, type TierLimits, type TierRole, type TierStatus, type TokenUsage, type ToolCall, type ToolDefinition, type ToolExecuteOptions, ToolRegistry, type ToolResult, type ToolsConfig, VISION_MODEL_PRIORITY, type WebSearchConfig, type WebhookConfig, type WorkspaceConfig, createCascade, nodeHttpFetch, preferIpv4Host, runCascade, streamCascade };
|
|
2522
|
+
export { AZURE_BASE_URL_TEMPLATE, type ApprovalRequest, type ApprovalResponse, type AuditEntry, AuditLogger, type BenchmarksConfig, type BudgetConfig, 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, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, type CascadeConfig, type CascadeEvent, type CascadeEventType, CascadeIgnore, type CascadeMessage, CascadeRouter, type CascadeRunOptions, type CascadeRunResult, type CascadeThemeName, CascadeToolError, ConfigManager, type ConversationMessage, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, type DashboardConfig, DashboardServer, type EscalationPayload, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, type GenerateOptions, type GenerateResult, type HookDefinition, type HooksConfig, HooksRunner, type Identity, type ImageAttachment, Keystore, LM_STUDIO_BASE_URL, type LegacyThemeName, MODELS, McpClient, type McpServerConfig$1 as McpServerConfig, type MemoryConfig, MemoryStore, type Message, type MessageContent, type MessagePayload, type MessageStatus, type MessageType, type ModelInfo, type ModelOverrides, OLLAMA_BASE_URL, PROVIDER_DISPLAY_NAMES, type PeerMessage, type PeerMessageEvent, type PeerSyncPayload, type PeerSyncType, type PermissionDecision, type PermissionDecisionPayload, type PermissionRequest, type PermissionTrailEntry, type PlanReviewConfig, type ProviderConfig, type ProviderType, type RuntimeNode, type RuntimeNodeLog, type RuntimeRefreshPayload, type RuntimeScope, type RuntimeSession, type RuntimeSnapshotPayload, type ScheduledTask, type Session, type SessionCheckpoint, type SessionMetadata, type SessionSubscriptionPayload, type StatusUpdate, type StoredMessage, type StreamChunk, T1Administrator, type T1ToT2Assignment, T1_MODEL_PRIORITY, T2Manager, type T2Result, type T2ToT3Assignment, T2_MODEL_PRIORITY, type T3Result, type T3ResultPayload, type T3SubtaskSpec, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, type TaskComplexity, TaskScheduler, Telemetry, type TelemetryConfig, type Theme, type ThemeColors, type ThemeName, type TierConfig, type TierLimits, type TierRole, type TierStatus, type TokenUsage, type ToolCall, type ToolDefinition, type ToolExecuteOptions, ToolRegistry, type ToolResult, type ToolsConfig, VISION_MODEL_PRIORITY, type WebSearchConfig, type WebhookConfig, type WorkspaceConfig, createCascade, nodeHttpFetch, preferIpv4Host, runCascade, streamCascade };
|
package/dist/index.d.ts
CHANGED
|
@@ -429,6 +429,14 @@ interface CascadeConfig {
|
|
|
429
429
|
policy: 'local-only';
|
|
430
430
|
}>;
|
|
431
431
|
};
|
|
432
|
+
/**
|
|
433
|
+
* Routing controls. `forceTier` pins the run's root tier — 'T1' (full
|
|
434
|
+
* hierarchy), 'T2' (manager + workers), 'T3' (single worker) — bypassing the
|
|
435
|
+
* complexity classifier. 'auto' (default) uses classification.
|
|
436
|
+
*/
|
|
437
|
+
routing?: {
|
|
438
|
+
forceTier?: 'auto' | 'T1' | 'T2' | 'T3';
|
|
439
|
+
};
|
|
432
440
|
/** Render the TUI in the alternate screen buffer (vim-style). Default: false. */
|
|
433
441
|
altScreen?: boolean;
|
|
434
442
|
}
|
|
@@ -595,6 +603,19 @@ interface PermissionRequest {
|
|
|
595
603
|
* auto-approve a later dangerous action.
|
|
596
604
|
*/
|
|
597
605
|
forceReprompt?: boolean;
|
|
606
|
+
/**
|
|
607
|
+
* Escalation trail — each engaged tier's non-binding recommendation as the
|
|
608
|
+
* request rose toward the user. Dangerous tools never get a terminal
|
|
609
|
+
* decision from a tier; the tier records its advice here instead, so the
|
|
610
|
+
* user sees who asked and what T2/T1 thought.
|
|
611
|
+
*/
|
|
612
|
+
trail?: PermissionTrailEntry[];
|
|
613
|
+
}
|
|
614
|
+
/** One tier's advisory verdict recorded while a request escalated upward. */
|
|
615
|
+
interface PermissionTrailEntry {
|
|
616
|
+
tier: 'T2' | 'T1';
|
|
617
|
+
verdict: 'approve' | 'deny' | 'unsure';
|
|
618
|
+
reason?: string;
|
|
598
619
|
}
|
|
599
620
|
/**
|
|
600
621
|
* A decision made at any tier (T2, T1, or USER) about a PermissionRequest.
|
|
@@ -1374,7 +1395,16 @@ declare abstract class BaseTier extends EventEmitter {
|
|
|
1374
1395
|
protected hierarchyContext: string;
|
|
1375
1396
|
/** Propagated AbortSignal — set by the tier's `execute()` before work begins. */
|
|
1376
1397
|
protected signal?: AbortSignal;
|
|
1398
|
+
/**
|
|
1399
|
+
* True for the run's ROOT tier (T3 for Simple, T2 for Moderate, T1 for
|
|
1400
|
+
* Complex). Its own synthesis stream is the user-facing answer and is
|
|
1401
|
+
* tagged `primary` so the desktop renders it live — background workers,
|
|
1402
|
+
* which would interleave, are not tagged.
|
|
1403
|
+
*/
|
|
1404
|
+
protected isPresenter: boolean;
|
|
1377
1405
|
constructor(role: TierRole, id?: string, parentId?: string);
|
|
1406
|
+
/** Mark this tier as the run's presenter (root tier). */
|
|
1407
|
+
setPresenter(on?: boolean): void;
|
|
1378
1408
|
getStatus(): TierStatus;
|
|
1379
1409
|
protected setStatus(status: TierStatus, output?: string): void;
|
|
1380
1410
|
protected setLabel(label: string): void;
|
|
@@ -1698,6 +1728,14 @@ declare class Cascade extends EventEmitter {
|
|
|
1698
1728
|
* do?" requests from being mis-routed into a multi-agent, multi-thousand-token run.
|
|
1699
1729
|
*/
|
|
1700
1730
|
private looksLikeReadOnlyInquiry;
|
|
1731
|
+
/**
|
|
1732
|
+
* Strong, explicit signals that a task needs the full hierarchy (planning +
|
|
1733
|
+
* multiple sections/workers). Deliberately conservative: requires a
|
|
1734
|
+
* build/implementation verb AND either an app/system-scale noun or an
|
|
1735
|
+
* explicit multi-part structure, so ordinary single-file asks (handled as
|
|
1736
|
+
* Simple/Moderate) don't get over-escalated.
|
|
1737
|
+
*/
|
|
1738
|
+
private looksClearlyComplex;
|
|
1701
1739
|
private static globCache;
|
|
1702
1740
|
private countWorkspaceFiles;
|
|
1703
1741
|
private determineComplexity;
|
|
@@ -2161,7 +2199,7 @@ declare class DashboardSocket {
|
|
|
2161
2199
|
}) => void): void;
|
|
2162
2200
|
emitToSocket(socketId: string, event: string, data: unknown): void;
|
|
2163
2201
|
onConfigGet(callback: (socketId: string) => void): void;
|
|
2164
|
-
onCascadeRun(callback: (prompt: string, model: string, socketId: string, sessionId?: string) => void): void;
|
|
2202
|
+
onCascadeRun(callback: (prompt: string, model: string, socketId: string, sessionId?: string, forceTier?: string) => void): void;
|
|
2165
2203
|
close(): void;
|
|
2166
2204
|
}
|
|
2167
2205
|
|
|
@@ -2182,6 +2220,13 @@ declare class DashboardServer {
|
|
|
2182
2220
|
* of runs the session performed in this server's lifetime.
|
|
2183
2221
|
*/
|
|
2184
2222
|
private sessionTaskIds;
|
|
2223
|
+
/**
|
|
2224
|
+
* Tool-approval requests awaiting a user decision from a connected client,
|
|
2225
|
+
* keyed by the request's uuid. The desktop shows a modal on
|
|
2226
|
+
* `permission:user-required` and answers with `permission:decision`; this
|
|
2227
|
+
* map is how that answer reaches the run that's blocked on it.
|
|
2228
|
+
*/
|
|
2229
|
+
private pendingApprovals;
|
|
2185
2230
|
private port;
|
|
2186
2231
|
private host;
|
|
2187
2232
|
private workspacePath;
|
|
@@ -2221,6 +2266,18 @@ declare class DashboardServer {
|
|
|
2221
2266
|
*/
|
|
2222
2267
|
private steerSessions;
|
|
2223
2268
|
private recordSessionTask;
|
|
2269
|
+
/**
|
|
2270
|
+
* Approval bridge: cascade calls this when a dangerous tool escalates to the
|
|
2271
|
+
* user. The request itself was already pushed to the client via the
|
|
2272
|
+
* `permission:user-required` forward; here we just park a resolver keyed by
|
|
2273
|
+
* the request id and wait for the client's `permission:decision` (handled in
|
|
2274
|
+
* onApprovalResponse). Never auto-approves — an unanswered request stays
|
|
2275
|
+
* pending until the client answers, the run ends, or the escalator's own
|
|
2276
|
+
* timeout denies it.
|
|
2277
|
+
*/
|
|
2278
|
+
private makeApprovalCallback;
|
|
2279
|
+
/** Deny + clear any approvals still pending for a session (run end / abort). */
|
|
2280
|
+
private denyPendingApprovals;
|
|
2224
2281
|
private persistRuntimeRow;
|
|
2225
2282
|
/**
|
|
2226
2283
|
* Continuing an existing session: the Cascade orchestrator is per-run and
|
|
@@ -2462,4 +2519,4 @@ declare class CascadeToolError extends Error {
|
|
|
2462
2519
|
declare function preferIpv4Host(url: string | undefined): string | undefined;
|
|
2463
2520
|
declare function nodeHttpFetch(input: string | URL | Request, init?: RequestInit, redirectCount?: number): Promise<Response>;
|
|
2464
2521
|
|
|
2465
|
-
export { AZURE_BASE_URL_TEMPLATE, type ApprovalRequest, type ApprovalResponse, type AuditEntry, AuditLogger, type BenchmarksConfig, type BudgetConfig, 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, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, type CascadeConfig, type CascadeEvent, type CascadeEventType, CascadeIgnore, type CascadeMessage, CascadeRouter, type CascadeRunOptions, type CascadeRunResult, type CascadeThemeName, CascadeToolError, ConfigManager, type ConversationMessage, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, type DashboardConfig, DashboardServer, type EscalationPayload, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, type GenerateOptions, type GenerateResult, type HookDefinition, type HooksConfig, HooksRunner, type Identity, type ImageAttachment, Keystore, LM_STUDIO_BASE_URL, type LegacyThemeName, MODELS, McpClient, type McpServerConfig$1 as McpServerConfig, type MemoryConfig, MemoryStore, type Message, type MessageContent, type MessagePayload, type MessageStatus, type MessageType, type ModelInfo, type ModelOverrides, OLLAMA_BASE_URL, PROVIDER_DISPLAY_NAMES, type PeerMessage, type PeerMessageEvent, type PeerSyncPayload, type PeerSyncType, type PermissionDecision, type PermissionDecisionPayload, type PermissionRequest, type PlanReviewConfig, type ProviderConfig, type ProviderType, type RuntimeNode, type RuntimeNodeLog, type RuntimeRefreshPayload, type RuntimeScope, type RuntimeSession, type RuntimeSnapshotPayload, type ScheduledTask, type Session, type SessionCheckpoint, type SessionMetadata, type SessionSubscriptionPayload, type StatusUpdate, type StoredMessage, type StreamChunk, T1Administrator, type T1ToT2Assignment, T1_MODEL_PRIORITY, T2Manager, type T2Result, type T2ToT3Assignment, T2_MODEL_PRIORITY, type T3Result, type T3ResultPayload, type T3SubtaskSpec, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, type TaskComplexity, TaskScheduler, Telemetry, type TelemetryConfig, type Theme, type ThemeColors, type ThemeName, type TierConfig, type TierLimits, type TierRole, type TierStatus, type TokenUsage, type ToolCall, type ToolDefinition, type ToolExecuteOptions, ToolRegistry, type ToolResult, type ToolsConfig, VISION_MODEL_PRIORITY, type WebSearchConfig, type WebhookConfig, type WorkspaceConfig, createCascade, nodeHttpFetch, preferIpv4Host, runCascade, streamCascade };
|
|
2522
|
+
export { AZURE_BASE_URL_TEMPLATE, type ApprovalRequest, type ApprovalResponse, type AuditEntry, AuditLogger, type BenchmarksConfig, type BudgetConfig, 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, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, type CascadeConfig, type CascadeEvent, type CascadeEventType, CascadeIgnore, type CascadeMessage, CascadeRouter, type CascadeRunOptions, type CascadeRunResult, type CascadeThemeName, CascadeToolError, ConfigManager, type ConversationMessage, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, type DashboardConfig, DashboardServer, type EscalationPayload, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, type GenerateOptions, type GenerateResult, type HookDefinition, type HooksConfig, HooksRunner, type Identity, type ImageAttachment, Keystore, LM_STUDIO_BASE_URL, type LegacyThemeName, MODELS, McpClient, type McpServerConfig$1 as McpServerConfig, type MemoryConfig, MemoryStore, type Message, type MessageContent, type MessagePayload, type MessageStatus, type MessageType, type ModelInfo, type ModelOverrides, OLLAMA_BASE_URL, PROVIDER_DISPLAY_NAMES, type PeerMessage, type PeerMessageEvent, type PeerSyncPayload, type PeerSyncType, type PermissionDecision, type PermissionDecisionPayload, type PermissionRequest, type PermissionTrailEntry, type PlanReviewConfig, type ProviderConfig, type ProviderType, type RuntimeNode, type RuntimeNodeLog, type RuntimeRefreshPayload, type RuntimeScope, type RuntimeSession, type RuntimeSnapshotPayload, type ScheduledTask, type Session, type SessionCheckpoint, type SessionMetadata, type SessionSubscriptionPayload, type StatusUpdate, type StoredMessage, type StreamChunk, T1Administrator, type T1ToT2Assignment, T1_MODEL_PRIORITY, T2Manager, type T2Result, type T2ToT3Assignment, T2_MODEL_PRIORITY, type T3Result, type T3ResultPayload, type T3SubtaskSpec, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, type TaskComplexity, TaskScheduler, Telemetry, type TelemetryConfig, type Theme, type ThemeColors, type ThemeName, type TierConfig, type TierLimits, type TierRole, type TierStatus, type TokenUsage, type ToolCall, type ToolDefinition, type ToolExecuteOptions, ToolRegistry, type ToolResult, type ToolsConfig, VISION_MODEL_PRIORITY, type WebSearchConfig, type WebhookConfig, type WorkspaceConfig, createCascade, nodeHttpFetch, preferIpv4Host, runCascade, streamCascade };
|
package/dist/index.js
CHANGED
|
@@ -190,7 +190,7 @@ var init_audit_logger = __esm({
|
|
|
190
190
|
});
|
|
191
191
|
|
|
192
192
|
// src/constants.ts
|
|
193
|
-
var CASCADE_VERSION = "0.13.
|
|
193
|
+
var CASCADE_VERSION = "0.13.2";
|
|
194
194
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
195
195
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
196
196
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -3123,6 +3123,13 @@ var BaseTier = class extends EventEmitter {
|
|
|
3123
3123
|
hierarchyContext = "";
|
|
3124
3124
|
/** Propagated AbortSignal — set by the tier's `execute()` before work begins. */
|
|
3125
3125
|
signal;
|
|
3126
|
+
/**
|
|
3127
|
+
* True for the run's ROOT tier (T3 for Simple, T2 for Moderate, T1 for
|
|
3128
|
+
* Complex). Its own synthesis stream is the user-facing answer and is
|
|
3129
|
+
* tagged `primary` so the desktop renders it live — background workers,
|
|
3130
|
+
* which would interleave, are not tagged.
|
|
3131
|
+
*/
|
|
3132
|
+
isPresenter = false;
|
|
3126
3133
|
constructor(role, id, parentId) {
|
|
3127
3134
|
super();
|
|
3128
3135
|
this.role = role;
|
|
@@ -3130,6 +3137,10 @@ var BaseTier = class extends EventEmitter {
|
|
|
3130
3137
|
this.parentId = parentId;
|
|
3131
3138
|
this.label = this.id;
|
|
3132
3139
|
}
|
|
3140
|
+
/** Mark this tier as the run's presenter (root tier). */
|
|
3141
|
+
setPresenter(on = true) {
|
|
3142
|
+
this.isPresenter = on;
|
|
3143
|
+
}
|
|
3133
3144
|
getStatus() {
|
|
3134
3145
|
return this.status;
|
|
3135
3146
|
}
|
|
@@ -3864,7 +3875,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
3864
3875
|
"T3",
|
|
3865
3876
|
options,
|
|
3866
3877
|
(chunk) => {
|
|
3867
|
-
this.emit("stream:token", { tierId: this.id, text: chunk.text });
|
|
3878
|
+
this.emit("stream:token", { tierId: this.id, text: chunk.text, primary: this.isPresenter });
|
|
3868
3879
|
}
|
|
3869
3880
|
);
|
|
3870
3881
|
let effectiveToolCalls = result.toolCalls ?? [];
|
|
@@ -5325,6 +5336,7 @@ ${peerOutputs}` : "";
|
|
|
5325
5336
|
chunkEnd++;
|
|
5326
5337
|
}
|
|
5327
5338
|
i = chunkEnd;
|
|
5339
|
+
const isLastChunk = chunkEnd >= completed.length;
|
|
5328
5340
|
const prompt = `Summarize these T3 worker outputs for section "${assignment.sectionTitle}" in 2-3 sentences.
|
|
5329
5341
|
${currentSummary ? `
|
|
5330
5342
|
PREVIOUS SUMMARY SO FAR:
|
|
@@ -5334,6 +5346,7 @@ NEW OUTPUTS TO INTEGRATE:
|
|
|
5334
5346
|
` : "\nOUTPUTS:\n"}${chunkText}${peerContext}`;
|
|
5335
5347
|
const messages = [{ role: "user", content: prompt }];
|
|
5336
5348
|
try {
|
|
5349
|
+
const streamFinal = isLastChunk && this.isPresenter ? (chunk) => this.emit("stream:token", { tierId: this.id, text: chunk.text, primary: true }) : void 0;
|
|
5337
5350
|
const result = await this.router.generate("T2", {
|
|
5338
5351
|
messages,
|
|
5339
5352
|
systemPrompt: this.systemPromptOverride + "You are a T2 Manager. Summarize the work of your T3 workers succinctly." + (this.hierarchyContext ? `
|
|
@@ -5341,7 +5354,7 @@ NEW OUTPUTS TO INTEGRATE:
|
|
|
5341
5354
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
5342
5355
|
maxTokens: 500,
|
|
5343
5356
|
...this.sectionModel ? { model: this.sectionModel } : {}
|
|
5344
|
-
});
|
|
5357
|
+
}, streamFinal);
|
|
5345
5358
|
currentSummary = result.content;
|
|
5346
5359
|
} catch (err) {
|
|
5347
5360
|
this.log(`aggregateResults: LLM summarization failed at chunk \u2014 returning raw T3 outputs. Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -5393,14 +5406,11 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
5393
5406
|
...this.sectionModel ? { model: this.sectionModel } : {}
|
|
5394
5407
|
});
|
|
5395
5408
|
const answer = result.content.trim().toUpperCase();
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
}
|
|
5399
|
-
if (answer.includes("NO")) {
|
|
5400
|
-
return { requestId: req.id, approved: false, always: true, decidedBy: "T2", reasoning: "T2 LLM evaluated: inconsistent with section goal" };
|
|
5401
|
-
}
|
|
5409
|
+
const verdict = answer.includes("YES") ? "approve" : answer.includes("NO") ? "deny" : "unsure";
|
|
5410
|
+
(req.trail ??= []).push({ tier: "T2", verdict, reason: `T2: ${verdict === "approve" ? "consistent with section goal" : verdict === "deny" ? "inconsistent with section goal" : "unsure"}` });
|
|
5402
5411
|
return null;
|
|
5403
5412
|
} catch {
|
|
5413
|
+
(req.trail ??= []).push({ tier: "T2", verdict: "unsure", reason: "T2 evaluation failed" });
|
|
5404
5414
|
return null;
|
|
5405
5415
|
}
|
|
5406
5416
|
}
|
|
@@ -6079,7 +6089,7 @@ Instructions:
|
|
|
6079
6089
|
systemPrompt: this.systemPromptOverride + "You are a final output compiler. Summarize and format the task results clearly.",
|
|
6080
6090
|
maxTokens: 8e3
|
|
6081
6091
|
}, (chunk) => {
|
|
6082
|
-
this.emit("stream:token", { tierId: this.id, text: chunk.text });
|
|
6092
|
+
this.emit("stream:token", { tierId: this.id, text: chunk.text, primary: this.isPresenter });
|
|
6083
6093
|
});
|
|
6084
6094
|
return result.content;
|
|
6085
6095
|
}
|
|
@@ -6108,14 +6118,11 @@ Reply with exactly one word: YES, NO, or UNSURE.
|
|
|
6108
6118
|
temperature: 0
|
|
6109
6119
|
});
|
|
6110
6120
|
const answer = result.content.trim().toUpperCase();
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
}
|
|
6114
|
-
if (answer.includes("NO")) {
|
|
6115
|
-
return { requestId: req.id, approved: false, always: true, decidedBy: "T1", reasoning: "T1 evaluated: not consistent with overall task goal" };
|
|
6116
|
-
}
|
|
6121
|
+
const verdict = answer.includes("YES") ? "approve" : answer.includes("NO") ? "deny" : "unsure";
|
|
6122
|
+
(req.trail ??= []).push({ tier: "T1", verdict, reason: `T1: ${verdict === "approve" ? "consistent with overall task goal" : verdict === "deny" ? "not consistent with overall task goal" : "unsure"}` });
|
|
6117
6123
|
return null;
|
|
6118
6124
|
} catch {
|
|
6125
|
+
(req.trail ??= []).push({ tier: "T1", verdict: "unsure", reason: "T1 evaluation failed" });
|
|
6119
6126
|
return null;
|
|
6120
6127
|
}
|
|
6121
6128
|
}
|
|
@@ -8222,6 +8229,8 @@ var CascadeConfigSchema = z.object({
|
|
|
8222
8229
|
privacy: z.object({
|
|
8223
8230
|
paths: z.array(z.object({ pattern: z.string().min(1), policy: z.enum(["local-only"]) })).default([])
|
|
8224
8231
|
}).optional(),
|
|
8232
|
+
/** Routing controls — forceTier pins the root tier, bypassing the classifier. */
|
|
8233
|
+
routing: z.object({ forceTier: z.enum(["auto", "T1", "T2", "T3"]).default("auto") }).optional(),
|
|
8225
8234
|
/**
|
|
8226
8235
|
* T3→T2 reinforcement: when enabled, a worker that discovers its subtask should
|
|
8227
8236
|
* fan out can call the `request_workers` tool to have its T2 manager spawn
|
|
@@ -9496,6 +9505,21 @@ ${last.partialOutput}` : "");
|
|
|
9496
9505
|
const producesArtifact = /\b(?:create|build|implement|generate|write|refactor|rewrite|add|fix|deploy|install|migrate|scaffold|set up|save (?:a|the)|report|\.(?:pdf|md|txt|json|csv|py|js|ts|tsx|jsx|html|docx?))\b/i.test(p);
|
|
9497
9506
|
return inquiry && !producesArtifact;
|
|
9498
9507
|
}
|
|
9508
|
+
/**
|
|
9509
|
+
* Strong, explicit signals that a task needs the full hierarchy (planning +
|
|
9510
|
+
* multiple sections/workers). Deliberately conservative: requires a
|
|
9511
|
+
* build/implementation verb AND either an app/system-scale noun or an
|
|
9512
|
+
* explicit multi-part structure, so ordinary single-file asks (handled as
|
|
9513
|
+
* Simple/Moderate) don't get over-escalated.
|
|
9514
|
+
*/
|
|
9515
|
+
looksClearlyComplex(prompt) {
|
|
9516
|
+
const p = prompt.trim();
|
|
9517
|
+
if (p.length < 24) return false;
|
|
9518
|
+
const buildVerb = /\b(?:build|implement|create|develop|design|scaffold|refactor|migrate|architect|set up|integrate)\b/i.test(p);
|
|
9519
|
+
const scaleNoun = /\b(?:app(?:lication)?|system|platform|service|api|backend|frontend|full[- ]?stack|website|dashboard|pipeline|microservices?|database schema|authentication|end[- ]to[- ]end|codebase|project|multiple files|several (?:files|modules|components)|test suite)\b/i.test(p);
|
|
9520
|
+
const multiPart = /(?:\b(?:and|then|also|plus|as well as)\b.*\b(?:and|then|also)\b)|(?:^|\n)\s*(?:[-*]|\d+[.)])\s+/i.test(p);
|
|
9521
|
+
return buildVerb && (scaleNoun || multiPart);
|
|
9522
|
+
}
|
|
9499
9523
|
// Cache glob scan results per workspace path to avoid repeated I/O.
|
|
9500
9524
|
static globCache = /* @__PURE__ */ new Map();
|
|
9501
9525
|
async countWorkspaceFiles(workspacePath) {
|
|
@@ -9579,10 +9603,16 @@ ${prompt}` : prompt;
|
|
|
9579
9603
|
let verdict;
|
|
9580
9604
|
if (match) {
|
|
9581
9605
|
verdict = match[1] === "simple" ? "Simple" : match[1] === "moderate" ? "Moderate" : "Complex";
|
|
9582
|
-
|
|
9606
|
+
if (verdict !== "Complex" && this.looksClearlyComplex(prompt)) {
|
|
9607
|
+
this.recordDecision("complexity", `Complex \u2014 heuristic floor over classifier "${verdict}": explicit multi-step build/implementation signals (T1 engaged)`);
|
|
9608
|
+
verdict = "Complex";
|
|
9609
|
+
} else {
|
|
9610
|
+
this.recordDecision("complexity", `${verdict} \u2014 classifier: ${reason || "no reason given"}`);
|
|
9611
|
+
}
|
|
9583
9612
|
} else {
|
|
9584
|
-
|
|
9585
|
-
|
|
9613
|
+
const words = prompt.trim().split(/\s+/).length;
|
|
9614
|
+
verdict = words <= 12 ? "Simple" : words >= 40 || this.looksClearlyComplex(prompt) ? "Complex" : "Moderate";
|
|
9615
|
+
this.recordDecision("complexity", `${verdict} \u2014 classifier output unparseable; defaulted by length/signals`);
|
|
9586
9616
|
}
|
|
9587
9617
|
return verdict;
|
|
9588
9618
|
} catch {
|
|
@@ -9634,7 +9664,15 @@ ${prompt}` : prompt;
|
|
|
9634
9664
|
}
|
|
9635
9665
|
escalator.resolveUserDecision(req.id, approved, always);
|
|
9636
9666
|
});
|
|
9637
|
-
const
|
|
9667
|
+
const forceTier = this.config.routing?.forceTier;
|
|
9668
|
+
const forced = forceTier === "T1" ? "Complex" : forceTier === "T2" ? "Moderate" : forceTier === "T3" ? "Simple" : void 0;
|
|
9669
|
+
let complexity;
|
|
9670
|
+
if (forced) {
|
|
9671
|
+
complexity = forced;
|
|
9672
|
+
this.recordDecision("complexity", `${forced} \u2014 manually forced via routing.forceTier=${forceTier}`);
|
|
9673
|
+
} else {
|
|
9674
|
+
complexity = await this.determineComplexity(options.prompt, options.workspacePath || process.cwd(), options.conversationHistory);
|
|
9675
|
+
}
|
|
9638
9676
|
this.telemetry.capture("cascade:session_start", {
|
|
9639
9677
|
complexity,
|
|
9640
9678
|
providerCount: this.config.providers.length,
|
|
@@ -9714,6 +9752,7 @@ ${prompt}` : prompt;
|
|
|
9714
9752
|
try {
|
|
9715
9753
|
if (complexity === "Simple") {
|
|
9716
9754
|
const t3 = new T3Worker(this.router, this.toolRegistry, "root");
|
|
9755
|
+
t3.setPresenter(true);
|
|
9717
9756
|
t3.setHierarchyContext("You are the DIRECT worker for this task. There is no T1 Administrator or T2 Manager involved in this run.");
|
|
9718
9757
|
if (identityPrompt) {
|
|
9719
9758
|
t3.setSystemPromptOverride(identityPrompt);
|
|
@@ -9738,6 +9777,7 @@ ${prompt}` : prompt;
|
|
|
9738
9777
|
this.emit("tier:status", { tierId: "t3-root", status: "COMPLETED", role: "T3" });
|
|
9739
9778
|
} else if (complexity === "Moderate") {
|
|
9740
9779
|
const t2 = new T2Manager(this.router, this.toolRegistry, "root");
|
|
9780
|
+
t2.setPresenter(true);
|
|
9741
9781
|
t2.setHierarchyContext("You are the ROOT Manager for this task. There is no T1 Administrator involved in this run. You are responsible for decomposing the task and managing T3 workers directly.");
|
|
9742
9782
|
if (identityPrompt) {
|
|
9743
9783
|
t2.setSystemPromptOverride(identityPrompt);
|
|
@@ -9787,6 +9827,7 @@ ${prompt}` : prompt;
|
|
|
9787
9827
|
}
|
|
9788
9828
|
} else {
|
|
9789
9829
|
const t1 = new T1Administrator(this.router, this.toolRegistry, this.config);
|
|
9830
|
+
t1.setPresenter(true);
|
|
9790
9831
|
t1.setHierarchyContext("You are the top-level Administrator. You are responsible for the overall plan and supervising multiple T2 Managers.");
|
|
9791
9832
|
if (identityPrompt) {
|
|
9792
9833
|
t1.setSystemPromptOverride(identityPrompt);
|
|
@@ -11244,7 +11285,8 @@ var DashboardSocket = class {
|
|
|
11244
11285
|
socket.on("cascade:run", (payload) => {
|
|
11245
11286
|
if (typeof payload?.prompt === "string" && payload.prompt.trim()) {
|
|
11246
11287
|
const sessionId = typeof payload.sessionId === "string" && payload.sessionId.trim() ? payload.sessionId.trim() : void 0;
|
|
11247
|
-
|
|
11288
|
+
const forceTier = ["T1", "T2", "T3"].includes(payload.forceTier) ? payload.forceTier : void 0;
|
|
11289
|
+
callback(payload.prompt.trim(), payload.model ?? "auto", socket.id, sessionId, forceTier);
|
|
11248
11290
|
}
|
|
11249
11291
|
});
|
|
11250
11292
|
});
|
|
@@ -11271,6 +11313,13 @@ var DashboardServer = class {
|
|
|
11271
11313
|
* of runs the session performed in this server's lifetime.
|
|
11272
11314
|
*/
|
|
11273
11315
|
sessionTaskIds = /* @__PURE__ */ new Map();
|
|
11316
|
+
/**
|
|
11317
|
+
* Tool-approval requests awaiting a user decision from a connected client,
|
|
11318
|
+
* keyed by the request's uuid. The desktop shows a modal on
|
|
11319
|
+
* `permission:user-required` and answers with `permission:decision`; this
|
|
11320
|
+
* map is how that answer reaches the run that's blocked on it.
|
|
11321
|
+
*/
|
|
11322
|
+
pendingApprovals = /* @__PURE__ */ new Map();
|
|
11274
11323
|
port;
|
|
11275
11324
|
host;
|
|
11276
11325
|
workspacePath;
|
|
@@ -11329,17 +11378,18 @@ var DashboardServer = class {
|
|
|
11329
11378
|
}
|
|
11330
11379
|
this.persistConfig();
|
|
11331
11380
|
});
|
|
11332
|
-
this.socket.onCascadeRun(async (prompt, model, socketId, requestedSessionId) => {
|
|
11381
|
+
this.socket.onCascadeRun(async (prompt, model, socketId, requestedSessionId, forceTier) => {
|
|
11333
11382
|
const sessionId = requestedSessionId ?? randomUUID();
|
|
11334
11383
|
const abortController = new AbortController();
|
|
11335
11384
|
this.activeControllers.set(sessionId, abortController);
|
|
11336
11385
|
const runPrompt = requestedSessionId ? this.buildContinuationPrompt(sessionId, prompt) : prompt;
|
|
11337
11386
|
const title = this.persistRunStart(sessionId, prompt);
|
|
11338
|
-
|
|
11387
|
+
let cfg = model !== "auto" ? { ...this.config, models: { ...this.config.models, t1: model } } : this.config;
|
|
11388
|
+
if (forceTier) cfg = { ...cfg, routing: { ...cfg.routing, forceTier } };
|
|
11339
11389
|
const cascade = new Cascade(cfg, this.workspacePath, this.store);
|
|
11340
11390
|
this.activeSessions.set(sessionId, cascade);
|
|
11341
11391
|
cascade.on("stream:token", (e) => {
|
|
11342
|
-
this.socket.emitToSocket(socketId, "stream:token", { sessionId, tierId: e.tierId, text: e.text });
|
|
11392
|
+
this.socket.emitToSocket(socketId, "stream:token", { sessionId, tierId: e.tierId, text: e.text, primary: e.primary });
|
|
11343
11393
|
});
|
|
11344
11394
|
cascade.on("tier:status", (e) => {
|
|
11345
11395
|
this.socket.emitToSocket(socketId, "tier:status", { sessionId, ...e });
|
|
@@ -11351,7 +11401,11 @@ var DashboardServer = class {
|
|
|
11351
11401
|
this.socket.emitPeerMessage(e);
|
|
11352
11402
|
});
|
|
11353
11403
|
try {
|
|
11354
|
-
const result = await cascade.run({
|
|
11404
|
+
const result = await cascade.run({
|
|
11405
|
+
prompt: runPrompt,
|
|
11406
|
+
signal: abortController.signal,
|
|
11407
|
+
approvalCallback: this.makeApprovalCallback(sessionId)
|
|
11408
|
+
});
|
|
11355
11409
|
this.recordSessionTask(sessionId, result.taskId);
|
|
11356
11410
|
this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
|
|
11357
11411
|
this.socket.emitToSocket(socketId, "session:complete", { sessionId, result });
|
|
@@ -11370,10 +11424,18 @@ var DashboardServer = class {
|
|
|
11370
11424
|
} finally {
|
|
11371
11425
|
this.activeSessions.delete(sessionId);
|
|
11372
11426
|
this.activeControllers.delete(sessionId);
|
|
11427
|
+
this.denyPendingApprovals(sessionId);
|
|
11373
11428
|
}
|
|
11374
11429
|
});
|
|
11375
11430
|
this.socket.onSessionHalt((sessionId) => {
|
|
11376
11431
|
this.activeControllers.get(sessionId)?.abort();
|
|
11432
|
+
this.denyPendingApprovals(sessionId);
|
|
11433
|
+
});
|
|
11434
|
+
this.socket.onApprovalResponse(({ requestId, approved, always }) => {
|
|
11435
|
+
const pending = this.pendingApprovals.get(requestId);
|
|
11436
|
+
if (!pending) return;
|
|
11437
|
+
this.pendingApprovals.delete(requestId);
|
|
11438
|
+
pending.resolve({ approved: !!approved, always: !!always });
|
|
11377
11439
|
});
|
|
11378
11440
|
this.socket.onSessionSteer((message, sessionId, nodeId) => {
|
|
11379
11441
|
const steered = this.steerSessions(message, sessionId, nodeId);
|
|
@@ -11541,6 +11603,29 @@ var DashboardServer = class {
|
|
|
11541
11603
|
list.push(taskId);
|
|
11542
11604
|
this.sessionTaskIds.set(sessionId, list);
|
|
11543
11605
|
}
|
|
11606
|
+
/**
|
|
11607
|
+
* Approval bridge: cascade calls this when a dangerous tool escalates to the
|
|
11608
|
+
* user. The request itself was already pushed to the client via the
|
|
11609
|
+
* `permission:user-required` forward; here we just park a resolver keyed by
|
|
11610
|
+
* the request id and wait for the client's `permission:decision` (handled in
|
|
11611
|
+
* onApprovalResponse). Never auto-approves — an unanswered request stays
|
|
11612
|
+
* pending until the client answers, the run ends, or the escalator's own
|
|
11613
|
+
* timeout denies it.
|
|
11614
|
+
*/
|
|
11615
|
+
makeApprovalCallback(sessionId) {
|
|
11616
|
+
return (request) => new Promise((resolve) => {
|
|
11617
|
+
this.pendingApprovals.set(request.id, { resolve, sessionId });
|
|
11618
|
+
});
|
|
11619
|
+
}
|
|
11620
|
+
/** Deny + clear any approvals still pending for a session (run end / abort). */
|
|
11621
|
+
denyPendingApprovals(sessionId) {
|
|
11622
|
+
for (const [id, pending] of this.pendingApprovals) {
|
|
11623
|
+
if (pending.sessionId === sessionId) {
|
|
11624
|
+
this.pendingApprovals.delete(id);
|
|
11625
|
+
pending.resolve({ approved: false, always: false });
|
|
11626
|
+
}
|
|
11627
|
+
}
|
|
11628
|
+
}
|
|
11544
11629
|
persistRuntimeRow(sessionId, title, status, latestPrompt) {
|
|
11545
11630
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11546
11631
|
const row = { sessionId, title, workspacePath: this.workspacePath, status, startedAt: now, updatedAt: now, latestPrompt, isGlobal: false };
|
|
@@ -11729,15 +11814,6 @@ ${prompt}`;
|
|
|
11729
11814
|
if (sessionId) this.socket.broadcastToRoom(`session:${sessionId}`, "session:halt", payload);
|
|
11730
11815
|
res.json({ success: true, ...payload });
|
|
11731
11816
|
});
|
|
11732
|
-
this.app.post("/api/approve", auth, mutationLimiter, (req, res) => {
|
|
11733
|
-
const body = req.body;
|
|
11734
|
-
const sessionId = typeof body["sessionId"] === "string" ? body["sessionId"] : void 0;
|
|
11735
|
-
const nodeId = typeof body["nodeId"] === "string" ? body["nodeId"] : void 0;
|
|
11736
|
-
const payload = { sessionId, nodeId, requestedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
11737
|
-
this.socket.broadcast("session:approve", payload);
|
|
11738
|
-
if (sessionId) this.socket.broadcastToRoom(`session:${sessionId}`, "session:approve", payload);
|
|
11739
|
-
res.json({ success: true, ...payload });
|
|
11740
|
-
});
|
|
11741
11817
|
this.app.post("/api/inject", auth, mutationLimiter, (req, res) => {
|
|
11742
11818
|
const body = req.body;
|
|
11743
11819
|
const message = typeof body["message"] === "string" ? body["message"] : void 0;
|
|
@@ -11988,7 +12064,7 @@ ${prompt}`;
|
|
|
11988
12064
|
const cascade = new Cascade(this.config, this.workspacePath, this.store);
|
|
11989
12065
|
this.activeSessions.set(sessionId, cascade);
|
|
11990
12066
|
cascade.on("stream:token", (e) => {
|
|
11991
|
-
this.socket.broadcastToRoom(`session:${sessionId}`, "stream:token", { sessionId, tierId: e.tierId, text: e.text });
|
|
12067
|
+
this.socket.broadcastToRoom(`session:${sessionId}`, "stream:token", { sessionId, tierId: e.tierId, text: e.text, primary: e.primary });
|
|
11992
12068
|
});
|
|
11993
12069
|
cascade.on("tier:status", (e) => {
|
|
11994
12070
|
this.socket.broadcastToRoom(`session:${sessionId}`, "tier:status", { sessionId, ...e });
|
|
@@ -12000,7 +12076,11 @@ ${prompt}`;
|
|
|
12000
12076
|
this.socket.emitPeerMessage(e);
|
|
12001
12077
|
});
|
|
12002
12078
|
try {
|
|
12003
|
-
const result = await cascade.run({
|
|
12079
|
+
const result = await cascade.run({
|
|
12080
|
+
prompt: runPrompt,
|
|
12081
|
+
identityId: body.identityId,
|
|
12082
|
+
approvalCallback: this.makeApprovalCallback(sessionId)
|
|
12083
|
+
});
|
|
12004
12084
|
this.recordSessionTask(sessionId, result.taskId);
|
|
12005
12085
|
this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
|
|
12006
12086
|
this.socket.broadcast("cost:update", {
|
|
@@ -12018,6 +12098,7 @@ ${prompt}`;
|
|
|
12018
12098
|
});
|
|
12019
12099
|
} finally {
|
|
12020
12100
|
this.activeSessions.delete(sessionId);
|
|
12101
|
+
this.denyPendingApprovals(sessionId);
|
|
12021
12102
|
}
|
|
12022
12103
|
})();
|
|
12023
12104
|
});
|