viberoom 0.3.0 → 0.4.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.
Files changed (64) hide show
  1. package/NOTICE +12 -0
  2. package/README.md +119 -96
  3. package/dist/hub.js +18 -0
  4. package/dist/launcher.js +22 -0
  5. package/dist/main.js +4 -3
  6. package/dist/persona.js +2 -2
  7. package/dist/recipes.js +37 -14
  8. package/dist/room.js +3 -2
  9. package/dist/server.js +16 -3
  10. package/package.json +8 -5
  11. package/scripts/vendor-acp.mjs +85 -0
  12. package/ui/app.css +7 -0
  13. package/ui/app.js +116 -26
  14. package/ui/index.html +2 -0
  15. package/vendor/acp/claude-agent-acp/LICENSE +191 -0
  16. package/vendor/acp/claude-agent-acp/dist/acp-agent.js +7694 -0
  17. package/vendor/acp/claude-agent-acp/dist/acp-subagents.js +13 -0
  18. package/vendor/acp/claude-agent-acp/dist/air-extension.js +63 -0
  19. package/vendor/acp/claude-agent-acp/dist/async-tasks.js +613 -0
  20. package/vendor/acp/claude-agent-acp/dist/clear-context-coordinator.js +80 -0
  21. package/vendor/acp/claude-agent-acp/dist/elicitation.js +304 -0
  22. package/vendor/acp/claude-agent-acp/dist/exit-plan.js +154 -0
  23. package/vendor/acp/claude-agent-acp/dist/file-change-audit.js +350 -0
  24. package/vendor/acp/claude-agent-acp/dist/fork-session.js +41 -0
  25. package/vendor/acp/claude-agent-acp/dist/goal-extension.js +50 -0
  26. package/vendor/acp/claude-agent-acp/dist/index.js +98 -0
  27. package/vendor/acp/claude-agent-acp/dist/lib.js +5 -0
  28. package/vendor/acp/claude-agent-acp/dist/native-subagents.js +422 -0
  29. package/vendor/acp/claude-agent-acp/dist/permissions/effects.js +166 -0
  30. package/vendor/acp/claude-agent-acp/dist/permissions/modes.js +41 -0
  31. package/vendor/acp/claude-agent-acp/dist/permissions/normalization.js +100 -0
  32. package/vendor/acp/claude-agent-acp/dist/permissions/options/filesystem.js +124 -0
  33. package/vendor/acp/claude-agent-acp/dist/permissions/options/shared.js +64 -0
  34. package/vendor/acp/claude-agent-acp/dist/permissions/options/shell.js +100 -0
  35. package/vendor/acp/claude-agent-acp/dist/permissions/options/tools.js +135 -0
  36. package/vendor/acp/claude-agent-acp/dist/permissions/options.js +60 -0
  37. package/vendor/acp/claude-agent-acp/dist/permissions/presentation.js +83 -0
  38. package/vendor/acp/claude-agent-acp/dist/permissions/response.js +19 -0
  39. package/vendor/acp/claude-agent-acp/dist/session-config-ids.js +4 -0
  40. package/vendor/acp/claude-agent-acp/dist/session-failure-extension.js +324 -0
  41. package/vendor/acp/claude-agent-acp/dist/session-mode.js +234 -0
  42. package/vendor/acp/claude-agent-acp/dist/session-titles.js +199 -0
  43. package/vendor/acp/claude-agent-acp/dist/settings.js +185 -0
  44. package/vendor/acp/claude-agent-acp/dist/tool-result-meta.js +19 -0
  45. package/vendor/acp/claude-agent-acp/dist/tools.js +1235 -0
  46. package/vendor/acp/claude-agent-acp/dist/utils.js +81 -0
  47. package/vendor/acp/claude-agent-acp/package.json +7 -0
  48. package/vendor/acp/claude-agent-sdk/LICENSE.md +1 -0
  49. package/vendor/acp/claude-agent-sdk/agentSdkTypes.d.ts +1 -0
  50. package/vendor/acp/claude-agent-sdk/bridge.d.ts +378 -0
  51. package/vendor/acp/claude-agent-sdk/bridge.mjs +221 -0
  52. package/vendor/acp/claude-agent-sdk/browser-sdk.d.ts +107 -0
  53. package/vendor/acp/claude-agent-sdk/browser-sdk.js +185 -0
  54. package/vendor/acp/claude-agent-sdk/extractFromBunfs.d.ts +1 -0
  55. package/vendor/acp/claude-agent-sdk/extractFromBunfs.js +156 -0
  56. package/vendor/acp/claude-agent-sdk/manifest.json +65 -0
  57. package/vendor/acp/claude-agent-sdk/manifest.zst.json +73 -0
  58. package/vendor/acp/claude-agent-sdk/package.json +7 -0
  59. package/vendor/acp/claude-agent-sdk/sdk-tools.d.ts +4129 -0
  60. package/vendor/acp/claude-agent-sdk/sdk.d.ts +8687 -0
  61. package/vendor/acp/claude-agent-sdk/sdk.mjs +204 -0
  62. package/vendor/acp/codex-acp/LICENSE +190 -0
  63. package/vendor/acp/codex-acp/dist/index.js +34238 -0
  64. package/vendor/acp/codex-acp/package.json +7 -0
@@ -0,0 +1,19 @@
1
+ import { applyClaudePermissionSelection, parseClaudePermissionSelection } from "./effects.js";
2
+ /** Decode, validate, and interpret an ACP response exactly once. */
3
+ export function decodeClaudePermissionResponse(response, toolName, input, toolUseID, offeredOptions, durableChangeSet) {
4
+ const selection = parseClaudePermissionSelection(response, toolName);
5
+ const offeredOption = offeredOptions.find((option) => option.optionId === selection.optionId);
6
+ if (!offeredOption) {
7
+ throw new Error(`Permission option was not offered: ${selection.optionId}`);
8
+ }
9
+ const permissionResult = applyClaudePermissionSelection(selection, {
10
+ toolName,
11
+ input,
12
+ toolUseID,
13
+ durableChangeSet,
14
+ });
15
+ return {
16
+ permissionResult,
17
+ ...(selection.contextResetMode ? { contextResetMode: selection.contextResetMode } : {}),
18
+ };
19
+ }
@@ -0,0 +1,4 @@
1
+ /** Shared sentinels and ids for session preferences managed by the adapter. */
2
+ export const DEFAULT_MODEL_ID = "default";
3
+ export const DEFAULT_AGENT_ID = "default";
4
+ export const EFFORT_CONFIG_ID = "effort";
@@ -0,0 +1,324 @@
1
+ import { USAGE_LIMIT_ERROR_PREFIXES, } from "../../claude-agent-sdk/sdk.mjs";
2
+ import { randomUUID } from "node:crypto";
3
+ import { AIR_SESSION_FAILURE_CAPABILITY, airCapabilityMeta, clientSupportsAirCapability, withAirMeta, } from "./air-extension.js";
4
+ export function airSessionFailureCapabilityMeta(...additionalCapabilities) {
5
+ return airCapabilityMeta(AIR_SESSION_FAILURE_CAPABILITY, ...additionalCapabilities);
6
+ }
7
+ export function createSessionFailureState() {
8
+ return {
9
+ epoch: randomUUID(),
10
+ revisions: new Map(),
11
+ active: new Map(),
12
+ };
13
+ }
14
+ const AIR_FAILURE_POLICY = {
15
+ advisory: {
16
+ category: "unknown",
17
+ actions: [],
18
+ fallbackTitle: "Claude reported a notice.",
19
+ },
20
+ auth_required: {
21
+ category: "access",
22
+ actions: ["login"],
23
+ fallbackTitle: "Sign in to continue using Claude.",
24
+ },
25
+ bad_request: {
26
+ category: "request",
27
+ actions: [],
28
+ fallbackTitle: "Claude could not process this request.",
29
+ },
30
+ budget_exhausted: {
31
+ category: "limit",
32
+ actions: ["new_session"],
33
+ fallbackTitle: "This Claude session reached its configured budget.",
34
+ },
35
+ context_exhausted: {
36
+ category: "limit",
37
+ actions: ["new_session"],
38
+ fallbackTitle: "This Claude turn reached its configured limit.",
39
+ },
40
+ internal_error: {
41
+ category: "service",
42
+ actions: ["retry", "new_session"],
43
+ fallbackTitle: "Claude Agent encountered an internal error.",
44
+ },
45
+ overloaded: {
46
+ category: "service",
47
+ actions: ["retry"],
48
+ fallbackTitle: "Claude is temporarily overloaded.",
49
+ },
50
+ provider_error: {
51
+ category: "service",
52
+ actions: ["retry"],
53
+ fallbackTitle: "The model provider reported an error.",
54
+ },
55
+ quota_exhausted: {
56
+ category: "limit",
57
+ actions: [],
58
+ fallbackTitle: "The Claude account has no available quota.",
59
+ },
60
+ rate_limited: {
61
+ category: "limit",
62
+ actions: ["retry"],
63
+ fallbackTitle: "Claude is temporarily rate limited.",
64
+ },
65
+ transport_lost: {
66
+ category: "connection",
67
+ actions: ["new_session"],
68
+ fallbackTitle: "The connection to Claude was lost.",
69
+ },
70
+ worker_shutdown: {
71
+ category: "connection",
72
+ actions: ["new_session"],
73
+ fallbackTitle: "The Claude worker is shutting down.",
74
+ },
75
+ };
76
+ export function sessionFailureMeta(failure) {
77
+ return withAirMeta(undefined, AIR_SESSION_FAILURE_CAPABILITY, {
78
+ id: failure.id,
79
+ revision: failure.revision,
80
+ category: failure.category,
81
+ severity: failure.severity,
82
+ title: failure.title,
83
+ ...(failure.details ? { details: failure.details } : {}),
84
+ actions: failure.actions,
85
+ });
86
+ }
87
+ /** `getSessionMessages` deliberately exposes only the API message and strips
88
+ * transcript-level `error` / `isApiErrorMessage` fields. The SDK exports the
89
+ * exact stable prefixes used by its synthetic usage-limit errors, so replay
90
+ * can recover this one typed failure without matching arbitrary model prose. */
91
+ export function assistantMessageText(apiMessage) {
92
+ if (!apiMessage || typeof apiMessage !== "object")
93
+ return undefined;
94
+ const { content } = apiMessage;
95
+ if (typeof content === "string")
96
+ return content || undefined;
97
+ if (!Array.isArray(content))
98
+ return undefined;
99
+ const text = content
100
+ .map((block) => block &&
101
+ typeof block === "object" &&
102
+ "type" in block &&
103
+ block.type === "text" &&
104
+ "text" in block &&
105
+ typeof block.text === "string"
106
+ ? block.text
107
+ : "")
108
+ .join("");
109
+ return text || undefined;
110
+ }
111
+ export function isSyntheticUsageLimitMessage(apiMessage) {
112
+ if (!apiMessage || typeof apiMessage !== "object")
113
+ return false;
114
+ const { model } = apiMessage;
115
+ if (model !== "<synthetic>")
116
+ return false;
117
+ const text = assistantMessageText(apiMessage);
118
+ if (!text)
119
+ return false;
120
+ return USAGE_LIMIT_ERROR_PREFIXES.some((prefix) => text.startsWith(prefix));
121
+ }
122
+ export function activeUsageLimitMessage(messages) {
123
+ let active;
124
+ for (const message of messages) {
125
+ if (message.type !== "assistant" || message.parent_tool_use_id !== null)
126
+ continue;
127
+ if (isSyntheticUsageLimitMessage(message.message)) {
128
+ const title = assistantMessageText(message.message);
129
+ if (title)
130
+ active = { uuid: message.uuid, title };
131
+ continue;
132
+ }
133
+ const model = message.message && typeof message.message === "object" && "model" in message.message
134
+ ? message.message.model
135
+ : undefined;
136
+ // A later real model answer proves the account recovered. Synthetic
137
+ // local-command/interruption messages do not: they can be appended while
138
+ // the provider remains unavailable.
139
+ if (model !== undefined && model !== "<synthetic>")
140
+ active = undefined;
141
+ }
142
+ return active;
143
+ }
144
+ export function supportsAirSessionFailures(capabilities) {
145
+ return clientSupportsAirCapability(capabilities, AIR_SESSION_FAILURE_CAPABILITY);
146
+ }
147
+ function sessionFailureRecoveryPolicy(kind, turnId) {
148
+ switch (kind) {
149
+ case "advisory":
150
+ return "never";
151
+ case "auth_required":
152
+ return "auth_status";
153
+ case "transport_lost":
154
+ case "worker_shutdown":
155
+ return "runtime_rebind";
156
+ case "quota_exhausted":
157
+ return "real_model_success";
158
+ default:
159
+ return turnId ? "next_attempt" : "real_model_success";
160
+ }
161
+ }
162
+ /** Owns the AIR failure lifecycle for both the live consumer and history replay.
163
+ * Published revisions become active only after delivery succeeds; recovery only
164
+ * removes internal active state because transcript records are historical. */
165
+ export class SessionFailureController {
166
+ sessionId;
167
+ state;
168
+ capabilities;
169
+ isCurrent;
170
+ sendUpdate;
171
+ logger;
172
+ constructor(options) {
173
+ this.sessionId = options.sessionId;
174
+ this.state = options.state;
175
+ this.capabilities = options.capabilities;
176
+ this.isCurrent = options.isCurrent;
177
+ this.sendUpdate = options.sendUpdate;
178
+ this.logger = options.logger;
179
+ }
180
+ isSupported() {
181
+ return supportsAirSessionFailures(this.capabilities);
182
+ }
183
+ async emit(failure) {
184
+ if (!this.isCurrent()) {
185
+ this.logger.error(`Session ${this.sessionId}: ignored AIR session failure from a stale consumer`);
186
+ return false;
187
+ }
188
+ try {
189
+ await this.sendUpdate({
190
+ sessionId: this.sessionId,
191
+ update: {
192
+ sessionUpdate: "session_info_update",
193
+ _meta: sessionFailureMeta(failure),
194
+ },
195
+ });
196
+ return true;
197
+ }
198
+ catch (error) {
199
+ this.logger.error(`Session ${this.sessionId}: failed to publish AIR session failure: ${error}`);
200
+ return false;
201
+ }
202
+ }
203
+ recordActive(failure) {
204
+ this.state.revisions.set(failure.id, failure.revision);
205
+ this.state.active.set(failure.id, failure);
206
+ this.state.lastNotice =
207
+ failure.kind === "advisory" ? { title: failure.title, id: failure.id } : undefined;
208
+ }
209
+ async clear(shouldClear = () => true) {
210
+ if (!this.isSupported())
211
+ return true;
212
+ this.state.lastNotice = undefined;
213
+ for (const failure of [...this.state.active.values()]) {
214
+ if (!shouldClear(failure))
215
+ continue;
216
+ this.state.active.delete(failure.id);
217
+ }
218
+ return true;
219
+ }
220
+ async prepare(kind, failureOptions = {}) {
221
+ if (!this.isSupported() || !this.isCurrent())
222
+ return undefined;
223
+ const policy = AIR_FAILURE_POLICY[kind];
224
+ const title = failureOptions.title || policy.fallbackTitle;
225
+ if (kind === "advisory") {
226
+ const id = this.state.lastNotice?.title === title
227
+ ? this.state.lastNotice.id
228
+ : `${this.sessionId}:notice:${this.state.epoch}:${this.state.nextIncident ?? 1}`;
229
+ if (this.state.lastNotice?.title !== title) {
230
+ this.state.nextIncident = (this.state.nextIncident ?? 1) + 1;
231
+ }
232
+ return {
233
+ id,
234
+ revision: (this.state.revisions.get(id) ?? 0) + 1,
235
+ kind,
236
+ category: policy.category,
237
+ severity: "warning",
238
+ title,
239
+ ...(failureOptions.details ? { details: failureOptions.details } : {}),
240
+ actions: policy.actions,
241
+ recoveryPolicy: sessionFailureRecoveryPolicy(kind),
242
+ };
243
+ }
244
+ const id = failureOptions.turnId && !failureOptions.sessionScoped
245
+ ? `${failureOptions.turnId}:error`
246
+ : `${this.sessionId}:session-error:${this.state.epoch}:${this.state.nextIncident ?? 1}`;
247
+ if (!failureOptions.turnId || failureOptions.sessionScoped) {
248
+ this.state.nextIncident = (this.state.nextIncident ?? 1) + 1;
249
+ }
250
+ return {
251
+ id,
252
+ revision: (this.state.revisions.get(id) ?? 0) + 1,
253
+ kind,
254
+ category: policy.category,
255
+ severity: failureOptions.severity ?? "error",
256
+ title,
257
+ ...(failureOptions.details ? { details: failureOptions.details } : {}),
258
+ actions: failureOptions.severity === "warning" ? [] : policy.actions,
259
+ recoveryPolicy: sessionFailureRecoveryPolicy(kind, failureOptions.turnId && !failureOptions.sessionScoped ? failureOptions.turnId : undefined),
260
+ ...(failureOptions.turnId && !failureOptions.sessionScoped
261
+ ? { turnId: failureOptions.turnId }
262
+ : {}),
263
+ };
264
+ }
265
+ async publish(kind, failureOptions = {}) {
266
+ const failure = await this.prepare(kind, failureOptions);
267
+ if (!failure)
268
+ return;
269
+ if (await this.emit(failure))
270
+ this.recordActive(failure);
271
+ }
272
+ async restore(id, kind, title, active = true) {
273
+ if (!this.isSupported() || !this.isCurrent())
274
+ return false;
275
+ const policy = AIR_FAILURE_POLICY[kind];
276
+ const failure = {
277
+ id,
278
+ revision: (this.state.revisions.get(id) ?? 0) + 1,
279
+ kind,
280
+ category: policy.category,
281
+ severity: "error",
282
+ title,
283
+ actions: policy.actions,
284
+ recoveryPolicy: sessionFailureRecoveryPolicy(kind),
285
+ };
286
+ if (!(await this.emit(failure)))
287
+ return false;
288
+ this.state.revisions.set(failure.id, failure.revision);
289
+ if (active) {
290
+ this.state.active.set(failure.id, failure);
291
+ }
292
+ else {
293
+ this.state.active.delete(failure.id);
294
+ }
295
+ return true;
296
+ }
297
+ }
298
+ export function providerFailureCategory(errorKind, isUsageLimit = false) {
299
+ if (isUsageLimit)
300
+ return "quota_exhausted";
301
+ switch (errorKind) {
302
+ case "authentication_failed":
303
+ case "oauth_org_not_allowed":
304
+ return "auth_required";
305
+ case "billing_error":
306
+ case "account_on_hold":
307
+ return "quota_exhausted";
308
+ case "rate_limit":
309
+ return "rate_limited";
310
+ case "overloaded":
311
+ return "overloaded";
312
+ case "invalid_request":
313
+ case "model_not_found":
314
+ return "bad_request";
315
+ case "max_output_tokens":
316
+ return "context_exhausted";
317
+ case "server_error":
318
+ case "unknown":
319
+ case undefined:
320
+ return "provider_error";
321
+ default:
322
+ return "provider_error";
323
+ }
324
+ }
@@ -0,0 +1,234 @@
1
+ import { RequestError, } from "@agentclientprotocol/sdk";
2
+ import { ALLOW_BYPASS } from "./permissions/modes.js";
3
+ export const MODE_CONFIG_ID = "mode";
4
+ export const AUTO_MODE_FALLBACK = "acceptEdits";
5
+ const AUTO_MODE_FALLBACK_NOTICE = "**Auto mode unavailable:** the selected model does not support Auto mode; using Accept edits instead.";
6
+ /** Owns session-mode policy and the ACP/SDK synchronization it requires. */
7
+ export class SessionModeManager {
8
+ options;
9
+ constructor(options) {
10
+ this.options = options;
11
+ }
12
+ async initialize({ query, requestedMode, currentModelInfo, currentModelId, }) {
13
+ const availableModes = this.buildAvailableModes();
14
+ let effectiveMode = requestedMode;
15
+ let autoModeFallbackWarningPending = false;
16
+ if (effectiveMode === "auto" &&
17
+ currentModelInfo !== undefined &&
18
+ currentModelInfo.supportsAutoMode !== true) {
19
+ this.options.logError(`permissions.defaultMode "auto" is not available for model ` +
20
+ `"${currentModelId}"; falling back to "${AUTO_MODE_FALLBACK}".`);
21
+ effectiveMode = AUTO_MODE_FALLBACK;
22
+ autoModeFallbackWarningPending = true;
23
+ await this.trySyncMode(query, effectiveMode, "Failed to sync clamped permissionMode to SDK:");
24
+ }
25
+ else if (!this.isAvailable(availableModes, effectiveMode)) {
26
+ this.options.logError(`permissions.defaultMode "${effectiveMode}" is not available in ` +
27
+ `this session; falling back to "default".`);
28
+ effectiveMode = "default";
29
+ await this.trySyncMode(query, effectiveMode, "Failed to sync clamped permissionMode to SDK:");
30
+ }
31
+ return {
32
+ modes: { currentModeId: effectiveMode, availableModes },
33
+ autoModeFallbackWarningPending,
34
+ };
35
+ }
36
+ static configOption(modes) {
37
+ return {
38
+ id: MODE_CONFIG_ID,
39
+ name: "Mode",
40
+ description: "Session permission mode",
41
+ category: "mode",
42
+ type: "select",
43
+ currentValue: modes.currentModeId,
44
+ options: modes.availableModes.map((mode) => ({
45
+ value: mode.id,
46
+ name: mode.name,
47
+ description: mode.description,
48
+ _meta: mode._meta,
49
+ })),
50
+ };
51
+ }
52
+ syncConfig(session, mode) {
53
+ session.modes = { ...session.modes, currentModeId: mode };
54
+ session.configOptions = session.configOptions.map((option) => option.id === MODE_CONFIG_ID && typeof option.currentValue === "string"
55
+ ? { ...option, currentValue: mode }
56
+ : option);
57
+ }
58
+ availableModeIds(modes) {
59
+ return modes.availableModes.map((mode) => mode.id);
60
+ }
61
+ effectiveMode(session, requestedMode) {
62
+ return requestedMode === "auto" && this.isAutoUnavailable(session)
63
+ ? AUTO_MODE_FALLBACK
64
+ : requestedMode;
65
+ }
66
+ applyPermissionFallback(session, permissionResult) {
67
+ const fallbackApplied = this.isAutoUnavailable(session) &&
68
+ permissionResult.behavior === "allow" &&
69
+ permissionResult.updatedPermissions?.some((update) => update.type === "setMode" && update.mode === "auto") === true;
70
+ if (!fallbackApplied || permissionResult.behavior !== "allow") {
71
+ return { permissionResult, fallbackApplied: false };
72
+ }
73
+ return {
74
+ permissionResult: {
75
+ ...permissionResult,
76
+ updatedPermissions: permissionResult.updatedPermissions?.map((update) => update.type === "setMode" && update.mode === "auto"
77
+ ? { ...update, mode: AUTO_MODE_FALLBACK }
78
+ : update),
79
+ },
80
+ fallbackApplied: true,
81
+ };
82
+ }
83
+ /** Reconcile mode after a model switch. The caller rebuilds config options
84
+ * before publishing the returned state change. */
85
+ async reconcileForModel(session, model) {
86
+ if (session.modes.currentModeId !== "auto" ||
87
+ model === undefined ||
88
+ model.supportsAutoMode === true) {
89
+ return false;
90
+ }
91
+ session.modes = {
92
+ availableModes: session.modes.availableModes,
93
+ currentModeId: AUTO_MODE_FALLBACK,
94
+ };
95
+ await this.trySyncMode(session.query, AUTO_MODE_FALLBACK, `Failed to sync permissionMode to "${AUTO_MODE_FALLBACK}" after model switch invalidated "auto":`);
96
+ return true;
97
+ }
98
+ async selectMode(sessionId, modeId) {
99
+ const session = this.requireOpenSession(sessionId);
100
+ const requestedMode = this.parseMode(modeId);
101
+ if (!this.isAvailable(session.modes.availableModes, requestedMode)) {
102
+ throw new Error(`Mode ${modeId} is not available in this session`);
103
+ }
104
+ const effectiveMode = this.effectiveMode(session, requestedMode);
105
+ try {
106
+ await session.query.setPermissionMode(effectiveMode);
107
+ }
108
+ catch (error) {
109
+ if (error instanceof Error) {
110
+ if (!error.message)
111
+ error.message = "Invalid Mode";
112
+ throw error;
113
+ }
114
+ // eslint-disable-next-line preserve-caught-error
115
+ throw new Error("Invalid Mode");
116
+ }
117
+ if (effectiveMode !== requestedMode) {
118
+ await this.publishFallbackWarning(sessionId, session);
119
+ }
120
+ return effectiveMode;
121
+ }
122
+ async setSessionMode(params) {
123
+ const effectiveMode = await this.selectMode(params.sessionId, params.modeId);
124
+ if (effectiveMode !== params.modeId) {
125
+ await this.publishCurrent(params.sessionId, effectiveMode);
126
+ }
127
+ await this.options.updateConfigOption(params.sessionId, MODE_CONFIG_ID, effectiveMode);
128
+ return {};
129
+ }
130
+ async publishCurrent(sessionId, mode) {
131
+ await this.options.sessionUpdate({
132
+ sessionId,
133
+ update: { sessionUpdate: "current_mode_update", currentModeId: mode },
134
+ });
135
+ }
136
+ async publishFallbackWarning(sessionId, session) {
137
+ session.autoModeFallbackWarningPending = false;
138
+ if (session.autoModeFallbackWarningShown)
139
+ return;
140
+ session.autoModeFallbackWarningShown = true;
141
+ try {
142
+ await this.options.sessionUpdate({
143
+ sessionId,
144
+ update: {
145
+ sessionUpdate: "agent_message_chunk",
146
+ content: { type: "text", text: AUTO_MODE_FALLBACK_NOTICE },
147
+ },
148
+ });
149
+ }
150
+ catch (error) {
151
+ // The fallback has already been applied; a failed advisory must not turn
152
+ // the successful mode change into a failed request.
153
+ this.options.logError(`Failed to publish Auto mode fallback warning: ${error}`);
154
+ }
155
+ }
156
+ async publishFallbackState(sessionId, session) {
157
+ await this.publishCurrent(sessionId, AUTO_MODE_FALLBACK);
158
+ await this.publishFallbackWarning(sessionId, session);
159
+ }
160
+ requireOpenSession(sessionId) {
161
+ const session = this.options.getSession(sessionId);
162
+ if (!session)
163
+ throw new Error("Session not found");
164
+ if (session.queryClosed) {
165
+ throw RequestError.internalError(undefined, this.options.sessionEndedMessage);
166
+ }
167
+ return session;
168
+ }
169
+ isAutoUnavailable(session, modelId = session.models.currentModelId) {
170
+ const modelInfo = session.modelInfos.find((model) => model.value === modelId);
171
+ return modelInfo !== undefined && modelInfo.supportsAutoMode !== true;
172
+ }
173
+ isAvailable(availableModes, modeId) {
174
+ return availableModes.some((mode) => mode.id === modeId);
175
+ }
176
+ parseMode(modeId) {
177
+ switch (modeId) {
178
+ case "auto":
179
+ case "default":
180
+ case "acceptEdits":
181
+ case "bypassPermissions":
182
+ case "dontAsk":
183
+ case "plan":
184
+ return modeId;
185
+ default:
186
+ throw new Error("Invalid Mode");
187
+ }
188
+ }
189
+ buildAvailableModes() {
190
+ const modes = [
191
+ {
192
+ id: "default",
193
+ name: "Manual",
194
+ description: "Always ask before making changes",
195
+ _meta: { kind: "standard" },
196
+ },
197
+ {
198
+ id: "acceptEdits",
199
+ name: "Accept edits",
200
+ description: "Automatically accept all file edits",
201
+ _meta: { kind: "standard" },
202
+ },
203
+ {
204
+ id: "plan",
205
+ name: "Plan",
206
+ description: "Create a plan before making changes",
207
+ _meta: { kind: "plan" },
208
+ },
209
+ {
210
+ id: "auto",
211
+ name: "Auto",
212
+ description: "Claude handles permission decisions",
213
+ _meta: { kind: "auto_review" },
214
+ },
215
+ ];
216
+ if (ALLOW_BYPASS) {
217
+ modes.push({
218
+ id: "bypassPermissions",
219
+ name: "Bypass permissions",
220
+ description: "Accepts all permissions",
221
+ _meta: { kind: "full_access" },
222
+ });
223
+ }
224
+ return modes;
225
+ }
226
+ async trySyncMode(query, mode, errorMessage) {
227
+ try {
228
+ await query.setPermissionMode(mode);
229
+ }
230
+ catch (error) {
231
+ this.options.logError(errorMessage, error);
232
+ }
233
+ }
234
+ }