@junghanacs/entwurf 0.12.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 +240 -0
- package/BASELINE.md +227 -0
- package/CHANGELOG.md +1210 -0
- package/CONTRIBUTING.md +63 -0
- package/DELIVERY.md +209 -0
- package/LICENSE +21 -0
- package/README.md +504 -0
- package/VERIFY.md +260 -0
- package/demo/README.md +188 -0
- package/demo/demo-baseline.sh +156 -0
- package/demo/demo.sh +183 -0
- package/docs/assets/entwurf-demo.gif +0 -0
- package/docs/assets/entwurf-doomemacs.gif +0 -0
- package/docs/assets/entwurf-entwurf.gif +0 -0
- package/docs/assets/entwurf-hero.jpg +0 -0
- package/docs/setup-clean-host.md +305 -0
- package/mcp/entwurf-bridge/src/index.ts +513 -0
- package/mcp/entwurf-bridge/start.sh +25 -0
- package/mcp/entwurf-bridge/test.sh +54 -0
- package/mcp/tsconfig.json +29 -0
- package/package.json +130 -0
- package/pi/entwurf-capabilities.json +9 -0
- package/pi/entwurf-targets.json +20 -0
- package/pi/meta-bridge/.claude-plugin/marketplace.json +12 -0
- package/pi/meta-bridge/entwurf-meta-receive/.claude-plugin/plugin.json +5 -0
- package/pi/meta-bridge/entwurf-meta-receive/hooks/hooks.json +50 -0
- package/pi/meta-bridge/entwurf-meta-receive/scripts/doorbell.sh +54 -0
- package/pi/settings.reference.json +43 -0
- package/pi/skill-plugin-example/.claude-plugin/plugin.json +4 -0
- package/pi/skill-plugin-example/skills/hello/SKILL.md +18 -0
- package/pi-extensions/acp-provider.ts +63 -0
- package/pi-extensions/entwurf-control.ts +1692 -0
- package/pi-extensions/lib/acp/acp-client.ts +90 -0
- package/pi-extensions/lib/acp/augment.ts +238 -0
- package/pi-extensions/lib/acp/backend-adapter.ts +319 -0
- package/pi-extensions/lib/acp/backend.ts +873 -0
- package/pi-extensions/lib/acp/config.ts +552 -0
- package/pi-extensions/lib/acp/context.ts +177 -0
- package/pi-extensions/lib/acp/engraving.ts +123 -0
- package/pi-extensions/lib/acp/event-mapper.ts +339 -0
- package/pi-extensions/lib/acp/models.ts +102 -0
- package/pi-extensions/lib/acp/overlay.ts +220 -0
- package/pi-extensions/lib/acp/prompts/engraving.md +1 -0
- package/pi-extensions/lib/acp/session-store.ts +418 -0
- package/pi-extensions/lib/acp/tool-surface.ts +184 -0
- package/pi-extensions/lib/entwurf-control-rpc.ts +208 -0
- package/pi-extensions/lib/entwurf-core.ts +2033 -0
- package/pi-extensions/lib/entwurf-deliverability.ts +123 -0
- package/pi-extensions/lib/entwurf-fact-provider.ts +158 -0
- package/pi-extensions/lib/entwurf-facts.ts +251 -0
- package/pi-extensions/lib/entwurf-mailbox-guard.ts +100 -0
- package/pi-extensions/lib/entwurf-peers-render.ts +154 -0
- package/pi-extensions/lib/entwurf-preflight.ts +247 -0
- package/pi-extensions/lib/entwurf-resume-args.ts +88 -0
- package/pi-extensions/lib/entwurf-self-address.ts +117 -0
- package/pi-extensions/lib/entwurf-v2-contract.ts +427 -0
- package/pi-extensions/lib/entwurf-v2-decider.ts +441 -0
- package/pi-extensions/lib/entwurf-v2-lock.ts +412 -0
- package/pi-extensions/lib/entwurf-v2-mailbox.ts +87 -0
- package/pi-extensions/lib/entwurf-v2-production.ts +326 -0
- package/pi-extensions/lib/entwurf-v2-release.ts +149 -0
- package/pi-extensions/lib/entwurf-v2-resume-marker.ts +34 -0
- package/pi-extensions/lib/entwurf-v2-runner.ts +196 -0
- package/pi-extensions/lib/entwurf-v2-send-fallback.ts +166 -0
- package/pi-extensions/lib/entwurf-v2-send.ts +271 -0
- package/pi-extensions/lib/entwurf-v2-spawn-production.ts +337 -0
- package/pi-extensions/lib/entwurf-v2-spawn.ts +323 -0
- package/pi-extensions/lib/entwurf-v2-surface.ts +205 -0
- package/pi-extensions/lib/meta-mailbox-body.ts +80 -0
- package/pi-extensions/lib/meta-session.ts +1951 -0
- package/pi-extensions/lib/project-trust-handler.ts +154 -0
- package/pi-extensions/lib/session-id.js +57 -0
- package/pi-extensions/lib/socket-discovery.ts +346 -0
- package/pi-extensions/lib/socket-probe.ts +92 -0
- package/pi-extensions/meta-bridge-hook.ts +250 -0
- package/pi-extensions/model-lock.ts +235 -0
- package/prompts/engraving.md +27 -0
- package/protocol.js +31 -0
- package/run.sh +2832 -0
- package/scripts/check-acp-backend-preflight.ts +130 -0
- package/scripts/check-acp-carrier-augment.ts +297 -0
- package/scripts/check-acp-config.ts +322 -0
- package/scripts/check-acp-event-mapper.ts +253 -0
- package/scripts/check-acp-overlay.ts +154 -0
- package/scripts/check-acp-prompt-builder.ts +191 -0
- package/scripts/check-acp-provider-surface.ts +159 -0
- package/scripts/check-acp-sdk-surface.ts +211 -0
- package/scripts/check-acp-session-reuse.ts +756 -0
- package/scripts/check-acp-session-store.ts +387 -0
- package/scripts/check-acp-tool-surface.ts +159 -0
- package/scripts/check-entwurf-bridge-boot.ts +199 -0
- package/scripts/check-entwurf-capabilities.ts +123 -0
- package/scripts/check-entwurf-control-rpc.ts +187 -0
- package/scripts/check-entwurf-deliverability.ts +158 -0
- package/scripts/check-entwurf-fact-provider.ts +316 -0
- package/scripts/check-entwurf-facts.ts +358 -0
- package/scripts/check-entwurf-mailbox-guard.ts +264 -0
- package/scripts/check-entwurf-peers-surface.ts +235 -0
- package/scripts/check-entwurf-resume-args.ts +149 -0
- package/scripts/check-entwurf-self-address.ts +209 -0
- package/scripts/check-entwurf-session-identity.ts +703 -0
- package/scripts/check-entwurf-v2-contract.ts +536 -0
- package/scripts/check-entwurf-v2-decider.ts +795 -0
- package/scripts/check-entwurf-v2-lock.ts +368 -0
- package/scripts/check-entwurf-v2-mailbox.ts +228 -0
- package/scripts/check-entwurf-v2-matrix.ts +437 -0
- package/scripts/check-entwurf-v2-production.ts +406 -0
- package/scripts/check-entwurf-v2-release.ts +245 -0
- package/scripts/check-entwurf-v2-runner.ts +452 -0
- package/scripts/check-entwurf-v2-send-fallback.ts +371 -0
- package/scripts/check-entwurf-v2-send.ts +454 -0
- package/scripts/check-entwurf-v2-spawn-production.ts +452 -0
- package/scripts/check-entwurf-v2-spawn.ts +398 -0
- package/scripts/check-entwurf-v2-surface.ts +352 -0
- package/scripts/check-keyset-overlap.py +120 -0
- package/scripts/check-mailbox-receipt-state.ts +170 -0
- package/scripts/check-meta-capability-source.ts +112 -0
- package/scripts/check-meta-dual-consumers.ts +154 -0
- package/scripts/check-meta-dual-read.ts +158 -0
- package/scripts/check-meta-listing.ts +138 -0
- package/scripts/check-meta-mailbox-state-write.ts +135 -0
- package/scripts/check-meta-migration.ts +212 -0
- package/scripts/check-meta-receiver-marker.ts +185 -0
- package/scripts/check-meta-record-v2.ts +191 -0
- package/scripts/check-meta-session.ts +673 -0
- package/scripts/check-model-lock.ts +408 -0
- package/scripts/check-package-source-routing.ts +253 -0
- package/scripts/check-pi-preflight.ts +304 -0
- package/scripts/check-project-trust-handler.ts +265 -0
- package/scripts/check-shell-quote.ts +121 -0
- package/scripts/check-socket-discovery.ts +428 -0
- package/scripts/check-socket-probe.ts +106 -0
- package/scripts/fixtures/probe-mcp-server.ts +33 -0
- package/scripts/gnew-rpc-drive.ts +211 -0
- package/scripts/lib/acp-child-cleanup.ts +116 -0
- package/scripts/meta-bridge-doctor.sh +315 -0
- package/scripts/meta-bridge-hook-log.sh +26 -0
- package/scripts/meta-bridge-install.sh +135 -0
- package/scripts/meta-bridge-prune.ts +199 -0
- package/scripts/meta-bridge-state.py +549 -0
- package/scripts/meta-bridge-statusline.sh +192 -0
- package/scripts/meta-bridge-store-doctor.ts +64 -0
- package/scripts/meta-bridge-uninstall.sh +39 -0
- package/scripts/new-session-id.ts +25 -0
- package/scripts/postinstall-chmod.cjs +58 -0
- package/scripts/raw-async-delivery/README.md +258 -0
- package/scripts/raw-async-delivery/cc-enqueue-addressed.sh +35 -0
- package/scripts/raw-async-delivery/cc-mailbox-rewake.sh +38 -0
- package/scripts/raw-async-delivery/cc-watch-filechanged.sh +19 -0
- package/scripts/raw-async-delivery/cc-watch-sessionstart.sh +15 -0
- package/scripts/raw-async-delivery/codex-local-appserver.sh +39 -0
- package/scripts/raw-async-delivery/plugin-entwurf-receive/.claude-plugin/plugin.json +5 -0
- package/scripts/raw-async-delivery/plugin-entwurf-receive/hooks/hooks.json +28 -0
- package/scripts/raw-async-delivery/plugin-entwurf-receive/scripts/watch-filechanged.sh +28 -0
- package/scripts/raw-async-delivery/plugin-entwurf-receive/scripts/watch-sessionstart.sh +27 -0
- package/scripts/raw-async-delivery/raw-agy-send.sh +29 -0
- package/scripts/raw-async-delivery/raw-claude-enqueue.sh +30 -0
- package/scripts/raw-async-delivery/raw-codex-ws-turn-start.py +164 -0
- package/scripts/raw-async-delivery/repro-addressed-routing.sh +96 -0
- package/scripts/raw-async-delivery/repro-plugin-idle-wake.sh +104 -0
- package/scripts/resolve-acp-bridge.ts +25 -0
- package/scripts/smoke-acp-bundled-mcp-live.ts +292 -0
- package/scripts/smoke-acp-carrier-augment-live.ts +192 -0
- package/scripts/smoke-acp-mcp-live.ts +129 -0
- package/scripts/smoke-acp-memory-containment-live.ts +389 -0
- package/scripts/smoke-acp-overlay-live.ts +314 -0
- package/scripts/smoke-acp-provider-live.ts +162 -0
- package/scripts/smoke-acp-raw-turn-live.ts +261 -0
- package/scripts/smoke-acp-session-reuse-live.ts +172 -0
- package/scripts/smoke-acp-skill-live.ts +144 -0
- package/scripts/smoke-acp-socket-citizen-live.ts +168 -0
- package/scripts/smoke-claude-native-resume-live.sh +198 -0
- package/scripts/smoke-entwurf-v2-matrix-live.ts +398 -0
- package/scripts/smoke-entwurf-v2-spawn-live.ts +175 -0
- package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +428 -0
- package/scripts/smoke-meta-async-drift.sh +171 -0
- package/scripts/smoke-meta-honesty.sh +147 -0
- package/scripts/smoke-meta-install-state.sh +403 -0
- package/scripts/smoke-meta-keyset-guard.sh +111 -0
- package/scripts/smoke-meta-prune.sh +174 -0
- package/scripts/smoke-resident-garden-guard.sh +433 -0
- package/scripts/smoke-session-id-name.ts +187 -0
- package/scripts/tsconfig.json +34 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* entwurf-v2-surface — 5d-3a: the ctx-free SURFACE adapter that joins a surface's raw params
|
|
3
|
+
* to `runEntwurfV2` and renders the outcome-rich result back to a human/tool string. It is the
|
|
4
|
+
* ONE place a surface (pi-native `entwurf_v2` tool now; the MCP `entwurf_v2` verb in 5d-3b)
|
|
5
|
+
* crosses into the v2 fence — so `entwurf-control.ts` (a root-tsc, emit-capable surface that
|
|
6
|
+
* CANNOT statically import the `.ts`-extension fence without TS5097) reaches this via a
|
|
7
|
+
* NON-LITERAL dynamic import, and the MCP bridge (already a `.ts`-import consumer) imports it
|
|
8
|
+
* directly. Either way the ctx binding stays OUT of the fence: the caller builds
|
|
9
|
+
* `senderProvider` from its own envelope source and passes it in `opts`.
|
|
10
|
+
*
|
|
11
|
+
* Three exports:
|
|
12
|
+
* - `toDispatchInput` — surface params → `DispatchInput` (the `wants_reply`→`wantsReply`
|
|
13
|
+
* snake→camel bridge; `intent` is REQUIRED and passed through verbatim, never inferred
|
|
14
|
+
* from mode/action — that would blur the F1 ownership contract).
|
|
15
|
+
* - `renderEntwurfV2Result` — the result union → `{ text, isError }`, surfacing the carry-overs
|
|
16
|
+
* the surface must NOT drop: a reject's reason+diagnostic, a control `rejectReason` (N3), a
|
|
17
|
+
* spawn `lock-retained` diagnostic, and the N1 delivered+lock-dirty `execution-failed`.
|
|
18
|
+
* - `runAndRenderEntwurfV2FromSurface` — assemble production deps + run + render, so the root
|
|
19
|
+
* surface never needs to name the `EntwurfV2RunResult` union (it only sees `{text,isError}`).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import * as path from "node:path";
|
|
23
|
+
import type { SenderEnvelope } from "./entwurf-control-rpc.ts";
|
|
24
|
+
import type { DispatchInput, EntwurfV2Mode } from "./entwurf-v2-decider.ts";
|
|
25
|
+
import { makeProductionEntwurfV2Deps, type ProductionEntwurfV2Opts } from "./entwurf-v2-production.ts";
|
|
26
|
+
import { type EntwurfV2RunResult, runEntwurfV2 } from "./entwurf-v2-runner.ts";
|
|
27
|
+
|
|
28
|
+
/** The operator-policy SSOT for v2 dispatch's preflight prefix-auto-approve roots (5d-4b).
|
|
29
|
+
* ONE shared env var feeds BOTH surfaces (pi-native + MCP) — a pi session and an MCP child
|
|
30
|
+
* both inherit it, so there is no per-surface config fork. `prefixRoots` is operator policy,
|
|
31
|
+
* not session-local UX, so it is an env var, not a pi flag. */
|
|
32
|
+
export const ENTWURF_PREFIX_ROOTS_ENV = "ENTWURF_PREFIX_ROOTS";
|
|
33
|
+
|
|
34
|
+
/** Parse `ENTWURF_PREFIX_ROOTS` into the preflight's `prefixRoots`. `path.delimiter`-
|
|
35
|
+
* separated (`:` on Linux/macOS); entries are trimmed, empty segments dropped. Unset / empty
|
|
36
|
+
* / delimiters-only ⇒ `[]` (no prefix promotion — frozen decision 7, no package default).
|
|
37
|
+
* It does NOT throw on a nonexistent/typo path: `preflight`'s normalize keeps an absolute
|
|
38
|
+
* fallback, so a bad root simply never matches (a typo must not broaden approve, and must not
|
|
39
|
+
* turn every owned-outcome dispatch into a loud failure). `~` is left for preflight to expand. */
|
|
40
|
+
export function parseEntwurfPrefixRootsEnv(raw: string | undefined = process.env[ENTWURF_PREFIX_ROOTS_ENV]): string[] {
|
|
41
|
+
if (!raw) return [];
|
|
42
|
+
return raw
|
|
43
|
+
.split(path.delimiter)
|
|
44
|
+
.map((s) => s.trim())
|
|
45
|
+
.filter((s) => s.length > 0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The raw shape a surface (pi tool / MCP verb) collects. `wants_reply` is snake_case to
|
|
49
|
+
* match the external `entwurf_v2` convention; the runner sees `wantsReply`. */
|
|
50
|
+
export interface SurfaceEntwurfV2Params {
|
|
51
|
+
target: string;
|
|
52
|
+
intent: "fire-and-forget" | "owned-outcome";
|
|
53
|
+
mode?: EntwurfV2Mode;
|
|
54
|
+
wants_reply?: boolean;
|
|
55
|
+
message: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** ctx-free run options. The caller (entwurf-control.ts / MCP bridge) builds `senderProvider`
|
|
59
|
+
* from its own envelope source — this module never touches `ExtensionContext`. Both surfaces
|
|
60
|
+
* leave `agentDir`/`prefixRoots` undefined by design: `runAndRenderEntwurfV2FromSurface` falls
|
|
61
|
+
* back to the `ENTWURF_PREFIX_ROOTS` env SSOT for `prefixRoots` (5d-4), and `agentDir` stays
|
|
62
|
+
* undefined (no surface sets it). Explicit opts still win — kept for tests / a future surface. */
|
|
63
|
+
export interface EntwurfV2SurfaceRunOptions {
|
|
64
|
+
senderProvider: () => SenderEnvelope | undefined;
|
|
65
|
+
agentDir?: string;
|
|
66
|
+
prefixRoots?: readonly string[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** What the surface renders: the human/tool text + whether it is an error (a non-delivery). */
|
|
70
|
+
export interface EntwurfV2SurfaceRendered {
|
|
71
|
+
text: string;
|
|
72
|
+
isError: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Surface params → the runner's `DispatchInput`. `wants_reply`→`wantsReply`; `intent` and
|
|
76
|
+
* `message` pass through; `mode`/`wantsReply` are left undefined when absent so the decider's
|
|
77
|
+
* own defaults (follow_up / false) apply — no double-default. */
|
|
78
|
+
export function toDispatchInput(params: SurfaceEntwurfV2Params): DispatchInput {
|
|
79
|
+
return {
|
|
80
|
+
target: params.target,
|
|
81
|
+
intent: params.intent,
|
|
82
|
+
message: params.message,
|
|
83
|
+
mode: params.mode,
|
|
84
|
+
wantsReply: params.wants_reply,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Detour B (B-a) — actionable rendering of an honest reject. The decider is UNCHANGED:
|
|
90
|
+
* a reject stays a reject (Hard Rule 3), and intent is NEVER auto-converted (Hard Rule 2 —
|
|
91
|
+
* owned→fire-and-forget mailbox fallback would break the F1 ownership contract). This only
|
|
92
|
+
* appends a one-line "what to do instead" to the reject TEXT, so an honest reject stops
|
|
93
|
+
* reading as "delivery impossible". Returns undefined for rejects with no useful next step.
|
|
94
|
+
*/
|
|
95
|
+
export function actionableRejectHint(reason: string): string | undefined {
|
|
96
|
+
switch (reason) {
|
|
97
|
+
case "backend-liveness-unsupported":
|
|
98
|
+
// A meta-session backend (e.g. claude-code self-fetch) has no liveness predicate, so
|
|
99
|
+
// owned-outcome has nothing to own. Replies go to the mailbox via fire-and-forget.
|
|
100
|
+
return (
|
|
101
|
+
"meta-session backend has no liveness predicate → owned-outcome is unsupported. " +
|
|
102
|
+
"To reply, dispatch with intent: fire-and-forget — it routes to the meta-mailbox when a " +
|
|
103
|
+
"deliverable/active receiver is armed (else it fail-closes as mailbox-undeliverable). " +
|
|
104
|
+
"(Intent is not auto-converted; you choose it.)"
|
|
105
|
+
);
|
|
106
|
+
case "owned-live-no-autosend":
|
|
107
|
+
// A live target is reachable, but owned-outcome is not an auto-send (Q2/F1).
|
|
108
|
+
return "target is live — owned-outcome never auto-sends. Use intent: fire-and-forget (with wants_reply if you need a reply).";
|
|
109
|
+
default:
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Render the outcome-rich result to `{ text, isError }`. A reject or a thrown/failed/dirty
|
|
115
|
+
* delivery is `isError:true`; a sent/fallback-sent/enqueued/observed delivery is `isError:false`.
|
|
116
|
+
* A control in-band `rejected` is a non-delivery (isError:true) and carries N3 `rejectReason`
|
|
117
|
+
* when present; a spawn `lock-retained` is fail-closed (isError:true) with its diagnostic. */
|
|
118
|
+
export function renderEntwurfV2Result(result: EntwurfV2RunResult): EntwurfV2SurfaceRendered {
|
|
119
|
+
switch (result.kind) {
|
|
120
|
+
case "rejected": {
|
|
121
|
+
const r = result.receipt;
|
|
122
|
+
let text = `entwurf_v2 rejected: ${r.reason} (observed liveness: ${r.observedLiveness ?? "n/a"})`;
|
|
123
|
+
const hint = actionableRejectHint(r.reason);
|
|
124
|
+
if (hint) text += `\n → ${hint}`;
|
|
125
|
+
if (result.diagnostic?.kind === "target-locked") {
|
|
126
|
+
const c = result.diagnostic.conflict;
|
|
127
|
+
text +=
|
|
128
|
+
`\n target-locked: ${c.lockPath}` +
|
|
129
|
+
`\n ${c.detail}` +
|
|
130
|
+
(c.holder ? `\n holder: pid ${c.holder.pid} on ${c.holder.hostname} since ${c.holder.createdAt}` : "");
|
|
131
|
+
}
|
|
132
|
+
return { text, isError: true };
|
|
133
|
+
}
|
|
134
|
+
case "executed": {
|
|
135
|
+
const o = result.outcome;
|
|
136
|
+
if (o.transport === "control-socket") {
|
|
137
|
+
const delivered = o.outcome === "sent" || o.outcome === "fallback-sent";
|
|
138
|
+
const reason = o.rejectReason ? ` (reason: ${o.rejectReason})` : "";
|
|
139
|
+
return {
|
|
140
|
+
text: `entwurf_v2 control-socket → ${o.outcome}${reason}`,
|
|
141
|
+
isError: !delivered,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (o.transport === "spawn-bg") {
|
|
145
|
+
const res = o.result;
|
|
146
|
+
if (res.kind === "lock-retained") {
|
|
147
|
+
const d = res.diagnostic;
|
|
148
|
+
return {
|
|
149
|
+
text:
|
|
150
|
+
`entwurf_v2 spawn-bg LOCK RETAINED (${res.reason}) — lock NOT released, operator must clear:` +
|
|
151
|
+
`\n target: ${d.targetGardenId}` +
|
|
152
|
+
`\n lockPath: ${d.lockPath}` +
|
|
153
|
+
`\n expectedSocketPath: ${d.expectedSocketPath}` +
|
|
154
|
+
`\n observeTimeoutMs: ${d.observeTimeoutMs}, killGraceMs: ${d.killGraceMs}` +
|
|
155
|
+
(res.error ? `\n error: ${res.error}` : ""),
|
|
156
|
+
isError: true,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (res.kind === "spawn-start-failed") {
|
|
160
|
+
return { text: `entwurf_v2 spawn-bg failed to start: ${res.error}`, isError: true };
|
|
161
|
+
}
|
|
162
|
+
const pid = "pid" in res && res.pid !== undefined ? ` (pid ${res.pid})` : "";
|
|
163
|
+
const exit = res.kind === "child-exited" ? ` exitCode=${res.exitCode}` : "";
|
|
164
|
+
return { text: `entwurf_v2 spawn-bg → ${res.kind}${pid}${exit}, lock released`, isError: false };
|
|
165
|
+
}
|
|
166
|
+
// meta-mailbox
|
|
167
|
+
return { text: "entwurf_v2 meta-mailbox → enqueued", isError: false };
|
|
168
|
+
}
|
|
169
|
+
case "execution-failed": {
|
|
170
|
+
if (result.releaseFailed && result.finalizedOutcome) {
|
|
171
|
+
// N1: the delivery/refusal reached a terminal outcome but releaseLock then threw.
|
|
172
|
+
return {
|
|
173
|
+
text:
|
|
174
|
+
`entwurf_v2 ${result.transport} DELIVERED (${result.finalizedOutcome}) but the lock is DIRTY ` +
|
|
175
|
+
`(release failed) — do NOT retry, a re-send would double-deliver. Clear the lock by hand.` +
|
|
176
|
+
`\n error: ${result.error}`,
|
|
177
|
+
isError: true,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
text: `entwurf_v2 ${result.transport} execution failed: ${result.error} (retry-safe: ${result.retrySafe})`,
|
|
182
|
+
isError: true,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Assemble production deps, run the v2 dispatch, and render the result. The root surface only
|
|
190
|
+
* ever sees `{ text, isError }` — it never names the `EntwurfV2RunResult` union — so the v2
|
|
191
|
+
* fence types stay behind this one entry point.
|
|
192
|
+
*/
|
|
193
|
+
export async function runAndRenderEntwurfV2FromSurface(
|
|
194
|
+
params: SurfaceEntwurfV2Params,
|
|
195
|
+
opts: EntwurfV2SurfaceRunOptions,
|
|
196
|
+
): Promise<EntwurfV2SurfaceRendered> {
|
|
197
|
+
const prodOpts: ProductionEntwurfV2Opts = {
|
|
198
|
+
senderProvider: opts.senderProvider,
|
|
199
|
+
agentDir: opts.agentDir,
|
|
200
|
+
// Explicit opts win (test / future surface override); otherwise the shared env SSOT.
|
|
201
|
+
prefixRoots: opts.prefixRoots ?? parseEntwurfPrefixRootsEnv(),
|
|
202
|
+
};
|
|
203
|
+
const result = await runEntwurfV2(toDispatchInput(params), makeProductionEntwurfV2Deps(prodOpts));
|
|
204
|
+
return renderEntwurfV2Result(result);
|
|
205
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* meta-mailbox-body — the SINGLE source for rendering an entwurf message as a
|
|
3
|
+
* meta-bridge mailbox body. Both transports that can deliver to a garden citizen
|
|
4
|
+
* with no live control socket use this:
|
|
5
|
+
* - the MCP bridge entwurf_v2 (mcp/entwurf-bridge) — external/Claude-host sends
|
|
6
|
+
* - the pi-native entwurf_v2 (pi-extensions/entwurf-control.ts) — pi-session sends
|
|
7
|
+
*
|
|
8
|
+
* The control-socket path carries the sender envelope inside its RPC framing; the
|
|
9
|
+
* mailbox path is just a file, so the envelope must be SERIALIZED INTO the body —
|
|
10
|
+
* else a receiver reading entwurf_inbox_read would not know who sent it, whether
|
|
11
|
+
* the sender is replyable (and at which sessionId), or whether a reply was wanted.
|
|
12
|
+
* The render mirrors the live "[entwurf received ⟵]" header so a transcript reads
|
|
13
|
+
* the same whether the message arrived over a socket or a mailbox.
|
|
14
|
+
*
|
|
15
|
+
* No filesystem/network IO and no mutation — the only ambient read is
|
|
16
|
+
* process.env.HOME for display abbreviation (so not strictly referentially pure,
|
|
17
|
+
* but deterministic per environment). Extracted so the two senders cannot drift
|
|
18
|
+
* in how a mailbox message presents who-sent-it — the field that round-trips
|
|
19
|
+
* garden-id replies.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** The fields a mailbox body needs from a sender. Structurally compatible with
|
|
23
|
+
* the SenderEnvelope of both entwurf_v2 surfaces. */
|
|
24
|
+
export interface MailboxSenderEnvelope {
|
|
25
|
+
sessionId: string;
|
|
26
|
+
agentId: string;
|
|
27
|
+
cwd: string;
|
|
28
|
+
timestamp: string; // ISO 8601 UTC
|
|
29
|
+
origin?: "pi-session" | "external-mcp" | "meta-session";
|
|
30
|
+
replyable?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** `~`-abbreviate a home-relative cwd for display. Reads process.env.HOME. */
|
|
34
|
+
function abbreviateHome(cwd: string): string {
|
|
35
|
+
const home = process.env.HOME;
|
|
36
|
+
if (!home) return cwd;
|
|
37
|
+
if (cwd === home) return "~";
|
|
38
|
+
if (cwd.startsWith(`${home}/`)) return `~${cwd.slice(home.length)}`;
|
|
39
|
+
return cwd;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Format a UTC ISO timestamp as `YYYY-MM-DD HH:MM:SS KST` (UTC+9, no DST).
|
|
43
|
+
* Returns the raw input when it does not parse. Pure. */
|
|
44
|
+
function formatKstTimestamp(iso: string): string {
|
|
45
|
+
const ms = Date.parse(iso);
|
|
46
|
+
if (Number.isNaN(ms)) return iso;
|
|
47
|
+
const kst = new Date(ms + 9 * 60 * 60 * 1000);
|
|
48
|
+
const pad = (n: number) => n.toString().padStart(2, "0");
|
|
49
|
+
return (
|
|
50
|
+
`${kst.getUTCFullYear()}-${pad(kst.getUTCMonth() + 1)}-${pad(kst.getUTCDate())} ` +
|
|
51
|
+
`${pad(kst.getUTCHours())}:${pad(kst.getUTCMinutes())}:${pad(kst.getUTCSeconds())} KST`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Render the full mailbox body: header envelope + separator + message. A
|
|
57
|
+
* replyable sender (pi-session, or a trusted meta-session) advertises its
|
|
58
|
+
* sessionId as the reply address; a non-replyable sender says so WITHOUT
|
|
59
|
+
* losing its origin — a record-backed meta-session that is currently inactive
|
|
60
|
+
* renders as `(meta-session, non-replyable)`, not degraded to `external`.
|
|
61
|
+
*/
|
|
62
|
+
export function formatMetaMailboxBody(sender: MailboxSenderEnvelope, message: string, wantsReply: boolean): string {
|
|
63
|
+
const replyable = sender.replyable === true;
|
|
64
|
+
const isMeta = sender.origin === "meta-session";
|
|
65
|
+
const kind = isMeta ? "meta-session, " : "";
|
|
66
|
+
const sessionLine = replyable
|
|
67
|
+
? `${sender.sessionId} (${kind}replyable — reply via entwurf_v2 to this sessionId, intent=fire-and-forget)`
|
|
68
|
+
: isMeta
|
|
69
|
+
? `${sender.sessionId} (meta-session, non-replyable)`
|
|
70
|
+
: `${sender.sessionId} (external, non-replyable)`;
|
|
71
|
+
return (
|
|
72
|
+
`[entwurf received ⟵]\n` +
|
|
73
|
+
` from: ${sender.agentId} @ ${abbreviateHome(sender.cwd)}\n` +
|
|
74
|
+
` session: ${sessionLine}\n` +
|
|
75
|
+
` at: ${formatKstTimestamp(sender.timestamp)}\n` +
|
|
76
|
+
` wants reply: ${wantsReply ? "yes" : "no"}\n` +
|
|
77
|
+
`────────────────────────────────────────\n` +
|
|
78
|
+
`${message}\n`
|
|
79
|
+
);
|
|
80
|
+
}
|