@hasna-internal/kai-scope 0.1.1-rc.2 → 0.1.1-rc.3
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.md +2 -36
- package/index.js +2 -0
- package/package.json +10 -36
- package/LICENSE +0 -21
- package/README.i18n.yaml +0 -6
- package/README.zh.md +0 -37
- package/lib/index.js +0 -357
- package/lib/invariant.js +0 -72
- package/lib/types/index.d.ts +0 -110
- package/lib/types/invariant.d.ts +0 -13
- package/lib/types/scoped-events.generated.d.ts +0 -18
- package/lib/types/store.d.ts +0 -147
package/README.md
CHANGED
|
@@ -1,37 +1,3 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @hasna-internal/kai-scope
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Scoped registration primitive. `createScope(ctx, key)` creates a tagged Cordis context whose backing fiber owns every registration made through it. `scopeOf(ctx)` reads the tag, and `scopeTarget(base, key)` routes scoped events to listeners with the same key while leaving unscoped listeners global. Keys form an optional parent chain (`bindScopeParent`): registration views inherit DOWN it — a child scope sees its ancestors' layers, nearest shadowing farthest — and event admission extends UP it — a listener tagged with an ancestor receives a descendant key's events, never the reverse. The agent loop creates one scope per live agent and an agent preset's standing mount is a parent scope over its agents, but the mechanism is key-agnostic so lower-level packages can use it without depending on either.
|
|
6
|
-
|
|
7
|
-
## Public API
|
|
8
|
-
|
|
9
|
-
- `createScope(ctx: Context, key: ScopeKey, options?): Scope` Mint a scope under `ctx`'s fiber. Usable synchronously (effect collection is uid-gated; service resolution falls through to the minting plugin's dependency surface). The typed, same-process key is trusted; an inactive minting context still fails through Cordis (`INACTIVE_EFFECT`). `options.parent` binds the enclosing scope via `bindScopeParent` before the scope is usable; the binding stays internal.
|
|
10
|
-
- `bindScopeParent(key, parent): ScopeParentBinding` / `scopeParentOf(key)` / `scopeChainOf(key)` The parent relation behind both chain directions. Binding is once: a key that already has a parent throws, and only the returned binding's `rebind(parent)` may re-link it — the blank-session recompose operation, valid only while nothing produced under the old parent is retained (the holder's contract — this relation cannot see what a session logged). Both the bind and every rebind reject a link closing a cycle. `scopeChainOf` returns `[key, parent, …]` nearest-first.
|
|
11
|
-
- `Scope.ctx` The tagged context: registrations through it are scope-visible AND scope-lifetime. Derived contexts (an `extend`, a fiber mounted under it) inherit the tag; nested scopes shadow (nearest tag wins).
|
|
12
|
-
- `Scope.rawDispose` The EXACT Cordis disposer for the backing fiber — a composite (generator) effect yields THIS function to nest the scope's teardown at that yield position (Cordis dedupes nested effects by function identity; yielding a wrapper leaves the scope disposing as a concurrent sibling).
|
|
13
|
-
- `Scope.dispose(): Promise<void>` Idempotent, shared quiescence boundary for every registration made through the scope. Racing/repeat calls await the same teardown, including when `rawDispose` invoked the underlying single-shot Cordis disposer first.
|
|
14
|
-
- `scopeOf(ctx: Context): ScopeKey | undefined` The tag a context (or any context derived from it) carries; `undefined` = context-global.
|
|
15
|
-
- `scopeTarget(base: T, key: ScopeKey | undefined): Scoped<T>` Build the opaque dispatch `thisArg` for a scope-filtered event. It composes `base`'s existing `Context.filter` with the scope predicate (untagged listener ⇒ admitted; tagged ⇒ admitted iff its tag is the key or an ancestor of it; `key === undefined` ⇒ untagged only). The carrier contains routing state only; the real subject is carried by the event arguments. `{ global: true }` listeners bypass filtering (Cordis semantics).
|
|
16
|
-
- `Scoped<T>` The compile-time opaque carrier brand: scope-filtered events demand it as their `this` type, so dispatching with a bare subject is a compile error. The type parameter records the subject type but does not expose its properties.
|
|
17
|
-
- `isScopeCarrier(value)` / `carrierKeyOf(value)` Runtime carrier marks, used by the dev invariants to assert every scope-filtered dispatch carries a carrier keyed to the subject its arguments name.
|
|
18
|
-
- `ScopeLayer` Aggregate contract for one registry's complete global or exact-scope contribution; `isEmpty()` controls scoped-layer reclamation.
|
|
19
|
-
- `ScopedLayers<L>` Own one eager global layer and lazy exact-scope layers. `peek()` never creates and stays chain-blind (a scope's OWN contributions — restrictions, guards — must not silently pick up an ancestor's), `chainLayers()` returns existing overlays farthest-ancestor-first, `merge()` materializes insertion-ordered named shadows along the chain, and `effect()` derives visibility and ownership from the same context while returning the exact Cordis disposer.
|
|
20
|
-
- `NamedEntries<V>` Insertion-ordered named storage with caller-owned duplicate diagnostics, lookup, and live iteration within one nonempty table generation; draining the table detaches existing iterators from later insertions, and `insert()` returns an idempotent exact-entry undo.
|
|
21
|
-
- `AnonymousEntries<V>` Insertion-ordered anonymous storage whose unique internal keys keep equal values as independent registrations; it uses the same drained-generation iterator boundary, and `append()` returns an idempotent exact-entry undo.
|
|
22
|
-
|
|
23
|
-
The optional `@hasna-internal/kai-scope/invariant` companion owns that runtime assertion. It uses the generated `scoped-events.generated.ts` resolver map to require a carrier for every declared scoped event and, when the payload exposes its routing subject, require identity with the carrier key. The Program-backed generator derives the map from event declarations and real `scopeTarget(base, key)` calls.
|
|
24
|
-
|
|
25
|
-
## Design contract
|
|
26
|
-
|
|
27
|
-
The registration context determines both visibility and ownership, preventing a registration from being visible in one scope but disposed with another. Scopes route trusted same-process plugins; they are not sandboxes or authority boundaries. See the [agent-scope Agent Note](../../../.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md#security-and-authority-are-non-goals) for rationale and security non-goals.
|
|
28
|
-
|
|
29
|
-
Scope-aware services define a concrete `ScopeLayer` that aggregates their heterogeneous tables and domain helpers. `ScopedLayers.effect()` accepts one synchronous action returning one synchronous undo, installs that undo before optional notification, and reclaims an exact-scope layer only when the complete aggregate is empty. `notify` defaults to `true`; the supplied callback owns whether observer failures throw or are contained. `EntryValues` remains internal, the storage classes are imported from the package root rather than a `/store` subpath, and the shared storage does not define registry-specific filtering or iteration policy. See the [shared scoped-layer storage Agent Note](../../../.agents/notes/implemented/architecture/2026-07-12-scoped-layers-store.md).
|
|
30
|
-
|
|
31
|
-
Handing out a scoped context hands out the minting plugin's service-resolution API (resolution walks the minting fiber's dependency chain, not the holder's) — mint it from the plugin whose dependencies the scoped registrations need to resolve.
|
|
32
|
-
|
|
33
|
-
## Known Limitations and Deferred Work
|
|
34
|
-
|
|
35
|
-
- **Only scope-aware APIs isolate state** — registries must file by `scopeOf()` and events must dispatch through `scopeTarget()`; an arbitrary Cordis service remains context-global merely because it is called through a scoped context.
|
|
36
|
-
- **A context carries one nearest scope key** — the hierarchy lives in the key-level parent relation, not in context tags; nested scope CONTEXTS still shadow to a single tag, and multi-membership policy sets remain unsupported.
|
|
37
|
-
- **Service reachability comes from the scope minter** — handing out `Scope.ctx` also hands out the minting plugin's injected services, so a broader minter cannot later be narrowed by the holder.
|
|
3
|
+
This package has been retired by its owner. This release is an empty placeholder with no dependencies or supported functionality. Do not use it.
|
package/index.js
ADDED
package/package.json
CHANGED
|
@@ -1,42 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna-internal/kai-scope",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"access": "public"
|
|
7
|
-
},
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
-
"directory": "packages/core/scope"
|
|
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
|
-
"./src/*": "./src/*",
|
|
26
|
-
"./package.json": "./package.json"
|
|
27
|
-
},
|
|
3
|
+
"version": "0.1.1-rc.3",
|
|
4
|
+
"description": "Retired package. Empty placeholder with no dependencies.",
|
|
5
|
+
"main": "index.js",
|
|
28
6
|
"files": [
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"lib/types/**/*.d.ts"
|
|
7
|
+
"index.js",
|
|
8
|
+
"README.md"
|
|
32
9
|
],
|
|
33
10
|
"license": "MIT",
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
"devDependencies": {
|
|
39
|
-
"@deepseek-ai/cordis": "^4.0.1",
|
|
40
|
-
"@hasna-internal/kai-invariants": "^0.1.1-rc.2"
|
|
11
|
+
"deprecated": "Retired by the package owner. Empty placeholder; do not use.",
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"registry": "https://registry.npmjs.org/",
|
|
14
|
+
"access": "public"
|
|
41
15
|
}
|
|
42
|
-
}
|
|
16
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 DeepSeek
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
package/README.i18n.yaml
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
-
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
-
# after editing either side, bring the other along and re-record with:
|
|
4
|
-
# pnpm run verify-translation-pairing --write packages/core/scope/README.md
|
|
5
|
-
README.md: 1ed09acd7ccb864b16fb5d2ac4390264085d1c9e
|
|
6
|
-
README.zh.md: 54edab0be7703a9ead64b42b617ab3ba2f69b7e3
|
package/README.zh.md
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# dsh-scope
|
|
2
|
-
|
|
3
|
-
[English](README.md) | 中文
|
|
4
|
-
|
|
5
|
-
带作用域的注册原语。`createScope(ctx, key)` 创建一个带标签的 Cordis 上下文,其底层 fiber 拥有通过该上下文进行的每项注册。`scopeOf(ctx)` 读取标签;`scopeTarget(base, key)` 将带作用域的事件路由到键相同的监听器,同时让无作用域监听器保持全局可见。键可以构成可选的父链(`bindScopeParent`):注册视图沿链**向下**继承——子作用域看得见祖先各层,近者遮蔽远者——事件放行沿链**向上**扩展——标签为祖先的监听器能收到子孙键的事件,反向永不成立。agent loop(智能体循环)为每个存活的 agent 创建一个作用域,agent preset 的常驻挂载则是其 agent 们的父作用域,但该机制与键的具体含义无关,底层包无需依赖两者即可使用。
|
|
6
|
-
|
|
7
|
-
## 公开 API
|
|
8
|
-
|
|
9
|
-
- `createScope(ctx: Context, key: ScopeKey, options?): Scope`:在 `ctx` 的 fiber 下创建作用域。可以同步使用(effect 收集受 uid 门禁约束;服务解析会沿创建该作用域的插件依赖范围继续查找)。同进程、带类型的键受信任;处于非活动状态的创建上下文仍会通过 Cordis 失败(`INACTIVE_EFFECT`)。`options.parent` 在作用域可用之前经 `bindScopeParent` 绑定其外围作用域;绑定句柄不外泄。
|
|
10
|
-
- `bindScopeParent(key, parent): ScopeParentBinding` / `scopeParentOf(key)` / `scopeChainOf(key)`:支撑两条链方向的父关系。绑定仅此一次:已有父级的键直接抛错,只有返回的绑定句柄的 `rebind(parent)` 才能重新绑定父级——即空白会话 recompose 的操作,仅当旧父之下产出的东西一概不被保留时才合法(这是持有方的约定——该关系看不见会话记录了什么)。绑定与每次 rebind 都拒绝会闭环的链接。`scopeChainOf` 返回 `[key, parent, …]`,最近者在前。
|
|
11
|
-
- `Scope.ctx`:带标签的上下文。通过它进行的注册既具备作用域可见性,也服从作用域生命周期。派生上下文(一次 `extend`、挂载于其下的 fiber)继承标签;嵌套作用域会遮蔽外层标签(最近的标签生效)。
|
|
12
|
-
- `Scope.rawDispose`:底层 fiber 的确切 Cordis disposer。组合式(generator)effect 会 yield 此函数,从而把作用域 teardown 嵌套在该 yield 位置(Cordis 按函数标识去重嵌套 effect;yield 一个包装函数会使作用域 teardown 成为并行的同级操作)。
|
|
13
|
-
- `Scope.dispose(): Promise<void>`:通过作用域进行的每项注册所共用的幂等完全停稳边界。竞态调用或重复调用会等待同一次 teardown;即使 `rawDispose` 先调用了底层单次 Cordis disposer 也是如此。
|
|
14
|
-
- `scopeOf(ctx: Context): ScopeKey | undefined`:上下文或其任意派生上下文携带的标签;`undefined` 表示上下文全局。
|
|
15
|
-
- `scopeTarget(base: T, key: ScopeKey | undefined): Scoped<T>`:为按作用域筛选的事件构造不透明分发 `thisArg`。它把 `base` 现有的 `Context.filter` 与作用域谓词组合起来(无标签监听器 ⇒ 放行;有标签监听器 ⇒ 仅当标签 === key,或标签为 key 的祖先时放行;`key === undefined` ⇒ 仅放行无标签监听器)。载体只包含路由状态;真实主体由事件参数携带。带 `{ global: true }` 的监听器绕过筛选(Cordis 语义)。
|
|
16
|
-
- `Scoped<T>`:编译期不透明载体 brand。按作用域筛选的事件要求它作为 `this` 类型,因此使用裸主体分发会产生编译错误。类型参数记录主体类型,但不公开其属性。
|
|
17
|
-
- `isScopeCarrier(value)`/`carrierKeyOf(value)`:运行时载体标记,开发不变式使用它们断言每次按作用域筛选的分发都携带载体,而且载体键与参数所指名的主体一致。
|
|
18
|
-
- `ScopeLayer`:一个注册表的完整全局贡献或精确作用域贡献的聚合约定;`isEmpty()` 控制带作用域层的回收。
|
|
19
|
-
- `ScopedLayers<L>`:持有一个立即构造的全局层与惰性的精确作用域层。`peek()` 从不创建且刻意不看链(某作用域**自己**的贡献——限制、守卫——不得悄悄继承祖先的),`chainLayers()` 按最远祖先在前返回已存在的各层,`merge()` 沿链物化按插入序的具名遮蔽,`effect()` 从同一上下文推导可见性与所有权,并返回精确的 Cordis disposer。
|
|
20
|
-
- `NamedEntries<V>`:按插入顺序排列的具名存储,调用方拥有重复项诊断、查找,以及一个非空表世代内的实时迭代。表清空后,现有迭代器与后续插入项脱离;`insert()` 返回幂等的精确条目撤销函数。
|
|
21
|
-
- `AnonymousEntries<V>`:按插入顺序排列的匿名存储;唯一内部键使相同值仍作为独立注册存在。它使用相同的清空世代迭代器边界;`append()` 返回幂等的精确条目撤销函数。
|
|
22
|
-
|
|
23
|
-
可选配套包 `@hasna-internal/kai-scope/invariant` 拥有该运行时断言。它使用生成的 `scoped-events.generated.ts` 解析器映射,要求每个已声明的带作用域事件都携带载体;当 payload 公开路由主体时,还要求路由主体与载体键严格相等。基于 Program 的生成器根据事件声明和真实的 `scopeTarget(base, key)` 调用生成该映射。
|
|
24
|
-
|
|
25
|
-
## 设计约定
|
|
26
|
-
|
|
27
|
-
注册上下文同时决定可见性和所有权,防止注册在一个作用域中可见、却随另一个作用域 dispose(资源释放)。作用域用于路由受信任的同进程插件;它们不是沙箱或权限边界。原理与明确排除的安全目标见 [agent 作用域 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.zh.md#security-and-authority-are-non-goals)。
|
|
28
|
-
|
|
29
|
-
感知作用域的服务会定义具体 `ScopeLayer`,聚合各自不同的表与领域辅助函数。`ScopedLayers.effect()` 接受一个返回同步撤销函数的同步动作,在可选通知前安装该撤销函数,并且只有在完整聚合为空时才回收精确作用域层。`notify` 默认为 `true`;由所提供的回调决定观测方失败是向外抛出还是在内部处理。`EntryValues` 保持内部可见;存储类从包根而非 `/store` 子路径导入;共享存储不定义注册表专属的筛选或迭代策略。详见[共享作用域层存储 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-12-scoped-layers-store.zh.md)。
|
|
30
|
-
|
|
31
|
-
交出带作用域的上下文,也会交出创建该上下文的插件的服务解析范围(解析会沿创建者 fiber 的依赖链,而非持有者的依赖链行进),因此应由具备这些带作用域注册所需依赖的插件来创建它。
|
|
32
|
-
|
|
33
|
-
## 已知限制与暂缓事项
|
|
34
|
-
|
|
35
|
-
- **只有感知作用域的表层才会隔离状态**:注册表必须按 `scopeOf()` 归档,事件必须通过 `scopeTarget()` 分发;仅仅通过带作用域的上下文调用任意 Cordis 服务,并不会改变该服务仍为上下文全局这一事实。
|
|
36
|
-
- **一个上下文只携带一个最近的作用域键**:层级关系存在于键级父关系中而非上下文标签里;嵌套作用域**上下文**仍遮蔽为单一标签,多成员策略集仍不受支持。
|
|
37
|
-
- **服务可达性来自作用域创建者**:交出 `Scope.ctx` 也会交出创建插件注入的服务范围,因此,若作用域创建者提供的服务范围较宽,持有者之后也无法将其收窄。
|
package/lib/index.js
DELETED
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
import { Context } from "@deepseek-ai/cordis";
|
|
2
|
-
//#region lib/types/store.js
|
|
3
|
-
/**
|
|
4
|
-
* Shared insertion-ordered storage and effect ownership for scope-aware registries.
|
|
5
|
-
*
|
|
6
|
-
* @module @hasna-internal/kai-scope
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* Insertion-ordered named entries with caller-owned duplicate diagnostics.
|
|
10
|
-
*
|
|
11
|
-
* Values are borrowed. Iterators are live within one nonempty table
|
|
12
|
-
* generation; draining the table detaches them from later insertions. Each
|
|
13
|
-
* successful insertion returns an idempotent undo for that exact entry.
|
|
14
|
-
*/
|
|
15
|
-
var NamedEntries = class {
|
|
16
|
-
duplicateError;
|
|
17
|
-
data = /* @__PURE__ */ new Map();
|
|
18
|
-
constructor(duplicateError) {
|
|
19
|
-
this.duplicateError = duplicateError;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Insert one unique name.
|
|
23
|
-
* @param name - name unique within this table.
|
|
24
|
-
* @param value - borrowed value to retain.
|
|
25
|
-
* @returns an idempotent undo that removes only this insertion.
|
|
26
|
-
*/
|
|
27
|
-
insert(name, value) {
|
|
28
|
-
const data = this.data;
|
|
29
|
-
if (data.has(name)) throw this.duplicateError(name);
|
|
30
|
-
data.set(name, value);
|
|
31
|
-
let active = true;
|
|
32
|
-
return () => {
|
|
33
|
-
if (!active) return;
|
|
34
|
-
active = false;
|
|
35
|
-
data.delete(name);
|
|
36
|
-
if (data.size === 0 && this.data === data) this.data = /* @__PURE__ */ new Map();
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Read one named value.
|
|
41
|
-
* @param name - name to resolve.
|
|
42
|
-
* @returns the retained value, or `undefined` when absent.
|
|
43
|
-
*/
|
|
44
|
-
get(name) {
|
|
45
|
-
return this.data.get(name);
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Test one name for membership.
|
|
49
|
-
* @param name - name to test.
|
|
50
|
-
* @returns whether the table contains that name.
|
|
51
|
-
*/
|
|
52
|
-
has(name) {
|
|
53
|
-
return this.data.has(name);
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Iterate live names in insertion order.
|
|
57
|
-
* @returns the native live key iterator.
|
|
58
|
-
*/
|
|
59
|
-
keys() {
|
|
60
|
-
return this.data.keys();
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Iterate live entries in insertion order.
|
|
64
|
-
* @returns the native live entry iterator.
|
|
65
|
-
*/
|
|
66
|
-
entries() {
|
|
67
|
-
return this.data.entries();
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Iterate live values in insertion order.
|
|
71
|
-
* @returns the native live value iterator.
|
|
72
|
-
*/
|
|
73
|
-
values() {
|
|
74
|
-
return this.data.values();
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Test whether this table has no entries.
|
|
78
|
-
* @returns whether the table is empty.
|
|
79
|
-
*/
|
|
80
|
-
isEmpty() {
|
|
81
|
-
return this.data.size === 0;
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
/**
|
|
85
|
-
* Insertion-ordered anonymous entries with independent registration identity.
|
|
86
|
-
*
|
|
87
|
-
* Equal values remain separate registrations. Values are borrowed, and
|
|
88
|
-
* iterators are live within one nonempty table generation; draining the table
|
|
89
|
-
* detaches them from later appends.
|
|
90
|
-
*/
|
|
91
|
-
var AnonymousEntries = class {
|
|
92
|
-
data = /* @__PURE__ */ new Map();
|
|
93
|
-
/**
|
|
94
|
-
* Append one independently owned value.
|
|
95
|
-
* @param value - borrowed value to retain.
|
|
96
|
-
* @returns an idempotent undo for this exact append.
|
|
97
|
-
*/
|
|
98
|
-
append(value) {
|
|
99
|
-
const data = this.data;
|
|
100
|
-
const key = Symbol();
|
|
101
|
-
data.set(key, value);
|
|
102
|
-
let active = true;
|
|
103
|
-
return () => {
|
|
104
|
-
if (!active) return;
|
|
105
|
-
active = false;
|
|
106
|
-
data.delete(key);
|
|
107
|
-
if (data.size === 0 && this.data === data) this.data = /* @__PURE__ */ new Map();
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Iterate live values in insertion order.
|
|
112
|
-
* @returns the native live value iterator.
|
|
113
|
-
*/
|
|
114
|
-
values() {
|
|
115
|
-
return this.data.values();
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Test whether this table has no entries.
|
|
119
|
-
* @returns whether the table is empty.
|
|
120
|
-
*/
|
|
121
|
-
isEmpty() {
|
|
122
|
-
return this.data.size === 0;
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
/**
|
|
126
|
-
* Own the global and exact-scope layers for one registry.
|
|
127
|
-
*
|
|
128
|
-
* Reads never create scoped layers. Registrations derive both visibility and
|
|
129
|
-
* effect ownership from the supplied Cordis context, collect undo before
|
|
130
|
-
* notification, and reclaim only a completely empty aggregate layer.
|
|
131
|
-
*/
|
|
132
|
-
var ScopedLayers = class {
|
|
133
|
-
createLayer;
|
|
134
|
-
onChange;
|
|
135
|
-
/** The eagerly constructed context-global layer. */
|
|
136
|
-
global;
|
|
137
|
-
scoped = /* @__PURE__ */ new Map();
|
|
138
|
-
constructor(createLayer, onChange) {
|
|
139
|
-
this.createLayer = createLayer;
|
|
140
|
-
this.onChange = onChange;
|
|
141
|
-
this.global = createLayer(void 0);
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Read an existing exact-scope overlay. Deliberately chain-blind: callers
|
|
145
|
-
* addressing one scope's OWN contributions (its restrictions, its guards)
|
|
146
|
-
* must not silently pick up an ancestor's — use {@link chainLayers} where
|
|
147
|
-
* inheritance is the point.
|
|
148
|
-
* @param scope - exact scope key; `undefined` denotes no overlay.
|
|
149
|
-
* @returns the existing scoped layer, or `undefined` without creating one.
|
|
150
|
-
*/
|
|
151
|
-
peek(scope) {
|
|
152
|
-
if (scope === void 0) return void 0;
|
|
153
|
-
return this.scoped.get(scope);
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Existing overlays along the scope's parent chain ({@link scopeChainOf}),
|
|
157
|
-
* farthest ancestor first and the exact scope last, so a caller layering
|
|
158
|
-
* them in order gives the nearest scope the final word.
|
|
159
|
-
* @param scope - viewing scope, or `undefined` for no overlays.
|
|
160
|
-
* @returns the existing layers, nearest last; absent overlays are skipped.
|
|
161
|
-
*/
|
|
162
|
-
chainLayers(scope) {
|
|
163
|
-
const layers = [];
|
|
164
|
-
for (const key of scopeChainOf(scope).reverse()) {
|
|
165
|
-
const layer = this.scoped.get(key);
|
|
166
|
-
if (layer !== void 0) layers.push(layer);
|
|
167
|
-
}
|
|
168
|
-
return layers;
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Materialize global named entries followed by scope-chain shadows,
|
|
172
|
-
* farthest ancestor first, so the nearest scope's entry wins a name.
|
|
173
|
-
* @param scope - viewing scope, or `undefined` for the global view.
|
|
174
|
-
* @param pick - select the named table from a layer.
|
|
175
|
-
* @returns an insertion-ordered effective map.
|
|
176
|
-
*/
|
|
177
|
-
merge(scope, pick) {
|
|
178
|
-
const merged = new Map(pick(this.global).entries());
|
|
179
|
-
for (const layer of this.chainLayers(scope)) for (const [name, value] of pick(layer).entries()) merged.set(name, value);
|
|
180
|
-
return merged;
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* Attach one synchronous layer mutation to its registration context.
|
|
184
|
-
* @param ctx - context that determines both scope visibility and effect ownership.
|
|
185
|
-
* @param action - atomic mutation returning its synchronous undo.
|
|
186
|
-
* @param options - Cordis effect label and optional change notification.
|
|
187
|
-
* @returns the exact disposer returned by `ctx.effect()`.
|
|
188
|
-
*/
|
|
189
|
-
effect(ctx, action, options) {
|
|
190
|
-
const scope = scopeOf(ctx);
|
|
191
|
-
const notify = options.notify ?? true;
|
|
192
|
-
return ctx.effect(function* () {
|
|
193
|
-
let layer;
|
|
194
|
-
let created = false;
|
|
195
|
-
if (scope === void 0) layer = this.global;
|
|
196
|
-
else {
|
|
197
|
-
const existing = this.scoped.get(scope);
|
|
198
|
-
if (existing === void 0) {
|
|
199
|
-
layer = this.createLayer(scope);
|
|
200
|
-
this.scoped.set(scope, layer);
|
|
201
|
-
created = true;
|
|
202
|
-
} else layer = existing;
|
|
203
|
-
}
|
|
204
|
-
let undo;
|
|
205
|
-
try {
|
|
206
|
-
undo = action(layer);
|
|
207
|
-
} catch (error) {
|
|
208
|
-
if (scope !== void 0 && created && layer.isEmpty()) this.scoped.delete(scope);
|
|
209
|
-
throw error;
|
|
210
|
-
}
|
|
211
|
-
yield () => {
|
|
212
|
-
undo();
|
|
213
|
-
if (scope !== void 0 && layer.isEmpty()) this.scoped.delete(scope);
|
|
214
|
-
if (notify) this.onChange();
|
|
215
|
-
};
|
|
216
|
-
if (notify) this.onChange();
|
|
217
|
-
}.bind(this), options.label);
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
//#endregion
|
|
221
|
-
//#region lib/types/index.js
|
|
222
|
-
/**
|
|
223
|
-
* Scoped-context primitive: mint a Cordis context that tags registrations with
|
|
224
|
-
* an opaque identity and build routing-only event carriers for that identity.
|
|
225
|
-
*
|
|
226
|
-
* @module @hasna-internal/kai-scope
|
|
227
|
-
*/
|
|
228
|
-
/** Context tag written by {@link createScope}. */
|
|
229
|
-
const kScope = Symbol("dsh.scope");
|
|
230
|
-
/** The key associated with each carrier. Presence distinguishes an unkeyed carrier from a non-carrier. */
|
|
231
|
-
const carrierKeys = /* @__PURE__ */ new WeakMap();
|
|
232
|
-
/**
|
|
233
|
-
* The enclosing scope of each key. One relation powers both directions of
|
|
234
|
-
* scope nesting: registration views inherit DOWN the chain (a child scope
|
|
235
|
-
* sees its ancestors' layers — {@link ScopedLayers}), and event admission
|
|
236
|
-
* extends UP it (a listener tagged with an ancestor receives events dispatched
|
|
237
|
-
* to a descendant key — {@link scopeTarget}).
|
|
238
|
-
*/
|
|
239
|
-
const scopeParents = /* @__PURE__ */ new WeakMap();
|
|
240
|
-
/** Cycle-checked write shared by the bind and every rebind. */
|
|
241
|
-
function linkScopeParent(key, parent) {
|
|
242
|
-
for (let cursor = parent; cursor !== void 0; cursor = scopeParents.get(cursor)) if (cursor === key) throw new Error("dsh-scope: scope parent link would form a cycle");
|
|
243
|
-
scopeParents.set(key, parent);
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* Bind `parent` as `key`'s enclosing scope, once.
|
|
247
|
-
*
|
|
248
|
-
* A key that already has a parent throws: there is no open re-link path, so a
|
|
249
|
-
* scope's ancestry cannot be moved by anyone but the original binder, who
|
|
250
|
-
* alone receives the {@link ScopeParentBinding}. A link that would close a
|
|
251
|
-
* cycle is rejected, because every chain consumer walks parents to the root.
|
|
252
|
-
* @param key - the child scope key.
|
|
253
|
-
* @param parent - its enclosing scope key.
|
|
254
|
-
* @returns the binding that alone may re-link this key.
|
|
255
|
-
*/
|
|
256
|
-
function bindScopeParent(key, parent) {
|
|
257
|
-
if (scopeParents.has(key)) throw new Error("dsh-scope: scope key is already bound to a parent; re-linking requires the binding returned by the original bind");
|
|
258
|
-
linkScopeParent(key, parent);
|
|
259
|
-
return { rebind(next) {
|
|
260
|
-
linkScopeParent(key, next);
|
|
261
|
-
} };
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Read one key's enclosing scope.
|
|
265
|
-
* @param key - the scope key to inspect.
|
|
266
|
-
* @returns its parent key, or `undefined` for a root scope.
|
|
267
|
-
*/
|
|
268
|
-
function scopeParentOf(key) {
|
|
269
|
-
return scopeParents.get(key);
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* The chain from a key to its root ancestor.
|
|
273
|
-
* @param key - the starting key, or `undefined` for the empty chain.
|
|
274
|
-
* @returns keys nearest-first: `[key, parent, grandparent, …]`.
|
|
275
|
-
*/
|
|
276
|
-
function scopeChainOf(key) {
|
|
277
|
-
const chain = [];
|
|
278
|
-
for (let cursor = key; cursor !== void 0; cursor = scopeParents.get(cursor)) chain.push(cursor);
|
|
279
|
-
return chain;
|
|
280
|
-
}
|
|
281
|
-
/** Follow a Cordis fiber through asynchronous teardown even if its raw disposer was already claimed. */
|
|
282
|
-
async function quiesceFiber(fiber) {
|
|
283
|
-
await Promise.resolve(fiber.dispose());
|
|
284
|
-
while (fiber.inertia !== void 0) await fiber.inertia;
|
|
285
|
-
}
|
|
286
|
-
/** Shared no-op plugin used as the backing scope fiber. */
|
|
287
|
-
function scope() {}
|
|
288
|
-
/**
|
|
289
|
-
* Mint a scope under `ctx`. The scoped context inherits the minting plugin's
|
|
290
|
-
* dependency API and owns every registration made through it.
|
|
291
|
-
* @param ctx - active context whose dependency API the scope inherits.
|
|
292
|
-
* @param key - opaque identity used for listener routing.
|
|
293
|
-
* @param options - optional scope-chain placement.
|
|
294
|
-
* @returns the scoped context and exact/shared disposal boundaries.
|
|
295
|
-
*/
|
|
296
|
-
function createScope(ctx, key, options) {
|
|
297
|
-
if (options?.parent !== void 0) bindScopeParent(key, options.parent);
|
|
298
|
-
const fiber = ctx.plugin(scope);
|
|
299
|
-
const scoped = fiber.ctx.extend({ [kScope]: key });
|
|
300
|
-
let disposing;
|
|
301
|
-
return {
|
|
302
|
-
ctx: scoped,
|
|
303
|
-
rawDispose: fiber.dispose,
|
|
304
|
-
dispose: () => disposing ??= quiesceFiber(fiber)
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Read the nearest scope tag inherited by a context.
|
|
309
|
-
* @param ctx - context to inspect.
|
|
310
|
-
* @returns its scope key, or `undefined` for an unscoped context.
|
|
311
|
-
*/
|
|
312
|
-
function scopeOf(ctx) {
|
|
313
|
-
return ctx[kScope];
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Build an opaque receiver that preserves the base filter, admits untagged
|
|
317
|
-
* listeners globally, and admits tagged listeners for a matching key or any
|
|
318
|
-
* of its ancestors ({@link bindScopeParent}): a listener owned by an enclosing
|
|
319
|
-
* scope receives every descendant scope's events, which is what lets one
|
|
320
|
-
* standing composition observe each of the agents composed under it. A tag
|
|
321
|
-
* BELOW the dispatch key stays excluded — events flow up the chain, never
|
|
322
|
-
* down.
|
|
323
|
-
* @param base - subject or service whose existing Cordis filter is preserved.
|
|
324
|
-
* @param key - routed scope identity, or `undefined` for an unscoped subject.
|
|
325
|
-
* @returns a carrier whose subject remains available only through event arguments.
|
|
326
|
-
*/
|
|
327
|
-
function scopeTarget(base, key) {
|
|
328
|
-
const baseFilter = base[Context.filter];
|
|
329
|
-
const carrier = { [Context.filter](ctx) {
|
|
330
|
-
if (baseFilter !== void 0 && !baseFilter.call(base, ctx)) return false;
|
|
331
|
-
const tag = scopeOf(ctx);
|
|
332
|
-
if (tag === void 0) return true;
|
|
333
|
-
for (let cursor = key; cursor !== void 0; cursor = scopeParents.get(cursor)) if (cursor === tag) return true;
|
|
334
|
-
return false;
|
|
335
|
-
} };
|
|
336
|
-
carrierKeys.set(carrier, key);
|
|
337
|
-
return carrier;
|
|
338
|
-
}
|
|
339
|
-
/**
|
|
340
|
-
* Test whether a value is a scope carrier.
|
|
341
|
-
* @param value - dispatch receiver to inspect.
|
|
342
|
-
* @returns whether {@link scopeTarget} created it.
|
|
343
|
-
*/
|
|
344
|
-
function isScopeCarrier(value) {
|
|
345
|
-
return typeof value === "object" && value !== null && carrierKeys.has(value);
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* Read a carrier's routing key.
|
|
349
|
-
* @param value - dispatch receiver to inspect.
|
|
350
|
-
* @returns the carrier key, or `undefined` for an unkeyed/non-carrier value.
|
|
351
|
-
*/
|
|
352
|
-
function carrierKeyOf(value) {
|
|
353
|
-
if (!isScopeCarrier(value)) return void 0;
|
|
354
|
-
return carrierKeys.get(value);
|
|
355
|
-
}
|
|
356
|
-
//#endregion
|
|
357
|
-
export { AnonymousEntries, NamedEntries, ScopedLayers, bindScopeParent, carrierKeyOf, createScope, isScopeCarrier, scopeChainOf, scopeOf, scopeParentOf, scopeTarget };
|
package/lib/invariant.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { carrierKeyOf, isScopeCarrier } from "@hasna-internal/kai-scope";
|
|
2
|
-
//#region lib/types/scoped-events.generated.js
|
|
3
|
-
/**
|
|
4
|
-
* Generated scoped-event routing-subject resolvers for dsh-scope invariants.
|
|
5
|
-
* Do not edit by hand; run `pnpm run gen-scoped-events`.
|
|
6
|
-
*
|
|
7
|
-
* @module @hasna-internal/kai-scope/scoped-events.generated
|
|
8
|
-
*/
|
|
9
|
-
const scopedSubjectResolvers = Object.freeze({
|
|
10
|
-
"agent/created": (args) => args[0]["agent"],
|
|
11
|
-
"agent/disposed": (args) => args[0]["agent"],
|
|
12
|
-
"agent/error": (args) => args[0]["agent"],
|
|
13
|
-
"agent/inbox/claimed": (args) => args[0]["agent"],
|
|
14
|
-
"agent/inbox/discarded": (args) => args[0]["agent"],
|
|
15
|
-
"agent/inbox/inserted": (args) => args[0]["agent"],
|
|
16
|
-
"agent/pre-step": (args) => args[0]["agent"],
|
|
17
|
-
"agent/request": (args) => args[0]["agent"],
|
|
18
|
-
"agent/request-error": (args) => args[0]["agent"],
|
|
19
|
-
"agent/session-start": (args) => args[0]["agent"],
|
|
20
|
-
"agent/status": (args) => args[0]["agent"],
|
|
21
|
-
"agent/turn-stopping": (args) => args[0]["agent"],
|
|
22
|
-
"approval/request": (args) => args[0]["agent"],
|
|
23
|
-
"goal/changed": (args) => args[0]["agent"],
|
|
24
|
-
"session/created": null,
|
|
25
|
-
"session/disposed": null,
|
|
26
|
-
"session/event": null,
|
|
27
|
-
"session/flush": null,
|
|
28
|
-
"subagent/end": null,
|
|
29
|
-
"subagent/start": null,
|
|
30
|
-
"system-prompt/assemble": (args) => args[1]["scope"],
|
|
31
|
-
"tools/code-dispatch-log": (args) => args[0]["agent"],
|
|
32
|
-
"tools/execute": (args) => args[0]["agent"],
|
|
33
|
-
"tools/post-execute": (args) => args[0]["agent"],
|
|
34
|
-
"tools/pre-execute": (args) => args[0]["agent"],
|
|
35
|
-
"tools/result": (args) => args[0]["agent"]
|
|
36
|
-
});
|
|
37
|
-
/**
|
|
38
|
-
* Resolve the routing key named by one scoped event payload. A null
|
|
39
|
-
* resolver means the payload cannot expose its external routing key, so the
|
|
40
|
-
* invariant checks carrier presence only.
|
|
41
|
-
* @param event - runtime Cordis event name.
|
|
42
|
-
* @returns the generated subject resolver, null for presence-only,
|
|
43
|
-
* or undefined when the event is not scope-filtered.
|
|
44
|
-
*/
|
|
45
|
-
function scopedSubjectResolverFor(event) {
|
|
46
|
-
return scopedSubjectResolvers[event];
|
|
47
|
-
}
|
|
48
|
-
//#endregion
|
|
49
|
-
//#region lib/types/invariant.js
|
|
50
|
-
/** Package-owned scoped-dispatch invariants. @module @hasna-internal/kai-scope/invariant */
|
|
51
|
-
const PACKAGE_NAME = "@hasna-internal/kai-scope";
|
|
52
|
-
/** Cordis companion plugin name. */
|
|
53
|
-
const name = "scope-invariant";
|
|
54
|
-
/** Services required before the companion can register. */
|
|
55
|
-
const inject = ["invariants"];
|
|
56
|
-
/** Install the scoped-dispatch contribution into its child registration fiber. */
|
|
57
|
-
const install = (ctx, fail) => {
|
|
58
|
-
ctx.on("internal/dispatch", (_mode, eventName, args, thisArg) => {
|
|
59
|
-
const subjectOf = scopedSubjectResolverFor(eventName);
|
|
60
|
-
if (subjectOf === void 0) return;
|
|
61
|
-
if (!isScopeCarrier(thisArg)) fail(`"${eventName}" is a scope-filtered event but was dispatched without a scope carrier — pass scopeTarget(base, subject) as the dispatch thisArg (agent events: use agentEvents(ctx, agent))`);
|
|
62
|
-
if (subjectOf !== null && carrierKeyOf(thisArg) !== subjectOf(args)) fail(`"${eventName}" was dispatched with a scope carrier keyed to a DIFFERENT subject than its arguments name — the carrier key and the event's subject must be the same object (use agentEvents(ctx, agent))`);
|
|
63
|
-
}, { global: true });
|
|
64
|
-
};
|
|
65
|
-
/**
|
|
66
|
-
* Register the scope invariant companion.
|
|
67
|
-
* @param ctx - Cordis context carrying the invariant service.
|
|
68
|
-
* @returns the installed registration's disposer after setup succeeds.
|
|
69
|
-
*/
|
|
70
|
-
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
71
|
-
//#endregion
|
|
72
|
-
export { apply, inject, name };
|
package/lib/types/index.d.ts
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Scoped-context primitive: mint a Cordis context that tags registrations with
|
|
3
|
-
* an opaque identity and build routing-only event carriers for that identity.
|
|
4
|
-
*
|
|
5
|
-
* @module @hasna-internal/kai-scope
|
|
6
|
-
*/
|
|
7
|
-
import type { Context } from '@deepseek-ai/cordis';
|
|
8
|
-
export { AnonymousEntries, NamedEntries, ScopedLayers } from './store.ts';
|
|
9
|
-
export type { ScopeLayer } from './store.ts';
|
|
10
|
-
/** An opaque, identity-compared scope key. */
|
|
11
|
-
export type ScopeKey = object;
|
|
12
|
-
declare const ScopedBrand: unique symbol;
|
|
13
|
-
/**
|
|
14
|
-
* A routing-only event receiver built by {@link scopeTarget}. The type
|
|
15
|
-
* parameter records the subject type for dispatch checking; the carrier does
|
|
16
|
-
* not expose the subject's properties. Event payloads carry the real subject.
|
|
17
|
-
*/
|
|
18
|
-
export type Scoped<T extends object> = object & {
|
|
19
|
-
readonly [ScopedBrand]: T;
|
|
20
|
-
};
|
|
21
|
-
/** The privileged handle to move one scope key's parent link. */
|
|
22
|
-
export interface ScopeParentBinding {
|
|
23
|
-
/**
|
|
24
|
-
* Re-link the bound key to a different parent, with the same cycle check as
|
|
25
|
-
* the bind. Valid only while nothing produced under the old parent is
|
|
26
|
-
* retained — the blank-session recompose contract, which the holder upholds
|
|
27
|
-
* because this relation cannot see what a session logged.
|
|
28
|
-
* @param parent - the new enclosing scope key.
|
|
29
|
-
*/
|
|
30
|
-
rebind(parent: ScopeKey): void;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Bind `parent` as `key`'s enclosing scope, once.
|
|
34
|
-
*
|
|
35
|
-
* A key that already has a parent throws: there is no open re-link path, so a
|
|
36
|
-
* scope's ancestry cannot be moved by anyone but the original binder, who
|
|
37
|
-
* alone receives the {@link ScopeParentBinding}. A link that would close a
|
|
38
|
-
* cycle is rejected, because every chain consumer walks parents to the root.
|
|
39
|
-
* @param key - the child scope key.
|
|
40
|
-
* @param parent - its enclosing scope key.
|
|
41
|
-
* @returns the binding that alone may re-link this key.
|
|
42
|
-
*/
|
|
43
|
-
export declare function bindScopeParent(key: ScopeKey, parent: ScopeKey): ScopeParentBinding;
|
|
44
|
-
/**
|
|
45
|
-
* Read one key's enclosing scope.
|
|
46
|
-
* @param key - the scope key to inspect.
|
|
47
|
-
* @returns its parent key, or `undefined` for a root scope.
|
|
48
|
-
*/
|
|
49
|
-
export declare function scopeParentOf(key: ScopeKey): ScopeKey | undefined;
|
|
50
|
-
/**
|
|
51
|
-
* The chain from a key to its root ancestor.
|
|
52
|
-
* @param key - the starting key, or `undefined` for the empty chain.
|
|
53
|
-
* @returns keys nearest-first: `[key, parent, grandparent, …]`.
|
|
54
|
-
*/
|
|
55
|
-
export declare function scopeChainOf(key: ScopeKey | undefined): ScopeKey[];
|
|
56
|
-
/** A minted registration scope and its quiescent disposal boundaries. */
|
|
57
|
-
export interface Scope {
|
|
58
|
-
/** Context through which scope-owned registrations are made. */
|
|
59
|
-
ctx: Context;
|
|
60
|
-
/** Exact Cordis disposer, used when nesting this scope in an ordered composite effect. */
|
|
61
|
-
rawDispose: () => Promise<void> | void;
|
|
62
|
-
/** Dispose every scope-owned registration; racing calls await the same completion. */
|
|
63
|
-
dispose(): Promise<void>;
|
|
64
|
-
}
|
|
65
|
-
/** Options accepted by {@link createScope}. */
|
|
66
|
-
export interface CreateScopeOptions {
|
|
67
|
-
/** Enclosing scope bound via {@link bindScopeParent} before the scope is usable; the binding stays internal. */
|
|
68
|
-
parent?: ScopeKey;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Mint a scope under `ctx`. The scoped context inherits the minting plugin's
|
|
72
|
-
* dependency API and owns every registration made through it.
|
|
73
|
-
* @param ctx - active context whose dependency API the scope inherits.
|
|
74
|
-
* @param key - opaque identity used for listener routing.
|
|
75
|
-
* @param options - optional scope-chain placement.
|
|
76
|
-
* @returns the scoped context and exact/shared disposal boundaries.
|
|
77
|
-
*/
|
|
78
|
-
export declare function createScope(ctx: Context, key: ScopeKey, options?: CreateScopeOptions): Scope;
|
|
79
|
-
/**
|
|
80
|
-
* Read the nearest scope tag inherited by a context.
|
|
81
|
-
* @param ctx - context to inspect.
|
|
82
|
-
* @returns its scope key, or `undefined` for an unscoped context.
|
|
83
|
-
*/
|
|
84
|
-
export declare function scopeOf(ctx: Context): ScopeKey | undefined;
|
|
85
|
-
/**
|
|
86
|
-
* Build an opaque receiver that preserves the base filter, admits untagged
|
|
87
|
-
* listeners globally, and admits tagged listeners for a matching key or any
|
|
88
|
-
* of its ancestors ({@link bindScopeParent}): a listener owned by an enclosing
|
|
89
|
-
* scope receives every descendant scope's events, which is what lets one
|
|
90
|
-
* standing composition observe each of the agents composed under it. A tag
|
|
91
|
-
* BELOW the dispatch key stays excluded — events flow up the chain, never
|
|
92
|
-
* down.
|
|
93
|
-
* @param base - subject or service whose existing Cordis filter is preserved.
|
|
94
|
-
* @param key - routed scope identity, or `undefined` for an unscoped subject.
|
|
95
|
-
* @returns a carrier whose subject remains available only through event arguments.
|
|
96
|
-
*/
|
|
97
|
-
export declare function scopeTarget<T extends object>(base: T, key: ScopeKey | undefined): Scoped<T>;
|
|
98
|
-
/**
|
|
99
|
-
* Test whether a value is a scope carrier.
|
|
100
|
-
* @param value - dispatch receiver to inspect.
|
|
101
|
-
* @returns whether {@link scopeTarget} created it.
|
|
102
|
-
*/
|
|
103
|
-
export declare function isScopeCarrier(value: unknown): value is Scoped<object>;
|
|
104
|
-
/**
|
|
105
|
-
* Read a carrier's routing key.
|
|
106
|
-
* @param value - dispatch receiver to inspect.
|
|
107
|
-
* @returns the carrier key, or `undefined` for an unkeyed/non-carrier value.
|
|
108
|
-
*/
|
|
109
|
-
export declare function carrierKeyOf(value: unknown): ScopeKey | undefined;
|
|
110
|
-
//# sourceMappingURL=index.d.ts.map
|
package/lib/types/invariant.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/** Package-owned scoped-dispatch invariants. @module @hasna-internal/kai-scope/invariant */
|
|
2
|
-
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
-
/** Cordis companion plugin name. */
|
|
4
|
-
export declare const name = "scope-invariant";
|
|
5
|
-
/** Services required before the companion can register. */
|
|
6
|
-
export declare const inject: string[];
|
|
7
|
-
/**
|
|
8
|
-
* Register the scope invariant companion.
|
|
9
|
-
* @param ctx - Cordis context carrying the invariant service.
|
|
10
|
-
* @returns the installed registration's disposer after setup succeeds.
|
|
11
|
-
*/
|
|
12
|
-
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
13
|
-
//# sourceMappingURL=invariant.d.ts.map
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generated scoped-event routing-subject resolvers for dsh-scope invariants.
|
|
3
|
-
* Do not edit by hand; run `pnpm run gen-scoped-events`.
|
|
4
|
-
*
|
|
5
|
-
* @module @hasna-internal/kai-scope/scoped-events.generated
|
|
6
|
-
*/
|
|
7
|
-
type ScopedSubjectResolver = (args: readonly unknown[]) => unknown;
|
|
8
|
-
/**
|
|
9
|
-
* Resolve the routing key named by one scoped event payload. A null
|
|
10
|
-
* resolver means the payload cannot expose its external routing key, so the
|
|
11
|
-
* invariant checks carrier presence only.
|
|
12
|
-
* @param event - runtime Cordis event name.
|
|
13
|
-
* @returns the generated subject resolver, null for presence-only,
|
|
14
|
-
* or undefined when the event is not scope-filtered.
|
|
15
|
-
*/
|
|
16
|
-
export declare function scopedSubjectResolverFor(event: string): ScopedSubjectResolver | null | undefined;
|
|
17
|
-
export {};
|
|
18
|
-
//# sourceMappingURL=scoped-events.generated.d.ts.map
|
package/lib/types/store.d.ts
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared insertion-ordered storage and effect ownership for scope-aware registries.
|
|
3
|
-
*
|
|
4
|
-
* @module @hasna-internal/kai-scope
|
|
5
|
-
*/
|
|
6
|
-
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
-
import type { ScopeKey } from './index.ts';
|
|
8
|
-
/** One scope's aggregate contribution to a registry. */
|
|
9
|
-
export interface ScopeLayer {
|
|
10
|
-
/** Whether every table in this layer is empty. */
|
|
11
|
-
isEmpty(): boolean;
|
|
12
|
-
}
|
|
13
|
-
/** Internal common read contract for the two entry-table implementations. */
|
|
14
|
-
interface EntryValues<V> {
|
|
15
|
-
values(): IterableIterator<V>;
|
|
16
|
-
isEmpty(): boolean;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Insertion-ordered named entries with caller-owned duplicate diagnostics.
|
|
20
|
-
*
|
|
21
|
-
* Values are borrowed. Iterators are live within one nonempty table
|
|
22
|
-
* generation; draining the table detaches them from later insertions. Each
|
|
23
|
-
* successful insertion returns an idempotent undo for that exact entry.
|
|
24
|
-
*/
|
|
25
|
-
export declare class NamedEntries<V> implements EntryValues<V> {
|
|
26
|
-
private readonly duplicateError;
|
|
27
|
-
private data;
|
|
28
|
-
constructor(duplicateError: (name: string) => Error);
|
|
29
|
-
/**
|
|
30
|
-
* Insert one unique name.
|
|
31
|
-
* @param name - name unique within this table.
|
|
32
|
-
* @param value - borrowed value to retain.
|
|
33
|
-
* @returns an idempotent undo that removes only this insertion.
|
|
34
|
-
*/
|
|
35
|
-
insert(name: string, value: V): () => void;
|
|
36
|
-
/**
|
|
37
|
-
* Read one named value.
|
|
38
|
-
* @param name - name to resolve.
|
|
39
|
-
* @returns the retained value, or `undefined` when absent.
|
|
40
|
-
*/
|
|
41
|
-
get(name: string): V | undefined;
|
|
42
|
-
/**
|
|
43
|
-
* Test one name for membership.
|
|
44
|
-
* @param name - name to test.
|
|
45
|
-
* @returns whether the table contains that name.
|
|
46
|
-
*/
|
|
47
|
-
has(name: string): boolean;
|
|
48
|
-
/**
|
|
49
|
-
* Iterate live names in insertion order.
|
|
50
|
-
* @returns the native live key iterator.
|
|
51
|
-
*/
|
|
52
|
-
keys(): IterableIterator<string>;
|
|
53
|
-
/**
|
|
54
|
-
* Iterate live entries in insertion order.
|
|
55
|
-
* @returns the native live entry iterator.
|
|
56
|
-
*/
|
|
57
|
-
entries(): IterableIterator<[string, V]>;
|
|
58
|
-
/**
|
|
59
|
-
* Iterate live values in insertion order.
|
|
60
|
-
* @returns the native live value iterator.
|
|
61
|
-
*/
|
|
62
|
-
values(): IterableIterator<V>;
|
|
63
|
-
/**
|
|
64
|
-
* Test whether this table has no entries.
|
|
65
|
-
* @returns whether the table is empty.
|
|
66
|
-
*/
|
|
67
|
-
isEmpty(): boolean;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Insertion-ordered anonymous entries with independent registration identity.
|
|
71
|
-
*
|
|
72
|
-
* Equal values remain separate registrations. Values are borrowed, and
|
|
73
|
-
* iterators are live within one nonempty table generation; draining the table
|
|
74
|
-
* detaches them from later appends.
|
|
75
|
-
*/
|
|
76
|
-
export declare class AnonymousEntries<V> implements EntryValues<V> {
|
|
77
|
-
private data;
|
|
78
|
-
/**
|
|
79
|
-
* Append one independently owned value.
|
|
80
|
-
* @param value - borrowed value to retain.
|
|
81
|
-
* @returns an idempotent undo for this exact append.
|
|
82
|
-
*/
|
|
83
|
-
append(value: V): () => void;
|
|
84
|
-
/**
|
|
85
|
-
* Iterate live values in insertion order.
|
|
86
|
-
* @returns the native live value iterator.
|
|
87
|
-
*/
|
|
88
|
-
values(): IterableIterator<V>;
|
|
89
|
-
/**
|
|
90
|
-
* Test whether this table has no entries.
|
|
91
|
-
* @returns whether the table is empty.
|
|
92
|
-
*/
|
|
93
|
-
isEmpty(): boolean;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Own the global and exact-scope layers for one registry.
|
|
97
|
-
*
|
|
98
|
-
* Reads never create scoped layers. Registrations derive both visibility and
|
|
99
|
-
* effect ownership from the supplied Cordis context, collect undo before
|
|
100
|
-
* notification, and reclaim only a completely empty aggregate layer.
|
|
101
|
-
*/
|
|
102
|
-
export declare class ScopedLayers<L extends ScopeLayer> {
|
|
103
|
-
private readonly createLayer;
|
|
104
|
-
private readonly onChange;
|
|
105
|
-
/** The eagerly constructed context-global layer. */
|
|
106
|
-
readonly global: L;
|
|
107
|
-
private readonly scoped;
|
|
108
|
-
constructor(createLayer: (scope: ScopeKey | undefined) => L, onChange: () => void);
|
|
109
|
-
/**
|
|
110
|
-
* Read an existing exact-scope overlay. Deliberately chain-blind: callers
|
|
111
|
-
* addressing one scope's OWN contributions (its restrictions, its guards)
|
|
112
|
-
* must not silently pick up an ancestor's — use {@link chainLayers} where
|
|
113
|
-
* inheritance is the point.
|
|
114
|
-
* @param scope - exact scope key; `undefined` denotes no overlay.
|
|
115
|
-
* @returns the existing scoped layer, or `undefined` without creating one.
|
|
116
|
-
*/
|
|
117
|
-
peek(scope: ScopeKey | undefined): L | undefined;
|
|
118
|
-
/**
|
|
119
|
-
* Existing overlays along the scope's parent chain ({@link scopeChainOf}),
|
|
120
|
-
* farthest ancestor first and the exact scope last, so a caller layering
|
|
121
|
-
* them in order gives the nearest scope the final word.
|
|
122
|
-
* @param scope - viewing scope, or `undefined` for no overlays.
|
|
123
|
-
* @returns the existing layers, nearest last; absent overlays are skipped.
|
|
124
|
-
*/
|
|
125
|
-
chainLayers(scope: ScopeKey | undefined): L[];
|
|
126
|
-
/**
|
|
127
|
-
* Materialize global named entries followed by scope-chain shadows,
|
|
128
|
-
* farthest ancestor first, so the nearest scope's entry wins a name.
|
|
129
|
-
* @param scope - viewing scope, or `undefined` for the global view.
|
|
130
|
-
* @param pick - select the named table from a layer.
|
|
131
|
-
* @returns an insertion-ordered effective map.
|
|
132
|
-
*/
|
|
133
|
-
merge<V>(scope: ScopeKey | undefined, pick: (layer: L) => NamedEntries<V>): Map<string, V>;
|
|
134
|
-
/**
|
|
135
|
-
* Attach one synchronous layer mutation to its registration context.
|
|
136
|
-
* @param ctx - context that determines both scope visibility and effect ownership.
|
|
137
|
-
* @param action - atomic mutation returning its synchronous undo.
|
|
138
|
-
* @param options - Cordis effect label and optional change notification.
|
|
139
|
-
* @returns the exact disposer returned by `ctx.effect()`.
|
|
140
|
-
*/
|
|
141
|
-
effect(ctx: Context, action: (layer: L) => () => void, options: {
|
|
142
|
-
label: string;
|
|
143
|
-
notify?: boolean;
|
|
144
|
-
}): () => void;
|
|
145
|
-
}
|
|
146
|
-
export {};
|
|
147
|
-
//# sourceMappingURL=store.d.ts.map
|