@dreb/coding-agent 2.45.4 → 2.47.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/README.md +10 -4
- package/dist/core/agent-session.d.ts +10 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +94 -23
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/context-buffer.d.ts +2 -0
- package/dist/core/context-buffer.d.ts.map +1 -1
- package/dist/core/context-buffer.js +8 -0
- package/dist/core/context-buffer.js.map +1 -1
- package/dist/core/dispatch-arbiter.d.ts +94 -0
- package/dist/core/dispatch-arbiter.d.ts.map +1 -0
- package/dist/core/dispatch-arbiter.js +316 -0
- package/dist/core/dispatch-arbiter.js.map +1 -0
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +7 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +10 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +35 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/git-repo-state.d.ts +2 -0
- package/dist/core/git-repo-state.d.ts.map +1 -1
- package/dist/core/git-repo-state.js +19 -0
- package/dist/core/git-repo-state.js.map +1 -1
- package/dist/core/model-routing-guide.d.ts +15 -0
- package/dist/core/model-routing-guide.d.ts.map +1 -0
- package/dist/core/model-routing-guide.js +186 -0
- package/dist/core/model-routing-guide.js.map +1 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +38 -2
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +18 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +33 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/thinking.d.ts +14 -0
- package/dist/core/thinking.d.ts.map +1 -1
- package/dist/core/thinking.js +35 -2
- package/dist/core/thinking.js.map +1 -1
- package/dist/core/tools/ask-user.d.ts +45 -0
- package/dist/core/tools/ask-user.d.ts.map +1 -0
- package/dist/core/tools/ask-user.js +210 -0
- package/dist/core/tools/ask-user.js.map +1 -0
- package/dist/core/tools/index.d.ts +18 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +8 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/subagent.d.ts +53 -6
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +343 -53
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/modes/interactive/components/ask-user.d.ts +59 -0
- package/dist/modes/interactive/components/ask-user.d.ts.map +1 -0
- package/dist/modes/interactive/components/ask-user.js +240 -0
- package/dist/modes/interactive/components/ask-user.js.map +1 -0
- package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +99 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +13 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +167 -6
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +18 -3
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +177 -59
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +32 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/agent-models.md +33 -1
- package/docs/dashboard.md +9 -3
- package/docs/extensions.md +33 -3
- package/docs/json.md +31 -5
- package/docs/rpc.md +105 -15
- package/docs/session.md +8 -2
- package/docs/settings.md +33 -1
- package/docs/skills.md +38 -0
- package/package.json +1 -1
- package/skills/model-routing-guide/SKILL.md +182 -0
|
@@ -21,6 +21,7 @@ import { parseModelPattern } from "../../core/model-resolver.js";
|
|
|
21
21
|
import { takeOverStdout, writeRawStdout } from "../../core/output-guard.js";
|
|
22
22
|
import { SessionManager } from "../../core/session-manager.js";
|
|
23
23
|
import { TabTitleGenerator } from "../../core/tab-title.js";
|
|
24
|
+
import { validateThinkingLevelForModel } from "../../core/thinking.js";
|
|
24
25
|
import { discoverAgentTypes, getBackgroundAgents, rehydrateBackgroundAgentsFromDisk, } from "../../core/tools/subagent.js";
|
|
25
26
|
import { theme } from "../interactive/theme/theme.js";
|
|
26
27
|
import { attachJsonlLineReader, serializeJsonLine } from "./jsonl.js";
|
|
@@ -155,6 +156,7 @@ export function toRpcBackgroundAgentInfo(a) {
|
|
|
155
156
|
sessionDir: a.sessionDir,
|
|
156
157
|
sessionFile: a.sessionFile,
|
|
157
158
|
cwd: a.cwd,
|
|
159
|
+
arbitrations: a.arbitrations?.map((record) => structuredClone(record)),
|
|
158
160
|
};
|
|
159
161
|
}
|
|
160
162
|
/** Discover available subagent types for RPC clients, sorted by display name. */
|
|
@@ -191,6 +193,7 @@ export function getSettingsForRpc(settingsManager) {
|
|
|
191
193
|
transport: settingsManager.getTransport(),
|
|
192
194
|
hideThinkingBlock: settingsManager.getHideThinkingBlock(),
|
|
193
195
|
agentModels: settingsManager.getAgentModels(),
|
|
196
|
+
subagentArbiter: settingsManager.getGlobalSubagentArbiterSettings(),
|
|
194
197
|
};
|
|
195
198
|
}
|
|
196
199
|
function formatSettingsErrors(errors) {
|
|
@@ -257,6 +260,7 @@ const SETTINGS_UPDATE_KEYS = [
|
|
|
257
260
|
"transport",
|
|
258
261
|
"hideThinkingBlock",
|
|
259
262
|
"agentModels",
|
|
263
|
+
"subagentArbiter",
|
|
260
264
|
];
|
|
261
265
|
const QUEUE_MODES = ["all", "one-at-a-time"];
|
|
262
266
|
const TRANSPORT_SETTINGS = ["sse", "websocket", "auto"];
|
|
@@ -534,6 +538,64 @@ export async function setSettingsForRpc(settingsManager, modelRegistry, update)
|
|
|
534
538
|
return validation;
|
|
535
539
|
}
|
|
536
540
|
}
|
|
541
|
+
let subagentArbiter = update.subagentArbiter === null ? undefined : update.subagentArbiter;
|
|
542
|
+
if (subagentArbiter !== undefined) {
|
|
543
|
+
if (!isPlainObject(subagentArbiter)) {
|
|
544
|
+
return { ok: false, error: "subagentArbiter must be an object or null" };
|
|
545
|
+
}
|
|
546
|
+
const validKeys = ["enabled", "model", "thinking", "guidePath"];
|
|
547
|
+
const unknownArbiterKeys = Object.keys(subagentArbiter).filter((key) => !validKeys.includes(key));
|
|
548
|
+
if (unknownArbiterKeys.length > 0) {
|
|
549
|
+
return { ok: false, error: `Unknown subagentArbiter key(s): ${unknownArbiterKeys.join(", ")}` };
|
|
550
|
+
}
|
|
551
|
+
if (subagentArbiter.enabled !== undefined && typeof subagentArbiter.enabled !== "boolean") {
|
|
552
|
+
return { ok: false, error: "subagentArbiter.enabled must be a boolean" };
|
|
553
|
+
}
|
|
554
|
+
if (subagentArbiter.enabled === false) {
|
|
555
|
+
// Explicit disablement is the recovery path for fail-closed policies.
|
|
556
|
+
// Preserve retained fields without validating them so malformed manual
|
|
557
|
+
// edits cannot trap users in a policy that blocks every child spawn.
|
|
558
|
+
subagentArbiter = { ...subagentArbiter };
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
561
|
+
if (subagentArbiter.model !== undefined &&
|
|
562
|
+
(typeof subagentArbiter.model !== "string" || !subagentArbiter.model.trim())) {
|
|
563
|
+
return { ok: false, error: "subagentArbiter.model must be a non-empty exact provider/model string" };
|
|
564
|
+
}
|
|
565
|
+
if (subagentArbiter.thinking !== undefined &&
|
|
566
|
+
(typeof subagentArbiter.thinking !== "string" || !isValidThinkingLevel(subagentArbiter.thinking))) {
|
|
567
|
+
return {
|
|
568
|
+
ok: false,
|
|
569
|
+
error: `Invalid subagentArbiter.thinking: ${JSON.stringify(subagentArbiter.thinking)}. Valid values: ${VALID_THINKING_LEVELS.join(", ")}`,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
if (subagentArbiter.guidePath !== undefined &&
|
|
573
|
+
(typeof subagentArbiter.guidePath !== "string" || !subagentArbiter.guidePath.trim())) {
|
|
574
|
+
return { ok: false, error: "subagentArbiter.guidePath must be a non-empty string" };
|
|
575
|
+
}
|
|
576
|
+
if (subagentArbiter.enabled === true && !subagentArbiter.model) {
|
|
577
|
+
return { ok: false, error: "Enabling subagentArbiter requires an exact provider/model" };
|
|
578
|
+
}
|
|
579
|
+
if (subagentArbiter.model) {
|
|
580
|
+
const slash = subagentArbiter.model.indexOf("/");
|
|
581
|
+
if (slash <= 0 || slash === subagentArbiter.model.length - 1 || /\s/.test(subagentArbiter.model)) {
|
|
582
|
+
return { ok: false, error: "subagentArbiter.model must be an exact provider/model" };
|
|
583
|
+
}
|
|
584
|
+
const provider = subagentArbiter.model.slice(0, slash);
|
|
585
|
+
const modelId = subagentArbiter.model.slice(slash + 1);
|
|
586
|
+
const models = await modelRegistry.getAvailable();
|
|
587
|
+
const model = models.find((candidate) => candidate.provider === provider && candidate.id === modelId);
|
|
588
|
+
if (!model)
|
|
589
|
+
return { ok: false, error: `Arbiter model not found: ${subagentArbiter.model}` };
|
|
590
|
+
if (subagentArbiter.thinking !== undefined) {
|
|
591
|
+
const validation = validateThinkingLevelForModel(model, subagentArbiter.thinking);
|
|
592
|
+
if (!validation.ok)
|
|
593
|
+
return validation;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
subagentArbiter = { ...subagentArbiter };
|
|
597
|
+
}
|
|
598
|
+
}
|
|
537
599
|
let trustedContextFolders;
|
|
538
600
|
if (update.trustedContextFolders !== undefined) {
|
|
539
601
|
try {
|
|
@@ -619,6 +681,9 @@ export async function setSettingsForRpc(settingsManager, modelRegistry, update)
|
|
|
619
681
|
settingsManager.setHideThinkingBlock(update.hideThinkingBlock);
|
|
620
682
|
}
|
|
621
683
|
const warnings = [];
|
|
684
|
+
if (update.subagentArbiter !== undefined) {
|
|
685
|
+
settingsManager.setGlobalSubagentArbiterSettings(subagentArbiter);
|
|
686
|
+
}
|
|
622
687
|
if (update.agentModels !== undefined) {
|
|
623
688
|
for (const [agentName, models] of Object.entries(update.agentModels)) {
|
|
624
689
|
if (settingsManager.hasProjectAgentModelOverride(agentName)) {
|
|
@@ -790,77 +855,105 @@ export async function navigateTreeForRpc(session, targetId, options) {
|
|
|
790
855
|
...(result.editorText !== undefined ? { editorText: result.editorText } : {}),
|
|
791
856
|
};
|
|
792
857
|
}
|
|
793
|
-
|
|
794
|
-
* Run in RPC mode.
|
|
795
|
-
* Listens for JSON commands on stdin, outputs events and responses on stdout.
|
|
796
|
-
*/
|
|
797
|
-
export async function runRpcMode(session, modelFallbackMessage) {
|
|
798
|
-
takeOverStdout();
|
|
799
|
-
const output = (obj) => {
|
|
800
|
-
writeRawStdout(serializeJsonLine(obj));
|
|
801
|
-
};
|
|
802
|
-
const success = (id, command, data) => {
|
|
803
|
-
if (data === undefined) {
|
|
804
|
-
return { id, type: "response", command, success: true };
|
|
805
|
-
}
|
|
806
|
-
return { id, type: "response", command, success: true, data };
|
|
807
|
-
};
|
|
808
|
-
const error = (id, command, message) => {
|
|
809
|
-
return { id, type: "response", command, success: false, error: message };
|
|
810
|
-
};
|
|
811
|
-
if (session.sessionFile && session.messages.length > 0) {
|
|
812
|
-
const rehydratedCount = rehydrateBackgroundAgentsFromDisk(session.sessionFile);
|
|
813
|
-
if (rehydratedCount > 0) {
|
|
814
|
-
console.error(`[rpc] Rehydrated ${rehydratedCount} background subagent${rehydratedCount === 1 ? "" : "s"} from disk`);
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
// Pending extension UI requests waiting for response
|
|
818
|
-
const pendingExtensionRequests = new Map();
|
|
819
|
-
// Shutdown request flag
|
|
820
|
-
let shutdownRequested = false;
|
|
821
|
-
let dailyCostTracker;
|
|
822
|
-
let dailyCostTrackerPrimed = false;
|
|
858
|
+
export function createRpcExtensionUIContext(output, pendingExtensionRequests, onAskStop = () => { }) {
|
|
823
859
|
/** Helper for dialog methods with signal/timeout support */
|
|
824
|
-
function createDialogPromise(opts, defaultValue, request, parseResponse) {
|
|
860
|
+
function createDialogPromise(opts, defaultValue, request, parseResponse, onTimeout) {
|
|
825
861
|
if (opts?.signal?.aborted)
|
|
826
862
|
return Promise.resolve(defaultValue);
|
|
827
863
|
const id = crypto.randomUUID();
|
|
828
864
|
return new Promise((resolve, reject) => {
|
|
829
865
|
let timeoutId;
|
|
866
|
+
let settled = false;
|
|
830
867
|
const cleanup = () => {
|
|
868
|
+
if (settled)
|
|
869
|
+
return false;
|
|
870
|
+
settled = true;
|
|
831
871
|
if (timeoutId)
|
|
832
872
|
clearTimeout(timeoutId);
|
|
833
873
|
opts?.signal?.removeEventListener("abort", onAbort);
|
|
834
874
|
pendingExtensionRequests.delete(id);
|
|
875
|
+
output({ type: "extension_ui_response_handled", id });
|
|
876
|
+
return true;
|
|
835
877
|
};
|
|
836
878
|
const onAbort = () => {
|
|
837
|
-
cleanup()
|
|
879
|
+
if (!cleanup())
|
|
880
|
+
return;
|
|
838
881
|
resolve(defaultValue);
|
|
839
882
|
};
|
|
840
883
|
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
841
884
|
if (opts?.timeout) {
|
|
842
885
|
timeoutId = setTimeout(() => {
|
|
843
|
-
cleanup()
|
|
886
|
+
if (!cleanup())
|
|
887
|
+
return;
|
|
888
|
+
onTimeout?.();
|
|
844
889
|
resolve(defaultValue);
|
|
845
890
|
}, opts.timeout);
|
|
846
891
|
}
|
|
892
|
+
const rpcRequest = {
|
|
893
|
+
type: "extension_ui_request",
|
|
894
|
+
id,
|
|
895
|
+
...request,
|
|
896
|
+
};
|
|
847
897
|
pendingExtensionRequests.set(id, {
|
|
898
|
+
request: rpcRequest,
|
|
848
899
|
resolve: (response) => {
|
|
849
|
-
cleanup()
|
|
850
|
-
|
|
900
|
+
if (!cleanup())
|
|
901
|
+
return;
|
|
902
|
+
try {
|
|
903
|
+
resolve(parseResponse(response));
|
|
904
|
+
}
|
|
905
|
+
catch (error) {
|
|
906
|
+
reject(error);
|
|
907
|
+
}
|
|
908
|
+
},
|
|
909
|
+
reject: (error) => {
|
|
910
|
+
if (!cleanup())
|
|
911
|
+
return;
|
|
912
|
+
reject(error);
|
|
851
913
|
},
|
|
852
|
-
reject,
|
|
853
914
|
});
|
|
854
|
-
output(
|
|
915
|
+
output(rpcRequest);
|
|
855
916
|
});
|
|
856
917
|
}
|
|
857
918
|
/**
|
|
858
919
|
* Create an extension UI context that uses the RPC protocol.
|
|
859
920
|
*/
|
|
860
|
-
|
|
921
|
+
return {
|
|
861
922
|
select: (title, options, opts) => createDialogPromise(opts, undefined, { method: "select", title, options, timeout: opts?.timeout }, (r) => "cancelled" in r && r.cancelled ? undefined : "value" in r ? r.value : undefined),
|
|
862
923
|
confirm: (title, message, opts) => createDialogPromise(opts, false, { method: "confirm", title, message, timeout: opts?.timeout }, (r) => "cancelled" in r && r.cancelled ? false : "confirmed" in r ? r.confirmed : false),
|
|
863
924
|
input: (title, placeholder, opts) => createDialogPromise(opts, undefined, { method: "input", title, placeholder, timeout: opts?.timeout }, (r) => "cancelled" in r && r.cancelled ? undefined : "value" in r ? r.value : undefined),
|
|
925
|
+
ask: (request, opts) => createDialogPromise(opts, undefined, {
|
|
926
|
+
method: "ask",
|
|
927
|
+
title: request.title ?? "Question",
|
|
928
|
+
question: request.question,
|
|
929
|
+
options: request.options,
|
|
930
|
+
allowFreeText: request.allowFreeText,
|
|
931
|
+
multiSelect: request.multiSelect,
|
|
932
|
+
multiline: request.multiline,
|
|
933
|
+
timeout: opts?.timeout,
|
|
934
|
+
// Preserve the authoritative deadline across Dashboard reload,
|
|
935
|
+
// resync, and drill-in hydration instead of restarting the full
|
|
936
|
+
// duration whenever the question component remounts.
|
|
937
|
+
expiresAt: opts?.timeout ? Date.now() + opts.timeout : undefined,
|
|
938
|
+
}, (response) => {
|
|
939
|
+
if ("cancelled" in response && response.cancelled) {
|
|
940
|
+
onAskStop();
|
|
941
|
+
return undefined;
|
|
942
|
+
}
|
|
943
|
+
const selected = response.selected;
|
|
944
|
+
const customText = response.customText;
|
|
945
|
+
if (!Array.isArray(selected) || !selected.every((value) => typeof value === "string")) {
|
|
946
|
+
throw new Error("Invalid RPC ask response: selected must be an array of strings");
|
|
947
|
+
}
|
|
948
|
+
if (customText !== undefined && typeof customText !== "string") {
|
|
949
|
+
throw new Error("Invalid RPC ask response: customText must be a string");
|
|
950
|
+
}
|
|
951
|
+
if (selected.length === 0 && !customText?.trim()) {
|
|
952
|
+
onAskStop();
|
|
953
|
+
return undefined;
|
|
954
|
+
}
|
|
955
|
+
return { selected, customText };
|
|
956
|
+
}, onAskStop),
|
|
864
957
|
notify(message, type) {
|
|
865
958
|
// Fire and forget - no response needed
|
|
866
959
|
output({
|
|
@@ -939,26 +1032,11 @@ export async function runRpcMode(session, modelFallbackMessage) {
|
|
|
939
1032
|
// Host should track editor state locally if needed
|
|
940
1033
|
return "";
|
|
941
1034
|
},
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
if ("cancelled" in response && response.cancelled) {
|
|
948
|
-
resolve(undefined);
|
|
949
|
-
}
|
|
950
|
-
else if ("value" in response) {
|
|
951
|
-
resolve(response.value);
|
|
952
|
-
}
|
|
953
|
-
else {
|
|
954
|
-
resolve(undefined);
|
|
955
|
-
}
|
|
956
|
-
},
|
|
957
|
-
reject,
|
|
958
|
-
});
|
|
959
|
-
output({ type: "extension_ui_request", id, method: "editor", title, prefill });
|
|
960
|
-
});
|
|
961
|
-
},
|
|
1035
|
+
editor: (title, prefill) => createDialogPromise(undefined, undefined, { method: "editor", title, prefill }, (response) => "cancelled" in response && response.cancelled
|
|
1036
|
+
? undefined
|
|
1037
|
+
: "value" in response
|
|
1038
|
+
? response.value
|
|
1039
|
+
: undefined),
|
|
962
1040
|
setEditorComponent() {
|
|
963
1041
|
// Custom editor components not supported in RPC mode
|
|
964
1042
|
},
|
|
@@ -982,7 +1060,45 @@ export async function runRpcMode(session, modelFallbackMessage) {
|
|
|
982
1060
|
setToolsExpanded(_expanded) {
|
|
983
1061
|
// Tool expansion not supported in RPC mode - no TUI
|
|
984
1062
|
},
|
|
985
|
-
}
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
/** Cancel every pending dialog during RPC host teardown. */
|
|
1066
|
+
export function cancelPendingRpcExtensionRequests(pendingExtensionRequests) {
|
|
1067
|
+
for (const [id, pending] of [...pendingExtensionRequests]) {
|
|
1068
|
+
pending.resolve({ type: "extension_ui_response", id, cancelled: true });
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
export async function runRpcMode(session, modelFallbackMessage) {
|
|
1072
|
+
takeOverStdout();
|
|
1073
|
+
const output = (obj) => {
|
|
1074
|
+
writeRawStdout(serializeJsonLine(obj));
|
|
1075
|
+
};
|
|
1076
|
+
const success = (id, command, data) => {
|
|
1077
|
+
if (data === undefined) {
|
|
1078
|
+
return { id, type: "response", command, success: true };
|
|
1079
|
+
}
|
|
1080
|
+
return { id, type: "response", command, success: true, data };
|
|
1081
|
+
};
|
|
1082
|
+
const error = (id, command, message) => {
|
|
1083
|
+
return { id, type: "response", command, success: false, error: message };
|
|
1084
|
+
};
|
|
1085
|
+
if (session.sessionFile && session.messages.length > 0) {
|
|
1086
|
+
const rehydratedCount = rehydrateBackgroundAgentsFromDisk(session.sessionFile);
|
|
1087
|
+
if (rehydratedCount > 0) {
|
|
1088
|
+
console.error(`[rpc] Rehydrated ${rehydratedCount} background subagent${rehydratedCount === 1 ? "" : "s"} from disk`);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
// Pending extension UI requests waiting for response. Keep the emitted
|
|
1092
|
+
// payload alongside its callbacks so recovery snapshots can reconstruct the
|
|
1093
|
+
// answer UI after a Dashboard reload or replay gap.
|
|
1094
|
+
const pendingExtensionRequests = new Map();
|
|
1095
|
+
// Shutdown request flag
|
|
1096
|
+
let shutdownRequested = false;
|
|
1097
|
+
let dailyCostTracker;
|
|
1098
|
+
let dailyCostTrackerPrimed = false;
|
|
1099
|
+
// Extension UI context uses the RPC protocol; built by a module-scope
|
|
1100
|
+
// factory so the dialog round trip is unit-testable (see createRpcExtensionUIContext).
|
|
1101
|
+
const createExtensionUIContext = () => createRpcExtensionUIContext(output, pendingExtensionRequests, () => void session.abort());
|
|
986
1102
|
// Set up extensions with RPC-based UI context
|
|
987
1103
|
await session.bindExtensions({
|
|
988
1104
|
uiContext: createExtensionUIContext(),
|
|
@@ -1121,6 +1237,7 @@ export async function runRpcMode(session, modelFallbackMessage) {
|
|
|
1121
1237
|
state: getStateForRpc(session, modelFallbackMessage),
|
|
1122
1238
|
messages: getDashboardMessagesForRpc(session),
|
|
1123
1239
|
backgroundAgents: getBackgroundAgents().map(toRpcBackgroundAgentInfo),
|
|
1240
|
+
pendingExtensionUiRequests: [...pendingExtensionRequests.values()].map(({ request }) => request),
|
|
1124
1241
|
};
|
|
1125
1242
|
return success(id, "get_dashboard_snapshot", data);
|
|
1126
1243
|
}
|
|
@@ -1450,6 +1567,7 @@ export async function runRpcMode(session, modelFallbackMessage) {
|
|
|
1450
1567
|
*/
|
|
1451
1568
|
let detachInput = () => { };
|
|
1452
1569
|
async function shutdown() {
|
|
1570
|
+
cancelPendingRpcExtensionRequests(pendingExtensionRequests);
|
|
1453
1571
|
const currentRunner = session.extensionRunner;
|
|
1454
1572
|
if (currentRunner?.hasHandlers("session_shutdown")) {
|
|
1455
1573
|
await currentRunner.emit({ type: "session_shutdown" });
|