oh-my-opencode-cohub 1.0.14 → 1.0.16
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 +68 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +98 -4
- package/dist/task-manager/tracker.d.ts +10 -1
- package/dist/task-manager/tracker.d.ts.map +1 -1
- package/dist/task-manager/types.d.ts +1 -0
- package/dist/task-manager/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,15 +16,35 @@ OpenCode 中文智能体编排插件 CoHub——纯调度模式、全中文提
|
|
|
16
16
|
- 📜 **AGENTS.md 全覆盖**:项目级和用户级 AGENTS.md 对**本插件所有代理**(内置 8 个 + 插件 4 个)均生效,无需额外配置
|
|
17
17
|
- 📖 **架构借鉴**:插件架构和代理编排理念借鉴了 oh-my-opencode-slim,但完全独立实现、无运行时依赖
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## 安装
|
|
20
20
|
|
|
21
|
-
###
|
|
21
|
+
### 前置条件
|
|
22
|
+
|
|
23
|
+
- [OpenCode](https://opencode.ai) 已安装
|
|
24
|
+
- 推荐安装 [Bun](https://bun.sh)(CLI 一键安装需要)
|
|
25
|
+
|
|
26
|
+
### 方式一:CLI 一键安装(推荐)
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
bunx oh-my-opencode-cohub install
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
CLI 会自动完成以下操作:
|
|
33
|
+
|
|
34
|
+
1. 将 `oh-my-opencode-cohub` 注册到 `~/.config/opencode/opencode.json` 的 `plugin` 数组
|
|
35
|
+
2. 将插件注册到 TUI 配置 `~/.config/opencode/tui.json`
|
|
36
|
+
3. 在 `opencode.json` 的 `agent` 字段中注册全部 12 个 `co-*` 代理
|
|
37
|
+
4. 写入默认配置文件 `~/.config/opencode/oh-my-opencode-cohub.json`
|
|
38
|
+
|
|
39
|
+
重启 OpenCode 即可生效。
|
|
40
|
+
|
|
41
|
+
### 方式二:npm 安装 + 手动配置
|
|
22
42
|
|
|
23
43
|
```bash
|
|
24
44
|
npm install oh-my-opencode-cohub
|
|
25
45
|
```
|
|
26
46
|
|
|
27
|
-
|
|
47
|
+
然后在 `~/.config/opencode/opencode.json` 中添加:
|
|
28
48
|
|
|
29
49
|
```json
|
|
30
50
|
{
|
|
@@ -32,15 +52,53 @@ npm install oh-my-opencode-cohub
|
|
|
32
52
|
}
|
|
33
53
|
```
|
|
34
54
|
|
|
35
|
-
重启 OpenCode 即可。
|
|
55
|
+
重启 OpenCode 即可。
|
|
56
|
+
|
|
57
|
+
### 卸载
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
bunx oh-my-opencode-cohub uninstall
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
CLI 会精确清理所有 `co-*` 代理和相关配置,不影响其他插件的配置数据。
|
|
36
64
|
|
|
37
|
-
|
|
65
|
+
如果使用 npm 手动安装,则需手动从 `opencode.json` 的 `plugin` 数组中移除 `"oh-my-opencode-cohub"`,然后 `npm uninstall oh-my-opencode-cohub`。
|
|
66
|
+
|
|
67
|
+
### 验证安装
|
|
68
|
+
|
|
69
|
+
安装并重启 OpenCode 后,确认以下效果:
|
|
70
|
+
|
|
71
|
+
- 对话中自动出现 **中文语言要求**(如 "你必须始终使用中文进行思考、推理和回复")
|
|
72
|
+
- 可用代理列表中出现 12 个 `co-*` 前缀代理(`co-orchestrator`、`co-oracle`、`co-librarian`、`co-explorer`、`co-designer`、`co-fixer`、`co-observer`、`co-council`、`co-rule-user`、`co-rule-project`、`co-rule-app`、`co-planner`)
|
|
73
|
+
- Orchestrator 自动进入纯调度模式(只做规划、委派、验证,不直接操作文件)
|
|
74
|
+
|
|
75
|
+
### 从源码安装(本地开发)
|
|
76
|
+
|
|
77
|
+
如果需要修改源码或使用开发版本:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# 1. 克隆项目
|
|
81
|
+
git clone <repo-url> && cd oh-my-opencode-cohub
|
|
82
|
+
|
|
83
|
+
# 2. 安装依赖
|
|
84
|
+
npm install
|
|
85
|
+
|
|
86
|
+
# 3. 构建(需要系统已安装 Bun)
|
|
87
|
+
npm run build
|
|
88
|
+
|
|
89
|
+
# 4. 方式 A:npm link 全局链接(推荐)
|
|
90
|
+
npm link
|
|
91
|
+
bunx oh-my-opencode-cohub install
|
|
92
|
+
# 重启 OpenCode 即可。之后修改源码只需重新 npm run build
|
|
93
|
+
|
|
94
|
+
# 4. 方式 B:手动配置绝对路径
|
|
95
|
+
# 编辑 ~/.config/opencode/opencode.json,在 plugin 数组中写入项目绝对路径:
|
|
96
|
+
# { "plugin": ["/absolute/path/to/oh-my-opencode-cohub"] }
|
|
97
|
+
# 然后手动运行 CLI 注册代理:
|
|
98
|
+
# node dist/cli/index.js install
|
|
99
|
+
```
|
|
38
100
|
|
|
39
|
-
|
|
40
|
-
- 加载中文代理提示词
|
|
41
|
-
- 注册 4 个插件代理:@rule-user、@rule-project、@rule-app、@planner
|
|
42
|
-
- 注入中文语言要求
|
|
43
|
-
- 所有代理(包括 @rule-user、@rule-project、@rule-app、@planner)自动继承项目级和用户级 AGENTS.md 规则
|
|
101
|
+
> **构建依赖**:`npm run build` 使用 `bun build` 和 `tsc`。系统需安装 [Bun](https://bun.sh)(构建工具)和 Node.js(类型声明生成)。所有 `@opencode-ai/*`、`@opentui/*`、`zod` 均为 external,不打包进 `dist/`。
|
|
44
102
|
|
|
45
103
|
## 代理一览
|
|
46
104
|
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAqClD,wBAAwB;AACxB,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAalD,CAAC;AAwEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,wrBAA+B,CAAC;AAEhE,QAAA,MAAM,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAqClD,wBAAwB;AACxB,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAalD,CAAC;AAwEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,wrBAA+B,CAAC;AAEhE,QAAA,MAAM,WAAW,EAAE,MAiSlB,CAAC;AAEF,eAAe,WAAW,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -443,11 +443,12 @@ class TaskTracker {
|
|
|
443
443
|
const label = typeof args.description === "string" ? args.description : alias;
|
|
444
444
|
this.jobs.set(alias, {
|
|
445
445
|
alias,
|
|
446
|
-
sessionId: "",
|
|
446
|
+
sessionId: args.task_id ?? "",
|
|
447
447
|
parentSessionId,
|
|
448
448
|
agent,
|
|
449
449
|
label,
|
|
450
450
|
status: "running",
|
|
451
|
+
background: args.background ?? false,
|
|
451
452
|
terminalReconciled: false,
|
|
452
453
|
createdAt: Date.now()
|
|
453
454
|
});
|
|
@@ -462,11 +463,24 @@ class TaskTracker {
|
|
|
462
463
|
}
|
|
463
464
|
}
|
|
464
465
|
if (latest) {
|
|
466
|
+
if (latest.background) {
|
|
467
|
+
if (sessionId)
|
|
468
|
+
latest.sessionId = sessionId;
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
465
471
|
latest.status = status;
|
|
466
472
|
if (sessionId)
|
|
467
473
|
latest.sessionId = sessionId;
|
|
468
474
|
}
|
|
469
475
|
}
|
|
476
|
+
updateByChildSessionId(sessionId, status) {
|
|
477
|
+
for (const job of this.jobs.values()) {
|
|
478
|
+
if (job.sessionId === sessionId && job.background && job.status === "running") {
|
|
479
|
+
job.status = status;
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
470
484
|
getBoardText(parentSessionId) {
|
|
471
485
|
const pid = parentSessionId || this._currentParentSessionId;
|
|
472
486
|
const activeJobs = [];
|
|
@@ -513,6 +527,14 @@ class TaskTracker {
|
|
|
513
527
|
job.terminalReconciled = true;
|
|
514
528
|
}
|
|
515
529
|
}
|
|
530
|
+
cleanupStaleJobs(timeoutMs) {
|
|
531
|
+
const now = Date.now();
|
|
532
|
+
for (const job of this.jobs.values()) {
|
|
533
|
+
if (job.background && job.status === "running" && now - job.createdAt > timeoutMs) {
|
|
534
|
+
job.status = "errored";
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
516
538
|
isReusable(alias) {
|
|
517
539
|
const job = this.jobs.get(alias);
|
|
518
540
|
return job?.status === "completed" && job.terminalReconciled;
|
|
@@ -660,7 +682,9 @@ var CoHubPlugin = async (input, options) => {
|
|
|
660
682
|
const agents = [
|
|
661
683
|
{
|
|
662
684
|
name: "co-orchestrator",
|
|
663
|
-
config: { mode: "primary", model: "deepseek/deepseek-v4-pro", variant: "max", prompt:
|
|
685
|
+
config: { mode: "primary", model: "deepseek/deepseek-v4-pro", variant: "max", prompt: ORCHESTRATOR_PROMPT + `
|
|
686
|
+
|
|
687
|
+
` + CHINESE_LANGUAGE_INSTRUCTION }
|
|
664
688
|
},
|
|
665
689
|
{
|
|
666
690
|
name: "co-oracle",
|
|
@@ -730,6 +754,46 @@ var CoHubPlugin = async (input, options) => {
|
|
|
730
754
|
}
|
|
731
755
|
}
|
|
732
756
|
syncAgentConfig();
|
|
757
|
+
function extractChildSessionId(output) {
|
|
758
|
+
if (!output || typeof output !== "object")
|
|
759
|
+
return;
|
|
760
|
+
const o = output;
|
|
761
|
+
const meta = o.metadata;
|
|
762
|
+
if (meta) {
|
|
763
|
+
if (typeof meta.sessionId === "string")
|
|
764
|
+
return meta.sessionId;
|
|
765
|
+
if (typeof meta.taskId === "string")
|
|
766
|
+
return meta.taskId;
|
|
767
|
+
if (typeof meta.task_id === "string")
|
|
768
|
+
return meta.task_id;
|
|
769
|
+
if (typeof meta.id === "string")
|
|
770
|
+
return meta.id;
|
|
771
|
+
}
|
|
772
|
+
const text = typeof o.output === "string" ? o.output : "";
|
|
773
|
+
const m = text.match(/\b(session|task)[_\s]?(?:id|ID)[:\s]+(\S+)/i);
|
|
774
|
+
if (m)
|
|
775
|
+
return m[2];
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
function extractSessionIdFromEvent(props) {
|
|
779
|
+
if (!props || typeof props !== "object")
|
|
780
|
+
return;
|
|
781
|
+
const p = props;
|
|
782
|
+
const info = p.info;
|
|
783
|
+
if (info?.id)
|
|
784
|
+
return info.id;
|
|
785
|
+
if (typeof p.sessionID === "string")
|
|
786
|
+
return p.sessionID;
|
|
787
|
+
if (typeof p.sessionId === "string")
|
|
788
|
+
return p.sessionId;
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
const STALE_TIMEOUT_MS = 30 * 60 * 1000;
|
|
792
|
+
const cleanupTimer = setInterval(() => {
|
|
793
|
+
try {
|
|
794
|
+
tracker.cleanupStaleJobs(STALE_TIMEOUT_MS);
|
|
795
|
+
} catch {}
|
|
796
|
+
}, 30000);
|
|
733
797
|
const agentConfigs = {};
|
|
734
798
|
for (const agent of agents) {
|
|
735
799
|
agentConfigs[agent.name] = {
|
|
@@ -765,14 +829,41 @@ var CoHubPlugin = async (input, options) => {
|
|
|
765
829
|
}
|
|
766
830
|
} catch {}
|
|
767
831
|
},
|
|
768
|
-
"tool.execute.after": async (input2,
|
|
832
|
+
"tool.execute.after": async (input2, output) => {
|
|
769
833
|
try {
|
|
770
834
|
if (input2.tool === "task") {
|
|
771
|
-
|
|
835
|
+
try {
|
|
836
|
+
const debugDir = path2.join(os2.homedir(), ".local", "share", "opencode", "storage", "oh-my-opencode-cohub");
|
|
837
|
+
if (!fs2.existsSync(debugDir))
|
|
838
|
+
fs2.mkdirSync(debugDir, { recursive: true });
|
|
839
|
+
fs2.writeFileSync(path2.join(debugDir, "task-output-debug.json"), JSON.stringify({
|
|
840
|
+
time: new Date().toISOString(),
|
|
841
|
+
callID: input2.callID,
|
|
842
|
+
output_keys: Object.keys(output || {}),
|
|
843
|
+
output: JSON.parse(JSON.stringify(output))
|
|
844
|
+
}, null, 2), "utf-8");
|
|
845
|
+
} catch {}
|
|
846
|
+
const childSessionId = extractChildSessionId(output);
|
|
847
|
+
tracker.updateAfterTask(input2.sessionID, "completed", childSessionId);
|
|
772
848
|
syncTrackerState(input2.sessionID ?? "");
|
|
773
849
|
}
|
|
774
850
|
} catch {}
|
|
775
851
|
},
|
|
852
|
+
event: async (input2) => {
|
|
853
|
+
try {
|
|
854
|
+
const e = input2.event;
|
|
855
|
+
const sessionId = extractSessionIdFromEvent(e.properties);
|
|
856
|
+
if (!sessionId)
|
|
857
|
+
return;
|
|
858
|
+
if (e.type === "session.idle") {
|
|
859
|
+
tracker.updateByChildSessionId(sessionId, "completed");
|
|
860
|
+
syncTrackerState(tracker.currentParentSessionId);
|
|
861
|
+
} else if (e.type === "session.deleted" || e.type === "session.error") {
|
|
862
|
+
tracker.updateByChildSessionId(sessionId, "errored");
|
|
863
|
+
syncTrackerState(tracker.currentParentSessionId);
|
|
864
|
+
}
|
|
865
|
+
} catch {}
|
|
866
|
+
},
|
|
776
867
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
777
868
|
try {
|
|
778
869
|
const board = tracker.getBoardText();
|
|
@@ -797,6 +888,9 @@ var CoHubPlugin = async (input, options) => {
|
|
|
797
888
|
},
|
|
798
889
|
"experimental.chat.system.transform": async (_input, output) => {
|
|
799
890
|
output.system.push(CHINESE_LANGUAGE_INSTRUCTION);
|
|
891
|
+
},
|
|
892
|
+
dispose: async () => {
|
|
893
|
+
clearInterval(cleanupTimer);
|
|
800
894
|
}
|
|
801
895
|
};
|
|
802
896
|
};
|
|
@@ -13,9 +13,14 @@ export declare class TaskTracker {
|
|
|
13
13
|
registerBeforeTask(parentSessionId: string, args: TaskArgs): string;
|
|
14
14
|
/**
|
|
15
15
|
* 在 tool.execute.after 中调用,更新任务完成状态
|
|
16
|
-
*
|
|
16
|
+
* 非背景任务:立即标 completed;背景任务:不更新(等 session.idle 事件)
|
|
17
17
|
*/
|
|
18
18
|
updateAfterTask(parentSessionId: string, status: TaskStatus, sessionId?: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* 根据子 session ID 更新任务状态(供 event hook 使用)
|
|
21
|
+
* 当背景任务的 session 变为 idle 时调用
|
|
22
|
+
*/
|
|
23
|
+
updateByChildSessionId(sessionId: string, status: TaskStatus): void;
|
|
19
24
|
/**
|
|
20
25
|
* 生成 Background Job Board 文本
|
|
21
26
|
*/
|
|
@@ -24,6 +29,10 @@ export declare class TaskTracker {
|
|
|
24
29
|
* 标记任务已调和(不再显示在 Unreconciled 中)
|
|
25
30
|
*/
|
|
26
31
|
reconcileJob(alias: string): void;
|
|
32
|
+
/**
|
|
33
|
+
* 清理超时的背景任务(超过 timeoutMs 仍 running 的标为 errored)
|
|
34
|
+
*/
|
|
35
|
+
cleanupStaleJobs(timeoutMs: number): void;
|
|
27
36
|
/**
|
|
28
37
|
* 检查任务是否可复用
|
|
29
38
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../../src/task-manager/tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE/D,qBAAa,WAAW;IACtB,OAAO,CAAC,IAAI,CAAgC;IAC5C,OAAO,CAAC,QAAQ,CAA6B;IAC7C,yCAAyC;IACzC,OAAO,CAAC,uBAAuB,CAAM;IAErC,IAAI,sBAAsB,IAAI,MAAM,CAEnC;IAED,mDAAmD;IACnD,OAAO,CAAC,KAAK;IAOb;;OAEG;IACH,kBAAkB,CAAC,eAAe,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,MAAM;
|
|
1
|
+
{"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../../src/task-manager/tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE/D,qBAAa,WAAW;IACtB,OAAO,CAAC,IAAI,CAAgC;IAC5C,OAAO,CAAC,QAAQ,CAA6B;IAC7C,yCAAyC;IACzC,OAAO,CAAC,uBAAuB,CAAM;IAErC,IAAI,sBAAsB,IAAI,MAAM,CAEnC;IAED,mDAAmD;IACnD,OAAO,CAAC,KAAK;IAOb;;OAEG;IACH,kBAAkB,CAAC,eAAe,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,MAAM;IAqBnE;;;OAGG;IACH,eAAe,CAAC,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAwBtF;;;OAGG;IACH,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI;IASnE;;OAEG;IACH,YAAY,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IA4CrD;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAOjC;;OAEG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IASzC;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAKlC,2BAA2B;IAC3B,gBAAgB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,EAAE;IAUnD,uBAAuB;IACvB,eAAe,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM;CASjD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/task-manager/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,WAAW,CAAC;AAE3E,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/task-manager/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,WAAW,CAAC;AAE3E,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB"}
|