@x-otto/cli 0.0.1-alpha.49 → 0.0.1-alpha.50

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 (119) hide show
  1. package/dist/{auth-command-D1T16ed-.js → auth-command-BYaFM76w.js} +2 -2
  2. package/dist/{auth-command-D1T16ed-.js.map → auth-command-BYaFM76w.js.map} +1 -1
  3. package/dist/backend-handler-DUafuNWw.js +3 -0
  4. package/dist/backend-handler-DUafuNWw.js.map +1 -0
  5. package/dist/build-id-DjE6eVpQ.js +3 -0
  6. package/dist/build-id-DjE6eVpQ.js.map +1 -0
  7. package/dist/bundled-extensions/plugin-feedback/otto-plugin.json +24 -0
  8. package/dist/bundled-extensions/plugin-feedback/panels/feedback.tsx +348 -0
  9. package/dist/bundled-extensions/plugin-feedback/panels/panel-host-globals.d.ts +67 -0
  10. package/dist/bundled-extensions/plugin-feedback/panels-dist/feedback.mjs +257 -0
  11. package/dist/bundled-extensions/plugin-feedback/plugin-dist/meta.json +1 -0
  12. package/dist/bundled-extensions/plugin-feedback/plugin-dist/plugin.cjs +11304 -0
  13. package/dist/bundled-extensions/plugin-feedback/plugin.ts +198 -0
  14. package/dist/bundled-extensions/plugin-feedback/src/coding-adapter.ts +183 -0
  15. package/dist/bundled-extensions/plugin-feedback/src/coding-lister.ts +26 -0
  16. package/dist/bundled-extensions/plugin-feedback/src/github-lister.ts +230 -0
  17. package/dist/bundled-extensions/plugin-feedback/src/issue-aggregator.ts +109 -0
  18. package/dist/bundled-extensions/plugin-feedback/src/issue-submitter.ts +241 -0
  19. package/dist/bundled-extensions/plugin-feedback/src/platform-resolver.ts +76 -0
  20. package/dist/bundled-extensions/plugin-feedback/src/pulse-survey-sink.ts +114 -0
  21. package/dist/bundled-extensions/plugin-feedback/src/resolved-sink.ts +163 -0
  22. package/dist/bundled-extensions/plugin-otto-wire-protocols/plugin-dist/meta.json +1 -1
  23. package/dist/bundled-extensions/plugin-otto-wire-protocols/plugin-dist/plugin.cjs +605 -725
  24. package/dist/bundled-extensions/plugin-otto-wire-protocols/src/anthropic-messages.ts +4 -22
  25. package/dist/bundled-extensions/plugin-otto-wire-protocols/src/openai-completions.ts +5 -5
  26. package/dist/bundled-extensions/plugin-otto-wire-protocols/src/openai-responses.ts +3 -15
  27. package/dist/command-dispatcher-BLT8IO4K.js +18 -0
  28. package/dist/command-dispatcher-BLT8IO4K.js.map +1 -0
  29. package/dist/create-host-app-CbJ8ptv-.js +3 -0
  30. package/dist/{create-host-app-BXQ8bU-B.js.map → create-host-app-CbJ8ptv-.js.map} +1 -1
  31. package/dist/{daemon-base-Y1XI61JY.js → daemon-base-D015ew5y.js} +1 -1
  32. package/dist/{daemon-base-Y1XI61JY.js.map → daemon-base-D015ew5y.js.map} +1 -1
  33. package/dist/{doctor-DXBLkbkP.js → doctor-EndB-ycb.js} +3 -3
  34. package/dist/{doctor-DXBLkbkP.js.map → doctor-EndB-ycb.js.map} +1 -1
  35. package/dist/{doctor-attach-DEMuyBOk.js → doctor-attach-DEoE7eFx.js} +2 -2
  36. package/dist/{doctor-attach-DEMuyBOk.js.map → doctor-attach-DEoE7eFx.js.map} +1 -1
  37. package/dist/{doctor-cdp-CnJUVNOB.js → doctor-cdp-Cj9IFpEP.js} +1 -1
  38. package/dist/{doctor-cdp-CnJUVNOB.js.map → doctor-cdp-Cj9IFpEP.js.map} +1 -1
  39. package/dist/{doctor-heap-Eu3LZj-F.js → doctor-heap-SIxiRvL8.js} +2 -2
  40. package/dist/{doctor-heap-Eu3LZj-F.js.map → doctor-heap-SIxiRvL8.js.map} +1 -1
  41. package/dist/{doctor-ps-JBgCH77n.js → doctor-ps-DNVOF6yY.js} +2 -2
  42. package/dist/{doctor-ps-JBgCH77n.js.map → doctor-ps-DNVOF6yY.js.map} +1 -1
  43. package/dist/extension-CoJqqktf.js +2 -0
  44. package/dist/extension-Dpn8gltG.js +179 -0
  45. package/dist/extension-Dpn8gltG.js.map +1 -0
  46. package/dist/feedback-QcONRnO3.js +30 -0
  47. package/dist/feedback-QcONRnO3.js.map +1 -0
  48. package/dist/feedback-backend-methods-BNm38ddr.js +3 -0
  49. package/dist/feedback-backend-methods-BNm38ddr.js.map +1 -0
  50. package/dist/{feishu-setup-5ZoxtH0J.js → feishu-setup-DbAq_BRH.js} +1 -1
  51. package/dist/feishu-setup-DbAq_BRH.js.map +1 -0
  52. package/dist/{ftue-doctor-recovery-8FeDBFOc.js → ftue-doctor-recovery-CKTMnxM8.js} +1 -1
  53. package/dist/{ftue-doctor-recovery-8FeDBFOc.js.map → ftue-doctor-recovery-CKTMnxM8.js.map} +1 -1
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +11 -11
  56. package/dist/index.js.map +1 -1
  57. package/dist/install-CEPczuet.js +11 -0
  58. package/dist/install-CEPczuet.js.map +1 -0
  59. package/dist/install-CG6b6fox.js +2 -0
  60. package/dist/interactive-schedule-Bv__2Kwc.js +3 -0
  61. package/dist/interactive-schedule-Bv__2Kwc.js.map +1 -0
  62. package/dist/{mcp-Cm4-OhXI.js → mcp-DjReRjuu.js} +1 -1
  63. package/dist/{mcp-Cm4-OhXI.js.map → mcp-DjReRjuu.js.map} +1 -1
  64. package/dist/{mcp-server-Dpza3yn2.js → mcp-server-Rr__aWO7.js} +3 -3
  65. package/dist/mcp-server-Rr__aWO7.js.map +1 -0
  66. package/dist/{migrate-RlBJqJnB.js → migrate-BBi3Txu2.js} +1 -1
  67. package/dist/{migrate-RlBJqJnB.js.map → migrate-BBi3Txu2.js.map} +1 -1
  68. package/dist/{observe-Dxfud-t7.js → observe-BqLCpmFD.js} +2 -2
  69. package/dist/{observe-Dxfud-t7.js.map → observe-BqLCpmFD.js.map} +1 -1
  70. package/dist/parse-cli-Db8pF_Ya.js +3 -0
  71. package/dist/parse-cli-Db8pF_Ya.js.map +1 -0
  72. package/dist/{persistence-CRFhOYx6.js → persistence-BvLRnpfi.js} +2 -2
  73. package/dist/{persistence-CRFhOYx6.js.map → persistence-BvLRnpfi.js.map} +1 -1
  74. package/dist/{plugin-assembly-check-DEozmcJ7.js → plugin-assembly-check-BntT5W6H.js} +1 -1
  75. package/dist/{plugin-assembly-check-DEozmcJ7.js.map → plugin-assembly-check-BntT5W6H.js.map} +1 -1
  76. package/dist/{proxy-evg7ZvoD.js → proxy-CFZDVIwI.js} +1 -1
  77. package/dist/proxy-CFZDVIwI.js.map +1 -0
  78. package/dist/proxy-CkSf8ziQ.js +2 -0
  79. package/dist/{ps-DiKCLKbs.js → ps-BVKv6rAo.js} +1 -1
  80. package/dist/{ps-DiKCLKbs.js.map → ps-BVKv6rAo.js.map} +1 -1
  81. package/dist/{registry-BdCcwyGy.js → registry-BUvu-es1.js} +4 -4
  82. package/dist/registry-BUvu-es1.js.map +1 -0
  83. package/dist/registry-DYHR-ONu.js +2 -0
  84. package/dist/{run-attach-DDpMk2_N.js → run-attach-X35puJ_n.js} +2 -2
  85. package/dist/{run-attach-DDpMk2_N.js.map → run-attach-X35puJ_n.js.map} +1 -1
  86. package/dist/schedule-Ke0jwatM.js +5 -0
  87. package/dist/schedule-Ke0jwatM.js.map +1 -0
  88. package/dist/serve-K6BOT5K_.js +21 -0
  89. package/dist/serve-K6BOT5K_.js.map +1 -0
  90. package/dist/{service-client-Cr4sxDVM.js → service-client-cfUUP4EE.js} +1 -1
  91. package/dist/{service-client-Cr4sxDVM.js.map → service-client-cfUUP4EE.js.map} +1 -1
  92. package/dist/{version-BrSIyY6s.js → version-DrCy32wR.js} +2 -2
  93. package/dist/{version-BrSIyY6s.js.map → version-DrCy32wR.js.map} +1 -1
  94. package/package.json +16 -13
  95. package/dist/build-id-DxfuzdUE.js +0 -3
  96. package/dist/build-id-DxfuzdUE.js.map +0 -1
  97. package/dist/command-dispatcher-DTOPjtZP.js +0 -21
  98. package/dist/command-dispatcher-DTOPjtZP.js.map +0 -1
  99. package/dist/create-host-app-BXQ8bU-B.js +0 -3
  100. package/dist/create-host-app-DG58-vRA.js +0 -2
  101. package/dist/extension-CTyrcdT1.js +0 -2
  102. package/dist/extension-DVEml5IN.js +0 -179
  103. package/dist/extension-DVEml5IN.js.map +0 -1
  104. package/dist/feedback-DvWUvSc4.js +0 -21
  105. package/dist/feedback-DvWUvSc4.js.map +0 -1
  106. package/dist/feedback-DzPLfcfN.js +0 -2
  107. package/dist/feishu-setup-5ZoxtH0J.js.map +0 -1
  108. package/dist/install-DlVJRHRT.js +0 -11
  109. package/dist/install-DlVJRHRT.js.map +0 -1
  110. package/dist/install-WCcQ9k9M.js +0 -2
  111. package/dist/mcp-server-Dpza3yn2.js.map +0 -1
  112. package/dist/parse-cli-BvcxTK88.js +0 -3
  113. package/dist/parse-cli-BvcxTK88.js.map +0 -1
  114. package/dist/proxy-BvdBsB1O.js +0 -2
  115. package/dist/proxy-evg7ZvoD.js.map +0 -1
  116. package/dist/registry-BOX7DaxS.js +0 -2
  117. package/dist/registry-BdCcwyGy.js.map +0 -1
  118. package/dist/serve-Ke0nwN9Y.js +0 -21
  119. package/dist/serve-Ke0nwN9Y.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"install-DlVJRHRT.js","names":[],"sources":["../src/commands/handlers/install.ts"],"sourcesContent":["/**\n * handlers/install.ts — /install 命令处理\n *\n * /install [--global] [--force] [--dry-run] [--as <type>] [--type <src>] <source>\n *\n * 安装 skill/plugin/mcp 包到项目 (.otto/) 或全局 (~/.otto/)。\n * 支持 file/url/git/npm 四种来源,自动检测内容类型。\n */\nimport { i18next } from '@x-otto/tui'\nimport { InstallManager, defaultPostInstallExecutor } from '@x-otto/coding'\nimport { formatBytes } from '@x-otto/shared'\nimport type { InstallOptions, InstallProgress, InstallResult } from '@x-otto/coding'\nimport type { McpServersConfig } from '@x-otto/setting'\nimport type { SelectOption } from '@x-otto/tui'\nimport type { CliContext } from '../../command-dispatcher'\n\nconst VALID_AS = new Set(['plugin', 'skill', 'mcp'])\n\nexport async function handleInstall(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n const parsed = parseArgs(args)\n if (!parsed.source) {\n ctx.tui.info.showToast(i18next.t('cmd.titleInstall'), i18next.t('cmd.installUsage'))\n return\n }\n\n const isGlobal = parsed.flags.has('--global')\n const isHere = parsed.flags.has('--here')\n\n if (isGlobal && isHere) {\n ctx.tui.info.showToast(i18next.t('cmd.titleInstall'), i18next.t('cmd.installMutuallyExclusive'))\n return\n }\n\n const options: InstallOptions = {\n // --here:锚到当前命令目录,绕过 ctx.app.workspaceDir 的向上工作区根查找。\n workspaceDir: isHere ? process.cwd() : ctx.app.workspaceDir,\n global: isGlobal,\n force: parsed.flags.has('--force'),\n dryRun: parsed.flags.has('--dry-run'),\n as: parsed.as,\n type: parsed.type,\n env: parsed.env,\n subPath: parsed.subPath,\n ref: parsed.ref,\n postInstallExecutor: defaultPostInstallExecutor,\n // RFC-131: 进度回调 — 仅非 dry-run 时注入\n onProgress: !parsed.dryRun ? (p) => {\n const prefix = p.total && p.current ? `[${p.current}/${p.total}] ` : ''\n ctx.tui.info.showToast('Installing', `${prefix}${p.message}`)\n } : undefined,\n }\n\n // 确认优先(P3 安全边界):始终先 dry-run 取 plan 展示——含将运行的 postinstall/setup\n // shell 命令——确认后才真装(项目级插件真装含 auto-trust + 立即跑 postinstall)。\n // --dry-run 仅预览,不提供安装按钮。\n await showConfirmPanel(parsed.source, options, ctx, !parsed.dryRun)\n}\n\n// ── 参数解析 ──\n\ninterface ParsedArgs {\n source: string | undefined\n flags: Set<string>\n as: 'plugin' | 'skill' | 'mcp' | undefined\n type: 'file' | 'url' | 'git' | 'npm' | undefined\n env: Record<string, string> | undefined\n /** RFC-201:monorepo 子目录(--sub-path,registry 条目 subPath 的 CLI 透传面)。 */\n subPath: string | undefined\n /**\n * RFC-221 D2/T221-3:git 来源的 tag/branch 名(`--ref`),透传到\n * `InstallOptions.ref` → `resolveGitSource(gitUrl, ref)`。仅对 git 来源有效,\n * 其余来源类型忽略此参数(不报错,与 subPath 对非 git/归档来源的静默忽略语义一致)。\n * 不支持 commit hash——见 `git-resolver.ts` 头部注释(主流托管方服务端默认拒绝)。\n */\n ref: string | undefined\n dryRun: boolean\n}\n\n/** 导出供单测覆盖 `--ref`/`--sub-path` 等标志解析行为(RFC-221 T221-3),非公开 API。 */\nexport function parseArgs(args: string[]): ParsedArgs {\n const flags = new Set<string>()\n let as: 'plugin' | 'skill' | 'mcp' | undefined\n let type: 'file' | 'url' | 'git' | 'npm' | undefined\n let env: Record<string, string> | undefined\n let subPath: string | undefined\n let ref: string | undefined\n let dryRun = false\n let i = 0\n\n while (i < args.length) {\n const a = args[i]!\n if (a === '--as') {\n i++\n const val = args[i]?.toLowerCase()\n if (VALID_AS.has(val ?? '')) as = val as typeof as\n } else if (a === '--type') {\n i++\n const val = args[i]?.toLowerCase()\n if (val === 'file' || val === 'url' || val === 'git' || val === 'npm') {\n type = val\n }\n } else if (a === '--sub-path') {\n // RFC-201 M0/M1:monorepo 子目录安装(registry 条目 subPath 的 CLI 透传面)\n i++\n subPath = args[i]\n } else if (a === '--ref') {\n // RFC-221 D2/T221-3:git 来源 tag/branch 指定安装\n i++\n ref = args[i]\n } else if (a === '--env') {\n i++\n const kv = args[i]\n if (kv) {\n const eq = kv.indexOf('=')\n if (eq > 0) {\n env = env ?? {}\n env[kv.slice(0, eq)] = kv.slice(eq + 1)\n }\n }\n } else if (a.startsWith('--')) {\n flags.add(a)\n } else {\n // positional (source)\n dryRun = flags.has('--dry-run')\n const rest = args.slice(i).filter((x) => !x.startsWith('--'))\n return { source: rest[0], flags, as, type, env, subPath, ref, dryRun }\n }\n i++\n }\n\n dryRun = flags.has('--dry-run')\n return { source: undefined, flags, as, type, env, subPath, ref, dryRun }\n}\n\n// ── 确认面板 ──\n\n/**\n * 先 dry-run 取安装 plan 并展示确认屏。\n *\n * RFC-226:命令发出到确认框出现之间新增 loading 反馈——dry-run 探测(git clone/npm pack/\n * url 下载 + 内容检测)本身耗时不短,此前全程静默。改造后:① 立即展示 loading 态弹窗\n * (options=[] 空选项态,D2);② dry-run 探测阶段(resolving/detecting)的进度通过局部\n * `onProgress`(不复用 `options.onProgress` toast 版本,D5b/R6)实时刷新 loading 文案;\n * ③ dry-run 完成后原地过渡为确认框或错误态(D3/D5a)。\n *\n * **修复**(dialog 栈堆积):此前每次状态转换(loading→进度更新→确认/错误/预览)都调用\n * `ctx.tui.modal.openModal({ kind: 'select' })`,而同步 select 路径每次 `router.navigate` 都是\n * push 新栈条目,导致对话框层层堆积、用户要按多次 Esc 才能退出。现改用框架层 `async` 字段:\n * loading 态由框架自动展示,dry-run 在 async promise 内完成,resolve 后框架用 `replace: true`\n * 原地过渡为真实内容。最终的确认/错误/预览弹窗由 `onSelect` 回调内驱动(此时 loading 弹窗\n * 已被框架 replace 为最终态,Esc 一次即可退出)。\n *\n * @param proceed true=确认后真装([安装, 取消]);false=纯预览(--dry-run,仅[关闭])。\n */\nasync function showConfirmPanel(\n source: string,\n options: InstallOptions,\n ctx: CliContext,\n proceed: boolean,\n): Promise<void> {\n const manager = new InstallManager()\n const loadingTitle = `${i18next.t('cmd.titleInstall')} — \"${source}\"`\n\n // dry-run 专用局部 onProgress——不复用 options.onProgress(toast 版本,只服务\n // executeInstall 真实安装路径)。探测阶段进度文案由 async promise 内部记录,\n // resolve 时作为 subtitle 回灌(框架 replace 一次性更新)。\n let progressMessage = i18next.t('cmd.installLoadingHint')\n const dryOptions: InstallOptions = {\n ...options,\n dryRun: true,\n onProgress: (p: InstallProgress) => {\n progressMessage = p.message\n },\n }\n\n ctx.tui.modal.openModal({\n kind: 'select',\n title: loadingTitle,\n subtitle: `${i18next.t('cmd.titleDryRun')}...\\n${progressMessage}`,\n layout: 'buttons',\n options: [],\n onCancel: () => {\n ctx.tui.info.showToast(i18next.t('cmd.titleInstall'), i18next.t('cmd.selectCancelled'))\n },\n // async:dry-run 探测在 promise 内完成,resolve 后框架用 replace:true 原地过渡。\n async: (async (): Promise<{ title?: string; subtitle?: string; options: SelectOption[] }> => {\n const result = await manager.install(\n source,\n dryOptions,\n () => ctx.app.settings.get('mcp_servers') as Record<string, unknown> | undefined,\n async (servers) => {\n await ctx.app.settings.persist({ mcp_servers: servers as McpServersConfig })\n },\n )\n\n if (!result.success) {\n return {\n title: loadingTitle,\n subtitle: i18next.t('cmd.dryRunDetectFailed', { err: result.error ?? i18next.t('cmd.unknownError') }),\n options: [{ label: i18next.t('cmd.commonClose'), value: 'close', color: 'gray' }],\n }\n }\n\n if (!result.plan || result.plan.length === 0) {\n return {\n title: loadingTitle,\n subtitle: i18next.t('cmd.dryRunPreviewOk', { name: result.name ?? i18next.t('cmd.unknownLabel') }),\n options: [{ label: i18next.t('cmd.commonClose'), value: 'close', color: 'gray' }],\n }\n }\n\n const plan = result.plan.filter((l) => !l.startsWith(i18next.t('cmd.planSourcePrefix')))\n const name = result.name ?? i18next.t('cmd.unknownLabel')\n const isMultiSkill = result.results !== undefined\n\n // RFC-130: 多 skill 仓库 → 聚合面板\n if (isMultiSkill) {\n const count = result.results!.length\n const title = `${i18next.t('cmd.titleInstall')} — \"${count} skills (${source})\"`\n\n if (!proceed) {\n return {\n title,\n subtitle: plan.join('\\n'),\n options: [{ label: i18next.t('cmd.commonClose'), value: 'close', color: 'gray' }],\n }\n }\n\n return {\n title,\n subtitle: plan.join('\\n'),\n options: [\n { label: i18next.t('cmd.installActionLabel'), value: 'install', color: 'accent' },\n { label: i18next.t('cmd.commonCancel'), value: 'cancel', color: 'gray' },\n ],\n }\n }\n\n // --dry-run:纯预览,不提供安装入口(修正旧语义——旧 dry-run 的\"安装\"按钮会真装)。\n if (!proceed) {\n return {\n title: `${i18next.t('cmd.titleDryRun')} — \"${name}\"`,\n subtitle: plan.join('\\n'),\n options: [{ label: i18next.t('cmd.commonClose'), value: 'close', color: 'gray' }],\n }\n }\n\n return {\n title: `${i18next.t('cmd.titleInstall')} — \"${name}\"`,\n subtitle: plan.join('\\n'),\n options: [\n { label: i18next.t('cmd.installActionLabel'), value: 'install', color: 'accent' },\n { label: i18next.t('cmd.commonCancel'), value: 'cancel', color: 'gray' },\n ],\n }\n })(),\n onSelect: async (v: string) => {\n if (v === 'cancel' || v === 'close') return\n if (v === 'install') {\n await executeInstall(source, { ...options, dryRun: false }, ctx)\n }\n },\n })\n}\n\n// ── 执行安装 ──\n\n/**\n * RFC-224 D2/D3:安装进度 fraction 计算——字节级进度(bytesDownloaded/bytesTotal)\n * 优先于多 skill 计数(current/total),两者互斥语义(见 InstallProgress JSDoc)。\n * 导出供单测覆盖(`install-progress-bytes.test.ts`),不作为公开 API。\n */\nexport function computeProgressFraction(p: {\n bytesDownloaded?: number\n bytesTotal?: number\n current?: number\n total?: number\n}): number {\n if (p.bytesTotal !== undefined && p.bytesDownloaded !== undefined) {\n return p.bytesDownloaded / p.bytesTotal\n }\n if (p.current && p.total) {\n return p.current / p.total\n }\n return 0\n}\n\n/**\n * RFC-224 D2/R4:安装进度 detail 文案格式化——bytesDownloaded 存在时渲染字节数\n * (`formatBytes`),Content-Length 缺失(bytesTotal 为 undefined)时降级为纯字节\n * 计数,不编造总数;否则保持原始 message 不变。导出供单测覆盖。\n */\nexport function formatProgressDetail(p: {\n message: string\n bytesDownloaded?: number\n bytesTotal?: number\n}): string {\n if (p.bytesDownloaded === undefined) return p.message\n if (p.bytesTotal !== undefined) {\n return `${p.message} ${formatBytes(p.bytesDownloaded)} / ${formatBytes(p.bytesTotal)}`\n }\n return `${p.message} ${formatBytes(p.bytesDownloaded)}`\n}\n\n/** `Refreshing…` 在四阶段清单中的下标(RFC-286 D4:该阶段由 CLI 持有)。 */\nconst REFRESHING_PHASE_INDEX = 3\n\n/** partial 面板最多直接列出的失败项条数,超出部分转由 command_echo 承载(RFC-286 D6)。 */\nconst PARTIAL_DETAIL_MAX_ITEMS = 3\n\n/**\n * RFC-286 D6:把失败子项渲染成面板可读的一行摘要。\n *\n * 面板是窄栏终端 UI,超过 `PARTIAL_DETAIL_MAX_ITEMS` 条时只列前几条并指向消息流\n * ——完整清单由 `executeInstall` 的 command_echo 承载(R5:不依赖 toast 单槽通道)。\n */\n/** 拼接多条子错误前去掉句末句号,避免出现「。;」这类双标点(RFC-290 T290-11 收尾)。 */\nfunction stripTrailingPeriod(text: string): string {\n return text.replace(/[。.]\\s*$/, '')\n}\n\nfunction describeFailures(\n subs: Array<{ success: boolean; name?: string; error?: string }> | undefined,\n unknownErrorLabel: string,\n): string {\n const failed = (subs ?? []).filter((r) => !r.success)\n if (failed.length === 0) return unknownErrorLabel\n const shown = failed.slice(0, PARTIAL_DETAIL_MAX_ITEMS)\n const details = shown\n .map((r) => `${r.name ?? '?'} —— ${stripTrailingPeriod(r.error ?? unknownErrorLabel)}`)\n .join(';')\n return failed.length > shown.length ? `${details}${i18next.t('install.failureOverflow')}` : details\n}\n\n/**\n * RFC-290 D2:冲突类失败 → `--force` 建议。\n *\n * **只读结构化的 `failureKind`,绝不匹配 `error` 文案**——错误文案是本地化的人读文本,\n * 子串嗅探(如判断是否含\"已存在\")会在换 locale 或改措辞时静默失效。\n * 单结果读顶层字段,多结果要求**全部**失败项同为 conflict(混合原因不给建议,R4)。\n */\nfunction resolveConflictNextStep(\n result: { failureKind?: 'conflict' },\n subs: Array<{ success: boolean; failureKind?: 'conflict' }> | undefined,\n): { nextStep: { kind: 'force'; label: string } } | undefined {\n const failed = (subs ?? []).filter((r) => !r.success)\n const isConflict = subs\n ? failed.length > 0 && failed.every((r) => r.failureKind === 'conflict')\n : result.failureKind === 'conflict'\n if (!isConflict) return undefined\n return { nextStep: { kind: 'force', label: i18next.t('install.nextStepForce') } }\n}\n\n/**\n * RFC-286 D2/D3/D6:安装终态裁决——把 `InstallResult` + reload 结果映射为进度对话框终态。\n *\n * 映射表(RFC-286 D2 唯一定义处):\n * - 全部成功且 reload 成功 → `success`(2s 自动关闭)\n * - 部分成功(ok>0 && fail>0)→ `partial`(不自动关闭)\n * - 全部失败 → `error`(不自动关闭)\n * - 产物已装、仅 reload 失败 → `partial`(degraded success,提示 /extension reload)\n *\n * 三种非 success 终态都显式下发 `percent` 与 `phaseStatuses`(R4),失败阶段标 `'error'`\n * ——该取值此前在全仓无任何生产者,`ProgressDialog` 的行内错误渲染是死代码,本函数激活它。\n *\n * 导出供单测覆盖(`install-terminal-state.test.ts`),不作为公开 API。\n */\nexport function computeTerminalProgress(\n result: {\n success: boolean\n error?: string\n failureKind?: 'conflict'\n results?: Array<{ success: boolean; name?: string; error?: string; failureKind?: 'conflict' }>\n },\n reloadError: string | undefined,\n unknownErrorLabel: string,\n): {\n status: 'success' | 'partial' | 'error'\n percent: number\n phaseStatuses: Array<'pending' | 'active' | 'done' | 'error'>\n errorMessage?: string\n nextStep?: { kind: 'force' | 'reload'; label: string }\n} {\n const subs = result.results\n const okCount = subs ? subs.filter((r) => r.success).length : result.success ? 1 : 0\n const failCount = subs ? subs.length - okCount : result.success ? 0 : 1\n\n // 全部失败 → error。失败阶段停在 `installing`(写盘阶段),而非 refreshing——\n // 安装根本没成功,不该显示\"刷新\"阶段被走到过。\n //\n // R3:错误文案按「顶层 error → 子项 error 还原 → 兜底文案」逐级降级。兜底只在\n // 上游连子项原因都没给时才允许出现,绝不用它掩盖上游缺口(本次事故的根因模式)。\n if (okCount === 0) {\n return {\n status: 'error',\n percent: 100,\n phaseStatuses: ['done', 'done', 'error', 'pending'],\n errorMessage: result.error ?? describeFailures(subs, unknownErrorLabel),\n ...(resolveConflictNextStep(result, subs) ?? {}),\n }\n }\n\n // 有成功项但也有失败项 → partial\n if (failCount > 0) {\n return {\n status: 'partial',\n percent: 100,\n phaseStatuses: ['done', 'done', 'error', 'done'],\n errorMessage:\n i18next.t('install.partialCounts', { ok: okCount, fail: failCount }) +\n describeFailures(subs, unknownErrorLabel),\n ...(resolveConflictNextStep(result, subs) ?? {}),\n }\n }\n\n // 全部装好但能力刷新失败 → degraded success(产物已落盘,不能报 error)\n if (reloadError) {\n return {\n status: 'partial',\n percent: 100,\n phaseStatuses: ['done', 'done', 'done', 'error'],\n errorMessage: i18next.t('cmd.installedRefreshErr', { err: reloadError }),\n nextStep: { kind: 'reload', label: i18next.t('install.nextStepReload') },\n }\n }\n\n return { status: 'success', percent: 100, phaseStatuses: ['done', 'done', 'done', 'done'] }\n}\n\nfunction applyTerminalProgress(\n ctx: CliContext,\n result: InstallResult,\n reloadError: string | undefined,\n): void {\n ctx.tui.modal.updateProgress(computeTerminalProgress(result, reloadError, i18next.t('cmd.unknownError')))\n}\n\nasync function executeInstall(\n source: string,\n options: InstallOptions,\n ctx: CliContext,\n): Promise<void> {\n const manager = new InstallManager()\n const label = i18next.t('cmd.titleInstall')\n\n // 打开进度条 dialog(聚焦阻断输入)\n // RFC-290 D5:阶段名与标题走 i18n——此前是硬编码英文字面量,中文环境下面板会突兀\n // 蹦出英文,与其余 20+ 弹窗不一致。\n const phases = [\n i18next.t('install.phaseResolving'),\n i18next.t('install.phaseDetecting'),\n i18next.t('install.phaseInstalling'),\n i18next.t('install.phaseRefreshing'),\n ]\n ctx.tui.modal.openModal({ kind: 'progress', title: i18next.t('install.title', { source }), phases })\n\n // 安装进度 → dialog 更新\n const phaseMap: Record<string, number> = { resolving: 0, detecting: 1, installing: 2, refreshing: 3 }\n const progressOptions: InstallOptions = {\n ...options,\n onProgress: (p) => {\n const idx = phaseMap[p.phase] ?? -1\n if (idx >= 0) {\n const statuses = phases.map((_, i) => {\n if (i < idx) return 'done' as const\n if (i === idx) return 'active' as const\n return 'pending' as const\n })\n const fraction = computeProgressFraction(p)\n const detail = formatProgressDetail(p)\n ctx.tui.modal.updateProgress({\n phaseIndex: idx,\n phaseStatuses: statuses,\n percent: Math.round(((idx + fraction) / phases.length) * 90),\n detail,\n subProgress: p.current !== undefined && p.total !== undefined ? { current: p.current, total: p.total } : undefined,\n })\n }\n },\n }\n\n const result = await manager.install(\n source,\n progressOptions,\n () => ctx.app.settings.get('mcp_servers') as Record<string, unknown> | undefined,\n async (servers) => {\n await ctx.app.settings.persist({ mcp_servers: servers as McpServersConfig })\n },\n )\n\n // ── 安装写盘结束 → 真实能力刷新(RFC-286 D4/D5) ──\n //\n // `refreshing` 阶段由本处 fire——`InstallManager` 不再自行 fire(它没有 App 句柄,\n // 其 refreshing 与真实 reload 无关,属伪进度)。谁执行谁上报:面板上的\n // \"Refreshing…\" 从此对应下方 `reloadCapabilities()` 的真实耗时。\n //\n // 时序变更:终态裁决移到 reload **之后**(此前在 reload 前就置 success 并开始\n // 2s 自动关闭倒计时)。这样 reload 失败才能降级为 partial 呈现给用户。\n const installOk = result.success || (result.results?.some((r) => r.success) ?? false)\n let reloadError: string | undefined\n if (installOk) {\n ctx.tui.modal.updateProgress({\n phaseIndex: REFRESHING_PHASE_INDEX,\n phaseStatuses: ['done', 'done', 'done', 'active'],\n percent: 90,\n detail: i18next.t('install.reloadingDetail'),\n })\n try {\n await ctx.app.reloadCapabilities()\n ctx.tui.plugins.setPluginRegistrations(ctx.app.getPluginRegistrations())\n } catch (err) {\n reloadError = err instanceof Error ? err.message : String(err)\n }\n }\n\n // 安装结束 → dialog 终态(RFC-286 D2/D3/R4:终态必须同时下发 percent 与\n // phaseStatuses,不得让面板继承中途快照——此前只传 status/errorMessage,\n // 导致失败面板冻结在 68% 且阶段行仍是 active,看起来像还在刷新)。\n applyTerminalProgress(ctx, result, reloadError)\n\n if (!result.success) {\n // 多 skill 部分失败时继续(D5:独立安装不回滚)\n // 全部失败时展示第一个错误\n if (!result.results || result.results.every((r) => !r.success)) {\n ctx.tui.info.showToast(label, i18next.t('cmd.installFailed', { err: result.error ?? i18next.t('cmd.unknownError') }))\n if (!result.results) return\n // 多 skill 全部失败但仍然有 results→继续执行 toast 汇总\n }\n }\n\n const scopeLabel = options.global ? i18next.t('cmd.scopeGlobal') : i18next.t('cmd.scopeProject')\n\n // RFC-130: 多 skill → 逐条 toast + 汇总\n if (result.results) {\n let ok = 0\n let fail = 0\n for (const r of result.results) {\n if (r.success) {\n ok++\n ctx.tui.info.showToast(\n label,\n i18next.t('cmd.installedTo', { name: r.name ?? 'extension', scope: scopeLabel, seg: r.targetPath ? ` (${r.targetPath})` : '' }),\n )\n } else {\n fail++\n ctx.tui.info.showToast(label, i18next.t('cmd.installFailed', { err: r.error ?? i18next.t('cmd.unknownError') }))\n }\n }\n const summary = `${ok} installed${fail > 0 ? `, ${fail} failed` : ''}`\n ctx.tui.info.showToast(label, summary)\n\n // RFC-286 D6/R5:失败清单必须落到消息流。上面的逐条 toast 是单槽通道\n // (`main.ts` 的 showToast 每次都覆盖上一条),多个失败项之间会互相顶掉,\n // 面板终态又只列前 3 条——没有这条 command_echo,超出部分将彻底不可见。\n if (fail > 0) {\n const failedLines = result.results\n .filter((r) => !r.success)\n .map((r) => `✗ ${r.name ?? '?'} —— ${r.error ?? i18next.t('cmd.unknownError')}`)\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/install',\n status: 'error',\n summary,\n detail: failedLines.join('\\n'),\n })\n }\n\n if (result.warnings && result.warnings.length > 0) {\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/install',\n status: 'warn',\n summary: `${result.warnings.length} 项警告`,\n detail: result.warnings.map((w) => `⚠ ${w}`).join('\\n'),\n })\n }\n\n // 能力刷新已在终态裁决前统一执行(RFC-286 D4,见上方 `refreshing` 阶段),\n // 失败已降级为 partial 面板终态;此处只补一条 command_echo 供回溯(R5:\n // 关键失败信息不依赖 toast 单槽通道)。\n reportReloadOutcome(ctx, reloadError)\n return\n }\n\n // RFC-080:postinstall 钩子结果反馈\n if (result.postinstall) {\n if (result.postinstall.success) {\n ctx.tui.info.showToast(label, `\"${result.name}\" installed (${scopeLabel}). postinstall: ok`)\n } else {\n const reason = result.postinstall.timeout\n ? 'timed out (30s)'\n : result.postinstall.error ?? `exit=${result.postinstall.exitCode ?? 'N/A'}`\n ctx.tui.info.showToast(label, `\"${result.name}\" installed (${scopeLabel}). postinstall: FAILED (${reason})`)\n\n const lines = [`Plugin \"${result.name}\" postinstall hook failed:`]\n lines.push(` command: ${result.postinstall.command}`)\n if (result.postinstall.exitCode != null) lines.push(` exit code: ${result.postinstall.exitCode}`)\n if (result.postinstall.stderr) lines.push(` stderr: ${result.postinstall.stderr}`)\n if (result.postinstall.truncated) lines.push(' (output truncated)')\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/install',\n status: 'error',\n summary: `\"${result.name}\" postinstall hook failed`,\n detail: lines.join('\\n'),\n })\n }\n } else {\n ctx.tui.info.showToast(\n label,\n i18next.t('cmd.installedTo', { name: result.name ?? 'extension', scope: scopeLabel, seg: result.targetPath ? ` (${result.targetPath})` : '' }),\n )\n }\n\n // warnings(RFC-080:信任持久化失败等非致命警告)\n if (result.warnings && result.warnings.length > 0) {\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/install',\n status: 'warn',\n summary: `${result.warnings.length} 项警告`,\n detail: result.warnings.map((w) => `⚠ ${w}`).join('\\n'),\n })\n }\n\n // 能力刷新已在终态裁决前统一执行(RFC-286 D4)。\n reportReloadOutcome(ctx, reloadError)\n}\n\n/**\n * RFC-286 D4/R5:能力刷新结果的消息通道反馈。\n *\n * 刷新本身已在终态裁决前执行完毕(面板 `Refreshing…` 阶段对应其真实耗时),失败时面板\n * 已降级为 `partial`。此处只负责消息流留痕:成功走 toast(非关键信息,可被覆盖);\n * 失败走 command_echo(关键信息,不依赖 toast 单槽通道)。\n */\nfunction reportReloadOutcome(ctx: CliContext, reloadError: string | undefined): void {\n if (reloadError) {\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/install',\n status: 'error',\n summary: i18next.t('cmd.installedRefreshErr', { err: reloadError }),\n })\n return\n }\n ctx.tui.info.showToast(i18next.t('cmd.titleInstall'), i18next.t('cmd.capRefreshedInstall'))\n}\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- import{i as e}from"./install-DlVJRHRT.js";export{e as handleInstall};
@@ -1 +0,0 @@
1
- {"version":3,"file":"mcp-server-Dpza3yn2.js","names":[],"sources":["../src/commands/mcp-server.ts"],"sourcesContent":["/**\n * mcp-server.ts —— RFC-384:`otto mcp-server` 独立进程入口。\n *\n * otto 作为 MCP server 供其他 agent/IDE 调用(grill 定案方案 A:独立进程)。\n * M1:协议骨架(stdio JSON-RPC 循环 + initialize/tools/list/tools/call + MCP 错误语义)。\n * M2:readonly 工具执行面(buildMcpServerDepsFromApp——能力面投影 + 无审批 ToolExecutor,\n * D3 定案规避 HITL;请求级 sessionId 透传 D2 定案)。\n *\n * 分帧:换行分隔 JSON(MCP stdio 官方分帧);日志全部走 stderr(stdout 是协议通道)。\n */\nimport { randomUUID } from 'node:crypto'\n\n/** MCP 协议常量(官方 spec):JSON-RPC 错误码。 */\nconst PARSE_ERROR = -32700\nconst METHOD_NOT_FOUND = -32601\nconst INVALID_PARAMS = -32602\nconst INTERNAL_ERROR = -32603\n\nexport interface McpToolDefinition {\n name: string\n description: string\n inputSchema: Record<string, unknown>\n}\n\n/** tools/call 结果文本化(MCP content 块形态)。 */\nexport interface McpCallResult {\n text: string\n isError: boolean\n}\n\nexport interface McpServerDeps {\n /** 暴露的工具目录(M2:装配层从 App 会话能力面投影注入;缺省空集)。 */\n tools?: McpToolDefinition[]\n /**\n * RFC-384 M4:全量已注册工具名(协议层 known-tool 判定用——write 工具不在 tools/list\n * 投影里但可被显式调用,存在性判定需覆盖全量而非仅投影)。\n */\n allToolNames?: string[]\n /**\n * RFC-384 M2:工具执行面——装配层注入(readonly 工具经 tool-executor 执行,\n * 无审批路径)。缺省 = 全部返回「not wired」协议错误。\n */\n callTool?: (\n name: string,\n args: Record<string, unknown>,\n sessionId: string | undefined,\n ) => Promise<McpCallResult>\n /** 测试注入:协议通道(缺省 = 真实 stdin/stdout)。 */\n readLine?: (handler: (line: string) => void) => void\n write?: (line: string) => void\n /** 优雅退出信号(测试注入)。 */\n signal?: AbortSignal\n /** RFC-384 M2:退出前清理回调(生产装配 = app.stop(),插件 dispose/落盘收尾)。 */\n onShutdown?: () => Promise<void>\n}\n\ninterface JsonRpcRequest {\n jsonrpc?: string\n id?: string | number\n method?: string\n params?: Record<string, unknown>\n}\n\nfunction rpcError(id: string | number | undefined, code: number, message: string): string {\n return `${JSON.stringify({ jsonrpc: '2.0', id, error: { code, message } })}\\n`\n}\n\nfunction rpcResult(id: string | number | undefined, result: unknown): string {\n return `${JSON.stringify({ jsonrpc: '2.0', id, result })}\\n`\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v)\n}\n\n/** M4 工具执行面:tools/call 经注入 callTool 执行(缺省 not wired)。 */\nexport function createMcpHandlers(\n tools: McpToolDefinition[],\n callTool?: McpServerDeps['callTool'],\n allToolNames?: string[],\n): (req: JsonRpcRequest) => Promise<string> {\n return async (req) => {\n const { id, method, params } = req\n switch (method) {\n case 'initialize': {\n return rpcResult(id, {\n protocolVersion: '2024-11-05',\n capabilities: { tools: {} },\n serverInfo: { name: 'otto', version: '0.0.1' },\n })\n }\n case 'notifications/initialized':\n // 通知无响应\n return ''\n case 'tools/list': {\n return rpcResult(id, {\n tools: tools.map((t) => ({\n name: t.name,\n description: t.description,\n inputSchema: t.inputSchema,\n })),\n })\n }\n case 'tools/call': {\n const name = typeof params?.name === 'string' ? params.name : ''\n // RFC-384 M4:known-tool 判定覆盖全量注册名(write 工具不在 list 投影但可显式调用)\n const knownNames = allToolNames && allToolNames.length > 0 ? allToolNames : tools.map((t) => t.name)\n if (!knownNames.includes(name)) {\n return rpcError(id, INVALID_PARAMS, `unknown tool: ${name}`)\n }\n if (!callTool) {\n return rpcError(id, INTERNAL_ERROR, `tool \"${name}\" execution not wired`)\n }\n const args = isRecord(params?.arguments) ? params!.arguments : {}\n // RFC-384 D2:请求级 sessionId(MCP params 顶层扩展键,官方客户端不受影响)。\n const sessionId = typeof params?.sessionId === 'string' ? params.sessionId : undefined\n try {\n const result = await callTool(name, args as Record<string, unknown>, sessionId)\n return rpcResult(id, {\n content: [{ type: 'text', text: result.text }],\n isError: result.isError,\n })\n } catch (err) {\n return rpcError(id, INTERNAL_ERROR, err instanceof Error ? err.message : String(err))\n }\n }\n default:\n return rpcError(id, METHOD_NOT_FOUND, `unknown method: ${method ?? ''}`)\n }\n }\n}\n\n/** stdio 逐行循环(换行分帧);非法 JSON 返回 parse error。 */\nexport function startMcpStdioLoop(\n deps: McpServerDeps,\n): { handle: (line: string) => void } {\n const write = deps.write ?? ((s) => process.stdout.write(s))\n const handleReq = createMcpHandlers(deps.tools ?? [], deps.callTool, deps.allToolNames)\n\n const handle = (line: string): void => {\n const trimmed = line.trim()\n if (!trimmed) return\n let req: JsonRpcRequest\n try {\n req = JSON.parse(trimmed) as JsonRpcRequest\n } catch {\n // 统一走微任务:与正常响应同序(逐行输入 → 逐行输出顺序一致)。\n void Promise.resolve().then(() => write(rpcError(undefined, PARSE_ERROR, 'parse error')))\n return\n }\n if (!req.method) return\n void handleReq(req).then((out) => {\n if (out) write(out)\n })\n }\n\n const readLine = deps.readLine\n if (readLine) {\n readLine(handle)\n } else {\n let buf = ''\n process.stdin.setEncoding('utf8')\n process.stdin.on('data', (chunk: string) => {\n buf += chunk\n for (;;) {\n const nl = buf.indexOf('\\n')\n if (nl === -1) break\n const line = buf.slice(0, nl)\n buf = buf.slice(nl + 1)\n handle(line)\n }\n })\n process.stdin.resume()\n }\n\n const signal = deps.signal\n if (signal) {\n signal.addEventListener('abort', () => {\n void (deps.onShutdown\n ? deps.onShutdown().catch(() => undefined).finally(() => process.exit(0))\n : Promise.resolve().then(() => process.exit(0)))\n })\n } else {\n const exit = (): void => {\n void (deps.onShutdown\n ? deps.onShutdown().catch(() => undefined).finally(() => process.exit(0))\n : Promise.resolve().then(() => process.exit(0)))\n }\n process.on('SIGTERM', exit)\n process.on('SIGINT', exit)\n }\n\n return { handle }\n}\n\n/** `otto mcp-server` 命令入口。 */\nexport async function runMcpServerCommand(\n _args: string[],\n deps: McpServerDeps = {},\n): Promise<number> {\n startMcpStdioLoop(deps)\n return 0\n}\n\n/**\n * RFC-384 M2/M4:从宿主 App 构建 server 面——readonly 工具投影 + 权限裁决执行面。\n *\n * 投影规则(D4 定案):exposure 非 hidden/deferred 且 readonly===true 的工具进\n * tools/list(write 工具不在投影但可显式调用,known-tool 判定覆盖全量)。\n *\n * 执行面(M4 权限裁决):\n * - readonly 工具 → 直通执行(D3 定案无 HITL 需求);\n * - write 工具 → 先经权限引擎 evaluate(permissionRegistry 注入时):\n * allow → 执行;ask → fail-closed 拒绝(MCP 通道无交互审批原语,返回明确错误);\n * deny → fail-closed 拒绝(带 policy 原因);\n * - permission 未注入 → write 工具 fail-closed 拒绝(不默认放行)。\n *\n * inputSchema:zod 4 原生 toJSONSchema(M3)。\n */\nexport function buildMcpServerDepsFromApp(\n appTools: import('@x-otto/interchange').AgentTool[],\n permission?: {\n evaluate(context: {\n timing: string\n toolName: string\n args: Record<string, unknown>\n agentName: string\n sessionId: string\n metadata: Record<string, unknown>\n }): { effect: 'allow' | 'ask' | 'deny'; reason?: string }\n },\n): Required<Pick<McpServerDeps, 'tools' | 'callTool' | 'allToolNames'>> {\n const readonlyTools = appTools.filter(\n (t) => t.readonly === true && t.exposure !== 'hidden' && t.exposure !== 'deferred',\n )\n\n const tools: McpToolDefinition[] = readonlyTools.map((t) => ({\n name: t.name,\n description: typeof t.description === 'function' ? t.name : t.description,\n // RFC-384 M3:zod 4 原生 toJSONSchema(含 description/enum/required/optional 语义)——\n // 协议垫片(interchange ZodType.toJSONSchema)可选调用,缺失时回退最小骨架\n // (执行层 zod safeParse 仍兜底校验)。\n inputSchema:\n (typeof t.parameters?.toJSONSchema === 'function'\n ? (t.parameters.toJSONSchema() as Record<string, unknown>)\n : undefined) ?? { type: 'object' },\n }))\n\n const allToolNames = appTools.map((t) => t.name)\n\n return {\n tools,\n allToolNames,\n callTool: async (name, args, sessionId) => {\n const tool = appTools.find((t) => t.name === name)\n const { createToolExecutor } = await import('@x-otto/agent')\n\n // RFC-384 M4:write 工具执行前权限裁决(fail-closed)。\n if (tool && tool.readonly !== true) {\n if (!permission) {\n return {\n text: `Tool \"${name}\" is a write tool but no permission registry is wired; ` +\n `denied (fail-closed).`,\n isError: true,\n }\n }\n const decision = permission.evaluate({\n timing: 'tool.execute.before',\n toolName: name,\n args,\n agentName: 'mcp-server',\n sessionId: sessionId ?? 'mcp-server',\n metadata: {},\n })\n if (decision.effect === 'ask') {\n return {\n text: `Tool \"${name}\" requires interactive approval, which is not available ` +\n `over MCP (fail-closed). Run it in an interactive otto session instead.`,\n isError: true,\n }\n }\n if (decision.effect === 'deny') {\n return {\n text: `Tool \"${name}\" denied by permission policy${decision.reason ? `: ${decision.reason}` : ''}.`,\n isError: true,\n }\n }\n // allow → 继续执行\n }\n\n const executor = createToolExecutor(appTools, {\n sessionId: sessionId ?? 'mcp-server',\n agentName: 'mcp-server',\n })\n const result = await executor.execute(\n { type: 'tool_call', id: randomUUID(), name, arguments: args },\n new AbortController().signal,\n )\n const text = result.content\n .map((c) => (c.type === 'text' ? c.text : `[${c.type}]`))\n .join('\\n')\n return { text: text || '(no output)', isError: result.isError === true }\n },\n }\n}\n"],"mappings":"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"}
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- import{ot as e}from"./command-dispatcher-DTOPjtZP.js";import{t}from"./version-BrSIyY6s.js";import{resolve as n}from"node:path";var r=e({SUB_COMMAND_NAMES:()=>a,getHelpLines:()=>l,parseCLI:()=>c});function i(e){return e.startsWith(`--`)||/^-[^\s-]/.test(e)}const a=new Set([`run`,`serve`,`daemon`,`doctor`,`config`,`migrate`,`auth`,`observe`,`debug`,`time-travel`,`mcp`,`mcp-server`,`persistenced`,`remote-persistence-server`,`persistence`,`extension`,`ps`,`feedback`,`export-trajectory`]),o=new Map([{flags:[`--print`,`-p`],type:`mode`,value:`print`},{flags:[`--interactive`,`-i`],type:`mode`,value:`interactive`},{flags:[`--json`],type:`mode`,value:`json`},{flags:[`--model`,`-m`],type:`string`,key:`model`},{flags:[`--config`,`-c`],type:`string`,key:`configPath`},{flags:[`--project`,`-d`],type:`path`,key:`projectDir`},{flags:[`--quiet`,`-q`],type:`boolean`,key:`quiet`},{flags:[`--verbose`],type:`boolean`,key:`verbose`},{flags:[`--max-tool-turns`],type:`number`,key:`maxToolTurns`},{flags:[`--max-tool-turn-extensions`],type:`number`,key:`maxToolTurnExtensions`},{flags:[`--prompt-token-budget`],type:`number`,key:`promptTokenBudget`},{flags:[`--prompt-time-budget`],type:`number`,key:`promptTimeBudgetMin`},{flags:[`--session-cost-budget`],type:`number`,key:`sessionCostBudget`},{flags:[`--no-stall-detection`],type:`boolean`,key:`noStallDetection`},{flags:[`--stall-window-turns`],type:`number`,key:`stallWindowTurns`},{flags:[`--stall-repeat-threshold`],type:`number`,key:`stallRepeatThreshold`},{flags:[`--continue`],type:`string`,key:`continueSession`},{flags:[`--attach`],type:`string`,key:`attachSession`},{flags:[`--force-write-lease`],type:`boolean`,key:`forceWriteLease`},{flags:[`--memory-restart-grace`],type:`boolean`,key:`memoryRestartGrace`},{flags:[`--resume-prompt`],type:`string`,key:`resumePrompt`},{flags:[`--session-url`],type:`string`,key:`sessionUrl`},{flags:[`--session-token`],type:`string`,key:`sessionToken`},{flags:[`--id`],type:`string`,key:`workspaceId`},{flags:[`--shadow`],type:`boolean`,key:`shadow`},{flags:[`--help`,`-h`],type:`boolean`,key:`showHelp`},{flags:[`--version`,`-v`],type:`action`,handler:u}].flatMap(e=>e.flags.map(t=>[t,e])));var s=class{i;constructor(e){this.args=e,this.i=0}current(){return this.args[this.i]}next(){return this.args[++this.i]}advance(){this.i++}skip(e){this.i+=e}pos(){return this.i}};function c(e){let t=e.slice(2),r=new s(t),c={mode:`interactive`,projectDir:process.cwd(),verbose:!1,quiet:!1},l=r.current(),u,d=``,f,p;l&&a.has(l)?(u=l,d=t.slice(1).join(` `),r.skip(1),u===`run`&&(c.prompt=t.slice(1).filter(e=>!i(e)).join(` `),c.mode=`print`)):l&&!i(l)&&(f=l,p=t.slice(1));let m=c.prompt?[c.prompt]:[];for(;r.current()!==void 0;){let e=r.current();if(e===`--inspect`){c.inspect=!0,r.advance();continue}if(e?.startsWith(`--inspect=`)){let t=Number(e.slice(10));c.inspect=Number.isInteger(t)&&t>0?t:!0,r.advance();continue}if(e?.startsWith(`--restart-generation=`)){let t=Number(e.slice(21));Number.isInteger(t)&&t>0&&(c.restartGeneration=t),r.advance();continue}let t=o.get(e);if(t){switch(r.advance(),t.type){case`boolean`:c[t.key]=!0;break;case`mode`:c.mode=t.value;break;case`string`:c[t.key]=r.current(),r.advance();break;case`number`:{let e=r.current();if(e===void 0||e.startsWith(`-`))break;let n=Number(e),i=t.key===`maxToolTurnExtensions`;Number.isFinite(n)&&(i?n>=0:n>0)&&(c[t.key]=n),r.advance();continue}case`path`:c[t.key]=n(r.current()??process.cwd()),r.advance();break;case`action`:t.handler();break}continue}e&&!i(e)&&!u&&(c.prompt===void 0&&c.mode===`interactive`&&(c.mode=`print`),m.push(e)),r.advance()}return m.length>0&&(c.prompt=m.join(` `)),{options:c,subCommand:u,subCommandArgs:d,unresolvedCommandName:f,unresolvedCommandArgs:p}}function l(){return[``,`otto - AI 助理命令行工具`,``,`用法:`,` otto [提示词] 使用提示词启动(print 模式)`,` otto 进入交互式 TUI 模式`,` otto --json "query" JSON 输出模式`,``,`选项:`,` -i, --interactive Interactive 模式(默认)`,` -p, --print Print 模式(非交互)`,` --json JSON 输出模式`,` -m, --model <id> 指定模型`,` -c, --config <path> 配置文件路径`,` -d, --project <dir> 项目目录`,` --verbose 输出详细日志`,` -q, --quiet 只输出 error 日志`,` --shadow 影子模式:otto 自有状态(凭据/会话/记忆/教训/配置/trace/日志)本次会话零磁盘写入(不保证 provider 服务端日志与 shell/OS 层痕迹;RFC-345)`,` --max-tool-turns <n> 单个用户回合最大工具回合数(默认 120)`,` --max-tool-turn-extensions <n> 触顶后压缩续跑的扩展次数(默认主会话 3 / 子 agent 0)`,` --prompt-token-budget <n> 单回合输出 token 预算(配置后轮次上限 ×4 退位安全阀;RFC-104)`,` --prompt-time-budget <min> 单回合墙钟时间预算(分钟)`,` --session-cost-budget <usd> 会话累计成本软预算(美元,超过一次性提醒不阻断;RFC-204)`,` --no-stall-detection 关闭早期停滞检测(默认开启;RFC-203)`,` --stall-window-turns <n> 停滞检测滑动窗口大小(需与 --stall-repeat-threshold 同时提供,默认 8)`,` --stall-repeat-threshold <n> 停滞检测同签名重复阈值(需与 --stall-window-turns 同时提供,默认 4)`,` --continue <id> 继续已有会话`,` --session-url <url> 远端会话持久化地址(如 http://host:3001/sessions)`,` --session-token <token> 远端会话持久化 Bearer token`,` --inspect[=port] 启用 Node.js inspector(默认: 9229)`,` -h, --help 显示帮助`,` --version, -v 显示版本`,``,`命令:`,` otto run <prompt> 执行一次性任务`,` otto serve [--port N] [--inspect] [--static <dir>] [--token <t>] 启动 HTTP/WS service(--static 同源托管前端 SPA,--token 启用鉴权)`,` otto doctor [--summary|--json] 环境健康检查(环境/配置/存储状态/连通性)`,` otto auth login <provider> 登录(cursor / anthropic / openai-codex / github-copilot)`,` otto auth status 查看各 provider 凭据来源`,` otto auth logout <provider> 注销并清除凭据`,` otto config export [--project] [-o <path>] 导出配置(RFC-219)`,` otto config import <file> [--dry-run] [--scope global|project] [--no-plugins] [--plugins-only] [--yes] 导入配置`,` otto config restore [--list] 撤销最近一次导入`,` otto migrate export [--no-sessions] [-o <path>] 导出配置+插件清单+会话数据为迁移包(换电脑用)`,` otto migrate import <archive> [--no-sessions] 导入迁移包(目标已有会话数据时跳过会话部分)`,` otto observe <action> <id> 观测面板(summary / timeline / spans)`,` otto observe debug <action> [...] 调试面板(breakpoints / points / state)`,` otto observe replay <action> <id> 时间旅行(checkpoints / scrubber / restore / fork / replay)`,` otto debug <action> [...] (alias: otto observe debug)`,` otto time-travel <action> <id> (alias: otto observe replay)`,` otto mcp login <name> MCP server OAuth 登录`,` otto mcp logout <name> 登出 MCP server`,` otto mcp list 列出已认证的 MCP server`,` otto extension create <name> 生成插件骨架(命令 + 面板 + manifest,开箱即用)`,` otto extension install <name> [--yes] 安装首方/自定义插件(anthropic-provider / deepseek-provider / browser-use / …)`,` otto extension update [name] 检查并重装已过期的已安装 skill/plugin`,` otto persistence local [...flags] 启动独立持久化服务(storage + events + sync)`,` otto persistence remote [...flags] 启动最小化远程持久化服务(5 REST 端点)`,` otto persistenced [...flags] (alias: otto persistence local)`,` otto remote-persistence-server [...flags] (alias: otto persistence remote)`,` otto ps [--sweep] [--include-weak] [--json] 进程群列表与孤儿清扫(SIGTERM only,RFC-196)`,` otto export-trajectory --session <id> --out <path> [--since-seq <n>] 导出会话轨迹 JSONL(RFC-385;默认关)`,``,"面板命令默认连 `otto serve` 实例(http://127.0.0.1:8417,OTTO_SERVICE_URL/--remote 可覆盖)。"]}function u(){process.stdout.write(`otto ${t()}\n`),process.exit(0)}export{c as n,r,a as t};
3
- //# sourceMappingURL=parse-cli-BvcxTK88.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"parse-cli-BvcxTK88.js","names":[],"sources":["../src/parse-cli.ts"],"sourcesContent":["import { resolve } from 'node:path'\n\nimport type { CLIOptions, RunMode } from './types'\nimport { getVersion } from './version'\n\nexport type SubCommand = 'run' | 'serve' | 'daemon' | 'doctor' | 'config' | 'migrate' | 'auth' | 'observe' | 'debug' | 'time-travel' | 'mcp' | 'mcp-server' | 'persistenced' | 'remote-persistence-server' | 'persistence' | 'extension' | 'ps' | 'feedback' | 'export-trajectory'\n\n/**\n * 真正的 flag 形态判定:`--xxx`、`-x`、`-x=value` 算 flag;\n * `- 文本`(连字符后跟空格)是**合法 prompt 开头**,不是 flag。\n *\n * 真机证据(Terminal-Bench 2.1 评测,2026-08-14):pytorch-model-recovery 任务的\n * instruction 以 \"- You are given a PyTorch state dictionary…\" 开头,旧判定\n * `arg.startsWith('-')` 把它当 flag 吞掉 → promptParts 为空 → otto 输出\n * \"no prompt provided\" → 三次 attempt 全部 0 分且无异常,被误判为模型能力失败。\n * 标准 CLI 语义下 `- ` 开头的参数不是 flag(minimist 同款行为),修复后此类 prompt\n * 正常进入会话。\n */\nfunction looksLikeFlag(arg: string): boolean {\n return arg.startsWith('--') || /^-[^\\s-]/.test(arg)\n}\n\nexport interface ParsedCLI {\n options: CLIOptions\n /** 未命中任何内建子命令时为 `undefined`(而非 `null` 哨兵——与 SUB_COMMAND_NAMES 的\n * 元素类型保持一致,消费方无需 `.filter(n => n !== null)` 剔除哨兵值)。 */\n subCommand: SubCommand | undefined\n subCommandArgs: string\n /**\n * RFC-112 D2:内建 `SUB_COMMAND_NAMES` 未命中的第一个位置参数(候选插件 CLI 命令名,\n * 如 `otto feishu ...` 里的 `\"feishu\"`)。`undefined` 表示第一个参数已命中内建命令,\n * 或压根没有位置参数。**不改变** `options.prompt`/`subCommand` 的既有构建逻辑——\n * 这两者仍然按\"当作 prompt\"的旧行为完整计算好,供插件命令名查无匹配时零改动回退。\n */\n unresolvedCommandName?: string\n /**\n * 与 `unresolvedCommandName` 配对:该候选名之后的**原始** args(未做 flag 过滤,含 `-h`\n * 等字面量),只在插件命令表命中该名字时使用(透传给插件可执行文件)。\n */\n unresolvedCommandArgs?: string[]\n}\n\n/** RFC-112 D2:导出供 `cli.ts` 构建插件 CLI 命令表时用作保留字集合(插件不得覆盖)。 */\nexport const SUB_COMMAND_NAMES = new Set<SubCommand>([\n 'run', 'serve', 'daemon', 'doctor', 'config', 'migrate', 'auth', 'observe', 'debug', 'time-travel', 'mcp', 'mcp-server', 'persistenced', 'remote-persistence-server', 'persistence', 'extension', 'ps', 'feedback', 'export-trajectory',\n])\n\ntype OptionEntry =\n | { flags: string[]; type: 'boolean'; key: keyof CLIOptions }\n | { flags: string[]; type: 'mode'; value: RunMode }\n | { flags: string[]; type: 'string'; key: keyof CLIOptions }\n | { flags: string[]; type: 'number'; key: keyof CLIOptions }\n | { flags: string[]; type: 'path'; key: keyof CLIOptions }\n | { flags: string[]; type: 'action'; handler: () => void }\n\nconst OPTIONS: OptionEntry[] = [\n { flags: ['--print', '-p'], type: 'mode', value: 'print' },\n { flags: ['--interactive', '-i'], type: 'mode', value: 'interactive' },\n { flags: ['--json'], type: 'mode', value: 'json' },\n { flags: ['--model', '-m'], type: 'string', key: 'model' },\n { flags: ['--config', '-c'], type: 'string', key: 'configPath' },\n { flags: ['--project', '-d'], type: 'path', key: 'projectDir' },\n { flags: ['--quiet', '-q'], type: 'boolean', key: 'quiet' },\n { flags: ['--verbose'], type: 'boolean', key: 'verbose' },\n { flags: ['--max-tool-turns'], type: 'number', key: 'maxToolTurns' },\n { flags: ['--max-tool-turn-extensions'], type: 'number', key: 'maxToolTurnExtensions' },\n { flags: ['--prompt-token-budget'], type: 'number', key: 'promptTokenBudget' },\n { flags: ['--prompt-time-budget'], type: 'number', key: 'promptTimeBudgetMin' },\n { flags: ['--session-cost-budget'], type: 'number', key: 'sessionCostBudget' },\n { flags: ['--no-stall-detection'], type: 'boolean', key: 'noStallDetection' },\n { flags: ['--stall-window-turns'], type: 'number', key: 'stallWindowTurns' },\n { flags: ['--stall-repeat-threshold'], type: 'number', key: 'stallRepeatThreshold' },\n { flags: ['--continue'], type: 'string', key: 'continueSession' },\n { flags: ['--attach'], type: 'string', key: 'attachSession' },\n { flags: ['--force-write-lease'], type: 'boolean', key: 'forceWriteLease' },\n // RFC-115 M3(P1-1 修正):仅由 cli.ts 的内存治理触发重启 chaperone 附加,不对外文档化——\n // 区分\"内存压力触发的自动重启\"与\"用户手动 otto --continue\",前者才应起算 D5 宽限期。\n { flags: ['--memory-restart-grace'], type: 'boolean', key: 'memoryRestartGrace' },\n // RFC-115 M4:仅由 cli.ts 的内存治理触发重启 chaperone 附加,不对外文档化——重启前\n // 会话正忙时的续跑原文,子进程启动后自动重新提交为续跑轮。\n { flags: ['--resume-prompt'], type: 'string', key: 'resumePrompt' },\n { flags: ['--session-url'], type: 'string', key: 'sessionUrl' },\n { flags: ['--session-token'], type: 'string', key: 'sessionToken' },\n { flags: ['--id'], type: 'string', key: 'workspaceId' },\n { flags: ['--shadow'], type: 'boolean', key: 'shadow' },\n { flags: ['--help', '-h'], type: 'boolean', key: 'showHelp' },\n { flags: ['--version', '-v'], type: 'action', handler: printVersion },\n]\n\nconst OPTION_LOOKUP: Map<string, OptionEntry> = new Map(\n OPTIONS.flatMap(e => e.flags.map(f => [f, e] as const)),\n)\n\nclass ArgCursor {\n private i: number\n constructor(private args: string[]) {\n this.i = 0\n }\n current(): string | undefined { return this.args[this.i] }\n next(): string | undefined { return this.args[++this.i] }\n advance(): void { this.i++ }\n skip(count: number): void { this.i += count }\n pos(): number { return this.i }\n}\n\nexport function parseCLI(argv: string[]): ParsedCLI {\n const args = argv.slice(2)\n const cursor = new ArgCursor(args)\n\n const options: CLIOptions = {\n mode: 'interactive',\n projectDir: process.cwd(),\n verbose: false,\n quiet: false,\n }\n\n const first = cursor.current()\n let subCommand: SubCommand | undefined\n let subCommandArgs = ''\n let unresolvedCommandName: string | undefined\n let unresolvedCommandArgs: string[] | undefined\n if (first && SUB_COMMAND_NAMES.has(first as SubCommand)) {\n subCommand = first as SubCommand\n subCommandArgs = args.slice(1).join(' ')\n cursor.skip(1)\n\n if (subCommand === 'run') {\n options.prompt = args.slice(1).filter(a => !looksLikeFlag(a)).join(' ')\n options.mode = 'print'\n }\n } else if (first && !looksLikeFlag(first)) {\n // RFC-112 D2:第一个位置参数不是内建命令,也不以 `-` 开头(不是 flag)——\n // 记录为候选插件 CLI 命令名,供 cli.ts 查插件表二次判定;不改变下方现有的\n // \"当作 prompt\" 逻辑(该逻辑仍会照常把 first 纳入 promptParts,回退路径零改动)。\n unresolvedCommandName = first\n unresolvedCommandArgs = args.slice(1)\n }\n\n let promptParts: string[] = options.prompt ? [options.prompt] : []\n\n while (cursor.current() !== undefined) {\n const arg = cursor.current()!\n\n if (arg === '--inspect') {\n options.inspect = true\n cursor.advance()\n continue\n }\n if (arg?.startsWith('--inspect=')) {\n const port = Number(arg.slice('--inspect='.length))\n options.inspect = Number.isInteger(port) && port > 0 ? port : true\n cursor.advance()\n continue\n }\n // RFC-196 M3:重启代际计数(chaperone 重启 spawn 注入,不文档化——内部可观测标记,\n // 供 ≥3 代告警与 otto ps GEN 列展示;不驱动任何行为分支)。\n if (arg?.startsWith('--restart-generation=')) {\n const gen = Number(arg.slice('--restart-generation='.length))\n if (Number.isInteger(gen) && gen > 0) options.restartGeneration = gen\n cursor.advance()\n continue\n }\n\n const entry = OPTION_LOOKUP.get(arg)\n if (entry) {\n cursor.advance()\n switch (entry.type) {\n case 'boolean':\n ;(options as unknown as Record<string, unknown>)[entry.key] = true\n break\n case 'mode':\n options.mode = entry.value\n break\n case 'string':\n ;(options as unknown as Record<string, unknown>)[entry.key] = cursor.current()\n cursor.advance()\n break\n case 'number':\n {\n // 08-13 backlog triage(终局复核修订):flag 末尾缺值时 cursor.current() 是\n // undefined → Number(undefined)=NaN 静默写进配置。改为 fail-closed:非有限\n // 值不写入(等同未传,消费方走默认值)。\n //\n // 0 值策略(复核发现的行为漂移):`maxToolTurnExtensions` 的 0 是**合法语义**\n // ——主会话显式\"禁止压缩续跑\"(缺省 3,agent-session.ts defaultMaxToolTurnExtensions),\n // 被 `>0` 拒绝会静默改回 3。故按 key 白名单允许 0:该 key 允许 `>=0`,\n // 其余 number flag 维持 `>0`(其消费方均无 0 特判,拒绝 0 无行为差异)。\n //\n // 缺值/下一 token 是另一 flag 时(如 `--max-tool-turns --quiet`):值以 `-`\n // 开头视为**未提供**——不写入、**不 advance**(否则会把 --quiet 当值吞掉,\n // 复核确认的 pre-existing 吞 flag 缺陷在此闭合)。number 值恒为正数,不以\n // `-` 开头,故该判定无合法值误伤。\n const raw = cursor.current()\n if (raw === undefined || raw.startsWith('-')) break\n const parsed = Number(raw)\n const allowsZero = entry.key === 'maxToolTurnExtensions'\n if (Number.isFinite(parsed) && (allowsZero ? parsed >= 0 : parsed > 0)) {\n ;(options as unknown as Record<string, unknown>)[entry.key] = parsed\n }\n cursor.advance()\n continue\n }\n case 'path':\n ;(options as unknown as Record<string, unknown>)[entry.key] = resolve(cursor.current() ?? process.cwd())\n cursor.advance()\n break\n case 'action':\n entry.handler()\n break\n }\n continue\n }\n\n if (arg && !looksLikeFlag(arg) && !subCommand) {\n if (options.prompt === undefined && options.mode === 'interactive') {\n options.mode = 'print'\n }\n promptParts.push(arg)\n }\n cursor.advance()\n }\n\n if (promptParts.length > 0) {\n options.prompt = promptParts.join(' ')\n }\n\n return { options, subCommand, subCommandArgs, unresolvedCommandName, unresolvedCommandArgs }\n}\n\n/** 帮助文本行(供 plain-text printHelp 与 TUI InfoDialog 共用)。 */\nexport function getHelpLines(): string[] {\n return [\n '',\n 'otto - AI 助理命令行工具',\n '',\n '用法:',\n ' otto [提示词] 使用提示词启动(print 模式)',\n ' otto 进入交互式 TUI 模式',\n ' otto --json \"query\" JSON 输出模式',\n '',\n '选项:',\n ' -i, --interactive Interactive 模式(默认)',\n ' -p, --print Print 模式(非交互)',\n ' --json JSON 输出模式',\n ' -m, --model <id> 指定模型',\n ' -c, --config <path> 配置文件路径',\n ' -d, --project <dir> 项目目录',\n ' --verbose 输出详细日志',\n ' -q, --quiet 只输出 error 日志',\n ' --shadow 影子模式:otto 自有状态(凭据/会话/记忆/教训/配置/trace/日志)本次会话零磁盘写入(不保证 provider 服务端日志与 shell/OS 层痕迹;RFC-345)',\n ' --max-tool-turns <n> 单个用户回合最大工具回合数(默认 120)',\n ' --max-tool-turn-extensions <n> 触顶后压缩续跑的扩展次数(默认主会话 3 / 子 agent 0)',\n ' --prompt-token-budget <n> 单回合输出 token 预算(配置后轮次上限 ×4 退位安全阀;RFC-104)',\n ' --prompt-time-budget <min> 单回合墙钟时间预算(分钟)',\n ' --session-cost-budget <usd> 会话累计成本软预算(美元,超过一次性提醒不阻断;RFC-204)',\n ' --no-stall-detection 关闭早期停滞检测(默认开启;RFC-203)',\n ' --stall-window-turns <n> 停滞检测滑动窗口大小(需与 --stall-repeat-threshold 同时提供,默认 8)',\n ' --stall-repeat-threshold <n> 停滞检测同签名重复阈值(需与 --stall-window-turns 同时提供,默认 4)',\n ' --continue <id> 继续已有会话',\n ' --session-url <url> 远端会话持久化地址(如 http://host:3001/sessions)',\n ' --session-token <token> 远端会话持久化 Bearer token',\n ' --inspect[=port] 启用 Node.js inspector(默认: 9229)',\n ' -h, --help 显示帮助',\n ' --version, -v 显示版本',\n '',\n '命令:',\n ' otto run <prompt> 执行一次性任务',\n ' otto serve [--port N] [--inspect] [--static <dir>] [--token <t>] 启动 HTTP/WS service(--static 同源托管前端 SPA,--token 启用鉴权)',\n ' otto doctor [--summary|--json] 环境健康检查(环境/配置/存储状态/连通性)',\n ' otto auth login <provider> 登录(cursor / anthropic / openai-codex / github-copilot)',\n ' otto auth status 查看各 provider 凭据来源',\n ' otto auth logout <provider> 注销并清除凭据',\n ' otto config export [--project] [-o <path>] 导出配置(RFC-219)',\n ' otto config import <file> [--dry-run] [--scope global|project] [--no-plugins] [--plugins-only] [--yes] 导入配置',\n ' otto config restore [--list] 撤销最近一次导入',\n ' otto migrate export [--no-sessions] [-o <path>] 导出配置+插件清单+会话数据为迁移包(换电脑用)',\n ' otto migrate import <archive> [--no-sessions] 导入迁移包(目标已有会话数据时跳过会话部分)',\n ' otto observe <action> <id> 观测面板(summary / timeline / spans)',\n ' otto observe debug <action> [...] 调试面板(breakpoints / points / state)',\n ' otto observe replay <action> <id> 时间旅行(checkpoints / scrubber / restore / fork / replay)',\n ' otto debug <action> [...] (alias: otto observe debug)',\n ' otto time-travel <action> <id> (alias: otto observe replay)',\n ' otto mcp login <name> MCP server OAuth 登录',\n ' otto mcp logout <name> 登出 MCP server',\n ' otto mcp list 列出已认证的 MCP server',\n ' otto extension create <name> 生成插件骨架(命令 + 面板 + manifest,开箱即用)',\n ' otto extension install <name> [--yes] 安装首方/自定义插件(anthropic-provider / deepseek-provider / browser-use / …)',\n ' otto extension update [name] 检查并重装已过期的已安装 skill/plugin',\n ' otto persistence local [...flags] 启动独立持久化服务(storage + events + sync)',\n ' otto persistence remote [...flags] 启动最小化远程持久化服务(5 REST 端点)',\n ' otto persistenced [...flags] (alias: otto persistence local)',\n ' otto remote-persistence-server [...flags] (alias: otto persistence remote)',\n ' otto ps [--sweep] [--include-weak] [--json] 进程群列表与孤儿清扫(SIGTERM only,RFC-196)',\n ' otto export-trajectory --session <id> --out <path> [--since-seq <n>] 导出会话轨迹 JSONL(RFC-385;默认关)',\n '',\n '面板命令默认连 `otto serve` 实例(http://127.0.0.1:8417,OTTO_SERVICE_URL/--remote 可覆盖)。',\n ]\n}\n\nfunction printVersion(): void {\n process.stdout.write(`otto ${getVersion()}\\n`)\n process.exit(0)\n}\n"],"mappings":"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SAEjB,EAAY,KAAK,EAAI,EAEvB,EAAO,SAAS,CAOlB,OAJI,EAAY,OAAS,IACvB,EAAQ,OAAS,EAAY,KAAK,IAAI,EAGjC,CAAE,UAAS,aAAY,iBAAgB,wBAAuB,wBAAuB,CAI9F,SAAgB,GAAyB,CACvC,MAAO,CACL,GACA,oBACA,GACA,MACA,8CACA,4CACA,yCACA,GACA,MACA,iDACA,4CACA,wCACA,mCACA,qCACA,mCACA,qCACA,2CACA,yHACA,oDACA,sEACA,uEACA,6CACA,iEACA,qDACA,gFACA,gFACA,qCACA,qEACA,mDACA,6DACA,mCACA,mCACA,GACA,MACA,2CACA,2HACA,0DACA,0FACA,qDACA,2CACA,8DACA,iHACA,4CACA,8EACA,4EACA,oEACA,8EACA,iGACA,+DACA,gEACA,sDACA,gDACA,oDACA,qEACA,gHACA,+DACA,2EACA,gEACA,wEACA,gFACA,kFACA,oGACA,GACA,gFACD,CAGH,SAAS,GAAqB,CAC5B,QAAQ,OAAO,MAAM,QAAQ,GAAY,CAAC,IAAI,CAC9C,QAAQ,KAAK,EAAE"}
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- import{t as e}from"./proxy-evg7ZvoD.js";export{e as handleProxy};
@@ -1 +0,0 @@
1
- {"version":3,"file":"proxy-evg7ZvoD.js","names":[],"sources":["../src/commands/handlers/proxy.ts"],"sourcesContent":["/**\n * /proxy 命令:代理配置管理(增删改查 + 测通)\n */\nimport { getDefaultProxy, probeProxy } from '@x-otto/shared'\nimport { i18next } from '@x-otto/tui'\n\nimport type { ProxyConfig } from '@x-otto/setting'\nimport type { CliContext } from '../../command-dispatcher'\n\n/**\n * 视图形状取自 `TuiBridge.stores.proxy.setView` 的参数类型——`ProxyPanelView` 是 tui 的\n * 内部视图契约,未从其 barrel 导出,也不该为了 cli 一处消费就扩大公共 API。用\n * `Parameters<>` 反查即可拿到同一份类型,且 tui 侧改字段时这里会同步编译报错。\n */\ntype ProxyView = Parameters<CliContext['tui']['stores']['proxy']['setView']>[0]\n\nconst PROXY_URL_RE = /^https?:\\/\\/.+/\n\nfunction getProxy(ctx: CliContext): ProxyConfig | undefined {\n return ctx.app.settings.get('proxy') as ProxyConfig | undefined\n}\n\n/**\n * 启动时从环境变量检测到的系统代理(RFC-288 D5)。由 `interactive-setup.ts` 在检测后\n * 写入——此前那里检测到只弹一次 toast 就丢弃,面板完全不知情,用户环境里明明有可用地址\n * 却仍需手打。**仅作建议值**:必须用户在面板上显式确认才落盘(沿用前身 RFC 规则 5)。\n */\nlet detectedEnvProxy: ProxyView['detectedEnvProxy']\n\nexport function setDetectedEnvProxy(value: string, source: string): void {\n detectedEnvProxy = { value, source }\n}\n\n/** 最近一次探测结果(面板显示用)。仅进程内瞬态,不落盘。 */\nlet lastProbe: ProxyView['probe']\n\nfunction pushProxyView(ctx: CliContext): void {\n const proxy = getProxy(ctx)\n ctx.tui.stores.proxy.setView({\n url: proxy?.url,\n enabled: proxy?.enabled,\n configPath: '~/.otto/config.json',\n // 配置态(上面两行)之外再给出**进程内实际生效**的代理,面板据此判断二者是否一致\n // (RFC-288 D3)。不一致=改了配置但未重启,这正是用户答不出\"我设了为什么没用\"的时刻。\n effectiveUrl: getDefaultProxy(),\n detectedEnvProxy,\n probe: lastProbe,\n })\n}\n\n/**\n * 探测目标 = **当前模型的 baseURL**(RFC-288 D4),不是任何固定的第三方端点。\n * 用户配代理的唯一目的就是访问他正在用的那个模型端点,探测它才直接回答用户的真实问题;\n * 固定端点在墙内两头不讨好(境外端点自身可能被墙=假阴性,国内端点不走代理也通=假阳性)。\n */\nfunction resolveProbeTarget(ctx: CliContext): string | undefined {\n const modelId = ctx.state.currentModelId\n if (!modelId) return undefined\n try {\n const model = ctx.app.modelRegistry.resolve(modelId)\n return model.baseUrl || undefined\n } catch {\n return undefined\n }\n}\n\nasync function runProbe(ctx: CliContext): Promise<void> {\n const target = resolveProbeTarget(ctx)\n if (!target) {\n lastProbe = { status: 'failed', reason: i18next.t('proxy.probeNoModel'), blamesProxy: false, target: '-' }\n pushProxyView(ctx)\n return\n }\n\n lastProbe = { status: 'probing' }\n pushProxyView(ctx)\n\n const result = await probeProxy(target)\n const host = safeHost(target)\n lastProbe = result.ok\n ? { status: 'ok', latencyMs: result.latencyMs, target: host }\n : {\n status: 'failed',\n reason: result.errorMessage ?? result.errorKind ?? 'unknown',\n // 只有\"连不上代理\"才归咎代理;目标不可达/超时不能让用户去反复检查一个正常的代理。\n blamesProxy: result.errorKind === 'proxy-unreachable',\n target: host,\n }\n pushProxyView(ctx)\n}\n\n/** 取 host 用于展示(完整 URL 太长会撑破面板行宽);解析失败时原样返回。 */\nfunction safeHost(url: string): string {\n try {\n return new URL(url).host\n } catch {\n return url\n }\n}\n\nexport async function handleProxy(args: string[], ctx: CliContext): Promise<void> {\n const arg = args[0]\n const settings = ctx.app.settings\n\n if (!arg) {\n pushProxyView(ctx)\n ctx.tui.panes.togglePane('proxy')\n return\n }\n\n if (arg === 'status') {\n const proxy = getProxy(ctx)\n if (proxy?.url) {\n const status = proxy.enabled !== false ? '已启用' : '已停用(配置保留)'\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/proxy status',\n summary: `代理状态:${status}`,\n detail: `代理地址:${proxy.url}\\n作用范围:全局(所有出站 HTTP 请求,本机地址除外)`,\n })\n } else {\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/proxy status',\n status: 'warn',\n summary: '代理未配置。使用 /proxy <url> 设置代理。',\n })\n }\n return\n }\n\n if (arg === 'on') {\n const proxy = getProxy(ctx)\n if (!proxy?.url) {\n ctx.tui.info.showToast('无代理地址', '请先使用 /proxy <url> 设置代理地址')\n return\n }\n await settings.persist({ proxy: { url: proxy.url, enabled: true } })\n pushProxyView(ctx)\n ctx.tui.info.showToast('代理已启用', proxy.url)\n return\n }\n\n if (arg === 'off') {\n const proxy = getProxy(ctx)\n if (!proxy?.url) {\n ctx.tui.info.showToast('无代理配置', '尚未设置代理')\n return\n }\n await settings.persist({ proxy: { url: proxy.url, enabled: false } })\n pushProxyView(ctx)\n ctx.tui.info.showToast('代理已停用', `配置保留(${proxy.url})`)\n return\n }\n\n // /proxy test —— 连通性探测(面板\"测试连接\"动作行走的也是这条)\n if (arg === 'test') {\n await runProbe(ctx)\n const p = lastProbe\n if (p?.status === 'ok') {\n ctx.tui.info.showToast(i18next.t('proxy.actionTest'), i18next.t('proxy.probeOk', { latency: p.latencyMs, target: p.target }))\n } else if (p?.status === 'failed') {\n ctx.tui.info.showToast(\n i18next.t('proxy.actionTest'),\n i18next.t(p.blamesProxy ? 'proxy.probeFailedProxy' : 'proxy.probeFailedTarget', { reason: p.reason }),\n )\n }\n return\n }\n\n if (arg === 'clear') {\n await settings.persist({ proxy: {} })\n pushProxyView(ctx)\n ctx.tui.info.showToast('代理已清除', '')\n return\n }\n\n if (PROXY_URL_RE.test(arg)) {\n await settings.persist({ proxy: { url: arg, enabled: true } })\n pushProxyView(ctx)\n ctx.tui.info.showToast('代理已设置', arg)\n return\n }\n\n // 不认识的参数:区分「看起来想设地址但格式不对」与「敲错了子命令」——前者给出具体的\n // 格式要求(面板 InputDialog 提交也走这条路径,是用户输错地址时唯一的反馈点),\n // 后者才回退到用法列表。此前两种情况都只弹泛泛的「用法」,用户不知道自己错在哪。\n if (arg.includes('://') || arg.includes('.') || arg.includes(':')) {\n ctx.tui.info.showToast('代理地址格式无效', `需以 http:// 或 https:// 开头,例如 http://127.0.0.1:7890(收到:${arg})`)\n return\n }\n ctx.tui.info.showToast('用法', '/proxy [<url>|on|off|status|clear]')\n}\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- import{i as e,r as t}from"./registry-BdCcwyGy.js";export{t as makeRegistryClient,e as searchRegistries};
@@ -1 +0,0 @@
1
- {"version":3,"file":"registry-BdCcwyGy.js","names":[],"sources":["../src/commands/handlers/registry.ts"],"sourcesContent":["/**\n * /registry 命令:插件注册源管理(add/remove/list + 检查)\n */\nimport { RegistryClient, builtinOfficialSource } from '@x-otto/coding'\nimport { OTTO_HOME, OTTO_USER_CONFIG_DIR } from '@x-otto/env'\nimport { rankCandidates } from '@x-otto/shared'\n\nimport type { App, LoadedRegistry, RegistryEntry } from '@x-otto/coding'\nimport type { CliContext } from '../../command-dispatcher'\nimport type { SelectOption } from '@x-otto/tui'\n\n/**\n * 官方源 URL(RFC-221 D1):registry.json 经独立发布仓库(`x-ottojs/otto-registry`)\n * 的 GitHub Pages 单独发布,仓库本身保持 private(路径 B,已 grill 确认)。\n * `OTTO_OFFICIAL_REGISTRY_URL` 环境变量覆盖机制保留(开发者本地测试/CI 调试用)。\n *\n * 发布机制(RFC-221 M1):CI workflow `.github/workflows/publish-registry.yml` 在\n * `docs/registry/registry.json` 变更推送到 main 后自动同步到 otto-registry 仓库。\n * 首次发布在 CI 验证完整链路前已手动推送完成(因 GitHub Actions 计费额度暂限制了\n * CI runner 执行,已直接 SSH push 验证内容可达,计费恢复后 CI 自动化同步即正常)。\n */\nfunction officialRegistryUrl(_app: Pick<App, 'workspaceDir'>): string {\n return process.env['OTTO_OFFICIAL_REGISTRY_URL'] ??\n 'https://x-ottojs.github.io/otto-registry/registry.json'\n}\n\n/**\n * `App` 版工厂(导出复用于 `/registry remove` 参数补全接线——interactive-setup.ts 只持有\n * `App`,不持有完整 `CliContext`,见 RFC-125 D3 同款模式)。\n */\nexport function makeRegistryClient(app: Pick<App, 'workspaceDir'>): RegistryClient {\n return new RegistryClient({\n configDir: OTTO_USER_CONFIG_DIR,\n ottoHome: OTTO_HOME,\n officialSource: builtinOfficialSource(officialRegistryUrl(app)),\n })\n}\n\nfunction makeClient(ctx: CliContext): RegistryClient {\n return makeRegistryClient(ctx.app)\n}\n\nexport async function handleRegistry(args: string[], ctx: CliContext): Promise<void> {\n const client = makeClient(ctx)\n const [sub, ...rest] = args.filter((a) => !a.startsWith('-'))\n\n try {\n if (sub === 'add') {\n const [name, url] = rest\n if (!name || !url) {\n ctx.tui.info.showToast('Registry', 'Usage: /registry add <name> <url>')\n return\n }\n client.addSource(name, url)\n ctx.tui.info.showToast('Registry', `已注册 '${name}' → ${url}`)\n return\n }\n if (sub === 'remove') {\n const [name] = rest\n if (!name) {\n ctx.tui.info.showToast('Registry', 'Usage: /registry remove <name>')\n return\n }\n client.removeSource(name)\n ctx.tui.info.showToast('Registry', `已移除 '${name}'`)\n return\n }\n // 缺省/list:升级为交互式选择面板(此前是纯文本 system_notification 推对话流,\n // 无上下选择/详情展开/启用禁用入口,无法快速浏览或操作多源)。\n // RFC-341 G2:用 listSourcesAsync 而非 listSources,与下方 loadAll 内部走的源列表\n // 一致(含内网可达时自动出现的 otto-official-internal)——否则面板选项与\n // entryCounts/staleFlags 会对不上号(loadAll 加载了内网源但列表没显示它)。\n const sources = await client.listSourcesAsync()\n\n // 异步加载各源的条目计数(用于面板内展示,fail-soft:加载失败不阻断面板打开)\n const { registries, warnings } = await client.loadAll()\n const entryCounts = new Map<string, number>()\n const staleFlags = new Map<string, boolean>()\n for (const reg of registries) {\n entryCounts.set(reg.sourceName, reg.entries.length)\n staleFlags.set(reg.sourceName, reg.stale)\n }\n\n const options: SelectOption[] = sources.map((s) => {\n const count = entryCounts.get(s.name)\n const countStr = count !== undefined ? `${count} entries` : ''\n const stale = staleFlags.get(s.name) ? ' (stale cache)' : ''\n const status = s.disabled ? '✗ disabled' : '✓ enabled'\n const builtin = s.builtin ? ' · builtin' : ''\n return {\n label: s.name,\n value: s.name,\n detail: `${status}${builtin}${countStr ? ` · ${countStr}` : ''}${stale}\\n${s.url}`,\n disabled: false, // disabled 源仍可选中查看详情/重新启用\n }\n })\n\n const enabledCount = sources.filter((s) => !s.disabled).length\n ctx.tui.modal.openModal({\n kind: 'select',\n title: `Registry Sources (${enabledCount}/${sources.length} enabled)`,\n subtitle: warnings.length > 0 ? warnings.map((w) => `⚠ ${w}`).join('\\n') : undefined,\n options,\n onSelect: (value: string) => {\n const source = sources.find((s) => s.name === value)\n if (!source) return\n // 选中后显示详情 toast + 操作提示(启用/禁用、删除非内置源)\n const count = entryCounts.get(source.name)\n const countStr = count !== undefined ? `${count} entries · ` : ''\n const details = `${countStr}${source.disabled ? 'disabled' : 'enabled'}${source.builtin ? ' · builtin' : ''}\\n${source.url}`\n const actionHints: string[] = []\n if (source.builtin) {\n actionHints.push(source.disabled ? '/registry re-enable (coming soon)' : '/registry disable (coming soon)')\n } else {\n actionHints.push(`/registry remove ${source.name}`)\n }\n ctx.tui.info.showToast(source.name, `${details}\\n${actionHints.join(' · ')}`)\n },\n onCancel: () => {},\n })\n } catch (error) {\n ctx.tui.info.showToast('Registry', error instanceof Error ? error.message : String(error))\n }\n}\n\n/** search 数据面(/search 命令与 /extension 发现入口共用;RFC-201 M3)。 */\nexport async function searchRegistries(\n ctx: CliContext,\n keyword: string,\n): Promise<{ hits: Array<{ entry: RegistryEntry; sourceName: string; stale: boolean }>; warnings: string[] }> {\n const client = makeClient(ctx)\n const { registries, warnings } = await client.loadAll()\n const candidates = registries.flatMap((registry) =>\n registry.entries.map((entry) => ({ entry, registry })),\n )\n const ranked = rankCandidates(\n candidates.map((hit) => ({\n text: hit.entry.id,\n aliases: [hit.entry.description, ...(hit.entry.keywords ?? [])],\n payload: hit,\n })),\n keyword,\n { maxResults: 20 },\n )\n return {\n hits: ranked.map((r) => ({\n entry: r.item.payload.entry,\n sourceName: r.item.payload.registry.sourceName,\n stale: r.item.payload.registry.stale,\n })),\n warnings,\n }\n}\n\nexport async function handleSearch(args: string[], ctx: CliContext): Promise<void> {\n const keyword = args.filter((a) => !a.startsWith('-')).join(' ').trim()\n if (!keyword) {\n ctx.tui.info.showToast('Search', 'Usage: /search <keyword>')\n return\n }\n\n // RFC-227:框架层 async loading 门——将\"await 全流程 → openModal\"改为\n // \"openModal + async 字段:promise 内做 loadAll+rank,resolve 返回内容,\n // 框架层自动处理 loading→内容过渡\"。ranked 在 outer scope 声明供 onSelect\n // 闭包引用,async promise 内赋值(lesson_104:透传,非重新计算)。\n interface Hit { entry: RegistryEntry; registry: LoadedRegistry }\n let ranked: Array<{ item: { payload: Hit } }> | undefined\n\n ctx.tui.modal.openModal({\n kind: 'select',\n title: `Search '${keyword}'`,\n subtitle: '', // loading 态框架层填充,此处占位\n options: [] as never, // async 存在时 options 由框架层管理\n searchable: true, // 先占 searchable 模式,等结果数决定是否真的 searchable\n onSelect: (v: string) => {\n if (!ranked) return\n const hit = ranked[Number(v)]\n if (!hit) return\n const { entry } = hit.item.payload\n const installArgs = ['--as', entry.type, ...(entry.subPath ? ['--sub-path', entry.subPath] : []), entry.source]\n void import('./install').then(({ handleInstall }) => handleInstall(installArgs, ctx))\n },\n onCancel: () => {},\n async: (async () => {\n const client = makeClient(ctx)\n const { registries, warnings } = await client.loadAll()\n\n interface Hit { entry: RegistryEntry; registry: LoadedRegistry }\n const candidates: Hit[] = registries.flatMap((registry) =>\n registry.entries.map((entry) => ({ entry, registry })),\n )\n\n ranked = rankCandidates(\n candidates.map((hit) => ({\n text: hit.entry.id,\n aliases: [hit.entry.description, ...(hit.entry.keywords ?? [])],\n payload: hit,\n })),\n keyword,\n { maxResults: 20 },\n )\n\n if (ranked.length === 0) {\n const extra = warnings.length > 0 ? `\\n${warnings.map((w) => `⚠ ${w}`).join('\\n')}` : ''\n // 零命中结果——返回空选项列表,subtitle 告知用户\n return {\n title: `Search '${keyword}' — 0 hits`,\n subtitle: `No matches for '${keyword}'.${extra}`,\n options: [],\n }\n }\n\n const idCounts = new Map<string, number>()\n for (const r of ranked) {\n const id = r.item.payload.entry.id\n idCounts.set(id, (idCounts.get(id) ?? 0) + 1)\n }\n\n const subtitleWarnings = warnings.length > 0\n ? `\\n${warnings.map((w) => `⚠ ${w}`).join('\\n')}`\n : ''\n\n return {\n title: `Search '${keyword}' — ${ranked.length} hits`,\n subtitle: `Enter install (preview first) · Esc cancel${subtitleWarnings}`,\n options: ranked.map((r, i) => {\n const { entry, registry } = r.item.payload\n const caps = entry.capabilities?.length ? `[${entry.capabilities.join(', ')}]` : ''\n const stale = registry.stale ? ' (stale cache)' : ''\n const dupMark = (idCounts.get(entry.id) ?? 0) > 1 ? ` @${registry.sourceName}` : ''\n return {\n label: `${entry.id}${dupMark} (${entry.type}) ${caps}${stale}`,\n value: String(i),\n detail: entry.description,\n }\n }),\n }\n })(),\n })\n}\n"],"mappings":"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"}
@@ -1,21 +0,0 @@
1
- #!/usr/bin/env node
2
- import{t as e}from"./create-host-app-BXQ8bU-B.js";import{t}from"./daemon-base-Y1XI61JY.js";import{SERVICE_PORT as n}from"@x-otto/env";import{resolve as r}from"node:path";import{createCodingService as i}from"@x-otto/service";async function a(n,r={}){let a=r.stdout??(e=>process.stdout.write(e)),c=r.stderr??(e=>process.stderr.write(e));if(n[0]===`help`||n[0]===`--help`)return s(a),0;let l,u,d,f;try{({port:l,inspect:u,staticDir:d,token:f}=o(n))}catch(e){return c(`otto serve: ${e instanceof Error?e.message:String(e)}\n`),1}let p=r.app??e({logger:{name:`otto-serve`,level:r.verbose?`debug`:`info`,destination:`stderr`},inspect:u,workspaceDir:r.projectDir,projectConfigPath:r.configPath,modelId:r.modelId});try{await p.start()}catch(e){return c(`otto serve: failed to start app: ${e instanceof Error?e.message:String(e)}\n`),1}let m=i(p,{port:l,host:`127.0.0.1`,staticDir:d,apiToken:f,wsAuthToken:f,authorityId:process.env.OTTO_AUTHORITY_ID||void 0});try{await m.start()}catch(e){return c(`otto serve: failed to listen on port ${l}: ${e instanceof Error?e.message:String(e)}\n`),await p.stop(),1}let h=m.httpServer.address().port;a(`otto service listening on http://127.0.0.1:${h}${f?` (auth: token required)`:``}
3
- ${d?` web: http://127.0.0.1:${h}/${f?` (打开时附加 ?token=<你的 token>)`:``} (静态页面同源托管 ${d})\n`:``} panels: otto observe summary <session-id>
4
- otto time-travel checkpoints <session-id>
5
- otto debug breakpoints list${u?``:" (debugger disabled — restart with `otto serve --inspect`)"}
6
- stop: Ctrl-C
7
- `),await t(r.signal),a(`otto serve: shutting down...
8
- `),await m.stop(),await p.stop(),process.exit(0)}function o(e){let t=n,i=!1,a,o,s=(t,n)=>{let r=e[t+1];if(r===void 0||r.startsWith(`--`))throw Error(`${n} requires a value`);return r};for(let n=0;n<e.length;n++){let c=e[n];if(c===`--port`||c===`-p`){let e=s(n,`--port`);n++;let r=Number(e);if(!Number.isInteger(r)||r<0||r>65535)throw Error(`invalid --port value: ${e}`);t=r}else c===`--inspect`?i=!0:c===`--static`?(a=r(s(n,`--static`)),n++):c===`--token`&&(o=s(n,`--token`),n++)}return{port:t,inspect:i,staticDir:a,token:o}}function s(e){e(`otto serve — 启动 HTTP/WS service(观测/调试/时间旅行面板的对端)
9
-
10
- 用法:
11
- otto serve [--port <n>] [--inspect] [--static <dir>] [--token <t>]
12
-
13
- 选项:
14
- --port, -p <n> 监听端口(默认 ${n},可经 OTTO_SERVICE_PORT 覆盖;0 = 随机)
15
- --inspect 启用调试器(断点/暂停面板需要)
16
- --static <dir> 同源托管一个已构建的前端目录(SPA),页面与 /api、/ws 同端口免 CORS
17
- --token <t> 启用鉴权:HTTP /api/* 需 Bearer,WS 需 ?token=;页面用 URL ?token=<t> 对上
18
-
19
- 面板命令默认连 http://127.0.0.1:${n}(可 --remote 覆盖)
20
- `)}export{a as runServeCommand};
21
- //# sourceMappingURL=serve-Ke0nwN9Y.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"serve-Ke0nwN9Y.js","names":[],"sources":["../src/commands/serve.ts"],"sourcesContent":["import type { AddressInfo } from 'node:net'\nimport { resolve } from 'node:path'\nimport { SERVICE_PORT } from '@x-otto/env'\nimport { createHostApp } from '../create-host-app'\nimport { createCodingService } from '@x-otto/service'\nimport type { AppContext } from '@x-otto/coding'\n\nimport { waitForShutdown } from './daemon-base'\n\nexport interface ServeDeps {\n stdout?: (s: string) => void\n stderr?: (s: string) => void\n /** 全局 CLI 选项透传(与 `otto run` 同源装配)。 */\n projectDir?: string\n configPath?: string\n modelId?: string\n verbose?: boolean\n /** 测试注入:替代生产装配的 app(结构 = AppContext + start/stop)。 */\n app?: AppContext & { start(): Promise<void>; stop(): Promise<void> }\n /** 测试注入:优雅退出信号(生产 = SIGINT/SIGTERM)。 */\n signal?: AbortSignal\n}\n\nexport async function runServeCommand(args: string[], deps: ServeDeps = {}): 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 printHelp(stdout)\n return 0\n }\n\n let port: number\n let inspect: boolean\n let staticDir: string | undefined\n let token: string | undefined\n try {\n ;({ port, inspect, staticDir, token } = parseServeArgs(args))\n } catch (error) {\n stderr(`otto serve: ${error instanceof Error ? error.message : String(error)}\\n`)\n return 1\n }\n\n const app =\n deps.app ??\n createHostApp({\n logger: {\n name: 'otto-serve',\n level: deps.verbose ? 'debug' : 'info',\n destination: 'stderr',\n },\n // `--inspect` 必须下传:`AppOptions.inspect` 是 `buildInspectDevtools` 的唯一开关\n // (devtools-setup.ts:19 `if (!inspect) return { devtools: null }`)。此前 serve 解析了\n // 该 flag、也据它打印\"调试器已启用\"提示,却从未传给 createApp —— 于是 `app.devtools`\n // 恒为 null,`/api/debugger/*` 全部 503、DebugBridge 也不会被构造。即 `otto serve\n // --inspect` 实际从不启用调试器(提示与事实相反)。\n inspect,\n workspaceDir: deps.projectDir,\n projectConfigPath: deps.configPath,\n modelId: deps.modelId,\n })\n\n try {\n await app.start()\n } catch (error) {\n stderr(`otto serve: failed to start app: ${error instanceof Error ? error.message : String(error)}\\n`)\n return 1\n }\n\n // --token 同时启用 HTTP Bearer(apiToken)与 WS ?token=(wsAuthToken);页面用 URL `?token=` 对上。\n const service = createCodingService(app, {\n port,\n host: '127.0.0.1',\n staticDir,\n apiToken: token,\n wsAuthToken: token,\n // RFC-335 M2:由 `otto daemon start` 下传,使 AuthorityDescriptor.instanceId 与\n // daemon state 记录一致(客户端据此确认\"连上的 authority 就是 state 里那个 daemon\")。\n // 前台直接 `otto serve` 时缺省,由 service 自行生成。\n authorityId: process.env['OTTO_AUTHORITY_ID'] || undefined,\n })\n\n try {\n await service.start()\n } catch (error) {\n stderr(`otto serve: failed to listen on port ${port}: ${error instanceof Error ? error.message : String(error)}\\n`)\n await app.stop()\n return 1\n }\n\n const actualPort = (service.httpServer.address() as AddressInfo).port\n // 不把 token 明文打进 stdout(会落 scrollback/CI 日志)——只提示需附加 ?token=。\n const webHint = token ? ' (打开时附加 ?token=<你的 token>)' : ''\n stdout(`otto service listening on http://127.0.0.1:${actualPort}${token ? ' (auth: token required)' : ''}\n${staticDir ? ` web: http://127.0.0.1:${actualPort}/${webHint} (静态页面同源托管 ${staticDir})\\n` : ''} panels: otto observe summary <session-id>\n otto time-travel checkpoints <session-id>\n otto debug breakpoints list${inspect ? '' : ' (debugger disabled — restart with `otto serve --inspect`)'}\n stop: Ctrl-C\n`)\n\n await waitForShutdown(deps.signal)\n\n stdout('otto serve: shutting down...\\n')\n await service.stop()\n await app.stop()\n // 硬退出:service.stop()/app.stop() 可能留下孤儿 handle(worker_threads port、\n // DB pool、trace tail),吊住事件循环导致 Ctrl+C 后进程不退。与 interactive 路径\n // (cli.ts 末尾 process.exit)对齐。stdio 已同步写,无截断风险。\n process.exit(0)\n}\n\nfunction parseServeArgs(args: string[]): {\n port: number\n inspect: boolean\n staticDir?: string\n token?: string\n} {\n let port = SERVICE_PORT\n let inspect = false\n let staticDir: string | undefined\n let token: string | undefined\n\n // 取下一个值;缺失或形如 --flag(误把下个选项当值)即报错,避免静默吞掉选项。\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 === '--port' || arg === '-p') {\n const raw = takeValue(i, '--port')\n i++\n const n = Number(raw)\n if (!Number.isInteger(n) || n < 0 || n > 65535) {\n throw new Error(`invalid --port value: ${raw}`)\n }\n port = n\n } else if (arg === '--inspect') {\n inspect = true\n } else if (arg === '--static') {\n staticDir = resolve(takeValue(i, '--static'))\n i++\n } else if (arg === '--token') {\n token = takeValue(i, '--token')\n i++\n }\n // 其它(含全局 flag --verbose/--model 等,经 ctx.args 透传进来)忽略——\n // 不能 throw,否则 `otto serve --verbose` 会误伤(全局 flag 由 parseCLI 已解析)。\n }\n\n return { port, inspect, staticDir, token }\n}\n\nfunction printHelp(stdout: (s: string) => void): void {\n stdout(`otto serve — 启动 HTTP/WS service(观测/调试/时间旅行面板的对端)\n\n用法:\n otto serve [--port <n>] [--inspect] [--static <dir>] [--token <t>]\n\n选项:\n --port, -p <n> 监听端口(默认 ${SERVICE_PORT},可经 OTTO_SERVICE_PORT 覆盖;0 = 随机)\n --inspect 启用调试器(断点/暂停面板需要)\n --static <dir> 同源托管一个已构建的前端目录(SPA),页面与 /api、/ws 同端口免 CORS\n --token <t> 启用鉴权:HTTP /api/* 需 Bearer,WS 需 ?token=;页面用 URL ?token=<t> 对上\n\n面板命令默认连 http://127.0.0.1:${SERVICE_PORT}(可 --remote 覆盖)\n`)\n}\n"],"mappings":";gOAuBA,eAAsB,EAAgB,EAAgB,EAAkB,EAAE,CAAmB,CAC3F,IAAM,EAAS,EAAK,SAAY,GAAM,QAAQ,OAAO,MAAM,EAAE,EACvD,EAAS,EAAK,SAAY,GAAM,QAAQ,OAAO,MAAM,EAAE,EAE7D,GAAI,EAAK,KAAO,QAAU,EAAK,KAAO,SAEpC,OADA,EAAU,EAAO,CACV,EAGT,IAAI,EACA,EACA,EACA,EACJ,GAAI,EACA,uCAAsC,EAAe,EAAK,QACrD,EAAO,CAEd,OADA,EAAO,eAAe,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CAAC,IAAI,CAC1E,EAGT,IAAM,EACJ,EAAK,KACL,EAAc,CACZ,OAAQ,CACN,KAAM,aACN,MAAO,EAAK,QAAU,QAAU,OAChC,YAAa,SACd,CAMD,UACA,aAAc,EAAK,WACnB,kBAAmB,EAAK,WACxB,QAAS,EAAK,QACf,CAAC,CAEJ,GAAI,CACF,MAAM,EAAI,OAAO,OACV,EAAO,CAEd,OADA,EAAO,oCAAoC,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CAAC,IAAI,CAC/F,EAIT,IAAM,EAAU,EAAoB,EAAK,CACvC,OACA,KAAM,YACN,YACA,SAAU,EACV,YAAa,EAIb,YAAa,QAAQ,IAAI,mBAAwB,IAAA,GAClD,CAAC,CAEF,GAAI,CACF,MAAM,EAAQ,OAAO,OACd,EAAO,CAGd,OAFA,EAAO,wCAAwC,EAAK,IAAI,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CAAC,IAAI,CACnH,MAAM,EAAI,MAAM,CACT,EAGT,IAAM,EAAc,EAAQ,WAAW,SAAS,CAAiB,KAGjE,EAAO,8CAA8C,IAAa,EAAQ,4BAA8B,GAAG;EAC3G,EAAY,+BAA+B,EAAW,GAFtC,EAAQ,6BAA+B,GAEU,eAAe,EAAU,KAAO,GAAG;;wCAE9D,EAAU,GAAK,+DAA+D;;EAEpH,CAEA,MAAM,EAAgB,EAAK,OAAO,CAElC,EAAO;EAAiC,CACxC,MAAM,EAAQ,MAAM,CACpB,MAAM,EAAI,MAAM,CAIhB,QAAQ,KAAK,EAAE,CAGjB,SAAS,EAAe,EAKtB,CACA,IAAI,EAAO,EACP,EAAU,GACV,EACA,EAGE,GAAa,EAAW,IAAyB,CACrD,IAAM,EAAM,EAAK,EAAI,GACrB,GAAI,IAAQ,IAAA,IAAa,EAAI,WAAW,KAAK,CAC3C,MAAU,MAAM,GAAG,EAAK,mBAAmB,CAE7C,OAAO,GAGT,IAAK,IAAI,EAAI,EAAG,EAAI,EAAK,OAAQ,IAAK,CACpC,IAAM,EAAM,EAAK,GACjB,GAAI,IAAQ,UAAY,IAAQ,KAAM,CACpC,IAAM,EAAM,EAAU,EAAG,SAAS,CAClC,IACA,IAAM,EAAI,OAAO,EAAI,CACrB,GAAI,CAAC,OAAO,UAAU,EAAE,EAAI,EAAI,GAAK,EAAI,MACvC,MAAU,MAAM,yBAAyB,IAAM,CAEjD,EAAO,OACE,IAAQ,YACjB,EAAU,GACD,IAAQ,YACjB,EAAY,EAAQ,EAAU,EAAG,WAAW,CAAC,CAC7C,KACS,IAAQ,YACjB,EAAQ,EAAU,EAAG,UAAU,CAC/B,KAMJ,MAAO,CAAE,OAAM,UAAS,YAAW,QAAO,CAG5C,SAAS,EAAU,EAAmC,CACpD,EAAO;;;;;;6BAMoB,EAAa;;;;;2BAKf,EAAa;EACtC"}