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 +5 -0
- package/README.md +1 -1
- package/dist/bundle/bin.cjs +87 -41
- package/dist/packages/core/database/src/plugin.js +32 -0
- package/dist/packages/knowledge/skills/src/skill-cmds.js +38 -5
- package/docs/plugin-authoring.md +19 -0
- package/llms.txt +1 -1
- package/package.json +2 -2
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
|
|
package/dist/bundle/bin.cjs
CHANGED
|
@@ -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
|
|
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/ →
|
|
191
|
-
//
|
|
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
|
|
195
|
-
|
|
196
|
-
|
|
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)) {
|
package/docs/plugin-authoring.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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": {
|