@greppleai/grip 0.0.1 → 0.1.4

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 (221) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/README.md +13 -7
  3. package/dist/bun/cli.d.ts.map +1 -1
  4. package/dist/bun/cli.js +4 -2
  5. package/dist/bun/cli.js.map +1 -1
  6. package/dist/bun/embedded-assets.d.ts +3 -0
  7. package/dist/bun/embedded-assets.d.ts.map +1 -0
  8. package/dist/bun/embedded-assets.js +52 -0
  9. package/dist/bun/embedded-assets.js.map +1 -0
  10. package/dist/cli/args.d.ts.map +1 -1
  11. package/dist/cli/args.js +1 -0
  12. package/dist/cli/args.js.map +1 -1
  13. package/dist/config.d.ts.map +1 -1
  14. package/dist/config.js +3 -3
  15. package/dist/config.js.map +1 -1
  16. package/dist/core/agent-session.d.ts.map +1 -1
  17. package/dist/core/agent-session.js +17 -2
  18. package/dist/core/agent-session.js.map +1 -1
  19. package/dist/core/extensions/types.d.ts +5 -1
  20. package/dist/core/extensions/types.d.ts.map +1 -1
  21. package/dist/core/extensions/types.js.map +1 -1
  22. package/dist/core/remote-workspace-protocol/contract.d.ts +13 -4
  23. package/dist/core/remote-workspace-protocol/contract.d.ts.map +1 -1
  24. package/dist/core/remote-workspace-protocol/contract.js +17 -1
  25. package/dist/core/remote-workspace-protocol/contract.js.map +1 -1
  26. package/dist/core/remote-workspace-protocol/guest-shared-client.d.ts +16 -0
  27. package/dist/core/remote-workspace-protocol/guest-shared-client.d.ts.map +1 -0
  28. package/dist/core/remote-workspace-protocol/guest-shared-client.js +54 -0
  29. package/dist/core/remote-workspace-protocol/guest-shared-client.js.map +1 -0
  30. package/dist/core/remote-workspace-protocol/guest-shared-outbound-scheduler.d.ts +23 -0
  31. package/dist/core/remote-workspace-protocol/guest-shared-outbound-scheduler.d.ts.map +1 -0
  32. package/dist/core/remote-workspace-protocol/guest-shared-outbound-scheduler.js +112 -0
  33. package/dist/core/remote-workspace-protocol/guest-shared-outbound-scheduler.js.map +1 -0
  34. package/dist/core/remote-workspace-protocol/guest-shared-uplink.d.ts +30 -0
  35. package/dist/core/remote-workspace-protocol/guest-shared-uplink.d.ts.map +1 -0
  36. package/dist/core/remote-workspace-protocol/guest-shared-uplink.js +243 -0
  37. package/dist/core/remote-workspace-protocol/guest-shared-uplink.js.map +1 -0
  38. package/dist/core/remote-workspace-protocol/lima-owner-directory.d.ts +3 -0
  39. package/dist/core/remote-workspace-protocol/lima-owner-directory.d.ts.map +1 -0
  40. package/dist/core/remote-workspace-protocol/lima-owner-directory.js +31 -0
  41. package/dist/core/remote-workspace-protocol/lima-owner-directory.js.map +1 -0
  42. package/dist/core/remote-workspace-protocol/lima-owner-lock.d.ts +9 -0
  43. package/dist/core/remote-workspace-protocol/lima-owner-lock.d.ts.map +1 -0
  44. package/dist/core/remote-workspace-protocol/lima-owner-lock.js +89 -0
  45. package/dist/core/remote-workspace-protocol/lima-owner-lock.js.map +1 -0
  46. package/dist/core/remote-workspace-protocol/session.d.ts +2 -0
  47. package/dist/core/remote-workspace-protocol/session.d.ts.map +1 -1
  48. package/dist/core/remote-workspace-protocol/session.js +3 -0
  49. package/dist/core/remote-workspace-protocol/session.js.map +1 -1
  50. package/dist/core/remote-workspace-protocol/shared-transport.d.ts +71 -0
  51. package/dist/core/remote-workspace-protocol/shared-transport.d.ts.map +1 -0
  52. package/dist/core/remote-workspace-protocol/shared-transport.js +68 -0
  53. package/dist/core/remote-workspace-protocol/shared-transport.js.map +1 -0
  54. package/dist/core/sandbox/command.d.ts +2 -4
  55. package/dist/core/sandbox/command.d.ts.map +1 -1
  56. package/dist/core/sandbox/command.js +24 -46
  57. package/dist/core/sandbox/command.js.map +1 -1
  58. package/dist/core/sandbox/config.d.ts +91 -0
  59. package/dist/core/sandbox/config.d.ts.map +1 -0
  60. package/dist/core/sandbox/config.js +88 -0
  61. package/dist/core/sandbox/config.js.map +1 -0
  62. package/dist/core/sandbox/lima-guest-binary.d.ts +11 -0
  63. package/dist/core/sandbox/lima-guest-binary.d.ts.map +1 -0
  64. package/dist/core/sandbox/lima-guest-binary.js +140 -0
  65. package/dist/core/sandbox/lima-guest-binary.js.map +1 -0
  66. package/dist/core/sandbox/lima.d.ts +22 -3
  67. package/dist/core/sandbox/lima.d.ts.map +1 -1
  68. package/dist/core/sandbox/lima.js +434 -42
  69. package/dist/core/sandbox/lima.js.map +1 -1
  70. package/dist/core/sandbox/shared-lima-roots.d.ts +11 -0
  71. package/dist/core/sandbox/shared-lima-roots.d.ts.map +1 -0
  72. package/dist/core/sandbox/shared-lima-roots.js +25 -0
  73. package/dist/core/sandbox/shared-lima-roots.js.map +1 -0
  74. package/dist/core/sandbox/update-shared-guest-binary.d.ts +8 -0
  75. package/dist/core/sandbox/update-shared-guest-binary.d.ts.map +1 -0
  76. package/dist/core/sandbox/update-shared-guest-binary.js +100 -0
  77. package/dist/core/sandbox/update-shared-guest-binary.js.map +1 -0
  78. package/dist/core/settings-manager.d.ts +1 -5
  79. package/dist/core/settings-manager.d.ts.map +1 -1
  80. package/dist/core/settings-manager.js.map +1 -1
  81. package/dist/core/tools/bash.d.ts.map +1 -1
  82. package/dist/core/tools/bash.js +25 -14
  83. package/dist/core/tools/bash.js.map +1 -1
  84. package/dist/core/tools/file-mutation-queue.d.ts +2 -0
  85. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  86. package/dist/core/tools/file-mutation-queue.js +9 -0
  87. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  88. package/dist/core/tools/find.d.ts.map +1 -1
  89. package/dist/core/tools/find.js +2 -3
  90. package/dist/core/tools/find.js.map +1 -1
  91. package/dist/core/tools/grep.d.ts.map +1 -1
  92. package/dist/core/tools/grep.js +2 -3
  93. package/dist/core/tools/grep.js.map +1 -1
  94. package/dist/core/tools/ls.d.ts.map +1 -1
  95. package/dist/core/tools/ls.js +2 -3
  96. package/dist/core/tools/ls.js.map +1 -1
  97. package/dist/core/tools/operations.d.ts +13 -2
  98. package/dist/core/tools/operations.d.ts.map +1 -1
  99. package/dist/core/tools/operations.js +132 -36
  100. package/dist/core/tools/operations.js.map +1 -1
  101. package/dist/core/tools/read.d.ts.map +1 -1
  102. package/dist/core/tools/read.js +3 -3
  103. package/dist/core/tools/read.js.map +1 -1
  104. package/dist/core/tools/render-utils.d.ts +1 -0
  105. package/dist/core/tools/render-utils.d.ts.map +1 -1
  106. package/dist/core/tools/render-utils.js +5 -0
  107. package/dist/core/tools/render-utils.js.map +1 -1
  108. package/dist/core/tools/write.d.ts.map +1 -1
  109. package/dist/core/tools/write.js +2 -3
  110. package/dist/core/tools/write.js.map +1 -1
  111. package/dist/daemon/activity-budget.d.ts +10 -0
  112. package/dist/daemon/activity-budget.d.ts.map +1 -0
  113. package/dist/daemon/activity-budget.js +34 -0
  114. package/dist/daemon/activity-budget.js.map +1 -0
  115. package/dist/daemon/config.d.ts +2 -0
  116. package/dist/daemon/config.d.ts.map +1 -1
  117. package/dist/daemon/config.js +15 -0
  118. package/dist/daemon/config.js.map +1 -1
  119. package/dist/daemon/outbound-budget.d.ts +10 -0
  120. package/dist/daemon/outbound-budget.d.ts.map +1 -0
  121. package/dist/daemon/outbound-budget.js +30 -0
  122. package/dist/daemon/outbound-budget.js.map +1 -0
  123. package/dist/daemon/security.d.ts.map +1 -1
  124. package/dist/daemon/security.js +3 -1
  125. package/dist/daemon/security.js.map +1 -1
  126. package/dist/daemon/server.d.ts.map +1 -1
  127. package/dist/daemon/server.js +104 -24
  128. package/dist/daemon/server.js.map +1 -1
  129. package/dist/daemon/shared-connection.d.ts +33 -0
  130. package/dist/daemon/shared-connection.d.ts.map +1 -0
  131. package/dist/daemon/shared-connection.js +271 -0
  132. package/dist/daemon/shared-connection.js.map +1 -0
  133. package/dist/daemon/shared-revision.d.ts +5 -0
  134. package/dist/daemon/shared-revision.d.ts.map +1 -0
  135. package/dist/daemon/shared-revision.js +61 -0
  136. package/dist/daemon/shared-revision.js.map +1 -0
  137. package/dist/daemon/shared-roots.d.ts +9 -0
  138. package/dist/daemon/shared-roots.d.ts.map +1 -0
  139. package/dist/daemon/shared-roots.js +33 -0
  140. package/dist/daemon/shared-roots.js.map +1 -0
  141. package/dist/daemon/workspace-runtime.d.ts +1 -1
  142. package/dist/daemon/workspace-runtime.d.ts.map +1 -1
  143. package/dist/daemon/workspace-runtime.js +42 -10
  144. package/dist/daemon/workspace-runtime.js.map +1 -1
  145. package/dist/main.d.ts.map +1 -1
  146. package/dist/main.js +5 -3
  147. package/dist/main.js.map +1 -1
  148. package/dist/modes/interactive/components/assistant-message.d.ts +1 -0
  149. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  150. package/dist/modes/interactive/components/assistant-message.js +13 -3
  151. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  152. package/dist/modes/interactive/components/branch-summary-message.d.ts +4 -3
  153. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  154. package/dist/modes/interactive/components/branch-summary-message.js +5 -4
  155. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  156. package/dist/modes/interactive/components/compaction-summary-message.d.ts +4 -3
  157. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  158. package/dist/modes/interactive/components/compaction-summary-message.js +5 -4
  159. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  160. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  161. package/dist/modes/interactive/components/custom-message.js +4 -3
  162. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  163. package/dist/modes/interactive/components/hook-execution.d.ts +3 -0
  164. package/dist/modes/interactive/components/hook-execution.d.ts.map +1 -1
  165. package/dist/modes/interactive/components/hook-execution.js +20 -2
  166. package/dist/modes/interactive/components/hook-execution.js.map +1 -1
  167. package/dist/modes/interactive/components/message-card.d.ts +9 -0
  168. package/dist/modes/interactive/components/message-card.d.ts.map +1 -0
  169. package/dist/modes/interactive/components/message-card.js +24 -0
  170. package/dist/modes/interactive/components/message-card.js.map +1 -0
  171. package/dist/modes/interactive/components/skill-invocation-message.d.ts +4 -3
  172. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  173. package/dist/modes/interactive/components/skill-invocation-message.js +5 -4
  174. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  175. package/dist/modes/interactive/components/tool-execution.d.ts +1 -1
  176. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  177. package/dist/modes/interactive/components/tool-execution.js +27 -21
  178. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  179. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  180. package/dist/modes/interactive/components/user-message.js +3 -2
  181. package/dist/modes/interactive/components/user-message.js.map +1 -1
  182. package/dist/modes/interactive/interactive-mode.d.ts +1 -3
  183. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  184. package/dist/modes/interactive/interactive-mode.js +106 -47
  185. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  186. package/dist/modes/interactive/theme/theme.d.ts +3 -0
  187. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  188. package/dist/modes/interactive/theme/theme.js +10 -0
  189. package/dist/modes/interactive/theme/theme.js.map +1 -1
  190. package/dist/utils/clipboard-native.d.ts.map +1 -1
  191. package/dist/utils/clipboard-native.js +1 -1
  192. package/dist/utils/clipboard-native.js.map +1 -1
  193. package/dist/utils/image-resize.d.ts.map +1 -1
  194. package/dist/utils/image-resize.js +5 -0
  195. package/dist/utils/image-resize.js.map +1 -1
  196. package/dist/utils/photon.d.ts.map +1 -1
  197. package/dist/utils/photon.js +1 -0
  198. package/dist/utils/photon.js.map +1 -1
  199. package/docs/daemon.md +6 -4
  200. package/docs/docs.json +1 -1
  201. package/docs/extensions.md +3 -0
  202. package/docs/index.md +8 -12
  203. package/docs/packages.md +3 -3
  204. package/docs/quickstart.md +11 -19
  205. package/docs/sandbox.md +40 -117
  206. package/docs/settings.md +12 -11
  207. package/docs/termux.md +1 -18
  208. package/docs/usage.md +2 -2
  209. package/examples/README.md +0 -2
  210. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  211. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  212. package/examples/extensions/sandbox/package.json +1 -1
  213. package/examples/extensions/with-deps/package.json +1 -1
  214. package/npm-shrinkwrap.json +12 -12
  215. package/package.json +5 -6
  216. package/dist/core/sandbox/docker.d.ts +0 -169
  217. package/dist/core/sandbox/docker.d.ts.map +0 -1
  218. package/dist/core/sandbox/docker.js +0 -633
  219. package/dist/core/sandbox/docker.js.map +0 -1
  220. package/examples/sandbox-docker/Dockerfile +0 -274
  221. package/examples/sandbox-docker/README.md +0 -88
@@ -21,7 +21,7 @@ import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScop
21
21
  import { DefaultPackageManager } from "../../core/package-manager.js";
22
22
  import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
23
23
  import { formatSandboxList, formatSandboxStartResult, formatSandboxStopResult, parseSandboxUserCommand, } from "../../core/sandbox/command.js";
24
- import { DockerSandboxService, redactSecrets, } from "../../core/sandbox/docker.js";
24
+ import { redactSecrets, } from "../../core/sandbox/config.js";
25
25
  import { LimaSandboxService } from "../../core/sandbox/lima.js";
26
26
  import { LocalSessionManager } from "../../core/session/local-session-manager.js";
27
27
  import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
@@ -253,10 +253,8 @@ export class InteractiveMode {
253
253
  // Tool output expansion state
254
254
  toolOutputExpanded = false;
255
255
  activeSandboxContainerId;
256
- activeSandboxRuntime;
257
256
  activeSandboxBackendConnected = false;
258
257
  sessionSandboxStates = new Map();
259
- managedSandboxContainers = new Map();
260
258
  managedLimaInstances = new Map();
261
259
  // Thinking block visibility state
262
260
  hideThinkingBlock = false;
@@ -273,6 +271,7 @@ export class InteractiveMode {
273
271
  hookExecutionSession;
274
272
  hookExecutionNotices = [];
275
273
  hookExecutionComponents = [];
274
+ pendingStopHookNotifications = [];
276
275
  activeToolHookExecutionGroups = new Map();
277
276
  hookExecutionTurnActive = false;
278
277
  signalCleanupHandlers = [];
@@ -522,7 +521,41 @@ export class InteractiveMode {
522
521
  });
523
522
  }
524
523
  }
525
- return new CombinedAutocompleteProvider([...slashCommands, ...templateCommands, ...extensionCommands, ...skillCommandList, ...ruleCommandList], this.activeSession.getCwd(), this.fdPath);
524
+ const base = new CombinedAutocompleteProvider([...slashCommands, ...templateCommands, ...extensionCommands, ...skillCommandList, ...ruleCommandList], this.activeSession.getCwd(), this.fdPath);
525
+ const provider = {
526
+ getSuggestions: async (lines, cursorLine, cursorCol, options) => {
527
+ const beforeCursor = (lines[cursorLine] ?? "").slice(0, cursorCol);
528
+ const nameMatch = beforeCursor.match(/^\/sandbox[ \t]+(start[ \t]+--name(?:[ \t]+([^\s]*))?)$/);
529
+ if (!options.force || !nameMatch)
530
+ return base.getSuggestions(lines, cursorLine, cursorCol, options);
531
+ if (options.signal.aborted)
532
+ return null;
533
+ try {
534
+ const instances = await this.createLimaSandboxService().list({
535
+ workspaceRoot: this.activeSession.getCwd(),
536
+ allWorkspaces: true,
537
+ });
538
+ if (options.signal.aborted)
539
+ return null;
540
+ const prefix = nameMatch[2] ?? "";
541
+ const items = instances
542
+ .filter((instance) => /^[a-z0-9][a-z0-9-]*$/.test(instance.name) && instance.name.startsWith(prefix))
543
+ .map((instance) => ({
544
+ value: `start --name ${instance.name}`,
545
+ label: instance.name,
546
+ description: instance.status,
547
+ }));
548
+ return items.length > 0 ? { items, prefix: nameMatch[1] } : null;
549
+ }
550
+ catch {
551
+ // A failed Lima lookup must not fall back to directory suggestions.
552
+ return null;
553
+ }
554
+ },
555
+ applyCompletion: (lines, cursorLine, cursorCol, item, prefix) => base.applyCompletion(lines, cursorLine, cursorCol, item, prefix),
556
+ shouldTriggerFileCompletion: (lines, cursorLine, cursorCol) => base.shouldTriggerFileCompletion(lines, cursorLine, cursorCol),
557
+ };
558
+ return provider;
526
559
  }
527
560
  setupAutocompleteProvider() {
528
561
  let provider = this.createBaseAutocompleteProvider();
@@ -1921,7 +1954,7 @@ export class InteractiveMode {
1921
1954
  select: (title, options, opts) => this.showExtensionSelector(title, options, opts),
1922
1955
  confirm: (title, message, opts) => this.showExtensionConfirm(title, message, opts),
1923
1956
  input: (title, placeholder, opts) => this.showExtensionInput(title, placeholder, opts),
1924
- notify: (message, type) => this.showExtensionNotify(message, type),
1957
+ notify: (message, type, options) => this.showExtensionNotify(message, type, options),
1925
1958
  onTerminalInput: (handler) => this.addExtensionTerminalInputListener(handler),
1926
1959
  setStatus: (key, text) => this.setExtensionStatus(key, text),
1927
1960
  setWorkingMessage: (message) => {
@@ -2151,7 +2184,11 @@ export class InteractiveMode {
2151
2184
  /**
2152
2185
  * Show a notification for extensions.
2153
2186
  */
2154
- showExtensionNotify(message, type) {
2187
+ showExtensionNotify(message, type, options) {
2188
+ if (options?.hookEvent === "Stop") {
2189
+ this.pendingStopHookNotifications.push({ message, type });
2190
+ return;
2191
+ }
2155
2192
  if (type === "error") {
2156
2193
  this.showError(message);
2157
2194
  }
@@ -2640,6 +2677,8 @@ export class InteractiveMode {
2640
2677
  const submittedText = text.trim();
2641
2678
  if (!submittedText)
2642
2679
  return;
2680
+ // Keep attempted commands as well as accepted prompts available through editor history.
2681
+ this.editor.addToHistory?.(submittedText);
2643
2682
  if (await this.handleBuiltInEditorCommand(submittedText))
2644
2683
  return;
2645
2684
  if (await this.handleBashEditorSubmission(submittedText))
@@ -2684,6 +2723,11 @@ export class InteractiveMode {
2684
2723
  const group = [notice];
2685
2724
  this.hookExecutionNotices.push(group);
2686
2725
  const component = new HookExecutionComponent(notice, this.getMarkdownThemeWithSettings());
2726
+ if (notice.event === "Stop") {
2727
+ for (const notification of this.pendingStopHookNotifications)
2728
+ component.appendAnnotation(notification.message);
2729
+ this.pendingStopHookNotifications = [];
2730
+ }
2687
2731
  component.setExpanded(this.toolOutputExpanded);
2688
2732
  this.hookExecutionComponents.push(component);
2689
2733
  this.chatContainer.addChild(component);
@@ -3069,6 +3113,10 @@ export class InteractiveMode {
3069
3113
  this.handleAgentEnd();
3070
3114
  break;
3071
3115
  case "agent_settled":
3116
+ for (const notification of this.pendingStopHookNotifications) {
3117
+ this.showExtensionNotify(notification.message, notification.type);
3118
+ }
3119
+ this.pendingStopHookNotifications = [];
3072
3120
  await this.checkShutdownRequested();
3073
3121
  this.ui.requestRender();
3074
3122
  break;
@@ -4952,6 +5000,7 @@ export class InteractiveMode {
4952
5000
  this.editorContainer.clear();
4953
5001
  this.editorContainer.addChild(editor);
4954
5002
  this.ui.setFocus(editor);
5003
+ this.updateToolBackendStatus();
4955
5004
  this.ui.requestRender();
4956
5005
  };
4957
5006
  try {
@@ -5230,12 +5279,6 @@ export class InteractiveMode {
5230
5279
  this.setupAutocompleteProvider();
5231
5280
  this.showLoadedResources({ force: true, showDiagnosticsWhenQuiet: true });
5232
5281
  }
5233
- createDockerSandboxService() {
5234
- return new DockerSandboxService({
5235
- settingsManager: this.settingsManager,
5236
- managedContainers: this.managedSandboxContainers,
5237
- });
5238
- }
5239
5282
  createLimaSandboxService() {
5240
5283
  return new LimaSandboxService({
5241
5284
  settingsManager: this.settingsManager,
@@ -5243,7 +5286,6 @@ export class InteractiveMode {
5243
5286
  });
5244
5287
  }
5245
5288
  async disposeRuntimeResources() {
5246
- const dockerSandboxService = this.createDockerSandboxService();
5247
5289
  const limaSandboxService = this.createLimaSandboxService();
5248
5290
  const errors = [];
5249
5291
  try {
@@ -5253,7 +5295,7 @@ export class InteractiveMode {
5253
5295
  errors.push(error instanceof Error ? error : new Error(String(error)));
5254
5296
  }
5255
5297
  try {
5256
- await Promise.all([dockerSandboxService.stopManagedContainers(), limaSandboxService.stopManagedInstances()]);
5298
+ await limaSandboxService.stopManagedInstances();
5257
5299
  }
5258
5300
  catch (error) {
5259
5301
  errors.push(error instanceof Error ? error : new Error(String(error)));
@@ -5262,13 +5304,11 @@ export class InteractiveMode {
5262
5304
  }
5263
5305
  async restoreCurrentSessionSandbox() {
5264
5306
  this.activeSandboxContainerId = undefined;
5265
- this.activeSandboxRuntime = undefined;
5266
5307
  this.activeSandboxBackendConnected = false;
5267
5308
  const state = this.sessionSandboxStates.get(getSessionSandboxKey(this.activeSession));
5268
5309
  if (!state)
5269
5310
  return;
5270
5311
  this.activeSandboxContainerId = state.containerId;
5271
- this.activeSandboxRuntime = state.runtime ?? "docker";
5272
5312
  try {
5273
5313
  await this.session.activateSandboxDaemon({
5274
5314
  url: state.url,
@@ -5286,7 +5326,6 @@ export class InteractiveMode {
5286
5326
  hasSandboxActivationConflict(sessionKey) {
5287
5327
  return (this.activeSandboxBackendConnected ||
5288
5328
  this.activeSandboxContainerId !== undefined ||
5289
- [...this.managedSandboxContainers.values()].some((container) => container.ownerId === this.activeSession.getSessionId()) ||
5290
5329
  [...(this.managedLimaInstances?.values() ?? [])].some((instance) => instance.ownerId === this.activeSession.getSessionId()) ||
5291
5330
  this.sessionSandboxStates.has(sessionKey));
5292
5331
  }
@@ -5300,13 +5339,12 @@ export class InteractiveMode {
5300
5339
  if (!this.activeSandboxBackendConnected && this.sessionSandboxStates.has(sessionKey)) {
5301
5340
  this.sessionSandboxStates.delete(sessionKey);
5302
5341
  this.activeSandboxContainerId = undefined;
5303
- this.activeSandboxRuntime = undefined;
5304
5342
  this.showStatus("Stale sandbox state cleared");
5305
5343
  return;
5306
5344
  }
5307
5345
  await this.session.clearRemoteSandbox();
5308
5346
  this.sessionSandboxStates.delete(sessionKey);
5309
- this.activeSandboxRuntime = undefined;
5347
+ this.activeSandboxContainerId = undefined;
5310
5348
  this.activeSandboxBackendConnected = false;
5311
5349
  this.refreshUiAfterBackendChange();
5312
5350
  this.updateToolBackendStatus();
@@ -5320,7 +5358,6 @@ export class InteractiveMode {
5320
5358
  const token = process.env.GRIP_REMOTE_TOKEN ?? "";
5321
5359
  const info = await this.session.activateSandboxDaemon({ url, token, expectedCwd });
5322
5360
  this.activeSandboxContainerId = undefined;
5323
- this.activeSandboxRuntime = undefined;
5324
5361
  this.activeSandboxBackendConnected = true;
5325
5362
  this.sessionSandboxStates.set(sessionKey, { type: "daemon", url, token, expectedCwd });
5326
5363
  this.refreshUiAfterBackendChange();
@@ -5368,10 +5405,15 @@ export class InteractiveMode {
5368
5405
  if (info)
5369
5406
  return info;
5370
5407
  const activationError = lastActivationError instanceof Error ? lastActivationError : new Error(String(lastActivationError));
5408
+ if (result.daemonUrl.startsWith("grip+ws:")) {
5409
+ // A shared-VM attach owns only its channel, never the VM or guest daemon.
5410
+ this.activeSandboxContainerId = undefined;
5411
+ this.activeSandboxBackendConnected = false;
5412
+ throw activationError;
5413
+ }
5371
5414
  try {
5372
5415
  await service.stop({ workspaceRoot, currentContainerId: result.containerId });
5373
5416
  this.activeSandboxContainerId = undefined;
5374
- this.activeSandboxRuntime = undefined;
5375
5417
  this.activeSandboxBackendConnected = false;
5376
5418
  }
5377
5419
  catch (cleanupError) {
@@ -5382,18 +5424,24 @@ export class InteractiveMode {
5382
5424
  }
5383
5425
  throw activationError;
5384
5426
  }
5385
- async startSandboxBackend(command, service, workspaceRoot, sessionKey, runtime) {
5427
+ async startSandboxBackend(command, service, workspaceRoot, sessionKey, updatingAttached = false) {
5386
5428
  const loader = this.showSandboxStartupLoader();
5387
5429
  try {
5388
5430
  const result = await service.start({
5389
5431
  workspaceRoot,
5390
- ...(command.image ? { image: command.image } : {}),
5391
5432
  ...(command.name ? { name: command.name } : {}),
5392
5433
  ...(command.template ? { limaTemplate: command.template } : {}),
5434
+ ...(command.update ? { update: true } : {}),
5393
5435
  sessionId: this.activeSession.getSessionId(),
5394
5436
  });
5437
+ if (updatingAttached) {
5438
+ await this.session.abort();
5439
+ await this.session.clearRemoteSandbox();
5440
+ this.sessionSandboxStates.delete(sessionKey);
5441
+ this.activeSandboxContainerId = undefined;
5442
+ this.activeSandboxBackendConnected = false;
5443
+ }
5395
5444
  this.activeSandboxContainerId = result.containerId;
5396
- this.activeSandboxRuntime = runtime;
5397
5445
  const info = await this.activateStartedSandbox(service, workspaceRoot, result);
5398
5446
  this.activeSandboxBackendConnected = true;
5399
5447
  this.sessionSandboxStates.set(sessionKey, {
@@ -5402,11 +5450,15 @@ export class InteractiveMode {
5402
5450
  token: result.token,
5403
5451
  expectedCwd: result.workspaceMountPath,
5404
5452
  containerId: result.containerId,
5405
- runtime,
5406
5453
  });
5407
5454
  this.refreshUiAfterBackendChange();
5408
5455
  this.updateToolBackendStatus();
5409
- this.showStatus([formatSandboxStartResult(result), this.formatToolBackendStatus(info)].join("\n"));
5456
+ this.showStatus([
5457
+ command.update
5458
+ ? formatSandboxStartResult(result).replace("Sandbox started:", "Sandbox updated:")
5459
+ : formatSandboxStartResult(result),
5460
+ this.formatToolBackendStatus(info),
5461
+ ].join("\n"));
5410
5462
  }
5411
5463
  finally {
5412
5464
  this.hideSandboxStartupLoader(loader);
@@ -5435,7 +5487,6 @@ export class InteractiveMode {
5435
5487
  }
5436
5488
  this.sessionSandboxStates.delete(sessionKey);
5437
5489
  this.activeSandboxContainerId = undefined;
5438
- this.activeSandboxRuntime = undefined;
5439
5490
  this.activeSandboxBackendConnected = false;
5440
5491
  return;
5441
5492
  }
@@ -5449,7 +5500,6 @@ export class InteractiveMode {
5449
5500
  }
5450
5501
  if (stoppedActiveSandbox) {
5451
5502
  this.activeSandboxContainerId = undefined;
5452
- this.activeSandboxRuntime = undefined;
5453
5503
  this.activeSandboxBackendConnected = false;
5454
5504
  }
5455
5505
  }
@@ -5463,6 +5513,20 @@ export class InteractiveMode {
5463
5513
  this.showStatus("Sandbox detached");
5464
5514
  return;
5465
5515
  }
5516
+ const attached = this.sessionSandboxStates.get(sessionKey);
5517
+ if (attached?.url?.startsWith("grip+ws:") &&
5518
+ attached.containerId === this.activeSandboxContainerId &&
5519
+ (!target || target === attached.containerId)) {
5520
+ if (this.activeSandboxBackendConnected)
5521
+ await this.session.clearRemoteSandbox();
5522
+ this.sessionSandboxStates.delete(sessionKey);
5523
+ this.activeSandboxContainerId = undefined;
5524
+ this.activeSandboxBackendConnected = false;
5525
+ this.refreshUiAfterBackendChange();
5526
+ this.updateToolBackendStatus();
5527
+ this.showStatus("Shared sandbox detached; VM and guest daemon remain running");
5528
+ return;
5529
+ }
5466
5530
  const result = await service.stop({
5467
5531
  workspaceRoot,
5468
5532
  target,
@@ -5471,7 +5535,7 @@ export class InteractiveMode {
5471
5535
  await this.reconcileStoppedSandbox(result, sessionKey);
5472
5536
  this.showStatus(formatSandboxStopResult(result));
5473
5537
  }
5474
- async executeSandboxCommand(command, dockerService, workspaceRoot, sessionKey) {
5538
+ async executeSandboxCommand(command, service, workspaceRoot, sessionKey) {
5475
5539
  switch (command.subcommand) {
5476
5540
  case "status":
5477
5541
  this.showStatus(this.formatToolBackendStatus(this.session.getToolBackendInfo()));
@@ -5482,29 +5546,24 @@ export class InteractiveMode {
5482
5546
  case "attach":
5483
5547
  if (!this.ensureSandboxCanActivate(sessionKey))
5484
5548
  return;
5485
- await this.attachSandboxBackend(command.url, dockerService, sessionKey);
5549
+ await this.attachSandboxBackend(command.url, service, sessionKey);
5486
5550
  return;
5487
- case "start":
5488
- if (!this.ensureSandboxCanActivate(sessionKey))
5551
+ case "start": {
5552
+ const attached = this.sessionSandboxStates.get(sessionKey);
5553
+ const updatingAttached = command.update === true &&
5554
+ attached?.url.startsWith("grip+ws:") === true &&
5555
+ attached.containerId === command.name &&
5556
+ this.activeSandboxBackendConnected;
5557
+ if (!updatingAttached && !this.ensureSandboxCanActivate(sessionKey))
5489
5558
  return;
5490
- {
5491
- const runtime = command.runtime ?? dockerService.resolveConfig().runtime;
5492
- const service = runtime === "lima" ? this.createLimaSandboxService() : dockerService;
5493
- await this.startSandboxBackend(command, service, workspaceRoot, sessionKey, runtime);
5494
- }
5559
+ await this.startSandboxBackend(command, service, workspaceRoot, sessionKey, updatingAttached);
5495
5560
  return;
5561
+ }
5496
5562
  case "list":
5497
- {
5498
- const runtime = command.runtime ?? dockerService.resolveConfig().runtime;
5499
- const service = runtime === "lima" ? this.createLimaSandboxService() : dockerService;
5500
- this.showStatus(formatSandboxList(await service.list({ workspaceRoot })));
5501
- }
5563
+ this.showStatus(formatSandboxList(await service.list({ workspaceRoot })));
5502
5564
  return;
5503
- case "stop": {
5504
- const runtime = this.activeSandboxRuntime ?? dockerService.resolveConfig().runtime;
5505
- const service = runtime === "lima" ? this.createLimaSandboxService() : dockerService;
5565
+ case "stop":
5506
5566
  await this.stopSandboxBackend(command.target, service, workspaceRoot, sessionKey);
5507
- }
5508
5567
  }
5509
5568
  }
5510
5569
  async handleSandboxCommand(text) {
@@ -5516,7 +5575,7 @@ export class InteractiveMode {
5516
5575
  this.showWarning(error instanceof Error ? error.message : String(error));
5517
5576
  return;
5518
5577
  }
5519
- const service = this.createDockerSandboxService();
5578
+ const service = this.createLimaSandboxService();
5520
5579
  const workspaceRoot = this.activeSession.getCwd();
5521
5580
  const sessionKey = getSessionSandboxKey(this.activeSession);
5522
5581
  try {