@zhin.js/plugin-runtime 1.0.1 → 1.1.1
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 +5 -0
- package/lib/agent-tools-host.d.ts +38 -0
- package/lib/agent-tools-host.js +2 -0
- package/lib/capability.d.ts +11 -0
- package/lib/database-host.d.ts +12 -4
- package/lib/generation-store.d.ts +32 -0
- package/lib/generation-store.js +38 -0
- package/lib/inbox.d.ts +35 -0
- package/lib/inbox.js +99 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.js +12 -0
- package/lib/plugin.d.ts +6 -1
- package/lib/root-controller.d.ts +7 -0
- package/lib/root-controller.js +38 -13
- package/lib/system-log.d.ts +19 -0
- package/lib/system-log.js +37 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,11 @@ import {
|
|
|
12
12
|
} from '@zhin.js/plugin-runtime';
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
`PluginSetupContext.addFeature(feature, localName, definition)` 是约定目录之外的内存
|
|
16
|
+
Capability 写入口。它仍由上层 Runtime 交给对应 Feature provider 校验和投影;Feature
|
|
17
|
+
通过 `authoring.setupMethod` 和 module augmentation 提供强类型 `addCommand`、
|
|
18
|
+
`addComponent`、`addTool` 等别名。底座只保存通用注册记录,不理解 IM。
|
|
19
|
+
|
|
15
20
|
## 生命周期
|
|
16
21
|
|
|
17
22
|
```text
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin-contributed Agent tool registered against the Agent Host.
|
|
3
|
+
* Captured at setup() time — execute closures must never call plugin locators.
|
|
4
|
+
*
|
|
5
|
+
* Access vocabulary (platforms / scopes / permissions / hidden) mirrors the
|
|
6
|
+
* `@zhin.js/tool` ToolIndex descriptor and is enforced per message by the
|
|
7
|
+
* Agent runtime via Core `canAccessTool` — one predicate for both paths.
|
|
8
|
+
*/
|
|
9
|
+
export interface AgentToolRegistration {
|
|
10
|
+
/** Runtime tool name exposed to the model (e.g. `lottery_sync`). */
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly description: string;
|
|
13
|
+
/** zod object schema or JSON Schema; drives the deferred catalog + arg validation. */
|
|
14
|
+
readonly inputSchema?: unknown;
|
|
15
|
+
readonly keywords?: readonly string[];
|
|
16
|
+
readonly tags?: readonly string[];
|
|
17
|
+
/** Limit to these adapter platforms (e.g. `['icqq']`); empty = all. */
|
|
18
|
+
readonly platforms?: readonly string[];
|
|
19
|
+
/** Limit to these channel scopes; empty = all. */
|
|
20
|
+
readonly scopes?: readonly ('private' | 'group' | 'channel')[];
|
|
21
|
+
readonly permissions?: readonly string[];
|
|
22
|
+
/** Registered but never offered to the model (callable by name only). */
|
|
23
|
+
readonly hidden?: boolean;
|
|
24
|
+
/** Per-tool approval policy; stacks with ExecPolicy. */
|
|
25
|
+
readonly approval?: 'never' | 'always';
|
|
26
|
+
/** Plugin/tool origin label (e.g. plugin name) for diagnostics. */
|
|
27
|
+
readonly source?: string;
|
|
28
|
+
execute(input: Record<string, unknown>): unknown | Promise<unknown>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Generation-owned Agent tool registry provided by the Agent Host.
|
|
32
|
+
* Absent when AI is not installed/enabled — plugins must guard with `has()`.
|
|
33
|
+
*/
|
|
34
|
+
export interface AgentToolsHost {
|
|
35
|
+
/** Register a tool for every Agent turn of this generation; returns disposal. */
|
|
36
|
+
register(tool: AgentToolRegistration): () => void;
|
|
37
|
+
}
|
|
38
|
+
export declare const agentToolsHostToken: import("./token.js").Token<AgentToolsHost>;
|
package/lib/capability.d.ts
CHANGED
|
@@ -6,5 +6,16 @@ export interface CapabilitySlot<T = unknown> {
|
|
|
6
6
|
readonly localName: string;
|
|
7
7
|
readonly source: string;
|
|
8
8
|
readonly definition: T;
|
|
9
|
+
/** Setup-owned slots follow their Plugin Scope instead of file-slot HMR. */
|
|
10
|
+
readonly origin?: 'setup';
|
|
11
|
+
}
|
|
12
|
+
/** A capability declared imperatively while its owning Plugin is assembled. */
|
|
13
|
+
export interface SetupCapabilityRegistration<T = unknown> {
|
|
14
|
+
readonly id: CapabilityId;
|
|
15
|
+
readonly owner: PluginId;
|
|
16
|
+
readonly feature: FeatureId;
|
|
17
|
+
readonly localName: string;
|
|
18
|
+
readonly source: string;
|
|
19
|
+
readonly definition: T;
|
|
9
20
|
}
|
|
10
21
|
export declare function createCapabilitySlot<T>(input: Omit<CapabilitySlot<T>, 'id'>): Readonly<CapabilitySlot<T>>;
|
package/lib/database-host.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
export interface DatabaseHostSelection {
|
|
2
|
+
where(query: Record<string, unknown>): DatabaseHostSelection;
|
|
3
|
+
orderBy(field: string, direction?: 'ASC' | 'DESC'): DatabaseHostSelection;
|
|
4
|
+
limit(count: number): DatabaseHostSelection;
|
|
5
|
+
then<TResult1 = Record<string, unknown>[], TResult2 = never>(onfulfilled?: ((value: Record<string, unknown>[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
6
|
+
}
|
|
1
7
|
/**
|
|
2
8
|
* Minimal model surface shared by lottery / rss / group-suite memory stores
|
|
3
9
|
* and the Host-backed `@zhin.js/database` adapter.
|
|
4
10
|
*/
|
|
5
11
|
export interface DatabaseHostModel {
|
|
6
|
-
select():
|
|
7
|
-
where(query: Record<string, unknown>): Promise<Record<string, unknown>[]>;
|
|
8
|
-
then<TResult1 = Record<string, unknown>[], TResult2 = never>(onfulfilled?: ((value: Record<string, unknown>[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
9
|
-
};
|
|
12
|
+
select(...fields: string[]): DatabaseHostSelection;
|
|
10
13
|
insert(row: Record<string, unknown>): Promise<unknown>;
|
|
11
14
|
delete(): {
|
|
12
15
|
where(query: Record<string, unknown>): Promise<unknown>;
|
|
@@ -14,6 +17,11 @@ export interface DatabaseHostModel {
|
|
|
14
17
|
update(patch: Record<string, unknown>): {
|
|
15
18
|
where(query: Record<string, unknown>): Promise<unknown>;
|
|
16
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* DB 侧计数(聚合下推,避免为计数整表加载)。
|
|
22
|
+
* 可选:轻量 test/memory host 可不实现,调用方降级为 select 计数。
|
|
23
|
+
*/
|
|
24
|
+
count?(where?: Record<string, unknown>): Promise<number>;
|
|
17
25
|
}
|
|
18
26
|
export type DatabaseHostType = 'related' | 'document' | 'keyvalue';
|
|
19
27
|
export interface DatabaseHostTable {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { DisposeStack, type Dispose } from './dispose.js';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal structural view of a plugin setup context: anything owning a
|
|
4
|
+
* generation-scoped lifecycle (e.g. PluginSetupContext) can provide values.
|
|
5
|
+
*/
|
|
6
|
+
export interface GenerationStoreContext {
|
|
7
|
+
readonly lifecycle: DisposeStack;
|
|
8
|
+
}
|
|
9
|
+
export interface GenerationStore<T> {
|
|
10
|
+
/**
|
|
11
|
+
* Publish `value` as the current generation's value. The registration is
|
|
12
|
+
* removed automatically when `context.lifecycle` disposes (generation end);
|
|
13
|
+
* the returned Dispose unregisters manually and is idempotent.
|
|
14
|
+
*/
|
|
15
|
+
provide(context: GenerationStoreContext, value: T): Dispose;
|
|
16
|
+
/** Latest live value; throws a store-named error when none was provided. */
|
|
17
|
+
use(): T;
|
|
18
|
+
/** Latest live value, or `undefined` when none was provided. */
|
|
19
|
+
tryUse(): T | undefined;
|
|
20
|
+
/** Drop every registration across all generations. Mainly for tests. */
|
|
21
|
+
clear(): void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Generation-scoped replacement for module-level `let _x` singletons.
|
|
25
|
+
*
|
|
26
|
+
* Provided values form a stack: the newest live registration wins, and when a
|
|
27
|
+
* generation ends its registration is removed via `context.lifecycle`,
|
|
28
|
+
* re-exposing the previous generation's value. This structurally prevents
|
|
29
|
+
* stale references to an already disposed generation (the repeater singleton
|
|
30
|
+
* / rss `_db` class of bugs).
|
|
31
|
+
*/
|
|
32
|
+
export declare function createGenerationStore<T>(name: string): GenerationStore<T>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generation-scoped replacement for module-level `let _x` singletons.
|
|
3
|
+
*
|
|
4
|
+
* Provided values form a stack: the newest live registration wins, and when a
|
|
5
|
+
* generation ends its registration is removed via `context.lifecycle`,
|
|
6
|
+
* re-exposing the previous generation's value. This structurally prevents
|
|
7
|
+
* stale references to an already disposed generation (the repeater singleton
|
|
8
|
+
* / rss `_db` class of bugs).
|
|
9
|
+
*/
|
|
10
|
+
export function createGenerationStore(name) {
|
|
11
|
+
const registrations = [];
|
|
12
|
+
const unregister = (registration) => {
|
|
13
|
+
const index = registrations.lastIndexOf(registration);
|
|
14
|
+
if (index >= 0)
|
|
15
|
+
registrations.splice(index, 1);
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
provide(context, value) {
|
|
19
|
+
const registration = Object.freeze({ value });
|
|
20
|
+
registrations.push(registration);
|
|
21
|
+
context.lifecycle.add(() => unregister(registration));
|
|
22
|
+
return () => unregister(registration);
|
|
23
|
+
},
|
|
24
|
+
use() {
|
|
25
|
+
const latest = registrations[registrations.length - 1];
|
|
26
|
+
if (!latest) {
|
|
27
|
+
throw new Error(`Generation store "${name}" has no live value — call provide(context, value) during plugin setup`);
|
|
28
|
+
}
|
|
29
|
+
return latest.value;
|
|
30
|
+
},
|
|
31
|
+
tryUse() {
|
|
32
|
+
return registrations[registrations.length - 1]?.value;
|
|
33
|
+
},
|
|
34
|
+
clear() {
|
|
35
|
+
registrations.length = 0;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
package/lib/inbox.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { DatabaseHost } from './database-host.js';
|
|
2
|
+
/**
|
|
3
|
+
* 统一收件箱(console endpoint-detail 消息历史/请求/通知视图)表定义与写入辅助。
|
|
4
|
+
*
|
|
5
|
+
* 列结构对齐 packages/im/zhin/src/setup/register-inbox.ts(legacy 读路径 SSOT),
|
|
6
|
+
* request/notice 额外补 consumed/consumed_at 列(对齐 legacy host-api
|
|
7
|
+
* endpoint-persistence 的 console_bot_requests/notices consumed 标记方式),
|
|
8
|
+
* 供 console-rpc-extended 的 endpoint:requestConsumed/noticeConsumed 写路径使用。
|
|
9
|
+
*/
|
|
10
|
+
export declare const INBOX_TABLE_MESSAGE = "unified_inbox_message";
|
|
11
|
+
export declare const INBOX_TABLE_REQUEST = "unified_inbox_request";
|
|
12
|
+
export declare const INBOX_TABLE_NOTICE = "unified_inbox_notice";
|
|
13
|
+
export declare const INBOX_MESSAGE_DEFINITION: Record<string, unknown>;
|
|
14
|
+
export declare const INBOX_REQUEST_DEFINITION: Record<string, unknown>;
|
|
15
|
+
export declare const INBOX_NOTICE_DEFINITION: Record<string, unknown>;
|
|
16
|
+
export declare const INBOX_TABLE_DEFINITIONS: Readonly<Record<string, Record<string, unknown>>>;
|
|
17
|
+
/**
|
|
18
|
+
* 在 DatabaseHost 上注册三张收件箱表(幂等:已定义或已启动的 host 跳过)。
|
|
19
|
+
* 必须在 host.start() 之前调用(define 在 started 后会抛错)。
|
|
20
|
+
*/
|
|
21
|
+
export declare function defineInboxTables(host: Pick<DatabaseHost, 'define' | 'tables' | 'started'>): void;
|
|
22
|
+
/**
|
|
23
|
+
* 写一行收件箱记录;表未注册(或 host 未启动导致 model 缺失)时返回 false。
|
|
24
|
+
* 写失败向上抛,由调用方决定降级策略。
|
|
25
|
+
*/
|
|
26
|
+
export declare function insertInboxRow(host: Pick<DatabaseHost, 'models'>, table: string, row: Record<string, unknown>): Promise<boolean>;
|
|
27
|
+
/** Console SSE 事件 fan-out 的最小结构(@zhin.js/host-http ConsoleEventHub 满足该形状)。 */
|
|
28
|
+
export interface RuntimeEventPublisher {
|
|
29
|
+
publish(type: string, data: unknown): void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Root 级事件发布口(console hub)。basic/cli console-api-installer 提供;
|
|
33
|
+
* 适配器等插件经 CapabilityContext.use 解析后 publish endpoint:request/notice 等事件。
|
|
34
|
+
*/
|
|
35
|
+
export declare const runtimeEventPublisherToken: import("./token.js").Token<RuntimeEventPublisher>;
|
package/lib/inbox.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { createToken } from './token.js';
|
|
2
|
+
/**
|
|
3
|
+
* 统一收件箱(console endpoint-detail 消息历史/请求/通知视图)表定义与写入辅助。
|
|
4
|
+
*
|
|
5
|
+
* 列结构对齐 packages/im/zhin/src/setup/register-inbox.ts(legacy 读路径 SSOT),
|
|
6
|
+
* request/notice 额外补 consumed/consumed_at 列(对齐 legacy host-api
|
|
7
|
+
* endpoint-persistence 的 console_bot_requests/notices consumed 标记方式),
|
|
8
|
+
* 供 console-rpc-extended 的 endpoint:requestConsumed/noticeConsumed 写路径使用。
|
|
9
|
+
*/
|
|
10
|
+
export const INBOX_TABLE_MESSAGE = 'unified_inbox_message';
|
|
11
|
+
export const INBOX_TABLE_REQUEST = 'unified_inbox_request';
|
|
12
|
+
export const INBOX_TABLE_NOTICE = 'unified_inbox_notice';
|
|
13
|
+
export const INBOX_MESSAGE_DEFINITION = {
|
|
14
|
+
id: { type: 'integer', primary: true, autoIncrement: true },
|
|
15
|
+
adapter: { type: 'text', nullable: false },
|
|
16
|
+
endpoint_id: { type: 'text', nullable: false },
|
|
17
|
+
platform_message_id: { type: 'text', nullable: false },
|
|
18
|
+
channel_id: { type: 'text', nullable: false },
|
|
19
|
+
channel_type: { type: 'text', nullable: false },
|
|
20
|
+
channel_name: { type: 'text', nullable: true },
|
|
21
|
+
channel_parent_type: { type: 'text', nullable: true },
|
|
22
|
+
channel_parent_id: { type: 'text', nullable: true },
|
|
23
|
+
sender_id: { type: 'text', nullable: false },
|
|
24
|
+
sender_name: { type: 'text', nullable: true },
|
|
25
|
+
sender_payload: { type: 'text', nullable: false },
|
|
26
|
+
content: { type: 'text', nullable: false },
|
|
27
|
+
raw: { type: 'text', nullable: true },
|
|
28
|
+
created_at: { type: 'integer', nullable: false },
|
|
29
|
+
};
|
|
30
|
+
export const INBOX_REQUEST_DEFINITION = {
|
|
31
|
+
id: { type: 'integer', primary: true, autoIncrement: true },
|
|
32
|
+
adapter: { type: 'text', nullable: false },
|
|
33
|
+
endpoint_id: { type: 'text', nullable: false },
|
|
34
|
+
platform_request_id: { type: 'text', nullable: false },
|
|
35
|
+
type: { type: 'text', nullable: false },
|
|
36
|
+
scene_type: { type: 'text', nullable: true },
|
|
37
|
+
scene_id: { type: 'text', nullable: false },
|
|
38
|
+
sub_type: { type: 'text', nullable: true },
|
|
39
|
+
actor_id: { type: 'text', nullable: false },
|
|
40
|
+
actor_name: { type: 'text', nullable: true },
|
|
41
|
+
comment: { type: 'text', nullable: true },
|
|
42
|
+
created_at: { type: 'integer', nullable: false },
|
|
43
|
+
resolved: { type: 'integer', nullable: false, default: 0 },
|
|
44
|
+
resolved_at: { type: 'integer', nullable: true },
|
|
45
|
+
consumed: { type: 'integer', nullable: false, default: 0 },
|
|
46
|
+
consumed_at: { type: 'integer', nullable: true },
|
|
47
|
+
};
|
|
48
|
+
export const INBOX_NOTICE_DEFINITION = {
|
|
49
|
+
id: { type: 'integer', primary: true, autoIncrement: true },
|
|
50
|
+
adapter: { type: 'text', nullable: false },
|
|
51
|
+
endpoint_id: { type: 'text', nullable: false },
|
|
52
|
+
platform_notice_id: { type: 'text', nullable: false },
|
|
53
|
+
type: { type: 'text', nullable: false },
|
|
54
|
+
scene_type: { type: 'text', nullable: true },
|
|
55
|
+
scene_id: { type: 'text', nullable: false },
|
|
56
|
+
sub_type: { type: 'text', nullable: true },
|
|
57
|
+
actor_id: { type: 'text', nullable: true },
|
|
58
|
+
actor_name: { type: 'text', nullable: true },
|
|
59
|
+
target_id: { type: 'text', nullable: true },
|
|
60
|
+
target_name: { type: 'text', nullable: true },
|
|
61
|
+
payload: { type: 'text', nullable: false },
|
|
62
|
+
created_at: { type: 'integer', nullable: false },
|
|
63
|
+
consumed: { type: 'integer', nullable: false, default: 0 },
|
|
64
|
+
consumed_at: { type: 'integer', nullable: true },
|
|
65
|
+
};
|
|
66
|
+
export const INBOX_TABLE_DEFINITIONS = Object.freeze({
|
|
67
|
+
[INBOX_TABLE_MESSAGE]: INBOX_MESSAGE_DEFINITION,
|
|
68
|
+
[INBOX_TABLE_REQUEST]: INBOX_REQUEST_DEFINITION,
|
|
69
|
+
[INBOX_TABLE_NOTICE]: INBOX_NOTICE_DEFINITION,
|
|
70
|
+
});
|
|
71
|
+
/**
|
|
72
|
+
* 在 DatabaseHost 上注册三张收件箱表(幂等:已定义或已启动的 host 跳过)。
|
|
73
|
+
* 必须在 host.start() 之前调用(define 在 started 后会抛错)。
|
|
74
|
+
*/
|
|
75
|
+
export function defineInboxTables(host) {
|
|
76
|
+
if (host.started)
|
|
77
|
+
return;
|
|
78
|
+
const existing = new Set(host.tables());
|
|
79
|
+
for (const [name, definition] of Object.entries(INBOX_TABLE_DEFINITIONS)) {
|
|
80
|
+
if (!existing.has(name))
|
|
81
|
+
host.define(name, definition);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 写一行收件箱记录;表未注册(或 host 未启动导致 model 缺失)时返回 false。
|
|
86
|
+
* 写失败向上抛,由调用方决定降级策略。
|
|
87
|
+
*/
|
|
88
|
+
export async function insertInboxRow(host, table, row) {
|
|
89
|
+
const model = host.models.get(table);
|
|
90
|
+
if (!model)
|
|
91
|
+
return false;
|
|
92
|
+
await model.insert(row);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Root 级事件发布口(console hub)。basic/cli console-api-installer 提供;
|
|
97
|
+
* 适配器等插件经 CapabilityContext.use 解析后 publish endpoint:request/notice 等事件。
|
|
98
|
+
*/
|
|
99
|
+
export const runtimeEventPublisherToken = createToken('zhin.runtime.event-publisher', 'Runtime event publisher (console SSE hub fan-out)');
|
package/lib/index.d.ts
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
|
+
/** @internal Runtime 内部:CapabilitySlot(feature 与 projection 之间的载体)。 */
|
|
1
2
|
export * from './capability.js';
|
|
2
3
|
export * from './dispose.js';
|
|
3
4
|
export * from './identity.js';
|
|
5
|
+
export * from './generation-store.js';
|
|
4
6
|
export * from './handoff.js';
|
|
7
|
+
/** @public 用户侧创作面:`definePlugin`(`plugin.ts` 约定入口,承诺 semver)。 */
|
|
5
8
|
export * from './plugin.js';
|
|
9
|
+
/** @internal Runtime 内部:RootRuntime / RootController。 */
|
|
6
10
|
export * from './root-controller.js';
|
|
7
11
|
export * from './shared-lifetime.js';
|
|
12
|
+
/** @internal Runtime 内部:SnapshotStore / RuntimeSnapshot。 */
|
|
8
13
|
export * from './snapshot.js';
|
|
9
14
|
export * from './token.js';
|
|
15
|
+
/** @public Host token:`scheduleHostToken`(承诺 semver)。 */
|
|
10
16
|
export * from './schedule-host.js';
|
|
17
|
+
/** @public Host token:`databaseHostToken`(承诺 semver)。 */
|
|
11
18
|
export * from './database-host.js';
|
|
19
|
+
/** @public Host token:`agentToolsHostToken`(experimental)。 */
|
|
20
|
+
export * from './agent-tools-host.js';
|
|
12
21
|
export * from './html-renderer.js';
|
|
22
|
+
/** @public Host token:`outboundHostToken`(承诺 semver)。 */
|
|
13
23
|
export * from './outbound-host.js';
|
|
24
|
+
export * from './inbox.js';
|
|
25
|
+
export * from './system-log.js';
|
package/lib/index.js
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
|
+
/** @internal Runtime 内部:CapabilitySlot(feature 与 projection 之间的载体)。 */
|
|
1
2
|
export * from './capability.js';
|
|
2
3
|
export * from './dispose.js';
|
|
3
4
|
export * from './identity.js';
|
|
5
|
+
export * from './generation-store.js';
|
|
4
6
|
export * from './handoff.js';
|
|
7
|
+
/** @public 用户侧创作面:`definePlugin`(`plugin.ts` 约定入口,承诺 semver)。 */
|
|
5
8
|
export * from './plugin.js';
|
|
9
|
+
/** @internal Runtime 内部:RootRuntime / RootController。 */
|
|
6
10
|
export * from './root-controller.js';
|
|
7
11
|
export * from './shared-lifetime.js';
|
|
12
|
+
/** @internal Runtime 内部:SnapshotStore / RuntimeSnapshot。 */
|
|
8
13
|
export * from './snapshot.js';
|
|
9
14
|
export * from './token.js';
|
|
15
|
+
/** @public Host token:`scheduleHostToken`(承诺 semver)。 */
|
|
10
16
|
export * from './schedule-host.js';
|
|
17
|
+
/** @public Host token:`databaseHostToken`(承诺 semver)。 */
|
|
11
18
|
export * from './database-host.js';
|
|
19
|
+
/** @public Host token:`agentToolsHostToken`(experimental)。 */
|
|
20
|
+
export * from './agent-tools-host.js';
|
|
12
21
|
export * from './html-renderer.js';
|
|
22
|
+
/** @public Host token:`outboundHostToken`(承诺 semver)。 */
|
|
13
23
|
export * from './outbound-host.js';
|
|
24
|
+
export * from './inbox.js';
|
|
25
|
+
export * from './system-log.js';
|
package/lib/plugin.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Dispose, DisposeStack } from './dispose.js';
|
|
2
2
|
import type { GenerationHandoffRegistry } from './handoff.js';
|
|
3
|
-
import type { PluginId } from './identity.js';
|
|
3
|
+
import type { FeatureId, PluginId } from './identity.js';
|
|
4
4
|
import type { Scope, Token } from './token.js';
|
|
5
5
|
export interface PluginMetadata {
|
|
6
6
|
readonly displayName?: string;
|
|
@@ -23,6 +23,11 @@ export interface PluginSetupContext<TConfig = unknown> {
|
|
|
23
23
|
readonly resources: Scope;
|
|
24
24
|
readonly lifecycle: DisposeStack;
|
|
25
25
|
readonly handoff: GenerationHandoffRegistry;
|
|
26
|
+
/**
|
|
27
|
+
* Registers an in-memory definition through the same validation and
|
|
28
|
+
* projection transaction used by convention-discovered capabilities.
|
|
29
|
+
*/
|
|
30
|
+
addFeature<TDefinition>(feature: FeatureId | string, localName: string, definition: TDefinition): void;
|
|
26
31
|
}
|
|
27
32
|
export interface PluginDefinition<TConfig = unknown> {
|
|
28
33
|
readonly name: string;
|
package/lib/root-controller.d.ts
CHANGED
|
@@ -4,6 +4,11 @@ export type RootState = 'idle' | 'running' | 'stopping' | 'stopped' | 'failed';
|
|
|
4
4
|
export type PrepareGeneration = (current: RuntimeSnapshot) => PreparedGeneration | Promise<PreparedGeneration>;
|
|
5
5
|
export type PrepareTransaction = (current: RuntimeSnapshot) => PreparedGeneration | undefined | Promise<PreparedGeneration | undefined>;
|
|
6
6
|
export type ControlErrorHandler = (error: unknown) => void;
|
|
7
|
+
export interface GenerationCommitEvent {
|
|
8
|
+
readonly previous: RuntimeSnapshot;
|
|
9
|
+
readonly current: RuntimeSnapshot;
|
|
10
|
+
}
|
|
11
|
+
export type GenerationCommitListener = (event: GenerationCommitEvent) => void;
|
|
7
12
|
export declare class RootController {
|
|
8
13
|
#private;
|
|
9
14
|
private readonly onControlError;
|
|
@@ -11,6 +16,8 @@ export declare class RootController {
|
|
|
11
16
|
constructor(initial: SnapshotState, onControlError?: ControlErrorHandler);
|
|
12
17
|
get state(): RootState;
|
|
13
18
|
get generation(): number;
|
|
19
|
+
/** Observe successful generation commits, including the initial start. */
|
|
20
|
+
onGenerationCommit(listener: GenerationCommitListener): () => void;
|
|
14
21
|
start(prepare: PrepareGeneration): Promise<RuntimeSnapshot>;
|
|
15
22
|
transact(prepare: PrepareTransaction): Promise<RuntimeSnapshot>;
|
|
16
23
|
reload(_target: PluginId | string, prepare: PrepareTransaction): Promise<RuntimeSnapshot>;
|
package/lib/root-controller.js
CHANGED
|
@@ -4,6 +4,7 @@ export class RootController {
|
|
|
4
4
|
snapshots;
|
|
5
5
|
#state = 'idle';
|
|
6
6
|
#tail = Promise.resolve();
|
|
7
|
+
#generationCommitListeners = new Set();
|
|
7
8
|
constructor(initial, onControlError = () => undefined) {
|
|
8
9
|
this.onControlError = onControlError;
|
|
9
10
|
this.snapshots = new SnapshotStore(initial);
|
|
@@ -14,6 +15,13 @@ export class RootController {
|
|
|
14
15
|
get generation() {
|
|
15
16
|
return this.snapshots.current.generation;
|
|
16
17
|
}
|
|
18
|
+
/** Observe successful generation commits, including the initial start. */
|
|
19
|
+
onGenerationCommit(listener) {
|
|
20
|
+
this.#generationCommitListeners.add(listener);
|
|
21
|
+
return () => {
|
|
22
|
+
this.#generationCommitListeners.delete(listener);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
17
25
|
start(prepare) {
|
|
18
26
|
return this.#enqueue(async () => {
|
|
19
27
|
if (this.#state !== 'idle') {
|
|
@@ -27,11 +35,9 @@ export class RootController {
|
|
|
27
35
|
activated = true;
|
|
28
36
|
await prepared.handoff.activateNext();
|
|
29
37
|
}
|
|
30
|
-
const handoff = prepared.handoff;
|
|
31
|
-
const snapshot = this.snapshots.commit(0, prepared);
|
|
32
|
-
prepared = undefined;
|
|
33
38
|
this.#state = 'running';
|
|
34
|
-
this.#
|
|
39
|
+
const snapshot = this.#commitGeneration(0, prepared);
|
|
40
|
+
prepared = undefined;
|
|
35
41
|
return snapshot;
|
|
36
42
|
}
|
|
37
43
|
catch (error) {
|
|
@@ -59,10 +65,8 @@ export class RootController {
|
|
|
59
65
|
activated = true;
|
|
60
66
|
await prepared.handoff.activateNext();
|
|
61
67
|
}
|
|
62
|
-
const
|
|
63
|
-
const snapshot = this.snapshots.commit(previous.value.generation, prepared);
|
|
68
|
+
const snapshot = this.#commitGeneration(previous.value.generation, prepared);
|
|
64
69
|
prepared = undefined;
|
|
65
|
-
this.#openNext(handoff);
|
|
66
70
|
return snapshot;
|
|
67
71
|
}
|
|
68
72
|
catch (error) {
|
|
@@ -93,17 +97,38 @@ export class RootController {
|
|
|
93
97
|
this.#tail = result.catch(() => undefined);
|
|
94
98
|
return result;
|
|
95
99
|
}
|
|
100
|
+
#commitGeneration(expectedGeneration, prepared) {
|
|
101
|
+
const previous = this.snapshots.current;
|
|
102
|
+
const snapshot = this.snapshots.commit(expectedGeneration, prepared);
|
|
103
|
+
this.#openNext(prepared.handoff);
|
|
104
|
+
const event = Object.freeze({ previous, current: snapshot });
|
|
105
|
+
const listeners = [...this.#generationCommitListeners];
|
|
106
|
+
queueMicrotask(() => {
|
|
107
|
+
for (const listener of listeners) {
|
|
108
|
+
try {
|
|
109
|
+
listener(event);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
this.#reportControlError(error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
return snapshot;
|
|
117
|
+
}
|
|
96
118
|
#openNext(handoff) {
|
|
97
119
|
try {
|
|
98
120
|
handoff?.openNext();
|
|
99
121
|
}
|
|
100
122
|
catch (error) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
123
|
+
this.#reportControlError(error);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
#reportControlError(error) {
|
|
127
|
+
try {
|
|
128
|
+
this.onControlError(error);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// Error reporting cannot roll back an already committed generation.
|
|
107
132
|
}
|
|
108
133
|
}
|
|
109
134
|
async #rollback(prepared, state, transactionError) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DatabaseHost } from './database-host.js';
|
|
2
|
+
/**
|
|
3
|
+
* 系统日志表(console logs 页数据源)定义与写入辅助。
|
|
4
|
+
*
|
|
5
|
+
* 列结构对齐 packages/im/core/src/models/system-log.ts(legacy SystemLogDefinition,
|
|
6
|
+
* 读取端为 packages/host/http console-rest-pages registerLogsRoutes)。
|
|
7
|
+
*/
|
|
8
|
+
export declare const SYSTEM_LOG_TABLE = "SystemLog";
|
|
9
|
+
export declare const SYSTEM_LOG_DEFINITION: Record<string, unknown>;
|
|
10
|
+
/**
|
|
11
|
+
* 在 DatabaseHost 上注册系统日志表(幂等:已定义或已启动的 host 跳过)。
|
|
12
|
+
* 必须在 host.start() 之前调用(define 在 started 后会抛错)。
|
|
13
|
+
*/
|
|
14
|
+
export declare function defineSystemLogTable(host: Pick<DatabaseHost, 'define' | 'tables' | 'started'>): void;
|
|
15
|
+
/**
|
|
16
|
+
* 写一行系统日志;表未注册(或 host 未启动导致 model 缺失)时返回 false。
|
|
17
|
+
* 写失败向上抛,由调用方决定降级策略。
|
|
18
|
+
*/
|
|
19
|
+
export declare function insertSystemLogRow(host: Pick<DatabaseHost, 'models'>, row: Record<string, unknown>): Promise<boolean>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 系统日志表(console logs 页数据源)定义与写入辅助。
|
|
3
|
+
*
|
|
4
|
+
* 列结构对齐 packages/im/core/src/models/system-log.ts(legacy SystemLogDefinition,
|
|
5
|
+
* 读取端为 packages/host/http console-rest-pages registerLogsRoutes)。
|
|
6
|
+
*/
|
|
7
|
+
export const SYSTEM_LOG_TABLE = 'SystemLog';
|
|
8
|
+
export const SYSTEM_LOG_DEFINITION = {
|
|
9
|
+
id: { type: 'integer', primary: true, autoIncrement: true },
|
|
10
|
+
level: { type: 'text', nullable: false },
|
|
11
|
+
name: { type: 'text', nullable: false },
|
|
12
|
+
message: { type: 'text', nullable: false },
|
|
13
|
+
source: { type: 'text', nullable: false },
|
|
14
|
+
timestamp: { type: 'date', nullable: false },
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* 在 DatabaseHost 上注册系统日志表(幂等:已定义或已启动的 host 跳过)。
|
|
18
|
+
* 必须在 host.start() 之前调用(define 在 started 后会抛错)。
|
|
19
|
+
*/
|
|
20
|
+
export function defineSystemLogTable(host) {
|
|
21
|
+
if (host.started)
|
|
22
|
+
return;
|
|
23
|
+
if (!host.tables().includes(SYSTEM_LOG_TABLE)) {
|
|
24
|
+
host.define(SYSTEM_LOG_TABLE, SYSTEM_LOG_DEFINITION);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 写一行系统日志;表未注册(或 host 未启动导致 model 缺失)时返回 false。
|
|
29
|
+
* 写失败向上抛,由调用方决定降级策略。
|
|
30
|
+
*/
|
|
31
|
+
export async function insertSystemLogRow(host, row) {
|
|
32
|
+
const model = host.models.get(SYSTEM_LOG_TABLE);
|
|
33
|
+
if (!model)
|
|
34
|
+
return false;
|
|
35
|
+
await model.insert(row);
|
|
36
|
+
return true;
|
|
37
|
+
}
|