oh-my-opencode-slim 0.9.10 → 0.9.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -388,7 +388,7 @@ Slim only intercepts `apply_patch` before native execution. It rewrites recovera
388
388
  <p><sub>Every merged contribution leaves a mark on the realm.</sub></p>
389
389
 
390
390
  <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
391
- [![All Contributors](https://img.shields.io/badge/all_contributors-35-orange.svg?style=flat-square)](#contributors-)
391
+ [![All Contributors](https://img.shields.io/badge/all_contributors-36-orange.svg?style=flat-square)](#contributors-)
392
392
  <!-- ALL-CONTRIBUTORS-BADGE:END -->
393
393
  </div>
394
394
 
@@ -445,6 +445,7 @@ Slim only intercepts `apply_patch` before native execution. It rewrites recovera
445
445
  <td align="center" valign="top" width="16.66%"><a href="https://nettee.io/"><img src="https://avatars.githubusercontent.com/u/3953668?v=4?s=100" width="100px;" alt="nettee"/><br /><sub><b>nettee</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=nettee" title="Code">💻</a></td>
446
446
  <td align="center" valign="top" width="16.66%"><a href="https://github.com/atomlink-ye"><img src="https://avatars.githubusercontent.com/u/48194045?v=4?s=100" width="100px;" alt="Link"/><br /><sub><b>Link</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=atomlink-ye" title="Code">💻</a></td>
447
447
  <td align="center" valign="top" width="16.66%"><a href="https://github.com/blaszewski"><img src="https://avatars.githubusercontent.com/u/14119531?v=4?s=100" width="100px;" alt="Bartosz Łaszewski"/><br /><sub><b>Bartosz Łaszewski</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=blaszewski" title="Code">💻</a></td>
448
+ <td align="center" valign="top" width="16.66%"><a href="https://github.com/huilang021x"><img src="https://avatars.githubusercontent.com/u/77293911?v=4?s=100" width="100px;" alt="huilang021x"/><br /><sub><b>huilang021x</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=huilang021x" title="Code">💻</a></td>
448
449
  </tr>
449
450
  </tbody>
450
451
  </table>
package/dist/cli/index.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  statSync as statSync2,
14
14
  writeFileSync
15
15
  } from "fs";
16
- import { pathToFileURL } from "url";
16
+ import { dirname as dirname3, join as join3 } from "path";
17
17
 
18
18
  // src/cli/paths.ts
19
19
  import { existsSync, mkdirSync } from "fs";
@@ -208,7 +208,8 @@ var AgentOverrideConfigSchema = z2.object({
208
208
  temperature: z2.number().min(0).max(2).optional(),
209
209
  variant: z2.string().optional().catch(undefined),
210
210
  skills: z2.array(z2.string()).optional(),
211
- mcps: z2.array(z2.string()).optional()
211
+ mcps: z2.array(z2.string()).optional(),
212
+ options: z2.record(z2.string(), z2.unknown()).optional()
212
213
  });
213
214
  var MultiplexerTypeSchema = z2.enum(["auto", "tmux", "zellij", "none"]);
214
215
  var MultiplexerLayoutSchema = z2.enum([
@@ -480,8 +481,49 @@ function generateLiteConfig(installConfig) {
480
481
 
481
482
  // src/cli/config-io.ts
482
483
  var PACKAGE_NAME = "oh-my-opencode-slim";
484
+ function normalizePathForMatch(path) {
485
+ return path.replaceAll("\\", "/");
486
+ }
487
+ function findPackageRoot(startPath) {
488
+ let currentPath = dirname3(startPath);
489
+ while (true) {
490
+ const packageJsonPath = join3(currentPath, "package.json");
491
+ if (existsSync3(packageJsonPath)) {
492
+ try {
493
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
494
+ if (packageJson.name === PACKAGE_NAME) {
495
+ return currentPath;
496
+ }
497
+ } catch {}
498
+ }
499
+ const parentPath = dirname3(currentPath);
500
+ if (parentPath === currentPath) {
501
+ return null;
502
+ }
503
+ currentPath = parentPath;
504
+ }
505
+ }
506
+ function isPackageManagerInstall(path) {
507
+ const normalizedPath = normalizePathForMatch(path);
508
+ return normalizedPath.includes(`/node_modules/${PACKAGE_NAME}`);
509
+ }
510
+ function isLocalPackageRootEntry(entry) {
511
+ if (!entry || entry.startsWith("file://")) {
512
+ return false;
513
+ }
514
+ const packageJsonPath = join3(entry, "package.json");
515
+ if (!existsSync3(packageJsonPath)) {
516
+ return false;
517
+ }
518
+ try {
519
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
520
+ return packageJson.name === PACKAGE_NAME;
521
+ } catch {
522
+ return false;
523
+ }
524
+ }
483
525
  function isPluginEntry(entry) {
484
- return entry === PACKAGE_NAME || entry.startsWith(`${PACKAGE_NAME}@`) || entry.startsWith("file://") && entry.includes(PACKAGE_NAME);
526
+ return entry === PACKAGE_NAME || entry.startsWith(`${PACKAGE_NAME}@`) || entry.startsWith("file://") && entry.includes(PACKAGE_NAME) || isLocalPackageRootEntry(entry);
485
527
  }
486
528
  function getPluginEntry() {
487
529
  const cliEntryPath = process.argv[1];
@@ -489,11 +531,11 @@ function getPluginEntry() {
489
531
  return PACKAGE_NAME;
490
532
  }
491
533
  try {
492
- const pluginEntryPath = cliEntryPath.match(/[\\/]dist[\\/]cli[\\/]index\.js$/) ? cliEntryPath.replace(/[\\/]dist[\\/]cli[\\/]index\.js$/, "/dist/index.js") : null;
493
- if (!pluginEntryPath) {
534
+ const packageRoot = findPackageRoot(cliEntryPath);
535
+ if (!packageRoot || isPackageManagerInstall(packageRoot)) {
494
536
  return PACKAGE_NAME;
495
537
  }
496
- return pathToFileURL(pluginEntryPath).href;
538
+ return packageRoot;
497
539
  } catch {
498
540
  return PACKAGE_NAME;
499
541
  }
@@ -683,8 +725,35 @@ function detectCurrentConfig() {
683
725
  return result;
684
726
  }
685
727
  // src/cli/system.ts
728
+ import { spawnSync as spawnSync2 } from "child_process";
686
729
  import { statSync as statSync3 } from "fs";
687
730
  var cachedOpenCodePath = null;
731
+ function resolvePathCommand(command) {
732
+ try {
733
+ const resolver = process.platform === "win32" ? "where" : "which";
734
+ const result = spawnSync2(resolver, [command], {
735
+ encoding: "utf-8",
736
+ stdio: ["ignore", "pipe", "ignore"]
737
+ });
738
+ if (result.status !== 0) {
739
+ return null;
740
+ }
741
+ const resolved = result.stdout.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
742
+ return resolved ?? null;
743
+ } catch {
744
+ return null;
745
+ }
746
+ }
747
+ function canExecute(command, args) {
748
+ try {
749
+ const result = spawnSync2(command, args, {
750
+ stdio: "ignore"
751
+ });
752
+ return result.status === 0;
753
+ } catch {
754
+ return false;
755
+ }
756
+ }
688
757
  function getOpenCodePaths() {
689
758
  const home = process.env.HOME || process.env.USERPROFILE || "";
690
759
  return [
@@ -720,6 +789,11 @@ function resolveOpenCodePath() {
720
789
  if (cachedOpenCodePath) {
721
790
  return cachedOpenCodePath;
722
791
  }
792
+ const pathOpenCodePath = resolvePathCommand("opencode");
793
+ if (pathOpenCodePath) {
794
+ cachedOpenCodePath = pathOpenCodePath;
795
+ return pathOpenCodePath;
796
+ }
723
797
  const paths = getOpenCodePaths();
724
798
  for (const opencodePath of paths) {
725
799
  if (opencodePath === "opencode")
@@ -735,33 +809,31 @@ function resolveOpenCodePath() {
735
809
  return "opencode";
736
810
  }
737
811
  async function isOpenCodeInstalled() {
812
+ const pathOpenCodePath = resolvePathCommand("opencode");
813
+ if (pathOpenCodePath && canExecute(pathOpenCodePath, ["--version"])) {
814
+ cachedOpenCodePath = pathOpenCodePath;
815
+ return true;
816
+ }
738
817
  const paths = getOpenCodePaths();
739
818
  for (const opencodePath of paths) {
740
- try {
741
- const proc = Bun.spawn([opencodePath, "--version"], {
742
- stdout: "pipe",
743
- stderr: "pipe"
744
- });
745
- await proc.exited;
746
- if (proc.exitCode === 0) {
747
- cachedOpenCodePath = opencodePath;
748
- return true;
749
- }
750
- } catch {}
819
+ if (opencodePath === "opencode")
820
+ continue;
821
+ if (canExecute(opencodePath, ["--version"])) {
822
+ cachedOpenCodePath = opencodePath;
823
+ return true;
824
+ }
751
825
  }
752
826
  return false;
753
827
  }
754
828
  async function getOpenCodeVersion() {
755
829
  const opencodePath = resolveOpenCodePath();
756
830
  try {
757
- const proc = Bun.spawn([opencodePath, "--version"], {
758
- stdout: "pipe",
759
- stderr: "pipe"
831
+ const result = spawnSync2(opencodePath, ["--version"], {
832
+ encoding: "utf-8",
833
+ stdio: ["ignore", "pipe", "ignore"]
760
834
  });
761
- const output = await new Response(proc.stdout).text();
762
- await proc.exited;
763
- if (proc.exitCode === 0) {
764
- return output.trim();
835
+ if (result.status === 0) {
836
+ return result.stdout.trim();
765
837
  }
766
838
  } catch {}
767
839
  return null;
@@ -59,6 +59,7 @@ export declare const AgentOverrideConfigSchema: z.ZodObject<{
59
59
  variant: z.ZodCatch<z.ZodOptional<z.ZodString>>;
60
60
  skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
61
61
  mcps: z.ZodOptional<z.ZodArray<z.ZodString>>;
62
+ options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
62
63
  }, z.core.$strip>;
63
64
  export declare const MultiplexerTypeSchema: z.ZodEnum<{
64
65
  auto: "auto";
@@ -127,6 +128,7 @@ export declare const PresetSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
127
128
  variant: z.ZodCatch<z.ZodOptional<z.ZodString>>;
128
129
  skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
129
130
  mcps: z.ZodOptional<z.ZodArray<z.ZodString>>;
131
+ options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
130
132
  }, z.core.$strip>>;
131
133
  export type Preset = z.infer<typeof PresetSchema>;
132
134
  export declare const WebsearchConfigSchema: z.ZodObject<{
@@ -231,6 +233,7 @@ export declare const PluginConfigSchema: z.ZodObject<{
231
233
  variant: z.ZodCatch<z.ZodOptional<z.ZodString>>;
232
234
  skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
233
235
  mcps: z.ZodOptional<z.ZodArray<z.ZodString>>;
236
+ options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
234
237
  }, z.core.$strip>>>>;
235
238
  agents: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
236
239
  model: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
@@ -241,6 +244,7 @@ export declare const PluginConfigSchema: z.ZodObject<{
241
244
  variant: z.ZodCatch<z.ZodOptional<z.ZodString>>;
242
245
  skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
243
246
  mcps: z.ZodOptional<z.ZodArray<z.ZodString>>;
247
+ options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
244
248
  }, z.core.$strip>>>;
245
249
  disabled_mcps: z.ZodOptional<z.ZodArray<z.ZodString>>;
246
250
  multiplexer: z.ZodOptional<z.ZodObject<{
@@ -11,6 +11,7 @@ export type PreparedFileState = {
11
11
  };
12
12
  export type PatchExecutionContext = {
13
13
  hunks: PatchHunk[];
14
+ pathsNormalized: boolean;
14
15
  staged: Map<string, PreparedFileState>;
15
16
  getPreparedFileState: (filePath: string, verb: 'update' | 'delete') => Promise<PreparedFileState>;
16
17
  assertPreparedPathMissing: (filePath: string, verb: 'add' | 'move') => Promise<void>;
@@ -1,6 +1,11 @@
1
+ interface AutoUpdateInstallContext {
2
+ installDir: string;
3
+ packageJsonPath: string;
4
+ }
5
+ export declare function resolveInstallContext(runtimePackageJsonPath?: string | null): AutoUpdateInstallContext | null;
1
6
  /**
2
- * Invalidates the current package by removing its directory and dependency entries.
3
- * This forces a clean state before running a fresh install.
4
- * @param packageName The name of the package to invalidate.
7
+ * Prepares the current install root for a clean re-install of the target version.
8
+ * Returns the install directory to run `bun install` in.
5
9
  */
6
- export declare function invalidatePackage(packageName?: string): boolean;
10
+ export declare function preparePackageUpdate(version: string, packageName?: string, runtimePackageJsonPath?: string | null): string | null;
11
+ export {};
@@ -9,6 +9,10 @@ export declare function extractChannel(version: string | null): string;
9
9
  * Resolves the version of the plugin when running in local development mode.
10
10
  */
11
11
  export declare function getLocalDevVersion(directory: string): string | null;
12
+ /**
13
+ * Resolves the package.json for the currently running plugin bundle.
14
+ */
15
+ export declare function getCurrentRuntimePackageJsonPath(currentModuleUrl?: string): string | null;
12
16
  /**
13
17
  * Searches across all config locations to find the current installation entry for this plugin.
14
18
  */
@@ -6,6 +6,15 @@ export declare function createTodoContinuationHook(ctx: PluginInput, config?: {
6
6
  autoEnableThreshold?: number;
7
7
  }): {
8
8
  tool: Record<string, unknown>;
9
+ handleToolExecuteAfter: (input: {
10
+ tool: string;
11
+ sessionID?: string;
12
+ }) => Promise<void>;
13
+ handleChatSystemTransform: (input: {
14
+ sessionID?: string;
15
+ }, output: {
16
+ system: string[];
17
+ }) => Promise<void>;
9
18
  handleEvent: (input: {
10
19
  event: {
11
20
  type: string;
@@ -0,0 +1,37 @@
1
+ export declare const TODO_HYGIENE_REMINDER = "If the active task changed or finished, update the todo list to match the current work state.";
2
+ export declare const TODO_FINAL_ACTIVE_REMINDER = "If you are finishing now, do not leave the active todo in_progress. Mark it completed, or move unfinished work back to pending.";
3
+ interface ToolInput {
4
+ tool: string;
5
+ sessionID?: string;
6
+ }
7
+ interface SystemInput {
8
+ sessionID?: string;
9
+ }
10
+ interface SystemOutput {
11
+ system: string[];
12
+ }
13
+ interface EventInput {
14
+ type: string;
15
+ properties?: {
16
+ info?: {
17
+ id?: string;
18
+ };
19
+ sessionID?: string;
20
+ };
21
+ }
22
+ interface Options {
23
+ getTodoState: (sessionID: string) => Promise<{
24
+ hasOpenTodos: boolean;
25
+ openCount: number;
26
+ inProgressCount: number;
27
+ pendingCount: number;
28
+ }>;
29
+ shouldInject?: (sessionID: string) => boolean;
30
+ log?: (message: string, meta?: Record<string, unknown>) => void;
31
+ }
32
+ export declare function createTodoHygiene(options: Options): {
33
+ handleToolExecuteAfter(input: ToolInput): Promise<void>;
34
+ handleChatSystemTransform(input: SystemInput, output: SystemOutput): Promise<void>;
35
+ handleEvent(event: EventInput): void;
36
+ };
37
+ export {};
package/dist/index.js CHANGED
@@ -502,7 +502,8 @@ var AgentOverrideConfigSchema = z2.object({
502
502
  temperature: z2.number().min(0).max(2).optional(),
503
503
  variant: z2.string().optional().catch(undefined),
504
504
  skills: z2.array(z2.string()).optional(),
505
- mcps: z2.array(z2.string()).optional()
505
+ mcps: z2.array(z2.string()).optional(),
506
+ options: z2.record(z2.string(), z2.unknown()).optional()
506
507
  });
507
508
  var MultiplexerTypeSchema = z2.enum(["auto", "tmux", "zellij", "none"]);
508
509
  var MultiplexerLayoutSchema = z2.enum([
@@ -1247,6 +1248,12 @@ function applyOverrides(agent, override) {
1247
1248
  agent.config.variant = override.variant;
1248
1249
  if (override.temperature !== undefined)
1249
1250
  agent.config.temperature = override.temperature;
1251
+ if (override.options) {
1252
+ agent.config.options = {
1253
+ ...agent.config.options,
1254
+ ...override.options
1255
+ };
1256
+ }
1250
1257
  }
1251
1258
  function applyDefaultPermissions(agent, configuredSkills) {
1252
1259
  const existing = agent.config.permission ?? {};
@@ -3899,15 +3906,36 @@ function collectPatchTargets(root, hunks) {
3899
3906
  }
3900
3907
  return [...targets];
3901
3908
  }
3902
- function validatePatchPaths(hunks) {
3909
+ function toRelativePatchPath(root, target) {
3910
+ const relative = path3.relative(root, target);
3911
+ return (relative.length === 0 ? "." : relative).replaceAll("\\", "/");
3912
+ }
3913
+ function normalizePatchPath(root, value) {
3914
+ return path3.isAbsolute(value) ? toRelativePatchPath(root, path3.resolve(value)) : value;
3915
+ }
3916
+ function normalizePatchPaths(root, hunks) {
3917
+ const resolvedRoot = path3.resolve(root);
3918
+ const normalized = [];
3919
+ let changed = false;
3903
3920
  for (const hunk of hunks) {
3904
- if (path3.isAbsolute(hunk.path)) {
3905
- throw createApplyPatchValidationError(`absolute patch paths are not allowed: ${hunk.path}`);
3906
- }
3907
- if (hunk.type === "update" && hunk.move_path && path3.isAbsolute(hunk.move_path)) {
3908
- throw createApplyPatchValidationError(`absolute patch paths are not allowed: ${hunk.move_path}`);
3921
+ const normalizedPath = normalizePatchPath(resolvedRoot, hunk.path);
3922
+ if (hunk.type !== "update") {
3923
+ changed ||= normalizedPath !== hunk.path;
3924
+ normalized.push(normalizedPath === hunk.path ? hunk : {
3925
+ ...hunk,
3926
+ path: normalizedPath
3927
+ });
3928
+ continue;
3909
3929
  }
3930
+ const normalizedMovePath = hunk.move_path ? normalizePatchPath(resolvedRoot, hunk.move_path) : undefined;
3931
+ changed ||= normalizedPath !== hunk.path || normalizedMovePath !== hunk.move_path;
3932
+ normalized.push(normalizedPath === hunk.path && normalizedMovePath === hunk.move_path ? hunk : {
3933
+ ...hunk,
3934
+ path: normalizedPath,
3935
+ move_path: normalizedMovePath
3936
+ });
3910
3937
  }
3938
+ return { hunks: normalized, changed };
3911
3939
  }
3912
3940
  async function guardPatchTargets(root, worktree, targets) {
3913
3941
  const guardContext = createPathGuardContext(root, worktree);
@@ -3933,7 +3961,6 @@ function parseValidatedPatch(patchText) {
3933
3961
  }
3934
3962
  throw createApplyPatchValidationError("no hunks found");
3935
3963
  }
3936
- validatePatchPaths(hunks);
3937
3964
  return hunks;
3938
3965
  }
3939
3966
  async function readPreparedFileText(filePath, verb) {
@@ -3947,8 +3974,9 @@ async function readPreparedFileText(filePath, verb) {
3947
3974
  }
3948
3975
  }
3949
3976
  async function createPatchExecutionContext(root, patchText, worktree) {
3950
- const hunks = parseValidatedPatch(patchText);
3951
- await guardPatchTargets(root, worktree, collectPatchTargets(root, hunks));
3977
+ const parsedHunks = parseValidatedPatch(patchText);
3978
+ await guardPatchTargets(root, worktree, collectPatchTargets(root, parsedHunks));
3979
+ const normalized = normalizePatchPaths(root, parsedHunks);
3952
3980
  const files = createFileCacheContext();
3953
3981
  const staged = new Map;
3954
3982
  async function assertPreparedPathMissing(filePath, verb) {
@@ -3986,7 +4014,8 @@ async function createPatchExecutionContext(root, patchText, worktree) {
3986
4014
  return state;
3987
4015
  }
3988
4016
  return {
3989
- hunks,
4017
+ hunks: normalized.hunks,
4018
+ pathsNormalized: normalized.changed,
3990
4019
  staged,
3991
4020
  getPreparedFileState,
3992
4021
  assertPreparedPathMissing
@@ -4162,13 +4191,22 @@ async function rewritePatch(root, patchText, cfg, worktree) {
4162
4191
  let clearDependencyGroup = function(filePath) {
4163
4192
  dependencyGroups.delete(filePath);
4164
4193
  };
4165
- const { hunks, staged, getPreparedFileState, assertPreparedPathMissing } = await createPatchExecutionContext(root, patchText, worktree);
4194
+ const {
4195
+ hunks,
4196
+ pathsNormalized,
4197
+ staged,
4198
+ getPreparedFileState,
4199
+ assertPreparedPathMissing
4200
+ } = await createPatchExecutionContext(root, patchText, worktree);
4166
4201
  const normalizedPatchText = normalizePatchText(patchText);
4167
4202
  const rewritten = [];
4168
4203
  let changed = false;
4169
4204
  let rewrittenChunks = 0;
4170
4205
  const rewriteModes = new Set;
4171
4206
  const totalChunks = hunks.reduce((count, hunk) => count + (hunk.type === "update" ? hunk.chunks.length : 0), 0);
4207
+ if (pathsNormalized) {
4208
+ rewriteModes.add("normalize:patch-paths");
4209
+ }
4172
4210
  const dependencyGroups = new Map;
4173
4211
  for (const hunk of hunks) {
4174
4212
  if (hunk.type === "add") {
@@ -4282,6 +4320,15 @@ async function rewritePatch(root, patchText, cfg, worktree) {
4282
4320
  }
4283
4321
  }
4284
4322
  if (!changed) {
4323
+ if (pathsNormalized) {
4324
+ return {
4325
+ patchText: formatPatch({ hunks }),
4326
+ changed: true,
4327
+ rewrittenChunks: 0,
4328
+ totalChunks,
4329
+ rewriteModes: [...rewriteModes].sort()
4330
+ };
4331
+ }
4285
4332
  if (normalizedPatchText !== patchText) {
4286
4333
  return {
4287
4334
  patchText: normalizedPatchText,
@@ -4362,8 +4409,13 @@ function createApplyPatchHook(ctx) {
4362
4409
  };
4363
4410
  }
4364
4411
  // src/hooks/auto-update-checker/cache.ts
4412
+ import * as fs5 from "fs";
4413
+ import * as path7 from "path";
4414
+ // src/hooks/auto-update-checker/checker.ts
4365
4415
  import * as fs4 from "fs";
4366
4416
  import * as path6 from "path";
4417
+ import { fileURLToPath } from "url";
4418
+
4367
4419
  // src/hooks/auto-update-checker/constants.ts
4368
4420
  import * as os2 from "os";
4369
4421
  import * as path5 from "path";
@@ -4382,80 +4434,7 @@ var configPaths = getOpenCodeConfigPaths();
4382
4434
  var USER_OPENCODE_CONFIG = configPaths[0];
4383
4435
  var USER_OPENCODE_CONFIG_JSONC = configPaths[1];
4384
4436
 
4385
- // src/hooks/auto-update-checker/cache.ts
4386
- function removeFromBunLock(packageName) {
4387
- const lockPath = path6.join(CACHE_DIR, "bun.lock");
4388
- if (!fs4.existsSync(lockPath))
4389
- return false;
4390
- try {
4391
- const content = fs4.readFileSync(lockPath, "utf-8");
4392
- let lock;
4393
- try {
4394
- lock = JSON.parse(stripJsonComments(content));
4395
- } catch {
4396
- return false;
4397
- }
4398
- let modified = false;
4399
- if (lock.workspaces?.[""]?.dependencies?.[packageName]) {
4400
- delete lock.workspaces[""].dependencies[packageName];
4401
- modified = true;
4402
- }
4403
- if (lock.packages?.[packageName]) {
4404
- delete lock.packages[packageName];
4405
- modified = true;
4406
- }
4407
- if (modified) {
4408
- fs4.writeFileSync(lockPath, JSON.stringify(lock, null, 2));
4409
- log(`[auto-update-checker] Removed from bun.lock: ${packageName}`);
4410
- }
4411
- return modified;
4412
- } catch (err) {
4413
- log(`[auto-update-checker] Failed to process bun.lock:`, err);
4414
- return false;
4415
- }
4416
- }
4417
- function invalidatePackage(packageName = PACKAGE_NAME) {
4418
- try {
4419
- const pkgDir = path6.join(CACHE_DIR, "node_modules", packageName);
4420
- const pkgJsonPath = path6.join(CACHE_DIR, "package.json");
4421
- let packageRemoved = false;
4422
- let dependencyRemoved = false;
4423
- let lockRemoved = false;
4424
- if (fs4.existsSync(pkgDir)) {
4425
- fs4.rmSync(pkgDir, { recursive: true, force: true });
4426
- log(`[auto-update-checker] Package removed: ${pkgDir}`);
4427
- packageRemoved = true;
4428
- }
4429
- if (fs4.existsSync(pkgJsonPath)) {
4430
- try {
4431
- const content = fs4.readFileSync(pkgJsonPath, "utf-8");
4432
- const pkgJson = JSON.parse(stripJsonComments(content));
4433
- if (pkgJson.dependencies?.[packageName]) {
4434
- delete pkgJson.dependencies[packageName];
4435
- fs4.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
4436
- log(`[auto-update-checker] Dependency removed from package.json: ${packageName}`);
4437
- dependencyRemoved = true;
4438
- }
4439
- } catch (err) {
4440
- log(`[auto-update-checker] Failed to update package.json for invalidation:`, err);
4441
- }
4442
- }
4443
- lockRemoved = removeFromBunLock(packageName);
4444
- if (!packageRemoved && !dependencyRemoved && !lockRemoved) {
4445
- log(`[auto-update-checker] Package not found, nothing to invalidate: ${packageName}`);
4446
- return false;
4447
- }
4448
- return true;
4449
- } catch (err) {
4450
- log("[auto-update-checker] Failed to invalidate package:", err);
4451
- return false;
4452
- }
4453
- }
4454
-
4455
4437
  // src/hooks/auto-update-checker/checker.ts
4456
- import * as fs5 from "fs";
4457
- import * as path7 from "path";
4458
- import { fileURLToPath } from "url";
4459
4438
  function isPrereleaseVersion(version) {
4460
4439
  return version.includes("-");
4461
4440
  }
@@ -4479,8 +4458,8 @@ function extractChannel(version) {
4479
4458
  }
4480
4459
  function getConfigPaths(directory) {
4481
4460
  return [
4482
- path7.join(directory, ".opencode", "opencode.json"),
4483
- path7.join(directory, ".opencode", "opencode.jsonc"),
4461
+ path6.join(directory, ".opencode", "opencode.json"),
4462
+ path6.join(directory, ".opencode", "opencode.jsonc"),
4484
4463
  USER_OPENCODE_CONFIG,
4485
4464
  USER_OPENCODE_CONFIG_JSONC
4486
4465
  ];
@@ -4488,9 +4467,9 @@ function getConfigPaths(directory) {
4488
4467
  function getLocalDevPath(directory) {
4489
4468
  for (const configPath of getConfigPaths(directory)) {
4490
4469
  try {
4491
- if (!fs5.existsSync(configPath))
4470
+ if (!fs4.existsSync(configPath))
4492
4471
  continue;
4493
- const content = fs5.readFileSync(configPath, "utf-8");
4472
+ const content = fs4.readFileSync(configPath, "utf-8");
4494
4473
  const config = JSON.parse(stripJsonComments(content));
4495
4474
  const plugins = config.plugin ?? [];
4496
4475
  for (const entry of plugins) {
@@ -4508,19 +4487,19 @@ function getLocalDevPath(directory) {
4508
4487
  }
4509
4488
  function findPackageJsonUp(startPath) {
4510
4489
  try {
4511
- const stat2 = fs5.statSync(startPath);
4512
- let dir = stat2.isDirectory() ? startPath : path7.dirname(startPath);
4490
+ const stat2 = fs4.statSync(startPath);
4491
+ let dir = stat2.isDirectory() ? startPath : path6.dirname(startPath);
4513
4492
  for (let i = 0;i < 10; i++) {
4514
- const pkgPath = path7.join(dir, "package.json");
4515
- if (fs5.existsSync(pkgPath)) {
4493
+ const pkgPath = path6.join(dir, "package.json");
4494
+ if (fs4.existsSync(pkgPath)) {
4516
4495
  try {
4517
- const content = fs5.readFileSync(pkgPath, "utf-8");
4496
+ const content = fs4.readFileSync(pkgPath, "utf-8");
4518
4497
  const pkg = JSON.parse(content);
4519
4498
  if (pkg.name === PACKAGE_NAME)
4520
4499
  return pkgPath;
4521
4500
  } catch {}
4522
4501
  }
4523
- const parent = path7.dirname(dir);
4502
+ const parent = path6.dirname(dir);
4524
4503
  if (parent === dir)
4525
4504
  break;
4526
4505
  dir = parent;
@@ -4536,19 +4515,28 @@ function getLocalDevVersion(directory) {
4536
4515
  const pkgPath = findPackageJsonUp(localPath);
4537
4516
  if (!pkgPath)
4538
4517
  return null;
4539
- const content = fs5.readFileSync(pkgPath, "utf-8");
4518
+ const content = fs4.readFileSync(pkgPath, "utf-8");
4540
4519
  const pkg = JSON.parse(content);
4541
4520
  return pkg.version ?? null;
4542
4521
  } catch {
4543
4522
  return null;
4544
4523
  }
4545
4524
  }
4525
+ function getCurrentRuntimePackageJsonPath(currentModuleUrl = import.meta.url) {
4526
+ try {
4527
+ const currentDir = path6.dirname(fileURLToPath(currentModuleUrl));
4528
+ return findPackageJsonUp(currentDir);
4529
+ } catch (err) {
4530
+ log("[auto-update-checker] Failed to resolve runtime package path:", err);
4531
+ return null;
4532
+ }
4533
+ }
4546
4534
  function findPluginEntry(directory) {
4547
4535
  for (const configPath of getConfigPaths(directory)) {
4548
4536
  try {
4549
- if (!fs5.existsSync(configPath))
4537
+ if (!fs4.existsSync(configPath))
4550
4538
  continue;
4551
- const content = fs5.readFileSync(configPath, "utf-8");
4539
+ const content = fs4.readFileSync(configPath, "utf-8");
4552
4540
  const config = JSON.parse(stripJsonComments(content));
4553
4541
  const plugins = config.plugin ?? [];
4554
4542
  for (const entry of plugins) {
@@ -4575,8 +4563,9 @@ function getCachedVersion() {
4575
4563
  if (cachedPackageVersion)
4576
4564
  return cachedPackageVersion;
4577
4565
  try {
4578
- if (fs5.existsSync(INSTALLED_PACKAGE_JSON)) {
4579
- const content = fs5.readFileSync(INSTALLED_PACKAGE_JSON, "utf-8");
4566
+ const runtimePackageJsonPath = getCurrentRuntimePackageJsonPath();
4567
+ if (runtimePackageJsonPath && fs4.existsSync(runtimePackageJsonPath)) {
4568
+ const content = fs4.readFileSync(runtimePackageJsonPath, "utf-8");
4580
4569
  const pkg = JSON.parse(content);
4581
4570
  if (pkg.version) {
4582
4571
  cachedPackageVersion = pkg.version;
@@ -4585,10 +4574,8 @@ function getCachedVersion() {
4585
4574
  }
4586
4575
  } catch {}
4587
4576
  try {
4588
- const currentDir = path7.dirname(fileURLToPath(import.meta.url));
4589
- const pkgPath = findPackageJsonUp(currentDir);
4590
- if (pkgPath) {
4591
- const content = fs5.readFileSync(pkgPath, "utf-8");
4577
+ if (fs4.existsSync(INSTALLED_PACKAGE_JSON)) {
4578
+ const content = fs4.readFileSync(INSTALLED_PACKAGE_JSON, "utf-8");
4592
4579
  const pkg = JSON.parse(content);
4593
4580
  if (pkg.version) {
4594
4581
  cachedPackageVersion = pkg.version;
@@ -4619,6 +4606,108 @@ async function getLatestVersion(channel = "latest") {
4619
4606
  }
4620
4607
  }
4621
4608
 
4609
+ // src/hooks/auto-update-checker/cache.ts
4610
+ function removeFromBunLock(installDir, packageName) {
4611
+ const lockPath = path7.join(installDir, "bun.lock");
4612
+ if (!fs5.existsSync(lockPath))
4613
+ return false;
4614
+ try {
4615
+ const content = fs5.readFileSync(lockPath, "utf-8");
4616
+ let lock;
4617
+ try {
4618
+ lock = JSON.parse(stripJsonComments(content));
4619
+ } catch {
4620
+ return false;
4621
+ }
4622
+ let modified = false;
4623
+ if (lock.workspaces?.[""]?.dependencies?.[packageName]) {
4624
+ delete lock.workspaces[""].dependencies[packageName];
4625
+ modified = true;
4626
+ }
4627
+ if (lock.packages?.[packageName]) {
4628
+ delete lock.packages[packageName];
4629
+ modified = true;
4630
+ }
4631
+ if (modified) {
4632
+ fs5.writeFileSync(lockPath, JSON.stringify(lock, null, 2));
4633
+ log(`[auto-update-checker] Removed from bun.lock: ${packageName}`);
4634
+ }
4635
+ return modified;
4636
+ } catch (err) {
4637
+ log(`[auto-update-checker] Failed to process bun.lock:`, err);
4638
+ return false;
4639
+ }
4640
+ }
4641
+ function ensureDependencyVersion(packageJsonPath, packageName, version) {
4642
+ if (!fs5.existsSync(packageJsonPath))
4643
+ return false;
4644
+ try {
4645
+ const content = fs5.readFileSync(packageJsonPath, "utf-8");
4646
+ const pkgJson = JSON.parse(stripJsonComments(content));
4647
+ const dependencies = { ...pkgJson.dependencies ?? {} };
4648
+ if (dependencies[packageName] === version) {
4649
+ return true;
4650
+ }
4651
+ dependencies[packageName] = version;
4652
+ pkgJson.dependencies = dependencies;
4653
+ fs5.writeFileSync(packageJsonPath, JSON.stringify(pkgJson, null, 2));
4654
+ log(`[auto-update-checker] Updated dependency in package.json: ${packageName} \u2192 ${version}`);
4655
+ return true;
4656
+ } catch (err) {
4657
+ log(`[auto-update-checker] Failed to update package.json dependency for auto-update:`, err);
4658
+ return false;
4659
+ }
4660
+ }
4661
+ function removeInstalledPackage(installDir, packageName) {
4662
+ const pkgDir = path7.join(installDir, "node_modules", packageName);
4663
+ if (!fs5.existsSync(pkgDir))
4664
+ return false;
4665
+ fs5.rmSync(pkgDir, { recursive: true, force: true });
4666
+ log(`[auto-update-checker] Package removed: ${pkgDir}`);
4667
+ return true;
4668
+ }
4669
+ function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
4670
+ if (runtimePackageJsonPath) {
4671
+ const packageDir = path7.dirname(runtimePackageJsonPath);
4672
+ const nodeModulesDir = path7.dirname(packageDir);
4673
+ if (path7.basename(packageDir) === PACKAGE_NAME && path7.basename(nodeModulesDir) === "node_modules") {
4674
+ const installDir = path7.dirname(nodeModulesDir);
4675
+ const packageJsonPath = path7.join(installDir, "package.json");
4676
+ if (fs5.existsSync(packageJsonPath)) {
4677
+ return { installDir, packageJsonPath };
4678
+ }
4679
+ }
4680
+ return null;
4681
+ }
4682
+ const legacyPackageJsonPath = path7.join(CACHE_DIR, "package.json");
4683
+ if (fs5.existsSync(legacyPackageJsonPath)) {
4684
+ return { installDir: CACHE_DIR, packageJsonPath: legacyPackageJsonPath };
4685
+ }
4686
+ return null;
4687
+ }
4688
+ function preparePackageUpdate(version, packageName = PACKAGE_NAME, runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
4689
+ try {
4690
+ const installContext = resolveInstallContext(runtimePackageJsonPath);
4691
+ if (!installContext) {
4692
+ log("[auto-update-checker] No install context found for auto-update");
4693
+ return null;
4694
+ }
4695
+ const dependencyReady = ensureDependencyVersion(installContext.packageJsonPath, packageName, version);
4696
+ if (!dependencyReady) {
4697
+ return null;
4698
+ }
4699
+ const packageRemoved = removeInstalledPackage(installContext.installDir, packageName);
4700
+ const lockRemoved = removeFromBunLock(installContext.installDir, packageName);
4701
+ if (!packageRemoved && !lockRemoved) {
4702
+ log(`[auto-update-checker] No cached package artifacts removed for ${packageName}; continuing with updated dependency spec`);
4703
+ }
4704
+ return installContext.installDir;
4705
+ } catch (err) {
4706
+ log("[auto-update-checker] Failed to prepare package update:", err);
4707
+ return null;
4708
+ }
4709
+ }
4710
+
4622
4711
  // src/hooks/auto-update-checker/index.ts
4623
4712
  function createAutoUpdateCheckerHook(ctx, options = {}) {
4624
4713
  const { showStartupToast = true, autoUpdate = true } = options;
@@ -4688,23 +4777,24 @@ Version is pinned. Update your plugin config to apply.`, "info", 8000);
4688
4777
  log("[auto-update-checker] Auto-update disabled, notification only");
4689
4778
  return;
4690
4779
  }
4691
- invalidatePackage(PACKAGE_NAME);
4692
- const installSuccess = await runBunInstallSafe();
4780
+ const installDir = preparePackageUpdate(latestVersion, PACKAGE_NAME);
4781
+ if (!installDir) {
4782
+ showToast(ctx, `OMO-Slim ${latestVersion}`, `v${latestVersion} available. Auto-update could not prepare the active install.`, "info", 8000);
4783
+ log("[auto-update-checker] Failed to prepare install root for auto-update");
4784
+ return;
4785
+ }
4786
+ const installSuccess = await runBunInstallSafe(installDir);
4693
4787
  if (installSuccess) {
4694
4788
  showToast(ctx, "OMO-Slim Updated!", `v${currentVersion} \u2192 v${latestVersion}
4695
4789
  Restart OpenCode to apply.`, "success", 8000);
4696
4790
  log(`[auto-update-checker] Update installed: ${currentVersion} \u2192 ${latestVersion}`);
4697
4791
  } else {
4698
- showToast(ctx, `OMO-Slim ${latestVersion}`, `v${latestVersion} available. Restart to apply.`, "info", 8000);
4792
+ showToast(ctx, `OMO-Slim ${latestVersion}`, `v${latestVersion} available, but auto-update failed to install it. Check logs or retry manually.`, "error", 8000);
4699
4793
  log("[auto-update-checker] bun install failed; update not installed");
4700
4794
  }
4701
4795
  }
4702
- function getAutoUpdateInstallDir() {
4703
- return CACHE_DIR;
4704
- }
4705
- async function runBunInstallSafe() {
4796
+ async function runBunInstallSafe(installDir) {
4706
4797
  try {
4707
- const installDir = getAutoUpdateInstallDir();
4708
4798
  const proc = Bun.spawn(["bun", "install"], {
4709
4799
  cwd: installDir,
4710
4800
  stdout: "pipe",
@@ -5297,6 +5387,126 @@ function createPostFileToolNudgeHook(options = {}) {
5297
5387
  }
5298
5388
  // src/hooks/todo-continuation/index.ts
5299
5389
  import { tool } from "@opencode-ai/plugin/tool";
5390
+
5391
+ // src/hooks/todo-continuation/todo-hygiene.ts
5392
+ var TODO_HYGIENE_REMINDER = "If the active task changed or finished, update the todo list to match the current work state.";
5393
+ var TODO_FINAL_ACTIVE_REMINDER = "If you are finishing now, do not leave the active todo in_progress. Mark it completed, or move unfinished work back to pending.";
5394
+ var RESET = new Set(["todowrite"]);
5395
+ var IGNORE = new Set(["auto_continue"]);
5396
+ function createTodoHygiene(options) {
5397
+ const pending = new Set;
5398
+ const done = new Set;
5399
+ function clear(sessionID) {
5400
+ pending.delete(sessionID);
5401
+ done.delete(sessionID);
5402
+ }
5403
+ function isFinalActive(state) {
5404
+ return state.inProgressCount === 1 && state.pendingCount === 0 && state.openCount === 1;
5405
+ }
5406
+ return {
5407
+ async handleToolExecuteAfter(input) {
5408
+ if (!input.sessionID) {
5409
+ return;
5410
+ }
5411
+ const tool = input.tool.toLowerCase();
5412
+ if (IGNORE.has(tool)) {
5413
+ return;
5414
+ }
5415
+ try {
5416
+ if (RESET.has(tool)) {
5417
+ const state = await options.getTodoState(input.sessionID);
5418
+ if (!state.hasOpenTodos) {
5419
+ clear(input.sessionID);
5420
+ options.log?.("Cleared todo hygiene cycle", {
5421
+ sessionID: input.sessionID,
5422
+ tool
5423
+ });
5424
+ return;
5425
+ }
5426
+ pending.delete(input.sessionID);
5427
+ done.delete(input.sessionID);
5428
+ if (isFinalActive(state)) {
5429
+ pending.add(input.sessionID);
5430
+ options.log?.("Armed final-active todo hygiene reminder", {
5431
+ sessionID: input.sessionID,
5432
+ tool
5433
+ });
5434
+ return;
5435
+ }
5436
+ options.log?.("Reset todo hygiene cycle", {
5437
+ sessionID: input.sessionID,
5438
+ tool
5439
+ });
5440
+ return;
5441
+ }
5442
+ if (pending.has(input.sessionID) || done.has(input.sessionID)) {
5443
+ return;
5444
+ }
5445
+ if (!(await options.getTodoState(input.sessionID)).hasOpenTodos) {
5446
+ return;
5447
+ }
5448
+ pending.add(input.sessionID);
5449
+ options.log?.("Armed todo hygiene reminder", {
5450
+ sessionID: input.sessionID,
5451
+ tool
5452
+ });
5453
+ } catch (error) {
5454
+ if (RESET.has(tool)) {
5455
+ clear(input.sessionID);
5456
+ }
5457
+ options.log?.("Skipped todo hygiene reminder: failed to inspect todos", {
5458
+ sessionID: input.sessionID,
5459
+ tool,
5460
+ error: error instanceof Error ? error.message : String(error)
5461
+ });
5462
+ }
5463
+ },
5464
+ async handleChatSystemTransform(input, output) {
5465
+ if (!input.sessionID || !pending.has(input.sessionID)) {
5466
+ return;
5467
+ }
5468
+ if (options.shouldInject && !options.shouldInject(input.sessionID)) {
5469
+ pending.delete(input.sessionID);
5470
+ done.add(input.sessionID);
5471
+ return;
5472
+ }
5473
+ try {
5474
+ const state = await options.getTodoState(input.sessionID);
5475
+ if (!state.hasOpenTodos) {
5476
+ clear(input.sessionID);
5477
+ return;
5478
+ }
5479
+ const finalActive = isFinalActive(state);
5480
+ const reminder = finalActive ? TODO_FINAL_ACTIVE_REMINDER : TODO_HYGIENE_REMINDER;
5481
+ pending.delete(input.sessionID);
5482
+ done.add(input.sessionID);
5483
+ output.system.push(reminder);
5484
+ options.log?.("Injected todo hygiene reminder", {
5485
+ sessionID: input.sessionID,
5486
+ reminder: finalActive ? "final-active" : "general"
5487
+ });
5488
+ } catch (error) {
5489
+ clear(input.sessionID);
5490
+ options.log?.("Skipped todo hygiene reminder: failed to inspect todos", {
5491
+ sessionID: input.sessionID,
5492
+ error: error instanceof Error ? error.message : String(error)
5493
+ });
5494
+ }
5495
+ },
5496
+ handleEvent(event) {
5497
+ if (event.type !== "session.deleted") {
5498
+ return;
5499
+ }
5500
+ const sessionID = event.properties?.sessionID ?? event.properties?.info?.id;
5501
+ if (!sessionID) {
5502
+ return;
5503
+ }
5504
+ clear(sessionID);
5505
+ }
5506
+ };
5507
+ }
5508
+
5509
+ // src/hooks/todo-continuation/index.ts
5300
5510
  var HOOK_NAME = "todo-continuation";
5301
5511
  var COMMAND_NAME = "auto-continue";
5302
5512
  var CONTINUATION_PROMPT = "[Auto-continue: enabled - there are incomplete todos remaining. Continue with the next uncompleted item. Press Esc to cancel. If you need user input or review for the next item, ask instead of proceeding.]";
@@ -5354,6 +5564,23 @@ function createTodoContinuationHook(ctx, config) {
5354
5564
  notifyingSessionIds: new Set,
5355
5565
  notificationBusyUntilBySession: new Map
5356
5566
  };
5567
+ const hygiene = createTodoHygiene({
5568
+ getTodoState: async (sessionID) => {
5569
+ const result = await ctx.client.session.todo({
5570
+ path: { id: sessionID }
5571
+ });
5572
+ const todos = result.data;
5573
+ const openTodos = todos.filter((todo) => !TERMINAL_TODO_STATUSES.includes(todo.status));
5574
+ return {
5575
+ hasOpenTodos: openTodos.length > 0,
5576
+ openCount: openTodos.length,
5577
+ inProgressCount: openTodos.filter((todo) => todo.status === "in_progress").length,
5578
+ pendingCount: openTodos.filter((todo) => todo.status === "pending").length
5579
+ };
5580
+ },
5581
+ shouldInject: (sessionID) => isOrchestratorSession(sessionID),
5582
+ log: (message, meta) => log(`[${HOOK_NAME}] ${message}`, meta)
5583
+ });
5357
5584
  function markNotificationStarted(sessionID) {
5358
5585
  state.notifyingSessionIds.add(sessionID);
5359
5586
  }
@@ -5411,6 +5638,13 @@ function createTodoContinuationHook(ctx, config) {
5411
5638
  async function handleEvent(input) {
5412
5639
  const { event } = input;
5413
5640
  const properties = event.properties ?? {};
5641
+ hygiene.handleEvent({
5642
+ type: event.type,
5643
+ properties: {
5644
+ info: properties.info,
5645
+ sessionID: properties.sessionID
5646
+ }
5647
+ });
5414
5648
  if (event.type === "session.idle" || event.type === "session.status" && properties.status?.type === "idle") {
5415
5649
  const sessionID = properties.sessionID;
5416
5650
  if (!sessionID) {
@@ -5692,6 +5926,8 @@ function createTodoContinuationHook(ctx, config) {
5692
5926
  }
5693
5927
  return {
5694
5928
  tool: { auto_continue: autoContinue },
5929
+ handleToolExecuteAfter: hygiene.handleToolExecuteAfter,
5930
+ handleChatSystemTransform: hygiene.handleChatSystemTransform,
5695
5931
  handleEvent,
5696
5932
  handleChatMessage,
5697
5933
  handleCommandExecuteBefore
@@ -7271,7 +7507,7 @@ var {spawn: spawn5 } = globalThis.Bun;
7271
7507
  // src/tools/ast-grep/constants.ts
7272
7508
  import { existsSync as existsSync6, statSync as statSync2 } from "fs";
7273
7509
  import { createRequire as createRequire2 } from "module";
7274
- import { dirname as dirname5, join as join9 } from "path";
7510
+ import { dirname as dirname6, join as join9 } from "path";
7275
7511
 
7276
7512
  // src/tools/ast-grep/downloader.ts
7277
7513
  import { chmodSync, existsSync as existsSync5, mkdirSync as mkdirSync2, unlinkSync } from "fs";
@@ -7428,7 +7664,7 @@ function findSgCliPathSync() {
7428
7664
  try {
7429
7665
  const require2 = createRequire2(import.meta.url);
7430
7666
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
7431
- const cliDir = dirname5(cliPkgPath);
7667
+ const cliDir = dirname6(cliPkgPath);
7432
7668
  const sgPath = join9(cliDir, binaryName);
7433
7669
  if (existsSync6(sgPath) && isValidBinary(sgPath)) {
7434
7670
  return sgPath;
@@ -7439,7 +7675,7 @@ function findSgCliPathSync() {
7439
7675
  try {
7440
7676
  const require2 = createRequire2(import.meta.url);
7441
7677
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
7442
- const pkgDir = dirname5(pkgPath);
7678
+ const pkgDir = dirname6(pkgPath);
7443
7679
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
7444
7680
  const binaryPath = join9(pkgDir, astGrepName);
7445
7681
  if (existsSync6(binaryPath) && isValidBinary(binaryPath)) {
@@ -8001,7 +8237,7 @@ import {
8001
8237
  // src/tools/lsp/config.ts
8002
8238
  import { existsSync as existsSync9 } from "fs";
8003
8239
  import { homedir as homedir4 } from "os";
8004
- import { dirname as dirname7, join as join10, resolve as resolve3 } from "path";
8240
+ import { dirname as dirname8, join as join10, resolve as resolve3 } from "path";
8005
8241
  import whichSync from "which";
8006
8242
 
8007
8243
  // src/tools/lsp/config-store.ts
@@ -8033,7 +8269,7 @@ function hasUserLspConfig() {
8033
8269
 
8034
8270
  // src/tools/lsp/constants.ts
8035
8271
  import { existsSync as existsSync8, readdirSync, statSync as statSync3 } from "fs";
8036
- import { dirname as dirname6, resolve as resolve2 } from "path";
8272
+ import { dirname as dirname7, resolve as resolve2 } from "path";
8037
8273
  var SEVERITY_MAP = {
8038
8274
  1: "error",
8039
8275
  2: "warning",
@@ -8053,10 +8289,10 @@ function* walkUpDirectories(start, stop) {
8053
8289
  let dir = resolve2(start);
8054
8290
  try {
8055
8291
  if (!statSync3(dir).isDirectory()) {
8056
- dir = dirname6(dir);
8292
+ dir = dirname7(dir);
8057
8293
  }
8058
8294
  } catch {
8059
- dir = dirname6(dir);
8295
+ dir = dirname7(dir);
8060
8296
  }
8061
8297
  let prevDir = "";
8062
8298
  while (dir !== prevDir && dir !== "/") {
@@ -8064,7 +8300,7 @@ function* walkUpDirectories(start, stop) {
8064
8300
  prevDir = dir;
8065
8301
  if (dir === stop)
8066
8302
  break;
8067
- dir = dirname6(dir);
8303
+ dir = dirname7(dir);
8068
8304
  }
8069
8305
  }
8070
8306
  function NearestRoot(includePatterns, excludePatterns) {
@@ -8610,7 +8846,7 @@ function getServerWorkspace(config, filePath) {
8610
8846
  return;
8611
8847
  }
8612
8848
  if (!config.root) {
8613
- return dirname7(resolve3(filePath));
8849
+ return dirname8(resolve3(filePath));
8614
8850
  }
8615
8851
  return config.root(filePath);
8616
8852
  }
@@ -8634,7 +8870,7 @@ function findInstalledServer(configs, filePath) {
8634
8870
  let firstNotInstalled = null;
8635
8871
  for (const config of configs) {
8636
8872
  const workspace = getServerWorkspace(config, filePath);
8637
- const resolvedCommand = resolveServerCommand(config.command, workspace ?? (filePath ? dirname7(resolve3(filePath)) : undefined));
8873
+ const resolvedCommand = resolveServerCommand(config.command, workspace ?? (filePath ? dirname8(resolve3(filePath)) : undefined));
8638
8874
  const server = toResolvedServer(config, resolvedCommand ?? undefined);
8639
8875
  log(`[LSP] Considering server for ${config.extensions.join(", ")}: ${config.id} with command ${config.command.join(" ")}`);
8640
8876
  if (resolvedCommand) {
@@ -8705,12 +8941,14 @@ function resolveServerCommand(command, cwd) {
8705
8941
  // src/tools/lsp/client.ts
8706
8942
  var START_TIMEOUT_MS = 5000;
8707
8943
  var REQUEST_TIMEOUT_MS = 5000;
8944
+ var DIAGNOSTICS_TIMEOUT_MS = 15000;
8708
8945
  var OPEN_FILE_DELAY_MS = 250;
8709
8946
  var INITIALIZE_DELAY_MS = 100;
8710
8947
  var DIAGNOSTIC_SETTLE_DELAY_MS = 250;
8711
8948
  var LSP_TIMEOUTS = {
8712
8949
  start: START_TIMEOUT_MS,
8713
8950
  request: REQUEST_TIMEOUT_MS,
8951
+ diagnostics: DIAGNOSTICS_TIMEOUT_MS,
8714
8952
  openFileDelay: OPEN_FILE_DELAY_MS,
8715
8953
  initializeDelay: INITIALIZE_DELAY_MS,
8716
8954
  diagnosticSettleDelay: DIAGNOSTIC_SETTLE_DELAY_MS
@@ -9135,7 +9373,7 @@ stderr: ${stderr}` : ""));
9135
9373
  await new Promise((r) => setTimeout(r, LSP_TIMEOUTS.initializeDelay));
9136
9374
  log("[lsp] LSPClient.initialize: complete", { server: this.server.id });
9137
9375
  }
9138
- async waitForPublishedDiagnostics(uri, timeoutMs = LSP_TIMEOUTS.request) {
9376
+ async waitForPublishedDiagnostics(uri, timeoutMs = LSP_TIMEOUTS.diagnostics) {
9139
9377
  const cachedDiagnostics = this.diagnosticsStore.get(uri);
9140
9378
  if (cachedDiagnostics) {
9141
9379
  return cachedDiagnostics;
@@ -9215,6 +9453,7 @@ stderr: ${stderr}` : ""));
9215
9453
  async diagnostics(filePath) {
9216
9454
  const absPath = resolve4(filePath);
9217
9455
  const uri = pathToFileURL(absPath).href;
9456
+ const startedAt = Date.now();
9218
9457
  await this.openFile(absPath);
9219
9458
  await new Promise((r) => setTimeout(r, LSP_TIMEOUTS.diagnosticSettleDelay));
9220
9459
  log("[lsp] diagnostics mode selected", {
@@ -9232,7 +9471,7 @@ stderr: ${stderr}` : ""));
9232
9471
  const result = this.connection ? await withTimeout(this.connection.sendRequest("textDocument/diagnostic", {
9233
9472
  textDocument: { uri },
9234
9473
  previousResultId: this.diagnosticResultIds.get(uri)
9235
- }), LSP_TIMEOUTS.request, `LSP diagnostics (${this.server.id})`) : undefined;
9474
+ }), LSP_TIMEOUTS.diagnostics, `LSP diagnostics (${this.server.id})`) : undefined;
9236
9475
  const report = result;
9237
9476
  if (report?.kind === "full") {
9238
9477
  if (report.resultId) {
@@ -9261,7 +9500,9 @@ stderr: ${stderr}` : ""));
9261
9500
  });
9262
9501
  }
9263
9502
  }
9264
- const cachedDiagnostics = await this.waitForPublishedDiagnostics(uri);
9503
+ const elapsed = Date.now() - startedAt;
9504
+ const remainingTimeout = Math.max(LSP_TIMEOUTS.diagnostics - elapsed, 0);
9505
+ const cachedDiagnostics = await this.waitForPublishedDiagnostics(uri, remainingTimeout);
9265
9506
  if (cachedDiagnostics) {
9266
9507
  return { items: cachedDiagnostics };
9267
9508
  }
@@ -9313,13 +9554,13 @@ import {
9313
9554
  unlinkSync as unlinkSync2,
9314
9555
  writeFileSync as writeFileSync3
9315
9556
  } from "fs";
9316
- import { dirname as dirname8, extname as extname3, join as join11, resolve as resolve5 } from "path";
9557
+ import { dirname as dirname9, extname as extname3, join as join11, resolve as resolve5 } from "path";
9317
9558
  import { fileURLToPath as fileURLToPath2 } from "url";
9318
9559
  function findServerProjectRoot(filePath, server) {
9319
9560
  if (server.root) {
9320
- return server.root(filePath) ?? dirname8(resolve5(filePath));
9561
+ return server.root(filePath) ?? dirname9(resolve5(filePath));
9321
9562
  }
9322
- return dirname8(resolve5(filePath));
9563
+ return dirname9(resolve5(filePath));
9323
9564
  }
9324
9565
  function uriToPath(uri) {
9325
9566
  return fileURLToPath2(uri);
@@ -9349,7 +9590,7 @@ async function withLspClient(filePath, fn) {
9349
9590
  throw new Error(formatServerLookupError(result));
9350
9591
  }
9351
9592
  const server = result.server;
9352
- const root = findServerProjectRoot(absPath, server) ?? dirname8(absPath);
9593
+ const root = findServerProjectRoot(absPath, server) ?? dirname9(absPath);
9353
9594
  log("[lsp] withLspClient: selected server", {
9354
9595
  filePath: absPath,
9355
9596
  extension: ext,
@@ -11545,6 +11786,13 @@ var OhMyOpenCodeLite = async (ctx) => {
11545
11786
  await multiplexerSessionManager.onSessionDeleted(input.event);
11546
11787
  await interviewManager.handleEvent(input);
11547
11788
  await postFileToolNudgeHook.event(input);
11789
+ if (input.event.type === "session.deleted") {
11790
+ const props = input.event.properties;
11791
+ const sessionID = props?.info?.id ?? props?.sessionID;
11792
+ if (sessionID) {
11793
+ sessionAgentMap.delete(sessionID);
11794
+ }
11795
+ }
11548
11796
  },
11549
11797
  "tool.execute.before": async (input, output) => {
11550
11798
  await applyPatchHook["tool.execute.before"](input, output);
@@ -11575,6 +11823,7 @@ var OhMyOpenCodeLite = async (ctx) => {
11575
11823
  ${output.system[0]}` : "");
11576
11824
  }
11577
11825
  }
11826
+ await todoContinuationHook.handleChatSystemTransform(input, output);
11578
11827
  await postFileToolNudgeHook["experimental.chat.system.transform"](input, output);
11579
11828
  },
11580
11829
  "experimental.chat.messages.transform": async (input, output) => {
@@ -11585,6 +11834,7 @@ ${output.system[0]}` : "");
11585
11834
  "tool.execute.after": async (input, output) => {
11586
11835
  await delegateTaskRetryHook["tool.execute.after"](input, output);
11587
11836
  await jsonErrorRecoveryHook["tool.execute.after"](input, output);
11837
+ await todoContinuationHook.handleToolExecuteAfter(input);
11588
11838
  await postFileToolNudgeHook["tool.execute.after"](input, output);
11589
11839
  }
11590
11840
  };
@@ -2,6 +2,7 @@ import type { Diagnostic, ResolvedServer } from './types';
2
2
  export declare const LSP_TIMEOUTS: {
3
3
  start: number;
4
4
  request: number;
5
+ diagnostics: number;
5
6
  openFileDelay: number;
6
7
  initializeDelay: number;
7
8
  diagnosticSettleDelay: number;
@@ -258,6 +258,13 @@
258
258
  "items": {
259
259
  "type": "string"
260
260
  }
261
+ },
262
+ "options": {
263
+ "type": "object",
264
+ "propertyNames": {
265
+ "type": "string"
266
+ },
267
+ "additionalProperties": {}
261
268
  }
262
269
  }
263
270
  }
@@ -321,6 +328,13 @@
321
328
  "items": {
322
329
  "type": "string"
323
330
  }
331
+ },
332
+ "options": {
333
+ "type": "object",
334
+ "propertyNames": {
335
+ "type": "string"
336
+ },
337
+ "additionalProperties": {}
324
338
  }
325
339
  }
326
340
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode-slim",
3
- "version": "0.9.10",
3
+ "version": "0.9.12",
4
4
  "description": "Lightweight agent orchestration plugin for OpenCode - a slimmed-down fork of oh-my-opencode",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -55,27 +55,27 @@
55
55
  "release:major": "npm version major && git push --follow-tags && npm publish"
56
56
  },
57
57
  "dependencies": {
58
+ "@ast-grep/cli": "^0.42.1",
59
+ "@modelcontextprotocol/sdk": "^1.29.0",
58
60
  "@mozilla/readability": "^0.6.0",
59
- "@ast-grep/cli": "^0.40.0",
60
- "@modelcontextprotocol/sdk": "^1.26.0",
61
- "@opencode-ai/plugin": "^1.2.6",
62
- "@opencode-ai/sdk": "^1.2.6",
61
+ "@opencode-ai/plugin": "^1.3.17",
62
+ "@opencode-ai/sdk": "^1.3.17",
63
63
  "jsdom": "^26.1.0",
64
- "lru-cache": "^11.2.2",
65
- "turndown": "^7.2.0",
66
- "vscode-jsonrpc": "^8.2.0",
64
+ "lru-cache": "^11.3.3",
65
+ "turndown": "^7.2.4",
66
+ "vscode-jsonrpc": "^8.2.1",
67
67
  "vscode-languageserver-protocol": "^3.17.5",
68
- "which": "^6.0.0",
68
+ "which": "^6.0.1",
69
69
  "zod": "^4.3.6"
70
70
  },
71
71
  "devDependencies": {
72
- "@biomejs/biome": "2.4.2",
72
+ "@biomejs/biome": "2.4.11",
73
73
  "@types/jsdom": "^21.1.7",
74
74
  "@types/node": "^24.6.1",
75
- "@types/turndown": "^5.0.5",
75
+ "@types/turndown": "^5.0.6",
76
76
  "@types/which": "^3.0.4",
77
77
  "all-contributors-cli": "^6.26.1",
78
- "bun-types": "1.3.9",
78
+ "bun-types": "1.3.12",
79
79
  "typescript": "^5.9.3"
80
80
  },
81
81
  "trustedDependencies": [
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: cartography
3
- description: Repository understanding and hierarchical codemap generation
3
+ description: Generate comprehensive hierarchical codemaps for UNFAMILIAR repositories. Expensive operation - only use when explicitly asked for codebase documentation or initial repository mapping
4
4
  ---
5
5
 
6
6
  # Cartography Skill