omo-ai 5.0.0-0.beta.21 → 5.0.0-0.beta.22

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.
@@ -1,8 +1,7 @@
1
- import { spawnSync } from "node:child_process"
2
1
  import { existsSync, realpathSync } from "node:fs"
3
2
  import { homedir as osHomedir } from "node:os"
4
3
  import { posix, win32 } from "node:path"
5
- import { propagateResult } from "./child-process.js"
4
+ import { propagateResult, runChild } from "./child-process.js"
6
5
 
7
6
  // A bun global install lives under <BUN_ROOT>/install/global/, and `bun add -g` links the launcher
8
7
  // into <BUN_ROOT>/bin. The link TARGET is what identifies the install, so every comparison below
@@ -120,19 +119,23 @@ export function resolveBunReexec(input) {
120
119
  }
121
120
 
122
121
  /**
123
- * Runs the decision. Returns true when bun took over the process, in which case the caller must
122
+ * Runs the decision. Resolves true when bun took over the process, in which case the caller must
124
123
  * return immediately: the child has already run to completion and its exit status is propagated.
125
124
  *
125
+ * The wait is asynchronous for the same reason the engine spawn is: this is the outer half of the
126
+ * launcher chain, and a node process blocked in `spawnSync` here dies to a SIGTERM without ever
127
+ * telling the bun child - which owns the engine - that anything happened.
128
+ *
126
129
  * Node's execArgv is deliberately dropped - node flags are not bun flags, and forwarding them
127
130
  * would fail the very launch this re-exec is meant to make work.
128
131
  */
129
- export function maybeReexecUnderBun(input) {
132
+ export async function maybeReexecUnderBun(input) {
130
133
  const decision = resolveBunReexec(input)
131
134
  if (!decision.reexec) return false
132
- const spawn = input.spawn ?? spawnSync
135
+ const run = input.spawn ?? runChild
133
136
  const propagate = input.propagate ?? propagateResult
134
137
  const argv = input.argv ?? process.argv
135
- const result = spawn(decision.bunPath, [input.scriptPath, ...argv.slice(2)], {
138
+ const result = await run(decision.bunPath, [input.scriptPath, ...argv.slice(2)], {
136
139
  stdio: "inherit",
137
140
  windowsHide: true,
138
141
  })
@@ -1,4 +1,23 @@
1
- import { spawnSync } from "node:child_process"
1
+ import { spawn } from "node:child_process"
2
+
3
+ // Signals a terminal or supervisor sends to the launcher, and what the launcher does with each.
4
+ // SIGTERM and SIGHUP reach one process, so the child has to be told. SIGINT is delivered to the
5
+ // whole foreground process group by the tty, so the child already has it and forwarding would
6
+ // interrupt it twice - the launcher only waits for the child to finish reacting to its own copy.
7
+ const HANDLED_SIGNALS = [
8
+ ["SIGTERM", true],
9
+ ["SIGHUP", true],
10
+ ["SIGINT", false],
11
+ ]
12
+
13
+ // The engine's own shutdown drains in ~1.5-2.5s. Ten seconds is the ceiling before the launcher
14
+ // stops waiting on a child that is not going to leave and reports the death it was asked for.
15
+ const DEFAULT_GRACE_MS = 10_000
16
+
17
+ function graceMs(env) {
18
+ const configured = Number(env.OMO_SIGNAL_GRACE_MS)
19
+ return Number.isFinite(configured) && configured > 0 ? configured : DEFAULT_GRACE_MS
20
+ }
2
21
 
3
22
  export function propagateResult(result) {
4
23
  if (result.error) throw result.error
@@ -9,11 +28,79 @@ export function propagateResult(result) {
9
28
  process.exitCode = result.status ?? 1
10
29
  }
11
30
 
12
- export function spawnNode(scriptPath, args, options = {}) {
13
- const result = spawnSync(process.execPath, [scriptPath, ...args], {
14
- stdio: "inherit",
15
- windowsHide: true,
16
- ...options,
31
+ /**
32
+ * Runs a child to completion without blocking this process's event loop, so the launcher can still
33
+ * answer signals while the engine is in the foreground.
34
+ *
35
+ * `spawnSync` cannot do this: no JS handler runs while it blocks, so a SIGTERM'd launcher dies
36
+ * instantly and leaves the engine reparented to init. Here the same signals are forwarded to the
37
+ * child, the child is given a bounded grace window to shut down on its own terms, and only then is
38
+ * the signal re-raised on this process.
39
+ *
40
+ * Windows has no POSIX signals: `process.on("SIGTERM")` never fires there and `subprocess.kill`
41
+ * would terminate the child abruptly, so nothing is forwarded and the wait is the whole behavior.
42
+ */
43
+ export function runChild(command, args, options = {}) {
44
+ const env = options.env ?? process.env
45
+ return new Promise((resolve, reject) => {
46
+ const child = spawn(command, args, {
47
+ stdio: "inherit",
48
+ windowsHide: true,
49
+ ...options,
50
+ })
51
+
52
+ let settled = false
53
+ let graceTimer
54
+ const listeners = []
55
+
56
+ const cleanup = () => {
57
+ for (const [signal, listener] of listeners) process.off(signal, listener)
58
+ if (graceTimer) clearTimeout(graceTimer)
59
+ }
60
+
61
+ const settle = (outcome) => {
62
+ if (settled) return
63
+ settled = true
64
+ cleanup()
65
+ outcome()
66
+ }
67
+
68
+ if (process.platform !== "win32") {
69
+ for (const [signal, forward] of HANDLED_SIGNALS) {
70
+ const listener = () => {
71
+ if (forward) {
72
+ try {
73
+ child.kill(signal)
74
+ } catch {
75
+ // The child is already gone; its exit event carries the real answer.
76
+ }
77
+ }
78
+ // A child that swallows the signal must not keep the launcher alive forever: after the
79
+ // grace window this process dies of the signal it was sent, exactly as it would have
80
+ // without a handler installed.
81
+ if (graceTimer) return
82
+ graceTimer = setTimeout(() => {
83
+ settle(() => {
84
+ // Every listener for this signal has to go, not just this module's: the re-raise only
85
+ // terminates the process when nothing is left to handle it.
86
+ process.removeAllListeners(signal)
87
+ process.kill(process.pid, signal)
88
+ })
89
+ }, graceMs(env))
90
+ }
91
+ listeners.push([signal, listener])
92
+ process.on(signal, listener)
93
+ }
94
+ }
95
+
96
+ child.on("error", (error) => settle(() => reject(error)))
97
+ // A child that died of the forwarded signal needs no special case: `propagateResult` turns a
98
+ // signaled result into this process dying by the same signal, which is exactly right.
99
+ child.on("exit", (status, signal) => settle(() => resolve({ status, signal })))
17
100
  })
101
+ }
102
+
103
+ export async function spawnNode(scriptPath, args, options = {}) {
104
+ const result = await runChild(process.execPath, [scriptPath, ...args], options)
18
105
  propagateResult(result)
19
106
  }
package/bin/lib/doctor.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { spawnSync } from "node:child_process"
1
2
  import { existsSync, readFileSync } from "node:fs"
2
3
  import { join } from "node:path"
3
4
  import { canonicalAgentDir } from "./agent-dir.js"
@@ -51,7 +52,148 @@ function engineVersionOrUnresolved(senpi) {
51
52
  }
52
53
  }
53
54
 
54
- export function runDoctor(inventory) {
55
+ // The interactive engine's own command line. A published install runs it as
56
+ // `<runtime> .../@code-yeongyu/senpi/dist/cli.js --extension <plugin>`; every non-interactive
57
+ // spelling carries an explicit `--mode`, and those are owned by whoever started them.
58
+ const ENGINE_MARKER = "senpi/dist/cli.js"
59
+ const MANAGED_MODE_FLAG = "--mode"
60
+
61
+ /**
62
+ * Reads the live process table. `ps` is the portable answer on macOS and Linux alike, and reading
63
+ * it can never be a reason for diagnostics to fail, so an unavailable or unparsable listing is an
64
+ * empty list.
65
+ */
66
+ function listProcesses() {
67
+ if (process.platform === "win32") return []
68
+ const listed = spawnSync("ps", ["-axo", "pid=,ppid=,etime=,tty=,args="], { encoding: "utf8" })
69
+ if (listed.status !== 0 || typeof listed.stdout !== "string") return []
70
+ const entries = []
71
+ for (const line of listed.stdout.split("\n")) {
72
+ const match = /^\s*(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(.*)$/.exec(line)
73
+ if (!match) continue
74
+ entries.push({
75
+ pid: Number(match[1]),
76
+ ppid: Number(match[2]),
77
+ elapsed: match[3],
78
+ tty: match[4],
79
+ command: match[5],
80
+ })
81
+ }
82
+ return entries
83
+ }
84
+
85
+ function isEngine(entry) {
86
+ return entry.command.includes(ENGINE_MARKER)
87
+ }
88
+
89
+ function isInteractive(entry) {
90
+ return !entry.command.includes(MANAGED_MODE_FLAG)
91
+ }
92
+
93
+ /**
94
+ * Splits every engine process this machine is running into the three groups that matter:
95
+ * - stale: interactive engines reparented to init, i.e. their launcher died underneath them
96
+ * - attached: interactive engines still owned by a live parent (somebody's session)
97
+ * - managed: rpc / app-server engines, which are owned by whatever embeds them
98
+ * Only the first group is ever reportable, and even then only as a report.
99
+ */
100
+ export function classifyEngineProcesses(entries) {
101
+ const stale = []
102
+ const attached = []
103
+ const managed = []
104
+ for (const entry of entries) {
105
+ if (!isEngine(entry)) continue
106
+ if (!isInteractive(entry)) managed.push(entry)
107
+ else if (entry.ppid === 1) stale.push(entry)
108
+ else attached.push(entry)
109
+ }
110
+ return { stale, attached, managed }
111
+ }
112
+
113
+ export function formatStaleEngineLines(stale) {
114
+ if (stale.length === 0) return []
115
+ const lines = stale.map((entry) =>
116
+ `WARN stale engine pid ${entry.pid} (age ${entry.elapsed}, tty ${entry.tty}) has no launcher; it was orphaned by a signaled launcher`
117
+ )
118
+ lines.push(`INFO reap them explicitly by pid: omo doctor --reap ${stale.map((entry) => entry.pid).join(" ")}`)
119
+ return lines
120
+ }
121
+
122
+ function parsePid(value) {
123
+ return /^[1-9]\d*$/.test(value) ? Number(value) : undefined
124
+ }
125
+
126
+ /**
127
+ * Terminates named pids and nothing else. Every refusal is deliberate: a pattern kill would reach
128
+ * live sessions, rpc hosts and app servers, so a pid is only ever signaled when the live process
129
+ * table still shows it as an orphaned interactive engine at the moment of the request.
130
+ */
131
+ export function reapStaleEngines(args, options = {}) {
132
+ const lines = []
133
+ if (args.length === 0) {
134
+ lines.push("FAIL --reap needs the pids to terminate: omo doctor --reap <pid> [pid...]")
135
+ return { lines, failed: true, reaped: [] }
136
+ }
137
+
138
+ const requested = []
139
+ let failed = false
140
+ for (const arg of args) {
141
+ const pid = parsePid(arg)
142
+ if (pid === undefined) {
143
+ lines.push(`FAIL refusing ${JSON.stringify(arg)}: --reap takes process ids`)
144
+ failed = true
145
+ continue
146
+ }
147
+ requested.push(pid)
148
+ }
149
+ if (requested.length === 0) return { lines, failed: true, reaped: [] }
150
+
151
+ const list = options.list ?? listProcesses
152
+ const kill = options.kill ?? ((pid, signal) => process.kill(pid, signal))
153
+ const entries = list()
154
+ const reaped = []
155
+ for (const pid of requested) {
156
+ const entry = entries.find((candidate) => candidate.pid === pid)
157
+ if (!entry || !isEngine(entry)) {
158
+ lines.push(`FAIL refusing pid ${pid}: it is not an omo engine process`)
159
+ failed = true
160
+ continue
161
+ }
162
+ if (!isInteractive(entry)) {
163
+ lines.push(`FAIL refusing pid ${pid}: it is a managed engine (${MANAGED_MODE_FLAG}), owned by whatever started it`)
164
+ failed = true
165
+ continue
166
+ }
167
+ if (entry.ppid !== 1) {
168
+ lines.push(`FAIL refusing pid ${pid}: its launcher (pid ${entry.ppid}) is alive, so it is a live session`)
169
+ failed = true
170
+ continue
171
+ }
172
+ try {
173
+ kill(pid, "SIGTERM")
174
+ reaped.push(pid)
175
+ lines.push(`PASS reaped stale engine pid ${pid} (age ${entry.elapsed}, tty ${entry.tty})`)
176
+ } catch (error) {
177
+ lines.push(`FAIL could not signal pid ${pid}: ${error.message}`)
178
+ failed = true
179
+ }
180
+ }
181
+ return { lines, failed, reaped }
182
+ }
183
+
184
+ function staleEngineReport(options) {
185
+ const list = options.list ?? listProcesses
186
+ return formatStaleEngineLines(classifyEngineProcesses(list()).stale)
187
+ }
188
+
189
+ export function runDoctor(inventory, args = [], options = {}) {
190
+ if (args[0] === "--reap") {
191
+ const result = reapStaleEngines(args.slice(1), options)
192
+ console.log(result.lines.join("\n"))
193
+ process.exitCode = result.failed ? 1 : 0
194
+ return
195
+ }
196
+
55
197
  let failed = false
56
198
  const lines = []
57
199
  for (const [label, artifact] of artifacts) {
@@ -91,6 +233,7 @@ export function runDoctor(inventory) {
91
233
 
92
234
  lines.push(`INFO omo ${packageManifest().version} (engine: senpi ${engineVersionOrUnresolved(senpi)})`)
93
235
  lines.push(...warningsForSettings())
236
+ lines.push(...staleEngineReport(options))
94
237
  if (needsSetupSuggestion(inventory)) {
95
238
  lines.push("INFO no credentials found; run omo setup to review sibling stores")
96
239
  }
@@ -89,12 +89,12 @@ function senpiEnvironment(senpiRoot) {
89
89
  return env
90
90
  }
91
91
 
92
- function spawnSenpi(args, withExtension) {
92
+ async function spawnSenpi(args, withExtension) {
93
93
  const senpi = resolveSenpi()
94
94
  const finalArgs = withExtension
95
95
  ? ["--extension", join(packageRoot, "plugin"), ...args]
96
96
  : args
97
- spawnNode(senpi.cliPath, finalArgs, { env: senpiEnvironment(senpi.packageRoot) })
97
+ await spawnNode(senpi.cliPath, finalArgs, { env: senpiEnvironment(senpi.packageRoot) })
98
98
  }
99
99
 
100
100
  function isInteractiveDefault(args) {
@@ -135,11 +135,11 @@ export async function runLauncher(args = process.argv.slice(2)) {
135
135
  reportLegacyFlatAdoption()
136
136
  const command = args[0]
137
137
  if (command === "ulw-loop") {
138
- spawnNode(join(packageRoot, "plugin", "runtime", "agent-toolkit", "ulw-loop", "cli.js"), args.slice(1))
138
+ await spawnNode(join(packageRoot, "plugin", "runtime", "agent-toolkit", "ulw-loop", "cli.js"), args.slice(1))
139
139
  return
140
140
  }
141
141
  if (command === "doctor") {
142
- runDoctor(await detectHarnesses())
142
+ runDoctor(await detectHarnesses(), args.slice(1))
143
143
  return
144
144
  }
145
145
  if (command === "setup") {
@@ -161,7 +161,7 @@ export async function runLauncher(args = process.argv.slice(2)) {
161
161
  return
162
162
  }
163
163
  if (earlyCommands.has(command) || command === "update") {
164
- spawnSenpi(args, false)
164
+ await spawnSenpi(args, false)
165
165
  return
166
166
  }
167
167
  if (isInteractiveDefault(args)) {
@@ -170,5 +170,5 @@ export async function runLauncher(args = process.argv.slice(2)) {
170
170
  console.error("omo: sibling credentials detected; run `omo setup` to review them")
171
171
  }
172
172
  }
173
- spawnSenpi(args, true)
173
+ await spawnSenpi(args, true)
174
174
  }
@@ -31,18 +31,35 @@ export function updateTarget(root = packageRoot, platform = process.platform) {
31
31
  return { manager: "npm", command: "npm i -g omo-ai@beta" }
32
32
  }
33
33
 
34
- export function resolveSenpi() {
34
+ export function resolveSenpi(options = {}) {
35
+ const {
36
+ resolveIndex = () => fileURLToPath(import.meta.resolve("@code-yeongyu/senpi")),
37
+ platform = process.platform,
38
+ } = options
35
39
  let indexPath
36
40
  try {
37
- indexPath = fileURLToPath(import.meta.resolve("@code-yeongyu/senpi"))
41
+ indexPath = resolveIndex()
38
42
  } catch (error) {
39
43
  throw new Error(`could not resolve @code-yeongyu/senpi; reinstall with: ${updateTarget().command} (${error.message})`)
40
44
  }
41
45
 
42
- const cliPath = join(dirname(indexPath), "cli.js")
46
+ const distDir = dirname(indexPath)
47
+ const cliPath = join(distDir, "cli.js")
43
48
  if (!existsSync(cliPath)) {
44
49
  throw new Error(`senpi CLI is missing at ${cliPath}; reinstall with: ${updateTarget().command}`)
45
50
  }
51
+ // The engine imports this module on every boot, and it is the file interrupted upgrades lose in
52
+ // the wild (npm reify dies on a Windows-locked native module and leaves a partial tree), so
53
+ // checking it here turns the engine's raw ERR_MODULE_NOT_FOUND stack into one actionable line.
54
+ const brandPath = join(distDir, "core", "brand.js")
55
+ if (!existsSync(brandPath)) {
56
+ const windowsHint = platform === "win32"
57
+ ? " (close every running omo/senpi process first: Windows locks loaded native modules, so npm fails with EBUSY mid-upgrade and leaves exactly this partial state)"
58
+ : ""
59
+ throw new Error(
60
+ `senpi engine files are incomplete: ${brandPath} is missing, which usually means an interrupted or failed omo-ai upgrade; reinstall with: ${updateTarget().command}${windowsHint}`,
61
+ )
62
+ }
46
63
  return { cliPath, packageRoot: dirname(dirname(indexPath)) }
47
64
  }
48
65
 
@@ -7,7 +7,7 @@ const packageRoot = fileURLToPath(new URL("..", import.meta.url))
7
7
  const cliPath = join(packageRoot, "plugin", "runtime", "agent-toolkit", "cli.js")
8
8
 
9
9
  try {
10
- spawnNode(cliPath, process.argv.slice(2))
10
+ await spawnNode(cliPath, process.argv.slice(2))
11
11
  } catch (error) {
12
12
  console.error(`omo-agent-toolkit: ${error.message}`)
13
13
  process.exitCode = 1
package/bin/omo.js CHANGED
@@ -16,7 +16,7 @@ try {
16
16
  // it is quiet and fail-open by contract, and Windows/npm installs never enter it. The shebang
17
17
  // above and the bin mapping in package.json are all Windows shims ever read, and both stay.
18
18
  ensureBunBinShim({ scriptPath })
19
- const reexeced = maybeReexecUnderBun({ scriptPath })
19
+ const reexeced = await maybeReexecUnderBun({ scriptPath })
20
20
  if (!reexeced) {
21
21
  if (process.argv[2] === "setup") await runSetup(process.argv.slice(3))
22
22
  else await runLauncher()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omo-ai",
3
- "version": "5.0.0-0.beta.21",
3
+ "version": "5.0.0-0.beta.22",
4
4
  "type": "module",
5
5
  "description": "omo native edition - the senpi-based OMO harness. Beta channel only: npm i -g omo-ai@beta",
6
6
  "bin": {
@@ -15,7 +15,7 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@babel/parser": "8.0.4",
18
- "@code-yeongyu/senpi": "2026.8.26"
18
+ "@code-yeongyu/senpi": "2026.8.26-2"
19
19
  },
20
20
  "repository": {
21
21
  "type": "git",
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- // omo:UQEyIlCnnJoEfKWfEqgll2SE1XzNtk_hZtYhZ6IzbX8:QaTGHT4AwTnwTe1o51eVN7OHuqyRLlfPutn_RoDdOuQ
2
+ // omo:MaszhefpRbL2YIuOej8EserwRUF7dG7-aI8f5gI8C9U:QaTGHT4AwTnwTe1o51eVN7OHuqyRLlfPutn_RoDdOuQ
3
3
  import{spawn as e}from"node:child_process";import{closeSync as t,openSync as r,writeSync as i}from"node:fs";import{join as n}from"node:path";import{fileURLToPath as o}from"node:url";import{randomUUID as a}from"node:crypto";import{open as s,readFile as l,rename as c,unlink as d}from"node:fs/promises";import{dirname as u}from"node:path";async function f(e){return JSON.parse(await l(e,"utf8"))}async function p(e,t,r=384){let i=`${e}.tmp-${process.pid}-${a()}`,n=await s(i,"wx",r);try{await n.writeFile(`${JSON.stringify(t,null,2)}\n`,"utf8"),await n.sync()}finally{await n.close()}await c(i,e),await async function(e){try{let t=await s(e,"r");try{await t.sync()}finally{await t.close()}}catch(e){let t=e instanceof Error&&"code"in e?e.code:void 0;if("win32"!==process.platform||!["EISDIR","EPERM"].includes(String(t)))throw e}}(u(e))}async function m(e,t){let r=await f(e);await p(e,{...r,...t})}async function w(e){try{await d(e)}catch(e){if(!(e instanceof Error&&"code"in e&&"ENOENT"===e.code))throw e}}import{execFile as h,spawn as y,spawnSync as v}from"node:child_process";import{readdirSync as g,watch as E,writeFileSync as I}from"node:fs";import{readFile as b}from"node:fs/promises";function S(){return"1"===process.env.OMO_MEMORY_SUPERVISOR_ALLOW_TEST_SEAMS}function O(){if(S()){let e=process.env.OMO_MEMORY_SUPERVISOR_PLATFORM;if("posix"===e||"win32"===e)return e}return"win32"===process.platform?"win32":"posix"}function _(e,t){let r=S()?process.env.OMO_MEMORY_SUPERVISOR_CLOCK_PATH:void 0;if(void 0===r){let r=setTimeout(t,Math.max(0,e-Date.now()));return()=>clearTimeout(r)}let i=!1,n=()=>{if(i)return;let n=P(r);!Number.isFinite(n)||n<e||(i=!0,o?.close(),clearInterval(a),t())};if(P(r)>=e)return i=!0,t(),()=>{};let o=E(r,n),a=setInterval(n,N);return n(),()=>{i||(i=!0,o.close(),clearInterval(a))}}var N=25;function P(e){let t=g(e).map(e=>/^(\d+)-(-?\d+(?:\.\d+)?)$/.exec(e)).filter(e=>null!==e).sort((e,t)=>Number(e[1])-Number(t[1])).at(-1);return void 0===t?Number.NaN:Number(t[2])}function M(e,t){if(!S())return;let r=process.env.OMO_MEMORY_SUPERVISOR_TASKKILL_RUN_DIR;void 0!==r&&I(`${r}/${e}-${process.pid}.json`,`${JSON.stringify({targetPid:t})}\n`,"utf8")}function x(e){if(!S())return;let t=process.env[e];if(void 0===t)return;let r=JSON.parse(t);if(!Array.isArray(r)||!r.every(e=>"string"==typeof e)||0===r.length)throw TypeError(`${e} must be a non-empty string array`);return r}function T(e,t,r){let[i,...n]=e;if(void 0===i)throw TypeError("termination command is required");if(r){let e=v(i,[...n,...t],{env:process.env,stdio:"ignore",windowsHide:!0});if(void 0!==e.error)throw e.error;return}y(i,[...n,...t],{env:process.env,stdio:"ignore",windowsHide:!0}).once("error",e=>process.stderr.write(`${e.message}\n`))}function R(e,t){M(`posix-${t}`,e);let r=x("OMO_MEMORY_SUPERVISOR_POSIX_SIGNAL_COMMAND");if(void 0===r)try{process.kill(-e,t)}catch(e){if(!(e instanceof Error&&"code"in e&&"ESRCH"===e.code))throw e}else T(r,["--signal-group",String(e),t],!1)}function k(e){void 0!==e&&M("win32-graceful",e)}function A(e,t){"win32"===e?(k(t.pid),t.kill()):void 0!==t.pid&&R(t.pid,"SIGTERM")}function $(e,t,r=!1){void 0!==t&&("win32"===e?function(e,t){T(x("OMO_MEMORY_SUPERVISOR_TASKKILL_COMMAND")??["taskkill"],["/pid",String(e),"/T","/F"],t)}(t,r):R(t,"SIGKILL"))}async function D(e){if("linux"===process.platform)return await async function(e){try{let t=await b(`/proc/${e}/stat`,"utf8"),r=t.lastIndexOf(")");if(r<0)return null;let i=t.slice(r+2).trim().split(/\s+/)[19];return void 0===i?null:`linux-proc-start-ticks:${i}`}catch{return null}}(e);if("darwin"===process.platform||"freebsd"===process.platform){let t=await async function(e,t){return await new Promise(r=>{h(e,[...t],{encoding:"utf8",timeout:2e3},(e,t)=>{if(null!==e)return void r(null);let i=t.trim();r(i.length>0?i:null)})})}("/bin/ps",["-o","lstart=","-p",String(e)]);return null===t?null:`ps-lstart:${t.replace(/\s+/g," ")}`}return null}import{existsSync as L,readFileSync as C}from"node:fs";import{join as j}from"node:path";var U=new Set(["providermodelnotfounderror","ratelimiterror","modelunavailableerror","providerconnectionerror","authenticationerror"]),F=new Set(["quotaexceedederror","insufficientcreditserror","freeusagelimiterror"]),q=new Set(["messageabortederror","permissiondeniederror","contextlengtherror","timeouterror","validationerror","syntaxerror","usererror"]),J=["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"],G=["quota will reset after","quota exceeded","free usage limit","billing limit","billing hard limit","monthly limit","plan limit","subscription quota","subscription limit","payment required","out of credits","credits exhausted","insufficient credits","insufficient balance","credit balance","usage limit for this month","exhausted your capacity","daily call limit","daily limit","usage limit reached for","in arrears","fair use policy","recharge and try","使用上限","额度不足","余额不足","已耗尽"],V=["rate limit","cooling down","credentials for model"];function Y(e){if(e.name){let t=e.name.toLowerCase();if(q.has(t))return!1;if(F.has(t))return!1;if(U.has(t))return!0}let t=e.message?.toLowerCase()??"";return!G.some(e=>t.includes(e))&&(!(!(r=t).includes("retrying in")||!V.some(e=>r.includes(e)))||(null!=e.statusCode&&(429===e.statusCode||503===e.statusCode||529===e.statusCode)||J.some(e=>t.includes(e))));var r}var H=/^Error: Model "([^"]+)" not found\. Use --list-models to see available models\.$/m,K=/^(?:Error:\s*)?No API key found for\s+([^\s.]+)/m,z=/(?:^|\s)(\d{3})\s*:/;function B(e){if(e.timedOut||0===e.code)return;let t=`${e.stderr}\n${e.stdout}`,r=H.exec(t)?.[1];if(void 0!==r)return{kind:"model_not_visible",id:r};let i=K.exec(t)?.[1];if(void 0!==i)return{kind:"auth_missing",provider:i};let n=function(e){for(let t of[e.stderr,e.stdout]){let e=t.split("\n").map(e=>e.trim()).find(e=>e.length>0);if(void 0!==e)return e.slice(0,200)}return}(e);if(void 0===n)return;let o=Number.parseInt(z.exec(n)?.[1]??"",10);return Y({message:n,...Number.isNaN(o)?{}:{statusCode:o}})?{kind:"provider_unavailable",detail:n}:void 0}import{randomUUID as X}from"node:crypto";import{closeSync as W,fsyncSync as Q,linkSync as Z,openSync as ee,readFileSync as te,unlinkSync as re,writeFileSync as ie}from"node:fs";import{dirname as ne,join as oe}from"node:path";import{randomUUID as ae}from"node:crypto";import{link as se,mkdir as le,open as ce,readFile as de,stat as ue,unlink as fe}from"node:fs/promises";import{hostname as pe}from"node:os";import me from"node:path";import{randomUUID as we}from"node:crypto";import{hostname as he}from"node:os";import{execFile as ye}from"node:child_process";import{readFile as ve}from"node:fs/promises";async function ge(e){if("linux"===process.platform)return await async function(e){try{let t=await ve(`/proc/${e}/stat`,"utf8"),r=t.lastIndexOf(")");if(r<0)return null;let i=t.slice(r+2).trim().split(/\s+/)[19];return void 0===i?null:`linux-proc-start-ticks:${i}`}catch{return null}}(e);if("darwin"===process.platform||"freebsd"===process.platform){let t=await async function(e,t){return await new Promise(r=>{ye(e,t,{encoding:"utf8",timeout:2e3},(e,t)=>{if(null!==e)return void r(null);let i=t.trim();r(i.length>0?i:null)})})}("/bin/ps",["-o","lstart=","-p",String(e)]);return null===t?null:`ps-lstart:${t.replace(/\s+/g," ")}`}return null}function Ee(e){try{return process.kill(e,0),"alive"}catch(e){let t=function(e){if(e instanceof Error&&"code"in e)return"string"==typeof e.code?e.code:void 0}(e);return"ESRCH"===t?"dead":"EPERM"===t?"alive":"unknown"}}function Ie(e){return"string"==typeof e&&e.length>0}function be(e){let t;try{t=JSON.parse(e)}catch{return null}if("object"!=typeof t||null===t||Array.isArray(t))return null;let r=t;return!Number.isInteger(r.pid)||Number(r.pid)<=0?null:Ie(r.process_start)&&Ie(r.hostname)&&Ie(r.nonce)?!Ie(r.created_at)||Number.isNaN(Date.parse(r.created_at))?null:Ie(r.purpose)&&(void 0===r.run_id||Ie(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 Se(e,t={}){if(0===e.length)throw Error("lock purpose must not be empty");let r=await ge(process.pid);return{pid:process.pid,process_start:r??"unavailable",hostname:he(),nonce:we(),created_at:(new Date).toISOString(),purpose:e,...void 0===t.runId?{}:{run_id:t.runId}}}class Oe extends Error{lockPath;owner;retriable=!0;constructor(e,t){super(`Lock is held: ${e}`),this.lockPath=e,this.owner=t,this.name="LockContentionError"}}function _e(e){if(e instanceof Error&&"code"in e)return"string"==typeof e.code?e.code:void 0}function Ne(e,t){return t?.throwIfAborted(),new Promise((r,i)=>{let n=setTimeout(a,e),o=()=>a(t?.reason??new DOMException("The operation was aborted","AbortError"));function a(e){clearTimeout(n),t?.removeEventListener("abort",o),void 0===e?r():i(e)}t?.addEventListener("abort",o,{once:!0})})}async function Pe(e){try{let t=await de(e,"utf8");return{raw:t,record:be(t)}}catch(e){let t=_e(e);if("ENOENT"===t)return null;if("\\"===me.sep&&"EPERM"===t)return{raw:"",record:null};throw e}}async function Me(e,t){await le(me.dirname(e),{recursive:!0,mode:448});let r=`${e}.candidate-${ae()}`;try{let i=await ce(r,"wx",384);try{await i.writeFile(`${JSON.stringify(t)}\n`,"utf8"),await i.sync()}finally{await i.close()}try{return await se(r,e),!0}catch(e){if("EEXIST"===_e(e))return!1;throw e}}finally{await fe(r).catch(e=>{if("ENOENT"!==_e(e))throw e})}}async function xe(e){if(e.hostname!==pe())return!1;let t=Ee(e.pid);if("dead"===t)return!0;if("unknown"===t)return!1;let r=await ge(e.pid);return null!==r&&"unavailable"!==e.process_start&&r!==e.process_start}async function Te(e,t,r){if(null===t.record||!await xe(t.record))return!1;let i=`${e}.recovery`,n={...r,nonce:ae(),created_at:(new Date).toISOString(),purpose:`${r.purpose}:recovery`};if(!await Me(i,n))return!1;try{let r=await Pe(e);return null===r||r.raw===t.raw&&null!==r.record&&(!!await xe(r.record)&&(await fe(e),!0))}finally{await Re(i,n)}}async function Re(e,t){let r=await Pe(e);if(null===r||r.record?.nonce!==t.nonce)return!1;try{return await fe(e),!0}catch(e){if("ENOENT"===_e(e))return!1;throw e}}async function ke(e,t,r,i){await async function(e,t,r={}){let i=r.waitTimeoutMs??0,n=r.retryDelayMs??25;if(i<0||n<=0)throw Error("lock wait options must be positive");let o=Date.now()+i;for(;;){if(r.signal?.throwIfAborted(),await Me(e,t))return;r.signal?.throwIfAborted();let i=await Pe(e);if(null!==i&&!await Te(e,i,t)){if(r.signal?.throwIfAborted(),Date.now()>=o)throw new Oe(e,i.record);await Ne(Math.min(n,Math.max(1,o-Date.now())),r.signal)}}}(e,t,i);try{return await r()}finally{await Re(e,t)}}new TextDecoder("utf-8",{fatal:!0,ignoreBOM:!0});import{dirname as Ae,join as $e}from"node:path";import{fileURLToPath as De}from"node:url";Ae(De(import.meta.url));import{dirname as Le,join as Ce}from"node:path";import{fileURLToPath as je}from"node:url";var Ue=Le(je(import.meta.url));Ce(Ue,"reflection-persona.md"),Ce(Ue,"dream-persona.md"),new Date(0).toISOString();function Fe(e){return oe(e,"terminal-claim.json")}function qe(e,t,r){return e.runId===t.runId&&e.attempt===t.attempt&&(void 0===r||e.kind===r)}function Je(e,t){let r=`${e}.${process.pid}.${X()}.candidate`;!function(e,t){let r=ee(e,"wx",384);try{ie(r,`${JSON.stringify(t,null,2)}\n`,"utf8"),Q(r)}finally{W(r)}}(r,t);try{Z(r,e)}catch(e){if("EEXIST"===ze(e))return!1;throw e}finally{Ye(r)}return function(e){let t;try{t=ee(e,"r")}catch(e){if("win32"===process.platform&&["EISDIR","EPERM"].includes(ze(e)??""))return;throw e}try{Q(t)}catch(e){if("win32"!==process.platform||!["EPERM","EACCES","EINVAL"].includes(ze(e)??""))throw e}finally{W(t)}}(ne(e)),!0}function Ge(e){let t;try{t=te(e,"utf8")}catch(e){if("ENOENT"===ze(e))return;throw e}try{return{raw:t,claim:Ke(JSON.parse(t))}}catch{return{raw:t,claim:void 0}}}async function Ve(e,t,r,i,n,o){let a=await Se("reflection-finalize",{runId:n.runId});return ke(oe(e,"terminal-claim-recovery.lock"),a,async()=>{let e=Ge(t);if(void 0!==e){if(e.raw!==r.raw)return e.claim;if("invalid"===i){if(void 0!==e.claim)return e.claim}else{if(void 0===e.claim)return;if("publish"!==e.claim.kind||!He(e.claim,o))return e.claim}Ye(t)}},{waitTimeoutMs:Number.POSITIVE_INFINITY})}function Ye(e){try{re(e)}catch(e){if("ENOENT"!==ze(e))throw e}}function He(e,t){return"dead"===(t.getPidLiveness??Ee)(e.claimantPid)}function Ke(e){if(!function(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}(e)||1!==e.version||"publish"!==e.kind&&"abandon"!==e.kind||"string"!=typeof e.runId||!Number.isInteger(e.attempt)||!Number.isInteger(e.claimantPid)||Number(e.claimantPid)<=0)throw TypeError("Invalid run terminal claim");return{version:1,kind:e.kind,runId:e.runId,attempt:Number(e.attempt),claimantPid:Number(e.claimantPid)}}function ze(e){return e instanceof Error&&"code"in e?String(e.code):void 0}import{join as Be}from"node:path";async function Xe(e,t,r){let i={runId:t.runId,attempt:t.attempt??1},n=await async function(e,t,r,i={},n=process.pid){let o=Fe(e),a={version:1,kind:r,runId:t.runId,attempt:t.attempt,claimantPid:n},s=0;for(;;){if(Je(o,a))return a;let n=Ge(o);if(void 0===n)continue;let l=n.claim;if(void 0===l){if(s>0)throw TypeError("Invalid run terminal claim");s+=1,await(i.beforeRecovery?.("invalid"));let r=await Ve(e,o,n,"invalid",t,i);if(void 0!==r)return r;continue}if(qe(l,t,r))return l;if("publish"!==l.kind||!He(l,i))return l;await(i.beforeRecovery?.("dead-publish"));let c=await Ve(e,o,n,"dead-publish",t,i);if(void 0!==c)return c}}(e,i,"publish");qe(n,i,"publish")&&(await p(j(e,"publishing.json"),{version:1,...i,finishedAt:r.finishedAt}),await async function(e,t,r,i=ke,n=Se){let o=await n("reflection-finalize",{runId:t});return i(Be(e,"terminalization.lock"),o,r,{waitTimeoutMs:Number.POSITIVE_INFINITY})}(e,t.runId,async()=>{if(L(j(e,"final.json"))||L(j(e,"abandoned.json")))return;if(!qe(function(e){return Ke(JSON.parse(te(Fe(e),"utf8")))}(e),i,"publish"))return;let n=void 0!==B({code:r.childExit.code,stdout:C(t.stdoutPath,"utf8"),stderr:C(t.stderrPath,"utf8"),timedOut:r.timedOut});n&&void 0!==t.nextAttempt&&await m(j(e,"ledger.json"),{attempt:t.nextAttempt.attempt,model:t.nextAttempt.model,...void 0===t.nextAttempt.thinking?{thinking:void 0}:{thinking:t.nextAttempt.thinking},launching:!0,pid:void 0,processStart:void 0,childPid:void 0,childProcessStart:void 0}),await p(j(e,"outcome.json"),r),n&&await w(Fe(e))}))}function We(e){try{i(3,`${JSON.stringify(e)}\n`)}catch(e){if(!(e instanceof Error&&"code"in e&&["EPIPE","EBADF"].includes(String(e.code))))throw e}}var Qe=process.argv.slice(2);try{if("--child-bootstrap"===Qe[0]){if(void 0===Qe[1])throw TypeError("run directory is required");await async function(t){if(!await async function(){for await(let e of process.stdin)if(Buffer.byteLength(e)>0)return!0;return!1}())return;let r=await f(n(t,"launch.json")),i=O(),o=e(r.command,[...r.args],{cwd:r.cwd,env:r.env,detached:!1,stdio:["ignore","inherit","inherit"],windowsHide:!0});We({code:o.pid??null,signal:"MODEL_PID"});let a=()=>{"win32"!==i&&"win32"!==process.platform||o.kill()};process.on("SIGTERM",a),process.on("SIGINT",a);let s=_(r.hardDeadlineAt,()=>{"win32"===i?A(i,o):R(process.pid,"SIGTERM")}),l=_(r.hardDeadlineAt+r.terminationGraceMs,()=>{$(i,process.pid)});We(await new Promise(e=>{let t=!1;o.once("error",()=>{t||(t=!0,e({code:null,signal:null}))}),o.once("close",(r,i)=>{t||(t=!0,e({code:r,signal:i}))})}).finally(()=>{s(),l()}))}(Qe[1])}else{if(void 0===Qe[0])throw TypeError("run directory is required");await async function(i){let a=n(i,"launch.json"),s=n(i,"ledger.json"),l=await f(a),c=O();await m(s,{launching:!1,pid:process.pid,processStart:await D(process.pid)});let d,u=r(l.stdoutPath,"w"),p=r(l.stderrPath,"w"),h=e(process.execPath,[o(import.meta.url),"--child-bootstrap",i],{env:process.env,detached:!0,stdio:["pipe",u,p,"pipe"],windowsHide:!0}),y=h.pid,v="",g=h.stdio[3];null!=g&&"setEncoding"in g&&(g.setEncoding("utf8"),g.on("data",e=>{v+=e;for(let e of v.split("\n"))try{let t=JSON.parse(e);"MODEL_PID"===t.signal&&"number"==typeof t.code&&(d=t.code)}catch(e){if(!(e instanceof SyntaxError))throw e}}));let E=(e=!1)=>{try{$(c,y,e)}catch(e){process.stderr.write(`${e instanceof Error?e.message:String(e)}\n`)}};if(process.once("exit",()=>E(!0)),process.once("SIGTERM",()=>{E(),y=void 0,process.exit(143)}),process.once("SIGINT",()=>{E(),y=void 0,process.exit(130)}),void 0===h.pid)throw Error("child bootstrap did not receive a pid");await m(s,{childPid:h.pid,childProcessStart:await D(h.pid)}),h.stdin?.end("1\n");let I,b=!1,N=Promise.resolve();"win32"===c&&(N=new Promise(e=>{I=e}));let M=_(l.hardDeadlineAt,()=>{b=!0,"win32"===c?k(h.pid):A(c,h)}),x=_(l.hardDeadlineAt+l.terminationGraceMs,()=>{$(c,d??y),I?.()}),T=await new Promise((e,t)=>{let r=!1;h.once("error",e=>{r||(r=!0,t(e))}),h.once("close",(t,i)=>{r||(r=!0,e({code:t,signal:i}))})}),R=function(e){let t=e.trim().split("\n").at(-1);if(void 0!==t&&0!==t.length)try{let e=JSON.parse(t),r="number"==typeof e.code||null===e.code?e.code:void 0,i="string"==typeof e.signal||null===e.signal?e.signal:void 0;return void 0===r||void 0===i||"MODEL_PID"===i?void 0:{code:r,signal:i}}catch{return}}(v);"win32"===c&&b&&void 0===R&&await N,M(),x(),y=void 0,t(u),t(p);let L=function(){let e=S()?process.env.OMO_MEMORY_SUPERVISOR_CLOCK_PATH:void 0;return void 0===e?Date.now():P(e)}(),C=(new Date).toISOString(),j={version:1,runId:l.runId,attempt:l.attempt,finishedAt:C,childExit:R??T,timedOut:b||Number.isFinite(L)&&L>=l.hardDeadlineAt};await Xe(i,l,j),await w(a),process.exit(0)}(Qe[0])}}catch(e){process.stderr.write(`${e instanceof Error?e.stack??e.message:String(e)}\n`),process.exitCode=1}