@deepseek-ai/dsh-cordis-client-runner 0.1.1-rc.2 → 0.1.2-alpha.2
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/README.i18n.yaml +2 -2
- package/README.md +100 -26
- package/README.zh.md +109 -35
- package/lib/client.js +904 -514
- package/lib/types/client/api-catalog.d.ts +3 -3
- package/lib/types/client/inspect-registry.d.ts +2 -2
- package/lib/types/client/orchestrator.d.ts +1 -2
- package/lib/types/client/providers.d.ts +1 -1
- package/lib/types/client/runtime.d.ts +2 -3
- package/package.json +11 -18
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/extensions/cordis-client-runner/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: ba813e3498752902b80102ecad5802187d95a129
|
|
6
|
+
README.zh.md: 1b975550dc4d08aef90f8e7a79a81a67983dfce1
|
package/README.md
CHANGED
|
@@ -1,35 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Browser half of dynamic Cordis packages for users and maintainers choosing, composing, or debugging how a page answers run requests and loads browser-half code."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# @deepseek-ai/dsh-cordis-client-runner
|
|
2
7
|
|
|
3
8
|
English | [中文](README.zh.md)
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
`dsh-cordis-client-runner` lets a page run the browser half of a dynamic Cordis package: it answers the host's run requests, loads the browser-half source into the page as a live plugin, and removes it when the host retracts the run. A person approves or declines a run — or starts one directly — and the result this package reports back becomes the `cordis_run` tool result the model reads. Nothing loads at activation and nothing is restored after a refresh; a page runs a dynamic package only when someone answers a run request or asks for it here.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Use this package](#use-this-package)
|
|
17
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
18
|
+
- [Further Exploration](#further-exploration)
|
|
19
|
+
- [Model Experience](#model-experience)
|
|
20
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
21
|
+
- [Dev Note](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## Use this package
|
|
27
|
+
|
|
28
|
+
Mount this plugin in a web client whose composition also mounts the host runner — the host half runs in the process, this browser half in the page. When a dynamic package that has a browser half is run, the open pages receive a run request; this package carries out the load on this page, and the UI package (`ui-cordis`) renders the panel and cards a person uses to answer it. Host-only packages need no browser half and therefore no page: the host runs them itself.
|
|
29
|
+
|
|
30
|
+
### What the page does
|
|
31
|
+
|
|
32
|
+
A browser half is written in plain JavaScript — no JSX, no TypeScript, no module imports — and runs as an async function. It receives a fixed set of names — `React`, `console`, `styles`, and `host` — while browser globals like `fetch` and `setTimeout` are unavailable. The plugin it returns can use the lifecycle verbs and only the services it declared in its own `inject`. Calling `host.call(method, args)` from the loaded half reaches its own host half. A crash that happens while React renders the loaded half is reported to the host with the slot, whether the crash removed the entry, and a message written for the author.
|
|
33
|
+
|
|
34
|
+
### What the run surface offers
|
|
35
|
+
|
|
36
|
+
A run surface can answer a pending host request — approving it, optionally covering future versions of the same plugin, or declining it — and can start a definition at the user's own gesture, which authorizes it. Each definition has at most one in-flight activity, so an affordance built on that state survives a remount. What the surface shows about this page is page-local: the last render crash per package, why this page's own attempt failed, and whether a package is loaded here — never the host's view of what is running.
|
|
37
|
+
|
|
38
|
+
### Lifecycle boundaries
|
|
39
|
+
|
|
40
|
+
Loading is idempotent: asking to load a revision this page already runs changes nothing, a newer revision replaces the loaded one, and the same revision after a retract loads afresh. Operations on a definition serialize. A refresh starts clean by design — the host still holds the definition, this page does not run it until asked again.
|
|
41
|
+
|
|
42
|
+
-----
|
|
6
43
|
|
|
7
|
-
|
|
44
|
+
<a id="understand-the-implementation"></a>
|
|
45
|
+
## Understand the implementation
|
|
8
46
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
3. **Guard facade** — `apply` receives a whitelisting proxy over the real fiber ctx: lifecycle verbs plus the services the returned plugin declared in its own `inject` (so the object form `{ inject: ['slots'], apply(ctx) {} }` is what reaches a service; a plain function has no declaration site and reaches none). The `slots` seat assigns the shadowing priority (registering IS shadowing, newest run wins); the `theme` seat pins the override layer's source to the package id and hangs its disposer on the fiber.
|
|
12
|
-
4. **Loader entries** — the guarded plugin is seated in the module table and mounted through `loader.create`, so a dynamic package rides the same activation gating, fiber-effect cleanup, and status projection as a static one. Unload is entry removal plus factory invalidation plus style removal.
|
|
13
|
-
5. **Run orchestration** — a `cordis/request-run` event asks this page whether to run a definition. Whoever answers drives the run in order: the host half first, then the source fetch, then the browser half, then one resolution carrying what happened. A user pressing "run" is itself the authorization and orchestrates the same way with nothing to answer — and for a host-only definition the run ends at the host half, because there is no second half to fetch or load here.
|
|
14
|
-
6. **Package-internal RPC** — a package's `host.call` routes to its own host half through the `dynamicCordisRunner` Remote namespace (`invoke`), and each routing failure code becomes its own teaching error. Both directions carry JSON only: an omitted argument travels as `null` (so `host.call('listServices')` is legal and the handler receives `null`), and a payload the generated codec refuses — a function, `undefined`, a class instance — becomes a teaching error naming the call and the contract instead of the codec's bare field name.
|
|
15
|
-
7. **Render-failure reflow** — the slot registry's supervision seam (`slots.onEntryError`) fires for every entry-boundary crash on the page; the ones belonging to a package this runner seated go to two outlets from that one observation: upstream to the authoring session (`reportRenderFailure`, for the model) and onto this package's own `renderFailures` face field (for the panel row). Ownership is keyed on component identity, recorded when the guard's `register` proxy seats it, because the registry stores the component verbatim — so no parallel ledger of entries has to be kept in step. This is post-settle diagnosis only: it carries no settle authority, never touches a run resolution, and a failed report is swallowed rather than turning one crash into two.
|
|
47
|
+
<details>
|
|
48
|
+
<summary>Implementation internals — click to expand</summary>
|
|
16
49
|
|
|
17
|
-
|
|
50
|
+
This section explains the design behind the browser half; the observable behavior is fully covered in [Use this package](#use-this-package).
|
|
18
51
|
|
|
19
|
-
|
|
52
|
+
### Design philosophy
|
|
20
53
|
|
|
21
|
-
|
|
54
|
+
The browser half is built on one principle: a dynamic package must ride the same activation gating, fiber-effect cleanup, and status projection as a static one. The evaluated plugin is seated in the module table and mounted through `loader.create`; unload is entry removal plus factory invalidation plus style removal. The guard is a whitelist — lifecycle verbs plus declared services — that mirrors the host-side sandbox facade, so a package author meets one contract on both halves. One observer feeds two outlets: the slot registry's entry-error seam is watched only here, and a crash belonging to a package this runner seated goes upstream to the host for the model and onto this package's own `renderFailures` for the panel.
|
|
22
55
|
|
|
23
|
-
|
|
56
|
+
### Source map
|
|
24
57
|
|
|
25
|
-
|
|
58
|
+
| File | Role |
|
|
59
|
+
|---|---|
|
|
60
|
+
| [`src/client/index.ts`](src/client/index.ts) | Plugin entry: runner, orchestrator, inspect registry, forwarded-event subscription |
|
|
61
|
+
| [`src/client/runtime.ts`](src/client/runtime.ts) | Load engine: convergence by run identity, guarded mount, retract |
|
|
62
|
+
| [`src/client/orchestrator.ts`](src/client/orchestrator.ts) | Run orchestration: host half first, source fetch, browser half, one resolution |
|
|
63
|
+
| [`src/client/evaluator.ts`](src/client/evaluator.ts) | Closure evaluation: the symbol surface and its teaching traps |
|
|
64
|
+
| [`src/client/guard.ts`](src/client/guard.ts) | The whitelisting `ctx` façade for loaded browser halves |
|
|
65
|
+
| [`src/client/inspect-registry.ts`](src/client/inspect-registry.ts) | Client Inspect Providers and the pending-query router |
|
|
66
|
+
| [`src/client/providers.ts`](src/client/providers.ts) | First-party client Inspect Providers (slots, theme, events) |
|
|
67
|
+
| [`src/client/timer.ts`](src/client/timer.ts) | The client timer service dynamic packages inject |
|
|
26
68
|
|
|
27
|
-
|
|
28
|
-
- `renderFailures` — this page's last render crash per definition (slot, teaching message, and whether the crash retired the entry from its cell), on the same notification channel as the live set. Page-local and current by construction: it clears when the package stops, is retracted, or loads again, so a row can render it directly. The host keeps its own last-across-pages copy for the model — the two have different owners and lifetimes, and a surface must not read the host's back in place of this one.
|
|
29
|
-
- `lastRunError` — why this page's own attempt failed, per definition. It outlives the activity, because the host disposes only the half a failed request started: a page can be looking at a definition the host reports as running while having nothing loaded itself.
|
|
30
|
-
- `approve(requestId)` / `decline(requestId)` / `startUserRun({ agentId, id, hasClientHalf })` — the two entries. All three are idempotent (per request id, and per definition for the user's own run), so a double press cannot start two runs. `hasClientHalf` is required: a host-only definition has no source to fetch, so the caller states the shape from the registry row it is acting on rather than the orchestrator learning it from a failed fetch. An answerable request always has a browser half, because the host runs a host-only definition itself instead of asking a page.
|
|
31
|
-
- `subscribe()` / `getSnapshot()` / `isLoaded(id)` — what this page has loaded. `isLoaded` is page-local truth, never the host's "it is running".
|
|
69
|
+
### How a run is carried out
|
|
32
70
|
|
|
71
|
+
A `cordis/request-run` event asks this page whether to run a definition. Whoever answers — the page after an approval, or the user pressing run — drives the orchestration: the host half first (so a host-half failure short-circuits before the browser has moved), then the source fetch, then the browser half, then one resolution carrying what happened. The browser half's source is evaluated as an async function body with the symbol surface as parameters, the returned plugin is guard-wrapped and mounted through the loader, and the resolution reports the loaded revision or the failing stage with the closure's, guard's, or fiber's message. `host.call` routes through the Remote namespace; an omitted argument travels as `null`, and a payload the generated codec refuses becomes a teaching error naming the call and the contract.
|
|
72
|
+
|
|
73
|
+
</details>
|
|
74
|
+
|
|
75
|
+
-----
|
|
76
|
+
|
|
77
|
+
<a id="further-exploration"></a>
|
|
78
|
+
## Further Exploration
|
|
79
|
+
|
|
80
|
+
Read these pages when the package-level contract is not enough. They move from the browser half to the host that asks it, the tools whose runs it answers, and the surface that renders it.
|
|
81
|
+
|
|
82
|
+
- [Host runner](../cordis-host-runner/README.md) — the registry and run round trip this package answers.
|
|
83
|
+
- [Tool package](../tool-cordis/README.md) — the model-facing tools whose run requests reach this page.
|
|
84
|
+
- [UI package](../ui-cordis/README.md) — the panel and cards that operate this face.
|
|
85
|
+
- [Extensions subsystem](../../../docs/subsystems/extensions.md) — the generated `ctx.dynamicCordisRunner` API and `cordis/*` events.
|
|
86
|
+
- [Dynamic client render and attachment ownership Agent Note](../../../.agents/notes/implemented/architecture/2026-08-17-dynamic-client-render-and-attachment-ownership.md) — how browser plugins own their rendering and CSS.
|
|
87
|
+
- [Client shells and dynamic packages Agent Note](../../../.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.md) — package placement and build faces for the client halves.
|
|
88
|
+
|
|
89
|
+
-----
|
|
90
|
+
|
|
91
|
+
<a id="model-experience"></a>
|
|
33
92
|
## Model Experience
|
|
34
93
|
|
|
35
94
|
### Run resolution, when a model asked for the run
|
|
@@ -50,19 +109,34 @@ Append-only. A resolution reaches the model only as the tool result for the requ
|
|
|
50
109
|
|
|
51
110
|
#### What the model sees
|
|
52
111
|
|
|
53
|
-
A browser half that loads cleanly can still crash when React renders it, and that crash lands after the run was answered — so the model would otherwise be told "ok" and never learn. Every entry-boundary crash of a package this page seated is sent to the host (`reportRenderFailure`) naming the slot, whether the crash retired the entry from its cell (`abdicated`: the package's UI is gone, not merely broken), and a message written for the author
|
|
112
|
+
A browser half that loads cleanly can still crash when React renders it, and that crash lands after the run was answered — so the model would otherwise be told "ok" and never learn. Every entry-boundary crash of a package this page seated is sent to the host (`reportRenderFailure`) naming the slot, whether the crash retired the entry from its cell (`abdicated`: the package's UI is gone, not merely broken), and a message written for the author. The host keeps the last one per package, steers the owning session with it, and exposes it through `cordis_inspect_self`; nothing here reaches a run resolution.
|
|
54
113
|
|
|
55
114
|
#### Token effect
|
|
56
115
|
|
|
57
|
-
Conditional and bounded by the host's retention, not by this page: one report per crash, and the host keeps only the latest per package, so a repeatedly crashing entry costs the model one
|
|
116
|
+
Conditional and bounded by the host's retention, not by this page: one report per crash, and the host keeps only the latest per package, so a repeatedly crashing entry costs the model one message rather than a growing list. The report never enters a tool result of its own — the model pays for it only when it is steered or asks.
|
|
58
117
|
|
|
59
118
|
#### KV Cache effect
|
|
60
119
|
|
|
61
|
-
None of its own. Reports travel over RPC and are stored, not appended to the conversation; the model reads them through an inspection it chose to make, which extends the tail like any other tool result.
|
|
120
|
+
None of its own. Reports travel over RPC and are stored, not appended to the conversation; the model reads them through a steering message or an inspection it chose to make, which extends the tail like any other tool result.
|
|
62
121
|
|
|
63
122
|
## Known Limitations and Deferred Work
|
|
64
123
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
124
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
These limits define where the browser half needs special care. They are current package constraints, not a task backlog.
|
|
128
|
+
|
|
129
|
+
- **A refused resolution is not retried** — the acknowledgement of `resolveRequestRun` is not read, so when the host declines a stale success (`accepted: false`, because the definition's revision moved on while this page was loading) the page keeps what it loaded and does not orchestrate again. The request stays answerable — another page's answer or the caller's cancellation settles it — and the stop that bumped the revision retracts the stale load.
|
|
130
|
+
- **The plugin stays parked until the host namespace exists** — it declares `remote.dynamicCordisRunner`, so it loads no browser half whose host half it could never reach.
|
|
131
|
+
- **Slot admission has no carrier** — the dispatched row declares services, not target slots, so per-deployment allow or deny lists for slot admission have nowhere to ride.
|
|
132
|
+
- **Guard whitelists are hand-mirrored twins** — the browser guard replicates the host-side sandbox facade; sharing one specification is deferred.
|
|
133
|
+
|
|
134
|
+
<a id="dev-note"></a>
|
|
135
|
+
### Dev Note
|
|
136
|
+
|
|
137
|
+
<details>
|
|
138
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
139
|
+
|
|
140
|
+
None.
|
|
141
|
+
|
|
142
|
+
</details>
|
package/README.zh.md
CHANGED
|
@@ -1,68 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "动态 Cordis 包的浏览器半说明,供选择、组合或排查页面如何应答运行请求并装载浏览器半代码的用户与维护者阅读。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# @deepseek-ai/dsh-cordis-client-runner
|
|
2
7
|
|
|
3
8
|
[English](README.md) | 中文
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
`dsh-cordis-client-runner` 让页面运行动态 Cordis 包的浏览器半:它应答 host 的运行请求、把浏览器半源码装载进页面成为活插件,并在 host 撤回该次运行时把它移除。人可以批准或拒绝一次运行——也可以直接启动一次——而本包回报的结果变成模型读到的 `cordis_run` 工具结果。激活时什么都不装载,刷新后也不恢复;一页只在有人应答运行请求或在此主动要求时,才运行动态包。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [使用本包](#use-this-package)
|
|
17
|
+
- [理解实现](#understand-the-implementation)
|
|
18
|
+
- [进一步探索](#further-exploration)
|
|
19
|
+
- [模型体验](#model-experience)
|
|
20
|
+
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
|
21
|
+
- [开发备注](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## 使用本包
|
|
27
|
+
|
|
28
|
+
在组合里同时挂载了 host runner 的 web 客户端中挂载本插件——host 半跑在进程里,浏览器半跑在页面里。当某个带浏览器半的动态包被运行时,打开的页面会收到一次运行请求;本包在本页执行装载,UI 包(`ui-cordis`)则渲染人用来应答它的面板与卡片。纯 host 包不需要浏览器半,也就不需要页面:host 自己运行它们。
|
|
29
|
+
|
|
30
|
+
### 页面会做什么
|
|
31
|
+
|
|
32
|
+
浏览器半用纯 JavaScript 编写——无 JSX、无 TypeScript、不能 import 模块——并作为一个 async 函数运行。它拿到一组固定的名字——`React`、`console`、`styles` 与 `host`——而 `fetch`、`setTimeout` 这类浏览器全局不可用。返回的插件只能使用生命周期动词,以及它自己在 `inject` 里声明的服务。从已装载半调用 `host.call(method, args)` 会到达它自己的 host 半。React 渲染已装载半时发生的崩溃会上报 host,点名槽位、崩溃是否已把条目摘掉,以及写给作者的 message。
|
|
33
|
+
|
|
34
|
+
### 运行界面提供什么
|
|
35
|
+
|
|
36
|
+
运行界面可以应答一次待审批的 host 请求——批准它(可选地同时覆盖同一插件的未来版本)或拒绝它——也可以按用户自己的手势启动一个定义,该手势本身就是授权。每个定义最多有一个在途活动,因此基于该状态构建的控件能在 remount 后存活。界面就本页显示的内容都是页面本地的:每包最后一次渲染崩溃、本页自己的尝试为何失败,以及某个包是否已在本页装载——绝不是 host 眼中「在跑」的视图。
|
|
37
|
+
|
|
38
|
+
### 生命周期边界
|
|
39
|
+
|
|
40
|
+
装载是幂等的:要求装载这一页已在运行的 revision 不会改变任何东西,更新的 revision 顶替已装载的那个,同一 revision 在 retract 之后再装则重新装载。同一定义的操作串行执行。刷新按设计从干净状态开始——host 仍持有定义,本页在再次被要求之前不运行它。
|
|
41
|
+
|
|
42
|
+
-----
|
|
6
43
|
|
|
7
|
-
|
|
44
|
+
<a id="understand-the-implementation"></a>
|
|
45
|
+
## 理解实现
|
|
8
46
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
3. **guard 门面** —— `apply` 收到的是真 fiber ctx 之上的白名单代理:生命周期动词,加上**返回的 plugin 自己在 `inject` 里声明**的服务(所以要用对象形态 `{ inject: ['slots'], apply(ctx) {} }` 才拿得到服务;裸函数没有声明位,拿不到任何服务)。`slots` 座位分配遮蔽 priority(注册即遮蔽,最新一次运行者胜出);`theme` 座位把覆盖层的 source 钉成包 id,并把它的 disposer 挂到 fiber 上。
|
|
12
|
-
4. **loader entry** —— 加了 guard 的插件被塞进模块表,再经 `loader.create` 挂载,于是动态包与静态包共享同一套激活门控、fiber effect 清理与状态投影。卸载 = 移除 entry + 失效 factory + 撤下样式。
|
|
13
|
-
5. **run 编排** —— 一条 `cordis/request-run` 事件问这一页要不要运行某个定义。回答的那一方按顺序把 run 跑完:先 host 半、再取源码、再浏览器半,最后一次回答带上结果。用户按下「运行」本身就是授权,同样走这条编排,只是没有要回答的对象;而纯 host 定义的 run 到 host 半就结束了 —— 这里没有第二半可取、也没有第二半可装。
|
|
14
|
-
6. **包内 RPC** —— 包内的 `host.call` 经 `dynamicCordisRunner` Remote namespace(`invoke`)转给它自己的 host 半,三种路由失败码各自变成对应的教学错误。两个方向都只驮 JSON:省略入参会以 `null` 过线(所以 `host.call('listServices')` 合法,handler 收到 `null`),而生成的 codec 拒收的载荷(函数、`undefined`、类实例)会变成一条点明「哪次调用 + 约定是什么」的教学错误,而不是 codec 那个光秃秃的字段名。
|
|
15
|
-
7. **渲染期失败回流** —— 槽位注册表的 supervision 接缝(`slots.onEntryError`)对页面上每一次 entry 边界崩溃都会通知;凡属于本 runner 落座过的包,那**一次**观察会分两个出口:一路上行给撰写它的会话(`reportRenderFailure`,给模型看),一路发布到本包 face 上的 `renderFailures`(给面板那一行看)。归属以 component 身份为键,在 guard 的 `register` 代理落座时记下 —— 注册表原样保存 component,所以不需要再维护一份与之同步的 entry 台账。这条通道纯属事后诊断:不驮任何 settle 权威、绝不触碰 run 的最终回答,而且报告本身失败时只吞不抛 —— 不让一次崩溃变成两次。
|
|
47
|
+
<details>
|
|
48
|
+
<summary>实现细节——点击展开</summary>
|
|
16
49
|
|
|
17
|
-
|
|
50
|
+
本节解释浏览器半背后的设计;可观察行为已在[使用本包](#use-this-package)中完整说明。
|
|
18
51
|
|
|
19
|
-
|
|
52
|
+
### 设计理念
|
|
20
53
|
|
|
21
|
-
|
|
54
|
+
浏览器半建立在一个原则之上:动态包必须与静态包共享同一套激活门控、fiber effect 清理与状态投影。求值后的插件被塞进模块表,并经 `loader.create` 挂载;卸载 = 移除 entry + 失效 factory + 撤下样式。guard 是一份白名单——生命周期动词加已声明服务——与 host 侧沙箱门面对称,因此包作者在两侧面对同一个约定。一个观察者供两个出口:只有这里监视槽位注册表的 entry 错误接缝,凡属于本 runner 落座过的包的崩溃,一路上行给 host(给模型),一路发布到本包自己的 `renderFailures`(给面板)。
|
|
22
55
|
|
|
23
|
-
|
|
56
|
+
### 源码地图
|
|
24
57
|
|
|
25
|
-
|
|
58
|
+
| 文件 | 职责 |
|
|
59
|
+
|---|---|
|
|
60
|
+
| [`src/client/index.ts`](src/client/index.ts) | 插件入口:runner、编排器、inspect 注册表、转发事件订阅 |
|
|
61
|
+
| [`src/client/runtime.ts`](src/client/runtime.ts) | 装载引擎:按运行标识收敛、guard 挂载、撤回 |
|
|
62
|
+
| [`src/client/orchestrator.ts`](src/client/orchestrator.ts) | 运行编排:先 host 半、再取源码、再浏览器半、一次结算 |
|
|
63
|
+
| [`src/client/evaluator.ts`](src/client/evaluator.ts) | 闭包求值:符号面及其教学陷阱 |
|
|
64
|
+
| [`src/client/guard.ts`](src/client/guard.ts) | 已装载浏览器半收到的白名单 `ctx` façade |
|
|
65
|
+
| [`src/client/inspect-registry.ts`](src/client/inspect-registry.ts) | Client Inspect Provider 与待答查询路由器 |
|
|
66
|
+
| [`src/client/providers.ts`](src/client/providers.ts) | 第一方 client Inspect Provider(slots、theme、events) |
|
|
67
|
+
| [`src/client/timer.ts`](src/client/timer.ts) | 动态包注入的 client 定时器服务 |
|
|
26
68
|
|
|
27
|
-
|
|
28
|
-
- `renderFailures` —— **本页**最后一次渲染崩溃,按定义索引(槽位、教学 message、以及这次崩溃是否已把 entry 从格位上摘掉),与 live 集合共用同一条通知通道。它按构造就是「本页当前」:包 stop、被 retract、或重新装载成功时即清空,所以界面可以直接照着渲染。host 那边另存一份「跨页面最后一次」给模型 —— 两份的归属与寿命本来就不同,界面**不要**改成回读 host 那份。
|
|
29
|
-
- `lastRunError` —— 本页自己那次尝试为何失败,按定义索引。它比活动活得更久:host 只拆失败请求自己启动的那半,所以一个页面可能看着 host 报告为「在跑」的定义,而自己什么都没装上。
|
|
30
|
-
- `approve(requestId)` / `decline(requestId)` / `startUserRun({ agentId, id, hasClientHalf })` —— 两条入口。三者都幂等(按 requestId,用户自发的 run 按定义 id),所以连点两次不会起两次 run。`hasClientHalf` 是必填:纯 host 定义没有源码可取,所以由调用方从它正在操作的注册表行里把这个事实说出来,而不是让编排器从一次失败的取码里反推。可回答的请求必然带浏览器半 —— 纯 host 定义是 host 自己起的,它不会去问页面。
|
|
31
|
-
- `subscribe()` / `getSnapshot()` / `isLoaded(id)` —— 这一页装了什么。`isLoaded` 是页面本地的事实,永远不等于 host 说的「在跑」。
|
|
69
|
+
### 一次 run 如何执行
|
|
32
70
|
|
|
71
|
+
一条 `cordis/request-run` 事件问这一页要不要运行某个定义。作答的一方——审批后的页面,或按下运行的用户——驱动编排:先 host 半(host 半失败会在浏览器动作之前短路),再取源码,再浏览器半,最后一次结算带上发生的一切。浏览器半源码作为 async 函数体求值,符号面就是参数;返回的插件经 guard 包装后通过 loader 挂载;结算报告已装载的 revision,或失败阶段加闭包、guard 或 fiber 的消息。`host.call` 经 Remote namespace 路由;省略的入参以 `null` 过线,而生成 codec 拒收的载荷会变成一条点名「哪次调用 + 约定是什么」的教学错误。
|
|
72
|
+
|
|
73
|
+
</details>
|
|
74
|
+
|
|
75
|
+
-----
|
|
76
|
+
|
|
77
|
+
<a id="further-exploration"></a>
|
|
78
|
+
## 进一步探索
|
|
79
|
+
|
|
80
|
+
当包级约定不够用时阅读以下页面。它们从浏览器半逐步进入发问的 host、其运行被应答的工具,以及渲染它的界面。
|
|
81
|
+
|
|
82
|
+
- [Host runner](../cordis-host-runner/README.zh.md)——本包应答的注册表与运行往返。
|
|
83
|
+
- [工具包](../tool-cordis/README.zh.md)——运行请求到达本页的模型侧工具。
|
|
84
|
+
- [UI 包](../ui-cordis/README.zh.md)——操作这个面的面板与卡片。
|
|
85
|
+
- [extensions 子系统](../../../docs/subsystems/extensions.zh.md)——生成的 `ctx.dynamicCordisRunner` API 与 `cordis/*` 事件。
|
|
86
|
+
- [动态客户端渲染与附件归属 Agent Note](../../../.agents/notes/implemented/architecture/2026-08-17-dynamic-client-render-and-attachment-ownership.zh.md)——浏览器插件如何拥有自己的渲染与 CSS。
|
|
87
|
+
- [客户端外壳与动态包 Agent Note](../../../.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.zh.md)——浏览器半的包归属与构建面。
|
|
88
|
+
|
|
89
|
+
-----
|
|
90
|
+
|
|
91
|
+
<a id="model-experience"></a>
|
|
33
92
|
## 模型体验
|
|
34
93
|
|
|
35
94
|
### 由模型发起那次 run 的最终回答
|
|
36
95
|
|
|
37
|
-
####
|
|
96
|
+
#### 模型看到的内容
|
|
38
97
|
|
|
39
|
-
|
|
98
|
+
本包自己不贡献任何工具、提示词或上下文;它撰写并到达模型的第一样内容,是为一次 `cordis/request-run` 往返发回的回答——host 把它变成那个被阻塞的 `cordis_run` 的结果。成功时带上已装载的 revision,以及(当浏览器半挂在这一页没有的服务上时)那些服务的名字。失败时带一个 reason:用户拒绝的 `rejected`、`host-half-failed` 或 `client-half-failed`;后者还带上本包自己的文本——出错阶段(`evaluate`、`module-import` 或 `activate`)加上闭包、guard 或 fiber 的消息。guard 的教学错误(未声明的服务、被遮蔽的浏览器全局、返回值里没有 `apply`)正是经这个字段到达模型的。而装载之后、React 渲染时才发生的崩溃,走下面那条独立的事后通道。
|
|
40
99
|
|
|
41
|
-
####
|
|
100
|
+
#### Token 影响
|
|
42
101
|
|
|
43
|
-
有条件且有界:每次 run 请求最多一个回答,花在 host 本来就会发出的那个 `cordis_run`
|
|
102
|
+
有条件且有界:每次 run 请求最多一个回答,花在 host 本来就会发出的那个 `cordis_run` 结果里。文本随数据而定(某个定义自己的错误消息),本包跨请求不留存任何东西——一页后续的装载失败是页面本地诊断,在模型侧没有任何承载物。
|
|
44
103
|
|
|
45
|
-
#### KV
|
|
104
|
+
#### KV Cache 影响
|
|
46
105
|
|
|
47
106
|
只追加。回答只作为「本来就在途的那次请求」的工具结果到达模型、延长历史尾部;本包撰写的内容不会重写或重排更早的请求 token,因此原本可复用的前缀仍然可复用。同一定义的多次运行各自产出各自的结果,而不是替换更早那一个。
|
|
48
107
|
|
|
49
108
|
### run 落定之后的渲染期失败
|
|
50
109
|
|
|
51
|
-
####
|
|
110
|
+
#### 模型看到的内容
|
|
111
|
+
|
|
112
|
+
一个装载得干干净净的浏览器半,仍可能在 React 渲染时崩溃,而那次崩溃发生在 run 已经被回答之后——否则模型只会被告知「ok」,永远学不到。凡是本页落座过的包,其 entry 边界的每一次崩溃都会发回 host(`reportRenderFailure`):点名槽位、说明这次崩溃是否已把 entry 从格位上摘掉(`abdicated`:包的 UI 是没了、而不只是坏了),以及一条写给作者的 message。host 每包只留最后一条,用它 steer 所属会话,并经由 `cordis_inspect_self` 暴露;这条通道上的任何东西都不会进入 run 的最终回答。
|
|
113
|
+
|
|
114
|
+
#### Token 影响
|
|
115
|
+
|
|
116
|
+
有条件,且其上界由 host 的留存策略决定、不由这一页决定:每次崩溃一条报告,而 host 每包只留最新一条——所以一个反复崩溃的 entry 对模型的代价是一条消息,而不是一张越来越长的清单。报告本身不会自带任何工具结果:模型只在被 steer 或主动去问的时候才为它付费。
|
|
117
|
+
|
|
118
|
+
#### KV Cache 影响
|
|
119
|
+
|
|
120
|
+
自身没有。报告经 RPC 送出并被存起来,而不是追加进对话;模型通过一条 steer 消息或自己发起的查看读到它们,那次查看与任何工具结果一样只延长尾部。
|
|
121
|
+
|
|
122
|
+
## 已知限制与延期工作
|
|
123
|
+
|
|
124
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
52
125
|
|
|
53
|
-
一个装载得干干净净的浏览器半,仍可能在 React 渲染时崩溃,而那次崩溃发生在 run 已经被回答之后 —— 否则模型只会被告知「ok」,永远学不到。凡是本页落座过的包,其 entry 边界的每一次崩溃都会发回 host(`reportRenderFailure`):点名槽位、说明这次崩溃是否已把 entry 从格位上摘掉(`abdicated`:包的 UI 是没了、而不只是坏了),以及一条写给作者的 message —— 崩溃文本,外加「文本里点到了某个被摘掉的浏览器全局、但文本自己没教」时补上的那句教学:绕过闭包陷阱的 `window.setInterval` 只会崩成 `is not a function`,它自己什么都解释不了。host 每包只留最后一条,经 `cordis_inspect` 透给模型;这条通道上的任何东西都不会进入 run 的最终回答。同一次观察还会落到 `renderFailures` 上给本页界面用 —— 一个观察者、两个出口,因为「跨页面最后一次崩溃(给模型)」与「这一页此刻正在显示什么」是两件寿命不同的事实。
|
|
54
126
|
|
|
55
|
-
|
|
127
|
+
这些限制说明浏览器半何时需要特别小心。它们是当前包约束,不是任务积压。
|
|
56
128
|
|
|
57
|
-
|
|
129
|
+
- **被拒绝的回答不会重试**——`resolveRequestRun` 的 ack 不读,所以当 host 拒绝一个陈旧的成功答复(`accepted: false`——这一页装载期间定义的 revision 被顶掉了),这一页会保留已装的东西、也不再重新编排。那次请求仍可作答(别的页面作答或调用方取消都能收尾),而顶掉 revision 的那次 stop 会 retract 掉陈旧装载。
|
|
130
|
+
- **host namespace 存在之前插件一直挂起**——它声明 `remote.dynamicCordisRunner`,因此绝不会装载一个永远够不到自己 host 半的浏览器半。
|
|
131
|
+
- **槽位准入没有载体**——下发行声明的是服务,不是目标槽位,因此按部署的槽位允许/拒绝清单无处可驮。
|
|
132
|
+
- **guard 白名单是手抄的孪生**——浏览器 guard 复刻 host 侧沙箱门面;抽取共享规格留待后续。
|
|
58
133
|
|
|
59
|
-
|
|
134
|
+
<a id="dev-note"></a>
|
|
135
|
+
### 开发备注
|
|
60
136
|
|
|
61
|
-
|
|
137
|
+
<details>
|
|
138
|
+
<summary>维护者的工作上下文——点击展开</summary>
|
|
62
139
|
|
|
63
|
-
|
|
140
|
+
无。
|
|
64
141
|
|
|
65
|
-
|
|
66
|
-
- 插件声明了 `remote.dynamic`,因此在 host 侧 namespace 存在之前一直挂起,而不是装载一些永远够不到自己 host 半的包。
|
|
67
|
-
- 槽位准入(按部署的允许/拒绝清单)没有载体:下发行声明的是服务,不是目标槽位。
|
|
68
|
-
- guard 白名单是 host 侧沙箱门面的手抄孪生;抽取共享规格留待后续。
|
|
142
|
+
</details>
|