@junghanacs/entwurf 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +299 -0
- package/DELIVERY.md +4 -3
- package/README.md +99 -67
- package/VERIFY.md +13 -7
- package/docs/acp-backend-rail.md +1 -1
- package/docs/external-mcp-host.md +147 -9
- package/docs/setup-clean-host.md +221 -16
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +65 -20
- package/mcp/entwurf-bridge/dist/pi-extensions/entwurf-capabilities.json +1 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +17 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +172 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +152 -17
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +271 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +988 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-receive-omp.js +574 -0
- package/mcp/entwurf-bridge/dist/scripts/omp-receive-facts.js +84 -0
- package/mcp/entwurf-bridge/src/index.ts +65 -19
- package/mcp/entwurf-bridge/tsconfig.build.json +19 -3
- package/mcp/tsconfig.json +10 -0
- package/package.json +14 -4
- package/pi/entwurf-capabilities.json +1 -0
- package/pi/meta-bridge-omp/entwurf-meta-omp/package.json +7 -0
- package/pi/omp-receive/entwurf-receive-omp/package.json +7 -0
- package/pi-extensions/entwurf-control.ts +12 -9
- package/pi-extensions/lib/entwurf-self-address.ts +18 -7
- package/pi-extensions/lib/meta-sender-identity.ts +1 -1
- package/pi-extensions/lib/meta-session.ts +219 -5
- package/pi-extensions/lib/mux-fresh-call.ts +171 -17
- package/pi-extensions/lib/omp-fresh-preflight.ts +275 -0
- package/pi-extensions/meta-bridge-omp.ts +1244 -0
- package/pi-extensions/meta-bridge-receive-omp.ts +666 -0
- package/run.sh +1041 -174
- package/scripts/agy-bridge.sh +4 -2
- package/scripts/check-copilot-birth-hook.ts +700 -23
- package/scripts/check-entwurf-self-address.ts +31 -0
- package/scripts/check-fresh-cut-gate.sh +12 -4
- package/scripts/check-gate-qualification.ts +8 -2
- package/scripts/check-harness-admission-parity.ts +143 -0
- package/scripts/check-meta-doctor-oracle.sh +20 -0
- package/scripts/check-omp-birth-hook.ts +1049 -0
- package/scripts/check-omp-fresh-preflight.ts +181 -0
- package/scripts/check-omp-receive-arm.ts +516 -0
- package/scripts/check-setup-qualification.sh +126 -0
- package/scripts/copilot-bridge-doctor.sh +104 -71
- package/scripts/copilot-bridge-install.sh +180 -20
- package/scripts/copilot-bridge-oracle.sh +223 -0
- package/scripts/copilot-bridge-uninstall.sh +137 -0
- package/scripts/dev-bin.sh +50 -9
- package/scripts/fake-copilot-vendor.sh +67 -0
- package/scripts/inventory-verification-surface.ts +1 -1
- package/scripts/meta-bridge-hook-log.sh +9 -1
- package/scripts/mutants/copilot-birth.json +96 -5
- package/scripts/mutants/mux-fresh-call.json +22 -22
- package/scripts/mutants/omp-birth.json +173 -0
- package/scripts/mutants/omp-fresh.json +300 -0
- package/scripts/mutants/omp-receive.json +135 -0
- package/scripts/mutants/pi-package-ownership.json +92 -0
- package/scripts/mutants/self-address.json +34 -0
- package/scripts/mutants/setup-verdict.json +148 -0
- package/scripts/mutants/source-install.json +32 -0
- package/scripts/omp-bridge-doctor.sh +315 -0
- package/scripts/omp-bridge-install.sh +221 -0
- package/scripts/omp-bridge-oracle.sh +154 -0
- package/scripts/omp-bridge-uninstall.sh +57 -0
- package/scripts/omp-mcp-bridge.sh +320 -0
- package/scripts/omp-mcp-config.py +392 -0
- package/scripts/omp-receive-doctor.sh +246 -0
- package/scripts/omp-receive-facts.ts +106 -0
- package/scripts/omp-receive-install.sh +228 -0
- package/scripts/omp-receive-uninstall.sh +60 -0
- package/scripts/omp-tool-surface.py +349 -0
- package/scripts/pi_settings_io.py +22 -0
- package/scripts/raw-omp-measure/README.md +420 -0
- package/scripts/raw-omp-measure/probe-extension.ts +76 -0
- package/scripts/raw-omp-measure/probe-receive-surface.ts +250 -0
- package/scripts/raw-omp-measure/source-audit.md +414 -0
- package/scripts/register-pi-package.py +525 -7
- package/scripts/register-pi-provider.py +162 -12
- package/scripts/smoke-agy-install-state.sh +86 -30
- package/scripts/smoke-agy-statusline-state.sh +9 -6
- package/scripts/smoke-omp-bridge-state.sh +221 -0
- package/scripts/smoke-omp-fresh-live.ts +497 -0
- package/scripts/smoke-omp-mcp-state.sh +327 -0
- package/scripts/smoke-omp-receive-live.ts +340 -0
- package/scripts/smoke-omp-receive-state.sh +196 -0
- package/scripts/smoke-setup-verdict.sh +291 -0
- package/scripts/smoke-user-scope-citizen.sh +388 -1
- package/scripts/tsconfig.json +2 -0
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
# raw-omp-measure — OMP v18.0.0 vendor measurement (issue #87)
|
|
2
|
+
|
|
3
|
+
Lane: `docs/adding-a-harness.md` step 1 (five measurements) + §3.5 LIVE discriminator +
|
|
4
|
+
one added axis, **pi-rail overlap** (GLG, 2026-08-27). The oracle is the vendor artifact
|
|
5
|
+
or the vendor process, never our assembler.
|
|
6
|
+
|
|
7
|
+
This file was written as a MEASUREMENT-ONLY ledger and stayed one through the audit,
|
|
8
|
+
review, cross-review and first LIVE run. Bundle A of the implementation lane
|
|
9
|
+
(2026-08-27) then closed the slots that could only be closed by writing something —
|
|
10
|
+
the native MCP entry and the installed birth unit. Those receipts are tagged
|
|
11
|
+
**[LIVE …, implementation lane]** so the two phases stay legible: everything without
|
|
12
|
+
that tag was taken before any omp config write or record mint.
|
|
13
|
+
|
|
14
|
+
Evidence-state vocabulary used below: **[source]** = read at `file:line` in the vendor
|
|
15
|
+
checkout; **[host]** = measured on this host (oracle) with the receipt named;
|
|
16
|
+
**[LIVE — pending]** = slot the implementer fills from a real omp process; do not close
|
|
17
|
+
a slot from recollection.
|
|
18
|
+
|
|
19
|
+
Source-layer claims were independently re-verified by a second model:
|
|
20
|
+
[`source-audit.md`](./source-audit.md) (xai/grok-4.6 fresh sibling, 2026-08-27) —
|
|
21
|
+
**20 CONFIRMED / 6 CORRECTED / 0 UNRESOLVED**. Corrections are folded below and marked
|
|
22
|
+
`audited`; the audit file holds the deep receipts.
|
|
23
|
+
|
|
24
|
+
## Vendor identity
|
|
25
|
+
|
|
26
|
+
- Installed: `omp/18.0.0`, ELF aarch64 at `~/.local/bin/omp` **[host]** (`omp --help` header,
|
|
27
|
+
`file` output; oracle, 2026-08-27).
|
|
28
|
+
- Source checkout used for `[source]` receipts: `~/repos/3rd/oh-my-pi` at tag `v18.0.0`
|
|
29
|
+
(`02696f5b`) + 2 natives lint commits (`50368368…`, `4142f881` HEAD) — no runtime diff
|
|
30
|
+
vs the tag claimed; treat `file:line` as v18.0.0 coordinates and remeasure at any
|
|
31
|
+
vendor upgrade. All `packages/…` paths below are relative to that checkout.
|
|
32
|
+
- State root on this host: `~/.omp/` (`agent/config.yml`, `agent/sessions/<cwd-key>/*.jsonl`,
|
|
33
|
+
`logs/omp.<date>.<pid>.log`, `natives/18.0.0`, `run/daemons`) **[host]**.
|
|
34
|
+
|
|
35
|
+
## M1 — Hook vocabulary and firing time
|
|
36
|
+
|
|
37
|
+
- **[source]** omp "hooks" are an **in-process extension event bus**, not spawned hook
|
|
38
|
+
commands. `--hook` is an alias of `--extension`; JS/TS hook factories load as extension
|
|
39
|
+
modules and bind via `pi.on(...)` (`docs/hooks.md` "Current status in runtime").
|
|
40
|
+
- **[source]** Event vocabulary (`docs/hooks.md` "Event surfaces",
|
|
41
|
+
`packages/coding-agent/src/extensibility/shared-events.ts`): session_start,
|
|
42
|
+
session_before_switch/switch, session_before_branch/branch, session_before_compact,
|
|
43
|
+
session.compacting, session_compact, session_before_tree/tree, session_shutdown;
|
|
44
|
+
context, before_agent_start, agent_start, agent_end, turn_start, turn_end,
|
|
45
|
+
auto_compaction_start/end, auto_retry_start/end, ttsr_triggered, todo_reminder;
|
|
46
|
+
tool_call (pre) / tool_result (post); mcp_notification; user_bash.
|
|
47
|
+
- **[source]** `session_start` emit sites — the NAME does not scope it to the visible host:
|
|
48
|
+
- TUI host: `packages/coding-agent/src/modes/controllers/extension-ui-controller.ts:311`
|
|
49
|
+
- print/rpc runtime init: `packages/coding-agent/src/modes/runtime-init.ts:147`
|
|
50
|
+
- ACP server mode: `packages/coding-agent/src/modes/acp/acp-agent.ts:2603`
|
|
51
|
+
- **every task subagent**: `packages/coding-agent/src/task/executor.ts:3305`
|
|
52
|
+
- **[source, audited C1/C2]** Firing time: the TUI host fires after first paint, before
|
|
53
|
+
the first prompt, once per process (`interactive-mode.ts:1221`, `:1238`,
|
|
54
|
+
`extension-ui-controller.ts:309-311`). `/new`, fork, and in-TUI resume re-fire as
|
|
55
|
+
`session_before_switch`/`session_switch` (reasons `"new"`/`"fork"`/`"resume"`,
|
|
56
|
+
`agent-session.ts:6910-8074`), **not** as `session_start` — a birth hook must treat
|
|
57
|
+
`session_switch` as a re-birth edge. The `:531` initialize site is test-only.
|
|
58
|
+
- **[LIVE 2026-08-27, observation]** on `/exit`, a `session_shutdown` with a `"print"`
|
|
59
|
+
context fired before the tui one (same run) — shutdown events arrive from non-tui
|
|
60
|
+
contexts too. No fence impact (shutdown is not a birth edge), but a future unit must
|
|
61
|
+
not treat `session_shutdown` as host-scoped.
|
|
62
|
+
- **[LIVE 2026-08-27]** `/tmp/omp-probe-live.jsonl`. Host `session_start` at TUI open with **no model turn** (`2026-08-27T10:53:21.125Z`); `agent_start`/`turn_start` only after the typed prompt (`10:55:41.414Z` / `10:55:41.466Z`):
|
|
63
|
+
`{"event":"session_start","pid":479624,"mode":"tui","hasUI":true,"cwd":"/tmp","sessionId":"01a042da-537a-7770-a275-7b8162eecca4","sessionFile":"/home/junghan/.omp/agent/sessions/-tmp/2026-08-27T10-53-19-610Z_01a042da-537a-7770-a275-7b8162eecca4.jsonl"}`
|
|
64
|
+
|
|
65
|
+
## M2 — Launch form and envelope
|
|
66
|
+
|
|
67
|
+
- **[source]** Declaration surfaces (`docs/extension-loading.md`): native roots
|
|
68
|
+
`<cwd>/.omp/extensions` and `~/.omp/agent/extensions`; `extensions:` arrays in
|
|
69
|
+
`~/.omp/agent/config.yml`, `<cwd>/.omp/config.yml`, legacy `settings.json`; CLI
|
|
70
|
+
`-e/--extension/--hook`; installed plugin manifests (`omp.extensions`/`pi.extensions`).
|
|
71
|
+
- **[source]** Envelope: **no stdin, no per-event child process.** A module
|
|
72
|
+
default-exports `function (pi: ExtensionAPI)`; handlers receive `(event, ctx)` where
|
|
73
|
+
`ctx: ExtensionContext` carries `mode`, `hasUI`, `cwd`, `sessionManager`
|
|
74
|
+
(`packages/coding-agent/src/extensibility/extensions/types.ts:456`).
|
|
75
|
+
- Consequence for a future birth unit: the payload runs **inside** the omp process
|
|
76
|
+
(`process.pid` == host pid) — unlike Claude Code's exec'd launcher. Step-6 join-key
|
|
77
|
+
design must start from that fact.
|
|
78
|
+
- **[source, audited A5/B1]** Subagent envelope: extension factories are re-executed per
|
|
79
|
+
subagent against a fresh per-session API (`sdk.ts:2000-2028`, `loader.ts:362-443`);
|
|
80
|
+
CLI `-e` paths flow into subagents via `session.extensionPaths` →
|
|
81
|
+
`structured-subagent.ts:442` unless `restrictToolNames`.
|
|
82
|
+
- **[LIVE 2026-08-27]** `/tmp/omp-probe-live.jsonl` `pid:479624` equals `ps` omp pid `479624` (ppid `479023` = tmux-server bash). Extension is in-process.
|
|
83
|
+
|
|
84
|
+
## M3 — Config writer
|
|
85
|
+
|
|
86
|
+
- **[source]** omp-native writer layer (`docs/mcp-config.md` "Source of truth in code"):
|
|
87
|
+
`packages/coding-agent/src/mcp/config-writer.ts`, targets project `.omp/mcp.json` /
|
|
88
|
+
user `~/.omp/agent/mcp.json` (profile-aware). General settings: `omp config` CLI →
|
|
89
|
+
`~/.omp/agent/config.yml`.
|
|
90
|
+
- **[host, pre-implementation]** `~/.omp/agent/config.yml` exists (modelRoles.default
|
|
91
|
+
`openai-codex/gpt-5.6-terra:high`, statusLine.separator ascii, plan.enabled). **No
|
|
92
|
+
omp-native mcp.json existed anywhere on this host** at measurement time — that absence
|
|
93
|
+
is what made the Claude import the effective source below. Bundle A's writer created
|
|
94
|
+
`~/.omp/agent/mcp.json`; `config.yml` remains untouched by entwurf.
|
|
95
|
+
- **[source]** Import layer (borrowed, not owned): translates Claude Code
|
|
96
|
+
(`~/.claude.json`, `~/.claude/mcp.json`, project `.claude/.mcp.json`/`.claude/mcp.json`),
|
|
97
|
+
Codex, Gemini, OpenCode, Cursor, Windsurf, VS Code (`docs/mcp-config.md` "Imported tool
|
|
98
|
+
configs"); cross-provider precedence puts OMP-native first, Claude Code third
|
|
99
|
+
(`docs/mcp-config.md` "Discovery and precedence").
|
|
100
|
+
- **[host]** The only effective entwurf-bridge source omp can see here is
|
|
101
|
+
`~/.claude.json` user `mcpServers.entwurf-bridge` =
|
|
102
|
+
`bash /home/junghan/repos/gh/entwurf/mcp/entwurf-bridge/start.sh` with
|
|
103
|
+
`ENTWURF_BRIDGE_EXTERNAL_AGENT_ID=external-mcp/claude-code`. **Borrowed-config
|
|
104
|
+
finding:** an omp session riding the import would hand the bridge Claude Code's
|
|
105
|
+
external id — wrong provenance for an omp citizen. This is evidence for the issue's
|
|
106
|
+
fence (imported config is borrowed), not a request to clone a second Claude writer.
|
|
107
|
+
- **[LIVE 2026-08-27]** TUI `/mcp list` pane (O1 key): `Claude Code (~/.claude.json):` / `entwurf-bridge ● connected`. Bridge child `479695` ppid `479624`; `/proc/479695/environ` has `ENTWURF_BRIDGE_EXTERNAL_AGENT_ID=external-mcp/claude-code`. Server key string is the plain `mcpServers` name **`entwurf-bridge`**.
|
|
108
|
+
- **Design stance (GLG, 2026-08-27):** treat OMP as an independent harness — model the
|
|
109
|
+
operator who never used Claude Code. The Claude import path is never a support
|
|
110
|
+
surface. The future MCP hand is an omp-native writer targeting `~/.omp/agent/mcp.json`
|
|
111
|
+
(vendor writer layer above), whose same-named entry **shadows** the import by vendor
|
|
112
|
+
precedence (OMP-native first, Claude Code third). Vendor-owned controls exist if ever
|
|
113
|
+
needed: user-level `disabledServers` denylist (highest precedence) and
|
|
114
|
+
`mcp.enableProjectConfig` (`docs/mcp-config.md:84-93`, `:426-441`, `:521`); the vendor
|
|
115
|
+
itself never mutates another tool's config (`:441`).
|
|
116
|
+
- **[LIVE 2026-08-27, implementation lane — SHADOWING CLOSED]** after `./run.sh install-omp-mcp`
|
|
117
|
+
wrote the native entry (pinned key `entwurf-bridge`, env `external-mcp/omp`), a fresh omp/18.0.0
|
|
118
|
+
TUI's own `/mcp list` pane reports the source as the NATIVE file and lists the server ONCE:
|
|
119
|
+
`Configured MCP Servers` / `User level (~/.omp/agent/mcp.json):` / `entwurf-bridge ● connected [stdio]`.
|
|
120
|
+
The earlier reading on this same host was `Claude Code (~/.claude.json): entwurf-bridge ● connected`
|
|
121
|
+
(above) — the attribution FLIPPED and the import does not appear beside it. The spawned bridge
|
|
122
|
+
child confirms it from the other side: `/proc/541648/environ` carries
|
|
123
|
+
`ENTWURF_BRIDGE_EXTERNAL_AGENT_ID=external-mcp/omp` (it carried `…/claude-code` before), with
|
|
124
|
+
ppid `541617` = the omp pid. Same-key first-wins, measured, with the vendor as the oracle.
|
|
125
|
+
- **[LIVE 2026-08-27, implementation lane — O2 CLOSED]** `disabledServers: ["entwurf-bridge"]`
|
|
126
|
+
added to `~/.omp/agent/mcp.json`, fresh TUI, `/mcp list`:
|
|
127
|
+
`User level (~/.omp/agent/mcp.json): entwurf-bridge ○ not connected [stdio]` and **no Claude Code
|
|
128
|
+
section at all** — and no MCP child was spawned (`pgrep -P <omp pid>` empty). Denylisting the
|
|
129
|
+
shared name suppresses the native entry AND starves the import, exactly as cross-review (3d)
|
|
130
|
+
predicted from source: it is never the hide-the-import tool. The config was restored afterwards;
|
|
131
|
+
the shipped writer REFUSES to install under such a denylist and `doctor-omp-mcp` is red while one
|
|
132
|
+
exists.
|
|
133
|
+
- **[LIVE 2026-08-27, implementation lane — inverse]** `./run.sh uninstall-omp-mcp` removed the file
|
|
134
|
+
it had created (`created-new` → absent, not an empty `$schema` stub) and `doctor-omp-mcp` then
|
|
135
|
+
reports `the CLAUDE IMPORT is currently the only source` — the honest pre-#87 state, named rather
|
|
136
|
+
than silent. Reinstalling returns the verdict to `native-wins`.
|
|
137
|
+
- **[source, cross-review (3)]** Shadowing contract (external review Defect-1, accepted
|
|
138
|
+
amended): the future native writer must pin the **same server key** as the imported
|
|
139
|
+
entry — the literal `mcpServers` map key `entwurf-bridge` — gate-pinned. Same-key
|
|
140
|
+
first-wins fully suppresses the import even when env differs
|
|
141
|
+
(`capability/index.ts:203-207`; `equivalent` is never consulted on a key hit), which
|
|
142
|
+
is exactly the label swap this stance wants. Never use
|
|
143
|
+
`disabledServers: ["entwurf-bridge"]` to hide the import while a native entry exists —
|
|
144
|
+
suppression is by name and kills **both** (`mcp/config.ts:123-127`,
|
|
145
|
+
`capability/index.ts:191-196`).
|
|
146
|
+
- **[source, audited I1/F1/F4]** Writer shape and precedence in code: user file
|
|
147
|
+
`~/.omp/agent/mcp.json` = `{ $schema, mcpServers: { <name>: { command, args?, env? } } }`
|
|
148
|
+
(`mcp/config-writer.ts:111-143`, `config/mcp-schema.json`); discovery priority
|
|
149
|
+
native=100 > omp-plugins=90 > **claude=80** > … > mcp-json=5 with first-wins dedupe
|
|
150
|
+
(`capability/index.ts:84-91`, `:183`), project-before-user inside each provider; the
|
|
151
|
+
vendor never mutates another tool's config (`config-writer.ts:301-304`) and toggles
|
|
152
|
+
imported servers only via user `disabledServers`/`enabledServers` (`mcp/config.ts:104-127`).
|
|
153
|
+
|
|
154
|
+
## M4 — Statusline / receive surfaces
|
|
155
|
+
|
|
156
|
+
- **[source — CORRECTED, audited H2/H3]** Visible identity: `setStatus(...)`
|
|
157
|
+
(`types.ts:284`) is the **only** rendering surface — the TUI maps it to hook-status
|
|
158
|
+
lines under the status line (`extension-ui-controller.ts:110`,
|
|
159
|
+
`status-line/component.ts:678-682`, `:2246-2251`), gated by
|
|
160
|
+
`statusLine.showHookStatus` default **true** (`settings-schema.ts:952-956`).
|
|
161
|
+
`setFooter`/`setHeader` are **TUI no-ops** at v18 (`extension-ui-controller.ts:139`).
|
|
162
|
+
The built-in statusLine's segments are a closed enum with **no custom-text/command
|
|
163
|
+
segment** (`status-line/segments.ts:731-757`) — no Copilot-style `statusLine.command`
|
|
164
|
+
route exists. Step-4 visible identity must ride `setStatus`.
|
|
165
|
+
- **[LIVE 2026-08-27, implementation lane]** `ctx.ui.setStatus("entwurf", "🪛 <gid> omp")` renders:
|
|
166
|
+
the TUI's bottom line read `🪛 20260827T211548-68ca9d omp` while idle, and switched to
|
|
167
|
+
`🪛 20260827T211716-487002 omp` after `/new`. Default `statusLine.showHookStatus` was in
|
|
168
|
+
force (untouched operator config). Note the call site is `ctx.ui.setStatus` on the EVENT context
|
|
169
|
+
(`docs/hooks.md` "Status line behavior"), not a method on the `pi` API object.
|
|
170
|
+
- **[source]** Wake surfaces: `pi.sendUserMessage(content, {deliverAs})` — **"idle
|
|
171
|
+
starts a turn"** (`types.ts:1418`); `pi.sendMessage(..., {triggerTurn, deliverAs})`
|
|
172
|
+
(`types.ts:1412`); contained `ctx.setInterval`/`setTimeout` for background watches;
|
|
173
|
+
`mcp_notification` events can be bridged into a turn (`types.ts` McpNotificationEvent).
|
|
174
|
+
Feature gate: extensions are on by default; `--no-extensions` disables discovery.
|
|
175
|
+
- Other surfaces that exist but are NOT measured here: `--mode rpc`, `omp acp`,
|
|
176
|
+
`omp ps` daemons, collab `join`. Naming them is step-1 inventory, not a claim.
|
|
177
|
+
- **[LIVE — pending]** none required this branch — idle-wake demonstration is step 7
|
|
178
|
+
work and needs its own lane; this branch only inventories the surfaces.
|
|
179
|
+
|
|
180
|
+
## M5 — Parent process topology
|
|
181
|
+
|
|
182
|
+
- **[source]** Extensions are in-process → the "hook process" IS the omp host process.
|
|
183
|
+
- **[source]** MCP stdio servers are spawned by the omp process; on Linux
|
|
184
|
+
`detached: true` → setsid (own session, no controlling terminal) but still a direct
|
|
185
|
+
child (`packages/coding-agent/src/mcp/transports/stdio.ts:41-57`, `:334`).
|
|
186
|
+
- Expected join: `extension process.pid == omp pid == mcp child ppid` — a one-process
|
|
187
|
+
join, structurally simpler than Claude's hook-launcher/pid-marker join.
|
|
188
|
+
- **[source, audited G1/G2]** Child env is `{ ...Bun.env, ...this.config.env }`
|
|
189
|
+
(`transports/stdio.ts:577-581`) — full parent env inherited, entry env merged on top,
|
|
190
|
+
**no omp-injected session/agent id**; setsid does not reparent (ppid stays omp pid);
|
|
191
|
+
teardown signals the process group then SIGKILLs after grace (`stdio.ts:474-494`).
|
|
192
|
+
- **[LIVE 2026-08-27]** same run (`ps -o pid,ppid,sid,args`):
|
|
193
|
+
`479023 185520 479023 -bash` (tmux server child)
|
|
194
|
+
`479624 479023 479023 omp --cwd /tmp -e …/probe-extension.ts`
|
|
195
|
+
`479695 479624 479695 node …/mcp/entwurf-bridge/src/index.ts` (own SID = setsid; ppid = omp).
|
|
196
|
+
Join: probe pid == omp pid == bridge ppid.
|
|
197
|
+
- **[LIVE 2026-08-27, implementation lane — the step-6 join in production]** with the shipped unit:
|
|
198
|
+
`541617 omp` (ppid 185520 = tmux server) and `541648 node …/entwurf-bridge/src/index.ts` with
|
|
199
|
+
ppid `541617`, own SID. The sender marker the extension wrote is
|
|
200
|
+
`~/.pi/agent/meta-senders/omp/541617.json` →
|
|
201
|
+
`{"backend":"omp","gardenId":"20260827T211548-68ca9d","ownerPid":541617,"ownerStartKey":"linux:100960160"}`.
|
|
202
|
+
Marker owner pid == omp pid == bridge child's ppid: the one-process join, measured end to end.
|
|
203
|
+
|
|
204
|
+
## §3.5 — Host-vs-subagent discriminator
|
|
205
|
+
|
|
206
|
+
- **[source]** `ExtensionMode = "tui" | "rpc" | "json" | "print"` and `ctx.hasUI`
|
|
207
|
+
(`extensibility/extensions/types.ts:453`, `:465`).
|
|
208
|
+
- **[source]** Runner default `#mode = "print"` (`extensibility/extensions/runner.ts:438`,
|
|
209
|
+
`:651`); only the interactive TUI controller passes `"tui"`
|
|
210
|
+
(`modes/controllers/extension-ui-controller.ts:302`, `:531`); rpc passes `"rpc"`
|
|
211
|
+
(`modes/rpc/rpc-mode.ts:961`).
|
|
212
|
+
- **[source]** Task subagents: `hasUI: false` (`task/executor.ts:3115`); their runner is
|
|
213
|
+
initialized **without** a mode argument (`executor.ts:3252`) → `"print"`; each emits its
|
|
214
|
+
own `session_start` (`executor.ts:3305`). Subagents inherit extensions AND parent MCP
|
|
215
|
+
via proxy tools unless `restrictToolNames` (`executor.ts:3021-3024`, `:3096`) — so a
|
|
216
|
+
birth extension WILL fire inside subagents; the discriminator is the only fence.
|
|
217
|
+
- **[source — CORRECTED, audited A1/A2/A4/B2]** Full host-mode matrix: plain TUI →
|
|
218
|
+
`"tui"`; `-p`/`--mode text|json` → `"print"`/`"json"`; `--mode rpc|rpc-ui` and
|
|
219
|
+
`omp acp` → `"rpc"`. **`ExtensionContext.hasUI` is NOT a fence**: rpc, rpc-ui, and ACP
|
|
220
|
+
all pass a real uiContext so `hasUI === true` (`runner.ts:879-881`); the `types.ts:465`
|
|
221
|
+
comment ("false in print/RPC mode") disagrees with code — a live instance of the §1(c)
|
|
222
|
+
docs-vs-code trap. ACP loads discovered extensions and emits `session_start`
|
|
223
|
+
(`main.ts:434-449`, `acp-agent.ts:2603`) but a `mode === "tui"` guard stays silent.
|
|
224
|
+
Bundled task agents do **not** set `restrictToolNames` (it is a spawn option:
|
|
225
|
+
plan-mode/security/compress, `structured-subagent.ts:386`) — default subagents DO load
|
|
226
|
+
extensions, so the discriminator is load-bearing, and it is `mode` **alone**.
|
|
227
|
+
- **[LIVE 2026-08-27, decisive]** `/tmp/omp-probe-live.jsonl`. Discriminator **held**, same pid `479624`, different session ids/files:
|
|
228
|
+
host `10:53:21.125Z` `{"event":"session_start","mode":"tui","hasUI":true,"sessionId":"01a042da-537a-7770-a275-7b8162eecca4","sessionFile":"…/-tmp/2026-08-27T10-53-19-610Z_01a042da-537a-7770-a275-7b8162eecca4.jsonl"}`
|
|
229
|
+
subagent `10:55:47.906Z` `{"event":"session_start","mode":"print","hasUI":false,"sessionId":"01a042dc-9540-7643-b3ef-99d223b459a9","sessionFile":"…/-tmp/2026-08-27T10-53-19-610Z_01a042da-537a-7770-a275-7b8162eecca4/Greeting.jsonl"}`
|
|
230
|
+
Record store `~/.pi/agent/meta-sessions` regular files: **519 before, 519 after**.
|
|
231
|
+
- **[LIVE 2026-08-27, implementation lane — PRODUCTION UNIT]** the same discriminator re-measured
|
|
232
|
+
with the SHIPPED birth extension installed (`~/.omp/agent/extensions/entwurf-meta-omp/index.ts`)
|
|
233
|
+
rather than the `-e` probe. One real TUI, one real task subagent (`GreetingResponder`, 8.5s),
|
|
234
|
+
record store **521 before, 521 after** that subagent.
|
|
235
|
+
`<pi-agent-dir>/meta-bridge-hook.log`:
|
|
236
|
+
`12:15:48.130Z INFO [omp] create record 20260827T211548-68ca9d.meta.json (edge=session_start, native=01a04325-d3ef-70c5-9392-7ac59a19c72e)`
|
|
237
|
+
then `12:16:37.100Z INFO [omp] scope-refused edge=session_start mode=print: not the visible tui host, no record minted`.
|
|
238
|
+
One host record, one refusal, zero subagent citizens — and no receiver marker or mailbox for
|
|
239
|
+
either.
|
|
240
|
+
- **[LIVE 2026-08-27, implementation lane — re-birth edge]** `/new` in that same TUI fired
|
|
241
|
+
`create record 20260827T211716-487002.meta.json (edge=session_switch(new), native=01a04327-2f41-722f-a24a-be5efb5de6cf)`
|
|
242
|
+
and re-pointed the sender marker under the same host pid. The switch edge is a real birth edge
|
|
243
|
+
(audit C2), not a theoretical one: a unit bound to `session_start` alone would have left this
|
|
244
|
+
session unminted with the previous citizen's id still on its status line.
|
|
245
|
+
- **[source, cross-review (4)]** ACP sessions skip on-disk MCP discovery
|
|
246
|
+
(`main.ts:446`) but client-supplied MCP still connects
|
|
247
|
+
(`acp-agent.ts:2608-2669`) — `enableMCP:false` is NOT a second fence; the tui guard
|
|
248
|
+
and the bridge's default send refusal remain the fences.
|
|
249
|
+
- Stop rule (issue #87): if the discriminator flips at any vendor upgrade, stop —
|
|
250
|
+
do not pile heuristics.
|
|
251
|
+
|
|
252
|
+
## Tool-name dialect (step 5 pre-measurement)
|
|
253
|
+
|
|
254
|
+
- **[source]** Mint: `mcp__${sanitizedServerName}_${normalizedToolName}` capped at 64
|
|
255
|
+
chars (`packages/coding-agent/src/mcp/tool-bridge.ts:396`); sanitizer lowercases,
|
|
256
|
+
replaces `[^a-z_]+` runs with `_`, collapses `_+`, trims edges (`tool-bridge.ts:351`);
|
|
257
|
+
redundant server-prefix stripping documented at `:344-349`.
|
|
258
|
+
- Computed for entwurf-bridge: `entwurf_v2` → `mcp__entwurf_bridge_entwurf_v` (digit
|
|
259
|
+
dropped), `entwurf_peers` → `mcp__entwurf_bridge_entwurf_peers`, etc. **The live omp
|
|
260
|
+
tool list is the acceptance oracle**, not this computation.
|
|
261
|
+
- **[source, audited E1]** All seven bridge tools computed in the audit (unique, ≤64
|
|
262
|
+
chars, no prefix-strip triggers). The approval layer consults the **same minted
|
|
263
|
+
string** — `tools.approval.<minted name>` (`tools/approval.ts:110`, `mcp__`
|
|
264
|
+
special-case `:261`); MCP tools default `approval: "write"` (`tool-bridge.ts:490`).
|
|
265
|
+
**No second Copilot-style permission dialect.**
|
|
266
|
+
- **[LIVE 2026-08-27]** `/tmp/omp-probe-live.jsonl` host `tool_dump_t25` (`10:53:46.128Z`, `mode:"tui"`, `toolCount:28`):
|
|
267
|
+
`["mcp__entwurf_bridge_entwurf_fresh_call","mcp__entwurf_bridge_entwurf_inbox_read","mcp__entwurf_bridge_entwurf_peers","mcp__entwurf_bridge_entwurf_register_native","mcp__entwurf_bridge_entwurf_resume_call","mcp__entwurf_bridge_entwurf_self","mcp__entwurf_bridge_entwurf_v"]`
|
|
268
|
+
Subagent dump (`mode:"print"`, `toolCount:26`) lists the **same seven names** (extensions inherited). Matches source computation; digit in `entwurf_v2` is eaten.
|
|
269
|
+
|
|
270
|
+
## M6 — pi-rail overlap (added axis, GLG 2026-08-27)
|
|
271
|
+
|
|
272
|
+
omp is a pi fork and keeps pi's env vocabulary. The entwurf pi rail and the omp rail
|
|
273
|
+
must never steer each other through shared knobs.
|
|
274
|
+
|
|
275
|
+
- **[source]** omp honors `PI_CONFIG_DIR` (default `.omp`) and `PI_CODING_AGENT_DIR`
|
|
276
|
+
(`packages/utils/src/dirs.ts:4-5`, `resolvePreProfileAgentDir` `:338`), plus
|
|
277
|
+
`PI_PROFILE`/`OMP_PROFILE` and `PI_SMOL_MODEL`-family role envs (`omp --help`).
|
|
278
|
+
Entwurf's offline verification sandboxes `PI_CODING_AGENT_DIR` (AGENTS.md Hard Rule
|
|
279
|
+
12) — the same knob now steers TWO harnesses. Any future omp gate/launcher must
|
|
280
|
+
decide explicitly which harness each `PI_*` var is addressing.
|
|
281
|
+
- **[source]** `PI_SESSION_ID`/`PI_AGENT_ID` appear **nowhere** in
|
|
282
|
+
`packages/coding-agent/src` — omp does not mint pi identity carriers itself (only
|
|
283
|
+
`MNEMOPI_SESSION_ID`, `packages/mnemopi/src/mcp-tools.ts:434`). The §6 danger is
|
|
284
|
+
pure **inheritance passthrough**: an omp launched from a pi citizen's shell hands the
|
|
285
|
+
parent's pi garden id to its MCP children (`adding-a-harness.md` §6 sanitization).
|
|
286
|
+
- **[source — refined, audited G4]** Legacy `.pi` survives **only** as a package.json
|
|
287
|
+
manifest alias (`pkg?.omp ?? pkg?.pi`, `discovery/helpers.ts:616-617`,
|
|
288
|
+
`plugins/loader.ts:267-273`); native discovery roots are `.omp`-only
|
|
289
|
+
(`discovery/builtin.ts:58-72`, `utils/src/dirs.ts:23`) — entwurf's pi extensions under
|
|
290
|
+
`~/.pi/` cannot be ambiently loaded by omp.
|
|
291
|
+
- **[source, audited G3]** Identity-adjacent inventory also includes
|
|
292
|
+
`PI_CODING_AGENT_SESSION_DIR` (`cli/args.ts:156`); the full PI_* read inventory
|
|
293
|
+
(40+ non-identity vars included) is in the audit G3 table.
|
|
294
|
+
- **[LIVE 2026-08-27]** clean tmux window (preflight `env | grep PI_SESSION_ID|PI_AGENT_ID` → `CLEAN_NO_PI_IDENTITY`). `/proc/479624/environ` and `/proc/479695/environ` grep `PI_SESSION_ID|PI_AGENT_ID|PI_CODING_AGENT_DIR`: **all absent**. Probe path is only the `-e` file (`…/scripts/raw-omp-measure/probe-extension.ts`); no `~/.pi/` extension path in the JSONL. (Bridge still carries borrowed `ENTWURF_BRIDGE_EXTERNAL_AGENT_ID=external-mcp/claude-code` — M3, not a PI_* carrier.)
|
|
295
|
+
|
|
296
|
+
## M7 — Receive surface (step 7), MEASURED 2026-08-30
|
|
297
|
+
|
|
298
|
+
Probe: `probe-receive-surface.ts`, loaded with `omp -e`. Host: oracle, `omp/18.0.0`
|
|
299
|
+
(the binary reports "New version 18.0.11 is available", so this host had NOT drifted).
|
|
300
|
+
Scratch logs were `/tmp/omp-receive-probe-run{1,2}.jsonl` and `/tmp/omp-order-probe.jsonl`;
|
|
301
|
+
they are gone with the host's `/tmp`, which is exactly why the decisive lines are pasted
|
|
302
|
+
BELOW rather than pointed at. A receipt parked in a host-local path is unreadable to the
|
|
303
|
+
next reviewer. **The vendor source
|
|
304
|
+
checkout named in `source-audit.md` is NOT present on this host**, so every row below is a
|
|
305
|
+
LIVE receipt from the running binary rather than a source read — which is why this section
|
|
306
|
+
exists at all.
|
|
307
|
+
|
|
308
|
+
- **[LIVE] `sendUserMessage` is on the FACTORY object, not the event ctx.**
|
|
309
|
+
`typeof pi.sendUserMessage === "function"`, `typeof pi.sendMessage === "function"`;
|
|
310
|
+
`typeof ctx.sendUserMessage === "undefined"`. This is the exact INVERSE of `setStatus`,
|
|
311
|
+
which lives on `ctx.ui` (M4). Two vendor surfaces, opposite placements, and neither is
|
|
312
|
+
guessable — the call site is a measurement, not a detail.
|
|
313
|
+
- **[LIVE] the idle wake is real.** `pi.sendUserMessage(text, {deliverAs:"user"})` called
|
|
314
|
+
at +12.005s on an idle `mode:"tui"` session with zero typing:
|
|
315
|
+
`agent_start` +31ms, `turn_start` +64ms, `turn_end` +2.45s, and the model answered the
|
|
316
|
+
probe token in the transcript. Returns `undefined` — not a promise, so there is nothing
|
|
317
|
+
to await and no rejection to swallow. This closes the `[LIVE — pending]` slot M4 left open.
|
|
318
|
+
- **[LIVE] `ctx.setInterval` fires while idle, and its canceller is `ctx.clearTimer`.**
|
|
319
|
+
58 ticks over 29s on a fully idle session with no model turn. **There is NO
|
|
320
|
+
`ctx.clearInterval`**: run 1 called it through `?.`, got a SILENT no-op, and the timer
|
|
321
|
+
ran 29s past its stop condition inside the TUI. Run 2 probed the canceller BY NAME and
|
|
322
|
+
`ctx.clearTimer(handle)` stopped it at exactly 3 ticks. `ctxKeys` also carries `isIdle`,
|
|
323
|
+
`hasPendingMessages`, `getAsyncJobSnapshot`, `abort`, `shutdown`, `invokeTool`.
|
|
324
|
+
- **[LIVE] two extensions in one process both work.** The installed birth unit and the
|
|
325
|
+
`-e` probe ran in the same pid: the birth unit minted a record, wrote its sender marker
|
|
326
|
+
and rendered `🪛 <gid> omp` on the status line while the probe logged its own events.
|
|
327
|
+
- **[LIVE] handler order follows DIRECTORY-NAME COLLATION, and the failure it creates is
|
|
328
|
+
real.** A temporary discovered unit `aa-order-probe` (sorting before `entwurf-meta-omp`)
|
|
329
|
+
ran its `session_start` handler at `04:46:57.310Z` with `markerPresentAtHandler:false`;
|
|
330
|
+
the birth unit wrote its record at `.330Z` and its marker at `.331Z` — **20ms later**.
|
|
331
|
+
`entwurf-receive-omp` sorts AFTER `entwurf-meta-omp`, so a receiver that assumed birth
|
|
332
|
+
had already run would arm on the first try **by accident**, and go green on every host
|
|
333
|
+
until a rename or a vendor scan-order change. That is why the shipped receiver defers on
|
|
334
|
+
a bounded `ctx.setInterval` instead. The probe unit was removed in the same session.
|
|
335
|
+
The three decisive lines, from `/tmp/omp-order-probe.jsonl` and the shared hook log:
|
|
336
|
+
|
|
337
|
+
```text
|
|
338
|
+
04:46:57.310Z {"edge":"session_start","unit":"aa-order-probe","pid":3135409,"mode":"tui","markerPresentAtHandler":false}
|
|
339
|
+
04:46:57.330Z INFO [omp] create record 20260830T134657-f4fbff.meta.json (edge=session_start, native=01a050fd-…)
|
|
340
|
+
04:46:57.331Z INFO [omp] sender marker 3135409 -> 20260830T134657-f4fbff
|
|
341
|
+
```
|
|
342
|
+
- **[LIVE] event-based retry cannot substitute for the timer.** Confirmed against M1: the
|
|
343
|
+
host `session_start` fires with no model turn, while `agent_start`/`turn_start` require
|
|
344
|
+
a typed prompt. A Copilot-style retry on turn edges would leave a freshly opened IDLE
|
|
345
|
+
session unaddressable until the operator typed — the one state bundle B exists to wake.
|
|
346
|
+
- **[LIVE] the D6 roundtrip, in the four lines that carry it.** Automated acceptance
|
|
347
|
+
(`smoke-omp-receive-live`, 11 assertions, exit 0), garden `20260830T142524-ab505f`:
|
|
348
|
+
|
|
349
|
+
```text
|
|
350
|
+
meta-mailbox state.json lastEnqueuedAt 2026-08-30T05:25:25.135Z / lastReadAt 05:25:28.859Z
|
|
351
|
+
receive log doorbell (signal) garden=20260830T142524-ab505f fresh=1 unread=1 → rang … unread=1
|
|
352
|
+
session transcript {"type":"toolCall","name":"mcp__entwurf_bridge_entwurf_inbox_read",
|
|
353
|
+
"arguments":{"gardenId":"20260830T142524-ab505f"}}
|
|
354
|
+
session transcript role:"toolResult" toolName:"mcp__entwurf_bridge_entwurf_inbox_read"
|
|
355
|
+
text: "[entwurf inbox read ⟵] garden: 20260830T142524-ab505f … receipt: lastReadAt=…"
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
The toolCall is the join that matters: `entwurf_inbox_read` takes a CALLER-SUPPLIED
|
|
359
|
+
garden id, so `lastReadAt` alone proves a read happened, not that THIS session performed
|
|
360
|
+
it. An earlier manual run had the same shape with a real authoritative sender — a
|
|
361
|
+
claude-code citizen to omp `20260830T135551-d9b5d4`, `entwurf_v2 meta-mailbox → enqueued`,
|
|
362
|
+
and the model answering in the same session.
|
|
363
|
+
- **[LIVE] D3 addressed isolation — the cell the Copilot row still lists as PENDING.** Two
|
|
364
|
+
omp citizens armed at once (`20260830T141425-597e5e` pid 3154765,
|
|
365
|
+
`20260830T141428-f8abe4` pid 3154835); one addressed enqueue:
|
|
366
|
+
|
|
367
|
+
```text
|
|
368
|
+
05:14:39.604Z INFO [omp-receive] pid=3154765 doorbell (signal) garden=20260830T141425-597e5e fresh=1 unread=1
|
|
369
|
+
05:14:39.606Z INFO [omp-receive] pid=3154765 rang garden=20260830T141425-597e5e unread=1
|
|
370
|
+
(no line for pid 3154835 anywhere in the log)
|
|
371
|
+
target state.json lastEnqueuedAt 05:14:39.599Z / lastReadAt 05:14:43.019Z
|
|
372
|
+
sibling state.json ABSENT — mailbox directory empty
|
|
373
|
+
sibling transcript FILE DOES NOT EXIST (omp persists lazily; it never took a turn)
|
|
374
|
+
```
|
|
375
|
+
- **[LIVE] the `/new` unarm, both directions.** Same pid 3140978:
|
|
376
|
+
|
|
377
|
+
```text
|
|
378
|
+
04:57:21.319Z INFO [omp-receive] unarmed garden=20260830T135551-d9b5d4 (edge=session_switch(new))
|
|
379
|
+
04:57:21.320Z INFO [omp-receive] armed garden=20260830T135721-af8973 … edge=session_switch(new)
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
and dispatch to the retired id then answered
|
|
383
|
+
`entwurf_v2 rejected: mailbox-undeliverable (observed liveness: unsupported)` — the unarm
|
|
384
|
+
is load-bearing, not tidiness.
|
|
385
|
+
- **[LIVE, and it changed an acceptance] the model refuses imperatives inside a mailbox
|
|
386
|
+
body — correctly.** An early draft of `smoke-omp-receive-live` asked the model to echo a
|
|
387
|
+
nonce; it drained the inbox, recorded the receipt, and answered *"Its acceptance
|
|
388
|
+
instruction was unverified, so no token was sent."* It was obeying OUR OWN doorbell,
|
|
389
|
+
which marks bodies untrusted. The acceptance now asserts the read (and the body arriving
|
|
390
|
+
in the drained result), never the model's compliance with an untrusted instruction.
|
|
391
|
+
|
|
392
|
+
## LIVE procedure (for the implementer sibling)
|
|
393
|
+
|
|
394
|
+
The probe is `scripts/raw-omp-measure/probe-extension.ts` (repo path). Adjust it freely
|
|
395
|
+
against the real API — the file is measurement scaffolding, not product.
|
|
396
|
+
|
|
397
|
+
1. Fresh tmux window, clean env (no `PI_SESSION_ID`/`PI_AGENT_ID` — verify with `env`).
|
|
398
|
+
`export OMP_PROBE_LOG=<scratch>/omp-probe.jsonl`.
|
|
399
|
+
2. `omp --cwd <scratch> -e /home/junghan/repos/gh/entwurf/scripts/raw-omp-measure/probe-extension.ts`
|
|
400
|
+
(default model is fine; turns below are tiny).
|
|
401
|
+
3. While idle at the TUI: capture `ps -eo pid,ppid,args | grep -E 'omp|entwurf'` and the
|
|
402
|
+
two `/proc/*/environ` greps (M5, M6). Wait ≥25s for the tool dumps (dialect).
|
|
403
|
+
4. One tiny turn: ask the model to spawn exactly one trivial task subagent (e.g. "use
|
|
404
|
+
the task tool to run a subagent that answers 'hi'; nothing else"). This produces the
|
|
405
|
+
§3.5 subagent `session_start` line.
|
|
406
|
+
5. Exit. Paste the decisive JSONL/ps/environ lines into the `[LIVE — pending]` slots
|
|
407
|
+
above (receipts must travel; host-local paths alone do not). Name the session file
|
|
408
|
+
under `~/.omp/agent/sessions/` as the vendor-side receipt.
|
|
409
|
+
6. Record store safety: count entwurf meta-records before and after; must be identical.
|
|
410
|
+
|
|
411
|
+
Do NOT: install anything, write any omp config, register a backend, mint a record,
|
|
412
|
+
send via `entwurf_v2`, or demonstrate idle-wake (step 7, separate lane).
|
|
413
|
+
|
|
414
|
+
**That fence belonged to the measurement branch and has been spent.** Bundle A of the
|
|
415
|
+
implementation lane (GLG grant, 2026-08-27) installed the birth unit and the native MCP
|
|
416
|
+
entry on this host under its own acceptance, and the receipts it took are the
|
|
417
|
+
`[LIVE …, implementation lane]` rows above. Idle-wake (step 7) is still out of bounds and
|
|
418
|
+
still has no unit. To re-take any of those rows, the procedure is now
|
|
419
|
+
`./run.sh install-omp-bridge && ./run.sh install-omp-mcp`, a real TUI, and the two
|
|
420
|
+
doctors — not this probe.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* raw-omp-measure probe — OMP v18.0.0 vendor measurement for issue #87.
|
|
3
|
+
*
|
|
4
|
+
* Loaded into a live omp process via `omp -e <this file>`. Logs one JSONL line
|
|
5
|
+
* per extension lifecycle event so the host-vs-subagent discriminator
|
|
6
|
+
* (`mode === "tui"` vs `"print"` + `hasUI:false`, adding-a-harness.md §3.5)
|
|
7
|
+
* and the in-process envelope (pid/ppid) get LIVE receipts from the vendor's
|
|
8
|
+
* own event bus. Measurement only — mints nothing, sends nothing.
|
|
9
|
+
*
|
|
10
|
+
* Log path: $OMP_PROBE_LOG (default /tmp/omp-probe.jsonl), append-only JSONL.
|
|
11
|
+
*/
|
|
12
|
+
import { appendFileSync } from "node:fs";
|
|
13
|
+
|
|
14
|
+
const LOG = process.env.OMP_PROBE_LOG ?? "/tmp/omp-probe.jsonl";
|
|
15
|
+
|
|
16
|
+
function log(obj: Record<string, unknown>): void {
|
|
17
|
+
try {
|
|
18
|
+
appendFileSync(LOG, `${JSON.stringify({ ts: new Date().toISOString(), ...obj })}\n`);
|
|
19
|
+
} catch {
|
|
20
|
+
// measurement probe: never take the host down
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function probe(pi: any): void {
|
|
25
|
+
const snap = (event: string, ctx: any, extra?: Record<string, unknown>) => {
|
|
26
|
+
// v18 ReadonlySessionManager (session-manager.ts:359-376): getSessionId / getSessionFile / getSessionDir.
|
|
27
|
+
// There is no getId().
|
|
28
|
+
let sessionFile: unknown;
|
|
29
|
+
let sessionId: unknown;
|
|
30
|
+
let sessionDir: unknown;
|
|
31
|
+
try {
|
|
32
|
+
sessionFile = ctx?.sessionManager?.getSessionFile?.();
|
|
33
|
+
} catch {}
|
|
34
|
+
try {
|
|
35
|
+
sessionId = ctx?.sessionManager?.getSessionId?.();
|
|
36
|
+
} catch {}
|
|
37
|
+
try {
|
|
38
|
+
sessionDir = ctx?.sessionManager?.getSessionDir?.();
|
|
39
|
+
} catch {}
|
|
40
|
+
log({
|
|
41
|
+
event,
|
|
42
|
+
pid: process.pid,
|
|
43
|
+
ppid: process.ppid,
|
|
44
|
+
mode: ctx?.mode,
|
|
45
|
+
hasUI: ctx?.hasUI,
|
|
46
|
+
cwd: ctx?.cwd,
|
|
47
|
+
sessionFile,
|
|
48
|
+
sessionId,
|
|
49
|
+
sessionDir,
|
|
50
|
+
...extra,
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const dumpEntwurfTools = (label: string, ctx: any) => {
|
|
55
|
+
try {
|
|
56
|
+
const all = (pi.getAllTools?.() ?? []) as Array<{ name?: string }>;
|
|
57
|
+
const entwurf = all.map((t) => t?.name).filter((n) => typeof n === "string" && n.includes("entwurf"));
|
|
58
|
+
log({ event: label, pid: process.pid, mode: ctx?.mode, toolCount: all.length, entwurfTools: entwurf });
|
|
59
|
+
} catch (err) {
|
|
60
|
+
log({ event: `${label}_error`, error: String(err) });
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
pi.on("session_start", (_e: any, ctx: any) => {
|
|
65
|
+
snap("session_start", ctx);
|
|
66
|
+
// MCP stdio children (imported config included) connect asynchronously;
|
|
67
|
+
// dump twice so a slow bridge start still lands in the receipt.
|
|
68
|
+
try {
|
|
69
|
+
ctx.setTimeout?.(() => dumpEntwurfTools("tool_dump_t10", ctx), 10_000);
|
|
70
|
+
ctx.setTimeout?.(() => dumpEntwurfTools("tool_dump_t25", ctx), 25_000);
|
|
71
|
+
} catch {}
|
|
72
|
+
});
|
|
73
|
+
pi.on("agent_start", (_e: any, ctx: any) => snap("agent_start", ctx));
|
|
74
|
+
pi.on("turn_start", (_e: any, ctx: any) => snap("turn_start", ctx));
|
|
75
|
+
pi.on("session_shutdown", (_e: any, ctx: any) => snap("session_shutdown", ctx));
|
|
76
|
+
}
|