super-dsh 0.1.5 → 0.1.6

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.
Files changed (35) hide show
  1. package/agent-agy/dist/agent-preset-projection.d.ts +31 -0
  2. package/agent-agy/dist/agent-preset-projection.js +33 -0
  3. package/agent-claude/dist/agent-preset-projection.js +30 -0
  4. package/agent-codex/dist/agent-preset-projection.js +37 -0
  5. package/agent-codex/dist/app-home.js +65 -0
  6. package/agent-codex/dist/spike.js +46 -0
  7. package/agent-hermes/dist/agent-preset-projection.js +34 -0
  8. package/agent-omp/dist/agent-preset-projection.js +36 -0
  9. package/agent-omp/dist/app-home.js +86 -0
  10. package/agent-pi/dist/agent-preset-projection.js +29 -0
  11. package/client/index.js +273 -93
  12. package/cordis.patch.yml +6 -5
  13. package/package.json +2 -2
  14. package/superd-hub/dist/agent-roster.d.ts +4 -0
  15. package/superd-hub/dist/agent-roster.js +2 -2
  16. package/superd-hub/dist/client-shim.d.ts +28 -0
  17. package/superd-hub/dist/client-shim.js +251 -0
  18. package/superd-hub/dist/fusion-transport.d.ts +23 -21
  19. package/superd-hub/dist/fusion-transport.js +12 -40
  20. package/superd-hub/dist/index-pass.d.ts +22 -0
  21. package/superd-hub/dist/index-pass.js +47 -0
  22. package/superd-hub/dist/index.d.ts +3 -3
  23. package/superd-hub/dist/index.js +3 -3
  24. package/superd-hub/dist/labels.d.ts +45 -0
  25. package/superd-hub/dist/labels.js +83 -0
  26. package/superd-hub/dist/roster.d.ts +4 -0
  27. package/superd-hub/dist/roster.js +9 -1
  28. package/superd-hub/dist/switch-core.d.ts +40 -0
  29. package/superd-hub/dist/switch-core.js +72 -0
  30. package/superd-hub/dist/toast-core.d.ts +43 -0
  31. package/superd-hub/dist/toast-core.js +63 -0
  32. package/superd-hub/dist/world-join.js +8 -3
  33. package/superd-hub/dist/world-mount.d.ts +27 -0
  34. package/superd-hub/dist/world-mount.js +67 -0
  35. package/superd-hub/lib/client/index.js +273 -93
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The `agentPreset` session projection, owned by the adapter.
3
+ *
4
+ * Upstream registers this fold from the `dsh-agent-presets` package, which our
5
+ * patch disables (the Antigravity provider replaces preset composition
6
+ * wholesale). But the Web UI depends on the projection regardless: the preset
7
+ * hero chip renders only when `session.projectionValues.agentPreset` resolves
8
+ * (presetOf in ui-agent-preset/seat-store.ts), and the session-header label
9
+ * reads it too. Without a registrant the projection is absent for every
10
+ * adapter session and both surfaces stay blank — roster or no roster.
11
+ *
12
+ * Same fold as upstream (packages/preset/agent-presets/src/session.ts):
13
+ * initialized from the creation header, advanced by `agent-preset/selected`
14
+ * events, which this adapter synthesizes (`"agy"`) on every session it
15
+ * prepares. Registered under the provider's own fiber via
16
+ * `ctx.inject(['sessionProjections'])`, so the key lives exactly as long as
17
+ * the provider is mounted.
18
+ */
19
+ import { z } from "zod";
20
+ /** Current Session preset, initialized from its header and advanced by selection events. */
21
+ export declare const agyAgentPresetProjection: {
22
+ key: "agentPreset";
23
+ stateSchema: z.ZodUnion<readonly [z.ZodString, z.ZodNull]>;
24
+ init: (header: import("@deepseek-ai/dsh-session").SessionHeader) => string;
25
+ apply: (state: string | null, event: import("@deepseek-ai/dsh-session").SessionEvent) => string | null;
26
+ wire: {
27
+ viewSchema: z.ZodUnion<readonly [z.ZodString, z.ZodNull]>;
28
+ view: (state: string | null) => string | null;
29
+ };
30
+ stateVersion: number;
31
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The `agentPreset` session projection, owned by the adapter.
3
+ *
4
+ * Upstream registers this fold from the `dsh-agent-presets` package, which our
5
+ * patch disables (the Antigravity provider replaces preset composition
6
+ * wholesale). But the Web UI depends on the projection regardless: the preset
7
+ * hero chip renders only when `session.projectionValues.agentPreset` resolves
8
+ * (presetOf in ui-agent-preset/seat-store.ts), and the session-header label
9
+ * reads it too. Without a registrant the projection is absent for every
10
+ * adapter session and both surfaces stay blank — roster or no roster.
11
+ *
12
+ * Same fold as upstream (packages/preset/agent-presets/src/session.ts):
13
+ * initialized from the creation header, advanced by `agent-preset/selected`
14
+ * events, which this adapter synthesizes (`"agy"`) on every session it
15
+ * prepares. Registered under the provider's own fiber via
16
+ * `ctx.inject(['sessionProjections'])`, so the key lives exactly as long as
17
+ * the provider is mounted.
18
+ */
19
+ import { z } from "zod";
20
+ const agentPresetSchema = z.union([z.string(), z.null()]);
21
+ /** Current Session preset, initialized from its header and advanced by selection events. */
22
+ export const agyAgentPresetProjection = {
23
+ key: "agentPreset",
24
+ stateSchema: agentPresetSchema,
25
+ // adapter-agy is single-preset: default every session to "agy" so the
26
+ // header chip resolves for scan-native (never-resumed) sessions too.
27
+ init: (header) => header.agentPreset ?? "agy",
28
+ apply: (state, event) => event.type === "agent-preset/selected"
29
+ ? event.data.agentPreset
30
+ : state,
31
+ wire: { viewSchema: agentPresetSchema, view: (state) => state },
32
+ stateVersion: 1,
33
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The `agentPreset` session projection, owned by the adapter.
3
+ *
4
+ * Upstream registers this fold from the `dsh-agent-presets` package, which
5
+ * the bundle patch disables (the claude provider replaces preset composition
6
+ * wholesale). But the Web UI depends on the projection regardless: the
7
+ * preset hero chip renders only when `session.projectionValues.agentPreset`
8
+ * resolves, and the session-header label reads it too. Without a registrant
9
+ * the projection is absent for every adapter session and both surfaces stay
10
+ * blank — roster or no roster (the omp line's "no mode" bug, re-observed as
11
+ * the claude "degraded preset" finding).
12
+ *
13
+ * Same fold as upstream; adapter-claude is single-preset: default every
14
+ * session to "claude" so the header chip resolves for sessions whose log
15
+ * carries no `agent-preset/selected` event yet. Registered under the
16
+ * provider's own fiber via `ctx.inject(['sessionProjections'])`.
17
+ */
18
+ import { z } from "zod";
19
+ const agentPresetSchema = z.union([z.string(), z.null()]);
20
+ /** Current Session preset, initialized from its header and advanced by selection events. */
21
+ export const claudeAgentPresetProjection = {
22
+ key: "agentPreset",
23
+ stateSchema: agentPresetSchema,
24
+ init: (header) => header.agentPreset ?? "claude",
25
+ apply: (state, event) => event.type === "agent-preset/selected"
26
+ ? event.data.agentPreset
27
+ : state,
28
+ wire: { viewSchema: agentPresetSchema, view: (state) => state },
29
+ stateVersion: 1,
30
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The `agentPreset` session projection, owned by the adapter.
3
+ *
4
+ * Upstream registers this fold from the `dsh-agent-presets` package, which our
5
+ * patch disables (the Codex provider replaces preset composition wholesale).
6
+ * But the Web UI depends on the projection regardless: the preset hero chip
7
+ * renders only when `session.projectionValues.agentPreset` resolves
8
+ * (`presetOf` in ui-agent-preset/seat-store.ts), and the session-header label
9
+ * reads it too. Without a registrant the projection is absent for every
10
+ * adapter session and both surfaces stay blank — roster or no roster (that
11
+ * was the "no mode" bug on the omp line: the roster service answered
12
+ * agentPresets/list 200, yet nothing drove the per-session projection the
13
+ * chip gates on).
14
+ *
15
+ * Same fold as upstream (packages/preset/agent-presets/src/session.ts):
16
+ * initialized from the creation header, advanced by `agent-preset/selected`
17
+ * events, which this adapter synthesizes (`"codex"`) on every session it
18
+ * prepares. Registered under the provider's own fiber via
19
+ * `ctx.inject(['sessionProjections'])`, so the key lives exactly as long as
20
+ * the provider is mounted.
21
+ */
22
+ import { z } from "zod";
23
+ const agentPresetSchema = z.union([z.string(), z.null()]);
24
+ /** Current Session preset, initialized from its header and advanced by selection events. */
25
+ export const codexAgentPresetProjection = {
26
+ key: "agentPreset",
27
+ stateSchema: agentPresetSchema,
28
+ // adapter-codex is single-preset: default every session to "codex" so the
29
+ // header chip resolves for scan-native (never-resumed) sessions too,
30
+ // instead of only those whose log carries an `agent-preset/selected` event.
31
+ init: (header) => header.agentPreset ?? "codex",
32
+ apply: (state, event) => event.type === "agent-preset/selected"
33
+ ? event.data.agentPreset
34
+ : state,
35
+ wire: { viewSchema: agentPresetSchema, view: (state) => state },
36
+ stateVersion: 1,
37
+ };
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Codex runtime app home bootstrap — idempotently creates the codex home and
3
+ * seeds it with the Codex CLI's own config set (config.toml / model catalog /
4
+ * auth) copied once from the native `~/.codex`.
5
+ *
6
+ * Why this is required, not cosmetic: a Codex home without `config.toml` makes
7
+ * the CLI fall back to its built-in OpenAI provider, so every turn dies on
8
+ * `wss://api.openai.com/v1/responses` with a region block (403 "Country,
9
+ * region, or territory not supported"). The native home is where the user's
10
+ * provider wiring lives (e.g. a local proxy: `model_provider = "custom"`,
11
+ * `base_url = "http://127.0.0.1:15721/v1"`, `model = "glm-5.3-flash"`), and the
12
+ * world deliberately runs with its own CODEX_HOME — so the config has to travel.
13
+ *
14
+ * Same manners as the OMP twin: never overwrites an existing file (a
15
+ * provisioned app home wins), skips missing sources, and is fail-soft — the app
16
+ * home is derived state, never a boot gate.
17
+ */
18
+ import { copyFileSync, existsSync, mkdirSync } from "node:fs";
19
+ import { homedir } from "node:os";
20
+ import { join } from "node:path";
21
+ import { resolveCodexHome } from "./codex-store.js";
22
+ /**
23
+ * Config files seeded into the codex home. `config.toml` names the provider and
24
+ * model, `cc-switch-model-catalog.json` is what its `model_catalog_json` key
25
+ * points at (a relative path, hence the same directory), and `auth.json` is the
26
+ * CLI's own credential record.
27
+ */
28
+ export const CODEX_HOME_CONFIG_FILES = ["config.toml", "cc-switch-model-catalog.json", "auth.json"];
29
+ /** Native codex home — the copy source (`AW_CODEX_SEED_HOME` overrides). */
30
+ export function defaultCodexSeedHome() {
31
+ const configured = process.env.AW_CODEX_SEED_HOME;
32
+ return configured !== undefined && configured !== "" ? configured : join(homedir(), ".codex");
33
+ }
34
+ /**
35
+ * Ensure the codex home exists and carries the CLI config set.
36
+ *
37
+ * @param options - target/source/files overrides.
38
+ * @returns the resolved codex home path (always, even on a failed seed).
39
+ */
40
+ export function ensureCodexAppHome(options = {}) {
41
+ const codexHome = options.codexHome ?? resolveCodexHome();
42
+ const source = options.seedHome ?? defaultCodexSeedHome();
43
+ const files = options.files ?? CODEX_HOME_CONFIG_FILES;
44
+ const seeded = [];
45
+ try {
46
+ mkdirSync(codexHome, { recursive: true });
47
+ for (const name of files) {
48
+ const dest = join(codexHome, name);
49
+ if (existsSync(dest))
50
+ continue;
51
+ const src = join(source, name);
52
+ if (!existsSync(src))
53
+ continue;
54
+ copyFileSync(src, dest);
55
+ seeded.push(name);
56
+ }
57
+ if (seeded.length > 0) {
58
+ options.warn?.(`codex-provider: app home ${codexHome} seeded from ${source} (${seeded.join(", ")})`);
59
+ }
60
+ }
61
+ catch (error) {
62
+ options.warn?.(`codex-provider: app-home bootstrap failed (fail-soft): ${String(error)}`);
63
+ }
64
+ return codexHome;
65
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * AW-E Task 1 spike (P0 gate): prove `@openai/codex-sdk@0.154.0` drives a real
3
+ * turn against the local cc-switch provider (glm-5.2) under an ISOLATED
4
+ * CODEX_HOME, and capture the raw ThreadEvent chain for the Task 2 projection
5
+ * fixtures. The SDK spawns its own exact-pinned codex binary — the 0.153.4
6
+ * CLI on PATH is never consulted (version-mix safety, 2026-09-10 fact).
7
+ */
8
+ import { Codex } from "@openai/codex-sdk";
9
+ function lastOfType(events, type) {
10
+ for (let i = events.length - 1; i >= 0; i -= 1) {
11
+ const evt = events[i];
12
+ if (evt.type === type)
13
+ return evt;
14
+ }
15
+ return undefined;
16
+ }
17
+ /** Run one real streamed turn and collect the full event chain. */
18
+ export async function runSpike(opts) {
19
+ const codex = new Codex({
20
+ // env REPLACES process.env (SDK contract) — carry it forward + redirect home.
21
+ env: { ...process.env, CODEX_HOME: opts.codexHome },
22
+ });
23
+ const thread = codex.startThread({
24
+ workingDirectory: opts.cwd,
25
+ skipGitRepoCheck: true,
26
+ approvalPolicy: "never",
27
+ sandboxMode: "read-only",
28
+ });
29
+ const events = [];
30
+ const streamed = await thread.runStreamed(opts.prompt ?? "Reply with exactly: ok");
31
+ for await (const evt of streamed.events) {
32
+ events.push(evt);
33
+ if (evt.type === "turn.failed") {
34
+ const error = evt.error;
35
+ throw new Error(`spike turn failed: ${error?.message ?? "unknown"}`);
36
+ }
37
+ }
38
+ const completed = lastOfType(events, "turn.completed");
39
+ const message = lastOfType(events, "item.completed");
40
+ return {
41
+ threadId: thread.id ?? "",
42
+ finalResponse: message?.item?.text ?? "",
43
+ usage: completed?.usage ?? null,
44
+ events,
45
+ };
46
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The `agentPreset` session projection, owned by the adapter.
3
+ *
4
+ * Upstream registers this fold from the `dsh-agent-presets` package, which our
5
+ * patch disables (the Hermes provider replaces preset composition wholesale).
6
+ * But the Web UI depends on the projection regardless: the preset hero chip
7
+ * renders only when `session.projectionValues.agentPreset` resolves
8
+ * (`presetOf` in ui-agent-preset/seat-store.ts), and the session-header label
9
+ * reads it too. Without a registrant the projection is absent for every
10
+ * adapter session and both surfaces stay blank — roster or no roster.
11
+ *
12
+ * Same fold as upstream (packages/preset/agent-presets/src/session.ts):
13
+ * initialized from the creation header, advanced by `agent-preset/selected`
14
+ * events, which this adapter synthesizes (`"hermes"`) on every session it
15
+ * prepares. Registered under the provider's own fiber via
16
+ * `ctx.inject(['sessionProjections'])`, so the key lives exactly as long as
17
+ * the provider is mounted.
18
+ */
19
+ import { z } from "zod";
20
+ const agentPresetSchema = z.union([z.string(), z.null()]);
21
+ /** Current Session preset, initialized from its header and advanced by selection events. */
22
+ export const hermesAgentPresetProjection = {
23
+ key: "agentPreset",
24
+ stateSchema: agentPresetSchema,
25
+ // adapter-hermes is single-preset: default every session to "hermes" so the
26
+ // header chip resolves for scan-native (never-resumed) sessions too,
27
+ // instead of only those whose log carries an `agent-preset/selected` event.
28
+ init: (header) => header.agentPreset ?? "hermes",
29
+ apply: (state, event) => event.type === "agent-preset/selected"
30
+ ? event.data.agentPreset
31
+ : state,
32
+ wire: { viewSchema: agentPresetSchema, view: (state) => state },
33
+ stateVersion: 1,
34
+ };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The `agentPreset` session projection, owned by the bridge.
3
+ *
4
+ * Upstream registers this fold from the `dsh-agent-presets` package, which our
5
+ * patch disables (OMP replaces preset composition wholesale). But the Web UI
6
+ * depends on the projection regardless: the preset hero chip renders only when
7
+ * `session.projectionValues.agentPreset` resolves (`presetOf` in
8
+ * ui-agent-preset/seat-store.ts), and the session-header label reads it too.
9
+ * Without a registrant the projection is absent for every bridge session and
10
+ * both surfaces stay blank — roster or no roster (that was the "no omp mode"
11
+ * bug: the roster service answered `agentPresets/list` 200, yet nothing drove
12
+ * the per-session projection the chip gates on).
13
+ *
14
+ * Same fold as upstream (packages/preset/agent-presets/src/session.ts):
15
+ * initialized from the creation header, advanced by `agent-preset/selected`
16
+ * events, which this bridge already synthesizes (`"omp"`) on every session it
17
+ * prepares. Registered under the provider's own fiber via
18
+ * `ctx.inject(['sessionProjections'])`, so the key lives exactly as long as
19
+ * the provider is mounted.
20
+ */
21
+ import { z } from "zod";
22
+ const agentPresetSchema = z.union([z.string(), z.null()]);
23
+ /** Current Session preset, initialized from its header and advanced by selection events. */
24
+ export const ompAgentPresetProjection = {
25
+ key: "agentPreset",
26
+ stateSchema: agentPresetSchema,
27
+ // omp-web is single-preset: default every session to "omp" so the header
28
+ // chip resolves for scan-native (never-resumed) sessions too, instead of
29
+ // only those whose log carries an `agent-preset/selected` event.
30
+ init: (header) => header.agentPreset ?? "omp",
31
+ apply: (state, event) => event.type === "agent-preset/selected"
32
+ ? event.data.agentPreset
33
+ : state,
34
+ wire: { viewSchema: agentPresetSchema, view: (state) => state },
35
+ stateVersion: 1,
36
+ };
@@ -0,0 +1,86 @@
1
+ /**
2
+ * OMP runtime app home bootstrap — idempotently creates the runtime's OMP home
3
+ * (`<DSH_HOME>/agents/omp/.omp`) and seeds it with the OMP config (config.yml /
4
+ * mcp.json / models.yml / .env) copied once from `~/.omp/agent`. Fail-soft: any
5
+ * error is reported and the host boot continues — the app home is derived state,
6
+ * never a boot gate.
7
+ */
8
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { OMP_HOME, OMP_NATIVE_HOME } from "./knobs.js";
11
+ /** Config files seeded into the app home from the native OMP agent dir. */
12
+ export const APP_HOME_CONFIG_FILES = ["config.yml", "mcp.json", "models.yml", ".env"];
13
+ /**
14
+ * Comma/space separated `mcpServers` names the launcher refuses to inherit
15
+ * (`AW_OMP_MCP_EXCLUDE=zai-vision`). Deployment policy lives in the launcher, so
16
+ * this module never names a vendor.
17
+ */
18
+ export const OMP_MCP_EXCLUDE_ENV = "AW_OMP_MCP_EXCLUDE";
19
+ /** Native OMP agent dir (`<OMP_NATIVE_HOME>/agent`) — the copy source. */
20
+ function defaultSourceAgentDir() {
21
+ return join(OMP_NATIVE_HOME, "agent");
22
+ }
23
+ /** Parse {@link OMP_MCP_EXCLUDE_ENV} into server names. */
24
+ function excludedMcpServers() {
25
+ return (process.env[OMP_MCP_EXCLUDE_ENV] ?? "")
26
+ .split(/[\s,]+/)
27
+ .map((name) => name.trim())
28
+ .filter((name) => name !== "");
29
+ }
30
+ /**
31
+ * Drop excluded stdio MCP servers from the app home's `mcp.json`.
32
+ *
33
+ * Why this exists: the native `~/.omp/agent/mcp.json` carries a `npx -y
34
+ * @z_ai/mcp-server@latest` stdio server, and every spawned MCP child is a real
35
+ * process (a live npx install ~200 MB) that this runtime does not need. Seeding
36
+ * copies the file verbatim, so the filter runs after seeding and is idempotent:
37
+ * it only rewrites the document when a listed server was actually present.
38
+ */
39
+ function pruneMcpServers(appHome, names, warn) {
40
+ if (names.length === 0)
41
+ return;
42
+ const file = join(appHome, "mcp.json");
43
+ if (!existsSync(file))
44
+ return;
45
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
46
+ const servers = parsed.mcpServers;
47
+ if (servers === undefined || typeof servers !== "object" || servers === null)
48
+ return;
49
+ const removed = names.filter((name) => Object.hasOwn(servers, name));
50
+ if (removed.length === 0)
51
+ return;
52
+ for (const name of removed)
53
+ delete servers[name];
54
+ writeFileSync(file, `${JSON.stringify(parsed, null, 2)}\n`);
55
+ warn?.(`omp-provider: pruned MCP servers from ${file}: ${removed.join(", ")}`);
56
+ }
57
+ /**
58
+ * Ensure the app home exists and is seeded. Never overwrites an existing file
59
+ * (an already-provisioned app home wins); a missing source file is skipped.
60
+ * Returns the resolved app home path.
61
+ */
62
+ export function ensureOmpAppHome(options = {}) {
63
+ const appHome = options.appHome ?? OMP_HOME;
64
+ const source = options.sourceAgentDir ?? defaultSourceAgentDir();
65
+ const files = options.files ?? APP_HOME_CONFIG_FILES;
66
+ try {
67
+ mkdirSync(appHome, { recursive: true });
68
+ for (const name of files) {
69
+ const dest = join(appHome, name);
70
+ if (existsSync(dest))
71
+ continue;
72
+ const src = join(source, name);
73
+ if (!existsSync(src))
74
+ continue;
75
+ copyFileSync(src, dest);
76
+ }
77
+ // Deployment policy, data-side: a seeded/kept `mcp.json` may still carry
78
+ // heavy stdio servers this runtime must not inherit (a vendor name does not
79
+ // belong in adapter code, so the launcher names them here).
80
+ pruneMcpServers(appHome, options.excludeMcpServers ?? excludedMcpServers(), options.warn);
81
+ }
82
+ catch (error) {
83
+ options.warn?.(`omp-provider: app-home bootstrap failed (fail-soft): ${String(error)}`);
84
+ }
85
+ return appHome;
86
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The `agentPreset` session projection, owned by the adapter.
3
+ *
4
+ * Upstream registers this fold from the `dsh-agent-presets` package, which our
5
+ * patch disables (the pi provider replaces preset composition wholesale).
6
+ * But the Web UI depends on the projection regardless: the preset hero chip
7
+ * renders only when `session.projectionValues.agentPreset` resolves, and the
8
+ * session-header label reads it too. Without a registrant the projection is
9
+ * absent for every adapter session and both surfaces stay blank (the omp
10
+ * line's "no mode" bug).
11
+ *
12
+ * Same fold as upstream; adapter-pi is single-preset: default every session
13
+ * to "pi" so the header chip resolves for scan-native (never-resumed)
14
+ * sessions too. Registered under the provider's own fiber via
15
+ * `ctx.inject(['sessionProjections'])`.
16
+ */
17
+ import { z } from "zod";
18
+ const agentPresetSchema = z.union([z.string(), z.null()]);
19
+ /** Current Session preset, initialized from its header and advanced by selection events. */
20
+ export const piAgentPresetProjection = {
21
+ key: "agentPreset",
22
+ stateSchema: agentPresetSchema,
23
+ init: (header) => header.agentPreset ?? "pi",
24
+ apply: (state, event) => event.type === "agent-preset/selected"
25
+ ? event.data.agentPreset
26
+ : state,
27
+ wire: { viewSchema: agentPresetSchema, view: (state) => state },
28
+ stateVersion: 1,
29
+ };