omo-ai 5.1.0 → 5.1.2

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 (41) hide show
  1. package/bin/lib/category-coverage.js +5 -1
  2. package/bin/lib/claude-code-floor.js +8 -4
  3. package/bin/lib/daemon-args.js +54 -0
  4. package/bin/lib/daemon-doctor-report.js +30 -0
  5. package/bin/lib/daemon-operations.js +190 -0
  6. package/bin/lib/daemon-rollback.js +158 -0
  7. package/bin/lib/daemon-status.js +128 -0
  8. package/bin/lib/daemon.js +109 -66
  9. package/bin/lib/doctor-migration.js +22 -7
  10. package/bin/lib/doctor-pi-config.js +70 -0
  11. package/bin/lib/doctor.js +2 -0
  12. package/bin/lib/engine-dependency.js +23 -0
  13. package/bin/lib/engine-models.js +5 -2
  14. package/bin/lib/launcher.js +15 -0
  15. package/bin/lib/provider-map.json +1 -1
  16. package/bin/lib/setup-opencode-models.d.ts +53 -0
  17. package/bin/lib/setup-opencode-models.js +121 -34
  18. package/package.json +2 -3
  19. package/plugin/CHANGELOG.md +80 -0
  20. package/plugin/extensions/memory-run-supervisor.mjs +1 -1
  21. package/plugin/extensions/omo-computer-use.js +1 -1
  22. package/plugin/extensions/omo-init-deep-advisor.js +2 -2
  23. package/plugin/extensions/omo-member.js +2 -2
  24. package/plugin/extensions/omo-task.js +2 -2
  25. package/plugin/extensions/omo.js +2 -2
  26. package/plugin/package.json +1 -1
  27. package/plugin/runtime/agent-toolkit-sdk/sdk.js +2 -2
  28. package/plugin/runtime/ast-grep-mcp/manifest.json +1 -1
  29. package/plugin/runtime/category-coverage/assets.generated.json +7 -0
  30. package/plugin/runtime/category-coverage/index.js +4 -4
  31. package/plugin/runtime/rollback-migrate.js +2 -0
  32. package/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  33. package/plugin/skills/hyperplan/SKILL.md +1 -0
  34. package/plugin/skills/init-deep/SKILL.md +1 -0
  35. package/plugin/skills/mass-ulw/SKILL.md +1 -0
  36. package/plugin/skills/refactor/SKILL.md +1 -0
  37. package/plugin/skills/remove-ai-slops/SKILL.md +1 -0
  38. package/plugin/skills/ulw-execute/SKILL.md +1 -0
  39. package/plugin/skills/ulw-loop/SKILL.md +1 -0
  40. package/plugin/skills/ulw-plan/SKILL.md +1 -0
  41. package/plugin/skills/ulw-research/SKILL.md +1 -0
@@ -4,10 +4,14 @@
4
4
  * converts each into what the native harness reads. Provider ids go through provider-map.json, the
5
5
  * table the credential stage uses, and every provider/model is checked against the engine's model
6
6
  * list; what does not resolve is returned with its reason, never converted. Read-only.
7
+ *
8
+ * Native's startup migration (omo-senpi config-startup) runs the same conversion without the
9
+ * engine's model list through `openCodeRoutingGap`, so both paths map names and providers alike.
7
10
  */
8
11
 
9
- import { existsSync, readdirSync, readFileSync } from "node:fs"
12
+ import { existsSync, readdirSync } from "node:fs"
10
13
  import { isAbsolute, join, relative, resolve } from "node:path"
14
+ import providerMap from "./provider-map.json" with { type: "json" }
11
15
  import { opencodeConfigSources, readOpencodeConfig } from "./setup-opencode-assets.js"
12
16
 
13
17
  // The OpenCode edition's legacy plugin files, highest precedence first, as its config migration
@@ -29,6 +33,9 @@ export const NATIVE_AGENT_NAMES = [
29
33
  // OpenCode's own primary agents: the native main session has no per-mode agent, it runs the default model.
30
34
  const OPENCODE_PRIMARY_AGENTS = new Set(["build", "plan"])
31
35
 
36
+ // The OpenCode edition's plan agents, keyed to the native agent that does the same job.
37
+ const AGENT_ALIASES = { metis: "plan-consultant", momus: "plan-reviewer" }
38
+
32
39
  // omo-config-core schema/legacy-category-names.ts: the loader renames these and reports it.
33
40
  const CATEGORY_ALIASES = { deep: "deep-low" }
34
41
 
@@ -44,10 +51,6 @@ function record(value) {
44
51
  return isPlainObject(value) ? value : {}
45
52
  }
46
53
 
47
- function readProviderMap() {
48
- return JSON.parse(readFileSync(new URL("./provider-map.json", import.meta.url), "utf8"))
49
- }
50
-
51
54
  // The first start of any current harness runs the config unification migration, which keeps none
52
55
  // of a legacy file's categories or agents and moves the file to
53
56
  // `~/.omo/migration-backup-<timestamp>-opencode-config/<path relative to home>`
@@ -69,12 +72,11 @@ function editionFile(path, home, notices) {
69
72
  }
70
73
 
71
74
  /**
72
- * The raw choices, before any translation. `opencodeBlock` is the `[opencode]` block of the user's
73
- * omo.json[c]: the unification migration's target, so an entry there wins over the legacy files.
75
+ * The OpenCode edition's own routing (`agents`, `categories`) from its legacy plugin files, or the
76
+ * migration backups the unification migration moved them into.
74
77
  */
75
- export function readModelChoices({ home, env, opencodeBlock }) {
78
+ export function readEditionRouting({ home, env }) {
76
79
  const notices = []
77
- const opencode = readOpencodeConfig(opencodeConfigSources(home, env).files, "model choices", notices)
78
80
  const globalDir = join(env.XDG_CONFIG_HOME || join(home, ".config"), "opencode")
79
81
  const explicitDir = env.OPENCODE_CONFIG_DIR?.trim()
80
82
  const directories = explicitDir ? [resolve(explicitDir), globalDir] : [globalDir]
@@ -83,14 +85,31 @@ export function readModelChoices({ home, env, opencodeBlock }) {
83
85
  .flatMap((directory) => EDITION_FILES.map((name) => editionFile(join(directory, name), home, notices)))
84
86
  .reverse()
85
87
  const edition = readOpencodeConfig(legacyFiles, "model choices", notices)
86
- const block = record(opencodeBlock)
88
+ return { agents: record(edition.agents), categories: record(edition.categories), notices }
89
+ }
90
+
91
+ /**
92
+ * The raw choices, before any translation. `opencodeBlock` is the `[opencode]` block of the user's
93
+ * omo.json[c]: the unification migration's target, so an entry there wins over the legacy files.
94
+ */
95
+ export function readModelChoices({ home, env, opencodeBlock }) {
96
+ const notices = []
97
+ const opencode = readOpencodeConfig(opencodeConfigSources(home, env).files, "model choices", notices)
98
+ const edition = readEditionRouting({ home, env })
87
99
  return {
88
100
  model: typeof opencode.model === "string" ? opencode.model : undefined,
89
101
  smallModel: typeof opencode.small_model === "string" ? opencode.small_model : undefined,
90
102
  opencodeAgents: record(opencode.agent),
91
- categories: { ...record(edition.categories), ...record(block.categories) },
92
- agents: { ...record(edition.agents), ...record(block.agents) },
93
- notices,
103
+ ...editionChoices(edition, opencodeBlock),
104
+ notices: [...notices, ...edition.notices],
105
+ }
106
+ }
107
+
108
+ function editionChoices(edition, opencodeBlock) {
109
+ const block = record(opencodeBlock)
110
+ return {
111
+ categories: { ...edition.categories, ...record(block.categories) },
112
+ agents: { ...edition.agents, ...record(block.agents) },
94
113
  }
95
114
  }
96
115
 
@@ -104,12 +123,12 @@ export function hasModelChoices(raw) {
104
123
  }
105
124
 
106
125
  function engineProvider(provider, modelId, context) {
107
- const { providerMap, oauthProviders, registry } = context
126
+ const { oauthProviders, registry } = context
108
127
  // A provider opencode signed in to with OAuth (`openai` through a ChatGPT plan) is served here by
109
128
  // the provider the credential stage says to `/login` to (provider-map.json `oauthLogins`), not by
110
129
  // the same id, which would need an API key the user never had.
111
130
  const login = oauthProviders.has(provider) && Object.hasOwn(providerMap.oauthLogins, provider) ? providerMap.oauthLogins[provider] : undefined
112
- if (login !== undefined && registry.models.get(login)?.has(modelId)) return login
131
+ if (login !== undefined && registry?.models.get(login)?.has(modelId)) return login
113
132
  if (providerMap.builtinProviderIds.includes(provider)) return provider
114
133
  return providerMap.providers[provider] ?? provider
115
134
  }
@@ -125,20 +144,30 @@ function splitSuffix(text) {
125
144
  return { selector: text.slice(0, colon), suffix: `:${token}` }
126
145
  }
127
146
 
128
- /** `{ ref, provider, modelId }` for a model string the engine serves, else `{ reason }`. */
147
+ /**
148
+ * `{ ref, provider, modelId }` for a model string the engine serves, else `{ reason }`. Without a
149
+ * `context.registry` (Native startup, which has no engine model list) only the provider is checked,
150
+ * against the engine's builtin providers.
151
+ */
129
152
  export function translateModelRef(raw, context) {
130
153
  if (typeof raw !== "string" || raw.trim() === "") return { reason: "not a model string" }
131
154
  const { selector, suffix } = splitSuffix(raw.trim())
132
155
  const slash = selector.indexOf("/")
156
+ const { registry } = context
133
157
  if (slash <= 0) {
134
158
  // A bare id is matched against every provider by the native resolver, so any provider serving it will do.
135
- const served = [...context.registry.models.values()].some((ids) => ids.has(selector))
159
+ const served = registry === undefined || [...registry.models.values()].some((ids) => ids.has(selector))
136
160
  return served ? { ref: `${selector}${suffix}`, modelId: selector } : { reason: `no omo provider serves a model named ${selector}` }
137
161
  }
138
162
  const source = selector.slice(0, slash)
139
163
  const modelId = selector.slice(slash + 1)
140
164
  const provider = engineProvider(source, modelId, context)
141
- const ids = context.registry.models.get(provider)
165
+ if (registry === undefined) {
166
+ return providerMap.builtinProviderIds.includes(provider)
167
+ ? { ref: `${provider}/${modelId}${suffix}`, provider, modelId }
168
+ : { reason: `provider ${source} is not one omo serves or was carried over` }
169
+ }
170
+ const ids = registry.models.get(provider)
142
171
  if (ids === undefined) return { reason: `provider ${source} is not one omo serves or was carried over` }
143
172
  if (!ids.has(modelId)) {
144
173
  return {
@@ -180,10 +209,10 @@ function chainItem(item, take) {
180
209
  * OpenCode edition tried them in. Undefined when the entry named models and none of them resolve:
181
210
  * its tuning alone would retune the builtin chain the user had replaced.
182
211
  */
183
- function convertEntry(label, entry, context, dropped) {
212
+ function convertEntry(label, entry, context, dropped, target) {
184
213
  const take = (raw) => {
185
214
  const result = translateModelRef(raw, context)
186
- if (result.reason !== undefined) dropped.push(`${label} model ${String(raw)}: ${result.reason}`)
215
+ if (result.reason !== undefined) dropped.push({ text: `${label} model ${String(raw)}: ${result.reason}`, target })
187
216
  return result.ref
188
217
  }
189
218
  const folded = !Array.isArray(entry.models) && entry.fallback_models !== undefined
@@ -208,17 +237,31 @@ function convertAgents(raw, context, dropped) {
208
237
  for (const [origin, entries] of [["opencode agent", raw.opencodeAgents], ["agent", raw.agents]]) {
209
238
  for (const [name, entry] of Object.entries(entries)) {
210
239
  if (!hasModelChoice(entry)) continue
211
- if (!native.has(name)) {
240
+ const target = Object.hasOwn(AGENT_ALIASES, name) ? AGENT_ALIASES[name] : name
241
+ // A native name beside its alias in the same source wins, as categories resolve the pair.
242
+ if (target !== name && Object.hasOwn(entries, target) && hasModelChoice(entries[target])) continue
243
+ if (!native.has(target)) {
212
244
  dropped.push(OPENCODE_PRIMARY_AGENTS.has(name) && origin === "opencode agent"
213
- ? `${origin} ${name}: the OpenCode primary agent; omo's main session runs the default model instead`
214
- : `${origin} ${name}: omo has no agent of that name (its agents: ${NATIVE_AGENT_NAMES.join(", ")}); route that work through a category instead`)
245
+ ? { text: `${origin} ${name}: the OpenCode primary agent; omo's main session runs the default model instead` }
246
+ : {
247
+ text: `${origin} ${name}: omo has no agent of that name (its agents: ${NATIVE_AGENT_NAMES.join(", ")}); route that work through a category instead`,
248
+ target: { section: "categories", name: "<category>", model: firstModel(entry, context) },
249
+ })
215
250
  continue
216
251
  }
217
- const converted = convertEntry(`${origin} ${name}`, entry, context, dropped)
218
- if (converted !== undefined) agents.set(name, converted)
252
+ const converted = convertEntry(`${origin} ${name}`, entry, context, dropped, { section: "agents", name: target })
253
+ if (converted !== undefined) agents.set(target, { entry: converted, from: name })
219
254
  }
220
255
  }
221
- return [...agents].map(([name, entry]) => ({ name, entry })).sort((left, right) => left.name.localeCompare(right.name))
256
+ return [...agents].map(([name, { entry, from }]) => ({ name, entry, from })).sort((left, right) => left.name.localeCompare(right.name))
257
+ }
258
+
259
+ // The model an unmappable agent named, as the native harness would spell it when it can.
260
+ function firstModel(entry, context) {
261
+ const raw = entry.model ?? [entry.models, entry.fallback_models].flat().find((item) => item !== undefined)
262
+ const text = isPlainObject(raw) ? raw.model : raw
263
+ if (typeof text !== "string") return undefined
264
+ return translateModelRef(text, context).ref ?? text
222
265
  }
223
266
 
224
267
  function convertCategories(raw, context, dropped) {
@@ -228,21 +271,21 @@ function convertCategories(raw, context, dropped) {
228
271
  const target = Object.hasOwn(CATEGORY_ALIASES, name) ? CATEGORY_ALIASES[name] : name
229
272
  // A canonical key beside its retired alias wins, as the loader resolves the pair.
230
273
  if (target !== name && Object.hasOwn(raw.categories, target)) continue
231
- const converted = convertEntry(`category ${name}`, entry, context, dropped)
232
- if (converted !== undefined) categories.set(target, converted)
274
+ const converted = convertEntry(`category ${name}`, entry, context, dropped, { section: "categories", name: target })
275
+ if (converted !== undefined) categories.set(target, { entry: converted, from: name })
233
276
  }
234
- return [...categories].map(([name, entry]) => ({ name, entry })).sort((left, right) => left.name.localeCompare(right.name))
277
+ return [...categories].map(([name, { entry, from }]) => ({ name, entry, from })).sort((left, right) => left.name.localeCompare(right.name))
235
278
  }
236
279
 
237
280
  function convertDefault(model, context, dropped) {
238
281
  if (model === undefined) return undefined
239
282
  const result = translateModelRef(model, context)
240
283
  if (result.reason !== undefined) {
241
- dropped.push(`default model ${model}: ${result.reason}`)
284
+ dropped.push({ text: `default model ${model}: ${result.reason}` })
242
285
  return undefined
243
286
  }
244
287
  if (result.provider === undefined) {
245
- dropped.push(`default model ${model}: names no provider, and omo's default model needs one`)
288
+ dropped.push({ text: `default model ${model}: names no provider, and omo's default model needs one` })
246
289
  return undefined
247
290
  }
248
291
  return { source: model, provider: result.provider, modelId: result.modelId, ref: `${result.provider}/${result.modelId}` }
@@ -253,11 +296,55 @@ function convertDefault(model, context, dropped) {
253
296
  * `oauthProviders` are the opencode provider ids whose opencode credential is an OAuth login.
254
297
  */
255
298
  export function convertModelChoices(raw, registry, oauthProviders = new Set()) {
256
- const context = { registry, providerMap: readProviderMap(), oauthProviders }
299
+ const context = { registry, oauthProviders }
257
300
  const dropped = []
258
301
  const defaultModel = convertDefault(raw.model, context, dropped)
259
- if (raw.smallModel !== undefined) dropped.push(`small_model ${raw.smallModel}: omo has no small-model setting`)
302
+ if (raw.smallModel !== undefined) dropped.push({ text: `small_model ${raw.smallModel}: omo has no small-model setting` })
260
303
  const categories = convertCategories(raw, context, dropped)
261
304
  const agents = convertAgents(raw, context, dropped)
262
- return { defaultModel, categories, agents, dropped }
305
+ return { defaultModel, categories, agents, dropped: dropped.map((item) => item.text) }
306
+ }
307
+
308
+ // omo-config-core loader/resolution.ts folds a legacy `[senpi]` block under `[native]`.
309
+ const NATIVE_BLOCKS = ["[senpi]", "[native]"]
310
+
311
+ /** The omo.jsonc member that sets `value` for `section.name` in the native harness. */
312
+ export function nativeRoutingLine(section, name, value) {
313
+ const members = Object.entries(value).map(([key, member]) => `"${key}": ${JSON.stringify(member)}`).join(", ")
314
+ return `"[native]": { "${section}": { "${name}": { ${members} } } }`
315
+ }
316
+
317
+ /**
318
+ * The OpenCode edition's model settings (`edition`: readEditionRouting, overlaid by `document`'s
319
+ * `[opencode]` block) that the native harness of `document` (a user omo.json[c]) does not use,
320
+ * one line each with the omo.jsonc member that would set it natively. The legacy migration never
321
+ * copies agents or categories (#7270), and `omo setup` carries them only with the user's consent, so
322
+ * this only reports: Native's startup notice and `omo doctor` print these lines. Names and providers
323
+ * convert as setup converts them, without the engine's model list; a key the native view (shared
324
+ * base, `[senpi]`, `[native]`) already sets is the user's and is not reported.
325
+ */
326
+ export function openCodeRoutingGap(document, edition) {
327
+ const raw = { opencodeAgents: {}, ...editionChoices(edition, record(document)["[opencode]"]) }
328
+ const context = { registry: undefined, oauthProviders: new Set() }
329
+ const dropped = []
330
+ const converted = { categories: convertCategories(raw, context, dropped), agents: convertAgents(raw, context, dropped) }
331
+ const carriable = ["agents", "categories"].flatMap((section) => converted[section]
332
+ .filter(({ name }) => !isConfigured(document, section, name))
333
+ .map(({ name, entry, from }) => `${section === "agents" ? "agent" : "category"} ${from}: add ${nativeRoutingLine(section, name, entry)}`))
334
+ const unmapped = dropped
335
+ .filter(({ target }) => target === undefined || !isConfigured(document, target.section, target.name))
336
+ .map(({ text, target }) => target === undefined
337
+ ? text
338
+ : `${text}: add ${nativeRoutingLine(target.section, target.name, { model: target.model ?? "<provider>/<model>" })}`)
339
+ return [...carriable, ...unmapped]
340
+ }
341
+
342
+ /** The one-line lead of the report, for `lines` from openCodeRoutingGap. */
343
+ export function openCodeRoutingNotice(lines) {
344
+ return `OpenCode edition model settings Native does not use; run omo setup to carry them over, or set them in omo.jsonc: ${lines.join("; ")}`
345
+ }
346
+
347
+ function isConfigured(document, section, name) {
348
+ return [record(document)[section], ...NATIVE_BLOCKS.map((key) => record(record(document)[key])[section])]
349
+ .some((entries) => Object.hasOwn(record(entries), name))
263
350
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omo-ai",
3
- "version": "5.1.0",
3
+ "version": "5.1.2",
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,8 +17,7 @@
17
17
  "postinstall": "node bin/senpi-patch.mjs"
18
18
  },
19
19
  "dependencies": {
20
- "@babel/parser": "8.0.4",
21
- "@code-yeongyu/senpi": "2026.9.28-7"
20
+ "@code-yeongyu/senpi": "2026.9.29-3"
22
21
  },
23
22
  "repository": {
24
23
  "type": "git",
@@ -7,6 +7,86 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [5.1.2] - 2026-09-29
11
+
12
+ ### Added
13
+
14
+ **A one-line installer at get.omo.dev.** ([#9166](https://github.com/code-yeongyu/oh-my-openagent/issues/9166)) `curl -fsSL https://get.omo.dev/install.sh | bash` on macOS and Linux, or `irm https://get.omo.dev/install.ps1 | iex` in Windows PowerShell, installs the native `omo` binary for your OS and CPU into `~/.local/bin`, after checking it against the release `SHA256SUMS`. When get.omo.dev is unreachable it falls back to the npm registry and GitHub Releases. `bun add -g omo-ai` keeps working. omo.dev now shows the command for your OS and has an install page at `/docs/install` ([#9177](https://github.com/code-yeongyu/oh-my-openagent/issues/9177)).
15
+
16
+ **`web_search` can use your own SearXNG.** ([senpi#2339](https://github.com/code-yeongyu/senpi/issues/2339)) Add `{ "provider": "searxng", "baseUrl": "http://localhost:8888" }` to `websearch.json`.
17
+
18
+ ### Changed
19
+
20
+ **An unknown slash command no longer reaches the model by accident.** ([senpi#2348](https://github.com/code-yeongyu/senpi/issues/2348)) A typo like `/ulw-exec` goes back into the editor with a `Did you mean /skill:ulw-execute?` hint instead of being sent as a prompt. A second Enter on the unchanged text sends it as a normal message, and Esc keeps editing.
21
+
22
+ **Moving from the OpenCode edition, OmO Native tells you which model choices it ignores.** ([#9147](https://github.com/code-yeongyu/oh-my-openagent/issues/9147), [#6794](https://github.com/code-yeongyu/oh-my-openagent/issues/6794)) On its first start, Native lists each agent and category model from your OpenCode edition settings that it ignores, with the `omo setup` step and the `omo.jsonc` key that carries it over. `omo setup` itself now carries the edition's `metis` and `momus` models over to `plan-consultant` and `plan-reviewer`.
23
+
24
+ **Hosted web search runs on the provider's cheaper search model.** ([senpi#2340](https://github.com/code-yeongyu/senpi/issues/2340)) On the same login, for example `claude-haiku-4-5` on Claude routes, when your model list shows it at a lower price than the session model. If it fails or finds nothing, the search retries on the session model. `"nativeModel": "session"` in `websearch.json` restores the old behavior.
25
+
26
+ **Without a `websearch.json`, `web_search` tries more than DuckDuckGo.** ([senpi#2339](https://github.com/code-yeongyu/senpi/issues/2339)) It now tries DuckDuckGo, Exa, Startpage, Mojeek, Ecosia and Google in turn and pauses an engine that blocks it, so your queries may reach these services. List only the providers you want in `websearch.json` to keep them away from the rest.
27
+
28
+ **Remote MCP servers declared by a skill no longer receive your `bearerTokenEnv` token.** ([senpi#2345](https://github.com/code-yeongyu/senpi/issues/2345)) The skill picks the server's URL, so declare that server in your own `mcp.json` to keep its auth. Stdio servers from skills you installed now expand `${VAR}` the way your own `mcp.json` does.
29
+
30
+ ### Fixed
31
+
32
+ **A running session survives `omo update`.** ([senpi#2358](https://github.com/code-yeongyu/senpi/issues/2358)) `omo update` and `bun install -g` replace the installed package, and a session started earlier used to fail every later request with `Cannot find module './<chunk>-<hash>.js'` until you restarted it. Each launch now runs from its own copy of the engine.
33
+
34
+ **Config you edit in `~/.pi/agent` is no longer silently ignored.** ([#9173](https://github.com/code-yeongyu/oh-my-openagent/issues/9173)) After OmO copied `~/.pi/agent` to `~/.omo/agent`, edits to the old copy had no effect. The next start now warns once per edited file and names the file OmO reads, `omo doctor` lists the same files, and `omo config import-pi <file>` copies the edit over.
35
+
36
+ **`omo doctor` no longer fails its computer-use check on every OS.** ([#9193](https://github.com/code-yeongyu/oh-my-openagent/issues/9193)) 5.1.1 shipped without an asset the check reads, so it always reported `ENOENT` for `assets.generated.json`.
37
+
38
+ **A finished task child no longer stays "running" after its parent's host dies.** ([#9183](https://github.com/code-yeongyu/oh-my-openagent/issues/9183)) The child's record kept its dead owner, so Desktop showed a working agent that no longer existed and task status and DAG waits kept waiting on it.
39
+
40
+ **Scrolling on X11 delivers every wheel click.** ([#9136](https://github.com/code-yeongyu/oh-my-openagent/issues/9136)) A foreground scroll from computer use sometimes dropped clicks and still reported success.
41
+
42
+ **Bedrock accepts tool schemas with a root `anyOf`, `oneOf` or `allOf`.** ([senpi#1947](https://github.com/code-yeongyu/senpi/issues/1947)) It also accepts schemas without an object type, which it used to reject.
43
+
44
+ **Memory reflection works when your only provider comes from an extension.** ([#9175](https://github.com/code-yeongyu/oh-my-openagent/issues/9175)) If that provider disappears you get one notice instead of the same failure on every run.
45
+
46
+ **Cursor runs each tool call once.** ([senpi#2334](https://github.com/code-yeongyu/senpi/issues/2334)) It used to run every call a second time, and the replayed write could undo a file fix.
47
+
48
+ **A resumed Claude conversation keeps its resume point.** ([senpi#1972](https://github.com/code-yeongyu/senpi/issues/1972)) When a monitor or goal starts the first turn on restore, the conversation no longer resends its whole history.
49
+
50
+ **In headless runs, a late Kibitzer verdict no longer replaces the final answer.** ([#9158](https://github.com/code-yeongyu/oh-my-openagent/pull/9158)) It used to add a turn after the answer that ended in `NO_REPLY`.
51
+
52
+ **An MCP server that changes its tool list mid-session gets the new list registered.** ([senpi#2188](https://github.com/code-yeongyu/senpi/issues/2188)) Added tools now show up and removed ones stay gone.
53
+
54
+ **Native OpenAI requests with hosted web search no longer fail every turn with `Tool choice 'web_search' not found in 'tools' parameter`.** ([senpi#2234](https://github.com/code-yeongyu/senpi/issues/2234))
55
+
56
+ **Web search through OpenAI and xAI lists only pages the search returned.** ([senpi#2337](https://github.com/code-yeongyu/senpi/issues/2337)) Links the model wrote on its own are no longer counted as sources.
57
+
58
+ **A rejected OpenAI Responses WebSocket request shows the provider's message.** ([senpi#2235](https://github.com/code-yeongyu/senpi/issues/2235)) It used to show `Error Code undefined: undefined`.
59
+
60
+ **Claude subscription sessions keep the field descriptions in custom tool schemas.** ([senpi#2145](https://github.com/code-yeongyu/senpi/issues/2145)) Claude no longer wastes a call on a missing required field.
61
+
62
+ **The OpenAI-compatible adapter merges back-to-back user messages for non-OpenAI hosts.** ([senpi#2120](https://github.com/code-yeongyu/senpi/issues/2120)) Direct OpenAI requests are unchanged.
63
+
64
+ **Extensions that depend on sloppy-mode CommonJS packages load again under Bun.** ([senpi#1841](https://github.com/code-yeongyu/senpi/issues/1841))
65
+
66
+ **Picking a skill that needs input from the slash menu now waits for you to type it.** ([#9168](https://github.com/code-yeongyu/oh-my-openagent/issues/9168)) On OmO Native, choosing `/ulw-execute`, `/ulw-plan`, `/ulw-loop`, `/ulw-research`, `/mass-ulw`, `/hyperplan`, `/init-deep`, `/refactor` or `/remove-ai-slops` from the slash menu with Enter sent it right away with nothing after it, so the skill started without the plan name, request or goal it works on. These skills now declare what they take, and Enter leaves `/ulw-execute ` (or `/skill:ulw-execute `) in the input with the hint shown in the menu; type the arguments and press Enter again to send. Skills that take no input still run on one Enter. The waiting half needs the engine release that reads the hint (code-yeongyu/senpi#2258); until OmO adopts it, the menu behaves as before.
67
+
68
+ **Your own `computer-use` skill no longer opens every session with a "Skill conflicts" warning.** ([#9160](https://github.com/code-yeongyu/oh-my-openagent/issues/9160)) With computer use on, OmO Native added its built-in `computer-use` skill on every start, even when you already had a skill of that name (Orca CLI ships one), so each session opened with a collision box. The built-in skill now steps aside for a same-name user, project or package skill, which is the one that loads, and `/computer status` says so in one line. The same applies to the `x-search` skill. `disabled_skills` now hides both the way it hides the bundled skills; the `computer` and `x_search` tools stay available either way.
69
+
70
+ ## [5.1.1] - 2026-09-29
71
+
72
+ ### Changed
73
+
74
+ **The `unspecified-low` category now opens on Claude Sonnet 5.5 at `medium`.** ([#9144](https://github.com/code-yeongyu/oh-my-openagent/issues/9144)) `unspecified-low` is where delegated work lands when no specialist category fits and the job is contained. It led with MiMo V2.6 Pro at `max`; Claude Sonnet 5.5 at `medium` now comes first, on the Claude subscription, Anthropic API, GitHub Copilot and OpenCode lanes. Every earlier rung stays, in the same order, behind it: MiMo V2.6 Pro, Grok 4.7, GPT-5.6 Terra, Claude Sonnet 5, Qwen 3.8 Max Preview, DeepSeek V4 Pro and MiMo V2.5 Pro. OmO Native picks Sonnet 5.5 once the engine release that lists it is adopted; until then the category keeps running on MiMo V2.6 Pro.
75
+
76
+ **The `deep-low` category now opens on plain GPT-5.6 Sol at `medium`.** ([#9144](https://github.com/code-yeongyu/oh-my-openagent/issues/9144)) `deep-low` led with the GPT-5.6 Sol Fast tier and fell back to plain GPT-5.6 Sol where the Fast tier is not served. The order is now reversed: plain GPT-5.6 Sol at `medium` runs first on every lane that serves it, and the Fast tier is the fallback on the OpenAI lanes. The category still opens when either Sol tier is connected.
77
+
78
+ **Each session's task children now run on their own host.** ([#9003](https://github.com/code-yeongyu/oh-my-openagent/issues/9003)) On macOS and Linux, background task children used to run as sessions of one engine host shared by every session on the machine, so one crash or one stuck host hit everybody's children. Now every session gets its own host (the Desktop app moves each thread onto its own host in its own release). A crash, idle exit or upgrade of one host leaves the others alone, and a finished session's host exits 15 minutes after its last client leaves. Isolation costs memory. With 4 parent sessions of 4 children each we measured 2.7 GB RSS and 0.8 GB physical footprint, against 0.78 GB and 0.24 GB for one shared host. An idle host costs 126 MB of physical footprint, about what the old shared host cost idle (137 MB). A session's host now starts in the background on its first prompt (`task.host_shard_prewarm`, default `first-turn`; `session-start` and `off` are also available), so the first child no longer waits for a host to boot: 1116 ms p50 / 1678 ms p95 from the `task` call to the child's first model request, against 1666 / 3280 ms without the pre-warm and 979 / 1593 ms on the old shared host when it was already running. Nothing caps the number of hosts. Children started by the previous release stay on the shared host until they finish. Before you downgrade, read the rollback steps in [docs/reference/omo-daemon.md](docs/reference/omo-daemon.md). Skipping them can leave a retained child stuck.
79
+
80
+ **`omo daemon` and `omo doctor` show every host.** ([#9003](https://github.com/code-yeongyu/oh-my-openagent/issues/9003)) `omo daemon status` (and `--json`) and `omo doctor` list the shared daemon and every per-session host with its pid, sessions, memory and crash count. `omo daemon gc` removes the state of hosts that have provably exited. `omo daemon stop --all` and `omo daemon handoff` act on every host, and `omo daemon stop --drain --all --wait` waits until every host has really stopped. `omo daemon rollback-prepare` moves retained children back to the shared host before a downgrade. `omo daemon run --foreground` now exits 2 because the engine host always runs detached; `--persistent` is still accepted.
81
+
82
+ **A crashed task host tells its parent once, and says when the children are back.** ([#9003](https://github.com/code-yeongyu/oh-my-openagent/issues/9003)) When a session's task host crashes while a child is working, the session gets one warning naming the host, its supervisor pid ("supervisor pid N") and the cause, and how many children are reattaching ("1 child" or "N children"). A closing line follows when they are all back; it adds ", C cancelled" when some of them were cancelled during recovery.
83
+
84
+ **`thread_read` labels tool results as `tool`.** A thread read from a live host now marks tool results with role `tool`, the same as a thread read from its transcript file. Models that read `thread_read` output see one role for tool results on both paths.
85
+
86
+ ### Fixed
87
+
88
+ **LazyCodex keeps the model you picked for a subagent role.** ([#5245](https://github.com/code-yeongyu/oh-my-openagent/issues/5245)) Every reinstall and marketplace auto-update copied the bundled agent files over `~/.codex/agents/` and put `explorer`, `librarian` and the other roles back on the bundled model, even when you had changed it. A model you set in those files now survives updates; a model LazyCodex wrote itself still moves to the new default. For a durable choice, set `[codex].agents.<role>.model` (and `reasoning`) in `~/.omo/omo.jsonc`; each sync applies it, and removing it restores the bundled default.
89
+
10
90
  ## [5.1.0] - 2026-09-28
11
91
 
12
92
  5.1.0 adds computer use to OmO Native as an experimental feature, and fixes Claude subscription logins across many sessions, GitHub Copilot tokens and hosts, goals that looped on a rejected login, and a long list of background task problems. `omo update` installs it, or run:
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- // omo:_0dqSosdAVjnOoT621bL6ZmEBL5PmfVFFNWa-At-znE:VfbjzNRugIPcA7fFoNmdsR-DDYSGWaYZ_nAdaDJhEqU
2
+ // omo:qwO6ZmfeJED6NR5LQgtRMlqT6Yfh9gyZt5sYnfhBCbw:VfbjzNRugIPcA7fFoNmdsR-DDYSGWaYZ_nAdaDJhEqU
3
3
  import{spawn as t}from"node:child_process";import*as e from"node:fs";import*as r from"node:fs/promises";function n(t){if(t instanceof Error&&"code"in t)return"string"==typeof t.code?t.code:void 0}async function i(t){for(let e=0;;e+=1)try{return await t()}catch(t){if("EINTR"!==n(t)||e>=128)throw t}}function o(t){for(let e=0;;e+=1)try{return t()}catch(t){if("EINTR"!==n(t)||e>=128)throw t}}import{Buffer as a}from"node:buffer";import{constants as s}from"node:fs";function l(t){return"number"==typeof t?0!==(t&s.O_EXCL):"string"==typeof t&&t.toLowerCase().includes("x")}function c(t){return"function"==typeof t}function u(t){return(...e)=>i(async()=>t(...e))}function d(t){return(...e)=>o(()=>t(...e))}function f(t,e){let r="async"===e?u:d,n=new Map;return new Proxy(t,{get(t,e){if(n.has(e))return n.get(e);let i=Reflect.get(t,e);if(!c(i))return i;let o=r(i),a=Reflect.get(i,"native");return c(a)&&Object.assign(o,{native:r(a)}),n.set(e,o),o}})}var p=new Set(["stat","sync","datasync","chmod","chown","utimes","truncate"]);var m=f(r,"async"),w=f(e,"sync"),{access:h,chmod:y,copyFile:g,cp:v,link:b,lstat:E,mkdir:S,mkdtemp:_,readFile:I,readdir:x,realpath:N,rename:O,rm:k,rmdir:P,stat:T,unlink:R}=m,{accessSync:M,chmodSync:A,fsyncSync:$,linkSync:C,lstatSync:D,mkdirSync:L,readFileSync:U,readdirSync:q,realpathSync:j,statSync:F,unlinkSync:J,writeFileSync:B,writeSync:G}=w,{constants:H,watch:V}=e,Y=async(...t)=>{let e=()=>r.open(...t);return function(t){return new Proxy(t,{get(t,e){let r=Reflect.get(t,e);if(!c(r))return r;let o=e=>Reflect.apply(r,t,[...e]);return"close"===e||e===Symbol.asyncDispose?(...t)=>{let e=o(t);return e instanceof Promise?e.catch(t=>{if("EINTR"!==n(t))throw t}):e}:"string"==typeof e&&p.has(e)?(...t)=>{let e=o(t);return e instanceof Promise?e.catch(e=>{if("EINTR"!==n(e))throw e;return i(async()=>o(t))}):e}:(...t)=>o(t)}})}(await(l(t[1]??"r")?e():i(e)))},z=(t,r,n)=>function(t,e,r,n){return l(r)?t(e,r,n):o(()=>t(e,r,n))}(e.openSync,t,r,n??void 0);function K(t){try{e.closeSync(t)}catch(t){if("EINTR"!==n(t))throw t}}function X(t){try{return o(()=>e.statSync(t)),!0}catch{return!1}}import{join as Z}from"node:path";import{fileURLToPath as W}from"node:url";import{randomUUID as Q}from"node:crypto";import{dirname as tt}from"node:path";async function et(t){return JSON.parse(await I(t,"utf8"))}async function rt(t,e,r=384){let n=`${t}.tmp-${process.pid}-${Q()}`,i=await Y(n,"wx",r),o=!1;try{try{await i.writeFile(`${JSON.stringify(e,null,2)}\n`,"utf8"),await i.sync()}finally{await i.close()}await O(n,t),o=!0,await async function(t){try{let e=await Y(t,"r");try{await e.sync()}finally{await e.close()}}catch(t){let e=t instanceof Error&&"code"in t?t.code:void 0;if("win32"!==process.platform||!["EISDIR","EPERM"].includes(String(e)))throw t}}(tt(t))}finally{o||await it(n)}}async function nt(t,e){let r=await et(t);await rt(t,{...r,...e})}async function it(t){try{await R(t)}catch(t){if(!(t instanceof Error&&"code"in t&&"ENOENT"===t.code))throw t}}import{spawn as ot,spawnSync as at}from"node:child_process";import{execFile as st}from"node:child_process";var lt=null;function ct(){return lt??=async function(){if("darwin"!==process.platform)return null;try{let{dlopen:t,FFIType:e}=await(import("bun:ffi")),r=t("/usr/lib/libSystem.B.dylib",{proc_pidinfo:{args:[e.i32,e.i32,e.u64,e.ptr,e.i32],returns:e.i32}});return(t,e,n,i,o)=>Number(r.symbols.proc_pidinfo(t,e,n,i,o))}catch{return null}}(),lt}var ut=null;async function dt(t){if("win32"!==process.platform)return null;if(!Number.isSafeInteger(t)||t<=0)return null;ut??=async function(){if("win32"!==process.platform)return null;try{let{dlopen:t,FFIType:e}=await(import("bun:ffi"));return t("kernel32.dll",{OpenProcess:{args:[e.u32,e.i32,e.u32],returns:e.ptr},GetProcessTimes:{args:[e.ptr,e.ptr,e.ptr,e.ptr,e.ptr],returns:e.i32},CloseHandle:{args:[e.ptr],returns:e.i32}}).symbols}catch{return null}}();let e=await ut;if(null===e)return null;let r=e.OpenProcess(4096,0,t);if(null===r||0===r||0n===r)return null;try{let t=new BigUint64Array(1),n=e.GetProcessTimes(r,t,new BigUint64Array(1),new BigUint64Array(1),new BigUint64Array(1)),i=t[0]??0n;return 0===n||0n===i?null:i}finally{e.CloseHandle(r)}}async function ft(t,e){return await new Promise(r=>{st(t,e,{encoding:"utf8",timeout:2e3,windowsHide:!0},(t,e)=>{if(null!==t)return void r(null);let n=e.trim();r(n.length>0?n:null)})})}var pt=function(t,e){let r;return n=>n!==e?t(n):(r??=t(n).then(t=>(null===t&&(r=void 0),t),t=>{throw r=void 0,t}),r)}(async function(t){if("linux"===process.platform)return await async function(t){try{let e=await I(`/proc/${t}/stat`,"utf8"),r=e.lastIndexOf(")");if(r<0)return null;let n=e.slice(r+2).trim().split(/\s+/)[19];return void 0===n?null:`linux-proc-start-ticks:${n}`}catch{return null}}(t);if("darwin"===process.platform||"freebsd"===process.platform){if("dead"===wt(t))return null;let e=await async function(t){if("darwin"!==process.platform)return null;if(!Number.isSafeInteger(t)||t<=0)return null;let e=await ct();if(null===e)return null;let r=new Uint8Array(136),n=0;try{n=e(t,3,0n,r,136)}catch{return null}if(136!==n)return null;let i=new DataView(r.buffer,r.byteOffset,r.byteLength),o=Number(i.getBigUint64(120,!0));return o>0?o:null}(t);if(null!==e)return`proc-start-epoch:${e}`;let r=await ft("/bin/ps",["-o","lstart=","-p",String(t)]);return null===r?null:`ps-lstart:${r.replace(/\s+/g," ")}`}return"win32"===process.platform?await async function(t){if("dead"===wt(t))return null;let e=await dt(t);if(null!==e)return`win32-creation-filetime:${e}`;let r=await ft("powershell.exe",["-NoProfile","-Command",`(Get-Process -Id ${t} -ErrorAction SilentlyContinue).StartTime.ToUniversalTime().ToString('o')`]);return null===r?null:`win32-creation-date:${r}`}(t):null},process.pid);function mt(t){let e=t.indexOf(":");return e<=0?null:t.slice(0,e)}function wt(t){try{return process.kill(t,0),"alive"}catch(t){let e=function(t){if(t instanceof Error&&"code"in t)return"string"==typeof t.code?t.code:void 0}(t);return"ESRCH"===e?"dead":"EPERM"===e?"alive":"unknown"}}function ht(){return"1"===process.env.OMO_MEMORY_SUPERVISOR_ALLOW_TEST_SEAMS}function yt(){if(ht()){let t=process.env.OMO_MEMORY_SUPERVISOR_PLATFORM;if("posix"===t||"win32"===t)return t}return"win32"===process.platform?"win32":"posix"}function gt(t,e){let r=ht()?process.env.OMO_MEMORY_SUPERVISOR_CLOCK_PATH:void 0;if(void 0===r){let r=setTimeout(e,Math.max(0,t-Date.now()));return()=>clearTimeout(r)}let n=!1,i=()=>{if(n)return;let i=bt(r);!Number.isFinite(i)||i<t||(n=!0,o?.close(),clearInterval(a),e())};if(bt(r)>=t)return n=!0,e(),()=>{};let o=V(r,i),a=setInterval(i,vt);return i(),()=>{n||(n=!0,o.close(),clearInterval(a))}}var vt=25;function bt(t){let e=q(t).map(t=>/^(\d+)-(-?\d+(?:\.\d+)?)$/.exec(t)).filter(t=>null!==t).sort((t,e)=>Number(t[1])-Number(e[1])).at(-1);return void 0===e?Number.NaN:Number(e[2])}function Et(t,e){if(!ht())return;let r=process.env.OMO_MEMORY_SUPERVISOR_TASKKILL_RUN_DIR;void 0!==r&&B(`${r}/${t}-${process.pid}.json`,`${JSON.stringify({targetPid:e})}\n`,"utf8")}function St(t){if(!ht())return;let e=process.env[t];if(void 0===e)return;let r=JSON.parse(e);if(!Array.isArray(r)||!r.every(t=>"string"==typeof t)||0===r.length)throw TypeError(`${t} must be a non-empty string array`);return r}function _t(t,e,r){let[n,...i]=t;if(void 0===n)throw TypeError("termination command is required");if(r){let t=at(n,[...i,...e],{env:process.env,stdio:"ignore",windowsHide:!0});if(void 0!==t.error)throw t.error;return}ot(n,[...i,...e],{env:process.env,stdio:"ignore",windowsHide:!0}).once("error",t=>process.stderr.write(`${t.message}\n`))}function It(t,e){Et(`posix-${e}`,t);let r=St("OMO_MEMORY_SUPERVISOR_POSIX_SIGNAL_COMMAND");if(void 0===r)try{process.kill(-t,e)}catch(t){if(!(t instanceof Error&&"code"in t&&"ESRCH"===t.code))throw t}else _t(r,["--signal-group",String(t),e],!1)}function xt(t){void 0!==t&&Et("win32-graceful",t)}function Nt(t,e){"win32"===t?(xt(e.pid),e.kill()):void 0!==e.pid&&It(e.pid,"SIGTERM")}function Ot(t,e,r=!1){void 0!==e&&("win32"===t?function(t,e){_t(St("OMO_MEMORY_SUPERVISOR_TASKKILL_COMMAND")??["taskkill"],["/pid",String(t),"/T","/F"],e)}(e,r):It(e,"SIGKILL"))}async function kt(t){return await pt(t)}import{join as Pt}from"node:path";function Tt(t){return"object"==typeof t&&null!==t}function Rt(t,e){try{return t?.[e]}catch(t){return void Error}}function Mt(t,e){let r=Rt(t,e);return Tt(r)?r:void 0}function At(t){if(!t)return"";if("string"==typeof t)return t.toLowerCase();if(!Tt(t))try{return JSON.stringify(t).toLowerCase()}catch(t){if(!(t instanceof Error))throw t;return""}let e=Mt(t,"data"),r=[e,Mt(t,"error"),t,Mt(e,"error")].filter(t=>void 0!==t);for(let t of r){let e=Rt(t,"message");if("string"==typeof e&&e.length>0)return e.toLowerCase()}let n=Rt(t,"name");if("string"==typeof n&&n.length>0){let t=n.match(/:\s*(.+)/);if(t)return t[1]?.trim().toLowerCase()??""}try{return JSON.stringify(t).toLowerCase()}catch(t){if(!(t instanceof Error))throw t;return""}}function $t(t,e){if(function(t){return"object"==typeof t&&null!==t}(t))try{return t[e]}catch(t){return void Error}}function Ct(t){return"terminal_quota_exhausted"===function(t){let e=$t(t,"data"),r=$t(t,"detail")??$t(e,"detail")??t,n=$t(r,"error")??r,i=$t(n,"type")??$t(r,"type");return"string"==typeof i?i.toLowerCase():void 0}(t)||(e=At(t),!/\bnon[-\s]+terminal\s+quota\b/i.test(e)&&!/\bnon[-\s]+terminal\s+billing\s+limit\b/i.test(e)&&(/\bterminal\s+quota\b/i.test(e)||/\bterminal\s+billing\s+limit\b/i.test(e)||/\bhard\s+billing\s+limit\b/i.test(e)));var e}function Dt(t){if(Ct(t))return"abort";let e=At(t),r=function(t){if(!Tt(t))return;let e=Mt(t,"data"),r=[t,e,Mt(t,"error"),Mt(e,"error")].filter(t=>void 0!==t);for(let t of r){let e=Rt(t,"name");if("string"==typeof e&&e.length>0)return e}}(t)?.toLowerCase().replace(/[_-]/g,"");return r?.includes("messageabortederror")||r?.includes("aborterror")?"abort":"contextoverflowerror"===r?"context_overflow":r?.includes("ailoadapikeyerror")||r?.includes("loadapi")||/api.?key.?is.?missing/i.test(e)&&/environment variable/i.test(e)?"missing_api_key":/api.?key/i.test(e)&&/must be a string/i.test(e)?"invalid_api_key":r?.includes("providermodelnotfounderror")||r?.includes("modelnotfounderror")||r?.includes("unknownerror")&&/model\s+not\s+found/i.test(e)?"model_not_found":r?.includes("quotaexceeded")||r?.includes("insufficientquota")||r?.includes("billingerror")||r?.includes("resourceexhausted")||r?.includes("insufficientcredits")||r?.includes("usagelimit")||/quota.?exceeded/i.test(e)||/exceeded.*quota/i.test(e)||/quota\b.*\breset/i.test(e)||/usage\s*quota/i.test(e)||/subscription.?(?:quota|limit)/i.test(e)||/insufficient.?(?:quota|balance|funds?|credits?)/i.test(e)||/credits?\s+exhausted/i.test(e)||/\b(?:session|weekly|monthly|daily|hourly|\d+[-\s]hour|plan|call)\s+limit\b/i.test(e)||/\bhit\s+your\b[^.]*\blimit\b/i.test(e)||/\bin\s+arrears\b/i.test(e)||/\brecharge\s+and\s+try\b/i.test(e)||/billing.?(?:hard.?)?limit/i.test(e)||/exhausted\s+your\s+capacity/i.test(e)||/resource.?exhausted/i.test(e)||/out\s+of\s+credits?/i.test(e)||/payment.?required/i.test(e)||/usage\s+limit/i.test(e)||/credit\s+balance.*too\s+low/i.test(e)||/limit\s+exhausted/i.test(e)||/使用上限/.test(e)||/用量上限/.test(e)||/达到.*限制/.test(e)||/额度.*不足/.test(e)||/余额.*不足/.test(e)||/已耗尽/.test(e)||function(t){return/预扣费额度失败/i.test(t)&&/用户剩余额度/i.test(t)||/用户剩余额度/i.test(t)&&/需要预扣费额度/i.test(t)}(e)?"quota_exceeded":void 0}var Lt=new Set(["providermodelnotfounderror","ratelimiterror","modelunavailableerror","providerconnectionerror","authenticationerror"]),Ut=new Set(["messageabortederror","permissiondeniederror","contextlengtherror","timeouterror","validationerror","syntaxerror","usererror"]),qt=["rate_limit","rate limit","usage_limit_reached","usage limit has been reached","quota","all credentials for model","cooling down","exhausted your capacity","not found","unavailable","insufficient","too many requests","over limit","overloaded","bad gateway","bad request","unknown provider","provider not found","model_not_supported","model not supported","model is not supported","connection error","network error","timeout","service unavailable","internal_server_error","free usage","usage exceeded","credit","balance","temporarily unavailable","try again","请稍后重试","503","502","504","429","529","selected provider is forbidden","provider is forbidden","频率限制","请求过于频繁","暂时不可用","服务不可用","server_error","an error occurred while processing","upstream request failed"],jt=["rate limit","cooling down","credentials for model"];function Ft(t){let e=t.name?.toLowerCase();if(void 0!==e&&Ut.has(e))return!1;if(Ct(t))return!1;if("quota_exceeded"===Dt(t))return!0;if(void 0!==e&&Lt.has(e))return!0;let r=t.message?.toLowerCase()??"";return!(!(n=r).includes("retrying in")||!jt.some(t=>n.includes(t)))||(null!=t.statusCode&&(429===t.statusCode||503===t.statusCode||529===t.statusCode)||qt.some(t=>r.includes(t)));var n}var Jt=[/prompt is too long/i,/request_too_large/i,/input is too long for requested model/i,/exceeds the context window/i,/exceeds (?:the )?(?:model'?s )?maximum context length(?: of [\d,]+ tokens?|\s*\([\d,]+\))/i,/input token count.*exceeds the maximum/i,/maximum prompt length is \d+/i,/reduce the length of the messages/i,/maximum context length is \d+ tokens/i,/exceeds (?:the )?maximum allowed input length of [\d,]+ tokens?/i,/input \(\d+ tokens\) is longer than the model'?s context length \d+ tokens/i,/exceeds the limit of \d+/i,/exceeds the available context size/i,/greater than the context length/i,/context window exceeds limit/i,/exceeded model token limit/i,/too large for model with \d+ maximum context length/i,/prompt has [\d,]+ tokens?, but the configured context size is [\d,]+ tokens?/i,/prompt too long; exceeded (?:max )?context length/i,/range of input length should be/i,/model_context_window_exceeded/i,/context[_ ]length[_ ]exceeded/i,/too many tokens/i,/token limit exceeded/i,/^4(?:00|13)\s*(?:status code)?\s*\(no body\)/i,/(?:request[ _])?(?:body|entity|payload)[_ ]too[_ ]large/i,/Request payload is \d+ (?:bytes, over the \d+ byte|tokens, over the \d+ token) limit Kiro accepts\./i,/Model context limit reached\. Conversation size exceeds model capacity\./i],Bt=[/^(Throttling error|Service unavailable):/i,/rate limit/i,/too many requests/i];var Gt=/^Error: Model "([^"]+)" not found\. Use --list-models to see available models\.$/m,Ht=/^(?:Error:\s*)?No API key found for\s+([^\s.]+)/m,Vt=/(?:^|\s)(\d{3})\s*:/;function Yt(t){if(t.timedOut||0===t.code)return;let e=`${t.stderr}\n${t.stdout}`,r=Gt.exec(e)?.[1];if(void 0!==r)return{kind:"model_not_visible",id:r};let n=Ht.exec(e)?.[1];if(void 0!==n)return{kind:"auth_missing",provider:n};let i=function(t){for(let e of[t.stderr,t.stdout]){let t=e.split("\n").map(t=>t.trim()).find(t=>t.length>0);if(void 0!==t)return t.slice(0,200)}return}(t);if(void 0===i)return;if(o=i,!Bt.some(t=>t.test(o))&&Jt.some(t=>t.test(o)))return{kind:"context_overflow",detail:i};var o;let a=Number.parseInt(Vt.exec(i)?.[1]??"",10);return Ft({message:i,...Number.isNaN(a)?{}:{statusCode:a}})?{kind:"provider_unavailable",detail:i}:void 0}import{randomUUID as zt}from"node:crypto";import{dirname as Kt,join as Xt}from"node:path";import{randomUUID as Zt}from"node:crypto";import{hostname as Wt}from"node:os";import Qt from"node:path";import te from"node:path";var ee=new Set;function re(t){ee.add(t)}function ne(t){ee.delete(t)}var ie=/\.candidate-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function oe(t){if(t instanceof Error&&"code"in t)return"string"==typeof t.code?t.code:void 0}function ae(t,e){try{t.onFailure?.(e)}catch{}}function se(t){if("win32"!==process.platform)return!1;let e=oe(t);return"EBUSY"===e||"EPERM"===e||"EACCES"===e}import{randomUUID as le}from"node:crypto";import{hostname as ce}from"node:os";function ue(t){return"string"==typeof t&&t.length>0}function de(t){let e;try{e=JSON.parse(t)}catch{return null}if("object"!=typeof e||null===e||Array.isArray(e))return null;let r=e;return!Number.isInteger(r.pid)||Number(r.pid)<=0?null:ue(r.process_start)&&ue(r.hostname)&&ue(r.nonce)?!ue(r.created_at)||Number.isNaN(Date.parse(r.created_at))?null:ue(r.purpose)&&(void 0===r.run_id||ue(r.run_id))?{pid:Number(r.pid),process_start:r.process_start,hostname:r.hostname,nonce:r.nonce,created_at:r.created_at,purpose:r.purpose,...void 0===r.run_id?{}:{run_id:r.run_id}}:null:null}async function fe(t,e={}){if(0===t.length)throw Error("lock purpose must not be empty");let r=await pt(process.pid);return{pid:process.pid,process_start:r??"unavailable",hostname:ce(),nonce:le(),created_at:(new Date).toISOString(),purpose:t,...void 0===e.runId?{}:{run_id:e.runId}}}class pe extends Error{lockPath;owner;retriable=!0;constructor(t,e){super(`Lock is held: ${t}`),this.lockPath=t,this.owner=e,this.name="LockContentionError"}}function me(t){if(t instanceof Error&&"code"in t)return"string"==typeof t.code?t.code:void 0}function we(t,e){if(void 0!==e)return e(t);if("win32"!==process.platform)return!1;let r=me(t);return"EBUSY"===r||"EPERM"===r||"EACCES"===r}async function he(t){for(let e=0;e<3;e+=1)try{return await(Ee.unlink??R)(t),ne(t),!0}catch(r){if("ENOENT"===me(r))return ne(t),!0;if(!we(r,Ee.isSharingError))throw re(t),Se(Qt.dirname(t)),r;if(e+1===3)return re(t),Se(Qt.dirname(t)),!1}return!1}function ye(t,e){return e?.throwIfAborted(),new Promise((r,n)=>{let i=setTimeout(a,t),o=()=>a(e?.reason??new DOMException("The operation was aborted","AbortError"));function a(t){clearTimeout(i),e?.removeEventListener("abort",o),void 0===t?r():n(t)}e?.addEventListener("abort",o,{once:!0})})}async function ge(t){try{let e=await I(t,"utf8");return{raw:e,record:de(e)}}catch(t){let e=me(t);if("ENOENT"===e)return null;if("\\"===Qt.sep&&"EPERM"===e)return{raw:"",record:null};throw t}}async function ve(t,e){await S(Qt.dirname(t),{recursive:!0,mode:448});let{candidatePath:r,handle:n}=await async function(t){for(let e=0;;e+=1){let r=`${t}.candidate-${Zt()}`;try{return{candidatePath:r,handle:await Y(r,"wx",384)}}catch(n){if(await he(r)||Se(Qt.dirname(t)),"EINTR"!==me(n)||e>=128)throw n}}}(t);try{try{await async function(t,e,r,n){let o="string"==typeof e?a.from(e,r??"utf8"):e,s=0;for(;s<o.length;){n?.throwIfAborted();let{bytesWritten:e}=await i(()=>t.write(o,s));if(e<=0)throw Error(`fs write made no progress at offset ${s}`);s+=e}}(n,`${JSON.stringify(e)}\n`,"utf8"),await n.sync()}finally{await n.close()}try{return await b(r,t),!0}catch(t){if(function(t){let e=me(t);return"EEXIST"===e||"ENOENT"===e}(t))return!1;throw t}}finally{await he(r)||Se(Qt.dirname(t))}}var be=new Set,Ee={};function Se(t){be.delete(t)}async function _e(t){if(t.hostname!==Wt())return!1;let e=wt(t.pid);if("dead"===e)return!0;if("unknown"===e)return!1;let r=await pt(t.pid);return null!==r&&"unavailable"!==t.process_start&&function(t,e){let r=mt(t);return null!==r&&r===mt(e)&&t!==e}(t.process_start,r)}async function Ie(t){let e=await ge(t);if(null===e||null===e.record||!await _e(e.record))return!1;let r=`${t}.reaping-${Zt()}`;try{await O(t,r)}catch(t){if("ENOENT"===me(t))return!1;if(we(t))return!1;throw t}let n=await ge(r);if(null!==n&&n.raw===e.raw)return await R(r),!0;try{await b(r,t)}catch(t){if("EEXIST"!==me(t))throw t}return await R(r),!1}async function xe(t,e,r){if(null===e.record||!await _e(e.record))return!1;let n=`${t}.recovery`,i={...r,nonce:Zt(),created_at:(new Date).toISOString(),purpose:`${r.purpose}:recovery`};for(let t=0;!await ve(n,i);t+=1)if(t>0||!await Ie(n))return!1;try{let r=await ge(t);if(null===r)return!0;if(r.raw!==e.raw||null===r.record)return!1;if(!await _e(r.record))return!1;let o=await ge(n);return null!==o&&o.record?.nonce===i.nonce&&(await R(t),!0)}finally{await Oe(n,i)}}async function Ne(t,e,r={}){let n=r.waitTimeoutMs??0,i=r.retryDelayMs??25;if(n<0||i<=0)throw Error("lock wait options must be positive");let o=Qt.dirname(t);be.has(o)||(be.add(o),await async function(t,e=Date.now,r={}){let n;try{n=await x(t)}catch(e){if("ENOENT"===oe(e)){for(let e of ee)te.dirname(e)===t&&ne(e);return 0}throw e}let i=0;for(let o of n){if(!ie.test(o))continue;let n=te.join(t,o);try{let t=await T(n);if(!ee.has(n)&&e()-t.mtimeMs<=36e5)continue;let o=!1;for(let t=0;t<3;t+=1)try{await(r.unlink??R)(n),ne(n),o=!0;break}catch(e){if("ENOENT"===oe(e)){ne(n),o=!0;break}if(!(r.isSharingError?.(e)??se(e)))throw e;t+1===3&&ae(r,n)}o&&(i+=1)}catch(t){"ENOENT"===oe(t)&&ne(n),ae(r,n)}}return i}(o,Date.now,{...void 0===Ee.unlink?{}:{unlink:Ee.unlink},...void 0===Ee.isSharingError?{}:{isSharingError:Ee.isSharingError},onFailure:()=>Se(o)}).catch(()=>{Se(o)}));let a=Date.now()+n;for(;;){r.signal?.throwIfAborted();let n=await ge(t);if(null===n){if(await ve(t,e))return;if(r.signal?.throwIfAborted(),n=await ge(t),null===n)continue}if(!await xe(t,n,e)){if(r.signal?.throwIfAborted(),Date.now()>=a)throw new pe(t,n.record);await ye(Math.min(i,Math.max(1,a-Date.now())),r.signal)}}}async function Oe(t,e){let r=await ge(t);if(null===r||r.record?.nonce!==e.nonce)return!1;try{return await R(t),!0}catch(t){if("ENOENT"===me(t))return!1;throw t}}async function ke(t,e,r,n){await Ne(t,e,n);try{return await r()}finally{await Oe(t,e)}}Promise.resolve();var Pe=[["\\bAKIA[0-9A-Z]{16}\\b",""],["\\b(?:bearer|token|api[_-]?key|secret|password|passwd|pwd)\\s*[=:]\\s*\\S{1,256}","i"],["\\bAuthorization\\s*:\\s*Bearer\\s+\\S{1,256}","i"],["\\bsk-(?:proj-)?[-_A-Za-z0-9]+\\b",""],["\\b(?:ghp|github_pat|glpat|xox[baprs])-[-_A-Za-z0-9]+\\b",""]];Pe.map(([t,e])=>new RegExp(t,e)),Pe.map(([t,e])=>new RegExp(t,`${e}g`));new TextDecoder("utf-8",{fatal:!0,ignoreBOM:!0});import{dirname as Te}from"node:path";import{fileURLToPath as Re}from"node:url";Te(Re(import.meta.url));import{dirname as Me}from"node:path";import{fileURLToPath as Ae}from"node:url";Me(Ae(import.meta.url)),new Date(0).toISOString();import{dirname as $e}from"node:path";import{fileURLToPath as Ce}from"node:url";$e(Ce(import.meta.url));function De(t){return Xt(t,"terminal-claim.json")}function Le(t,e,r){return t.runId===e.runId&&t.attempt===e.attempt&&(void 0===r||t.kind===r)}function Ue(t,e){let r=`${t}.${process.pid}.${zt()}.candidate`;!function(t,e){let r=z(t,"wx",384);try{B(r,`${JSON.stringify(e,null,2)}\n`,"utf8"),$(r)}finally{K(r)}}(r,e);try{C(r,t)}catch(t){if("EEXIST"===Ge(t))return!1;throw t}finally{Fe(r)}return function(t){let e;try{e=z(t,"r")}catch(t){if("win32"===process.platform&&["EISDIR","EPERM"].includes(Ge(t)??""))return;throw t}try{$(e)}catch(t){if("win32"!==process.platform||!["EPERM","EACCES","EINVAL"].includes(Ge(t)??""))throw t}finally{K(e)}}(Kt(t)),!0}function qe(t){let e;try{e=U(t,"utf8")}catch(t){if("ENOENT"===Ge(t))return;throw t}try{return{raw:e,claim:Be(JSON.parse(e))}}catch{return{raw:e,claim:void 0}}}async function je(t,e,r,n,i,o){let a=await fe("reflection-finalize",{runId:i.runId});return ke(Xt(t,"terminal-claim-recovery.lock"),a,async()=>{let t=qe(e);if(void 0!==t){if(t.raw!==r.raw)return t.claim;if("invalid"===n){if(void 0!==t.claim)return t.claim}else{if(void 0===t.claim)return;if("publish"!==t.claim.kind||!Je(t.claim,o))return t.claim}Fe(e)}},{waitTimeoutMs:Number.POSITIVE_INFINITY})}function Fe(t){try{J(t)}catch(t){if("ENOENT"!==Ge(t))throw t}}function Je(t,e){return"dead"===(e.getPidLiveness??wt)(t.claimantPid)}function Be(t){if(!function(t){return null!==t&&"object"==typeof t&&!Array.isArray(t)}(t)||1!==t.version||"publish"!==t.kind&&"abandon"!==t.kind||"string"!=typeof t.runId||!Number.isInteger(t.attempt)||!Number.isInteger(t.claimantPid)||Number(t.claimantPid)<=0)throw TypeError("Invalid run terminal claim");return{version:1,kind:t.kind,runId:t.runId,attempt:Number(t.attempt),claimantPid:Number(t.claimantPid)}}function Ge(t){return t instanceof Error&&"code"in t?String(t.code):void 0}import{join as He}from"node:path";async function Ve(t,e,r){let n={runId:e.runId,attempt:e.attempt??1},i=await async function(t,e,r,n={},i=process.pid){let o=De(t),a={version:1,kind:r,runId:e.runId,attempt:e.attempt,claimantPid:i},s=0;for(;;){if(Ue(o,a))return a;let i=qe(o);if(void 0===i)continue;let l=i.claim;if(void 0===l){if(s>0)throw TypeError("Invalid run terminal claim");s+=1,await(n.beforeRecovery?.("invalid"));let r=await je(t,o,i,"invalid",e,n);if(void 0!==r)return r;continue}if(Le(l,e,r))return l;if("publish"!==l.kind||!Je(l,n))return l;await(n.beforeRecovery?.("dead-publish"));let c=await je(t,o,i,"dead-publish",e,n);if(void 0!==c)return c}}(t,n,"publish");Le(i,n,"publish")&&(await rt(Pt(t,"publishing.json"),{version:1,...n,finishedAt:r.finishedAt}),await async function(t,e,r,n=ke,i=fe){let o=await i("reflection-finalize",{runId:e});return n(He(t,"terminalization.lock"),o,r,{waitTimeoutMs:Number.POSITIVE_INFINITY})}(t,e.runId,async()=>{if(X(Pt(t,"final.json"))||X(Pt(t,"abandoned.json")))return;if(!Le(function(t){return Be(JSON.parse(U(De(t),"utf8")))}(t),n,"publish"))return;let i=void 0!==Yt({code:r.childExit.code,stdout:U(e.stdoutPath,"utf8"),stderr:U(e.stderrPath,"utf8"),timedOut:r.timedOut});i&&void 0!==e.nextAttempt&&await nt(Pt(t,"ledger.json"),{attempt:e.nextAttempt.attempt,model:e.nextAttempt.model,...void 0===e.nextAttempt.thinking?{thinking:void 0}:{thinking:e.nextAttempt.thinking},launching:!0,pid:void 0,processStart:void 0,childPid:void 0,childProcessStart:void 0}),await rt(Pt(t,"outcome.json"),r),i&&await it(De(t))}))}function Ye(t){try{G(3,`${JSON.stringify(t)}\n`)}catch(t){if(!(t instanceof Error&&"code"in t&&["EPIPE","EBADF"].includes(String(t.code))))throw t}}var ze=process.argv.slice(2);try{if("--child-bootstrap"===ze[0]){if(void 0===ze[1])throw TypeError("run directory is required");await async function(e){if(!await async function(){for await(let t of process.stdin)if(Buffer.byteLength(t)>0)return!0;return!1}())return;let r=await et(Z(e,"launch.json")),n=yt(),i=t(r.command,[...r.args],{cwd:r.cwd,env:r.env,detached:!1,stdio:["ignore","inherit","inherit"],windowsHide:!0});Ye({code:i.pid??null,signal:"MODEL_PID"});let o=()=>{"win32"!==n&&"win32"!==process.platform||i.kill()};process.on("SIGTERM",o),process.on("SIGINT",o);let a=gt(r.hardDeadlineAt,()=>{"win32"===n?Nt(n,i):It(process.pid,"SIGTERM")}),s=gt(r.hardDeadlineAt+r.terminationGraceMs,()=>{Ot(n,process.pid)});Ye(await new Promise(t=>{let e=!1;i.once("error",()=>{e||(e=!0,t({code:null,signal:null}))}),i.once("close",(r,n)=>{e||(e=!0,t({code:r,signal:n}))})}).finally(()=>{a(),s()}))}(ze[1])}else{if(void 0===ze[0])throw TypeError("run directory is required");await async function(e){let r=Z(e,"launch.json"),n=Z(e,"ledger.json"),i=await et(r),o=yt();await nt(n,{launching:!1,pid:process.pid,processStart:await kt(process.pid)});let a,s=z(i.stdoutPath,"w"),l=z(i.stderrPath,"w"),c=t(process.execPath,[W(import.meta.url),"--child-bootstrap",e],{env:process.env,detached:!0,stdio:["pipe",s,l,"pipe"],windowsHide:!0}),u=c.pid,d="",f=c.stdio[3];null!=f&&"setEncoding"in f&&(f.setEncoding("utf8"),f.on("data",t=>{d+=t;for(let t of d.split("\n"))try{let e=JSON.parse(t);"MODEL_PID"===e.signal&&"number"==typeof e.code&&(a=e.code)}catch(t){if(!(t instanceof SyntaxError))throw t}}));let p=(t=!1)=>{try{Ot(o,u,t)}catch(t){process.stderr.write(`${t instanceof Error?t.message:String(t)}\n`)}};if(process.once("exit",()=>p(!0)),process.once("SIGTERM",()=>{p(),u=void 0,process.exit(143)}),process.once("SIGINT",()=>{p(),u=void 0,process.exit(130)}),void 0===c.pid)throw Error("child bootstrap did not receive a pid");await nt(n,{childPid:c.pid,childProcessStart:await kt(c.pid)}),c.stdin?.end("1\n");let m,w=!1,h=Promise.resolve();"win32"===o&&(h=new Promise(t=>{m=t}));let y=gt(i.hardDeadlineAt,()=>{w=!0,"win32"===o?xt(c.pid):Nt(o,c)}),g=gt(i.hardDeadlineAt+i.terminationGraceMs,()=>{Ot(o,a??u),m?.()}),v=await new Promise((t,e)=>{let r=!1;c.once("error",t=>{r||(r=!0,e(t))}),c.once("close",(e,n)=>{r||(r=!0,t({code:e,signal:n}))})}),b=function(t){let e=t.trim().split("\n").at(-1);if(void 0!==e&&0!==e.length)try{let t=JSON.parse(e),r="number"==typeof t.code||null===t.code?t.code:void 0,n="string"==typeof t.signal||null===t.signal?t.signal:void 0;return void 0===r||void 0===n||"MODEL_PID"===n?void 0:{code:r,signal:n}}catch{return}}(d);"win32"===o&&w&&void 0===b&&await h,y(),g(),u=void 0,K(s),K(l);let E=function(){let t=ht()?process.env.OMO_MEMORY_SUPERVISOR_CLOCK_PATH:void 0;return void 0===t?Date.now():bt(t)}(),S=(new Date).toISOString(),_={version:1,runId:i.runId,attempt:i.attempt,finishedAt:S,childExit:b??v,timedOut:w||Number.isFinite(E)&&E>=i.hardDeadlineAt};await Ve(e,i,_),await it(r),process.exit(0)}(ze[0])}}catch(t){process.stderr.write(`${t instanceof Error?t.stack??t.message:String(t)}\n`),process.exitCode=1}