ygl-trees 0.76.0 → 0.76.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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.76.1(2026-10-17)--ai 多 Agent 支持 + ctx.emit 桥接
4
+ - **skill link --ai <agent>**:一键连接到多种 AI Agent 的技能目录——pi(~/.pi/agent/skills/) · claude · cursor · windsurf · custom(--to)
5
+ - **ctx.emit 桥接**:eventbus 内部事件自动转发到 Cordis ctx.emit——插件可用 ctx.on("ygl") 监听(文档承诺兑现)
6
+ - **plugin-authoring.md**:补 ctx.on 事件监听用法
7
+
3
8
  ## 0.76.0(2026-10-17)Cordis 深化——生命周期+事件驱动(A2A 任务 fedc7ab9)
4
9
  - **生命周期真实化**:DatabaseService 补 stop() 钩子(关 SQLite + 释放 .ygllock + 关 ORM);bin.ts finally 走 Service.stop() 链(fallback DB 仍手动收口);3 项单测证明 boot→stop 后资源释放
5
10
  - **事件驱动真实化**:新建 eventbus(EventEmitter 单例);logEvent 落表后同步 emitEvent(node.unlock/kb.add/claim.resolve 等);webui SSE 升级为事件驱动即时推送(1.5s 轮询保留为兜底)
package/README.md CHANGED
@@ -163,7 +163,7 @@ agent brief 状态 + 建议动作 + 健康度(脏索引、存量
163
163
 
164
164
  `skill import <SKILL.md>` 直接解析 frontmatter。
165
165
 
166
- `skill link [--global]` 一键软连接到 `.agents/skills/`(DSH/Claude 等 Agent 的技能发现路径即刻可用)。
166
+ `skill link [--global] [--ai pi|claude|cursor|windsurf]` 一键软连接到 `.agents/skills/`(DSH/Claude 等 Agent 的技能发现路径即刻可用)。
167
167
 
168
168
  ### rel:关联织物(把研究资产挂到树节点)
169
169
 
@@ -12,11 +12,11 @@ let node_crypto = require("node:crypto");
12
12
  let _deepseek_ai_cordis = require("@deepseek-ai/cordis");
13
13
  require("reflect-metadata");
14
14
  let typeorm = require("typeorm");
15
+ let node_events = require("node:events");
15
16
  let _deepseek_ai_cordis_plugin_timer = require("@deepseek-ai/cordis-plugin-timer");
16
17
  _deepseek_ai_cordis_plugin_timer = require_rolldown_runtime.__toESM(_deepseek_ai_cordis_plugin_timer);
17
18
  let node_child_process = require("node:child_process");
18
19
  let _deepseek_ai_dsh_atomic_write = require("@deepseek-ai/dsh-atomic-write");
19
- let node_events = require("node:events");
20
20
  let _deepseek_ai_schemastery = require("@deepseek-ai/schemastery");
21
21
  _deepseek_ai_schemastery = require_rolldown_runtime.__toESM(_deepseek_ai_schemastery);
22
22
  //#region packages/core/text/src/text.ts
@@ -1477,6 +1477,45 @@ var init_orm = require_rolldown_runtime.__esmMin((() => {
1477
1477
  cached = null;
1478
1478
  }));
1479
1479
  //#endregion
1480
+ //#region packages/core/text/src/eventbus.ts
1481
+ /**
1482
+ * Cross-domain event bus — connects logEvent writes to real-time consumers.
1483
+ *
1484
+ * Architecture: a process-wide EventEmitter singleton. Write services call
1485
+ * `emitEvent()` after logEvent persists to the events table; consumers
1486
+ * (webui SSE, user plugins, future watchers) subscribe with `onEvent()`.
1487
+ *
1488
+ * This makes "ctx.on reactivity" real without requiring a full Cordis
1489
+ * event system — a lightweight bridge that the webui and plugins can use.
1490
+ *
1491
+ * @module @ygl/core-text/eventbus
1492
+ */
1493
+ var eventbus_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
1494
+ emitEvent: () => emitEvent,
1495
+ onEvent: () => onEvent,
1496
+ onEventKind: () => onEventKind
1497
+ });
1498
+ /** Publish a cross-domain event (called after events table write). */
1499
+ function emitEvent(evt) {
1500
+ bus.emit("ygl", evt);
1501
+ bus.emit(`ygl:${evt.kind}`, evt);
1502
+ }
1503
+ /** Subscribe to all events. Returns an unsubscribe function. */
1504
+ function onEvent(handler) {
1505
+ bus.on("ygl", handler);
1506
+ return () => bus.off("ygl", handler);
1507
+ }
1508
+ /** Subscribe to a specific kind (e.g. "node.unlock"). Returns unsubscribe. */
1509
+ function onEventKind(kind, handler) {
1510
+ bus.on(`ygl:${kind}`, handler);
1511
+ return () => bus.off(`ygl:${kind}`, handler);
1512
+ }
1513
+ var bus;
1514
+ var init_eventbus = require_rolldown_runtime.__esmMin((() => {
1515
+ bus = new node_events.EventEmitter();
1516
+ bus.setMaxListeners(50);
1517
+ }));
1518
+ //#endregion
1480
1519
  //#region packages/core/database/src/plugin.ts
1481
1520
  /**
1482
1521
  * DatabasePlugin — provides ctx.database (DatabaseService).
@@ -1509,6 +1548,36 @@ var DatabaseService = class extends _deepseek_ai_cordis.Service {
1509
1548
  };
1510
1549
  function apply$3(ctx, config) {
1511
1550
  new DatabaseService(ctx, config.file);
1551
+ try {
1552
+ const { onEvent, onEventKind } = (init_eventbus(), require_rolldown_runtime.__toCommonJS(eventbus_exports));
1553
+ const kindSubs = [onEvent((evt) => {
1554
+ try {
1555
+ ctx.emit("ygl", evt);
1556
+ } catch {}
1557
+ })];
1558
+ for (const kind of [
1559
+ "node.unlock",
1560
+ "node.lock",
1561
+ "node.add",
1562
+ "node.link",
1563
+ "kb.add",
1564
+ "kb.rm",
1565
+ "claim.resolve",
1566
+ "claim.add",
1567
+ "task.status",
1568
+ "project.new",
1569
+ "skill.add"
1570
+ ]) kindSubs.push(onEventKind(kind, (evt) => {
1571
+ try {
1572
+ ctx.emit(`ygl:${kind}`, evt);
1573
+ } catch {}
1574
+ }));
1575
+ ctx.fiber?.collect?.(() => {
1576
+ for (const u of kindSubs) try {
1577
+ u();
1578
+ } catch {}
1579
+ });
1580
+ } catch {}
1512
1581
  }
1513
1582
  //#endregion
1514
1583
  //#region packages/web/app/src/index.ts
@@ -3989,45 +4058,6 @@ function preflightTypeCheck(db) {
3989
4058
  return issues;
3990
4059
  }
3991
4060
  //#endregion
3992
- //#region packages/core/text/src/eventbus.ts
3993
- /**
3994
- * Cross-domain event bus — connects logEvent writes to real-time consumers.
3995
- *
3996
- * Architecture: a process-wide EventEmitter singleton. Write services call
3997
- * `emitEvent()` after logEvent persists to the events table; consumers
3998
- * (webui SSE, user plugins, future watchers) subscribe with `onEvent()`.
3999
- *
4000
- * This makes "ctx.on reactivity" real without requiring a full Cordis
4001
- * event system — a lightweight bridge that the webui and plugins can use.
4002
- *
4003
- * @module @ygl/core-text/eventbus
4004
- */
4005
- var eventbus_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
4006
- emitEvent: () => emitEvent,
4007
- onEvent: () => onEvent,
4008
- onEventKind: () => onEventKind
4009
- });
4010
- /** Publish a cross-domain event (called after events table write). */
4011
- function emitEvent(evt) {
4012
- bus.emit("ygl", evt);
4013
- bus.emit(`ygl:${evt.kind}`, evt);
4014
- }
4015
- /** Subscribe to all events. Returns an unsubscribe function. */
4016
- function onEvent(handler) {
4017
- bus.on("ygl", handler);
4018
- return () => bus.off("ygl", handler);
4019
- }
4020
- /** Subscribe to a specific kind (e.g. "node.unlock"). Returns unsubscribe. */
4021
- function onEventKind(kind, handler) {
4022
- bus.on(`ygl:${kind}`, handler);
4023
- return () => bus.off(`ygl:${kind}`, handler);
4024
- }
4025
- var bus;
4026
- var init_eventbus = require_rolldown_runtime.__esmMin((() => {
4027
- bus = new node_events.EventEmitter();
4028
- bus.setMaxListeners(50);
4029
- }));
4030
- //#endregion
4031
4061
  //#region packages/research/pipeline/src/writes.ts
4032
4062
  var writes_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
4033
4063
  artifactAdd: () => artifactAdd,
@@ -8825,7 +8855,23 @@ function run$4(env, action, rest, args) {
8825
8855
  return 0;
8826
8856
  case "link": {
8827
8857
  const os = require("node:os");
8828
- const targetDir = args?.flags?.global === true ? node_path.join(os.homedir(), ".agents", "skills") : node_path.join(process.cwd(), ".agents", "skills");
8858
+ const globalFlag = args?.flags?.global === true;
8859
+ const aiName = String(args?.flags?.ai || "").toLowerCase();
8860
+ const customTo = String(args?.flags?.to || "");
8861
+ const AI_SKILL_DIRS = {
8862
+ pi: () => node_path.join(os.homedir(), ".pi", "agent", "skills"),
8863
+ claude: () => node_path.join(os.homedir(), ".claude", "skills"),
8864
+ cursor: () => node_path.join(os.homedir(), ".cursor", "skills"),
8865
+ windsurf: () => node_path.join(os.homedir(), ".windsurf", "skills")
8866
+ };
8867
+ let targetDir;
8868
+ if (customTo) targetDir = node_path.resolve(customTo);
8869
+ else if (aiName && AI_SKILL_DIRS[aiName]) targetDir = AI_SKILL_DIRS[aiName]();
8870
+ else if (aiName && aiName !== "custom") {
8871
+ bad(ctx, `unknown AI agent: ${aiName}`);
8872
+ out(ctx, ` supported: ${Object.keys(AI_SKILL_DIRS).join(", ")} | custom (with --to <path>)`);
8873
+ return 2;
8874
+ } else targetDir = globalFlag ? node_path.join(os.homedir(), ".agents", "skills") : node_path.join(process.cwd(), ".agents", "skills");
8829
8875
  let skillsDir = null;
8830
8876
  for (let dir = process.cwd(), i = 0; i < 6; i++, dir = node_path.dirname(dir)) {
8831
8877
  const candidate = node_path.join(dir, "skills");
@@ -49,4 +49,36 @@ class DatabaseService extends cordis_1.Service {
49
49
  exports.DatabaseService = DatabaseService;
50
50
  function apply(ctx, config) {
51
51
  new DatabaseService(ctx, config.file);
52
+ // 0.76 bridge: eventbus (internal) → ctx.emit (Cordis public API).
53
+ // This makes "ctx.on()/emit()" work as documented — plugins can listen
54
+ // with ctx.on("ygl", handler) or ctx.on("ygl:node.unlock", handler).
55
+ // The eventbus remains the internal write-path transport.
56
+ try {
57
+ const { onEvent, onEventKind } = require("../../../core/text/src/eventbus");
58
+ const unsub = onEvent((evt) => { try {
59
+ ctx.emit("ygl", evt);
60
+ }
61
+ catch { /* */ } });
62
+ // Also bridge kind-specific channels
63
+ const kindSubs = [unsub];
64
+ const knownKinds = ["node.unlock", "node.lock", "node.add", "node.link",
65
+ "kb.add", "kb.rm", "claim.resolve", "claim.add", "task.status",
66
+ "project.new", "skill.add"];
67
+ for (const kind of knownKinds) {
68
+ kindSubs.push(onEventKind(kind, (evt) => { try {
69
+ ctx.emit(`ygl:${kind}`, evt);
70
+ }
71
+ catch { /* */ } }));
72
+ }
73
+ // Cleanup on Context dispose (best-effort — this fork has no ctx.dispose)
74
+ ctx.fiber?.collect?.(() => {
75
+ for (const u of kindSubs) {
76
+ try {
77
+ u();
78
+ }
79
+ catch { /* */ }
80
+ }
81
+ });
82
+ }
83
+ catch { /* eventbus optional */ }
52
84
  }
@@ -187,13 +187,46 @@ function run(env, action, rest, args) {
187
187
  ]);
188
188
  return 0;
189
189
  case "link": {
190
- // One-command symlink: skills/ → .agents/skills/ (or ~/.agents/skills/ with --global)
191
- // so host agents (DSH, Claude, etc.) can discover them without manual junction setup.
190
+ // One-command symlink: skills/ → AI agent's skill directory.
191
+ // Supported targets:
192
+ // (default) ./.agents/skills/ (project-level, DSH/Claude)
193
+ // --global ~/.agents/skills/ (user-level, DSH/Claude)
194
+ // --ai pi ~/.pi/agent/skills/ (Pi agent)
195
+ // --ai claude ~/.claude/skills/ (Claude Code)
196
+ // --ai cursor ~/.cursor/skills/ (Cursor IDE)
197
+ // --ai custom --to <path> (any directory)
192
198
  const os = require("node:os");
193
199
  const globalFlag = args?.flags?.global === true;
194
- const targetDir = globalFlag
195
- ? path.join(os.homedir(), ".agents", "skills")
196
- : path.join(process.cwd(), ".agents", "skills");
200
+ const aiName = String(args?.flags?.ai || "").toLowerCase();
201
+ const customTo = String(args?.flags?.to || "");
202
+ // AI agent skill directory registry
203
+ const AI_SKILL_DIRS = {
204
+ pi: () => path.join(os.homedir(), ".pi", "agent", "skills"),
205
+ claude: () => path.join(os.homedir(), ".claude", "skills"),
206
+ cursor: () => path.join(os.homedir(), ".cursor", "skills"),
207
+ windsurf: () => path.join(os.homedir(), ".windsurf", "skills"),
208
+ };
209
+ let targetDir;
210
+ if (customTo) {
211
+ // --ai custom --to <path>
212
+ targetDir = path.resolve(customTo);
213
+ }
214
+ else if (aiName && AI_SKILL_DIRS[aiName]) {
215
+ // --ai <known-agent>
216
+ targetDir = AI_SKILL_DIRS[aiName]();
217
+ }
218
+ else if (aiName && aiName !== "custom") {
219
+ // --ai <unknown> → error with suggestions
220
+ (0, fmt_1.bad)(ctx, `unknown AI agent: ${aiName}`);
221
+ (0, fmt_1.out)(ctx, ` supported: ${Object.keys(AI_SKILL_DIRS).join(", ")} | custom (with --to <path>)`);
222
+ return 2;
223
+ }
224
+ else {
225
+ // default or --global
226
+ targetDir = globalFlag
227
+ ? path.join(os.homedir(), ".agents", "skills")
228
+ : path.join(process.cwd(), ".agents", "skills");
229
+ }
197
230
  // Find source skills directory (probe upward from cwd, same as repoRoot)
198
231
  let skillsDir = null;
199
232
  for (let dir = process.cwd(), i = 0; i < 6; i++, dir = path.dirname(dir)) {
@@ -93,3 +93,22 @@ repo-root/
93
93
  | `plugin list` 显示 ✗ 不可解析 | `plugin remove <name>` 后重装;或 `doctor` 看 plugin_profile 项 |
94
94
  | 启动报 "N plugin issue(s) during startup" | 读终端给出的 report 路径(`$YGL_HOME/logs/startup-*.log`) |
95
95
  | 装了命令组不生效 | 命令组在**下次进程启动**时注册(CLI 每命令独立进程,自然生效) |
96
+
97
+
98
+ ## 事件监听(0.76+)
99
+
100
+ 插件可以通过 Cordis 标准 `ctx.on` 监听跨域事件:
101
+
102
+ ```typescript
103
+ // 监听全部事件
104
+ ctx.on("ygl", (evt) => {
105
+ console.log(evt.kind, evt.entityId); // e.g. "node.unlock" "sn123"
106
+ });
107
+
108
+ // 监听特定类型
109
+ ctx.on("ygl:node.unlock", (evt) => { /* ... */ });
110
+ ctx.on("ygl:kb.add", (evt) => { /* ... */ });
111
+ ctx.on("ygl:claim.resolve", (evt) => { /* ... */ });
112
+ ```
113
+
114
+ 事件在 logEvent 落表后即时发出(事件驱动 SSE 同通道)。支持的事件类型见 `packages/core/text/src/eventbus.ts`。
package/llms.txt CHANGED
@@ -1,4 +1,4 @@
1
- # ygl-trees v0.74.0 — AI Agent Research Workbench
1
+ # ygl-trees v0.76.1 — AI Agent Research Workbench
2
2
 
3
3
  > Single SQLite source of truth. Research → Production → Knowledge Reuse + Tech Tree.
4
4
  > CLI for AI agents: every command supports --json (machine-readable, including errors).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ygl-trees",
3
- "version": "0.76.0",
3
+ "version": "0.76.1",
4
4
  "description": "猿光 · 科技树 — database-centric CLI for research → production → knowledge reuse, built for AI agents.",
5
5
  "type": "commonjs",
6
6
  "main": "dist/apps/cli/index.js",
@@ -26,8 +26,8 @@
26
26
  "reflect-metadata": "^0.2.2",
27
27
  "typeorm": "^1.1.2",
28
28
  "@deepseek-ai/cordis-plugin-timer": "~1.1.7-alpha.2",
29
- "@deepseek-ai/cordis": "~4.0.5-alpha.2",
30
29
  "@deepseek-ai/cosmokit": "~1.8.6-alpha.2",
30
+ "@deepseek-ai/cordis": "~4.0.5-alpha.2",
31
31
  "@deepseek-ai/schemastery": "~3.18.5-alpha.2"
32
32
  },
33
33
  "devDependencies": {