@junghanacs/entwurf 0.12.0 → 0.12.2
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/CHANGELOG.md +40 -0
- package/README.md +88 -28
- package/docs/setup-clean-host.md +117 -219
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +454 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +111 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-core.js +1683 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +76 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +121 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +155 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +119 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-preflight.js +160 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-resume-args.js +63 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +81 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +290 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +254 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +365 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-mailbox.js +64 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +218 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-release.js +108 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-resume-marker.js +33 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +116 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send-fallback.js +125 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +184 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +237 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn.js +216 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +164 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +66 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +1502 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/session-id.js +50 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-discovery.js +259 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-probe.js +81 -0
- package/mcp/entwurf-bridge/dist/protocol.js +29 -0
- package/mcp/entwurf-bridge/start.sh +49 -7
- package/mcp/entwurf-bridge/test.sh +12 -3
- package/mcp/entwurf-bridge/tsconfig.build.json +42 -0
- package/package.json +30 -9
- package/pi/meta-bridge/.claude-plugin/marketplace.json +0 -1
- package/pi-extensions/lib/entwurf-v2-contract-schema.ts +101 -0
- package/pi-extensions/lib/entwurf-v2-contract.ts +10 -78
- package/pi-extensions/lib/entwurf-v2-decider.ts +6 -2
- package/pi-extensions/lib/entwurf-v2-production.ts +26 -4
- package/run.sh +150 -15
- package/scripts/check-entwurf-bridge-pi-free.ts +146 -0
- package/scripts/check-entwurf-v2-contract.ts +6 -4
- package/scripts/check-meta-manifest-schema.py +145 -0
- package/scripts/meta-bridge-install.sh +17 -3
- package/scripts/meta-bridge-state.py +37 -10
- package/scripts/smoke-acp-bundled-mcp-live.ts +13 -2
- package/scripts/smoke-acp-carrier-augment-live.ts +35 -19
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-id — garden-native session id SSOT (`YYYYMMDDTHHMMSS-[0-9a-f]{6}`).
|
|
3
|
+
*
|
|
4
|
+
* Authored as `.js` for the exact reason protocol.js is (see its header): this
|
|
5
|
+
* leaf is imported from BOTH runtime paths —
|
|
6
|
+
* - tsc-emit path: pi-extension `.ts` files under the root tsconfig (allowJs),
|
|
7
|
+
* - `node --experimental-strip-types` path: pure unit gates (check-meta-session,
|
|
8
|
+
* new-session-id) that import a lib with a literal specifier.
|
|
9
|
+
* strip-types does not substitute `.ts` for a literal `.js` import specifier, and
|
|
10
|
+
* the root config cannot enable `allowImportingTsExtensions` without losing the
|
|
11
|
+
* tsc emit that check-models relies on. A real `.js` leaf resolves identically in
|
|
12
|
+
* every path, so the id grammar has ONE definition instead of one-per-importer.
|
|
13
|
+
*
|
|
14
|
+
* Keep dependency-free except `node:crypto`. The validator/grammar here is the
|
|
15
|
+
* same one the 0.9.0 resident garden guard and entwurf spawn collision pre-check
|
|
16
|
+
* enforce — do NOT fork it.
|
|
17
|
+
*/
|
|
18
|
+
import { randomBytes } from "node:crypto";
|
|
19
|
+
/** `YYYYMMDDTHHMMSS-[0-9a-f]{6}`. Anchored; no surrounding slop. */
|
|
20
|
+
export const SESSION_ID_RE = /^\d{8}T\d{6}-[0-9a-f]{6}$/;
|
|
21
|
+
/**
|
|
22
|
+
* @param {unknown} value
|
|
23
|
+
* @returns {value is string}
|
|
24
|
+
*/
|
|
25
|
+
export function isValidSessionId(value) {
|
|
26
|
+
return typeof value === "string" && SESSION_ID_RE.test(value);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Local (KST on operator machines) denote-style timestamp `YYYYMMDDTHHMMSS`.
|
|
30
|
+
* Garden sort sense. Local components on purpose — the denote corpus is local.
|
|
31
|
+
*
|
|
32
|
+
* @param {Date} [now]
|
|
33
|
+
* @returns {string}
|
|
34
|
+
*/
|
|
35
|
+
export function formatSessionTimestamp(now = new Date()) {
|
|
36
|
+
const p = (n, w = 2) => String(n).padStart(w, "0");
|
|
37
|
+
return (`${p(now.getFullYear(), 4)}${p(now.getMonth() + 1)}${p(now.getDate())}` +
|
|
38
|
+
`T${p(now.getHours())}${p(now.getMinutes())}${p(now.getSeconds())}`);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Durable garden sessionId minted at the session's true birth. 6 hex suffix
|
|
42
|
+
* defeats same-second parallel-spawn collision; callers that spawn still
|
|
43
|
+
* header-scan pre-check (assertSessionIdAvailableForSpawn).
|
|
44
|
+
*
|
|
45
|
+
* @param {Date} [now]
|
|
46
|
+
* @returns {string}
|
|
47
|
+
*/
|
|
48
|
+
export function generateSessionId(now = new Date()) {
|
|
49
|
+
return `${formatSessionTimestamp(now)}-${randomBytes(3).toString("hex")}`;
|
|
50
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* socket-discovery — the SOCKET-axis wiring for the fact-provider (0.11 Stage 0
|
|
3
|
+
* step 4, slice 3). Turns the control-socket directory + the in-domain citizen
|
|
4
|
+
* list into the `SocketProbe[]` that `resolveFactList` (slice 2) consumes.
|
|
5
|
+
*
|
|
6
|
+
* Why a probe per in-domain citizen, not just a directory listing: slice 2's
|
|
7
|
+
* frozen invariant is that EVERY in-domain (pi) citizen must arrive PROBED — a
|
|
8
|
+
* dormant citizen whose socket file is gone must read as `dead` (ENOENT =
|
|
9
|
+
* positive proof of absence) so it routes dormant→resumable, never as an
|
|
10
|
+
* unprobed `null`/`indeterminate` that would strand it (resolveFactList throws
|
|
11
|
+
* on an unprobed in-domain citizen). So we probe the union of
|
|
12
|
+
* (sockets present in the dir) ∪ (every in-domain citizen's canonical path):
|
|
13
|
+
* a dir-present socket yields alive / indeterminate / dead; a citizen with no
|
|
14
|
+
* file yields `dead` via ENOENT. Three-valued throughout (`probeSocketLiveness`)
|
|
15
|
+
* — an indeterminate stall is NEVER folded to dead (F3). This is exactly why we
|
|
16
|
+
* cannot reuse the legacy `getLiveSessions` (alive-only listing): folding the
|
|
17
|
+
* hidden indeterminate/dead sockets into "absent" would resurrect the F3 split.
|
|
18
|
+
*
|
|
19
|
+
* This slice fills the LIVENESS axis and, for live sockets, best-effort runtime
|
|
20
|
+
* enrich via the control RPC `get_info` (cwd / model / idle). `SocketProbe`'s
|
|
21
|
+
* enrich fields remain nullable-by-design: a dead/indeterminate socket or a
|
|
22
|
+
* failed enrich is HONEST, not synthetic, and carries `infoError` when known.
|
|
23
|
+
*
|
|
24
|
+
* Three socket-axis hazards are surfaced (slice 4c, Fable 검수), never swallowed:
|
|
25
|
+
* - SYMLINK (P1, security): a `<gid>.sock` that is a symlink can redirect to
|
|
26
|
+
* another session's listener, so gid X would probe ALIVE on Y's socket — a
|
|
27
|
+
* forgery of 동결결정3's correlation authority (the socket filename = the gid).
|
|
28
|
+
* The legacy bridge `getLiveSessions` guarded this (`entry.isSymbolicLink()`);
|
|
29
|
+
* deriving the listing from facts would drop that guard unless we re-assert it
|
|
30
|
+
* here. A symlinked socket is NEVER probed: a citizen owning one is forced to
|
|
31
|
+
* `dead` (→ dormant → resume a fresh process, never SEND to a hijacked
|
|
32
|
+
* listener); a record-less one is quarantined out of the listing entirely.
|
|
33
|
+
* Both surface as `symlinkedGardenIds`.
|
|
34
|
+
* - MALFORMED NAME (P3): a `*.sock` whose stem is not a garden id has no citizen
|
|
35
|
+
* to correlate to and is dropped — but VISIBLY (`malformedNames`), not
|
|
36
|
+
* silently (the legacy path listed any non-empty name; a silent regex drop
|
|
37
|
+
* would violate "no silent drops").
|
|
38
|
+
* - DIR-READ ERROR (P2e②): a missing dir (ENOENT) is the normal fresh-install
|
|
39
|
+
* empty; ANY OTHER readdir failure (EACCES, …) is asymmetric loss of the whole
|
|
40
|
+
* socket axis and is surfaced as `dirError`, not catch-all'd to empty (which
|
|
41
|
+
* would silently vanish every socket-only session). When the dir is untrusted
|
|
42
|
+
* this way, in-domain citizens are NOT probed (a non-ENOENT readdir failure
|
|
43
|
+
* means we cannot confirm the canonical path is not a symlink, and `connect()`
|
|
44
|
+
* would follow one) — they are reported `indeterminate` (liveness unknown),
|
|
45
|
+
* held not stranded: once the dir reads again they route normally (GPi Q2/P1).
|
|
46
|
+
* The provider (slice 4b) folds these three into kind-tagged `EntwurfDiagnostic`s;
|
|
47
|
+
* this lib only reports the raw facts so the import stays one-way (provider →
|
|
48
|
+
* socket-discovery, never back).
|
|
49
|
+
*
|
|
50
|
+
* Deps (dir / readdir / probe) are injectable so the gate drives it without IO.
|
|
51
|
+
*/
|
|
52
|
+
import * as fs from "node:fs/promises";
|
|
53
|
+
import * as os from "node:os";
|
|
54
|
+
import * as path from "node:path";
|
|
55
|
+
import { fetchControlSocketRuntimeInfo, formatRuntimeModel } from "./entwurf-control-rpc.js";
|
|
56
|
+
import { SESSION_ID_RE } from "./session-id.js";
|
|
57
|
+
import { probeSocketLiveness } from "./socket-probe.js";
|
|
58
|
+
/** Canonical control-socket directory; the socket filename IS the gardenId
|
|
59
|
+
* (동결결정3 correlation authority). */
|
|
60
|
+
export const CONTROL_SOCKET_DIR = path.join(os.homedir(), ".pi", "entwurf-control");
|
|
61
|
+
export const SOCKET_SUFFIX = ".sock";
|
|
62
|
+
// A control-socket filename is a bare garden id. We reuse the repo-wide
|
|
63
|
+
// `SESSION_ID_RE` SSOT (not a local copy): 동결결정3 makes the socket filename the
|
|
64
|
+
// correlation authority, which only holds if the socket axis and the meta-record
|
|
65
|
+
// axis speak the SAME id grammar — a drifted local regex would silently drop a
|
|
66
|
+
// legitimate gid's socket from the scan. A malformed name has no citizen to
|
|
67
|
+
// correlate to and is ignored.
|
|
68
|
+
export function controlSocketPath(gardenId, dir = CONTROL_SOCKET_DIR) {
|
|
69
|
+
return path.join(dir, `${gardenId}${SOCKET_SUFFIX}`);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* A1 narrow (0.11.0): does this PROBE-FREE single-lstat inspection of a gid's canonical
|
|
73
|
+
* control socket mean a record-LESS pi endpoint is addressable as a socket-only target?
|
|
74
|
+
* TRUE only for a confirmed NON-SYMLINK socket file (`socket-file`); a symlinked /
|
|
75
|
+
* absent / not-socket / `indeterminate` path is conservatively NOT promoted (never trust a
|
|
76
|
+
* symlink, never claim a target on an unprovable lstat). Shared by the v2 production
|
|
77
|
+
* `resolveTarget` so the socket-only acceptance uses the SAME lstat classification the
|
|
78
|
+
* listing/conflict paths use — listing↔dispatch cannot drift on what counts as a real
|
|
79
|
+
* control socket. The decider still does its own under-lock `inspectSocket` probe; this is
|
|
80
|
+
* only the presence hint that promotes `bad-target` → fire-and-forget socket-only pi.
|
|
81
|
+
*/
|
|
82
|
+
export function isSocketOnlyPiCandidate(inspection) {
|
|
83
|
+
return inspection.kind === "socket-file";
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Inspect the EXACT control-socket path given (no gid re-derivation) and classify it by
|
|
87
|
+
* lstat alone. This is the path-addressed core of the inspection: the 5c-3 spawn-bg watcher
|
|
88
|
+
* observes `plan.expectedSocketPath` and MUST inspect that exact path (its contract forbids
|
|
89
|
+
* re-deriving a path from the gid), so the path-taking form is the SSOT and
|
|
90
|
+
* `inspectTargetControlSocket` is the thin gid→path wrapper over it. `lstatFn` is injectable
|
|
91
|
+
* so the gate drives every branch without a real filesystem; the default is `fs.lstat`
|
|
92
|
+
* (which, unlike connect, does NOT follow the final symlink — that is the whole point: a
|
|
93
|
+
* symlink is caught as an address-conflict and never connected, P1).
|
|
94
|
+
*/
|
|
95
|
+
export async function inspectControlSocketPath(socketPath, lstatFn = (p) => fs.lstat(p)) {
|
|
96
|
+
let st;
|
|
97
|
+
try {
|
|
98
|
+
st = await lstatFn(socketPath);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
const code = err.code;
|
|
102
|
+
if (code === "ENOENT")
|
|
103
|
+
return { kind: "absent", socketPath };
|
|
104
|
+
// EACCES / unknown: not provably absent, so never treat as dead-and-spawn.
|
|
105
|
+
return { kind: "indeterminate", socketPath, error: code ?? "unknown lstat error" };
|
|
106
|
+
}
|
|
107
|
+
// lstat does NOT dereference the final component — a symlink is caught HERE and
|
|
108
|
+
// never connected (P1). A non-socket regular file / dir / fifo at the canonical
|
|
109
|
+
// path is address corruption, not a live socket.
|
|
110
|
+
if (st.isSymbolicLink())
|
|
111
|
+
return { kind: "address-conflict", socketPath, reason: "symlink" };
|
|
112
|
+
if (st.isSocket())
|
|
113
|
+
return { kind: "socket-file", socketPath };
|
|
114
|
+
return { kind: "address-conflict", socketPath, reason: "not-socket" };
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The gid-addressed inspection (?2 — lstat-then-connect, v2 decider helper): derive the
|
|
118
|
+
* canonical control-socket path for `gardenId` and inspect it. A thin wrapper over
|
|
119
|
+
* `inspectControlSocketPath` so the decider's gid-keyed path and the watcher's exact-path
|
|
120
|
+
* observation share ONE lstat classifier (no drift in the P1 symlink guard).
|
|
121
|
+
*/
|
|
122
|
+
export async function inspectTargetControlSocket(gardenId, dir = CONTROL_SOCKET_DIR, lstatFn = (p) => fs.lstat(p)) {
|
|
123
|
+
return inspectControlSocketPath(controlSocketPath(gardenId, dir), lstatFn);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Map a target's socket inspection to a measured `SocketLiveness` (to feed
|
|
127
|
+
* resolveDispatch) or a pre-probe address-conflict signal. `absent` (ENOENT only) is
|
|
128
|
+
* the honest `dead` (the citizen is dormant; its canonical socket is the path a resume
|
|
129
|
+
* will create). `socket-file` is the only case that connects. `address-conflict`
|
|
130
|
+
* (symlink / not-a-socket) and `indeterminate` never connect.
|
|
131
|
+
*
|
|
132
|
+
* Shared SSOT for the v2 decider (5b, decideDispatch) AND the dead-control-send
|
|
133
|
+
* fallback resolver (5c-2b): both must map an inspection the SAME way, or one could
|
|
134
|
+
* route a stalled socket where the other reclaims it — exactly the F3 split this lib
|
|
135
|
+
* exists to prevent. A per-caller copy would drift; this is the single mapper.
|
|
136
|
+
*/
|
|
137
|
+
export async function mapInspectionToLiveness(inspection, probeSocket) {
|
|
138
|
+
switch (inspection.kind) {
|
|
139
|
+
case "absent":
|
|
140
|
+
return { liveness: "dead", socketPath: inspection.socketPath };
|
|
141
|
+
case "socket-file": {
|
|
142
|
+
const liveness = await probeSocket(inspection.socketPath);
|
|
143
|
+
return { liveness, socketPath: inspection.socketPath };
|
|
144
|
+
}
|
|
145
|
+
case "indeterminate":
|
|
146
|
+
return { liveness: "indeterminate", socketPath: inspection.socketPath };
|
|
147
|
+
case "address-conflict":
|
|
148
|
+
return { addressConflict: true };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Probe the union of (control sockets present in `dir`) ∪ (`piCitizenGardenIds`)
|
|
153
|
+
* and return one `SocketProbe` per gardenId (liveness + live get_info enrich), plus
|
|
154
|
+
* the three surfaced hazards. A missing directory (ENOENT) is the normal empty
|
|
155
|
+
* (`dirError=null`) — the in-domain citizens are still probed (their absent
|
|
156
|
+
* canonical paths read `dead`); any OTHER readdir failure sets `dirError`. A
|
|
157
|
+
* symlinked `*.sock` is never probed (P1): a citizen owning one is forced `dead`,
|
|
158
|
+
* a record-less one is dropped from `probes` entirely. Output sorted by gardenId.
|
|
159
|
+
*/
|
|
160
|
+
export async function scanSocketProbes(piCitizenGardenIds, deps = {}) {
|
|
161
|
+
const dir = deps.dir ?? CONTROL_SOCKET_DIR;
|
|
162
|
+
const readdir = deps.readdir ??
|
|
163
|
+
(async (d) => {
|
|
164
|
+
const dirents = await fs.readdir(d, { withFileTypes: true });
|
|
165
|
+
return dirents.map((e) => ({ name: e.name, isSymbolicLink: e.isSymbolicLink() }));
|
|
166
|
+
});
|
|
167
|
+
const probe = deps.probe ?? ((p) => probeSocketLiveness(p));
|
|
168
|
+
// Deterministic gates commonly inject fake readdir/probe over fake paths. In that
|
|
169
|
+
// case, default enrich must stay no-op unless the test explicitly injects getInfo.
|
|
170
|
+
// Real production calls inject neither readdir nor probe, so they get live RPC
|
|
171
|
+
// enrich by default.
|
|
172
|
+
const getInfo = deps.getInfo ??
|
|
173
|
+
(deps.readdir || deps.probe
|
|
174
|
+
? async () => ({ cwd: null, model: null, idle: null })
|
|
175
|
+
: getRuntimeInfoOverControlSocket);
|
|
176
|
+
let entries = [];
|
|
177
|
+
let dirError = null;
|
|
178
|
+
try {
|
|
179
|
+
entries = await readdir(dir);
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
// ENOENT = fresh install / no sessions yet = the normal empty. Anything else
|
|
183
|
+
// (EACCES, EIO, …) is real loss of the socket axis — surface it, don't hide it.
|
|
184
|
+
const code = err?.code;
|
|
185
|
+
if (code !== "ENOENT") {
|
|
186
|
+
dirError = err instanceof Error ? err.message : String(err);
|
|
187
|
+
}
|
|
188
|
+
entries = [];
|
|
189
|
+
}
|
|
190
|
+
const socketGids = new Set();
|
|
191
|
+
const symlinkedGardenIds = [];
|
|
192
|
+
const malformedNames = [];
|
|
193
|
+
for (const entry of entries) {
|
|
194
|
+
if (!entry.name.endsWith(SOCKET_SUFFIX))
|
|
195
|
+
continue;
|
|
196
|
+
const gid = entry.name.slice(0, -SOCKET_SUFFIX.length);
|
|
197
|
+
if (!SESSION_ID_RE.test(gid)) {
|
|
198
|
+
malformedNames.push(entry.name);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (entry.isSymbolicLink) {
|
|
202
|
+
// Never trust a symlinked socket: it can point at another session's
|
|
203
|
+
// listener and forge an `alive` for this gid (동결결정3 authority forgery).
|
|
204
|
+
symlinkedGardenIds.push(gid);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
socketGids.add(gid);
|
|
208
|
+
}
|
|
209
|
+
const symlinkSet = new Set(symlinkedGardenIds);
|
|
210
|
+
// A non-ENOENT readdir failure means the dir is untrusted: we could not enumerate
|
|
211
|
+
// it, so we cannot confirm a canonical path is not a symlink. connect() follows
|
|
212
|
+
// symlinks, so probing here would defeat the P1 guard — hold every citizen at
|
|
213
|
+
// `indeterminate` instead (the socket-dir-read-error diagnostic carries the why).
|
|
214
|
+
const dirUntrusted = dirError !== null;
|
|
215
|
+
const allGids = [...new Set([...socketGids, ...piCitizenGardenIds])].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
216
|
+
const probes = [];
|
|
217
|
+
for (const gardenId of allGids) {
|
|
218
|
+
// A citizen whose canonical socket is a symlink is forced `dead` (→ dormant →
|
|
219
|
+
// resume a fresh process) rather than probed through the untrusted link. A
|
|
220
|
+
// record-less symlink gid is not in `allGids` at all (dropped above).
|
|
221
|
+
let liveness;
|
|
222
|
+
if (symlinkSet.has(gardenId)) {
|
|
223
|
+
liveness = "dead";
|
|
224
|
+
}
|
|
225
|
+
else if (dirUntrusted) {
|
|
226
|
+
liveness = "indeterminate";
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
liveness = await probe(controlSocketPath(gardenId, dir));
|
|
230
|
+
}
|
|
231
|
+
let cwd = null;
|
|
232
|
+
let model = null;
|
|
233
|
+
let idle = null;
|
|
234
|
+
let infoError = null;
|
|
235
|
+
if (liveness === "alive") {
|
|
236
|
+
try {
|
|
237
|
+
const info = await getInfo(controlSocketPath(gardenId, dir));
|
|
238
|
+
cwd = info.cwd;
|
|
239
|
+
model = info.model;
|
|
240
|
+
idle = info.idle;
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
infoError = err instanceof Error ? err.message : String(err);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
probes.push({ gardenId, liveness, cwd, model, idle, infoError });
|
|
247
|
+
}
|
|
248
|
+
symlinkedGardenIds.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
249
|
+
malformedNames.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
250
|
+
return { probes, symlinkedGardenIds, malformedNames, dirError };
|
|
251
|
+
}
|
|
252
|
+
async function getRuntimeInfoOverControlSocket(socketPath) {
|
|
253
|
+
const info = await fetchControlSocketRuntimeInfo(socketPath, { timeout: 1500 });
|
|
254
|
+
return {
|
|
255
|
+
cwd: info.cwd ?? null,
|
|
256
|
+
model: formatRuntimeModel(info) ?? null,
|
|
257
|
+
idle: info.idle ?? null,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* socket-probe — single source of truth for control-socket liveness.
|
|
3
|
+
*
|
|
4
|
+
* Why this is a shared lib (not a per-file copy): the entwurf-control
|
|
5
|
+
* extension (`pi-extensions/entwurf-control.ts`) AND the MCP bridge
|
|
6
|
+
* (`mcp/entwurf-bridge/src/index.ts`) both probe `~/.pi/entwurf-control/*.sock`.
|
|
7
|
+
* They used to carry independent `isSocketAlive` copies. If only one side
|
|
8
|
+
* learned the three-valued classification the two probes would diverge — the
|
|
9
|
+
* bridge routes a timeout target one way (mailbox fallback) while the
|
|
10
|
+
* extension's GC reclaims the same socket. Both now consume this module so the
|
|
11
|
+
* liveness semantics are identical on every surface. (0.11 Stage 0 step 4
|
|
12
|
+
* plans a probe lib extraction anyway; this is its first slice.)
|
|
13
|
+
*
|
|
14
|
+
* Three-valued, not boolean — the F3 fix. A connect probe can mean three
|
|
15
|
+
* different things, and collapsing them to a boolean is what let
|
|
16
|
+
* `gcStaleSockets` permanently unlink a *live* socket that merely stalled
|
|
17
|
+
* under load (→ every later probe sees it as dormant → live-session resume =
|
|
18
|
+
* identity split). The cure: never destroy a socket we do not understand.
|
|
19
|
+
* - alive → a listener accepted the connection (positive proof)
|
|
20
|
+
* - dead → ECONNREFUSED / ENOENT only (positive proof of absence)
|
|
21
|
+
* - indeterminate → timeout, EACCES, or any other/unknown error
|
|
22
|
+
* (no proof either way — keep the file, hide from listing)
|
|
23
|
+
*/
|
|
24
|
+
import * as net from "node:net";
|
|
25
|
+
export const DEFAULT_PROBE_TIMEOUT_MS = 300;
|
|
26
|
+
/**
|
|
27
|
+
* Pure classification of a connect-time error code into liveness. Only the two
|
|
28
|
+
* codes that unambiguously mean "no live listener at this path" are dead:
|
|
29
|
+
* ECONNREFUSED (socket file exists, nothing listening) and ENOENT (no socket
|
|
30
|
+
* file). EVERYTHING else — EACCES, ETIMEDOUT, an undefined code, an unknown
|
|
31
|
+
* code — is indeterminate. The default direction matters: when we don't know,
|
|
32
|
+
* we do not destroy. (동결결정: "ECONNREFUSED/ENOENT만 dead", taken literally.)
|
|
33
|
+
*/
|
|
34
|
+
export function classifyConnectError(code) {
|
|
35
|
+
return code === "ECONNREFUSED" || code === "ENOENT" ? "dead" : "indeterminate";
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* GC policy: a stale-socket sweep may reclaim ONLY a demonstrably dead socket.
|
|
39
|
+
* An indeterminate probe (timeout / unknown error) must survive the sweep —
|
|
40
|
+
* reclaiming it is exactly the F3 live-session split. Alive obviously survives.
|
|
41
|
+
*/
|
|
42
|
+
export function shouldUnlinkOnGc(liveness) {
|
|
43
|
+
return liveness === "dead";
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Listing policy: a session appears in the live listing only on a positive
|
|
47
|
+
* connect. Indeterminate is hidden from the listing (preserving the prior
|
|
48
|
+
* boolean listing semantics) but — unlike GC — is NOT unlinked. dead is hidden
|
|
49
|
+
* too. This keeps "what GC reclaims" strictly narrower than "what is listed".
|
|
50
|
+
*/
|
|
51
|
+
export function shouldListAsLive(liveness) {
|
|
52
|
+
return liveness === "alive";
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Probe a control socket and classify it. Positive connect → alive; a
|
|
56
|
+
* connect-time error is routed through `classifyConnectError`; a connect that
|
|
57
|
+
* neither connects nor errors within `timeoutMs` → indeterminate (the load
|
|
58
|
+
* stall case F3 is about — never coerced to dead).
|
|
59
|
+
*/
|
|
60
|
+
export async function probeSocketLiveness(socketPath, opts = {}) {
|
|
61
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
|
|
62
|
+
return await new Promise((resolve) => {
|
|
63
|
+
const socket = net.createConnection(socketPath);
|
|
64
|
+
const timer = setTimeout(() => {
|
|
65
|
+
socket.destroy();
|
|
66
|
+
resolve("indeterminate");
|
|
67
|
+
}, timeoutMs);
|
|
68
|
+
const settle = (liveness) => {
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
socket.removeAllListeners();
|
|
71
|
+
resolve(liveness);
|
|
72
|
+
};
|
|
73
|
+
socket.once("connect", () => {
|
|
74
|
+
socket.end();
|
|
75
|
+
settle("alive");
|
|
76
|
+
});
|
|
77
|
+
socket.once("error", (err) => {
|
|
78
|
+
settle(classifyConnectError(err.code));
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared wire-format constants for entwurf surfaces that must agree
|
|
3
|
+
* across the root bridge and pi-extension / MCP helper code.
|
|
4
|
+
*
|
|
5
|
+
* Keep this file dependency-free. Single source for both runtime paths:
|
|
6
|
+
* - tsc-emit path: typechecked under root tsconfig.json (allowJs: true)
|
|
7
|
+
* and copied through to the .tmp-verify directories by tsc emit.
|
|
8
|
+
* - Node --experimental-strip-types path (mcp bridges): resolves explicit
|
|
9
|
+
* .js imports literally to this file on disk.
|
|
10
|
+
*
|
|
11
|
+
* Why .js and not .ts: strip-types does not substitute .ts source for a
|
|
12
|
+
* literal .js import specifier, and the root config cannot enable
|
|
13
|
+
* allowImportingTsExtensions without losing tsc emit (which check-models
|
|
14
|
+
* relies on). Authoring as .js side-steps the whole drift surface.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Opening marker for the project-context block inserted by entwurf's
|
|
18
|
+
* `enrichTaskWithProjectContext`. The ACP bridge uses the same marker to
|
|
19
|
+
* detect entwurf-spawned first prompts and remove only the duplicate cwd
|
|
20
|
+
* AGENTS.md section from its own pi-context augment.
|
|
21
|
+
*/
|
|
22
|
+
export const ENTWURF_PROJECT_CONTEXT_OPEN_TAG = "<project-context";
|
|
23
|
+
/**
|
|
24
|
+
* Sender-side UI echo custom message type for successful entwurf_v2 send calls.
|
|
25
|
+
* The bridge emits it, the control renderer draws it, and the provider-level
|
|
26
|
+
* context filter removes it before LLM conversion. Keep as one SSOT: a typo
|
|
27
|
+
* here otherwise becomes a silent UI/context split.
|
|
28
|
+
*/
|
|
29
|
+
export const ENTWURF_SENT_MESSAGE_TYPE = "entwurf-sent";
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# entwurf-bridge MCP server launcher.
|
|
2
|
+
# entwurf-bridge MCP server launcher (dual-mode, chosen by LOCATION).
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# PUBLISHED / INSTALLED (path is under node_modules): runs the prebuilt JS at
|
|
5
|
+
# dist/mcp/entwurf-bridge/src/index.js with plain `node`. This path exists
|
|
6
|
+
# because Node's --experimental-strip-types REFUSES `.ts` under node_modules
|
|
7
|
+
# (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING), so the 0.12.0 strip-types
|
|
8
|
+
# launcher broke the moment the package was npm-installed. `prepack`
|
|
9
|
+
# (pnpm run build-bridge → rm -rf dist && tsc -p tsconfig.build.json) emits a
|
|
10
|
+
# clean dist/ into the tarball; the package `files` allowlist ("mcp/") carries it.
|
|
11
|
+
#
|
|
12
|
+
# DEV CLONE (path is NOT under node_modules): runs src/index.ts via
|
|
13
|
+
# --experimental-strip-types. The clone lives outside node_modules so strip-types
|
|
14
|
+
# is allowed, edits are picked up with no build step, and a leftover dist/ (e.g.
|
|
15
|
+
# emitted by `prepack` during a `pnpm check` pack gate) can never silently shadow
|
|
16
|
+
# the source. Node >= 22.6 (engines.node in ../../package.json).
|
|
6
17
|
#
|
|
7
18
|
# Env file loading is strictly opt-in — the launcher never reads any dotfile
|
|
8
19
|
# unless ENTWURF_BRIDGE_ENV_FILE points at one. Rationale: entwurf is a
|
|
@@ -10,7 +21,16 @@
|
|
|
10
21
|
# bleed the original author's dotfile habits into every consumer's shell.
|
|
11
22
|
set -euo pipefail
|
|
12
23
|
|
|
13
|
-
|
|
24
|
+
SOURCE="${BASH_SOURCE[0]}"
|
|
25
|
+
while [ -L "$SOURCE" ]; do
|
|
26
|
+
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
|
27
|
+
TARGET="$(readlink "$SOURCE")"
|
|
28
|
+
case "$TARGET" in
|
|
29
|
+
/*) SOURCE="$TARGET" ;;
|
|
30
|
+
*) SOURCE="$DIR/$TARGET" ;;
|
|
31
|
+
esac
|
|
32
|
+
done
|
|
33
|
+
HERE="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
|
14
34
|
|
|
15
35
|
if [ -n "${ENTWURF_BRIDGE_ENV_FILE:-}" ] && [ -f "$ENTWURF_BRIDGE_ENV_FILE" ]; then
|
|
16
36
|
set -a
|
|
@@ -19,7 +39,29 @@ if [ -n "${ENTWURF_BRIDGE_ENV_FILE:-}" ] && [ -f "$ENTWURF_BRIDGE_ENV_FILE" ]; t
|
|
|
19
39
|
set +a
|
|
20
40
|
fi
|
|
21
41
|
|
|
22
|
-
#
|
|
23
|
-
#
|
|
42
|
+
# rootDir is the repo root (the bridge graph spans mcp/ and pi-extensions/),
|
|
43
|
+
# so tsc mirrors the source tree under dist/ — hence the nested entry path.
|
|
44
|
+
DIST_ENTRY="$HERE/dist/mcp/entwurf-bridge/src/index.js"
|
|
45
|
+
SRC_ENTRY="$HERE/src/index.ts"
|
|
46
|
+
|
|
47
|
+
# Mode is decided by LOCATION, not by "does dist happen to exist". A dev clone
|
|
48
|
+
# lives outside node_modules and ALWAYS runs the TS source via strip-types, so an
|
|
49
|
+
# edit is picked up immediately and a stale dist (e.g. left behind by a prior
|
|
50
|
+
# `npm pack`/`prepack` during `pnpm check`) can never silently shadow the source.
|
|
51
|
+
# An installed package lives under node_modules, where Node refuses strip-types,
|
|
52
|
+
# so it MUST run the prebuilt dist. All chatter goes to stderr so it never
|
|
53
|
+
# confuses an MCP client reading JSON-RPC frames from stdout.
|
|
54
|
+
case "$HERE" in
|
|
55
|
+
*/node_modules/*)
|
|
56
|
+
if [ ! -f "$DIST_ENTRY" ]; then
|
|
57
|
+
echo "entwurf-bridge: installed under node_modules but the prebuilt dist is missing:" >&2
|
|
58
|
+
echo " $DIST_ENTRY" >&2
|
|
59
|
+
echo " The published tarball ships dist via prepack; reinstall @junghanacs/entwurf." >&2
|
|
60
|
+
exit 1
|
|
61
|
+
fi
|
|
62
|
+
exec node "$DIST_ENTRY"
|
|
63
|
+
;;
|
|
64
|
+
esac
|
|
65
|
+
|
|
24
66
|
exec node --experimental-strip-types --disable-warning=ExperimentalWarning \
|
|
25
|
-
"$
|
|
67
|
+
"$SRC_ENTRY"
|
|
@@ -2,8 +2,17 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
# entwurf-bridge smoke (v2-only): tool registration + a few no-side-effect calls.
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
SOURCE="${BASH_SOURCE[0]}"
|
|
6
|
+
while [ -L "$SOURCE" ]; do
|
|
7
|
+
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
|
8
|
+
TARGET="$(readlink "$SOURCE")"
|
|
9
|
+
case "$TARGET" in
|
|
10
|
+
/*) SOURCE="$TARGET" ;;
|
|
11
|
+
*) SOURCE="$DIR/$TARGET" ;;
|
|
12
|
+
esac
|
|
13
|
+
done
|
|
14
|
+
ROOT_DIR="$(cd -P "$(dirname "$SOURCE")/../.." && pwd)"
|
|
15
|
+
BRIDGE_LAUNCHER="$ROOT_DIR/mcp/entwurf-bridge/start.sh"
|
|
7
16
|
|
|
8
17
|
fail() { echo "FAIL: $*" >&2; exit 1; }
|
|
9
18
|
ok() { echo "ok: $*"; }
|
|
@@ -18,7 +27,7 @@ jsonrpc() {
|
|
|
18
27
|
-u ENTWURF_META_SENDER_MARKER \
|
|
19
28
|
-u ENTWURF_BRIDGE_REQUIRE_META_SENDER \
|
|
20
29
|
ENTWURF_META_SENDERS_DIR="$tmp_meta_senders" \
|
|
21
|
-
|
|
30
|
+
bash "$BRIDGE_LAUNCHER" <<'EOF'
|
|
22
31
|
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}
|
|
23
32
|
{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}
|
|
24
33
|
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Emit config for the entwurf-bridge MCP server (0.12.1 C — install fix).
|
|
3
|
+
//
|
|
4
|
+
// WHY THIS EXISTS: Node's `--experimental-strip-types` REFUSES `.ts` files
|
|
5
|
+
// under `node_modules` (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING). So the
|
|
6
|
+
// 0.12.0 launcher (`node --experimental-strip-types src/index.ts`) works from a
|
|
7
|
+
// dev clone but breaks the moment the package is installed under node_modules
|
|
8
|
+
// (every npm/pnpm consumer). This config tsc-EMITS the bridge boot closure to a
|
|
9
|
+
// plain-JS tree (`dist/`) that boots with vanilla `node` from anywhere. start.sh
|
|
10
|
+
// runs dist/ when its path is under node_modules (installed) and runs the
|
|
11
|
+
// strip-types src/ otherwise (dev clone).
|
|
12
|
+
//
|
|
13
|
+
// FENCE NOTE: the bridge graph is the SAME `.ts`-extension fence the root
|
|
14
|
+
// tsconfig excludes (root is emit-capable and can't allow `.ts` imports). This
|
|
15
|
+
// config pairs `allowImportingTsExtensions` with `rewriteRelativeImportExtensions`
|
|
16
|
+
// (TS 5.7+) instead of `noEmit`, so emitted JS references `.js` paths, including
|
|
17
|
+
// the lazy `await import("./entwurf-preflight.ts")` → `./entwurf-preflight.js`.
|
|
18
|
+
// That dynamic import stays dynamic in the output, so the eager boot closure
|
|
19
|
+
// remains pi-free (check-entwurf-bridge-pi-free) while the owned-outcome resume
|
|
20
|
+
// branch still lazy-loads the pi-backed preflight. This is an EMIT pass, not a
|
|
21
|
+
// typecheck pass — the noEmit typecheck fence (scripts/tsconfig.json) is
|
|
22
|
+
// untouched, so every `.ts` is still typechecked by exactly one `tsc --noEmit`.
|
|
23
|
+
"extends": "../../tsconfig.json",
|
|
24
|
+
"compilerOptions": {
|
|
25
|
+
"allowImportingTsExtensions": true,
|
|
26
|
+
"rewriteRelativeImportExtensions": true,
|
|
27
|
+
"noEmit": false,
|
|
28
|
+
"declaration": false,
|
|
29
|
+
"sourceMap": false,
|
|
30
|
+
"outDir": "./dist",
|
|
31
|
+
"rootDir": "../.."
|
|
32
|
+
},
|
|
33
|
+
// Clear the root `exclude` (it filters out every `.ts`-extension lib file the
|
|
34
|
+
// bridge graph needs). Only node_modules must stay excluded.
|
|
35
|
+
"exclude": ["../../node_modules"],
|
|
36
|
+
// Just the bridge entry (+ the dependency-free protocol.js it reaches via an
|
|
37
|
+
// explicit `.js` import). tsc auto-follows the import graph — STATIC imports
|
|
38
|
+
// AND the lazy `await import("./entwurf-preflight.ts")` — and emits exactly the
|
|
39
|
+
// boot closure, nothing more. This tracks graph drift automatically and keeps
|
|
40
|
+
// the pi-lane-only acp/* (unreached at boot) out of the tarball.
|
|
41
|
+
"include": ["./src/index.ts", "../../protocol.js"]
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junghanacs/entwurf",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.2",
|
|
4
4
|
"description": "Garden-citizen dispatch substrate and meta-bridge for Claude Code, Codex, Antigravity, and pi harnesses.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -19,17 +19,17 @@
|
|
|
19
19
|
"access": "public"
|
|
20
20
|
},
|
|
21
21
|
"keywords": [
|
|
22
|
-
"pi",
|
|
23
|
-
"pi-package",
|
|
24
|
-
"pi-extension",
|
|
25
|
-
"pi-coding-agent",
|
|
26
|
-
"garden-session",
|
|
27
22
|
"entwurf",
|
|
23
|
+
"garden-id",
|
|
24
|
+
"garden-citizen",
|
|
28
25
|
"dispatch",
|
|
29
26
|
"meta-bridge",
|
|
27
|
+
"mcp",
|
|
30
28
|
"claude-code",
|
|
31
29
|
"codex",
|
|
32
|
-
"
|
|
30
|
+
"antigravity",
|
|
31
|
+
"pi-coding-agent",
|
|
32
|
+
"pi-extension"
|
|
33
33
|
],
|
|
34
34
|
"type": "module",
|
|
35
35
|
"files": [
|
|
@@ -89,8 +89,10 @@
|
|
|
89
89
|
"typescript": "^6.0.2"
|
|
90
90
|
},
|
|
91
91
|
"scripts": {
|
|
92
|
-
"prepare": "husky 2>/dev/null || true",
|
|
92
|
+
"prepare": "husky 2>/dev/null || true; npm run --silent build-bridge",
|
|
93
93
|
"postinstall": "node scripts/postinstall-chmod.cjs",
|
|
94
|
+
"build-bridge": "rm -rf mcp/entwurf-bridge/dist && tsc -p mcp/entwurf-bridge/tsconfig.build.json",
|
|
95
|
+
"prepack": "pnpm --silent run build-bridge",
|
|
94
96
|
"prepublishOnly": "pnpm run check && pnpm run check-pack-install",
|
|
95
97
|
"setup": "./run.sh setup",
|
|
96
98
|
"check-shell-quote": "./run.sh check-shell-quote",
|
|
@@ -116,8 +118,9 @@
|
|
|
116
118
|
"typecheck": "tsc --noEmit && tsc -p mcp/tsconfig.json && tsc -p scripts/tsconfig.json",
|
|
117
119
|
"lint": "biome check .",
|
|
118
120
|
"format": "biome check --write .",
|
|
121
|
+
"check-meta-manifest-schema": "./run.sh check-meta-manifest-schema",
|
|
119
122
|
"check-package-source-routing": "./run.sh check-package-source-routing",
|
|
120
|
-
"check": "pnpm lint && pnpm typecheck && ./run.sh check-shell-quote && ./run.sh check-entwurf-session-identity && ./run.sh check-meta-session && ./run.sh check-meta-record-v2 && ./run.sh check-mailbox-receipt-state && ./run.sh check-entwurf-capabilities && ./run.sh check-meta-dual-read && ./run.sh check-meta-mailbox-state-write && ./run.sh check-meta-receiver-marker && ./run.sh check-meta-migration && ./run.sh check-meta-dual-consumers && ./run.sh check-meta-capability-source && ./run.sh check-socket-probe && ./run.sh smoke-meta-honesty && ./run.sh smoke-meta-install-state && ./run.sh smoke-meta-prune && ./run.sh smoke-meta-keyset-guard && ./run.sh check-package-source-routing && ./run.sh check-model-lock && ./run.sh check-dep-versions && ./run.sh check-install-preflight && ./run.sh check-pi-import-surface && ./run.sh check-env-namespace && ./run.sh check-pi-runtime-version && ./run.sh check-pi-preflight && ./run.sh check-project-trust-handler && ./run.sh check-entwurf-v2-contract && ./run.sh check-entwurf-v2-lock && ./run.sh check-entwurf-v2-decider && ./run.sh check-entwurf-v2-matrix && ./run.sh check-entwurf-v2-release && ./run.sh check-entwurf-v2-send && ./run.sh check-entwurf-v2-send-fallback && ./run.sh check-entwurf-v2-mailbox && ./run.sh check-entwurf-v2-runner && ./run.sh check-entwurf-control-rpc && ./run.sh check-entwurf-v2-production && ./run.sh check-entwurf-v2-surface && ./run.sh check-entwurf-bridge-boot && ./run.sh check-entwurf-v2-spawn && ./run.sh check-entwurf-resume-args && ./run.sh check-entwurf-v2-spawn-production && ./run.sh check-entwurf-facts && ./run.sh check-socket-discovery && ./run.sh check-meta-listing && ./run.sh check-entwurf-fact-provider && ./run.sh check-entwurf-peers-surface && ./run.sh check-entwurf-self-address && ./run.sh check-entwurf-deliverability && ./run.sh check-entwurf-mailbox-guard && ./run.sh check-auth-boundary && ./run.sh check-acp-provider-surface && ./run.sh check-acp-sdk-surface && ./run.sh check-acp-overlay && ./run.sh check-acp-tool-surface && ./run.sh check-acp-event-mapper && ./run.sh check-acp-prompt-builder && ./run.sh check-acp-config && ./run.sh check-acp-session-store && ./run.sh check-acp-backend-preflight && ./run.sh check-acp-session-reuse && ./run.sh check-acp-carrier-augment && ./run.sh check-pack"
|
|
123
|
+
"check": "pnpm lint && pnpm typecheck && ./run.sh check-shell-quote && ./run.sh check-entwurf-session-identity && ./run.sh check-meta-session && ./run.sh check-meta-record-v2 && ./run.sh check-mailbox-receipt-state && ./run.sh check-entwurf-capabilities && ./run.sh check-meta-dual-read && ./run.sh check-meta-mailbox-state-write && ./run.sh check-meta-receiver-marker && ./run.sh check-meta-migration && ./run.sh check-meta-dual-consumers && ./run.sh check-meta-capability-source && ./run.sh check-socket-probe && ./run.sh smoke-meta-honesty && ./run.sh smoke-meta-install-state && ./run.sh smoke-meta-prune && ./run.sh smoke-meta-keyset-guard && ./run.sh check-meta-manifest-schema && ./run.sh check-package-source-routing && ./run.sh check-model-lock && ./run.sh check-dep-versions && ./run.sh check-install-preflight && ./run.sh check-pi-import-surface && ./run.sh check-env-namespace && ./run.sh check-pi-runtime-version && ./run.sh check-pi-preflight && ./run.sh check-project-trust-handler && ./run.sh check-entwurf-v2-contract && ./run.sh check-entwurf-v2-lock && ./run.sh check-entwurf-v2-decider && ./run.sh check-entwurf-v2-matrix && ./run.sh check-entwurf-v2-release && ./run.sh check-entwurf-v2-send && ./run.sh check-entwurf-v2-send-fallback && ./run.sh check-entwurf-v2-mailbox && ./run.sh check-entwurf-v2-runner && ./run.sh check-entwurf-control-rpc && ./run.sh check-entwurf-v2-production && ./run.sh check-entwurf-v2-surface && ./run.sh check-entwurf-bridge-boot && ./run.sh check-entwurf-bridge-pi-free && ./run.sh check-entwurf-v2-spawn && ./run.sh check-entwurf-resume-args && ./run.sh check-entwurf-v2-spawn-production && ./run.sh check-entwurf-facts && ./run.sh check-socket-discovery && ./run.sh check-meta-listing && ./run.sh check-entwurf-fact-provider && ./run.sh check-entwurf-peers-surface && ./run.sh check-entwurf-self-address && ./run.sh check-entwurf-deliverability && ./run.sh check-entwurf-mailbox-guard && ./run.sh check-auth-boundary && ./run.sh check-acp-provider-surface && ./run.sh check-acp-sdk-surface && ./run.sh check-acp-overlay && ./run.sh check-acp-tool-surface && ./run.sh check-acp-event-mapper && ./run.sh check-acp-prompt-builder && ./run.sh check-acp-config && ./run.sh check-acp-session-store && ./run.sh check-acp-backend-preflight && ./run.sh check-acp-session-reuse && ./run.sh check-acp-carrier-augment && ./run.sh check-pack"
|
|
121
124
|
},
|
|
122
125
|
"pi": {
|
|
123
126
|
"extensions": [
|
|
@@ -126,5 +129,23 @@
|
|
|
126
129
|
"./pi-extensions/model-lock.ts"
|
|
127
130
|
],
|
|
128
131
|
"image": "https://raw.githubusercontent.com/junghan0611/entwurf/main/docs/assets/entwurf-hero.jpg"
|
|
132
|
+
},
|
|
133
|
+
"bin": {
|
|
134
|
+
"entwurf": "./run.sh",
|
|
135
|
+
"entwurf-bridge": "./mcp/entwurf-bridge/start.sh"
|
|
136
|
+
},
|
|
137
|
+
"peerDependenciesMeta": {
|
|
138
|
+
"@earendil-works/pi-ai": {
|
|
139
|
+
"optional": true
|
|
140
|
+
},
|
|
141
|
+
"@earendil-works/pi-coding-agent": {
|
|
142
|
+
"optional": true
|
|
143
|
+
},
|
|
144
|
+
"@earendil-works/pi-tui": {
|
|
145
|
+
"optional": true
|
|
146
|
+
},
|
|
147
|
+
"typebox": {
|
|
148
|
+
"optional": true
|
|
149
|
+
}
|
|
129
150
|
}
|
|
130
151
|
}
|