@dreb/coding-agent 2.34.3 → 2.36.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/CHANGELOG.md +10 -0
- package/README.md +1 -1
- package/dist/cli/args.d.ts +3 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +3 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +30 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +172 -130
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/export-html/template.js +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +5 -102
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/git-branch.d.ts +19 -0
- package/dist/core/git-branch.d.ts.map +1 -0
- package/dist/core/git-branch.js +109 -0
- package/dist/core/git-branch.js.map +1 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +21 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +117 -42
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +6 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +8 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/{modes/interactive → core}/tab-title.d.ts +12 -3
- package/dist/core/tab-title.d.ts.map +1 -0
- package/dist/{modes/interactive → core}/tab-title.js +58 -16
- package/dist/core/tab-title.js.map +1 -0
- package/dist/core/tools/search.d.ts.map +1 -1
- package/dist/core/tools/search.js +4 -1
- package/dist/core/tools/search.js.map +1 -1
- package/dist/core/tools/subagent.d.ts +63 -2
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +459 -73
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/core/tools/web.d.ts +1 -1
- package/dist/core/tools/web.d.ts.map +1 -1
- package/dist/core/tools/web.js +60 -16
- package/dist/core/tools/web.js.map +1 -1
- package/dist/main.d.ts +10 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +54 -4
- package/dist/main.js.map +1 -1
- package/dist/modes/index.d.ts +2 -2
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +1 -1
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +4 -40
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +2 -2
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/index.d.ts +2 -2
- package/dist/modes/rpc/index.d.ts.map +1 -1
- package/dist/modes/rpc/index.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +114 -6
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +140 -2
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +97 -0
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +656 -27
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +312 -1
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/dashboard.md +254 -0
- package/docs/development.md +8 -5
- package/docs/rpc.md +652 -5
- package/examples/sdk/12-full-control.ts +1 -1
- package/package.json +1 -1
- package/dist/modes/interactive/tab-title.d.ts.map +0 -1
- package/dist/modes/interactive/tab-title.js.map +0 -1
|
@@ -11,16 +11,544 @@
|
|
|
11
11
|
* - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response
|
|
12
12
|
*/
|
|
13
13
|
import * as crypto from "node:crypto";
|
|
14
|
+
import { basename } from "node:path";
|
|
15
|
+
import { isValidThinkingLevel, VALID_THINKING_LEVELS } from "../../cli/args.js";
|
|
14
16
|
import { VERSION } from "../../config.js";
|
|
17
|
+
import { DailyCostTracker } from "../../core/daily-cost-tracker.js";
|
|
18
|
+
import { getGitBranch } from "../../core/git-branch.js";
|
|
15
19
|
import { parseModelPattern } from "../../core/model-resolver.js";
|
|
16
20
|
import { takeOverStdout, writeRawStdout } from "../../core/output-guard.js";
|
|
17
21
|
import { SessionManager } from "../../core/session-manager.js";
|
|
22
|
+
import { TabTitleGenerator } from "../../core/tab-title.js";
|
|
23
|
+
import { discoverAgentTypes, getBackgroundAgents, rehydrateBackgroundAgentsFromDisk, } from "../../core/tools/subagent.js";
|
|
18
24
|
import { theme } from "../interactive/theme/theme.js";
|
|
19
25
|
import { attachJsonlLineReader, serializeJsonLine } from "./jsonl.js";
|
|
26
|
+
/**
|
|
27
|
+
* Map a core {@link SessionInfo} to the RPC DTO, converting Date fields to ISO strings.
|
|
28
|
+
* Shared by the `list_sessions` and `list_all_sessions` handlers so their shapes cannot drift.
|
|
29
|
+
*/
|
|
30
|
+
export function toRpcSessionInfo(s) {
|
|
31
|
+
return {
|
|
32
|
+
path: s.path,
|
|
33
|
+
id: s.id,
|
|
34
|
+
cwd: s.cwd,
|
|
35
|
+
name: s.name,
|
|
36
|
+
created: s.created.toISOString(),
|
|
37
|
+
modified: s.modified.toISOString(),
|
|
38
|
+
messageCount: s.messageCount,
|
|
39
|
+
firstMessage: s.firstMessage,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
20
42
|
export function getPerformanceStatsData(session) {
|
|
21
43
|
const tracker = session.getPerformanceTracker();
|
|
22
44
|
return { models: tracker.getAllRollingAverages() };
|
|
23
45
|
}
|
|
46
|
+
export function getScopedModelsForRpc(session) {
|
|
47
|
+
return session.scopedModels.map(({ model, thinkingLevel }) => ({
|
|
48
|
+
provider: model.provider,
|
|
49
|
+
id: model.id,
|
|
50
|
+
name: model.name,
|
|
51
|
+
reasoning: model.reasoning,
|
|
52
|
+
thinkingLevel,
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
export function getResourcesForRpc(session) {
|
|
56
|
+
const extensionsResult = session.resourceLoader.getExtensions();
|
|
57
|
+
return {
|
|
58
|
+
contextFiles: session.resourceLoader.getAgentsFiles().agentsFiles.map((file) => ({ path: file.path })),
|
|
59
|
+
skills: session.getFilteredSkills().map((skill) => ({ name: skill.name, description: skill.description })),
|
|
60
|
+
extensions: extensionsResult.extensions.map((extension) => ({
|
|
61
|
+
path: extension.path,
|
|
62
|
+
name: extension.sourceInfo.source || basename(extension.path),
|
|
63
|
+
})),
|
|
64
|
+
promptTemplates: session.promptTemplates.map((template) => ({
|
|
65
|
+
name: template.name,
|
|
66
|
+
description: template.description,
|
|
67
|
+
})),
|
|
68
|
+
systemPromptPresent: session.resourceLoader.getSystemPrompt() !== undefined ||
|
|
69
|
+
session.resourceLoader.getAppendSystemPrompt().length > 0,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export function getPendingMessagesForRpc(session) {
|
|
73
|
+
return {
|
|
74
|
+
steering: [...session.getSteeringMessages()],
|
|
75
|
+
followUp: [...session.getFollowUpMessages()],
|
|
76
|
+
steeringMessages: session.getSteeringMessagePayloads().map((message) => ({
|
|
77
|
+
text: message.text,
|
|
78
|
+
images: message.images ? [...message.images] : undefined,
|
|
79
|
+
})),
|
|
80
|
+
followUpMessages: session.getFollowUpMessagePayloads().map((message) => ({
|
|
81
|
+
text: message.text,
|
|
82
|
+
images: message.images ? [...message.images] : undefined,
|
|
83
|
+
})),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export function getStateForRpc(session, modelFallbackMessage) {
|
|
87
|
+
return {
|
|
88
|
+
model: session.model,
|
|
89
|
+
scopedModels: getScopedModelsForRpc(session),
|
|
90
|
+
usingSubscription: session.model ? session.modelRegistry.isUsingOAuth(session.model) : false,
|
|
91
|
+
thinkingLevel: session.thinkingLevel,
|
|
92
|
+
isStreaming: session.isStreaming,
|
|
93
|
+
isCompacting: session.isCompacting,
|
|
94
|
+
steeringMode: session.steeringMode,
|
|
95
|
+
followUpMode: session.followUpMode,
|
|
96
|
+
sessionFile: session.sessionFile,
|
|
97
|
+
sessionId: session.sessionId,
|
|
98
|
+
sessionName: session.sessionName,
|
|
99
|
+
autoCompactionEnabled: session.autoCompactionEnabled,
|
|
100
|
+
messageCount: session.messages.length,
|
|
101
|
+
pendingMessageCount: session.pendingMessageCount,
|
|
102
|
+
contextUsage: session.getContextUsage(),
|
|
103
|
+
modelFallbackMessage,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Handle the `delete_session` RPC command: wires the active session into the core
|
|
108
|
+
* {@link SessionManager.deleteSession} guard and maps the result to a discriminated
|
|
109
|
+
* union the handler serializes. Extracted (like {@link getPerformanceStatsData}) so the guard
|
|
110
|
+
* wiring is unit-testable without a live RPC session. Note: the authoritative active-session
|
|
111
|
+
* guard lives in core — this passes the active path through; it does not re-implement it.
|
|
112
|
+
* Uses the same unrestricted path-based addressing as `switch_session` (no containment guard —
|
|
113
|
+
* see PR #315 discussion).
|
|
114
|
+
*/
|
|
115
|
+
export async function deleteSessionForRpc(sessionManager, sessionPath) {
|
|
116
|
+
const activePath = sessionManager.getSessionFile();
|
|
117
|
+
const result = await SessionManager.deleteSession(sessionPath, {
|
|
118
|
+
activeSessionPath: activePath,
|
|
119
|
+
});
|
|
120
|
+
if (!result.ok) {
|
|
121
|
+
return { ok: false, error: result.error ?? "Unknown deletion error" };
|
|
122
|
+
}
|
|
123
|
+
return { ok: true, method: result.method };
|
|
124
|
+
}
|
|
125
|
+
/** Handle the `list_all_sessions` RPC command: list every project's sessions as RPC DTOs. */
|
|
126
|
+
export async function listAllSessionsForRpc() {
|
|
127
|
+
const sessions = await SessionManager.listAll();
|
|
128
|
+
return sessions.map(toRpcSessionInfo);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Map a {@link BackgroundAgentInfo} registry entry to the RPC DTO, converting the
|
|
132
|
+
* epoch-ms timestamp to an ISO string. Shared shape guard for `list_background_agents`.
|
|
133
|
+
*/
|
|
134
|
+
export function toRpcBackgroundAgentInfo(a) {
|
|
135
|
+
return {
|
|
136
|
+
agentId: a.agentId,
|
|
137
|
+
agentType: a.agentType,
|
|
138
|
+
taskSummary: a.taskSummary,
|
|
139
|
+
startedAt: new Date(a.startedAt).toISOString(),
|
|
140
|
+
status: a.status,
|
|
141
|
+
sessionDir: a.sessionDir,
|
|
142
|
+
sessionFile: a.sessionFile,
|
|
143
|
+
cwd: a.cwd,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/** Discover available subagent types for RPC clients, sorted by display name. */
|
|
147
|
+
export function listAgentTypesForRpc(cwd) {
|
|
148
|
+
return [...discoverAgentTypes(cwd).values()]
|
|
149
|
+
.map(({ name, description }) => ({ name, description }))
|
|
150
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Handle the `get_settings` RPC command: snapshot the persistent default settings.
|
|
154
|
+
*
|
|
155
|
+
* Reads the SettingsManager's merged (global + project) view — these are the values that
|
|
156
|
+
* seed fresh runtimes, NOT the live session state (`get_state` reports that). Extracted
|
|
157
|
+
* (like {@link deleteSessionForRpc}) so it is unit-testable without a live RPC session.
|
|
158
|
+
*/
|
|
159
|
+
export function getSettingsForRpc(settingsManager) {
|
|
160
|
+
return {
|
|
161
|
+
defaultProvider: settingsManager.getDefaultProvider(),
|
|
162
|
+
defaultModel: settingsManager.getDefaultModel(),
|
|
163
|
+
defaultThinkingLevel: settingsManager.getDefaultThinkingLevel(),
|
|
164
|
+
steeringMode: settingsManager.getSteeringMode(),
|
|
165
|
+
followUpMode: settingsManager.getFollowUpMode(),
|
|
166
|
+
compactionEnabled: settingsManager.getCompactionEnabled(),
|
|
167
|
+
retryEnabled: settingsManager.getRetryEnabled(),
|
|
168
|
+
imageAutoResize: settingsManager.getImageAutoResize(),
|
|
169
|
+
blockImages: settingsManager.getBlockImages(),
|
|
170
|
+
enableSkillCommands: settingsManager.getEnableSkillCommands(),
|
|
171
|
+
autoLoadNestedContext: settingsManager.getAutoLoadNestedContext(),
|
|
172
|
+
transport: settingsManager.getTransport(),
|
|
173
|
+
hideThinkingBlock: settingsManager.getHideThinkingBlock(),
|
|
174
|
+
agentModels: settingsManager.getAgentModels(),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const SETTINGS_UPDATE_KEYS = [
|
|
178
|
+
"defaultProvider",
|
|
179
|
+
"defaultModel",
|
|
180
|
+
"defaultThinkingLevel",
|
|
181
|
+
"steeringMode",
|
|
182
|
+
"followUpMode",
|
|
183
|
+
"compactionEnabled",
|
|
184
|
+
"retryEnabled",
|
|
185
|
+
"imageAutoResize",
|
|
186
|
+
"blockImages",
|
|
187
|
+
"enableSkillCommands",
|
|
188
|
+
"autoLoadNestedContext",
|
|
189
|
+
"transport",
|
|
190
|
+
"hideThinkingBlock",
|
|
191
|
+
"agentModels",
|
|
192
|
+
];
|
|
193
|
+
const QUEUE_MODES = ["all", "one-at-a-time"];
|
|
194
|
+
const TRANSPORT_SETTINGS = ["sse", "websocket", "auto"];
|
|
195
|
+
function isPlainObject(value) {
|
|
196
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
const proto = Object.getPrototypeOf(value);
|
|
200
|
+
return proto === Object.prototype || proto === null;
|
|
201
|
+
}
|
|
202
|
+
function validateAgentModels(value) {
|
|
203
|
+
if (!isPlainObject(value)) {
|
|
204
|
+
return {
|
|
205
|
+
ok: false,
|
|
206
|
+
error: "Invalid agentModels: must be a plain object mapping agent names to model fallback arrays",
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
for (const [agentName, models] of Object.entries(value)) {
|
|
210
|
+
if (!Array.isArray(models)) {
|
|
211
|
+
return {
|
|
212
|
+
ok: false,
|
|
213
|
+
error: `Invalid agentModels[${JSON.stringify(agentName)}]: expected an array of non-empty strings`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
const invalidModel = models.find((model) => typeof model !== "string" || model.trim().length === 0);
|
|
217
|
+
if (invalidModel !== undefined) {
|
|
218
|
+
return {
|
|
219
|
+
ok: false,
|
|
220
|
+
error: `Invalid agentModels[${JSON.stringify(agentName)}]: expected an array of non-empty strings`,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return { ok: true };
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Simple promise-based mutex that serializes settings writes. RPC commands are dispatched
|
|
228
|
+
* concurrently (`void handleInputLine(line)`) so two `set_settings` commands can overlap.
|
|
229
|
+
* Without serialization, concurrent commands race on SettingsManager's shared error bucket
|
|
230
|
+
* (`drainErrors()` clears the array for everyone): the first to drain takes all errors
|
|
231
|
+
* (including the second's), and the second reports false success despite its write failing.
|
|
232
|
+
*
|
|
233
|
+
* This lock ensures only one apply+flush+drain block runs at a time — each `set_settings`
|
|
234
|
+
* gets an error window isolated from other `set_settings` commands and from stale errors.
|
|
235
|
+
*
|
|
236
|
+
* Known limitation: the lock does NOT cover the per-runtime commands (`set_model`,
|
|
237
|
+
* `set_steering_mode`, etc.), which persist through the same SettingsManager write queue
|
|
238
|
+
* and record failures into the same shared error bucket. Because `flush()` awaits the
|
|
239
|
+
* entire shared queue, a concurrent runtime setter's write failure can land in this
|
|
240
|
+
* operation's post-flush drain window and be reported as a `set_settings` failure (loud
|
|
241
|
+
* but mis-attributed — never a silent success). Per-operation error isolation in
|
|
242
|
+
* SettingsManager is the proper fix and is tracked in
|
|
243
|
+
* https://github.com/aebrer/dreb/issues/319.
|
|
244
|
+
*/
|
|
245
|
+
let settingsWriteQueue = Promise.resolve();
|
|
246
|
+
function settingsWriteLock(fn) {
|
|
247
|
+
const prev = settingsWriteQueue;
|
|
248
|
+
const next = prev.then(fn, fn);
|
|
249
|
+
settingsWriteQueue = next.catch(() => { });
|
|
250
|
+
return next;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Handle the `set_settings` RPC command: validate and persist default settings.
|
|
254
|
+
*
|
|
255
|
+
* Writes persistent defaults via SettingsManager only — never touches live session state
|
|
256
|
+
* (the existing per-runtime commands do that). The whole payload is validated before
|
|
257
|
+
* anything is applied: on any invalid field, nothing changes.
|
|
258
|
+
*
|
|
259
|
+
* Persistence is verified loudly: if the settings file failed to load at startup,
|
|
260
|
+
* SettingsManager.save() silently no-ops — this handler reports that as an error instead
|
|
261
|
+
* of returning success while nothing was written. Write failures surface the same way.
|
|
262
|
+
*
|
|
263
|
+
* The apply+flush+drain block is serialized via {@link settingsWriteLock} so that
|
|
264
|
+
* concurrent `set_settings` commands (dispatched concurrently by the RPC input loop)
|
|
265
|
+
* cannot race on the shared error bucket. Pre-existing stale errors (from other commands
|
|
266
|
+
* like `set_model` that record write failures but never drain) are discarded before
|
|
267
|
+
* applying. See the {@link settingsWriteLock} doc for the remaining attribution caveat
|
|
268
|
+
* with runtime setters that write concurrently during the flush window.
|
|
269
|
+
*/
|
|
270
|
+
export async function setSettingsForRpc(settingsManager, modelRegistry, update) {
|
|
271
|
+
// --- Validate everything first; apply nothing on any failure ---
|
|
272
|
+
if (update === undefined || update === null || typeof update !== "object" || Array.isArray(update)) {
|
|
273
|
+
return { ok: false, error: "set_settings requires a settings object" };
|
|
274
|
+
}
|
|
275
|
+
const unknownKeys = Object.keys(update).filter((key) => !SETTINGS_UPDATE_KEYS.includes(key));
|
|
276
|
+
if (unknownKeys.length > 0) {
|
|
277
|
+
return {
|
|
278
|
+
ok: false,
|
|
279
|
+
error: `Unknown settings key(s): ${unknownKeys.join(", ")}. Valid keys: ${SETTINGS_UPDATE_KEYS.join(", ")}`,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const hasAnySetting = SETTINGS_UPDATE_KEYS.some((key) => update[key] !== undefined);
|
|
283
|
+
if (!hasAnySetting) {
|
|
284
|
+
return { ok: false, error: "set_settings requires at least one setting to change" };
|
|
285
|
+
}
|
|
286
|
+
if (update.defaultThinkingLevel !== undefined) {
|
|
287
|
+
if (typeof update.defaultThinkingLevel !== "string" || !isValidThinkingLevel(update.defaultThinkingLevel)) {
|
|
288
|
+
return {
|
|
289
|
+
ok: false,
|
|
290
|
+
error: `Invalid defaultThinkingLevel: ${JSON.stringify(update.defaultThinkingLevel)}. Valid values: ${VALID_THINKING_LEVELS.join(", ")}`,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
for (const key of ["steeringMode", "followUpMode"]) {
|
|
295
|
+
const value = update[key];
|
|
296
|
+
if (value !== undefined && !QUEUE_MODES.includes(value)) {
|
|
297
|
+
return {
|
|
298
|
+
ok: false,
|
|
299
|
+
error: `Invalid ${key}: ${JSON.stringify(value)}. Valid values: ${QUEUE_MODES.join(", ")}`,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
for (const key of [
|
|
304
|
+
"compactionEnabled",
|
|
305
|
+
"retryEnabled",
|
|
306
|
+
"imageAutoResize",
|
|
307
|
+
"blockImages",
|
|
308
|
+
"enableSkillCommands",
|
|
309
|
+
"autoLoadNestedContext",
|
|
310
|
+
"hideThinkingBlock",
|
|
311
|
+
]) {
|
|
312
|
+
const value = update[key];
|
|
313
|
+
if (value !== undefined && typeof value !== "boolean") {
|
|
314
|
+
return { ok: false, error: `Invalid ${key}: ${JSON.stringify(value)}. Must be a boolean` };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (update.transport !== undefined) {
|
|
318
|
+
if (typeof update.transport !== "string" ||
|
|
319
|
+
!TRANSPORT_SETTINGS.includes(update.transport)) {
|
|
320
|
+
return {
|
|
321
|
+
ok: false,
|
|
322
|
+
error: `Invalid transport: ${JSON.stringify(update.transport)}. Valid values: ${TRANSPORT_SETTINGS.join(", ")}`,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (update.agentModels !== undefined) {
|
|
327
|
+
const validation = validateAgentModels(update.agentModels);
|
|
328
|
+
if (!validation.ok) {
|
|
329
|
+
return validation;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const hasProvider = update.defaultProvider !== undefined;
|
|
333
|
+
const hasModel = update.defaultModel !== undefined;
|
|
334
|
+
if (hasProvider !== hasModel) {
|
|
335
|
+
return {
|
|
336
|
+
ok: false,
|
|
337
|
+
error: "defaultProvider and defaultModel must be set together",
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
if (hasProvider && hasModel) {
|
|
341
|
+
if (typeof update.defaultProvider !== "string" || typeof update.defaultModel !== "string") {
|
|
342
|
+
return { ok: false, error: "defaultProvider and defaultModel must be strings" };
|
|
343
|
+
}
|
|
344
|
+
const models = await modelRegistry.getAvailable();
|
|
345
|
+
const match = models.find((m) => m.provider === update.defaultProvider && m.id === update.defaultModel);
|
|
346
|
+
if (!match) {
|
|
347
|
+
return {
|
|
348
|
+
ok: false,
|
|
349
|
+
error: `Model not found: ${update.defaultProvider}/${update.defaultModel}`,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Serialize the apply+flush+drain block so concurrent set_settings commands
|
|
354
|
+
// cannot race on the shared error bucket. See settingsWriteLock doc.
|
|
355
|
+
return settingsWriteLock(async () => {
|
|
356
|
+
// Discard stale errors left by other operations (set_model, set_steering_mode, etc.)
|
|
357
|
+
// that record write failures into SettingsManager's shared error bucket but never
|
|
358
|
+
// drain it. Without this, we'd mis-attribute their failures to this operation.
|
|
359
|
+
settingsManager.drainErrors();
|
|
360
|
+
// Persisting would silently no-op if the settings file failed to load — fail loudly
|
|
361
|
+
// instead. Checked INSIDE the lock (after the stale-error discard) so a concurrent
|
|
362
|
+
// reload() that flips the load-error state cannot slip between check and apply and
|
|
363
|
+
// turn this write into a silent no-op reported as success.
|
|
364
|
+
if (settingsManager.hasGlobalSettingsLoadError()) {
|
|
365
|
+
return {
|
|
366
|
+
ok: false,
|
|
367
|
+
error: "Cannot write settings: the global settings file failed to load (fix or remove the corrupt settings.json first)",
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
// --- Apply (validated) ---
|
|
371
|
+
if (update.defaultProvider !== undefined && update.defaultModel !== undefined) {
|
|
372
|
+
settingsManager.setDefaultModelAndProvider(update.defaultProvider, update.defaultModel);
|
|
373
|
+
}
|
|
374
|
+
if (update.defaultThinkingLevel !== undefined) {
|
|
375
|
+
settingsManager.setDefaultThinkingLevel(update.defaultThinkingLevel);
|
|
376
|
+
}
|
|
377
|
+
if (update.steeringMode !== undefined) {
|
|
378
|
+
settingsManager.setSteeringMode(update.steeringMode);
|
|
379
|
+
}
|
|
380
|
+
if (update.followUpMode !== undefined) {
|
|
381
|
+
settingsManager.setFollowUpMode(update.followUpMode);
|
|
382
|
+
}
|
|
383
|
+
if (update.compactionEnabled !== undefined) {
|
|
384
|
+
settingsManager.setCompactionEnabled(update.compactionEnabled);
|
|
385
|
+
}
|
|
386
|
+
if (update.retryEnabled !== undefined) {
|
|
387
|
+
settingsManager.setRetryEnabled(update.retryEnabled);
|
|
388
|
+
}
|
|
389
|
+
if (update.imageAutoResize !== undefined) {
|
|
390
|
+
settingsManager.setImageAutoResize(update.imageAutoResize);
|
|
391
|
+
}
|
|
392
|
+
if (update.blockImages !== undefined) {
|
|
393
|
+
settingsManager.setBlockImages(update.blockImages);
|
|
394
|
+
}
|
|
395
|
+
if (update.enableSkillCommands !== undefined) {
|
|
396
|
+
settingsManager.setEnableSkillCommands(update.enableSkillCommands);
|
|
397
|
+
}
|
|
398
|
+
if (update.autoLoadNestedContext !== undefined) {
|
|
399
|
+
settingsManager.setAutoLoadNestedContext(update.autoLoadNestedContext);
|
|
400
|
+
}
|
|
401
|
+
if (update.transport !== undefined) {
|
|
402
|
+
settingsManager.setTransport(update.transport);
|
|
403
|
+
}
|
|
404
|
+
if (update.hideThinkingBlock !== undefined) {
|
|
405
|
+
settingsManager.setHideThinkingBlock(update.hideThinkingBlock);
|
|
406
|
+
}
|
|
407
|
+
const warnings = [];
|
|
408
|
+
if (update.agentModels !== undefined) {
|
|
409
|
+
for (const [agentName, models] of Object.entries(update.agentModels)) {
|
|
410
|
+
if (settingsManager.hasProjectAgentModelOverride(agentName)) {
|
|
411
|
+
warnings.push(`A project-level agentModels override for ${JSON.stringify(agentName)} (.dreb/settings.json) ` +
|
|
412
|
+
"takes precedence — this change to global settings will have no effect. " +
|
|
413
|
+
"Edit the project settings file to change it.");
|
|
414
|
+
}
|
|
415
|
+
if (models.length > 0) {
|
|
416
|
+
settingsManager.setAgentModelsForAgent(agentName, models);
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
settingsManager.removeAgentModelsForAgent(agentName);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
// Ensure durability and surface write errors instead of losing them.
|
|
424
|
+
await settingsManager.flush();
|
|
425
|
+
const writeErrors = settingsManager.drainErrors();
|
|
426
|
+
if (writeErrors.length > 0) {
|
|
427
|
+
const detail = writeErrors.map((e) => `${e.scope}: ${e.error.message}`).join("; ");
|
|
428
|
+
return { ok: false, error: `Failed to persist settings: ${detail}` };
|
|
429
|
+
}
|
|
430
|
+
return warnings.length > 0
|
|
431
|
+
? { ok: true, settings: getSettingsForRpc(settingsManager), warnings }
|
|
432
|
+
: { ok: true, settings: getSettingsForRpc(settingsManager) };
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
function normalizePreview(text) {
|
|
436
|
+
return text.replace(/\s+/g, " ").trim().slice(0, 200);
|
|
437
|
+
}
|
|
438
|
+
function extractTextContent(content) {
|
|
439
|
+
if (typeof content === "string")
|
|
440
|
+
return content;
|
|
441
|
+
if (!Array.isArray(content))
|
|
442
|
+
return "";
|
|
443
|
+
let text = "";
|
|
444
|
+
for (const part of content) {
|
|
445
|
+
if (typeof part === "object" &&
|
|
446
|
+
part !== null &&
|
|
447
|
+
"type" in part &&
|
|
448
|
+
part.type === "text" &&
|
|
449
|
+
"text" in part &&
|
|
450
|
+
typeof part.text === "string") {
|
|
451
|
+
text += part.text;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return text;
|
|
455
|
+
}
|
|
456
|
+
function getRpcEntryPreview(node) {
|
|
457
|
+
const entry = node.entry;
|
|
458
|
+
switch (entry.type) {
|
|
459
|
+
case "message": {
|
|
460
|
+
const msg = entry.message;
|
|
461
|
+
const role = msg.role;
|
|
462
|
+
if (role === "user") {
|
|
463
|
+
return normalizePreview(extractTextContent(msg.content));
|
|
464
|
+
}
|
|
465
|
+
if (role === "assistant") {
|
|
466
|
+
const textContent = normalizePreview(extractTextContent(msg.content));
|
|
467
|
+
if (textContent)
|
|
468
|
+
return textContent;
|
|
469
|
+
if (msg.stopReason === "aborted")
|
|
470
|
+
return "(aborted)";
|
|
471
|
+
if (msg.errorMessage)
|
|
472
|
+
return normalizePreview(msg.errorMessage);
|
|
473
|
+
return "(no content)";
|
|
474
|
+
}
|
|
475
|
+
if (role === "toolResult") {
|
|
476
|
+
return normalizePreview(`[${msg.toolName ?? "tool"}]`);
|
|
477
|
+
}
|
|
478
|
+
if (role === "bashExecution") {
|
|
479
|
+
return normalizePreview(`[bash]: ${msg.command ?? ""}`);
|
|
480
|
+
}
|
|
481
|
+
return normalizePreview(`[${role}]`);
|
|
482
|
+
}
|
|
483
|
+
case "custom_message":
|
|
484
|
+
return normalizePreview(`[${entry.customType}]: ${extractTextContent(entry.content)}`);
|
|
485
|
+
case "compaction":
|
|
486
|
+
return normalizePreview(`[compaction: ${Math.round(entry.tokensBefore / 1000)}k tokens]`);
|
|
487
|
+
case "branch_summary":
|
|
488
|
+
return normalizePreview(`[branch summary]: ${entry.summary}`);
|
|
489
|
+
case "model_change":
|
|
490
|
+
return normalizePreview(`[model: ${entry.modelId}]`);
|
|
491
|
+
case "thinking_level_change":
|
|
492
|
+
return normalizePreview(`[thinking: ${entry.thinkingLevel}]`);
|
|
493
|
+
case "custom":
|
|
494
|
+
return normalizePreview(`[custom: ${entry.customType}]`);
|
|
495
|
+
case "label":
|
|
496
|
+
return normalizePreview(`[label: ${entry.label ?? "(cleared)"}]`);
|
|
497
|
+
case "session_info":
|
|
498
|
+
return normalizePreview(`[title: ${entry.name || "empty"}]`);
|
|
499
|
+
default: {
|
|
500
|
+
// Compile-time exhaustiveness guard: a new SessionEntry type forces an update here.
|
|
501
|
+
const _exhaustive = entry;
|
|
502
|
+
// Runtime: unknown types from forward-compat/corrupt session files get a placeholder.
|
|
503
|
+
return normalizePreview(`[${entry.type}]`);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function toRpcTreeNode(node) {
|
|
508
|
+
const role = node.entry.type === "message" ? String(node.entry.message.role) : undefined;
|
|
509
|
+
return {
|
|
510
|
+
id: node.entry.id,
|
|
511
|
+
parentId: node.entry.parentId,
|
|
512
|
+
type: node.entry.type,
|
|
513
|
+
...(role !== undefined ? { role } : {}),
|
|
514
|
+
preview: getRpcEntryPreview(node),
|
|
515
|
+
timestamp: node.entry.timestamp,
|
|
516
|
+
...(node.label !== undefined ? { label: node.label } : {}),
|
|
517
|
+
children: [],
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Map core session tree nodes to stable RPC DTOs without leaking raw entries/messages.
|
|
522
|
+
* Uses an explicit stack so deep linear session trees do not overflow the JS call stack.
|
|
523
|
+
*/
|
|
524
|
+
export function toRpcTreeNodes(nodes) {
|
|
525
|
+
const roots = [];
|
|
526
|
+
const stack = [];
|
|
527
|
+
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
528
|
+
stack.push({ source: nodes[i], targetSiblings: roots });
|
|
529
|
+
}
|
|
530
|
+
while (stack.length > 0) {
|
|
531
|
+
const { source, targetSiblings } = stack.pop();
|
|
532
|
+
const dto = toRpcTreeNode(source);
|
|
533
|
+
targetSiblings.push(dto);
|
|
534
|
+
for (let i = source.children.length - 1; i >= 0; i--) {
|
|
535
|
+
stack.push({ source: source.children[i], targetSiblings: dto.children });
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return roots;
|
|
539
|
+
}
|
|
540
|
+
/** Return the current session tree and active leaf as RPC DTOs. */
|
|
541
|
+
export function getTreeForRpc(sessionManager) {
|
|
542
|
+
return { roots: toRpcTreeNodes(sessionManager.getTree()), leafId: sessionManager.getLeafId() };
|
|
543
|
+
}
|
|
544
|
+
/** Navigate the active session tree, returning only the stable RPC result fields. */
|
|
545
|
+
export async function navigateTreeForRpc(session, targetId, options) {
|
|
546
|
+
const result = await session.navigateTree(targetId, options ?? {});
|
|
547
|
+
return {
|
|
548
|
+
cancelled: result.cancelled,
|
|
549
|
+
...(result.editorText !== undefined ? { editorText: result.editorText } : {}),
|
|
550
|
+
};
|
|
551
|
+
}
|
|
24
552
|
/**
|
|
25
553
|
* Run in RPC mode.
|
|
26
554
|
* Listens for JSON commands on stdin, outputs events and responses on stdout.
|
|
@@ -39,10 +567,18 @@ export async function runRpcMode(session, modelFallbackMessage) {
|
|
|
39
567
|
const error = (id, command, message) => {
|
|
40
568
|
return { id, type: "response", command, success: false, error: message };
|
|
41
569
|
};
|
|
570
|
+
if (session.sessionFile && session.messages.length > 0) {
|
|
571
|
+
const rehydratedCount = rehydrateBackgroundAgentsFromDisk(session.sessionFile);
|
|
572
|
+
if (rehydratedCount > 0) {
|
|
573
|
+
console.error(`[rpc] Rehydrated ${rehydratedCount} background subagent${rehydratedCount === 1 ? "" : "s"} from disk`);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
42
576
|
// Pending extension UI requests waiting for response
|
|
43
577
|
const pendingExtensionRequests = new Map();
|
|
44
578
|
// Shutdown request flag
|
|
45
579
|
let shutdownRequested = false;
|
|
580
|
+
let dailyCostTracker;
|
|
581
|
+
let dailyCostTrackerPrimed = false;
|
|
46
582
|
/** Helper for dialog methods with signal/timeout support */
|
|
47
583
|
function createDialogPromise(opts, defaultValue, request, parseResponse) {
|
|
48
584
|
if (opts?.signal?.aborted)
|
|
@@ -244,8 +780,51 @@ export async function runRpcMode(session, modelFallbackMessage) {
|
|
|
244
780
|
output({ type: "extension_error", extensionPath: err.extensionPath, event: err.event, error: err.error });
|
|
245
781
|
},
|
|
246
782
|
});
|
|
783
|
+
const getDailyCost = async () => {
|
|
784
|
+
dailyCostTracker ??= new DailyCostTracker();
|
|
785
|
+
if (!dailyCostTrackerPrimed) {
|
|
786
|
+
await dailyCostTracker.refresh();
|
|
787
|
+
dailyCostTrackerPrimed = true;
|
|
788
|
+
}
|
|
789
|
+
return dailyCostTracker.getDailyCost();
|
|
790
|
+
};
|
|
791
|
+
const cwd = session.sessionManager.getCwd();
|
|
792
|
+
const tabTitleSettings = session.settingsManager.getTabTitleSettings();
|
|
793
|
+
const tabTitleGenerator = !session.sessionName && tabTitleSettings?.enabled !== false
|
|
794
|
+
? new TabTitleGenerator(tabTitleSettings, {
|
|
795
|
+
setTitle: () => { },
|
|
796
|
+
setSessionName: (name) => {
|
|
797
|
+
if (!session.sessionName) {
|
|
798
|
+
session.setSessionName(name);
|
|
799
|
+
}
|
|
800
|
+
},
|
|
801
|
+
getMessages: () => session.messages,
|
|
802
|
+
getModel: () => session.model,
|
|
803
|
+
getModelRegistry: () => session.modelRegistry,
|
|
804
|
+
getProvider: () => session.model?.provider,
|
|
805
|
+
getAgentModelsOverride: (name) => session.settingsManager.getAgentModelsForAgent(name),
|
|
806
|
+
getBranch: () => getGitBranch(cwd),
|
|
807
|
+
getRepo: () => basename(cwd),
|
|
808
|
+
getCwd: () => cwd,
|
|
809
|
+
onError: (err) => {
|
|
810
|
+
console.error(`[rpc] Tab title auto-generation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
811
|
+
},
|
|
812
|
+
})
|
|
813
|
+
: undefined;
|
|
247
814
|
// Output all agent events as JSON
|
|
248
815
|
session.subscribe((event) => {
|
|
816
|
+
if (tabTitleGenerator && !session.sessionName) {
|
|
817
|
+
if (event.type === "tool_execution_end") {
|
|
818
|
+
tabTitleGenerator.onToolEnd({
|
|
819
|
+
toolName: event.toolName,
|
|
820
|
+
isError: event.isError,
|
|
821
|
+
result: event.result,
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
else if (event.type === "message_end") {
|
|
825
|
+
tabTitleGenerator.onMessageEnd(event.message);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
249
828
|
output(event);
|
|
250
829
|
});
|
|
251
830
|
// Handle a single command
|
|
@@ -291,22 +870,16 @@ export async function runRpcMode(session, modelFallbackMessage) {
|
|
|
291
870
|
// State
|
|
292
871
|
// =================================================================
|
|
293
872
|
case "get_state": {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
autoCompactionEnabled: session.autoCompactionEnabled,
|
|
305
|
-
messageCount: session.messages.length,
|
|
306
|
-
pendingMessageCount: session.pendingMessageCount,
|
|
307
|
-
modelFallbackMessage,
|
|
308
|
-
};
|
|
309
|
-
return success(id, "get_state", state);
|
|
873
|
+
return success(id, "get_state", getStateForRpc(session, modelFallbackMessage));
|
|
874
|
+
}
|
|
875
|
+
case "get_resources": {
|
|
876
|
+
return success(id, "get_resources", getResourcesForRpc(session));
|
|
877
|
+
}
|
|
878
|
+
case "get_git_branch": {
|
|
879
|
+
return success(id, "get_git_branch", { branch: getGitBranch(session.sessionManager.getCwd()) });
|
|
880
|
+
}
|
|
881
|
+
case "get_daily_cost": {
|
|
882
|
+
return success(id, "get_daily_cost", { cost: await getDailyCost() });
|
|
310
883
|
}
|
|
311
884
|
// =================================================================
|
|
312
885
|
// Model
|
|
@@ -400,6 +973,12 @@ export async function runRpcMode(session, modelFallbackMessage) {
|
|
|
400
973
|
session.setFollowUpMode(command.mode);
|
|
401
974
|
return success(id, "set_follow_up_mode");
|
|
402
975
|
}
|
|
976
|
+
case "get_pending_messages": {
|
|
977
|
+
return success(id, "get_pending_messages", getPendingMessagesForRpc(session));
|
|
978
|
+
}
|
|
979
|
+
case "clear_pending_messages": {
|
|
980
|
+
return success(id, "clear_pending_messages", session.clearQueue());
|
|
981
|
+
}
|
|
403
982
|
// =================================================================
|
|
404
983
|
// Compaction
|
|
405
984
|
// =================================================================
|
|
@@ -411,6 +990,10 @@ export async function runRpcMode(session, modelFallbackMessage) {
|
|
|
411
990
|
session.setAutoCompactionEnabled(command.enabled);
|
|
412
991
|
return success(id, "set_auto_compaction");
|
|
413
992
|
}
|
|
993
|
+
case "abort_compaction": {
|
|
994
|
+
session.abortCompaction();
|
|
995
|
+
return success(id, "abort_compaction");
|
|
996
|
+
}
|
|
414
997
|
// =================================================================
|
|
415
998
|
// Retry
|
|
416
999
|
// =================================================================
|
|
@@ -451,6 +1034,13 @@ export async function runRpcMode(session, modelFallbackMessage) {
|
|
|
451
1034
|
const cancelled = !(await session.switchSession(command.sessionPath));
|
|
452
1035
|
return success(id, "switch_session", { cancelled });
|
|
453
1036
|
}
|
|
1037
|
+
case "delete_session": {
|
|
1038
|
+
const result = await deleteSessionForRpc(session.sessionManager, command.sessionPath);
|
|
1039
|
+
if (!result.ok) {
|
|
1040
|
+
return error(id, "delete_session", result.error);
|
|
1041
|
+
}
|
|
1042
|
+
return success(id, "delete_session", { method: result.method });
|
|
1043
|
+
}
|
|
454
1044
|
case "fork": {
|
|
455
1045
|
const result = await session.fork(command.entryId);
|
|
456
1046
|
return success(id, "fork", { text: result.selectedText, cancelled: result.cancelled });
|
|
@@ -459,6 +1049,23 @@ export async function runRpcMode(session, modelFallbackMessage) {
|
|
|
459
1049
|
const messages = session.getUserMessagesForForking();
|
|
460
1050
|
return success(id, "get_fork_messages", { messages });
|
|
461
1051
|
}
|
|
1052
|
+
case "get_tree": {
|
|
1053
|
+
return success(id, "get_tree", getTreeForRpc(session.sessionManager));
|
|
1054
|
+
}
|
|
1055
|
+
case "navigate_tree": {
|
|
1056
|
+
try {
|
|
1057
|
+
const result = await navigateTreeForRpc(session, command.targetId, {
|
|
1058
|
+
summarize: command.summarize,
|
|
1059
|
+
customInstructions: command.customInstructions,
|
|
1060
|
+
replaceInstructions: command.replaceInstructions,
|
|
1061
|
+
label: command.label,
|
|
1062
|
+
});
|
|
1063
|
+
return success(id, "navigate_tree", result);
|
|
1064
|
+
}
|
|
1065
|
+
catch (e) {
|
|
1066
|
+
return error(id, "navigate_tree", e instanceof Error ? e.message : String(e));
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
462
1069
|
case "get_last_assistant_text": {
|
|
463
1070
|
const text = session.getLastAssistantText();
|
|
464
1071
|
return success(id, "get_last_assistant_text", { text });
|
|
@@ -487,17 +1094,38 @@ export async function runRpcMode(session, modelFallbackMessage) {
|
|
|
487
1094
|
const cwd = session.sessionManager.getCwd();
|
|
488
1095
|
const sessionDir = session.sessionManager.getSessionDir();
|
|
489
1096
|
const sessions = await SessionManager.list(cwd, sessionDir);
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
1097
|
+
return success(id, "list_sessions", { sessions: sessions.map(toRpcSessionInfo) });
|
|
1098
|
+
}
|
|
1099
|
+
case "list_all_sessions": {
|
|
1100
|
+
return success(id, "list_all_sessions", { sessions: await listAllSessionsForRpc() });
|
|
1101
|
+
}
|
|
1102
|
+
// =================================================================
|
|
1103
|
+
// Background agents
|
|
1104
|
+
// =================================================================
|
|
1105
|
+
case "list_background_agents": {
|
|
1106
|
+
return success(id, "list_background_agents", {
|
|
1107
|
+
agents: getBackgroundAgents().map(toRpcBackgroundAgentInfo),
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
case "list_agent_types": {
|
|
1111
|
+
return success(id, "list_agent_types", {
|
|
1112
|
+
agentTypes: listAgentTypesForRpc(session.sessionManager.getCwd()),
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
// =================================================================
|
|
1116
|
+
// Settings (persistent defaults)
|
|
1117
|
+
// =================================================================
|
|
1118
|
+
case "get_settings": {
|
|
1119
|
+
return success(id, "get_settings", getSettingsForRpc(session.settingsManager));
|
|
1120
|
+
}
|
|
1121
|
+
case "set_settings": {
|
|
1122
|
+
const result = await setSettingsForRpc(session.settingsManager, session.modelRegistry, command.settings);
|
|
1123
|
+
if (!result.ok) {
|
|
1124
|
+
return error(id, "set_settings", result.error);
|
|
1125
|
+
}
|
|
1126
|
+
return success(id, "set_settings", result.warnings && result.warnings.length > 0
|
|
1127
|
+
? { ...result.settings, warnings: result.warnings }
|
|
1128
|
+
: result.settings);
|
|
501
1129
|
}
|
|
502
1130
|
// =================================================================
|
|
503
1131
|
// Version
|
|
@@ -549,6 +1177,7 @@ export async function runRpcMode(session, modelFallbackMessage) {
|
|
|
549
1177
|
if (currentRunner?.hasHandlers("session_shutdown")) {
|
|
550
1178
|
await currentRunner.emit({ type: "session_shutdown" });
|
|
551
1179
|
}
|
|
1180
|
+
dailyCostTracker?.dispose();
|
|
552
1181
|
detachInput();
|
|
553
1182
|
process.stdin.pause();
|
|
554
1183
|
process.exit(0);
|