@junghanacs/entwurf 0.12.10 → 0.13.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 +6 -3
- package/BASELINE.md +56 -163
- package/CHANGELOG.md +35 -0
- package/CONTRIBUTING.md +4 -2
- package/DELIVERY.md +117 -261
- package/README.md +74 -399
- package/VERIFY.md +59 -85
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +231 -0
- package/docs/external-mcp-host.md +132 -0
- package/docs/fresh-cut-policy.md +99 -0
- package/docs/setup-clean-host.md +123 -328
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +21 -48
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +12 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +155 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +16 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/engraving.js +42 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +66 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +190 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +12 -4
- package/mcp/entwurf-bridge/src/index.ts +21 -48
- package/package.json +14 -9
- package/pi/settings.reference.json +1 -1
- package/pi-extensions/acp-provider.ts +20 -10
- package/pi-extensions/entwurf-control.ts +20 -49
- package/pi-extensions/lib/acp/acp-client.ts +22 -0
- package/pi-extensions/lib/acp/augment.ts +99 -2
- package/pi-extensions/lib/acp/backend-adapter.ts +190 -14
- package/pi-extensions/lib/acp/backend.ts +310 -37
- package/pi-extensions/lib/acp/config.ts +19 -5
- package/pi-extensions/lib/acp/engraving.ts +46 -2
- package/pi-extensions/lib/acp/event-mapper.ts +26 -16
- package/pi-extensions/lib/acp/models.ts +69 -7
- package/pi-extensions/lib/acp/overlay.ts +234 -5
- package/pi-extensions/lib/acp/tool-surface.ts +12 -4
- package/prompts/engraving.md +13 -5
- package/run.sh +386 -119
- package/scripts/check-acp-carrier-augment.ts +246 -10
- package/scripts/check-acp-cortex.ts +668 -0
- package/scripts/check-acp-prompt-lifecycle.ts +565 -0
- package/scripts/check-acp-provider-surface.ts +50 -6
- package/scripts/check-acp-sdk-surface.ts +60 -21
- package/scripts/check-acp-session-reuse.ts +64 -1
- package/scripts/check-acp-stop-reason.ts +342 -0
- package/scripts/check-entwurf-v2-surface.ts +41 -0
- package/scripts/check-gate-qualification.ts +7 -2
- package/scripts/check-probe-cli-shim.ts +879 -0
- package/scripts/check-probe-ordering.ts +2482 -0
- package/scripts/check-release-gate-outcomes.ts +332 -0
- package/scripts/check-shell-quote.ts +4 -4
- package/scripts/fixtures/probe-cli-shim +20 -0
- package/scripts/fixtures/probe-mcp-server.ts +168 -12
- package/scripts/lib/live-skip.ts +33 -0
- package/scripts/lib/probe-acp-turn.ts +228 -0
- package/scripts/lib/probe-cli-shim.ts +464 -0
- package/scripts/lib/probe-cli-target.ts +165 -0
- package/scripts/lib/probe-event-log.ts +383 -0
- package/scripts/lib/probe-verdict.ts +1213 -0
- package/scripts/lib/step-outcome.sh +88 -0
- package/scripts/mutants/acp-augment.json +106 -0
- package/scripts/mutants/acp-cortex.json +196 -0
- package/scripts/mutants/acp-prompt-lifecycle.json +100 -0
- package/scripts/mutants/acp-stop-reason.json +80 -0
- package/scripts/mutants/probe-ordering.json +1054 -0
- package/scripts/mutants/release-gate.json +105 -0
- package/scripts/mutants/v2-surface.json +26 -15
- package/scripts/smoke-acp-bundled-mcp-live.ts +4 -3
- package/scripts/smoke-acp-carrier-augment-live.ts +2 -2
- package/scripts/smoke-acp-cortex-live.ts +386 -0
- package/scripts/smoke-acp-long-turn-live.ts +185 -0
- package/scripts/smoke-acp-mcp-live.ts +2 -2
- package/scripts/smoke-acp-memory-containment-live.ts +2 -2
- package/scripts/smoke-acp-ordering-probe-live.ts +848 -0
- package/scripts/smoke-acp-overlay-live.ts +2 -2
- package/scripts/smoke-acp-provider-live.ts +2 -2
- package/scripts/smoke-acp-raw-turn-live.ts +3 -3
- package/scripts/smoke-acp-session-reuse-live.ts +2 -2
- package/scripts/smoke-acp-skill-live.ts +2 -2
- package/scripts/smoke-acp-socket-citizen-live.ts +4 -3
- package/scripts/smoke-acp-v2-send-live.ts +4 -3
- package/scripts/smoke-agy-native-push-live.ts +4 -3
- package/scripts/smoke-claude-native-resume-live.sh +13 -3
- package/scripts/smoke-entwurf-chain-live.ts +352 -0
- package/scripts/smoke-entwurf-v2-matrix-live.ts +2 -2
- package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +2 -4
- package/scripts/smoke-meta-install-state.sh +4 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// §11-7 ordering probe — the ACP-side turn driver (probe-dedicated raw client).
|
|
2
|
+
//
|
|
3
|
+
// §11-7 names two client seams and requires picking one explicitly. This lane
|
|
4
|
+
// picks the RAW CLIENT (not the stdio wire proxy), so it carries §11-7's bound:
|
|
5
|
+
// it is NOT the production path and must be gated as issuing the SAME calls,
|
|
6
|
+
// arguments, and order as the backend's real sequence (backend.ts runNewTurn) —
|
|
7
|
+
// otherwise the probe measures a lookalike. check-probe-ordering is that gate;
|
|
8
|
+
// it drives this exact function over a recording fake connection.
|
|
9
|
+
//
|
|
10
|
+
// Production values arrive through the `adapter` / `enrichMcpServers` seams —
|
|
11
|
+
// the LIVE runner tsc-emits pi-extensions and injects the REAL claudeAdapter +
|
|
12
|
+
// enrichMcpServersWithEnvelope (backend-adapter.ts carries `.js` value imports,
|
|
13
|
+
// so it is not strip-types-loadable from here; the emit-then-import pattern is
|
|
14
|
+
// the house one, see check-acp-session-reuse). This module therefore holds ONLY
|
|
15
|
+
// the orchestration backend.ts owns: phase order, arg assembly, timeouts, and
|
|
16
|
+
// phase-attributed failure — each pinned against backend.ts source by the gate
|
|
17
|
+
// so production drift turns the gate red instead of silently unbinding the probe.
|
|
18
|
+
//
|
|
19
|
+
// Every phase start/end is stamped into the shared NDJSON log — including
|
|
20
|
+
// set-model, because §11-7 measured that dropping it misreads an enforceModel
|
|
21
|
+
// stall as C or D.
|
|
22
|
+
|
|
23
|
+
import { PROTOCOL_VERSION } from "@agentclientprotocol/sdk";
|
|
24
|
+
import type { AcpConnectionLike } from "../../pi-extensions/lib/acp/acp-client.ts";
|
|
25
|
+
import type { ResolvedAcpConfig } from "../../pi-extensions/lib/acp/config.ts";
|
|
26
|
+
import { PROBE_EVENTS, type ProbeEventName } from "./probe-event-log.ts";
|
|
27
|
+
|
|
28
|
+
export interface ProbePhaseTimeouts {
|
|
29
|
+
initializeMs: number;
|
|
30
|
+
newSessionMs: number;
|
|
31
|
+
setModelMs: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// BOOTSTRAP phase timeouts — MUST equal backend.ts's INITIALIZE/NEW_SESSION/
|
|
35
|
+
// SET_MODEL_TIMEOUT_MS (check-probe-ordering pins them against the source).
|
|
36
|
+
// §11-7: a bootstrap-phase D verdict is only readable against production's own
|
|
37
|
+
// boundaries, so the probe may not invent its own.
|
|
38
|
+
export const PROBE_PHASE_TIMEOUTS: ProbePhaseTimeouts = {
|
|
39
|
+
initializeMs: 30_000,
|
|
40
|
+
newSessionMs: 30_000,
|
|
41
|
+
setModelMs: 30_000,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// The prompt phase has NO production counterpart to match: backend.ts ends a
|
|
45
|
+
// prompt on lifecycle events only (resolve / abort / child gone) and carries no
|
|
46
|
+
// wall-clock cutoff, by policy — an active turn is not a failed turn for being
|
|
47
|
+
// long. This value is therefore the MEASUREMENT HARNESS's own bounded
|
|
48
|
+
// observation horizon: it stops a probe RUN from hanging forever. It is not a
|
|
49
|
+
// production contract, not an equivalence, and not a deadline any §11-7 verdict
|
|
50
|
+
// is read against. Horizon expiry means the run's prompt window is
|
|
51
|
+
// INCONCLUSIVE — the artifact is preserved and read as "not measured", never as
|
|
52
|
+
// a model of production killing a turn.
|
|
53
|
+
export const PROBE_PROMPT_OBSERVATION_MS = 600_000;
|
|
54
|
+
|
|
55
|
+
/** The two adapter methods the turn drives — satisfied by the REAL claudeAdapter
|
|
56
|
+
* (emitted) in the LIVE runner and by recording stubs in the gate. */
|
|
57
|
+
export interface ProbeAdapterSeam {
|
|
58
|
+
buildSessionMeta(
|
|
59
|
+
params: { modelId: string; nativeModelId: string; config: ResolvedAcpConfig },
|
|
60
|
+
carrier: string | null,
|
|
61
|
+
): Record<string, unknown> | undefined;
|
|
62
|
+
enforceModel(params: {
|
|
63
|
+
connection: AcpConnectionLike;
|
|
64
|
+
acpSessionId: string;
|
|
65
|
+
modelId: string;
|
|
66
|
+
nativeModelId: string;
|
|
67
|
+
}): Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Production's enrichMcpServersWithEnvelope shape (config.ts). */
|
|
71
|
+
export type ProbeMcpEnricher = (
|
|
72
|
+
servers: ResolvedAcpConfig["mcpServers"],
|
|
73
|
+
envelope: { modelId?: string; piSessionId?: string },
|
|
74
|
+
) => ResolvedAcpConfig["mcpServers"];
|
|
75
|
+
|
|
76
|
+
/** The turn phases in production order. `initialize` failures are P0/I0 input
|
|
77
|
+
* (run-invalidating), the rest map to phase-qualified D readings. */
|
|
78
|
+
export type ProbeTurnPhase = "initialize" | "newSession" | "enforceModel" | "prompt";
|
|
79
|
+
|
|
80
|
+
/** A phase-attributed turn failure — the classifier needs WHICH wire step died,
|
|
81
|
+
* never just that the turn died. */
|
|
82
|
+
export class ProbePhaseError extends Error {
|
|
83
|
+
readonly phase: ProbeTurnPhase;
|
|
84
|
+
readonly timedOut: boolean;
|
|
85
|
+
|
|
86
|
+
constructor(phase: ProbeTurnPhase, cause: Error, timedOut: boolean) {
|
|
87
|
+
super(`${phase}: ${cause.message}`);
|
|
88
|
+
this.phase = phase;
|
|
89
|
+
this.timedOut = timedOut;
|
|
90
|
+
this.cause = cause;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
class PhaseTimeoutError extends Error {}
|
|
95
|
+
|
|
96
|
+
// Same guard shape as backend.ts withTimeout / smoke-acp-raw-turn-live: always
|
|
97
|
+
// clear the timer so a passing turn cannot pin the event loop on a stale timer.
|
|
98
|
+
function withPhaseTimeout<T>(label: string, p: Promise<T>, ms: number): Promise<T> {
|
|
99
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
100
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
101
|
+
timer = setTimeout(() => reject(new PhaseTimeoutError(`${label} timed out after ${ms}ms`)), ms);
|
|
102
|
+
});
|
|
103
|
+
return Promise.race([p, timeout]).finally(() => {
|
|
104
|
+
if (timer) clearTimeout(timer);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface ProbeTurnParams {
|
|
109
|
+
cwd: string;
|
|
110
|
+
modelId: string;
|
|
111
|
+
nativeModelId: string;
|
|
112
|
+
config: ResolvedAcpConfig;
|
|
113
|
+
/** Production carrier (adapter.loadCarrier result) — folded into `_meta` by
|
|
114
|
+
* buildSessionMeta so the probe's newSession matches production shape. */
|
|
115
|
+
carrier: string | null;
|
|
116
|
+
promptText: string;
|
|
117
|
+
adapter: ProbeAdapterSeam;
|
|
118
|
+
enrichMcpServers: ProbeMcpEnricher;
|
|
119
|
+
log: (event: ProbeEventName, payload?: Record<string, unknown>) => void;
|
|
120
|
+
/** Bootstrap boundaries (production-pinned). Defaults to PROBE_PHASE_TIMEOUTS. */
|
|
121
|
+
timeouts?: ProbePhaseTimeouts;
|
|
122
|
+
/** Harness observation horizon for the prompt phase — NOT a production deadline. */
|
|
123
|
+
promptObservationMs?: number;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface ProbeTurnResult {
|
|
127
|
+
acpSessionId: string;
|
|
128
|
+
stopReason: string | undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function runPhase<T>(
|
|
132
|
+
phase: ProbeTurnPhase,
|
|
133
|
+
startEvent: ProbeEventName,
|
|
134
|
+
endEvent: ProbeEventName,
|
|
135
|
+
log: ProbeTurnParams["log"],
|
|
136
|
+
ms: number,
|
|
137
|
+
body: () => Promise<T>,
|
|
138
|
+
): Promise<T> {
|
|
139
|
+
log(startEvent, { timeoutMs: ms });
|
|
140
|
+
try {
|
|
141
|
+
const result = await withPhaseTimeout(phase, body(), ms);
|
|
142
|
+
log(endEvent, { ok: true });
|
|
143
|
+
return result;
|
|
144
|
+
} catch (err) {
|
|
145
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
146
|
+
const timedOut = error instanceof PhaseTimeoutError;
|
|
147
|
+
log(endEvent, { ok: false, timedOut, error: error.message });
|
|
148
|
+
throw new ProbePhaseError(phase, error, timedOut);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Drive ONE production-shaped turn over an already-connected ACP connection:
|
|
154
|
+
*
|
|
155
|
+
* initialize → newSession → enforceModel(setSessionConfigOption) → prompt
|
|
156
|
+
*
|
|
157
|
+
* The connection seam is `AcpConnectionLike` — the same seam backend.ts drives —
|
|
158
|
+
* so the sameness gate runs this function over a recording fake with zero
|
|
159
|
+
* launch/IO coupling.
|
|
160
|
+
*/
|
|
161
|
+
export async function driveProbeTurn(connection: AcpConnectionLike, params: ProbeTurnParams): Promise<ProbeTurnResult> {
|
|
162
|
+
const t = params.timeouts ?? PROBE_PHASE_TIMEOUTS;
|
|
163
|
+
const log = params.log;
|
|
164
|
+
|
|
165
|
+
await runPhase("initialize", PROBE_EVENTS.initializeStart, PROBE_EVENTS.initializeEnd, log, t.initializeMs, () =>
|
|
166
|
+
connection.initialize({
|
|
167
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
168
|
+
clientCapabilities: {},
|
|
169
|
+
clientInfo: { name: "entwurf", version: "s2d" },
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// _meta + wire servers exactly as backend.ts assembles them (S2g / carrier-less shape): the
|
|
174
|
+
// injected production buildSessionMeta (omitted key when undefined) + the
|
|
175
|
+
// injected production envelope enrichment over the resolved config.
|
|
176
|
+
const sessionMeta = params.adapter.buildSessionMeta(
|
|
177
|
+
{ modelId: params.modelId, nativeModelId: params.nativeModelId, config: params.config },
|
|
178
|
+
params.carrier,
|
|
179
|
+
);
|
|
180
|
+
const wireMcpServers = params.enrichMcpServers(params.config.mcpServers, {
|
|
181
|
+
modelId: params.modelId,
|
|
182
|
+
piSessionId: process.env.PI_SESSION_ID?.trim() || undefined,
|
|
183
|
+
});
|
|
184
|
+
const newSessionArgs =
|
|
185
|
+
sessionMeta === undefined
|
|
186
|
+
? { cwd: params.cwd, mcpServers: wireMcpServers }
|
|
187
|
+
: { cwd: params.cwd, mcpServers: wireMcpServers, _meta: sessionMeta };
|
|
188
|
+
|
|
189
|
+
const created = await runPhase(
|
|
190
|
+
"newSession",
|
|
191
|
+
PROBE_EVENTS.newSessionStart,
|
|
192
|
+
PROBE_EVENTS.newSessionEnd,
|
|
193
|
+
log,
|
|
194
|
+
t.newSessionMs,
|
|
195
|
+
() => connection.newSession(newSessionArgs),
|
|
196
|
+
);
|
|
197
|
+
const acpSessionId = created?.sessionId;
|
|
198
|
+
if (!acpSessionId) {
|
|
199
|
+
throw new ProbePhaseError("newSession", new Error("newSession returned no sessionId"), false);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
await runPhase("enforceModel", PROBE_EVENTS.setModelStart, PROBE_EVENTS.setModelEnd, log, t.setModelMs, () =>
|
|
203
|
+
params.adapter.enforceModel({
|
|
204
|
+
connection,
|
|
205
|
+
acpSessionId,
|
|
206
|
+
modelId: params.modelId,
|
|
207
|
+
nativeModelId: params.nativeModelId,
|
|
208
|
+
}),
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// The horizon below bounds THIS MEASUREMENT RUN, not the turn: production has
|
|
212
|
+
// no prompt deadline, so an expiry here is an inconclusive observation.
|
|
213
|
+
const promptObservationMs = params.promptObservationMs ?? PROBE_PROMPT_OBSERVATION_MS;
|
|
214
|
+
const promptResult = await runPhase(
|
|
215
|
+
"prompt",
|
|
216
|
+
PROBE_EVENTS.promptStart,
|
|
217
|
+
PROBE_EVENTS.promptEnd,
|
|
218
|
+
log,
|
|
219
|
+
promptObservationMs,
|
|
220
|
+
() =>
|
|
221
|
+
connection.prompt({
|
|
222
|
+
sessionId: acpSessionId,
|
|
223
|
+
prompt: [{ type: "text", text: params.promptText }],
|
|
224
|
+
}),
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
return { acpSessionId, stopReason: promptResult?.stopReason };
|
|
228
|
+
}
|