@oh-my-pi/pi-coding-agent 15.7.1 → 15.7.3

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 (177) hide show
  1. package/CHANGELOG.md +95 -6
  2. package/dist/types/auto-thinking/classifier.d.ts +35 -0
  3. package/dist/types/cli/args.d.ts +1 -1
  4. package/dist/types/cli/extension-flags.d.ts +36 -0
  5. package/dist/types/config/config-file.d.ts +4 -0
  6. package/dist/types/config/file-lock.d.ts +23 -0
  7. package/dist/types/config/keybindings.d.ts +2 -1
  8. package/dist/types/config/model-registry.d.ts +6 -0
  9. package/dist/types/config/settings-schema.d.ts +112 -69
  10. package/dist/types/edit/hashline/diff.d.ts +9 -3
  11. package/dist/types/eval/__tests__/agent-bridge.test.d.ts +1 -0
  12. package/dist/types/eval/__tests__/budget-bridge.test.d.ts +1 -0
  13. package/dist/types/eval/__tests__/idle-timeout.test.d.ts +1 -0
  14. package/dist/types/eval/agent-bridge.d.ts +25 -0
  15. package/dist/types/eval/backend.d.ts +17 -2
  16. package/dist/types/eval/budget-bridge.d.ts +29 -0
  17. package/dist/types/eval/idle-timeout.d.ts +28 -0
  18. package/dist/types/eval/js/executor.d.ts +8 -0
  19. package/dist/types/eval/js/tool-bridge.d.ts +2 -1
  20. package/dist/types/eval/py/executor.d.ts +13 -0
  21. package/dist/types/exec/bash-executor.d.ts +1 -0
  22. package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
  23. package/dist/types/extensibility/extensions/runner.d.ts +7 -0
  24. package/dist/types/extensibility/plugins/git-url.d.ts +11 -1
  25. package/dist/types/extensibility/plugins/manager.d.ts +12 -1
  26. package/dist/types/extensibility/shared-events.d.ts +2 -2
  27. package/dist/types/memory-backend/index.d.ts +1 -1
  28. package/dist/types/memory-backend/resolve.d.ts +1 -1
  29. package/dist/types/memory-backend/types.d.ts +3 -3
  30. package/dist/types/mnemopi/backend.d.ts +4 -0
  31. package/dist/types/mnemopi/config.d.ts +29 -0
  32. package/dist/types/mnemopi/state.d.ts +72 -0
  33. package/dist/types/modes/components/custom-editor.d.ts +2 -2
  34. package/dist/types/modes/components/model-selector.d.ts +3 -2
  35. package/dist/types/modes/components/omfg-panel.d.ts +19 -0
  36. package/dist/types/modes/controllers/command-controller.d.ts +7 -0
  37. package/dist/types/modes/controllers/input-controller.d.ts +1 -3
  38. package/dist/types/modes/controllers/omfg-controller.d.ts +10 -0
  39. package/dist/types/modes/controllers/omfg-rule.d.ts +26 -0
  40. package/dist/types/modes/gradient-highlight.d.ts +5 -1
  41. package/dist/types/modes/interactive-mode.d.ts +7 -3
  42. package/dist/types/modes/magic-keywords.d.ts +14 -0
  43. package/dist/types/modes/markdown-prose.d.ts +27 -0
  44. package/dist/types/modes/orchestrate.d.ts +7 -2
  45. package/dist/types/modes/shared.d.ts +1 -1
  46. package/dist/types/modes/theme/theme.d.ts +2 -1
  47. package/dist/types/modes/turn-budget.d.ts +18 -0
  48. package/dist/types/modes/types.d.ts +7 -3
  49. package/dist/types/modes/ultrathink.d.ts +7 -2
  50. package/dist/types/modes/workflow.d.ts +15 -0
  51. package/dist/types/sdk.d.ts +15 -4
  52. package/dist/types/session/agent-session.d.ts +59 -23
  53. package/dist/types/session/session-manager.d.ts +18 -0
  54. package/dist/types/session/session-storage.d.ts +6 -0
  55. package/dist/types/session/shake-types.d.ts +24 -0
  56. package/dist/types/task/executor.d.ts +2 -2
  57. package/dist/types/thinking.d.ts +39 -1
  58. package/dist/types/tiny/device.d.ts +3 -3
  59. package/dist/types/tiny/models.d.ts +34 -1
  60. package/dist/types/tiny/title-protocol.d.ts +4 -0
  61. package/dist/types/tools/index.d.ts +19 -3
  62. package/dist/types/tools/memory-edit.d.ts +1 -1
  63. package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
  64. package/package.json +10 -10
  65. package/src/auto-thinking/classifier.ts +180 -0
  66. package/src/autoresearch/tools/run-experiment.ts +45 -113
  67. package/src/cli/args.ts +39 -16
  68. package/src/cli/extension-flags.ts +48 -0
  69. package/src/cli/plugin-cli.ts +11 -2
  70. package/src/config/config-file.ts +98 -13
  71. package/src/config/file-lock.ts +60 -17
  72. package/src/config/keybindings.ts +78 -27
  73. package/src/config/model-registry.ts +7 -1
  74. package/src/config/settings-schema.ts +118 -71
  75. package/src/config/settings.ts +12 -0
  76. package/src/edit/hashline/diff.ts +87 -22
  77. package/src/edit/renderer.ts +16 -12
  78. package/src/edit/streaming.ts +17 -6
  79. package/src/eval/__tests__/agent-bridge.test.ts +433 -0
  80. package/src/eval/__tests__/budget-bridge.test.ts +69 -0
  81. package/src/eval/__tests__/idle-timeout.test.ts +66 -0
  82. package/src/eval/__tests__/shared-executors.test.ts +53 -0
  83. package/src/eval/agent-bridge.ts +295 -0
  84. package/src/eval/backend.ts +17 -2
  85. package/src/eval/budget-bridge.ts +48 -0
  86. package/src/eval/idle-timeout.ts +80 -0
  87. package/src/eval/js/executor.ts +35 -7
  88. package/src/eval/js/index.ts +2 -1
  89. package/src/eval/js/shared/local-module-loader.ts +75 -10
  90. package/src/eval/js/shared/prelude.txt +85 -1
  91. package/src/eval/js/tool-bridge.ts +9 -0
  92. package/src/eval/py/executor.ts +41 -14
  93. package/src/eval/py/index.ts +2 -1
  94. package/src/eval/py/prelude.py +132 -1
  95. package/src/exec/bash-executor.ts +2 -3
  96. package/src/extensibility/custom-tools/types.ts +2 -2
  97. package/src/extensibility/extensions/runner.ts +12 -2
  98. package/src/extensibility/plugins/git-url.ts +90 -4
  99. package/src/extensibility/plugins/manager.ts +103 -7
  100. package/src/extensibility/shared-events.ts +2 -2
  101. package/src/internal-urls/docs-index.generated.ts +88 -88
  102. package/src/main.ts +50 -56
  103. package/src/memory-backend/index.ts +1 -1
  104. package/src/memory-backend/resolve.ts +3 -3
  105. package/src/memory-backend/types.ts +3 -3
  106. package/src/{mnemosyne → mnemopi}/backend.ts +70 -70
  107. package/src/{mnemosyne → mnemopi}/config.ts +36 -36
  108. package/src/{mnemosyne → mnemopi}/state.ts +67 -67
  109. package/src/modes/acp/acp-agent.ts +13 -3
  110. package/src/modes/components/agent-dashboard.ts +6 -6
  111. package/src/modes/components/custom-editor.ts +4 -11
  112. package/src/modes/components/extensions/state-manager.ts +3 -4
  113. package/src/modes/components/footer.ts +18 -12
  114. package/src/modes/components/hook-selector.ts +86 -20
  115. package/src/modes/components/model-selector.ts +20 -11
  116. package/src/modes/components/oauth-selector.ts +93 -21
  117. package/src/modes/components/omfg-panel.ts +141 -0
  118. package/src/modes/components/settings-defs.ts +9 -2
  119. package/src/modes/components/settings-selector.ts +4 -1
  120. package/src/modes/components/status-line/segments.ts +13 -5
  121. package/src/modes/components/tips.txt +2 -1
  122. package/src/modes/components/tool-execution.ts +38 -19
  123. package/src/modes/components/tree-selector.ts +4 -3
  124. package/src/modes/components/user-message-selector.ts +94 -19
  125. package/src/modes/components/user-message.ts +8 -1
  126. package/src/modes/controllers/command-controller.ts +57 -0
  127. package/src/modes/controllers/event-controller.ts +65 -3
  128. package/src/modes/controllers/input-controller.ts +14 -11
  129. package/src/modes/controllers/omfg-controller.ts +283 -0
  130. package/src/modes/controllers/omfg-rule.ts +647 -0
  131. package/src/modes/controllers/selector-controller.ts +21 -6
  132. package/src/modes/gradient-highlight.ts +23 -6
  133. package/src/modes/interactive-mode.ts +41 -7
  134. package/src/modes/magic-keywords.ts +20 -0
  135. package/src/modes/markdown-prose.ts +247 -0
  136. package/src/modes/orchestrate.ts +17 -11
  137. package/src/modes/shared.ts +3 -11
  138. package/src/modes/theme/theme.ts +6 -0
  139. package/src/modes/turn-budget.ts +31 -0
  140. package/src/modes/types.ts +7 -1
  141. package/src/modes/ultrathink.ts +16 -10
  142. package/src/modes/utils/hotkeys-markdown.ts +1 -1
  143. package/src/modes/workflow.ts +42 -0
  144. package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
  145. package/src/prompts/system/auto-thinking-difficulty.md +12 -0
  146. package/src/prompts/system/omfg-user.md +51 -0
  147. package/src/prompts/system/system-prompt.md +1 -0
  148. package/src/prompts/system/workflow-notice.md +70 -0
  149. package/src/prompts/tools/eval.md +13 -1
  150. package/src/prompts/tools/memory-edit.md +1 -1
  151. package/src/sdk.ts +86 -38
  152. package/src/session/agent-session.ts +558 -80
  153. package/src/session/session-manager.ts +32 -0
  154. package/src/session/session-storage.ts +68 -8
  155. package/src/session/shake-types.ts +44 -0
  156. package/src/slash-commands/builtin-registry.ts +41 -16
  157. package/src/task/executor.ts +3 -3
  158. package/src/task/index.ts +6 -6
  159. package/src/thinking.ts +73 -1
  160. package/src/tiny/device.ts +4 -10
  161. package/src/tiny/models.ts +54 -2
  162. package/src/tiny/title-protocol.ts +11 -1
  163. package/src/tiny/worker.ts +19 -7
  164. package/src/tools/eval.ts +202 -26
  165. package/src/tools/grouped-file-output.ts +9 -2
  166. package/src/tools/index.ts +17 -5
  167. package/src/tools/memory-edit.ts +4 -4
  168. package/src/tools/memory-recall.ts +5 -5
  169. package/src/tools/memory-reflect.ts +5 -5
  170. package/src/tools/memory-retain.ts +4 -4
  171. package/src/tools/render-utils.ts +2 -1
  172. package/src/tools/search.ts +480 -76
  173. package/dist/types/mnemosyne/backend.d.ts +0 -4
  174. package/dist/types/mnemosyne/config.d.ts +0 -29
  175. package/dist/types/mnemosyne/state.d.ts +0 -72
  176. /package/dist/types/{mnemosyne → mnemopi}/index.d.ts +0 -0
  177. /package/src/{mnemosyne → mnemopi}/index.ts +0 -0
@@ -25,6 +25,29 @@ const KNOWN_HOSTS: Record<string, (pathname: string, hash: string) => { user: st
25
25
  "codeberg.org": extractStandard,
26
26
  };
27
27
 
28
+ /**
29
+ * Namespaced shorthand prefixes accepted by `omp plugin install`, mapped to
30
+ * their canonical host. `PluginManager.install` normalizes non-GitHub prefixes
31
+ * before invoking bun because bun only treats `github:` as a hosted shorthand.
32
+ */
33
+ const SHORTHAND_PREFIXES: Record<string, string> = {
34
+ github: "github.com",
35
+ gitlab: "gitlab.com",
36
+ bitbucket: "bitbucket.org",
37
+ codeberg: "codeberg.org",
38
+ sourcehut: "git.sr.ht",
39
+ srht: "git.sr.ht",
40
+ };
41
+
42
+ /**
43
+ * `<prefix>:<user>/<repo>[.git][#<ref>]` shape. `<repo>` is non-greedy so the
44
+ * optional `.git` suffix and `#ref` tail bind tightly; `<repo>` may itself
45
+ * contain `/` to support nested GitLab groups (`gitlab:group/sub/project`).
46
+ * `<user>` rejects `/`, `:`, `#` to keep protocol URLs (`https://…`) and
47
+ * scp-like SSH (`git@github.com:user/repo`) out of this path.
48
+ */
49
+ const SHORTHAND_RE = /^([a-z]+):([^/:#]+)\/([^#]+?)(?:\.git)?(?:#(.+))?$/i;
50
+
28
51
  function stripUrlCredentials(url: string): string {
29
52
  if (!url.includes("://")) return url;
30
53
  try {
@@ -209,14 +232,55 @@ function parseGenericGitUrl(url: string): GitSource | null {
209
232
  return { type: "git", repo, host, path: normalizedPath, ref, pinned: Boolean(ref) };
210
233
  }
211
234
 
235
+ /**
236
+ * Match an npm/bun-style namespaced shorthand (`github:user/repo`, optionally
237
+ * `…#ref` or `….git`). Returns null for protocol URLs and any prefix not in
238
+ * `SHORTHAND_PREFIXES` so the caller can fall through to the generic paths.
239
+ */
240
+ function tryNamespacedShorthand(trimmed: string): GitSource | null {
241
+ // Cheap gate: bail out before touching protocol URLs (`https://`, `ssh://`,
242
+ // `git://`) where the char after the colon is always `/`. The shorthand we
243
+ // care about never starts with `<scheme>://`.
244
+ if (!/^[a-z]+:[^/]/i.test(trimmed)) return null;
245
+ const match = trimmed.match(SHORTHAND_RE);
246
+ if (!match) return null;
247
+ const prefix = (match[1] ?? "").toLowerCase();
248
+ const host = SHORTHAND_PREFIXES[prefix];
249
+ if (!host) return null;
250
+ const user = match[2] ?? "";
251
+ const repoPath = match[3] ?? "";
252
+ if (!user || !repoPath) return null;
253
+ const ref = match[4];
254
+ if (ref) {
255
+ try {
256
+ decodeURIComponent(ref);
257
+ } catch {
258
+ return null;
259
+ }
260
+ }
261
+ const fullPath = `${user}/${repoPath}`;
262
+ return {
263
+ type: "git",
264
+ repo: `https://${host}/${fullPath}`,
265
+ host,
266
+ path: fullPath,
267
+ ref: ref || undefined,
268
+ pinned: Boolean(ref),
269
+ };
270
+ }
271
+
212
272
  /**
213
273
  * Parse git source into a GitSource.
214
274
  *
215
275
  * Rules:
216
- * - With `git:` prefix, accept shorthand forms.
276
+ * - Namespaced shorthand (`github:user/repo`, `gitlab:`, `bitbucket:`,
277
+ * `codeberg:`, `sourcehut:`/`srht:`) is accepted directly; installers should
278
+ * normalize entries that bun does not understand natively.
279
+ * - With `git:` prefix, accept generic shorthand forms.
217
280
  * - Without `git:` prefix, only accept explicit protocol URLs.
218
281
  *
219
282
  * Handles:
283
+ * - `github:user/repo[#ref]`-style namespaced shorthand
220
284
  * - `git:` prefixed URLs (`git:github.com/user/repo`)
221
285
  * - SSH SCP-like URLs (`git:git@github.com:user/repo`)
222
286
  * - HTTPS/HTTP/SSH/git protocol URLs
@@ -227,10 +291,23 @@ function parseGenericGitUrl(url: string): GitSource | null {
227
291
  */
228
292
  export function parseGitUrl(source: string): GitSource | null {
229
293
  const trimmed = source.trim();
230
- const hasGitPrefix = /^git:(?!\/\/)/i.test(trimmed);
231
- const url = hasGitPrefix ? trimmed.slice(4).trim() : trimmed;
232
294
 
233
- if (!hasGitPrefix && !/^(https?|ssh|git):\/\//i.test(url)) {
295
+ const shorthand = tryNamespacedShorthand(trimmed);
296
+ if (shorthand) return shorthand;
297
+
298
+ // Strip the `git+` URL prefix that npm/bun accept (`git+https://…`,
299
+ // `git+ssh://…`, `git+git://…`). The rest of the pipeline only deals with
300
+ // bare schemes.
301
+ const stripped = /^git\+/i.test(trimmed) ? trimmed.slice(4) : trimmed;
302
+
303
+ const hasGitPrefix = /^git:(?!\/\/)/i.test(stripped);
304
+ const url = hasGitPrefix ? stripped.slice(4).trim() : stripped;
305
+
306
+ // Accept: explicit protocol URL, `git:` shorthand, or scp-like SSH
307
+ // (`git@host:user/repo`). The scp form is unambiguous — no local path
308
+ // starts with `git@` — and matches the syntax that `git clone` itself
309
+ // accepts, which `bun install` forwards through.
310
+ if (!hasGitPrefix && !/^(https?|ssh|git):\/\//i.test(url) && !/^git@[^:]+:.+\/.+/i.test(url)) {
234
311
  return null;
235
312
  }
236
313
 
@@ -279,3 +356,12 @@ export function parseGitUrl(source: string): GitSource | null {
279
356
 
280
357
  return parseGenericGitUrl(url);
281
358
  }
359
+
360
+ /**
361
+ * Returns true if the spec is parseable as a git source (protocol URL,
362
+ * scp-like SSH wrapped in `git:`, plain `git:` shorthand, or namespaced
363
+ * shorthand like `github:user/repo`). The inverse of "this is an npm spec".
364
+ */
365
+ export function isGitSpec(spec: string): boolean {
366
+ return parseGitUrl(spec) !== null;
367
+ }
@@ -10,6 +10,7 @@ import {
10
10
  isEnoent,
11
11
  logger,
12
12
  } from "@oh-my-pi/pi-utils";
13
+ import { type GitSource, parseGitUrl } from "./git-url";
13
14
  import { extractPackageName, parsePluginSpec } from "./parser";
14
15
  import type {
15
16
  DoctorCheck,
@@ -29,8 +30,14 @@ import type {
29
30
  /** Valid npm package name pattern (scoped and unscoped, with optional version) */
30
31
  const VALID_PACKAGE_NAME = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*(@[a-z0-9-._^~>=<]+)?$/i;
31
32
 
33
+ /** Characters that are never valid in any plugin install spec — git or npm. */
34
+ const SHELL_METACHARS = /[;&|`$(){}<>\\\n\r\t]/;
35
+
32
36
  /**
33
- * Validate package name to prevent command injection.
37
+ * Validate package name to prevent command injection. npm specs only — git
38
+ * specs (`github:user/repo`, `https://github.com/...`, ...) MUST go through
39
+ * {@link validateGitSpec} instead because they contain characters npm rejects
40
+ * (`:`, `/`, `#`, `+`, `@` in non-version positions).
34
41
  */
35
42
  function validatePackageName(name: string): void {
36
43
  // Remove version specifier for validation
@@ -44,6 +51,29 @@ function validatePackageName(name: string): void {
44
51
  }
45
52
  }
46
53
 
54
+ /**
55
+ * Validate a git install spec — accepts `:`, `/`, `#`, `+`, `.`, `-`, `_`,
56
+ * `~`, `@` (which would all fail {@link validatePackageName}) but rejects
57
+ * shell metacharacters so the spec stays safe when forwarded to bun install.
58
+ * `Bun.spawn` does not invoke a shell, but defense-in-depth keeps things
59
+ * obvious for future readers.
60
+ */
61
+ function validateGitSpec(spec: string): void {
62
+ if (SHELL_METACHARS.test(spec)) {
63
+ throw new Error(`Invalid characters in plugin source: ${spec}`);
64
+ }
65
+ }
66
+
67
+ function gitInstallSpec(original: string, source: GitSource): string {
68
+ if (/^github:/i.test(original) || !/^[a-z]+:[^/]/i.test(original)) {
69
+ return original;
70
+ }
71
+ if (!source.ref || source.repo.includes("#")) {
72
+ return source.repo;
73
+ }
74
+ return `${source.repo}#${source.ref}`;
75
+ }
76
+
47
77
  // =============================================================================
48
78
  // Plugin Manager
49
79
  // =============================================================================
@@ -127,12 +157,39 @@ export class PluginManager {
127
157
  }
128
158
  }
129
159
 
160
+ /**
161
+ * Read the `dependencies` map from `plugins/package.json`. Returns an empty
162
+ * object when the file does not exist yet so callers can diff `before`
163
+ * against `after` to discover the package bun just installed under its
164
+ * real name (git specs do not encode the package name in the spec itself).
165
+ */
166
+ async #readDeps(pkgJsonPath: string): Promise<Record<string, string>> {
167
+ try {
168
+ const json = await Bun.file(pkgJsonPath).json();
169
+ return (json.dependencies as Record<string, string>) ?? {};
170
+ } catch (err) {
171
+ if (isEnoent(err)) return {};
172
+ throw err;
173
+ }
174
+ }
175
+
130
176
  // ==========================================================================
131
177
  // Install / Uninstall
132
178
  // ==========================================================================
133
179
 
134
180
  /**
135
- * Install a plugin from npm with optional feature selection.
181
+ * Install a plugin with optional feature selection.
182
+ *
183
+ * Accepts:
184
+ * - npm specs: `pkg`, `pkg@1.2.3`, `@scope/pkg`, `pkg[features]`
185
+ * - namespaced git shorthand: `github:user/repo[#ref]`, `gitlab:`, `bitbucket:`,
186
+ * `codeberg:`, `sourcehut:`/`srht:`
187
+ * - full git URLs: `https://github.com/user/repo`, `git@github.com:user/repo`,
188
+ * `ssh://…`, `git+https://…`
189
+ *
190
+ * For git specs the package name is not knowable from the spec, so the
191
+ * installer diffs `plugins/package.json` `dependencies` before and after
192
+ * to find the newly added key.
136
193
  *
137
194
  * @param specString - Package specifier with optional features: "pkg", "pkg[feat]", "pkg[*]", "pkg[]"
138
195
  * @param options - Install options
@@ -140,7 +197,12 @@ export class PluginManager {
140
197
  */
141
198
  async install(specString: string, options: InstallOptions = {}): Promise<InstalledPlugin> {
142
199
  const spec = parsePluginSpec(specString);
143
- validatePackageName(spec.packageName);
200
+ const gitSource = parseGitUrl(spec.packageName);
201
+ if (gitSource) {
202
+ validateGitSpec(spec.packageName);
203
+ } else {
204
+ validatePackageName(spec.packageName);
205
+ }
144
206
 
145
207
  await this.#ensurePackageJson();
146
208
 
@@ -154,9 +216,12 @@ export class PluginManager {
154
216
  enabled: true,
155
217
  };
156
218
  }
219
+ const pkgJsonPath = getPluginsPackageJson();
220
+ const depsBefore = gitSource ? await this.#readDeps(pkgJsonPath) : {};
221
+ const packageInstallSpec = gitSource ? gitInstallSpec(spec.packageName, gitSource) : spec.packageName;
157
222
 
158
223
  // Run npm install
159
- const proc = Bun.spawn(["bun", "install", spec.packageName], {
224
+ const proc = Bun.spawn(["bun", "install", packageInstallSpec], {
160
225
  cwd: getPluginsDir(),
161
226
  stdin: "ignore",
162
227
  stdout: "pipe",
@@ -169,9 +234,40 @@ export class PluginManager {
169
234
  const stderr = await new Response(proc.stderr).text();
170
235
  throw new Error(`npm install failed: ${stderr}`);
171
236
  }
172
-
173
- // Resolve actual package name (strip version specifier)
174
- const actualName = extractPackageName(spec.packageName);
237
+ // Resolve actual package name. npm specs encode the name (strip version);
238
+ // git specs do not, so diff plugins/package.json deps to find the new entry.
239
+ let actualName: string;
240
+ if (gitSource) {
241
+ const depsAfter = await this.#readDeps(pkgJsonPath);
242
+ let resolved: string | undefined;
243
+ for (const key of Object.keys(depsAfter)) {
244
+ if (!(key in depsBefore)) {
245
+ resolved = key;
246
+ break;
247
+ }
248
+ }
249
+ // Fallback: a force-reinstall of an already-present git plugin will not
250
+ // add a new key, just rewrite the existing one to the new spec value.
251
+ // Match by the install value for force-reinstalls where no new key is
252
+ // added (non-GitHub shorthands are normalized before bun sees them).
253
+ if (!resolved) {
254
+ const needle = packageInstallSpec.replace(/^git\+/i, "");
255
+ for (const [key, value] of Object.entries(depsAfter)) {
256
+ if (typeof value === "string" && value.includes(needle)) {
257
+ resolved = key;
258
+ break;
259
+ }
260
+ }
261
+ }
262
+ if (!resolved) {
263
+ throw new Error(
264
+ `Installed ${spec.packageName} but could not determine package name from plugins/package.json`,
265
+ );
266
+ }
267
+ actualName = resolved;
268
+ } else {
269
+ actualName = extractPackageName(spec.packageName);
270
+ }
175
271
  const pkgPath = path.join(getPluginsNodeModules(), actualName, "package.json");
176
272
 
177
273
  let pkg: { name: string; version: string; omp?: PluginManifest; pi?: PluginManifest };
@@ -204,13 +204,13 @@ export interface TurnEndEvent {
204
204
  export interface AutoCompactionStartEvent {
205
205
  type: "auto_compaction_start";
206
206
  reason: "threshold" | "overflow" | "idle" | "incomplete";
207
- action: "context-full" | "handoff";
207
+ action: "context-full" | "handoff" | "shake" | "shake-summary";
208
208
  }
209
209
 
210
210
  /** Fired when auto-compaction ends */
211
211
  export interface AutoCompactionEndEvent {
212
212
  type: "auto_compaction_end";
213
- action: "context-full" | "handoff";
213
+ action: "context-full" | "handoff" | "shake" | "shake-summary";
214
214
  result: CompactionResult | undefined;
215
215
  aborted: boolean;
216
216
  willRetry: boolean;