Skip to content

Machine-unique identity for the reflection lock host check - #11863

Open
meutex wants to merge 16 commits into
code-yeongyu:devfrom
meutex:meutex/issue-1793303560-machine-unique-identity-for-the-reflecti
Open

meutex wants to merge 16 commits into
code-yeongyu:devfrom
meutex:meutex/issue-1793303560-machine-unique-identity-for-the-reflecti

Conversation

@meutex

@meutex meutex commented Oct 30, 2026 •

Copy link
Copy Markdown

Problem

As someone who runs OmO memory on more than one machine that share a memory directory, I want the reflection lock's holder identity to be unique per machine, so that another machine's live reflection run is never mistaken for my own dead run and reclaimed.

Change

The reflection lock records which machine holds it only by hostname, and hostnames are not globally unique, so two of a user's machines with the same name treat each other's live lock as local, fail to find the recorded pid locally, and reclaim a run that is still running. Record a platform machine id alongside the hostname and decide "is this lock mine" by id first, keeping exact hostname equality as the fallback for legacy records and for a machine that cannot read its id.

  • One shared rule: the host-identity decision lives in exactly one new exported module, packages/memory-core/src/reflection/host-identity.ts, and is used by packages/memory-core/src/reflection/stale-holder.ts, by all four launcherHostname comparisons in packages/omo-senpi/src/components/memory/worker/run-reconciliation-prelaunch.ts, and (through the regenerated bundle) by omo doctor. No second copy of the comparison exists anywhere in the change.
  • Equal machine ids mean local: the existing pid-liveness, pid-reuse, terminal-claim and outcome rules then apply unchanged. Different machine ids mean foreign: the verdict is stuck, never dead, whatever the two hostnames say, and the reason text names the recorded hostname without printing either machine id.
  • Two machines with the same hostname (or LocalHostName) but different machine ids keep each other's locks stuck. Covered by a test: a new scenario in packages/memory-core/src/reflection/stale-holder.scenarios.test-support.ts carrying a rule: tag, asserted by stale-holder.test.ts, so deleting the id rule changes that scenario's verdict.
  • A hostname change on the same machine still reclaims that machine's own dead lock (the [Bug]: Memory reflection lock permanently stuck on macOS when dynamic DHCP hostname changes (#10377 followup) #11818 case): same machine id, different recorded hostname, launcher dead, valid outcome recorded -> dead. New scenario with its own rule: tag.
  • Records without a machine id fall back to exact hostname equality: a legacy active.lock with no launcherMachineId keeps today's verdicts, and the existing other-host and launcher-identity scenarios stay green.
  • A null current id lookup falls back to exact hostname equality too: when the record carries an id but the lookup returns null (unavailable or timed out), the check compares hostnames and does not turn a would-be local lock foreign; a test injects the null lookup.
  • A lookup timeout is not cached for the process: a probe that fails or times out once and then succeeds is re-attempted by a later call in the same process and the id rule then applies. Only a successful id is memoized.
  • No shared placeholder identity: an unreadable platform machine id is null, never a fixed string that every id-less machine would share (this is why the existing "no-platform-machine-id" sentinel is not used as an identity).
  • No lookup on the hot path: no machine-id read happens when active.lock is absent, and none when the record carries no launcherMachineId. A scenario proves the lookup is not called (a probe that would fail the test if invoked).
  • The writer records the id: currentLauncherIdentity() stamps launcherMachineId next to launcherHostname on ReservedRun, and a failed lookup still lets the reservation succeed with the field omitted (hostname fallback covers it).
  • Nothing derived from the machine id is written inside the memory repo or its synced state: the fix adds no file next to the memory state, because memory syncs across the user's devices.
  • Prelaunch reconciliation uses the same rule: a foreign-id active reservation is never released or quarantined, while a same-id, different-hostname one is still recoverable. ReconcileContext gains the machineId seam and run-reconciliation.ts supplies its default; covered by a test in the omo-senpi worker reconciliation suite.
  • Doctor parity holds: packages/omo-native/bin/lib/reflection-lock.generated.js is regenerated with bun script/build-reflection-lock.ts (never hand-edited) and script/build-reflection-lock.test.ts, packages/omo-native/test/doctor-reflection-lock.test.ts and packages/omo-native/test/doctor-reflection-lock-parity.test.ts pass.
  • The sync peer's device identity is untouched: deviceFingerprint keeps its current sentinel and permanent cache behaviour and the existing sync tests stay green.
  • Gates pass: bun run typecheck, bun test, tsgo --noEmit -p packages/omo-senpi/tsconfig.json, bun run test:senpi, and bun run lint:ratchet.
  • A changelog fragment changes/unreleased/.md exists, named exactly for the opened PR, added in a commit after the PR was opened, with a ### Fixed section and non-empty text (style of changes/unreleased/11392.md).
  • The pull request targets dev, is written in English, and fills .github/pull_request_template.md including QA & Evidence with decisive sanitized excerpts and a sha256 per evidence file (the evidence files themselves stay local and are never committed).

Testing

rm -rf .sentrux && git status --short | head -40
exit=0

Fixes #11822


Summary by cubic

Records a hashed platform machine id beside the hostname in the reflection lock so two machines sharing a hostname no longer reclaim each other's live reflection run. The raw id is hashed at the source with a domain-separated sha256 and never stored or compared, including in quarantine evidence. Host identity is decided by the hash first, with exact hostname equality as the fallback for legacy records and unreadable ids.

  • A foreign machine hash marks the lock stuck, never dead, regardless of matching hostnames; a same-hash, different-hostname lock is still reclaimed.
  • The id is read lazily and never on the hot path, and only successful lookups are memoized; a failed or timed-out lookup is re-attempted.
  • Prelaunch reconciliation, omo doctor, and the Senpi /doctor reservation check share the same rule through one exported module, loaded from the #memory-host-identity lazy sidecar so omo.js stays under its bundle budget.

Written for commit 89783c7. Summary will update on new commits.

View guided diff

@code-yeongyu

Copy link
Copy Markdown
Owner

Thanks a lot for this, @meutex, and welcome! Recording a machine id next to the hostname is the fix we wanted for #11822, and we're taking this PR forward for it. We'll follow up here with any review notes.

@meutex

meutex commented Oct 31, 2026

Copy link
Copy Markdown
Author

Thanks @code-yeongyu, really glad this is the fix you wanted!

Whenever you get a chance to review, please share any feedback or changes you'd like and I'll update the PR quickly. Also happy to rebase if #11819 lands first.

Looking forward to seeing it in a release. Thanks again!

@code-yeongyu

Copy link
Copy Markdown
Owner

Thanks a lot for this, @meutex! The review found a few things to finish before it can land, and since it fixes an issue we filed (#11822), we're taking care of them ourselves as maintainer edits on top of your branch. Your commits and credit stay as they are. The main changes:

  • The lock stores a hashed, domain-separated machine identifier instead of the raw platform machine ID, the same way the device fingerprint already works.
  • The Senpi /doctor reservation check uses the same identity as the lock, so the two stay in sync.
  • The changelog fragment is renamed to 11863.md with the right link.

There's nothing you need to do. We'll post here once the update is pushed. Thanks again for picking this up!

…ctor ownership by it (code-yeongyu#11863)

The reflection lock recorded the raw platform machine id, which also spread into
quarantine evidence. The id is now hashed at the source with a domain-separated
sha256 (omo-reflection-host:) and every comparison uses the hash; locks written
without one still fall back to hostname equality. The Senpi /doctor reservation
check now uses classifyHostIdentity like reconcile and the omo-native doctor.
Renames the changelog fragment to 11863.md.
@code-yeongyu

Copy link
Copy Markdown
Owner

The update is pushed (head 28c8f8b): the lock now stores only a hashed machine identifier, /doctor decides ownership the same way, the latest dev is merged in, and the changelog fragment is 11863.md. Your original commits are unchanged. Thanks again, @meutex!

# Conflicts:
#	packages/omo-senpi/plugin/extensions/memory-run-supervisor.mjs
#	packages/omo-senpi/plugin/extensions/omo-memory-commands.js
#	packages/omo-senpi/plugin/extensions/omo-memory-doctor.js
#	packages/omo-senpi/plugin/extensions/omo-memory-memfs.js
#	packages/omo-senpi/plugin/extensions/omo-memory-palace.js
#	packages/omo-senpi/plugin/extensions/omo-memory-sync.js
#	packages/omo-senpi/plugin/extensions/omo-onboarding-scan.js
#	packages/omo-senpi/plugin/extensions/omo-side-panel.js
#	packages/omo-senpi/plugin/extensions/omo.js
@gitguardian

gitguardian Bot commented Nov 1, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

Since your pull request originates from a forked repository, GitGuardian is not able to associate the secrets uncovered with secret incidents on your GitGuardian dashboard.
Skipping this check run and merging your pull request will create secret incidents on your GitGuardian dashboard.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
39351725 Triggered Generic Password 82eed01 .github/workflows/ci.yml View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 29 files

Confidence score: 2/5

  • The generated memory-run-supervisor.mjs still uses hostname-only stale-lock checks, so machines with the same hostname can reclaim each other’s live locks. Regenerate the bundle with the machine-ID rule.
  • In host-identity.ts, matching machine hashes are treated as local even when hostnames differ, so cloned hosts sharing a memory directory can treat each other’s live locks as local. Keep a hostname mismatch from classifying the holder as local.
  • In run-reconciliation.test.ts, the “never released or quarantined” test only checks active.runId, so it can pass if reservation state changes. Compare the full state before and after reconciliation.
  • In stale-holder.scenarios.test-support.ts, labels don’t map to // rule: tags and machine-id-hot-path is reused, so they can’t reliably identify which fail-closed rule a scenario covers. Align each label with a unique rule tag.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/omo-senpi/src/components/memory/worker/run-reconciliation.test.ts">

<violation number="1" location="packages/omo-senpi/src/components/memory/worker/run-reconciliation.test.ts:354">
P3: This test name promises the reservation is 'never released or quarantined', but it only checks that `active.runId` survives. Capture `await item.store.readState()` before reconciling and assert `toEqual` against it afterwards, matching the untouched-reservation test below. That also catches unintended rewrites of the launcher fields.</violation>
</file>

<file name="packages/memory-core/src/reflection/stale-holder.scenarios.test-support.ts">

<violation number="1" location="packages/memory-core/src/reflection/stale-holder.scenarios.test-support.ts:72">
P3: These new `rule` labels don't match any `// rule:` tag in `stale-holder.ts`, and `machine-id-hot-path` is reused by two scenarios. The file's own convention says each label names a fail-closed rule, so these labels break that contract. Use the production rule names, or drop the `rule` field for the non-rule cases.</violation>
</file>

<file name="packages/omo-senpi/plugin/extensions/memory-run-supervisor.mjs">

<violation number="1" location="packages/omo-senpi/plugin/extensions/memory-run-supervisor.mjs:2">
P1: This generated bundle does not contain the new machine-ID rule. The stale-lock check still compares only hostnames, so same-hostname machines can still reclaim each other's live locks. The hash reader is also emitted as an IIFE that never calls its async arrow, so it is dead code. Rebuild with the supported build path (`build-extension-core`) so the artifact matches `host-identity.ts` and `stale-holder.ts`.</violation>
</file>

<file name="packages/memory-core/src/reflection/host-identity.ts">

<violation number="1" location="packages/memory-core/src/reflection/host-identity.ts:84">
P2: Equal machine hashes return `local` even when the hostnames differ. Hosts that share a machine-id (cloned VM images, containers from one image) and a memory directory will now see each other's live locks as local, and the PID check runs against the wrong machine. Before this change the hostname mismatch made them `stuck`. Keep a hash match from overriding a hostname mismatch unless a same-host signal, such as the launcher process start identity, also matches.</violation>
</file>

Reply to a comment to ask cubic a question or push back. It learns from your replies.

View guided diff | Re-trigger cubic

@@ -1,6 +1,6 @@
#!/usr/bin/env node

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This generated bundle does not contain the new machine-ID rule. The stale-lock check still compares only hostnames, so same-hostname machines can still reclaim each other's live locks. The hash reader is also emitted as an IIFE that never calls its async arrow, so it is dead code. Rebuild with the supported build path (build-extension-core) so the artifact matches host-identity.ts and stale-holder.ts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/omo-senpi/plugin/extensions/memory-run-supervisor.mjs, line 2:

<comment>This generated bundle does not contain the new machine-ID rule. The stale-lock check still compares only hostnames, so same-hostname machines can still reclaim each other's live locks. The hash reader is also emitted as an IIFE that never calls its async arrow, so it is dead code. Rebuild with the supported build path (`build-extension-core`) so the artifact matches `host-identity.ts` and `stale-holder.ts`.</comment>

<file context>
@@ -1,6 +1,6 @@
 #!/usr/bin/env node
-// omo:17unDBtZ8IdzIBewpDPRQ6zCcIj8vuXhA57dubcbuQo:T3ymrGm5KDcZy6QTjJTp9Rcu2E3gGnhv758soXu6Vc4
-import{spawn as e}from"node:child_process";import{Buffer as t}from"node:buffer";import*as r from"node:fs";import*as n from"node:fs/promises";function i(e){if(e instanceof Error&&"code"in e)return"string"==typeof e.code?e.code:void 0}async function o(e){for(let t=0;;t+=1)try{return await e()}catch(e){if("EINTR"!==i(e)||t>=128)throw e}}function a(e){for(let t=0;;t+=1)try{return e()}catch(e){if("EINTR"!==i(e)||t>=128)throw e}}var s=new Set(["EPERM","EBUSY","EACCES"]),l=[10,25,50,100,200,400],c=e=>new Promise(t=>setTimeout(t,e));import{Buffer as u}from"node:buffer";import{constants as d}from"node:fs";import*as f from"node:fs/promises";function p(e){return"number"==typeof e?0!==(e&d.O_EXCL):"string"==typeof e&&e.toLowerCase().includes("x")}async function m(e,t,r,n){let i="string"==typeof t?u.from(t,r??"utf8"):t,a=0;for(;a<i.length;){n?.throwIfAborted();let{bytesWritten:t}=await o(()=>e.write(i,a));if(t<=0)throw Error(`fs write made no progress at offset ${a}`);a+=t}}function w(e){return"function"==typeof e}function h(e){return(...t)=>o(async()=>e(...t))}function y(e){return(...t)=>a(()=>e(...t))}function v(e,t){let r="async"===t?h:y,n=new Map;return new Proxy(e,{get(e,t){if(n.has(t))return n.get(t);let i=Reflect.get(e,t);if(!w(i))return i;let o=r(i),a=Reflect.get(i,"native");return w(a)&&Object.assign(o,{native:r(a)}),n.set(t,o),o}})}var g=new Set(["stat","sync","datasync","chmod","chown","utimes","truncate"]);var b=v(n,"async");function E(e,t,r){return async function(e,t,r){let n="win32"===r.platform?r.delaysMs??l:[],a=r.sleep??c;for(let l=0;;l+=1)try{return void await o(()=>void 0===r.admit?r.rename(e,t):r.admit(()=>r.rename(e,t)))}catch(e){let t=n[l],r=i(e);if(void 0===t||void 0===r||!s.has(r))throw e;await a(t)}}(e,t,{rename:(e,t)=>n.rename(e,t),platform:process.platform,...void 0===r?{}:{admit:r}})}var S=v(r,"sync"),{access:_,chmod:k,copyFile:x,cp:O,link:I,lstat:N,mkdir:$,mkdtemp:A,readFile:M,readdir:T,readlink:R,realpath:P,rm:C,rmdir:D,stat:L,statfs:j,unlink:z}=b,{accessSync:F,chmodSync:q,fsyncSync:U,linkSync:Z,lstatSync:B,mkdirSync:J,readFileSync:G,readdirSync:W,realpathSync:H,statSync:K,unlinkSync:Y,writeFileSync:V,writeSync:X}=S,{constants:Q,watch:ee}=r,te=async(...e)=>{let t=()=>n.open(...e);return function(e){return new Proxy(e,{get(e,t){let r=Reflect.get(e,t);if(!w(r))return r;let n=t=>Reflect.apply(r,e,[...t]);return"close"===t||t===Symbol.asyncDispose?(...e)=>{let t=n(e);return t instanceof Promise?t.catch(e=>{if("EINTR"!==i(e))throw e}):t}:"string"==typeof t&&g.has(t)?(...e)=>{let t=n(e);return t instanceof Promise?t.catch(t=>{if("EINTR"!==i(t))throw t;return o(async()=>n(e))}):t}:(...e)=>n(e)}})}(await(p(e[1]??"r")?t():o(t)))};var re=async(e,r,a)=>{if("string"!=typeof r&&!(r instanceof Uint8Array))return n.writeFile(e,r,a);if("object"==typeof(s=e)&&!(s instanceof URL)&&!t.isBuffer(s)&&"fd"in s){let t="string"==typeof a?{encoding:a}:a??{};return t.signal?.throwIfAborted(),await m(e,r,t.encoding??void 0,t.signal),void(!0===t.flush&&await o(()=>e.sync()))}var s;return async function(e,t,r,n){let a="string"==typeof r?{encoding:r}:r??{};a.signal?.throwIfAborted();let s=a.flag??n,l=()=>f.open(e,s,a.mode),c=await(p(s)?l():o(l));try{await m(c,t,a.encoding??"utf8",a.signal),!0===a.flush&&await o(()=>c.sync())}finally{await c.close().catch(e=>{if("EINTR"!==i(e))throw e})}}(e,r,a??void 0,"w")},ne=(e,t,n)=>function(e,t,r,n){return p(r)?e(t,r,n):a(()=>e(t,r,n))}(r.openSync,e,t,n??void 0);function ie(e){try{r.closeSync(e)}catch(e){if("EINTR"!==i(e))throw e}}function oe(e){try{return a(()=>r.statSync(e)),!0}catch{return!1}}import{join as ae}from"node:path";import{fileURLToPath as se}from"node:url";import{homedir as le}from"node:os";import{join as ce,resolve as ue}from"node:path";function de(e,t){let r=e.OMO_MEMORY_HOME;return void 0===r||""===r.trim()?ce(le(),".omo","memory"):ue(t,r)}import{constants as fe}from"node:os";var pe={darwin:{names:["/usr/lib/libSystem.B.dylib"],errnoSymbol:"__error"},linux:{names:["libc.so.6","libc.musl-x86_64.so.1","libc.musl-aarch64.so.1","libc.so"],errnoSymbol:"__errno_location"}},me=null;function we(){return me??=async function(){let e,t=pe[process.platform];if(void 0===t)return null;try{e=await(import("bun:ffi"))}catch{return null}for(let r of t.names)try{let n=e.dlopen(r,{flock:{args:[e.FFIType.i32,e.FFIType.i32],returns:e.FFIType.i32},[t.errnoSymbol]:{args:[],returns:e.FFIType.ptr}}),i=n.symbols[t.errnoSymbol];if(void 0===i)continue;return(t,r)=>{let o=Number(n.symbols.flock(t,r));if(0===o)return{rc:o,errno:0};let a=i();return{rc:o,errno:null===a?-1:e.read.i32(a,0)}}}catch{continue}return null}(),me}async function he(){return null!==await we()}fe.errno;import ye from"node:path";import ve from"node:path";import{randomUUID as ge}from"node:crypto";import{hostname as be}from"node:os";import{execFile as Ee}from"node:child_process";var Se=null;function _e(){return Se??=async function(){if("darwin"!==process.platform)return null;try{let{dlopen:e,FFIType:t}=await(import("bun:ffi")),r=e("/usr/lib/libSystem.B.dylib",{proc_pidinfo:{args:[t.i32,t.i32,t.u64,t.ptr,t.i32],returns:t.i32}});return(e,t,n,i,o)=>Number(r.symbols.proc_pidinfo(e,t,n,i,o))}catch{return null}}(),Se}var ke=null;async function xe(e){if("win32"!==process.platform)return null;if(!Number.isSafeInteger(e)||e<=0)return null;ke??=async function(){if("win32"!==process.platform)return null;try{let{dlopen:e,FFIType:t}=await(import("bun:ffi"));return e("kernel32.dll",{OpenProcess:{args:[t.u32,t.i32,t.u32],returns:t.ptr},GetProcessTimes:{args:[t.ptr,t.ptr,t.ptr,t.ptr,t.ptr],returns:t.i32},CloseHandle:{args:[t.ptr],returns:t.i32}}).symbols}catch{return null}}();let t=await ke;if(null===t)return null;let r=t.OpenProcess(4096,0,e);if(null===r||0===r||0n===r)return null;try{let e=new BigUint64Array(1),n=t.GetProcessTimes(r,e,new BigUint64Array(1),new BigUint64Array(1),new BigUint64Array(1)),i=e[0]??0n;return 0===n||0n===i?null:i}finally{t.CloseHandle(r)}}async function Oe(e,t){return await new Promise(r=>{Ee(e,t,{encoding:"utf8",timeout:2e3,windowsHide:!0},(e,t)=>{if(null!==e)return void r(null);let n=t.trim();r(n.length>0?n:null)})})}var Ie=function(e,t){let r;return n=>n!==t?e(n):(r??=e(n).then(e=>(null===e&&(r=void 0),e),e=>{throw r=void 0,e}),r)}(async function(e){if("linux"===process.platform)return await async function(e){try{let t=await M(`/proc/${e}/stat`,"utf8"),r=t.lastIndexOf(")");if(r<0)return null;let n=t.slice(r+2).trim().split(/\s+/)[19];return void 0===n?null:`linux-proc-start-ticks:${n}`}catch{return null}}(e);if("darwin"===process.platform||"freebsd"===process.platform){if("dead"===$e(e))return null;let t=await async function(e){if("darwin"!==process.platform)return null;if(!Number.isSafeInteger(e)||e<=0)return null;let t=await _e();if(null===t)return null;let r=new Uint8Array(136),n=0;try{n=t(e,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}(e);if(null!==t)return`proc-start-epoch:${t}`;let r=await Oe("/bin/ps",["-o","lstart=","-p",String(e)]);return null===r?null:`ps-lstart:${r.replace(/\s+/g," ")}`}return"win32"===process.platform?await async function(e){if("dead"===$e(e))return null;let t=await xe(e);if(null!==t)return`win32-creation-filetime:${t}`;let r=await Oe("powershell.exe",["-NoProfile","-Command",`(Get-Process -Id ${e} -ErrorAction SilentlyContinue).StartTime.ToUniversalTime().ToString('o')`]);return null===r?null:`win32-creation-date:${r}`}(e):null},process.pid);function Ne(e){let t=e.indexOf(":");return t<=0?null:e.slice(0,t)}function $e(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 Ae(e){return"string"==typeof e&&e.length>0}function Me(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:Ae(r.process_start)&&Ae(r.hostname)&&Ae(r.nonce)?!Ae(r.created_at)||Number.isNaN(Date.parse(r.created_at))?null:Ae(r.purpose)&&(void 0===r.run_id||Ae(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},...!0===r.git_activity_lock?{git_activity_lock:!0}:{}}:null:null}async function Te(e,t={}){if(0===e.length)throw Error("lock purpose must not be empty");let r=await Ie(process.pid);return{pid:process.pid,process_start:r??"unavailable",hostname:be(),nonce:ge(),created_at:(new Date).toISOString(),purpose:e,...void 0===t.runId?{}:{run_id:t.runId},...await he()?{git_activity_lock:!0}:{}}}var Re=new Map;function Pe(e,t,r){Re.set(e,{previous:t,takeoverNonce:r})}function Ce(e,t){Re.get(e)?.takeoverNonce===t&&Re.delete(e)}function De(e){if(e instanceof Error&&"code"in e)return"string"==typeof e.code?e.code:void 0}async function Le(e){try{let t=await M(e,"utf8"),r=await L(e,{bigint:!0});return{raw:t,record:Me(t),dev:r.dev,ino:r.ino,mtimeMs:Number(r.mtimeMs)}}catch(e){let t=De(e);if("ENOENT"===t)return null;if("\\"===ve.sep&&"EPERM"===t)return{raw:"",record:null,dev:0,ino:0,mtimeMs:Date.now()};throw e}}function je(e,t){return e.dev===t.dev&&e.ino===t.ino&&e.mtimeMs===t.mtimeMs&&e.raw===t.raw}async function ze(e,t){Ce(e,t.nonce);let r=await Le(e);if(null===r||r.record?.nonce!==t.nonce)return!1;try{return await z(e),!0}catch(e){if("ENOENT"===De(e))return!1;throw e}}import{randomUUID as Fe}from"node:crypto";import qe from"node:path";import Ue from"node:path";var Ze=new Set;function Be(e){Ze.add(e)}function Je(e){Ze.delete(e)}var Ge=/\.candidate-(?:(\d+)-)?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function We(e){let t=Ge.exec(e)?.[1];return void 0!==t&&"dead"===$e(Number(t))}function He(e){if(e instanceof Error&&"code"in e)return"string"==typeof e.code?e.code:void 0}function Ke(e,t){try{e.onFailure?.(t)}catch{}}function Ye(e){if("win32"!==process.platform)return!1;let t=He(e);return"EBUSY"===t||"EPERM"===t||"EACCES"===t}var Ve=new Map;function Xe(e){Ve.delete(e)}var Qe=new Set(["EACCES","EPERM","ENOTSUP"]);function et(e){if(e instanceof Error&&"code"in e)return"string"==typeof e.code?e.code:void 0}function tt(e,t){if(void 0!==t)return t(e);if("win32"!==process.platform)return!1;let r=et(e);return"EBUSY"===r||"EPERM"===r||"EACCES"===r}var rt={};function nt(){return rt}async function it(e){for(let t=0;t<3;t+=1)try{return await(rt.unlink??z)(e),Je(e),!0}catch(r){if("ENOENT"===et(r))return Je(e),!0;if(!tt(r,rt.isSharingError))throw Be(e),Xe(qe.dirname(e)),r;if(t+1===3)return Be(e),Xe(qe.dirname(e)),!1}return!1}async function ot(e,t){await $(qe.dirname(e),{recursive:!0,mode:448});let{candidatePath:r,handle:n}=await async function(e){for(let t=0;;t+=1){let r=`${e}.candidate-${process.pid}-${Fe()}`;try{return{candidatePath:r,handle:await te(r,"wx",384)}}catch(n){if(await it(r)||Xe(qe.dirname(e)),"EINTR"!==et(n)||t>=128)throw n}}}(e);try{try{await m(n,`${JSON.stringify(t)}\n`,"utf8"),await n.sync()}finally{await n.close()}try{return await(rt.link??I)(r,e),!0}catch(r){if(function(e){let t=et(e);return"EEXIST"===t||"ENOENT"===t}(r))return!1;if(Qe.has(et(r)??""))return await async function(e,t){let r;try{r=await te(e,"wx",384)}catch(e){if("EEXIST"===et(e))return!1;throw e}try{return await(rt.writeFallback??m)(r,`${JSON.stringify(t)}\n`,"utf8"),await r.sync(),!0}finally{await r.close()}}(e,t);throw r}}finally{await it(r)||Xe(qe.dirname(e))}}function at(e,t,r=Math.random){let n=Math.min(250,t*2**Math.min(e,30));return Math.max(1,Math.round(n/2+r()*n/2))}function st(e,t){return t?.throwIfAborted(),new Promise((r,n)=>{let i=setTimeout(a,e),o=()=>a(t?.reason??new DOMException("The operation was aborted","AbortError"));function a(e){clearTimeout(i),t?.removeEventListener("abort",o),void 0===e?r():n(e)}t?.addEventListener("abort",o,{once:!0})})}import{randomUUID as lt}from"node:crypto";import{hostname as ct}from"node:os";function ut(e){if(e instanceof Error&&"code"in e)return"string"==typeof e.code?e.code:void 0}async function dt(e){if(e.hostname!==ct())return!1;let t=$e(e.pid);if("dead"===t)return!0;if("unknown"===t)return!1;let r=await Ie(e.pid);return null!==r&&"unavailable"!==e.process_start&&function(e,t){let r=Ne(e);return null!==r&&r===Ne(t)&&e!==t}(e.process_start,r)}async function ft(e,t,r){return null!==e.record?dt(e.record):t-e.mtimeMs>r}async function pt(e,t,r){let n=await Le(e);if(null===n||!await ft(n,t,r))return!1;let i=`${e}.reaping-${lt()}`;try{await E(e,i)}catch(e){if("ENOENT"===ut(e))return!1;if(tt(e))return!1;throw e}let o=await Le(i);if(null!==o&&je(o,n))return await z(i),!0;try{await I(i,e)}catch(e){if("EEXIST"!==ut(e))throw e}return await z(i),!1}async function mt(e,t,r,n,i){if(!await ft(t,n,i))return!1;let o=`${e}.recovery`,a={...r,nonce:lt(),created_at:(new Date).toISOString(),purpose:`${r.purpose}:recovery`};for(let e=0;null!==await Le(o)||!await ot(o,a);e+=1)if(e>0||!await pt(o,n,i))return!1;try{let s=await Le(e);if(null===s)return!0;if(!je(s,t))return!1;if(!await ft(s,n,i))return!1;let l=await Le(o);return null!==l&&l.record?.nonce===a.nonce&&(await z(e),null!==s.record&&Pe(e,s.record,r.nonce),!0)}finally{await ze(o,a)}}class wt extends Error{lockPath;owner;retriable=!0;constructor(e,t){super(`Lock is held: ${e}`),this.lockPath=e,this.owner=t,this.name="LockContentionError"}}async function ht(e,t,r={}){let n=r.waitTimeoutMs??0,i=r.retryDelayMs??25,o=r.incompleteLockGraceMs??5e3,a=r.now??Date.now;if(n<0||i<=0||o<0)throw Error("lock wait options must be positive");let s=a()+n;try{return await async function(e,t,r,n){let{retryDelayMs:i,incompleteLockGraceMs:o,now:a,deadline:s}=n,l=!1,c=!1;for(let n=0;;n+=1){r.signal?.throwIfAborted();let u=await Le(e);if(null===u){if(await ot(e,t))return{sawOtherOwner:l};if(r.signal?.throwIfAborted(),l=!0,c=yt(e,t,c),u=await Le(e),null===u)continue}if(l=!0,c=yt(e,t,c),await mt(e,u,t,a(),o))c=!0;else{if(r.signal?.throwIfAborted(),a()>=s)throw new wt(e,u.record);await st(Math.min(at(n,i),Math.max(1,s-a())),r.signal)}}}(e,t,r,{retryDelayMs:i,incompleteLockGraceMs:o,now:a,deadline:s})}catch(r){throw Ce(e,t.nonce),r}}function yt(e,t,r){return r&&Ce(e,t.nonce),!1}import{hostname as vt}from"node:os";import gt from"node:path";function bt(e){if(e instanceof Error&&"code"in e)return"string"==typeof e.code?e.code:void 0}async function Et(e,t){let r=!0===(await N(e).catch(()=>{}))?.isFile()?await async function(e){let t;try{t=await M(e,"utf8")}catch(e){if("ENOENT"===bt(e))return null;throw e}let r=Me(t);return null===r||r.hostname!==vt()||"dead"!==$e(r.pid)?null:await dt(r)?r:null}(e):null;if(null===r)return!1;let n=await Te("dead-owner-sweep");try{await ht(e,n,{waitTimeoutMs:0})}catch(e){if(e instanceof wt)return!1;throw e}return await ze(e,n),t.onReclaimed?.(e,r),!0}async function St(e,t,r={}){let n,i=ye.dirname(e);if(function(e,t){let r=Ve.get(e);return!(void 0!==r&&t-r<6e5||(Ve.set(e,t),0))}(i,(r.now??Date.now)())){let t=nt();await async function(e,t=Date.now,r={}){let n;try{n=await T(e)}catch(t){if("ENOENT"===He(t)){for(let t of Ze)Ue.dirname(t)===e&&Je(t);return 0}throw t}let i=0;for(let o of n){if(!Ge.test(o))continue;let n=Ue.join(e,o);try{let e=await L(n);if(!(Ze.has(n)||We(o)||t()-e.mtimeMs>36e5))continue;let a=!1;for(let e=0;e<3;e+=1)try{await(r.unlink??z)(n),Je(n),a=!0;break}catch(t){if("ENOENT"===He(t)){Je(n),a=!0;break}if(!(r.isSharingError?.(t)??Ye(t)))throw t;e+1===3&&Ke(r,n)}a&&(i+=1)}catch(e){"ENOENT"===He(e)&&Je(n),Ke(r,n)}}return i}(i,Date.now,{...void 0===t.unlink?{}:{unlink:t.unlink},...void 0===t.isSharingError?{}:{isSharingError:t.isSharingError},onFailure:()=>Xe(i)}).catch(()=>{Xe(i)}),await async function(e,t={}){let r;try{r=await T(e)}catch(r){return"ENOENT"!==bt(r)&&t.onFailure?.(e,r),0}let n=0;for(let i of r){if(!i.endsWith(".lock"))continue;let r=gt.join(e,i);try{await Et(r,t)&&(n+=1)}catch(e){t.onFailure?.(r,e)}}return n}(i,{onReclaimed:(t,r)=>{ye.resolve(t)===ye.resolve(e)&&(n=r)}})}let o=await ht(e,t,r);void 0===n||o.sawOtherOwner||void 0!==function(e,t){let r=Re.get(e);return r?.takeoverNonce===t?r.previous:void 0}(e,t.nonce)||Pe(e,n,t.nonce)}async function _t(e,t,r,n){await St(e,t,n);try{return await r()}finally{await ze(e,t)}}import{randomUUID as kt}from"node:crypto";import xt from"node:path";var Ot=".ticket";function It(e,t,r){if(!Number.isInteger(r)||r<1)throw Error(`${t} slot must be a positive integer, got ${r}`);return xt.join(e,`${t}.slot-${r}.lock`)}var Nt=0,$t=Promise.resolve(),At={};async function Mt(e,t,r){await $(e,{recursive:!0,mode:448});let n=xt.join(e,`${t}.staging`);await re(n,`${JSON.stringify(r)}\n`,{mode:384}),await E(n,xt.join(e,t))}async function Tt(e){try{return(await T(e)).filter(e=>e.endsWith(Ot)).sort()}catch(e){if("ENOENT"===Lt(e))return[];throw e}}async function Rt(e){try{await z(e)}catch(e){if("ENOENT"!==Lt(e))throw e}}async function Pt(e,t){let r,n=xt.join(e,t);try{r=await(At.readFile??M)(n,"utf8")}catch(e){if("ENOENT"===Lt(e))return!0;if(function(e){if(void 0!==At.isSharingError)return At.isSharingError(e);if("win32"!==process.platform)return!1;let t=Lt(e);return"EBUSY"===t||"EPERM"===t||"EACCES"===t}(e))return!1;throw e}let i=Me(r);return!(null===i||!await dt(i))&&(await Rt(n),!0)}async function Ct(e,t,r,n){for(let i=1;i<=r;i+=1){let r=It(e,t.purpose,i);try{return await St(r,t,{waitTimeoutMs:0,...void 0===n?{}:{signal:n}}),{slot:i,release:()=>ze(r,t),retainFor:async(e,n)=>{let i=`${r}.handoff-${t.nonce}`;try{await re(i,`${JSON.stringify({...t,pid:e,process_start:n??"unavailable",git_activity_lock:void 0})}\n`,{mode:384}),await E(i,r)}finally{await Rt(i)}},releaseSync:()=>{try{return Me(G(r,"utf8"))?.nonce===t.nonce&&(Y(r),!0)}catch(e){if("ENOENT"===Lt(e))return!1;throw e}}}}catch(e){if(e instanceof wt)continue;throw e}}}async function Dt(e,t,r={}){let n=r.maxConcurrent??t.defaultSlots,i=r.waitTimeoutMs??t.waitTimeoutMs,o=r.retryDelayMs??25;if(!Number.isInteger(n)||n<1)throw Error(`${t.purpose} maxConcurrent must be a positive integer, got ${n}`);if(i<0||o<=0)throw Error("lock wait options must be positive");let{signal:a}=r,s=function(e,t){return xt.join(e,`${t}.tickets`)}(e,t.purpose),l=(Nt=(Nt+1)%1e6,`${String(Date.now()).padStart(16,"0")}-${String(Nt).padStart(6,"0")}-${String(process.pid).padStart(10,"0")}-${kt()}${Ot}`),c=Date.now(),u=c+i,d=$t.then(async()=>{a?.throwIfAborted(),await Mt(s,l,await Te(`${t.purpose}:ticket`))});$t=d.catch(()=>{});try{await d;let r=await Te(t.purpose);for(let i=0;;i+=1){a?.throwIfAborted();let d=await Tt(s),f=d[0];if(void 0!==f&&d.includes(l)){if(f===l){let t=await Ct(e,r,n,a);if(void 0!==t){try{await Rt(xt.join(s,l))}catch(e){throw await t.release(),e}return t}}else if(await Pt(s,f))continue}else await Mt(s,l,await Te(`${t.purpose}:ticket`));let p=Date.now();if(p>=u)throw t.busyError(p-c,n);await st(Math.min(at(i,o),Math.max(1,u-p)),a)}}finally{await Rt(xt.join(s,l)).catch(()=>{})}}function Lt(e){if(e instanceof Error&&"code"in e)return"string"==typeof e.code?e.code:void 0}(function(){let e=process.env.OMO_TEST_GIT_LOCK_SETTLE_MS;if(void 0===e)return;let t=Number.parseInt(e,10);Number.isInteger(t)})(),["process\\.env\\.[A-Za-z_][A-Za-z0-9_]*","os\\.environ(?:\\.get\\(|\\[)?","env\\.[A-Za-z_][A-Za-z0-9_]*","\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}","\\$[A-Za-z_][A-Za-z0-9_]*","\\{\\{[^}\\n]*\\}\\}","\\[(?:redacted|filtered)\\]","(?:redacted|filtered)","(?:bearer|changeme|placeholder|example|none|null|undefined|true|false)"].join("|"),String.raw`#!/bin/sh
+// omo:8k2FT4GovKvN7YShtCDwrqWS38f4tm2u0bSbwfGeqqk:viYy2JhSMiPCPpKvkbLvBGbfTtgudufwwHbyvDvnL4o
+import{spawn as e}from"node:child_process";import{Buffer as t}from"node:buffer";import*as r from"node:fs";import*as n from"node:fs/promises";function i(e){if(e instanceof Error&&"code"in e)return"string"==typeof e.code?e.code:void 0}async function o(e){for(let t=0;;t+=1)try{return await e()}catch(e){if("EINTR"!==i(e)||t>=128)throw e}}function a(e){for(let t=0;;t+=1)try{return e()}catch(e){if("EINTR"!==i(e)||t>=128)throw e}}var s=new Set(["EPERM","EBUSY","EACCES"]),l=[10,25,50,100,200,400],c=e=>new Promise(t=>setTimeout(t,e));import{Buffer as u}from"node:buffer";import{constants as d}from"node:fs";import*as f from"node:fs/promises";function p(e){return"number"==typeof e?0!==(e&d.O_EXCL):"string"==typeof e&&e.toLowerCase().includes("x")}async function m(e,t,r,n){let i="string"==typeof t?u.from(t,r??"utf8"):t,a=0;for(;a<i.length;){n?.throwIfAborted();let{bytesWritten:t}=await o(()=>e.write(i,a));if(t<=0)throw Error(`fs write made no progress at offset ${a}`);a+=t}}function w(e){return"function"==typeof e}function h(e){return(...t)=>o(async()=>e(...t))}function y(e){return(...t)=>a(()=>e(...t))}function v(e,t){let r="async"===t?h:y,n=new Map;return new Proxy(e,{get(e,t){if(n.has(t))return n.get(t);let i=Reflect.get(e,t);if(!w(i))return i;let o=r(i),a=Reflect.get(i,"native");return w(a)&&Object.assign(o,{native:r(a)}),n.set(t,o),o}})}var g=new Set(["stat","sync","datasync","chmod","chown","utimes","truncate"]);var b=v(n,"async");function E(e,t,r){return async function(e,t,r){let n="win32"===r.platform?r.delaysMs??l:[],a=r.sleep??c;for(let l=0;;l+=1)try{return void await o(()=>void 0===r.admit?r.rename(e,t):r.admit(()=>r.rename(e,t)))}catch(e){let t=n[l],r=i(e);if(void 0===t||void 0===r||!s.has(r))throw e;await a(t)}}(e,t,{rename:(e,t)=>n.rename(e,t),platform:process.platform,...void 0===r?{}:{admit:r}})}var S=v(r,"sync"),{access:_,chmod:x,copyFile:k,cp:O,link:I,lstat:N,mkdir:$,mkdtemp:M,readFile:A,readdir:T,readlink:R,realpath:P,rm:D,rmdir:C,stat:L,statfs:j,unlink:F}=b,{accessSync:z,chmodSync:U,fsyncSync:q,linkSync:Z,lstatSync:B,mkdirSync:J,readFileSync:G,readdirSync:H,realpathSync:W,statSync:K,unlinkSync:Y,writeFileSync:V,writeSync:X}=S,{constants:Q,watch:ee}=r,te=async(...e)=>{let t=()=>n.open(...e);return function(e){return new Proxy(e,{get(e,t){let r=Reflect.get(e,t);if(!w(r))return r;let n=t=>Reflect.apply(r,e,[...t]);return"close"===t||t===Symbol.asyncDispose?(...e)=>{let t=n(e);return t instanceof Promise?t.catch(e=>{if("EINTR"!==i(e))throw e}):t}:"string"==typeof t&&g.has(t)?(...e)=>{let t=n(e);return t instanceof Promise?t.catch(t=>{if("EINTR"!==i(t))throw t;return o(async()=>n(e))}):t}:(...e)=>n(e)}})}(await(p(e[1]??"r")?t():o(t)))};var re=async(e,r,a)=>{if("string"!=typeof r&&!(r instanceof Uint8Array))return n.writeFile(e,r,a);if("object"==typeof(s=e)&&!(s instanceof URL)&&!t.isBuffer(s)&&"fd"in s){let t="string"==typeof a?{encoding:a}:a??{};return t.signal?.throwIfAborted(),await m(e,r,t.encoding??void 0,t.signal),void(!0===t.flush&&await o(()=>e.sync()))}var s;return async function(e,t,r,n){let a="string"==typeof r?{encoding:r}:r??{};a.signal?.throwIfAborted();let s=a.flag??n,l=()=>f.open(e,s,a.mode),c=await(p(s)?l():o(l));try{await m(c,t,a.encoding??"utf8",a.signal),!0===a.flush&&await o(()=>c.sync())}finally{await c.close().catch(e=>{if("EINTR"!==i(e))throw e})}}(e,r,a??void 0,"w")},ne=(e,t,n)=>function(e,t,r,n){return p(r)?e(t,r,n):a(()=>e(t,r,n))}(r.openSync,e,t,n??void 0);function ie(e){try{r.closeSync(e)}catch(e){if("EINTR"!==i(e))throw e}}function oe(e){try{return a(()=>r.statSync(e)),!0}catch{return!1}}import{join as ae}from"node:path";import{fileURLToPath as se}from"node:url";import{homedir as le}from"node:os";import{join as ce,resolve as ue}from"node:path";function de(e,t){let r=e.OMO_MEMORY_HOME;return void 0===r||""===r.trim()?ce(le(),".omo","memory"):ue(t,r)}import{constants as fe}from"node:os";var pe={darwin:{names:["/usr/lib/libSystem.B.dylib"],errnoSymbol:"__error"},linux:{names:["libc.so.6","libc.musl-x86_64.so.1","libc.musl-aarch64.so.1","libc.so"],errnoSymbol:"__errno_location"}},me=null;function we(){return me??=async function(){let e,t=pe[process.platform];if(void 0===t)return null;try{e=await(import("bun:ffi"))}catch{return null}for(let r of t.names)try{let n=e.dlopen(r,{flock:{args:[e.FFIType.i32,e.FFIType.i32],returns:e.FFIType.i32},[t.errnoSymbol]:{args:[],returns:e.FFIType.ptr}}),i=n.symbols[t.errnoSymbol];if(void 0===i)continue;return(t,r)=>{let o=Number(n.symbols.flock(t,r));if(0===o)return{rc:o,errno:0};let a=i();return{rc:o,errno:null===a?-1:e.read.i32(a,0)}}}catch{continue}return null}(),me}async function he(){return null!==await we()}fe.errno;import ye from"node:path";import ve from"node:path";import{randomUUID as ge}from"node:crypto";import{hostname as be}from"node:os";import{execFile as Ee}from"node:child_process";var Se=null;function _e(){return Se??=async function(){if("darwin"!==process.platform)return null;try{let{dlopen:e,FFIType:t}=await(import("bun:ffi")),r=e("/usr/lib/libSystem.B.dylib",{proc_pidinfo:{args:[t.i32,t.i32,t.u64,t.ptr,t.i32],returns:t.i32}});return(e,t,n,i,o)=>Number(r.symbols.proc_pidinfo(e,t,n,i,o))}catch{return null}}(),Se}var xe=null;async function ke(e){if("win32"!==process.platform)return null;if(!Number.isSafeInteger(e)||e<=0)return null;xe??=async function(){if("win32"!==process.platform)return null;try{let{dlopen:e,FFIType:t}=await(import("bun:ffi"));return e("kernel32.dll",{OpenProcess:{args:[t.u32,t.i32,t.u32],returns:t.ptr},GetProcessTimes:{args:[t.ptr,t.ptr,t.ptr,t.ptr,t.ptr],returns:t.i32},CloseHandle:{args:[t.ptr],returns:t.i32}}).symbols}catch{return null}}();let t=await xe;if(null===t)return null;let r=t.OpenProcess(4096,0,e);if(null===r||0===r||0n===r)return null;try{let e=new BigUint64Array(1),n=t.GetProcessTimes(r,e,new BigUint64Array(1),new BigUint64Array(1),new BigUint64Array(1)),i=e[0]??0n;return 0===n||0n===i?null:i}finally{t.CloseHandle(r)}}async function Oe(e,t){return await new Promise(r=>{Ee(e,t,{encoding:"utf8",timeout:2e3,windowsHide:!0},(e,t)=>{if(null!==e)return void r(null);let n=t.trim();r(n.length>0?n:null)})})}var Ie=function(e,t){let r;return n=>n!==t?e(n):(r??=e(n).then(e=>(null===e&&(r=void 0),e),e=>{throw r=void 0,e}),r)}(async function(e){if("linux"===process.platform)return await async function(e){try{let t=await A(`/proc/${e}/stat`,"utf8"),r=t.lastIndexOf(")");if(r<0)return null;let n=t.slice(r+2).trim().split(/\s+/)[19];return void 0===n?null:`linux-proc-start-ticks:${n}`}catch{return null}}(e);if("darwin"===process.platform||"freebsd"===process.platform){if("dead"===$e(e))return null;let t=await async function(e){if("darwin"!==process.platform)return null;if(!Number.isSafeInteger(e)||e<=0)return null;let t=await _e();if(null===t)return null;let r=new Uint8Array(136),n=0;try{n=t(e,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}(e);if(null!==t)return`proc-start-epoch:${t}`;let r=await Oe("/bin/ps",["-o","lstart=","-p",String(e)]);return null===r?null:`ps-lstart:${r.replace(/\s+/g," ")}`}return"win32"===process.platform?await async function(e){if("dead"===$e(e))return null;let t=await ke(e);if(null!==t)return`win32-creation-filetime:${t}`;let r=await Oe("powershell.exe",["-NoProfile","-Command",`(Get-Process -Id ${e} -ErrorAction SilentlyContinue).StartTime.ToUniversalTime().ToString('o')`]);return null===r?null:`win32-creation-date:${r}`}(e):null},process.pid);function Ne(e){let t=e.indexOf(":");return t<=0?null:e.slice(0,t)}function $e(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 Me(e){return"string"==typeof e&&e.length>0}function Ae(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:Me(r.process_start)&&Me(r.hostname)&&Me(r.nonce)?!Me(r.created_at)||Number.isNaN(Date.parse(r.created_at))?null:Me(r.purpose)&&(void 0===r.run_id||Me(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},...!0===r.git_activity_lock?{git_activity_lock:!0}:{}}:null:null}async function Te(e,t={}){if(0===e.length)throw Error("lock purpose must not be empty");let r=await Ie(process.pid);return{pid:process.pid,process_start:r??"unavailable",hostname:be(),nonce:ge(),created_at:(new Date).toISOString(),purpose:e,...void 0===t.runId?{}:{run_id:t.runId},...await he()?{git_activity_lock:!0}:{}}}var Re=new Map;function Pe(e,t,r){Re.set(e,{previous:t,takeoverNonce:r})}function De(e,t){Re.get(e)?.takeoverNonce===t&&Re.delete(e)}function Ce(e){if(e instanceof Error&&"code"in e)return"string"==typeof e.code?e.code:void 0}async function Le(e){try{let t=await A(e,"utf8"),r=await L(e,{bigint:!0});return{raw:t,record:Ae(t),dev:r.dev,ino:r.ino,mtimeMs:Number(r.mtimeMs)}}catch(e){let t=Ce(e);if("ENOENT"===t)return null;if("\\"===ve.sep&&"EPERM"===t)return{raw:"",record:null,dev:0,ino:0,mtimeMs:Date.now()};throw e}}function je(e,t){return e.dev===t.dev&&e.ino===t.ino&&e.mtimeMs===t.mtimeMs&&e.raw===t.raw}async function Fe(e,t){De(e,t.nonce);let r=await Le(e);if(null===r||r.record?.nonce!==t.nonce)return!1;try{return await F(e),!0}catch(e){if("ENOENT"===Ce(e))return!1;throw e}}import{randomUUID as ze}from"node:crypto";import Ue from"node:path";import qe from"node:path";var Ze=new Set;function Be(e){Ze.add(e)}function Je(e){Ze.delete(e)}var Ge=/\.candidate-(?:(\d+)-)?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function He(e){let t=Ge.exec(e)?.[1];return void 0!==t&&"dead"===$e(Number(t))}function We(e){if(e instanceof Error&&"code"in e)return"string"==typeof e.code?e.code:void 0}function Ke(e,t){try{e.onFailure?.(t)}catch{}}function Ye(e){if("win32"!==process.platform)return!1;let t=We(e);return"EBUSY"===t||"EPERM"===t||"EACCES"===t}var Ve=new Map;function Xe(e){Ve.delete(e)}var Qe=new Set(["EACCES","EPERM","ENOTSUP"]);function et(e){if(e instanceof Error&&"code"in e)return"string"==typeof e.code?e.code:void 0}function tt(e,t){if(void 0!==t)return t(e);if("win32"!==process.platform)return!1;let r=et(e);return"EBUSY"===r||"EPERM"===r||"EACCES"===r}var rt={};function nt(){return rt}async function it(e){for(let t=0;t<3;t+=1)try{return await(rt.unlink??F)(e),Je(e),!0}catch(r){if("ENOENT"===et(r))return Je(e),!0;if(!tt(r,rt.isSharingError))throw Be(e),Xe(Ue.dirname(e)),r;if(t+1===3)return Be(e),Xe(Ue.dirname(e)),!1}return!1}async function ot(e,t){await $(Ue.dirname(e),{recursive:!0,mode:448});let{candidatePath:r,handle:n}=await async function(e){for(let t=0;;t+=1){let r=`${e}.candidate-${process.pid}-${ze()}`;try{return{candidatePath:r,handle:await te(r,"wx",384)}}catch(n){if(await it(r)||Xe(Ue.dirname(e)),"EINTR"!==et(n)||t>=128)throw n}}}(e);try{try{await m(n,`${JSON.stringify(t)}\n`,"utf8"),await n.sync()}finally{await n.close()}try{return await(rt.link??I)(r,e),!0}catch(r){if(function(e){let t=et(e);return"EEXIST"===t||"ENOENT"===t}(r))return!1;if(Qe.has(et(r)??""))return await async function(e,t){let r;try{r=await te(e,"wx",384)}catch(e){if("EEXIST"===et(e))return!1;throw e}try{return await(rt.writeFallback??m)(r,`${JSON.stringify(t)}\n`,"utf8"),await r.sync(),!0}finally{await r.close()}}(e,t);throw r}}finally{await it(r)||Xe(Ue.dirname(e))}}function at(e,t,r=Math.random){let n=Math.min(250,t*2**Math.min(e,30));return Math.max(1,Math.round(n/2+r()*n/2))}function st(e,t){return t?.throwIfAborted(),new Promise((r,n)=>{let i=setTimeout(a,e),o=()=>a(t?.reason??new DOMException("The operation was aborted","AbortError"));function a(e){clearTimeout(i),t?.removeEventListener("abort",o),void 0===e?r():n(e)}t?.addEventListener("abort",o,{once:!0})})}import{randomUUID as lt}from"node:crypto";import{hostname as ct}from"node:os";function ut(e){if(e instanceof Error&&"code"in e)return"string"==typeof e.code?e.code:void 0}async function dt(e){if(e.hostname!==ct())return!1;let t=$e(e.pid);if("dead"===t)return!0;if("unknown"===t)return!1;let r=await Ie(e.pid);return null!==r&&"unavailable"!==e.process_start&&function(e,t){let r=Ne(e);return null!==r&&r===Ne(t)&&e!==t}(e.process_start,r)}async function ft(e,t,r){return null!==e.record?dt(e.record):t-e.mtimeMs>r}async function pt(e,t,r){let n=await Le(e);if(null===n||!await ft(n,t,r))return!1;let i=`${e}.reaping-${lt()}`;try{await E(e,i)}catch(e){if("ENOENT"===ut(e))return!1;if(tt(e))return!1;throw e}let o=await Le(i);if(null!==o&&je(o,n))return await F(i),!0;try{await I(i,e)}catch(e){if("EEXIST"!==ut(e))throw e}return await F(i),!1}async function mt(e,t,r,n,i){if(!await ft(t,n,i))return!1;let o=`${e}.recovery`,a={...r,nonce:lt(),created_at:(new Date).toISOString(),purpose:`${r.purpose}:recovery`};for(let e=0;null!==await Le(o)||!await ot(o,a);e+=1)if(e>0||!await pt(o,n,i))return!1;try{let s=await Le(e);if(null===s)return!0;if(!je(s,t))return!1;if(!await ft(s,n,i))return!1;let l=await Le(o);return null!==l&&l.record?.nonce===a.nonce&&(await F(e),null!==s.record&&Pe(e,s.record,r.nonce),!0)}finally{await Fe(o,a)}}class wt extends Error{lockPath;owner;retriable=!0;constructor(e,t){super(`Lock is held: ${e}`),this.lockPath=e,this.owner=t,this.name="LockContentionError"}}async function ht(e,t,r={}){let n=r.waitTimeoutMs??0,i=r.retryDelayMs??25,o=r.incompleteLockGraceMs??5e3,a=r.now??Date.now;if(n<0||i<=0||o<0)throw Error("lock wait options must be positive");let s=a()+n;try{return await async function(e,t,r,n){let{retryDelayMs:i,incompleteLockGraceMs:o,now:a,deadline:s}=n,l=!1,c=!1;for(let n=0;;n+=1){r.signal?.throwIfAborted();let u=await Le(e);if(null===u){if(await ot(e,t))return{sawOtherOwner:l};if(r.signal?.throwIfAborted(),l=!0,c=yt(e,t,c),u=await Le(e),null===u)continue}if(l=!0,c=yt(e,t,c),await mt(e,u,t,a(),o))c=!0;else{if(r.signal?.throwIfAborted(),a()>=s)throw new wt(e,u.record);await st(Math.min(at(n,i),Math.max(1,s-a())),r.signal)}}}(e,t,r,{retryDelayMs:i,incompleteLockGraceMs:o,now:a,deadline:s})}catch(r){throw De(e,t.nonce),r}}function yt(e,t,r){return r&&De(e,t.nonce),!1}import{hostname as vt}from"node:os";import gt from"node:path";function bt(e){if(e instanceof Error&&"code"in e)return"string"==typeof e.code?e.code:void 0}async function Et(e,t){let r=!0===(await N(e).catch(()=>{}))?.isFile()?await async function(e){let t;try{t=await A(e,"utf8")}catch(e){if("ENOENT"===bt(e))return null;throw e}let r=Ae(t);return null===r||r.hostname!==vt()||"dead"!==$e(r.pid)?null:await dt(r)?r:null}(e):null;if(null===r)return!1;let n=await Te("dead-owner-sweep");try{await ht(e,n,{waitTimeoutMs:0})}catch(e){if(e instanceof wt)return!1;throw e}return await Fe(e,n),t.onReclaimed?.(e,r),!0}async function St(e,t,r={}){let n,i=ye.dirname(e);if(function(e,t){let r=Ve.get(e);return!(void 0!==r&&t-r<6e5||(Ve.set(e,t),0))}(i,(r.now??Date.now)())){let t=nt();await async function(e,t=Date.now,r={}){let n;try{n=await T(e)}catch(t){if("ENOENT"===We(t)){for(let t of Ze)qe.dirname(t)===e&&Je(t);return 0}throw t}let i=0;for(let o of n){if(!Ge.test(o))continue;let n=qe.join(e,o);try{let e=await L(n);if(!(Ze.has(n)||He(o)||t()-e.mtimeMs>36e5))continue;let a=!1;for(let e=0;e<3;e+=1)try{await(r.unlink??F)(n),Je(n),a=!0;break}catch(t){if("ENOENT"===We(t)){Je(n),a=!0;break}if(!(r.isSharingError?.(t)??Ye(t)))throw t;e+1===3&&Ke(r,n)}a&&(i+=1)}catch(e){"ENOENT"===We(e)&&Je(n),Ke(r,n)}}return i}(i,Date.now,{...void 0===t.unlink?{}:{unlink:t.unlink},...void 0===t.isSharingError?{}:{isSharingError:t.isSharingError},onFailure:()=>Xe(i)}).catch(()=>{Xe(i)}),await async function(e,t={}){let r;try{r=await T(e)}catch(r){return"ENOENT"!==bt(r)&&t.onFailure?.(e,r),0}let n=0;for(let i of r){if(!i.endsWith(".lock"))continue;let r=gt.join(e,i);try{await Et(r,t)&&(n+=1)}catch(e){t.onFailure?.(r,e)}}return n}(i,{onReclaimed:(t,r)=>{ye.resolve(t)===ye.resolve(e)&&(n=r)}})}let o=await ht(e,t,r);void 0===n||o.sawOtherOwner||void 0!==function(e,t){let r=Re.get(e);return r?.takeoverNonce===t?r.previous:void 0}(e,t.nonce)||Pe(e,n,t.nonce)}async function _t(e,t,r,n){await St(e,t,n);try{return await r()}finally{await Fe(e,t)}}import{randomUUID as xt}from"node:crypto";import kt from"node:path";var Ot=".ticket";function It(e,t,r){if(!Number.isInteger(r)||r<1)throw Error(`${t} slot must be a positive integer, got ${r}`);return kt.join(e,`${t}.slot-${r}.lock`)}var Nt=0,$t=Promise.resolve(),Mt={};async function At(e,t,r){await $(e,{recursive:!0,mode:448});let n=kt.join(e,`${t}.staging`);await re(n,`${JSON.stringify(r)}\n`,{mode:384}),await E(n,kt.join(e,t))}async function Tt(e){try{return(await T(e)).filter(e=>e.endsWith(Ot)).sort()}catch(e){if("ENOENT"===Lt(e))return[];throw e}}async function Rt(e){try{await F(e)}catch(e){if("ENOENT"!==Lt(e))throw e}}async function Pt(e,t){let r,n=kt.join(e,t);try{r=await(Mt.readFile??A)(n,"utf8")}catch(e){if("ENOENT"===Lt(e))return!0;if(function(e){if(void 0!==Mt.isSharingError)return Mt.isSharingError(e);if("win32"!==process.platform)return!1;let t=Lt(e);return"EBUSY"===t||"EPERM"===t||"EACCES"===t}(e))return!1;throw e}let i=Ae(r);return!(null===i||!await dt(i))&&(await Rt(n),!0)}async function Dt(e,t,r,n){for(let i=1;i<=r;i+=1){let r=It(e,t.purpose,i);try{return await St(r,t,{waitTimeoutMs:0,...void 0===n?{}:{signal:n}}),{slot:i,release:()=>Fe(r,t),retainFor:async(e,n)=>{let i=`${r}.handoff-${t.nonce}`;try{await re(i,`${JSON.stringify({...t,pid:e,process_start:n??"unavailable",git_activity_lock:void 0})}\n`,{mode:384}),await E(i,r)}finally{await Rt(i)}},releaseSync:()=>{try{return Ae(G(r,"utf8"))?.nonce===t.nonce&&(Y(r),!0)}catch(e){if("ENOENT"===Lt(e))return!1;throw e}}}}catch(e){if(e instanceof wt)continue;throw e}}}async function Ct(e,t,r={}){let n=r.maxConcurrent??t.defaultSlots,i=r.waitTimeoutMs??t.waitTimeoutMs,o=r.retryDelayMs??25;if(!Number.isInteger(n)||n<1)throw Error(`${t.purpose} maxConcurrent must be a positive integer, got ${n}`);if(i<0||o<=0)throw Error("lock wait options must be positive");let{signal:a}=r,s=function(e,t){return kt.join(e,`${t}.tickets`)}(e,t.purpose),l=(Nt=(Nt+1)%1e6,`${String(Date.now()).padStart(16,"0")}-${String(Nt).padStart(6,"0")}-${String(process.pid).padStart(10,"0")}-${xt()}${Ot}`),c=Date.now(),u=c+i,d=$t.then(async()=>{a?.throwIfAborted(),await At(s,l,await Te(`${t.purpose}:ticket`))});$t=d.catch(()=>{});try{await d;let r=await Te(t.purpose);for(let i=0;;i+=1){a?.throwIfAborted();let d=await Tt(s),f=d[0];if(void 0!==f&&d.includes(l)){if(f===l){let t=await Dt(e,r,n,a);if(void 0!==t){try{await Rt(kt.join(s,l))}catch(e){throw await t.release(),e}return t}}else if(await Pt(s,f))continue}else await At(s,l,await Te(`${t.purpose}:ticket`));let p=Date.now();if(p>=u)throw t.busyError(p-c,n);await st(Math.min(at(i,o),Math.max(1,u-p)),a)}}finally{await Rt(kt.join(s,l)).catch(()=>{})}}function Lt(e){if(e instanceof Error&&"code"in e)return"string"==typeof e.code?e.code:void 0}(function(){let e=process.env.OMO_TEST_GIT_LOCK_SETTLE_MS;if(void 0===e)return;let t=Number.parseInt(e,10);Number.isInteger(t)})(),["process\\.env\\.[A-Za-z_][A-Za-z0-9_]*","os\\.environ(?:\\.get\\(|\\[)?","env\\.[A-Za-z_][A-Za-z0-9_]*","\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}","\\$[A-Za-z_][A-Za-z0-9_]*","\\{\\{[^}\\n]*\\}\\}","\\[(?:redacted|filtered)\\]","(?:redacted|filtered)","(?:bearer|changeme|placeholder|example|none|null|undefined|true|false)"].join("|"),String.raw`#!/bin/sh
 # Refuse a hand-written claim receipt header in memory notes. Installed by omo memory-core; regenerated on startup.
</file context>

if (currentMachineHash === null) {
return hostnameVerdict(facts.recordedHostname, facts.currentHostname)
}
return facts.recordedMachineHash === currentMachineHash

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Equal machine hashes return local even when the hostnames differ. Hosts that share a machine-id (cloned VM images, containers from one image) and a memory directory will now see each other's live locks as local, and the PID check runs against the wrong machine. Before this change the hostname mismatch made them stuck. Keep a hash match from overriding a hostname mismatch unless a same-host signal, such as the launcher process start identity, also matches.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/memory-core/src/reflection/host-identity.ts, line 84:

<comment>Equal machine hashes return `local` even when the hostnames differ. Hosts that share a machine-id (cloned VM images, containers from one image) and a memory directory will now see each other's live locks as local, and the PID check runs against the wrong machine. Before this change the hostname mismatch made them `stuck`. Keep a hash match from overriding a hostname mismatch unless a same-host signal, such as the launcher process start identity, also matches.</comment>

<file context>
@@ -0,0 +1,93 @@
+  if (currentMachineHash === null) {
+    return hostnameVerdict(facts.recordedHostname, facts.currentHostname)
+  }
+  return facts.recordedMachineHash === currentMachineHash
+    ? { state: "local" }
+    : { state: "foreign", recordedHostname: facts.recordedHostname, viaMachineId: true }
</file context>


// then
expect(results).toEqual([])
expect((await item.store.readState()).active?.runId).toBe("run-orphan")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This test name promises the reservation is 'never released or quarantined', but it only checks that active.runId survives. Capture await item.store.readState() before reconciling and assert toEqual against it afterwards, matching the untouched-reservation test below. That also catches unintended rewrites of the launcher fields.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/omo-senpi/src/components/memory/worker/run-reconciliation.test.ts, line 354:

<comment>This test name promises the reservation is 'never released or quarantined', but it only checks that `active.runId` survives. Capture `await item.store.readState()` before reconciling and assert `toEqual` against it afterwards, matching the untouched-reservation test below. That also catches unintended rewrites of the launcher fields.</comment>

<file context>
@@ -324,6 +333,48 @@ describe("reflection and dream run reconciliation", () => {
+
+    // then
+    expect(results).toEqual([])
+    expect((await item.store.readState()).active?.runId).toBe("run-orphan")
+  }, 30_000)
+
</file context>

{ name: "another host owns the lock", rule: "other-host", facts: deadHolder({ files: { [LOCK]: lock({ launcherHostname: "other-host" }) } }), expected: stuck(RUN, "it is owned by host other-host, which cannot be checked from here") },
{ name: "another machine holds the lock under the same hostname", rule: "machine-id", facts: deadHolder({ machineId: "machine-b", files: { [LOCK]: lock({ launcherMachineHash: "machine-a" }) } }), expected: stuck(RUN, "it is owned by a different machine (host this-host), which cannot be checked from here") },
{ name: "the same machine with a renamed hostname still owns its dead lock", rule: "machine-id-renamed-host", facts: deadHolder({ machineId: "machine-a", files: { [LOCK]: lock({ launcherMachineHash: "machine-a", launcherHostname: "other-host" }) } }), expected: { state: "dead", runId: RUN } },
{ name: "a null machine id lookup falls back to hostname equality", rule: "machine-id-null", facts: deadHolder({ machineId: null, files: { [LOCK]: lock({ launcherMachineHash: "machine-a" }) } }), expected: { state: "dead", runId: RUN } },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: These new rule labels don't match any // rule: tag in stale-holder.ts, and machine-id-hot-path is reused by two scenarios. The file's own convention says each label names a fail-closed rule, so these labels break that contract. Use the production rule names, or drop the rule field for the non-rule cases.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/memory-core/src/reflection/stale-holder.scenarios.test-support.ts, line 72:

<comment>These new `rule` labels don't match any `// rule:` tag in `stale-holder.ts`, and `machine-id-hot-path` is reused by two scenarios. The file's own convention says each label names a fail-closed rule, so these labels break that contract. Use the production rule names, or drop the `rule` field for the non-rule cases.</comment>

<file context>
@@ -53,6 +67,11 @@ export const SCENARIOS: readonly Scenario[] = [
   { name: "another host owns the lock", rule: "other-host", facts: deadHolder({ files: { [LOCK]: lock({ launcherHostname: "other-host" }) } }), expected: stuck(RUN, "it is owned by host other-host, which cannot be checked from here") },
+  { name: "another machine holds the lock under the same hostname", rule: "machine-id", facts: deadHolder({ machineId: "machine-b", files: { [LOCK]: lock({ launcherMachineHash: "machine-a" }) } }), expected: stuck(RUN, "it is owned by a different machine (host this-host), which cannot be checked from here") },
+  { name: "the same machine with a renamed hostname still owns its dead lock", rule: "machine-id-renamed-host", facts: deadHolder({ machineId: "machine-a", files: { [LOCK]: lock({ launcherMachineHash: "machine-a", launcherHostname: "other-host" }) } }), expected: { state: "dead", runId: RUN } },
+  { name: "a null machine id lookup falls back to hostname equality", rule: "machine-id-null", facts: deadHolder({ machineId: null, files: { [LOCK]: lock({ launcherMachineHash: "machine-a" }) } }), expected: { state: "dead", runId: RUN } },
+  { name: "no machine id is read when active.lock is absent", rule: "machine-id-hot-path", facts: deadHolder({ failMachineId: true, files: { [LOCK]: undefined } }), expected: { state: "none" } },
+  { name: "no machine id is read when the record carries no launcherMachineHash", rule: "machine-id-hot-path", facts: deadHolder({ failMachineId: true }), expected: { state: "dead", runId: RUN } },
</file context>

# Conflicts:
#	packages/omo-senpi/plugin/extensions/memory-run-supervisor.mjs
#	packages/omo-senpi/plugin/extensions/omo-memory-commands.js
#	packages/omo-senpi/plugin/extensions/omo-memory-doctor.js
#	packages/omo-senpi/plugin/extensions/omo-memory-memfs.js
#	packages/omo-senpi/plugin/extensions/omo-memory-palace.js
#	packages/omo-senpi/plugin/extensions/omo-memory-sync.js
#	packages/omo-senpi/plugin/extensions/omo-onboarding-scan.js
#	packages/omo-senpi/plugin/extensions/omo-side-panel.js
#	packages/omo-senpi/plugin/extensions/omo.js
# Conflicts:
#	packages/omo-senpi/plugin/extensions/memory-run-supervisor.mjs
#	packages/omo-senpi/plugin/extensions/omo-memory-commands.js
#	packages/omo-senpi/plugin/extensions/omo-memory-doctor.js
#	packages/omo-senpi/plugin/extensions/omo-memory-memfs.js
#	packages/omo-senpi/plugin/extensions/omo-memory-palace.js
#	packages/omo-senpi/plugin/extensions/omo-memory-sync.js
#	packages/omo-senpi/plugin/extensions/omo-onboarding-scan.js
#	packages/omo-senpi/plugin/extensions/omo-side-panel.js
#	packages/omo-senpi/plugin/extensions/omo.js
The machine-id reader and hashed host rule (code-yeongyu#11863) entered omo.js's eager
graph and pushed it 2,030 B past bundle-budget.json, failing bundle-size and
the OTLP small-main-entry byte check (one cause). They now load through
memory-core's #memory-host-identity, mapped to the recall-store sidecar in the
plugin manifest (the code-yeongyu#11994 / #memory-external-projection mechanism). Callers
pass the holder record as-is; the rule owns the type guard, the missing-hostname
case and the default reader. omo.js grows +478 B over dev instead (locally
1,460,004 B, budget 1,460,160). A build test pins the boundary.
# Conflicts:
#	packages/omo-senpi/plugin/extensions/memory-run-supervisor.mjs
#	packages/omo-senpi/plugin/extensions/omo-claim-receipt.js
#	packages/omo-senpi/plugin/extensions/omo-computer-use.js
#	packages/omo-senpi/plugin/extensions/omo-desktop-toolkit.js
#	packages/omo-senpi/plugin/extensions/omo-device-account.js
#	packages/omo-senpi/plugin/extensions/omo-feedback.js
#	packages/omo-senpi/plugin/extensions/omo-init-deep-advisor.js
#	packages/omo-senpi/plugin/extensions/omo-member.js
#	packages/omo-senpi/plugin/extensions/omo-memory-commands.js
#	packages/omo-senpi/plugin/extensions/omo-memory-doctor.js
#	packages/omo-senpi/plugin/extensions/omo-memory-memfs.js
#	packages/omo-senpi/plugin/extensions/omo-memory-palace.js
#	packages/omo-senpi/plugin/extensions/omo-memory-sync.js
#	packages/omo-senpi/plugin/extensions/omo-onboarding-scan.js
#	packages/omo-senpi/plugin/extensions/omo-recall-store.js
#	packages/omo-senpi/plugin/extensions/omo-session-history.js
#	packages/omo-senpi/plugin/extensions/omo-side-panel.js
#	packages/omo-senpi/plugin/extensions/omo-task.js
#	packages/omo-senpi/plugin/extensions/omo-telemetry-otlp.js
#	packages/omo-senpi/plugin/extensions/omo-telemetry-posthog.js
#	packages/omo-senpi/plugin/extensions/omo-telemetry-recovery.js
#	packages/omo-senpi/plugin/extensions/omo-thread-client.js
#	packages/omo-senpi/plugin/extensions/omo.js

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reflection lock host identity uses LocalHostName, which is not globally unique

2 participants