oh-my-opencode-slim 0.9.11 → 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";
@@ -481,8 +481,49 @@ function generateLiteConfig(installConfig) {
481
481
 
482
482
  // src/cli/config-io.ts
483
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
+ }
484
525
  function isPluginEntry(entry) {
485
- 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);
486
527
  }
487
528
  function getPluginEntry() {
488
529
  const cliEntryPath = process.argv[1];
@@ -490,11 +531,11 @@ function getPluginEntry() {
490
531
  return PACKAGE_NAME;
491
532
  }
492
533
  try {
493
- const pluginEntryPath = cliEntryPath.match(/[\\/]dist[\\/]cli[\\/]index\.js$/) ? cliEntryPath.replace(/[\\/]dist[\\/]cli[\\/]index\.js$/, "/dist/index.js") : null;
494
- if (!pluginEntryPath) {
534
+ const packageRoot = findPackageRoot(cliEntryPath);
535
+ if (!packageRoot || isPackageManagerInstall(packageRoot)) {
495
536
  return PACKAGE_NAME;
496
537
  }
497
- return pathToFileURL(pluginEntryPath).href;
538
+ return packageRoot;
498
539
  } catch {
499
540
  return PACKAGE_NAME;
500
541
  }
@@ -684,8 +725,35 @@ function detectCurrentConfig() {
684
725
  return result;
685
726
  }
686
727
  // src/cli/system.ts
728
+ import { spawnSync as spawnSync2 } from "child_process";
687
729
  import { statSync as statSync3 } from "fs";
688
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
+ }
689
757
  function getOpenCodePaths() {
690
758
  const home = process.env.HOME || process.env.USERPROFILE || "";
691
759
  return [
@@ -721,6 +789,11 @@ function resolveOpenCodePath() {
721
789
  if (cachedOpenCodePath) {
722
790
  return cachedOpenCodePath;
723
791
  }
792
+ const pathOpenCodePath = resolvePathCommand("opencode");
793
+ if (pathOpenCodePath) {
794
+ cachedOpenCodePath = pathOpenCodePath;
795
+ return pathOpenCodePath;
796
+ }
724
797
  const paths = getOpenCodePaths();
725
798
  for (const opencodePath of paths) {
726
799
  if (opencodePath === "opencode")
@@ -736,33 +809,31 @@ function resolveOpenCodePath() {
736
809
  return "opencode";
737
810
  }
738
811
  async function isOpenCodeInstalled() {
812
+ const pathOpenCodePath = resolvePathCommand("opencode");
813
+ if (pathOpenCodePath && canExecute(pathOpenCodePath, ["--version"])) {
814
+ cachedOpenCodePath = pathOpenCodePath;
815
+ return true;
816
+ }
739
817
  const paths = getOpenCodePaths();
740
818
  for (const opencodePath of paths) {
741
- try {
742
- const proc = Bun.spawn([opencodePath, "--version"], {
743
- stdout: "pipe",
744
- stderr: "pipe"
745
- });
746
- await proc.exited;
747
- if (proc.exitCode === 0) {
748
- cachedOpenCodePath = opencodePath;
749
- return true;
750
- }
751
- } catch {}
819
+ if (opencodePath === "opencode")
820
+ continue;
821
+ if (canExecute(opencodePath, ["--version"])) {
822
+ cachedOpenCodePath = opencodePath;
823
+ return true;
824
+ }
752
825
  }
753
826
  return false;
754
827
  }
755
828
  async function getOpenCodeVersion() {
756
829
  const opencodePath = resolveOpenCodePath();
757
830
  try {
758
- const proc = Bun.spawn([opencodePath, "--version"], {
759
- stdout: "pipe",
760
- stderr: "pipe"
831
+ const result = spawnSync2(opencodePath, ["--version"], {
832
+ encoding: "utf-8",
833
+ stdio: ["ignore", "pipe", "ignore"]
761
834
  });
762
- const output = await new Response(proc.stdout).text();
763
- await proc.exited;
764
- if (proc.exitCode === 0) {
765
- return output.trim();
835
+ if (result.status === 0) {
836
+ return result.stdout.trim();
766
837
  }
767
838
  } catch {}
768
839
  return null;
@@ -21,10 +21,7 @@ export type ResolvedPreparedUpdate = {
21
21
  nextText: string;
22
22
  };
23
23
  export declare function isMissingPathError(error: unknown): boolean;
24
- export declare function parseValidatedPatch(root: string, patchText: string, worktree?: string): Promise<{
25
- hunks: PatchHunk[];
26
- pathsNormalized: boolean;
27
- }>;
24
+ export declare function parseValidatedPatch(patchText: string): PatchHunk[];
28
25
  export declare function createPatchExecutionContext(root: string, patchText: string, worktree?: string): Promise<PatchExecutionContext>;
29
26
  export declare function resolvePreparedUpdate(filePath: string, currentText: string, hunk: UpdatePatchHunk, cfg: ApplyPatchRuntimeOptions): ResolvedPreparedUpdate;
30
27
  export declare function stageAddedText(contents: string): string;
@@ -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
@@ -3906,44 +3906,19 @@ function collectPatchTargets(root, hunks) {
3906
3906
  }
3907
3907
  return [...targets];
3908
3908
  }
3909
- function validatePatchPaths(hunks) {
3910
- for (const hunk of hunks) {
3911
- if (path3.isAbsolute(hunk.path)) {
3912
- throw createApplyPatchValidationError(`absolute patch paths are not allowed: ${hunk.path}`);
3913
- }
3914
- if (hunk.type === "update" && hunk.move_path && path3.isAbsolute(hunk.move_path)) {
3915
- throw createApplyPatchValidationError(`absolute patch paths are not allowed: ${hunk.move_path}`);
3916
- }
3917
- }
3918
- }
3919
- function toPortablePatchPath(filePath) {
3920
- return filePath.split(path3.sep).join("/");
3921
- }
3922
3909
  function toRelativePatchPath(root, target) {
3923
3910
  const relative = path3.relative(root, target);
3924
- return toPortablePatchPath(relative.length === 0 ? path3.basename(target) : relative);
3911
+ return (relative.length === 0 ? "." : relative).replaceAll("\\", "/");
3925
3912
  }
3926
- async function normalizeAbsolutePatchPath(root, worktree, value) {
3927
- if (!path3.isAbsolute(value)) {
3928
- return value;
3929
- }
3930
- const guardContext = createPathGuardContext(root, worktree);
3931
- const target = path3.resolve(value);
3932
- await guard(guardContext, target);
3933
- const [rootReal, targetReal] = await Promise.all([
3934
- guardContext.rootReal,
3935
- realCached(guardContext, target)
3936
- ]);
3937
- if (!inside(rootReal, targetReal)) {
3938
- throw createApplyPatchBlockedError(`patch contains path outside workspace root: ${target}`);
3939
- }
3940
- return toRelativePatchPath(root, target);
3913
+ function normalizePatchPath(root, value) {
3914
+ return path3.isAbsolute(value) ? toRelativePatchPath(root, path3.resolve(value)) : value;
3941
3915
  }
3942
- async function normalizeAbsolutePatchPaths(root, worktree, hunks) {
3916
+ function normalizePatchPaths(root, hunks) {
3917
+ const resolvedRoot = path3.resolve(root);
3943
3918
  const normalized = [];
3944
3919
  let changed = false;
3945
3920
  for (const hunk of hunks) {
3946
- const normalizedPath = await normalizeAbsolutePatchPath(root, worktree, hunk.path);
3921
+ const normalizedPath = normalizePatchPath(resolvedRoot, hunk.path);
3947
3922
  if (hunk.type !== "update") {
3948
3923
  changed ||= normalizedPath !== hunk.path;
3949
3924
  normalized.push(normalizedPath === hunk.path ? hunk : {
@@ -3952,7 +3927,7 @@ async function normalizeAbsolutePatchPaths(root, worktree, hunks) {
3952
3927
  });
3953
3928
  continue;
3954
3929
  }
3955
- const normalizedMovePath = hunk.move_path ? await normalizeAbsolutePatchPath(root, worktree, hunk.move_path) : undefined;
3930
+ const normalizedMovePath = hunk.move_path ? normalizePatchPath(resolvedRoot, hunk.move_path) : undefined;
3956
3931
  changed ||= normalizedPath !== hunk.path || normalizedMovePath !== hunk.move_path;
3957
3932
  normalized.push(normalizedPath === hunk.path && normalizedMovePath === hunk.move_path ? hunk : {
3958
3933
  ...hunk,
@@ -3969,7 +3944,7 @@ async function guardPatchTargets(root, worktree, targets) {
3969
3944
  }
3970
3945
  return targets.length;
3971
3946
  }
3972
- async function parseValidatedPatch(root, patchText, worktree) {
3947
+ function parseValidatedPatch(patchText) {
3973
3948
  let hunks;
3974
3949
  try {
3975
3950
  hunks = parsePatchStrict(patchText).hunks;
@@ -3986,12 +3961,7 @@ async function parseValidatedPatch(root, patchText, worktree) {
3986
3961
  }
3987
3962
  throw createApplyPatchValidationError("no hunks found");
3988
3963
  }
3989
- const normalizedPatch = await normalizeAbsolutePatchPaths(root, worktree, hunks);
3990
- validatePatchPaths(normalizedPatch.hunks);
3991
- return {
3992
- hunks: normalizedPatch.hunks,
3993
- pathsNormalized: normalizedPatch.changed
3994
- };
3964
+ return hunks;
3995
3965
  }
3996
3966
  async function readPreparedFileText(filePath, verb) {
3997
3967
  try {
@@ -4004,8 +3974,9 @@ async function readPreparedFileText(filePath, verb) {
4004
3974
  }
4005
3975
  }
4006
3976
  async function createPatchExecutionContext(root, patchText, worktree) {
4007
- const { hunks, pathsNormalized } = await parseValidatedPatch(root, patchText, worktree);
4008
- 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);
4009
3980
  const files = createFileCacheContext();
4010
3981
  const staged = new Map;
4011
3982
  async function assertPreparedPathMissing(filePath, verb) {
@@ -4043,8 +4014,8 @@ async function createPatchExecutionContext(root, patchText, worktree) {
4043
4014
  return state;
4044
4015
  }
4045
4016
  return {
4046
- hunks,
4047
- pathsNormalized,
4017
+ hunks: normalized.hunks,
4018
+ pathsNormalized: normalized.changed,
4048
4019
  staged,
4049
4020
  getPreparedFileState,
4050
4021
  assertPreparedPathMissing
@@ -4233,6 +4204,9 @@ async function rewritePatch(root, patchText, cfg, worktree) {
4233
4204
  let rewrittenChunks = 0;
4234
4205
  const rewriteModes = new Set;
4235
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
+ }
4236
4210
  const dependencyGroups = new Map;
4237
4211
  for (const hunk of hunks) {
4238
4212
  if (hunk.type === "add") {
@@ -4352,7 +4326,7 @@ async function rewritePatch(root, patchText, cfg, worktree) {
4352
4326
  changed: true,
4353
4327
  rewrittenChunks: 0,
4354
4328
  totalChunks,
4355
- rewriteModes: ["normalize:patch-paths"]
4329
+ rewriteModes: [...rewriteModes].sort()
4356
4330
  };
4357
4331
  }
4358
4332
  if (normalizedPatchText !== patchText) {
@@ -4435,8 +4409,13 @@ function createApplyPatchHook(ctx) {
4435
4409
  };
4436
4410
  }
4437
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
4438
4415
  import * as fs4 from "fs";
4439
4416
  import * as path6 from "path";
4417
+ import { fileURLToPath } from "url";
4418
+
4440
4419
  // src/hooks/auto-update-checker/constants.ts
4441
4420
  import * as os2 from "os";
4442
4421
  import * as path5 from "path";
@@ -4455,80 +4434,7 @@ var configPaths = getOpenCodeConfigPaths();
4455
4434
  var USER_OPENCODE_CONFIG = configPaths[0];
4456
4435
  var USER_OPENCODE_CONFIG_JSONC = configPaths[1];
4457
4436
 
4458
- // src/hooks/auto-update-checker/cache.ts
4459
- function removeFromBunLock(packageName) {
4460
- const lockPath = path6.join(CACHE_DIR, "bun.lock");
4461
- if (!fs4.existsSync(lockPath))
4462
- return false;
4463
- try {
4464
- const content = fs4.readFileSync(lockPath, "utf-8");
4465
- let lock;
4466
- try {
4467
- lock = JSON.parse(stripJsonComments(content));
4468
- } catch {
4469
- return false;
4470
- }
4471
- let modified = false;
4472
- if (lock.workspaces?.[""]?.dependencies?.[packageName]) {
4473
- delete lock.workspaces[""].dependencies[packageName];
4474
- modified = true;
4475
- }
4476
- if (lock.packages?.[packageName]) {
4477
- delete lock.packages[packageName];
4478
- modified = true;
4479
- }
4480
- if (modified) {
4481
- fs4.writeFileSync(lockPath, JSON.stringify(lock, null, 2));
4482
- log(`[auto-update-checker] Removed from bun.lock: ${packageName}`);
4483
- }
4484
- return modified;
4485
- } catch (err) {
4486
- log(`[auto-update-checker] Failed to process bun.lock:`, err);
4487
- return false;
4488
- }
4489
- }
4490
- function invalidatePackage(packageName = PACKAGE_NAME) {
4491
- try {
4492
- const pkgDir = path6.join(CACHE_DIR, "node_modules", packageName);
4493
- const pkgJsonPath = path6.join(CACHE_DIR, "package.json");
4494
- let packageRemoved = false;
4495
- let dependencyRemoved = false;
4496
- let lockRemoved = false;
4497
- if (fs4.existsSync(pkgDir)) {
4498
- fs4.rmSync(pkgDir, { recursive: true, force: true });
4499
- log(`[auto-update-checker] Package removed: ${pkgDir}`);
4500
- packageRemoved = true;
4501
- }
4502
- if (fs4.existsSync(pkgJsonPath)) {
4503
- try {
4504
- const content = fs4.readFileSync(pkgJsonPath, "utf-8");
4505
- const pkgJson = JSON.parse(stripJsonComments(content));
4506
- if (pkgJson.dependencies?.[packageName]) {
4507
- delete pkgJson.dependencies[packageName];
4508
- fs4.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
4509
- log(`[auto-update-checker] Dependency removed from package.json: ${packageName}`);
4510
- dependencyRemoved = true;
4511
- }
4512
- } catch (err) {
4513
- log(`[auto-update-checker] Failed to update package.json for invalidation:`, err);
4514
- }
4515
- }
4516
- lockRemoved = removeFromBunLock(packageName);
4517
- if (!packageRemoved && !dependencyRemoved && !lockRemoved) {
4518
- log(`[auto-update-checker] Package not found, nothing to invalidate: ${packageName}`);
4519
- return false;
4520
- }
4521
- return true;
4522
- } catch (err) {
4523
- log("[auto-update-checker] Failed to invalidate package:", err);
4524
- return false;
4525
- }
4526
- }
4527
-
4528
4437
  // src/hooks/auto-update-checker/checker.ts
4529
- import * as fs5 from "fs";
4530
- import * as path7 from "path";
4531
- import { fileURLToPath } from "url";
4532
4438
  function isPrereleaseVersion(version) {
4533
4439
  return version.includes("-");
4534
4440
  }
@@ -4552,8 +4458,8 @@ function extractChannel(version) {
4552
4458
  }
4553
4459
  function getConfigPaths(directory) {
4554
4460
  return [
4555
- path7.join(directory, ".opencode", "opencode.json"),
4556
- path7.join(directory, ".opencode", "opencode.jsonc"),
4461
+ path6.join(directory, ".opencode", "opencode.json"),
4462
+ path6.join(directory, ".opencode", "opencode.jsonc"),
4557
4463
  USER_OPENCODE_CONFIG,
4558
4464
  USER_OPENCODE_CONFIG_JSONC
4559
4465
  ];
@@ -4561,9 +4467,9 @@ function getConfigPaths(directory) {
4561
4467
  function getLocalDevPath(directory) {
4562
4468
  for (const configPath of getConfigPaths(directory)) {
4563
4469
  try {
4564
- if (!fs5.existsSync(configPath))
4470
+ if (!fs4.existsSync(configPath))
4565
4471
  continue;
4566
- const content = fs5.readFileSync(configPath, "utf-8");
4472
+ const content = fs4.readFileSync(configPath, "utf-8");
4567
4473
  const config = JSON.parse(stripJsonComments(content));
4568
4474
  const plugins = config.plugin ?? [];
4569
4475
  for (const entry of plugins) {
@@ -4581,19 +4487,19 @@ function getLocalDevPath(directory) {
4581
4487
  }
4582
4488
  function findPackageJsonUp(startPath) {
4583
4489
  try {
4584
- const stat2 = fs5.statSync(startPath);
4585
- let dir = stat2.isDirectory() ? startPath : path7.dirname(startPath);
4490
+ const stat2 = fs4.statSync(startPath);
4491
+ let dir = stat2.isDirectory() ? startPath : path6.dirname(startPath);
4586
4492
  for (let i = 0;i < 10; i++) {
4587
- const pkgPath = path7.join(dir, "package.json");
4588
- if (fs5.existsSync(pkgPath)) {
4493
+ const pkgPath = path6.join(dir, "package.json");
4494
+ if (fs4.existsSync(pkgPath)) {
4589
4495
  try {
4590
- const content = fs5.readFileSync(pkgPath, "utf-8");
4496
+ const content = fs4.readFileSync(pkgPath, "utf-8");
4591
4497
  const pkg = JSON.parse(content);
4592
4498
  if (pkg.name === PACKAGE_NAME)
4593
4499
  return pkgPath;
4594
4500
  } catch {}
4595
4501
  }
4596
- const parent = path7.dirname(dir);
4502
+ const parent = path6.dirname(dir);
4597
4503
  if (parent === dir)
4598
4504
  break;
4599
4505
  dir = parent;
@@ -4609,19 +4515,28 @@ function getLocalDevVersion(directory) {
4609
4515
  const pkgPath = findPackageJsonUp(localPath);
4610
4516
  if (!pkgPath)
4611
4517
  return null;
4612
- const content = fs5.readFileSync(pkgPath, "utf-8");
4518
+ const content = fs4.readFileSync(pkgPath, "utf-8");
4613
4519
  const pkg = JSON.parse(content);
4614
4520
  return pkg.version ?? null;
4615
4521
  } catch {
4616
4522
  return null;
4617
4523
  }
4618
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
+ }
4619
4534
  function findPluginEntry(directory) {
4620
4535
  for (const configPath of getConfigPaths(directory)) {
4621
4536
  try {
4622
- if (!fs5.existsSync(configPath))
4537
+ if (!fs4.existsSync(configPath))
4623
4538
  continue;
4624
- const content = fs5.readFileSync(configPath, "utf-8");
4539
+ const content = fs4.readFileSync(configPath, "utf-8");
4625
4540
  const config = JSON.parse(stripJsonComments(content));
4626
4541
  const plugins = config.plugin ?? [];
4627
4542
  for (const entry of plugins) {
@@ -4648,8 +4563,9 @@ function getCachedVersion() {
4648
4563
  if (cachedPackageVersion)
4649
4564
  return cachedPackageVersion;
4650
4565
  try {
4651
- if (fs5.existsSync(INSTALLED_PACKAGE_JSON)) {
4652
- 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");
4653
4569
  const pkg = JSON.parse(content);
4654
4570
  if (pkg.version) {
4655
4571
  cachedPackageVersion = pkg.version;
@@ -4658,10 +4574,8 @@ function getCachedVersion() {
4658
4574
  }
4659
4575
  } catch {}
4660
4576
  try {
4661
- const currentDir = path7.dirname(fileURLToPath(import.meta.url));
4662
- const pkgPath = findPackageJsonUp(currentDir);
4663
- if (pkgPath) {
4664
- const content = fs5.readFileSync(pkgPath, "utf-8");
4577
+ if (fs4.existsSync(INSTALLED_PACKAGE_JSON)) {
4578
+ const content = fs4.readFileSync(INSTALLED_PACKAGE_JSON, "utf-8");
4665
4579
  const pkg = JSON.parse(content);
4666
4580
  if (pkg.version) {
4667
4581
  cachedPackageVersion = pkg.version;
@@ -4692,6 +4606,108 @@ async function getLatestVersion(channel = "latest") {
4692
4606
  }
4693
4607
  }
4694
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
+
4695
4711
  // src/hooks/auto-update-checker/index.ts
4696
4712
  function createAutoUpdateCheckerHook(ctx, options = {}) {
4697
4713
  const { showStartupToast = true, autoUpdate = true } = options;
@@ -4761,23 +4777,24 @@ Version is pinned. Update your plugin config to apply.`, "info", 8000);
4761
4777
  log("[auto-update-checker] Auto-update disabled, notification only");
4762
4778
  return;
4763
4779
  }
4764
- invalidatePackage(PACKAGE_NAME);
4765
- 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);
4766
4787
  if (installSuccess) {
4767
4788
  showToast(ctx, "OMO-Slim Updated!", `v${currentVersion} \u2192 v${latestVersion}
4768
4789
  Restart OpenCode to apply.`, "success", 8000);
4769
4790
  log(`[auto-update-checker] Update installed: ${currentVersion} \u2192 ${latestVersion}`);
4770
4791
  } else {
4771
- 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);
4772
4793
  log("[auto-update-checker] bun install failed; update not installed");
4773
4794
  }
4774
4795
  }
4775
- function getAutoUpdateInstallDir() {
4776
- return CACHE_DIR;
4777
- }
4778
- async function runBunInstallSafe() {
4796
+ async function runBunInstallSafe(installDir) {
4779
4797
  try {
4780
- const installDir = getAutoUpdateInstallDir();
4781
4798
  const proc = Bun.spawn(["bun", "install"], {
4782
4799
  cwd: installDir,
4783
4800
  stdout: "pipe",
@@ -5370,6 +5387,126 @@ function createPostFileToolNudgeHook(options = {}) {
5370
5387
  }
5371
5388
  // src/hooks/todo-continuation/index.ts
5372
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
5373
5510
  var HOOK_NAME = "todo-continuation";
5374
5511
  var COMMAND_NAME = "auto-continue";
5375
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.]";
@@ -5427,6 +5564,23 @@ function createTodoContinuationHook(ctx, config) {
5427
5564
  notifyingSessionIds: new Set,
5428
5565
  notificationBusyUntilBySession: new Map
5429
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
+ });
5430
5584
  function markNotificationStarted(sessionID) {
5431
5585
  state.notifyingSessionIds.add(sessionID);
5432
5586
  }
@@ -5484,6 +5638,13 @@ function createTodoContinuationHook(ctx, config) {
5484
5638
  async function handleEvent(input) {
5485
5639
  const { event } = input;
5486
5640
  const properties = event.properties ?? {};
5641
+ hygiene.handleEvent({
5642
+ type: event.type,
5643
+ properties: {
5644
+ info: properties.info,
5645
+ sessionID: properties.sessionID
5646
+ }
5647
+ });
5487
5648
  if (event.type === "session.idle" || event.type === "session.status" && properties.status?.type === "idle") {
5488
5649
  const sessionID = properties.sessionID;
5489
5650
  if (!sessionID) {
@@ -5765,6 +5926,8 @@ function createTodoContinuationHook(ctx, config) {
5765
5926
  }
5766
5927
  return {
5767
5928
  tool: { auto_continue: autoContinue },
5929
+ handleToolExecuteAfter: hygiene.handleToolExecuteAfter,
5930
+ handleChatSystemTransform: hygiene.handleChatSystemTransform,
5768
5931
  handleEvent,
5769
5932
  handleChatMessage,
5770
5933
  handleCommandExecuteBefore
@@ -7344,7 +7507,7 @@ var {spawn: spawn5 } = globalThis.Bun;
7344
7507
  // src/tools/ast-grep/constants.ts
7345
7508
  import { existsSync as existsSync6, statSync as statSync2 } from "fs";
7346
7509
  import { createRequire as createRequire2 } from "module";
7347
- import { dirname as dirname5, join as join9 } from "path";
7510
+ import { dirname as dirname6, join as join9 } from "path";
7348
7511
 
7349
7512
  // src/tools/ast-grep/downloader.ts
7350
7513
  import { chmodSync, existsSync as existsSync5, mkdirSync as mkdirSync2, unlinkSync } from "fs";
@@ -7501,7 +7664,7 @@ function findSgCliPathSync() {
7501
7664
  try {
7502
7665
  const require2 = createRequire2(import.meta.url);
7503
7666
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
7504
- const cliDir = dirname5(cliPkgPath);
7667
+ const cliDir = dirname6(cliPkgPath);
7505
7668
  const sgPath = join9(cliDir, binaryName);
7506
7669
  if (existsSync6(sgPath) && isValidBinary(sgPath)) {
7507
7670
  return sgPath;
@@ -7512,7 +7675,7 @@ function findSgCliPathSync() {
7512
7675
  try {
7513
7676
  const require2 = createRequire2(import.meta.url);
7514
7677
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
7515
- const pkgDir = dirname5(pkgPath);
7678
+ const pkgDir = dirname6(pkgPath);
7516
7679
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
7517
7680
  const binaryPath = join9(pkgDir, astGrepName);
7518
7681
  if (existsSync6(binaryPath) && isValidBinary(binaryPath)) {
@@ -8074,7 +8237,7 @@ import {
8074
8237
  // src/tools/lsp/config.ts
8075
8238
  import { existsSync as existsSync9 } from "fs";
8076
8239
  import { homedir as homedir4 } from "os";
8077
- 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";
8078
8241
  import whichSync from "which";
8079
8242
 
8080
8243
  // src/tools/lsp/config-store.ts
@@ -8106,7 +8269,7 @@ function hasUserLspConfig() {
8106
8269
 
8107
8270
  // src/tools/lsp/constants.ts
8108
8271
  import { existsSync as existsSync8, readdirSync, statSync as statSync3 } from "fs";
8109
- import { dirname as dirname6, resolve as resolve2 } from "path";
8272
+ import { dirname as dirname7, resolve as resolve2 } from "path";
8110
8273
  var SEVERITY_MAP = {
8111
8274
  1: "error",
8112
8275
  2: "warning",
@@ -8126,10 +8289,10 @@ function* walkUpDirectories(start, stop) {
8126
8289
  let dir = resolve2(start);
8127
8290
  try {
8128
8291
  if (!statSync3(dir).isDirectory()) {
8129
- dir = dirname6(dir);
8292
+ dir = dirname7(dir);
8130
8293
  }
8131
8294
  } catch {
8132
- dir = dirname6(dir);
8295
+ dir = dirname7(dir);
8133
8296
  }
8134
8297
  let prevDir = "";
8135
8298
  while (dir !== prevDir && dir !== "/") {
@@ -8137,7 +8300,7 @@ function* walkUpDirectories(start, stop) {
8137
8300
  prevDir = dir;
8138
8301
  if (dir === stop)
8139
8302
  break;
8140
- dir = dirname6(dir);
8303
+ dir = dirname7(dir);
8141
8304
  }
8142
8305
  }
8143
8306
  function NearestRoot(includePatterns, excludePatterns) {
@@ -8683,7 +8846,7 @@ function getServerWorkspace(config, filePath) {
8683
8846
  return;
8684
8847
  }
8685
8848
  if (!config.root) {
8686
- return dirname7(resolve3(filePath));
8849
+ return dirname8(resolve3(filePath));
8687
8850
  }
8688
8851
  return config.root(filePath);
8689
8852
  }
@@ -8707,7 +8870,7 @@ function findInstalledServer(configs, filePath) {
8707
8870
  let firstNotInstalled = null;
8708
8871
  for (const config of configs) {
8709
8872
  const workspace = getServerWorkspace(config, filePath);
8710
- const resolvedCommand = resolveServerCommand(config.command, workspace ?? (filePath ? dirname7(resolve3(filePath)) : undefined));
8873
+ const resolvedCommand = resolveServerCommand(config.command, workspace ?? (filePath ? dirname8(resolve3(filePath)) : undefined));
8711
8874
  const server = toResolvedServer(config, resolvedCommand ?? undefined);
8712
8875
  log(`[LSP] Considering server for ${config.extensions.join(", ")}: ${config.id} with command ${config.command.join(" ")}`);
8713
8876
  if (resolvedCommand) {
@@ -8778,12 +8941,14 @@ function resolveServerCommand(command, cwd) {
8778
8941
  // src/tools/lsp/client.ts
8779
8942
  var START_TIMEOUT_MS = 5000;
8780
8943
  var REQUEST_TIMEOUT_MS = 5000;
8944
+ var DIAGNOSTICS_TIMEOUT_MS = 15000;
8781
8945
  var OPEN_FILE_DELAY_MS = 250;
8782
8946
  var INITIALIZE_DELAY_MS = 100;
8783
8947
  var DIAGNOSTIC_SETTLE_DELAY_MS = 250;
8784
8948
  var LSP_TIMEOUTS = {
8785
8949
  start: START_TIMEOUT_MS,
8786
8950
  request: REQUEST_TIMEOUT_MS,
8951
+ diagnostics: DIAGNOSTICS_TIMEOUT_MS,
8787
8952
  openFileDelay: OPEN_FILE_DELAY_MS,
8788
8953
  initializeDelay: INITIALIZE_DELAY_MS,
8789
8954
  diagnosticSettleDelay: DIAGNOSTIC_SETTLE_DELAY_MS
@@ -9208,7 +9373,7 @@ stderr: ${stderr}` : ""));
9208
9373
  await new Promise((r) => setTimeout(r, LSP_TIMEOUTS.initializeDelay));
9209
9374
  log("[lsp] LSPClient.initialize: complete", { server: this.server.id });
9210
9375
  }
9211
- async waitForPublishedDiagnostics(uri, timeoutMs = LSP_TIMEOUTS.request) {
9376
+ async waitForPublishedDiagnostics(uri, timeoutMs = LSP_TIMEOUTS.diagnostics) {
9212
9377
  const cachedDiagnostics = this.diagnosticsStore.get(uri);
9213
9378
  if (cachedDiagnostics) {
9214
9379
  return cachedDiagnostics;
@@ -9288,6 +9453,7 @@ stderr: ${stderr}` : ""));
9288
9453
  async diagnostics(filePath) {
9289
9454
  const absPath = resolve4(filePath);
9290
9455
  const uri = pathToFileURL(absPath).href;
9456
+ const startedAt = Date.now();
9291
9457
  await this.openFile(absPath);
9292
9458
  await new Promise((r) => setTimeout(r, LSP_TIMEOUTS.diagnosticSettleDelay));
9293
9459
  log("[lsp] diagnostics mode selected", {
@@ -9305,7 +9471,7 @@ stderr: ${stderr}` : ""));
9305
9471
  const result = this.connection ? await withTimeout(this.connection.sendRequest("textDocument/diagnostic", {
9306
9472
  textDocument: { uri },
9307
9473
  previousResultId: this.diagnosticResultIds.get(uri)
9308
- }), LSP_TIMEOUTS.request, `LSP diagnostics (${this.server.id})`) : undefined;
9474
+ }), LSP_TIMEOUTS.diagnostics, `LSP diagnostics (${this.server.id})`) : undefined;
9309
9475
  const report = result;
9310
9476
  if (report?.kind === "full") {
9311
9477
  if (report.resultId) {
@@ -9334,7 +9500,9 @@ stderr: ${stderr}` : ""));
9334
9500
  });
9335
9501
  }
9336
9502
  }
9337
- 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);
9338
9506
  if (cachedDiagnostics) {
9339
9507
  return { items: cachedDiagnostics };
9340
9508
  }
@@ -9386,13 +9554,13 @@ import {
9386
9554
  unlinkSync as unlinkSync2,
9387
9555
  writeFileSync as writeFileSync3
9388
9556
  } from "fs";
9389
- 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";
9390
9558
  import { fileURLToPath as fileURLToPath2 } from "url";
9391
9559
  function findServerProjectRoot(filePath, server) {
9392
9560
  if (server.root) {
9393
- return server.root(filePath) ?? dirname8(resolve5(filePath));
9561
+ return server.root(filePath) ?? dirname9(resolve5(filePath));
9394
9562
  }
9395
- return dirname8(resolve5(filePath));
9563
+ return dirname9(resolve5(filePath));
9396
9564
  }
9397
9565
  function uriToPath(uri) {
9398
9566
  return fileURLToPath2(uri);
@@ -9422,7 +9590,7 @@ async function withLspClient(filePath, fn) {
9422
9590
  throw new Error(formatServerLookupError(result));
9423
9591
  }
9424
9592
  const server = result.server;
9425
- const root = findServerProjectRoot(absPath, server) ?? dirname8(absPath);
9593
+ const root = findServerProjectRoot(absPath, server) ?? dirname9(absPath);
9426
9594
  log("[lsp] withLspClient: selected server", {
9427
9595
  filePath: absPath,
9428
9596
  extension: ext,
@@ -11618,6 +11786,13 @@ var OhMyOpenCodeLite = async (ctx) => {
11618
11786
  await multiplexerSessionManager.onSessionDeleted(input.event);
11619
11787
  await interviewManager.handleEvent(input);
11620
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
+ }
11621
11796
  },
11622
11797
  "tool.execute.before": async (input, output) => {
11623
11798
  await applyPatchHook["tool.execute.before"](input, output);
@@ -11648,6 +11823,7 @@ var OhMyOpenCodeLite = async (ctx) => {
11648
11823
  ${output.system[0]}` : "");
11649
11824
  }
11650
11825
  }
11826
+ await todoContinuationHook.handleChatSystemTransform(input, output);
11651
11827
  await postFileToolNudgeHook["experimental.chat.system.transform"](input, output);
11652
11828
  },
11653
11829
  "experimental.chat.messages.transform": async (input, output) => {
@@ -11658,6 +11834,7 @@ ${output.system[0]}` : "");
11658
11834
  "tool.execute.after": async (input, output) => {
11659
11835
  await delegateTaskRetryHook["tool.execute.after"](input, output);
11660
11836
  await jsonErrorRecoveryHook["tool.execute.after"](input, output);
11837
+ await todoContinuationHook.handleToolExecuteAfter(input);
11661
11838
  await postFileToolNudgeHook["tool.execute.after"](input, output);
11662
11839
  }
11663
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode-slim",
3
- "version": "0.9.11",
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