@x-otto/cli 0.0.1-alpha.52 → 0.0.1-alpha.53

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 (29) hide show
  1. package/dist/{auth-command-DF_POx4L.js → auth-command--ky3JspQ.js} +2 -2
  2. package/dist/{auth-command-DF_POx4L.js.map → auth-command--ky3JspQ.js.map} +1 -1
  3. package/dist/build-id-C94zHRXj.js +3 -0
  4. package/dist/build-id-C94zHRXj.js.map +1 -0
  5. package/dist/{command-dispatcher-FRiCoN16.js → command-dispatcher-CKj6qR-G.js} +3 -3
  6. package/dist/{command-dispatcher-FRiCoN16.js.map → command-dispatcher-CKj6qR-G.js.map} +1 -1
  7. package/dist/{create-host-app-BgjEc81C.js → create-host-app-BtifD7KK.js} +2 -2
  8. package/dist/{create-host-app-BgjEc81C.js.map → create-host-app-BtifD7KK.js.map} +1 -1
  9. package/dist/{doctor-2AA2A9G7.js → doctor-BAxvhLws.js} +2 -2
  10. package/dist/{doctor-2AA2A9G7.js.map → doctor-BAxvhLws.js.map} +1 -1
  11. package/dist/{feedback-BsU8b1k9.js → feedback-CJVKfI0S.js} +2 -2
  12. package/dist/{feedback-BsU8b1k9.js.map → feedback-CJVKfI0S.js.map} +1 -1
  13. package/dist/{feedback-backend-methods-C2SzcIzm.js → feedback-backend-methods-C23Z2suw.js} +2 -2
  14. package/dist/{feedback-backend-methods-C2SzcIzm.js.map → feedback-backend-methods-C23Z2suw.js.map} +1 -1
  15. package/dist/index.js +11 -11
  16. package/dist/index.js.map +1 -1
  17. package/dist/{interactive-schedule-aQEHfUKL.js → interactive-schedule-BKiI3Qrw.js} +2 -2
  18. package/dist/{interactive-schedule-aQEHfUKL.js.map → interactive-schedule-BKiI3Qrw.js.map} +1 -1
  19. package/dist/{mcp-server-C2bw9J2i.js → mcp-server-CfJK4pav.js} +2 -2
  20. package/dist/{mcp-server-C2bw9J2i.js.map → mcp-server-CfJK4pav.js.map} +1 -1
  21. package/dist/{parse-cli-C1r2cQ0K.js → parse-cli-B9Go-f6A.js} +2 -2
  22. package/dist/{parse-cli-C1r2cQ0K.js.map → parse-cli-B9Go-f6A.js.map} +1 -1
  23. package/dist/{serve-DHqx-Mab.js → serve-ChSSkjLs.js} +2 -2
  24. package/dist/{serve-DHqx-Mab.js.map → serve-ChSSkjLs.js.map} +1 -1
  25. package/dist/{version-CdzXY8m2.js → version-BL66TSQP.js} +2 -2
  26. package/dist/{version-CdzXY8m2.js.map → version-BL66TSQP.js.map} +1 -1
  27. package/package.json +16 -16
  28. package/dist/build-id-BmQPXMGN.js +0 -3
  29. package/dist/build-id-BmQPXMGN.js.map +0 -1
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{at as e}from"./command-dispatcher-FRiCoN16.js";import{t}from"./bundled-extensions-C3n8jg20.js";import{createApp as n}from"@x-otto/coding";var r=e({createHostApp:()=>a,hostSelfDescribingOptions:()=>i});function i(){let e=t();return{pluginBundledDirs:e?[e]:[]}}function a(e){return n({...i(),...e})}export{r as n,a as t};
3
- //# sourceMappingURL=create-host-app-BgjEc81C.js.map
2
+ import{ot as e}from"./command-dispatcher-CKj6qR-G.js";import{t}from"./bundled-extensions-C3n8jg20.js";import{createApp as n}from"@x-otto/coding";var r=e({createHostApp:()=>a,hostSelfDescribingOptions:()=>i});function i(){let e=t();return{pluginBundledDirs:e?[e]:[]}}function a(e){return n({...i(),...e})}export{r as n,a as t};
3
+ //# sourceMappingURL=create-host-app-BtifD7KK.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-host-app-BgjEc81C.js","names":[],"sources":["../src/create-host-app.ts"],"sourcesContent":["/**\n * create-host-app.ts —— RFC-364 M1:cli 侧**唯一**的 App 装配入口。\n *\n * ## 要解决的问题\n * `createApp()` 有一类参数属于「**宿主自描述**」——otto 这个发布物自己带了什么、\n * 布局长什么样(当前是 `pluginBundledDirs`,即随 cli 分发的内置扩展目录)。这类信息\n * 调用方无从得知、也不该关心,但它此前是每个调用点都必须**记得传**的可选参数。\n *\n * 后果(RFC-364 §1.2 实测):RFC-341 引入 bundled 层时只接线了 `cli.ts` 一处,\n * `serve`/`feedback`/`auth` 三处全漏,表现为**静默少装插件**:\n *\n * ```\n * [serve 方式] createApp({workspaceDir}) → activePlugins: 12, bundled: []\n * [cli 方式] createApp({..., pluginBundledDirs}) → activePlugins: 12, bundled: ['plugin-otto-wire-protocols'](skill-inductor 2026-08-13 起外置)\n * ```\n *\n * 纯 npm 安装环境下更严重:任何 `dependsOn` bundled 插件的第三方 provider 插件会被\n * 拓扑排序判定 missingDeps 并 fail-closed 剔除。\n *\n * ## 为什么是「收敛装配点」而不是「把知识搬到下游包」\n * 初版方案曾想把 `bundledExtensionsDir()` 下沉到 `@x-otto/env`(coding 与 cli 的共同\n * 依赖),已**实测证伪**:该函数靠 `import.meta.url` 相对定位 cli 自己的 dist,而 env\n * 与 cli 的物理相对关系两种形态下都不固定——\n * pnpm workspace: packages/cli/node_modules/@x-otto/env -> ../../../env(符号链接)\n * npm 安装形态 : node_modules/@x-otto/env(hoist,与 cli/dist 无固定相对关系)\n * 即「宿主发布物布局」这个知识**只有 cli 有资格拥有**,搬到任何下游包都会把确定的 bug\n * 换成更隐蔽的路径 bug。所以正解是收敛**装配点**,而非搬迁**知识**。\n *\n * ## 契约(RFC-364 §4 规则 1)\n * 宿主自描述参数不得做成调用方必传参数。cli 内部**任何**需要 App 的地方都应调用本函数,\n * 不得直接 `createApp()`——新增入口默认就是对的,而不是\"新增一次漏传机会\"。\n * 有架构门禁测试钉住这条(`create-host-app-sole-assembly.test.ts`)。\n */\nimport { createApp } from '@x-otto/coding'\n\nimport { bundledExtensionsDir } from './bundled-extensions'\n\nimport type { App, AppOptions } from '@x-otto/coding'\n\n/**\n * 宿主自描述参数——「otto 这个发布物自己带了什么」。\n *\n * 单独导出而非内联,是因为 engine 子进程(`coding/src/engine-main.ts`,在 coding 包内、\n * 拿不到 cli 的 dist 布局)需要由父进程把该值显式下传(RFC-364 T364-03)。\n */\nexport function hostSelfDescribingOptions(): Pick<AppOptions, 'pluginBundledDirs'> {\n const dir = bundledExtensionsDir()\n return { pluginBundledDirs: dir ? [dir] : [] }\n}\n\n/**\n * 装配一个 App:调用方给业务参数,宿主自描述参数由本函数补齐。\n *\n * 调用方**可以**显式覆盖 `pluginBundledDirs`(测试隔离、非常规部署),\n * 显式传入时以调用方为准——保留逃生舱,但缺省不再是\"什么都没有\"。\n */\nexport function createHostApp(options: AppOptions): App {\n return createApp({\n ...hostSelfDescribingOptions(),\n ...options,\n })\n}\n"],"mappings":";gNA6CA,SAAgB,GAAmE,CACjF,IAAM,EAAM,GAAsB,CAClC,MAAO,CAAE,kBAAmB,EAAM,CAAC,EAAI,CAAG,EAAE,CAAE,CAShD,SAAgB,EAAc,EAA0B,CACtD,OAAO,EAAU,CACf,GAAG,GAA2B,CAC9B,GAAG,EACJ,CAAC"}
1
+ {"version":3,"file":"create-host-app-BtifD7KK.js","names":[],"sources":["../src/create-host-app.ts"],"sourcesContent":["/**\n * create-host-app.ts —— RFC-364 M1:cli 侧**唯一**的 App 装配入口。\n *\n * ## 要解决的问题\n * `createApp()` 有一类参数属于「**宿主自描述**」——otto 这个发布物自己带了什么、\n * 布局长什么样(当前是 `pluginBundledDirs`,即随 cli 分发的内置扩展目录)。这类信息\n * 调用方无从得知、也不该关心,但它此前是每个调用点都必须**记得传**的可选参数。\n *\n * 后果(RFC-364 §1.2 实测):RFC-341 引入 bundled 层时只接线了 `cli.ts` 一处,\n * `serve`/`feedback`/`auth` 三处全漏,表现为**静默少装插件**:\n *\n * ```\n * [serve 方式] createApp({workspaceDir}) → activePlugins: 12, bundled: []\n * [cli 方式] createApp({..., pluginBundledDirs}) → activePlugins: 12, bundled: ['plugin-otto-wire-protocols'](skill-inductor 2026-08-13 起外置)\n * ```\n *\n * 纯 npm 安装环境下更严重:任何 `dependsOn` bundled 插件的第三方 provider 插件会被\n * 拓扑排序判定 missingDeps 并 fail-closed 剔除。\n *\n * ## 为什么是「收敛装配点」而不是「把知识搬到下游包」\n * 初版方案曾想把 `bundledExtensionsDir()` 下沉到 `@x-otto/env`(coding 与 cli 的共同\n * 依赖),已**实测证伪**:该函数靠 `import.meta.url` 相对定位 cli 自己的 dist,而 env\n * 与 cli 的物理相对关系两种形态下都不固定——\n * pnpm workspace: packages/cli/node_modules/@x-otto/env -> ../../../env(符号链接)\n * npm 安装形态 : node_modules/@x-otto/env(hoist,与 cli/dist 无固定相对关系)\n * 即「宿主发布物布局」这个知识**只有 cli 有资格拥有**,搬到任何下游包都会把确定的 bug\n * 换成更隐蔽的路径 bug。所以正解是收敛**装配点**,而非搬迁**知识**。\n *\n * ## 契约(RFC-364 §4 规则 1)\n * 宿主自描述参数不得做成调用方必传参数。cli 内部**任何**需要 App 的地方都应调用本函数,\n * 不得直接 `createApp()`——新增入口默认就是对的,而不是\"新增一次漏传机会\"。\n * 有架构门禁测试钉住这条(`create-host-app-sole-assembly.test.ts`)。\n */\nimport { createApp } from '@x-otto/coding'\n\nimport { bundledExtensionsDir } from './bundled-extensions'\n\nimport type { App, AppOptions } from '@x-otto/coding'\n\n/**\n * 宿主自描述参数——「otto 这个发布物自己带了什么」。\n *\n * 单独导出而非内联,是因为 engine 子进程(`coding/src/engine-main.ts`,在 coding 包内、\n * 拿不到 cli 的 dist 布局)需要由父进程把该值显式下传(RFC-364 T364-03)。\n */\nexport function hostSelfDescribingOptions(): Pick<AppOptions, 'pluginBundledDirs'> {\n const dir = bundledExtensionsDir()\n return { pluginBundledDirs: dir ? [dir] : [] }\n}\n\n/**\n * 装配一个 App:调用方给业务参数,宿主自描述参数由本函数补齐。\n *\n * 调用方**可以**显式覆盖 `pluginBundledDirs`(测试隔离、非常规部署),\n * 显式传入时以调用方为准——保留逃生舱,但缺省不再是\"什么都没有\"。\n */\nexport function createHostApp(options: AppOptions): App {\n return createApp({\n ...hostSelfDescribingOptions(),\n ...options,\n })\n}\n"],"mappings":";gNA6CA,SAAgB,GAAmE,CACjF,IAAM,EAAM,GAAsB,CAClC,MAAO,CAAE,kBAAmB,EAAM,CAAC,EAAI,CAAG,EAAE,CAAE,CAShD,SAAgB,EAAc,EAA0B,CACtD,OAAO,EAAU,CACf,GAAG,GAA2B,CAC9B,GAAG,EACJ,CAAC"}
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import{arch as e,cpus as t,freemem as n,homedir as r,platform as i,release as a,totalmem as o}from"node:os";import{join as s}from"node:path";import{accessSync as c,constants as l,existsSync as u,statSync as d}from"node:fs";import{SESSION_MAX_HISTORY_MESSAGES as f,SESSION_MAX_PER_SESSION_BYTES as p,SESSION_MIN_PER_SESSION_BYTES as m,SESSION_RESUME_WINDOW_MESSAGES as h,SESSION_TOTAL_RESIDENCY_BUDGET as g}from"@x-otto/session-contract";const _=s(r(),`.otto`);async function v(e,t){let n=t.stdout,r=t.stderr,i=e.find(e=>!e.startsWith(`-`));if(i===`ps`||i===`attach`||i===`heap`){let n=e.filter(e=>e!==i);switch(i){case`ps`:{let{runDoctorPs:e}=await import(`./doctor-ps-DNVOF6yY.js`);return e(n,t)}case`attach`:{let{runDoctorAttach:e}=await import(`./doctor-attach-DEoE7eFx.js`);return e(n,t)}case`heap`:{let{runDoctorHeap:e}=await import(`./doctor-heap-SIxiRvL8.js`);return e(n,t)}}}if(i!==void 0)return r(`otto doctor: unknown subcommand "${i}" (available: ps, attach, heap)\n`),1;if(e.includes(`--help`)||e.includes(`-h`))return A(n),0;let a=e.includes(`--json`)?`json`:e.includes(`--summary`)?`summary`:`detailed`;try{let e=await y();return a===`json`?n(JSON.stringify(e,null,2)+`
3
- `):a===`summary`?O(n,e):k(n,e),0}catch(e){return r(`otto doctor: ${e instanceof Error?e.message:String(e)}\n`),1}}async function y(){let e=await b(),t=await x(),n=await w(),r=await T(),{plugins:i,providerPluginDiscovered:a}=await S();return{environment:e,configuration:t,state:n,connectivity:r,plugins:i,ftue:await C(a)}}async function b(){return{runtime:`Node.js ${process.version}`,installPath:process.execPath,shell:process.env.SHELL??`(unknown)`,terminal:process.env.TERM??`(unknown)`,cpu:`${t().length}×${e()}`,memory:{total:o(),free:n()},platform:i(),release:a()}}async function x(){let e=process.env.ANTHROPIC_API_KEY?`api-key (ANTHROPIC_API_KEY)`:process.env.OTTO_API_KEY?`api-key (OTTO_API_KEY)`:process.env.AWS_ACCESS_KEY_ID?`aws-bedrock (env keys)`:`not configured`;return{workspaceDir:process.cwd(),storageDir:_,hasRipgrep:await E(`rg`),hasGit:await E(`git`),authMode:e,residencyBudget:{totalBytes:g,minPerSessionBytes:m,maxPerSessionBytes:p,historyMessages:f,resumeWindowMessages:h}}}async function S(){try{let{discoverPluginsWithBundled:e}=await import(`./discover-with-bundled-B5d4q___.js`),{hostSelfDescribingOptions:t}=await import(`./create-host-app-BgjEc81C.js`).then(e=>e.n),{checkPluginAssembly:n,formatAssemblyGap:i}=await import(`./plugin-assembly-check-BntT5W6H.js`),a=e({cwd:process.cwd(),homedir:r()}),o=t().pluginBundledDirs??[],s=a.some(e=>e.manifest.capabilities?.includes(`provider`)),c=a.filter(e=>e.scope===`bundled`).length>0&&o.length===0,l=n({discovered:a.map(e=>({id:e.id,scope:e.scope})),active:c?a.filter(e=>e.scope!==`bundled`).map(e=>({id:e.id,scope:e.scope})):a.map(e=>({id:e.id,scope:e.scope}))});return{plugins:{discovered:a.length,gaps:i(l),missing:l.missingFromRuntime},providerPluginDiscovered:s}}catch{return{plugins:void 0,providerPluginDiscovered:!1}}}async function C(e){try{let{createDefaultAuthStore:t}=await import(`@x-otto/ai`),{deriveDoctorFtueGuidance:n}=await import(`./ftue-doctor-recovery-CKTMnxM8.js`),{SettingsManager:r}=await import(`@x-otto/setting`),{OTTO_CONFIG_FILENAME:i}=await import(`@x-otto/env`),a=t({watchForExternalChanges:!1});await a.ensureInitialized();let o=a.listProviders().length>0,c=new r({projectConfigPath:s(_,i)});return await c.load(),n({providerPluginDiscovered:e,hasAnyCredential:o,firstPromptSubmitted:typeof c.get(`first_prompt_submitted_at`)==`number`})}catch{return}}async function w(){let e=s(_,`storage.db`);if(!u(e))return{dbPath:e,dbExists:!1};try{let{SqlitePersistence:t}=await import(`@x-otto/persistence`),n=new t({dbPath:e,namespace:`doctor`,wal:!1});try{let t=n.getInfo();return{dbPath:e,dbExists:!0,dbSize:D(e),...t}}finally{n.close()}}catch{return{dbPath:e,dbExists:!0,integrity:`error (unable to open)`}}}async function T(){let e=`api.anthropic.com`,t=!1,n,r=Date.now();try{let i=new AbortController,a=setTimeout(()=>i.abort(),5e3);await fetch(`https://${e}`,{method:`HEAD`,signal:i.signal}),clearTimeout(a),t=!0,n=Date.now()-r}catch{t=!1}return{providerHost:e,providerReachable:t,providerLatency:n,storageHostConfigured:!1}}async function E(e){let t=new Set;for(let e of(process.env.PATH??``).split(`:`))e&&t.add(e);t.add(`/usr/local/bin`),t.add(`/usr/bin`);for(let n of t)try{return c(s(n,e),l.X_OK),!0}catch{}return!1}function D(e){try{return d(e).size}catch{return 0}}function O(e,t){let{environment:n,configuration:r,state:i,connectivity:a}=t,o=i?.dbExists&&i.integrity===`ok`?`✅`:`❌`,s=a.providerReachable?`✅`:`❌`,c=t.plugins&&t.plugins.gaps.length>0?` ⚠ 插件装配缺口 ${t.plugins.missing.length} 项(otto doctor 查看详情)`:``,l=t.ftue&&t.ftue.status!==`looks-configured`?` ⚠ ${t.ftue.summary} → ${t.ftue.recoveryAction}`:``;e([`otto doctor — ${n.runtime} | ${r.storageDir} | DB:${o} ${s}`,i?` ${i.snapshots??`?`} snapshots, ${i.changeLogEntries??`?`} change_log`:``,c,l].filter(Boolean).join(`
3
+ `):a===`summary`?O(n,e):k(n,e),0}catch(e){return r(`otto doctor: ${e instanceof Error?e.message:String(e)}\n`),1}}async function y(){let e=await b(),t=await x(),n=await w(),r=await T(),{plugins:i,providerPluginDiscovered:a}=await S();return{environment:e,configuration:t,state:n,connectivity:r,plugins:i,ftue:await C(a)}}async function b(){return{runtime:`Node.js ${process.version}`,installPath:process.execPath,shell:process.env.SHELL??`(unknown)`,terminal:process.env.TERM??`(unknown)`,cpu:`${t().length}×${e()}`,memory:{total:o(),free:n()},platform:i(),release:a()}}async function x(){let e=process.env.ANTHROPIC_API_KEY?`api-key (ANTHROPIC_API_KEY)`:process.env.OTTO_API_KEY?`api-key (OTTO_API_KEY)`:process.env.AWS_ACCESS_KEY_ID?`aws-bedrock (env keys)`:`not configured`;return{workspaceDir:process.cwd(),storageDir:_,hasRipgrep:await E(`rg`),hasGit:await E(`git`),authMode:e,residencyBudget:{totalBytes:g,minPerSessionBytes:m,maxPerSessionBytes:p,historyMessages:f,resumeWindowMessages:h}}}async function S(){try{let{discoverPluginsWithBundled:e}=await import(`./discover-with-bundled-B5d4q___.js`),{hostSelfDescribingOptions:t}=await import(`./create-host-app-BtifD7KK.js`).then(e=>e.n),{checkPluginAssembly:n,formatAssemblyGap:i}=await import(`./plugin-assembly-check-BntT5W6H.js`),a=e({cwd:process.cwd(),homedir:r()}),o=t().pluginBundledDirs??[],s=a.some(e=>e.manifest.capabilities?.includes(`provider`)),c=a.filter(e=>e.scope===`bundled`).length>0&&o.length===0,l=n({discovered:a.map(e=>({id:e.id,scope:e.scope})),active:c?a.filter(e=>e.scope!==`bundled`).map(e=>({id:e.id,scope:e.scope})):a.map(e=>({id:e.id,scope:e.scope}))});return{plugins:{discovered:a.length,gaps:i(l),missing:l.missingFromRuntime},providerPluginDiscovered:s}}catch{return{plugins:void 0,providerPluginDiscovered:!1}}}async function C(e){try{let{createDefaultAuthStore:t}=await import(`@x-otto/ai`),{deriveDoctorFtueGuidance:n}=await import(`./ftue-doctor-recovery-CKTMnxM8.js`),{SettingsManager:r}=await import(`@x-otto/setting`),{OTTO_CONFIG_FILENAME:i}=await import(`@x-otto/env`),a=t({watchForExternalChanges:!1});await a.ensureInitialized();let o=a.listProviders().length>0,c=new r({projectConfigPath:s(_,i)});return await c.load(),n({providerPluginDiscovered:e,hasAnyCredential:o,firstPromptSubmitted:typeof c.get(`first_prompt_submitted_at`)==`number`})}catch{return}}async function w(){let e=s(_,`storage.db`);if(!u(e))return{dbPath:e,dbExists:!1};try{let{SqlitePersistence:t}=await import(`@x-otto/persistence`),n=new t({dbPath:e,namespace:`doctor`,wal:!1});try{let t=n.getInfo();return{dbPath:e,dbExists:!0,dbSize:D(e),...t}}finally{n.close()}}catch{return{dbPath:e,dbExists:!0,integrity:`error (unable to open)`}}}async function T(){let e=`api.anthropic.com`,t=!1,n,r=Date.now();try{let i=new AbortController,a=setTimeout(()=>i.abort(),5e3);await fetch(`https://${e}`,{method:`HEAD`,signal:i.signal}),clearTimeout(a),t=!0,n=Date.now()-r}catch{t=!1}return{providerHost:e,providerReachable:t,providerLatency:n,storageHostConfigured:!1}}async function E(e){let t=new Set;for(let e of(process.env.PATH??``).split(`:`))e&&t.add(e);t.add(`/usr/local/bin`),t.add(`/usr/bin`);for(let n of t)try{return c(s(n,e),l.X_OK),!0}catch{}return!1}function D(e){try{return d(e).size}catch{return 0}}function O(e,t){let{environment:n,configuration:r,state:i,connectivity:a}=t,o=i?.dbExists&&i.integrity===`ok`?`✅`:`❌`,s=a.providerReachable?`✅`:`❌`,c=t.plugins&&t.plugins.gaps.length>0?` ⚠ 插件装配缺口 ${t.plugins.missing.length} 项(otto doctor 查看详情)`:``,l=t.ftue&&t.ftue.status!==`looks-configured`?` ⚠ ${t.ftue.summary} → ${t.ftue.recoveryAction}`:``;e([`otto doctor — ${n.runtime} | ${r.storageDir} | DB:${o} ${s}`,i?` ${i.snapshots??`?`} snapshots, ${i.changeLogEntries??`?`} change_log`:``,c,l].filter(Boolean).join(`
4
4
  `)+`
5
5
  `)}function k(e,t){let{environment:n,configuration:r,state:i,connectivity:a}=t;if(e(`
6
6
  Environment
@@ -33,4 +33,4 @@ First-time setup
33
33
  otto doctor --summary
34
34
  otto doctor --json
35
35
  `)}function j(e){if(e===0)return`0 B`;let t=[`B`,`KB`,`MB`,`GB`,`TB`],n=Math.floor(Math.log(e)/Math.log(1024));return`${(e/1024**n).toFixed(1)} ${t[n]}`}export{v as runDoctorCommand};
36
- //# sourceMappingURL=doctor-2AA2A9G7.js.map
36
+ //# sourceMappingURL=doctor-BAxvhLws.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"doctor-2AA2A9G7.js","names":[],"sources":["../src/commands/doctor.ts"],"sourcesContent":["import { homedir, release, platform, arch, cpus, totalmem, freemem } from 'node:os'\nimport { existsSync, statSync, accessSync, constants } from 'node:fs'\nimport { join } from 'node:path'\nimport {\n SESSION_TOTAL_RESIDENCY_BUDGET,\n SESSION_MIN_PER_SESSION_BYTES,\n SESSION_MAX_PER_SESSION_BYTES,\n SESSION_MAX_HISTORY_MESSAGES,\n SESSION_RESUME_WINDOW_MESSAGES,\n} from '@x-otto/session-contract'\nexport interface DoctorDeps {\n stdout: (s: string) => void\n stderr: (s: string) => void\n}\n\nexport interface DoctorReport {\n environment: EnvironmentSection\n configuration: ConfigurationSection\n state?: StateSection\n connectivity: ConnectivitySection\n /** RFC-364 M2:插件装配一致性——发现得到却未装载的缺口。 */\n plugins?: PluginsSection\n /** RFC-326 M6-01:FTUE 环境自检(廉价事实推导,非权威 readiness 判定,见模块头注释)。 */\n ftue?: import('./ftue/ftue-doctor-recovery').DoctorFtueGuidance\n}\n\ninterface PluginsSection {\n discovered: number\n /** 装配缺口的可执行诊断行(空数组 = 无缺口)。 */\n gaps: string[]\n /** 缺口涉及的插件 id(供 --json 消费方精确处理)。 */\n missing: Array<{ id: string; scope?: string }>\n}\n\ninterface EnvironmentSection {\n runtime: string\n installPath: string\n shell: string\n terminal: string\n cpu: string\n memory: { total: number; free: number }\n platform: string\n release: string\n}\n\ninterface ConfigurationSection {\n workspaceDir: string\n storageDir: string\n hasRipgrep: boolean\n hasGit: boolean\n authMode: string\n /** RFC-323:驻留预算配置(只读展示,使基线估算偏差可被校准) */\n residencyBudget?: {\n totalBytes: number\n minPerSessionBytes: number\n maxPerSessionBytes: number\n historyMessages: number\n resumeWindowMessages: number\n }\n}\n\ninterface StateSection {\n dbPath: string\n dbExists: boolean\n dbSize?: number\n walEnabled?: boolean\n snapshots?: number\n changeLogEntries?: number\n sessions?: number\n integrity?: string\n}\n\ninterface ConnectivitySection {\n providerHost: string\n providerReachable: boolean\n providerLatency?: number\n storageHostConfigured: boolean\n}\n\nconst OTTO_DIR = join(homedir(), '.otto')\n\nexport async function runDoctorCommand(\n args: string[],\n deps: DoctorDeps,\n): Promise<number> {\n const stdout = deps.stdout\n const stderr = deps.stderr\n\n // RFC-200 D1.0:子命令路由——首个位置参数(非 -- 开头)分发到诊断子命令;\n // 无子命令时走既有健康检查(行为零变化)。\n const sub = args.find((a) => !a.startsWith('-'))\n if (sub === 'ps' || sub === 'attach' || sub === 'heap') {\n const rest = args.filter((a) => a !== sub)\n switch (sub) {\n case 'ps': {\n const { runDoctorPs } = await import('./handlers/doctor-ps')\n return runDoctorPs(rest, deps)\n }\n case 'attach': {\n const { runDoctorAttach } = await import('./handlers/doctor-attach')\n return runDoctorAttach(rest, deps)\n }\n case 'heap': {\n const { runDoctorHeap } = await import('./handlers/doctor-heap')\n return runDoctorHeap(rest, deps)\n }\n }\n }\n if (sub !== undefined) {\n stderr(`otto doctor: unknown subcommand \"${sub}\" (available: ps, attach, heap)\\n`)\n return 1\n }\n\n if (args.includes('--help') || args.includes('-h')) {\n printHelp(stdout)\n return 0\n }\n\n const outputFormat = args.includes('--json') ? 'json' : args.includes('--summary') ? 'summary' : 'detailed'\n\n try {\n const report = await collectReport()\n\n if (outputFormat === 'json') {\n stdout(JSON.stringify(report, null, 2) + '\\n')\n } else if (outputFormat === 'summary') {\n printSummary(stdout, report)\n } else {\n printDetailed(stdout, report)\n }\n\n return 0\n } catch (error) {\n stderr(`otto doctor: ${error instanceof Error ? error.message : String(error)}\\n`)\n return 1\n }\n}\n\n/** 收集结构化诊断报告(TUI /doctor 折叠档复用;不打印)。 */\nexport async function collectReport(): Promise<DoctorReport> {\n const env = await collectEnvironment()\n const config = await collectConfiguration()\n const state = await collectState()\n const connectivity = await collectConnectivity()\n const { plugins, providerPluginDiscovered } = await collectPluginsAndProviderFlag()\n const ftue = await collectFtueGuidance(providerPluginDiscovered)\n\n return { environment: env, configuration: config, state, connectivity, plugins, ftue }\n}\n\nasync function collectEnvironment(): Promise<EnvironmentSection> {\n return {\n runtime: `Node.js ${process.version}`,\n installPath: process.execPath,\n shell: process.env.SHELL ?? '(unknown)',\n terminal: process.env.TERM ?? '(unknown)',\n cpu: `${cpus().length}×${arch()}`,\n memory: { total: totalmem(), free: freemem() },\n platform: platform(),\n release: release(),\n }\n}\n\nasync function collectConfiguration(): Promise<ConfigurationSection> {\n const authMode = process.env.ANTHROPIC_API_KEY\n ? 'api-key (ANTHROPIC_API_KEY)'\n : process.env.OTTO_API_KEY\n ? 'api-key (OTTO_API_KEY)'\n : process.env.AWS_ACCESS_KEY_ID\n ? 'aws-bedrock (env keys)'\n : 'not configured'\n\n return {\n workspaceDir: process.cwd(),\n storageDir: OTTO_DIR,\n hasRipgrep: await checkBinary('rg'),\n hasGit: await checkBinary('git'),\n authMode,\n // RFC-323 M3:暴露驻留预算配置,使\"基线 1500MB 估算偏差\"(R-2)可被真机数据校准。\n // 纯静态读常量,不接运行时状态——运行时有效分配值取决于活跃会话数 N,在 ps/doctor-ps 展示。\n residencyBudget: {\n totalBytes: SESSION_TOTAL_RESIDENCY_BUDGET,\n minPerSessionBytes: SESSION_MIN_PER_SESSION_BYTES,\n maxPerSessionBytes: SESSION_MAX_PER_SESSION_BYTES,\n historyMessages: SESSION_MAX_HISTORY_MESSAGES,\n resumeWindowMessages: SESSION_RESUME_WINDOW_MESSAGES,\n },\n }\n}\n\n/**\n * RFC-364 M2:插件装配一致性自检。\n *\n * 判据是「cli 侧完整发现(含 bundled 层)」与「App 实际装载」的差集。但 doctor 是\n * 轻量诊断命令(RFC-150 D3 明确要求不承担重依赖),构造真实 App 代价过高——\n * 故此处只做**发现侧**的自检:确认 bundled 目录可解析且其中的扩展能被发现。\n *\n * 真正的「发现 vs 装载」全量比对由运行时侧承担(App 启动路径),本处提供的是\n * 用户可主动运行的诊断入口:当用户报告\"插件不见了\"时,`otto doctor` 能立刻回答\n * \"otto 这一侧到底看见了哪些\"。\n */\n/**\n * RFC-326 M6-01:与 `providerPluginDiscovered` 标志合并计算——`discoverPluginsWithBundled`\n * 是本函数唯一的重量级调用,`collectFtueGuidance` 需要同一份发现结果的 `provider`\n * capability 过滤,若各自重新扫描一次会重复触发文件系统遍历。`providerPluginDiscovered`\n * 在扫描失败时缺省 `false`(诊断失败不阻断其余部分,与既有 fail-soft 语义一致;\n * FTUE 分支据此会诚实报告\"未发现 provider 插件\"而非静默跳过整个 ftue 字段)。\n */\nasync function collectPluginsAndProviderFlag(): Promise<{\n plugins: PluginsSection | undefined\n providerPluginDiscovered: boolean\n}> {\n try {\n const { discoverPluginsWithBundled } = await import('../discover-with-bundled')\n const { hostSelfDescribingOptions } = await import('../create-host-app')\n const { checkPluginAssembly, formatAssemblyGap } = await import('../plugin-assembly-check')\n\n const discovered = discoverPluginsWithBundled({ cwd: process.cwd(), homedir: homedir() })\n const bundledDirs = hostSelfDescribingOptions().pluginBundledDirs ?? []\n const providerPluginDiscovered = discovered.some((p) => p.manifest.capabilities?.includes('provider'))\n\n // 宿主自描述参数解析不出目录,但发现层却报告了 bundled 作用域插件(或反之)——\n // 这正是 M1 修的那类装配漂移,在此处显式暴露。\n const bundledSeen = discovered.filter((p) => p.scope === 'bundled')\n const assemblyBroken = bundledSeen.length > 0 && bundledDirs.length === 0\n\n const result = checkPluginAssembly({\n discovered: discovered.map((p) => ({ id: p.id, scope: p.scope })),\n // 发现侧自检:把「宿主参数解析失败」建模为\"全部 bundled 未装载\"\n active: assemblyBroken\n ? discovered.filter((p) => p.scope !== 'bundled').map((p) => ({ id: p.id, scope: p.scope }))\n : discovered.map((p) => ({ id: p.id, scope: p.scope })),\n })\n\n return {\n plugins: {\n discovered: discovered.length,\n gaps: formatAssemblyGap(result),\n missing: result.missingFromRuntime,\n },\n providerPluginDiscovered,\n }\n } catch {\n // 诊断失败不阻断 doctor 其余部分(与既有 collectState 的 fail-soft 一致)。\n return { plugins: undefined, providerPluginDiscovered: false }\n }\n}\n\n/**\n * RFC-326 M6-01:FTUE 环境自检——三个廉价事实推导诊断状态(见\n * `ftue-doctor-recovery.ts` 头注释的判据边界)。`AuthStore` 构造零 App 依赖\n * (默认读全局 `AUTH_PATH`),`listProviders()` 只读本地文件/env,不发网络请求。\n */\nasync function collectFtueGuidance(\n providerPluginDiscovered: boolean,\n): Promise<import('./ftue/ftue-doctor-recovery').DoctorFtueGuidance | undefined> {\n try {\n const { createDefaultAuthStore } = await import('@x-otto/ai')\n const { deriveDoctorFtueGuidance } = await import('./ftue/ftue-doctor-recovery')\n const { SettingsManager } = await import('@x-otto/setting')\n const { OTTO_CONFIG_FILENAME } = await import('@x-otto/env')\n\n // watchForExternalChanges:false —— doctor 是一次性命令,跑完即退出,不需要\n // fs.watch(同 auth-command.ts 的既有约定,见其头注释)。\n const authStore = createDefaultAuthStore({ watchForExternalChanges: false })\n // listProviders() 读内存缓存(this.cache),不像 getAuthSource/hasCredential 那样\n // 自带 ensureInitialized() 前置——首次调用前必须显式触发一次磁盘加载,否则读到空态。\n await authStore.ensureInitialized()\n const hasAnyCredential = authStore.listProviders().length > 0\n\n const settings = new SettingsManager({ projectConfigPath: join(OTTO_DIR, OTTO_CONFIG_FILENAME) })\n await settings.load()\n const firstPromptSubmitted = typeof settings.get('first_prompt_submitted_at') === 'number'\n\n return deriveDoctorFtueGuidance({ providerPluginDiscovered, hasAnyCredential, firstPromptSubmitted })\n } catch {\n // 诊断失败不阻断 doctor 其余部分。\n return undefined\n }\n}\n\nasync function collectState(): Promise<StateSection | undefined> {\n const dbPath = join(OTTO_DIR, 'storage.db')\n if (!existsSync(dbPath)) {\n return { dbPath, dbExists: false }\n }\n\n try {\n const { SqlitePersistence } = await import('@x-otto/persistence')\n const store = new SqlitePersistence({ dbPath, namespace: 'doctor', wal: false })\n try {\n const info = store.getInfo() as {\n walEnabled: boolean\n snapshots: number\n changeLogEntries: number\n sessions: number\n integrity: string\n }\n\n return {\n dbPath,\n dbExists: true,\n dbSize: getFileSizeSync(dbPath),\n ...info,\n }\n } finally {\n store.close()\n }\n } catch {\n return { dbPath, dbExists: true, integrity: 'error (unable to open)' }\n }\n}\n\nasync function collectConnectivity(): Promise<ConnectivitySection> {\n let providerHost = 'api.anthropic.com'\n let providerReachable = false\n let providerLatency: number | undefined\n\n const start = Date.now()\n try {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), 5000)\n await fetch(`https://${providerHost}`, {\n method: 'HEAD',\n signal: controller.signal,\n })\n clearTimeout(timeout)\n providerReachable = true\n providerLatency = Date.now() - start\n } catch {\n providerReachable = false\n }\n\n return {\n providerHost,\n providerReachable,\n providerLatency,\n storageHostConfigured: false,\n }\n}\n\n/**\n * 二进制探测(08-13 backlog triage 修复):此前只扫 /usr/local/bin 与 /usr/bin——\n * Homebrew 原生(/opt/homebrew/bin)、nix(~/.nix-profile/bin)等 PATH 布局被误报缺失。\n * 改为遍历 process.env.PATH 拆分项逐一探测(X_OK 替换魔数 1),并保留两个系统目录\n * 作为 PATH 未含时的兜底(部分守护进程环境 PATH 极简)。\n */\nexport async function checkBinary(name: string): Promise<boolean> {\n const dirs = new Set<string>()\n for (const p of (process.env['PATH'] ?? '').split(':')) {\n if (p) dirs.add(p)\n }\n dirs.add('/usr/local/bin')\n dirs.add('/usr/bin')\n for (const dir of dirs) {\n try {\n accessSync(join(dir, name), constants.X_OK)\n return true\n } catch {\n // 目录不存在或无可执行位——继续下一个候选。\n }\n }\n return false\n}\n\nfunction getFileSizeSync(path: string): number {\n try {\n return statSync(path).size\n } catch {\n return 0\n }\n}\n\nfunction printSummary(stdout: (s: string) => void, report: DoctorReport): void {\n const { environment: env, configuration: config, state, connectivity: conn } = report\n const dbOk = state?.dbExists && state.integrity === 'ok' ? '✅' : '❌'\n const netOk = conn.providerReachable ? '✅' : '❌'\n const pluginGap = report.plugins && report.plugins.gaps.length > 0\n ? ` ⚠ 插件装配缺口 ${report.plugins.missing.length} 项(otto doctor 查看详情)`\n : ''\n // RFC-326 M6-01:只在未配置(非 looks-configured)时提示——已配置用户不需要看\n // 这行噪音,未配置用户能立刻看到下一步命令,不必翻到 detailed 输出。\n const ftueHint = report.ftue && report.ftue.status !== 'looks-configured'\n ? ` ⚠ ${report.ftue.summary} → ${report.ftue.recoveryAction}`\n : ''\n stdout([\n `otto doctor — ${env.runtime} | ${config.storageDir} | DB:${dbOk} ${netOk}`,\n state ? ` ${state.snapshots ?? '?'} snapshots, ${state.changeLogEntries ?? '?'} change_log` : '',\n pluginGap,\n ftueHint,\n ].filter(Boolean).join('\\n') + '\\n')\n}\n\nfunction printDetailed(stdout: (s: string) => void, report: DoctorReport): void {\n const { environment: env, configuration: config, state, connectivity: conn } = report\n\n stdout('\\nEnvironment\\n')\n stdout(` Runtime: ${env.runtime}\\n`)\n stdout(` Install: ${env.installPath}\\n`)\n stdout(` Shell: ${env.shell}\\n`)\n stdout(` Terminal: ${env.terminal} (${env.release})\\n`)\n stdout(` CPU: ${env.cpu}\\n`)\n stdout(` Memory: ${formatBytes(env.memory.free)} / ${formatBytes(env.memory.total)} free\\n`)\n if (config.residencyBudget) {\n const rb = config.residencyBudget\n stdout(` Residency: budget=${formatBytes(rb.totalBytes)} per-session=${formatBytes(rb.minPerSessionBytes)}~${formatBytes(rb.maxPerSessionBytes)} history=${rb.historyMessages} resume=${rb.resumeWindowMessages}\\n`)\n }\n\n stdout('\\nConfiguration\\n')\n stdout(` Workspace: ${config.workspaceDir}\\n`)\n stdout(` Storage: ${config.storageDir} ${existsSync(config.storageDir) ? '✅' : '❌'}\\n`)\n stdout(` ripgrep: ${config.hasRipgrep ? '✅' : '❌'}\\n`)\n stdout(` git: ${config.hasGit ? '✅' : '❌'}\\n`)\n stdout(` Auth: ${config.authMode}\\n`)\n\n // RFC-364 M2:插件装配一致性——有缺口才打印(无缺口时不制造噪音)。\n if (report.plugins) {\n stdout('\\nPlugins\\n')\n stdout(` Discovered: ${report.plugins.discovered}\\n`)\n if (report.plugins.gaps.length > 0) {\n stdout(' Assembly: ❌ 检测到装配缺口\\n')\n for (const line of report.plugins.gaps) stdout(` ${line}\\n`)\n } else {\n stdout(' Assembly: ✅\\n')\n }\n }\n\n if (state) {\n stdout('\\nState\\n')\n stdout(` SQLite DB: ${state.dbPath}\\n`)\n if (state.dbExists) {\n stdout(` size: ${formatBytes(state.dbSize ?? 0)}\\n`)\n stdout(` integrity: ${state.integrity === 'ok' ? 'ok ✅' : state.integrity ?? 'N/A'}\\n`)\n if (state.snapshots !== undefined) stdout(` snapshots: ${state.snapshots}\\n`)\n if (state.changeLogEntries !== undefined) stdout(` change_log: ${state.changeLogEntries} entries\\n`)\n if (state.sessions !== undefined) stdout(` sessions: ${state.sessions}\\n`)\n if (state.walEnabled !== undefined) stdout(` WAL: ${state.walEnabled ? 'enabled' : 'disabled'}\\n`)\n } else {\n stdout(` (no storage.db found — start otto to create one)\\n`)\n }\n }\n\n stdout('\\nConnectivity\\n')\n stdout(` ${conn.providerHost}: ${conn.providerReachable ? `reachable (${conn.providerLatency}ms) ✅` : 'unreachable ❌'}\\n`)\n stdout(` storage host: ${conn.storageHostConfigured ? 'configured' : 'not configured (local only)'}\\n`)\n\n // RFC-326 M6-01:FTUE 环境自检——廉价事实推导,非权威 readiness 判定\n // (见 ftue-doctor-recovery.ts 头注释;权威判定只在 TUI 运行时的 FtueController)。\n if (report.ftue) {\n const icon = report.ftue.status === 'looks-configured' ? '✅' : '⚠'\n stdout('\\nFirst-time setup\\n')\n stdout(` ${icon} ${report.ftue.summary}\\n`)\n stdout(` → ${report.ftue.recoveryAction}\\n`)\n }\n\n stdout('\\n')\n}\n\nfunction printHelp(stdout: (s: string) => void): void {\n stdout(`otto doctor — 环境健康诊断\n\n用法:\n otto doctor [--summary|--json]\n\n选项:\n --summary 紧凑输出(一行摘要)\n --json 结构化 JSON(redacted)\n --help, -h 帮助信息\n\n示例:\n otto doctor\n otto doctor --summary\n otto doctor --json\n`)\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) return '0 B'\n const units = ['B', 'KB', 'MB', 'GB', 'TB']\n const i = Math.floor(Math.log(bytes) / Math.log(1024))\n return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"doctor-BAxvhLws.js","names":[],"sources":["../src/commands/doctor.ts"],"sourcesContent":["import { homedir, release, platform, arch, cpus, totalmem, freemem } from 'node:os'\nimport { existsSync, statSync, accessSync, constants } from 'node:fs'\nimport { join } from 'node:path'\nimport {\n SESSION_TOTAL_RESIDENCY_BUDGET,\n SESSION_MIN_PER_SESSION_BYTES,\n SESSION_MAX_PER_SESSION_BYTES,\n SESSION_MAX_HISTORY_MESSAGES,\n SESSION_RESUME_WINDOW_MESSAGES,\n} from '@x-otto/session-contract'\nexport interface DoctorDeps {\n stdout: (s: string) => void\n stderr: (s: string) => void\n}\n\nexport interface DoctorReport {\n environment: EnvironmentSection\n configuration: ConfigurationSection\n state?: StateSection\n connectivity: ConnectivitySection\n /** RFC-364 M2:插件装配一致性——发现得到却未装载的缺口。 */\n plugins?: PluginsSection\n /** RFC-326 M6-01:FTUE 环境自检(廉价事实推导,非权威 readiness 判定,见模块头注释)。 */\n ftue?: import('./ftue/ftue-doctor-recovery').DoctorFtueGuidance\n}\n\ninterface PluginsSection {\n discovered: number\n /** 装配缺口的可执行诊断行(空数组 = 无缺口)。 */\n gaps: string[]\n /** 缺口涉及的插件 id(供 --json 消费方精确处理)。 */\n missing: Array<{ id: string; scope?: string }>\n}\n\ninterface EnvironmentSection {\n runtime: string\n installPath: string\n shell: string\n terminal: string\n cpu: string\n memory: { total: number; free: number }\n platform: string\n release: string\n}\n\ninterface ConfigurationSection {\n workspaceDir: string\n storageDir: string\n hasRipgrep: boolean\n hasGit: boolean\n authMode: string\n /** RFC-323:驻留预算配置(只读展示,使基线估算偏差可被校准) */\n residencyBudget?: {\n totalBytes: number\n minPerSessionBytes: number\n maxPerSessionBytes: number\n historyMessages: number\n resumeWindowMessages: number\n }\n}\n\ninterface StateSection {\n dbPath: string\n dbExists: boolean\n dbSize?: number\n walEnabled?: boolean\n snapshots?: number\n changeLogEntries?: number\n sessions?: number\n integrity?: string\n}\n\ninterface ConnectivitySection {\n providerHost: string\n providerReachable: boolean\n providerLatency?: number\n storageHostConfigured: boolean\n}\n\nconst OTTO_DIR = join(homedir(), '.otto')\n\nexport async function runDoctorCommand(\n args: string[],\n deps: DoctorDeps,\n): Promise<number> {\n const stdout = deps.stdout\n const stderr = deps.stderr\n\n // RFC-200 D1.0:子命令路由——首个位置参数(非 -- 开头)分发到诊断子命令;\n // 无子命令时走既有健康检查(行为零变化)。\n const sub = args.find((a) => !a.startsWith('-'))\n if (sub === 'ps' || sub === 'attach' || sub === 'heap') {\n const rest = args.filter((a) => a !== sub)\n switch (sub) {\n case 'ps': {\n const { runDoctorPs } = await import('./handlers/doctor-ps')\n return runDoctorPs(rest, deps)\n }\n case 'attach': {\n const { runDoctorAttach } = await import('./handlers/doctor-attach')\n return runDoctorAttach(rest, deps)\n }\n case 'heap': {\n const { runDoctorHeap } = await import('./handlers/doctor-heap')\n return runDoctorHeap(rest, deps)\n }\n }\n }\n if (sub !== undefined) {\n stderr(`otto doctor: unknown subcommand \"${sub}\" (available: ps, attach, heap)\\n`)\n return 1\n }\n\n if (args.includes('--help') || args.includes('-h')) {\n printHelp(stdout)\n return 0\n }\n\n const outputFormat = args.includes('--json') ? 'json' : args.includes('--summary') ? 'summary' : 'detailed'\n\n try {\n const report = await collectReport()\n\n if (outputFormat === 'json') {\n stdout(JSON.stringify(report, null, 2) + '\\n')\n } else if (outputFormat === 'summary') {\n printSummary(stdout, report)\n } else {\n printDetailed(stdout, report)\n }\n\n return 0\n } catch (error) {\n stderr(`otto doctor: ${error instanceof Error ? error.message : String(error)}\\n`)\n return 1\n }\n}\n\n/** 收集结构化诊断报告(TUI /doctor 折叠档复用;不打印)。 */\nexport async function collectReport(): Promise<DoctorReport> {\n const env = await collectEnvironment()\n const config = await collectConfiguration()\n const state = await collectState()\n const connectivity = await collectConnectivity()\n const { plugins, providerPluginDiscovered } = await collectPluginsAndProviderFlag()\n const ftue = await collectFtueGuidance(providerPluginDiscovered)\n\n return { environment: env, configuration: config, state, connectivity, plugins, ftue }\n}\n\nasync function collectEnvironment(): Promise<EnvironmentSection> {\n return {\n runtime: `Node.js ${process.version}`,\n installPath: process.execPath,\n shell: process.env.SHELL ?? '(unknown)',\n terminal: process.env.TERM ?? '(unknown)',\n cpu: `${cpus().length}×${arch()}`,\n memory: { total: totalmem(), free: freemem() },\n platform: platform(),\n release: release(),\n }\n}\n\nasync function collectConfiguration(): Promise<ConfigurationSection> {\n const authMode = process.env.ANTHROPIC_API_KEY\n ? 'api-key (ANTHROPIC_API_KEY)'\n : process.env.OTTO_API_KEY\n ? 'api-key (OTTO_API_KEY)'\n : process.env.AWS_ACCESS_KEY_ID\n ? 'aws-bedrock (env keys)'\n : 'not configured'\n\n return {\n workspaceDir: process.cwd(),\n storageDir: OTTO_DIR,\n hasRipgrep: await checkBinary('rg'),\n hasGit: await checkBinary('git'),\n authMode,\n // RFC-323 M3:暴露驻留预算配置,使\"基线 1500MB 估算偏差\"(R-2)可被真机数据校准。\n // 纯静态读常量,不接运行时状态——运行时有效分配值取决于活跃会话数 N,在 ps/doctor-ps 展示。\n residencyBudget: {\n totalBytes: SESSION_TOTAL_RESIDENCY_BUDGET,\n minPerSessionBytes: SESSION_MIN_PER_SESSION_BYTES,\n maxPerSessionBytes: SESSION_MAX_PER_SESSION_BYTES,\n historyMessages: SESSION_MAX_HISTORY_MESSAGES,\n resumeWindowMessages: SESSION_RESUME_WINDOW_MESSAGES,\n },\n }\n}\n\n/**\n * RFC-364 M2:插件装配一致性自检。\n *\n * 判据是「cli 侧完整发现(含 bundled 层)」与「App 实际装载」的差集。但 doctor 是\n * 轻量诊断命令(RFC-150 D3 明确要求不承担重依赖),构造真实 App 代价过高——\n * 故此处只做**发现侧**的自检:确认 bundled 目录可解析且其中的扩展能被发现。\n *\n * 真正的「发现 vs 装载」全量比对由运行时侧承担(App 启动路径),本处提供的是\n * 用户可主动运行的诊断入口:当用户报告\"插件不见了\"时,`otto doctor` 能立刻回答\n * \"otto 这一侧到底看见了哪些\"。\n */\n/**\n * RFC-326 M6-01:与 `providerPluginDiscovered` 标志合并计算——`discoverPluginsWithBundled`\n * 是本函数唯一的重量级调用,`collectFtueGuidance` 需要同一份发现结果的 `provider`\n * capability 过滤,若各自重新扫描一次会重复触发文件系统遍历。`providerPluginDiscovered`\n * 在扫描失败时缺省 `false`(诊断失败不阻断其余部分,与既有 fail-soft 语义一致;\n * FTUE 分支据此会诚实报告\"未发现 provider 插件\"而非静默跳过整个 ftue 字段)。\n */\nasync function collectPluginsAndProviderFlag(): Promise<{\n plugins: PluginsSection | undefined\n providerPluginDiscovered: boolean\n}> {\n try {\n const { discoverPluginsWithBundled } = await import('../discover-with-bundled')\n const { hostSelfDescribingOptions } = await import('../create-host-app')\n const { checkPluginAssembly, formatAssemblyGap } = await import('../plugin-assembly-check')\n\n const discovered = discoverPluginsWithBundled({ cwd: process.cwd(), homedir: homedir() })\n const bundledDirs = hostSelfDescribingOptions().pluginBundledDirs ?? []\n const providerPluginDiscovered = discovered.some((p) => p.manifest.capabilities?.includes('provider'))\n\n // 宿主自描述参数解析不出目录,但发现层却报告了 bundled 作用域插件(或反之)——\n // 这正是 M1 修的那类装配漂移,在此处显式暴露。\n const bundledSeen = discovered.filter((p) => p.scope === 'bundled')\n const assemblyBroken = bundledSeen.length > 0 && bundledDirs.length === 0\n\n const result = checkPluginAssembly({\n discovered: discovered.map((p) => ({ id: p.id, scope: p.scope })),\n // 发现侧自检:把「宿主参数解析失败」建模为\"全部 bundled 未装载\"\n active: assemblyBroken\n ? discovered.filter((p) => p.scope !== 'bundled').map((p) => ({ id: p.id, scope: p.scope }))\n : discovered.map((p) => ({ id: p.id, scope: p.scope })),\n })\n\n return {\n plugins: {\n discovered: discovered.length,\n gaps: formatAssemblyGap(result),\n missing: result.missingFromRuntime,\n },\n providerPluginDiscovered,\n }\n } catch {\n // 诊断失败不阻断 doctor 其余部分(与既有 collectState 的 fail-soft 一致)。\n return { plugins: undefined, providerPluginDiscovered: false }\n }\n}\n\n/**\n * RFC-326 M6-01:FTUE 环境自检——三个廉价事实推导诊断状态(见\n * `ftue-doctor-recovery.ts` 头注释的判据边界)。`AuthStore` 构造零 App 依赖\n * (默认读全局 `AUTH_PATH`),`listProviders()` 只读本地文件/env,不发网络请求。\n */\nasync function collectFtueGuidance(\n providerPluginDiscovered: boolean,\n): Promise<import('./ftue/ftue-doctor-recovery').DoctorFtueGuidance | undefined> {\n try {\n const { createDefaultAuthStore } = await import('@x-otto/ai')\n const { deriveDoctorFtueGuidance } = await import('./ftue/ftue-doctor-recovery')\n const { SettingsManager } = await import('@x-otto/setting')\n const { OTTO_CONFIG_FILENAME } = await import('@x-otto/env')\n\n // watchForExternalChanges:false —— doctor 是一次性命令,跑完即退出,不需要\n // fs.watch(同 auth-command.ts 的既有约定,见其头注释)。\n const authStore = createDefaultAuthStore({ watchForExternalChanges: false })\n // listProviders() 读内存缓存(this.cache),不像 getAuthSource/hasCredential 那样\n // 自带 ensureInitialized() 前置——首次调用前必须显式触发一次磁盘加载,否则读到空态。\n await authStore.ensureInitialized()\n const hasAnyCredential = authStore.listProviders().length > 0\n\n const settings = new SettingsManager({ projectConfigPath: join(OTTO_DIR, OTTO_CONFIG_FILENAME) })\n await settings.load()\n const firstPromptSubmitted = typeof settings.get('first_prompt_submitted_at') === 'number'\n\n return deriveDoctorFtueGuidance({ providerPluginDiscovered, hasAnyCredential, firstPromptSubmitted })\n } catch {\n // 诊断失败不阻断 doctor 其余部分。\n return undefined\n }\n}\n\nasync function collectState(): Promise<StateSection | undefined> {\n const dbPath = join(OTTO_DIR, 'storage.db')\n if (!existsSync(dbPath)) {\n return { dbPath, dbExists: false }\n }\n\n try {\n const { SqlitePersistence } = await import('@x-otto/persistence')\n const store = new SqlitePersistence({ dbPath, namespace: 'doctor', wal: false })\n try {\n const info = store.getInfo() as {\n walEnabled: boolean\n snapshots: number\n changeLogEntries: number\n sessions: number\n integrity: string\n }\n\n return {\n dbPath,\n dbExists: true,\n dbSize: getFileSizeSync(dbPath),\n ...info,\n }\n } finally {\n store.close()\n }\n } catch {\n return { dbPath, dbExists: true, integrity: 'error (unable to open)' }\n }\n}\n\nasync function collectConnectivity(): Promise<ConnectivitySection> {\n let providerHost = 'api.anthropic.com'\n let providerReachable = false\n let providerLatency: number | undefined\n\n const start = Date.now()\n try {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), 5000)\n await fetch(`https://${providerHost}`, {\n method: 'HEAD',\n signal: controller.signal,\n })\n clearTimeout(timeout)\n providerReachable = true\n providerLatency = Date.now() - start\n } catch {\n providerReachable = false\n }\n\n return {\n providerHost,\n providerReachable,\n providerLatency,\n storageHostConfigured: false,\n }\n}\n\n/**\n * 二进制探测(08-13 backlog triage 修复):此前只扫 /usr/local/bin 与 /usr/bin——\n * Homebrew 原生(/opt/homebrew/bin)、nix(~/.nix-profile/bin)等 PATH 布局被误报缺失。\n * 改为遍历 process.env.PATH 拆分项逐一探测(X_OK 替换魔数 1),并保留两个系统目录\n * 作为 PATH 未含时的兜底(部分守护进程环境 PATH 极简)。\n */\nexport async function checkBinary(name: string): Promise<boolean> {\n const dirs = new Set<string>()\n for (const p of (process.env['PATH'] ?? '').split(':')) {\n if (p) dirs.add(p)\n }\n dirs.add('/usr/local/bin')\n dirs.add('/usr/bin')\n for (const dir of dirs) {\n try {\n accessSync(join(dir, name), constants.X_OK)\n return true\n } catch {\n // 目录不存在或无可执行位——继续下一个候选。\n }\n }\n return false\n}\n\nfunction getFileSizeSync(path: string): number {\n try {\n return statSync(path).size\n } catch {\n return 0\n }\n}\n\nfunction printSummary(stdout: (s: string) => void, report: DoctorReport): void {\n const { environment: env, configuration: config, state, connectivity: conn } = report\n const dbOk = state?.dbExists && state.integrity === 'ok' ? '✅' : '❌'\n const netOk = conn.providerReachable ? '✅' : '❌'\n const pluginGap = report.plugins && report.plugins.gaps.length > 0\n ? ` ⚠ 插件装配缺口 ${report.plugins.missing.length} 项(otto doctor 查看详情)`\n : ''\n // RFC-326 M6-01:只在未配置(非 looks-configured)时提示——已配置用户不需要看\n // 这行噪音,未配置用户能立刻看到下一步命令,不必翻到 detailed 输出。\n const ftueHint = report.ftue && report.ftue.status !== 'looks-configured'\n ? ` ⚠ ${report.ftue.summary} → ${report.ftue.recoveryAction}`\n : ''\n stdout([\n `otto doctor — ${env.runtime} | ${config.storageDir} | DB:${dbOk} ${netOk}`,\n state ? ` ${state.snapshots ?? '?'} snapshots, ${state.changeLogEntries ?? '?'} change_log` : '',\n pluginGap,\n ftueHint,\n ].filter(Boolean).join('\\n') + '\\n')\n}\n\nfunction printDetailed(stdout: (s: string) => void, report: DoctorReport): void {\n const { environment: env, configuration: config, state, connectivity: conn } = report\n\n stdout('\\nEnvironment\\n')\n stdout(` Runtime: ${env.runtime}\\n`)\n stdout(` Install: ${env.installPath}\\n`)\n stdout(` Shell: ${env.shell}\\n`)\n stdout(` Terminal: ${env.terminal} (${env.release})\\n`)\n stdout(` CPU: ${env.cpu}\\n`)\n stdout(` Memory: ${formatBytes(env.memory.free)} / ${formatBytes(env.memory.total)} free\\n`)\n if (config.residencyBudget) {\n const rb = config.residencyBudget\n stdout(` Residency: budget=${formatBytes(rb.totalBytes)} per-session=${formatBytes(rb.minPerSessionBytes)}~${formatBytes(rb.maxPerSessionBytes)} history=${rb.historyMessages} resume=${rb.resumeWindowMessages}\\n`)\n }\n\n stdout('\\nConfiguration\\n')\n stdout(` Workspace: ${config.workspaceDir}\\n`)\n stdout(` Storage: ${config.storageDir} ${existsSync(config.storageDir) ? '✅' : '❌'}\\n`)\n stdout(` ripgrep: ${config.hasRipgrep ? '✅' : '❌'}\\n`)\n stdout(` git: ${config.hasGit ? '✅' : '❌'}\\n`)\n stdout(` Auth: ${config.authMode}\\n`)\n\n // RFC-364 M2:插件装配一致性——有缺口才打印(无缺口时不制造噪音)。\n if (report.plugins) {\n stdout('\\nPlugins\\n')\n stdout(` Discovered: ${report.plugins.discovered}\\n`)\n if (report.plugins.gaps.length > 0) {\n stdout(' Assembly: ❌ 检测到装配缺口\\n')\n for (const line of report.plugins.gaps) stdout(` ${line}\\n`)\n } else {\n stdout(' Assembly: ✅\\n')\n }\n }\n\n if (state) {\n stdout('\\nState\\n')\n stdout(` SQLite DB: ${state.dbPath}\\n`)\n if (state.dbExists) {\n stdout(` size: ${formatBytes(state.dbSize ?? 0)}\\n`)\n stdout(` integrity: ${state.integrity === 'ok' ? 'ok ✅' : state.integrity ?? 'N/A'}\\n`)\n if (state.snapshots !== undefined) stdout(` snapshots: ${state.snapshots}\\n`)\n if (state.changeLogEntries !== undefined) stdout(` change_log: ${state.changeLogEntries} entries\\n`)\n if (state.sessions !== undefined) stdout(` sessions: ${state.sessions}\\n`)\n if (state.walEnabled !== undefined) stdout(` WAL: ${state.walEnabled ? 'enabled' : 'disabled'}\\n`)\n } else {\n stdout(` (no storage.db found — start otto to create one)\\n`)\n }\n }\n\n stdout('\\nConnectivity\\n')\n stdout(` ${conn.providerHost}: ${conn.providerReachable ? `reachable (${conn.providerLatency}ms) ✅` : 'unreachable ❌'}\\n`)\n stdout(` storage host: ${conn.storageHostConfigured ? 'configured' : 'not configured (local only)'}\\n`)\n\n // RFC-326 M6-01:FTUE 环境自检——廉价事实推导,非权威 readiness 判定\n // (见 ftue-doctor-recovery.ts 头注释;权威判定只在 TUI 运行时的 FtueController)。\n if (report.ftue) {\n const icon = report.ftue.status === 'looks-configured' ? '✅' : '⚠'\n stdout('\\nFirst-time setup\\n')\n stdout(` ${icon} ${report.ftue.summary}\\n`)\n stdout(` → ${report.ftue.recoveryAction}\\n`)\n }\n\n stdout('\\n')\n}\n\nfunction printHelp(stdout: (s: string) => void): void {\n stdout(`otto doctor — 环境健康诊断\n\n用法:\n otto doctor [--summary|--json]\n\n选项:\n --summary 紧凑输出(一行摘要)\n --json 结构化 JSON(redacted)\n --help, -h 帮助信息\n\n示例:\n otto doctor\n otto doctor --summary\n otto doctor --json\n`)\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) return '0 B'\n const units = ['B', 'KB', 'MB', 'GB', 'TB']\n const i = Math.floor(Math.log(bytes) / Math.log(1024))\n return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{n as e,t}from"./build-id-BmQPXMGN.js";import{t as n}from"./create-host-app-BgjEc81C.js";import{platform as r,release as i}from"node:os";import{execFile as a}from"node:child_process";import{openBrowser as o}from"@x-otto/shared";import{SDK_VERSION as s}from"@x-otto/env";import{promisify as c}from"node:util";const l=c(a);function u(e){let t=e.trim().replace(/\.git$/,``),n=t.match(/^git@[^:]+:(.+)$/);if(n)return n[1];let r=t.match(/^https?:\/\/[^/]+\/(.+)$/);if(r)return r[1]}async function d(e){try{let{stdout:t}=await l(`git`,[`remote`,`get-url`,`origin`],{cwd:e,timeout:5e3});return u(t)}catch{return}}async function f(e){return e.configuredRepo?e.configuredRepo:await d(e.cwd)||e.cliRepoArg}function p(n){return[`- otto version: ${s} (build ${e}, ${t}@dev)`,`- Node: ${process.version}`,`- OS: ${r()} ${process.arch} ${i()}`,`- Model: ${n??`(none configured)`}`].join(`
2
+ import{n as e,t}from"./build-id-C94zHRXj.js";import{t as n}from"./create-host-app-BtifD7KK.js";import{platform as r,release as i}from"node:os";import{execFile as a}from"node:child_process";import{openBrowser as o}from"@x-otto/shared";import{SDK_VERSION as s}from"@x-otto/env";import{promisify as c}from"node:util";const l=c(a);function u(e){let t=e.trim().replace(/\.git$/,``),n=t.match(/^git@[^:]+:(.+)$/);if(n)return n[1];let r=t.match(/^https?:\/\/[^/]+\/(.+)$/);if(r)return r[1]}async function d(e){try{let{stdout:t}=await l(`git`,[`remote`,`get-url`,`origin`],{cwd:e,timeout:5e3});return u(t)}catch{return}}async function f(e){return e.configuredRepo?e.configuredRepo:await d(e.cwd)||e.cliRepoArg}function p(n){return[`- otto version: ${s} (build ${e}, ${t}@dev)`,`- Node: ${process.version}`,`- OS: ${r()} ${process.arch} ${i()}`,`- Model: ${n??`(none configured)`}`].join(`
3
3
  `)}function m(e){let t,n,r,i,a,o=!1,s,c,l,u,d=(t,n)=>{let r=e[t+1];if(r===void 0||r.startsWith(`--`))throw Error(`${n} requires a value`);return r};for(let f=0;f<e.length;f++){let p=e[f];if(p===`--title`)t=d(f,`--title`),f++;else if(p===`--reproduce`)n=d(f,`--reproduce`),f++;else if(p===`--expected`)r=d(f,`--expected`),f++;else if(p===`--actual`)i=d(f,`--actual`),f++;else if(p===`--repo`)a=d(f,`--repo`),f++;else if(p===`--browser`)o=!0;else if(p===`--list`)s=!0;else if(p===`--state`){let e=d(f,`--state`);if(e===`open`||e===`closed`||e===`all`)c=e;else throw Error(`--state must be one of open|closed|all, got: ${e}`);f++}else if(p===`--source`){let e=d(f,`--source`);if(e===`github`||e===`coding`||e===`all`)l=e;else throw Error(`--source must be one of github|coding|all, got: ${e}`);f++}else p===`--resolve`&&(u=d(f,`--resolve`),f++)}return{title:t,reproduce:n,expected:r,actual:i,repo:a,browser:o,list:s,state:c,source:l,resolve:u}}function h(e){e(`Usage: otto feedback [options]
4
4
 
5
5
  Report issues, list feedback, or mark them resolved — via GitHub (public) or Coding (internal).
@@ -27,4 +27,4 @@ Options:
27
27
  `),1;let n=await f({configuredRepo:l.settings.get(`feedback_repo`),cliRepoArg:a.repo,cwd:t.projectDir??process.cwd()});if(!n)return i(`otto feedback: could not resolve a target repository — pass --repo or set "feedback_repo" in .otto/config.json
28
28
  `),1;let u=p(void 0),d={title:a.title,reproduce:a.reproduce,expected:a.expected,actual:a.actual,envInfo:u,repo:n};if(!a.browser&&e.submitIssueViaGh){let t=await e.submitIssueViaGh(d);if(t.ok)return r(`Issue created: ${t.url}\n`),0;i(`otto feedback: gh channel failed (${t.kind}) — falling back to browser\n`)}let m=e.buildIssueUrl?.(d);return m?(o(m),r(`Opened: ${m}\n`),0):(i(`otto feedback: feedback plugin does not support buildIssueUrl
29
29
  `),1)}finally{await l.stop()}}export{p as buildFeedbackEnvInfo,f as resolveFeedbackRepo,g as runFeedbackCommand};
30
- //# sourceMappingURL=feedback-BsU8b1k9.js.map
30
+ //# sourceMappingURL=feedback-CJVKfI0S.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"feedback-BsU8b1k9.js","names":["BUILD_ID"],"sources":["../src/commands/feedback.ts"],"sourcesContent":["/**\n * /feedback 命令的 TUI 接线层(提交/展示)。\n */\nimport { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport { platform, release } from 'node:os'\nimport { SDK_VERSION } from '@x-otto/env'\nimport { BUILD_ID, BUILD_BRANCH, BUILD_HASH } from '../build-id'\nimport { createHostApp } from '../create-host-app'\nimport type { App } from '@x-otto/coding'\nimport { openBrowser } from '@x-otto/shared'\n\nconst execFileAsync = promisify(execFile)\n\n/** git remote origin URL → `owner/repo`(支持 `git@github.com:owner/repo.git` 与 `https://github.com/owner/repo.git` 两种形态)。 */\nfunction parseOwnerRepoFromGitUrl(url: string): string | undefined {\n const trimmed = url.trim().replace(/\\.git$/, '')\n const sshMatch = trimmed.match(/^git@[^:]+:(.+)$/)\n if (sshMatch) return sshMatch[1]\n const httpsMatch = trimmed.match(/^https?:\\/\\/[^/]+\\/(.+)$/)\n if (httpsMatch) return httpsMatch[1]\n return undefined\n}\n\n/** `git remote get-url origin` 解析当前工作目录的 owner/repo;非 git 仓库/无 origin 时返回 undefined(fail-soft,不抛出)。 */\nasync function resolveGitRemoteRepo(cwd: string): Promise<string | undefined> {\n try {\n const { stdout } = await execFileAsync('git', ['remote', 'get-url', 'origin'], { cwd, timeout: 5000 })\n return parseOwnerRepoFromGitUrl(stdout)\n } catch {\n return undefined\n }\n}\n\n/**\n * RFC-207 §3.4:目标仓库解析优先级——`feedback_repo` 配置 > git remote 解析 >\n * `--repo` 命令行参数。全部缺失时返回 `undefined`。\n */\nexport async function resolveFeedbackRepo(opts: {\n configuredRepo?: string\n cliRepoArg?: string\n cwd: string\n}): Promise<string | undefined> {\n if (opts.configuredRepo) return opts.configuredRepo\n const fromGit = await resolveGitRemoteRepo(opts.cwd)\n if (fromGit) return fromGit\n return opts.cliRepoArg\n}\n\n/**\n * RFC-207 §3.1 环境信息行(issue body 的\"环境信息\"小节,脱敏——只含版本/运行时/OS/\n * 模型,不含用户路径/环境变量等敏感信息)。\n */\nexport function buildFeedbackEnvInfo(modelId: string | undefined): string {\n return [\n `- otto version: ${SDK_VERSION} (build ${BUILD_ID}, ${BUILD_HASH}@${BUILD_BRANCH})`,\n `- Node: ${process.version}`,\n `- OS: ${platform()} ${process.arch} ${release()}`,\n `- Model: ${modelId ?? '(none configured)'}`,\n ].join('\\n')\n}\n\nexport interface FeedbackCommandDeps {\n stdout?: (s: string) => void\n stderr?: (s: string) => void\n projectDir?: string\n configPath?: string\n /** 测试注入:替代生产装配的 app(真实 App 类型——需要 feedbackRegistry/submitIssueViaGh,\n * 非 ServiceHostPort/AppContext 窄接口能覆盖,同 serve.ts 的 AppContext 注入模式不同)。 */\n app?: App\n}\n\nfunction parseFeedbackArgs(args: string[]): {\n title?: string\n reproduce?: string\n expected?: string\n actual?: string\n repo?: string\n browser: boolean\n // RFC-430 M2:面板等价物 flag 形态(不强制 --title)。\n list?: boolean\n state?: 'open' | 'closed' | 'all'\n source?: 'github' | 'coding' | 'all'\n resolve?: string\n} {\n let title: string | undefined\n let reproduce: string | undefined\n let expected: string | undefined\n let actual: string | undefined\n let repo: string | undefined\n let browser = false\n let list: boolean | undefined\n let state: 'open' | 'closed' | 'all' | undefined\n let source: 'github' | 'coding' | 'all' | undefined\n let resolve: string | undefined\n\n const takeValue = (i: number, name: string): string => {\n const raw = args[i + 1]\n if (raw === undefined || raw.startsWith('--')) {\n throw new Error(`${name} requires a value`)\n }\n return raw\n }\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]\n if (arg === '--title') { title = takeValue(i, '--title'); i++ }\n else if (arg === '--reproduce') { reproduce = takeValue(i, '--reproduce'); i++ }\n else if (arg === '--expected') { expected = takeValue(i, '--expected'); i++ }\n else if (arg === '--actual') { actual = takeValue(i, '--actual'); i++ }\n else if (arg === '--repo') { repo = takeValue(i, '--repo'); i++ }\n else if (arg === '--browser') { browser = true }\n // RFC-430 M2:面板等价物 flag——任何其一出现即走 list/resolve 分支,不要求 --title。\n else if (arg === '--list') { list = true }\n else if (arg === '--state') {\n const v = takeValue(i, '--state');\n if (v === 'open' || v === 'closed' || v === 'all') state = v\n else throw new Error(`--state must be one of open|closed|all, got: ${v}`)\n i++\n }\n else if (arg === '--source') {\n const v = takeValue(i, '--source');\n if (v === 'github' || v === 'coding' || v === 'all') source = v\n else throw new Error(`--source must be one of github|coding|all, got: ${v}`)\n i++\n }\n else if (arg === '--resolve') { resolve = takeValue(i, '--resolve'); i++ }\n }\n\n return { title, reproduce, expected, actual, repo, browser, list, state, source, resolve }\n}\n\nfunction printFeedbackHelp(stdout: (s: string) => void): void {\n stdout(`Usage: otto feedback [options]\n\nReport issues, list feedback, or mark them resolved — via GitHub (public) or Coding (internal).\nInteractive panel-driven flow is available via \"/feedback\" inside otto.\n\nNon-interactive actions (at least one required):\n --title <title> Short issue title (create a new issue)\n --list List issues from configured sources (github + coding)\n --resolve <src:id> Mark an issue as resolved (e.g., --resolve github:123)\n\nOptions:\n --reproduce <text> Steps to reproduce (when creating an issue)\n --expected <text> Expected behavior (when creating an issue)\n --actual <text> Actual behavior (when creating an issue)\n --repo <owner/repo> Target repository (defaults to feedback_repo config, then git remote origin)\n --browser Skip the gh CLI channel and always use the browser fallback\n --state <open|closed|all> Filter list by state (default: open)\n --source <github|coding|all> Filter list by source (default: all)\n --help Show this help\n`)\n}\n\n/**\n * `otto feedback` 非交互顶层子命令(RFC-207 §3.2/§3.4)——CI/脚本化调用场景,\n * `--title`/`--body` 系参数直接传参跳过 TUI openInput 链。复用与 `/feedback` 斜杠\n * 命令相同的核心逻辑(`resolveFeedbackRepo`/`buildFeedbackEnvInfo`)+ 同一个\n * `FeedbackRegistry`/插件实现(`app.feedbackRegistry`),两个入口不重复业务逻辑。\n */\nexport async function runFeedbackCommand(args: string[], deps: FeedbackCommandDeps = {}): Promise<number> {\n const stdout = deps.stdout ?? ((s) => process.stdout.write(s))\n const stderr = deps.stderr ?? ((s) => process.stderr.write(s))\n\n if (args[0] === 'help' || args[0] === '--help') {\n printFeedbackHelp(stdout)\n return 0\n }\n\n let parsed: ReturnType<typeof parseFeedbackArgs>\n try {\n parsed = parseFeedbackArgs(args)\n } catch (error) {\n stderr(`otto feedback: ${error instanceof Error ? error.message : String(error)}\\n`)\n return 1\n }\n\n // RFC-430 M2:list/resolve 分支不要求 --title(list/resolve 与 create 互斥)。\n const isListMode = parsed.list === true\n const isResolveMode = parsed.resolve !== undefined\n if (!parsed.title && !isListMode && !isResolveMode) {\n stderr('otto feedback: either --title, --list, or --resolve is required (see --help)\\n')\n return 1\n }\n\n const app =\n deps.app ??\n createHostApp({\n logger: { name: 'otto-feedback', level: 'error', destination: 'stderr' },\n workspaceDir: deps.projectDir,\n projectConfigPath: deps.configPath,\n })\n\n try {\n await app.start()\n } catch (error) {\n stderr(`otto feedback: failed to start app: ${error instanceof Error ? error.message : String(error)}\\n`)\n return 1\n }\n\n try {\n const provider = app.feedbackRegistry.get()\n if (!provider) {\n stderr('otto feedback: feedback plugin is not available (disabled or not installed)\\n')\n return 1\n }\n\n if (isListMode) {\n const limitPerSource = 20\n const result = await provider.listIssues({\n state: parsed.state ?? 'open',\n limitPerSource,\n })\n let items = result.items\n if (parsed.source && parsed.source !== 'all') {\n items = items.filter((i) => i.source === parsed.source)\n }\n if (items.length === 0) {\n stdout(parsed.state === 'open' ? 'No open issues found.\\n' : `No issues in state '${parsed.state}' found.\\n`)\n } else {\n for (const item of items) {\n const resolvedMark = await provider.listResolvedMarks?.()\n const resolved = resolvedMark?.some(\n (m) => m.source === item.source && m.platformIssueId === item.platformIssueId,\n ) ?? false\n const sourceLabel = item.source === 'github' ? '[GH]' : '[COD]'\n const stateLabel = item.state === 'closed' ? 'closed' : 'open'\n const resolvedLabel = resolved ? ' ✓resolved' : ''\n stdout(`${sourceLabel} #${item.platformIssueId} ${item.title} (${stateLabel}${resolvedLabel}) ${item.url}\\n`)\n }\n }\n if (result.unavailable) {\n for (const [source, reason] of Object.entries(result.unavailable)) {\n stdout(`! ${source}: ${reason}\\n`)\n }\n }\n return 0\n }\n\n if (isResolveMode) {\n const match = parsed.resolve?.match(/^(github|coding):(.+)$/)\n if (!match) {\n stderr(`otto feedback: --resolve must be in form source:platformIssueId (e.g., --resolve github:123), got: ${parsed.resolve}\\n`)\n return 1\n }\n const source = match[1] as 'github' | 'coding'\n const platformIssueId = match[2] as string\n const listResult = await provider.listIssues({ state: 'all', limitPerSource: 100 })\n const issue = listResult.items.find(\n (i) => i.source === (source as 'github' | 'coding') && i.platformIssueId === platformIssueId,\n )\n if (!issue) {\n stderr(`otto feedback: issue not found in source '${source}' with id '${platformIssueId}'\\n`)\n return 1\n }\n const ok = await provider.markIssueResolved?.({\n source: source as 'github' | 'coding',\n platformIssueId,\n url: issue.url,\n title: issue.title,\n }) ?? false\n if (ok) {\n stdout(`Marked as resolved: ${issue.title} (${issue.url})\\n`)\n } else {\n stderr(`otto feedback: failed to mark issue as resolved (shadow mode or dry-run?)\\n`)\n return 1\n }\n return 0\n }\n\n // Create 分支(既有逻辑,--title 存在)。\n if (!parsed.title) {\n stderr('otto feedback: --title is required when creating an issue (see --help)\\n')\n return 1\n }\n\n const configuredRepo = app.settings.get('feedback_repo') as string | undefined\n const repo = await resolveFeedbackRepo({ configuredRepo, cliRepoArg: parsed.repo, cwd: deps.projectDir ?? process.cwd() })\n if (!repo) {\n stderr('otto feedback: could not resolve a target repository — pass --repo or set \"feedback_repo\" in .otto/config.json\\n')\n return 1\n }\n\n const envInfo = buildFeedbackEnvInfo(undefined)\n const input = {\n title: parsed.title,\n reproduce: parsed.reproduce,\n expected: parsed.expected,\n actual: parsed.actual,\n envInfo,\n repo,\n }\n\n if (!parsed.browser && provider.submitIssueViaGh) {\n const result = await provider.submitIssueViaGh(input)\n if (result.ok) {\n stdout(`Issue created: ${result.url}\\n`)\n return 0\n }\n stderr(`otto feedback: gh channel failed (${result.kind}) — falling back to browser\\n`)\n }\n\n const url = provider.buildIssueUrl?.(input)\n if (!url) {\n stderr('otto feedback: feedback plugin does not support buildIssueUrl\\n')\n return 1\n }\n openBrowser(url)\n stdout(`Opened: ${url}\\n`)\n return 0\n } finally {\n await app.stop()\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"feedback-CJVKfI0S.js","names":["BUILD_ID"],"sources":["../src/commands/feedback.ts"],"sourcesContent":["/**\n * /feedback 命令的 TUI 接线层(提交/展示)。\n */\nimport { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport { platform, release } from 'node:os'\nimport { SDK_VERSION } from '@x-otto/env'\nimport { BUILD_ID, BUILD_BRANCH, BUILD_HASH } from '../build-id'\nimport { createHostApp } from '../create-host-app'\nimport type { App } from '@x-otto/coding'\nimport { openBrowser } from '@x-otto/shared'\n\nconst execFileAsync = promisify(execFile)\n\n/** git remote origin URL → `owner/repo`(支持 `git@github.com:owner/repo.git` 与 `https://github.com/owner/repo.git` 两种形态)。 */\nfunction parseOwnerRepoFromGitUrl(url: string): string | undefined {\n const trimmed = url.trim().replace(/\\.git$/, '')\n const sshMatch = trimmed.match(/^git@[^:]+:(.+)$/)\n if (sshMatch) return sshMatch[1]\n const httpsMatch = trimmed.match(/^https?:\\/\\/[^/]+\\/(.+)$/)\n if (httpsMatch) return httpsMatch[1]\n return undefined\n}\n\n/** `git remote get-url origin` 解析当前工作目录的 owner/repo;非 git 仓库/无 origin 时返回 undefined(fail-soft,不抛出)。 */\nasync function resolveGitRemoteRepo(cwd: string): Promise<string | undefined> {\n try {\n const { stdout } = await execFileAsync('git', ['remote', 'get-url', 'origin'], { cwd, timeout: 5000 })\n return parseOwnerRepoFromGitUrl(stdout)\n } catch {\n return undefined\n }\n}\n\n/**\n * RFC-207 §3.4:目标仓库解析优先级——`feedback_repo` 配置 > git remote 解析 >\n * `--repo` 命令行参数。全部缺失时返回 `undefined`。\n */\nexport async function resolveFeedbackRepo(opts: {\n configuredRepo?: string\n cliRepoArg?: string\n cwd: string\n}): Promise<string | undefined> {\n if (opts.configuredRepo) return opts.configuredRepo\n const fromGit = await resolveGitRemoteRepo(opts.cwd)\n if (fromGit) return fromGit\n return opts.cliRepoArg\n}\n\n/**\n * RFC-207 §3.1 环境信息行(issue body 的\"环境信息\"小节,脱敏——只含版本/运行时/OS/\n * 模型,不含用户路径/环境变量等敏感信息)。\n */\nexport function buildFeedbackEnvInfo(modelId: string | undefined): string {\n return [\n `- otto version: ${SDK_VERSION} (build ${BUILD_ID}, ${BUILD_HASH}@${BUILD_BRANCH})`,\n `- Node: ${process.version}`,\n `- OS: ${platform()} ${process.arch} ${release()}`,\n `- Model: ${modelId ?? '(none configured)'}`,\n ].join('\\n')\n}\n\nexport interface FeedbackCommandDeps {\n stdout?: (s: string) => void\n stderr?: (s: string) => void\n projectDir?: string\n configPath?: string\n /** 测试注入:替代生产装配的 app(真实 App 类型——需要 feedbackRegistry/submitIssueViaGh,\n * 非 ServiceHostPort/AppContext 窄接口能覆盖,同 serve.ts 的 AppContext 注入模式不同)。 */\n app?: App\n}\n\nfunction parseFeedbackArgs(args: string[]): {\n title?: string\n reproduce?: string\n expected?: string\n actual?: string\n repo?: string\n browser: boolean\n // RFC-430 M2:面板等价物 flag 形态(不强制 --title)。\n list?: boolean\n state?: 'open' | 'closed' | 'all'\n source?: 'github' | 'coding' | 'all'\n resolve?: string\n} {\n let title: string | undefined\n let reproduce: string | undefined\n let expected: string | undefined\n let actual: string | undefined\n let repo: string | undefined\n let browser = false\n let list: boolean | undefined\n let state: 'open' | 'closed' | 'all' | undefined\n let source: 'github' | 'coding' | 'all' | undefined\n let resolve: string | undefined\n\n const takeValue = (i: number, name: string): string => {\n const raw = args[i + 1]\n if (raw === undefined || raw.startsWith('--')) {\n throw new Error(`${name} requires a value`)\n }\n return raw\n }\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]\n if (arg === '--title') { title = takeValue(i, '--title'); i++ }\n else if (arg === '--reproduce') { reproduce = takeValue(i, '--reproduce'); i++ }\n else if (arg === '--expected') { expected = takeValue(i, '--expected'); i++ }\n else if (arg === '--actual') { actual = takeValue(i, '--actual'); i++ }\n else if (arg === '--repo') { repo = takeValue(i, '--repo'); i++ }\n else if (arg === '--browser') { browser = true }\n // RFC-430 M2:面板等价物 flag——任何其一出现即走 list/resolve 分支,不要求 --title。\n else if (arg === '--list') { list = true }\n else if (arg === '--state') {\n const v = takeValue(i, '--state');\n if (v === 'open' || v === 'closed' || v === 'all') state = v\n else throw new Error(`--state must be one of open|closed|all, got: ${v}`)\n i++\n }\n else if (arg === '--source') {\n const v = takeValue(i, '--source');\n if (v === 'github' || v === 'coding' || v === 'all') source = v\n else throw new Error(`--source must be one of github|coding|all, got: ${v}`)\n i++\n }\n else if (arg === '--resolve') { resolve = takeValue(i, '--resolve'); i++ }\n }\n\n return { title, reproduce, expected, actual, repo, browser, list, state, source, resolve }\n}\n\nfunction printFeedbackHelp(stdout: (s: string) => void): void {\n stdout(`Usage: otto feedback [options]\n\nReport issues, list feedback, or mark them resolved — via GitHub (public) or Coding (internal).\nInteractive panel-driven flow is available via \"/feedback\" inside otto.\n\nNon-interactive actions (at least one required):\n --title <title> Short issue title (create a new issue)\n --list List issues from configured sources (github + coding)\n --resolve <src:id> Mark an issue as resolved (e.g., --resolve github:123)\n\nOptions:\n --reproduce <text> Steps to reproduce (when creating an issue)\n --expected <text> Expected behavior (when creating an issue)\n --actual <text> Actual behavior (when creating an issue)\n --repo <owner/repo> Target repository (defaults to feedback_repo config, then git remote origin)\n --browser Skip the gh CLI channel and always use the browser fallback\n --state <open|closed|all> Filter list by state (default: open)\n --source <github|coding|all> Filter list by source (default: all)\n --help Show this help\n`)\n}\n\n/**\n * `otto feedback` 非交互顶层子命令(RFC-207 §3.2/§3.4)——CI/脚本化调用场景,\n * `--title`/`--body` 系参数直接传参跳过 TUI openInput 链。复用与 `/feedback` 斜杠\n * 命令相同的核心逻辑(`resolveFeedbackRepo`/`buildFeedbackEnvInfo`)+ 同一个\n * `FeedbackRegistry`/插件实现(`app.feedbackRegistry`),两个入口不重复业务逻辑。\n */\nexport async function runFeedbackCommand(args: string[], deps: FeedbackCommandDeps = {}): Promise<number> {\n const stdout = deps.stdout ?? ((s) => process.stdout.write(s))\n const stderr = deps.stderr ?? ((s) => process.stderr.write(s))\n\n if (args[0] === 'help' || args[0] === '--help') {\n printFeedbackHelp(stdout)\n return 0\n }\n\n let parsed: ReturnType<typeof parseFeedbackArgs>\n try {\n parsed = parseFeedbackArgs(args)\n } catch (error) {\n stderr(`otto feedback: ${error instanceof Error ? error.message : String(error)}\\n`)\n return 1\n }\n\n // RFC-430 M2:list/resolve 分支不要求 --title(list/resolve 与 create 互斥)。\n const isListMode = parsed.list === true\n const isResolveMode = parsed.resolve !== undefined\n if (!parsed.title && !isListMode && !isResolveMode) {\n stderr('otto feedback: either --title, --list, or --resolve is required (see --help)\\n')\n return 1\n }\n\n const app =\n deps.app ??\n createHostApp({\n logger: { name: 'otto-feedback', level: 'error', destination: 'stderr' },\n workspaceDir: deps.projectDir,\n projectConfigPath: deps.configPath,\n })\n\n try {\n await app.start()\n } catch (error) {\n stderr(`otto feedback: failed to start app: ${error instanceof Error ? error.message : String(error)}\\n`)\n return 1\n }\n\n try {\n const provider = app.feedbackRegistry.get()\n if (!provider) {\n stderr('otto feedback: feedback plugin is not available (disabled or not installed)\\n')\n return 1\n }\n\n if (isListMode) {\n const limitPerSource = 20\n const result = await provider.listIssues({\n state: parsed.state ?? 'open',\n limitPerSource,\n })\n let items = result.items\n if (parsed.source && parsed.source !== 'all') {\n items = items.filter((i) => i.source === parsed.source)\n }\n if (items.length === 0) {\n stdout(parsed.state === 'open' ? 'No open issues found.\\n' : `No issues in state '${parsed.state}' found.\\n`)\n } else {\n for (const item of items) {\n const resolvedMark = await provider.listResolvedMarks?.()\n const resolved = resolvedMark?.some(\n (m) => m.source === item.source && m.platformIssueId === item.platformIssueId,\n ) ?? false\n const sourceLabel = item.source === 'github' ? '[GH]' : '[COD]'\n const stateLabel = item.state === 'closed' ? 'closed' : 'open'\n const resolvedLabel = resolved ? ' ✓resolved' : ''\n stdout(`${sourceLabel} #${item.platformIssueId} ${item.title} (${stateLabel}${resolvedLabel}) ${item.url}\\n`)\n }\n }\n if (result.unavailable) {\n for (const [source, reason] of Object.entries(result.unavailable)) {\n stdout(`! ${source}: ${reason}\\n`)\n }\n }\n return 0\n }\n\n if (isResolveMode) {\n const match = parsed.resolve?.match(/^(github|coding):(.+)$/)\n if (!match) {\n stderr(`otto feedback: --resolve must be in form source:platformIssueId (e.g., --resolve github:123), got: ${parsed.resolve}\\n`)\n return 1\n }\n const source = match[1] as 'github' | 'coding'\n const platformIssueId = match[2] as string\n const listResult = await provider.listIssues({ state: 'all', limitPerSource: 100 })\n const issue = listResult.items.find(\n (i) => i.source === (source as 'github' | 'coding') && i.platformIssueId === platformIssueId,\n )\n if (!issue) {\n stderr(`otto feedback: issue not found in source '${source}' with id '${platformIssueId}'\\n`)\n return 1\n }\n const ok = await provider.markIssueResolved?.({\n source: source as 'github' | 'coding',\n platformIssueId,\n url: issue.url,\n title: issue.title,\n }) ?? false\n if (ok) {\n stdout(`Marked as resolved: ${issue.title} (${issue.url})\\n`)\n } else {\n stderr(`otto feedback: failed to mark issue as resolved (shadow mode or dry-run?)\\n`)\n return 1\n }\n return 0\n }\n\n // Create 分支(既有逻辑,--title 存在)。\n if (!parsed.title) {\n stderr('otto feedback: --title is required when creating an issue (see --help)\\n')\n return 1\n }\n\n const configuredRepo = app.settings.get('feedback_repo') as string | undefined\n const repo = await resolveFeedbackRepo({ configuredRepo, cliRepoArg: parsed.repo, cwd: deps.projectDir ?? process.cwd() })\n if (!repo) {\n stderr('otto feedback: could not resolve a target repository — pass --repo or set \"feedback_repo\" in .otto/config.json\\n')\n return 1\n }\n\n const envInfo = buildFeedbackEnvInfo(undefined)\n const input = {\n title: parsed.title,\n reproduce: parsed.reproduce,\n expected: parsed.expected,\n actual: parsed.actual,\n envInfo,\n repo,\n }\n\n if (!parsed.browser && provider.submitIssueViaGh) {\n const result = await provider.submitIssueViaGh(input)\n if (result.ok) {\n stdout(`Issue created: ${result.url}\\n`)\n return 0\n }\n stderr(`otto feedback: gh channel failed (${result.kind}) — falling back to browser\\n`)\n }\n\n const url = provider.buildIssueUrl?.(input)\n if (!url) {\n stderr('otto feedback: feedback plugin does not support buildIssueUrl\\n')\n return 1\n }\n openBrowser(url)\n stdout(`Opened: ${url}\\n`)\n return 0\n } finally {\n await app.stop()\n }\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{t as e}from"./backend-handler-DUafuNWw.js";import{buildFeedbackEnvInfo as t,resolveFeedbackRepo as n}from"./feedback-BsU8b1k9.js";import{openBrowser as r}from"@x-otto/shared";import{z as i}from"zod";const a=i.object({title:i.string(),reproduce:i.string().optional(),expected:i.string().optional(),actual:i.string().optional(),envInfo:i.string(),repo:i.string()});function o(o,s){return e({"feedback.listIssues":{params:i.object({state:i.enum([`open`,`closed`,`all`]).optional().default(`open`),limitPerSource:i.number().optional().default(20)}),run:async e=>o.listIssues({state:e.state,limitPerSource:e.limitPerSource})},"feedback.resolvePlatform":{params:i.object({}),run:async()=>({platform:await o.resolveFeedbackPlatform?.()??`github`})},"feedback.markResolved":{params:i.object({source:i.enum([`github`,`coding`]),platformIssueId:i.string(),url:i.string(),title:i.string()}),run:async e=>({ok:await o.markIssueResolved?.(e)??!1})},"feedback.unmarkResolved":{params:i.object({source:i.enum([`github`,`coding`]),platformIssueId:i.string()}),run:async e=>({ok:await o.unmarkIssueResolved?.(e.source,e.platformIssueId)??!1})},"feedback.listResolvedMarks":{params:i.object({}),run:async()=>({marks:await o.listResolvedMarks?.()??[]})},"feedback.buildIssueUrl":{params:a,run:async e=>({url:o.buildIssueUrl?.(e)??``})},"feedback.buildCodingIssueUrl":{params:a,run:async e=>({url:o.buildCodingIssueUrl?.(e)??null})},"feedback.openExternal":{params:i.object({url:i.string().optional()}),run:async e=>(e.url&&r(e.url),{ok:!!e.url})},"feedback.submitIssue":{params:i.object({title:i.string(),reproduce:i.string().optional(),expected:i.string().optional(),actual:i.string().optional()}),run:async e=>{if(!e.title||e.title.trim().length===0)return{ok:!1,error:`title-required`};let i=await n({configuredRepo:void 0,cwd:s.workspaceDir});if(!i)return{ok:!1,error:`repo-unresolved`};let a={title:e.title.trim(),reproduce:e.reproduce,expected:e.expected,actual:e.actual,envInfo:t(s.getModelId()),repo:i};if(o.submitIssueViaGh){let e=await o.submitIssueViaGh(a);if(e.ok)return{ok:!0,url:e.url}}let c=o.buildIssueUrl?.(a);return c?(r(c),{ok:!0,url:c}):{ok:!1,error:`no-channel`}}}},`feedback`)}export{o as createFeedbackBackendHandler};
3
- //# sourceMappingURL=feedback-backend-methods-C2SzcIzm.js.map
2
+ import{t as e}from"./backend-handler-DUafuNWw.js";import{buildFeedbackEnvInfo as t,resolveFeedbackRepo as n}from"./feedback-CJVKfI0S.js";import{openBrowser as r}from"@x-otto/shared";import{z as i}from"zod";const a=i.object({title:i.string(),reproduce:i.string().optional(),expected:i.string().optional(),actual:i.string().optional(),envInfo:i.string(),repo:i.string()});function o(o,s){return e({"feedback.listIssues":{params:i.object({state:i.enum([`open`,`closed`,`all`]).optional().default(`open`),limitPerSource:i.number().optional().default(20)}),run:async e=>o.listIssues({state:e.state,limitPerSource:e.limitPerSource})},"feedback.resolvePlatform":{params:i.object({}),run:async()=>({platform:await o.resolveFeedbackPlatform?.()??`github`})},"feedback.markResolved":{params:i.object({source:i.enum([`github`,`coding`]),platformIssueId:i.string(),url:i.string(),title:i.string()}),run:async e=>({ok:await o.markIssueResolved?.(e)??!1})},"feedback.unmarkResolved":{params:i.object({source:i.enum([`github`,`coding`]),platformIssueId:i.string()}),run:async e=>({ok:await o.unmarkIssueResolved?.(e.source,e.platformIssueId)??!1})},"feedback.listResolvedMarks":{params:i.object({}),run:async()=>({marks:await o.listResolvedMarks?.()??[]})},"feedback.buildIssueUrl":{params:a,run:async e=>({url:o.buildIssueUrl?.(e)??``})},"feedback.buildCodingIssueUrl":{params:a,run:async e=>({url:o.buildCodingIssueUrl?.(e)??null})},"feedback.openExternal":{params:i.object({url:i.string().optional()}),run:async e=>(e.url&&r(e.url),{ok:!!e.url})},"feedback.submitIssue":{params:i.object({title:i.string(),reproduce:i.string().optional(),expected:i.string().optional(),actual:i.string().optional()}),run:async e=>{if(!e.title||e.title.trim().length===0)return{ok:!1,error:`title-required`};let i=await n({configuredRepo:void 0,cwd:s.workspaceDir});if(!i)return{ok:!1,error:`repo-unresolved`};let a={title:e.title.trim(),reproduce:e.reproduce,expected:e.expected,actual:e.actual,envInfo:t(s.getModelId()),repo:i};if(o.submitIssueViaGh){let e=await o.submitIssueViaGh(a);if(e.ok)return{ok:!0,url:e.url}}let c=o.buildIssueUrl?.(a);return c?(r(c),{ok:!0,url:c}):{ok:!1,error:`no-channel`}}}},`feedback`)}export{o as createFeedbackBackendHandler};
3
+ //# sourceMappingURL=feedback-backend-methods-C23Z2suw.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"feedback-backend-methods-C2SzcIzm.js","names":[],"sources":["../src/commands/feedback-backend-methods.ts"],"sourcesContent":["/**\n * feedback-backend-methods.ts —— RFC-430 M3:feedback 面板 in-process backend methods。\n *\n * 对齐 schedule-backend-methods.ts 的 in-process backend handler 模式(RFC-349 D1)。\n * feedback 的全部数据/动作经 FeedbackRegistry → plugin-feedback provider 执行,\n * 这些只存活在 App 主进程,故注册为 in-process handler(非 MCP server 子进程)。\n *\n * RFC-439:分发收敛为类型化注册表(backend-handler.ts)——方法 = zod schema + run,\n * 参数校验/前缀剥离/未知方法 fail-closed 由注册表统一承担,不再手写 switch/双重断言。\n *\n * 面板侧调用经 `toBackendWireMethod` 统一加 `backend/` 前缀,注册表接收时剥离前缀。\n */\nimport { z } from 'zod'\nimport { openBrowser } from '@x-otto/shared'\nimport type { FeedbackProvider, FeedbackSource } from '@x-otto/plugin'\nimport { createBackendHandler, type BackendMethod } from './backend-handler'\nimport { buildFeedbackEnvInfo, resolveFeedbackRepo } from './feedback'\n\nexport interface FeedbackBackendDeps {\n /** 仓库解析 cwd(git remote 解析的基准目录)。 */\n workspaceDir: string\n /** 当前会话模型 id(envInfo 用;无会话/未配模型返回 undefined)。 */\n getModelId: () => string | undefined\n}\n\n/** buildIssueUrl / buildCodingIssueUrl / submitIssue 共享的 issue 输入形状。 */\nconst issueInputSchema = z.object({\n title: z.string(),\n reproduce: z.string().optional(),\n expected: z.string().optional(),\n actual: z.string().optional(),\n envInfo: z.string(),\n repo: z.string(),\n}) satisfies z.ZodType<{\n title: string\n reproduce?: string\n expected?: string\n actual?: string\n envInfo: string\n repo: string\n}>\n\nexport function createFeedbackBackendHandler(\n provider: FeedbackProvider,\n deps: FeedbackBackendDeps,\n): {\n call: (method: string, params: Record<string, unknown>) => Promise<unknown>\n} {\n const methods = {\n 'feedback.listIssues': {\n params: z.object({\n state: z.enum(['open', 'closed', 'all']).optional().default('open'),\n limitPerSource: z.number().optional().default(20),\n }),\n run: async (params: { state: 'open' | 'closed' | 'all'; limitPerSource: number }) =>\n provider.listIssues({ state: params.state, limitPerSource: params.limitPerSource }),\n },\n\n 'feedback.resolvePlatform': {\n params: z.object({}),\n run: async () => ({ platform: (await provider.resolveFeedbackPlatform?.()) ?? 'github' }),\n },\n\n 'feedback.markResolved': {\n params: z.object({\n source: z.enum(['github', 'coding']),\n platformIssueId: z.string(),\n url: z.string(),\n title: z.string(),\n }),\n run: async (params: {\n source: FeedbackSource\n platformIssueId: string\n url: string\n title: string\n }) => ({ ok: (await provider.markIssueResolved?.(params)) ?? false }),\n },\n\n 'feedback.unmarkResolved': {\n params: z.object({\n source: z.enum(['github', 'coding']),\n platformIssueId: z.string(),\n }),\n run: async (params: { source: FeedbackSource; platformIssueId: string }) =>\n ({ ok: (await provider.unmarkIssueResolved?.(params.source, params.platformIssueId)) ?? false }),\n },\n\n 'feedback.listResolvedMarks': {\n params: z.object({}),\n run: async () => ({ marks: (await provider.listResolvedMarks?.()) ?? [] }),\n },\n\n 'feedback.buildIssueUrl': {\n params: issueInputSchema,\n run: async (params) => ({ url: provider.buildIssueUrl?.(params) ?? '' }),\n },\n\n 'feedback.buildCodingIssueUrl': {\n params: issueInputSchema,\n run: async (params) => ({ url: provider.buildCodingIssueUrl?.(params) ?? null }),\n },\n\n 'feedback.openExternal': {\n params: z.object({ url: z.string().optional() }),\n run: async (params: { url?: string }) => {\n // RFC-430 D4:openExternal(url) 动作——面板进程内不直接 spawn 浏览器,\n // 由宿主侧 openBrowser 打开(setup-wizard/feedback.ts 同一通道)。\n if (params.url) openBrowser(params.url)\n return { ok: Boolean(params.url) }\n },\n },\n\n 'feedback.submitIssue': {\n params: z.object({\n title: z.string(),\n reproduce: z.string().optional(),\n expected: z.string().optional(),\n actual: z.string().optional(),\n }),\n run: async (params: {\n title: string\n reproduce?: string\n expected?: string\n actual?: string\n }): Promise<{ ok: boolean; error?: string; url?: string }> => {\n // RFC-430 D5:面板内「新建」动作——对齐 CLI openInput 链的提交通道语义:\n // gh 通道优先(submitIssueViaGh),失败/未实现降级浏览器通道(buildIssueUrl/\n // buildCodingIssueUrl + openBrowser)。repo/envInfo 由宿主侧组装(同 feedback.ts)。\n if (!params.title || params.title.trim().length === 0) {\n return { ok: false, error: 'title-required' }\n }\n const configuredRepo = undefined // 面板路径无 --repo 参数;走配置 > git remote 解析\n const repo = await resolveFeedbackRepo({ configuredRepo, cwd: deps.workspaceDir })\n if (!repo) {\n return { ok: false, error: 'repo-unresolved' }\n }\n const fullInput = {\n title: params.title.trim(),\n reproduce: params.reproduce,\n expected: params.expected,\n actual: params.actual,\n envInfo: buildFeedbackEnvInfo(deps.getModelId()),\n repo,\n }\n if (provider.submitIssueViaGh) {\n const result = await provider.submitIssueViaGh(fullInput)\n if (result.ok) return { ok: true, url: result.url }\n // gh 失败——降级浏览器通道兜底完成本次提交(复用已拼装好的 input)。\n }\n const url = provider.buildIssueUrl?.(fullInput)\n if (url) {\n openBrowser(url)\n return { ok: true, url }\n }\n return { ok: false, error: 'no-channel' }\n },\n },\n } satisfies Record<string, BackendMethod>\n\n return createBackendHandler(methods, 'feedback')\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"feedback-backend-methods-C23Z2suw.js","names":[],"sources":["../src/commands/feedback-backend-methods.ts"],"sourcesContent":["/**\n * feedback-backend-methods.ts —— RFC-430 M3:feedback 面板 in-process backend methods。\n *\n * 对齐 schedule-backend-methods.ts 的 in-process backend handler 模式(RFC-349 D1)。\n * feedback 的全部数据/动作经 FeedbackRegistry → plugin-feedback provider 执行,\n * 这些只存活在 App 主进程,故注册为 in-process handler(非 MCP server 子进程)。\n *\n * RFC-439:分发收敛为类型化注册表(backend-handler.ts)——方法 = zod schema + run,\n * 参数校验/前缀剥离/未知方法 fail-closed 由注册表统一承担,不再手写 switch/双重断言。\n *\n * 面板侧调用经 `toBackendWireMethod` 统一加 `backend/` 前缀,注册表接收时剥离前缀。\n */\nimport { z } from 'zod'\nimport { openBrowser } from '@x-otto/shared'\nimport type { FeedbackProvider, FeedbackSource } from '@x-otto/plugin'\nimport { createBackendHandler, type BackendMethod } from './backend-handler'\nimport { buildFeedbackEnvInfo, resolveFeedbackRepo } from './feedback'\n\nexport interface FeedbackBackendDeps {\n /** 仓库解析 cwd(git remote 解析的基准目录)。 */\n workspaceDir: string\n /** 当前会话模型 id(envInfo 用;无会话/未配模型返回 undefined)。 */\n getModelId: () => string | undefined\n}\n\n/** buildIssueUrl / buildCodingIssueUrl / submitIssue 共享的 issue 输入形状。 */\nconst issueInputSchema = z.object({\n title: z.string(),\n reproduce: z.string().optional(),\n expected: z.string().optional(),\n actual: z.string().optional(),\n envInfo: z.string(),\n repo: z.string(),\n}) satisfies z.ZodType<{\n title: string\n reproduce?: string\n expected?: string\n actual?: string\n envInfo: string\n repo: string\n}>\n\nexport function createFeedbackBackendHandler(\n provider: FeedbackProvider,\n deps: FeedbackBackendDeps,\n): {\n call: (method: string, params: Record<string, unknown>) => Promise<unknown>\n} {\n const methods = {\n 'feedback.listIssues': {\n params: z.object({\n state: z.enum(['open', 'closed', 'all']).optional().default('open'),\n limitPerSource: z.number().optional().default(20),\n }),\n run: async (params: { state: 'open' | 'closed' | 'all'; limitPerSource: number }) =>\n provider.listIssues({ state: params.state, limitPerSource: params.limitPerSource }),\n },\n\n 'feedback.resolvePlatform': {\n params: z.object({}),\n run: async () => ({ platform: (await provider.resolveFeedbackPlatform?.()) ?? 'github' }),\n },\n\n 'feedback.markResolved': {\n params: z.object({\n source: z.enum(['github', 'coding']),\n platformIssueId: z.string(),\n url: z.string(),\n title: z.string(),\n }),\n run: async (params: {\n source: FeedbackSource\n platformIssueId: string\n url: string\n title: string\n }) => ({ ok: (await provider.markIssueResolved?.(params)) ?? false }),\n },\n\n 'feedback.unmarkResolved': {\n params: z.object({\n source: z.enum(['github', 'coding']),\n platformIssueId: z.string(),\n }),\n run: async (params: { source: FeedbackSource; platformIssueId: string }) =>\n ({ ok: (await provider.unmarkIssueResolved?.(params.source, params.platformIssueId)) ?? false }),\n },\n\n 'feedback.listResolvedMarks': {\n params: z.object({}),\n run: async () => ({ marks: (await provider.listResolvedMarks?.()) ?? [] }),\n },\n\n 'feedback.buildIssueUrl': {\n params: issueInputSchema,\n run: async (params) => ({ url: provider.buildIssueUrl?.(params) ?? '' }),\n },\n\n 'feedback.buildCodingIssueUrl': {\n params: issueInputSchema,\n run: async (params) => ({ url: provider.buildCodingIssueUrl?.(params) ?? null }),\n },\n\n 'feedback.openExternal': {\n params: z.object({ url: z.string().optional() }),\n run: async (params: { url?: string }) => {\n // RFC-430 D4:openExternal(url) 动作——面板进程内不直接 spawn 浏览器,\n // 由宿主侧 openBrowser 打开(setup-wizard/feedback.ts 同一通道)。\n if (params.url) openBrowser(params.url)\n return { ok: Boolean(params.url) }\n },\n },\n\n 'feedback.submitIssue': {\n params: z.object({\n title: z.string(),\n reproduce: z.string().optional(),\n expected: z.string().optional(),\n actual: z.string().optional(),\n }),\n run: async (params: {\n title: string\n reproduce?: string\n expected?: string\n actual?: string\n }): Promise<{ ok: boolean; error?: string; url?: string }> => {\n // RFC-430 D5:面板内「新建」动作——对齐 CLI openInput 链的提交通道语义:\n // gh 通道优先(submitIssueViaGh),失败/未实现降级浏览器通道(buildIssueUrl/\n // buildCodingIssueUrl + openBrowser)。repo/envInfo 由宿主侧组装(同 feedback.ts)。\n if (!params.title || params.title.trim().length === 0) {\n return { ok: false, error: 'title-required' }\n }\n const configuredRepo = undefined // 面板路径无 --repo 参数;走配置 > git remote 解析\n const repo = await resolveFeedbackRepo({ configuredRepo, cwd: deps.workspaceDir })\n if (!repo) {\n return { ok: false, error: 'repo-unresolved' }\n }\n const fullInput = {\n title: params.title.trim(),\n reproduce: params.reproduce,\n expected: params.expected,\n actual: params.actual,\n envInfo: buildFeedbackEnvInfo(deps.getModelId()),\n repo,\n }\n if (provider.submitIssueViaGh) {\n const result = await provider.submitIssueViaGh(fullInput)\n if (result.ok) return { ok: true, url: result.url }\n // gh 失败——降级浏览器通道兜底完成本次提交(复用已拼装好的 input)。\n }\n const url = provider.buildIssueUrl?.(fullInput)\n if (url) {\n openBrowser(url)\n return { ok: true, url }\n }\n return { ok: false, error: 'no-channel' }\n },\n },\n } satisfies Record<string, BackendMethod>\n\n return createBackendHandler(methods, 'feedback')\n}\n"],"mappings":"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"}