@zhin.js/plugin-runtime 1.0.0 → 1.1.0

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,29 @@
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
+ export interface AgentToolRegistration {
6
+ /** Runtime tool name exposed to the model (e.g. `lottery_sync`). */
7
+ readonly name: string;
8
+ readonly description: string;
9
+ /** zod object schema or JSON Schema; drives the deferred catalog + arg validation. */
10
+ readonly inputSchema?: unknown;
11
+ readonly keywords?: readonly string[];
12
+ readonly tags?: readonly string[];
13
+ readonly permissions?: readonly string[];
14
+ readonly hidden?: boolean;
15
+ /** Per-tool approval policy; stacks with ExecPolicy. */
16
+ readonly approval?: 'never' | 'always';
17
+ /** Plugin/tool origin label (e.g. plugin name) for diagnostics. */
18
+ readonly source?: string;
19
+ execute(input: Record<string, unknown>): unknown | Promise<unknown>;
20
+ }
21
+ /**
22
+ * Generation-owned Agent tool registry provided by the Agent Host.
23
+ * Absent when AI is not installed/enabled — plugins must guard with `has()`.
24
+ */
25
+ export interface AgentToolsHost {
26
+ /** Register a tool for every Agent turn of this generation; returns disposal. */
27
+ register(tool: AgentToolRegistration): () => void;
28
+ }
29
+ export declare const agentToolsHostToken: import("./token.js").Token<AgentToolsHost>;
@@ -0,0 +1,2 @@
1
+ import { createToken } from './token.js';
2
+ export const agentToolsHostToken = createToken('zhin.agent-tools.host', 'Plugin Runtime agent tools host');
@@ -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>;
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
@@ -9,5 +9,8 @@ export * from './snapshot.js';
9
9
  export * from './token.js';
10
10
  export * from './schedule-host.js';
11
11
  export * from './database-host.js';
12
+ export * from './agent-tools-host.js';
12
13
  export * from './html-renderer.js';
13
14
  export * from './outbound-host.js';
15
+ export * from './inbox.js';
16
+ export * from './system-log.js';
package/lib/index.js CHANGED
@@ -9,5 +9,8 @@ export * from './snapshot.js';
9
9
  export * from './token.js';
10
10
  export * from './schedule-host.js';
11
11
  export * from './database-host.js';
12
+ export * from './agent-tools-host.js';
12
13
  export * from './html-renderer.js';
13
14
  export * from './outbound-host.js';
15
+ export * from './inbox.js';
16
+ export * from './system-log.js';
@@ -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>;
@@ -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.#openNext(handoff);
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 handoff = prepared.handoff;
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
- try {
102
- this.onControlError(error);
103
- }
104
- catch {
105
- // Error reporting cannot roll back an already committed generation.
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/plugin-runtime",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Zero-dependency Plugin tree, Scope and atomic generation runtime for Zhin",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",