@junghanacs/entwurf 0.22.0 → 0.23.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.
Files changed (98) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +380 -0
  3. package/DELIVERY.md +10 -2
  4. package/README.md +43 -4
  5. package/VERIFY.md +6 -0
  6. package/docs/setup-clean-host.md +11 -1
  7. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +39 -40
  8. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-declaration.js +609 -0
  9. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +95 -56
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +40 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +6 -2
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +7 -1
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peer-observe.js +67 -2
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +9 -1
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +24 -0
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +423 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +98 -0
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +1018 -0
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +213 -0
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +46 -273
  21. package/mcp/entwurf-bridge/dist/scripts/peer-facts.js +109 -0
  22. package/mcp/entwurf-bridge/src/index.ts +41 -41
  23. package/mcp/entwurf-bridge/tsconfig.build.json +1 -0
  24. package/package.json +4 -4
  25. package/pi-extensions/entwurf-control.ts +91 -52
  26. package/pi-extensions/lib/codex-declaration.js +612 -0
  27. package/pi-extensions/lib/codex-fresh-preflight.ts +107 -51
  28. package/pi-extensions/lib/entwurf-control-rpc.ts +46 -0
  29. package/pi-extensions/lib/entwurf-fact-provider.ts +13 -2
  30. package/pi-extensions/lib/entwurf-facts.ts +14 -1
  31. package/pi-extensions/lib/entwurf-peer-observe.ts +72 -2
  32. package/pi-extensions/lib/entwurf-peers-render.ts +9 -1
  33. package/pi-extensions/lib/entwurf-self-address.ts +33 -0
  34. package/pi-extensions/lib/fresh-call-composition.ts +493 -0
  35. package/pi-extensions/lib/fresh-call-dispatch.ts +142 -0
  36. package/pi-extensions/lib/herdr-fresh-call.ts +1378 -0
  37. package/pi-extensions/lib/herdr-placement.ts +276 -0
  38. package/pi-extensions/lib/mux-fresh-call.ts +95 -301
  39. package/run.sh +396 -8
  40. package/scripts/check-codex-birth-hook.ts +187 -0
  41. package/scripts/check-entwurf-control-rpc.ts +138 -0
  42. package/scripts/check-entwurf-fact-provider.ts +4 -2
  43. package/scripts/check-entwurf-facts.ts +12 -6
  44. package/scripts/check-entwurf-peers-surface.ts +3 -1
  45. package/scripts/check-entwurf-self-address.ts +68 -0
  46. package/scripts/check-fresh-call-dispatch.ts +301 -0
  47. package/scripts/check-gate-qualification.ts +16 -4
  48. package/scripts/check-herdr-activation.ts +1293 -0
  49. package/scripts/check-herdr-fresh-call.ts +1250 -0
  50. package/scripts/check-herdr-placement.ts +383 -0
  51. package/scripts/check-herdr-plugin-build.ts +842 -0
  52. package/scripts/check-herdr-plugin-profile.ts +371 -0
  53. package/scripts/check-herdr-plugin.ts +523 -0
  54. package/scripts/check-herdr-runtime-bootstrap.ts +1264 -0
  55. package/scripts/check-herdr-sandbox.ts +456 -0
  56. package/scripts/check-herdr-supply.ts +184 -0
  57. package/scripts/check-mux-launcher-fence.ts +138 -0
  58. package/scripts/check-peer-facts.ts +331 -0
  59. package/scripts/check-release-gate-outcomes.ts +12 -0
  60. package/scripts/check-typing-call-fence.ts +296 -0
  61. package/scripts/codex-birth-doctor.sh +159 -16
  62. package/scripts/codex-birth-install.sh +178 -72
  63. package/scripts/codex-birth-uninstall.sh +162 -7
  64. package/scripts/fixtures/herdr-supply.json +29 -0
  65. package/scripts/herdr-activation.mjs +536 -0
  66. package/scripts/herdr-plugin-activate.mjs +270 -0
  67. package/scripts/herdr-plugin-deactivate.mjs +193 -0
  68. package/scripts/herdr-runtime.mjs +1203 -0
  69. package/scripts/install-herdr-ci.sh +96 -0
  70. package/scripts/lib/codex-fresh-live-protocol.ts +2 -2
  71. package/scripts/lib/tree-digest.ts +97 -0
  72. package/scripts/meta-bridge-install.sh +19 -2
  73. package/scripts/meta-bridge-state.py +167 -11
  74. package/scripts/mutants/codex-caller-seat.json +6 -8
  75. package/scripts/mutants/codex-native.json +195 -29
  76. package/scripts/mutants/control-socket-disconnect.json +57 -0
  77. package/scripts/mutants/fresh-call-dispatch.json +154 -0
  78. package/scripts/mutants/herdr-activation.json +305 -0
  79. package/scripts/mutants/herdr-fresh-call.json +448 -0
  80. package/scripts/mutants/herdr-placement.json +161 -0
  81. package/scripts/mutants/herdr-plugin-build.json +160 -0
  82. package/scripts/mutants/herdr-plugin-profile.json +202 -0
  83. package/scripts/mutants/herdr-plugin.json +133 -0
  84. package/scripts/mutants/herdr-runtime-bootstrap.json +411 -0
  85. package/scripts/mutants/herdr-supply.json +133 -0
  86. package/scripts/mutants/meta-hook-session-switch.json +2 -2
  87. package/scripts/mutants/mux-fresh-call.json +104 -17
  88. package/scripts/mutants/mux-launcher-fence.json +13 -0
  89. package/scripts/mutants/omp-fresh.json +4 -4
  90. package/scripts/mutants/peer-facts.json +98 -0
  91. package/scripts/mutants/self-address.json +33 -0
  92. package/scripts/mutants/typing-call-fence.json +17 -0
  93. package/scripts/peer-facts.ts +120 -0
  94. package/scripts/register-pi-package.py +8 -0
  95. package/scripts/register-pi-provider.py +88 -12
  96. package/scripts/smoke-codex-birth.sh +281 -12
  97. package/scripts/smoke-herdr-fresh-call-live.ts +1027 -0
  98. package/scripts/smoke-herdr-plugin-build-live.ts +543 -0
@@ -64,7 +64,6 @@ import * as process from "node:process";
64
64
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
65
65
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
66
66
  import { z } from "zod";
67
- import { codexCallerFreshPreflight, codexFreshPreflight } from "../../../pi-extensions/lib/codex-fresh-preflight.js";
68
67
  import { controlSocketPathIn, defaultControlSocketDir } from "../../../pi-extensions/lib/control-socket-path.js";
69
68
  import { resolveMailboxReceiverFacts } from "../../../pi-extensions/lib/entwurf-deliverability.js";
70
69
  import { listEntwurfFacts } from "../../../pi-extensions/lib/entwurf-fact-provider.js";
@@ -74,9 +73,9 @@ import { nativePushSupported } from "../../../pi-extensions/lib/entwurf-v2-contr
74
73
  import { resolveMailboxWakeModeCapability } from "../../../pi-extensions/lib/entwurf-v2-decider.js";
75
74
  import { runAndRenderEntwurfV2FromSurface } from "../../../pi-extensions/lib/entwurf-v2-surface.js";
76
75
  import { makeVisibleResumeDeps, renderVisibleResume, visibleResume, } from "../../../pi-extensions/lib/entwurf-v2-visible-resume.js";
76
+ import { dispatchFreshCall, renderDispatchedFreshCall } from "../../../pi-extensions/lib/fresh-call-dispatch.js";
77
77
  import { probeNativeSenderAlive, reconcileSenderIdentityClaims, resolveCodexRequestSenderIdentity, resolveTrustedMetaSenderIdentity, } from "../../../pi-extensions/lib/meta-sender-identity.js";
78
78
  import { applyOmpBridgeChildRootPolicy, defaultMetaMailboxDir, defaultMetaSessionsDir, makeStoreRecordReader, readActiveStoreEntries, readMetaInbox, readMetaReceiverMarker, readMetaSenderMarker, requireBackend, } from "../../../pi-extensions/lib/meta-session.js";
79
- import { freshCall, renderFreshCall } from "../../../pi-extensions/lib/mux-fresh-call.js";
80
79
  import { RESUME_CALL_REJECT_HINT, resumeCall } from "../../../pi-extensions/lib/mux-resume-call.js";
81
80
  import { registerNativeConversation } from "../../../pi-extensions/lib/native-push/register.js";
82
81
  const HOME = os.homedir();
@@ -598,29 +597,29 @@ server.tool("entwurf_register_native", "Register an ALREADY-RUNNING native conve
598
597
  // its own garden id, a fresh cell answered with its uuidv7 `PI_SESSION_ID` value read out of the
599
598
  // environment by an MCP server it had spawned itself — confidently, and wrong. A sibling launched
600
599
  // against that answer would call home to a garden id nobody holds.
601
- server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling in the operator's tmux and hand it a first task. Five fixed " +
602
- "backends only: pi, claude-code, copilot, omp, codex. The sibling's FIRST action is a callback to you carrying a nonce, and the " +
603
- "sender envelope of that callback is its garden id — that is how you learn the address of something that " +
604
- "did not exist a moment ago. This returns a LAUNCH receipt (tmux window/pane plus that nonce) and nothing " +
605
- "else: it does NOT mean the runtime started, the first turn ran, or the task was delivered. Nothing polls " +
606
- "for the callback; if it never arrives the window is visible. For EXISTING " +
607
- "citizens use entwurf_v2 this tool only creates, and entwurf_peers only reports. Model is REQUIRED and " +
608
- "is passed to the chosen runtime CLI (`provider/model` for pi; model id/alias for Claude Code; a model name " +
609
- "or `auto` for copilot; a fuzzy model pattern for omp or codex). Copilot, omp, and codex are refused BEFORE " +
610
- "any window opens when their required birth, MCP, receive/delivery, or visible-identity units are absent; " +
611
- "Codex also requires the operator-owned default app-server socket, which entwurf never starts. An optional " +
612
- "cwd starts the sibling in ONE literal absolute existing directory (cross-repo fresh) never pick resume " +
613
- "for a dormant record's cwd. Omitted/empty cwd means the caller's own directory. " +
614
- "An optional placement.tmuxSession is an expert override naming ONE EXISTING session on this agent's own " +
615
- "tmux server, and it ALWAYS wins. Omitted, the seat follows the CALLER: a CODEX CALLER opens beside its own " +
616
- "TUI pane (matched by thread-id in that pane's title; 0 or 2+ matches REFUSE, never fall back), and every " +
617
- "other caller opens in its own session. " +
618
- "A missing named session is tmux-session-missing and NOTHING is created. " +
619
- "There are no arbitrary command/env knobs. Do not put secrets in the task — model and task argv are visible to " +
620
- "same-user processes on this host. Requires that this agent itself runs inside tmux.", {
600
+ server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling beside you and hand it a first task. WHERE it opens is decided by where THIS " +
601
+ "agent runs, never by a parameter: inside herdr (HERDR_ENV=1) it opens a NEW UNFOCUSED TAB in your own " +
602
+ "herdr workspace, pi and claude-code " +
603
+ "only; everywhere else it opens in the operator's tmux with all five backends (pi, " +
604
+ "claude-code, copilot, omp, codex). No fallback: an incomplete herdr context is refused by name, " +
605
+ "never by a tmux window you cannot see from herdr. The sibling's FIRST action " +
606
+ "is a callback to you carrying a nonce, whose sender envelope is its garden id — that is how you learn the " +
607
+ "address of a thing that did not exist a moment ago. This returns a LAUNCH receipt (the owner's " +
608
+ "coordinates plus that nonce): it does NOT mean the runtime started, the first turn ran, or " +
609
+ "the task was delivered. Those coordinates are a VIEW, never an address — a herdr tab/pane id can change under " +
610
+ "a running sibling. Nothing polls. " +
611
+ "For EXISTING citizens use entwurf_v2 this tool only creates, and entwurf_peers only reports. Model is REQUIRED and passed to the chosen " +
612
+ "runtime CLI (`provider/model` for pi, an id/alias for Claude Code, a pattern for the rest). On tmux, copilot/omp/codex are refused BEFORE any window " +
613
+ "opens when their birth, MCP, receive or visible-identity units are absent, and codex also needs the " +
614
+ "operator-owned app-server socket entwurf never starts; in herdr they are refused by name first. " +
615
+ "Optional placement.tmuxSession is a TMUX-ONLY seat naming ONE EXISTING session and " +
616
+ "ALWAYS wins; omitted, the seat follows the CALLER, never the backend opened: a CODEX CALLER opens beside " +
617
+ "its own TUI pane, every other caller in its own session; a missing one is tmux-session-missing and " +
618
+ "NOTHING is created. In herdr the field is refused by name. Do not put secrets in the task — model and task argv are visible to same-user " +
619
+ "processes.", {
621
620
  backend: z
622
621
  .enum(["pi", "claude-code", "copilot", "omp", "codex"])
623
- .describe("Which fixed runtime to open. Only these five; there is no arbitrary command."),
622
+ .describe("Which fixed runtime to open. Only these five, and only pi/claude-code when this agent runs inside herdr; there is no arbitrary command."),
624
623
  model: z
625
624
  .string()
626
625
  .min(1)
@@ -643,7 +642,7 @@ server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling in the operato
643
642
  cwd: z
644
643
  .string()
645
644
  .optional()
646
- .describe("Optional literal ABSOLUTE path of an existing directory to start the sibling in (cross-repo fresh). Taken exactly as given — no trim, no realpath, no project-name resolution; '#' is refused (tmux format expansion). Omit or pass \"\" to start where the CALLER is: its own directory, or for a Codex caller its own record directory, because this bridge is the app-server's child and that process's directory is not the caller's. The receipt echoes the directory that was REQUESTED or the caller record it came from, never an observation of where the pane landed."),
645
+ .describe("Optional literal ABSOLUTE path of an existing directory to start the sibling in (cross-repo fresh). Omit or pass \"\" to start where the CALLER is this agent's own cwd on BOTH rails, or for a Codex caller its own record directory, because this bridge is the app-server's child and that process's directory is not the caller's. Taken exactly as given: no trim, no realpath, no project-name resolution. '#' is refused on the tmux rail only, because tmux format-expands a start directory; inside herdr it is an ordinary path character. The receipt echoes the directory that was REQUESTED or the caller record it came from, never an observation of where the pane landed."),
647
646
  placement: z
648
647
  .object({
649
648
  tmuxSession: z
@@ -651,7 +650,7 @@ server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling in the operato
651
650
  .describe("EXACT name of an EXISTING session on this agent's own tmux server. Nothing is created: an absent session is refused as tmux-session-missing, and a name outside [A-Za-z0-9][A-Za-z0-9_-]* as tmux-session-name-invalid."),
652
651
  })
653
652
  .optional()
654
- .describe("Optional expert seat override: open the sibling in ONE EXISTING tmux session of this agent's own server, and it always wins. When omitted the seat follows the CALLER, never the backend being opened: a Codex CALLER opens beside its own TUI pane, matched by thread-id in that pane's terminal title (0 or 2+ matching panes refuse, never fall back); every other caller opens in its own session. A pane title is a placement input only — never an address, liveness or delivery fact. Nothing is ever created. Independent of cwd; neither is inferred from the other. The receipt reports the selected name (absent for the caller-pane rule, which observed a session rather than requesting a name), its source, and resolved target session id."),
653
+ .describe("Optional expert seat override, TMUX ONLY: open the sibling in ONE EXISTING tmux session of this agent's own server, and it always wins. When omitted the seat follows the CALLER, never the backend being opened: a Codex CALLER opens beside its own TUI pane, matched by thread-id in that pane's terminal title (0 or 2+ matching panes refuse, never fall back); every other caller opens in its own session. A pane title is a placement input only — never an address, liveness or delivery fact. Nothing is ever created. Inside herdr this field is refused by name — placement there belongs to herdr, and a tmux session name would silently place the sibling somewhere else. Independent of cwd; neither is inferred from the other. The receipt reports the selected name (absent for the caller-pane rule, which observed a session rather than requesting a name), its source, and resolved target session id."),
655
654
  }, async ({ backend, model, task, cwd, placement }, extra) => {
656
655
  let callerGardenId = null;
657
656
  // Present exactly when the reconciled caller is a record-backed codex citizen. Its ONLY
@@ -685,21 +684,21 @@ server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling in the operato
685
684
  callerGardenId = null;
686
685
  }
687
686
  try {
688
- // TWO capability axes, in this order, both pre-mutation and neither standing in for
689
- // the other. The TARGET axis first "entwurf cannot open a Codex sibling here at
690
- // all" is the more fundamental answer than "and it would not know where to put it".
691
- // The CALLER axis second, and only when the seat anchor will actually be consulted:
692
- // a codex caller that named an explicit placement never reads a pane title, so
693
- // refusing it for a missing `thread-id` would refuse an unused capability.
694
- const targetMissing = backend === "codex" ? await codexFreshPreflight(process.env) : null;
695
- const callerMissing = targetMissing === null && callerNativeSessionId !== undefined && placement === undefined
696
- ? codexCallerFreshPreflight(process.env)
697
- : null;
698
- const missing = targetMissing ?? callerMissing;
699
- const result = missing
700
- ? { ok: false, reason: missing }
701
- : freshCall({ backend, model, task, cwd, placement, callerGardenId, callerNativeSessionId, callerCwd });
702
- const rendered = renderFreshCall(result);
687
+ // Rail choice, Codex preflight ordering and rendering all live in the composition root,
688
+ // so this surface and pi's own cannot drift apart on any of them. The two caller-derived
689
+ // codex inputs ride with the request: the root consults them only on the tmux rail,
690
+ // which is where #95 lane B/C measured them.
691
+ const dispatched = await dispatchFreshCall({
692
+ backend,
693
+ model,
694
+ task,
695
+ cwd,
696
+ placement,
697
+ callerGardenId,
698
+ callerNativeSessionId,
699
+ callerCwd,
700
+ });
701
+ const rendered = renderDispatchedFreshCall(dispatched);
703
702
  return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
704
703
  }
705
704
  catch (err) {