@junghanacs/entwurf 0.41.2 → 0.50.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 +4 -2
- package/BASELINE.md +110 -1
- package/CHANGELOG.md +328 -0
- package/CONTRIBUTING.md +1 -1
- package/ENTWURF-CLI.json +1229 -0
- package/ENTWURF.json +284 -0
- package/FAQ.md +4 -4
- package/README.md +125 -184
- package/VERIFY.md +2 -2
- package/docs/external-mcp-host.md +3 -3
- package/docs/setup-clean-host.md +37 -12
- package/mcp/entwurf-bridge/dist/ENTWURF.json +284 -0
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +46 -196
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/agent-guidance.js +299 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +21 -25
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +498 -29
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +47 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +613 -33
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-resume-call.js +497 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +3 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +23 -20
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +68 -21
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +7 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-location.js +347 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-owner-fence.js +1132 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js +1 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js +7 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +18 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +53 -60
- package/mcp/entwurf-bridge/dist/scripts/herdr-rails.mjs +40 -7
- package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +1 -27
- package/mcp/entwurf-bridge/dist/scripts/meta-bridge-prune.js +1 -1
- package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +1 -1
- package/mcp/entwurf-bridge/dist/scripts/peer-facts.js +1 -1
- package/mcp/entwurf-bridge/src/index.ts +149 -327
- package/mcp/entwurf-bridge/tsconfig.build.json +7 -1
- package/native/win32-x64/manifest.json +1 -1
- package/package.json +8 -4
- package/pi/pi-durable/bootstrap.mjs +179 -6
- package/pi/pi-durable/carrier/runtime.js +108 -9
- package/pi/pi-durable/carrier/session-name.js +11 -0
- package/pi/pi-durable/carrier/sessions.js +96 -20
- package/pi/pi-durable/overlay/upstream-pin.json +11 -5
- package/pi-extensions/lib/agent-guidance.ts +341 -0
- package/pi-extensions/lib/entwurf-v2-surface.ts +25 -31
- package/pi-extensions/lib/entwurf-v2-visible-resume.ts +725 -54
- package/pi-extensions/lib/fresh-call-dispatch.ts +64 -12
- package/pi-extensions/lib/herdr-fresh-call.ts +919 -40
- package/pi-extensions/lib/herdr-resume-call.ts +810 -0
- package/pi-extensions/lib/meta-session.ts +13 -8
- package/pi-extensions/lib/mux-fresh-call.ts +23 -26
- package/pi-extensions/lib/mux-resume-call.ts +95 -23
- package/pi-extensions/lib/pi-durable-fresh-preflight.ts +9 -2
- package/pi-extensions/lib/pi-durable-location.ts +446 -0
- package/pi-extensions/lib/pi-durable-owner-fence.ts +1461 -0
- package/pi-extensions/lib/pi-durable-runtime.ts +4 -0
- package/pi-extensions/lib/remote-pair-send.ts +7 -4
- package/pi-extensions/lib/resume-launch-identity.ts +31 -12
- package/pi-extensions/meta-bridge-pi-durable.ts +52 -79
- package/run.sh +417 -654
- package/scripts/build-bridge.sh +10 -0
- package/scripts/check-capability-bundle-reach.ts +86 -0
- package/scripts/check-claude-launch.ts +341 -0
- package/scripts/check-entwurf-self-address.ts +3 -3
- package/scripts/check-entwurf-v2-surface.ts +94 -98
- package/scripts/check-fresh-call-dispatch.ts +70 -9
- package/scripts/check-fresh-cut-gate.sh +23 -9
- package/scripts/check-gate-qualification.ts +24 -17
- package/scripts/check-herdr-activation.ts +301 -23
- package/scripts/check-herdr-fresh-call.ts +161 -25
- package/scripts/check-herdr-plugin-build.ts +173 -1
- package/scripts/check-herdr-plugin.ts +28 -2
- package/scripts/check-herdr-runtime-bootstrap.ts +136 -0
- package/scripts/check-install-surface.ts +1236 -6
- package/scripts/check-meta-doctor-oracle.sh +1 -1
- package/scripts/check-meta-facts.ts +20 -1
- package/scripts/check-mux-resume-call.ts +48 -3
- package/scripts/check-peer-facts.ts +39 -2
- package/scripts/cli-help.mjs +222 -0
- package/scripts/dev-bin.sh +39 -2
- package/scripts/herdr-activation.mjs +28 -6
- package/scripts/herdr-operator-link.mjs +389 -0
- package/scripts/herdr-plugin-activate.mjs +5 -0
- package/scripts/herdr-plugin-deactivate.mjs +18 -5
- package/scripts/herdr-rails.mjs +43 -7
- package/scripts/herdr-runtime.mjs +27 -0
- package/scripts/meta-bridge-fresh-cut.ts +1 -27
- package/scripts/meta-bridge-prune.ts +1 -1
- package/scripts/meta-facts.ts +1 -1
- package/scripts/mutants/agent-guidance.json +160 -0
- package/scripts/mutants/bridge-boot-resume.json +6 -8
- package/scripts/mutants/capability-bundle-reach.json +124 -0
- package/scripts/mutants/claude-launch.json +142 -0
- package/scripts/mutants/cli-help-source.json +420 -0
- package/scripts/mutants/control-send-receipt.json +21 -4
- package/scripts/mutants/dev-gate-dispatch.json +109 -0
- package/scripts/mutants/fresh-call-dispatch.json +38 -2
- package/scripts/mutants/fresh-cut.json +15 -0
- package/scripts/mutants/herdr-activation.json +128 -0
- package/scripts/mutants/herdr-fresh-call.json +278 -5
- package/scripts/mutants/herdr-operator-link.json +318 -0
- package/scripts/mutants/herdr-plugin-build.json +55 -0
- package/scripts/mutants/herdr-plugin.json +17 -0
- package/scripts/mutants/herdr-resume-call.json +288 -0
- package/scripts/mutants/herdr-runtime-bootstrap.json +48 -0
- package/scripts/mutants/meta-facts.json +15 -0
- package/scripts/mutants/mux-fresh-call.json +14 -3
- package/scripts/mutants/mux-resume-call.json +89 -3
- package/scripts/mutants/peer-facts.json +15 -0
- package/scripts/mutants/pi-durable.json +996 -6
- package/scripts/mutants/remote-pair-send.json +21 -2
- package/scripts/mutants/self-address.json +10 -10
- package/scripts/mutants/source-install.json +35 -0
- package/scripts/mutants/v2-surface.json +50 -64
- package/scripts/mutants/v2-visible-resume.json +266 -6
- package/scripts/peer-facts.ts +1 -1
- package/scripts/smoke-agy-install-state.sh +66 -0
- package/scripts/smoke-meta-prune.sh +23 -0
- package/scripts/smoke-omp-fresh-live.ts +12 -3
- package/scripts/smoke-omp-receive-live.ts +7 -1
- package/scripts/smoke-pi-durable-fresh-live.ts +12 -2
- package/team.json.example +39 -0
|
@@ -47,6 +47,7 @@ export function piDurableLayoutAt(root) {
|
|
|
47
47
|
adapter: path.join(dist, "pi-extensions", "meta-bridge-pi-durable.js"),
|
|
48
48
|
bridgeEntry: path.join(dist, "mcp", "entwurf-bridge", "src", "index.js"),
|
|
49
49
|
deliverability: path.join(dist, "pi-extensions", "lib", "entwurf-deliverability.js"),
|
|
50
|
+
ownerFence: path.join(dist, "pi-extensions", "lib", "pi-durable-owner-fence.js"),
|
|
50
51
|
};
|
|
51
52
|
}
|
|
52
53
|
/**
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import * as fs from "node:fs";
|
|
27
27
|
import * as path from "node:path";
|
|
28
|
+
import { ENTWURF_DELIVERY_RESULT_CLAUSES } from "./agent-guidance.js";
|
|
28
29
|
import { controlSocketPathIn } from "./control-socket-path.js";
|
|
29
30
|
import { defaultMetaSessionsDir, metaRecordExistsByGardenId, requireGardenId } from "./meta-session.js";
|
|
30
31
|
import { inspectControlSocketPath } from "./socket-discovery.js";
|
|
@@ -204,13 +205,15 @@ export function classifyRemoteReceipt(r) {
|
|
|
204
205
|
}
|
|
205
206
|
return indeterminate(`exit ${r.exitCode} disagrees with its receipt`);
|
|
206
207
|
}
|
|
208
|
+
// The three static explanatory clauses are authored in root ENTWURF.json (#154 E1a); the receipt
|
|
209
|
+
// syntax, the route label, the slots and the refused/invalid lines stay here.
|
|
207
210
|
function render(route, o) {
|
|
208
211
|
const via = `via ${route.ssh}`;
|
|
209
212
|
switch (o.kind) {
|
|
210
213
|
case "remote-accepted":
|
|
211
214
|
return {
|
|
212
215
|
isError: false,
|
|
213
|
-
text: `entwurf_v2 remote ${via} → accepted
|
|
216
|
+
text: `entwurf_v2 remote ${via} → ${ENTWURF_DELIVERY_RESULT_CLAUSES["remote-accepted"]}\n${o.text}`,
|
|
214
217
|
};
|
|
215
218
|
case "remote-reported-error":
|
|
216
219
|
// The other host's dispatch returned isError. That can follow a write (an enqueue whose
|
|
@@ -218,7 +221,7 @@ function render(route, o) {
|
|
|
218
221
|
// non-delivery nor safety to resend: the other host's own text below is the fact.
|
|
219
222
|
return {
|
|
220
223
|
isError: true,
|
|
221
|
-
text: `entwurf_v2 remote ${via} →
|
|
224
|
+
text: `entwurf_v2 remote ${via} → ${ENTWURF_DELIVERY_RESULT_CLAUSES["remote-reported-error"]}\n${o.text}`,
|
|
222
225
|
};
|
|
223
226
|
case "remote-pair-refused":
|
|
224
227
|
return {
|
|
@@ -230,8 +233,8 @@ function render(route, o) {
|
|
|
230
233
|
case "remote-indeterminate":
|
|
231
234
|
return {
|
|
232
235
|
isError: true,
|
|
233
|
-
text: `entwurf_v2 remote ${via} → indeterminate: ${o.why}.
|
|
234
|
-
|
|
236
|
+
text: `entwurf_v2 remote ${via} → indeterminate: ${o.why}. ` +
|
|
237
|
+
`${ENTWURF_DELIVERY_RESULT_CLAUSES["remote-indeterminate"]}${o.stderrTail ? `\n${o.stderrTail}` : ""}`,
|
|
235
238
|
};
|
|
236
239
|
}
|
|
237
240
|
}
|
|
@@ -10,13 +10,15 @@
|
|
|
10
10
|
* must equal `record.nativeSessionId`, an addressable read (#52) rather than a plain targeted one,
|
|
11
11
|
* a recorded ACP provider must resolve its bridge.
|
|
12
12
|
*
|
|
13
|
-
* The domain is pi and claude-code (#147 L2)
|
|
14
|
-
* record when
|
|
15
|
-
* the transcript header (the launch needs its provider/model/cwd). For
|
|
16
|
-
* transcript at all — the vendor owns it, and `claude --resume <id>` needs
|
|
17
|
-
* checks only that the recorded path is an absolute file that still exists.
|
|
18
|
-
*
|
|
19
|
-
*
|
|
13
|
+
* The domain is pi and claude-code (#147 L2), and pi-durable (#157 B3). Each owns its transcript
|
|
14
|
+
* and re-attaches to the same record when its native session reopens; this leaf only names that
|
|
15
|
+
* session. For pi it reads the transcript header (the launch needs its provider/model/cwd). For
|
|
16
|
+
* claude-code it reads no transcript at all — the vendor owns it, and `claude --resume <id>` needs
|
|
17
|
+
* nothing from it — and checks only that the recorded path is an absolute file that still exists.
|
|
18
|
+
* For pi-durable it reads nothing beyond the record: the parent's reservation re-reads the record
|
|
19
|
+
* and its location inside its own holds, and the launched child selects its session from the record
|
|
20
|
+
* itself, so this leaf only says WHICH backend the address names. Every other backend is a named
|
|
21
|
+
* refusal: native-push citizens have no resume authority (AGENTS Hard Rule 10).
|
|
20
22
|
*
|
|
21
23
|
* It takes a bare garden id, not a plan: the plan type it used to destructure belonged to
|
|
22
24
|
* the removed transport, and the identity question never needed the rest of it.
|
|
@@ -27,7 +29,7 @@ import { AcpModelNotCuratedError, isCuratedAcpModelId, PROVIDER_ID } from "./acp
|
|
|
27
29
|
import { getEntwurfExplicitExtensions, readSessionIdentity } from "./entwurf-core.js";
|
|
28
30
|
import { readAddressableMetaIdentity } from "./meta-session.js";
|
|
29
31
|
/** The backends with a visible same-id resume. A closed set: nothing outside it is reopened. */
|
|
30
|
-
export const RESUME_BACKENDS = ["pi", "claude-code"];
|
|
32
|
+
export const RESUME_BACKENDS = ["pi", "claude-code", "pi-durable"];
|
|
31
33
|
/**
|
|
32
34
|
* One of the TWO expected refusals on this leaf: the target is a citizen of a backend that has no
|
|
33
35
|
* same-id resume at all. The other is `AcpModelNotCuratedError` (#128, `acp/model-ids.js`).
|
|
@@ -38,8 +40,7 @@ export const RESUME_BACKENDS = ["pi", "claude-code"];
|
|
|
38
40
|
* except by looking. A Claude Code or agy garden id is different in kind: nothing is broken,
|
|
39
41
|
* the operator simply asked a capability boundary to do something it does not cover, and the
|
|
40
42
|
* honest answer is a named refusal rather than an error report about a store that is fine. Since
|
|
41
|
-
* #147 L2 the boundary is `RESUME_BACKENDS`; a Codex, agy, Copilot
|
|
42
|
-
* this refusal.
|
|
43
|
+
* #147 L2 the boundary is `RESUME_BACKENDS`; a Codex, agy, Copilot or OMP garden id is this refusal.
|
|
43
44
|
*
|
|
44
45
|
* It carries a `reason` so the caller matches on a FIELD. Parsing the message string would make
|
|
45
46
|
* the wording load-bearing, and re-reading the record to ask "was it pi?" would read an address
|
|
@@ -50,7 +51,7 @@ export class ResumeBackendUnsupportedError extends Error {
|
|
|
50
51
|
backend;
|
|
51
52
|
constructor(gardenId, backend) {
|
|
52
53
|
super(`resume-launch-identity: ${gardenId} is a ${backend} citizen — same-id resume is a host-adapter relaunch ` +
|
|
53
|
-
`capability, and its domain is ${RESUME_BACKENDS.join("
|
|
54
|
+
`capability, and its domain is ${RESUME_BACKENDS.join(", ")}. ` +
|
|
54
55
|
`This is a capability boundary, not citizen rank.`);
|
|
55
56
|
this.name = "ResumeBackendUnsupportedError";
|
|
56
57
|
this.backend = backend;
|
|
@@ -128,6 +129,12 @@ export function resolveResumeLaunchIdentity(gardenId) {
|
|
|
128
129
|
const record = readAddressableMetaIdentity(gardenId);
|
|
129
130
|
if (record.backend === "claude-code")
|
|
130
131
|
return resolveClaudeCodeLaunchIdentity(gardenId, record);
|
|
132
|
+
if (record.backend === "pi-durable") {
|
|
133
|
+
if (!path.isAbsolute(record.cwd)) {
|
|
134
|
+
throw new Error(`resume-launch-identity: ${gardenId} records a relative cwd ${JSON.stringify(record.cwd)}.`);
|
|
135
|
+
}
|
|
136
|
+
return { backend: "pi-durable", nativeSessionId: record.nativeSessionId, cwd: record.cwd };
|
|
137
|
+
}
|
|
131
138
|
if (record.backend !== "pi") {
|
|
132
139
|
throw new ResumeBackendUnsupportedError(gardenId, record.backend);
|
|
133
140
|
}
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* createPiDurableContact({ bridgeEntry })
|
|
11
11
|
* .extension tools-only durable Extension, handed to the overlay's
|
|
12
12
|
* `openDurable({ extensions })` BEFORE the Harness opens
|
|
13
|
-
* .attach(session) once `openDurable` returns:
|
|
13
|
+
* .attach(session) once `openDurable` returns: commit the record's binding and its
|
|
14
|
+
* location evidence (`lib/pi-durable-location.ts`), name this process as
|
|
14
15
|
* sender, spawn the compiled entwurf-bridge as a DIRECT child, then
|
|
15
16
|
* release the tools
|
|
16
17
|
*
|
|
@@ -93,17 +94,21 @@
|
|
|
93
94
|
* refusal.
|
|
94
95
|
*
|
|
95
96
|
* CALLER FRESH (the sixth tool). `entwurf_fresh_call` lets this durable citizen open a sibling, under the
|
|
96
|
-
* bridge's own schema (pinned below, including the `pi-durable` backend itself).
|
|
97
|
-
* beside the tmux pane this host was launched in, which a fresh-opened durable host inherits
|
|
98
|
-
* operator host started outside tmux gets the bridge's named `no-tmux-context` refusal.
|
|
99
|
-
*
|
|
97
|
+
* bridge's own schema (pinned below, including the `pi-durable` backend itself). Outside herdr, the bridge
|
|
98
|
+
* places it beside the tmux pane this host was launched in, which a fresh-opened durable host inherits,
|
|
99
|
+
* and an operator host started outside tmux gets the bridge's named `no-tmux-context` refusal. Inside
|
|
100
|
+
* herdr (`HERDR_ENV=1`, which the bridge child inherits from this host) it requests a new herdr tab
|
|
101
|
+
* instead, for pi, claude-code or pi-durable only; the other backends are refused by name, and nothing
|
|
102
|
+
* falls back to tmux. Exposing the tool is not a caller LIVE proof.
|
|
100
103
|
*
|
|
101
104
|
* CALLER RESUME (the seventh tool, #147 L1). `entwurf_resume_call` lets this durable citizen reopen a
|
|
102
|
-
* DORMANT pi or Claude Code citizen under its own garden id (#147 L2
|
|
103
|
-
* single-`target` schema (pinned below). The bridge decides everything
|
|
104
|
-
* exactly as for any other caller: other backends have no same-id
|
|
105
|
-
*
|
|
106
|
-
*
|
|
105
|
+
* DORMANT pi, pi-durable or Claude Code citizen under its own garden id (#147 L2; pi-durable since
|
|
106
|
+
* #157 B3), under the bridge's own single-`target` schema (pinned below). The bridge decides everything
|
|
107
|
+
* else from the target's record, exactly as for any other caller: other backends have no same-id
|
|
108
|
+
* resume, a session a live process holds is refused, and outside herdr the window opens in the tmux
|
|
109
|
+
* session this host was launched in. Inside herdr a pi, pi-durable or Claude Code target reopens, in a
|
|
110
|
+
* new herdr tab (Claude Code since #157 stage 4). Exposing the tool names no new authority and is not a caller LIVE proof; it never resumes
|
|
111
|
+
* this durable host itself.
|
|
107
112
|
*
|
|
108
113
|
* NOT HERE: crash-safe receive. `entwurf_inbox_read` archives a body before the durable tool
|
|
109
114
|
* result commits, so a crash between the two loses that body to the model; recovery settles the
|
|
@@ -114,7 +119,9 @@ import * as fs from "node:fs";
|
|
|
114
119
|
import * as path from "node:path";
|
|
115
120
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
116
121
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
117
|
-
import {
|
|
122
|
+
import { ENTWURF_TOOL_GUIDANCE } from "./lib/agent-guidance.js";
|
|
123
|
+
import { defaultMetaMailboxDir, defaultMetaReceiversDir, defaultMetaSendersDir, defaultMetaSessionsDir, listAllMetaIdentitiesDir, readAddressableMetaIdentity, readMetaReceiverMarker, readMetaSenderMarker, requireGardenId, writeMetaReceiverMarker, writeMetaSenderMarker, } from "./lib/meta-session.js";
|
|
124
|
+
import { commitPiDurableBirth } from "./lib/pi-durable-location.js";
|
|
118
125
|
/** The backend id this contact mints under. */
|
|
119
126
|
export const PI_DURABLE_BACKEND = "pi-durable";
|
|
120
127
|
/**
|
|
@@ -162,26 +169,26 @@ export const PI_DURABLE_TOOL_SCHEMAS = {
|
|
|
162
169
|
target: {
|
|
163
170
|
type: "string",
|
|
164
171
|
minLength: 1,
|
|
165
|
-
description: "
|
|
172
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_v2.parameters["target"],
|
|
166
173
|
},
|
|
167
174
|
intent: {
|
|
168
175
|
type: "string",
|
|
169
176
|
enum: ["fire-and-forget"],
|
|
170
|
-
description:
|
|
177
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_v2.parameters["intent"],
|
|
171
178
|
},
|
|
172
179
|
message: {
|
|
173
180
|
type: "string",
|
|
174
181
|
minLength: 1,
|
|
175
182
|
maxLength: 16000,
|
|
176
|
-
description:
|
|
183
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_v2.parameters["message"],
|
|
177
184
|
},
|
|
178
185
|
mode: {
|
|
179
|
-
description:
|
|
186
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_v2.parameters["mode"],
|
|
180
187
|
type: "string",
|
|
181
188
|
enum: ["steer", "follow_up"],
|
|
182
189
|
},
|
|
183
190
|
wants_reply: {
|
|
184
|
-
description: "
|
|
191
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_v2.parameters["wants_reply"],
|
|
185
192
|
type: "boolean",
|
|
186
193
|
},
|
|
187
194
|
},
|
|
@@ -194,7 +201,7 @@ export const PI_DURABLE_TOOL_SCHEMAS = {
|
|
|
194
201
|
gardenId: {
|
|
195
202
|
type: "string",
|
|
196
203
|
minLength: 1,
|
|
197
|
-
description:
|
|
204
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_inbox_read.parameters["gardenId"],
|
|
198
205
|
},
|
|
199
206
|
},
|
|
200
207
|
required: ["gardenId"],
|
|
@@ -207,32 +214,32 @@ export const PI_DURABLE_TOOL_SCHEMAS = {
|
|
|
207
214
|
backend: {
|
|
208
215
|
type: "string",
|
|
209
216
|
enum: ["pi", "claude-code", "copilot", "omp", "codex", "pi-durable"],
|
|
210
|
-
description:
|
|
217
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_fresh_call.parameters["backend"],
|
|
211
218
|
},
|
|
212
219
|
model: {
|
|
213
220
|
type: "string",
|
|
214
221
|
minLength: 1,
|
|
215
222
|
maxLength: 200,
|
|
216
223
|
pattern: "^[A-Za-z0-9][A-Za-z0-9._/:\\[\\]-]*$",
|
|
217
|
-
description:
|
|
224
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_fresh_call.parameters["model"],
|
|
218
225
|
},
|
|
219
226
|
task: {
|
|
220
227
|
type: "string",
|
|
221
228
|
minLength: 1,
|
|
222
229
|
maxLength: 16000,
|
|
223
|
-
description:
|
|
230
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_fresh_call.parameters["task"],
|
|
224
231
|
},
|
|
225
232
|
cwd: {
|
|
226
|
-
description:
|
|
233
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_fresh_call.parameters["cwd"],
|
|
227
234
|
type: "string",
|
|
228
235
|
},
|
|
229
236
|
placement: {
|
|
230
|
-
description:
|
|
237
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_fresh_call.parameters["placement"],
|
|
231
238
|
type: "object",
|
|
232
239
|
properties: {
|
|
233
240
|
tmuxSession: {
|
|
234
241
|
type: "string",
|
|
235
|
-
description:
|
|
242
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_fresh_call.parameters["placement.tmuxSession"],
|
|
236
243
|
},
|
|
237
244
|
},
|
|
238
245
|
required: ["tmuxSession"],
|
|
@@ -248,7 +255,7 @@ export const PI_DURABLE_TOOL_SCHEMAS = {
|
|
|
248
255
|
type: "string",
|
|
249
256
|
minLength: 1,
|
|
250
257
|
pattern: "^\\d{8}T\\d{6}-[0-9a-f]{6}$",
|
|
251
|
-
description:
|
|
258
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_resume_call.parameters["target"],
|
|
252
259
|
},
|
|
253
260
|
},
|
|
254
261
|
required: ["target"],
|
|
@@ -261,7 +268,7 @@ export const PI_DURABLE_TOOL_SCHEMAS = {
|
|
|
261
268
|
type: "string",
|
|
262
269
|
minLength: 1,
|
|
263
270
|
pattern: "^\\d{8}T\\d{6}-[0-9a-f]{6}$",
|
|
264
|
-
description: "
|
|
271
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_retire_call.parameters["target"],
|
|
265
272
|
},
|
|
266
273
|
},
|
|
267
274
|
required: ["target"],
|
|
@@ -458,37 +465,14 @@ export class PiDurableIdentityNotReady extends Error {
|
|
|
458
465
|
/** The receiver marker's owner kind: the durable host process, which holds the watch AND keys the sender marker. */
|
|
459
466
|
export const PI_DURABLE_RECEIVER_OWNER_KIND = "pi-durable-host";
|
|
460
467
|
const TOOL_DESCRIPTIONS = {
|
|
461
|
-
entwurf_self:
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
entwurf_callback: "Call home once, before a fresh sibling's task: the entwurf-bridge reads the caller's garden id and " +
|
|
470
|
-
"nonce from this host's launch environment and delivers the nonce to that caller. No arguments. Refuses by " +
|
|
471
|
-
"name when this host was not launched with a callback pair.",
|
|
472
|
-
entwurf_fresh_call: "Open ONE fresh visible sibling beside this durable host through the entwurf-bridge and hand it a first task " +
|
|
473
|
-
"(backend, model and task required; optional cwd and placement). Returns the bridge's LAUNCH receipt or its " +
|
|
474
|
-
"named refusal — not a started runtime, a delivered task or a reply; the sibling's callback names it. Opening " +
|
|
475
|
-
"beside this host needs the tmux pane this host itself was launched in.",
|
|
476
|
-
entwurf_resume_call: "Reopen ONE dormant pi or Claude Code citizen under its own garden id in a visible tmux window through the " +
|
|
477
|
-
"entwurf-bridge (target required; the record supplies the session and cwd — no task, no prompt, no turn). " +
|
|
478
|
-
"Returns the bridge's LAUNCH and OBSERVATION receipts or its named refusal: a live target, another backend, " +
|
|
479
|
-
"or a Claude Code citizen not proven dormant is refused by name, and opening the window needs the tmux " +
|
|
480
|
-
"session this host itself was launched in.",
|
|
481
|
-
entwurf_retire_call: "Only when the human operator explicitly requested this particular target/work: make ONE retirement attempt for that request " +
|
|
482
|
-
"through the bridge (target only, Pi or Claude Code; self retirement refuses). Development work, a role or " +
|
|
483
|
-
"a record-backed actor is NOT a grant. Use host approval where provided/configured; " +
|
|
484
|
-
"Pi and pi-durable have no built-in per-call approval prompt by default. An explicit operator request and " +
|
|
485
|
-
"agent operating policy are required; attribution and MCP annotations are hints, not grants or proof of approval. " +
|
|
486
|
-
"No autonomous cleanup, timers or automatic " +
|
|
487
|
-
"retry of unknown, cancelled or lost results. Do not manually /resume or /clear the target during the request. " +
|
|
488
|
-
"Pi shutdown-requested is only an ACK, not process exit, flush or model silence. Claude Code exited is that " +
|
|
489
|
-
"fd-pinned process only, not conversation end, children or flush. The JSON receipt keeps outcome, record and " +
|
|
490
|
-
"release separate; receipt return does not mean exit, and cancellation does not undo an action. Invoke once " +
|
|
491
|
-
"per explicitly named target, never infer a roster. This contact does not turn hints into authorization.",
|
|
468
|
+
entwurf_self: ENTWURF_TOOL_GUIDANCE.entwurf_self.descriptions.durable,
|
|
469
|
+
entwurf_peers: ENTWURF_TOOL_GUIDANCE.entwurf_peers.descriptions.durable,
|
|
470
|
+
entwurf_v2: ENTWURF_TOOL_GUIDANCE.entwurf_v2.descriptions.durable,
|
|
471
|
+
entwurf_inbox_read: ENTWURF_TOOL_GUIDANCE.entwurf_inbox_read.descriptions.durable,
|
|
472
|
+
entwurf_callback: ENTWURF_TOOL_GUIDANCE.entwurf_callback.descriptions.durable,
|
|
473
|
+
entwurf_fresh_call: ENTWURF_TOOL_GUIDANCE.entwurf_fresh_call.descriptions.durable,
|
|
474
|
+
entwurf_resume_call: ENTWURF_TOOL_GUIDANCE.entwurf_resume_call.descriptions.durable,
|
|
475
|
+
entwurf_retire_call: ENTWURF_TOOL_GUIDANCE.entwurf_retire_call.descriptions.durable,
|
|
492
476
|
};
|
|
493
477
|
/** Announce, never push: the notice names the garden and the tool, never the body. */
|
|
494
478
|
function doorbellNotice(gardenId, unread) {
|
|
@@ -870,9 +854,18 @@ export function createPiDurableContact(options) {
|
|
|
870
854
|
}
|
|
871
855
|
const ownerPid = options.ownerPid ?? process.pid;
|
|
872
856
|
const sendersDir = path.resolve(options.sendersDir ?? defaultMetaSendersDir());
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
|
|
857
|
+
const receiversDir = path.resolve(options.receiversDir ?? defaultMetaReceiversDir());
|
|
858
|
+
// The record and its location evidence, bound by one minted attachIncarnation under the
|
|
859
|
+
// record-arm lock of the receivers root this birth uses. A refusal here fails the birth;
|
|
860
|
+
// there is no unbound fallback. The pair is not one atomic commit: see the leaf's
|
|
861
|
+
// FAILURE STATES for what each failure leaves behind.
|
|
862
|
+
const upsert = commitPiDurableBirth({
|
|
863
|
+
nativeSessionId: id,
|
|
864
|
+
cwd,
|
|
865
|
+
sessionDirectory: session.directory,
|
|
866
|
+
ownerPid,
|
|
867
|
+
sessionsDir: options.sessionsDir ?? defaultMetaSessionsDir(),
|
|
868
|
+
receiversDir,
|
|
876
869
|
});
|
|
877
870
|
const gardenId = upsert.record.gardenId;
|
|
878
871
|
const markerPath = writeMetaSenderMarker({
|
|
@@ -887,7 +880,7 @@ export function createPiDurableContact(options) {
|
|
|
887
880
|
sessionsDir: upsert.dir,
|
|
888
881
|
sendersDir,
|
|
889
882
|
mailboxDir: path.resolve(options.mailboxDir ?? defaultMetaMailboxDir()),
|
|
890
|
-
receiversDir
|
|
883
|
+
receiversDir,
|
|
891
884
|
};
|
|
892
885
|
const client = await connect({
|
|
893
886
|
command: process.execPath,
|
|
@@ -30,30 +30,58 @@
|
|
|
30
30
|
* `--kind` token) stays with the rail that spends it; this leaf carries no argv.
|
|
31
31
|
*/
|
|
32
32
|
/**
|
|
33
|
-
* The
|
|
34
|
-
* that is not evidence of support
|
|
33
|
+
* The backends the herdr rail opens through `tab create` + `agent start --kind`. Herdr's own
|
|
34
|
+
* `--kind` enum is much larger and that is not evidence of support. This set alone keys the
|
|
35
|
+
* agent-start maps (`HERDR_AGENT_KIND`, `HERDR_CANONICAL_EXECUTABLE`): a backend outside it has no
|
|
36
|
+
* herdr kind, and giving it one would start a different program under its name.
|
|
35
37
|
*
|
|
36
38
|
* The JSDoc cast is what keeps the TYPE half of this single source honest. Without it TypeScript
|
|
37
|
-
* infers `readonly string[]` from a `.mjs` file and `
|
|
39
|
+
* infers `readonly string[]` from a `.mjs` file and `HerdrAgentStartBackend` silently widens to
|
|
38
40
|
* `string` — the tuple would still be one source at runtime while the compiler had stopped
|
|
39
41
|
* checking anything. A gate pins both halves.
|
|
40
42
|
*
|
|
41
43
|
* @type {readonly ["pi", "claude-code"]}
|
|
42
44
|
*/
|
|
43
|
-
export const
|
|
45
|
+
export const HERDR_AGENT_START_BACKENDS = Object.freeze(["pi", "claude-code"]);
|
|
46
|
+
/**
|
|
47
|
+
* The backends the herdr rail opens through ONE fixed `layout.apply` request on the caller's own
|
|
48
|
+
* herdr socket (#157 A-L1). pi-durable has no herdr agent kind, so it is launched as the package's
|
|
49
|
+
* own verified `run.sh pi-durable …` argv vector in a new tab — never disguised as `--kind pi`.
|
|
50
|
+
*
|
|
51
|
+
* @type {readonly ["pi-durable"]}
|
|
52
|
+
*/
|
|
53
|
+
export const HERDR_LAYOUT_LAUNCH_BACKENDS = Object.freeze(["pi-durable"]);
|
|
54
|
+
/**
|
|
55
|
+
* Everything `entwurf_fresh_call` actually opens inside herdr: the union of the two launch domains.
|
|
56
|
+
* Admission and refusal read THIS set, so every backend outside it is a named pre-mutation reject
|
|
57
|
+
* and no backend is admitted by one domain while another surface says it is refused.
|
|
58
|
+
*
|
|
59
|
+
* The status pane's RAILS block renders the two domains as separate groups — agent start, then
|
|
60
|
+
* layout — over this aggregate (#157 A-L2). The install narration covers only the integrations it
|
|
61
|
+
* activated, and pi-durable is never one: it has no herdr agent integration to activate.
|
|
62
|
+
*
|
|
63
|
+
* @type {readonly ["pi", "claude-code", "pi-durable"]}
|
|
64
|
+
*/
|
|
65
|
+
export const HERDR_FRESH_CALL_BACKENDS = Object.freeze([
|
|
66
|
+
...HERDR_AGENT_START_BACKENDS,
|
|
67
|
+
...HERDR_LAYOUT_LAUNCH_BACKENDS,
|
|
68
|
+
]);
|
|
44
69
|
/**
|
|
45
70
|
* ONE model string per backend, and it is an example of SYNTAX, never of choice.
|
|
46
71
|
*
|
|
47
72
|
* `[관측: GLG, 날것 PC, 2026-09-17]` "일단 정확한 모델명을 모른다" — after a green install the first
|
|
48
73
|
* thing missing is a call that works, and the two runtimes spell a model differently enough that
|
|
49
74
|
* guessing fails: pi wants a provider-qualified path, Claude Code wants the vendor id alone.
|
|
50
|
-
* That difference is the whole content of this map.
|
|
75
|
+
* That difference is the whole content of this map. pi-durable takes pi's shape — its preflight
|
|
76
|
+
* refuses a model that is not provider-qualified (`pi-durable-model-not-provider-qualified`) — so its
|
|
77
|
+
* example is the same shape string, not a choice of model.
|
|
51
78
|
*
|
|
52
|
-
* @type {Readonly<Record<"pi" | "claude-code", string>>}
|
|
79
|
+
* @type {Readonly<Record<"pi" | "claude-code" | "pi-durable", string>>}
|
|
53
80
|
*/
|
|
54
81
|
export const MODEL_SYNTAX_EXAMPLE = Object.freeze({
|
|
55
82
|
pi: "openai-codex/gpt-5.6-terra",
|
|
56
83
|
"claude-code": "claude-sonnet-5",
|
|
84
|
+
"pi-durable": "openai-codex/gpt-5.6-terra",
|
|
57
85
|
});
|
|
58
86
|
/**
|
|
59
87
|
* How a citizen on each backend is REACHED once it exists, in the operator's words. This is the
|
|
@@ -64,9 +92,14 @@ export const MODEL_SYNTAX_EXAMPLE = Object.freeze({
|
|
|
64
92
|
* citizen pi: the install says green, `pi` starts, and nothing is there. Citizenship is argv-gated
|
|
65
93
|
* on purpose, and that gate is invisible unless something says it out loud.
|
|
66
94
|
*
|
|
67
|
-
*
|
|
95
|
+
* pi-durable's note says what it is NOT here as much as how it is reached: it is not an integration
|
|
96
|
+
* this plugin activates, and herdr has no agent kind for it, so nothing detects it or associates a
|
|
97
|
+
* session with it.
|
|
98
|
+
*
|
|
99
|
+
* @type {Readonly<Record<"pi" | "claude-code" | "pi-durable", string>>}
|
|
68
100
|
*/
|
|
69
101
|
export const RAIL_ENTRY_NOTE = Object.freeze({
|
|
70
102
|
pi: "start it as `pi --entwurf-control` to be a garden citizen — a plain `pi` loads the extension but has no garden id, no control socket and no entwurf tools",
|
|
71
103
|
"claude-code": "nothing to add — an ordinary `claude` picks up the entwurf tools through MCP",
|
|
104
|
+
"pi-durable": "not a Herdr integration this plugin activates — inside Herdr, `entwurf_fresh_call` opens it in a new tab through ONE layout request; Herdr has no agent kind for it, so it is neither detected nor associated with a session, and its peers placement reads unobserved",
|
|
72
105
|
});
|
|
@@ -111,33 +111,7 @@ import { classifyMarkerOwner, defaultMetaMailboxDir, defaultMetaReceiversDir, de
|
|
|
111
111
|
import { resolveNativePushAdapter } from "../pi-extensions/lib/native-push/adapter.js";
|
|
112
112
|
import { probeSocketLiveness } from "../pi-extensions/lib/socket-probe.js";
|
|
113
113
|
function usage(code) {
|
|
114
|
-
console.error(
|
|
115
|
-
"usage: node --experimental-strip-types scripts/meta-bridge-fresh-cut.ts",
|
|
116
|
-
" (dev clone: `./run.sh meta-bridge-fresh-cut` · installed package: `entwurf meta-bridge-fresh-cut`)",
|
|
117
|
-
"",
|
|
118
|
-
" Open a fresh meta-bridge generation: quiesce-check every live surface, then",
|
|
119
|
-
" archive meta-sessions/ + meta-mailbox/ to `<dir>.archive-<timestamp>` siblings,",
|
|
120
|
-
" clear dead sender/receiver markers and dead control sockets, and open empty",
|
|
121
|
-
" live dirs. No migration, no restore: the archive is forensic bytes only.",
|
|
122
|
-
"",
|
|
123
|
-
"store = ENTWURF_META_SESSIONS_DIR || <PI_CODING_AGENT_DIR|~/.pi/agent>/meta-sessions",
|
|
124
|
-
"mailbox = ENTWURF_META_MAILBOX_DIR || <PI_CODING_AGENT_DIR|~/.pi/agent>/meta-mailbox",
|
|
125
|
-
"sockets = ENTWURF_DIR || ~/.pi/entwurf-control",
|
|
126
|
-
"",
|
|
127
|
-
"EXIT CONTRACT (#54) — what already moved, not how bad it was:",
|
|
128
|
-
` ${FRESH_CUT_EXIT.COMPLETE} cut complete: generation archived (or none existed), fresh v3 store open,`,
|
|
129
|
-
" residue cleared. `&& setup` is correct here.",
|
|
130
|
-
` ${FRESH_CUT_EXIT.NO_MOVE} NOTHING MOVED: a live/unprovable surface, an occupied archive destination,`,
|
|
131
|
-
" an unreadable surface, or a first rename that failed. The host is unchanged —",
|
|
132
|
-
" fix the named cause and re-run. Do NOT run setup: the store it refused is still there.",
|
|
133
|
-
` ${FRESH_CUT_EXIT.USAGE} usage.`,
|
|
134
|
-
` ${FRESH_CUT_EXIT.HALF_CUT} CUT TRANSITION INCOMPLETE: at least one archive move happened,`,
|
|
135
|
-
" but the fresh generation is not confirmed open. Inspect, or re-run to finish under a new stamp.",
|
|
136
|
-
` ${FRESH_CUT_EXIT.CLEANUP_INCOMPLETE} CUT COMPLETE, CLEANUP INCOMPLETE: the generation IS archived and the fresh`,
|
|
137
|
-
" generation IS open — install and citizen birth are unblocked, so setup may run —",
|
|
138
|
-
" but marker/socket residue survived. Prefer fixing it and re-running BEFORE setup; after new citizen birth,",
|
|
139
|
-
" remove the named residue manually instead of archiving the new generation with another cut.",
|
|
140
|
-
].join("\n"));
|
|
114
|
+
console.error("meta-bridge-fresh-cut: see `entwurf help meta-bridge-fresh-cut` (from that checkout's root: `./run.sh help meta-bridge-fresh-cut`)");
|
|
141
115
|
process.exit(code);
|
|
142
116
|
}
|
|
143
117
|
/** Denote-style local timestamp for archive dir names. */
|
|
@@ -27,7 +27,7 @@ function shellQuote(s) {
|
|
|
27
27
|
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
28
28
|
}
|
|
29
29
|
function usage(code) {
|
|
30
|
-
console.error("
|
|
30
|
+
console.error("meta-bridge-prune: see `entwurf help meta-bridge-prune` (from that checkout's root: `./run.sh help meta-bridge-prune`)");
|
|
31
31
|
process.exit(code);
|
|
32
32
|
}
|
|
33
33
|
let dirArg;
|
|
@@ -37,7 +37,7 @@ const arg = process.argv[2];
|
|
|
37
37
|
// A dash argv is a flag this command does not have, not a store directory — treating
|
|
38
38
|
// it as a path would answer `--help` with "empty store, exit 0", a silent wrong fact.
|
|
39
39
|
if (process.argv.length > 3 || (arg !== undefined && arg.startsWith("-"))) {
|
|
40
|
-
console.error("
|
|
40
|
+
console.error("meta-facts: see `entwurf help meta-facts` (from that checkout's root: `./run.sh help meta-facts`)");
|
|
41
41
|
process.exit(2);
|
|
42
42
|
}
|
|
43
43
|
let facts;
|
|
@@ -77,7 +77,7 @@ const arg = process.argv[2];
|
|
|
77
77
|
// A dash argv is a flag this command does not have, not a store directory — treating it as
|
|
78
78
|
// a path would answer `--help` with "empty store, exit 0", a silent wrong fact.
|
|
79
79
|
if (process.argv.length > 3 || (arg !== undefined && arg.startsWith("-"))) {
|
|
80
|
-
console.error("
|
|
80
|
+
console.error("peer-facts: see `entwurf help peer-facts` (from that checkout's root: `./run.sh help peer-facts`)");
|
|
81
81
|
process.exit(2);
|
|
82
82
|
}
|
|
83
83
|
const storeDir = arg ?? defaultMetaSessionsDir();
|