@junghanacs/entwurf 0.40.0 → 0.41.1
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 +55 -2
- package/CHANGELOG.md +562 -0
- package/CONTRIBUTING.md +1 -1
- package/DELIVERY.md +18 -2
- package/FAQ.md +8 -4
- package/README.md +60 -45
- package/VERIFY.md +14 -11
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +6 -2
- package/docs/external-mcp-host.md +8 -6
- package/docs/fresh-cut-policy.md +9 -1
- package/docs/setup-clean-host.md +76 -20
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +81 -19
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/cc-retire-pidfd.py +236 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/cc-retire-request.js +473 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/control-retire.js +125 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-retire-production.js +149 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-retire.js +158 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +2 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +283 -20
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +7 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +47 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +3 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +96 -16
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +5 -29
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-launch.js +28 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +26 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-retire-request.js +76 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +67 -17
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/retirement-event.js +211 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js +124 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +53 -2
- package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +1 -1
- package/mcp/entwurf-bridge/dist/scripts/probe-bridge-command.js +1 -0
- package/mcp/entwurf-bridge/src/index.ts +93 -19
- package/mcp/entwurf-bridge/test.sh +2 -2
- package/mcp/entwurf-bridge/tsconfig.build.json +10 -1
- package/package.json +15 -14
- package/pi/meta-bridge/entwurf-meta-receive/scripts/hook-launch.sh +3 -3
- package/pi/pi-durable/carrier/tui.js +18 -3
- package/pi/pi-durable/overlay/upstream-pin.json +12 -12
- package/pi-extensions/entwurf-control.ts +55 -20
- package/pi-extensions/lib/acp/backend-adapter.ts +5 -3
- package/pi-extensions/lib/acp/backend.ts +12 -9
- package/pi-extensions/lib/cc-retire-pidfd.py +236 -0
- package/pi-extensions/lib/cc-retire-request.ts +553 -0
- package/pi-extensions/lib/control-retire.ts +195 -0
- package/pi-extensions/lib/entwurf-control-rpc.ts +17 -1
- package/pi-extensions/lib/entwurf-v2-retire-production.ts +227 -0
- package/pi-extensions/lib/entwurf-v2-retire.ts +269 -0
- package/pi-extensions/lib/entwurf-v2-surface.ts +2 -1
- package/pi-extensions/lib/entwurf-v2-visible-resume.ts +390 -36
- package/pi-extensions/lib/fresh-call-composition.ts +15 -0
- package/pi-extensions/lib/fresh-call-dispatch.ts +45 -4
- package/pi-extensions/lib/herdr-fresh-call.ts +5 -0
- package/pi-extensions/lib/meta-session.ts +140 -16
- package/pi-extensions/lib/mux-fresh-call.ts +7 -27
- package/pi-extensions/lib/mux-launch.ts +29 -0
- package/pi-extensions/lib/mux-resume-call.ts +33 -12
- package/pi-extensions/lib/pi-retire-request.ts +111 -0
- package/pi-extensions/lib/resume-launch-identity.ts +104 -18
- package/pi-extensions/lib/retirement-event.ts +253 -0
- package/pi-extensions/meta-bridge-hook.ts +142 -13
- package/pi-extensions/meta-bridge-pi-durable.ts +57 -3
- package/run.sh +123 -34
- package/scripts/agy-bridge-config.py +4 -1
- package/scripts/build-bridge.sh +14 -0
- package/scripts/check-capability-bundle-reach.ts +135 -2
- package/scripts/check-entwurf-bridge-boot.ts +2 -1
- package/scripts/check-entwurf-fact-provider.ts +1 -0
- package/scripts/check-entwurf-v2-production.ts +2 -0
- package/scripts/check-entwurf-v2-surface.ts +7 -0
- package/scripts/check-gate-qualification.ts +14 -8
- package/scripts/check-herdr-activation.ts +2 -2
- package/scripts/check-herdr-placement.ts +1 -0
- package/scripts/check-hook-launch-topology.ts +6 -15
- package/scripts/check-install-container.sh +2 -2
- package/scripts/check-meta-doctor-oracle.sh +27 -1
- package/scripts/check-meta-facts.ts +8 -3
- package/scripts/check-meta-hook-session-switch.ts +230 -13
- package/scripts/check-meta-identity-consumers.ts +1 -0
- package/scripts/check-meta-listing.ts +1 -0
- package/scripts/check-meta-manifest-schema.py +1 -1
- package/scripts/check-meta-session.ts +31 -0
- package/scripts/check-meta-v3-record.ts +55 -1
- package/scripts/check-mux-resume-call.ts +35 -9
- package/scripts/check-peer-facts.ts +1 -0
- package/scripts/check-pi-durable-contact.ts +3 -1
- package/scripts/check-pi-durable-send.ts +4 -2
- package/scripts/check-pi-mcp-bridge.ts +1 -0
- package/scripts/check-resume-launch-identity.ts +62 -23
- package/scripts/lib/codex-fresh-live-protocol.ts +3 -2
- package/scripts/lib/meta-hook-bundle.ts +72 -0
- package/scripts/meta-bridge-claude-floor.sh +13 -9
- package/scripts/meta-bridge-doctor.sh +17 -0
- package/scripts/meta-bridge-install.sh +6 -0
- package/scripts/meta-facts.ts +1 -1
- package/scripts/mutants/bridge-boot-resume.json +5 -2
- package/scripts/mutants/cc-retire-pidfd.json +129 -0
- package/scripts/mutants/cc-retire-request.json +171 -0
- package/scripts/mutants/control-retire.json +105 -0
- package/scripts/mutants/control-send-receipt.json +1 -1
- package/scripts/mutants/copilot-receive.json +2 -6
- package/scripts/mutants/entwurf-v2-retire.json +476 -0
- package/scripts/mutants/fresh-call-dispatch.json +37 -0
- package/scripts/mutants/meta-hook-session-switch.json +88 -0
- package/scripts/mutants/meta-identity.json +57 -0
- package/scripts/mutants/mux-fresh-call.json +1 -1
- package/scripts/mutants/mux-resume-call.json +40 -4
- package/scripts/mutants/pack-install.json +4 -4
- package/scripts/mutants/pi-durable.json +139 -0
- package/scripts/mutants/pi-floor.json +5 -5
- package/scripts/mutants/pi-retire-request.json +72 -0
- package/scripts/mutants/resume-launch-identity.json +35 -8
- package/scripts/mutants/retirement-event.json +195 -0
- package/scripts/mutants/v2-surface.json +16 -0
- package/scripts/mutants/v2-visible-resume.json +283 -13
- package/scripts/probe-bridge-command.ts +1 -0
- package/scripts/smoke-agy-install-state.sh +2 -2
- package/scripts/smoke-codex-fresh-live.ts +2 -1
- package/scripts/smoke-copilot-mcp-state.sh +1 -1
- package/scripts/smoke-herdr-raw-install-live.sh +15 -7
- package/scripts/smoke-meta-install-state.sh +4 -4
- package/scripts/smoke-mux-lifecycle-live.ts +10 -10
- package/scripts/smoke-omp-mcp-state.sh +1 -1
- package/scripts/smoke-pi-durable-fresh-live.ts +5 -3
- package/scripts/smoke-pi-provider-state.sh +3 -3
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/** Internal retire dispatcher. Host-authoritative caller resolution is an injected
|
|
2
|
+
* composition seam, NOT authorization. Public exposure still needs an operator policy;
|
|
3
|
+
* no role DB, permission boolean, or actor-label permission is manufactured here.
|
|
4
|
+
* The same existing v2 target lock spans fresh reads, preflight, the requested record, ONE adapter
|
|
5
|
+
* call and its outcome record.
|
|
6
|
+
*
|
|
7
|
+
* Recording is two mandatory synchronous callbacks, NOT permission hooks: `recordRequested` runs
|
|
8
|
+
* after the locked preflight and before the adapter, and must return the attempt's 32-hex event
|
|
9
|
+
* id; `recordOutcome` runs once after that attempt and must return `undefined`. This module knows
|
|
10
|
+
* no path, file format or clock. A recorder that throws or returns anything else is a record
|
|
11
|
+
* failure: before the adapter it refuses (`retirement-record-unavailable`, adapter never entered);
|
|
12
|
+
* after it, the backend outcome stands and only `record` says so. A returned Promise is never
|
|
13
|
+
* awaited, `.then`-ed or treated as completion — what it does afterwards is outside this receipt.
|
|
14
|
+
* `record: not-attempted` means no recorder was called, not a runtime proof that no bytes exist.
|
|
15
|
+
*
|
|
16
|
+
* `signal` is request cancellation, not approval. It is checked before the lock, after the
|
|
17
|
+
* locked preflight and after the requested record; once the adapter is entered it is the
|
|
18
|
+
* adapter's own business (Pi takes none; CC receives it through the composition).
|
|
19
|
+
*/
|
|
20
|
+
import type { CcRetireExpected, CcRetireResult } from "./cc-retire-request.ts";
|
|
21
|
+
import { controlSocketPathIn } from "./control-socket-path.js";
|
|
22
|
+
import type { SenderEnvelope } from "./entwurf-control-rpc.ts";
|
|
23
|
+
import { isOutOfSocketDomainGardenIdConflict } from "./entwurf-facts.ts";
|
|
24
|
+
import type { AcquireLockResult, LockClaim, ReleaseResult } from "./entwurf-v2-lock.ts";
|
|
25
|
+
import type { MetaIdentity } from "./meta-session.ts";
|
|
26
|
+
import type { PiRetireRequestResult } from "./pi-retire-request.ts";
|
|
27
|
+
import { isValidSessionId } from "./session-id.js";
|
|
28
|
+
import {
|
|
29
|
+
isRecordLessSocketCandidate,
|
|
30
|
+
mapInspectionToLiveness,
|
|
31
|
+
type TargetSocketInspection,
|
|
32
|
+
} from "./socket-discovery.ts";
|
|
33
|
+
import type { SocketLiveness } from "./socket-probe.ts";
|
|
34
|
+
|
|
35
|
+
export interface RetireDispatcherDeps {
|
|
36
|
+
/** Existing host resolver's verified result, not a caller-authored envelope. */
|
|
37
|
+
resolveCaller(): Promise<SenderEnvelope | null>;
|
|
38
|
+
acquireLock(gardenId: string): AcquireLockResult;
|
|
39
|
+
releaseLock(claim: LockClaim): ReleaseResult;
|
|
40
|
+
/** Owning metaRecordExistsByGardenId / readAddressableMetaIdentity seams. */
|
|
41
|
+
recordExists(gardenId: string): boolean;
|
|
42
|
+
readIdentity(gardenId: string): MetaIdentity;
|
|
43
|
+
/** One composition-owned directory shared by inspection, probe and Pi request. */
|
|
44
|
+
controlSocketDir: string;
|
|
45
|
+
inspectPath(socketPath: string): Promise<TargetSocketInspection>;
|
|
46
|
+
probeSocket(socketPath: string): Promise<SocketLiveness>;
|
|
47
|
+
requestPi(
|
|
48
|
+
target: MetaIdentity,
|
|
49
|
+
expected: { gardenId: string; nativeSessionId: string },
|
|
50
|
+
options: { controlSocketDir: string },
|
|
51
|
+
): Promise<PiRetireRequestResult>;
|
|
52
|
+
/** Composition binds existing requestCcRetire and its owning first/late witness deps. */
|
|
53
|
+
requestCc(expected: CcRetireExpected): Promise<CcRetireResult>;
|
|
54
|
+
/** Append the requested event; return its 32 lowercase hex id only after the append succeeded. */
|
|
55
|
+
recordRequested(context: RetireRecordContext): string;
|
|
56
|
+
/** Append the outcome event of that attempt. `undefined`, not `void`: an async recorder does not type. */
|
|
57
|
+
recordOutcome(eventId: string, context: RetireRecordContext, outcome: RetireDispatchOutcome): undefined;
|
|
58
|
+
/** This request's cancellation. Not an approval. */
|
|
59
|
+
signal?: AbortSignal;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type RetireTargetBackend = "pi" | "claude-code";
|
|
63
|
+
|
|
64
|
+
/** Attribution only: who asked, about whom, on which rail. */
|
|
65
|
+
export interface RetireRecordContext {
|
|
66
|
+
actorGardenId: string;
|
|
67
|
+
targetGardenId: string;
|
|
68
|
+
targetBackend: RetireTargetBackend;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type RetireRecordReceipt =
|
|
72
|
+
| { status: "not-attempted" }
|
|
73
|
+
| { status: "requested-append-error"; cause: string }
|
|
74
|
+
| { status: "complete"; eventId: string }
|
|
75
|
+
| { status: "outcome-append-error"; eventId: string; cause: string };
|
|
76
|
+
|
|
77
|
+
export type RetireDispatchOutcome =
|
|
78
|
+
| {
|
|
79
|
+
kind: "refused";
|
|
80
|
+
reason:
|
|
81
|
+
| "target-invalid"
|
|
82
|
+
| "caller-unavailable"
|
|
83
|
+
| "self-target"
|
|
84
|
+
| "target-locked"
|
|
85
|
+
| "target-not-found"
|
|
86
|
+
| "record-less-socket"
|
|
87
|
+
| "target-backend-unsupported"
|
|
88
|
+
| "target-address-conflict"
|
|
89
|
+
| "target-dead"
|
|
90
|
+
| "target-indeterminate"
|
|
91
|
+
| "target-drifted"
|
|
92
|
+
| "request-aborted"
|
|
93
|
+
| "retirement-record-unavailable";
|
|
94
|
+
detail?: string;
|
|
95
|
+
}
|
|
96
|
+
| { kind: "preflight-error"; cause: string }
|
|
97
|
+
| { kind: "pi"; result: PiRetireRequestResult }
|
|
98
|
+
| { kind: "claude-code"; result: CcRetireResult }
|
|
99
|
+
| { kind: "unknown"; backend: RetireTargetBackend; cause: string };
|
|
100
|
+
|
|
101
|
+
export type RetireReleaseReceipt =
|
|
102
|
+
| { status: "not-acquired" }
|
|
103
|
+
| { status: ReleaseResult }
|
|
104
|
+
| { status: "error"; cause: string };
|
|
105
|
+
|
|
106
|
+
/** Neither backend outcome is upgraded: Pi ACK is request-only, CC exited is fd-process
|
|
107
|
+
* exit only (not conversation, children or flush). Cleanup failure is independently non-green
|
|
108
|
+
* and NEVER erases a possibly effective action or grants permission to replay it.
|
|
109
|
+
*/
|
|
110
|
+
export interface RetireDispatchReceipt {
|
|
111
|
+
targetGardenId: string;
|
|
112
|
+
actor: SenderEnvelope | null;
|
|
113
|
+
outcome: RetireDispatchOutcome;
|
|
114
|
+
/** Independent of `outcome` and `release`: a record failure never rewrites either. */
|
|
115
|
+
record: RetireRecordReceipt;
|
|
116
|
+
release: RetireReleaseReceipt;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const EVENT_ID_RE = /^[0-9a-f]{32}$/;
|
|
120
|
+
|
|
121
|
+
function causeOf(error: unknown): string {
|
|
122
|
+
return error instanceof Error ? error.message : String(error);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function retireCitizen(
|
|
126
|
+
targetGardenId: string,
|
|
127
|
+
deps: RetireDispatcherDeps,
|
|
128
|
+
): Promise<RetireDispatchReceipt> {
|
|
129
|
+
let actor: SenderEnvelope | null = null;
|
|
130
|
+
const beforeLock = (outcome: RetireDispatchOutcome): RetireDispatchReceipt => ({
|
|
131
|
+
targetGardenId,
|
|
132
|
+
actor,
|
|
133
|
+
outcome,
|
|
134
|
+
record: { status: "not-attempted" },
|
|
135
|
+
release: { status: "not-acquired" },
|
|
136
|
+
});
|
|
137
|
+
const aborted = () => deps.signal?.aborted === true;
|
|
138
|
+
if (!isValidSessionId(targetGardenId)) return beforeLock({ kind: "refused", reason: "target-invalid" });
|
|
139
|
+
let acquired: AcquireLockResult;
|
|
140
|
+
try {
|
|
141
|
+
actor = await deps.resolveCaller();
|
|
142
|
+
if (!actor || !isValidSessionId(actor.sessionId) || actor.origin === "external-mcp") {
|
|
143
|
+
return beforeLock({ kind: "refused", reason: "caller-unavailable" });
|
|
144
|
+
}
|
|
145
|
+
if (actor.sessionId === targetGardenId) return beforeLock({ kind: "refused", reason: "self-target" });
|
|
146
|
+
if (aborted()) return beforeLock({ kind: "refused", reason: "request-aborted" });
|
|
147
|
+
acquired = deps.acquireLock(targetGardenId);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
return beforeLock({ kind: "preflight-error", cause: causeOf(error) });
|
|
150
|
+
}
|
|
151
|
+
if (!acquired.ok) return beforeLock({ kind: "refused", reason: "target-locked", detail: acquired.conflict.detail });
|
|
152
|
+
const claim = acquired.claim;
|
|
153
|
+
const actorGardenId = actor.sessionId;
|
|
154
|
+
let enteredBackend: RetireTargetBackend | null = null;
|
|
155
|
+
// Assigned inside runLocked; the cast keeps control flow from narrowing it to null here.
|
|
156
|
+
let attempt = null as { eventId: string; context: RetireRecordContext } | null;
|
|
157
|
+
let record: RetireRecordReceipt = { status: "not-attempted" };
|
|
158
|
+
let outcome: RetireDispatchOutcome;
|
|
159
|
+
let release: RetireReleaseReceipt;
|
|
160
|
+
/** The last step before ONE adapter: cancellation, requested record, cancellation again.
|
|
161
|
+
* Returns the outcome that replaces the adapter, or null to enter it. */
|
|
162
|
+
const admit = (targetBackend: RetireTargetBackend): RetireDispatchOutcome | null => {
|
|
163
|
+
if (aborted()) return { kind: "refused", reason: "request-aborted" };
|
|
164
|
+
const context: RetireRecordContext = { actorGardenId, targetGardenId, targetBackend };
|
|
165
|
+
let eventId: unknown;
|
|
166
|
+
try {
|
|
167
|
+
eventId = deps.recordRequested(context);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
record = { status: "requested-append-error", cause: causeOf(error) };
|
|
170
|
+
return { kind: "refused", reason: "retirement-record-unavailable" };
|
|
171
|
+
}
|
|
172
|
+
if (typeof eventId !== "string" || !EVENT_ID_RE.test(eventId)) {
|
|
173
|
+
record = { status: "requested-append-error", cause: "recorder returned no event id" };
|
|
174
|
+
return { kind: "refused", reason: "retirement-record-unavailable" };
|
|
175
|
+
}
|
|
176
|
+
attempt = { eventId, context };
|
|
177
|
+
if (aborted()) return { kind: "refused", reason: "request-aborted" };
|
|
178
|
+
enteredBackend = targetBackend;
|
|
179
|
+
return null;
|
|
180
|
+
};
|
|
181
|
+
const runLocked = async (): Promise<RetireDispatchOutcome> => {
|
|
182
|
+
const socketPath = controlSocketPathIn(deps.controlSocketDir, targetGardenId);
|
|
183
|
+
if (!deps.recordExists(targetGardenId)) {
|
|
184
|
+
const inspection = await deps.inspectPath(socketPath);
|
|
185
|
+
return {
|
|
186
|
+
kind: "refused",
|
|
187
|
+
reason: isRecordLessSocketCandidate(inspection) ? "record-less-socket" : "target-not-found",
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
const identity = deps.readIdentity(targetGardenId);
|
|
191
|
+
if (identity.gardenId !== targetGardenId) throw new Error("addressable target read returned a different garden id");
|
|
192
|
+
if (identity.backend !== "pi" && identity.backend !== "claude-code") {
|
|
193
|
+
return { kind: "refused", reason: "target-backend-unsupported" };
|
|
194
|
+
}
|
|
195
|
+
const inspection = await deps.inspectPath(socketPath);
|
|
196
|
+
if (identity.backend === "pi") {
|
|
197
|
+
const live = await mapInspectionToLiveness(inspection, deps.probeSocket);
|
|
198
|
+
if ("addressConflict" in live) return { kind: "refused", reason: "target-address-conflict" };
|
|
199
|
+
if (live.liveness === "dead") return { kind: "refused", reason: "target-dead" };
|
|
200
|
+
if (live.liveness !== "alive") return { kind: "refused", reason: "target-indeterminate" };
|
|
201
|
+
// Fresh after awaited lstat/connect; timestamps are metadata, not birth identity.
|
|
202
|
+
const current = deps.readIdentity(targetGardenId);
|
|
203
|
+
if (
|
|
204
|
+
["gardenId", "backend", "nativeSessionId", "createdAt"].some(
|
|
205
|
+
(key) => current[key as keyof MetaIdentity] !== identity[key as keyof MetaIdentity],
|
|
206
|
+
)
|
|
207
|
+
) {
|
|
208
|
+
return { kind: "refused", reason: "target-drifted" };
|
|
209
|
+
}
|
|
210
|
+
const replaced = admit("pi");
|
|
211
|
+
if (replaced) return replaced;
|
|
212
|
+
const result = await deps.requestPi(
|
|
213
|
+
current,
|
|
214
|
+
{ gardenId: current.gardenId, nativeSessionId: current.nativeSessionId },
|
|
215
|
+
{
|
|
216
|
+
controlSocketDir: deps.controlSocketDir,
|
|
217
|
+
},
|
|
218
|
+
);
|
|
219
|
+
return { kind: "pi", result };
|
|
220
|
+
}
|
|
221
|
+
// Same out-of-socket-domain conflict policy as entwurf-v2-production. Non-socket
|
|
222
|
+
// regular entries do not claim a garden address; symlinks/socket entries do.
|
|
223
|
+
if (inspection.kind === "indeterminate")
|
|
224
|
+
throw new Error(`control address inspection indeterminate: ${inspection.error}`);
|
|
225
|
+
const sockets = new Set(inspection.kind === "socket-file" ? [targetGardenId] : []);
|
|
226
|
+
const symlinks = new Set(
|
|
227
|
+
inspection.kind === "address-conflict" && inspection.reason === "symlink" ? [targetGardenId] : [],
|
|
228
|
+
);
|
|
229
|
+
if (isOutOfSocketDomainGardenIdConflict(identity.backend, targetGardenId, sockets, symlinks)) {
|
|
230
|
+
return { kind: "refused", reason: "target-address-conflict" };
|
|
231
|
+
}
|
|
232
|
+
const replaced = admit("claude-code");
|
|
233
|
+
if (replaced) return replaced;
|
|
234
|
+
const { gardenId, backend, nativeSessionId, createdAt, attachIncarnation } = identity;
|
|
235
|
+
const result = await deps.requestCc({ gardenId, backend, nativeSessionId, createdAt, attachIncarnation });
|
|
236
|
+
return { kind: "claude-code", result };
|
|
237
|
+
};
|
|
238
|
+
try {
|
|
239
|
+
try {
|
|
240
|
+
outcome = await runLocked();
|
|
241
|
+
} catch (error) {
|
|
242
|
+
outcome =
|
|
243
|
+
enteredBackend === null
|
|
244
|
+
? { kind: "preflight-error", cause: causeOf(error) }
|
|
245
|
+
: { kind: "unknown", backend: enteredBackend, cause: causeOf(error) };
|
|
246
|
+
}
|
|
247
|
+
// An attempt that was recorded as requested gets ONE outcome record, before release.
|
|
248
|
+
if (attempt) {
|
|
249
|
+
const { eventId, context } = attempt;
|
|
250
|
+
try {
|
|
251
|
+
const returned: unknown = deps.recordOutcome(eventId, context, outcome);
|
|
252
|
+
record =
|
|
253
|
+
returned === undefined
|
|
254
|
+
? { status: "complete", eventId }
|
|
255
|
+
: { status: "outcome-append-error", eventId, cause: "recorder returned a value" };
|
|
256
|
+
} catch (error) {
|
|
257
|
+
record = { status: "outcome-append-error", eventId, cause: causeOf(error) };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
} finally {
|
|
261
|
+
// One acquired claim, one release attempt. No throw here may overwrite outcome.
|
|
262
|
+
try {
|
|
263
|
+
release = { status: deps.releaseLock(claim) };
|
|
264
|
+
} catch (error) {
|
|
265
|
+
release = { status: "error", cause: causeOf(error) };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return { targetGardenId, actor, outcome, record, release };
|
|
269
|
+
}
|
|
@@ -78,7 +78,8 @@ export function actionableRejectHint(reason: string): string | undefined {
|
|
|
78
78
|
"this citizen's record is intact but its session is not running, and delivery has no way to wake it. " +
|
|
79
79
|
"The resume that used to answer here launched a hidden background child and was withdrawn under the " +
|
|
80
80
|
"visible-first rule; the visible same-id resume is the separate lifecycle verb entwurf_resume_call. " +
|
|
81
|
-
"
|
|
81
|
+
"Only on an explicit operator request, use entwurf_resume_call {target} for Pi or Claude Code; " +
|
|
82
|
+
"it submits no prompt. This capability hint is not permission to resume automatically."
|
|
82
83
|
);
|
|
83
84
|
case "indeterminate-no-spawn":
|
|
84
85
|
// The socket probe did not settle, so liveness is UNKNOWN — an unestablished probe
|