@junghanacs/entwurf 0.12.6 → 0.12.7
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 +31 -17
- package/BASELINE.md +42 -8
- package/CHANGELOG.md +22 -0
- package/DELIVERY.md +71 -14
- package/README.md +77 -45
- package/VERIFY.md +18 -13
- package/docs/setup-clean-host.md +80 -22
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +90 -66
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +54 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +153 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +436 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/context.js +157 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/engraving.js +105 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +90 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +194 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +153 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +42 -9
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +49 -13
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +104 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +30 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-native-push.js +57 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +10 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-release.js +9 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +21 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +5 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +17 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +125 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/adapter.js +158 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +61 -0
- package/mcp/entwurf-bridge/dist/scripts/agy-imprint.js +166 -0
- package/mcp/entwurf-bridge/dist/scripts/doctor-pi-provider.js +130 -0
- package/mcp/entwurf-bridge/dist/scripts/meta-bridge-prune.js +178 -0
- package/mcp/entwurf-bridge/dist/scripts/new-session-id.js +24 -0
- package/mcp/entwurf-bridge/src/index.ts +101 -67
- package/mcp/entwurf-bridge/test.sh +1 -1
- package/mcp/entwurf-bridge/tsconfig.build.json +23 -3
- package/package.json +10 -5
- package/pi-extensions/lib/entwurf-deliverability.ts +62 -9
- package/pi-extensions/lib/entwurf-self-address.ts +58 -15
- package/pi-extensions/lib/entwurf-v2-contract.ts +120 -12
- package/pi-extensions/lib/entwurf-v2-decider.ts +60 -0
- package/pi-extensions/lib/entwurf-v2-native-push.ts +86 -0
- package/pi-extensions/lib/entwurf-v2-production.ts +20 -0
- package/pi-extensions/lib/entwurf-v2-release.ts +9 -0
- package/pi-extensions/lib/entwurf-v2-runner.ts +29 -1
- package/pi-extensions/lib/entwurf-v2-send.ts +7 -0
- package/pi-extensions/lib/entwurf-v2-surface.ts +17 -0
- package/pi-extensions/lib/meta-sender-identity.ts +154 -0
- package/pi-extensions/lib/native-push/adapter.ts +255 -0
- package/pi-extensions/lib/native-push/register.ts +99 -0
- package/run.sh +756 -214
- package/scripts/agy-bridge-config.py +446 -0
- package/scripts/agy-bridge.sh +359 -0
- package/scripts/agy-hooks-bridge.sh +193 -0
- package/scripts/agy-hooks-config.py +257 -0
- package/scripts/agy-imprint.sh +28 -0
- package/scripts/agy-imprint.ts +193 -0
- package/scripts/agy-statusline-bridge.sh +176 -0
- package/scripts/agy-statusline-config.py +213 -0
- package/scripts/agy-statusline.sh +256 -0
- package/scripts/build-bridge.sh +20 -0
- package/scripts/check-agy-sender-identity.ts +364 -0
- package/scripts/check-entwurf-bridge-boot.ts +8 -2
- package/scripts/check-entwurf-deliverability.ts +34 -0
- package/scripts/check-entwurf-self-address.ts +78 -11
- package/scripts/check-entwurf-v2-contract.ts +136 -1
- package/scripts/check-entwurf-v2-decider.ts +95 -1
- package/scripts/check-entwurf-v2-matrix.ts +14 -3
- package/scripts/check-entwurf-v2-native-push.ts +193 -0
- package/scripts/check-entwurf-v2-production.ts +68 -1
- package/scripts/check-entwurf-v2-runner.ts +58 -0
- package/scripts/check-entwurf-v2-surface.ts +35 -0
- package/scripts/check-install-surface.ts +357 -0
- package/scripts/check-native-push-adapter.ts +319 -0
- package/scripts/check-native-push-register.ts +130 -0
- package/scripts/dev-bin.sh +195 -0
- package/scripts/doctor-pi-provider.ts +140 -0
- package/scripts/meta-bridge-doctor.sh +36 -2
- package/scripts/register-pi-package.py +37 -3
- package/scripts/register-pi-provider.py +287 -0
- package/scripts/smoke-agy-hooks-state.sh +172 -0
- package/scripts/smoke-agy-install-state.sh +660 -0
- package/scripts/smoke-agy-native-push-live.ts +243 -0
- package/scripts/smoke-agy-statusline-state.sh +300 -0
- package/scripts/smoke-meta-async-drift.sh +9 -2
- package/scripts/smoke-meta-install-state.sh +20 -0
- package/scripts/smoke-pi-provider-state.sh +182 -0
- package/scripts/smoke-user-scope-citizen.sh +62 -0
- package/scripts/with-dist-lock.sh +81 -0
- package/scripts/__pycache__/meta-bridge-state.cpython-312.pyc +0 -0
- package/scripts/__pycache__/meta-bridge-state.cpython-313.pyc +0 -0
- package/scripts/__pycache__/register-pi-package.cpython-313.pyc +0 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* meta-bridge-prune — LISTING-ONLY hygiene surface for the meta-session store.
|
|
4
|
+
*
|
|
5
|
+
* 1.0.0 policy (GLG + GPT힣, 2026-06-06): list candidates, NEVER delete. The
|
|
6
|
+
* store is the native→garden lookup authority, so this surface is deliberately
|
|
7
|
+
* conservative:
|
|
8
|
+
* - orphan (transcript gone) / stale (recordUpdatedAt older than ttl) are
|
|
9
|
+
* SUGGESTED for manual prune with the exact rm command printed — but removed
|
|
10
|
+
* by no one here. transcript-gone is a strong abandonment signal, not proof; a
|
|
11
|
+
* backend path migration / cleanup / config-dir change can also vacate it. A
|
|
12
|
+
* NULL transcriptPath (v2 nullable-at-birth, e.g. pi) is "unknown", NOT gone —
|
|
13
|
+
* never orphan on null; only a non-empty string path that no longer exists.
|
|
14
|
+
* - corrupt JSON / body↔filename drift / duplicate nativeSessionId are
|
|
15
|
+
* AMBIGUOUS / manual-only: the operator decides which authority survives.
|
|
16
|
+
* Never blindly rm a duplicate pair.
|
|
17
|
+
* No --apply in 1.0.0. The script prints, the operator removes. doctor stays the
|
|
18
|
+
* fail-loud surface (store-doctor reds on corrupt/duplicate/drift); prune is the
|
|
19
|
+
* separate janitor LISTING that keeps a green store from silently bloating with
|
|
20
|
+
* transcript-gone records — which doctor intentionally does NOT fail on.
|
|
21
|
+
*/
|
|
22
|
+
import fs from "node:fs";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import { defaultMetaSessionsDir, parseMetaIdentity } from "../pi-extensions/lib/meta-session.js";
|
|
25
|
+
/** POSIX single-quote a path so the printed rm command survives spaces/specials. */
|
|
26
|
+
function shellQuote(s) {
|
|
27
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
28
|
+
}
|
|
29
|
+
function usage(code) {
|
|
30
|
+
console.error("usage: node --experimental-strip-types scripts/meta-bridge-prune.ts [meta-sessions-dir] [--ttl-days N]");
|
|
31
|
+
process.exit(code);
|
|
32
|
+
}
|
|
33
|
+
let dirArg;
|
|
34
|
+
let ttlDays = 30;
|
|
35
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
36
|
+
const a = process.argv[i];
|
|
37
|
+
if (a === "--ttl-days") {
|
|
38
|
+
const v = process.argv[++i];
|
|
39
|
+
const n = Number(v);
|
|
40
|
+
if (!Number.isFinite(n) || n < 0)
|
|
41
|
+
usage(2);
|
|
42
|
+
ttlDays = n;
|
|
43
|
+
}
|
|
44
|
+
else if (a.startsWith("--ttl-days=")) {
|
|
45
|
+
const n = Number(a.slice("--ttl-days=".length));
|
|
46
|
+
if (!Number.isFinite(n) || n < 0)
|
|
47
|
+
usage(2);
|
|
48
|
+
ttlDays = n;
|
|
49
|
+
}
|
|
50
|
+
else if (a === "-h" || a === "--help") {
|
|
51
|
+
usage(0);
|
|
52
|
+
}
|
|
53
|
+
else if (a.startsWith("-")) {
|
|
54
|
+
console.error(`unknown flag: ${a}`);
|
|
55
|
+
usage(2);
|
|
56
|
+
}
|
|
57
|
+
else if (dirArg === undefined) {
|
|
58
|
+
dirArg = a;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
usage(2);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const dir = dirArg ?? defaultMetaSessionsDir();
|
|
65
|
+
const ttlMs = ttlDays * 24 * 60 * 60 * 1000;
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
console.log("meta-bridge prune candidates");
|
|
68
|
+
console.log(`store: ${dir}`);
|
|
69
|
+
console.log(`ttl: ${ttlDays}d (stale threshold)`);
|
|
70
|
+
console.log("");
|
|
71
|
+
if (!fs.existsSync(dir)) {
|
|
72
|
+
console.log(`store does not exist yet (0 records).`);
|
|
73
|
+
console.log("");
|
|
74
|
+
console.log("No files removed.");
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
const goodByNative = new Map();
|
|
78
|
+
const ambiguous = [];
|
|
79
|
+
let scanned = 0;
|
|
80
|
+
for (const filename of fs.readdirSync(dir).sort()) {
|
|
81
|
+
if (!filename.endsWith(".meta.json"))
|
|
82
|
+
continue;
|
|
83
|
+
scanned += 1;
|
|
84
|
+
const file = path.join(dir, filename);
|
|
85
|
+
// dual-read (3D-4 commit1): parseMetaIdentity reads v1 AND v2, normalizing to
|
|
86
|
+
// identity (transcriptPath nullable, lastSeen → recordUpdatedAt).
|
|
87
|
+
let id;
|
|
88
|
+
try {
|
|
89
|
+
id = parseMetaIdentity(fs.readFileSync(file, "utf8"));
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
ambiguous.push(`${filename}: corrupt — ${err instanceof Error ? err.message : String(err)}`);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const expectedFilename = `${id.gardenId}.meta.json`;
|
|
96
|
+
if (filename !== expectedFilename) {
|
|
97
|
+
ambiguous.push(`${filename}: body/filename drift — body gardenId=${id.gardenId}, expected ${expectedFilename}`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const list = goodByNative.get(id.nativeSessionId) ?? [];
|
|
101
|
+
list.push({
|
|
102
|
+
file: filename,
|
|
103
|
+
gardenId: id.gardenId,
|
|
104
|
+
backend: id.backend,
|
|
105
|
+
nativeSessionId: id.nativeSessionId,
|
|
106
|
+
transcriptPath: id.transcriptPath,
|
|
107
|
+
recordUpdatedAt: id.recordUpdatedAt,
|
|
108
|
+
});
|
|
109
|
+
goodByNative.set(id.nativeSessionId, list);
|
|
110
|
+
}
|
|
111
|
+
const orphan = [];
|
|
112
|
+
const stale = [];
|
|
113
|
+
let keep = 0;
|
|
114
|
+
for (const [nativeSessionId, list] of goodByNative.entries()) {
|
|
115
|
+
if (list.length > 1) {
|
|
116
|
+
// Authority ambiguity — operator must choose which survives. Never auto-pick.
|
|
117
|
+
ambiguous.push(`duplicate nativeSessionId ${JSON.stringify(nativeSessionId)} in ${list.map((g) => g.file).join(", ")} — manual-only, do not blindly remove`);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const g = list[0];
|
|
121
|
+
// G2 (3D-4): null transcriptPath is NOT orphan. v2 (pi nullable-at-birth) leaves
|
|
122
|
+
// it null = "unknown / not yet known", which is not "the file went away". Orphan
|
|
123
|
+
// is ONLY a non-empty string path that no longer exists on disk. A null path
|
|
124
|
+
// falls through to the recordUpdatedAt staleness check like any live record.
|
|
125
|
+
const tp = g.transcriptPath;
|
|
126
|
+
if (typeof tp === "string" && tp !== "" && !fs.existsSync(tp)) {
|
|
127
|
+
orphan.push(g);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
// recordUpdatedAt is only proven a non-empty string by the parser, not a real
|
|
131
|
+
// date. KEEP means live + RECENT; an unparseable recordUpdatedAt cannot prove
|
|
132
|
+
// recency, so it is not a silent keep — it goes manual-only like any defect.
|
|
133
|
+
const updatedMs = Date.parse(g.recordUpdatedAt);
|
|
134
|
+
if (!Number.isFinite(updatedMs)) {
|
|
135
|
+
ambiguous.push(`${g.file}: unparseable recordUpdatedAt ${JSON.stringify(g.recordUpdatedAt)} — cannot prove recent, manual-only`);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (now - updatedMs > ttlMs)
|
|
139
|
+
stale.push(g);
|
|
140
|
+
else
|
|
141
|
+
keep += 1;
|
|
142
|
+
}
|
|
143
|
+
const ageDays = (iso) => {
|
|
144
|
+
const ms = now - Date.parse(iso);
|
|
145
|
+
if (!Number.isFinite(ms))
|
|
146
|
+
return "?";
|
|
147
|
+
return String(Math.floor(ms / (24 * 60 * 60 * 1000)));
|
|
148
|
+
};
|
|
149
|
+
console.log(`ORPHAN transcript-gone (${orphan.length}):`);
|
|
150
|
+
for (const g of orphan) {
|
|
151
|
+
console.log(`- ${g.gardenId} ${g.backend} native=${g.nativeSessionId} transcript=${g.transcriptPath ?? "(null)"}`);
|
|
152
|
+
}
|
|
153
|
+
console.log("");
|
|
154
|
+
console.log(`STALE recordUpdatedAt>${ttlDays}d (${stale.length}):`);
|
|
155
|
+
for (const g of stale) {
|
|
156
|
+
console.log(`- ${g.gardenId} ${g.backend} recordUpdatedAt=${g.recordUpdatedAt} age=${ageDays(g.recordUpdatedAt)}d`);
|
|
157
|
+
}
|
|
158
|
+
console.log("");
|
|
159
|
+
console.log(`AMBIGUOUS manual-only (${ambiguous.length}):`);
|
|
160
|
+
for (const reason of ambiguous) {
|
|
161
|
+
console.log(`- ${reason}`);
|
|
162
|
+
}
|
|
163
|
+
console.log("");
|
|
164
|
+
console.log(`KEEP (live/recent): ${keep} record(s)`);
|
|
165
|
+
console.log(`scanned: ${scanned} record(s)`);
|
|
166
|
+
console.log("");
|
|
167
|
+
console.log("No files removed.");
|
|
168
|
+
const removable = [...orphan, ...stale];
|
|
169
|
+
if (removable.length > 0) {
|
|
170
|
+
console.log("To prune an orphan/stale candidate, inspect it then remove its file manually:");
|
|
171
|
+
for (const g of removable) {
|
|
172
|
+
console.log(` rm -- ${shellQuote(path.join(dir, `${g.gardenId}.meta.json`))}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (ambiguous.length > 0) {
|
|
176
|
+
console.log("AMBIGUOUS records are NOT listed as rm commands — inspect each and decide which authority survives.");
|
|
177
|
+
}
|
|
178
|
+
process.exit(0);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* new-session-id — print one fresh garden-native sessionId and exit.
|
|
3
|
+
*
|
|
4
|
+
* The garden launcher (operator alias / wrapper) calls this so every
|
|
5
|
+
* `--entwurf-control` session is born a garden citizen:
|
|
6
|
+
*
|
|
7
|
+
* pi --session-id "$(./run.sh new-session-id)" --entwurf-control \
|
|
8
|
+
* --emacs-agent-socket server …
|
|
9
|
+
*
|
|
10
|
+
* The id is `generateSessionId()` from entwurf-core — the single SSOT for the
|
|
11
|
+
* locked `YYYYMMDDTHHMMSS-[0-9a-f]{6}` grammar. Do NOT reimplement the format in
|
|
12
|
+
* the shell (it would drift from the validator the resident guard enforces).
|
|
13
|
+
*
|
|
14
|
+
* Stdout is the id and nothing else (no trailing prose), so `$(…)` captures a
|
|
15
|
+
* clean value. Errors go to stderr with a nonzero exit.
|
|
16
|
+
*/
|
|
17
|
+
import { generateSessionId } from "../pi-extensions/lib/entwurf-core.js";
|
|
18
|
+
try {
|
|
19
|
+
process.stdout.write(`${generateSessionId()}\n`);
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
process.stderr.write(`new-session-id failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
@@ -52,18 +52,19 @@ import { receiverMarkerMatchesIdentity } from "../../../pi-extensions/lib/entwur
|
|
|
52
52
|
import { listEntwurfFacts } from "../../../pi-extensions/lib/entwurf-fact-provider.ts";
|
|
53
53
|
import { renderEntwurfPeers } from "../../../pi-extensions/lib/entwurf-peers-render.ts";
|
|
54
54
|
import { computeSelfAddressability } from "../../../pi-extensions/lib/entwurf-self-address.ts";
|
|
55
|
+
import { nativePushSupported } from "../../../pi-extensions/lib/entwurf-v2-contract.ts";
|
|
55
56
|
import { runAndRenderEntwurfV2FromSurface } from "../../../pi-extensions/lib/entwurf-v2-surface.ts";
|
|
57
|
+
import {
|
|
58
|
+
probeNativeSenderAlive,
|
|
59
|
+
resolveTrustedMetaSenderIdentity,
|
|
60
|
+
} from "../../../pi-extensions/lib/meta-sender-identity.ts";
|
|
56
61
|
import {
|
|
57
62
|
defaultMetaMailboxDir,
|
|
58
63
|
defaultMetaSessionsDir,
|
|
59
|
-
type MetaIdentity,
|
|
60
|
-
type MetaSenderMarker,
|
|
61
|
-
parentPid,
|
|
62
|
-
readMetaIdentityByGardenId,
|
|
63
64
|
readMetaInbox,
|
|
64
65
|
readMetaReceiverMarker,
|
|
65
|
-
readMetaSenderMarker,
|
|
66
66
|
} from "../../../pi-extensions/lib/meta-session.ts";
|
|
67
|
+
import { registerNativeConversation } from "../../../pi-extensions/lib/native-push/register.ts";
|
|
67
68
|
|
|
68
69
|
const HOME = os.homedir();
|
|
69
70
|
const DEFAULT_ENTWURF_DIR = path.join(HOME, ".pi", "entwurf-control");
|
|
@@ -150,22 +151,6 @@ class EntwurfSenderIdentityError extends Error {
|
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
153
|
|
|
153
|
-
// Resolve the meta-sender marker for THIS MCP process. ENTWURF_META_SENDER_MARKER is an
|
|
154
|
-
// explicit override (test / wiring). Otherwise try the shared ancestor: process.ppid
|
|
155
|
-
// first, then one step up (Claude may run the hook through a shell wrapper, shifting
|
|
156
|
-
// the shared ancestor). readMetaSenderMarker's pid+start-key guard rejects a
|
|
157
|
-
// dead/reused owner, so a wrong marker is never trusted on any candidate.
|
|
158
|
-
function resolveMetaSenderMarker(): MetaSenderMarker | null {
|
|
159
|
-
const explicit = process.env.ENTWURF_META_SENDER_MARKER?.trim();
|
|
160
|
-
if (explicit) return readMetaSenderMarker({ markerPath: explicit });
|
|
161
|
-
const candidates = [process.ppid, parentPid(process.ppid)].filter((p): p is number => typeof p === "number" && p > 0);
|
|
162
|
-
for (const ownerPid of candidates) {
|
|
163
|
-
const marker = readMetaSenderMarker({ backend: "claude-code", ownerPid });
|
|
164
|
-
if (marker) return marker;
|
|
165
|
-
}
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
154
|
function buildStrictPiSenderEnvelope(): SenderEnvelope {
|
|
170
155
|
const sessionId = process.env.PI_SESSION_ID?.trim();
|
|
171
156
|
const agentId = process.env.PI_AGENT_ID?.trim();
|
|
@@ -195,47 +180,49 @@ function buildStrictPiSenderEnvelope(): SenderEnvelope {
|
|
|
195
180
|
};
|
|
196
181
|
}
|
|
197
182
|
|
|
198
|
-
function buildTrustedMetaSenderEnvelope(cwd: string = process.cwd()): SenderEnvelope | null {
|
|
199
|
-
// No pi-session identity. Try the meta-sender marker: a native backend that
|
|
200
|
-
//
|
|
201
|
-
// shared parent pid — this MCP child's process.ppid IS the
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (!marker) return null;
|
|
207
|
-
|
|
208
|
-
// Validate the marker against its backing meta-record: a stale marker (record
|
|
209
|
-
// deleted, or backend/nativeSessionId drift) must NOT grant a replyable
|
|
210
|
-
// identity. The record store is the authority; the marker is only a pid→garden
|
|
211
|
-
// hint.
|
|
212
|
-
let identity: MetaIdentity | null = null;
|
|
213
|
-
try {
|
|
214
|
-
// dual-read (3D-4 commit1): identity-only check (backend/nativeSessionId), so
|
|
215
|
-
// it survives the v2 cut. Reads both v1 and v2 records.
|
|
216
|
-
const id = readMetaIdentityByGardenId(marker.gardenId);
|
|
217
|
-
if (id.backend === marker.backend && id.nativeSessionId === marker.nativeSessionId) identity = id;
|
|
218
|
-
} catch {
|
|
219
|
-
identity = null;
|
|
220
|
-
}
|
|
221
|
-
if (!identity) return null;
|
|
222
|
-
|
|
223
|
-
// SE-2 slice 2e-b: identity is trusted, but `replyable` is a SEPARATE fact — can THIS
|
|
224
|
-
// session's own receiver inbox actually wake? Read the receiver presence marker (slice
|
|
225
|
-
// 2b) and require it to match the identity (the same SSOT helper the mailbox guard uses).
|
|
226
|
-
// recordBacked is true here by construction; ownerAlive+watchArmed BOTH come from the
|
|
227
|
-
// matched receiver marker (readMetaReceiverMarker's verifyOwner folds a dead/reused owner
|
|
228
|
-
// to null, so a match means a live, armed receiver — the sender marker only proves
|
|
229
|
-
// identity, not an armed watch). Inactive → the meta identity is STILL returned (who-sent
|
|
230
|
-
// must survive; degrading to null would erase the sender) but with replyable:false.
|
|
231
|
-
const receiver = readMetaReceiverMarker({ gardenId: identity.gardenId });
|
|
232
|
-
const active = receiverMarkerMatchesIdentity(receiver, identity);
|
|
233
|
-
const self = computeSelfAddressability({
|
|
234
|
-
origin: "meta-session",
|
|
235
|
-
recordBacked: true,
|
|
236
|
-
ownerAlive: active,
|
|
237
|
-
watchArmed: active,
|
|
183
|
+
async function buildTrustedMetaSenderEnvelope(cwd: string = process.cwd()): Promise<SenderEnvelope | null> {
|
|
184
|
+
// No pi-session identity. Try the meta-sender marker: a native backend that minted a
|
|
185
|
+
// garden-id from its own hook (Claude SessionStart / agy PreInvocation). The marker is
|
|
186
|
+
// keyed by the shared parent pid — this MCP child's process.ppid IS the native host the
|
|
187
|
+
// hook ran under (NOT cwd inference). A trusted marker promotes this process from
|
|
188
|
+
// anonymous external-mcp to a meta-session sender addressed by its garden-id.
|
|
189
|
+
const trusted = resolveTrustedMetaSenderIdentity({
|
|
190
|
+
markerPath: process.env.ENTWURF_META_SENDER_MARKER?.trim() || undefined,
|
|
238
191
|
});
|
|
192
|
+
if (!trusted) return null;
|
|
193
|
+
const { marker, identity } = trusted;
|
|
194
|
+
|
|
195
|
+
// Identity is trusted — but `replyable` is a SEPARATE fact, and WHICH fact depends on the
|
|
196
|
+
// rail a reply would ride (보정①). The domain comes from nativePushSupported(backend), not
|
|
197
|
+
// from wakeMode: `direct-inject` also covers codex/pi, which have no native-push adapter.
|
|
198
|
+
// self-fetch (claude-code): can this citizen's own inbox wake? → the receiver presence
|
|
199
|
+
// marker (readMetaReceiverMarker folds a dead/reused owner to null, so a match means a
|
|
200
|
+
// live, ARMED receiver — the sender marker proves identity, never an armed watch).
|
|
201
|
+
// native-push (antigravity): there is no inbox and no watch. A reply is injected into a
|
|
202
|
+
// live app-server conversation, so only an adapter probe can answer. Composing the
|
|
203
|
+
// receiver atom here would demand `watchArmed` from a backend that never arms one, and
|
|
204
|
+
// every agy citizen would report replyable:false forever.
|
|
205
|
+
// Either way an inactive/unreachable citizen STILL returns its identity (who-sent must
|
|
206
|
+
// survive; degrading to null would erase the sender) — only with replyable:false.
|
|
207
|
+
const facts = nativePushSupported(identity.backend)
|
|
208
|
+
? {
|
|
209
|
+
origin: "meta-session" as const,
|
|
210
|
+
metaDeliveryDomain: "native-push" as const,
|
|
211
|
+
recordBacked: true,
|
|
212
|
+
probeAlive: await probeNativeSenderAlive(identity),
|
|
213
|
+
}
|
|
214
|
+
: (() => {
|
|
215
|
+
const receiver = readMetaReceiverMarker({ gardenId: identity.gardenId });
|
|
216
|
+
const active = receiverMarkerMatchesIdentity(receiver, identity);
|
|
217
|
+
return {
|
|
218
|
+
origin: "meta-session" as const,
|
|
219
|
+
metaDeliveryDomain: "self-fetch" as const,
|
|
220
|
+
recordBacked: true,
|
|
221
|
+
ownerAlive: active,
|
|
222
|
+
watchArmed: active,
|
|
223
|
+
};
|
|
224
|
+
})();
|
|
225
|
+
const self = computeSelfAddressability(facts);
|
|
239
226
|
|
|
240
227
|
return {
|
|
241
228
|
sessionId: identity.gardenId,
|
|
@@ -247,13 +234,15 @@ function buildTrustedMetaSenderEnvelope(cwd: string = process.cwd()): SenderEnve
|
|
|
247
234
|
};
|
|
248
235
|
}
|
|
249
236
|
|
|
250
|
-
|
|
237
|
+
// async only for the native-push branch's adapter probe: a pi sender and a claude-code
|
|
238
|
+
// sender still resolve from files alone, so their cost is unchanged.
|
|
239
|
+
async function buildAuthoritativeSelfEnvelope(): Promise<SenderEnvelope> {
|
|
251
240
|
const sessionId = process.env.PI_SESSION_ID?.trim();
|
|
252
241
|
const agentId = process.env.PI_AGENT_ID?.trim();
|
|
253
242
|
const cwd = process.cwd();
|
|
254
243
|
if (sessionId && agentId && cwd) return buildStrictPiSenderEnvelope();
|
|
255
244
|
|
|
256
|
-
const meta = buildTrustedMetaSenderEnvelope(cwd);
|
|
245
|
+
const meta = await buildTrustedMetaSenderEnvelope(cwd);
|
|
257
246
|
if (meta) return meta;
|
|
258
247
|
|
|
259
248
|
const missing: string[] = [];
|
|
@@ -263,13 +252,13 @@ function buildAuthoritativeSelfEnvelope(): SenderEnvelope {
|
|
|
263
252
|
throw new EntwurfEnvelopeWiringError(missing);
|
|
264
253
|
}
|
|
265
254
|
|
|
266
|
-
function buildSendSenderEnvelope(): SenderEnvelope {
|
|
255
|
+
async function buildSendSenderEnvelope(): Promise<SenderEnvelope> {
|
|
267
256
|
const sessionId = process.env.PI_SESSION_ID?.trim();
|
|
268
257
|
const agentId = process.env.PI_AGENT_ID?.trim();
|
|
269
258
|
const cwd = process.cwd();
|
|
270
259
|
if (sessionId && agentId && cwd) return buildStrictPiSenderEnvelope();
|
|
271
260
|
|
|
272
|
-
const meta = buildTrustedMetaSenderEnvelope(cwd);
|
|
261
|
+
const meta = await buildTrustedMetaSenderEnvelope(cwd);
|
|
273
262
|
if (meta) return meta;
|
|
274
263
|
|
|
275
264
|
// No marker. Anonymous external is allowed ONLY when not explicitly forbidden.
|
|
@@ -364,7 +353,7 @@ server.tool(
|
|
|
364
353
|
try {
|
|
365
354
|
// Resolved ONCE so the dispatch-moment timestamp is fixed and the control RPC sender
|
|
366
355
|
// + the mailbox body sender share one envelope. No replyability gate (see above).
|
|
367
|
-
const sender = buildSendSenderEnvelope();
|
|
356
|
+
const sender = await buildSendSenderEnvelope();
|
|
368
357
|
const rendered = await runAndRenderEntwurfV2FromSurface(
|
|
369
358
|
{ target, intent, message, mode, wants_reply },
|
|
370
359
|
// agentDir / prefixRoots intentionally omitted: runAndRenderEntwurfV2FromSurface falls
|
|
@@ -389,7 +378,7 @@ server.tool(
|
|
|
389
378
|
{},
|
|
390
379
|
async () => {
|
|
391
380
|
try {
|
|
392
|
-
const sender = buildAuthoritativeSelfEnvelope();
|
|
381
|
+
const sender = await buildAuthoritativeSelfEnvelope();
|
|
393
382
|
const kst = formatKstTimestamp(sender.timestamp);
|
|
394
383
|
const extra: Record<string, string> = {};
|
|
395
384
|
const lines = [
|
|
@@ -498,6 +487,51 @@ server.tool(
|
|
|
498
487
|
},
|
|
499
488
|
);
|
|
500
489
|
|
|
490
|
+
server.tool(
|
|
491
|
+
"entwurf_register_native",
|
|
492
|
+
"Register an ALREADY-RUNNING native conversation as a garden citizen — it does NOT spawn " +
|
|
493
|
+
"a new one (that is a separate, deferred capability; do not use this to create a sibling). " +
|
|
494
|
+
"Give the backend + its native conversation id + the cwd, and this binds them to a garden id " +
|
|
495
|
+
"so entwurf_v2 can reach the conversation (fire-and-forget → native-push). The conversation " +
|
|
496
|
+
"must be LIVE: it is probed first, and a dead/indeterminate probe is refused (no garden id is " +
|
|
497
|
+
"minted for a pointer that does not resolve to a real host). Re-registering the same " +
|
|
498
|
+
"conversation attaches to the SAME garden id and refreshes the cwd. Only 'antigravity' is " +
|
|
499
|
+
"registerable on this lane. No mailbox receiver marker is written — native-push has no " +
|
|
500
|
+
"idle-wake watch; the returned garden id is the reply handle.",
|
|
501
|
+
{
|
|
502
|
+
backend: z
|
|
503
|
+
.enum(["antigravity"])
|
|
504
|
+
.describe("The native backend hosting the conversation. Only 'antigravity' — codex is a separate lane."),
|
|
505
|
+
nativeSessionId: z
|
|
506
|
+
.string()
|
|
507
|
+
.min(1)
|
|
508
|
+
.describe("The backend's native conversation id (antigravity conversationId) to bind to a garden id."),
|
|
509
|
+
cwd: z
|
|
510
|
+
.string()
|
|
511
|
+
.min(1)
|
|
512
|
+
.describe(
|
|
513
|
+
"The working directory to record for this citizen — REQUIRED (a native conversation's metadata cannot confirm it, so you must state it).",
|
|
514
|
+
),
|
|
515
|
+
},
|
|
516
|
+
async ({ backend, nativeSessionId, cwd }) => {
|
|
517
|
+
try {
|
|
518
|
+
const result = await registerNativeConversation({ backend, nativeSessionId, cwd });
|
|
519
|
+
return textOk(
|
|
520
|
+
`[entwurf register native ⟶]\n` +
|
|
521
|
+
` backend: ${result.backend}\n` +
|
|
522
|
+
` conversation: ${result.nativeSessionId}\n` +
|
|
523
|
+
` action: ${result.action}\n` +
|
|
524
|
+
` gardenId: ${result.gardenId}\n` +
|
|
525
|
+
` cwd: ${result.cwd}\n\n` +
|
|
526
|
+
`Reach it with entwurf_v2 (target=${result.gardenId}, intent=fire-and-forget). No receiver ` +
|
|
527
|
+
`marker was written (native-push has no idle-wake mailbox watch).`,
|
|
528
|
+
);
|
|
529
|
+
} catch (err) {
|
|
530
|
+
return textErr(`entwurf_register_native error: ${err instanceof Error ? err.message : String(err)}`);
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
);
|
|
534
|
+
|
|
501
535
|
// ============================================================================
|
|
502
536
|
// Main
|
|
503
537
|
// ============================================================================
|
|
@@ -44,7 +44,7 @@ python3 - <<'PY' <<<"$TOOLS_JSON"
|
|
|
44
44
|
import json, sys
|
|
45
45
|
obj=json.load(sys.stdin)
|
|
46
46
|
names={t['name'] for t in obj['result']['tools']}
|
|
47
|
-
expected={'entwurf_v2','entwurf_self','entwurf_peers','entwurf_inbox_read'}
|
|
47
|
+
expected={'entwurf_v2','entwurf_self','entwurf_peers','entwurf_inbox_read','entwurf_register_native'}
|
|
48
48
|
missing=expected-names
|
|
49
49
|
legacy={'entwurf','entwurf_resume','entwurf_send'} & names
|
|
50
50
|
if missing:
|
|
@@ -52,16 +52,36 @@
|
|
|
52
52
|
// Under an installed marketplace source below node_modules that is
|
|
53
53
|
// ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING — the SAME strip-types fence
|
|
54
54
|
// start.sh (0.12.1) and the store-doctor (0.12.4) already cross by shipping JS.
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
// dev clone). Its only repo import is meta-session.ts, ALREADY emitted for the
|
|
55
|
+
// At 0.12.5 this was the last known `.ts`-at-runtime surface; emitting it here
|
|
56
|
+
// closed that release's set (installer copies dist JS when installed, keeps
|
|
57
|
+
// `.ts` for a dev clone). Its only repo import is meta-session.ts, ALREADY emitted for the
|
|
58
58
|
// store-doctor, so this adds ONE leaf JS (dist/pi-extensions/meta-bridge-hook.js)
|
|
59
59
|
// and ZERO new deps. It is a plugin hook, never in index.ts's boot closure, so
|
|
60
60
|
// check-entwurf-bridge-pi-free is unaffected.
|
|
61
|
+
//
|
|
62
|
+
// WHY agy-imprint rides THIS build (0.12.7): the npm bin resolves to
|
|
63
|
+
// scripts/agy-imprint.sh under node_modules. Running its raw .ts there hits the
|
|
64
|
+
// same ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING fence. Emit the hook leaf as
|
|
65
|
+
// dist/scripts/agy-imprint.js; the stable bin chooses it only when installed and
|
|
66
|
+
// keeps transparent strip-types source execution in a dev clone.
|
|
67
|
+
//
|
|
68
|
+
// WHY the three OPERATOR commands ride this build (0.12.7): `entwurf` dispatches
|
|
69
|
+
// them through run.sh, whose REPO_DIR is under node_modules once installed — so
|
|
70
|
+
// they hit the same fence the imprint hook did, and shipped dead. They are not
|
|
71
|
+
// dev gates: doctor-pi-provider is the pi-ownership verdict, new-session-id is
|
|
72
|
+
// the operator alias that mints a garden citizen (docs/setup-clean-host.md tells
|
|
73
|
+
// operators to run it from the installed bin), and meta-bridge-prune is the store
|
|
74
|
+
// maintenance verb. check-*/smoke-* stay OUT on purpose — they are dev-clone-only
|
|
75
|
+
// and run_ts refuses them under an installed package rather than emitting 70+
|
|
76
|
+
// gate leaves into the tarball.
|
|
61
77
|
"include": [
|
|
62
78
|
"./src/index.ts",
|
|
63
79
|
"../../protocol.js",
|
|
64
80
|
"../../scripts/meta-bridge-store-doctor.ts",
|
|
81
|
+
"../../scripts/agy-imprint.ts",
|
|
82
|
+
"../../scripts/doctor-pi-provider.ts",
|
|
83
|
+
"../../scripts/new-session-id.ts",
|
|
84
|
+
"../../scripts/meta-bridge-prune.ts",
|
|
65
85
|
"../../pi-extensions/meta-bridge-hook.ts"
|
|
66
86
|
]
|
|
67
87
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junghanacs/entwurf",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.7",
|
|
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": {
|
|
@@ -60,7 +60,9 @@
|
|
|
60
60
|
"VERIFY.md",
|
|
61
61
|
"DELIVERY.md",
|
|
62
62
|
"CONTRIBUTING.md",
|
|
63
|
-
"CHANGELOG.md"
|
|
63
|
+
"CHANGELOG.md",
|
|
64
|
+
"!**/__pycache__",
|
|
65
|
+
"!**/*.pyc"
|
|
64
66
|
],
|
|
65
67
|
"engines": {
|
|
66
68
|
"node": ">=22.6.0"
|
|
@@ -90,11 +92,12 @@
|
|
|
90
92
|
"scripts": {
|
|
91
93
|
"prepare": "husky 2>/dev/null || true; npm run --silent build-bridge",
|
|
92
94
|
"postinstall": "node scripts/postinstall-chmod.cjs",
|
|
93
|
-
"build-bridge": "
|
|
95
|
+
"build-bridge": "bash scripts/with-dist-lock.sh bash scripts/build-bridge.sh",
|
|
94
96
|
"prepack": "pnpm --silent run build-bridge",
|
|
95
97
|
"prepublishOnly": "pnpm run check && pnpm run check-pack-install",
|
|
96
98
|
"setup": "./run.sh setup",
|
|
97
99
|
"check-shell-quote": "./run.sh check-shell-quote",
|
|
100
|
+
"check-install-surface": "./run.sh check-install-surface",
|
|
98
101
|
"check-entwurf-session-identity": "./run.sh check-entwurf-session-identity",
|
|
99
102
|
"check-meta-session": "./run.sh check-meta-session",
|
|
100
103
|
"check-meta-record-v2": "./run.sh check-meta-record-v2",
|
|
@@ -119,7 +122,7 @@
|
|
|
119
122
|
"format": "biome check --write .",
|
|
120
123
|
"check-meta-manifest-schema": "./run.sh check-meta-manifest-schema",
|
|
121
124
|
"check-package-source-routing": "./run.sh check-package-source-routing",
|
|
122
|
-
"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-user-scope-citizen && ./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"
|
|
125
|
+
"check": "pnpm lint && pnpm typecheck && ./run.sh check-shell-quote && ./run.sh check-install-surface && ./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-agy-install-state && ./run.sh smoke-agy-statusline-state && ./run.sh smoke-agy-hooks-state && ./run.sh smoke-pi-provider-state && ./run.sh smoke-user-scope-citizen && ./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-native-push && ./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-native-push-adapter && ./run.sh check-native-push-register && ./run.sh check-agy-sender-identity && ./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
126
|
},
|
|
124
127
|
"pi": {
|
|
125
128
|
"extensions": [
|
|
@@ -132,7 +135,9 @@
|
|
|
132
135
|
"bin": {
|
|
133
136
|
"entwurf": "./run.sh",
|
|
134
137
|
"entwurf-bridge": "./mcp/entwurf-bridge/start.sh",
|
|
135
|
-
"entwurf-statusline": "./scripts/meta-bridge-statusline.sh"
|
|
138
|
+
"entwurf-statusline": "./scripts/meta-bridge-statusline.sh",
|
|
139
|
+
"entwurf-agy-statusline": "./scripts/agy-statusline.sh",
|
|
140
|
+
"entwurf-agy-imprint": "./scripts/agy-imprint.sh"
|
|
136
141
|
},
|
|
137
142
|
"peerDependenciesMeta": {
|
|
138
143
|
"@earendil-works/pi-ai": {
|
|
@@ -3,20 +3,34 @@
|
|
|
3
3
|
* (SE-1/SE-2 slice 2c). "If I enqueue a conversational reply to this target's mailbox
|
|
4
4
|
* right now, will a model actually see it — or will it rot as garbage?"
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* Three predicates, all pure (facts injected, no IO) — but on TWO different delivery
|
|
7
|
+
* axes that must never be collapsed into one (보정①):
|
|
7
8
|
*
|
|
8
9
|
* - computeMetaReceiverActive(facts): the shared "is this receiver active?" atom —
|
|
9
10
|
* recordBacked AND ownerAlive AND watchArmed. This is the SAME conjunction the
|
|
10
11
|
* self-addressability predicate uses for its meta branch; both import it so the
|
|
11
12
|
* "active receiver" definition has ONE source of truth (concept shared, API split).
|
|
13
|
+
* `watchArmed` is the MAILBOX-ONLY "idle-wake watch armed" signal (meta-session
|
|
14
|
+
* receiver marker) — it belongs to the mailbox axis and nothing else.
|
|
12
15
|
*
|
|
13
16
|
* - mailboxConversationalDeliverable(facts): the enqueue gate. A mailbox enqueue +
|
|
14
|
-
* doorbell only delivers for a SELF-FETCH backend (Claude Code
|
|
15
|
-
*
|
|
16
|
-
* mailbox drain at all — enqueuing for it is the SE-1 false success
|
|
17
|
-
* into a void). So deliverable = wakeMode === "self-fetch" AND the
|
|
18
|
-
* active. This is the guard that the v1 fallback, MCP v1, pi-native v1,
|
|
19
|
-
* decider/send-fallback enqueue sites must all pass before writing a
|
|
17
|
+
* doorbell only delivers for a SELF-FETCH backend (Claude Code): the receiver
|
|
18
|
+
* drains its own inbox on wake. A DIRECT-INJECT backend (pi / codex / antigravity)
|
|
19
|
+
* has no mailbox drain at all — enqueuing for it is the SE-1 false success
|
|
20
|
+
* ("✓ delivered" into a void). So deliverable = wakeMode === "self-fetch" AND the
|
|
21
|
+
* receiver is active. This is the guard that the v1 fallback, MCP v1, pi-native v1,
|
|
22
|
+
* and the v2 decider/send-fallback enqueue sites must all pass before writing a
|
|
23
|
+
* .msg (slice 2d).
|
|
24
|
+
*
|
|
25
|
+
* - nativePushDeliverable(facts): the SEPARATE deliverability predicate for a
|
|
26
|
+
* NATIVE-PUSH backend (antigravity). A native-push citizen has no mailbox and no
|
|
27
|
+
* idle-wake watch — delivery is a direct injection into a LIVE app-server
|
|
28
|
+
* conversation the adapter probe located. So deliverable = recordBacked AND
|
|
29
|
+
* probeAlive. It MUST NOT reuse computeMetaReceiverActive: that atom folds in
|
|
30
|
+
* `watchArmed`, so composing it here would smuggle a mailbox liveness fact into a
|
|
31
|
+
* domain that has no mailbox (보정① — native-push replyable ≠ mailbox receiver).
|
|
32
|
+
* The two axes are pinned apart in code so a future replyable-sender path cannot
|
|
33
|
+
* quietly collapse them.
|
|
20
34
|
*
|
|
21
35
|
* The contract is "mailboxConversationalDeliverable", NOT a broad "deliverable": it is
|
|
22
36
|
* specifically about a conversational reply that needs a live doorbell wake, NOT about
|
|
@@ -103,8 +117,10 @@ export interface MailboxDeliverabilityResult {
|
|
|
103
117
|
|
|
104
118
|
/**
|
|
105
119
|
* The conversational-mailbox enqueue gate. False (no enqueue) unless the backend is
|
|
106
|
-
* self-fetch AND the receiver is active. A direct-inject backend (pi
|
|
107
|
-
* outright — it has no mailbox drain, so an enqueue would be a
|
|
120
|
+
* self-fetch AND the receiver is active. A direct-inject backend (pi / codex /
|
|
121
|
+
* antigravity) is refused outright — it has no mailbox drain, so an enqueue would be a
|
|
122
|
+
* silent false success. A native-push backend's live delivery goes through
|
|
123
|
+
* nativePushDeliverable, not this gate.
|
|
108
124
|
*/
|
|
109
125
|
export function mailboxConversationalDeliverable(facts: MailboxDeliverabilityFacts): MailboxDeliverabilityResult {
|
|
110
126
|
if (facts.wakeMode !== "self-fetch") {
|
|
@@ -121,3 +137,40 @@ export function mailboxConversationalDeliverable(facts: MailboxDeliverabilityFac
|
|
|
121
137
|
: `self-fetch receiver inactive — ${recv.reason}`,
|
|
122
138
|
};
|
|
123
139
|
}
|
|
140
|
+
|
|
141
|
+
// ── native-push deliverability (봉인 6) ──────────────────────────────────────
|
|
142
|
+
// A DISTINCT axis from the mailbox one above. Native-push (antigravity) has no
|
|
143
|
+
// mailbox and no idle-wake watch: delivery is a direct injection into a live
|
|
144
|
+
// app-server conversation the adapter probe found. Kept in this file so the two
|
|
145
|
+
// deliverability predicates sit side by side and their axis separation is visible,
|
|
146
|
+
// but sharing NOTHING with computeMetaReceiverActive (which requires watchArmed).
|
|
147
|
+
|
|
148
|
+
export interface NativePushDeliverabilityFacts {
|
|
149
|
+
/** The target garden id is backed by a live meta-record (identity exists). */
|
|
150
|
+
recordBacked?: boolean;
|
|
151
|
+
/** An adapter probe found the target's live native conversation (route resolved). */
|
|
152
|
+
probeAlive?: boolean;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface NativePushDeliverabilityResult {
|
|
156
|
+
deliverable: boolean;
|
|
157
|
+
reason: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The native-push deliverability predicate (봉인 6). deliverable ⟺ recordBacked ∧
|
|
162
|
+
* probeAlive. This DELIBERATELY does NOT reuse computeMetaReceiverActive — that atom
|
|
163
|
+
* requires `watchArmed`, the mailbox-only "idle-wake watch armed" signal, which is
|
|
164
|
+
* meaningless for a backend with no mailbox (보정①). Fail-closed: an undefined fact
|
|
165
|
+
* is treated as false, never optimistic; each failure names its own cause so a
|
|
166
|
+
* record-less target is never conflated with a probe that found no live conversation.
|
|
167
|
+
*/
|
|
168
|
+
export function nativePushDeliverable(facts: NativePushDeliverabilityFacts): NativePushDeliverabilityResult {
|
|
169
|
+
if (facts.recordBacked !== true) {
|
|
170
|
+
return { deliverable: false, reason: "no backing meta-record" };
|
|
171
|
+
}
|
|
172
|
+
if (facts.probeAlive !== true) {
|
|
173
|
+
return { deliverable: false, reason: "adapter probe found no live native conversation" };
|
|
174
|
+
}
|
|
175
|
+
return { deliverable: true, reason: "record backed, native conversation probed alive" };
|
|
176
|
+
}
|