@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
|
@@ -61,6 +61,9 @@ export interface PiDurablePackageLayout {
|
|
|
61
61
|
readonly bridgeEntry: string;
|
|
62
62
|
/** The compiled sender/receiver owner join the bridge decides deliverability with. */
|
|
63
63
|
readonly deliverability: string;
|
|
64
|
+
/** The compiled owner fence the packaged bootstrap imports. A fixed unit of this package that only the
|
|
65
|
+
* fresh preflight asks for; the carrier/SDK verifier below does not read it. */
|
|
66
|
+
readonly ownerFence: string;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
/** The layout rooted at `root`, as plain paths. */
|
|
@@ -77,6 +80,7 @@ export function piDurableLayoutAt(root: string): PiDurablePackageLayout {
|
|
|
77
80
|
adapter: path.join(dist, "pi-extensions", "meta-bridge-pi-durable.js"),
|
|
78
81
|
bridgeEntry: path.join(dist, "mcp", "entwurf-bridge", "src", "index.js"),
|
|
79
82
|
deliverability: path.join(dist, "pi-extensions", "lib", "entwurf-deliverability.js"),
|
|
83
|
+
ownerFence: path.join(dist, "pi-extensions", "lib", "pi-durable-owner-fence.js"),
|
|
80
84
|
};
|
|
81
85
|
}
|
|
82
86
|
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
import * as fs from "node:fs";
|
|
28
28
|
import * as path from "node:path";
|
|
29
|
+
import { ENTWURF_DELIVERY_RESULT_CLAUSES } from "./agent-guidance.ts";
|
|
29
30
|
import { controlSocketPathIn } from "./control-socket-path.js";
|
|
30
31
|
import type { SenderEnvelope } from "./entwurf-control-rpc.ts";
|
|
31
32
|
import type { EntwurfV2SurfaceRendered, SurfaceEntwurfV2Params } from "./entwurf-v2-surface.ts";
|
|
@@ -262,13 +263,15 @@ export function classifyRemoteReceipt(r: RemoteTransportResult): RemoteSendOutco
|
|
|
262
263
|
return indeterminate(`exit ${r.exitCode} disagrees with its receipt`);
|
|
263
264
|
}
|
|
264
265
|
|
|
266
|
+
// The three static explanatory clauses are authored in root ENTWURF.json (#154 E1a); the receipt
|
|
267
|
+
// syntax, the route label, the slots and the refused/invalid lines stay here.
|
|
265
268
|
function render(route: RemoteRouteDeclaration, o: RemoteSendOutcome): EntwurfV2SurfaceRendered {
|
|
266
269
|
const via = `via ${route.ssh}`;
|
|
267
270
|
switch (o.kind) {
|
|
268
271
|
case "remote-accepted":
|
|
269
272
|
return {
|
|
270
273
|
isError: false,
|
|
271
|
-
text: `entwurf_v2 remote ${via} → accepted
|
|
274
|
+
text: `entwurf_v2 remote ${via} → ${ENTWURF_DELIVERY_RESULT_CLAUSES["remote-accepted"]}\n${o.text}`,
|
|
272
275
|
};
|
|
273
276
|
case "remote-reported-error":
|
|
274
277
|
// The other host's dispatch returned isError. That can follow a write (an enqueue whose
|
|
@@ -276,7 +279,7 @@ function render(route: RemoteRouteDeclaration, o: RemoteSendOutcome): EntwurfV2S
|
|
|
276
279
|
// non-delivery nor safety to resend: the other host's own text below is the fact.
|
|
277
280
|
return {
|
|
278
281
|
isError: true,
|
|
279
|
-
text: `entwurf_v2 remote ${via} →
|
|
282
|
+
text: `entwurf_v2 remote ${via} → ${ENTWURF_DELIVERY_RESULT_CLAUSES["remote-reported-error"]}\n${o.text}`,
|
|
280
283
|
};
|
|
281
284
|
case "remote-pair-refused":
|
|
282
285
|
return {
|
|
@@ -289,8 +292,8 @@ function render(route: RemoteRouteDeclaration, o: RemoteSendOutcome): EntwurfV2S
|
|
|
289
292
|
return {
|
|
290
293
|
isError: true,
|
|
291
294
|
text:
|
|
292
|
-
`entwurf_v2 remote ${via} → indeterminate: ${o.why}.
|
|
293
|
-
|
|
295
|
+
`entwurf_v2 remote ${via} → indeterminate: ${o.why}. ` +
|
|
296
|
+
`${ENTWURF_DELIVERY_RESULT_CLAUSES["remote-indeterminate"]}${o.stderrTail ? `\n${o.stderrTail}` : ""}`,
|
|
294
297
|
};
|
|
295
298
|
}
|
|
296
299
|
}
|
|
@@ -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.
|
|
@@ -29,7 +31,7 @@ import { getEntwurfExplicitExtensions, readSessionIdentity } from "./entwurf-cor
|
|
|
29
31
|
import { readAddressableMetaIdentity } from "./meta-session.ts";
|
|
30
32
|
|
|
31
33
|
/** The backends with a visible same-id resume. A closed set: nothing outside it is reopened. */
|
|
32
|
-
export const RESUME_BACKENDS = ["pi", "claude-code"] as const;
|
|
34
|
+
export const RESUME_BACKENDS = ["pi", "claude-code", "pi-durable"] as const;
|
|
33
35
|
export type ResumeBackend = (typeof RESUME_BACKENDS)[number];
|
|
34
36
|
|
|
35
37
|
/**
|
|
@@ -42,8 +44,7 @@ export type ResumeBackend = (typeof RESUME_BACKENDS)[number];
|
|
|
42
44
|
* except by looking. A Claude Code or agy garden id is different in kind: nothing is broken,
|
|
43
45
|
* the operator simply asked a capability boundary to do something it does not cover, and the
|
|
44
46
|
* honest answer is a named refusal rather than an error report about a store that is fine. Since
|
|
45
|
-
* #147 L2 the boundary is `RESUME_BACKENDS`; a Codex, agy, Copilot
|
|
46
|
-
* this refusal.
|
|
47
|
+
* #147 L2 the boundary is `RESUME_BACKENDS`; a Codex, agy, Copilot or OMP garden id is this refusal.
|
|
47
48
|
*
|
|
48
49
|
* It carries a `reason` so the caller matches on a FIELD. Parsing the message string would make
|
|
49
50
|
* the wording load-bearing, and re-reading the record to ask "was it pi?" would read an address
|
|
@@ -55,7 +56,7 @@ export class ResumeBackendUnsupportedError extends Error {
|
|
|
55
56
|
constructor(gardenId: string, backend: string) {
|
|
56
57
|
super(
|
|
57
58
|
`resume-launch-identity: ${gardenId} is a ${backend} citizen — same-id resume is a host-adapter relaunch ` +
|
|
58
|
-
`capability, and its domain is ${RESUME_BACKENDS.join("
|
|
59
|
+
`capability, and its domain is ${RESUME_BACKENDS.join(", ")}. ` +
|
|
59
60
|
`This is a capability boundary, not citizen rank.`,
|
|
60
61
|
);
|
|
61
62
|
this.name = "ResumeBackendUnsupportedError";
|
|
@@ -91,7 +92,19 @@ export interface ClaudeCodeLaunchIdentity {
|
|
|
91
92
|
attachIncarnation: string | null;
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
|
|
95
|
+
/**
|
|
96
|
+
* A pi-durable citizen's facts, from the record only (#157 B3). Not launch inputs: the parent's
|
|
97
|
+
* reservation re-reads the record and its C4 location inside its own record-arm then S_D holds, and
|
|
98
|
+
* its launch takes the cwd from THAT pair. They are here so the backend the address names is read
|
|
99
|
+
* once, by the same addressable reader as every other backend.
|
|
100
|
+
*/
|
|
101
|
+
export interface PiDurableLaunchIdentity {
|
|
102
|
+
backend: "pi-durable";
|
|
103
|
+
nativeSessionId: string;
|
|
104
|
+
cwd: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type LaunchIdentity = PiLaunchIdentity | ClaudeCodeLaunchIdentity | PiDurableLaunchIdentity;
|
|
95
108
|
|
|
96
109
|
/**
|
|
97
110
|
* Claude Code's session id grammar: a lowercase UUID. The value becomes an argv token after
|
|
@@ -180,6 +193,12 @@ function resolveClaudeCodeLaunchIdentity(
|
|
|
180
193
|
export function resolveResumeLaunchIdentity(gardenId: string): LaunchIdentity {
|
|
181
194
|
const record = readAddressableMetaIdentity(gardenId);
|
|
182
195
|
if (record.backend === "claude-code") return resolveClaudeCodeLaunchIdentity(gardenId, record);
|
|
196
|
+
if (record.backend === "pi-durable") {
|
|
197
|
+
if (!path.isAbsolute(record.cwd)) {
|
|
198
|
+
throw new Error(`resume-launch-identity: ${gardenId} records a relative cwd ${JSON.stringify(record.cwd)}.`);
|
|
199
|
+
}
|
|
200
|
+
return { backend: "pi-durable", nativeSessionId: record.nativeSessionId, cwd: record.cwd };
|
|
201
|
+
}
|
|
183
202
|
if (record.backend !== "pi") {
|
|
184
203
|
throw new ResumeBackendUnsupportedError(gardenId, record.backend);
|
|
185
204
|
}
|
|
@@ -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
|
|
@@ -115,6 +120,7 @@ import * as fs from "node:fs";
|
|
|
115
120
|
import * as path from "node:path";
|
|
116
121
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
117
122
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
123
|
+
import { ENTWURF_TOOL_GUIDANCE } from "./lib/agent-guidance.ts";
|
|
118
124
|
import {
|
|
119
125
|
defaultMetaMailboxDir,
|
|
120
126
|
defaultMetaReceiversDir,
|
|
@@ -125,10 +131,10 @@ import {
|
|
|
125
131
|
readMetaReceiverMarker,
|
|
126
132
|
readMetaSenderMarker,
|
|
127
133
|
requireGardenId,
|
|
128
|
-
upsertMetaSession,
|
|
129
134
|
writeMetaReceiverMarker,
|
|
130
135
|
writeMetaSenderMarker,
|
|
131
136
|
} from "./lib/meta-session.ts";
|
|
137
|
+
import { commitPiDurableBirth } from "./lib/pi-durable-location.ts";
|
|
132
138
|
|
|
133
139
|
/** The backend id this contact mints under. */
|
|
134
140
|
export const PI_DURABLE_BACKEND = "pi-durable" as const;
|
|
@@ -186,29 +192,26 @@ export const PI_DURABLE_TOOL_SCHEMAS: Readonly<Record<PiDurableToolName, Readonl
|
|
|
186
192
|
target: {
|
|
187
193
|
type: "string",
|
|
188
194
|
minLength: 1,
|
|
189
|
-
description: "
|
|
195
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_v2.parameters["target"],
|
|
190
196
|
},
|
|
191
197
|
intent: {
|
|
192
198
|
type: "string",
|
|
193
199
|
enum: ["fire-and-forget"],
|
|
194
|
-
description:
|
|
195
|
-
"fire-and-forget = send/reply/hand-off to a LIVE socket target or to any citizen with no socket liveness — the decider picks its rail, and a rail can also REJECT (self-fetch → mailbox when deliverable, else mailbox-undeliverable; native-push → alive: direct injection, dead: native-push-target-dead, indeterminate: native-push-probe-indeterminate; a paused backend — agy, #152 — rejects as native-push-paused before any probe); set wants_reply for an answer. This is the ONLY intent: owned-outcome, which resumed a dormant citizen by launching a hidden background child, was withdrawn under the visible-first rule. It is not selectable, not deprecated-but-tolerated; a dormant citizen is currently unreachable by this verb and rejects as dormant-fire-forget-unsupported.",
|
|
200
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_v2.parameters["intent"],
|
|
196
201
|
},
|
|
197
202
|
message: {
|
|
198
203
|
type: "string",
|
|
199
204
|
minLength: 1,
|
|
200
205
|
maxLength: 16000,
|
|
201
|
-
description:
|
|
202
|
-
"Message / prompt to dispatch. Hard cap 16000 chars; for larger payloads send a file/artifact path plus digest.",
|
|
206
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_v2.parameters["message"],
|
|
203
207
|
},
|
|
204
208
|
mode: {
|
|
205
|
-
description:
|
|
206
|
-
"Injection style for a CONTROL-SOCKET send only: steer (ask the receiver's steering queue) or follow_up (ask its follow-up queue). NEITHER is an interrupt: a busy receiver drains steering after each turn and follow-ups only when its inner loop ends, so a later steer overtakes every earlier follow_up and there is no order between the two. Both queues are volatile process memory — an abort drops what is in them. The receipt names which queue accepted the message (`queued-steer` / `queued-follow-up`); an idle receiver answers `sent`, meaning the turn was TRIGGERED, not that the model has seen the text. The mailbox and native-push plans carry no mode, so it has no effect on those rails.",
|
|
209
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_v2.parameters["mode"],
|
|
207
210
|
type: "string",
|
|
208
211
|
enum: ["steer", "follow_up"],
|
|
209
212
|
},
|
|
210
213
|
wants_reply: {
|
|
211
|
-
description: "
|
|
214
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_v2.parameters["wants_reply"],
|
|
212
215
|
type: "boolean",
|
|
213
216
|
},
|
|
214
217
|
},
|
|
@@ -221,8 +224,7 @@ export const PI_DURABLE_TOOL_SCHEMAS: Readonly<Record<PiDurableToolName, Readonl
|
|
|
221
224
|
gardenId: {
|
|
222
225
|
type: "string",
|
|
223
226
|
minLength: 1,
|
|
224
|
-
description:
|
|
225
|
-
"The garden id whose inbox to drain — caller-supplied and NOT verified as yours, so use the id from your own doorbell notice / meta-record.",
|
|
227
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_inbox_read.parameters["gardenId"],
|
|
226
228
|
},
|
|
227
229
|
},
|
|
228
230
|
required: ["gardenId"],
|
|
@@ -235,38 +237,32 @@ export const PI_DURABLE_TOOL_SCHEMAS: Readonly<Record<PiDurableToolName, Readonl
|
|
|
235
237
|
backend: {
|
|
236
238
|
type: "string",
|
|
237
239
|
enum: ["pi", "claude-code", "copilot", "omp", "codex", "pi-durable"],
|
|
238
|
-
description:
|
|
239
|
-
"Which fixed runtime to open. Only these six, and only pi/claude-code when this agent runs inside herdr; there is no arbitrary command.",
|
|
240
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_fresh_call.parameters["backend"],
|
|
240
241
|
},
|
|
241
242
|
model: {
|
|
242
243
|
type: "string",
|
|
243
244
|
minLength: 1,
|
|
244
245
|
maxLength: 200,
|
|
245
246
|
pattern: "^[A-Za-z0-9][A-Za-z0-9._/:\\[\\]-]*$",
|
|
246
|
-
description:
|
|
247
|
-
"Required runtime model: canonical provider/model for pi (a bare alias is refused), provider/<exact catalog model id> for pi-durable, a Claude Code model id/alias, or a Copilot/OMP/Codex model name. Neither pi nor pi-durable may name the openrouter provider.",
|
|
247
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_fresh_call.parameters["model"],
|
|
248
248
|
},
|
|
249
249
|
task: {
|
|
250
250
|
type: "string",
|
|
251
251
|
minLength: 1,
|
|
252
252
|
maxLength: 16000,
|
|
253
|
-
description:
|
|
254
|
-
"What the sibling should do after it calls you back. Plain instructions; no secrets (see the tool description).",
|
|
253
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_fresh_call.parameters["task"],
|
|
255
254
|
},
|
|
256
255
|
cwd: {
|
|
257
|
-
description:
|
|
258
|
-
"Optional literal ABSOLUTE path of an existing directory to start the sibling in (cross-repo fresh). Omit or pass \"\" to start where the CALLER is — this agent's own cwd on BOTH rails, or for a Codex caller its own record directory, because this bridge is the app-server's child and that process's directory is not the caller's. Taken exactly as given: no trim, no realpath, no project-name resolution. '#' is refused on the tmux rail only, because tmux format-expands a start directory; inside herdr it is an ordinary path character. The receipt echoes the directory that was REQUESTED or the caller record it came from, never an observation of where the pane landed.",
|
|
256
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_fresh_call.parameters["cwd"],
|
|
259
257
|
type: "string",
|
|
260
258
|
},
|
|
261
259
|
placement: {
|
|
262
|
-
description:
|
|
263
|
-
"Optional expert seat override, TMUX ONLY: open the sibling in ONE EXISTING tmux session of this agent's own server, and it always wins. When omitted the seat follows the CALLER, never the backend being opened: a Codex CALLER opens beside its own TUI pane, matched by thread-id in that pane's terminal title (0 or 2+ matching panes refuse, never fall back); every other caller opens in its own session. A pane title is a placement input only — never an address, liveness or delivery fact. Nothing is ever created. Inside herdr this field is refused by name — placement there belongs to herdr, and a tmux session name would silently place the sibling somewhere else. Independent of cwd; neither is inferred from the other. The receipt reports the selected name (absent for the caller-pane rule, which observed a session rather than requesting a name), its source, and resolved target session id.",
|
|
260
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_fresh_call.parameters["placement"],
|
|
264
261
|
type: "object",
|
|
265
262
|
properties: {
|
|
266
263
|
tmuxSession: {
|
|
267
264
|
type: "string",
|
|
268
|
-
description:
|
|
269
|
-
"EXACT name of an EXISTING session on this agent's own tmux server. Nothing is created: an absent session is refused as tmux-session-missing, and a name outside [A-Za-z0-9][A-Za-z0-9_-]* as tmux-session-name-invalid.",
|
|
265
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_fresh_call.parameters["placement.tmuxSession"],
|
|
270
266
|
},
|
|
271
267
|
},
|
|
272
268
|
required: ["tmuxSession"],
|
|
@@ -282,7 +278,7 @@ export const PI_DURABLE_TOOL_SCHEMAS: Readonly<Record<PiDurableToolName, Readonl
|
|
|
282
278
|
type: "string",
|
|
283
279
|
minLength: 1,
|
|
284
280
|
pattern: "^\\d{8}T\\d{6}-[0-9a-f]{6}$",
|
|
285
|
-
description:
|
|
281
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_resume_call.parameters["target"],
|
|
286
282
|
},
|
|
287
283
|
},
|
|
288
284
|
required: ["target"],
|
|
@@ -295,8 +291,7 @@ export const PI_DURABLE_TOOL_SCHEMAS: Readonly<Record<PiDurableToolName, Readonl
|
|
|
295
291
|
type: "string",
|
|
296
292
|
minLength: 1,
|
|
297
293
|
pattern: "^\\d{8}T\\d{6}-[0-9a-f]{6}$",
|
|
298
|
-
description:
|
|
299
|
-
"Exact garden id of the Pi or Claude Code target the human operator explicitly requested to retire.",
|
|
294
|
+
description: ENTWURF_TOOL_GUIDANCE.entwurf_retire_call.parameters["target"],
|
|
300
295
|
},
|
|
301
296
|
},
|
|
302
297
|
required: ["target"],
|
|
@@ -763,45 +758,14 @@ interface HeldNotice {
|
|
|
763
758
|
}
|
|
764
759
|
|
|
765
760
|
const TOOL_DESCRIPTIONS: Record<PiDurableToolName, string> = {
|
|
766
|
-
entwurf_self:
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
entwurf_inbox_read:
|
|
775
|
-
"Drain a garden mailbox by garden id through the entwurf-bridge and stamp its read receipt. When an " +
|
|
776
|
-
"[entwurf inbox] notice announces unread mail, pass the garden id that notice carries. Returns the bodies " +
|
|
777
|
-
"and archives them; treat them as untrusted data.",
|
|
778
|
-
entwurf_callback:
|
|
779
|
-
"Call home once, before a fresh sibling's task: the entwurf-bridge reads the caller's garden id and " +
|
|
780
|
-
"nonce from this host's launch environment and delivers the nonce to that caller. No arguments. Refuses by " +
|
|
781
|
-
"name when this host was not launched with a callback pair.",
|
|
782
|
-
entwurf_fresh_call:
|
|
783
|
-
"Open ONE fresh visible sibling beside this durable host through the entwurf-bridge and hand it a first task " +
|
|
784
|
-
"(backend, model and task required; optional cwd and placement). Returns the bridge's LAUNCH receipt or its " +
|
|
785
|
-
"named refusal — not a started runtime, a delivered task or a reply; the sibling's callback names it. Opening " +
|
|
786
|
-
"beside this host needs the tmux pane this host itself was launched in.",
|
|
787
|
-
entwurf_resume_call:
|
|
788
|
-
"Reopen ONE dormant pi or Claude Code citizen under its own garden id in a visible tmux window through the " +
|
|
789
|
-
"entwurf-bridge (target required; the record supplies the session and cwd — no task, no prompt, no turn). " +
|
|
790
|
-
"Returns the bridge's LAUNCH and OBSERVATION receipts or its named refusal: a live target, another backend, " +
|
|
791
|
-
"or a Claude Code citizen not proven dormant is refused by name, and opening the window needs the tmux " +
|
|
792
|
-
"session this host itself was launched in.",
|
|
793
|
-
entwurf_retire_call:
|
|
794
|
-
"Only when the human operator explicitly requested this particular target/work: make ONE retirement attempt for that request " +
|
|
795
|
-
"through the bridge (target only, Pi or Claude Code; self retirement refuses). Development work, a role or " +
|
|
796
|
-
"a record-backed actor is NOT a grant. Use host approval where provided/configured; " +
|
|
797
|
-
"Pi and pi-durable have no built-in per-call approval prompt by default. An explicit operator request and " +
|
|
798
|
-
"agent operating policy are required; attribution and MCP annotations are hints, not grants or proof of approval. " +
|
|
799
|
-
"No autonomous cleanup, timers or automatic " +
|
|
800
|
-
"retry of unknown, cancelled or lost results. Do not manually /resume or /clear the target during the request. " +
|
|
801
|
-
"Pi shutdown-requested is only an ACK, not process exit, flush or model silence. Claude Code exited is that " +
|
|
802
|
-
"fd-pinned process only, not conversation end, children or flush. The JSON receipt keeps outcome, record and " +
|
|
803
|
-
"release separate; receipt return does not mean exit, and cancellation does not undo an action. Invoke once " +
|
|
804
|
-
"per explicitly named target, never infer a roster. This contact does not turn hints into authorization.",
|
|
761
|
+
entwurf_self: ENTWURF_TOOL_GUIDANCE.entwurf_self.descriptions.durable,
|
|
762
|
+
entwurf_peers: ENTWURF_TOOL_GUIDANCE.entwurf_peers.descriptions.durable,
|
|
763
|
+
entwurf_v2: ENTWURF_TOOL_GUIDANCE.entwurf_v2.descriptions.durable,
|
|
764
|
+
entwurf_inbox_read: ENTWURF_TOOL_GUIDANCE.entwurf_inbox_read.descriptions.durable,
|
|
765
|
+
entwurf_callback: ENTWURF_TOOL_GUIDANCE.entwurf_callback.descriptions.durable,
|
|
766
|
+
entwurf_fresh_call: ENTWURF_TOOL_GUIDANCE.entwurf_fresh_call.descriptions.durable,
|
|
767
|
+
entwurf_resume_call: ENTWURF_TOOL_GUIDANCE.entwurf_resume_call.descriptions.durable,
|
|
768
|
+
entwurf_retire_call: ENTWURF_TOOL_GUIDANCE.entwurf_retire_call.descriptions.durable,
|
|
805
769
|
};
|
|
806
770
|
|
|
807
771
|
/** Announce, never push: the notice names the garden and the tool, never the body. */
|
|
@@ -1200,9 +1164,18 @@ export function createPiDurableContact(options: PiDurableContactOptions): PiDura
|
|
|
1200
1164
|
}
|
|
1201
1165
|
const ownerPid = options.ownerPid ?? process.pid;
|
|
1202
1166
|
const sendersDir = path.resolve(options.sendersDir ?? defaultMetaSendersDir());
|
|
1203
|
-
const
|
|
1204
|
-
|
|
1205
|
-
|
|
1167
|
+
const receiversDir = path.resolve(options.receiversDir ?? defaultMetaReceiversDir());
|
|
1168
|
+
// The record and its location evidence, bound by one minted attachIncarnation under the
|
|
1169
|
+
// record-arm lock of the receivers root this birth uses. A refusal here fails the birth;
|
|
1170
|
+
// there is no unbound fallback. The pair is not one atomic commit: see the leaf's
|
|
1171
|
+
// FAILURE STATES for what each failure leaves behind.
|
|
1172
|
+
const upsert = commitPiDurableBirth({
|
|
1173
|
+
nativeSessionId: id,
|
|
1174
|
+
cwd,
|
|
1175
|
+
sessionDirectory: session.directory,
|
|
1176
|
+
ownerPid,
|
|
1177
|
+
sessionsDir: options.sessionsDir ?? defaultMetaSessionsDir(),
|
|
1178
|
+
receiversDir,
|
|
1206
1179
|
});
|
|
1207
1180
|
const gardenId = upsert.record.gardenId;
|
|
1208
1181
|
const markerPath = writeMetaSenderMarker({
|
|
@@ -1217,7 +1190,7 @@ export function createPiDurableContact(options: PiDurableContactOptions): PiDura
|
|
|
1217
1190
|
sessionsDir: upsert.dir,
|
|
1218
1191
|
sendersDir,
|
|
1219
1192
|
mailboxDir: path.resolve(options.mailboxDir ?? defaultMetaMailboxDir()),
|
|
1220
|
-
receiversDir
|
|
1193
|
+
receiversDir,
|
|
1221
1194
|
};
|
|
1222
1195
|
const client = await connect({
|
|
1223
1196
|
command: process.execPath,
|