omo-ai 5.1.35 → 5.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/bin/lib/engine-environment.js +113 -0
  2. package/bin/lib/herdr-agent-hint.js +29 -0
  3. package/bin/lib/launcher.js +7 -102
  4. package/bin/lib/provider-map.json +1 -1
  5. package/bin/lib/setup-assets-import.js +2 -1
  6. package/bin/lib/setup-model-choices-import.js +5 -1
  7. package/package.json +2 -2
  8. package/plugin/CHANGELOG.md +100 -0
  9. package/plugin/extensions/gateway-rules-extension.mjs +1 -1
  10. package/plugin/extensions/gateway-store-worker.mjs +2 -2
  11. package/plugin/extensions/memory-run-supervisor.mjs +2 -2
  12. package/plugin/extensions/omo-computer-use.js +2 -2
  13. package/plugin/extensions/omo-init-deep-advisor.js +1 -1
  14. package/plugin/extensions/omo-member.js +2 -2
  15. package/plugin/extensions/omo-memory-doctor.js +2 -2
  16. package/plugin/extensions/omo-memory-memfs.js +2 -2
  17. package/plugin/extensions/omo-memory-palace.js +2 -2
  18. package/plugin/extensions/omo-side-panel.js +2 -2
  19. package/plugin/extensions/omo-task.js +2 -2
  20. package/plugin/extensions/omo-thread-client.js +2 -2
  21. package/plugin/extensions/omo.js +2 -2
  22. package/plugin/package.json +1 -1
  23. package/plugin/runtime/agent-toolkit-sdk/sdk.js +2 -2
  24. package/plugin/runtime/ast-grep-mcp/manifest.json +1 -1
  25. package/plugin/runtime/category-coverage/index.js +1 -1
  26. package/plugin/runtime/lsp-daemon/dist/.omo-runtime-manifest.json +1 -1
  27. package/plugin/runtime/rollback-migrate.js +2 -2
  28. package/plugin/runtime/thread-sdk/sdk.js +2 -2
  29. package/plugin/skills/browser/runtime/omowright/index.js +4 -4
  30. package/plugin/skills/browser/runtime/omowright/manifest.json +2 -2
  31. package/plugin/skills/frontend/SKILL.md +8 -1
  32. package/plugin/skills/frontend/references/design/README.md +3 -2
  33. package/plugin/skills/frontend/references/perfection/README.md +2 -1
  34. package/plugin/skills/frontend/scripts/perfection/accent-stripes/color.ts +132 -0
  35. package/plugin/skills/frontend/scripts/perfection/accent-stripes/css-parse.ts +103 -0
  36. package/plugin/skills/frontend/scripts/perfection/accent-stripes/declarations.ts +156 -0
  37. package/plugin/skills/frontend/scripts/perfection/accent-stripes/findings.ts +34 -0
  38. package/plugin/skills/frontend/scripts/perfection/accent-stripes/gradient.ts +70 -0
  39. package/plugin/skills/frontend/scripts/perfection/accent-stripes/lint.ts +110 -0
  40. package/plugin/skills/frontend/scripts/perfection/accent-stripes/markup.ts +148 -0
  41. package/plugin/skills/frontend/scripts/perfection/accent-stripes/selector.ts +46 -0
  42. package/plugin/skills/frontend/scripts/perfection/accent-stripes/tailwind.ts +117 -0
  43. package/plugin/skills/frontend/scripts/perfection/check-accent-stripes.ts +50 -0
  44. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/negative/exempt-states.css +4 -0
  45. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/negative/flat-stripe-side.tsx +11 -0
  46. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/negative/full-surface-gradients.css +34 -0
  47. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/negative/inline-ok.tsx +7 -0
  48. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/negative/neutral-and-flat.css +6 -0
  49. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/negative/pseudo-overlays.css +3 -0
  50. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/negative/tailwind-ok.tsx +11 -0
  51. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/negative/uniform-and-rings.css +3 -0
  52. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/negative/wide-blocks-and-hairlines.css +3 -0
  53. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/positive/apply.css +3 -0
  54. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/positive/border-top-card.css +5 -0
  55. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/positive/cva-variant.ts +1 -0
  56. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/positive/gradient-edges.css +26 -0
  57. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/positive/inline-styles.tsx +6 -0
  58. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/positive/inset-shadow.css +4 -0
  59. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/positive/non-visible-focus-and-nested.tsx +6 -0
  60. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/positive/pseudo-bar.css +13 -0
  61. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/positive/split-rule-and-token.scss +14 -0
  62. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/positive/styled-components.tsx +14 -0
  63. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/positive/tailwind-cards.tsx +8 -0
  64. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/positive/tailwind-inset-and-style.vue +11 -0
  65. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/positive/tailwind-pseudo.html +3 -0
@@ -0,0 +1,113 @@
1
+ import { existsSync, realpathSync } from "node:fs"
2
+ import { delimiter, isAbsolute, join, relative, sep } from "node:path"
3
+ import { canonicalAgentDir } from "./agent-dir.js"
4
+ import { HERDR_AGENT_MARKER } from "./herdr-agent-hint.js"
5
+ import { nearestNodeBin, packageManifest, packageRoot, readJson, releaseChannel, updateTarget } from "./package-paths.js"
6
+
7
+ // Identity the engine adopts for this install: what the user sees, where state lives, which
8
+ // environment prefix is read first, what goes on the wire, and which channel to check for
9
+ // updates. The engine consumes this once and scrubs it, so nested engine processes are
10
+ // unaffected.
11
+ function pluginChangelogSource() {
12
+ try {
13
+ const pluginRoot = join(packageRoot, "plugin")
14
+ const changelogPath = join(pluginRoot, "CHANGELOG.md")
15
+ if (!existsSync(changelogPath)) return undefined
16
+ const version = readJson(join(pluginRoot, "package.json")).version
17
+ return typeof version === "string" && version ? { path: changelogPath, version } : { path: changelogPath }
18
+ } catch {
19
+ return undefined
20
+ }
21
+ }
22
+
23
+ function brandProfile() {
24
+ const update = updateTarget()
25
+ // The changelog source is advisory: a missing plugin manifest or file must disable
26
+ // startup notes, never fail the launch.
27
+ const changelog = pluginChangelogSource()
28
+ return {
29
+ name: "OmO",
30
+ command: "omo",
31
+ displayVersion: packageManifest().version,
32
+ configDir: ".omo",
33
+ // Engine state lives at the canonical `~/.omo/agent`, never directly under the config
34
+ // directory: a flat home would disagree with the directory every omo surface resolves.
35
+ flatLayout: false,
36
+ envPrefix: "OMO",
37
+ userAgent: "omo",
38
+ originator: "omo",
39
+ ...(changelog ? { changelog } : {}),
40
+ update: {
41
+ packageName: "omo-ai",
42
+ distTag: releaseChannel(),
43
+ command: update.command,
44
+ changelogUrl: "https://github.com/code-yeongyu/oh-my-openagent/releases",
45
+ },
46
+ }
47
+ }
48
+
49
+ function canonicalPath(path) {
50
+ try {
51
+ return realpathSync.native(path)
52
+ } catch {
53
+ return path
54
+ }
55
+ }
56
+
57
+ function containsPath(root, target) {
58
+ const rel = relative(canonicalPath(root), canonicalPath(target))
59
+ return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel))
60
+ }
61
+
62
+ // A compiled omo session exports its own payload as the brand-scoped package dir, and every shell it
63
+ // spawns inherits that. The engine reads these names before the legacy PI_PACKAGE_DIR, so a release
64
+ // omo started from such a shell would run on the foreign payload. A deliberate relocation names this
65
+ // install's engine and survives; a root that does not contain the engine belongs to another install.
66
+ function dropForeignPackageDirs(env, senpiRoot) {
67
+ for (const name of ["OMO_PACKAGE_DIR", "SENPI_PACKAGE_DIR"]) {
68
+ const root = env[name]
69
+ if (root && !containsPath(root, senpiRoot)) delete env[name]
70
+ }
71
+ }
72
+
73
+ /**
74
+ * The one environment every engine this launcher starts gets. `overrides` (a host call's own
75
+ * environment) is merged in here, before the last step, so nothing a caller passes can put back
76
+ * what this function removes.
77
+ */
78
+ export function senpiEnvironment(senpiRoot, overrides = {}) {
79
+ const env = { ...process.env }
80
+ delete env.OMO_BIN
81
+ delete env.SENPI_BIN
82
+ dropForeignPackageDirs(env, senpiRoot)
83
+ // One directory for every surface. The legacy name travels too, so a bare senpi spawned by a
84
+ // tool inherits the same state instead of falling back to its own home.
85
+ const agentDir = canonicalAgentDir(env)
86
+ env.OMO_CODING_AGENT_DIR = agentDir
87
+ env.SENPI_CODING_AGENT_DIR = agentDir
88
+ // senpi's footer reads this marker to show the OmO Native badge for omo-ai installs, which load
89
+ // the plugin via --extension and therefore never match the settings-packages detection gates.
90
+ env.OMO_NATIVE = "1"
91
+ // This launcher already decided which runtime the product runs on, possibly by re-execing itself
92
+ // under bun. Handing that answer down stops the engine from making its own, conflicting choice.
93
+ env.SENPI_RUNTIME = process.versions.bun ? "bun" : "node"
94
+ env.SENPI_BRAND = JSON.stringify(brandProfile())
95
+ const binDir = nearestNodeBin(senpiRoot)
96
+ if (binDir) {
97
+ const pathKey = Object.keys(env).find((key) => key.toLowerCase() === "path") ?? "PATH"
98
+ const path = env[pathKey]
99
+ env[pathKey] = path ? `${binDir}${delimiter}${path}` : binDir
100
+ const shim = join(binDir, process.platform === "win32" ? "senpi.cmd" : "senpi")
101
+ if (existsSync(shim)) env.SENPI_BIN = shim
102
+ }
103
+ // Anything resolving the product by name must re-enter through this launcher, otherwise it
104
+ // would reach the engine directly and lose the brand.
105
+ env.OMO_BIN = join(packageRoot, "bin", "omo.js")
106
+ const merged = { ...env, ...overrides }
107
+ // Only the interactive launch writes this record of the herdr hint it set (herdr-agent-hint.js),
108
+ // after this function returns. No engine started here may carry one from the parent environment
109
+ // or from a caller's overrides: the plugin would treat it as the launcher's and could remove a
110
+ // HERDR_AGENT the user set (#10448).
111
+ delete merged[HERDR_AGENT_MARKER]
112
+ return merged
113
+ }
@@ -0,0 +1,29 @@
1
+ // herdr names the agent that owns a pane from the pane's foreground process (#10448). The engine
2
+ // runs as `bun …/cli.js` (or `node …/cli.js`), which herdr cannot place, so `herdr agent prompt`
3
+ // refused every OmO pane. HERDR_AGENT is the hint herdr reads from that process's environment; OmO
4
+ // is a pi-family agent to herdr, which already lists these panes as `pi`.
5
+ //
6
+ // The hint is only for an interactive engine the user sees in the pane: a TTY on stdin and no
7
+ // print or protocol flag before `--`. A HERDR_AGENT the user set is left alone. The launcher also
8
+ // sets OMO_HERDR_AGENT_HINT to the value it wrote, so the plugin releases only a hint the launcher
9
+ // set (packages/omo-senpi/src/extension/herdr-agent-hint.ts). Only this function writes that
10
+ // record: one inherited from the parent environment is dropped first, so it can never vouch for a
11
+ // HERDR_AGENT the user set.
12
+
13
+ const NON_INTERACTIVE_FLAGS = new Set(["-p", "--print", "--mode"])
14
+
15
+ export const HERDR_AGENT_LABEL = "pi"
16
+ export const HERDR_AGENT_MARKER = "OMO_HERDR_AGENT_HINT"
17
+
18
+ export function withHerdrAgentHint(env, args, stdinIsTTY = process.stdin.isTTY === true) {
19
+ delete env[HERDR_AGENT_MARKER]
20
+ if (env.HERDR_ENV !== "1") return env
21
+ if (env.HERDR_AGENT !== undefined && env.HERDR_AGENT !== "") return env
22
+ if (!stdinIsTTY) return env
23
+ const end = args.indexOf("--")
24
+ const flags = end === -1 ? args : args.slice(0, end)
25
+ if (flags.some((arg) => NON_INTERACTIVE_FLAGS.has(arg) || arg.startsWith("--mode="))) return env
26
+ env.HERDR_AGENT = HERDR_AGENT_LABEL
27
+ env[HERDR_AGENT_MARKER] = HERDR_AGENT_LABEL
28
+ return env
29
+ }
@@ -1,6 +1,6 @@
1
1
  import { spawnSync } from "node:child_process"
2
- import { existsSync, realpathSync } from "node:fs"
3
- import { delimiter, isAbsolute, join, relative, sep } from "node:path"
2
+ import { existsSync } from "node:fs"
3
+ import { join } from "node:path"
4
4
  import { isEngineCommand } from "./engine-commands.js"
5
5
  import { spawnNode } from "./child-process.js"
6
6
  import { doctorCoverageLines } from "./category-coverage.js"
@@ -13,7 +13,9 @@ import { runDoctor } from "./doctor.js"
13
13
  import { ensureEnginePrepared, preparePluginLaunchSpec } from "./engine-prepare.js"
14
14
  import { migrateLegacyBunGlobalManifest } from "./legacy-bun-global-migration.js"
15
15
  import { adoptLegacyFlatState, canonicalAgentDir } from "./agent-dir.js"
16
- import { nearestNodeBin, packageManifest, packageRoot, readJson, releaseBanner, releaseChannel, resolveSenpi, updateTarget } from "./package-paths.js"
16
+ import { senpiEnvironment } from "./engine-environment.js"
17
+ import { withHerdrAgentHint } from "./herdr-agent-hint.js"
18
+ import { packageManifest, packageRoot, readJson, releaseBanner, resolveSenpi, updateTarget } from "./package-paths.js"
17
19
  import { runSelfUpdate } from "./self-update.js"
18
20
  import { isSelfUpdate } from "./update-args.js"
19
21
  import { detectHarnesses } from "./setup-detect.js"
@@ -21,48 +23,6 @@ import { readSetupSuggestionCache, spawnSetupSuggestionRefresh } from "./setup-d
21
23
  import { printSetupReport } from "./setup-report.js"
22
24
  import { refuseRetiredCommand } from "./retired-command.js"
23
25
 
24
- // Identity the engine adopts for this install: what the user sees, where state lives, which
25
- // environment prefix is read first, what goes on the wire, and which channel to check for
26
- // updates. The engine consumes this once and scrubs it, so nested engine processes are
27
- // unaffected.
28
- function pluginChangelogSource() {
29
- try {
30
- const pluginRoot = join(packageRoot, "plugin")
31
- const changelogPath = join(pluginRoot, "CHANGELOG.md")
32
- if (!existsSync(changelogPath)) return undefined
33
- const version = readJson(join(pluginRoot, "package.json")).version
34
- return typeof version === "string" && version ? { path: changelogPath, version } : { path: changelogPath }
35
- } catch {
36
- return undefined
37
- }
38
- }
39
-
40
- function brandProfile() {
41
- const update = updateTarget()
42
- // The changelog source is advisory: a missing plugin manifest or file must disable
43
- // startup notes, never fail the launch.
44
- const changelog = pluginChangelogSource()
45
- return {
46
- name: "OmO",
47
- command: "omo",
48
- displayVersion: packageManifest().version,
49
- configDir: ".omo",
50
- // Engine state lives at the canonical `~/.omo/agent`, never directly under the config
51
- // directory: a flat home would disagree with the directory every omo surface resolves.
52
- flatLayout: false,
53
- envPrefix: "OMO",
54
- userAgent: "omo",
55
- originator: "omo",
56
- ...(changelog ? { changelog } : {}),
57
- update: {
58
- packageName: "omo-ai",
59
- distTag: releaseChannel(),
60
- command: update.command,
61
- changelogUrl: "https://github.com/code-yeongyu/oh-my-openagent/releases",
62
- },
63
- }
64
- }
65
-
66
26
  function engineVersion() {
67
27
  try {
68
28
  return readJson(join(resolveSenpi().packageRoot, "package.json")).version
@@ -71,62 +31,6 @@ function engineVersion() {
71
31
  }
72
32
  }
73
33
 
74
- function canonicalPath(path) {
75
- try {
76
- return realpathSync.native(path)
77
- } catch {
78
- return path
79
- }
80
- }
81
-
82
- function containsPath(root, target) {
83
- const rel = relative(canonicalPath(root), canonicalPath(target))
84
- return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel))
85
- }
86
-
87
- // A compiled omo session exports its own payload as the brand-scoped package dir, and every shell it
88
- // spawns inherits that. The engine reads these names before the legacy PI_PACKAGE_DIR, so a release
89
- // omo started from such a shell would run on the foreign payload. A deliberate relocation names this
90
- // install's engine and survives; a root that does not contain the engine belongs to another install.
91
- function dropForeignPackageDirs(env, senpiRoot) {
92
- for (const name of ["OMO_PACKAGE_DIR", "SENPI_PACKAGE_DIR"]) {
93
- const root = env[name]
94
- if (root && !containsPath(root, senpiRoot)) delete env[name]
95
- }
96
- }
97
-
98
- function senpiEnvironment(senpiRoot) {
99
- const env = { ...process.env }
100
- delete env.OMO_BIN
101
- delete env.SENPI_BIN
102
- dropForeignPackageDirs(env, senpiRoot)
103
- // One directory for every surface. The legacy name travels too, so a bare senpi spawned by a
104
- // tool inherits the same state instead of falling back to its own home.
105
- const agentDir = canonicalAgentDir(env)
106
- env.OMO_CODING_AGENT_DIR = agentDir
107
- env.SENPI_CODING_AGENT_DIR = agentDir
108
- // senpi's footer reads this marker to show the OmO Native badge for omo-ai installs, which load
109
- // the plugin via --extension and therefore never match the settings-packages detection gates.
110
- env.OMO_NATIVE = "1"
111
- // This launcher already decided which runtime the product runs on, possibly by re-execing itself
112
- // under bun. Handing that answer down stops the engine from making its own, conflicting choice.
113
- env.SENPI_RUNTIME = process.versions.bun ? "bun" : "node"
114
- env.SENPI_BRAND = JSON.stringify(brandProfile())
115
-
116
- const binDir = nearestNodeBin(senpiRoot)
117
- if (binDir) {
118
- const pathKey = Object.keys(env).find((key) => key.toLowerCase() === "path") ?? "PATH"
119
- const path = env[pathKey]
120
- env[pathKey] = path ? `${binDir}${delimiter}${path}` : binDir
121
- const shim = join(binDir, process.platform === "win32" ? "senpi.cmd" : "senpi")
122
- if (existsSync(shim)) env.SENPI_BIN = shim
123
- }
124
- // Anything resolving the product by name must re-enter through this launcher, otherwise it
125
- // would reach the engine directly and lose the brand.
126
- env.OMO_BIN = join(packageRoot, "bin", "omo.js")
127
- return env
128
- }
129
-
130
34
  function preparedSenpi() {
131
35
  preparePluginLaunchSpec({ pluginRoot: join(packageRoot, "plugin") })
132
36
  const senpi = resolveSenpi()
@@ -144,6 +48,7 @@ async function spawnSenpi(args, withExtension) {
144
48
  ? ["--extension", join(packageRoot, "plugin"), ...args]
145
49
  : args
146
50
  const env = senpiEnvironment(senpi.packageRoot)
51
+ if (withExtension) withHerdrAgentHint(env, args)
147
52
  if (process.platform !== "win32" && typeof process.execve === "function") {
148
53
  try {
149
54
  process.execve(process.execPath, [process.execPath, senpi.cliPath, ...finalArgs], env)
@@ -203,7 +108,7 @@ export function engineHostCall(engineArgs, options) {
203
108
  const senpi = preparedSenpi()
204
109
  const result = spawnSync(process.execPath, [senpi.cliPath, ...engineArgs], {
205
110
  encoding: "utf8",
206
- env: { ...senpiEnvironment(senpi.packageRoot), ...options.env },
111
+ env: senpiEnvironment(senpi.packageRoot, options.env),
207
112
  windowsHide: true,
208
113
  })
209
114
  return { exitCode: result.status ?? 1, stdout: result.stdout ?? "", stderr: result.stderr ?? "" }
@@ -1,5 +1,5 @@
1
1
  {
2
- "_comment": "Derived from @code-yeongyu/senpi 2026.10.16 builtinProviders(). builtinProviderIds and oauthProviderIds are the engine registry verbatim; providers maps a source harness id onto the omo provider that serves the same endpoint; oauthLogins maps a source OAuth id onto the omo provider to run /login for. Re-derive all four whenever the senpi pin changes.",
2
+ "_comment": "Derived from @code-yeongyu/senpi 2026.10.19 builtinProviders(). builtinProviderIds and oauthProviderIds are the engine registry verbatim; providers maps a source harness id onto the omo provider that serves the same endpoint; oauthLogins maps a source OAuth id onto the omo provider to run /login for. Re-derive all four whenever the senpi pin changes.",
3
3
  "builtinProviderIds": [
4
4
  "alibaba-token-plan",
5
5
  "amazon-bedrock",
@@ -119,7 +119,8 @@ export function planAssets({ runtime, agentDir }) {
119
119
  const notices = [...source.notices]
120
120
  const present = source.mcpServers.length > 0 || source.skills.length > 0
121
121
  const paths = { mcp: join(agentDir, "mcp.json"), skills: join(agentDir, "skills") }
122
- const result = classifyAssets(source, paths, bundledSkillNames(join(packageRoot, "plugin", "skills")))
122
+ const pluginRoot = runtime.pluginRoot ?? join(packageRoot, "plugin")
123
+ const result = classifyAssets(source, paths, bundledSkillNames(join(pluginRoot, "skills")))
123
124
  if (result.servers.blocked.length > 0) notices.push(`WARN senpi: malformed mcp.json; these servers were not imported: ${result.servers.blocked.join(", ")}`)
124
125
  return {
125
126
  agentDir,
@@ -177,7 +177,11 @@ export async function planModelChoices(stage) {
177
177
  if (!hasModelChoices(raw)) return empty
178
178
  let registry
179
179
  try {
180
- registry = await (stage.loadRegistry ?? loadEngineModels)({ agentDir: stage.agentDir, customProviders: stage.customProviders })
180
+ registry = await (stage.loadRegistry ?? loadEngineModels)({
181
+ agentDir: stage.agentDir,
182
+ customProviders: stage.customProviders,
183
+ loadCatalog: stage.runtime.loadModelCatalog,
184
+ })
181
185
  } catch (error) {
182
186
  return { ...empty, notices: [...raw.notices, `WARN senpi: could not read the engine's model list (${error.message}); model choices were not carried`] }
183
187
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omo-ai",
3
- "version": "5.1.35",
3
+ "version": "5.1.38",
4
4
  "type": "module",
5
5
  "description": "OmO Native - the standalone omo command with the OMO extension built in: bun add -g omo-ai",
6
6
  "bin": {
@@ -17,7 +17,7 @@
17
17
  "postinstall": "node bin/senpi-patch.mjs"
18
18
  },
19
19
  "dependencies": {
20
- "@code-yeongyu/senpi": "2026.10.16"
20
+ "@code-yeongyu/senpi": "2026.10.19"
21
21
  },
22
22
  "repository": {
23
23
  "type": "git",
@@ -7,6 +7,106 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [5.1.38] - 2026-10-19
11
+
12
+ 5.1.37 was tagged but its publish stopped on Linux (the release binaries outgrew their size budget), so its main packages never reached npm; everything listed for it ships in this release. ([#10566](https://github.com/code-yeongyu/oh-my-openagent/pull/10566))
13
+
14
+ **Release binaries may be up to 182 MiB.** The per-platform size budget moves from 160 MiB to 182 MiB after measuring what grew: the Bun 1.4.3 runtime adds 9.5 MB to every binary and the senpi update about 1.25 MB, so linux-x64 reached 173.4 MB. Nothing was bundled by mistake. ([#10566](https://github.com/code-yeongyu/oh-my-openagent/pull/10566))
15
+
16
+ 5.1.36 was prepared but never published; everything listed for it ships in this release. ([#10552](https://github.com/code-yeongyu/oh-my-openagent/pull/10552))
17
+
18
+ **The engine moves to senpi 2026.10.19: a hung extension handler no longer freezes a session, and queued input survives a crash.** An extension handler that never settles on a lifecycle event is named in the session after 60 s and released after 20 min, or at once on Esc or the turn deadline. Input queued with a client id is written to the session file before it is queued and delivered exactly once. The mouse wheel scrolls the transcript again while a question prompt is waiting, Alt+Q restores queued messages on WSL, and the /btw panel no longer repeats a running eval's output in the scrollback. Anthropic requests of 4096+ characters to `api.anthropic.com` are now sent gzip-compressed, as Claude Code does. ([senpi#3650](https://github.com/code-yeongyu/senpi/issues/3650), [senpi#3549](https://github.com/code-yeongyu/senpi/issues/3549), [senpi#3604](https://github.com/code-yeongyu/senpi/issues/3604), [senpi#3628](https://github.com/code-yeongyu/senpi/issues/3628), [senpi#3649](https://github.com/code-yeongyu/senpi/issues/3649), [senpi#3643](https://github.com/code-yeongyu/senpi/issues/3643), [#10552](https://github.com/code-yeongyu/oh-my-openagent/pull/10552))
19
+
20
+ **Goals keep going while they make progress, and long sessions open and stay light (senpi 2026.10.18-1).** A goal no longer stops after a fixed 150 automatic continuations; it stops only after 60 in a row with no new tool effect, todo change or message from a person, and says why and that any message resumes it. Opening a very large session on a shared host no longer stalls the other sessions, a reopened session keeps only its latest compaction window in memory (four of the largest local transcripts went from 1441 MB to 216 MB of live heap), and reading a very long history over RPC pages instead of dropping the connection. A Claude subscription from `CLAUDE_CODE_OAUTH_TOKEN` or an ambient Claude Code login now shows as a connected subscription, and a host daemon inherits every credential variable the engine reads. Provider accounts can be ordered, and grep starts 5 to 14 `rg` processes where it used to start 63 to 87. ([#10280](https://github.com/code-yeongyu/oh-my-openagent/issues/10280), [#10509](https://github.com/code-yeongyu/oh-my-openagent/pull/10509)) The engine also fixes ChatGPT browser sign-in dead-ending on "State mismatch." when another session holds the callback port ([senpi #3627](https://github.com/code-yeongyu/senpi/pull/3627)), and a compiled engine now finds the Claude Code binary shipped beside its executable instead of failing the lookup ([senpi #3640](https://github.com/code-yeongyu/senpi/pull/3640)).
21
+
22
+ **A client re-attaching to a retained session gets the full task snapshot again, and can pull it on demand.** The task RPC bridge listens for `session_resumed`, which senpi fires when the first client re-attaches to a still-open session, and re-emits the complete `omo.task.updated` snapshot instead of waiting for the next store change to alter its fingerprint; a new `omo.task.list` request answers the same payload for the parent session it names. ([#10016](https://github.com/code-yeongyu/oh-my-openagent/issues/10016), [#10122](https://github.com/code-yeongyu/oh-my-openagent/pull/10122))
23
+ **Memory recall's search index takes a quarter of the memory it did, and a memory write no longer rebuilds it.** Every session that recalls memory keeps a search index of the whole memory. It stored each note's words as a separate table, so a memory of 6,406 notes cost about 231 MB per session, and each memory write threw the index away and rebuilt it, taking 3 to 11 seconds. Words are now numbered once and each note keeps a compact list of numbers that survives a memory write, so the same memory costs 55 MB and a write updates the index in about 16 ms. The notes found and their order are unchanged. ([#10510](https://github.com/code-yeongyu/oh-my-openagent/issues/10510), [#10512](https://github.com/code-yeongyu/oh-my-openagent/pull/10512))
24
+
25
+ **A background task child follows its session when another client replaces it.** A raw RPC client that issues `new_session`, `switch_session` or `fork` on a daemon child's routing handle moves that session to a new durable id and transcript; the child now observes the `session_replaced` record, re-stamps its record's session path and durable id, appends a `replaced` task event, and reattaches to the new transcript instead of the abandoned one. When the replacement stops a turn the child was running, the child ends `interrupted` with the reason and a `task_send` action, and the parent is notified because it did not ask for that stop (a parent's own interrupt stays silent); this needs a senpi that names the cause on `agent_end` (senpi#3357), and an older host keeps the previous error. ([#10018](https://github.com/code-yeongyu/oh-my-openagent/issues/10018), [#10112](https://github.com/code-yeongyu/oh-my-openagent/pull/10112))
26
+ **`unspecified-low` tries GLM 5.3 before GPT.** A user with Claude and Z.ai (or OpenCode Go) but no Xiaomi or xAI provider used to fall from Sonnet 5.5 straight to GPT-6.1 Sol once Sonnet was unavailable. The chain now has `glm-5.3` at high thinking right after Grok 4.7 and ahead of the GPT rungs, so the order for users who hold MiMo or Grok is unchanged. ([#10306](https://github.com/code-yeongyu/oh-my-openagent/issues/10306), [#10311](https://github.com/code-yeongyu/oh-my-openagent/pull/10311))
27
+
28
+ **`omo --help` no longer moves your OpenCode config before you start a session.** On a fresh install `--help` (and `--list-models` / `--list-tips`) loads the omo extension, and its startup migration moved `~/.config/opencode/oh-my-openagent.json` into `~/.omo/migration-backup-*`, so a 4.19.x OpenCode plugin fell back to its defaults. The migration now runs only on launches that can start a session, and `--help` still lists omo's own flags. Thanks to @bluet for the report and @twotnguyen for #10433. ([#10428](https://github.com/code-yeongyu/oh-my-openagent/issues/10428), [#10454](https://github.com/code-yeongyu/oh-my-openagent/pull/10454))
29
+
30
+ **Built, tested and released on Bun 1.4.3.** CI, the release workflows and the devcontainer move from Bun 1.4.2 to 1.4.3, the current stable release, which fixes CVE-2026-48618 (TLS host names) and idle garbage collection in workers. ([#10432](https://github.com/code-yeongyu/oh-my-openagent/pull/10432))
31
+
32
+ **OpenCode edition: Sisyphus names GPT-6.1 Sol and GPT-6 Luna as themselves.** On `gpt-6.1-sol` (and its `-fast`, dashed and Venice `gpt-61-sol` ids) and on `gpt-6-luna`, the Sisyphus and Sisyphus-Junior GPT prompts introduced the model as "a GPT-family model"; they now say "GPT-6.1 Sol" and "GPT-6 Luna". ([#10291](https://github.com/code-yeongyu/oh-my-openagent/issues/10291), [#10292](https://github.com/code-yeongyu/oh-my-openagent/pull/10292))
33
+
34
+ **Memory session shutdown waits for the work it started in the memory directory.** Shutdown now waits, within its existing drain budget, for an in-flight repo maintenance pass and for the status refreshes started at bind and settle, all of which run git inside the identity directory. Before, they could still hold files open after shutdown returned; on Windows the next delete of that directory failed with `EBUSY`/`EFAULT`, which is how the memory pressure-dream test failed intermittently in CI. ([#10154](https://github.com/code-yeongyu/oh-my-openagent/issues/10154), [#10312](https://github.com/code-yeongyu/oh-my-openagent/pull/10312))
35
+
36
+ **Oh My OpenAgent loads on OpenCode 2.** OpenCode 2 refused the plugin with `Plugin must export a default definition with an id and an effect or setup function`, because both entry points exported only the OpenCode 1.x shape. The server and TUI entries now also export an OpenCode 2 `setup` built with `Plugin.define` from `@opencode/plugin`, next to the OpenCode 1.x `server` and `tui` entries, which keep working as before. Each OpenCode 2 start shows a notice that points to OmO Native, with the install command and guide link. ([#8485](https://github.com/code-yeongyu/oh-my-openagent/issues/8485), [#10439](https://github.com/code-yeongyu/oh-my-openagent/pull/10439))
37
+
38
+ **The frontend skill keeps coloured edge stripes off rounded cards, and a linter catches the ones that slip through.** A card with a coloured stripe along one edge, its rounded corners bending the stripe, is one of the most recognizable marks of generated UI, and the skill only banned it when the stripe marked a selected or focused item. It now rules the stripe out before drafting, whether it decorates the card or marks state, and offers what to use instead: a tonal surface, a heading in the accent colour, a glyph, or spacing. The new `check-accent-stripes.ts` script finds the pattern in CSS, Tailwind classes, inline styles, and styled-components, and the skill's checklist runs it before visual QA. Focus rings, form validation, neutral dividers, and tables are left alone. ([#10437](https://github.com/code-yeongyu/oh-my-openagent/issues/10437))
39
+
40
+ **A store extension's `orderBy` can only name columns its statement returns, and its text never reaches the SQL.** `orderBy` used to be pasted into the gateway's ordered read as given, so a `LIMIT`, an `OFFSET` or a column the statement does not select failed as broken SQL (`near "LIMIT": syntax error`, `no such column`). Each comma-separated term must now be one of the statement's own output columns (matched case-insensitively, as SQLite does), optionally with `COLLATE BINARY`/`NOCASE`/`RTRIM`, `ASC`/`DESC` and `NULLS FIRST`/`NULLS LAST`. The ORDER BY is rebuilt from the matched column name and those fixed keywords. Anything else, including quoted or bracketed names, comments, look-alike Unicode letters and unknown columns, is refused with `extension_schema_violation` and a message that names the rule and the columns the statement does return; put a `LIMIT` in the statement itself. ([#9626](https://github.com/code-yeongyu/oh-my-openagent/pull/9626))
41
+
42
+ **The standalone `omo` binary's `omo setup` carries your OpenCode model choices again.** The release binary printed `could not read the engine's model list (ENOENT ... '/package.json')` and skipped the default model and agent/category picks, and it would have imported a user skill named like a bundled one over the bundled skill. Setup in the binary now reads the provider catalog and bundled skills it ships with; the npm install is unchanged. Thanks to @bluet for the report. ([#10429](https://github.com/code-yeongyu/oh-my-openagent/issues/10429), [#10455](https://github.com/code-yeongyu/oh-my-openagent/pull/10455))
43
+
44
+ **Only you can stop computer use with the stop chord on macOS and Linux X11.** A chord a program sends (the agent's own key presses, `xdotool`, another app posting key events) used to stop every session, and repeated, could keep computer use stopped; it is now ignored, while a press on a real keyboard still stops every running session at once. Wayland's shortcut portal cannot tell the two apart, and Windows follows in a separate change. ([#10444](https://github.com/code-yeongyu/oh-my-openagent/issues/10444), [#10449](https://github.com/code-yeongyu/oh-my-openagent/pull/10449))
45
+
46
+ **On Windows every session can now arm the stop chord, and only a key press on a real keyboard triggers it.** The chord used to be a `RegisterHotKey` registration, which only one process can hold: a second session's engine could not arm it, so its computer use refused input with `StopPathUnavailable`; and a chord a program sent (the agent's own key presses or any other app's `SendInput`) stopped computer use like a real press. A low-level keyboard hook now watches the chord in every session, ignores injected key events, and never swallows a key; one physical press stops every running session at once. ([#10444](https://github.com/code-yeongyu/oh-my-openagent/issues/10444), [#10453](https://github.com/code-yeongyu/oh-my-openagent/pull/10453))
47
+
48
+ **Computer use works on arm64 Linux and Windows.** Each OmO release now carries the desktop engine for Linux arm64 (glibc) and Windows arm64, built on native arm64 machines and checked with its self-test before upload. The compiled OmO binaries for those hosts include the engine, and an npm install downloads it on first use, where it used to report that no engine is built for the host. Musl Linux still has no engine. ([#10440](https://github.com/code-yeongyu/oh-my-openagent/issues/10440), [#10442](https://github.com/code-yeongyu/oh-my-openagent/pull/10442))
49
+
50
+ **A memory-sync account can be recovered on a new device with a passkey.** memory-core can enroll a dedicated recovery passkey and, on a fresh device, use it to rejoin the account: the device's native bridge derives the recovery keys from the passkey and keeps them in the main process, the device walks the served history from the recovery key's own record and pins it, and only a wrap that record vouches for is opened. A wrap the server made up is refused, whatever the server sends. Nothing syncs to a server yet. ([#10412](https://github.com/code-yeongyu/oh-my-openagent/pull/10412))
51
+
52
+ **`herdr agent prompt` reaches OmO panes.** herdr only types into a pane whose foreground process it can name, and the OmO engine runs as `bun …/cli.js`, so every OmO pane was refused with `agent_not_ready`. Under herdr, the interactive engine now starts with `HERDR_AGENT=pi`, the label herdr already shows for these panes. OmO drops it from the environment it hands to anything it starts. Thanks to @devswha for the report. ([#10448](https://github.com/code-yeongyu/oh-my-openagent/issues/10448), [#10452](https://github.com/code-yeongyu/oh-my-openagent/pull/10452))
53
+
54
+ **A memory writer killed mid-commit no longer leaves the next writer stuck on a git lock, and a live commit's lock is never taken.** When a memory writer dies holding a git `index.lock` or ref lock, the writer that takes over its memory-write lock now removes that lock, but only after proving the previous owner dead, seeing no other process still working in the repository (a killed writer's own git command can outlive it), and seeing no process holding the file open. When any of that cannot be proven, including when the open-file check is slow or never answers, the lock stays put and the write fails with an error that names each lock file, says why it was kept, and gives the way out: `/memory unlock <path>` re-checks that one lock, shows what it found, refuses while any live holder, repository user or memory writer is visible, and removes only that file after you confirm (`--force` skips the prompt in a non-interactive session, never a refusal). On Linux desktops and SSH logins where some of your own processes cannot be inspected, the automatic removal does not fire and this command is the way out; [#10322](https://github.com/code-yeongyu/oh-my-openagent/issues/10322) tracks making liveness independent of `/proc`. On Linux this check reads `/proc` directly, so recovery also works on minimal installs and containers without `lsof`. ([#10177](https://github.com/code-yeongyu/oh-my-openagent/issues/10177)) ([#10199](https://github.com/code-yeongyu/oh-my-openagent/pull/10199))
55
+
56
+ **Synced memory refuses a forged or reordered history on the device, the same way the server does.** memory-core now checks every sync object, the hash chain linking them, and the device keyring folded from it, using the format the service ships. A device rejects a record signed by a key that was rotated out or revoked, a dropped or reordered entry, a recovery join from a removed recovery key, and a batch sealed for another account. It treats the server as untrusted: a device trusts an account history only if it starts from the root it pinned during its own pairing or recovery, so a history the server invents, or a different root that looks valid, is refused before any key is opened. A key wrap opens only from a record of that trusted history, and only through the device's own keyring. A missing or damaged pin is treated as no trust at all, and the device asks to re-pair or recover. A replayed batch, a swapped record body, an unsigned row in a vetoed epoch and a row with fields the format does not define are each refused and counted. Nothing syncs to a server yet. ([#10178](https://github.com/code-yeongyu/oh-my-openagent/issues/10178), [#10298](https://github.com/code-yeongyu/oh-my-openagent/pull/10298))
57
+
58
+ **A memory that already exists on a device joins its synced space without losing anything.** Each project's synced memory gets one identity that every device computes the same way from the repository, so two devices enabling sync offline land in one space. When local files meet the space's copy, nothing is deleted: body lines are only added, a list keeps every element from both sides, and a frontmatter value that differs keeps both whole values side by side, never spliced together and never a conflict note in the file. Leftover hub conflict copies are folded in and removed only after a read-back proves every line made it. Nothing syncs to a server yet. ([#10206](https://github.com/code-yeongyu/oh-my-openagent/pull/10206))
59
+
60
+ **`runtime_fallback` moves to the next model when a Cursor pool is spent.** cursor-opencode-provider reports a spent pool as `capacity_limit`, which the fallback classifier did not recognize, so the session ended on the error instead of trying the next rung of the chain. It is now treated like the `resource_exhausted` error it replaces. ([#10170](https://github.com/code-yeongyu/oh-my-openagent/issues/10170), [#10224](https://github.com/code-yeongyu/oh-my-openagent/pull/10224))
61
+
62
+ ## [5.1.37] - 2026-10-19
63
+
64
+ 5.1.36 was prepared but never published; everything listed for it ships in this release. ([#10552](https://github.com/code-yeongyu/oh-my-openagent/pull/10552))
65
+
66
+ **The engine moves to senpi 2026.10.19: a hung extension handler no longer freezes a session, and queued input survives a crash.** An extension handler that never settles on a lifecycle event is named in the session after 60 s and released after 20 min, or at once on Esc or the turn deadline. Input queued with a client id is written to the session file before it is queued and delivered exactly once. The mouse wheel scrolls the transcript again while a question prompt is waiting, Alt+Q restores queued messages on WSL, and the /btw panel no longer repeats a running eval's output in the scrollback. Anthropic requests of 4096+ characters to `api.anthropic.com` are now sent gzip-compressed, as Claude Code does. ([senpi#3650](https://github.com/code-yeongyu/senpi/issues/3650), [senpi#3549](https://github.com/code-yeongyu/senpi/issues/3549), [senpi#3604](https://github.com/code-yeongyu/senpi/issues/3604), [senpi#3628](https://github.com/code-yeongyu/senpi/issues/3628), [senpi#3649](https://github.com/code-yeongyu/senpi/issues/3649), [senpi#3643](https://github.com/code-yeongyu/senpi/issues/3643), [#10552](https://github.com/code-yeongyu/oh-my-openagent/pull/10552))
67
+
68
+ **Goals keep going while they make progress, and long sessions open and stay light (senpi 2026.10.18-1).** A goal no longer stops after a fixed 150 automatic continuations; it stops only after 60 in a row with no new tool effect, todo change or message from a person, and says why and that any message resumes it. Opening a very large session on a shared host no longer stalls the other sessions, a reopened session keeps only its latest compaction window in memory (four of the largest local transcripts went from 1441 MB to 216 MB of live heap), and reading a very long history over RPC pages instead of dropping the connection. A Claude subscription from `CLAUDE_CODE_OAUTH_TOKEN` or an ambient Claude Code login now shows as a connected subscription, and a host daemon inherits every credential variable the engine reads. Provider accounts can be ordered, and grep starts 5 to 14 `rg` processes where it used to start 63 to 87. ([#10280](https://github.com/code-yeongyu/oh-my-openagent/issues/10280), [#10509](https://github.com/code-yeongyu/oh-my-openagent/pull/10509)) The engine also fixes ChatGPT browser sign-in dead-ending on "State mismatch." when another session holds the callback port ([senpi #3627](https://github.com/code-yeongyu/senpi/pull/3627)), and a compiled engine now finds the Claude Code binary shipped beside its executable instead of failing the lookup ([senpi #3640](https://github.com/code-yeongyu/senpi/pull/3640)).
69
+
70
+ **A client re-attaching to a retained session gets the full task snapshot again, and can pull it on demand.** The task RPC bridge listens for `session_resumed`, which senpi fires when the first client re-attaches to a still-open session, and re-emits the complete `omo.task.updated` snapshot instead of waiting for the next store change to alter its fingerprint; a new `omo.task.list` request answers the same payload for the parent session it names. ([#10016](https://github.com/code-yeongyu/oh-my-openagent/issues/10016), [#10122](https://github.com/code-yeongyu/oh-my-openagent/pull/10122))
71
+ **Memory recall's search index takes a quarter of the memory it did, and a memory write no longer rebuilds it.** Every session that recalls memory keeps a search index of the whole memory. It stored each note's words as a separate table, so a memory of 6,406 notes cost about 231 MB per session, and each memory write threw the index away and rebuilt it, taking 3 to 11 seconds. Words are now numbered once and each note keeps a compact list of numbers that survives a memory write, so the same memory costs 55 MB and a write updates the index in about 16 ms. The notes found and their order are unchanged. ([#10510](https://github.com/code-yeongyu/oh-my-openagent/issues/10510), [#10512](https://github.com/code-yeongyu/oh-my-openagent/pull/10512))
72
+
73
+ **A background task child follows its session when another client replaces it.** A raw RPC client that issues `new_session`, `switch_session` or `fork` on a daemon child's routing handle moves that session to a new durable id and transcript; the child now observes the `session_replaced` record, re-stamps its record's session path and durable id, appends a `replaced` task event, and reattaches to the new transcript instead of the abandoned one. When the replacement stops a turn the child was running, the child ends `interrupted` with the reason and a `task_send` action, and the parent is notified because it did not ask for that stop (a parent's own interrupt stays silent); this needs a senpi that names the cause on `agent_end` (senpi#3357), and an older host keeps the previous error. ([#10018](https://github.com/code-yeongyu/oh-my-openagent/issues/10018), [#10112](https://github.com/code-yeongyu/oh-my-openagent/pull/10112))
74
+ **`unspecified-low` tries GLM 5.3 before GPT.** A user with Claude and Z.ai (or OpenCode Go) but no Xiaomi or xAI provider used to fall from Sonnet 5.5 straight to GPT-6.1 Sol once Sonnet was unavailable. The chain now has `glm-5.3` at high thinking right after Grok 4.7 and ahead of the GPT rungs, so the order for users who hold MiMo or Grok is unchanged. ([#10306](https://github.com/code-yeongyu/oh-my-openagent/issues/10306), [#10311](https://github.com/code-yeongyu/oh-my-openagent/pull/10311))
75
+
76
+ **`omo --help` no longer moves your OpenCode config before you start a session.** On a fresh install `--help` (and `--list-models` / `--list-tips`) loads the omo extension, and its startup migration moved `~/.config/opencode/oh-my-openagent.json` into `~/.omo/migration-backup-*`, so a 4.19.x OpenCode plugin fell back to its defaults. The migration now runs only on launches that can start a session, and `--help` still lists omo's own flags. Thanks to @bluet for the report and @twotnguyen for #10433. ([#10428](https://github.com/code-yeongyu/oh-my-openagent/issues/10428), [#10454](https://github.com/code-yeongyu/oh-my-openagent/pull/10454))
77
+
78
+ **Built, tested and released on Bun 1.4.3.** CI, the release workflows and the devcontainer move from Bun 1.4.2 to 1.4.3, the current stable release, which fixes CVE-2026-48618 (TLS host names) and idle garbage collection in workers. ([#10432](https://github.com/code-yeongyu/oh-my-openagent/pull/10432))
79
+
80
+ **OpenCode edition: Sisyphus names GPT-6.1 Sol and GPT-6 Luna as themselves.** On `gpt-6.1-sol` (and its `-fast`, dashed and Venice `gpt-61-sol` ids) and on `gpt-6-luna`, the Sisyphus and Sisyphus-Junior GPT prompts introduced the model as "a GPT-family model"; they now say "GPT-6.1 Sol" and "GPT-6 Luna". ([#10291](https://github.com/code-yeongyu/oh-my-openagent/issues/10291), [#10292](https://github.com/code-yeongyu/oh-my-openagent/pull/10292))
81
+
82
+ **Memory session shutdown waits for the work it started in the memory directory.** Shutdown now waits, within its existing drain budget, for an in-flight repo maintenance pass and for the status refreshes started at bind and settle, all of which run git inside the identity directory. Before, they could still hold files open after shutdown returned; on Windows the next delete of that directory failed with `EBUSY`/`EFAULT`, which is how the memory pressure-dream test failed intermittently in CI. ([#10154](https://github.com/code-yeongyu/oh-my-openagent/issues/10154), [#10312](https://github.com/code-yeongyu/oh-my-openagent/pull/10312))
83
+
84
+ **Oh My OpenAgent loads on OpenCode 2.** OpenCode 2 refused the plugin with `Plugin must export a default definition with an id and an effect or setup function`, because both entry points exported only the OpenCode 1.x shape. The server and TUI entries now also export an OpenCode 2 `setup` built with `Plugin.define` from `@opencode/plugin`, next to the OpenCode 1.x `server` and `tui` entries, which keep working as before. Each OpenCode 2 start shows a notice that points to OmO Native, with the install command and guide link. ([#8485](https://github.com/code-yeongyu/oh-my-openagent/issues/8485), [#10439](https://github.com/code-yeongyu/oh-my-openagent/pull/10439))
85
+
86
+ **The frontend skill keeps coloured edge stripes off rounded cards, and a linter catches the ones that slip through.** A card with a coloured stripe along one edge, its rounded corners bending the stripe, is one of the most recognizable marks of generated UI, and the skill only banned it when the stripe marked a selected or focused item. It now rules the stripe out before drafting, whether it decorates the card or marks state, and offers what to use instead: a tonal surface, a heading in the accent colour, a glyph, or spacing. The new `check-accent-stripes.ts` script finds the pattern in CSS, Tailwind classes, inline styles, and styled-components, and the skill's checklist runs it before visual QA. Focus rings, form validation, neutral dividers, and tables are left alone. ([#10437](https://github.com/code-yeongyu/oh-my-openagent/issues/10437))
87
+
88
+ **A store extension's `orderBy` can only name columns its statement returns, and its text never reaches the SQL.** `orderBy` used to be pasted into the gateway's ordered read as given, so a `LIMIT`, an `OFFSET` or a column the statement does not select failed as broken SQL (`near "LIMIT": syntax error`, `no such column`). Each comma-separated term must now be one of the statement's own output columns (matched case-insensitively, as SQLite does), optionally with `COLLATE BINARY`/`NOCASE`/`RTRIM`, `ASC`/`DESC` and `NULLS FIRST`/`NULLS LAST`. The ORDER BY is rebuilt from the matched column name and those fixed keywords. Anything else, including quoted or bracketed names, comments, look-alike Unicode letters and unknown columns, is refused with `extension_schema_violation` and a message that names the rule and the columns the statement does return; put a `LIMIT` in the statement itself. ([#9626](https://github.com/code-yeongyu/oh-my-openagent/pull/9626))
89
+
90
+ **The standalone `omo` binary's `omo setup` carries your OpenCode model choices again.** The release binary printed `could not read the engine's model list (ENOENT ... '/package.json')` and skipped the default model and agent/category picks, and it would have imported a user skill named like a bundled one over the bundled skill. Setup in the binary now reads the provider catalog and bundled skills it ships with; the npm install is unchanged. Thanks to @bluet for the report. ([#10429](https://github.com/code-yeongyu/oh-my-openagent/issues/10429), [#10455](https://github.com/code-yeongyu/oh-my-openagent/pull/10455))
91
+
92
+ **Only you can stop computer use with the stop chord on macOS and Linux X11.** A chord a program sends (the agent's own key presses, `xdotool`, another app posting key events) used to stop every session, and repeated, could keep computer use stopped; it is now ignored, while a press on a real keyboard still stops every running session at once. Wayland's shortcut portal cannot tell the two apart, and Windows follows in a separate change. ([#10444](https://github.com/code-yeongyu/oh-my-openagent/issues/10444), [#10449](https://github.com/code-yeongyu/oh-my-openagent/pull/10449))
93
+
94
+ **On Windows every session can now arm the stop chord, and only a key press on a real keyboard triggers it.** The chord used to be a `RegisterHotKey` registration, which only one process can hold: a second session's engine could not arm it, so its computer use refused input with `StopPathUnavailable`; and a chord a program sent (the agent's own key presses or any other app's `SendInput`) stopped computer use like a real press. A low-level keyboard hook now watches the chord in every session, ignores injected key events, and never swallows a key; one physical press stops every running session at once. ([#10444](https://github.com/code-yeongyu/oh-my-openagent/issues/10444), [#10453](https://github.com/code-yeongyu/oh-my-openagent/pull/10453))
95
+
96
+ **Computer use works on arm64 Linux and Windows.** Each OmO release now carries the desktop engine for Linux arm64 (glibc) and Windows arm64, built on native arm64 machines and checked with its self-test before upload. The compiled OmO binaries for those hosts include the engine, and an npm install downloads it on first use, where it used to report that no engine is built for the host. Musl Linux still has no engine. ([#10440](https://github.com/code-yeongyu/oh-my-openagent/issues/10440), [#10442](https://github.com/code-yeongyu/oh-my-openagent/pull/10442))
97
+
98
+ **A memory-sync account can be recovered on a new device with a passkey.** memory-core can enroll a dedicated recovery passkey and, on a fresh device, use it to rejoin the account: the device's native bridge derives the recovery keys from the passkey and keeps them in the main process, the device walks the served history from the recovery key's own record and pins it, and only a wrap that record vouches for is opened. A wrap the server made up is refused, whatever the server sends. Nothing syncs to a server yet. ([#10412](https://github.com/code-yeongyu/oh-my-openagent/pull/10412))
99
+
100
+ **`herdr agent prompt` reaches OmO panes.** herdr only types into a pane whose foreground process it can name, and the OmO engine runs as `bun …/cli.js`, so every OmO pane was refused with `agent_not_ready`. Under herdr, the interactive engine now starts with `HERDR_AGENT=pi`, the label herdr already shows for these panes. OmO drops it from the environment it hands to anything it starts. Thanks to @devswha for the report. ([#10448](https://github.com/code-yeongyu/oh-my-openagent/issues/10448), [#10452](https://github.com/code-yeongyu/oh-my-openagent/pull/10452))
101
+
102
+ **A memory writer killed mid-commit no longer leaves the next writer stuck on a git lock, and a live commit's lock is never taken.** When a memory writer dies holding a git `index.lock` or ref lock, the writer that takes over its memory-write lock now removes that lock, but only after proving the previous owner dead, seeing no other process still working in the repository (a killed writer's own git command can outlive it), and seeing no process holding the file open. When any of that cannot be proven, including when the open-file check is slow or never answers, the lock stays put and the write fails with an error that names each lock file, says why it was kept, and gives the way out: `/memory unlock <path>` re-checks that one lock, shows what it found, refuses while any live holder, repository user or memory writer is visible, and removes only that file after you confirm (`--force` skips the prompt in a non-interactive session, never a refusal). On Linux desktops and SSH logins where some of your own processes cannot be inspected, the automatic removal does not fire and this command is the way out; [#10322](https://github.com/code-yeongyu/oh-my-openagent/issues/10322) tracks making liveness independent of `/proc`. On Linux this check reads `/proc` directly, so recovery also works on minimal installs and containers without `lsof`. ([#10177](https://github.com/code-yeongyu/oh-my-openagent/issues/10177)) ([#10199](https://github.com/code-yeongyu/oh-my-openagent/pull/10199))
103
+
104
+ **Synced memory refuses a forged or reordered history on the device, the same way the server does.** memory-core now checks every sync object, the hash chain linking them, and the device keyring folded from it, using the format the service ships. A device rejects a record signed by a key that was rotated out or revoked, a dropped or reordered entry, a recovery join from a removed recovery key, and a batch sealed for another account. It treats the server as untrusted: a device trusts an account history only if it starts from the root it pinned during its own pairing or recovery, so a history the server invents, or a different root that looks valid, is refused before any key is opened. A key wrap opens only from a record of that trusted history, and only through the device's own keyring. A missing or damaged pin is treated as no trust at all, and the device asks to re-pair or recover. A replayed batch, a swapped record body, an unsigned row in a vetoed epoch and a row with fields the format does not define are each refused and counted. Nothing syncs to a server yet. ([#10178](https://github.com/code-yeongyu/oh-my-openagent/issues/10178), [#10298](https://github.com/code-yeongyu/oh-my-openagent/pull/10298))
105
+
106
+ **A memory that already exists on a device joins its synced space without losing anything.** Each project's synced memory gets one identity that every device computes the same way from the repository, so two devices enabling sync offline land in one space. When local files meet the space's copy, nothing is deleted: body lines are only added, a list keeps every element from both sides, and a frontmatter value that differs keeps both whole values side by side, never spliced together and never a conflict note in the file. Leftover hub conflict copies are folded in and removed only after a read-back proves every line made it. Nothing syncs to a server yet. ([#10206](https://github.com/code-yeongyu/oh-my-openagent/pull/10206))
107
+
108
+ **`runtime_fallback` moves to the next model when a Cursor pool is spent.** cursor-opencode-provider reports a spent pool as `capacity_limit`, which the fallback classifier did not recognize, so the session ended on the error instead of trying the next rung of the chain. It is now treated like the `resource_exhausted` error it replaces. ([#10170](https://github.com/code-yeongyu/oh-my-openagent/issues/10170), [#10224](https://github.com/code-yeongyu/oh-my-openagent/pull/10224))
109
+
10
110
  ## [5.1.35] - 2026-10-16
11
111
 
12
112
  **The omo-senpi typecheck gives the same result wherever the checkout sits.** It used to pick up `@types/node` from folders above the checkout, so a stray older copy there failed the typecheck while CI passed on the same commit. omo-senpi now declares its own `@types/node` and `bun-types` and reads type packages only from its own `node_modules`. ([#10320](https://github.com/code-yeongyu/oh-my-openagent/issues/10320), [#10332](https://github.com/code-yeongyu/oh-my-openagent/pull/10332))
@@ -1,2 +1,2 @@
1
- // omo:fwMmjRq3LfcfBEqtFEYCGEdg8RljSYFoni0TDAayIMw:5LLt7853QZ0R275mM__hYa8UeDUj-ySee_PqmOY8R7U
1
+ // omo:N7yV2WZOIOdRnwwCEF2QqkFTSVMebOlQ84G7HpOZFBE:5LLt7853QZ0R275mM__hYa8UeDUj-ySee_PqmOY8R7U
2
2
  function e(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;")}function r(r,s){let i=[`<operating-rules version="${e(r).replaceAll('"',"&quot;")}">`];for(let r of s)i.push(`- ${e(r)}`);return i.push("</operating-rules>"),i.join("\n")}function s(e,r){if(null===e||"object"!=typeof e||Array.isArray(e))throw Error(`${r} must be an object`);return e}function i(e,r){let s=e[r];if("string"!=typeof s||0===s.length)throw Error(`${r} must be a non-empty string`);return s}function o(e){let r=s(e,"a rules target"),o=i(r,"session_durable_id"),n=r.binding_id;if(null!=n&&"string"!=typeof n)throw Error("binding_id must be a string");let t=r.behavioral;if(null===t)return{session_durable_id:o,binding_id:n??void 0,behavioral:null};if(!Array.isArray(t)||!t.every(e=>"string"==typeof e))throw Error("behavioral must be an array of rule texts, or null to clear the session's rules");return{session_durable_id:o,binding_id:n??void 0,behavioral:t}}function n(e,r){let o=i(s(r,"blockForSession args"),"session_durable_id"),n=e.one(["version","block"],"SELECT version, block FROM gateway_rules_blocks WHERE session_durable_id = ?",[o]);return void 0===n?null:{version:String(n.version),block:String(n.block)}}function t(e,r){let o=i(s(r,"sessionsWithRules args"),"scope");return{sessions:e.all(["session_durable_id","version"],"SELECT session_durable_id, version FROM gateway_rules_blocks WHERE scope = ?",[o],"session_durable_id").map(e=>({session_durable_id:String(e.session_durable_id),version:String(e.version)}))}}async function l(n,t){let l=s(t,"rulesCommitted args"),a=i(l,"scope"),d=i(l,"version"),u=l.now??Date.now();if("number"!=typeof u||!Number.isFinite(u))throw Error("now must be a finite timestamp");if(!Array.isArray(l.targets))throw Error("targets must be an array");let _=[];for(let s of l.targets){let i=o(s);if(null===i.behavioral){let e=n.exec("DELETE FROM gateway_rules_blocks WHERE session_durable_id = ?",[i.session_durable_id]);_.push({session_durable_id:i.session_durable_id,outcome:e>0?"removed":"absent"});continue}let t=r(d,i.behavioral),l=n.exec("INSERT INTO gateway_rules_blocks (session_durable_id, scope, version, block, updated_at) VALUES (?, ?, ?, ?, ?)\n ON CONFLICT(session_durable_id) DO UPDATE SET scope = excluded.scope, version = excluded.version, block = excluded.block, updated_at = excluded.updated_at\n WHERE gateway_rules_blocks.version != excluded.version",[i.session_durable_id,a,d,t,u]);if(void 0===i.binding_id){_.push({session_durable_id:i.session_durable_id,outcome:l>0?"updated":"unchanged"});continue}let c=await n.enqueue({binding_id:i.binding_id,event_id:`rules_changed:${a}:${d}:${i.session_durable_id}`,text:[`<operating-rules-changed scope="${e(a).replaceAll('"',"&quot;")}" version="${e(d).replaceAll('"',"&quot;")}">`,"The operating rules for this gateway scope changed; this session's system prompt carries the new block from its next turn.","</operating-rules-changed>"].join("\n"),mode:"follow_up"});_.push({session_durable_id:i.session_durable_id,outcome:l>0?"updated":"unchanged",delivery:"ok"===c.kind?{kind:"ok",delivery_id:c.delivery_id,deduplicated:!0===c.deduplicated}:{kind:"error",error:c.error}})}return{scope:a,version:d,outcomes:_}}export{n as blockForSession,l as rulesCommitted,t as sessionsWithRules};