@hasna-internal/kai-cordis-client-runner 0.1.1-rc.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.
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Per-package browser lifecycle: evaluate the closure, wrap `apply` in the guard
3
+ * facade, seat a ready-made factory in the module table, and create a loader
4
+ * entry — so dynamic packages ride the exact machinery static plugins do
5
+ * (activation gating on inject, fiber-effect cleanup, status projection). Unload
6
+ * = loader entry removal (fiber disposal cascades slot entries and facade
7
+ * effects) + factory invalidation + style removal.
8
+ *
9
+ * The engine answers its caller: `load` resolves with what this page ended up
10
+ * with, which is what the run orchestration reports back to the host. Loads
11
+ * converge by Plugin Run ID against live state, not history: loading the exact
12
+ * activation this page already runs is a no-op that still answers, another run
13
+ * replaces it, and the same Package after a retract loads afresh. Per-Plugin
14
+ * serialization keeps a second request from interleaving with one in flight.
15
+ */
16
+ import type { Context } from '@deepseek-ai/cordis';
17
+ import type { Loader } from '@deepseek-ai/cordis-plugin-loader';
18
+ import type { CordisDynamicPackageId, CordisDynamicPluginId, CordisDynamicPluginRunId } from '@hasna-internal/kai-api-remotes/client';
19
+ import type { SessionId } from '@hasna-internal/kai-client-connection/client';
20
+ import type { ClientModuleSystem } from '@hasna-internal/kai-client-modules/client';
21
+ import type { SlotRegistry } from '@hasna-internal/kai-client-runtime/client';
22
+ /**
23
+ * Snapshot source a surface can subscribe to (the render seam's observable
24
+ * shape). Lives here because both this engine and the run orchestration publish
25
+ * through it, and the orchestration already depends on this module.
26
+ */
27
+ export interface CordisObservable<T> {
28
+ /** Current value; the reference is stable between mutations. */
29
+ getSnapshot(): T;
30
+ /**
31
+ * Observe mutations.
32
+ * @param fn - notified after each committed change.
33
+ * @returns unsubscribe.
34
+ */
35
+ subscribe(fn: () => void): () => void;
36
+ }
37
+ /** Which stage of a load failed, as the page classified it. */
38
+ export type DynamicCordisLoadErrorCause = 'evaluate' | 'module-import' | 'activate';
39
+ /** Error fields retained by the page runner and Host transport. */
40
+ export interface CordisErrorDetails {
41
+ /** Original error message. */
42
+ message: string;
43
+ /** Original stack when the thrown value supplied one. */
44
+ stack?: string;
45
+ }
46
+ /** One package's browser half as the host handed it over. */
47
+ export interface DynamicCordisClientHalf {
48
+ /** Stable Plugin instance. */
49
+ pluginId: CordisDynamicPluginId;
50
+ /** Immutable Package source version. */
51
+ packageId: CordisDynamicPackageId;
52
+ /** Exact activation. */
53
+ pluginRunId: CordisDynamicPluginRunId;
54
+ /** Session the run is carried out for; a later render failure is reported under it. */
55
+ agentId: SessionId;
56
+ /** Label from the define call; also the plugin name. */
57
+ name: string;
58
+ /** Browser-half source: an async function body returning a plugin. */
59
+ code: string;
60
+ }
61
+ /**
62
+ * One render-time crash of a dynamic package's slot entry, as this page reports
63
+ * it. Post-settle diagnosis only: the run it belongs to was answered long before
64
+ * (a package that crashes while rendering loaded successfully), so this never
65
+ * reaches a run resolution.
66
+ */
67
+ export interface DynamicCordisRenderFailure {
68
+ /** Slot key the crashed entry rendered under. */
69
+ slot: string;
70
+ /** What the author has to read to fix it: the crash text, plus a redirect when it names a withheld global. */
71
+ message: string;
72
+ /** Original render failure stack when available. */
73
+ stack?: string;
74
+ /** Whether the crash retired the entry from its cell — the package's UI is gone, not merely broken. */
75
+ abdicated: boolean;
76
+ }
77
+ /**
78
+ * What this page ended up with. A parked package is a success — the browser half
79
+ * settled and waits on declared services this page has not got.
80
+ */
81
+ export type DynamicCordisLoadResult = {
82
+ ok: true;
83
+ pluginRunId: CordisDynamicPluginRunId;
84
+ waitingFor?: string[];
85
+ } | ({
86
+ ok: false;
87
+ cause: DynamicCordisLoadErrorCause;
88
+ error?: unknown;
89
+ } & CordisErrorDetails);
90
+ /** Runner dependencies, resolved by the plugin entry at activation. */
91
+ export interface DynamicCordisRunnerEnv {
92
+ /** The client root context (service reads and the guard's fiber owner). */
93
+ ctx: Context;
94
+ /** Client cordis Loader: dynamic packages become entries under it. */
95
+ loader: Loader;
96
+ /** Module table, for factory invalidation before every (re-)registration. */
97
+ modules: ClientModuleSystem;
98
+ /** Slot registry, for the entry-crash supervision seam. */
99
+ slots: SlotRegistry;
100
+ /** Route one `host.call` to the package's host half through the Remote namespace. */
101
+ invoke(pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId, method: string, args: unknown): Promise<unknown>;
102
+ /**
103
+ * Send one render-time crash back to the session that authored the package.
104
+ * Fire-and-forget by contract: the crash already happened, and a failed report
105
+ * must not become a second failure.
106
+ * @param agentId - session the crashed package was run for.
107
+ * @param id - the crashed package.
108
+ * @param failure - slot, teaching text, and whether the entry was retired.
109
+ */
110
+ reportRenderFailure(agentId: SessionId, pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId, failure: DynamicCordisRenderFailure): void;
111
+ /** Send one post-activation Client guard rejection to the owning Agent. */
112
+ reportGuardFailure(agentId: SessionId, pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId, failure: CordisErrorDetails): void;
113
+ }
114
+ /** One live package's contribution summary in this page. */
115
+ export interface DynamicCordisLivePackage {
116
+ /** Stable Plugin instance. */
117
+ pluginId: CordisDynamicPluginId;
118
+ /** Immutable Package source version. */
119
+ packageId: CordisDynamicPackageId;
120
+ /** Exact activation loaded in this page. */
121
+ pluginRunId: CordisDynamicPluginRunId;
122
+ /** Label from the define call. */
123
+ name: string;
124
+ /** Slot names this package registered into here. */
125
+ slots: string[];
126
+ /** Live injected-style tag count. */
127
+ styleCount: number;
128
+ }
129
+ /** The browser-side load engine for dynamic packages. */
130
+ export declare class DynamicCordisPackageRunner {
131
+ private readonly env;
132
+ private readonly live;
133
+ /** Serializes load/unload per package id (a second request can outrun a slow load). */
134
+ private readonly queues;
135
+ private readonly changeListeners;
136
+ /** Page-local shadowing rank. A later registration receives a lower priority. */
137
+ private nextPriority;
138
+ /**
139
+ * Which package seated which component, and for whom. Component identity is the
140
+ * only attribution key that holds:
141
+ * - the registry stores the component verbatim, so a crashed entry carries its
142
+ * own way back — no parallel entry ledger to keep in step;
143
+ * - `entry.registrant` is `options.registrant ?? fiber.name` and the facade does
144
+ * not strip a package-supplied one, so a package could name itself something
145
+ * else — attributing by it would let a package impersonate another;
146
+ * - the assigned shadowing priority is unique but absent on chain entries (their
147
+ * election is deliberately left alone), so it would miss chain crashes;
148
+ * - a package torn down between the crash and the report is still attributable,
149
+ * because this index does not depend on the live record.
150
+ *
151
+ * Two packages cannot collide here: each browser half is evaluated in its own
152
+ * closure, so no component object reaches two of them. A collision is only
153
+ * possible inside ONE package (the same component seated twice), where both
154
+ * entries map to the same id and the value is identical.
155
+ */
156
+ private readonly owners;
157
+ /** This page's last render crash per package: what a run surface shows on the row. */
158
+ private readonly failures;
159
+ private readonly unwatch;
160
+ private snapshotCache;
161
+ private failureCache;
162
+ /** @param env - loader/module/slot wiring plus the two host verbs this engine uses. */
163
+ constructor(env: DynamicCordisRunnerEnv);
164
+ /**
165
+ * Observe live-set changes (the run-state surface's re-render seam).
166
+ * @param fn - notified after every converged mutation.
167
+ * @returns unsubscribe.
168
+ */
169
+ subscribe(fn: () => void): () => void;
170
+ /**
171
+ * This page's last render crash per package, on the same notification channel as
172
+ * the live set — a surface that already subscribed learns about a crash without
173
+ * a second mechanism to wire.
174
+ */
175
+ readonly renderFailures: CordisObservable<ReadonlyMap<CordisDynamicPluginId, DynamicCordisRenderFailure>>;
176
+ /**
177
+ * What this page currently has loaded (stable reference between mutations, so
178
+ * it can back a snapshot selector).
179
+ * @returns one row per live package.
180
+ */
181
+ getSnapshot(): readonly DynamicCordisLivePackage[];
182
+ /**
183
+ * Whether this page has the browser half loaded — page-local truth, never the
184
+ * host's "it is running".
185
+ * @param pluginId - stable Plugin identity.
186
+ * @returns true while one activation of the Plugin is live here.
187
+ */
188
+ isLoaded(pluginId: CordisDynamicPluginId): boolean;
189
+ /**
190
+ * Load one browser half into this page and answer what happened.
191
+ * @param half - source for one exact Host activation.
192
+ * @returns the outcome the run orchestration reports to the host.
193
+ */
194
+ load(half: DynamicCordisClientHalf): Promise<DynamicCordisLoadResult>;
195
+ /**
196
+ * Unload one package (`cordis/dynamic-retract`: a stop, or an undefine
197
+ * that stops first).
198
+ * @param pluginId - stable Plugin identity.
199
+ * @param pluginRunId - exact activation being retracted; a newer run survives.
200
+ */
201
+ retract(pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId): void;
202
+ /** Unload everything (plugin disposal path). */
203
+ dispose(): Promise<void>;
204
+ private notify;
205
+ /** Queue one package operation behind that package's previous ones. */
206
+ private enqueue;
207
+ private mount;
208
+ /**
209
+ * Wrap the evaluated plugin so `apply` sees the guard facade; the surface
210
+ * doubles as the module-table module. The plugin's OWN `inject` survives (the
211
+ * object form's declaration is the facade's service gate, mirroring the host
212
+ * sandbox reading `ctx.fiber.inject`); the function form has no declaration
213
+ * site and therefore reaches no service.
214
+ */
215
+ private guardedSurface;
216
+ /**
217
+ * Unload one package's contributions. Takes the pieces rather than the record
218
+ * because a load can fail before any record is seated.
219
+ */
220
+ private teardown;
221
+ }
222
+ /**
223
+ * Preserve error fields for a load result without fabricating a stack.
224
+ * @param error - original thrown value.
225
+ * @returns its message and original string stack, when present.
226
+ */
227
+ export declare function errorDetails(error: unknown): CordisErrorDetails;
228
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Generated by scripts/gen-client-catalog.ts — do not edit by hand; run
3
+ * `pnpm run gen-client-catalog` to regenerate (freshness-gated by
4
+ * `pnpm run verify-client-catalog` in doc-sync).
5
+ *
6
+ * The compile-time contract of the shipped web bundle's slot surface, as
7
+ * `cordis_inspect what:"client"` serves it to the model: every SlotMap key a
8
+ * browser half can register into, what that register call must pass, what the
9
+ * component receives, who already occupies the seat, and which owner has to be
10
+ * mounted for the seat to exist. Data only — this module is the one legitimate
11
+ * meeting point of the two planes, so it carries strings, never client imports.
12
+ *
13
+ * @module @hasna-internal/kai-cordis-client-runner/client/slot-catalog
14
+ */
15
+ /** One option a register call passes for a given slot cardinality. */
16
+ export interface ClientSlotOption {
17
+ /** Option name as written in the register options object. */
18
+ name: string;
19
+ /** Whether the cardinality requires it. */
20
+ requirement: string;
21
+ /** Accepted type, in source spelling. */
22
+ type: string;
23
+ /** What it does, from the registrant's side. */
24
+ doc: string;
25
+ }
26
+ /** One browser-half slot a dynamic package can contribute UI into. */
27
+ export interface ClientSlotEntry {
28
+ /** SlotMap key passed as the register call's `name`. */
29
+ key: string;
30
+ /** Cardinality: `single`, `list`, `keyed`, or `chain`. */
31
+ kind: string;
32
+ /** Data scope: `root`, `session`, or `session-maybe`. */
33
+ scope: string;
34
+ /** First sentence of the contract prose. */
35
+ summary: string;
36
+ /** Full contract prose from the SlotMap declaration. */
37
+ doc: string;
38
+ /** Options this cardinality accepts (beyond `name`). */
39
+ registerOptions: readonly ClientSlotOption[];
40
+ /** Declarations of the props the owner passes down, with their own documentation. */
41
+ ownerProps: readonly string[];
42
+ /** Names of the shapes those props reference; deliberately not expanded here. */
43
+ ownerPropsReferences: readonly string[];
44
+ /** Framework-supplied component props for this scope. */
45
+ standardProps: readonly string[];
46
+ /** For keyed slots: how the key set is constrained and which keys are taken. */
47
+ keyDomain: string;
48
+ /** Opaque per-render-site context passed to slot-level hooks, when the slot declares one. */
49
+ hookContext: string;
50
+ /** Slot-level inject face every entry receives, when the slot declares one. */
51
+ slotInject: string;
52
+ /** Which mounted entry makes this slot exist. */
53
+ declaredBy: string;
54
+ /** Entries the shipped composition already registered here. */
55
+ occupants: readonly string[];
56
+ /** `shadows-shipped-ui` when registering here replaces shipped UI; `none` when additive. */
57
+ replaceRisk: string;
58
+ /** A minimal browser half that registers into this slot. */
59
+ example: string;
60
+ /** Source pointer of the contract declaration. */
61
+ source: string;
62
+ }
63
+ /** Rules that apply to every browser-half contribution, in reading order. */
64
+ export declare const CLIENT_NOTES: readonly string[];
65
+ /** Every slot the shipped web bundle declares, sorted by key. */
66
+ export declare const CLIENT_SLOT_API: readonly ClientSlotEntry[];
67
+ //# sourceMappingURL=slot-catalog.d.ts.map
@@ -0,0 +1,84 @@
1
+ /** Browser implementation of the Cordis timer Service. */
2
+ import { Service } from '@deepseek-ai/cordis';
3
+ import type { Context } from '@deepseek-ai/cordis';
4
+ declare module '@deepseek-ai/cordis' {
5
+ interface Context extends Pick<ClientTimerService, 'interval' | 'timeout' | 'throttle' | 'debounce' | 'setTimeout' | 'setInterval'> {
6
+ /** Browser timer Service used by the mixed-in Context helpers. */
7
+ timer: ClientTimerService;
8
+ }
9
+ }
10
+ type WithDispose<T> = T & {
11
+ dispose: () => void;
12
+ };
13
+ /** Browser timer Service with the same public API as the Host Cordis TimerService. */
14
+ export declare class ClientTimerService extends Service {
15
+ /** Register the Service and mix its lifecycle-safe helpers onto Context. */
16
+ constructor(ctx: Context);
17
+ /**
18
+ * Run a callback once through {@link timeout}.
19
+ * @param callback - Work to run after the delay.
20
+ * @param delay - Delay in milliseconds.
21
+ * @returns Disposer that cancels the pending callback early.
22
+ * @deprecated Use `ctx.timeout()` instead.
23
+ */
24
+ setTimeout(callback: () => void, delay: number): () => void;
25
+ /**
26
+ * Run a callback repeatedly through {@link interval}.
27
+ * @param callback - Work to run on each tick.
28
+ * @param delay - Interval in milliseconds.
29
+ * @returns Disposer that stops the interval early.
30
+ * @deprecated Use `ctx.interval()` instead.
31
+ */
32
+ setInterval(callback: () => void, delay: number): () => void;
33
+ /**
34
+ * Run a callback once after a delay.
35
+ * @param callback - work to run.
36
+ * @param delay - delay in milliseconds.
37
+ * @returns disposer that cancels the callback.
38
+ */
39
+ timeout(callback: () => void, delay: number): () => void;
40
+ /**
41
+ * Wait for a delay.
42
+ * @param delay - delay in milliseconds.
43
+ * @returns promise resolved after the delay.
44
+ */
45
+ timeout(delay: number): Promise<void>;
46
+ /**
47
+ * Run a callback repeatedly.
48
+ * @param callback - work to run on each tick.
49
+ * @param delay - interval in milliseconds.
50
+ * @returns disposer that stops the interval.
51
+ */
52
+ interval(callback: () => void, delay: number): () => void;
53
+ /**
54
+ * Iterate over timer ticks.
55
+ * @param delay - interval in milliseconds.
56
+ * @returns async iterator of ticks.
57
+ */
58
+ interval<R = any>(delay: number): AsyncIterableIterator<void, R, void>;
59
+ /** Build a delayed wrapper whose pending callback belongs to the calling Fiber. */
60
+ private schedule;
61
+ /**
62
+ * Return a throttled function whose timer is disposed with the calling Fiber.
63
+ * @param callback - Function to throttle.
64
+ * @param delay - Minimum interval between calls in milliseconds.
65
+ * @param noTrailing - Whether to suppress a delayed trailing call.
66
+ * @returns Throttled function with an early disposer.
67
+ */
68
+ throttle<F extends (...args: any[]) => void>(callback: F, delay: number, noTrailing?: boolean): WithDispose<F>;
69
+ /**
70
+ * Return a debounced function whose timer is disposed with the calling Fiber.
71
+ * @param callback - Function to debounce.
72
+ * @param delay - Quiet period in milliseconds.
73
+ * @returns Debounced function with an early disposer.
74
+ */
75
+ debounce<F extends (...args: any[]) => void>(callback: F, delay: number): WithDispose<F>;
76
+ }
77
+ /**
78
+ * Install the browser timer Service on one Client composition.
79
+ * @param ctx - Client context that owns the Service and mixed-in helpers.
80
+ * @returns Nothing after registering the Service.
81
+ */
82
+ export declare function provideClientTimer(ctx: Context): void;
83
+ export {};
84
+ //# sourceMappingURL=timer.d.ts.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Dynamic-package runner plugin, node half. Pure browser-side capability: the
3
+ * empty apply exists so the row appears in the host cordis.yml / Loader, while
4
+ * the browser half ships through exports["./client"], discovered from the
5
+ * package.json dshClient declaration.
6
+ */
7
+ /** Host plugin body — this package contributes nothing host-side. */
8
+ export declare function apply(): void;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@hasna-internal/kai-cordis-client-runner`.
3
+ * @module @hasna-internal/kai-cordis-client-runner/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "cordis-client-runner-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@hasna-internal/kai-cordis-client-runner",
3
+ "description": "Browser half of dynamic dual-half plugin packages: event subscription, closure evaluation, guard facade, and loader entries",
4
+ "version": "0.1.1-rc.2",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/extensions/cordis-client-runner"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./invariant": {
22
+ "types": "./lib/types/invariant.d.ts",
23
+ "default": "./lib/invariant.js"
24
+ },
25
+ "./client": {
26
+ "types": "./lib/types/client/index.d.ts",
27
+ "default": "./lib/client.js"
28
+ },
29
+ "./src/*": "./src/*",
30
+ "./package.json": "./package.json"
31
+ },
32
+ "dsh": {
33
+ "client": {
34
+ "inject": [
35
+ "@hasna-internal/kai-client-runtime",
36
+ "@hasna-internal/kai-api-remotes",
37
+ "@hasna-internal/kai-client-modules",
38
+ "@hasna-internal/kai-client-ui-theme"
39
+ ],
40
+ "platform": "web"
41
+ }
42
+ },
43
+ "license": "MIT",
44
+ "peerDependencies": {
45
+ "@deepseek-ai/cordis-plugin-loader": "^1.0.2",
46
+ "@hasna-internal/kai-api-remotes": "^0.1.1-rc.2",
47
+ "@hasna-internal/kai-client-connection": "^0.1.1-rc.2",
48
+ "@hasna-internal/kai-client-modules": "^0.1.1-rc.2",
49
+ "@hasna-internal/kai-client-ui-theme": "^0.1.1-rc.2",
50
+ "@hasna-internal/kai-client-runtime": "^0.1.1-rc.2",
51
+ "@deepseek-ai/cordis": "^4.0.1",
52
+ "@hasna-internal/kai-invariants": "^0.1.1-rc.2"
53
+ },
54
+ "devDependencies": {
55
+ "@types/react": "~18.3.1",
56
+ "react": "^18.2.0",
57
+ "@hasna-internal/kai-api-remotes": "^0.1.1-rc.2",
58
+ "@hasna-internal/kai-client-ui-theme": "^0.1.1-rc.2",
59
+ "@deepseek-ai/cordis-plugin-loader": "^1.0.2",
60
+ "@hasna-internal/kai-client-modules": "^0.1.1-rc.2",
61
+ "@hasna-internal/kai-client-connection": "^0.1.1-rc.2",
62
+ "@hasna-internal/kai-client-runtime": "^0.1.1-rc.2",
63
+ "@deepseek-ai/cordis": "^4.0.1",
64
+ "@hasna-internal/kai-invariants": "^0.1.1-rc.2"
65
+ },
66
+ "files": [
67
+ "lib/index.js",
68
+ "lib/invariant.js",
69
+ "lib/client.js",
70
+ "lib/types/**/*.d.ts"
71
+ ],
72
+ "scripts": {
73
+ "bundle": "tsdown",
74
+ "watch": "tsdown --watch"
75
+ }
76
+ }