oh-my-opencode-cohub 1.0.15 → 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
@@ -72,6 +72,34 @@ CLI 会精确清理所有 `co-*` 代理和相关配置,不影响其他插件
72
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
73
  - Orchestrator 自动进入纯调度模式(只做规划、委派、验证,不直接操作文件)
74
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
+ ```
100
+
101
+ > **构建依赖**:`npm run build` 使用 `bun build` 和 `tsc`。系统需安装 [Bun](https://bun.sh)(构建工具)和 Node.js(类型声明生成)。所有 `@opencode-ai/*`、`@opentui/*`、`zod` 均为 external,不打包进 `dist/`。
102
+
75
103
  ## 代理一览
76
104
 
77
105
  ### 代理(12 个,全中文提示词)
@@ -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;
@@ -732,6 +754,46 @@ var CoHubPlugin = async (input, options) => {
732
754
  }
733
755
  }
734
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);
735
797
  const agentConfigs = {};
736
798
  for (const agent of agents) {
737
799
  agentConfigs[agent.name] = {
@@ -767,14 +829,41 @@ var CoHubPlugin = async (input, options) => {
767
829
  }
768
830
  } catch {}
769
831
  },
770
- "tool.execute.after": async (input2, _output) => {
832
+ "tool.execute.after": async (input2, output) => {
771
833
  try {
772
834
  if (input2.tool === "task") {
773
- 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);
774
848
  syncTrackerState(input2.sessionID ?? "");
775
849
  }
776
850
  } catch {}
777
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
+ },
778
867
  "experimental.chat.messages.transform": async (_input, output) => {
779
868
  try {
780
869
  const board = tracker.getBoardText();
@@ -799,6 +888,9 @@ var CoHubPlugin = async (input, options) => {
799
888
  },
800
889
  "experimental.chat.system.transform": async (_input, output) => {
801
890
  output.system.push(CHINESE_LANGUAGE_INSTRUCTION);
891
+ },
892
+ dispose: async () => {
893
+ clearInterval(cleanupTimer);
802
894
  }
803
895
  };
804
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.15",
3
+ "version": "1.0.16",
4
4
  "description": "OpenCode 中文智能体编排插件 CoHub - 纯调度模式 / 全中文提示词 / 规范分析代理 / 方案制定代理 / 独立运行",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",