diff --git a/docs/config-catalog.i18n.yaml b/docs/config-catalog.i18n.yaml
index 6cdc1e42ee..e3de095ad5 100644
--- a/docs/config-catalog.i18n.yaml
+++ b/docs/config-catalog.i18n.yaml
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/config-catalog.md
-config-catalog.md: 5a265c583382b846507f7aae6f59be4341d0a823
-config-catalog.zh.md: d410d383bf7cbe576227edf792f92bb019ad043a
+config-catalog.md: d25843091d453687bad5035c49bcb200cb03fff2
+config-catalog.zh.md: 1cc3002799afc10ca84f74044c7b28f91490ffa3
diff --git a/docs/config-catalog.md b/docs/config-catalog.md
index 5a265c5833..d25843091d 100644
--- a/docs/config-catalog.md
+++ b/docs/config-catalog.md
@@ -863,7 +863,7 @@ export interface Config {
}
```
-Source: [`packages/host/webserver/src/index.ts:45`](../packages/host/webserver/src/index.ts)
+Source: [`packages/host/webserver/src/index.ts:59`](../packages/host/webserver/src/index.ts)
diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md
index d410d383bf..1cc3002799 100644
--- a/docs/config-catalog.zh.md
+++ b/docs/config-catalog.zh.md
@@ -865,7 +865,7 @@ export interface Config {
}
```
-来源:[`packages/host/webserver/src/index.ts:45`](../packages/host/webserver/src/index.ts)
+来源:[`packages/host/webserver/src/index.ts:59`](../packages/host/webserver/src/index.ts)
diff --git a/docs/event-producer-consumer.i18n.yaml b/docs/event-producer-consumer.i18n.yaml
index 5fdbf36ed6..77c2dd6536 100644
--- a/docs/event-producer-consumer.i18n.yaml
+++ b/docs/event-producer-consumer.i18n.yaml
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/event-producer-consumer.md
-event-producer-consumer.md: d75d64b58b041c117bf2eaa1612c1b4683378a00
-event-producer-consumer.zh.md: adfc11782e179077b17199fc4ea6e8f52b3c465b
+event-producer-consumer.md: d3901b1ddc7d99d4960324ab7459388fa4612bbe
+event-producer-consumer.zh.md: 346a293a226719f781a9587eb0adefcf3f6bed08
diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md
index d75d64b58b..d3901b1ddc 100644
--- a/docs/event-producer-consumer.md
+++ b/docs/event-producer-consumer.md
@@ -57,6 +57,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
| `tools/post-execute` | `waterfall` | [`packages/core/tools/src/index.ts:175`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`spill-policy`](../packages/spill/spill-policy), [`tool-fs-search`](../packages/fs/tool-fs-search) |
| `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:152`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`tool-jobs`](../packages/jobs/tool-jobs) |
| `tools/result` | `emit` | [`packages/core/tools/src/index.ts:197`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`agent-instructions`](../packages/context/agent-instructions), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) |
+| `webserver/index-inject` | `emit` | [`packages/host/webserver/src/index.ts:34`](../packages/host/webserver/src/index.ts) | `webserver` (`emit`) | - |
| `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:79`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow), [`workflow`](../packages/workflow/workflow) |
| `workflow/agent-start` | `emit` | [`packages/workflow/workflow/src/index.ts:68`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow), [`workflow`](../packages/workflow/workflow) |
| `workflow/end` | `emit` | [`packages/workflow/workflow/src/index.ts:89`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`workflow`](../packages/workflow/workflow) |
diff --git a/docs/event-producer-consumer.zh.md b/docs/event-producer-consumer.zh.md
index adfc11782e..346a293a22 100644
--- a/docs/event-producer-consumer.zh.md
+++ b/docs/event-producer-consumer.zh.md
@@ -59,6 +59,7 @@
| `tools/post-execute` | `waterfall` | [`packages/core/tools/src/index.ts:175`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`spill-policy`](../packages/spill/spill-policy), [`tool-fs-search`](../packages/fs/tool-fs-search) |
| `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:152`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`tool-jobs`](../packages/jobs/tool-jobs) |
| `tools/result` | `emit` | [`packages/core/tools/src/index.ts:197`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`agent-instructions`](../packages/context/agent-instructions), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) |
+| `webserver/index-inject` | `emit` | [`packages/host/webserver/src/index.ts:34`](../packages/host/webserver/src/index.ts) | `webserver` (`emit`) | - |
| `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:79`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow), [`workflow`](../packages/workflow/workflow) |
| `workflow/agent-start` | `emit` | [`packages/workflow/workflow/src/index.ts:68`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow), [`workflow`](../packages/workflow/workflow) |
| `workflow/end` | `emit` | [`packages/workflow/workflow/src/index.ts:89`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`workflow`](../packages/workflow/workflow) |
diff --git a/docs/subsystems/client-modules.i18n.yaml b/docs/subsystems/client-modules.i18n.yaml
index 130e99d258..4c8f701de2 100644
--- a/docs/subsystems/client-modules.i18n.yaml
+++ b/docs/subsystems/client-modules.i18n.yaml
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/client-modules.md
-client-modules.md: 52fa4ef73766e8e7e7601e604f78aded9940328f
-client-modules.zh.md: e830349be77000759684a76b2834478a803e3e11
+client-modules.md: 81c85e2f0eb1d7248459f6ca092ed89f602e8415
+client-modules.zh.md: 37b8acc0d6d335802333100b5e55e709153911da
diff --git a/docs/subsystems/client-modules.md b/docs/subsystems/client-modules.md
index 52fa4ef737..81c85e2f0e 100644
--- a/docs/subsystems/client-modules.md
+++ b/docs/subsystems/client-modules.md
@@ -82,7 +82,7 @@ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnp
### `ctx.clientModules` — `ClientModuleRegistry`
-The web plugin table service: incremental `dsh.client` scan + wire composition + bundle route + index tap. Construction runs the activation scan synchronously — a malformed declaration or missing bundle among the already-loaded entries aggregates into one loud throw (FAILED fiber; the boot activation audit reports it).
+The web plugin table service: incremental `dsh.client` scan + wire composition + bundle route + index injection rows. Construction runs the activation scan synchronously — a malformed declaration or missing bundle among the already-loaded entries aggregates into one loud throw (FAILED fiber; the boot activation audit reports it).
```ts cordis-catalog
/**
@@ -122,5 +122,5 @@ onRebuilt(listener: (id: string, rev: string) => void): () => void
onGraphChanged(listener: () => void): () => void
```
-Source: [`packages/client/modules/src/index.ts:295`](../../packages/client/modules/src/index.ts)
+Source: [`packages/client/modules/src/index.ts:282`](../../packages/client/modules/src/index.ts)
diff --git a/docs/subsystems/client-modules.zh.md b/docs/subsystems/client-modules.zh.md
index e830349be7..37b8acc0d6 100644
--- a/docs/subsystems/client-modules.zh.md
+++ b/docs/subsystems/client-modules.zh.md
@@ -82,7 +82,7 @@ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnp
### `ctx.clientModules` — `ClientModuleRegistry`
-The web plugin table service: incremental `dsh.client` scan + wire composition + bundle route + index tap. Construction runs the activation scan synchronously — a malformed declaration or missing bundle among the already-loaded entries aggregates into one loud throw (FAILED fiber; the boot activation audit reports it).
+The web plugin table service: incremental `dsh.client` scan + wire composition + bundle route + index injection rows. Construction runs the activation scan synchronously — a malformed declaration or missing bundle among the already-loaded entries aggregates into one loud throw (FAILED fiber; the boot activation audit reports it).
```ts cordis-catalog
/**
@@ -122,5 +122,5 @@ onRebuilt(listener: (id: string, rev: string) => void): () => void
onGraphChanged(listener: () => void): () => void
```
-Source: [`packages/client/modules/src/index.ts:295`](../../packages/client/modules/src/index.ts)
+Source: [`packages/client/modules/src/index.ts:282`](../../packages/client/modules/src/index.ts)
diff --git a/docs/subsystems/web-server.i18n.yaml b/docs/subsystems/web-server.i18n.yaml
index 8ed53c1fa7..519c6e7043 100644
--- a/docs/subsystems/web-server.i18n.yaml
+++ b/docs/subsystems/web-server.i18n.yaml
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/web-server.md
-web-server.md: 414863d88cdc34163882cdc3dfe8dc7c1471ac77
-web-server.zh.md: d756192ce733a3fe79c9eaf44e5d556c4bac90a8
+web-server.md: 7dd700d24f63d31f34d8711069477414292e3bf6
+web-server.zh.md: ca67ff231e9aad9144fa5203ee46435f636a93da
diff --git a/docs/subsystems/web-server.md b/docs/subsystems/web-server.md
index 414863d88c..7dd700d24f 100644
--- a/docs/subsystems/web-server.md
+++ b/docs/subsystems/web-server.md
@@ -102,7 +102,46 @@ tapIndex(transform: (html: string) => string): () => void
* @returns the transformed body.
*/
applyIndexTaps(html: string): string
+
+/**
+ * Gather the structured injection table: one `webserver/index-inject` emit,
+ * every subscriber pushes its current rows. Fresh per call, so subscribers
+ * read live state (module graph, theme preference) at emit time.
+ * @returns rows in subscriber activation order.
+ */
+collectIndexInjections(): IndexInjection[]
+
+/**
+ * Render one index.html body: the structured injection table first, then
+ * the raw `tapIndex` transforms over the result.
+ * @param html - the raw index.html body.
+ * @returns the transformed body.
+ */
+renderIndex(html: string): string
```
-Source: [`packages/host/webserver/src/index.ts:59`](../../packages/host/webserver/src/index.ts)
+Source: [`packages/host/webserver/src/index.ts:73`](../../packages/host/webserver/src/index.ts)
+
+
+
+### `webserver/*` events
+
+
+
+#### `webserver/index-inject` — emit
+
+Collect the structured index injection table. Emitted on every index render and every worker boot-payload request; listeners push their current rows, so a row's data is read fresh at emit time.
+
+```ts cordis-catalog
+/**
+ * Collect the structured index injection table. Emitted on every index
+ * render and every worker boot-payload request; listeners push their
+ * current rows, so a row's data is read fresh at emit time.
+ * @param table - Mutable row table; listeners append in activation order.
+ * @mode emit
+ */
+'webserver/index-inject'(table: IndexInjection[]): void
+```
+
+Source: [`packages/host/webserver/src/index.ts:34`](../../packages/host/webserver/src/index.ts)
diff --git a/docs/subsystems/web-server.zh.md b/docs/subsystems/web-server.zh.md
index d756192ce7..ca67ff231e 100644
--- a/docs/subsystems/web-server.zh.md
+++ b/docs/subsystems/web-server.zh.md
@@ -102,7 +102,46 @@ tapIndex(transform: (html: string) => string): () => void
* @returns the transformed body.
*/
applyIndexTaps(html: string): string
+
+/**
+ * Gather the structured injection table: one `webserver/index-inject` emit,
+ * every subscriber pushes its current rows. Fresh per call, so subscribers
+ * read live state (module graph, theme preference) at emit time.
+ * @returns rows in subscriber activation order.
+ */
+collectIndexInjections(): IndexInjection[]
+
+/**
+ * Render one index.html body: the structured injection table first, then
+ * the raw `tapIndex` transforms over the result.
+ * @param html - the raw index.html body.
+ * @returns the transformed body.
+ */
+renderIndex(html: string): string
```
-Source: [`packages/host/webserver/src/index.ts:59`](../../packages/host/webserver/src/index.ts)
+Source: [`packages/host/webserver/src/index.ts:73`](../../packages/host/webserver/src/index.ts)
+
+
+
+### `webserver/*` events
+
+
+
+#### `webserver/index-inject` — emit
+
+Collect the structured index injection table. Emitted on every index render and every worker boot-payload request; listeners push their current rows, so a row's data is read fresh at emit time.
+
+```ts cordis-catalog
+/**
+ * Collect the structured index injection table. Emitted on every index
+ * render and every worker boot-payload request; listeners push their
+ * current rows, so a row's data is read fresh at emit time.
+ * @param table - Mutable row table; listeners append in activation order.
+ * @mode emit
+ */
+'webserver/index-inject'(table: IndexInjection[]): void
+```
+
+Source: [`packages/host/webserver/src/index.ts:34`](../../packages/host/webserver/src/index.ts)
diff --git a/packages/extensions/tool-cordis/src/api-catalog.ts b/packages/extensions/tool-cordis/src/api-catalog.ts
index 708c179df5..a20c0b9aff 100644
--- a/packages/extensions/tool-cordis/src/api-catalog.ts
+++ b/packages/extensions/tool-cordis/src/api-catalog.ts
@@ -459,8 +459,8 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
},
{
key: 'clientModules',
- summary: 'The web plugin table service: incremental `dsh.client` scan + wire composition + bundle route + index tap.',
- description: 'The web plugin table service: incremental `dsh.client` scan + wire composition + bundle route + index tap. Construction runs the activation scan synchronously — a malformed declaration or missing bundle among the already-loaded entries aggregates into one loud throw (FAILED fiber; the boot activation audit reports it).',
+ summary: 'The web plugin table service: incremental `dsh.client` scan + wire composition + bundle route + index injection rows.',
+ description: 'The web plugin table service: incremental `dsh.client` scan + wire composition + bundle route + index injection rows. Construction runs the activation scan synchronously — a malformed declaration or missing bundle among the already-loaded entries aggregates into one loud throw (FAILED fiber; the boot activation audit reports it).',
methods: [
{
signature: 'graph(): WebBootGraph',
@@ -2217,6 +2217,18 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
parameters: [{ name: 'html', description: 'the raw index.html body.' }],
returns: 'the transformed body.',
},
+ {
+ signature: 'collectIndexInjections(): IndexInjection[]',
+ description: 'Gather the structured injection table: one `webserver/index-inject` emit, every subscriber pushes its current rows. Fresh per call, so subscribers read live state (module graph, theme preference) at emit time.',
+ parameters: [],
+ returns: 'rows in subscriber activation order.',
+ },
+ {
+ signature: 'renderIndex(html: string): string',
+ description: 'Render one index.html body: the structured injection table first, then the raw `tapIndex` transforms over the result.',
+ parameters: [{ name: 'html', description: 'the raw index.html body.' }],
+ returns: 'the transformed body.',
+ },
],
},
{
@@ -2685,6 +2697,14 @@ export const EVENT_API: readonly EventApiEntry[] = [
description: 'Observe the frozen, lossless-JSON final outcome. Listener failures are contained. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): keyed by `exec.agent`.',
parameters: [{ name: 'exec', description: 'the execution object that traversed the pipeline.' }, { name: 'result', description: 'a deep-frozen snapshot of the final returned result.' }],
},
+ {
+ name: 'webserver/index-inject',
+ mode: 'emit',
+ signature: '\'webserver/index-inject\'(table: IndexInjection[]): void',
+ summary: 'Collect the structured index injection table.',
+ description: 'Collect the structured index injection table. Emitted on every index render and every worker boot-payload request; listeners push their current rows, so a row\'s data is read fresh at emit time.',
+ parameters: [{ name: 'table', description: 'Mutable row table; listeners append in activation order.' }],
+ },
{
name: 'workflow/agent-end',
mode: 'emit',
@@ -3297,6 +3317,14 @@ export const TYPE_API: readonly TypeApiEntry[] = [
name: 'InboxTarget',
declaration: 'export type InboxTarget = \'next-turn\' | \'next-step\';',
},
+ {
+ name: 'IndexInjection',
+ declaration: 'export type IndexInjection = {\n kind: \'global\';\n name: string;\n value: unknown;\n} | {\n kind: \'script\';\n placement: IndexInjectionPlacement;\n text: string;\n} | {\n kind: \'script-src\';\n placement: IndexInjectionPlacement;\n src: string;\n} | {\n kind: \'style\';\n text: string;\n} | {\n kind: \'html\';\n placement: IndexInjectionPlacement;\n html: string;\n};',
+ },
+ {
+ name: 'IndexInjectionPlacement',
+ declaration: 'export type IndexInjectionPlacement = \'head\' | \'body\';',
+ },
{
name: 'InvariantFailure',
declaration: 'export type InvariantFailure = (message: string) => never;',
diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts
index a088890413..4b1f71c5e9 100644
--- a/scripts/gen-cordis-catalog.ts
+++ b/scripts/gen-cordis-catalog.ts
@@ -184,6 +184,7 @@ export const EVENT_SCOPE_PAGE: Record = {
'system-prompt': 'system-prompt.md',
'session-telemetry': 'session-telemetry.md',
'tools': 'tools.md',
+ 'webserver': 'web-server.md',
'workflow': 'workflow.md',
}
@@ -480,6 +481,7 @@ export const LINK_MAP: Readonly> = {
PresetSpec: 'permission-presets.md',
InvariantInstaller: 'invariants.md',
WebRoute: 'web-server.md',
+ IndexInjection: 'web-server.md',
StorageBackend: 'storage.md',
StorageForms: 'storage.md',
Domain: 'storage.md',