openfox 2.0.77 → 2.0.78

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 (63) hide show
  1. package/dist/{chat-handler-FNRKORKP.js → chat-handler-5ZHPYU24.js} +18 -17
  2. package/dist/{chunk-MF3UW36X.js → chunk-37WF2Z3T.js} +84 -17
  3. package/dist/{chunk-OBQPXUXI.js → chunk-5CQRB7AD.js} +2 -2
  4. package/dist/chunk-64TKWWNF.js +7 -0
  5. package/dist/{chunk-LXFLRM7M.js → chunk-6AE3JPDJ.js} +2 -2
  6. package/dist/chunk-AANVMTYU.js +327 -0
  7. package/dist/{chunk-YMFCFMMB.js → chunk-AFJLY43Z.js} +8 -3
  8. package/dist/{chunk-CI5BWHW7.js → chunk-DI4SIPFT.js} +46 -27
  9. package/dist/{chunk-XNCHOD2X.js → chunk-EHVDAVYG.js} +3 -2
  10. package/dist/{chunk-DH3SSBZN.js → chunk-EYIWUONA.js} +2 -2
  11. package/dist/{chunk-NGJ5XJ7D.js → chunk-M7EFXRDS.js} +57 -290
  12. package/dist/{chunk-4QHDQP2N.js → chunk-MTUYDBSL.js} +2 -2
  13. package/dist/{chunk-YPHLCRFP.js → chunk-NUR2B4RU.js} +3 -3
  14. package/dist/{chunk-46RORSKU.js → chunk-NZIYELSQ.js} +9 -7
  15. package/dist/{chunk-7QXRQ5ZL.js → chunk-RGUUZD5B.js} +5 -1
  16. package/dist/{chunk-YDV7RIQ6.js → chunk-SGBXBP3S.js} +143 -43
  17. package/dist/{chunk-O3NUNU4H.js → chunk-SJTG56CH.js} +5 -4
  18. package/dist/{chunk-DJASQ2ZB.js → chunk-TSVFRWSG.js} +24 -5
  19. package/dist/{chunk-3TQN25OM.js → chunk-UIU7OGHP.js} +25 -9
  20. package/dist/{chunk-BX3VJP2D.js → chunk-UMX7MMNW.js} +573 -221
  21. package/dist/{chunk-VMYDUZWI.js → chunk-VGWJFXPR.js} +23 -11
  22. package/dist/{chunk-CPTC2MD5.js → chunk-WUSWNLTV.js} +3 -3
  23. package/dist/{chunk-5GOOSZX7.js → chunk-X6BXZFO7.js} +1 -1
  24. package/dist/cli/dev.js +1 -1
  25. package/dist/cli/index.js +1 -1
  26. package/dist/{compactor-BJXFE5PL.js → compactor-G7B3V24F.js} +9 -8
  27. package/dist/{events-FLWRLM6V.js → events-KNOZX7YG.js} +7 -4
  28. package/dist/{folding-ONH3D4OK.js → folding-OPP2BVUB.js} +2 -2
  29. package/dist/{inspect-proxy-JHZV34QR.js → inspect-proxy-DRYIIH4O.js} +4 -4
  30. package/dist/{manager-OPMCSYN2.js → manager-ATWZUL3O.js} +5 -5
  31. package/dist/{orchestrator-67BZJEOO.js → orchestrator-L6LX5BV7.js} +17 -16
  32. package/dist/package.json +4 -1
  33. package/dist/{path-security-ZROWNL22.js → path-security-HUXQHEQ5.js} +10 -7
  34. package/dist/{platform-QGXO3CNR.js → platform-VBNNMTR5.js} +4 -4
  35. package/dist/{processor-BCOSEMZK.js → processor-UQX5KL56.js} +18 -17
  36. package/dist/{project-creator-C22J43BC.js → project-creator-NNUITNUI.js} +3 -3
  37. package/dist/{projects-WSVINK2C.js → projects-ZGJGICGT.js} +3 -3
  38. package/dist/{protocol-Ceapo3jt.d.ts → protocol-BXxJmsOW.d.ts} +2 -1
  39. package/dist/{protocol-EWEVW65B.js → protocol-Q7X4NEME.js} +3 -3
  40. package/dist/provider/index.d.ts +4 -4
  41. package/dist/{serve-BD44GN7N.js → serve-QWC3U5MB.js} +21 -20
  42. package/dist/server/index.d.ts +27 -4
  43. package/dist/server/index.js +20 -19
  44. package/dist/server-3XUD2NB7.js +40 -0
  45. package/dist/sessions-SFG42M4W.js +42 -0
  46. package/dist/{settings-7XDMSPIC.js → settings-6M55N4Q2.js} +5 -3
  47. package/dist/shared/index.d.ts +3 -3
  48. package/dist/shared/index.js +1 -1
  49. package/dist/{tools-T7S45DOQ.js → tools-HGKUD327.js} +23 -20
  50. package/dist/{types-DABVUbUu.d.ts → types-C308SU4B.d.ts} +2 -0
  51. package/dist/{types-pssMT-oY.d.ts → types-o07LJcLI.d.ts} +1 -1
  52. package/dist/{update-56VZOLQJ.js → update-NWZALB5M.js} +2 -2
  53. package/dist/web/assets/{index-BuUd6IgK.js → index-7_xKqeJc.js} +75 -75
  54. package/dist/web/assets/index-DRkTjPo3.css +32 -0
  55. package/dist/web/index.html +4 -4
  56. package/dist/web/manifest.webmanifest +1 -1
  57. package/dist/web/registerSW.js +1 -1
  58. package/dist/web/sw.js +1 -1
  59. package/dist/{workspace-6QTTUMMW.js → workspace-J74ZMSOJ.js} +14 -2
  60. package/package.json +4 -1
  61. package/dist/chunk-NUXS5OQB.js +0 -7
  62. package/dist/server-OLRW27DH.js +0 -39
  63. package/dist/web/assets/index-BNVBxKnB.css +0 -32
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  getEventStore
3
- } from "./chunk-NGJ5XJ7D.js";
3
+ } from "./chunk-M7EFXRDS.js";
4
4
  import {
5
5
  createChatPathConfirmationMessage
6
- } from "./chunk-O3NUNU4H.js";
6
+ } from "./chunk-SJTG56CH.js";
7
7
 
8
8
  // src/server/tools/path-security.ts
9
9
  import { realpath } from "fs/promises";
@@ -416,10 +416,8 @@ function providePathConfirmation(callId, approved, alwaysAllow) {
416
416
  });
417
417
  } catch {
418
418
  }
419
- if (approved) {
420
- if (alwaysAllow) {
421
- addAllowedPaths(pending.sessionId, pending.paths);
422
- } else {
419
+ if (approved && alwaysAllow) {
420
+ if (pending.reason !== "dangerous_command" && pending.reason !== "git_no_verify") {
423
421
  addAllowedPaths(pending.sessionId, pending.paths);
424
422
  }
425
423
  }
@@ -451,6 +449,9 @@ function cancelPathConfirmationsForSession(sessionId, reason) {
451
449
  function hasPendingPathConfirmation(callId) {
452
450
  return pendingConfirmations.has(callId);
453
451
  }
452
+ function getConfirmationSessionId(callId) {
453
+ return pendingConfirmations.get(callId)?.sessionId;
454
+ }
454
455
  function getPendingConfirmationsBySession() {
455
456
  const bySession = {};
456
457
  for (const [_callId, pending] of pendingConfirmations.entries()) {
@@ -490,6 +491,7 @@ export {
490
491
  cancelPathConfirmation,
491
492
  cancelPathConfirmationsForSession,
492
493
  hasPendingPathConfirmation,
494
+ getConfirmationSessionId,
493
495
  getPendingConfirmationsBySession
494
496
  };
495
- //# sourceMappingURL=chunk-46RORSKU.js.map
497
+ //# sourceMappingURL=chunk-NZIYELSQ.js.map
@@ -181,6 +181,10 @@ function runMigrations(db2) {
181
181
  logger.info("Migrating sessions table: adding workspace column");
182
182
  db2.exec(`ALTER TABLE sessions ADD COLUMN workspace TEXT`);
183
183
  }
184
+ if (!columnNames.includes("branch")) {
185
+ logger.info("Migrating sessions table: adding branch column");
186
+ db2.exec(`ALTER TABLE sessions ADD COLUMN branch TEXT`);
187
+ }
184
188
  logger.info("Database migrations completed");
185
189
  }
186
190
 
@@ -188,4 +192,4 @@ export {
188
192
  initDatabase,
189
193
  getDatabase
190
194
  };
191
- //# sourceMappingURL=chunk-7QXRQ5ZL.js.map
195
+ //# sourceMappingURL=chunk-RGUUZD5B.js.map
@@ -1,3 +1,14 @@
1
+ import {
2
+ checkAborted,
3
+ createProcess,
4
+ getProcessLogs,
5
+ getProcessStatus,
6
+ getSessionProcesses,
7
+ spawnShell,
8
+ spawnShellProcess,
9
+ startProcessCommand,
10
+ stopProcess
11
+ } from "./chunk-5CQRB7AD.js";
1
12
  import {
2
13
  createMcpTools
3
14
  } from "./chunk-NWO6GRYE.js";
@@ -7,7 +18,14 @@ import {
7
18
  import {
8
19
  getGitBranch,
9
20
  listWorkspaces
10
- } from "./chunk-MF3UW36X.js";
21
+ } from "./chunk-37WF2Z3T.js";
22
+ import {
23
+ PathAccessDeniedError,
24
+ extractAbsolutePathsFromCommand,
25
+ extractSensitivePathsFromCommand,
26
+ registerPathConfirmation,
27
+ requestPathAccess
28
+ } from "./chunk-NZIYELSQ.js";
11
29
  import {
12
30
  AskUserInterrupt,
13
31
  askUserTool
@@ -24,47 +42,30 @@ import {
24
42
  createToolCallEvent,
25
43
  createToolResultEvent,
26
44
  streamLLMPure
27
- } from "./chunk-XNCHOD2X.js";
28
- import {
29
- startInspectProxy
30
- } from "./chunk-DH3SSBZN.js";
31
- import {
32
- getProject
33
- } from "./chunk-LXFLRM7M.js";
34
- import {
35
- checkAborted,
36
- createProcess,
37
- getProcessLogs,
38
- getProcessStatus,
39
- getSessionProcesses,
40
- spawnShell,
41
- spawnShellProcess,
42
- startProcessCommand,
43
- stopProcess
44
- } from "./chunk-OBQPXUXI.js";
45
+ } from "./chunk-EHVDAVYG.js";
45
46
  import {
46
47
  SETTINGS_KEYS,
47
48
  deleteSetting,
48
49
  getSetting,
49
50
  setSetting
50
- } from "./chunk-YMFCFMMB.js";
51
+ } from "./chunk-AFJLY43Z.js";
52
+ import {
53
+ startInspectProxy
54
+ } from "./chunk-EYIWUONA.js";
55
+ import {
56
+ getProject
57
+ } from "./chunk-6AE3JPDJ.js";
51
58
  import {
52
59
  getMaxPerSession,
53
60
  getSessionProcessCount,
54
61
  terminateProcessTree
55
62
  } from "./chunk-I72ACQ6R.js";
56
- import {
57
- PathAccessDeniedError,
58
- extractAbsolutePathsFromCommand,
59
- extractSensitivePathsFromCommand,
60
- requestPathAccess
61
- } from "./chunk-46RORSKU.js";
62
63
  import {
63
64
  getCurrentContextWindowId,
64
65
  getCurrentWindowMessageOptions,
65
66
  getEventStore,
66
67
  getRuntimeConfig
67
- } from "./chunk-NGJ5XJ7D.js";
68
+ } from "./chunk-M7EFXRDS.js";
68
69
  import {
69
70
  buildContextMessagesFromEventHistory,
70
71
  foldContextState,
@@ -74,13 +75,14 @@ import {
74
75
  handleToolResult,
75
76
  reorderToolMessages,
76
77
  stripOrphanedToolCalls
77
- } from "./chunk-DJASQ2ZB.js";
78
+ } from "./chunk-TSVFRWSG.js";
78
79
  import {
79
80
  createChatDoneMessage,
80
81
  createChatMessageMessage,
81
82
  createChatMessageUpdatedMessage,
83
+ createChatPathConfirmationMessage,
82
84
  createQueueStateMessage
83
- } from "./chunk-O3NUNU4H.js";
85
+ } from "./chunk-SJTG56CH.js";
84
86
  import {
85
87
  modelSupportsVision
86
88
  } from "./chunk-V4IE7HJY.js";
@@ -111,7 +113,34 @@ import { readFile as readFile2, stat, readdir } from "fs/promises";
111
113
  import { extname, join } from "path";
112
114
 
113
115
  // src/server/tools/tool-helpers.ts
116
+ import { randomUUID } from "crypto";
114
117
  import { resolve, isAbsolute } from "path";
118
+ function requestUserConfirmation(context, toolLabel, desc) {
119
+ if (context.dangerLevel === "dangerous") return Promise.resolve(true);
120
+ if (context.isSubAgent) return Promise.resolve(false);
121
+ if (typeof context.onEvent !== "function") return Promise.resolve(false);
122
+ const callId = context.toolCallId ?? randomUUID();
123
+ try {
124
+ const eventStore = getEventStore();
125
+ eventStore.append(context.sessionId, {
126
+ type: "path.confirmation_pending",
127
+ data: { callId, tool: toolLabel, paths: [desc], workdir: context.workdir, reason: "dangerous_command" }
128
+ });
129
+ } catch {
130
+ }
131
+ const promise = registerPathConfirmation(
132
+ callId,
133
+ [desc],
134
+ context.sessionId,
135
+ toolLabel,
136
+ context.workdir,
137
+ "dangerous_command"
138
+ );
139
+ context.onEvent(
140
+ createChatPathConfirmationMessage(callId, toolLabel, [desc], context.workdir, "dangerous_command")
141
+ );
142
+ return promise;
143
+ }
115
144
  function buildSignal(timeoutMs, contextSignal) {
116
145
  const timeoutSignal = AbortSignal.timeout(timeoutMs);
117
146
  if (contextSignal) {
@@ -192,7 +221,7 @@ function createTool(name, definition, handler2) {
192
221
  try {
193
222
  return await handler2(args, context, helpers);
194
223
  } catch (error) {
195
- if (error instanceof PathAccessDeniedError) {
224
+ if (error instanceof PathAccessDeniedError || error instanceof AskUserInterrupt) {
196
225
  throw error;
197
226
  }
198
227
  return {
@@ -1009,6 +1038,47 @@ function stripTailPipe(command) {
1009
1038
  }
1010
1039
 
1011
1040
  // src/server/tools/shell.ts
1041
+ var ESCAPE_PATTERNS = [
1042
+ /\bcd\s+['"]?(?:\.\.|\/|~)/,
1043
+ /\bcd\s+\$/,
1044
+ // cd with variable expansion ($HOME, ${HOME})
1045
+ /\bcd\s+\$\(/,
1046
+ // cd with command substitution $(...)
1047
+ /\bcd\s+`/,
1048
+ // cd with backtick substitution
1049
+ /\bgit\s+-C\s+/,
1050
+ /GIT_DIR=/,
1051
+ /\bgit\s+--work-tree[\s=]/,
1052
+ /\bgit\s+--git-dir[\s=]/
1053
+ ];
1054
+ function detectEscapePattern(command) {
1055
+ const normalized = command.replace(/'[^']*'/g, " ").replace(/"[^"]*"/g, " ");
1056
+ for (const pattern of ESCAPE_PATTERNS) {
1057
+ const match = normalized.match(pattern);
1058
+ if (match) {
1059
+ return match[0].trim();
1060
+ }
1061
+ }
1062
+ const segments = command.split(/[;&|]|&&|\|\|/);
1063
+ for (const segment of segments) {
1064
+ const trimmed = segment.trim();
1065
+ if (/^cd\s+.+\.\./.test(trimmed) || /^cd\s+['"]?\//.test(trimmed) || /^cd\s+['"]?~/.test(trimmed) || /^cd\s+['"]?\.\./.test(trimmed) || /^cd\s+.*\$\(/.test(trimmed) || // cd with $(...) substitution
1066
+ /^cd\s+.*`/.test(trimmed) || // cd with backtick substitution
1067
+ /^cd\s+.*\$\{?[A-Za-z_]/.test(trimmed)) {
1068
+ return trimmed;
1069
+ }
1070
+ }
1071
+ return null;
1072
+ }
1073
+ function detectGitMutation(command) {
1074
+ const gitMatch = command.match(
1075
+ /\bgit\s+['"]?(?:checkout|switch|branch\s+(-[dDmcMC]|--delete|--move|--copy|--force|-f)|-b\s+\S+|merge|rebase|reset|cherry-pick|worktree|clone|pull|push|fetch|update-ref|symbolic-ref)/
1076
+ );
1077
+ if (gitMatch) {
1078
+ return gitMatch[0].trim();
1079
+ }
1080
+ return null;
1081
+ }
1012
1082
  var rtkAvailable;
1013
1083
  async function checkRtkAvailability() {
1014
1084
  if (rtkAvailable !== void 0) return rtkAvailable;
@@ -1052,7 +1122,7 @@ var runCommandTool = createTool(
1052
1122
  type: "function",
1053
1123
  function: {
1054
1124
  name: "run_command",
1055
- description: 'Execute a shell command. Returns stdout, stderr, and exit code. Does NOT support trailing "&" for backgrounding \u2014 use background_process tool instead.',
1125
+ description: 'Execute a shell command. Returns stdout, stderr, and exit code. Does NOT support trailing "&" for backgrounding \u2014 use background_process tool instead.\nCommands run from your working directory automatically \u2014 do NOT prepend "cd /path/" to change to a directory you are already in. Doing so triggers unnecessary security confirmations.',
1056
1126
  parameters: {
1057
1127
  type: "object",
1058
1128
  properties: {
@@ -1080,6 +1150,26 @@ var runCommandTool = createTool(
1080
1150
  `Use background_process tool (action: "start") for background/long-running commands instead of '&'. See the tool description for details.`
1081
1151
  );
1082
1152
  }
1153
+ const escapeMatch = detectEscapePattern(args.command);
1154
+ if (escapeMatch) {
1155
+ const desc = `Command "${args.command}" contains workspace escape pattern "${escapeMatch}". Allow this command?`;
1156
+ const approved = await requestUserConfirmation(context, "command", desc);
1157
+ if (!approved) {
1158
+ return helpers.error(
1159
+ `User denied: command "${escapeMatch}" would escape the workspace. Use the workspace tool to switch workspaces directly.`
1160
+ );
1161
+ }
1162
+ }
1163
+ const mutationMatch = detectGitMutation(args.command);
1164
+ if (mutationMatch) {
1165
+ const desc = `Command "${args.command}" modifies Git state (${mutationMatch}). Allow this Git operation?`;
1166
+ const approved = await requestUserConfirmation(context, "command", desc);
1167
+ if (!approved) {
1168
+ return helpers.error(
1169
+ `User denied: "${mutationMatch}" modifies Git state. Use the workspace tool to switch workspaces or branches.`
1170
+ );
1171
+ }
1172
+ }
1083
1173
  const workingDir = args.cwd ? helpers.resolvePath(args.cwd) : context.workdir;
1084
1174
  const pathsToCheck = [workingDir];
1085
1175
  const commandPaths = extractAbsolutePathsFromCommand(args.command);
@@ -2212,7 +2302,7 @@ async function executeTools(assistantMsgId, toolCalls, ctx, append) {
2212
2302
  const onProgress = ctx.onMessage ? createToolProgressHandler(append, assistantMsgId, toolCall.id, ctx.sessionId) : void 0;
2213
2303
  const toolContext = {
2214
2304
  sessionManager: ctx.sessionManager,
2215
- workdir: ctx.workdir,
2305
+ workdir: ctx.sessionManager.getEffectiveWorkdir(ctx.sessionId),
2216
2306
  sessionId: ctx.sessionId,
2217
2307
  signal: ctx.signal,
2218
2308
  llmClient: ctx.llmClient,
@@ -2517,7 +2607,7 @@ ${CONTINUE_PROMPT}` : CONTINUE_PROMPT;
2517
2607
  sessionManager.setCurrentContextSize(sessionId, result.usage.promptTokens, config.subAgentMetadata?.subAgentId);
2518
2608
  if (!compacting) {
2519
2609
  const contextState2 = sessionManager.getContextState(sessionId);
2520
- const { shouldCompact, appendCompactionPrompt } = await import("./compactor-BJXFE5PL.js");
2610
+ const { shouldCompact, appendCompactionPrompt } = await import("./compactor-G7B3V24F.js");
2521
2611
  if (shouldCompact(contextState2.currentTokens, contextState2.maxTokens, runtimeConfig.context.compactionThreshold)) {
2522
2612
  appendCompactionPrompt(sessionId, append);
2523
2613
  compacting = true;
@@ -3452,7 +3542,7 @@ var callSubAgentTool = {
3452
3542
  };
3453
3543
  }
3454
3544
  try {
3455
- const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-T7S45DOQ.js");
3545
+ const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-HGKUD327.js");
3456
3546
  const toolRegistry = getToolRegistryForAgent2(agentDef);
3457
3547
  const turnMetrics = new TurnMetrics();
3458
3548
  const result = await executeSubAgent({
@@ -4455,7 +4545,7 @@ var mcpConfigTool = createTool(
4455
4545
  mcpNotifyChanged?.(sessionId);
4456
4546
  }
4457
4547
  async function rebuildTools() {
4458
- const { setMcpTools: setMcpTools2 } = await import("./tools-T7S45DOQ.js");
4548
+ const { setMcpTools: setMcpTools2 } = await import("./tools-HGKUD327.js");
4459
4549
  const mcpTools = createMcpTools(mcpManagerForTools);
4460
4550
  setMcpTools2(mcpTools);
4461
4551
  }
@@ -4734,7 +4824,7 @@ var workspaceTool = createTool(
4734
4824
  type: "function",
4735
4825
  function: {
4736
4826
  name: "workspace",
4737
- description: 'Manage workspaces for the current session. A workspace is a cloned copy of the project.\n\nUse "switch" to move between workspaces. Target "original" for the project root, or a workspace name.\nIf the workspace does not exist yet, it is created automatically.\n\nSetup commands (e.g. npm install) are configured via .openfox/workspace.json.\n\nActions:\n- switch: Switch to a workspace (target: "original" or a name, optional branch for new workspaces)\n- list: List all workspaces with their current branch and active status\n- delete: Delete a workspace by name (cannot delete "original")',
4827
+ description: 'Manage workspaces for the current session. A workspace is a cloned copy of the project.\n\nUse "switch" to move between workspaces. Target "original" for the project root, or a workspace name.\nIf the workspace does not exist yet, it is created automatically.\n\nYou can also change the branch of the current workspace by calling switch with the current\nworkspace name and a branch parameter. The branch will be checked out without recreating\nthe workspace and without losing uncommitted changes.\n\nIf the requested branch does not exist, it will be created. By default it is based on the\ndefault branch of the project (origin/HEAD). You can specify sourceBranch to base the new\nbranch on a different existing branch.\n\nActions that change the workspace (switch, delete) require explicit user approval via a confirmation dialog.\n\nSetup commands (e.g. npm install) are configured via .openfox/workspace.json.\n\nActions:\n- switch: Switch to a workspace (target: "original" or a name, optional branch for new workspaces)\n- list: List all workspaces with their current branch and active status\n- delete: Delete a workspace by name (cannot delete "original")',
4738
4828
  parameters: {
4739
4829
  type: "object",
4740
4830
  properties: {
@@ -4749,7 +4839,11 @@ var workspaceTool = createTool(
4749
4839
  },
4750
4840
  branch: {
4751
4841
  type: "string",
4752
- description: "Optional git branch to check out when creating a new workspace"
4842
+ description: "Optional git branch to check out when creating or switching to a workspace"
4843
+ },
4844
+ sourceBranch: {
4845
+ type: "string",
4846
+ description: "If branch does not exist, base the new branch on this existing branch. Default: origin/HEAD."
4753
4847
  }
4754
4848
  },
4755
4849
  required: ["action"]
@@ -4767,7 +4861,13 @@ var workspaceTool = createTool(
4767
4861
  if (!args.target || typeof args.target !== "string") {
4768
4862
  return helpers.error('Parameter "target" is required for action=switch ("original" or a workspace name)');
4769
4863
  }
4770
- const updated = await sessionManager.switchWorkspace(sessionId, args.target, args.branch);
4864
+ const currentSession = sessionManager.getSession(sessionId);
4865
+ const isBranchChange = args.branch !== void 0 && currentSession != null && args.target !== "original" && currentSession.workspace?.split("/").pop() === args.target;
4866
+ const label = args.target === "original" ? "original project" : `workspace "${args.target}"`;
4867
+ const desc = isBranchChange ? `Change branch to "${args.branch}" on ${label}` : `Switch to ${label}${args.branch ? ` on branch "${args.branch}"` : ""}`;
4868
+ const approved = await requestUserConfirmation(context, "workspace", desc);
4869
+ if (!approved) return helpers.error(`User denied: ${isBranchChange ? "branch change" : `switch to ${label}`}`);
4870
+ const updated = await sessionManager.switchWorkspace(sessionId, args.target, args.branch, args.sourceBranch);
4771
4871
  const wsName = args.target === "original" ? "original" : updated.workspace?.split("/").pop() ?? args.target;
4772
4872
  const branch = await getGitBranch(updated.workspace ?? updated.workdir);
4773
4873
  return helpers.success(
@@ -4789,7 +4889,7 @@ var workspaceTool = createTool(
4789
4889
  const project = sessionManager.getProject(session.projectId);
4790
4890
  if (!project) return helpers.error("Project not found");
4791
4891
  const currentBranch = await getGitBranch(session.workspace ?? session.workdir);
4792
- const named = await listWorkspaces(project.name);
4892
+ const named = await listWorkspaces(project.name, project.workdir);
4793
4893
  const activeWsName = session.workspace?.split("/").pop() ?? null;
4794
4894
  const workspaces = [
4795
4895
  { name: "original", branch: currentBranch, active: !session.workspace },
@@ -4805,9 +4905,9 @@ var workspaceTool = createTool(
4805
4905
  if (!args.target || typeof args.target !== "string") {
4806
4906
  return helpers.error('Parameter "target" is required for action=delete (the workspace name)');
4807
4907
  }
4808
- if (args.target === "original") {
4809
- return helpers.error("Cannot delete the original workspace");
4810
- }
4908
+ if (args.target === "original") return helpers.error("Cannot delete the original workspace");
4909
+ const approved = await requestUserConfirmation(context, "workspace", `Delete workspace "${args.target}"`);
4910
+ if (!approved) return helpers.error(`User denied: delete workspace "${args.target}"`);
4811
4911
  await sessionManager.deleteWorkspace(sessionId, args.target);
4812
4912
  return helpers.success(
4813
4913
  JSON.stringify(
@@ -5108,4 +5208,4 @@ export {
5108
5208
  getToolRegistryForAgent,
5109
5209
  createToolRegistry
5110
5210
  };
5111
- //# sourceMappingURL=chunk-YDV7RIQ6.js.map
5211
+ //# sourceMappingURL=chunk-SGBXBP3S.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createServerMessage,
3
3
  isClientMessage
4
- } from "./chunk-5GOOSZX7.js";
4
+ } from "./chunk-X6BXZFO7.js";
5
5
 
6
6
  // src/server/ws/protocol.ts
7
7
  function enrichMessagesWithToolResults(messages) {
@@ -43,7 +43,7 @@ function serializeServerMessage(message) {
43
43
  function createErrorMessage(code, message, correlationId) {
44
44
  return createServerMessage("error", { code, message }, correlationId);
45
45
  }
46
- function createSessionStateMessage(session, messages, pendingConfirmations = [], pendingQuestions, gitStatus, correlationId) {
46
+ function createSessionStateMessage(session, messages, pendingConfirmations = [], pendingQuestions, gitStatus, correlationId, hiddenCount) {
47
47
  const enrichedMessages = enrichMessagesWithToolResults(messages);
48
48
  return createServerMessage(
49
49
  "session.state",
@@ -52,7 +52,8 @@ function createSessionStateMessage(session, messages, pendingConfirmations = [],
52
52
  messages: enrichedMessages,
53
53
  pendingConfirmations,
54
54
  ...pendingQuestions ? { pendingQuestions } : {},
55
- ...gitStatus ? { gitStatus } : {}
55
+ ...gitStatus ? { gitStatus } : {},
56
+ ...hiddenCount !== void 0 ? { hiddenCount } : {}
56
57
  },
57
58
  correlationId
58
59
  );
@@ -343,4 +344,4 @@ export {
343
344
  createQueueStateMessage,
344
345
  storedEventToServerMessage
345
346
  };
346
- //# sourceMappingURL=chunk-O3NUNU4H.js.map
347
+ //# sourceMappingURL=chunk-SJTG56CH.js.map
@@ -305,15 +305,34 @@ function applyTurnEventsToSnapshotMessages(initialMessages, events) {
305
305
  });
306
306
  return messages.map((msg) => ({ ...msg, isStreaming: msg.isStreaming ?? true }));
307
307
  }
308
- function buildMessagesFromStoredEvents(events) {
308
+ function buildMessagesFromStoredEvents(events, maxVisibleItems) {
309
309
  const snapshotEvent = [...events].reverse().find((event) => event.type === "turn.snapshot");
310
310
  if (snapshotEvent) {
311
311
  const snapshot = snapshotEvent.data;
312
- const snapshotMessages = snapshot.messages.map(snapshotMessageToMessage);
313
312
  const laterEvents = events.filter((event) => event.seq > snapshotEvent.seq);
314
- return applyStoredMessageEvents(snapshotMessages, laterEvents);
313
+ const laterMessageCount = new Set(
314
+ laterEvents.filter((e) => e.type === "message.start").map((e) => e.data.messageId)
315
+ ).size;
316
+ const totalOriginal = snapshot.messages.length + laterMessageCount;
317
+ let preSlice;
318
+ if (maxVisibleItems !== void 0 && maxVisibleItems > 0 && snapshot.messages.length > maxVisibleItems) {
319
+ preSlice = snapshot.messages.slice(-maxVisibleItems);
320
+ } else {
321
+ preSlice = snapshot.messages;
322
+ }
323
+ const snapshotMessages = preSlice.map(snapshotMessageToMessage);
324
+ const messages2 = applyStoredMessageEvents(snapshotMessages, laterEvents);
325
+ if (maxVisibleItems !== void 0 && maxVisibleItems > 0 && messages2.length > maxVisibleItems) {
326
+ return { messages: messages2.slice(-maxVisibleItems), hiddenCount: totalOriginal - maxVisibleItems };
327
+ }
328
+ const hiddenCount = maxVisibleItems !== void 0 && maxVisibleItems > 0 && totalOriginal > maxVisibleItems ? totalOriginal - maxVisibleItems : 0;
329
+ return { messages: messages2, hiddenCount };
330
+ }
331
+ const messages = applyStoredMessageEvents([], events);
332
+ if (maxVisibleItems !== void 0 && maxVisibleItems > 0 && messages.length > maxVisibleItems) {
333
+ return { messages: messages.slice(-maxVisibleItems), hiddenCount: messages.length - maxVisibleItems };
315
334
  }
316
- return applyStoredMessageEvents([], events);
335
+ return { messages, hiddenCount: 0 };
317
336
  }
318
337
  function buildContextMessagesFromStoredEvents(events, windowId, options) {
319
338
  const includeVerifier = options?.includeVerifier ?? true;
@@ -876,4 +895,4 @@ export {
876
895
  buildSnapshot,
877
896
  buildSnapshotFromSessionState
878
897
  };
879
- //# sourceMappingURL=chunk-DJASQ2ZB.js.map
898
+ //# sourceMappingURL=chunk-TSVFRWSG.js.map
@@ -2,18 +2,24 @@ import {
2
2
  getPendingQuestionsForSession
3
3
  } from "./chunk-EU3WWTFH.js";
4
4
  import {
5
+ getMaxVisibleItems
6
+ } from "./chunk-AFJLY43Z.js";
7
+ import {
8
+ combineEventsWithSnapshot,
5
9
  getEventStore,
6
- getRuntimeConfig,
7
- updateSessionMetadata
8
- } from "./chunk-NGJ5XJ7D.js";
10
+ getRuntimeConfig
11
+ } from "./chunk-M7EFXRDS.js";
9
12
  import {
10
13
  buildMessagesFromStoredEvents,
11
14
  foldPendingConfirmations
12
- } from "./chunk-DJASQ2ZB.js";
15
+ } from "./chunk-TSVFRWSG.js";
16
+ import {
17
+ updateSessionMetadata
18
+ } from "./chunk-AANVMTYU.js";
13
19
  import {
14
20
  createContextStateMessage,
15
21
  createSessionStateMessage
16
- } from "./chunk-O3NUNU4H.js";
22
+ } from "./chunk-SJTG56CH.js";
17
23
  import {
18
24
  logger
19
25
  } from "./chunk-K44MW7JJ.js";
@@ -89,13 +95,23 @@ function applyGeneratedSessionName(sessionId, name, deps) {
89
95
  });
90
96
  const updatedSession = deps.sessionManager.getSession(sessionId);
91
97
  if (updatedSession) {
92
- const events = deps.eventStore.getEvents(sessionId);
93
- const messages = buildMessagesFromStoredEvents(events);
98
+ const { snapshot, events: eventsSinceSnapshot } = deps.eventStore.getEventsSinceSnapshot(sessionId);
99
+ const events = combineEventsWithSnapshot(sessionId, snapshot, eventsSinceSnapshot);
100
+ const maxVisibleItems = getMaxVisibleItems();
101
+ const { messages, hiddenCount } = buildMessagesFromStoredEvents(events, maxVisibleItems || void 0);
94
102
  const pendingConfirmations = foldPendingConfirmations(events);
95
103
  const pendingQuestions = getPendingQuestionsForSession(sessionId);
96
104
  deps.broadcastForSession(
97
105
  sessionId,
98
- createSessionStateMessage(updatedSession, messages, pendingConfirmations, pendingQuestions)
106
+ createSessionStateMessage(
107
+ updatedSession,
108
+ messages,
109
+ pendingConfirmations,
110
+ pendingQuestions,
111
+ void 0,
112
+ void 0,
113
+ hiddenCount
114
+ )
99
115
  );
100
116
  }
101
117
  }
@@ -184,4 +200,4 @@ export {
184
200
  buildRunChatTurnParams,
185
201
  generateSessionNameForSession
186
202
  };
187
- //# sourceMappingURL=chunk-3TQN25OM.js.map
203
+ //# sourceMappingURL=chunk-UIU7OGHP.js.map