oh-my-opencode-cohub 1.6.0 → 1.7.0

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/dist/cli/index.js CHANGED
@@ -206,7 +206,7 @@ function registerCoHubAgents() {
206
206
  → 没有 → **立即停下来,先调用 request_plan_approval**
207
207
  → 新用户消息已撤销批准?→ **重新展示方案并再次调用 request_plan_approval**
208
208
  </自检清单>
209
- `, model: "deepseek/deepseek-v4-pro", variant: "max" },
209
+ `, model: "deepseek/deepseek-v4-pro", variant: "max", permission: { "plan-execute": "ask" } },
210
210
  "co-oracle": { description: "战略顾问", mode: "subagent", prompt: `你是 Oracle——战略技术顾问和代码审查者。
211
211
 
212
212
  **角色**: 高智商调试、架构决策、代码审查、简化、工程指导。
@@ -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;AAqIlD,QAAA,MAAM,WAAW,EAAE,MAsblB,CAAC;AAEF,eAAO,MAAM,MAAM,QAAc,CAAC;AAClC,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;AAsIlD,QAAA,MAAM,WAAW,EAAE,MAkelB,CAAC;AAEF,eAAO,MAAM,MAAM,QAAc,CAAC;AAClC,eAAe,WAAW,CAAC"}
package/dist/index.js CHANGED
@@ -673,6 +673,13 @@ import { tool } from "@opencode-ai/plugin";
673
673
 
674
674
  class PlanApprovalManager {
675
675
  sessions = new Map;
676
+ audit;
677
+ constructor(audit) {
678
+ this.audit = audit;
679
+ }
680
+ getGeneration(sessionID) {
681
+ return this.sessions.get(sessionID)?.generation ?? 0;
682
+ }
676
683
  observeUserMessage(sessionID, agent) {
677
684
  if (agent !== "co-orchestrator") {
678
685
  if (!this.sessions.has(sessionID)) {
@@ -680,10 +687,18 @@ class PlanApprovalManager {
680
687
  }
681
688
  }
682
689
  const state = this.sessions.get(sessionID) ?? { generation: 0 };
690
+ const oldApprovedGeneration = state.approvedGeneration;
683
691
  state.generation += 1;
684
692
  delete state.approvedGeneration;
685
693
  delete state.plan;
686
694
  this.sessions.set(sessionID, state);
695
+ this.audit?.record({
696
+ event: "revoked",
697
+ session: sessionID.slice(0, 20),
698
+ generation: state.generation,
699
+ revoked: oldApprovedGeneration,
700
+ agent: agent && agent !== "co-orchestrator" ? agent : undefined
701
+ });
687
702
  return true;
688
703
  }
689
704
  approve(sessionID, plan) {
@@ -696,6 +711,13 @@ class PlanApprovalManager {
696
711
  }
697
712
  state.approvedGeneration = state.generation;
698
713
  state.plan = plan;
714
+ this.audit?.record({
715
+ event: "approved",
716
+ session: sessionID.slice(0, 20),
717
+ generation: state.generation,
718
+ fileCount: plan.files.length,
719
+ summaryLen: plan.summary.length
720
+ });
699
721
  }
700
722
  isApproved(sessionID) {
701
723
  const state = this.sessions.get(sessionID);
@@ -714,6 +736,32 @@ class PlanApprovalManager {
714
736
  ` + (state.plan ? ` plan_summary: ${state.plan.summary.slice(0, 120)}
715
737
  ` + ` plan_files: ${state.plan.files.slice(0, 5).join(", ")}${state.plan.files.length > 5 ? "..." : ""}
716
738
  ` : "") + `</plan_gate>`;
739
+ }
740
+ getInjectionText(sessionID) {
741
+ const state = this.sessions.get(sessionID);
742
+ if (!state || state.generation === 0)
743
+ return null;
744
+ const approved = this.isApproved(sessionID);
745
+ const status = approved ? "已批准" : "未批准";
746
+ const lines = [];
747
+ lines.push("");
748
+ lines.push("--- 行为准则(本次 LLM 请求注入,不持久化) ---");
749
+ lines.push(`当前 generation: ${state.generation},方案状态: ${status}`);
750
+ lines.push("");
751
+ lines.push("核心规则:");
752
+ lines.push("1. 需要改代码 -> 先输出方案 -> todowrite -> request_plan_approval -> 等弹窗确认");
753
+ lines.push("2. 文件操作 -> 委派子代理,禁止亲自使用 read/grep/edit/bash/write");
754
+ lines.push("3. 未批准方案时禁止委派 co-fixer / co-designer");
755
+ lines.push("4. 每个新用户消息自动撤销旧批准");
756
+ lines.push("");
757
+ if (approved) {
758
+ lines.push("可委派 co-fixer / co-designer");
759
+ lines.push("");
760
+ }
761
+ lines.push("自检:需要改代码?-> 先方案 委派 fixer?-> 查状态锁");
762
+ lines.push("--- 注入结束 ---");
763
+ return lines.join(`
764
+ `);
717
765
  }
718
766
  cleanup(sessionID) {
719
767
  this.sessions.delete(sessionID);
@@ -748,23 +796,42 @@ function createRequestPlanApprovalTool(planManager) {
748
796
  const truncatedSummary = summary.length > 200 ? summary.slice(0, 200) + "..." : summary;
749
797
  const truncatedFiles = files.length > 8 ? [...files.slice(0, 8), `...等 ${files.length} 个文件`] : files;
750
798
  const truncatedVerification = verification.length > 150 ? verification.slice(0, 150) + "..." : verification;
751
- await toolContext.ask({
752
- permission: "plan-execute",
753
- always: [],
754
- patterns: [
755
- `\uD83D\uDCCB 方案: ${truncatedSummary}`,
756
- `\uD83D\uDCC1 文件 (${files.length}个): ${truncatedFiles.join(", ")}`,
757
- `\uD83D\uDD0D 验证: ${truncatedVerification}`
758
- ],
759
- metadata: {
760
- session_id: sessionID,
761
- generation: String(planManager["sessions"]?.get(sessionID)?.generation ?? 0),
762
- summary,
763
- file_count: String(files.length)
764
- }
799
+ const currentGen = planManager.getGeneration(sessionID);
800
+ planManager.audit?.record({
801
+ event: "approval_requested",
802
+ session: sessionID.slice(0, 20),
803
+ generation: currentGen,
804
+ agent: "co-orchestrator",
805
+ fileCount: files.length,
806
+ summaryLen: summary.length
765
807
  });
808
+ try {
809
+ await toolContext.ask({
810
+ permission: "plan-execute",
811
+ always: [],
812
+ patterns: [
813
+ `\uD83D\uDCCB 方案: ${truncatedSummary}`,
814
+ `\uD83D\uDCC1 文件 (${files.length}个): ${truncatedFiles.join(", ")}`,
815
+ `\uD83D\uDD0D 验证: ${truncatedVerification}`
816
+ ],
817
+ metadata: {
818
+ session_id: sessionID,
819
+ generation: String(currentGen),
820
+ summary,
821
+ file_count: String(files.length)
822
+ }
823
+ });
824
+ } catch (err) {
825
+ planManager.audit?.record({
826
+ event: "rejected",
827
+ session: sessionID.slice(0, 20),
828
+ generation: currentGen,
829
+ reason: "permission_rejected"
830
+ });
831
+ throw err;
832
+ }
766
833
  planManager.approve(sessionID, { summary, files, verification });
767
- return `✅ 方案已批准(generation ${planManager["sessions"]?.get(sessionID)?.generation ?? "?"})。
834
+ return `✅ 方案已批准(generation ${currentGen})。
768
835
  ` + `现在可以委派 @co-fixer / @co-designer 执行。
769
836
  ` + `\uD83D\uDCCB ${summary}
770
837
  ` + `\uD83D\uDCC1 ${files.length} 个文件
@@ -774,6 +841,60 @@ function createRequestPlanApprovalTool(planManager) {
774
841
  return { request_plan_approval };
775
842
  }
776
843
 
844
+ // src/plan-gate-audit.ts
845
+ import * as fs from "node:fs";
846
+ import * as path from "node:path";
847
+
848
+ class BoundedPlanGateAudit {
849
+ events = [];
850
+ logPath;
851
+ maxEvents = 50;
852
+ constructor(logPath) {
853
+ this.logPath = logPath;
854
+ this.restore();
855
+ }
856
+ restore() {
857
+ try {
858
+ const data = fs.readFileSync(this.logPath, "utf-8");
859
+ const parsed = JSON.parse(data);
860
+ if (parsed && parsed.version === 1 && Array.isArray(parsed.events)) {
861
+ this.events = parsed.events;
862
+ }
863
+ } catch {
864
+ this.events = [];
865
+ }
866
+ }
867
+ record(e) {
868
+ try {
869
+ const event = { ...e, at: new Date().toISOString() };
870
+ this.events.push(event);
871
+ if (this.events.length > this.maxEvents) {
872
+ this.events = this.events.slice(-this.maxEvents);
873
+ }
874
+ this.flush();
875
+ } catch (err) {
876
+ console.warn("[BoundedPlanGateAudit] record 失败:", err);
877
+ }
878
+ }
879
+ flush() {
880
+ try {
881
+ const data = JSON.stringify({ version: 1, events: this.events });
882
+ const tmpPath = this.logPath + ".tmp";
883
+ const dir = path.dirname(this.logPath);
884
+ if (!fs.existsSync(dir)) {
885
+ fs.mkdirSync(dir, { recursive: true });
886
+ }
887
+ fs.writeFileSync(tmpPath, data, "utf-8");
888
+ try {
889
+ fs.unlinkSync(this.logPath);
890
+ } catch {}
891
+ fs.renameSync(tmpPath, this.logPath);
892
+ } catch (err) {
893
+ console.warn("[BoundedPlanGateAudit] flush 失败:", err);
894
+ }
895
+ }
896
+ }
897
+
777
898
  // src/context/types.ts
778
899
  var DEFAULT_CONTEXT_CONFIG = {
779
900
  strategy: {
@@ -849,12 +970,12 @@ function extractRelevantFiles(messages, maxFiles, windowSize) {
849
970
  if (part.type === "tool" && part.state) {
850
971
  const state = part.state;
851
972
  const input = state.input ?? {};
852
- const path = extractPath(input);
853
- if (path) {
854
- if (!fileMap.has(path)) {
855
- fileMap.set(path, { path, summary: "" });
973
+ const path2 = extractPath(input);
974
+ if (path2) {
975
+ if (!fileMap.has(path2)) {
976
+ fileMap.set(path2, { path: path2, summary: "" });
856
977
  }
857
- const existing = fileMap.get(path);
978
+ const existing = fileMap.get(path2);
858
979
  if (typeof input.offset === "number") {
859
980
  const limit = typeof input.limit === "number" ? input.limit : 50;
860
981
  existing.lines = `${input.offset}-${input.offset + limit}`;
@@ -1177,15 +1298,15 @@ class ContextEngine {
1177
1298
  }
1178
1299
 
1179
1300
  // src/config/loader.ts
1180
- import * as fs from "node:fs";
1181
- import * as path from "node:path";
1301
+ import * as fs2 from "node:fs";
1302
+ import * as path2 from "node:path";
1182
1303
  import * as os from "node:os";
1183
- var CONFIG_PATH = path.join(os.homedir(), ".config", "opencode", "oh-my-opencode-cohub.json");
1304
+ var CONFIG_PATH = path2.join(os.homedir(), ".config", "opencode", "oh-my-opencode-cohub.json");
1184
1305
  function loadCoHubConfig() {
1185
1306
  try {
1186
- if (!fs.existsSync(CONFIG_PATH))
1307
+ if (!fs2.existsSync(CONFIG_PATH))
1187
1308
  return {};
1188
- const raw = fs.readFileSync(CONFIG_PATH, "utf-8");
1309
+ const raw = fs2.readFileSync(CONFIG_PATH, "utf-8");
1189
1310
  return JSON.parse(raw);
1190
1311
  } catch {
1191
1312
  return {};
@@ -1222,12 +1343,12 @@ async function abortSession(client, sessionId) {
1222
1343
  await client.session.abort({ path: { id: sessionId } });
1223
1344
  } catch {}
1224
1345
  }
1225
- async function promptWithTimeout(client, path2, body, timeoutMs, directory) {
1226
- const sessionId = path2.id;
1346
+ async function promptWithTimeout(client, path3, body, timeoutMs, directory) {
1347
+ const sessionId = path3.id;
1227
1348
  let timer;
1228
1349
  try {
1229
1350
  const promptPromise = client.session.prompt({
1230
- path: path2,
1351
+ path: path3,
1231
1352
  body,
1232
1353
  query: directory ? { directory } : undefined
1233
1354
  });
@@ -1534,8 +1655,8 @@ function createCouncilTool(ctx, councilManager) {
1534
1655
  }
1535
1656
 
1536
1657
  // src/index.ts
1537
- import * as fs2 from "node:fs";
1538
- import * as path2 from "node:path";
1658
+ import * as fs3 from "node:fs";
1659
+ import * as path3 from "node:path";
1539
1660
  import * as os2 from "node:os";
1540
1661
  function loadFileOverrides(projectDir) {
1541
1662
  const overrides = {};
@@ -1555,21 +1676,21 @@ function loadFileOverrides(projectDir) {
1555
1676
  ];
1556
1677
  const searchDirs = [];
1557
1678
  if (projectDir) {
1558
- searchDirs.push(path2.join(projectDir, ".opencode", "oh-my-opencode-cohub"));
1679
+ searchDirs.push(path3.join(projectDir, ".opencode", "oh-my-opencode-cohub"));
1559
1680
  }
1560
- searchDirs.push(path2.join(os2.homedir(), ".config", "opencode", "oh-my-opencode-cohub"));
1681
+ searchDirs.push(path3.join(os2.homedir(), ".config", "opencode", "oh-my-opencode-cohub"));
1561
1682
  for (const agent of agentNames) {
1562
1683
  for (const dir of searchDirs) {
1563
- const replacePath = path2.join(dir, `${agent}.md`);
1564
- if (!overrides[agent]?.replace && fs2.existsSync(replacePath)) {
1684
+ const replacePath = path3.join(dir, `${agent}.md`);
1685
+ if (!overrides[agent]?.replace && fs3.existsSync(replacePath)) {
1565
1686
  try {
1566
- overrides[agent] = { ...overrides[agent], replace: fs2.readFileSync(replacePath, "utf-8") };
1687
+ overrides[agent] = { ...overrides[agent], replace: fs3.readFileSync(replacePath, "utf-8") };
1567
1688
  } catch {}
1568
1689
  }
1569
- const appendPath = path2.join(dir, `${agent}_append.md`);
1570
- if (!overrides[agent]?.append && fs2.existsSync(appendPath)) {
1690
+ const appendPath = path3.join(dir, `${agent}_append.md`);
1691
+ if (!overrides[agent]?.append && fs3.existsSync(appendPath)) {
1571
1692
  try {
1572
- overrides[agent] = { ...overrides[agent], append: fs2.readFileSync(appendPath, "utf-8") };
1693
+ overrides[agent] = { ...overrides[agent], append: fs3.readFileSync(appendPath, "utf-8") };
1573
1694
  } catch {}
1574
1695
  }
1575
1696
  }
@@ -1598,28 +1719,30 @@ var CoHubPlugin = async (input, options) => {
1598
1719
  }
1599
1720
  const promptOverrides = { ...configOverrides, ...fileOverrides };
1600
1721
  const tracker = new TaskTracker;
1601
- const planManager = new PlanApprovalManager;
1722
+ const STATE_DIR = path3.join(os2.homedir(), ".local", "share", "opencode", "storage", "oh-my-opencode-cohub");
1723
+ const STATE_FILE = path3.join(STATE_DIR, "tracker-state.json");
1724
+ const PLAN_GATE_AUDIT_PATH = path3.join(STATE_DIR, "plan-gate-audit.json");
1725
+ const planAudit = new BoundedPlanGateAudit(PLAN_GATE_AUDIT_PATH);
1726
+ const planManager = new PlanApprovalManager(planAudit);
1602
1727
  const planTools = createRequestPlanApprovalTool(planManager);
1603
- const STATE_DIR = path2.join(os2.homedir(), ".local", "share", "opencode", "storage", "oh-my-opencode-cohub");
1604
- const STATE_FILE = path2.join(STATE_DIR, "tracker-state.json");
1605
1728
  function syncTrackerState(sessionId) {
1606
1729
  try {
1607
- if (!fs2.existsSync(STATE_DIR)) {
1608
- fs2.mkdirSync(STATE_DIR, { recursive: true });
1730
+ if (!fs3.existsSync(STATE_DIR)) {
1731
+ fs3.mkdirSync(STATE_DIR, { recursive: true });
1609
1732
  }
1610
1733
  const state = {
1611
1734
  updatedAt: Date.now(),
1612
1735
  runningAgents: tracker.getRunningAgents(sessionId),
1613
1736
  runningCount: tracker.getRunningCount(sessionId)
1614
1737
  };
1615
- fs2.writeFileSync(STATE_FILE, JSON.stringify(state), "utf-8");
1738
+ fs3.writeFileSync(STATE_FILE, JSON.stringify(state), "utf-8");
1616
1739
  } catch {}
1617
1740
  }
1618
- const AGENT_CONFIG_FILE = path2.join(STATE_DIR, "cohub-state.json");
1741
+ const AGENT_CONFIG_FILE = path3.join(STATE_DIR, "cohub-state.json");
1619
1742
  function syncAgentConfig() {
1620
1743
  try {
1621
- if (!fs2.existsSync(STATE_DIR)) {
1622
- fs2.mkdirSync(STATE_DIR, { recursive: true });
1744
+ if (!fs3.existsSync(STATE_DIR)) {
1745
+ fs3.mkdirSync(STATE_DIR, { recursive: true });
1623
1746
  }
1624
1747
  const configs = agents.map((a) => {
1625
1748
  const modelStr = a.config.model;
@@ -1634,7 +1757,7 @@ var CoHubPlugin = async (input, options) => {
1634
1757
  provider
1635
1758
  };
1636
1759
  });
1637
- fs2.writeFileSync(AGENT_CONFIG_FILE, JSON.stringify({ updatedAt: Date.now(), agents: configs }), "utf-8");
1760
+ fs3.writeFileSync(AGENT_CONFIG_FILE, JSON.stringify({ updatedAt: Date.now(), agents: configs }), "utf-8");
1638
1761
  } catch {}
1639
1762
  }
1640
1763
  const agents = [
@@ -1806,7 +1929,7 @@ var CoHubPlugin = async (input, options) => {
1806
1929
  c.agent[name] = config;
1807
1930
  }
1808
1931
  try {
1809
- fs2.writeFileSync(path2.join(STATE_DIR, "config-hook-ran.json"), JSON.stringify({ ran: true, count: agents.length }));
1932
+ fs3.writeFileSync(path3.join(STATE_DIR, "config-hook-ran.json"), JSON.stringify({ ran: true, count: agents.length }));
1810
1933
  } catch {}
1811
1934
  },
1812
1935
  "tool.execute.before": async (input2, output) => {
@@ -1814,9 +1937,24 @@ var CoHubPlugin = async (input, options) => {
1814
1937
  const beforeArgs = output.args ?? {};
1815
1938
  const beforeSubagentType = typeof beforeArgs.subagent_type === "string" ? beforeArgs.subagent_type : "";
1816
1939
  if ((beforeSubagentType === "co-fixer" || beforeSubagentType === "co-designer") && !planManager.isApproved(input2.sessionID)) {
1940
+ planAudit.record({
1941
+ event: "task_denied",
1942
+ session: input2.sessionID?.slice(0, 20) ?? "?",
1943
+ generation: planManager.getGeneration(input2.sessionID),
1944
+ agent: beforeSubagentType,
1945
+ reason: "unapproved"
1946
+ });
1817
1947
  throw new Error(`[CoHub 方案批准门禁] 当前 session 尚未通过方案批准。
1818
1948
  ` + `请先:1) 输出可验证方案 → 2) todowrite 记录 → 3) 调用 request_plan_approval 弹出确认框 → ` + `4) 用户在弹窗中允许后,才能委派 ${beforeSubagentType}。`);
1819
1949
  }
1950
+ if (beforeSubagentType === "co-fixer" || beforeSubagentType === "co-designer") {
1951
+ planAudit.record({
1952
+ event: "task_allowed",
1953
+ session: input2.sessionID?.slice(0, 20) ?? "?",
1954
+ generation: planManager.getGeneration(input2.sessionID),
1955
+ agent: beforeSubagentType
1956
+ });
1957
+ }
1820
1958
  }
1821
1959
  try {
1822
1960
  if (input2.tool === "task") {
@@ -1852,19 +1990,19 @@ var CoHubPlugin = async (input, options) => {
1852
1990
  if (details) {
1853
1991
  output.args[targetField] += details;
1854
1992
  }
1855
- const logPath = path2.join(os2.tmpdir(), "opencode", "ctx-diag.log");
1993
+ const logPath = path3.join(os2.tmpdir(), "opencode", "ctx-diag.log");
1856
1994
  try {
1857
- const stat = fs2.statSync(logPath);
1995
+ const stat = fs3.statSync(logPath);
1858
1996
  if (stat.size > 50 * 1024) {
1859
- const lines = fs2.readFileSync(logPath, "utf-8").trim().split(`
1997
+ const lines = fs3.readFileSync(logPath, "utf-8").trim().split(`
1860
1998
  `);
1861
- fs2.writeFileSync(logPath, lines.slice(-30).join(`
1999
+ fs3.writeFileSync(logPath, lines.slice(-30).join(`
1862
2000
  `) + `
1863
2001
  `);
1864
2002
  }
1865
2003
  } catch {}
1866
2004
  const finalPrompt = typeof output.args?.prompt === "string" ? output.args.prompt : "";
1867
- fs2.appendFileSync(logPath, JSON.stringify({
2005
+ fs3.appendFileSync(logPath, JSON.stringify({
1868
2006
  time: new Date().toISOString(),
1869
2007
  session: input2.sessionID?.slice(0, 20) ?? "?",
1870
2008
  subagent: subagentType,
@@ -1931,20 +2069,38 @@ var CoHubPlugin = async (input, options) => {
1931
2069
  },
1932
2070
  "experimental.chat.messages.transform": async (_input, output) => {
1933
2071
  try {
2072
+ if (!output.messages || !Array.isArray(output.messages))
2073
+ return;
2074
+ let lastUserMsg;
2075
+ let sessionID;
2076
+ for (let i = output.messages.length - 1;i >= 0; i--) {
2077
+ const m = output.messages[i];
2078
+ if (m.info.role === "user") {
2079
+ lastUserMsg = m;
2080
+ sessionID = m.info?.sessionID;
2081
+ break;
2082
+ }
2083
+ }
2084
+ if (!lastUserMsg)
2085
+ return;
1934
2086
  const board = tracker.getBoardText();
1935
- if (board && output.messages && Array.isArray(output.messages)) {
1936
- for (let i = output.messages.length - 1;i >= 0; i--) {
1937
- const msg = output.messages[i];
1938
- if (msg.info.role === "user") {
1939
- for (let j = msg.parts.length - 1;j >= 0; j--) {
1940
- const part = msg.parts[j];
1941
- if (part.type === "text") {
1942
- part.text += `
2087
+ if (board) {
2088
+ for (let j = lastUserMsg.parts.length - 1;j >= 0; j--) {
2089
+ const part = lastUserMsg.parts[j];
2090
+ if (part.type === "text") {
2091
+ part.text += `
1943
2092
 
1944
2093
  ` + board;
1945
- break;
1946
- }
1947
- }
2094
+ break;
2095
+ }
2096
+ }
2097
+ }
2098
+ const planInject = sessionID ? planManager.getInjectionText(sessionID) : null;
2099
+ if (planInject) {
2100
+ for (let j = lastUserMsg.parts.length - 1;j >= 0; j--) {
2101
+ const part = lastUserMsg.parts[j];
2102
+ if (part.type === "text") {
2103
+ part.text += planInject;
1948
2104
  break;
1949
2105
  }
1950
2106
  }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * PlanGate 有界审计日志
3
+ *
4
+ * 记录方案批准生命周期事件(批准请求、批准、拒绝、撤销、门禁拦截等)。
5
+ * 有界缓冲区(上限 50 条),原子写入(tmp + rename),
6
+ * 所有方法 fail-open(绝不 throw)。
7
+ */
8
+ export interface AuditEvent {
9
+ /** ISO timestamp(由 record() 自动填充) */
10
+ at: string;
11
+ /** 事件类型 */
12
+ event: string;
13
+ /** sessionID 截断前 20 字符 */
14
+ session: string;
15
+ /** 事件发生时该 session 的 generation */
16
+ generation: number;
17
+ /** 被撤销的 generation(仅 revoked 事件) */
18
+ revoked?: number;
19
+ /** 代理名称(如 "co-fixer" / "co-designer") */
20
+ agent?: string;
21
+ /** 拒绝原因(如 "unapproved" / "permission_rejected") */
22
+ reason?: string;
23
+ /** 涉及文件数量 */
24
+ fileCount?: number;
25
+ /** 方案摘要长度,不记录原文 */
26
+ summaryLen?: number;
27
+ }
28
+ export declare class BoundedPlanGateAudit {
29
+ private events;
30
+ private readonly logPath;
31
+ private readonly maxEvents;
32
+ /**
33
+ * @param logPath 审计日志文件路径(如 STATE_DIR/plan-gate-audit.json)
34
+ */
35
+ constructor(logPath: string);
36
+ /** 从已有文件恢复事件 */
37
+ private restore;
38
+ /**
39
+ * 追加审计事件。
40
+ * 超过 50 条自动裁剪(保留最近 50 条),然后调用 flush()。
41
+ * fail-open:内部错误仅 console.warn,不 throw。
42
+ */
43
+ record(e: Omit<AuditEvent, 'at'>): void;
44
+ /**
45
+ * 将当前缓冲区原子写入磁盘(tmp + rename)。
46
+ * fail-open:内部错误仅 console.warn,不 throw。
47
+ */
48
+ flush(): void;
49
+ }
50
+ //# sourceMappingURL=plan-gate-audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-gate-audit.d.ts","sourceRoot":"","sources":["../src/plan-gate-audit.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,MAAM,WAAW,UAAU;IACzB,qCAAqC;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW;IACX,KAAK,EAAE,MAAM,CAAC;IACd,0BAA0B;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mBAAmB;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAMD,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAM;IAEhC;;OAEG;gBACS,OAAO,EAAE,MAAM;IAS3B,gBAAgB;IAChB,OAAO,CAAC,OAAO;IAiBf;;;;OAIG;IACH,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,IAAI;IAiBvC;;;OAGG;IACH,KAAK,IAAI,IAAI;CAoBd"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=plan-gate-audit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-gate-audit.test.d.ts","sourceRoot":"","sources":["../src/plan-gate-audit.test.ts"],"names":[],"mappings":""}
@@ -6,6 +6,7 @@
6
6
  * 通过 request_plan_approval 工具和 ctx.ask() 实现原生确认框门禁。
7
7
  */
8
8
  import { tool } from '@opencode-ai/plugin';
9
+ import { BoundedPlanGateAudit } from './plan-gate-audit';
9
10
  export interface PlanInfo {
10
11
  summary: string;
11
12
  files: string[];
@@ -14,6 +15,16 @@ export interface PlanInfo {
14
15
  export declare class PlanApprovalManager {
15
16
  /** sessionID → SessionPlanState */
16
17
  private sessions;
18
+ /** 有界审计日志(可选) */
19
+ readonly audit?: BoundedPlanGateAudit;
20
+ /**
21
+ * @param audit 可选的审计日志实例
22
+ */
23
+ constructor(audit?: BoundedPlanGateAudit);
24
+ /**
25
+ * 获取指定 session 当前 generation(用于审计记录)。
26
+ */
27
+ getGeneration(sessionID: string): number;
17
28
  /**
18
29
  * 观察用户消息。仅在 orchestrator session 上建立状态。
19
30
  * @param sessionID 当前 session ID
@@ -36,6 +47,12 @@ export declare class PlanApprovalManager {
36
47
  * 仅对已登记的 orchestrator session 返回内容。
37
48
  */
38
49
  getSystemContext(sessionID: string): string;
50
+ /**
51
+ * 生成注入到消息末尾的行为准则文本(利用 recency bias 对抗注意力衰减)。
52
+ * 仅对已观察的 orchestrator session 返回内容,否则返回 null。
53
+ * 不超过 25 行,不持久化到 DB(由调用方注入到 messages.transform)。
54
+ */
55
+ getInjectionText(sessionID: string): string | null;
39
56
  /**
40
57
  * 清理指定 session 的状态。
41
58
  */
@@ -1 +1 @@
1
- {"version":3,"file":"plan-gate.d.ts","sourceRoot":"","sources":["../src/plan-gate.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAM3C,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB;AAYD,qBAAa,mBAAmB;IAC9B,mCAAmC;IACnC,OAAO,CAAC,QAAQ,CAAuC;IAEvD;;;;;OAKG;IACH,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO;IAkB9D;;;;OAIG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI;IAehD;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAMtC;;;OAGG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAc3C;;OAEG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIhC;;OAEG;IACH,IAAI,kBAAkB,IAAI,MAAM,CAE/B;CACF;AAQD;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,mBAAmB,GAC/B,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC,CAyEzC"}
1
+ {"version":3,"file":"plan-gate.d.ts","sourceRoot":"","sources":["../src/plan-gate.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAmB,MAAM,mBAAmB,CAAC;AAM1E,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB;AAYD,qBAAa,mBAAmB;IAC9B,mCAAmC;IACnC,OAAO,CAAC,QAAQ,CAAuC;IACvD,iBAAiB;IACjB,QAAQ,CAAC,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAEtC;;OAEG;gBACS,KAAK,CAAC,EAAE,oBAAoB;IAIxC;;OAEG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAIxC;;;;;OAKG;IACH,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO;IA6B9D;;;;OAIG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI;IAwBhD;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAMtC;;;OAGG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAc3C;;;;OAIG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IA2BlD;;OAEG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIhC;;OAEG;IACH,IAAI,kBAAkB,IAAI,MAAM,CAE/B;CACF;AAQD;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,mBAAmB,GAC/B,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC,CAiGzC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode-cohub",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Mr-cjf/oh-my-opencode-cohub.git"