@weavecode/weave 0.1.1 → 0.1.2

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.
Files changed (166) hide show
  1. package/node_modules/@weavecode/cli/dist/cli.js +4 -4
  2. package/node_modules/@weavecode/cli/dist/commands/chpass.js +1 -1
  3. package/node_modules/@weavecode/cli/dist/commands/daemon-auth.js +1 -1
  4. package/node_modules/@weavecode/cli/dist/commands/daemon.d.ts +7 -0
  5. package/node_modules/@weavecode/cli/dist/commands/daemon.js +2 -2
  6. package/node_modules/@weavecode/cli/dist/commands/dashboard.js +2 -2
  7. package/node_modules/@weavecode/cli/dist/commands/doctor.js +1 -1
  8. package/node_modules/@weavecode/cli/dist/commands/init.js +1 -1
  9. package/node_modules/@weavecode/cli/dist/commands/scan.js +1 -1
  10. package/node_modules/@weavecode/cli/dist/commands/status.js +2 -2
  11. package/node_modules/@weavecode/cli/dist/commands/uninstall.js +1 -1
  12. package/node_modules/@weavecode/cli/dist/i18n/dict/en.js +1 -9
  13. package/node_modules/@weavecode/cli/dist/i18n/dict/zh.d.ts +0 -1
  14. package/node_modules/@weavecode/cli/dist/i18n/dict/zh.js +2 -10
  15. package/node_modules/@weavecode/cli/dist/i18n/index.js +1 -1
  16. package/node_modules/@weavecode/cli/dist/i18n/t.js +1 -1
  17. package/node_modules/@weavecode/cli/dist/init/claudemd-gen.d.ts +2 -3
  18. package/node_modules/@weavecode/cli/dist/init/claudemd-gen.js +4 -4
  19. package/node_modules/@weavecode/cli/dist/init/executor.js +3 -3
  20. package/node_modules/@weavecode/cli/dist/init/git-commit.js +2 -2
  21. package/node_modules/@weavecode/cli/dist/init/harnesses/claude.js +7 -7
  22. package/node_modules/@weavecode/cli/dist/init/harnesses/codex.js +1 -1
  23. package/node_modules/@weavecode/cli/dist/init/harnesses/zcode.d.ts +29 -0
  24. package/node_modules/@weavecode/cli/dist/init/harnesses/zcode.js +1 -0
  25. package/node_modules/@weavecode/cli/dist/init/instruction-link.js +1 -1
  26. package/node_modules/@weavecode/cli/dist/init/plugin-templates.js +1 -1
  27. package/node_modules/@weavecode/cli/dist/init/prompt.js +4 -4
  28. package/node_modules/@weavecode/cli/dist/init/uninstall.js +6 -6
  29. package/node_modules/@weavecode/cli/dist/init/wiring.js +1 -1
  30. package/node_modules/@weavecode/cli/dist/parser.js +1 -1
  31. package/node_modules/@weavecode/cli/dist/statusline/ansi.js +1 -1
  32. package/node_modules/@weavecode/cli/dist/statusline/mock-input.js +1 -1
  33. package/node_modules/@weavecode/cli/dist/statusline/preview.js +2 -2
  34. package/node_modules/@weavecode/cli/package.json +5 -5
  35. package/node_modules/@weavecode/core/dist/harness-checks.js +1 -1
  36. package/node_modules/@weavecode/core/dist/index.d.ts +1 -1
  37. package/node_modules/@weavecode/core/dist/index.js +1 -1
  38. package/node_modules/@weavecode/core/dist/platform.js +1 -1
  39. package/node_modules/@weavecode/core/dist/types.d.ts +0 -8
  40. package/node_modules/@weavecode/core/dist/types.js +1 -1
  41. package/node_modules/@weavecode/core/dist/utils/fs.js +1 -1
  42. package/node_modules/@weavecode/core/dist/utils/logger.js +1 -1
  43. package/node_modules/@weavecode/core/package.json +1 -1
  44. package/node_modules/@weavecode/harness/dist/claude/assets.d.ts +10 -0
  45. package/node_modules/@weavecode/harness/dist/claude/assets.js +4 -1
  46. package/node_modules/@weavecode/harness/dist/claude/capture.d.ts +4 -2
  47. package/node_modules/@weavecode/harness/dist/claude/capture.js +15 -9
  48. package/node_modules/@weavecode/harness/dist/claude/detect.js +1 -1
  49. package/node_modules/@weavecode/harness/dist/claude/event-source.js +1 -1
  50. package/node_modules/@weavecode/harness/dist/claude/index.d.ts +6 -1
  51. package/node_modules/@weavecode/harness/dist/claude/index.js +1 -1
  52. package/node_modules/@weavecode/harness/dist/claude/mcp-gen.js +1 -1
  53. package/node_modules/@weavecode/harness/dist/claude/messaging.js +2 -2
  54. package/node_modules/@weavecode/harness/dist/claude/model-aliases.d.ts +14 -0
  55. package/node_modules/@weavecode/harness/dist/claude/model-aliases.js +1 -1
  56. package/node_modules/@weavecode/harness/dist/claude/peers.js +1 -1
  57. package/node_modules/@weavecode/harness/dist/claude/plan.d.ts +2 -0
  58. package/node_modules/@weavecode/harness/dist/claude/plan.js +1 -0
  59. package/node_modules/@weavecode/harness/dist/claude/plugins.js +4 -4
  60. package/node_modules/@weavecode/harness/dist/claude/prompt-inbox.js +1 -1
  61. package/node_modules/@weavecode/harness/dist/claude/prompt.js +3 -3
  62. package/node_modules/@weavecode/harness/dist/claude/questions.js +2 -2
  63. package/node_modules/@weavecode/harness/dist/claude/settings-gen.js +1 -1
  64. package/node_modules/@weavecode/harness/dist/claude/side-question.d.ts +12 -0
  65. package/node_modules/@weavecode/harness/dist/claude/side-question.js +1 -0
  66. package/node_modules/@weavecode/harness/dist/claude/statusline/config.d.ts +200 -0
  67. package/node_modules/@weavecode/harness/dist/claude/statusline/config.js +1 -0
  68. package/node_modules/@weavecode/harness/dist/claude/statusline/generator.d.ts +12 -0
  69. package/node_modules/@weavecode/harness/dist/claude/statusline/generator.js +745 -0
  70. package/node_modules/@weavecode/harness/dist/claude/statusline/git-cache.d.ts +10 -0
  71. package/node_modules/@weavecode/harness/dist/claude/statusline/git-cache.js +1 -0
  72. package/node_modules/@weavecode/harness/dist/claude/statusline/surface.d.ts +20 -0
  73. package/node_modules/@weavecode/harness/dist/claude/statusline/surface.js +2 -0
  74. package/node_modules/@weavecode/harness/dist/claude/transcript.js +3 -3
  75. package/node_modules/@weavecode/harness/dist/codex/assets.js +1 -1
  76. package/node_modules/@weavecode/harness/dist/codex/detect.js +1 -1
  77. package/node_modules/@weavecode/harness/dist/codex/index.d.ts +1 -0
  78. package/node_modules/@weavecode/harness/dist/codex/index.js +1 -1
  79. package/node_modules/@weavecode/harness/dist/codex/mcp.d.ts +30 -0
  80. package/node_modules/@weavecode/harness/dist/codex/mcp.js +1 -0
  81. package/node_modules/@weavecode/harness/dist/codex/messaging.js +1 -1
  82. package/node_modules/@weavecode/harness/dist/codex/plugin-state.js +1 -1
  83. package/node_modules/@weavecode/harness/dist/codex/toml.js +9 -9
  84. package/node_modules/@weavecode/harness/dist/driver.d.ts +110 -2
  85. package/node_modules/@weavecode/harness/dist/driver.js +1 -1
  86. package/node_modules/@weavecode/harness/dist/init/generated.js +2 -2
  87. package/node_modules/@weavecode/harness/dist/init/pipeline.js +1 -1
  88. package/node_modules/@weavecode/harness/dist/registry-wiring.js +1 -1
  89. package/node_modules/@weavecode/harness/dist/shared/install-link.js +1 -1
  90. package/node_modules/@weavecode/harness/dist/zcode/assets.d.ts +8 -0
  91. package/node_modules/@weavecode/harness/dist/zcode/assets.js +1 -0
  92. package/node_modules/@weavecode/harness/dist/zcode/capture.d.ts +36 -0
  93. package/node_modules/@weavecode/harness/dist/zcode/capture.js +3 -0
  94. package/node_modules/@weavecode/harness/dist/zcode/detect.d.ts +2 -0
  95. package/node_modules/@weavecode/harness/dist/zcode/detect.js +1 -0
  96. package/node_modules/@weavecode/harness/dist/zcode/event-source.d.ts +8 -0
  97. package/node_modules/@weavecode/harness/dist/zcode/event-source.js +1 -0
  98. package/node_modules/@weavecode/harness/dist/zcode/index.d.ts +13 -0
  99. package/node_modules/@weavecode/harness/dist/zcode/index.js +1 -0
  100. package/node_modules/@weavecode/harness/dist/zcode/mcp.d.ts +6 -0
  101. package/node_modules/@weavecode/harness/dist/zcode/mcp.js +2 -0
  102. package/node_modules/@weavecode/harness/dist/zcode/messaging.d.ts +13 -0
  103. package/node_modules/@weavecode/harness/dist/zcode/messaging.js +1 -0
  104. package/node_modules/@weavecode/harness/dist/zcode/resume.d.ts +7 -0
  105. package/node_modules/@weavecode/harness/dist/zcode/resume.js +1 -0
  106. package/node_modules/@weavecode/harness/package.json +2 -2
  107. package/node_modules/@weavecode/server/dist/daemon/dev-entry.js +1 -1
  108. package/node_modules/@weavecode/server/dist/daemon/hook-pipeline.d.ts +8 -1
  109. package/node_modules/@weavecode/server/dist/daemon/hook-pipeline.js +1 -1
  110. package/node_modules/@weavecode/server/dist/daemon/hooks-routes.js +1 -1
  111. package/node_modules/@weavecode/server/dist/daemon/server.d.ts +7 -0
  112. package/node_modules/@weavecode/server/dist/daemon/server.js +8 -6
  113. package/node_modules/@weavecode/server/dist/daemon/session-probe.d.ts +13 -0
  114. package/node_modules/@weavecode/server/dist/daemon/session-probe.js +1 -1
  115. package/node_modules/@weavecode/server/dist/daemon/snapshot-refresher.d.ts +7 -1
  116. package/node_modules/@weavecode/server/dist/daemon/snapshot-refresher.js +1 -1
  117. package/node_modules/@weavecode/server/dist/daemon/sse-hub.d.ts +40 -0
  118. package/node_modules/@weavecode/server/dist/daemon/sse-hub.js +1 -0
  119. package/node_modules/@weavecode/server/dist/daemon/start.js +1 -1
  120. package/node_modules/@weavecode/server/dist/db/db.js +20 -2
  121. package/node_modules/@weavecode/server/dist/hooks/bus.d.ts +5 -0
  122. package/node_modules/@weavecode/server/dist/hooks/inbox-worker.d.ts +8 -1
  123. package/node_modules/@weavecode/server/dist/hooks/inbox-worker.js +3 -3
  124. package/node_modules/@weavecode/server/dist/hooks/message-deliverer.d.ts +29 -0
  125. package/node_modules/@weavecode/server/dist/hooks/message-deliverer.js +2 -2
  126. package/node_modules/@weavecode/server/dist/hooks/mirror.d.ts +9 -4
  127. package/node_modules/@weavecode/server/dist/hooks/mirror.js +4 -4
  128. package/node_modules/@weavecode/server/dist/hooks/session-purge.d.ts +3 -0
  129. package/node_modules/@weavecode/server/dist/hooks/session-purge.js +1 -1
  130. package/node_modules/@weavecode/server/dist/hooks/side-questions.d.ts +41 -0
  131. package/node_modules/@weavecode/server/dist/hooks/side-questions.js +1 -0
  132. package/node_modules/@weavecode/server/dist/hooks/toolkit.js +5 -5
  133. package/node_modules/@weavecode/server/dist/install/apply-update.js +1 -1
  134. package/node_modules/@weavecode/server/dist/install/capabilities.d.ts +10 -0
  135. package/node_modules/@weavecode/server/dist/install/capabilities.js +1 -1
  136. package/node_modules/@weavecode/server/dist/install/codex-toml.d.ts +8 -26
  137. package/node_modules/@weavecode/server/dist/install/codex-toml.js +1 -1
  138. package/node_modules/@weavecode/server/dist/install/global-config.d.ts +2 -4
  139. package/node_modules/@weavecode/server/dist/install/global-config.js +1 -2
  140. package/node_modules/@weavecode/server/dist/install/installer.d.ts +3 -5
  141. package/node_modules/@weavecode/server/dist/install/installer.js +1 -3
  142. package/node_modules/@weavecode/server/dist/install/mcp-rows.d.ts +5 -5
  143. package/node_modules/@weavecode/server/dist/install/mcp-rows.js +1 -1
  144. package/node_modules/@weavecode/server/dist/registry.js +2 -2
  145. package/node_modules/@weavecode/server/dist/repositories/hook-events.d.ts +7 -0
  146. package/node_modules/@weavecode/server/dist/repositories/hook-events.js +2 -2
  147. package/node_modules/@weavecode/server/dist/repositories/sessions.d.ts +15 -4
  148. package/node_modules/@weavecode/server/dist/repositories/sessions.js +2 -2
  149. package/node_modules/@weavecode/server/dist/repositories/side-questions.d.ts +50 -0
  150. package/node_modules/@weavecode/server/dist/repositories/side-questions.js +6 -0
  151. package/node_modules/@weavecode/server/dist/statusline/config.d.ts +4 -198
  152. package/node_modules/@weavecode/server/dist/statusline/config.js +1 -1
  153. package/node_modules/@weavecode/server/dist/statusline/generator.d.ts +5 -0
  154. package/node_modules/@weavecode/server/dist/statusline/generator.js +1 -717
  155. package/node_modules/@weavecode/server/dist/statusline/manager.d.ts +5 -5
  156. package/node_modules/@weavecode/server/dist/statusline/manager.js +3 -4
  157. package/node_modules/@weavecode/server/dist/surfaces.d.ts +11 -1
  158. package/node_modules/@weavecode/server/dist/surfaces.js +1 -1
  159. package/node_modules/@weavecode/server/package.json +4 -4
  160. package/node_modules/@weavecode/server/web/dist/assets/index-CHWbLAje.js +594 -0
  161. package/node_modules/@weavecode/server/web/dist/assets/index-D0s5v8QS.css +11 -0
  162. package/node_modules/@weavecode/server/web/dist/index.html +2 -2
  163. package/node_modules/@weavecode/templates/package.json +2 -2
  164. package/package.json +6 -6
  165. package/node_modules/@weavecode/server/web/dist/assets/index-D5WFh77i.css +0 -11
  166. package/node_modules/@weavecode/server/web/dist/assets/index-DJibaGnJ.js +0 -585
@@ -1,5 +1,5 @@
1
- import{cp as I,mkdir as P,readFile as T,readdir as h,rm as w,writeFile as F}from"node:fs/promises";import{spawn as S}from"node:child_process";import{homedir as H}from"node:os";import c from"node:path";import{hashFile as y}from"../install/fingerprint.js";import{fileAssetTargetDir as L}from"../install/file-assets.js";import{assetSurfaceFor as N,pluginSurfaceFor as $}from"../surfaces.js";import{marketLibraryRoots as q}from"../market/resolve.js";import{marketIdOf as m}from"../market/identity.js";import{installPluginPack as A}from"../install/plugin-install.js";import{effective as V,setProjectEnabled as O}from"./plugin-project-state.js";import{builtinToolkitRoot as K,discoverToolkitPacks as B}from"./toolkit-scan.js";export class ToolkitError extends Error{code;constructor(t,e){super(t),this.code=e,this.name="ToolkitError"}}export const MIN_MOD_HOST_VERSION=[2,1,287];const W=1e4;export function isVersionAtLeast(a,t){const e=a.split(".").map(n=>Number.parseInt(n,10));for(let n=0;n<t.length;n++){const s=Number.isFinite(e[n])?e[n]:0;if(s>t[n])return!0;if(s<t[n])return!1}return!0}let b;function J(a){if(!(a.exitCode!==null||a.signalCode!==null)){if(process.platform==="win32"&&a.pid!==void 0){S("taskkill",["/pid",String(a.pid),"/t","/f"],{windowsHide:!0,stdio:"ignore"}).unref();return}a.kill("SIGKILL")}}export async function detectClaudeVersion(){if(b&&Date.now()-b.at<6e5)return b.value;const a=await new Promise(t=>{let e="";const n=process.platform==="win32"?S("claude --version",{windowsHide:!0,shell:!0,stdio:["ignore","pipe","pipe"]}):S("claude",["--version"],{windowsHide:!0,stdio:["ignore","pipe","pipe"]}),s=setTimeout(()=>{J(n),t(null)},5e3);n.stdout?.on("data",i=>e+=i.toString()),n.on("error",()=>{clearTimeout(s),t(null)}),n.on("close",()=>{clearTimeout(s);const i=/(\d+\.\d+\.\d+)/.exec(e);t(i?i[1]:null)})});return a!==null&&(b={value:a,at:Date.now()}),a}async function C(a,t,e){let n;try{n=await h(c.join(a,...t.split("/").filter(Boolean)),{withFileTypes:!0})}catch{return}for(const s of n){if(s.name==="node_modules"||s.name===".git")continue;const i=t?`${t}/${s.name}`:s.name;s.isDirectory()?await C(a,i,e):e.push({rel:i,abs:c.join(a,...i.split("/"))})}}const p=a=>a.ccPack||a.kind==="mod";async function R(a,t){const e=[],n=t.targets.includes("claude");if(t.skills.length>0)for(const s of t.targets){if(s==="claude"&&p(t))continue;const i=N(s).projectSkillsDir();for(const o of t.skills){const r=[];await C(c.join(t.dir,"skills",o),"",r);for(const l of r)e.push({src:l.abs,rel:`${i}/${o}/${l.rel}`})}}if(n&&!p(t))for(const[s,i]of[["commands","command"],["agents","agent"]]){const o=c.join(t.dir,s),r=[];if(await C(o,"",r),r.length===0)continue;const l=L(i,"claude","project",a,H());for(const u of r){const g=u.rel.split("/").pop();e.push({src:u.abs,rel:v(c.relative(a,c.join(l,g)))})}}return e}function v(a){return a.split(c.sep).join("/")}function x(a,t){return c.join(a,".weave","toolkit",`${t}.json`)}async function k(a){try{const t=JSON.parse(await T(a,"utf8"));return t&&typeof t=="object"&&typeof t.packId=="string"?t:null}catch{return null}}export async function projectFileNamespaces(a){const t=new Map,e=c.join(a,".weave","toolkit");let n;try{n=await h(e)}catch{return t}for(const s of n){if(!s.endsWith(".json"))continue;const i=await k(c.join(e,s)),o=i?m(i.sourceDir):void 0;if(!(!i||!o))for(const r of Object.keys(i.files))t.set(r,o)}return t}export async function projectPackFiles(a){const t=new Map,e=c.join(a,".weave","toolkit");let n;try{n=await h(e)}catch{return t}for(const s of n){if(!s.endsWith(".json"))continue;const i=await k(c.join(e,s));if(i)for(const[o,r]of Object.entries(i.files))typeof r=="string"&&t.set(o,r)}return t}async function M(a){try{return await T(a),!0}catch{return!1}}async function _(a,t){const e=new Set;for(const s of t){let i=c.posix.dirname(s);for(;i&&i!=="."&&i!=="/";)e.add(i),i=c.posix.dirname(i)}const n=[...e].sort((s,i)=>i.split("/").length-s.split("/").length);for(const s of n){const i=c.join(a,...s.split("/"));try{(await h(i)).length===0&&await w(i,{recursive:!0,force:!0})}catch{}}}export function evaluateModHostGate(a){return process.env.WEAVE_SKIP_MOD_VERSION_CHECK==="1"?{ok:!0}:a?isVersionAtLeast(a,MIN_MOD_HOST_VERSION)?{ok:!0}:{ok:!1,message:`\u5BBF\u4E3B Claude Code ${a} \u4F4E\u4E8E 2.1.287\uFF0Cmod \u5DE5\u5177\u5305\u4E0D\u843D\u76D8\uFF08\u8BF7\u5148\u5347\u7EA7 Claude Code\uFF09`}:{ok:!0,warning:"\u65E0\u6CD5\u68C0\u6D4B\u5BBF\u4E3B Claude Code \u7248\u672C\uFF0Cmod \u5DE5\u5177\u5305\u9700\u8981 \u2265 2.1.287 \u624D\u80FD\u88AB\u52A0\u8F7D"}}export class ToolkitService{deps;packsCache;constructor(t){this.deps=t}sources(){const e=[{root:this.deps.builtinRoot??K(),provenance:"builtin"}];if(this.deps.libraries)for(const s of q(this.deps.libraries))e.push({root:s,provenance:"market"});const n=new Set;return e.filter(s=>{const i=c.resolve(s.root);return n.has(i)?!1:(n.add(i),!0)})}async allPacks(){const t=this.sources(),e=t.map(r=>c.resolve(r.root)).join("|"),n=Date.now(),s=this.packsCache;if(s&&s.key===e&&n-s.at<W)return s.packs;const i=Promise.all(t.map(r=>B(r))).then(r=>r.flat()),o={at:n,key:e,packs:i};return this.packsCache=o,i.catch(()=>{this.packsCache===o&&(this.packsCache=void 0)}),i}async list(t){const e=this.requireProject(t),n=await this.allPacks(),s=await Promise.all(n.map(i=>this.packInfo(e,i)));return await this.healMeta(e,s),{packs:s,legacyCopies:await this.scanLegacyCopies(e)}}async enable(t,e,n){const s=this.requireProject(t),i=await this.resolvePack(e,n),o=[];if(i.kind==="mod"&&i.targets.includes("claude")){const d=this.deps.claudeVersion??detectClaudeVersion,f=evaluateModHostGate(await d());if(!f.ok)throw new ToolkitError(f.message??"mod host gate","host-version");f.warning&&o.push(f.warning)}const r=s.path;if(p(i)&&i.targets.includes("claude")){const d=this.pluginSurface();await A(d,i),await d.setProjectEnabled(r,i.name,!0,m(i.dir))}const u=x(r,i.id),g=await this.readRecordHealed(r,i.id),j=g??{packId:i.id,provenance:i.provenance,sourceRoot:i.sourceRoot,sourceDir:i.dir,enabledAt:new Date().toISOString(),files:{}};let E=g===null;const D=await R(r,i);for(const d of D){const f=c.join(r,...d.rel.split("/"));if(j.files[d.rel]!==void 0){await M(f)||(await P(c.dirname(f),{recursive:!0}),await I(d.src,f));continue}await y(f)==="missing"&&(await P(c.dirname(f),{recursive:!0}),await I(d.src,f),j.files[d.rel]=await y(d.src),E=!0)}return D.length>0?E&&(await P(c.dirname(u),{recursive:!0}),await F(u,JSON.stringify(j,null,2)+`
2
- `,"utf8")):g&&await w(u,{force:!0}),i.weavePack&&O(s,i.id,!0,{projects:this.deps.projects}),this.rememberEnabled(s,i.id,!0),{pack:await this.packInfo(this.requireProject(t),i),warnings:o}}async disable(t,e,n){const s=this.requireProject(t),i=await this.resolvePack(e,n);return await this.applyDisabled(s,i),{pack:await this.packInfo(this.requireProject(t),i),warnings:[]}}async applyDisabled(t,e){await this.removePackFiles(t,e,e.sourceRoot),p(e)&&e.targets.includes("claude")&&await this.pluginSurface().setProjectEnabled(t.path,e.name,!1,m(e.dir)),e.weavePack&&O(t,e.id,!1,{projects:this.deps.projects}),this.rememberEnabled(t,e.id,!1)}async uninstall(t,e,n){const s=this.requireProject(t),i=await this.resolvePack(e,n);return await this.applyDisabled(s,i),p(i)&&i.targets.includes("claude")&&await this.pluginSurface().uninstall(i.name,m(i.dir)),{pack:await this.packInfo(this.requireProject(t),i),warnings:[]}}async removePackFiles(t,e,n){const s=x(t.path,e.id),i=await k(s);if(!i||n&&c.resolve(i.sourceRoot)!==c.resolve(n))return;const o=[];for(const[r,l]of Object.entries(i.files)){const u=c.join(t.path,...r.split("/"));await y(u)===l&&(await w(u,{force:!0}),o.push(r))}await w(s,{force:!0}),await _(t.path,o)}requireProject(t){const e=this.deps.projects.getById(t);if(!e)throw new ToolkitError(`Project not found: ${t}`,"not-found");return e}async resolvePack(t,e){this.packsCache=void 0;const n=(await this.allPacks()).filter(o=>o.id===t);if(e){const o=n.find(r=>c.resolve(r.sourceRoot)===c.resolve(e));if(o)return o}const i=n.find(o=>o.provenance==="builtin")??n[0];if(!i)throw new ToolkitError(`Toolkit pack not found: ${t}`,"not-found");return i}async computeEnabled(t,e){const n=[],s=await this.readRecordHealed(t.path,e.id);if((await R(t.path,e)).length>0){let o=!!s;if(s){for(const r of Object.keys(s.files))if(!await M(c.join(t.path,...r.split("/")))){o=!1;break}}n.push(o)}if(p(e)&&e.targets.includes("claude")){const o=this.pluginSurface(),r=m(e.dir);n.push(await o.isInstalled(e.name,r)&&await o.readProjectEnabled(t.path,e.name,r))}return e.weavePack&&n.push(V(t,e.id,{states:this.deps.states,defaultEnabled:!1})),n.length>0?n.every(Boolean):!1}async installedScopes(t){const e=(await this.pluginSurface().readInstalled())[t];return!e||e.length===0?void 0:[...new Set(e.map(s=>s.scope??"user"))]}async packInfo(t,e){const n=await this.computeEnabled(t,e),s=m(e.dir),i=p(e)&&e.targets.includes("claude")?this.pluginSurface().pluginKey(e.name,s):void 0,o=i?await this.installedScopes(i):void 0,r={...e,...s?{namespace:s}:{},enabled:n,installed:await this.computeInstalled(t,e,n),...i?{key:i}:{},...o&&o.length>0?{scopes:o}:{}};if(e.weavePack){const l=this.deps.plugins.list().find(u=>u.id===e.id);l&&(r.weave={status:l.status,ruleCount:l.ruleCount,...l.error?{error:l.error}:{}})}return r}async computeInstalled(t,e,n){return e.weavePack&&!p(e)?!0:p(e)&&e.targets.includes("claude")?this.pluginSurface().isInstalled(e.name,m(e.dir)):n||!!await this.readRecordHealed(t.path,e.id)}enabledIndex(t){const n=this.deps.projects.getMeta(t.id)?.toolkit,s=n&&typeof n=="object"?n.enabled:void 0;return Array.isArray(s)?s.filter(i=>typeof i=="string"):[]}rememberEnabled(t,e,n){const s=new Set(this.enabledIndex(t));n?s.add(e):s.delete(e),this.deps.projects.setMeta(t.id,{toolkit:{enabled:[...s].sort()}})}async healMeta(t,e){const n=[...new Set(e.filter(r=>r.enabled).map(r=>r.id))].sort(),s=[...new Set(this.enabledIndex(t))].sort(),i=new Set(e.map(r=>r.id)),o=[...new Set([...s.filter(r=>!i.has(r)),...n])].sort();o.join(`
3
- `)!==s.join(`
4
- `)&&this.deps.projects.setMeta(t.id,{toolkit:{enabled:o}})}pluginSurface(){return this.deps.pluginSurface??$("claude")}async readRecordHealed(t,e){const n=x(t,e),s=await k(n);if(!s)return null;const i=v(c.relative(t,this.pluginSurface().projectPluginDir(t))),o=Object.keys(s.files).filter(l=>l.startsWith(`${i}/`));if(o.length===0)return s;const r=[];for(const l of o){const u=c.join(t,...l.split("/"));await y(u)===s.files[l]&&(await w(u,{force:!0}),r.push(l)),delete s.files[l]}return await _(t,r),await F(n,JSON.stringify(s,null,2)+`
5
- `,"utf8"),s}async scanLegacyCopies(t){const e=this.pluginSurface().projectPluginDir(t.path);let n;try{n=await h(e,{withFileTypes:!0})}catch{return[]}return n.map(s=>v(c.relative(t.path,c.join(e,s.name)))).sort()}async cleanupLegacyCopies(t){const e=this.requireProject(t),n=this.pluginSurface().projectPluginDir(e.path);let s;try{s=await h(n,{withFileTypes:!0})}catch{return{removed:[]}}const i=[];for(const o of s){const r=c.join(n,o.name);await w(r,{recursive:!0,force:!0}),i.push(v(c.relative(e.path,r)))}try{(await h(n)).length===0&&await w(n,{recursive:!0,force:!0})}catch{}return{removed:i.sort()}}}
1
+ import{cp as I,mkdir as P,readFile as T,readdir as h,rm as w,writeFile as F}from"node:fs/promises";import{spawn as S}from"node:child_process";import{homedir as H}from"node:os";import c from"node:path";import{hashFile as y}from"../install/fingerprint.js";import{fileAssetTargetDir as L}from"../install/file-assets.js";import{assetSurfaceFor as N,pluginSurfaceFor as $}from"../surfaces.js";import{marketLibraryRoots as q}from"../market/resolve.js";import{marketIdOf as m}from"../market/identity.js";import{installPluginPack as A}from"../install/plugin-install.js";import{effective as V,setProjectEnabled as O}from"./plugin-project-state.js";import{builtinToolkitRoot as K,discoverToolkitPacks as B}from"./toolkit-scan.js";export class ToolkitError extends Error{code;constructor(t,e){super(t),this.code=e,this.name="ToolkitError"}}export const MIN_MOD_HOST_VERSION=[2,1,287];const W=1e4;export function isVersionAtLeast(a,t){const e=a.split(".").map(s=>Number.parseInt(s,10));for(let s=0;s<t.length;s++){const n=Number.isFinite(e[s])?e[s]:0;if(n>t[s])return!0;if(n<t[s])return!1}return!0}let b;function J(a){if(!(a.exitCode!==null||a.signalCode!==null)){if(process.platform==="win32"&&a.pid!==void 0){S("taskkill",["/pid",String(a.pid),"/t","/f"],{windowsHide:!0,stdio:"ignore"}).unref();return}a.kill("SIGKILL")}}export async function detectClaudeVersion(){if(b&&Date.now()-b.at<6e5)return b.value;const a=await new Promise(t=>{let e="";const s=process.platform==="win32"?S("claude --version",{windowsHide:!0,shell:!0,stdio:["ignore","pipe","pipe"]}):S("claude",["--version"],{windowsHide:!0,stdio:["ignore","pipe","pipe"]}),n=setTimeout(()=>{J(s),t(null)},5e3);s.stdout?.on("data",i=>e+=i.toString()),s.on("error",()=>{clearTimeout(n),t(null)}),s.on("close",()=>{clearTimeout(n);const i=/(\d+\.\d+\.\d+)/.exec(e);t(i?i[1]:null)})});return a!==null&&(b={value:a,at:Date.now()}),a}async function C(a,t,e){let s;try{s=await h(c.join(a,...t.split("/").filter(Boolean)),{withFileTypes:!0})}catch{return}for(const n of s){if(n.name==="node_modules"||n.name===".git")continue;const i=t?`${t}/${n.name}`:n.name;n.isDirectory()?await C(a,i,e):e.push({rel:i,abs:c.join(a,...i.split("/"))})}}const p=a=>a.ccPack||a.kind==="mod";async function R(a,t){const e=[];if(t.skills.length>0)for(const s of t.targets){if(s==="claude"&&p(t))continue;const n=N(s).projectSkillsDir();for(const i of t.skills){const o=[];await C(c.join(t.dir,"skills",i),"",o);for(const r of o)e.push({src:r.abs,rel:`${n}/${i}/${r.rel}`})}}if(!p(t))for(const s of t.targets)for(const[n,i]of[["commands","command"],["agents","agent"]]){const o=c.join(t.dir,n),r=[];if(await C(o,"",r),r.length===0)continue;const l=G(i,s,a);if(l)for(const u of r){const g=u.rel.split("/").pop();e.push({src:u.abs,rel:v(c.relative(a,c.join(l,g)))})}}return e}function G(a,t,e){try{return L(a,t,"project",e,H())}catch{return null}}function v(a){return a.split(c.sep).join("/")}function x(a,t){return c.join(a,".weave","toolkit",`${t}.json`)}async function k(a){try{const t=JSON.parse(await T(a,"utf8"));return t&&typeof t=="object"&&typeof t.packId=="string"?t:null}catch{return null}}export async function projectFileNamespaces(a){const t=new Map,e=c.join(a,".weave","toolkit");let s;try{s=await h(e)}catch{return t}for(const n of s){if(!n.endsWith(".json"))continue;const i=await k(c.join(e,n)),o=i?m(i.sourceDir):void 0;if(!(!i||!o))for(const r of Object.keys(i.files))t.set(r,o)}return t}export async function projectPackFiles(a){const t=new Map,e=c.join(a,".weave","toolkit");let s;try{s=await h(e)}catch{return t}for(const n of s){if(!n.endsWith(".json"))continue;const i=await k(c.join(e,n));if(i)for(const[o,r]of Object.entries(i.files))typeof r=="string"&&t.set(o,r)}return t}async function M(a){try{return await T(a),!0}catch{return!1}}async function _(a,t){const e=new Set;for(const n of t){let i=c.posix.dirname(n);for(;i&&i!=="."&&i!=="/";)e.add(i),i=c.posix.dirname(i)}const s=[...e].sort((n,i)=>i.split("/").length-n.split("/").length);for(const n of s){const i=c.join(a,...n.split("/"));try{(await h(i)).length===0&&await w(i,{recursive:!0,force:!0})}catch{}}}export function evaluateModHostGate(a){return process.env.WEAVE_SKIP_MOD_VERSION_CHECK==="1"?{ok:!0}:a?isVersionAtLeast(a,MIN_MOD_HOST_VERSION)?{ok:!0}:{ok:!1,message:`\u5BBF\u4E3B Claude Code ${a} \u4F4E\u4E8E 2.1.287\uFF0Cmod \u5DE5\u5177\u5305\u4E0D\u843D\u76D8\uFF08\u8BF7\u5148\u5347\u7EA7 Claude Code\uFF09`}:{ok:!0,warning:"\u65E0\u6CD5\u68C0\u6D4B\u5BBF\u4E3B Claude Code \u7248\u672C\uFF0Cmod \u5DE5\u5177\u5305\u9700\u8981 \u2265 2.1.287 \u624D\u80FD\u88AB\u52A0\u8F7D"}}export class ToolkitService{deps;packsCache;constructor(t){this.deps=t}sources(){const e=[{root:this.deps.builtinRoot??K(),provenance:"builtin"}];if(this.deps.libraries)for(const n of q(this.deps.libraries))e.push({root:n,provenance:"market"});const s=new Set;return e.filter(n=>{const i=c.resolve(n.root);return s.has(i)?!1:(s.add(i),!0)})}async allPacks(){const t=this.sources(),e=t.map(r=>c.resolve(r.root)).join("|"),s=Date.now(),n=this.packsCache;if(n&&n.key===e&&s-n.at<W)return n.packs;const i=Promise.all(t.map(r=>B(r))).then(r=>r.flat()),o={at:s,key:e,packs:i};return this.packsCache=o,i.catch(()=>{this.packsCache===o&&(this.packsCache=void 0)}),i}async list(t){const e=this.requireProject(t),s=await this.allPacks(),n=await Promise.all(s.map(i=>this.packInfo(e,i)));return await this.healMeta(e,n),{packs:n,legacyCopies:await this.scanLegacyCopies(e)}}async enable(t,e,s){const n=this.requireProject(t),i=await this.resolvePack(e,s),o=[];if(i.kind==="mod"&&i.targets.includes("claude")){const d=this.deps.claudeVersion??detectClaudeVersion,f=evaluateModHostGate(await d());if(!f.ok)throw new ToolkitError(f.message??"mod host gate","host-version");f.warning&&o.push(f.warning)}const r=n.path;if(p(i)&&i.targets.includes("claude")){const d=this.pluginSurface();await A(d,i),await d.setProjectEnabled(r,i.name,!0,m(i.dir))}const u=x(r,i.id),g=await this.readRecordHealed(r,i.id),j=g??{packId:i.id,provenance:i.provenance,sourceRoot:i.sourceRoot,sourceDir:i.dir,enabledAt:new Date().toISOString(),files:{}};let D=g===null;const E=await R(r,i);for(const d of E){const f=c.join(r,...d.rel.split("/"));if(j.files[d.rel]!==void 0){await M(f)||(await P(c.dirname(f),{recursive:!0}),await I(d.src,f));continue}await y(f)==="missing"&&(await P(c.dirname(f),{recursive:!0}),await I(d.src,f),j.files[d.rel]=await y(d.src),D=!0)}return E.length>0?D&&(await P(c.dirname(u),{recursive:!0}),await F(u,JSON.stringify(j,null,2)+`
2
+ `,"utf8")):g&&await w(u,{force:!0}),i.weavePack&&O(n,i.id,!0,{projects:this.deps.projects}),this.rememberEnabled(n,i.id,!0),{pack:await this.packInfo(this.requireProject(t),i),warnings:o}}async disable(t,e,s){const n=this.requireProject(t),i=await this.resolvePack(e,s);return await this.applyDisabled(n,i),{pack:await this.packInfo(this.requireProject(t),i),warnings:[]}}async applyDisabled(t,e){await this.removePackFiles(t,e,e.sourceRoot),p(e)&&e.targets.includes("claude")&&await this.pluginSurface().setProjectEnabled(t.path,e.name,!1,m(e.dir)),e.weavePack&&O(t,e.id,!1,{projects:this.deps.projects}),this.rememberEnabled(t,e.id,!1)}async uninstall(t,e,s){const n=this.requireProject(t),i=await this.resolvePack(e,s);return await this.applyDisabled(n,i),p(i)&&i.targets.includes("claude")&&await this.pluginSurface().uninstall(i.name,m(i.dir)),{pack:await this.packInfo(this.requireProject(t),i),warnings:[]}}async removePackFiles(t,e,s){const n=x(t.path,e.id),i=await k(n);if(!i||s&&c.resolve(i.sourceRoot)!==c.resolve(s))return;const o=[];for(const[r,l]of Object.entries(i.files)){const u=c.join(t.path,...r.split("/"));await y(u)===l&&(await w(u,{force:!0}),o.push(r))}await w(n,{force:!0}),await _(t.path,o)}requireProject(t){const e=this.deps.projects.getById(t);if(!e)throw new ToolkitError(`Project not found: ${t}`,"not-found");return e}async resolvePack(t,e){this.packsCache=void 0;const s=(await this.allPacks()).filter(o=>o.id===t);if(e){const o=s.find(r=>c.resolve(r.sourceRoot)===c.resolve(e));if(o)return o}const i=s.find(o=>o.provenance==="builtin")??s[0];if(!i)throw new ToolkitError(`Toolkit pack not found: ${t}`,"not-found");return i}async computeEnabled(t,e){const s=[],n=await this.readRecordHealed(t.path,e.id);if((await R(t.path,e)).length>0){let o=!!n;if(n){for(const r of Object.keys(n.files))if(!await M(c.join(t.path,...r.split("/")))){o=!1;break}}s.push(o)}if(p(e)&&e.targets.includes("claude")){const o=this.pluginSurface(),r=m(e.dir);s.push(await o.isInstalled(e.name,r)&&await o.readProjectEnabled(t.path,e.name,r))}return e.weavePack&&s.push(V(t,e.id,{states:this.deps.states,defaultEnabled:!1})),s.length>0?s.every(Boolean):!1}async installedScopes(t){const e=(await this.pluginSurface().readInstalled())[t];return!e||e.length===0?void 0:[...new Set(e.map(n=>n.scope??"user"))]}async packInfo(t,e){const s=await this.computeEnabled(t,e),n=m(e.dir),i=p(e)&&e.targets.includes("claude")?this.pluginSurface().pluginKey(e.name,n):void 0,o=i?await this.installedScopes(i):void 0,r={...e,...n?{namespace:n}:{},enabled:s,installed:await this.computeInstalled(t,e,s),...i?{key:i}:{},...o&&o.length>0?{scopes:o}:{}};if(e.weavePack){const l=this.deps.plugins.list().find(u=>u.id===e.id);l&&(r.weave={status:l.status,ruleCount:l.ruleCount,...l.error?{error:l.error}:{}})}return r}async computeInstalled(t,e,s){return e.weavePack&&!p(e)?!0:p(e)&&e.targets.includes("claude")?this.pluginSurface().isInstalled(e.name,m(e.dir)):s||!!await this.readRecordHealed(t.path,e.id)}enabledIndex(t){const s=this.deps.projects.getMeta(t.id)?.toolkit,n=s&&typeof s=="object"?s.enabled:void 0;return Array.isArray(n)?n.filter(i=>typeof i=="string"):[]}rememberEnabled(t,e,s){const n=new Set(this.enabledIndex(t));s?n.add(e):n.delete(e),this.deps.projects.setMeta(t.id,{toolkit:{enabled:[...n].sort()}})}async healMeta(t,e){const s=[...new Set(e.filter(r=>r.enabled).map(r=>r.id))].sort(),n=[...new Set(this.enabledIndex(t))].sort(),i=new Set(e.map(r=>r.id)),o=[...new Set([...n.filter(r=>!i.has(r)),...s])].sort();o.join(`
3
+ `)!==n.join(`
4
+ `)&&this.deps.projects.setMeta(t.id,{toolkit:{enabled:o}})}pluginSurface(){return this.deps.pluginSurface??$("claude")}async readRecordHealed(t,e){const s=x(t,e),n=await k(s);if(!n)return null;const i=v(c.relative(t,this.pluginSurface().projectPluginDir(t))),o=Object.keys(n.files).filter(l=>l.startsWith(`${i}/`));if(o.length===0)return n;const r=[];for(const l of o){const u=c.join(t,...l.split("/"));await y(u)===n.files[l]&&(await w(u,{force:!0}),r.push(l)),delete n.files[l]}return await _(t,r),await F(s,JSON.stringify(n,null,2)+`
5
+ `,"utf8"),n}async scanLegacyCopies(t){const e=this.pluginSurface().projectPluginDir(t.path);let s;try{s=await h(e,{withFileTypes:!0})}catch{return[]}return s.map(n=>v(c.relative(t.path,c.join(e,n.name)))).sort()}async cleanupLegacyCopies(t){const e=this.requireProject(t),s=this.pluginSurface().projectPluginDir(e.path);let n;try{n=await h(s,{withFileTypes:!0})}catch{return{removed:[]}}const i=[];for(const o of n){const r=c.join(s,o.name);await w(r,{recursive:!0,force:!0}),i.push(v(c.relative(e.path,r)))}try{(await h(s)).length===0&&await w(s,{recursive:!0,force:!0})}catch{}return{removed:i.sort()}}}
@@ -1 +1 @@
1
- import{rm as g,cp as p,mkdir as d}from"node:fs/promises";import{homedir as y}from"node:os";import f from"node:path";import{installMcp as w,uninstallMcp as S}from"./installer.js";import{writeGlobalMcpServer as k}from"./global-config.js";import{writeCodexMcpServer as x}from"./codex-toml.js";import{applyStatuslineConfig as h}from"../statusline/manager.js";import{readStatuslineConfig as C}from"../statusline/manager.js";import{buildLibraryIndex as l,findSkillSource as E,findMcpSource as F,findFileAssetSource as M}from"./library-index.js";import{detectUpdates as $}from"./updates.js";import{materializePluginTemplate as b}from"./plugin-templates.js";import{applyInitFile as v}from"./init-files.js";import{FILE_ASSET_SPECS as A,fileAssetTargetDir as D,isFileAssetCategory as T}from"./file-assets.js";import{assetSurfaceFor as u}from"../surfaces.js";export async function applyUpdate(r,o,m,s){const c=m??(s?await l(s):await l([])),t=o.agent??"claude";if(o.category==="skill"){const e=E(c,o.name);if(!e)throw new Error(`No library source for skill "${o.name}"`);const n=o.scope==="user"?u(t).globalSkillsRoots(y())[0]:f.join(r,...u(t).projectSkillsDir().split("/")),i=f.join(n,o.name);await g(i,{recursive:!0,force:!0}),await p(e.dirPath,i,{recursive:!0});return}if(T(o.category)){const e=M(c,o.category,o.name);if(!e)throw new Error(`No library source for ${o.category} "${o.name}"`);const n=D(o.category,t,o.scope,r,y());await d(n,{recursive:!0});const i=f.join(n,`${o.name}${A[o.category].ext}`);await g(i,{force:!0}),await p(e.filePath,i);return}const a=F(c,o.name);if(!a)throw new Error(`No library source for MCP "${o.name}"`);if(t==="codex"){await x(o.name,a.config);return}if(o.scope==="user")await k(o.name,a.config);else{try{await S(r,o.name)}catch{}await w(r,o.name,a.config)}}export async function syncAll(r,o,m,s){const c=s??await l(o),t=await $(r,o,m,c),a=[];for(const e of t.skills)e.custom||!e.outdated||(await applyUpdate(r,{category:"skill",name:e.name,scope:e.scope,agent:e.agent},c),a.push({category:"skill",name:e.name,scope:e.scope,agent:e.agent}));for(const e of t.mcp)e.custom||!e.outdated||(await applyUpdate(r,{category:"mcp",name:e.name,scope:e.scope,agent:e.agent},c),a.push({category:"mcp",name:e.name,scope:e.scope,agent:e.agent}));for(const e of t.files)e.custom||!e.outdated||(await applyUpdate(r,{category:e.category,name:e.name,scope:e.scope},c),a.push({category:e.category,name:e.name,scope:e.scope}));for(const e of t.pluginTemplates)e.outdated&&(await b(e),a.push({category:e.category,name:e.name,scope:"project"}));for(const e of t.initFiles)await v(e)&&a.push({category:"init",name:e.relPath,scope:"project"});return{updated:a,skipped:0}}export async function refreshStatusline(r){const o=await C(r);await h(r,o)}
1
+ import{rm as u,cp as y,mkdir as d}from"node:fs/promises";import{homedir as f}from"node:os";import l from"node:path";import{applyStatuslineConfig as w}from"../statusline/manager.js";import{readStatuslineConfig as S}from"../statusline/manager.js";import{buildLibraryIndex as g,findSkillSource as k,findMcpSource as h,findFileAssetSource as x}from"./library-index.js";import{detectUpdates as E}from"./updates.js";import{materializePluginTemplate as F}from"./plugin-templates.js";import{applyInitFile as $}from"./init-files.js";import{FILE_ASSET_SPECS as A,fileAssetTargetDir as C,isFileAssetCategory as b}from"./file-assets.js";import{assetSurfaceFor as p}from"../surfaces.js";export async function applyUpdate(t,o,m,i){const c=m??(i?await g(i):await g([])),a=o.agent??"claude";if(o.category==="skill"){const e=k(c,o.name);if(!e)throw new Error(`No library source for skill "${o.name}"`);const n=o.scope==="user"?p(a).globalSkillsRoots(f())[0]:l.join(t,...p(a).projectSkillsDir().split("/")),s=l.join(n,o.name);await u(s,{recursive:!0,force:!0}),await y(e.dirPath,s,{recursive:!0});return}if(b(o.category)){const e=x(c,o.category,o.name);if(!e)throw new Error(`No library source for ${o.category} "${o.name}"`);const n=C(o.category,a,o.scope,t,f());await d(n,{recursive:!0});const s=l.join(n,`${o.name}${A[o.category].ext}`);await u(s,{force:!0}),await y(e.filePath,s);return}const r=h(c,o.name);if(!r)throw new Error(`No library source for MCP "${o.name}"`);await p(a).mcpWrite(o.scope,t,f(),o.name,r.config)}export async function syncAll(t,o,m,i){const c=i??await g(o),a=await E(t,o,m,c),r=[];for(const e of a.skills)e.custom||!e.outdated||(await applyUpdate(t,{category:"skill",name:e.name,scope:e.scope,agent:e.agent},c),r.push({category:"skill",name:e.name,scope:e.scope,agent:e.agent}));for(const e of a.mcp)e.custom||!e.outdated||(await applyUpdate(t,{category:"mcp",name:e.name,scope:e.scope,agent:e.agent},c),r.push({category:"mcp",name:e.name,scope:e.scope,agent:e.agent}));for(const e of a.files)e.custom||!e.outdated||(await applyUpdate(t,{category:e.category,name:e.name,scope:e.scope},c),r.push({category:e.category,name:e.name,scope:e.scope}));for(const e of a.pluginTemplates)e.outdated&&(await F(e),r.push({category:e.category,name:e.name,scope:"project"}));for(const e of a.initFiles)await $(e)&&r.push({category:"init",name:e.relPath,scope:"project"});return{updated:r,skipped:0}}export async function refreshStatusline(t){const o=await S(t);await w(t,o)}
@@ -15,8 +15,18 @@ export interface CategorySurface {
15
15
  export interface CategoryCapability {
16
16
  surfaces: CategorySurface[];
17
17
  }
18
+ /**
19
+ * 某 harness 的 statusline 面:`tui` 答「TUI 里能不能注入」(今天只有 claude
20
+ * 能),`segments` 是它能渲染的块。缺席 = 该 harness 没有 statusline 概念。
21
+ */
22
+ export interface StatuslineCapability {
23
+ harness: Harness;
24
+ tui: 'injectable' | 'none';
25
+ segments: string[];
26
+ }
18
27
  /** `GET /capabilities` 响应体(web 镜像于 api.ts,唯一事实源在这里)。 */
19
28
  export interface InstallCapabilities {
20
29
  categories: Record<InstallCategory, CategoryCapability>;
30
+ statusline: StatuslineCapability[];
21
31
  }
22
32
  export declare function installCapabilities(): InstallCapabilities;
@@ -1 +1 @@
1
- import n from"node:path";import{homedir as a}from"node:os";import{HARNESS_KEYS as l}from"@weavecode/core";import{FILE_ASSET_SPECS as m,FILE_ASSET_CATEGORIES as h}from"./file-assets.js";import{assetSurfaces as u}from"../surfaces.js";function f(t,c){return n.join("~",n.relative(c,t)).split(n.sep).join("/")}export function installCapabilities(){const t=a(),c={};for(const s of h){const o=m[s].dirs,r=[];for(const e of l){const p=o[e];if(!p)continue;const i=[];p.project&&i.push("project"),p.user&&i.push("user"),i.length>0&&r.push({harness:e,scopes:i})}c[s]={surfaces:r}}return c.skill={surfaces:u().map(s=>{const o=[],r={};s.projectSkillsDir()&&(o.push("project"),r.project=s.projectSkillsDir());const e=s.globalSkillsRoots(t)[0];return e&&(o.push("user"),r.user=f(e,t)),{harness:s.harness,scopes:o,dest:r}})},c.mcp={surfaces:u().map(s=>{const o={},r=s.mcpDest("project",t),e=s.mcpDest("user",t);return r&&(o.project=r),e&&(o.user=f(e,t)),{harness:s.harness,scopes:s.mcpScopes(),dest:o}}).filter(s=>s.scopes.length>0)},{categories:c}}
1
+ import p from"node:path";import{homedir as m}from"node:os";import{HARNESS_KEYS as l}from"@weavecode/core";import{FILE_ASSET_SPECS as S,FILE_ASSET_CATEGORIES as h}from"./file-assets.js";import{assetSurfaces as u,statuslineSurfaces as j}from"../surfaces.js";function f(r,c){return p.join("~",p.relative(c,r)).split(p.sep).join("/")}export function installCapabilities(){const r=m(),c={};for(const s of h){const t=S[s].dirs,e=[];for(const o of l){const n=t[o];if(!n)continue;const i=[];n.project&&i.push("project"),n.user&&i.push("user"),i.length>0&&e.push({harness:o,scopes:i})}c[s]={surfaces:e}}c.skill={surfaces:u().map(s=>{const t=[],e={};s.projectSkillsDir()&&(t.push("project"),e.project=s.projectSkillsDir());const o=s.globalSkillsRoots(r)[0];return o&&(t.push("user"),e.user=f(o,r)),{harness:s.harness,scopes:t,dest:e}})},c.mcp={surfaces:u().map(s=>{const t={},e=s.mcpDest("project",r),o=s.mcpDest("user",r);return e&&(t.project=e),o&&(t.user=f(o,r)),{harness:s.harness,scopes:s.mcpScopes(),dest:t}}).filter(s=>s.scopes.length>0)};const a=j().map(({harness:s,surface:t})=>({harness:s,tui:t.tui,segments:[...t.segments]}));return{categories:c,statusline:a}}
@@ -1,38 +1,20 @@
1
1
  import type { McpServerConfig } from './installer.js';
2
2
  /**
3
- * Codex config.toml 的 **fs 侧**(路径解析 + MCP servers 表示法)。
4
- *
5
- * 行级增量编辑器(纯文本层)已下沉 @weavecode/harness 的 `codex/toml.ts`
6
- * (P2 专属格式下沉);本模块原位置 re-export 下沉符号,存量 import 零改动。
7
- * MCP 表示法的下沉留后续批次(其类型 McpServerConfig 仍住 server)。
3
+ * Codex config.toml 的 server 侧入口:路径解析 + 把 harness 里的表示法绑上默认
4
+ * 路径。行级编辑与 `[mcp_servers.<name>]` 这一形都住 @weavecode/harness
5
+ * (`codex/toml.ts` / `codex/mcp.ts`),本模块原位置 re-export,存量 import 零改动。
8
6
  */
9
- export { CodexTomlCorruptError, parseDottedKey, sectionHeaderKey, renderTomlKey, renderTomlHeader, renderTomlString, splitTomlSections, upsertTomlSection, removeTomlSection, parseSectionEntries, upsertCodexPluginEnabledInText, } from '@weavecode/harness';
7
+ export { CodexTomlCorruptError, parseDottedKey, sectionHeaderKey, renderTomlKey, renderTomlHeader, renderTomlString, splitTomlSections, upsertTomlSection, removeTomlSection, parseSectionEntries, upsertCodexPluginEnabledInText, readCodexMcpFromText, upsertCodexMcpInText, removeCodexMcpFromText, } from '@weavecode/harness';
10
8
  export type { TomlSection } from '@weavecode/harness';
11
- /** Absolute path of the Codex user config (`~/.codex/config.toml`),
12
- * respecting the test override. 批 4a:路径本体在 AssetSurface(`userConfigFile`)。 */
9
+ /** Absolute path of the Codex user config (`~/.codex/config.toml`).
10
+ * 批 4a:路径本体在 AssetSurface(`userConfigFile`)。 */
13
11
  export declare function codexConfigFile(home?: string): string;
14
- /** Read the `mcp_servers` map from config.toml text (empty when absent or
15
- * unparseable). stdio servers only — entries without a `command` (e.g. remote
16
- * url servers) are skipped. */
17
- export declare function readCodexMcpFromText(text: string): Record<string, McpServerConfig>;
18
- /** Insert or replace an `[mcp_servers.<name>]` entry in config.toml text,
19
- * preserving everything else. Merge-validated: if preserving user-added keys
20
- * produces invalid TOML, the section is written clean instead. */
21
- export declare function upsertCodexMcpInText(text: string, name: string, config: McpServerConfig): string;
22
- /** Remove an `[mcp_servers.<name>]` entry (with its sub-sections) from
23
- * config.toml text. Returns whether a section was removed. */
24
- export declare function removeCodexMcpFromText(text: string, name: string): {
25
- text: string;
26
- removed: boolean;
27
- };
28
- export declare function setCodexConfigFileOverride(file: string | null): void;
29
12
  /** Read MCP servers from `~/.codex/config.toml` (empty when absent/corrupt). */
30
13
  export declare function readCodexMcpServers(file?: string): Promise<Record<string, McpServerConfig>>;
31
14
  /** Whether an `[mcp_servers.<name>]` section exists (cheap line-level check). */
32
15
  export declare function codexMcpExists(name: string, file?: string): Promise<boolean>;
33
- /** Insert or replace an MCP server in `~/.codex/config.toml`. Refuses to
34
- * write a file that does not parse (protects the user's hand-written config);
35
- * creates the file when absent. */
16
+ /** Insert or replace an MCP server in `~/.codex/config.toml` (refuses a
17
+ * config that does not parse; creates the file when absent). */
36
18
  export declare function writeCodexMcpServer(name: string, config: McpServerConfig, file?: string): Promise<void>;
37
19
  /** Remove an MCP server section from `~/.codex/config.toml`. Returns whether
38
20
  * a section was removed (false = was not configured). */
@@ -1 +1 @@
1
- import{readFile as p,writeFile as g,access as C,mkdir as E}from"node:fs/promises";import{homedir as M}from"node:os";import b from"node:path";import{parse as m}from"smol-toml";import{assetSurfaceFor as $}from"../surfaces.js";import{CodexTomlCorruptError as d,parseSectionEntries as h,removeTomlSection as k,renderTomlHeader as F,renderTomlKey as O,renderTomlString as l,sameKey as y,splitTomlSections as v,upsertTomlSection as w}from"@weavecode/harness";export{CodexTomlCorruptError,parseDottedKey,sectionHeaderKey,renderTomlKey,renderTomlHeader,renderTomlString,splitTomlSections,upsertTomlSection,removeTomlSection,parseSectionEntries,upsertCodexPluginEnabledInText}from"@weavecode/harness";export function codexConfigFile(e=M()){return S??$("codex").userConfigFile(e)}export function readCodexMcpFromText(e){let r;try{r=m(e)}catch{return{}}const n=r.mcp_servers;if(!n||typeof n!="object"||Array.isArray(n))return{};const t={};for(const[s,o]of Object.entries(n)){const a=j(o);a&&(t[s]=a)}return t}function j(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const r=e;if(typeof r.command!="string")return null;const n={command:r.command};if(Array.isArray(r.args)){const t=r.args.filter(s=>typeof s=="string");t.length>0&&(n.args=t)}if(r.env&&typeof r.env=="object"&&!Array.isArray(r.env)){const t={};for(const[s,o]of Object.entries(r.env))typeof o=="string"&&(t[s]=o);Object.keys(t).length>0&&(n.env=t)}return n}function T(e,r,n,t,s){const o=[`command = ${l(r.command)}`];if(r.args&&r.args.length>0&&o.push(`args = [${r.args.map(l).join(", ")}]`),s&&n){const i=new Set(["command","args","env"]);for(const[c,u]of h(n.lines.slice(1)))i.has(c)||o.push(u)}const a=Object.entries(r.env??{}),f=s&&t?h(t.lines.slice(1)).filter(([i])=>!(i in(r.env??{}))):[];if(a.length>0||f.length>0){o.push(""),o.push(F([...e,"env"]));for(const[i,c]of a)o.push(`${O(i)} = ${l(c)}`);for(const[,i]of f)o.push(i)}return o}export function upsertCodexMcpInText(e,r,n){const t=["mcp_servers",r],s=A(e);if(s)throw s;const o=v(e),a=o.find(c=>y(c.key,t)),f=o.find(c=>y(c.key,[...t,"env"])),i=w(e,t,T(t,n,a,f,!0));try{return m(i),i}catch{const c=w(e,t,T(t,n,a,f,!1));try{return m(c),c}catch(u){throw new d(`unable to produce valid TOML for [mcp_servers.${r}]: ${u instanceof Error?u.message:String(u)}`)}}}export function removeCodexMcpFromText(e,r){return k(e,["mcp_servers",r])}function A(e){return v(e)[0].lines.find(t=>/^\s*mcp_servers\s*=/.test(t))?new d('~/.codex/config.toml defines "mcp_servers" as an inline value \u2014 weave only manages the [mcp_servers.<name>] section form'):null}let S=null;export function setCodexConfigFileOverride(e){S=e}async function _(e){try{return await p(e,"utf-8")}catch{return""}}async function x(e){try{return await C(e),!0}catch{return!1}}export async function readCodexMcpServers(e=codexConfigFile()){return await x(e)?readCodexMcpFromText(await p(e,"utf-8")):{}}export async function codexMcpExists(e,r=codexConfigFile()){return await x(r)?K(await p(r,"utf-8"),["mcp_servers",e]):!1}function K(e,r){return v(e).some(n=>y(n.key,r))}export async function writeCodexMcpServer(e,r,n=codexConfigFile()){const t=await _(n);if(t.trim()!=="")try{m(t)}catch(s){throw new d(`${n} is not valid TOML \u2014 refusing to write: ${s instanceof Error?s.message:String(s)}`)}await E(b.dirname(n),{recursive:!0}),await g(n,upsertCodexMcpInText(t,e,r),"utf-8")}export async function removeCodexMcpServer(e,r=codexConfigFile()){if(!await x(r))return!1;const n=await p(r,"utf-8");try{m(n)}catch(o){throw new d(`${r} is not valid TOML \u2014 refusing to write: ${o instanceof Error?o.message:String(o)}`)}const{text:t,removed:s}=removeCodexMcpFromText(n,e);return s&&await g(r,t,"utf-8"),s}
1
+ import{homedir as t}from"node:os";import{assetSurfaceFor as x}from"../surfaces.js";import{codexMcpExists as c,readCodexMcpServers as d,removeCodexMcpServer as n,writeCodexMcpServer as p}from"@weavecode/harness";export{CodexTomlCorruptError,parseDottedKey,sectionHeaderKey,renderTomlKey,renderTomlHeader,renderTomlString,splitTomlSections,upsertTomlSection,removeTomlSection,parseSectionEntries,upsertCodexPluginEnabledInText,readCodexMcpFromText,upsertCodexMcpInText,removeCodexMcpFromText}from"@weavecode/harness";export function codexConfigFile(e=t()){return x("codex").userConfigFile(e)}export function readCodexMcpServers(e=codexConfigFile()){return d(e)}export function codexMcpExists(e,r=codexConfigFile()){return c(e,r)}export function writeCodexMcpServer(e,r,o=codexConfigFile()){return p(e,r,o)}export function removeCodexMcpServer(e,r=codexConfigFile()){return n(e,r)}
@@ -20,10 +20,8 @@ import { type InstallLinkMode } from '../market/install-link.js';
20
20
  * host config: `~/.claude/settings.json` (`enabledPlugins`) for Claude and
21
21
  * `~/.codex/config.toml` (`[plugins."key"] enabled`) for Codex.
22
22
  */
23
- /** `~/.claude.json` could not be parsed; refuse to write to avoid corruption. */
24
- export declare class ClaudeJsonCorruptError extends Error {
25
- constructor(message: string);
26
- }
23
+ /** `~/.claude.json` 解析不了就拒写(实现在 harness 的 claude 资产面)。 */
24
+ export { ClaudeJsonCorruptError } from '@weavecode/harness';
27
25
  /** Where a global skill came from — used as the grouping key in the UI. */
28
26
  export type GlobalSkillSource = `${Harness}-personal` | `${Harness}-plugin`;
29
27
  /** A single resolved skill directory. `dir` is absolute (may be a symlink). */
@@ -1,2 +1 @@
1
- import{readFile as x,writeFile as W,readdir as m,access as _,stat as j,realpath as $}from"node:fs/promises";import{homedir as f}from"node:os";import l from"node:path";import{AssetNotFoundError as g}from"./installer.js";import{FileTooLargeError as z,MAX_BYTES as J}from"./reader.js";import{assetSurfaceFor as h,assetSurfaces as O,pluginStateFor as S,pluginSurfaceFor as R}from"../surfaces.js";import{installForm as L,linkOrCopy as I,removeInstalled as U}from"../market/install-link.js";export class ClaudeJsonCorruptError extends Error{constructor(e){super(e),this.name="ClaudeJsonCorruptError"}}const B=".claude.json",K=".claude/plugins/installed_plugins.json";function P(){return l.join(f(),B)}function k(){return h("claude").globalSkillsRoots(f())[0]}function N(t=f()){return l.join(t,K)}function X(){return h("claude").userConfigFile(f())}function M(){return h("codex").globalSkillsRoots(f())[0]}function Y(){return h("codex").globalSkillsRoots(f())}function C(){const t=h("codex").pluginCacheDir?.(f());if(!t)throw new Error("codex has no plugin cache surface");return t}export function globalPersonalSkillRootsOwned(){return O().flatMap(t=>t.globalSkillsRoots(f()).map(e=>({harness:t.harness,root:e})))}export function globalPersonalSkillRoots(){return globalPersonalSkillRootsOwned().map(t=>t.root)}async function A(t){try{return await _(t),!0}catch{return!1}}async function y(t){try{return(await j(t)).isDirectory()}catch{return!1}}export async function readGlobalClaudeJson(){const t=P();if(!await A(t))return{};let e;try{e=await x(t,"utf-8")}catch{return{}}let n;try{n=JSON.parse(e)}catch(o){throw new ClaudeJsonCorruptError(`~/.claude.json is not valid JSON \u2014 refusing to write: ${o instanceof Error?o.message:String(o)}`)}if(!n||typeof n!="object"||Array.isArray(n))throw new ClaudeJsonCorruptError("~/.claude.json is not a JSON object \u2014 refusing to write");return n}export async function readGlobalMcpServers(){const e=(await readGlobalClaudeJson()).mcpServers;return!e||typeof e!="object"||Array.isArray(e)?{}:e}async function T(t){await W(P(),`${JSON.stringify(t,null,2)}
2
- `,"utf-8")}export async function writeGlobalMcpServer(t,e){const n=await readGlobalClaudeJson(),o=n.mcpServers??{};o[t]=e,n.mcpServers=o,await T(n)}export async function removeGlobalMcpServer(t){const e=await readGlobalClaudeJson(),n=e.mcpServers??{};t in n&&(delete n[t],e.mcpServers=n,await T(e))}export async function readGlobalSkills(){const t=k();try{const e=await m(t,{withFileTypes:!0}),n=[];for(const o of e)!o.isDirectory()&&!o.isSymbolicLink()||await y(l.join(t,o.name))&&n.push(o.name);return n.sort((o,r)=>o.localeCompare(r))}catch{return[]}}export async function installGlobalSkill(t,e,n="claude"){const o=h(n).globalSkillsRoots(f())[0],r=l.join(o,e);if(await A(r))throw new Error(`Global ${n} skill "${e}" already exists`);return await I(t,r),r}export async function uninstallGlobalSkill(t,e="claude"){const n=h(e).globalSkillsRoots(f())[0];await U(l.join(n,t))}async function F(t=f()){const e=N(t);if(!await A(e))return{};try{const n=JSON.parse(await x(e,"utf-8"));return n&&typeof n=="object"&&!Array.isArray(n)?n:{}}catch{return{}}}async function v(t){if(!await y(t))return[];let e;try{e=await m(t,{withFileTypes:!0})}catch{return[]}const n=[];for(const o of e){if(o.name.startsWith(".")||!o.isDirectory()&&!o.isSymbolicLink())continue;const r=l.join(t,o.name);if(!await y(r))continue;const i=await L(r);i!=="absent"&&n.push({name:o.name,dir:r,form:i})}return n.sort((o,r)=>o.name.localeCompare(r.name))}export async function installedPluginNamespaces(t=f()){const n=(await F(t)).plugins;if(!n||typeof n!="object"||Array.isArray(n))return[];const o=[];for(const[r,i]of Object.entries(n)){const{market:s}=R("claude").parsePluginKey(r);for(const a of Array.isArray(i)?i:[])a&&typeof a.installPath=="string"&&o.push({dir:a.installPath,namespace:s})}return o}async function q(){const e=(await F()).plugins;if(!e||typeof e!="object"||Array.isArray(e))return[];const n=[];for(const[o,r]of Object.entries(e)){const i=Array.isArray(r)?r:[];if(i.length===0)continue;const s=[...i].filter(d=>d&&typeof d.installPath=="string").pop();if(!s?.installPath)continue;const a=await v(l.join(s.installPath,"skills")),{name:c,market:p}=R("claude").parsePluginKey(o);n.push({source:"claude-plugin",label:c,namespace:p,skills:a,pluginKey:o})}return n.sort((o,r)=>o.label.localeCompare(r.label))}async function H(){const t=new Map;for(const n of Y())for(const o of await v(n))t.has(o.name)||t.set(o.name,o);return{source:"codex-personal",label:"Codex \xB7 \u4E2A\u4EBA",skills:[...t.values()].sort((n,o)=>n.name.localeCompare(o.name))}}async function Q(){const t=C();if(!await y(t))return[];const e=[];let n;try{n=await m(t,{withFileTypes:!0})}catch{return[]}for(const o of n){if(!o.isDirectory()&&!o.isSymbolicLink())continue;const r=l.join(t,o.name);let i;try{i=await m(r,{withFileTypes:!0})}catch{continue}for(const s of i){if(!s.isDirectory()&&!s.isSymbolicLink())continue;const a=l.join(r,s.name);let c;try{c=await m(a,{withFileTypes:!0})}catch{continue}const p=c.filter(w=>w.isDirectory()||w.isSymbolicLink());if(p.length===0)continue;const d=p.map(w=>w.name).sort().pop(),u=await v(l.join(a,d,"skills")),b=`${s.name}@${o.name}`;e.push({source:"codex-plugin",label:s.name,namespace:o.name,skills:u,pluginKey:b})}}return e.sort((o,r)=>o.label.localeCompare(r.label))}async function V(){const t=k(),e=await readGlobalSkills(),n=[];for(const o of e){const r=l.join(t,o),i=await L(r);i!=="absent"&&n.push({name:o,dir:r,form:i})}return{source:"claude-personal",label:"Claude Code \xB7 \u4E2A\u4EBA",skills:n}}export async function readGlobalSkillGroups(){const[t,e,n,o,r,i]=await Promise.all([V(),q(),H(),Q(),readClaudePluginEnabled(),readCodexPluginEnabled()]),s=(a,c)=>({...a,enabled:a.pluginKey?c[a.pluginKey]??!1:void 0});return[t,...e.map(a=>s(a,r)),n,...o.map(a=>s(a,i))]}export async function resolveGlobalSkillDir(t,e,n){if(t==="claude-personal"){const u=l.join(k(),e);return await y(u)?u:null}if(t==="codex-personal"){const u=l.join(M(),e);return await y(u)?u:null}if(t==="claude-plugin"){if(!n)return null;const b=(await F()).plugins;if(!b||typeof b!="object"||Array.isArray(b))return null;const w=b[n],E=[...Array.isArray(w)?w:[]].filter(G=>G&&typeof G.installPath=="string").pop();if(!E?.installPath)return null;const D=l.join(E.installPath,"skills",e);return await y(D)?D:null}if(!n)return null;const o=n.lastIndexOf("@");if(o<=0)return null;const r=n.slice(0,o),i=n.slice(o+1),s=l.join(C(),i,r);if(!await y(s))return null;let a;try{a=await m(s,{withFileTypes:!0})}catch{return null}const c=a.filter(u=>u.isDirectory()||u.isSymbolicLink());if(c.length===0)return null;const p=c.map(u=>u.name).sort().pop(),d=l.join(s,p,"skills",e);return await y(d)?d:null}function Z(t){const e=l.resolve(t),n=f();return O().find(r=>{const i=[...r.globalSkillsRoots(n)],s=r.pluginCacheDir?.(n);return s&&i.push(s),i.some(a=>e===l.resolve(a)||e.startsWith(l.resolve(a)+l.sep))})?.harness??"claude"}export async function listGlobalSkillFiles(t){const e=Z(t),n=[],o=async(r,i)=>{let s;try{s=await m(r,{withFileTypes:!0})}catch{return}for(const a of s){if(a.name.startsWith("."))continue;const c=l.join(r,a.name),p=i?`${i}/${a.name}`:a.name;if((a.isDirectory()||a.isSymbolicLink())&&await y(c))await o(c,p);else if(a.isFile()||a.isSymbolicLink())try{const d=await j(c);d.isFile()&&n.push({absPath:c,relPath:p,category:"skill",agent:e,mtimeMs:d.mtimeMs})}catch{}}};return await o(t,""),n.sort((r,i)=>r.relPath.localeCompare(i.relPath))}export async function readGlobalSkillFile(t,e){const n=l.normalize(e).replace(/\\/g,"/");if(l.isAbsolute(e))throw new g(`Absolute paths are not allowed: "${e}"`);if(n.split("/").some(p=>p===".."))throw new g(`Path traversal is not allowed: "${e}"`);const o=l.join(t,n);let r;try{r=await j(o)}catch{throw new g(`File not found: "${e}"`)}if(!r.isFile())throw new g(`Not a file: "${e}"`);if(r.size>J)throw new z(`File exceeds ${J} bytes: "${e}"`);let i=t,s=o;try{[i,s]=await Promise.all([$(t),$(o)])}catch{throw new g(`File not found: "${e}"`)}const a=i.endsWith(l.sep)?i:i+l.sep;if(s!==i&&!s.startsWith(a))throw new g(`Resolved path escapes the skill directory: "${e}"`);return{content:await x(o,"utf-8"),size:r.size}}export async function readClaudePluginEnabled(){return S("claude").readGlobalEnabled()}export async function writeClaudePluginEnabled(t,e){return S("claude").writeGlobalEnabled(t,e)}export async function readCodexPluginEnabled(){return S("codex").readGlobalEnabled()}export async function writeCodexPluginEnabled(t,e){return S("codex").writeGlobalEnabled(t,e)}export{k as globalSkillsPath,P as claudeJsonPath,N as claudePluginsJsonPath,X as claudeSettingsPath,M as codexSkillsPath,C as codexPluginsCachePath};
1
+ import{readFile as E,readdir as h,access as J,stat as x,realpath as G}from"node:fs/promises";import{homedir as c}from"node:os";import l from"node:path";import{AssetNotFoundError as g}from"./installer.js";import{readClaudeJson as N}from"@weavecode/harness";import{FileTooLargeError as W,MAX_BYTES as v}from"./reader.js";import{assetSurfaceFor as y,assetSurfaces as R,pluginStateFor as k,pluginSurfaceFor as $}from"../surfaces.js";import{installForm as L,linkOrCopy as _,removeInstalled as z}from"../market/install-link.js";export{ClaudeJsonCorruptError}from"@weavecode/harness";const I=".claude.json",U=".claude/plugins/installed_plugins.json";function B(){return l.join(c(),I)}function S(){return y("claude").globalSkillsRoots(c())[0]}function M(t=c()){return l.join(t,U)}function K(){return y("claude").userConfigFile(c())}function O(){return y("codex").globalSkillsRoots(c())[0]}function X(){return y("codex").globalSkillsRoots(c())}function P(){const t=y("codex").pluginCacheDir?.(c());if(!t)throw new Error("codex has no plugin cache surface");return t}export function globalPersonalSkillRootsOwned(){return R().flatMap(t=>t.globalSkillsRoots(c()).map(n=>({harness:t.harness,root:n})))}export function globalPersonalSkillRoots(){return globalPersonalSkillRootsOwned().map(t=>t.root)}async function T(t){try{return await J(t),!0}catch{return!1}}async function m(t){try{return(await x(t)).isDirectory()}catch{return!1}}export function readGlobalClaudeJson(){return N(c())}export async function readGlobalMcpServers(){const t=(await readGlobalClaudeJson()).mcpServers;return!t||typeof t!="object"||Array.isArray(t)?{}:t}export async function writeGlobalMcpServer(t,n){await y("claude").mcpWrite("user","",c(),t,n)}export async function removeGlobalMcpServer(t){await y("claude").mcpRemove("user","",c(),t)}export async function readGlobalSkills(){const t=S();try{const n=await h(t,{withFileTypes:!0}),o=[];for(const e of n)!e.isDirectory()&&!e.isSymbolicLink()||await m(l.join(t,e.name))&&o.push(e.name);return o.sort((e,r)=>e.localeCompare(r))}catch{return[]}}export async function installGlobalSkill(t,n,o="claude"){const e=y(o).globalSkillsRoots(c())[0],r=l.join(e,n);if(await T(r))throw new Error(`Global ${o} skill "${n}" already exists`);return await _(t,r),r}export async function uninstallGlobalSkill(t,n="claude"){const o=y(n).globalSkillsRoots(c())[0];await z(l.join(o,t))}async function C(t=c()){const n=M(t);if(!await T(n))return{};try{const o=JSON.parse(await E(n,"utf-8"));return o&&typeof o=="object"&&!Array.isArray(o)?o:{}}catch{return{}}}async function j(t){if(!await m(t))return[];let n;try{n=await h(t,{withFileTypes:!0})}catch{return[]}const o=[];for(const e of n){if(e.name.startsWith(".")||!e.isDirectory()&&!e.isSymbolicLink())continue;const r=l.join(t,e.name);if(!await m(r))continue;const i=await L(r);i!=="absent"&&o.push({name:e.name,dir:r,form:i})}return o.sort((e,r)=>e.name.localeCompare(r.name))}export async function installedPluginNamespaces(t=c()){const o=(await C(t)).plugins;if(!o||typeof o!="object"||Array.isArray(o))return[];const e=[];for(const[r,i]of Object.entries(o)){const{market:s}=$("claude").parsePluginKey(r);for(const a of Array.isArray(i)?i:[])a&&typeof a.installPath=="string"&&e.push({dir:a.installPath,namespace:s})}return e}async function Y(){const n=(await C()).plugins;if(!n||typeof n!="object"||Array.isArray(n))return[];const o=[];for(const[e,r]of Object.entries(n)){const i=Array.isArray(r)?r:[];if(i.length===0)continue;const s=[...i].filter(d=>d&&typeof d.installPath=="string").pop();if(!s?.installPath)continue;const a=await j(l.join(s.installPath,"skills")),{name:u,market:p}=$("claude").parsePluginKey(e);o.push({source:"claude-plugin",label:u,namespace:p,skills:a,pluginKey:e})}return o.sort((e,r)=>e.label.localeCompare(r.label))}async function q(){const t=new Map;for(const o of X())for(const e of await j(o))t.has(e.name)||t.set(e.name,e);return{source:"codex-personal",label:"Codex \xB7 \u4E2A\u4EBA",skills:[...t.values()].sort((o,e)=>o.name.localeCompare(e.name))}}async function H(){const t=P();if(!await m(t))return[];const n=[];let o;try{o=await h(t,{withFileTypes:!0})}catch{return[]}for(const e of o){if(!e.isDirectory()&&!e.isSymbolicLink())continue;const r=l.join(t,e.name);let i;try{i=await h(r,{withFileTypes:!0})}catch{continue}for(const s of i){if(!s.isDirectory()&&!s.isSymbolicLink())continue;const a=l.join(r,s.name);let u;try{u=await h(a,{withFileTypes:!0})}catch{continue}const p=u.filter(w=>w.isDirectory()||w.isSymbolicLink());if(p.length===0)continue;const d=p.map(w=>w.name).sort().pop(),f=await j(l.join(a,d,"skills")),b=`${s.name}@${e.name}`;n.push({source:"codex-plugin",label:s.name,namespace:e.name,skills:f,pluginKey:b})}}return n.sort((e,r)=>e.label.localeCompare(r.label))}async function Q(){const t=S(),n=await readGlobalSkills(),o=[];for(const e of n){const r=l.join(t,e),i=await L(r);i!=="absent"&&o.push({name:e,dir:r,form:i})}return{source:"claude-personal",label:"Claude Code \xB7 \u4E2A\u4EBA",skills:o}}export async function readGlobalSkillGroups(){const[t,n,o,e,r,i]=await Promise.all([Q(),Y(),q(),H(),readClaudePluginEnabled(),readCodexPluginEnabled()]),s=(a,u)=>({...a,enabled:a.pluginKey?u[a.pluginKey]??!1:void 0});return[t,...n.map(a=>s(a,r)),o,...e.map(a=>s(a,i))]}export async function resolveGlobalSkillDir(t,n,o){if(t==="claude-personal"){const f=l.join(S(),n);return await m(f)?f:null}if(t==="codex-personal"){const f=l.join(O(),n);return await m(f)?f:null}if(t==="claude-plugin"){if(!o)return null;const b=(await C()).plugins;if(!b||typeof b!="object"||Array.isArray(b))return null;const w=b[o],F=[...Array.isArray(w)?w:[]].filter(D=>D&&typeof D.installPath=="string").pop();if(!F?.installPath)return null;const A=l.join(F.installPath,"skills",n);return await m(A)?A:null}if(!o)return null;const e=o.lastIndexOf("@");if(e<=0)return null;const r=o.slice(0,e),i=o.slice(e+1),s=l.join(P(),i,r);if(!await m(s))return null;let a;try{a=await h(s,{withFileTypes:!0})}catch{return null}const u=a.filter(f=>f.isDirectory()||f.isSymbolicLink());if(u.length===0)return null;const p=u.map(f=>f.name).sort().pop(),d=l.join(s,p,"skills",n);return await m(d)?d:null}function V(t){const n=l.resolve(t),o=c();return R().find(r=>{const i=[...r.globalSkillsRoots(o)],s=r.pluginCacheDir?.(o);return s&&i.push(s),i.some(a=>n===l.resolve(a)||n.startsWith(l.resolve(a)+l.sep))})?.harness??"claude"}export async function listGlobalSkillFiles(t){const n=V(t),o=[],e=async(r,i)=>{let s;try{s=await h(r,{withFileTypes:!0})}catch{return}for(const a of s){if(a.name.startsWith("."))continue;const u=l.join(r,a.name),p=i?`${i}/${a.name}`:a.name;if((a.isDirectory()||a.isSymbolicLink())&&await m(u))await e(u,p);else if(a.isFile()||a.isSymbolicLink())try{const d=await x(u);d.isFile()&&o.push({absPath:u,relPath:p,category:"skill",agent:n,mtimeMs:d.mtimeMs})}catch{}}};return await e(t,""),o.sort((r,i)=>r.relPath.localeCompare(i.relPath))}export async function readGlobalSkillFile(t,n){const o=l.normalize(n).replace(/\\/g,"/");if(l.isAbsolute(n))throw new g(`Absolute paths are not allowed: "${n}"`);if(o.split("/").some(p=>p===".."))throw new g(`Path traversal is not allowed: "${n}"`);const e=l.join(t,o);let r;try{r=await x(e)}catch{throw new g(`File not found: "${n}"`)}if(!r.isFile())throw new g(`Not a file: "${n}"`);if(r.size>v)throw new W(`File exceeds ${v} bytes: "${n}"`);let i=t,s=e;try{[i,s]=await Promise.all([G(t),G(e)])}catch{throw new g(`File not found: "${n}"`)}const a=i.endsWith(l.sep)?i:i+l.sep;if(s!==i&&!s.startsWith(a))throw new g(`Resolved path escapes the skill directory: "${n}"`);return{content:await E(e,"utf-8"),size:r.size}}export async function readClaudePluginEnabled(){return k("claude").readGlobalEnabled()}export async function writeClaudePluginEnabled(t,n){return k("claude").writeGlobalEnabled(t,n)}export async function readCodexPluginEnabled(){return k("codex").readGlobalEnabled()}export async function writeCodexPluginEnabled(t,n){return k("codex").writeGlobalEnabled(t,n)}export{S as globalSkillsPath,B as claudeJsonPath,M as claudePluginsJsonPath,K as claudeSettingsPath,O as codexSkillsPath,P as codexPluginsCachePath};
@@ -1,4 +1,5 @@
1
1
  import type { Harness } from '@weavecode/core';
2
+ import type { McpServerConfig } from '@weavecode/harness';
2
3
  import { type FileAssetCategory } from './file-assets.js';
3
4
  /**
4
5
  * Asset install/uninstall — pure filesystem operations, no sqlite writes.
@@ -14,11 +15,8 @@ export declare class InstallConflictError extends Error {
14
15
  export declare class AssetNotFoundError extends Error {
15
16
  constructor(message: string);
16
17
  }
17
- export interface McpServerConfig {
18
- command: string;
19
- args?: string[];
20
- env?: Record<string, string>;
21
- }
18
+ /** MCP 配置的通用形(定义随 `AssetSurface` 的方法签名住在 harness 包)。 */
19
+ export type { McpServerConfig };
22
20
  /** Which coding agent an install targets — Claude Code or Codex surfaces. */
23
21
  export type InstallAgent = Harness;
24
22
  /** Copy an entire skill directory into the agent's `<skills>/<name>/`. */
@@ -1,3 +1 @@
1
- import{cp as p,rm as S,readFile as E,writeFile as x,access as g,mkdir as C}from"node:fs/promises";import{homedir as y}from"node:os";import n from"node:path";import{FILE_ASSET_SPECS as $,fileAssetTargetDir as v}from"./file-assets.js";import{assetSurfaceFor as F}from"../surfaces.js";import{forgetInstalled as f,recordInstalled as d}from"./install-ledger.js";export class InstallConflictError extends Error{constructor(t){super(t),this.name="InstallConflictError"}}export class AssetNotFoundError extends Error{constructor(t){super(t),this.name="AssetNotFoundError"}}const w=".mcp.json";function c(r){if(!r||r.includes("/")||r.includes("\\")||r.includes("..")||r.includes(n.sep))throw new Error(`Invalid asset name: "${r}"`)}async function l(r){try{return await g(r),!0}catch{return!1}}function j(r){return F(r).projectSkillsDir()}export async function installSkill(r,t,i,s="claude"){c(i);const e=n.join(r,j(s),i);if(await l(e))throw new InstallConflictError(`Skill "${i}" is already installed${s==="codex"?" (Codex)":""}`);return await p(t,e,{recursive:!0}),await d(r,{category:"skill",name:i,agent:s}),e}export async function uninstallSkill(r,t,i="claude"){c(t);const s=n.join(r,j(i),t);if(!await l(s))throw new AssetNotFoundError(`Skill "${t}" is not installed${i==="codex"?" (Codex)":""}`);await S(s,{recursive:!0,force:!0}),await f(r,{category:"skill",name:t,agent:i})}export async function installFileAsset(r,t,i,s,e="claude",o="project"){c(i);const a=v(t,e,o,r,y()),u=n.join(a,`${i}${$[t].ext}`);if(await l(u))throw new InstallConflictError(`${t} "${i}" is already installed`);return await C(a,{recursive:!0}),await p(s,u),o==="project"&&await d(r,{category:"file",name:i,agent:e,assetCategory:t}),u}export async function uninstallFileAsset(r,t,i,s="claude",e="project"){c(i);const o=v(t,s,e,r,y()),a=n.join(o,`${i}${$[t].ext}`);if(!await l(a))throw new AssetNotFoundError(`${t} "${i}" is not installed`);await S(a,{force:!0}),e==="project"&&await f(r,{category:"file",name:i,agent:s,assetCategory:t})}export async function installMcp(r,t,i){c(t);const s=n.join(r,w),e=await readMcpJson(s),o=e.mcpServers??{};if(t in o)throw new InstallConflictError(`MCP server "${t}" is already configured`);o[t]=i,e.mcpServers=o,await x(s,`${JSON.stringify(e,null,2)}
2
- `,"utf-8"),await d(r,{category:"mcp",name:t,agent:"claude"})}export async function uninstallMcp(r,t){c(t);const i=n.join(r,w),s=await readMcpJson(i),e=s.mcpServers??{};if(!(t in e))throw new AssetNotFoundError(`MCP server "${t}" is not configured`);delete e[t],s.mcpServers=e,await x(i,`${JSON.stringify(s,null,2)}
3
- `,"utf-8"),await f(r,{category:"mcp",name:t,agent:"claude"})}export async function readMcpJson(r){try{const t=await E(r,"utf-8"),i=JSON.parse(t);return i&&typeof i=="object"?i:{}}catch{return{}}}export async function readMcpServers(r){return(await readMcpJson(n.join(r,w))).mcpServers??{}}
1
+ import{cp as x,rm as y,readFile as k,access as C,mkdir as g}from"node:fs/promises";import{homedir as a}from"node:os";import c from"node:path";import{FILE_ASSET_SPECS as $,fileAssetTargetDir as S}from"./file-assets.js";import{assetSurfaceFor as n}from"../surfaces.js";import{forgetInstalled as w,recordInstalled as p}from"./install-ledger.js";export class InstallConflictError extends Error{constructor(r){super(r),this.name="InstallConflictError"}}export class AssetNotFoundError extends Error{constructor(r){super(r),this.name="AssetNotFoundError"}}function u(t){if(!t||t.includes("/")||t.includes("\\")||t.includes("..")||t.includes(c.sep))throw new Error(`Invalid asset name: "${t}"`)}async function d(t){try{return await C(t),!0}catch{return!1}}function E(t){return n(t).projectSkillsDir()}export async function installSkill(t,r,i,e="claude"){u(i);const s=c.join(t,E(e),i);if(await d(s))throw new InstallConflictError(`Skill "${i}" is already installed${e==="codex"?" (Codex)":""}`);return await x(r,s,{recursive:!0}),await p(t,{category:"skill",name:i,agent:e}),s}export async function uninstallSkill(t,r,i="claude"){u(r);const e=c.join(t,E(i),r);if(!await d(e))throw new AssetNotFoundError(`Skill "${r}" is not installed${i==="codex"?" (Codex)":""}`);await y(e,{recursive:!0,force:!0}),await w(t,{category:"skill",name:r,agent:i})}export async function installFileAsset(t,r,i,e,s="claude",l="project"){u(i);const o=S(r,s,l,t,a()),f=c.join(o,`${i}${$[r].ext}`);if(await d(f))throw new InstallConflictError(`${r} "${i}" is already installed`);return await g(o,{recursive:!0}),await x(e,f),l==="project"&&await p(t,{category:"file",name:i,agent:s,assetCategory:r}),f}export async function uninstallFileAsset(t,r,i,e="claude",s="project"){u(i);const l=S(r,e,s,t,a()),o=c.join(l,`${i}${$[r].ext}`);if(!await d(o))throw new AssetNotFoundError(`${r} "${i}" is not installed`);await y(o,{force:!0}),s==="project"&&await w(t,{category:"file",name:i,agent:e,assetCategory:r})}export async function installMcp(t,r,i){if(r in await readMcpServers(t))throw new InstallConflictError(`MCP server "${r}" is already configured`);await n("claude").mcpWrite("project",t,a(),r,i),await p(t,{category:"mcp",name:r,agent:"claude"})}export async function uninstallMcp(t,r){if(!await n("claude").mcpRemove("project",t,a(),r))throw new AssetNotFoundError(`MCP server "${r}" is not configured`);await w(t,{category:"mcp",name:r,agent:"claude"})}export async function readMcpJson(t){try{const r=await k(t,"utf-8"),i=JSON.parse(r);return i&&typeof i=="object"?i:{}}catch{return{}}}export async function readMcpServers(t){return n("claude").mcpRead("project",t,a())}
@@ -1,11 +1,11 @@
1
1
  import type { ToolStateRow } from '@weavecode/core';
2
- import { type McpServerConfig } from './installer.js';
2
+ import type { McpServerConfig } from './installer.js';
3
3
  export type McpToolRow = ToolStateRow<McpServerConfig>;
4
4
  /**
5
- * MCP 工具行装配(工具对象模型首个产消方):三处表示法(项目 `.mcp.json` /
6
- * `~/.claude.json` / `~/.codex/config.toml`)读出的服务器映射展平成
7
- * harness × scope 状态行。范围来自 `AssetSurface.mcpScopes`;启停覆写与
8
- * 生效态由服务端解析(词表桥在 settings-mcp,文件表示法不出 server)。
5
+ * MCP 工具行装配(工具对象模型首个产消方):每个 surface 按自己的 `mcpScopes()`
6
+ * 读出服务器映射、展平成 harness × scope 状态行——表示法(项目 `.mcp.json` /
7
+ * `~/.claude.json` / `~/.codex/config.toml`)只活在各自的 `AssetSurface.mcpRead`
8
+ * 里,本函数不认识任何一种。启停覆写与生效态由服务端解析(词表桥在 settings-mcp)。
9
9
  * 行行携带 installScopes——web 的视图选择与安装控制不依赖能力表二次拉取。
10
10
  */
11
11
  export declare function readMcpRows(projectPath: string): Promise<McpToolRow[]>;
@@ -1 +1 @@
1
- import{readMcpServers as d}from"./installer.js";import{readGlobalMcpServers as v}from"./global-config.js";import{readCodexMcpServers as M}from"./codex-toml.js";import{readMcpEnableMap as S,mcpOverrideOf as h,resolveMcpEffective as w}from"./settings-mcp.js";import{assetSurfaces as x}from"../surfaces.js";export async function readMcpRows(s){const[p,a,n,f]=await Promise.all([d(s).catch(()=>({})),v().catch(()=>({})),M().catch(()=>({})),S(s)]),l=new Map(x().map(e=>[e.harness,e.mcpScopes()])),t=[],c=(e,m,i,r)=>{for(const[o,u]of Object.entries(i))t.push({key:o,category:"mcp",harness:e,scope:m,installScopes:l.get(e)??[],enable:r?h(r,o):null,effective:r?w(r,o):!0,detail:u})};return c("claude","project",p,f),c("claude","user",a,null),c("codex","user",n,null),t}
1
+ import{homedir as i}from"node:os";import{readMcpEnableMap as l,mcpOverrideOf as m,resolveMcpEffective as u}from"./settings-mcp.js";import{assetSurfaces as d}from"../surfaces.js";export async function readMcpRows(r){const t=await l(r),a=[];for(const e of d()){const n=e.mcpScopes();for(const s of n){const o=e.harness==="claude"&&s==="project"?t:null,f=await e.mcpRead(s,r,i()).catch(()=>({}));for(const[c,p]of Object.entries(f))a.push({key:c,category:"mcp",harness:e.harness,scope:s,installScopes:n,enable:o?m(o,c):null,effective:o?u(o,c):!0,detail:p})}}return a}
@@ -1,2 +1,2 @@
1
- import{basename as d,join as p}from"node:path";import{mkdirSync as y,writeFileSync as l}from"node:fs";import{openDatabase as m}from"./db/db.js";import{ProjectRepository as a}from"./repositories/projects.js";import{HookDispatchRepository as u}from"./repositories/hook-dispatches.js";import{HookEventRepository as h}from"./repositories/hook-events.js";import{SessionRepository as j}from"./repositories/sessions.js";import{generateToken as w}from"./daemon/auth.js";import{purgeProjectPipeline as P}from"./daemon/hook-pipeline.js";import{hooksRoot as k}from"./hooks/mirror.js";export function registerProject(t,s,c){const o=m(c);try{const r=new a(o),n=r.getByPath(t)?.token??w(),i=r.register({path:t,name:d(t),token:n,source:s});return y(p(t,".weave"),{recursive:!0}),l(p(t,".weave","token"),n+`
2
- `,"utf-8"),{id:i.id,token:n,path:i.path}}finally{o.close()}}export function unregisterProject(t,s,c){const o=m(s);try{const r=new a(o),e=r.getByPath(t);if(!e)return null;const n=new u(o).deleteByProject(e.id),i=new h(o).deleteByProject(e.id),f=new j(o).removeByProject(e.id);return P(c??k(),e.token),r.remove(e.id),{id:e.id,name:e.name,path:e.path,purged:{events:i,dispatches:n,sessions:f}}}finally{o.close()}}
1
+ import{basename as a,join as c}from"node:path";import{mkdirSync as f,writeFileSync as l}from"node:fs";import{openDatabase as m}from"./db/db.js";import{ProjectRepository as d}from"./repositories/projects.js";import{HookDispatchRepository as u}from"./repositories/hook-dispatches.js";import{HookEventRepository as j}from"./repositories/hook-events.js";import{SessionRepository as w}from"./repositories/sessions.js";import{SessionWatchRepository as P}from"./repositories/session-watches.js";import{SessionSequenceRepository as h}from"./repositories/session-sequences.js";import{SideQuestionRepository as k}from"./repositories/side-questions.js";import{generateToken as B}from"./daemon/auth.js";import{purgeProjectPipeline as R}from"./daemon/hook-pipeline.js";import{hooksRoot as v}from"./hooks/mirror.js";export function registerProject(t,s,p){const o=m(p);try{const r=new d(o),n=r.getByPath(t)?.token??B(),i=r.register({path:t,name:a(t),token:n,source:s});return f(c(t,".weave"),{recursive:!0}),l(c(t,".weave","token"),n+`
2
+ `,"utf-8"),{id:i.id,token:n,path:i.path}}finally{o.close()}}export function unregisterProject(t,s,p){const o=m(s);try{const r=new d(o),e=r.getByPath(t);if(!e)return null;const n=new u(o).deleteByProject(e.id),i=new j(o).deleteByProject(e.id);new P(o).deleteByProject(e.id),new h(o).deleteByProject(e.id),new k(o).deleteByProject(e.id);const y=new w(o).removeByProject(e.id);return R(p??v(),e.token),r.remove(e.id),{id:e.id,name:e.name,path:e.path,purged:{events:i,dispatches:n,sessions:y}}}finally{o.close()}}
@@ -52,6 +52,13 @@ export declare class HookEventRepository {
52
52
  deleteBySession(sessionId: string): number;
53
53
  /** 归档项目时清空其全部采集事件(含 session_id 为空的散行)。 */
54
54
  deleteByProject(projectId: number): number;
55
+ /**
56
+ * 按工具名择行(最近的 limit 条,倒序):计划正文只藏在 ExitPlanMode 与「写计划
57
+ * 文件」两类调用里,`needle` 再按路径特征收一道——不筛就得把整轮工具调用连同
58
+ * 几 MB 的 tool_input 一起捞回来。`payload` 是归一化 JSON(toolName 顶格),故
59
+ * 直接按 `"toolName":"<名>"` 匹配,不必解 JSON。
60
+ */
61
+ listByTools(sessionId: string, tools: readonly string[], needle: string | null, limit: number): HookEventRow[];
55
62
  /** `types` 非空只取这些事件类型(对话视图按开/闭轮类型翻页,不拉整条事件链)。 */
56
63
  listBySession(sessionId: string, limit?: number, beforeId?: number, types?: readonly string[]): HookEventRow[];
57
64
  }
@@ -1,2 +1,2 @@
1
- import{readDaemonSettingsSync as d,DEFAULT_EVENT_RETENTION_DAYS as p}from"../daemon/settings.js";export class HookEventRepository{db;lastPruneDay=-1;insertEvent;pruneByCutoff;constructor(e){this.db=e,this.insertEvent=e.prepare(`INSERT INTO hook_events (project_id, source, event_type, session_id, payload, created_at)
2
- VALUES (?, ?, ?, ?, ?, ?)`),this.pruneByCutoff=e.prepare("DELETE FROM hook_events WHERE created_at < ?")}resetPrune(){this.lastPruneDay=-1}insert(e){this.pruneExpired();const s=this.insertEvent.run(e.projectId,e.source,e.eventType,e.sessionId??null,e.payload,new Date().toISOString());return Number(s.lastInsertRowid)}insertMany(e){this.pruneExpired();const s=[];this.db.exec("BEGIN");try{for(const t of e){const r=this.insertEvent.run(t.projectId,t.source,t.eventType,t.sessionId??null,t.payload,new Date().toISOString());s.push(Number(r.lastInsertRowid))}this.db.exec("COMMIT")}catch(t){throw this.db.exec("ROLLBACK"),t}return s}pruneExpired(){const e=Math.floor(Date.now()/864e5);if(e===this.lastPruneDay)return;this.lastPruneDay=e;const s=d().events?.retentionDays??p,t=new Date((e-s)*864e5).toISOString();this.pruneByCutoff.run(t)}listByProject(e,s=100){return this.db.prepare("SELECT * FROM hook_events WHERE project_id = ? ORDER BY created_at DESC LIMIT ?").all(e,s).map(t=>a(t))}deleteBySession(e){return Number(this.db.prepare("DELETE FROM hook_events WHERE session_id = ?").run(e).changes)}deleteByProject(e){return Number(this.db.prepare("DELETE FROM hook_events WHERE project_id = ?").run(e).changes)}listBySession(e,s=300,t,r){const o=["session_id = ?"],i=[e];return t!=null&&(o.push("id < ?"),i.push(t)),r?.length&&(o.push(`event_type IN (${r.map(()=>"?").join(",")})`),i.push(...r)),this.db.prepare(`SELECT * FROM hook_events WHERE ${o.join(" AND ")} ORDER BY id DESC LIMIT ?`).all(...i,s).map(u=>a(u)).reverse()}}function a(n){return{id:Number(n.id),projectId:Number(n.project_id),source:String(n.source),eventType:String(n.event_type),sessionId:n.session_id==null?null:String(n.session_id),payload:String(n.payload),createdAt:String(n.created_at)}}
1
+ import{readDaemonSettingsSync as E,DEFAULT_EVENT_RETENTION_DAYS as d}from"../daemon/settings.js";export class HookEventRepository{db;lastPruneDay=-1;insertEvent;pruneByCutoff;constructor(e){this.db=e,this.insertEvent=e.prepare(`INSERT INTO hook_events (project_id, source, event_type, session_id, payload, created_at)
2
+ VALUES (?, ?, ?, ?, ?, ?)`),this.pruneByCutoff=e.prepare("DELETE FROM hook_events WHERE created_at < ?")}resetPrune(){this.lastPruneDay=-1}insert(e){this.pruneExpired();const s=this.insertEvent.run(e.projectId,e.source,e.eventType,e.sessionId??null,e.payload,new Date().toISOString());return Number(s.lastInsertRowid)}insertMany(e){this.pruneExpired();const s=[];this.db.exec("BEGIN");try{for(const t of e){const r=this.insertEvent.run(t.projectId,t.source,t.eventType,t.sessionId??null,t.payload,new Date().toISOString());s.push(Number(r.lastInsertRowid))}this.db.exec("COMMIT")}catch(t){throw this.db.exec("ROLLBACK"),t}return s}pruneExpired(){const e=Math.floor(Date.now()/864e5);if(e===this.lastPruneDay)return;this.lastPruneDay=e;const s=E().events?.retentionDays??d,t=new Date((e-s)*864e5).toISOString();this.pruneByCutoff.run(t)}listByProject(e,s=100){return this.db.prepare("SELECT * FROM hook_events WHERE project_id = ? ORDER BY created_at DESC LIMIT ?").all(e,s).map(t=>p(t))}deleteBySession(e){return Number(this.db.prepare("DELETE FROM hook_events WHERE session_id = ?").run(e).changes)}deleteByProject(e){return Number(this.db.prepare("DELETE FROM hook_events WHERE project_id = ?").run(e).changes)}listByTools(e,s,t,r){if(s.length===0)return[];const o=["session_id = ?","event_type IN ('PreToolUse','PostToolUse')",`(${s.map(()=>"payload LIKE ?").join(" OR ")})`],i=[e,...s.map(a=>`%"toolName":"${a}"%`)];return t&&(o.push("payload LIKE ?"),i.push(`%${t}%`)),this.db.prepare(`SELECT * FROM hook_events WHERE ${o.join(" AND ")} ORDER BY id DESC LIMIT ?`).all(...i,r).map(a=>p(a))}listBySession(e,s=300,t,r){const o=["session_id = ?"],i=[e];return t!=null&&(o.push("id < ?"),i.push(t)),r?.length&&(o.push(`event_type IN (${r.map(()=>"?").join(",")})`),i.push(...r)),this.db.prepare(`SELECT * FROM hook_events WHERE ${o.join(" AND ")} ORDER BY id DESC LIMIT ?`).all(...i,s).map(a=>p(a)).reverse()}}function p(n){return{id:Number(n.id),projectId:Number(n.project_id),source:String(n.source),eventType:String(n.event_type),sessionId:n.session_id==null?null:String(n.session_id),payload:String(n.payload),createdAt:String(n.created_at)}}
@@ -13,12 +13,16 @@ export interface SessionRow {
13
13
  eventCount?: number;
14
14
  /** 会话归属 harness = 最新一条 hook 事件的 source(listWithCounts 查询填充)。 */
15
15
  harness?: Harness | null;
16
- /** 回合开合的服务端基线:最后一条 UserPromptSubmit 晚于最后一条 Stop/SessionEnd
17
- * (listWithCounts 查询填充)——前端帧态丢失/重挂载时的权威兜底。 */
16
+ /** 回合开合的服务端基线:最后一条 UserPromptSubmit 晚于最后一条回合收尾事件
17
+ * (Stop/StopFailure/SessionEnd,以及非压缩的 SessionStart——终端挂掉后 resume
18
+ * 起来不会有 Stop;listWithCounts 查询填充)——前端帧态丢失/重挂载时的权威兜底。 */
18
19
  turnOpen?: boolean;
19
20
  /** 最新一条 hook 事件类型(listWithCounts 查询填充)——web 状态总线判「关闭」
20
21
  * (SessionEnd)不必再为每个会话拉事件。 */
21
22
  lastEventType?: string | null;
23
+ /** 这个会话真跑过(有过 UserPromptSubmit 或收尾事件;listWithCounts 查询填充)。
24
+ * 「有没有转录」的判据要用它:没跑过的会话本来就还没写过转录,没内容不是丢失。 */
25
+ hasRun?: boolean;
22
26
  /** 自动移除时刻(= lastSeenAt + 该档保留窗;listWithCounts 派生,两档——没等到
23
27
  * 用户 prompt 的新会话快过期,收到过 prompt 的走设置页「无操作会话 n 天后自动
24
28
  * 移除」那条线;会话行剩余存活倒计时即读它)。 */
@@ -45,10 +49,17 @@ export declare class SessionRepository {
45
49
  listWithCounts(projectId?: number): SessionRow[];
46
50
  /**
47
51
  * 单会话 turnOpen(消息域 auto 裁决的忙/闲基准):语义与 listWithCounts 的
48
- * 计算字段完全一致(最后一条 UserPromptSubmit 晚于最后一条 Stop/SessionEnd)。
49
- * 会话不存在 → null;存在但零事件 → false(同列表口径:无 ups 即不判开)。
52
+ * 计算字段完全一致(最后一条 UserPromptSubmit 晚于最后一条回合收尾事件,
53
+ * 判据同 turnCloseSql)。会话不存在 → null;存在但零事件 → false(同列表口径:
54
+ * 无 ups 即不判开)。
50
55
  */
51
56
  turnOpenOf(sessionId: string): boolean | null;
57
+ /**
58
+ * 单会话「真跑过吗」(有过 UPS 或收尾事件),与 listWithCounts 的 `hasRun` 同一
59
+ * 判据。打开终端前判转录是否**丢过**要用它:刚建、还没发第一条 prompt 的会话
60
+ * 本来就还没有转录文件,那不算丢失。
61
+ */
62
+ hasRunOf(sessionId: string): boolean;
52
63
  /**
53
64
  * 单会话判活输入(`daemon/claude-liveness.ts` 的 `pidAliveOf` 消费形状)——
54
65
  * harness / lastEventType / eventCount 都是 listWithCounts 的计算字段,单行版与
@@ -1,5 +1,5 @@
1
- import{isHarness as _}from"@weavecode/core";import{readDaemonSettingsSync as a,DEFAULT_SESSION_IDLE_DAYS as u}from"../daemon/settings.js";export const PROMPTLESS_GRACE_MS=60*6e4;function p(){return(a().sessions?.idleDays??u)*864e5}export class SessionRepository{db;constructor(e){this.db=e}touch(e,s){const t=new Date().toISOString();return this.db.prepare(`INSERT INTO sessions (session_id, project_id, last_seen_at, created_at)
1
+ import{HARNESS_COMPACTION as S,HARNESS_LIFECYCLE as p,isHarness as _}from"@weavecode/core";import{readDaemonSettingsSync as c,DEFAULT_SESSION_IDLE_DAYS as R}from"../daemon/settings.js";export const PROMPTLESS_GRACE_MS=60*6e4;function T(){return(c().sessions?.idleDays??R)*864e5}function d(i){const e=new Set;for(const n of Object.values(p)){for(const o of n.close)e.add(o);for(const o of n.sessionMarkers)e.add(o);n.sessionExit&&e.add(n.sessionExit)}const s=[...e].map(n=>`'${n}'`).join(","),t=Object.values(S).filter(n=>n.end&&n.endSource).map(n=>`(${i}.event_type = '${n.end}' AND COALESCE(json_extract(${i}.payload, '$.raw.source'), '') = '${n.endSource}')`).join(" OR ");return`${i}.event_type IN (${s})${t?` AND NOT (${t})`:""}`}export class SessionRepository{db;constructor(e){this.db=e}touch(e,s){const t=new Date().toISOString();return this.db.prepare(`INSERT INTO sessions (session_id, project_id, last_seen_at, created_at)
2
2
  VALUES (?, ?, ?, ?)
3
3
  ON CONFLICT(session_id) DO UPDATE SET
4
4
  last_seen_at = excluded.last_seen_at,
5
- project_id = COALESCE(sessions.project_id, excluded.project_id)`).run(e,s??null,t,t),this.getBySessionId(e)}getBySessionId(e){const s=this.db.prepare("SELECT * FROM sessions WHERE session_id = ?").get(e);return s?o(s):void 0}list(e){return(e===void 0?this.db.prepare("SELECT * FROM sessions ORDER BY created_at DESC LIMIT 200").all():this.db.prepare("SELECT * FROM sessions WHERE project_id = ? ORDER BY created_at DESC LIMIT 200").all(e)).map(o)}listWithCounts(e){const s="SELECT s.*, COUNT(e.id) AS event_count, (SELECT e2.source FROM hook_events e2 WHERE e2.session_id = s.session_id ORDER BY e2.id DESC LIMIT 1) AS harness, (SELECT e5.event_type FROM hook_events e5 WHERE e5.session_id = s.session_id ORDER BY e5.id DESC LIMIT 1) AS last_event_type, (SELECT MAX(e3.id) FROM hook_events e3 WHERE e3.session_id = s.session_id AND e3.event_type = 'UserPromptSubmit') AS last_ups, (SELECT MAX(e4.id) FROM hook_events e4 WHERE e4.session_id = s.session_id AND e4.event_type IN ('Stop', 'StopFailure', 'SessionEnd')) AS last_stop FROM sessions s LEFT JOIN hook_events e ON e.session_id = s.session_id";return(e===void 0?this.db.prepare(`${s} GROUP BY s.session_id ORDER BY s.created_at DESC LIMIT 200`).all():this.db.prepare(`${s} WHERE s.project_id = ? GROUP BY s.session_id ORDER BY s.created_at DESC LIMIT 200`).all(e)).map(i=>{const l=i.harness,r=i.last_ups,E=i.last_stop,d=i.last_event_type;return{...o(i),eventCount:Number(i.event_count??0),harness:_(l)?l:null,turnOpen:r!=null&&(E==null||Number(r)>Number(E)),lastEventType:d??null,autoDeleteAt:new Date(Date.parse(String(i.last_seen_at))+(r==null?PROMPTLESS_GRACE_MS:p())).toISOString()}})}turnOpenOf(e){const s=this.db.prepare("SELECT (SELECT MAX(e3.id) FROM hook_events e3 WHERE e3.session_id = ? AND e3.event_type = 'UserPromptSubmit') AS last_ups, (SELECT MAX(e4.id) FROM hook_events e4 WHERE e4.session_id = ? AND e4.event_type IN ('Stop', 'StopFailure', 'SessionEnd')) AS last_stop").get(e,e);if(!this.getBySessionId(e))return null;const t=s?.last_ups??null,i=s?.last_stop??null;return t!=null&&(i==null||Number(t)>Number(i))}livenessRowOf(e){const s=this.db.prepare("SELECT s.session_id, s.last_seen_at, COUNT(e.id) AS event_count, (SELECT e2.source FROM hook_events e2 WHERE e2.session_id = s.session_id ORDER BY e2.id DESC LIMIT 1) AS harness, (SELECT e5.event_type FROM hook_events e5 WHERE e5.session_id = s.session_id ORDER BY e5.id DESC LIMIT 1) AS last_event_type FROM sessions s LEFT JOIN hook_events e ON e.session_id = s.session_id WHERE s.session_id = ? GROUP BY s.session_id").get(e);if(!s)return null;const t=s.harness;return{sessionId:String(s.session_id),harness:_(t)?t:null,lastSeenAt:String(s.last_seen_at),lastEventType:s.last_event_type==null?null:String(s.last_event_type),eventCount:Number(s.event_count??0)}}setPendingTitle(e,s){const t=this.getBySessionId(e);if(t)return t.pendingTitle===s?t:t.title===s?(t.pendingTitle!=null&&this.db.prepare("UPDATE sessions SET pending_title = NULL WHERE session_id = ?").run(e),this.getBySessionId(e)):(this.db.prepare("UPDATE sessions SET title = ?, pending_title = ? WHERE session_id = ?").run(s,s,e),this.getBySessionId(e))}applyReportedTitle(e,s){const t=this.getBySessionId(e);return t?t.pendingTitle!=null?s===t.pendingTitle?(this.db.prepare("UPDATE sessions SET title = ?, pending_title = NULL WHERE session_id = ?").run(s,e),t.title!==s):!1:s!=null&&s!==t.title?(this.db.prepare("UPDATE sessions SET title = ? WHERE session_id = ?").run(s,e),!0):!1:!1}confirmPendingTitle(e,s){return this.db.prepare("UPDATE sessions SET title = ?, pending_title = NULL WHERE session_id = ? AND pending_title = ?").run(s,e,s).changes>0}remove(e){this.db.prepare("DELETE FROM sessions WHERE session_id = ?").run(e)}removeByProject(e){return Number(this.db.prepare("DELETE FROM sessions WHERE project_id = ?").run(e).changes)}}function o(n){return{sessionId:String(n.session_id),projectId:n.project_id==null?null:Number(n.project_id),title:n.title==null?null:String(n.title),pendingTitle:n.pending_title==null?null:String(n.pending_title),lastSeenAt:String(n.last_seen_at),createdAt:String(n.created_at)}}
5
+ project_id = COALESCE(sessions.project_id, excluded.project_id)`).run(e,s??null,t,t),this.getBySessionId(e)}getBySessionId(e){const s=this.db.prepare("SELECT * FROM sessions WHERE session_id = ?").get(e);return s?l(s):void 0}list(e){return(e===void 0?this.db.prepare("SELECT * FROM sessions ORDER BY created_at DESC LIMIT 200").all():this.db.prepare("SELECT * FROM sessions WHERE project_id = ? ORDER BY created_at DESC LIMIT 200").all(e)).map(l)}listWithCounts(e){const s=`SELECT s.*, COUNT(e.id) AS event_count, (SELECT e2.source FROM hook_events e2 WHERE e2.session_id = s.session_id ORDER BY e2.id DESC LIMIT 1) AS harness, (SELECT e5.event_type FROM hook_events e5 WHERE e5.session_id = s.session_id ORDER BY e5.id DESC LIMIT 1) AS last_event_type, (SELECT MAX(e3.id) FROM hook_events e3 WHERE e3.session_id = s.session_id AND e3.event_type = 'UserPromptSubmit') AS last_ups, (SELECT MAX(e6.id) FROM hook_events e6 WHERE e6.session_id = s.session_id AND ${d("e6")}) AS last_close, (SELECT MAX(e4.id) FROM hook_events e4 WHERE e4.session_id = s.session_id AND e4.event_type IN ('Stop', 'StopFailure', 'SessionEnd')) AS last_stop FROM sessions s LEFT JOIN hook_events e ON e.session_id = s.session_id`;return(e===void 0?this.db.prepare(`${s} GROUP BY s.session_id ORDER BY s.created_at DESC LIMIT 200`).all():this.db.prepare(`${s} WHERE s.project_id = ? GROUP BY s.session_id ORDER BY s.created_at DESC LIMIT 200`).all(e)).map(n=>{const o=n.harness,r=n.last_ups,E=n.last_close,a=n.last_stop,u=n.last_event_type;return{...l(n),eventCount:Number(n.event_count??0),harness:_(o)?o:null,turnOpen:r!=null&&(E==null||Number(r)>Number(E)),lastEventType:u??null,hasRun:r!=null||a!=null,autoDeleteAt:new Date(Date.parse(String(n.last_seen_at))+(r==null?PROMPTLESS_GRACE_MS:T())).toISOString()}})}turnOpenOf(e){const s=this.db.prepare(`SELECT (SELECT MAX(e3.id) FROM hook_events e3 WHERE e3.session_id = ? AND e3.event_type = 'UserPromptSubmit') AS last_ups, (SELECT MAX(e6.id) FROM hook_events e6 WHERE e6.session_id = ? AND ${d("e6")}) AS last_close`).get(e,e);if(!this.getBySessionId(e))return null;const t=s?.last_ups??null,n=s?.last_close??null;return t!=null&&(n==null||Number(t)>Number(n))}hasRunOf(e){const s=this.db.prepare("SELECT EXISTS(SELECT 1 FROM hook_events e WHERE e.session_id = ? AND e.event_type IN ('UserPromptSubmit','Stop','StopFailure','SessionEnd')) AS hit").get(e);return Number(s?.hit??0)>0}livenessRowOf(e){const s=this.db.prepare("SELECT s.session_id, s.last_seen_at, COUNT(e.id) AS event_count, (SELECT e2.source FROM hook_events e2 WHERE e2.session_id = s.session_id ORDER BY e2.id DESC LIMIT 1) AS harness, (SELECT e5.event_type FROM hook_events e5 WHERE e5.session_id = s.session_id ORDER BY e5.id DESC LIMIT 1) AS last_event_type FROM sessions s LEFT JOIN hook_events e ON e.session_id = s.session_id WHERE s.session_id = ? GROUP BY s.session_id").get(e);if(!s)return null;const t=s.harness;return{sessionId:String(s.session_id),harness:_(t)?t:null,lastSeenAt:String(s.last_seen_at),lastEventType:s.last_event_type==null?null:String(s.last_event_type),eventCount:Number(s.event_count??0)}}setPendingTitle(e,s){const t=this.getBySessionId(e);if(t)return t.pendingTitle===s?t:t.title===s?(t.pendingTitle!=null&&this.db.prepare("UPDATE sessions SET pending_title = NULL WHERE session_id = ?").run(e),this.getBySessionId(e)):(this.db.prepare("UPDATE sessions SET title = ?, pending_title = ? WHERE session_id = ?").run(s,s,e),this.getBySessionId(e))}applyReportedTitle(e,s){const t=this.getBySessionId(e);return t?t.pendingTitle!=null?s===t.pendingTitle?(this.db.prepare("UPDATE sessions SET title = ?, pending_title = NULL WHERE session_id = ?").run(s,e),t.title!==s):!1:s!=null&&s!==t.title?(this.db.prepare("UPDATE sessions SET title = ? WHERE session_id = ?").run(s,e),!0):!1:!1}confirmPendingTitle(e,s){return this.db.prepare("UPDATE sessions SET title = ?, pending_title = NULL WHERE session_id = ? AND pending_title = ?").run(s,e,s).changes>0}remove(e){this.db.prepare("DELETE FROM sessions WHERE session_id = ?").run(e)}removeByProject(e){return Number(this.db.prepare("DELETE FROM sessions WHERE project_id = ?").run(e).changes)}}function l(i){return{sessionId:String(i.session_id),projectId:i.project_id==null?null:Number(i.project_id),title:i.title==null?null:String(i.title),pendingTitle:i.pending_title==null?null:String(i.pending_title),lastSeenAt:String(i.last_seen_at),createdAt:String(i.created_at)}}
@@ -0,0 +1,50 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ /** pending = 已打进会话、等回答;answered = 认领到回答;timeout = 超窗没等到。 */
3
+ export type SideQuestionStatus = 'pending' | 'answered' | 'timeout';
4
+ export interface SideQuestionRow {
5
+ id: number;
6
+ sessionId: string;
7
+ projectId: number | null;
8
+ question: string;
9
+ answer: string | null;
10
+ /** 认领到的那条采集事件(SubagentStop);事件清掉后只留 answer 原文。 */
11
+ answerEventId: number | null;
12
+ status: SideQuestionStatus;
13
+ askedAt: string;
14
+ answeredAt: string | null;
15
+ }
16
+ /**
17
+ * 旁路会话域(claude = `/btw`):宿主侧问不留任何痕迹——问题只有 weave 自己
18
+ * 发出去时才知道,回答只能从采集事件认领。故这是**记录系统**而不是递送管道:
19
+ * 消息域「不落库」的规矩在这里不适用,不记就永远没有。
20
+ */
21
+ export declare class SideQuestionRepository {
22
+ private readonly db;
23
+ constructor(db: DatabaseSync);
24
+ insert(input: {
25
+ sessionId: string;
26
+ projectId: number | null;
27
+ question: string;
28
+ }): SideQuestionRow;
29
+ getById(id: number): SideQuestionRow | undefined;
30
+ /**
31
+ * 认领一条回答:该会话**最早一条**仍未落定、且提问时刻不早于 `since` 的行。
32
+ * 条件更新(`status = 'pending'`)天然幂等,并发只赢一次;返回 null = 没有
33
+ * 待认领的行(人工在宿主面板里发起的侧问、或回答来得太晚)。
34
+ */
35
+ claim(sessionId: string, answer: string, eventId: number, since: string): SideQuestionRow | null;
36
+ /**
37
+ * 待答超窗落定(时间窗过了宿主不会再作答:面板可能被 Esc 关掉、主回合可能
38
+ * 已经收尾)。惰性执行——在列表读取前跑一次,不另起定时器。
39
+ */
40
+ expirePending(before: string): number;
41
+ /** 游标向前翻页(`beforeId` 缺省 = 最新一页),倒序。 */
42
+ list(sessionId: string, opts: {
43
+ beforeId?: number;
44
+ limit: number;
45
+ }): SideQuestionRow[];
46
+ /** 删会话级联(与 events / 队列行同口径,不计数)。 */
47
+ deleteBySession(sessionId: string): number;
48
+ /** 归档/注销项目连带清(行上自带 project_id,不必赶在会话行删除之前)。 */
49
+ deleteByProject(projectId: number): number;
50
+ }
@@ -0,0 +1,6 @@
1
+ export class SideQuestionRepository{db;constructor(e){this.db=e}insert(e){const t=this.db.prepare(`INSERT INTO side_questions (session_id, project_id, question, status, asked_at)
2
+ VALUES (?, ?, ?, 'pending', ?)`).run(e.sessionId,e.projectId,e.question,new Date().toISOString());return this.getById(Number(t.lastInsertRowid))}getById(e){const t=this.db.prepare("SELECT * FROM side_questions WHERE id = ?").get(e);return t?d(t):void 0}claim(e,t,n,i){const r=this.db.prepare(`SELECT id FROM side_questions
3
+ WHERE session_id = ? AND status = 'pending' AND asked_at >= ?
4
+ ORDER BY id LIMIT 1`).get(e,i);return r&&Number(this.db.prepare(`UPDATE side_questions SET answer = ?, answer_event_id = ?, status = 'answered', answered_at = ?
5
+ WHERE id = ? AND status = 'pending'`).run(t,n,new Date().toISOString(),r.id).changes)===1?this.getById(r.id)??null:null}expirePending(e){return Number(this.db.prepare(`UPDATE side_questions SET status = 'timeout'
6
+ WHERE status = 'pending' AND asked_at < ?`).run(e).changes)}list(e,t){const{beforeId:n,limit:i}=t;return(n?this.db.prepare("SELECT * FROM side_questions WHERE session_id = ? AND id < ? ORDER BY id DESC LIMIT ?").all(e,n,i):this.db.prepare("SELECT * FROM side_questions WHERE session_id = ? ORDER BY id DESC LIMIT ?").all(e,i)).map(d)}deleteBySession(e){return Number(this.db.prepare("DELETE FROM side_questions WHERE session_id = ?").run(e).changes)}deleteByProject(e){return Number(this.db.prepare("DELETE FROM side_questions WHERE project_id = ?").run(e).changes)}}function d(s){return{id:Number(s.id),sessionId:String(s.session_id),projectId:s.project_id==null?null:Number(s.project_id),question:String(s.question),answer:s.answer==null?null:String(s.answer),answerEventId:s.answer_event_id==null?null:Number(s.answer_event_id),status:String(s.status),askedAt:String(s.asked_at),answeredAt:s.answered_at==null?null:String(s.answered_at)}}