@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
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// ACP backend adapter rail — the PRODUCT seam by which a curated model id selects
|
|
2
|
-
// which ACP backend (claude / future backend / …) drives a turn. See
|
|
2
|
+
// which ACP backend (claude / future backend / …) drives a turn. See the adapter contract in
|
|
3
|
+
// docs/acp-backend-rail.md.
|
|
3
4
|
//
|
|
4
5
|
// This seam is DISTINCT from `AcpTurnDeps` (backend.ts), which is the test/runtime
|
|
5
|
-
// seam (fake spawn/connection/clock for the gates).
|
|
6
|
-
//
|
|
6
|
+
// seam (fake spawn/connection/clock for the gates). Merging them would make a
|
|
7
|
+
// fake-deps fixture look like
|
|
7
8
|
// a fake backend and force the adapter to carry clock/sessionDir/createConnection.
|
|
8
9
|
// The wiring is `defaultDeps(adapter)` — the turn loop in backend.ts stays
|
|
9
10
|
// backend-invariant; only these per-backend functions change with `adapter`.
|
|
@@ -19,15 +20,32 @@
|
|
|
19
20
|
|
|
20
21
|
import { readFileSync } from "node:fs";
|
|
21
22
|
import { createRequire } from "node:module";
|
|
23
|
+
import { homedir } from "node:os";
|
|
22
24
|
import { dirname, join } from "node:path";
|
|
23
25
|
|
|
24
26
|
import type { AcpConnectionLike } from "./acp-client.js";
|
|
25
|
-
import type
|
|
27
|
+
import { enrichMcpServersWithEnvelope, type ResolvedAcpConfig } from "./config.js";
|
|
26
28
|
import { loadEngraving } from "./engraving.js";
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
+
import {
|
|
30
|
+
CORTEX_MODEL_PREFIX,
|
|
31
|
+
curatedClaudeModels,
|
|
32
|
+
curatedCortexModels,
|
|
33
|
+
SUPPORTED_ANTHROPIC_MODEL_IDS,
|
|
34
|
+
SUPPORTED_CORTEX_MODEL_IDS,
|
|
35
|
+
} from "./models.js";
|
|
36
|
+
import { claudeLaunchEnvDefaults, ensureClaudeConfigOverlay, ensureCortexDualHomeOverlay } from "./overlay.js";
|
|
29
37
|
import { buildClaudeSessionMeta } from "./tool-surface.js";
|
|
30
38
|
|
|
39
|
+
// POSIX-safe single-quote wrapper for shell arg interpolation. Byte-for-byte
|
|
40
|
+
// identical to the reference in entwurf-core.ts; PARITY-PINNED by
|
|
41
|
+
// scripts/check-shell-quote.ts (SOURCE_SITES). Used only by the cortex override
|
|
42
|
+
// path below, where operator-configured connection/model tokens are appended to
|
|
43
|
+
// an operator `bash -lc` string — quoting keeps a connection name with shell
|
|
44
|
+
// metacharacters from being reinterpreted by the shell.
|
|
45
|
+
function shellQuote(value: string): string {
|
|
46
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
47
|
+
}
|
|
48
|
+
|
|
31
49
|
// ---------------------------------------------------------------------------
|
|
32
50
|
// Shared shapes
|
|
33
51
|
// ---------------------------------------------------------------------------
|
|
@@ -64,13 +82,19 @@ export interface AcpCarrierParams {
|
|
|
64
82
|
|
|
65
83
|
/** ensureOverlay input — cwd + (native) model id + the resolved config. A backend
|
|
66
84
|
* whose overlay/env depends on its OWN settings reads them off `config.adapterSettings`
|
|
67
|
-
* here; backend.ts never inspects config. Same shape as AcpLaunchParams
|
|
68
|
-
* launch are distinct phases, so they keep distinct names). */
|
|
85
|
+
* here; backend.ts never inspects config. Same shape as AcpLaunchParams plus the
|
|
86
|
+
* session key (overlay and launch are distinct phases, so they keep distinct names). */
|
|
69
87
|
export interface AcpOverlayParams {
|
|
70
88
|
cwd: string;
|
|
71
89
|
modelId: string;
|
|
72
90
|
nativeModelId: string;
|
|
73
91
|
config: ResolvedAcpConfig;
|
|
92
|
+
/** The AUTHORITATIVE per-session key backend.ts already computed
|
|
93
|
+
* (resolveSessionKey: opts.sessionId → PI_SESSION_ID → cwd). A session-scoped
|
|
94
|
+
* overlay MUST scope on this value, never on an ambient re-derivation — the
|
|
95
|
+
* re-derived form drops `opts.sessionId`, so two sessions in one process/cwd
|
|
96
|
+
* would alias one overlay (GPT review 2026-07-29, cortex P0-1). */
|
|
97
|
+
sessionKey: string;
|
|
74
98
|
}
|
|
75
99
|
|
|
76
100
|
/** buildSessionMeta input — mirrors the newSession `_meta` inputs. */
|
|
@@ -194,7 +218,7 @@ function resolveClaudeLaunch(): AcpLaunchSpec {
|
|
|
194
218
|
export const claudeAdapter: AcpBackendAdapter = {
|
|
195
219
|
backend: "claude",
|
|
196
220
|
|
|
197
|
-
// Claude owns its UNPREFIXED curated ids only (
|
|
221
|
+
// Claude owns its UNPREFIXED curated ids only (rail “Adapter contract”). The native id
|
|
198
222
|
// equals the curated id — claude carries no curation prefix to strip.
|
|
199
223
|
routeModel(modelId) {
|
|
200
224
|
return SUPPORTED_CLAUDE_IDS.has(modelId) ? { nativeModelId: modelId } : undefined;
|
|
@@ -270,14 +294,166 @@ export const claudeAdapter: AcpBackendAdapter = {
|
|
|
270
294
|
// ---------------------------------------------------------------------------
|
|
271
295
|
|
|
272
296
|
/** Registered adapters. Order carries NO routing authority — routeModel decides.
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
|
|
297
|
+
* claude (unprefixed ids) + cortex (the `cortex-` prefix). A further backend
|
|
298
|
+
* appends here with its own reserved prefix; the fail-fast below proves no two
|
|
299
|
+
* adapters claim one id. */
|
|
300
|
+
// ---------------------------------------------------------------------------
|
|
301
|
+
// cortex adapter — Snowflake Cortex Code, the first non-claude backend on the
|
|
302
|
+
// rail (docs/acp-backend-rail.md, “Shipped adapters”). It adds ZERO to the common layer:
|
|
303
|
+
// everything cortex-specific lives here + models.ts + overlay.ts + the gates.
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
const SUPPORTED_CORTEX_IDS: ReadonlySet<string> = new Set(SUPPORTED_CORTEX_MODEL_IDS);
|
|
307
|
+
|
|
308
|
+
/** Cortex's OWN settings: a Snowflake connection name, or null. Opaque to
|
|
309
|
+
* config.ts / backend.ts — only cortexAdapter reads it (casting back). */
|
|
310
|
+
export interface CortexAdapterSettings {
|
|
311
|
+
cortexConnection: string | null;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** The env var an operator sets to pin a Snowflake connection per-shell without
|
|
315
|
+
* editing settings.json. Wins over `entwurfProvider.cortexConnection`. The
|
|
316
|
+
* ENTWURF_ACP_* convention (PR #40's legacy PI_SHELL_ACP* cortex-connection var renamed). */
|
|
317
|
+
export const CORTEX_CONNECTION_ENV = "ENTWURF_ACP_CORTEX_CONNECTION";
|
|
318
|
+
|
|
319
|
+
export const cortexAdapter: AcpBackendAdapter = {
|
|
320
|
+
backend: "cortex",
|
|
321
|
+
|
|
322
|
+
// Cortex owns the reserved `cortex-` prefix (rail “Adapter contract”). routeModel strips it to the
|
|
323
|
+
// native id: `cortex-auto` → "auto", `cortex-claude-sonnet-5` → "claude-sonnet-5".
|
|
324
|
+
routeModel(modelId) {
|
|
325
|
+
if (!SUPPORTED_CORTEX_IDS.has(modelId)) return undefined;
|
|
326
|
+
return { nativeModelId: modelId.slice(CORTEX_MODEL_PREFIX.length) };
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
curatedModels() {
|
|
330
|
+
return curatedCortexModels();
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
// Cortex's ONLY own setting is the connection name. env override wins over
|
|
334
|
+
// settings (per-shell pin); empty/whitespace → null (Cortex falls back to its
|
|
335
|
+
// own default connection). A non-string settings value fails loud.
|
|
336
|
+
resolveAdapterSettings({ mergedBlock, projectBlock, globalPath, projectPath }): CortexAdapterSettings {
|
|
337
|
+
const raw = mergedBlock.cortexConnection;
|
|
338
|
+
if (raw !== undefined && typeof raw !== "string") {
|
|
339
|
+
const offending = projectBlock.cortexConnection !== undefined ? projectPath : globalPath;
|
|
340
|
+
throw new Error(`${offending}: invalid entwurfProvider settings: cortexConnection must be a string`);
|
|
341
|
+
}
|
|
342
|
+
const envConn = process.env[CORTEX_CONNECTION_ENV]?.trim();
|
|
343
|
+
const settingsConn = raw?.trim();
|
|
344
|
+
const cortexConnection = envConn || settingsConn || null;
|
|
345
|
+
return { cortexConnection };
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
// `cortex acp serve` resolved from PATH (the CLI itself IS the ACP server — no
|
|
349
|
+
// `*-acp` npm package, unlike claude). `-c <conn>` appended when a connection
|
|
350
|
+
// is pinned. NO `-m`: the model is enforced per-turn via
|
|
351
|
+
// session/set_config_option (enforceModel below, CP0-M measured GO) and a
|
|
352
|
+
// launch pin would be a SECOND model authority that drifts from it.
|
|
353
|
+
// CORTEX_ACP_COMMAND override runs via `bash -lc` with the selection flags
|
|
354
|
+
// appended so the bridge's choice wins (later yargs args override earlier ones).
|
|
355
|
+
resolveLaunch({ config }) {
|
|
356
|
+
const settings = config.adapterSettings as CortexAdapterSettings | undefined;
|
|
357
|
+
const connection = settings?.cortexConnection?.trim() || undefined;
|
|
358
|
+
const selectionArgs: string[] = [];
|
|
359
|
+
if (connection) selectionArgs.push("-c", connection);
|
|
360
|
+
const override = process.env.CORTEX_ACP_COMMAND?.trim();
|
|
361
|
+
if (override) {
|
|
362
|
+
const command = selectionArgs.length > 0 ? `${override} ${selectionArgs.map(shellQuote).join(" ")}` : override;
|
|
363
|
+
return { command: "bash", args: ["-lc", command] };
|
|
364
|
+
}
|
|
365
|
+
return { command: "cortex", args: ["acp", "serve", ...selectionArgs] };
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
// The overlay location is SESSION-SCOPED (never static), so the spawn env
|
|
369
|
+
// rides ensureOverlay(...).envOverrides; there is no static launch env. The
|
|
370
|
+
// v1.1.8-era CORTEX_DISABLE_AUTO_APPLY_PROFILES knob was retired with the
|
|
371
|
+
// dual-HOME redesign: profiles now live inside the overlay-owned isolated
|
|
372
|
+
// home (empty by construction), and the knob is unmeasured on v1.1.52.
|
|
373
|
+
launchEnvDefaults() {
|
|
374
|
+
return {};
|
|
375
|
+
},
|
|
376
|
+
|
|
377
|
+
// Dual-HOME containment (CP0 D2/D3/D9/D10 — see the overlay module header):
|
|
378
|
+
// refuse an ambient CORTEX_HOME outright, then materialize the session-scoped
|
|
379
|
+
// isolated HOME with auth symlinks, `autoUpdate:false`, and the mcp.json
|
|
380
|
+
// projection of the envelope-enriched explicit servers (cortex ignores the
|
|
381
|
+
// wire mcpServers param, so this file IS how tools reach a cortex session).
|
|
382
|
+
ensureOverlay({ modelId, config, sessionKey }) {
|
|
383
|
+
// D3 — presence refusal, empty string included: upstream's resolver treats
|
|
384
|
+
// a set-but-empty CORTEX_HOME differently from unset, and one ambient value
|
|
385
|
+
// would silently bypass SNOWFLAKE_HOME (the probe's CLAUDE_CODE_EXECUTABLE
|
|
386
|
+
// precondition is the same family). Refuse the ambiguity; never pick a side.
|
|
387
|
+
if ("CORTEX_HOME" in process.env) {
|
|
388
|
+
throw new Error(
|
|
389
|
+
"entwurf: CORTEX_HOME is present in the environment (empty string included) — it overrides " +
|
|
390
|
+
"SNOWFLAKE_HOME inside cortex and would bypass the dual-HOME overlay entirely (CP0 D3). " +
|
|
391
|
+
"Unset it to run a cortex ACP turn.",
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
// The scope authority is the AUTHORITATIVE params.sessionKey backend.ts
|
|
395
|
+
// computed — never an ambient re-derivation, which would drop opts.sessionId
|
|
396
|
+
// and alias two same-process/cwd sessions onto one overlay (P0-1). The
|
|
397
|
+
// envelope below still reads PI_SESSION_ID: that is the identity CARRIER for
|
|
398
|
+
// the bridge child (the same source the turn loop's wire enrichment uses),
|
|
399
|
+
// a different axis from overlay-dir scoping.
|
|
400
|
+
const piSessionId = process.env.PI_SESSION_ID?.trim() || undefined;
|
|
401
|
+
const enriched = enrichMcpServersWithEnvelope(config.mcpServers, { modelId, piSessionId });
|
|
402
|
+
const overlay = ensureCortexDualHomeOverlay({
|
|
403
|
+
scopeKey: sessionKey,
|
|
404
|
+
mcpServers: enriched,
|
|
405
|
+
realHome: homedir(),
|
|
406
|
+
});
|
|
407
|
+
return { envOverrides: { HOME: overlay.home, SNOWFLAKE_HOME: overlay.snowflakeHome } };
|
|
408
|
+
},
|
|
409
|
+
|
|
410
|
+
// System-prompt-carrier-less (ACP rail “Cortex Code audit”): Cortex ACP exposes no
|
|
411
|
+
// `_meta.systemPrompt` and has no developer_instructions / GEMINI_SYSTEM_MD
|
|
412
|
+
// equivalent. (It does READ `_meta` — a caller-session-id seam, measured but
|
|
413
|
+
// unexplored and deliberately not part of this contract.) loadCarrier returns
|
|
414
|
+
// null WITHOUT calling loadEngraving, so the cortex turn never touches the
|
|
415
|
+
// shipped-engraving / appendSystemPrompt signature; buildSessionMeta returns
|
|
416
|
+
// undefined so backend.ts omits the `_meta` key entirely. The operator
|
|
417
|
+
// engraving instead rides the first-user augment (augment.ts).
|
|
418
|
+
loadCarrier() {
|
|
419
|
+
return null;
|
|
420
|
+
},
|
|
421
|
+
|
|
422
|
+
buildSessionMeta() {
|
|
423
|
+
return undefined;
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
// Per-turn enforcement via session/set_config_option — the SAME wire call the
|
|
427
|
+
// claude adapter makes, measured live against cortex v1.1.52 (CP0-M): the
|
|
428
|
+
// option id is "model", accepted values are the NATIVE ids (`auto`,
|
|
429
|
+
// `claude-sonnet-5`, `openai-gpt-5.4`, …), and a value cortex no longer
|
|
430
|
+
// serves fails loud BEFORE the prompt (`Unsupported model: …`). PR #40's
|
|
431
|
+
// launch-time `-m` pin was retired for this: set-model is the single model
|
|
432
|
+
// authority (resolveLaunch never passes `-m`), and "auto" is set explicitly
|
|
433
|
+
// rather than treated as an unspoken default.
|
|
434
|
+
async enforceModel({ connection, acpSessionId, nativeModelId, modelId }) {
|
|
435
|
+
const setConfig = connection.setSessionConfigOption;
|
|
436
|
+
if (typeof setConfig !== "function") {
|
|
437
|
+
throw new Error(`setSessionConfigOption unsupported — cannot enforce model ${modelId}`);
|
|
438
|
+
}
|
|
439
|
+
await setConfig.call(connection, { sessionId: acpSessionId, configId: "model", value: nativeModelId });
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
// A connection change must invalidate a reused session (rail: Adapter contract). Flat,
|
|
443
|
+
// sorted-stable primitive map; reads ONLY the opaque adapterSettings. `backend`
|
|
444
|
+
// + `nativeModelId` are added by backend.ts.
|
|
445
|
+
configSignatureFields(adapterSettings) {
|
|
446
|
+
const settings = adapterSettings as CortexAdapterSettings | undefined;
|
|
447
|
+
return { cortexConnection: settings?.cortexConnection ?? null };
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
const ADAPTERS: readonly AcpBackendAdapter[] = [claudeAdapter, cortexAdapter];
|
|
276
452
|
|
|
277
453
|
/**
|
|
278
454
|
* Resolve the backend adapter that owns `modelId`.
|
|
279
455
|
*
|
|
280
|
-
*
|
|
456
|
+
* Routing fail-fast contract (rail “Adapter contract”):
|
|
281
457
|
* - 0 matches → throw (unknown model — no silent default).
|
|
282
458
|
* - 2+ matches → throw (prefix collision — a startup-visible registry bug).
|
|
283
459
|
*
|
|
@@ -301,7 +477,7 @@ export function resolveAcpBackendAdapter(modelId: string): { adapter: AcpBackend
|
|
|
301
477
|
}
|
|
302
478
|
|
|
303
479
|
/** Every curated model row across all registered adapters — for provider registration.
|
|
304
|
-
*
|
|
480
|
+
* Registration-time fail-fast (rail “Adapter contract”): every curated id must route to EXACTLY
|
|
305
481
|
* one adapter and no id may be duplicated across adapters. Catching it here means a
|
|
306
482
|
* prefix-collision / duplicate surfaces at provider registration, not mid-turn. */
|
|
307
483
|
export function allCuratedModels(): AcpModelRow[] {
|