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 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
- ### 配置 opencode.json
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 即可。12 个代理自动注册,中文语言要求自动注入。
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
- 安装后,OpenCode 会自动:
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
 
@@ -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,MAqNlB,CAAC;AAEF,eAAe,WAAW,CAAC"}
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: "你是 CoHub 纯调度者。用中文回复。" }
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, _output) => {
832
+ "tool.execute.after": async (input2, output) => {
769
833
  try {
770
834
  if (input2.tool === "task") {
771
- tracker.updateAfterTask(input2.sessionID, "completed");
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
- * 匹配 parentSessionId 下最近创建且仍为 running 的任务
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;IAoBnE;;;OAGG;IACH,eAAe,CAAC,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAmBtF;;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,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
+ {"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"}
@@ -6,6 +6,7 @@ export interface JobRecord {
6
6
  agent: string;
7
7
  label: string;
8
8
  status: TaskStatus;
9
+ background: boolean;
9
10
  terminalReconciled: boolean;
10
11
  createdAt: number;
11
12
  }
@@ -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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode-cohub",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "OpenCode 中文智能体编排插件 CoHub - 纯调度模式 / 全中文提示词 / 规范分析代理 / 方案制定代理 / 独立运行",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",