@x-otto/cli 0.0.1-alpha.43 → 0.0.1-alpha.45

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 (60) hide show
  1. package/dist/build-id-C9rdzI9o.js +3 -0
  2. package/dist/build-id-C9rdzI9o.js.map +1 -0
  3. package/dist/bundled-extensions/plugin-otto-wire-protocols/plugin-dist/meta.json +1 -1
  4. package/dist/bundled-extensions/plugin-otto-wire-protocols/plugin-dist/plugin.cjs +2 -1
  5. package/dist/bundled-extensions/plugin-otto-wire-protocols/src/openai-responses.ts +5 -1
  6. package/dist/{command-dispatcher-DhcLUPR5.js → command-dispatcher-DaehlD7G.js} +4 -4
  7. package/dist/{command-dispatcher-DhcLUPR5.js.map → command-dispatcher-DaehlD7G.js.map} +1 -1
  8. package/dist/{daemon-base-Bc9p51Um.js → daemon-base-Y1XI61JY.js} +1 -1
  9. package/dist/{daemon-base-Bc9p51Um.js.map → daemon-base-Y1XI61JY.js.map} +1 -1
  10. package/dist/{doctor-lT2QhG2q.js → doctor-DXBLkbkP.js} +9 -9
  11. package/dist/doctor-DXBLkbkP.js.map +1 -0
  12. package/dist/{doctor-attach-_zeIJewx.js → doctor-attach-DEMuyBOk.js} +2 -2
  13. package/dist/{doctor-attach-_zeIJewx.js.map → doctor-attach-DEMuyBOk.js.map} +1 -1
  14. package/dist/{doctor-cdp-K_mIFRfA.js → doctor-cdp-CnJUVNOB.js} +1 -1
  15. package/dist/{doctor-cdp-K_mIFRfA.js.map → doctor-cdp-CnJUVNOB.js.map} +1 -1
  16. package/dist/{doctor-heap-Rr2nNvcw.js → doctor-heap-Eu3LZj-F.js} +2 -2
  17. package/dist/{doctor-heap-Rr2nNvcw.js.map → doctor-heap-Eu3LZj-F.js.map} +1 -1
  18. package/dist/{doctor-ps-OL3FGQYu.js → doctor-ps-JBgCH77n.js} +2 -2
  19. package/dist/{doctor-ps-OL3FGQYu.js.map → doctor-ps-JBgCH77n.js.map} +1 -1
  20. package/dist/export-trajectory-D-BcH0Wt.js +14 -0
  21. package/dist/export-trajectory-D-BcH0Wt.js.map +1 -0
  22. package/dist/{feedback-BQ_oz5nh.js → feedback-YENHh-7m.js} +2 -2
  23. package/dist/{feedback-BQ_oz5nh.js.map → feedback-YENHh-7m.js.map} +1 -1
  24. package/dist/feedback-xnIYoLVB.js +2 -0
  25. package/dist/{feishu-setup-B6ETf-8Y.js → feishu-setup-5ZoxtH0J.js} +1 -1
  26. package/dist/{feishu-setup-B6ETf-8Y.js.map → feishu-setup-5ZoxtH0J.js.map} +1 -1
  27. package/dist/{ftue-doctor-recovery-DeAf8jVo.js → ftue-doctor-recovery-8FeDBFOc.js} +1 -1
  28. package/dist/{ftue-doctor-recovery-DeAf8jVo.js.map → ftue-doctor-recovery-8FeDBFOc.js.map} +1 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +7 -7
  31. package/dist/index.js.map +1 -1
  32. package/dist/{mcp-9P_AmnDk.js → mcp-Cm4-OhXI.js} +1 -1
  33. package/dist/{mcp-9P_AmnDk.js.map → mcp-Cm4-OhXI.js.map} +1 -1
  34. package/dist/{mcp-server-BK4L-YBN.js → mcp-server-Dpza3yn2.js} +1 -1
  35. package/dist/{mcp-server-BK4L-YBN.js.map → mcp-server-Dpza3yn2.js.map} +1 -1
  36. package/dist/{migrate-DDNkik_0.js → migrate-RlBJqJnB.js} +1 -1
  37. package/dist/{migrate-DDNkik_0.js.map → migrate-RlBJqJnB.js.map} +1 -1
  38. package/dist/{observe-CzIv09hO.js → observe-Dxfud-t7.js} +1 -1
  39. package/dist/{observe-CzIv09hO.js.map → observe-Dxfud-t7.js.map} +1 -1
  40. package/dist/parse-cli-Cbc7Df1n.js +3 -0
  41. package/dist/parse-cli-Cbc7Df1n.js.map +1 -0
  42. package/dist/{persistence-DOaizHMA.js → persistence-CRFhOYx6.js} +2 -2
  43. package/dist/{persistence-DOaizHMA.js.map → persistence-CRFhOYx6.js.map} +1 -1
  44. package/dist/{plugin-assembly-check-Ov17ra7G.js → plugin-assembly-check-DEozmcJ7.js} +1 -1
  45. package/dist/{plugin-assembly-check-Ov17ra7G.js.map → plugin-assembly-check-DEozmcJ7.js.map} +1 -1
  46. package/dist/{ps-BovE9XMQ.js → ps-DiKCLKbs.js} +1 -1
  47. package/dist/{ps-BovE9XMQ.js.map → ps-DiKCLKbs.js.map} +1 -1
  48. package/dist/{run-attach-z7YECztf.js → run-attach-DDpMk2_N.js} +1 -1
  49. package/dist/{run-attach-z7YECztf.js.map → run-attach-DDpMk2_N.js.map} +1 -1
  50. package/dist/{serve-DaxogAZ5.js → serve-Ke0nwN9Y.js} +2 -2
  51. package/dist/{serve-DaxogAZ5.js.map → serve-Ke0nwN9Y.js.map} +1 -1
  52. package/dist/{version-Eh8zZKgk.js → version-DM0x7ndr.js} +2 -2
  53. package/dist/{version-Eh8zZKgk.js.map → version-DM0x7ndr.js.map} +1 -1
  54. package/package.json +12 -12
  55. package/dist/build-id-ClyMIKLy.js +0 -3
  56. package/dist/build-id-ClyMIKLy.js.map +0 -1
  57. package/dist/doctor-lT2QhG2q.js.map +0 -1
  58. package/dist/feedback-C2Hdxp1y.js +0 -2
  59. package/dist/parse-cli-3fWEIMWc.js +0 -3
  60. package/dist/parse-cli-3fWEIMWc.js.map +0 -1
@@ -9,4 +9,4 @@ import{OTTO_PROJECT_CONFIG_RELPATH as e}from"@x-otto/env";import{SettingsManager
9
9
  logout <name> Remove stored credentials
10
10
  list List authenticated MCP servers
11
11
  `),1)}export{s as runMcpCommand};
12
- //# sourceMappingURL=mcp-9P_AmnDk.js.map
12
+ //# sourceMappingURL=mcp-Cm4-OhXI.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-9P_AmnDk.js","names":[],"sources":["../src/commands/mcp.ts"],"sourcesContent":["import { OTTO_PROJECT_CONFIG_RELPATH } from '@x-otto/env'\nimport { createFileTokenStore, login } from '@x-otto/mcp'\nimport { SettingsManager } from '@x-otto/setting'\n\nimport type { McpServerConfig } from '@x-otto/mcp'\n\nexport interface McpDeps {\n stderr?: (s: string) => void\n}\n\nexport async function runMcpList(): Promise<void> {\n const store = createFileTokenStore()\n const servers = await store.list()\n\n if (servers.length === 0) {\n process.stdout.write('No authenticated MCP servers.\\n')\n return\n }\n\n process.stdout.write('Authenticated MCP servers:\\n')\n for (const name of servers) {\n const creds = await store.load(name)\n const status = creds?.expiresAt\n ? creds.expiresAt > Date.now()\n ? 'valid'\n : 'expired'\n : 'no expiry'\n process.stdout.write(` ${name} (${status})\\n`)\n }\n}\n\nexport async function runMcpLogout(serverName: string): Promise<void> {\n const store = createFileTokenStore()\n await store.delete(serverName)\n process.stdout.write(`Logged out of '${serverName}'.\\n`)\n}\n\nexport async function runMcpLogin(serverName: string): Promise<void> {\n const settings = new SettingsManager()\n await settings.load()\n const servers = settings.get('mcp_servers') as Record<string, McpServerConfig> | undefined\n\n if (!servers?.[serverName]) {\n process.stderr.write(\n `MCP server '${serverName}' not found in settings. Add it to ${OTTO_PROJECT_CONFIG_RELPATH}:\\n` +\n ` \"mcp_servers\": { \"${serverName}\": { \"type\": \"http\", \"url\": \"https://...\" } }\\n`,\n )\n process.exit(1)\n }\n\n const config = servers[serverName]\n if (config.type === 'stdio' || (!config.url && config.command)) {\n process.stderr.write(\n `MCP server '${serverName}' is a stdio server. OAuth login only works with HTTP-based MCP servers.\\n`,\n )\n process.exit(1)\n }\n if (!config.url) {\n process.stderr.write(`MCP server '${serverName}' has no URL configured.\\n`)\n process.exit(1)\n }\n\n process.stdout.write(`Logging into MCP server '${serverName}' (${config.url})...\\n`)\n process.stdout.write('A browser window will open for authentication.\\n')\n\n try {\n const result = await login({\n serverUrl: config.url,\n serverName,\n })\n\n process.stdout.write(`✓ Login successful.\\n`)\n if (result.registered) {\n process.stdout.write(' (Dynamic Client Registration completed)\\n')\n }\n } catch (err) {\n process.stderr.write(`Login failed: ${(err as Error).message}\\n`)\n process.exit(1)\n }\n}\n\n/**\n * `otto mcp <action> [name]` sub-command dispatcher.\n * Extracted from the inline if-chain in cli.ts.\n */\nexport async function runMcpCommand(args: string[], deps: McpDeps = {}): Promise<number> {\n const stderr = deps.stderr ?? ((s) => process.stderr.write(s))\n const action = args[0]\n const serverName = args[1]\n\n if (action === 'login' && serverName) {\n await runMcpLogin(serverName)\n return 0\n }\n if (action === 'logout' && serverName) {\n await runMcpLogout(serverName)\n return 0\n }\n if (action === 'list') {\n await runMcpList()\n return 0\n }\n\n stderr(\n 'Usage: otto mcp <login|logout|list> [server-name]\\n' +\n ' login <name> Authenticate with an MCP server (OAuth 2.1)\\n' +\n ' logout <name> Remove stored credentials\\n' +\n ' list List authenticated MCP servers\\n',\n )\n return 1\n}\n"],"mappings":";0KAUA,eAAsB,GAA4B,CAChD,IAAM,EAAQ,GAAsB,CAC9B,EAAU,MAAM,EAAM,MAAM,CAElC,GAAI,EAAQ,SAAW,EAAG,CACxB,QAAQ,OAAO,MAAM;EAAkC,CACvD,OAGF,QAAQ,OAAO,MAAM;EAA+B,CACpD,IAAK,IAAM,KAAQ,EAAS,CAC1B,IAAM,EAAQ,MAAM,EAAM,KAAK,EAAK,CAC9B,EAAS,GAAO,UAClB,EAAM,UAAY,KAAK,KAAK,CAC1B,QACA,UACF,YACJ,QAAQ,OAAO,MAAM,KAAK,EAAK,IAAI,EAAO,KAAK,EAInD,eAAsB,EAAa,EAAmC,CAEpE,MADc,GAAsB,CACxB,OAAO,EAAW,CAC9B,QAAQ,OAAO,MAAM,kBAAkB,EAAW,MAAM,CAG1D,eAAsB,EAAY,EAAmC,CACnE,IAAM,EAAW,IAAI,EACrB,MAAM,EAAS,MAAM,CACrB,IAAM,EAAU,EAAS,IAAI,cAAc,CAEtC,IAAU,KACb,QAAQ,OAAO,MACb,eAAe,EAAW,qCAAqC,EAA4B,yBAClE,EAAW,iDACrC,CACD,QAAQ,KAAK,EAAE,EAGjB,IAAM,EAAS,EAAQ,IACnB,EAAO,OAAS,SAAY,CAAC,EAAO,KAAO,EAAO,WACpD,QAAQ,OAAO,MACb,eAAe,EAAW,4EAC3B,CACD,QAAQ,KAAK,EAAE,EAEZ,EAAO,MACV,QAAQ,OAAO,MAAM,eAAe,EAAW,4BAA4B,CAC3E,QAAQ,KAAK,EAAE,EAGjB,QAAQ,OAAO,MAAM,4BAA4B,EAAW,KAAK,EAAO,IAAI,QAAQ,CACpF,QAAQ,OAAO,MAAM;EAAmD,CAExE,GAAI,CACF,IAAM,EAAS,MAAM,EAAM,CACzB,UAAW,EAAO,IAClB,aACD,CAAC,CAEF,QAAQ,OAAO,MAAM;EAAwB,CACzC,EAAO,YACT,QAAQ,OAAO,MAAM;EAA8C,OAE9D,EAAK,CACZ,QAAQ,OAAO,MAAM,iBAAkB,EAAc,QAAQ,IAAI,CACjE,QAAQ,KAAK,EAAE,EAQnB,eAAsB,EAAc,EAAgB,EAAgB,EAAE,CAAmB,CACvF,IAAM,EAAS,EAAK,SAAY,GAAM,QAAQ,OAAO,MAAM,EAAE,EACvD,EAAS,EAAK,GACd,EAAa,EAAK,GAqBxB,OAnBI,IAAW,SAAW,GACxB,MAAM,EAAY,EAAW,CACtB,GAEL,IAAW,UAAY,GACzB,MAAM,EAAa,EAAW,CACvB,GAEL,IAAW,QACb,MAAM,GAAY,CACX,IAGT,EACE;;;;EAID,CACM"}
1
+ {"version":3,"file":"mcp-Cm4-OhXI.js","names":[],"sources":["../src/commands/mcp.ts"],"sourcesContent":["import { OTTO_PROJECT_CONFIG_RELPATH } from '@x-otto/env'\nimport { createFileTokenStore, login } from '@x-otto/mcp'\nimport { SettingsManager } from '@x-otto/setting'\n\nimport type { McpServerConfig } from '@x-otto/mcp'\n\nexport interface McpDeps {\n stderr?: (s: string) => void\n}\n\nexport async function runMcpList(): Promise<void> {\n const store = createFileTokenStore()\n const servers = await store.list()\n\n if (servers.length === 0) {\n process.stdout.write('No authenticated MCP servers.\\n')\n return\n }\n\n process.stdout.write('Authenticated MCP servers:\\n')\n for (const name of servers) {\n const creds = await store.load(name)\n const status = creds?.expiresAt\n ? creds.expiresAt > Date.now()\n ? 'valid'\n : 'expired'\n : 'no expiry'\n process.stdout.write(` ${name} (${status})\\n`)\n }\n}\n\nexport async function runMcpLogout(serverName: string): Promise<void> {\n const store = createFileTokenStore()\n await store.delete(serverName)\n process.stdout.write(`Logged out of '${serverName}'.\\n`)\n}\n\nexport async function runMcpLogin(serverName: string): Promise<void> {\n const settings = new SettingsManager()\n await settings.load()\n const servers = settings.get('mcp_servers') as Record<string, McpServerConfig> | undefined\n\n if (!servers?.[serverName]) {\n process.stderr.write(\n `MCP server '${serverName}' not found in settings. Add it to ${OTTO_PROJECT_CONFIG_RELPATH}:\\n` +\n ` \"mcp_servers\": { \"${serverName}\": { \"type\": \"http\", \"url\": \"https://...\" } }\\n`,\n )\n process.exit(1)\n }\n\n const config = servers[serverName]\n if (config.type === 'stdio' || (!config.url && config.command)) {\n process.stderr.write(\n `MCP server '${serverName}' is a stdio server. OAuth login only works with HTTP-based MCP servers.\\n`,\n )\n process.exit(1)\n }\n if (!config.url) {\n process.stderr.write(`MCP server '${serverName}' has no URL configured.\\n`)\n process.exit(1)\n }\n\n process.stdout.write(`Logging into MCP server '${serverName}' (${config.url})...\\n`)\n process.stdout.write('A browser window will open for authentication.\\n')\n\n try {\n const result = await login({\n serverUrl: config.url,\n serverName,\n })\n\n process.stdout.write(`✓ Login successful.\\n`)\n if (result.registered) {\n process.stdout.write(' (Dynamic Client Registration completed)\\n')\n }\n } catch (err) {\n process.stderr.write(`Login failed: ${(err as Error).message}\\n`)\n process.exit(1)\n }\n}\n\n/**\n * `otto mcp <action> [name]` sub-command dispatcher.\n * Extracted from the inline if-chain in cli.ts.\n */\nexport async function runMcpCommand(args: string[], deps: McpDeps = {}): Promise<number> {\n const stderr = deps.stderr ?? ((s) => process.stderr.write(s))\n const action = args[0]\n const serverName = args[1]\n\n if (action === 'login' && serverName) {\n await runMcpLogin(serverName)\n return 0\n }\n if (action === 'logout' && serverName) {\n await runMcpLogout(serverName)\n return 0\n }\n if (action === 'list') {\n await runMcpList()\n return 0\n }\n\n stderr(\n 'Usage: otto mcp <login|logout|list> [server-name]\\n' +\n ' login <name> Authenticate with an MCP server (OAuth 2.1)\\n' +\n ' logout <name> Remove stored credentials\\n' +\n ' list List authenticated MCP servers\\n',\n )\n return 1\n}\n"],"mappings":";0KAUA,eAAsB,GAA4B,CAChD,IAAM,EAAQ,GAAsB,CAC9B,EAAU,MAAM,EAAM,MAAM,CAElC,GAAI,EAAQ,SAAW,EAAG,CACxB,QAAQ,OAAO,MAAM;EAAkC,CACvD,OAGF,QAAQ,OAAO,MAAM;EAA+B,CACpD,IAAK,IAAM,KAAQ,EAAS,CAC1B,IAAM,EAAQ,MAAM,EAAM,KAAK,EAAK,CAC9B,EAAS,GAAO,UAClB,EAAM,UAAY,KAAK,KAAK,CAC1B,QACA,UACF,YACJ,QAAQ,OAAO,MAAM,KAAK,EAAK,IAAI,EAAO,KAAK,EAInD,eAAsB,EAAa,EAAmC,CAEpE,MADc,GAAsB,CACxB,OAAO,EAAW,CAC9B,QAAQ,OAAO,MAAM,kBAAkB,EAAW,MAAM,CAG1D,eAAsB,EAAY,EAAmC,CACnE,IAAM,EAAW,IAAI,EACrB,MAAM,EAAS,MAAM,CACrB,IAAM,EAAU,EAAS,IAAI,cAAc,CAEtC,IAAU,KACb,QAAQ,OAAO,MACb,eAAe,EAAW,qCAAqC,EAA4B,yBAClE,EAAW,iDACrC,CACD,QAAQ,KAAK,EAAE,EAGjB,IAAM,EAAS,EAAQ,IACnB,EAAO,OAAS,SAAY,CAAC,EAAO,KAAO,EAAO,WACpD,QAAQ,OAAO,MACb,eAAe,EAAW,4EAC3B,CACD,QAAQ,KAAK,EAAE,EAEZ,EAAO,MACV,QAAQ,OAAO,MAAM,eAAe,EAAW,4BAA4B,CAC3E,QAAQ,KAAK,EAAE,EAGjB,QAAQ,OAAO,MAAM,4BAA4B,EAAW,KAAK,EAAO,IAAI,QAAQ,CACpF,QAAQ,OAAO,MAAM;EAAmD,CAExE,GAAI,CACF,IAAM,EAAS,MAAM,EAAM,CACzB,UAAW,EAAO,IAClB,aACD,CAAC,CAEF,QAAQ,OAAO,MAAM;EAAwB,CACzC,EAAO,YACT,QAAQ,OAAO,MAAM;EAA8C,OAE9D,EAAK,CACZ,QAAQ,OAAO,MAAM,iBAAkB,EAAc,QAAQ,IAAI,CACjE,QAAQ,KAAK,EAAE,EAQnB,eAAsB,EAAc,EAAgB,EAAgB,EAAE,CAAmB,CACvF,IAAM,EAAS,EAAK,SAAY,GAAM,QAAQ,OAAO,MAAM,EAAE,EACvD,EAAS,EAAK,GACd,EAAa,EAAK,GAqBxB,OAnBI,IAAW,SAAW,GACxB,MAAM,EAAY,EAAW,CACtB,GAEL,IAAW,UAAY,GACzB,MAAM,EAAa,EAAW,CACvB,GAEL,IAAW,QACb,MAAM,GAAY,CACX,IAGT,EACE;;;;EAID,CACM"}
@@ -2,4 +2,4 @@
2
2
  import{randomUUID as e}from"node:crypto";const t=-32603;function n(e,t,n){return`${JSON.stringify({jsonrpc:`2.0`,id:e,error:{code:t,message:n}})}\n`}function r(e,t){return`${JSON.stringify({jsonrpc:`2.0`,id:e,result:t})}\n`}function i(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function a(e,a,o){return async s=>{let{id:c,method:l,params:u}=s;switch(l){case`initialize`:return r(c,{protocolVersion:`2024-11-05`,capabilities:{tools:{}},serverInfo:{name:`otto`,version:`0.0.1`}});case`notifications/initialized`:return``;case`tools/list`:return r(c,{tools:e.map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema}))});case`tools/call`:{let s=typeof u?.name==`string`?u.name:``;if(!(o&&o.length>0?o:e.map(e=>e.name)).includes(s))return n(c,-32602,`unknown tool: ${s}`);if(!a)return n(c,t,`tool "${s}" execution not wired`);let l=i(u?.arguments)?u.arguments:{},d=typeof u?.sessionId==`string`?u.sessionId:void 0;try{let e=await a(s,l,d);return r(c,{content:[{type:`text`,text:e.text}],isError:e.isError})}catch(e){return n(c,t,e instanceof Error?e.message:String(e))}}default:return n(c,-32601,`unknown method: ${l??``}`)}}}function o(e){let t=e.write??(e=>process.stdout.write(e)),r=a(e.tools??[],e.callTool,e.allToolNames),i=e=>{let i=e.trim();if(!i)return;let a;try{a=JSON.parse(i)}catch{Promise.resolve().then(()=>t(n(void 0,-32700,`parse error`)));return}a.method&&r(a).then(e=>{e&&t(e)})},o=e.readLine;if(o)o(i);else{let e=``;process.stdin.setEncoding(`utf8`),process.stdin.on(`data`,t=>{for(e+=t;;){let t=e.indexOf(`
3
3
  `);if(t===-1)break;let n=e.slice(0,t);e=e.slice(t+1),i(n)}}),process.stdin.resume()}let s=e.signal;if(s)s.addEventListener(`abort`,()=>{e.onShutdown?e.onShutdown().catch(()=>void 0).finally(()=>process.exit(0)):Promise.resolve().then(()=>process.exit(0))});else{let t=()=>{e.onShutdown?e.onShutdown().catch(()=>void 0).finally(()=>process.exit(0)):Promise.resolve().then(()=>process.exit(0))};process.on(`SIGTERM`,t),process.on(`SIGINT`,t)}return{handle:i}}async function s(e,t={}){return o(t),0}function c(t,n){return{tools:t.filter(e=>e.readonly===!0&&e.exposure!==`hidden`&&e.exposure!==`deferred`).map(e=>({name:e.name,description:typeof e.description==`function`?e.name:e.description,inputSchema:(typeof e.parameters?.toJSONSchema==`function`?e.parameters.toJSONSchema():void 0)??{type:`object`}})),allToolNames:t.map(e=>e.name),callTool:async(r,i,a)=>{let o=t.find(e=>e.name===r),{createToolExecutor:s}=await import(`@x-otto/agent`);if(o&&o.readonly!==!0){if(!n)return{text:`Tool "${r}" is a write tool but no permission registry is wired; denied (fail-closed).`,isError:!0};let e=n.evaluate({timing:`tool.execute.before`,toolName:r,args:i,agentName:`mcp-server`,sessionId:a??`mcp-server`,metadata:{}});if(e.effect===`ask`)return{text:`Tool "${r}" requires interactive approval, which is not available over MCP (fail-closed). Run it in an interactive otto session instead.`,isError:!0};if(e.effect===`deny`)return{text:`Tool "${r}" denied by permission policy${e.reason?`: ${e.reason}`:``}.`,isError:!0}}let c=await s(t,{sessionId:a??`mcp-server`,agentName:`mcp-server`}).execute({type:`tool_call`,id:e(),name:r,arguments:i},new AbortController().signal);return{text:c.content.map(e=>e.type===`text`?e.text:`[${e.type}]`).join(`
4
4
  `)||`(no output)`,isError:c.isError===!0}}}}export{c as buildMcpServerDepsFromApp,s as runMcpServerCommand};
5
- //# sourceMappingURL=mcp-server-BK4L-YBN.js.map
5
+ //# sourceMappingURL=mcp-server-Dpza3yn2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-server-BK4L-YBN.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
+ {"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":";yCAaA,MAGM,EAAiB,OA+CvB,SAAS,EAAS,EAAiC,EAAc,EAAyB,CACxF,MAAO,GAAG,KAAK,UAAU,CAAE,QAAS,MAAO,KAAI,MAAO,CAAE,OAAM,UAAS,CAAE,CAAC,CAAC,IAG7E,SAAS,EAAU,EAAiC,EAAyB,CAC3E,MAAO,GAAG,KAAK,UAAU,CAAE,QAAS,MAAO,KAAI,SAAQ,CAAC,CAAC,IAG3D,SAAS,EAAS,EAA0C,CAC1D,OAAO,OAAO,GAAM,YAAY,GAAc,CAAC,MAAM,QAAQ,EAAE,CAIjE,SAAgB,EACd,EACA,EACA,EAC0C,CAC1C,OAAO,KAAO,IAAQ,CACpB,GAAM,CAAE,KAAI,SAAQ,UAAW,EAC/B,OAAQ,EAAR,CACE,IAAK,aACH,OAAO,EAAU,EAAI,CACnB,gBAAiB,aACjB,aAAc,CAAE,MAAO,EAAE,CAAE,CAC3B,WAAY,CAAE,KAAM,OAAQ,QAAS,QAAS,CAC/C,CAAC,CAEJ,IAAK,4BAEH,MAAO,GACT,IAAK,aACH,OAAO,EAAU,EAAI,CACnB,MAAO,EAAM,IAAK,IAAO,CACvB,KAAM,EAAE,KACR,YAAa,EAAE,YACf,YAAa,EAAE,YAChB,EAAE,CACJ,CAAC,CAEJ,IAAK,aAAc,CACjB,IAAM,EAAO,OAAO,GAAQ,MAAS,SAAW,EAAO,KAAO,GAG9D,GAAI,EADe,GAAgB,EAAa,OAAS,EAAI,EAAe,EAAM,IAAK,GAAM,EAAE,KAAK,EACpF,SAAS,EAAK,CAC5B,OAAO,EAAS,EAAI,OAAgB,iBAAiB,IAAO,CAE9D,GAAI,CAAC,EACH,OAAO,EAAS,EAAI,EAAgB,SAAS,EAAK,uBAAuB,CAE3E,IAAM,EAAO,EAAS,GAAQ,UAAU,CAAG,EAAQ,UAAY,EAAE,CAE3D,EAAY,OAAO,GAAQ,WAAc,SAAW,EAAO,UAAY,IAAA,GAC7E,GAAI,CACF,IAAM,EAAS,MAAM,EAAS,EAAM,EAAiC,EAAU,CAC/E,OAAO,EAAU,EAAI,CACnB,QAAS,CAAC,CAAE,KAAM,OAAQ,KAAM,EAAO,KAAM,CAAC,CAC9C,QAAS,EAAO,QACjB,CAAC,OACK,EAAK,CACZ,OAAO,EAAS,EAAI,EAAgB,aAAe,MAAQ,EAAI,QAAU,OAAO,EAAI,CAAC,EAGzF,QACE,OAAO,EAAS,EAAI,OAAkB,mBAAmB,GAAU,KAAK,GAMhF,SAAgB,EACd,EACoC,CACpC,IAAM,EAAQ,EAAK,QAAW,GAAM,QAAQ,OAAO,MAAM,EAAE,EACrD,EAAY,EAAkB,EAAK,OAAS,EAAE,CAAE,EAAK,SAAU,EAAK,aAAa,CAEjF,EAAU,GAAuB,CACrC,IAAM,EAAU,EAAK,MAAM,CAC3B,GAAI,CAAC,EAAS,OACd,IAAI,EACJ,GAAI,CACF,EAAM,KAAK,MAAM,EAAQ,MACnB,CAED,QAAQ,SAAS,CAAC,SAAW,EAAM,EAAS,IAAA,GAAW,OAAa,cAAc,CAAC,CAAC,CACzF,OAEG,EAAI,QACJ,EAAU,EAAI,CAAC,KAAM,GAAQ,CAC5B,GAAK,EAAM,EAAI,EACnB,EAGE,EAAW,EAAK,SACtB,GAAI,EACF,EAAS,EAAO,KACX,CACL,IAAI,EAAM,GACV,QAAQ,MAAM,YAAY,OAAO,CACjC,QAAQ,MAAM,GAAG,OAAS,GAAkB,CAE1C,IADA,GAAO,IACE,CACP,IAAM,EAAK,EAAI,QAAQ;EAAK,CAC5B,GAAI,IAAO,GAAI,MACf,IAAM,EAAO,EAAI,MAAM,EAAG,EAAG,CAC7B,EAAM,EAAI,MAAM,EAAK,EAAE,CACvB,EAAO,EAAK,GAEd,CACF,QAAQ,MAAM,QAAQ,CAGxB,IAAM,EAAS,EAAK,OACpB,GAAI,EACF,EAAO,iBAAiB,YAAe,CAC/B,EAAK,WACP,EAAK,YAAY,CAAC,UAAY,IAAA,GAAU,CAAC,YAAc,QAAQ,KAAK,EAAE,CAAC,CACvE,QAAQ,SAAS,CAAC,SAAW,QAAQ,KAAK,EAAE,CAAC,EACjD,KACG,CACL,IAAM,MAAmB,CACjB,EAAK,WACP,EAAK,YAAY,CAAC,UAAY,IAAA,GAAU,CAAC,YAAc,QAAQ,KAAK,EAAE,CAAC,CACvE,QAAQ,SAAS,CAAC,SAAW,QAAQ,KAAK,EAAE,CAAC,EAEnD,QAAQ,GAAG,UAAW,EAAK,CAC3B,QAAQ,GAAG,SAAU,EAAK,CAG5B,MAAO,CAAE,SAAQ,CAInB,eAAsB,EACpB,EACA,EAAsB,EAAE,CACP,CAEjB,OADA,EAAkB,EAAK,CAChB,EAkBT,SAAgB,EACd,EACA,EAUsE,CAmBtE,MAAO,CACL,MAnBoB,EAAS,OAC5B,GAAM,EAAE,WAAa,IAAQ,EAAE,WAAa,UAAY,EAAE,WAAa,WACzE,CAEgD,IAAK,IAAO,CAC3D,KAAM,EAAE,KACR,YAAa,OAAO,EAAE,aAAgB,WAAa,EAAE,KAAO,EAAE,YAI9D,aACG,OAAO,EAAE,YAAY,cAAiB,WAClC,EAAE,WAAW,cAAc,CAC5B,IAAA,KAAc,CAAE,KAAM,SAAU,CACvC,EAAE,CAMD,aAJmB,EAAS,IAAK,GAAM,EAAE,KAAK,CAK9C,SAAU,MAAO,EAAM,EAAM,IAAc,CACzC,IAAM,EAAO,EAAS,KAAM,GAAM,EAAE,OAAS,EAAK,CAC5C,CAAE,sBAAuB,MAAM,OAAO,iBAG5C,GAAI,GAAQ,EAAK,WAAa,GAAM,CAClC,GAAI,CAAC,EACH,MAAO,CACL,KAAM,SAAS,EAAK,8EAEpB,QAAS,GACV,CAEH,IAAM,EAAW,EAAW,SAAS,CACnC,OAAQ,sBACR,SAAU,EACV,OACA,UAAW,aACX,UAAW,GAAa,aACxB,SAAU,EAAE,CACb,CAAC,CACF,GAAI,EAAS,SAAW,MACtB,MAAO,CACL,KAAM,SAAS,EAAK,gIAEpB,QAAS,GACV,CAEH,GAAI,EAAS,SAAW,OACtB,MAAO,CACL,KAAM,SAAS,EAAK,+BAA+B,EAAS,OAAS,KAAK,EAAS,SAAW,GAAG,GACjG,QAAS,GACV,CASL,IAAM,EAAS,MAJE,EAAmB,EAAU,CAC5C,UAAW,GAAa,aACxB,UAAW,aACZ,CAAC,CAC4B,QAC5B,CAAE,KAAM,YAAa,GAAI,GAAY,CAAE,OAAM,UAAW,EAAM,CAC9D,IAAI,iBAAiB,CAAC,OACvB,CAID,MAAO,CAAE,KAHI,EAAO,QACjB,IAAK,GAAO,EAAE,OAAS,OAAS,EAAE,KAAO,IAAI,EAAE,KAAK,GAAI,CACxD,KAAK;EAAK,EACU,cAAe,QAAS,EAAO,UAAY,GAAM,EAE3E"}
@@ -11,4 +11,4 @@ import{runConfigMigrationCommand as e}from"./config-migration-DatflIxs.js";impor
11
11
  export [--no-sessions] [-o <path>] 导出配置+插件清单+会话数据为迁移包
12
12
  import <archive> [--no-sessions] 导入迁移包(目标已有会话数据时跳过会话部分)
13
13
  `),n.sub?1:0}}export{E as runMigrateCommand};
14
- //# sourceMappingURL=migrate-DDNkik_0.js.map
14
+ //# sourceMappingURL=migrate-RlBJqJnB.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"migrate-DDNkik_0.js","names":[],"sources":["../src/commands/migrate.ts"],"sourcesContent":["/**\n * /migrate 命令:旧版配置文件 → 新版格式在线迁移向导。\n */\nimport { existsSync, mkdirSync, writeFileSync, readdirSync, statSync, cpSync } from 'node:fs'\nimport { hostname } from 'node:os'\nimport { join, resolve } from 'node:path'\nimport { OTTO_HOME, OTTO_SESSIONS_DIR } from '@x-otto/env'\nimport {\n createArchive,\n extractArchive,\n validateExtractedPaths,\n checkArchiveBomb,\n createTempDir,\n cleanTempDir,\n} from '@x-otto/coding'\nimport { buildExportPayload, SettingsManager } from '@x-otto/setting'\nimport { runConfigMigrationCommand } from './config-migration'\n\nexport interface MigrateIO {\n stdout: (s: string) => void\n stderr: (s: string) => void\n}\n\ninterface ParsedArgs {\n sub: string | undefined\n positional: string[]\n flags: Set<string>\n values: Map<string, string>\n}\n\nfunction parseArgs(args: string[]): ParsedArgs {\n const [sub, ...rest] = args\n const positional: string[] = []\n const flags = new Set<string>()\n const values = new Map<string, string>()\n for (let i = 0; i < rest.length; i++) {\n const a = rest[i]!\n if (a === '-o' || a === '--output') {\n const next = rest[++i]\n if (next) values.set('output', next)\n continue\n }\n if (a.startsWith('--')) {\n flags.add(a)\n continue\n }\n positional.push(a)\n }\n return { sub, positional, flags, values }\n}\n\nfunction timestampNow(): string {\n return new Date().toISOString().replace(/[:.]/g, '-')\n}\n\n/**\n * `OTTO_SESSIONS_DIR` 下机器绑定/纯诊断子目录,`migrate export` 打包时排除\n * (对抗审核 M4 完成审核发现的 RFC 漂移修复:初版实现只排除 `locks/`,用\"排除法\"\n * 会把新增子目录默认打包进迁移包,是脆弱的开放式假设)。\n *\n * - `locks`:写租约信息,随进程/机器绑定,新机器上无意义(`OTTO_SESSION_LOCKS_DIR`)。\n * - `trace`:append-log 诊断轨迹,非会话内容本身,实测可堆积至 25GB+(见\n * `packages/coding/src/app/trace-orphan-sweeper.ts` 头注释记录的真实生产数据),\n * 打包会让迁移包体积失控且无迁移价值(新机器上这些轨迹对应的是旧机器的执行历史)。\n *\n * `panel-state/` **不**在排除清单——它是真实用户数据(面板草稿/todoList 等持久化\n * 状态,见 `packages/runtime/src/create-agent-runtime.ts` `DiskPanelStatePersistence`),\n * 随会话搬迁是用户期望的行为,全量打包/仅打包\"sessions.db + 固定白名单\"两种方案都\n * 会静默丢失该数据(前者不排除、后者是未预料到 panel-state 存在的过度收紧)。\n * `sessions.db-wal`/`sessions.db-shm`(SQLite WAL 模式伴生文件)也需随 `sessions.db`\n * 一起打包——它们不是独立目录而是文件,`readdirSync` 顶层遍历天然包含,无需特殊处理。\n */\nconst MACHINE_BOUND_SESSION_SUBDIRS = new Set(['locks', 'trace'])\n\n/** 目标 sessions.db 是否已存在且非空(RFC-219 重要事项规则 7 判定依据)。 */\nfunction targetSessionsExist(): boolean {\n const dbPath = join(OTTO_SESSIONS_DIR, 'sessions.db')\n if (!existsSync(dbPath)) return false\n try {\n return statSync(dbPath).size > 0\n } catch {\n return false\n }\n}\n\n// ── otto migrate export ──\n\nasync function runMigrateExport(parsed: ParsedArgs, io: MigrateIO): Promise<number> {\n const noSessions = parsed.flags.has('--no-sessions')\n const stagingDir = createTempDir()\n\n try {\n const settings = new SettingsManager({ projectConfigPath: join(OTTO_HOME, 'config.json') })\n await settings.load()\n const payload = buildExportPayload(settings.config, 'global', hostname())\n mkdirSync(stagingDir, { recursive: true })\n writeFileSync(join(stagingDir, 'config.json'), JSON.stringify(payload, null, 2), 'utf-8')\n\n if (!noSessions && existsSync(OTTO_SESSIONS_DIR)) {\n const sessionsStaging = join(stagingDir, 'sessions')\n mkdirSync(sessionsStaging, { recursive: true })\n const entries = readdirSync(OTTO_SESSIONS_DIR)\n for (const entry of entries) {\n // 对抗审核 M4 完成审核修复(RFC 漂移):排除机器绑定/纯诊断子目录,非仅 locks。\n if (MACHINE_BOUND_SESSION_SUBDIRS.has(entry)) continue\n cpSync(join(OTTO_SESSIONS_DIR, entry), join(sessionsStaging, entry), { recursive: true })\n }\n }\n\n const outputPath = parsed.values.get('output')\n ? resolve(parsed.values.get('output')!)\n : join(process.cwd(), `otto-migrate-${timestampNow()}.tar.gz`)\n mkdirSync(join(outputPath, '..'), { recursive: true })\n\n await createArchive(stagingDir, outputPath)\n\n io.stdout(`迁移包已导出: ${outputPath}\\n`)\n if (noSessions) {\n io.stdout('(--no-sessions:未包含会话数据)\\n')\n } else {\n // RFC-338 Q2(用户定稿方案 A):粘贴原文与草稿随包属既定行为,但**必须显式声明**——\n // 这些内容可能含密钥/日志/隐私代码,用户在分享迁移包前有权知情。\n // 该提示有测试守卫(migrate.test.ts),防止后续改动静默移除。\n io.stdout('(包含会话数据:对话历史、草稿与粘贴内容原文——分享前请确认其中无敏感信息)\\n')\n }\n return 0\n } finally {\n cleanTempDir(stagingDir)\n }\n}\n\n// ── otto migrate import ──\n\nasync function runMigrateImport(parsed: ParsedArgs, io: MigrateIO): Promise<number> {\n const archivePath = parsed.positional[0]\n if (!archivePath) {\n io.stderr('otto migrate import <archive> [--no-sessions]\\n')\n return 1\n }\n const resolvedArchive = resolve(archivePath)\n if (!existsSync(resolvedArchive)) {\n io.stderr(`迁移包不存在:${resolvedArchive}\\n`)\n return 1\n }\n\n const extractDir = createTempDir()\n try {\n await extractArchive(resolvedArchive, extractDir)\n const { totalBytes } = validateExtractedPaths(extractDir)\n checkArchiveBomb(resolvedArchive, totalBytes)\n\n const configPath = join(extractDir, 'config.json')\n if (!existsSync(configPath)) {\n io.stderr('迁移包缺少 config.json(非合法 otto migrate 产物)\\n')\n return 1\n }\n\n // 复用 M3 config import 完整逻辑(差异计算+合并+安全门+备份+插件安装),\n // 不重新实现——RFC-219 M3→M4 接线设计,见本文件头注释。\n const configExitCode = await runConfigMigrationCommand(\n ['import', configPath, '--yes'],\n { stdout: io.stdout, stderr: io.stderr },\n )\n if (configExitCode !== 0) {\n return configExitCode\n }\n\n // 会话导入 v1 严格边界(重要事项规则 7):目标已有非空 sessions.db → 跳过,不合并。\n const noSessions = parsed.flags.has('--no-sessions')\n const sessionsStaging = join(extractDir, 'sessions')\n if (noSessions) {\n io.stdout('(--no-sessions:跳过会话导入)\\n')\n } else if (!existsSync(sessionsStaging)) {\n io.stdout('迁移包不含会话数据,跳过\\n')\n } else if (targetSessionsExist()) {\n io.stdout('目标已有会话数据,跳过会话导入(不合并/不覆盖,避免数据冲突)\\n')\n } else {\n mkdirSync(OTTO_SESSIONS_DIR, { recursive: true })\n cpSync(sessionsStaging, OTTO_SESSIONS_DIR, { recursive: true })\n io.stdout('✓ 会话数据已导入\\n')\n }\n\n return 0\n } catch (err) {\n io.stderr(`迁移导入失败:${err instanceof Error ? err.message : String(err)}\\n`)\n return 1\n } finally {\n cleanTempDir(extractDir)\n }\n}\n\n// ── 入口 ──\n\nexport async function runMigrateCommand(args: string[], io: MigrateIO): Promise<number> {\n const parsed = parseArgs(args)\n switch (parsed.sub) {\n case 'export':\n return runMigrateExport(parsed, io)\n case 'import':\n return runMigrateImport(parsed, io)\n default:\n io.stdout(\n 'otto migrate <命令>\\n' +\n ' export [--no-sessions] [-o <path>] 导出配置+插件清单+会话数据为迁移包\\n' +\n ' import <archive> [--no-sessions] 导入迁移包(目标已有会话数据时跳过会话部分)\\n',\n )\n return parsed.sub ? 1 : 0\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"migrate-RlBJqJnB.js","names":[],"sources":["../src/commands/migrate.ts"],"sourcesContent":["/**\n * /migrate 命令:旧版配置文件 → 新版格式在线迁移向导。\n */\nimport { existsSync, mkdirSync, writeFileSync, readdirSync, statSync, cpSync } from 'node:fs'\nimport { hostname } from 'node:os'\nimport { join, resolve } from 'node:path'\nimport { OTTO_HOME, OTTO_SESSIONS_DIR } from '@x-otto/env'\nimport {\n createArchive,\n extractArchive,\n validateExtractedPaths,\n checkArchiveBomb,\n createTempDir,\n cleanTempDir,\n} from '@x-otto/coding'\nimport { buildExportPayload, SettingsManager } from '@x-otto/setting'\nimport { runConfigMigrationCommand } from './config-migration'\n\nexport interface MigrateIO {\n stdout: (s: string) => void\n stderr: (s: string) => void\n}\n\ninterface ParsedArgs {\n sub: string | undefined\n positional: string[]\n flags: Set<string>\n values: Map<string, string>\n}\n\nfunction parseArgs(args: string[]): ParsedArgs {\n const [sub, ...rest] = args\n const positional: string[] = []\n const flags = new Set<string>()\n const values = new Map<string, string>()\n for (let i = 0; i < rest.length; i++) {\n const a = rest[i]!\n if (a === '-o' || a === '--output') {\n const next = rest[++i]\n if (next) values.set('output', next)\n continue\n }\n if (a.startsWith('--')) {\n flags.add(a)\n continue\n }\n positional.push(a)\n }\n return { sub, positional, flags, values }\n}\n\nfunction timestampNow(): string {\n return new Date().toISOString().replace(/[:.]/g, '-')\n}\n\n/**\n * `OTTO_SESSIONS_DIR` 下机器绑定/纯诊断子目录,`migrate export` 打包时排除\n * (对抗审核 M4 完成审核发现的 RFC 漂移修复:初版实现只排除 `locks/`,用\"排除法\"\n * 会把新增子目录默认打包进迁移包,是脆弱的开放式假设)。\n *\n * - `locks`:写租约信息,随进程/机器绑定,新机器上无意义(`OTTO_SESSION_LOCKS_DIR`)。\n * - `trace`:append-log 诊断轨迹,非会话内容本身,实测可堆积至 25GB+(见\n * `packages/coding/src/app/trace-orphan-sweeper.ts` 头注释记录的真实生产数据),\n * 打包会让迁移包体积失控且无迁移价值(新机器上这些轨迹对应的是旧机器的执行历史)。\n *\n * `panel-state/` **不**在排除清单——它是真实用户数据(面板草稿/todoList 等持久化\n * 状态,见 `packages/runtime/src/create-agent-runtime.ts` `DiskPanelStatePersistence`),\n * 随会话搬迁是用户期望的行为,全量打包/仅打包\"sessions.db + 固定白名单\"两种方案都\n * 会静默丢失该数据(前者不排除、后者是未预料到 panel-state 存在的过度收紧)。\n * `sessions.db-wal`/`sessions.db-shm`(SQLite WAL 模式伴生文件)也需随 `sessions.db`\n * 一起打包——它们不是独立目录而是文件,`readdirSync` 顶层遍历天然包含,无需特殊处理。\n */\nconst MACHINE_BOUND_SESSION_SUBDIRS = new Set(['locks', 'trace'])\n\n/** 目标 sessions.db 是否已存在且非空(RFC-219 重要事项规则 7 判定依据)。 */\nfunction targetSessionsExist(): boolean {\n const dbPath = join(OTTO_SESSIONS_DIR, 'sessions.db')\n if (!existsSync(dbPath)) return false\n try {\n return statSync(dbPath).size > 0\n } catch {\n return false\n }\n}\n\n// ── otto migrate export ──\n\nasync function runMigrateExport(parsed: ParsedArgs, io: MigrateIO): Promise<number> {\n const noSessions = parsed.flags.has('--no-sessions')\n const stagingDir = createTempDir()\n\n try {\n const settings = new SettingsManager({ projectConfigPath: join(OTTO_HOME, 'config.json') })\n await settings.load()\n const payload = buildExportPayload(settings.config, 'global', hostname())\n mkdirSync(stagingDir, { recursive: true })\n writeFileSync(join(stagingDir, 'config.json'), JSON.stringify(payload, null, 2), 'utf-8')\n\n if (!noSessions && existsSync(OTTO_SESSIONS_DIR)) {\n const sessionsStaging = join(stagingDir, 'sessions')\n mkdirSync(sessionsStaging, { recursive: true })\n const entries = readdirSync(OTTO_SESSIONS_DIR)\n for (const entry of entries) {\n // 对抗审核 M4 完成审核修复(RFC 漂移):排除机器绑定/纯诊断子目录,非仅 locks。\n if (MACHINE_BOUND_SESSION_SUBDIRS.has(entry)) continue\n cpSync(join(OTTO_SESSIONS_DIR, entry), join(sessionsStaging, entry), { recursive: true })\n }\n }\n\n const outputPath = parsed.values.get('output')\n ? resolve(parsed.values.get('output')!)\n : join(process.cwd(), `otto-migrate-${timestampNow()}.tar.gz`)\n mkdirSync(join(outputPath, '..'), { recursive: true })\n\n await createArchive(stagingDir, outputPath)\n\n io.stdout(`迁移包已导出: ${outputPath}\\n`)\n if (noSessions) {\n io.stdout('(--no-sessions:未包含会话数据)\\n')\n } else {\n // RFC-338 Q2(用户定稿方案 A):粘贴原文与草稿随包属既定行为,但**必须显式声明**——\n // 这些内容可能含密钥/日志/隐私代码,用户在分享迁移包前有权知情。\n // 该提示有测试守卫(migrate.test.ts),防止后续改动静默移除。\n io.stdout('(包含会话数据:对话历史、草稿与粘贴内容原文——分享前请确认其中无敏感信息)\\n')\n }\n return 0\n } finally {\n cleanTempDir(stagingDir)\n }\n}\n\n// ── otto migrate import ──\n\nasync function runMigrateImport(parsed: ParsedArgs, io: MigrateIO): Promise<number> {\n const archivePath = parsed.positional[0]\n if (!archivePath) {\n io.stderr('otto migrate import <archive> [--no-sessions]\\n')\n return 1\n }\n const resolvedArchive = resolve(archivePath)\n if (!existsSync(resolvedArchive)) {\n io.stderr(`迁移包不存在:${resolvedArchive}\\n`)\n return 1\n }\n\n const extractDir = createTempDir()\n try {\n await extractArchive(resolvedArchive, extractDir)\n const { totalBytes } = validateExtractedPaths(extractDir)\n checkArchiveBomb(resolvedArchive, totalBytes)\n\n const configPath = join(extractDir, 'config.json')\n if (!existsSync(configPath)) {\n io.stderr('迁移包缺少 config.json(非合法 otto migrate 产物)\\n')\n return 1\n }\n\n // 复用 M3 config import 完整逻辑(差异计算+合并+安全门+备份+插件安装),\n // 不重新实现——RFC-219 M3→M4 接线设计,见本文件头注释。\n const configExitCode = await runConfigMigrationCommand(\n ['import', configPath, '--yes'],\n { stdout: io.stdout, stderr: io.stderr },\n )\n if (configExitCode !== 0) {\n return configExitCode\n }\n\n // 会话导入 v1 严格边界(重要事项规则 7):目标已有非空 sessions.db → 跳过,不合并。\n const noSessions = parsed.flags.has('--no-sessions')\n const sessionsStaging = join(extractDir, 'sessions')\n if (noSessions) {\n io.stdout('(--no-sessions:跳过会话导入)\\n')\n } else if (!existsSync(sessionsStaging)) {\n io.stdout('迁移包不含会话数据,跳过\\n')\n } else if (targetSessionsExist()) {\n io.stdout('目标已有会话数据,跳过会话导入(不合并/不覆盖,避免数据冲突)\\n')\n } else {\n mkdirSync(OTTO_SESSIONS_DIR, { recursive: true })\n cpSync(sessionsStaging, OTTO_SESSIONS_DIR, { recursive: true })\n io.stdout('✓ 会话数据已导入\\n')\n }\n\n return 0\n } catch (err) {\n io.stderr(`迁移导入失败:${err instanceof Error ? err.message : String(err)}\\n`)\n return 1\n } finally {\n cleanTempDir(extractDir)\n }\n}\n\n// ── 入口 ──\n\nexport async function runMigrateCommand(args: string[], io: MigrateIO): Promise<number> {\n const parsed = parseArgs(args)\n switch (parsed.sub) {\n case 'export':\n return runMigrateExport(parsed, io)\n case 'import':\n return runMigrateImport(parsed, io)\n default:\n io.stdout(\n 'otto migrate <命令>\\n' +\n ' export [--no-sessions] [-o <path>] 导出配置+插件清单+会话数据为迁移包\\n' +\n ' import <archive> [--no-sessions] 导入迁移包(目标已有会话数据时跳过会话部分)\\n',\n )\n return parsed.sub ? 1 : 0\n }\n}\n"],"mappings":"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"}
@@ -80,4 +80,4 @@ ${i.join(`
80
80
  `)}
81
81
  `),0}async function C(e,t,n){let{spans:r}=await b(e,t);if(r.length===0)return n(`Session ${t}: no trace events found.\n`),0;let i=new Map,a=-1;for(let e of r){let t=e.turn??0;t>a&&(a=t);let n=i.get(t)??[];n.push(e),i.set(t,n)}n(`Trace Spans — ${t} (${a+1} turns)
82
82
  `);for(let e=0;e<=a;e++){let t=i.get(e);if(t){n(`▼ Turn ${e} (${t.length} events)\n`);for(let e of t){let t=e.node?`[${e.node}] `:``;n(` ├─ ${e.kind.padEnd(12)} ${t}${e.digest??``}\n`)}}}return 0}async function w(e,t,n,r){try{let r=await e.get(`/api/sessions/${t}/trace/timeline`);return n(`${JSON.stringify(r,null,2)}\n`),0}catch(e){return r(`observe --json: ${e instanceof Error?e.message:String(e)}\n`),1}}export{y as runObserveCommand};
83
- //# sourceMappingURL=observe-CzIv09hO.js.map
83
+ //# sourceMappingURL=observe-Dxfud-t7.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"observe-CzIv09hO.js","names":[],"sources":["../src/commands/debug.ts","../src/commands/time-travel.ts","../src/commands/observe.ts"],"sourcesContent":["/**\n * `otto debug` 子命令 —— 调试面板:断点管理 / 状态查看。\n *\n * 设计依据:design/observability-platform.md §4/§5 + design/m17-platform-activation.md §1/§3。\n * 数据源 = service REST(/api/debugger 路由;此前各自起 in-memory 进程,\n * 断点状态随进程消失、对运行中引擎无效)。默认连 `otto serve --inspect`\n * 实例,`--remote <url>` 覆盖。断点缺省 disabled,设置即对运行中会话生效。\n */\nimport { BREAKPOINT_POINTS } from '@x-otto/agent'\nimport {\n createServiceClient,\n reportServiceError,\n resolveRemote,\n type ServiceClient,\n} from './service-client'\n\nconst POINT_NAMES: readonly string[] = BREAKPOINT_POINTS.map((d) => d.point)\n\nexport interface DebugDeps {\n stdout?: (s: string) => void\n stderr?: (s: string) => void\n /** fetch 注入(测试用)。 */\n fetch?: typeof globalThis.fetch\n}\n\ninterface BreakpointEntry {\n point: string\n enabled: boolean\n set?: boolean\n}\n\nexport async function runDebugCommand(\n args: string[],\n deps: DebugDeps = {},\n): Promise<number> {\n const stdout = deps.stdout ?? ((s) => process.stdout.write(s))\n const stderr = deps.stderr ?? ((s) => process.stderr.write(s))\n\n let baseUrl: string\n let rest: string[]\n try {\n ;({ baseUrl, rest } = resolveRemote(args))\n } catch (error) {\n return reportServiceError('debug', error, stderr)\n }\n\n const [action, ...sub] = rest\n\n if (!action || action === 'help') {\n stdout(`otto debug — 调试面板(断点管理 / 状态查看)\n\n用法:\n otto debug breakpoints list 列出所有断点及启用状态\n otto debug breakpoints set <point> 设置断点(默认启用)\n otto debug breakpoints remove <point> 移除断点\n otto debug breakpoints enable <point> 启用断点\n otto debug breakpoints disable <point> 禁用断点\n otto debug breakpoints enable-all 全部启用\n otto debug breakpoints disable-all 全部禁用\n otto debug points 列出所有可用断点位\n otto debug state 调试器状态(断点统计 + 挂起的暂停)\n\n选项:\n --remote <url> service 地址(默认 OTTO_SERVICE_URL 或 http:\n\n断点位(BREAKPOINT_POINTS):\n${POINT_NAMES.map((p) => ` ${p}`).join('\\n')}\n\n注:需要 \\`otto serve --inspect\\` 启动的 service(调试器默认关闭)。\nresume/inject 等运行时命令经 REST:POST /api/debugger/resume | /inject。\n`)\n return 0\n }\n\n const client = createServiceClient(baseUrl, deps.fetch)\n\n try {\n switch (action) {\n case 'breakpoints': {\n const op = sub[0]\n switch (op) {\n case 'list':\n return await listBreakpoints(client, stdout)\n case 'set':\n return await setBreakpoint(client, sub[1], true, stdout, stderr)\n case 'remove':\n return await setBreakpoint(client, sub[1], false, stdout, stderr, 'removed')\n case 'enable':\n return await setBreakpoint(client, sub[1], true, stdout, stderr, 'enabled')\n case 'disable':\n return await setBreakpoint(client, sub[1], false, stdout, stderr, 'disabled')\n case 'enable-all':\n await client.post('/api/debugger/breakpoints/enable-all')\n stdout('All breakpoints enabled.\\n')\n return 0\n case 'disable-all':\n await client.post('/api/debugger/breakpoints/disable-all')\n stdout('All breakpoints disabled.\\n')\n return 0\n default:\n stderr(`otto debug breakpoints: unknown sub-action '${op}'.\\n`)\n return 1\n }\n }\n case 'points':\n return await listPoints(client, stdout)\n case 'state':\n return await showState(client, stdout)\n default:\n stderr(`otto debug: unknown action '${action}'. Use breakpoints | points | state.\\n`)\n return 1\n }\n } catch (error) {\n return reportServiceError('debug', error, stderr)\n }\n}\n\nasync function listBreakpoints(client: ServiceClient, stdout: (s: string) => void): Promise<number> {\n const { breakpoints } = await client.get<{ breakpoints: BreakpointEntry[] }>(\n '/api/debugger/breakpoints',\n )\n if (breakpoints.length === 0) {\n stdout('No breakpoints set.\\n')\n return 0\n }\n stdout(`${'point'.padEnd(36)} enabled\\n${'-'.repeat(46)}\\n`)\n for (const bp of breakpoints) {\n stdout(`${bp.point.padEnd(36)} ${bp.enabled ? '✓' : '✗'}\\n`)\n }\n return 0\n}\n\nfunction isValidPoint(point: string | undefined): point is string {\n return !!point && POINT_NAMES.includes(point)\n}\n\nasync function setBreakpoint(\n client: ServiceClient,\n point: string | undefined,\n enabled: boolean,\n stdout: (s: string) => void,\n stderr: (s: string) => void,\n verb?: string,\n): Promise<number> {\n if (!isValidPoint(point)) {\n stderr(\n `otto debug: invalid or missing breakpoint point.\\nUse 'otto debug points' to list valid points.\\n`,\n )\n return 1\n }\n await client.put(`/api/debugger/breakpoints/${point}`, { enabled })\n stdout(`Breakpoint ${verb ?? `set: ${point} (${enabled ? 'enabled' : 'disabled'})`}${verb ? `: ${point}` : ''}\\n`)\n return 0\n}\n\nasync function listPoints(client: ServiceClient, stdout: (s: string) => void): Promise<number> {\n const { breakpoints } = await client.get<{ breakpoints: BreakpointEntry[] }>(\n '/api/debugger/breakpoints',\n )\n const byPoint = new Map(breakpoints.map((bp) => [bp.point, bp]))\n\n stdout('Available breakpoint points:\\n')\n for (const def of BREAKPOINT_POINTS) {\n const bp = byPoint.get(def.point)\n const status = bp?.set ? (bp.enabled ? 'enabled' : 'disabled') : 'not set'\n stdout(` ${def.point.padEnd(36)} ${status}\\n`)\n }\n return 0\n}\n\nasync function showState(client: ServiceClient, stdout: (s: string) => void): Promise<number> {\n const state = await client.get<{\n breakpoints: { total: number; enabled: number }\n paused: boolean\n pendingPauses: { pauseId: string; point: string; turn: number }[]\n }>('/api/debugger/state')\n\n stdout(`Debugger state\n breakpoints: ${state.breakpoints.enabled}/${state.breakpoints.total} enabled\n paused: ${state.paused ? 'yes' : 'no'}\n`)\n for (const p of state.pendingPauses) {\n stdout(` ⏸ ${p.point} (turn ${p.turn}) pauseId=${p.pauseId}\\n`)\n }\n return 0\n}\n","/**\n * `otto time-travel` 子命令 —— 回放+回滚面板。\n *\n * 设计依据:design/observability-platform.md §4/§5 + design/m17-platform-activation.md §3。\n * 数据源 = service REST(timetravel 路由)。默认连 `otto serve` 实例,`--remote <url>` 覆盖。\n */\nimport { SERVICE_URL } from '@x-otto/env'\n\nimport {\n createServiceClient,\n reportServiceError,\n resolveRemote,\n type ServiceClient,\n} from './service-client'\n\nexport interface TimeTravelDeps {\n stdout?: (s: string) => void\n stderr?: (s: string) => void\n /** fetch 注入(测试用)。 */\n fetch?: typeof globalThis.fetch\n}\n\ninterface CheckpointInfo {\n seq: number\n label?: string\n createdAt: number\n traceSeq?: number\n}\n\ninterface TracebackView {\n toSeq: number\n baselineCheckpointSeq: number | null\n fromTraceSeq: number\n events: unknown[]\n messages: { role: string; content: unknown }[]\n}\n\nexport async function runTimeTravelCommand(\n args: string[],\n deps: TimeTravelDeps = {},\n): Promise<number> {\n const stdout = deps.stdout ?? ((s) => process.stdout.write(s))\n const stderr = deps.stderr ?? ((s) => process.stderr.write(s))\n\n let baseUrl: string\n let rest: string[]\n try {\n ;({ baseUrl, rest } = resolveRemote(args))\n } catch (error) {\n return reportServiceError('time-travel', error, stderr)\n }\n\n const [action, sessionId, ...extra] = rest\n\n if (!action || action === 'help') {\n stdout(`otto time-travel — 回放+回滚面板\n\n用法:\n otto time-travel checkpoints <session-id> 列出所有 checkpoint\n otto time-travel checkpoint <session-id> [--label X] 创建新 checkpoint\n otto time-travel scrubber <session-id> <seq> 显示 traceback 视图\n otto time-travel restore <session-id> <seq> [--mode M] [--rollback]\n 恢复/回滚 (mode: conversation|code|both)\n otto time-travel fork <session-id> <seq> 从 checkpoint 分叉新会话\n otto time-travel replay <session-id> 启动确定性重放\n\n选项:\n --remote <url> service 地址(默认 OTTO_SERVICE_URL 或 ${SERVICE_URL})\n`)\n return 0\n }\n\n if (!sessionId) {\n stderr('otto time-travel: session-id is required.\\n')\n return 1\n }\n\n const client = createServiceClient(baseUrl, deps.fetch)\n\n try {\n switch (action) {\n case 'checkpoints':\n return await listCheckpoints(client, sessionId, stdout)\n case 'checkpoint':\n return await createCheckpoint(client, sessionId, extra, stdout)\n case 'scrubber':\n return await showScrubber(client, sessionId, extra[0], stdout, stderr)\n case 'restore':\n return await restoreCheckpoint(client, sessionId, extra, stdout, stderr)\n case 'fork':\n return await forkCheckpoint(client, sessionId, extra[0], stdout, stderr)\n case 'replay':\n return await startReplay(client, sessionId, stdout)\n default:\n stderr(`otto time-travel: unknown action '${action}'.\\n`)\n return 1\n }\n } catch (error) {\n return reportServiceError('time-travel', error, stderr)\n }\n}\n\nasync function listCheckpoints(\n client: ServiceClient,\n sessionId: string,\n stdout: (s: string) => void,\n): Promise<number> {\n const { checkpoints } = await client.get<{ checkpoints: CheckpointInfo[] }>(\n `/api/sessions/${sessionId}/checkpoints`,\n )\n if (checkpoints.length === 0) {\n stdout(`Session ${sessionId}: no checkpoints found.\\n`)\n return 0\n }\n\n stdout(`Checkpoints — ${sessionId} (${checkpoints.length})\\n`)\n stdout(`${'seq'.padStart(4)} ${'traceSeq'.padStart(9)} ${'label'.padEnd(24)} ${'created'}\\n`)\n stdout(`${'-'.repeat(70)}\\n`)\n for (const cp of checkpoints) {\n const label = (cp.label ?? '').padEnd(24)\n const created = new Date(cp.createdAt).toISOString()\n const traceSeq = cp.traceSeq !== undefined ? String(cp.traceSeq).padStart(9) : ' -'\n stdout(`${String(cp.seq).padStart(4)} ${traceSeq} ${label} ${created}\\n`)\n }\n return 0\n}\n\nasync function createCheckpoint(\n client: ServiceClient,\n sessionId: string,\n rest: string[],\n stdout: (s: string) => void,\n): Promise<number> {\n const labelIdx = rest.indexOf('--label')\n const label = labelIdx >= 0 ? rest[labelIdx + 1] : undefined\n\n const { checkpoint } = await client.post<{ checkpoint: CheckpointInfo }>(\n `/api/sessions/${sessionId}/checkpoints`,\n { label },\n )\n stdout(\n `Checkpoint created: seq=${checkpoint.seq}${checkpoint.label ? ` label=\"${checkpoint.label}\"` : ''} traceSeq=${checkpoint.traceSeq}\\n`,\n )\n return 0\n}\n\nasync function showScrubber(\n client: ServiceClient,\n sessionId: string,\n seqRaw: string | undefined,\n stdout: (s: string) => void,\n stderr: (s: string) => void,\n): Promise<number> {\n const seq = Number(seqRaw)\n if (!Number.isInteger(seq)) {\n stderr('otto time-travel scrubber: seq (integer) is required.\\n')\n return 1\n }\n\n const view = await client.get<TracebackView>(`/api/sessions/${sessionId}/traceback?to=${seq}`)\n\n stdout(`Traceback — ${sessionId} @ seq=${view.toSeq}\\n`)\n if (view.baselineCheckpointSeq !== null) {\n stdout(` Baseline: checkpoint seq=${view.baselineCheckpointSeq} (fromTraceSeq=${view.fromTraceSeq})\\n`)\n } else {\n stdout(` Baseline: none (folded from seq 0)\\n`)\n }\n stdout(` Events folded: ${view.events.length}\\n`)\n stdout(` Messages reconstructed: ${view.messages.length}\\n\\n`)\n\n for (const msg of view.messages) {\n const role = msg.role.padEnd(12)\n const content =\n typeof msg.content === 'string'\n ? msg.content.slice(0, 120)\n : JSON.stringify(msg.content).slice(0, 120)\n stdout(` ${role} ${content}\\n`)\n }\n return 0\n}\n\nasync function restoreCheckpoint(\n client: ServiceClient,\n sessionId: string,\n rest: string[],\n stdout: (s: string) => void,\n stderr: (s: string) => void,\n): Promise<number> {\n const seq = Number(rest[0])\n if (!Number.isInteger(seq)) {\n stderr('otto time-travel restore: seq (integer) is required.\\n')\n return 1\n }\n\n const modeIdx = rest.indexOf('--mode')\n const modeRaw = modeIdx >= 0 ? rest[modeIdx + 1] : 'both'\n if (modeRaw !== 'conversation' && modeRaw !== 'code' && modeRaw !== 'both') {\n stderr(`otto time-travel restore: invalid mode '${modeRaw}'. Use conversation | code | both.\\n`)\n return 1\n }\n const rollback = rest.includes('--rollback')\n\n await client.post(`/api/sessions/${sessionId}/checkpoints/${seq}/restore`, {\n mode: modeRaw,\n truncate: rollback,\n })\n stdout(`Restored to checkpoint seq=${seq} mode=${modeRaw} rollback=${rollback}\\n`)\n return 0\n}\n\nasync function forkCheckpoint(\n client: ServiceClient,\n sessionId: string,\n seqRaw: string | undefined,\n stdout: (s: string) => void,\n stderr: (s: string) => void,\n): Promise<number> {\n const seq = Number(seqRaw)\n if (!Number.isInteger(seq)) {\n stderr('otto time-travel fork: seq (integer) is required.\\n')\n return 1\n }\n\n const result = await client.post<{ forkedSessionId: string }>(\n `/api/sessions/${sessionId}/checkpoints/${seq}/fork`,\n {},\n )\n stdout(`Forked session: ${result.forkedSessionId} (from ${sessionId} @ seq=${seq})\\n`)\n return 0\n}\n\nasync function startReplay(\n client: ServiceClient,\n sessionId: string,\n stdout: (s: string) => void,\n): Promise<number> {\n const result = await client.post<{ replaySessionId: string }>(\n `/api/sessions/${sessionId}/replay`,\n {},\n )\n stdout(`Replay session created: ${result.replaySessionId} (source: ${sessionId})\\n`)\n return 0\n}\n","/**\n * `otto observe` 子命令 —— 观测面板:trace 时间线 / 摘要 / span 树。\n *\n * 设计依据:design/observability-platform.md §4/§5 + design/m17-platform-activation.md §3。\n * 数据源 = service REST。\n * 默认连 `http://127.0.0.1:${SERVICE_PORT}`(`otto serve` 启动),`--remote <url>` 覆盖;\n * service 不可达 → 明确报错而非静默空数据。聚合算法在 @x-otto/agent,两端同源。\n */\nimport { SERVICE_URL } from '@x-otto/env'\n\nimport {\n createServiceClient,\n reportServiceError,\n resolveRemote,\n type ServiceClient,\n} from './service-client'\nimport { runDebugCommand } from './debug'\nimport { runTimeTravelCommand } from './time-travel'\nimport type { TraceSpan, TraceSummary } from '@x-otto/agent'\nimport type { TraceTimelineView } from '@x-otto/trace-view'\n\nexport interface ObserveDeps {\n /** 输出(便于测试注入)。 */\n stdout?: (s: string) => void\n stderr?: (s: string) => void\n /** fetch 注入(测试用)。 */\n fetch?: typeof globalThis.fetch\n}\n\nexport async function runObserveCommand(\n args: string[],\n deps: ObserveDeps = {},\n): Promise<number> {\n const stdout = deps.stdout ?? ((s) => process.stdout.write(s))\n const stderr = deps.stderr ?? ((s) => process.stderr.write(s))\n\n // RFC-369 M4:observe 吸收 debug/time-travel 为子命令。\n // `otto observe debug ...` → runDebugCommand\n // `otto observe replay ...` → runTimeTravelCommand\n const [subCommand, ...subRest] = args\n if (subCommand === 'debug') {\n return runDebugCommand(subRest, deps)\n }\n if (subCommand === 'replay') {\n return runTimeTravelCommand(subRest, deps)\n }\n\n // 提取 --json 标志(在 resolveRemote 之前,避免被当作 remote URL)\n const jsonIdx = args.indexOf('--json')\n const json = jsonIdx >= 0\n const cleanArgs = json ? args.filter((_, i) => i !== jsonIdx) : args\n\n let baseUrl: string\n let rest: string[]\n try {\n ;({ baseUrl, rest } = resolveRemote(cleanArgs))\n } catch (error) {\n return reportServiceError('observe', error, stderr)\n }\n\n const [action, sessionId] = rest\n\n if (!action || action === 'help') {\n stdout(`otto observe — 观测面板(trace 时间线 / 摘要 / span 树)\n\n用法:\n otto observe summary <session-id> trace 聚合统计\n otto observe timeline <session-id> 文本时间线\n otto observe spans <session-id> span 树视图\n otto observe debug <action> [...] 调试面板(breakpoints / points / state)\n otto observe replay <action> <id> 时间旅行(checkpoints / scrubber / restore / fork / replay)\n\n选项:\n --remote <url> service 地址(默认 OTTO_SERVICE_URL 或 ${SERVICE_URL})\n --json 输出结构化 JSON(需 service 运行)\n`)\n return 0\n }\n\n if (!sessionId) {\n stderr('otto observe: session-id is required.\\n')\n return 1\n }\n\n const client = createServiceClient(baseUrl, deps.fetch)\n\n try {\n // --json 模式:调用 /trace/timeline 输出结构化 JSON\n if (json) {\n return await observeJson(client, sessionId, stdout, stderr)\n }\n switch (action) {\n case 'summary':\n return await observeSummary(client, sessionId, stdout)\n case 'timeline':\n return await observeTimeline(client, sessionId, stdout)\n case 'spans':\n return await observeSpans(client, sessionId, stdout)\n default:\n stderr(`otto observe: unknown action '${action}'. Use summary | timeline | spans.\\n`)\n return 1\n }\n } catch (error) {\n return reportServiceError('observe', error, stderr)\n }\n}\n\nfunction fetchSpans(client: ServiceClient, sessionId: string): Promise<{ spans: TraceSpan[] }> {\n return client.get<{ spans: TraceSpan[] }>(`/api/sessions/${sessionId}/trace/spans`)\n}\n\nasync function observeSummary(\n client: ServiceClient,\n sessionId: string,\n stdout: (s: string) => void,\n): Promise<number> {\n const summary = await client.get<TraceSummary>(`/api/sessions/${sessionId}/trace/summary`)\n\n if (summary.totalEvents === 0) {\n stdout(`Session ${sessionId}: no trace events found.\\n`)\n return 0\n }\n\n const { totalEvents, kinds, turns, toolCalls, firstTs, lastTs } = summary\n const duration = firstTs !== undefined && lastTs !== undefined ? `${lastTs - firstTs}ms` : 'N/A'\n\n stdout(`Trace Summary — ${sessionId}\n──────────────────────────────────────────\n Total events: ${totalEvents}\n Turns: ${turns}\n Tool calls: ${toolCalls}\n Time span: ${duration}\n First event: ${firstTs ? new Date(firstTs).toISOString() : 'N/A'}\n Last event: ${lastTs ? new Date(lastTs).toISOString() : 'N/A'}\n\n By kind:\n${Object.entries(kinds).map(([k, v]) => ` ${k.padEnd(14)} ${v}`).join('\\n')}\n`)\n return 0\n}\n\nasync function observeTimeline(\n client: ServiceClient,\n sessionId: string,\n stdout: (s: string) => void,\n): Promise<number> {\n const { spans } = await fetchSpans(client, sessionId)\n if (spans.length === 0) {\n stdout(`Session ${sessionId}: no trace events found.\\n`)\n return 0\n }\n\n const lines = spans.map((span) => {\n const ts = new Date(span.ts).toISOString().slice(11, 23)\n const turn = span.turn !== undefined ? `T${String(span.turn).padStart(2)}` : ' -'\n const node = (span.node ?? '').padEnd(26)\n return `${String(span.seq).padStart(5)} ${ts} ${turn} ${span.kind.padEnd(12)} ${node}${span.digest ?? ''}`\n })\n\n stdout(`Trace Timeline — ${sessionId} (${lines.length} events)\n${'seq'.padStart(5)} ${'time'} ${'turn'} ${'kind'.padEnd(12)} ${'node'.padEnd(26)} digest\n${'-'.repeat(100)}\n${lines.join('\\n')}\n`)\n return 0\n}\n\nasync function observeSpans(\n client: ServiceClient,\n sessionId: string,\n stdout: (s: string) => void,\n): Promise<number> {\n const { spans } = await fetchSpans(client, sessionId)\n if (spans.length === 0) {\n stdout(`Session ${sessionId}: no trace events found.\\n`)\n return 0\n }\n\n const turnSpans = new Map<number, TraceSpan[]>()\n let maxTurn = -1\n for (const span of spans) {\n const turn = span.turn ?? 0\n if (turn > maxTurn) maxTurn = turn\n const group = turnSpans.get(turn) ?? []\n group.push(span)\n turnSpans.set(turn, group)\n }\n\n stdout(`Trace Spans — ${sessionId} (${maxTurn + 1} turns)\n`)\n for (let t = 0; t <= maxTurn; t++) {\n const group = turnSpans.get(t)\n if (!group) continue\n stdout(`▼ Turn ${t} (${group.length} events)\\n`)\n for (const span of group) {\n const node = span.node ? `[${span.node}] ` : ''\n stdout(` ├─ ${span.kind.padEnd(12)} ${node}${span.digest ?? ''}\\n`)\n }\n }\n return 0\n}\n\nasync function observeJson(\n client: ServiceClient,\n sessionId: string,\n stdout: (s: string) => void,\n stderr: (s: string) => void,\n): Promise<number> {\n try {\n const timeline = await client.get<TraceTimelineView>(\n `/api/sessions/${sessionId}/trace/timeline`,\n )\n stdout(`${JSON.stringify(timeline, null, 2)}\\n`)\n return 0\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error)\n stderr(`observe --json: ${msg}\\n`)\n return 1\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"observe-Dxfud-t7.js","names":[],"sources":["../src/commands/debug.ts","../src/commands/time-travel.ts","../src/commands/observe.ts"],"sourcesContent":["/**\n * `otto debug` 子命令 —— 调试面板:断点管理 / 状态查看。\n *\n * 设计依据:design/observability-platform.md §4/§5 + design/m17-platform-activation.md §1/§3。\n * 数据源 = service REST(/api/debugger 路由;此前各自起 in-memory 进程,\n * 断点状态随进程消失、对运行中引擎无效)。默认连 `otto serve --inspect`\n * 实例,`--remote <url>` 覆盖。断点缺省 disabled,设置即对运行中会话生效。\n */\nimport { BREAKPOINT_POINTS } from '@x-otto/agent'\nimport {\n createServiceClient,\n reportServiceError,\n resolveRemote,\n type ServiceClient,\n} from './service-client'\n\nconst POINT_NAMES: readonly string[] = BREAKPOINT_POINTS.map((d) => d.point)\n\nexport interface DebugDeps {\n stdout?: (s: string) => void\n stderr?: (s: string) => void\n /** fetch 注入(测试用)。 */\n fetch?: typeof globalThis.fetch\n}\n\ninterface BreakpointEntry {\n point: string\n enabled: boolean\n set?: boolean\n}\n\nexport async function runDebugCommand(\n args: string[],\n deps: DebugDeps = {},\n): Promise<number> {\n const stdout = deps.stdout ?? ((s) => process.stdout.write(s))\n const stderr = deps.stderr ?? ((s) => process.stderr.write(s))\n\n let baseUrl: string\n let rest: string[]\n try {\n ;({ baseUrl, rest } = resolveRemote(args))\n } catch (error) {\n return reportServiceError('debug', error, stderr)\n }\n\n const [action, ...sub] = rest\n\n if (!action || action === 'help') {\n stdout(`otto debug — 调试面板(断点管理 / 状态查看)\n\n用法:\n otto debug breakpoints list 列出所有断点及启用状态\n otto debug breakpoints set <point> 设置断点(默认启用)\n otto debug breakpoints remove <point> 移除断点\n otto debug breakpoints enable <point> 启用断点\n otto debug breakpoints disable <point> 禁用断点\n otto debug breakpoints enable-all 全部启用\n otto debug breakpoints disable-all 全部禁用\n otto debug points 列出所有可用断点位\n otto debug state 调试器状态(断点统计 + 挂起的暂停)\n\n选项:\n --remote <url> service 地址(默认 OTTO_SERVICE_URL 或 http:\n\n断点位(BREAKPOINT_POINTS):\n${POINT_NAMES.map((p) => ` ${p}`).join('\\n')}\n\n注:需要 \\`otto serve --inspect\\` 启动的 service(调试器默认关闭)。\nresume/inject 等运行时命令经 REST:POST /api/debugger/resume | /inject。\n`)\n return 0\n }\n\n const client = createServiceClient(baseUrl, deps.fetch)\n\n try {\n switch (action) {\n case 'breakpoints': {\n const op = sub[0]\n switch (op) {\n case 'list':\n return await listBreakpoints(client, stdout)\n case 'set':\n return await setBreakpoint(client, sub[1], true, stdout, stderr)\n case 'remove':\n return await setBreakpoint(client, sub[1], false, stdout, stderr, 'removed')\n case 'enable':\n return await setBreakpoint(client, sub[1], true, stdout, stderr, 'enabled')\n case 'disable':\n return await setBreakpoint(client, sub[1], false, stdout, stderr, 'disabled')\n case 'enable-all':\n await client.post('/api/debugger/breakpoints/enable-all')\n stdout('All breakpoints enabled.\\n')\n return 0\n case 'disable-all':\n await client.post('/api/debugger/breakpoints/disable-all')\n stdout('All breakpoints disabled.\\n')\n return 0\n default:\n stderr(`otto debug breakpoints: unknown sub-action '${op}'.\\n`)\n return 1\n }\n }\n case 'points':\n return await listPoints(client, stdout)\n case 'state':\n return await showState(client, stdout)\n default:\n stderr(`otto debug: unknown action '${action}'. Use breakpoints | points | state.\\n`)\n return 1\n }\n } catch (error) {\n return reportServiceError('debug', error, stderr)\n }\n}\n\nasync function listBreakpoints(client: ServiceClient, stdout: (s: string) => void): Promise<number> {\n const { breakpoints } = await client.get<{ breakpoints: BreakpointEntry[] }>(\n '/api/debugger/breakpoints',\n )\n if (breakpoints.length === 0) {\n stdout('No breakpoints set.\\n')\n return 0\n }\n stdout(`${'point'.padEnd(36)} enabled\\n${'-'.repeat(46)}\\n`)\n for (const bp of breakpoints) {\n stdout(`${bp.point.padEnd(36)} ${bp.enabled ? '✓' : '✗'}\\n`)\n }\n return 0\n}\n\nfunction isValidPoint(point: string | undefined): point is string {\n return !!point && POINT_NAMES.includes(point)\n}\n\nasync function setBreakpoint(\n client: ServiceClient,\n point: string | undefined,\n enabled: boolean,\n stdout: (s: string) => void,\n stderr: (s: string) => void,\n verb?: string,\n): Promise<number> {\n if (!isValidPoint(point)) {\n stderr(\n `otto debug: invalid or missing breakpoint point.\\nUse 'otto debug points' to list valid points.\\n`,\n )\n return 1\n }\n await client.put(`/api/debugger/breakpoints/${point}`, { enabled })\n stdout(`Breakpoint ${verb ?? `set: ${point} (${enabled ? 'enabled' : 'disabled'})`}${verb ? `: ${point}` : ''}\\n`)\n return 0\n}\n\nasync function listPoints(client: ServiceClient, stdout: (s: string) => void): Promise<number> {\n const { breakpoints } = await client.get<{ breakpoints: BreakpointEntry[] }>(\n '/api/debugger/breakpoints',\n )\n const byPoint = new Map(breakpoints.map((bp) => [bp.point, bp]))\n\n stdout('Available breakpoint points:\\n')\n for (const def of BREAKPOINT_POINTS) {\n const bp = byPoint.get(def.point)\n const status = bp?.set ? (bp.enabled ? 'enabled' : 'disabled') : 'not set'\n stdout(` ${def.point.padEnd(36)} ${status}\\n`)\n }\n return 0\n}\n\nasync function showState(client: ServiceClient, stdout: (s: string) => void): Promise<number> {\n const state = await client.get<{\n breakpoints: { total: number; enabled: number }\n paused: boolean\n pendingPauses: { pauseId: string; point: string; turn: number }[]\n }>('/api/debugger/state')\n\n stdout(`Debugger state\n breakpoints: ${state.breakpoints.enabled}/${state.breakpoints.total} enabled\n paused: ${state.paused ? 'yes' : 'no'}\n`)\n for (const p of state.pendingPauses) {\n stdout(` ⏸ ${p.point} (turn ${p.turn}) pauseId=${p.pauseId}\\n`)\n }\n return 0\n}\n","/**\n * `otto time-travel` 子命令 —— 回放+回滚面板。\n *\n * 设计依据:design/observability-platform.md §4/§5 + design/m17-platform-activation.md §3。\n * 数据源 = service REST(timetravel 路由)。默认连 `otto serve` 实例,`--remote <url>` 覆盖。\n */\nimport { SERVICE_URL } from '@x-otto/env'\n\nimport {\n createServiceClient,\n reportServiceError,\n resolveRemote,\n type ServiceClient,\n} from './service-client'\n\nexport interface TimeTravelDeps {\n stdout?: (s: string) => void\n stderr?: (s: string) => void\n /** fetch 注入(测试用)。 */\n fetch?: typeof globalThis.fetch\n}\n\ninterface CheckpointInfo {\n seq: number\n label?: string\n createdAt: number\n traceSeq?: number\n}\n\ninterface TracebackView {\n toSeq: number\n baselineCheckpointSeq: number | null\n fromTraceSeq: number\n events: unknown[]\n messages: { role: string; content: unknown }[]\n}\n\nexport async function runTimeTravelCommand(\n args: string[],\n deps: TimeTravelDeps = {},\n): Promise<number> {\n const stdout = deps.stdout ?? ((s) => process.stdout.write(s))\n const stderr = deps.stderr ?? ((s) => process.stderr.write(s))\n\n let baseUrl: string\n let rest: string[]\n try {\n ;({ baseUrl, rest } = resolveRemote(args))\n } catch (error) {\n return reportServiceError('time-travel', error, stderr)\n }\n\n const [action, sessionId, ...extra] = rest\n\n if (!action || action === 'help') {\n stdout(`otto time-travel — 回放+回滚面板\n\n用法:\n otto time-travel checkpoints <session-id> 列出所有 checkpoint\n otto time-travel checkpoint <session-id> [--label X] 创建新 checkpoint\n otto time-travel scrubber <session-id> <seq> 显示 traceback 视图\n otto time-travel restore <session-id> <seq> [--mode M] [--rollback]\n 恢复/回滚 (mode: conversation|code|both)\n otto time-travel fork <session-id> <seq> 从 checkpoint 分叉新会话\n otto time-travel replay <session-id> 启动确定性重放\n\n选项:\n --remote <url> service 地址(默认 OTTO_SERVICE_URL 或 ${SERVICE_URL})\n`)\n return 0\n }\n\n if (!sessionId) {\n stderr('otto time-travel: session-id is required.\\n')\n return 1\n }\n\n const client = createServiceClient(baseUrl, deps.fetch)\n\n try {\n switch (action) {\n case 'checkpoints':\n return await listCheckpoints(client, sessionId, stdout)\n case 'checkpoint':\n return await createCheckpoint(client, sessionId, extra, stdout)\n case 'scrubber':\n return await showScrubber(client, sessionId, extra[0], stdout, stderr)\n case 'restore':\n return await restoreCheckpoint(client, sessionId, extra, stdout, stderr)\n case 'fork':\n return await forkCheckpoint(client, sessionId, extra[0], stdout, stderr)\n case 'replay':\n return await startReplay(client, sessionId, stdout)\n default:\n stderr(`otto time-travel: unknown action '${action}'.\\n`)\n return 1\n }\n } catch (error) {\n return reportServiceError('time-travel', error, stderr)\n }\n}\n\nasync function listCheckpoints(\n client: ServiceClient,\n sessionId: string,\n stdout: (s: string) => void,\n): Promise<number> {\n const { checkpoints } = await client.get<{ checkpoints: CheckpointInfo[] }>(\n `/api/sessions/${sessionId}/checkpoints`,\n )\n if (checkpoints.length === 0) {\n stdout(`Session ${sessionId}: no checkpoints found.\\n`)\n return 0\n }\n\n stdout(`Checkpoints — ${sessionId} (${checkpoints.length})\\n`)\n stdout(`${'seq'.padStart(4)} ${'traceSeq'.padStart(9)} ${'label'.padEnd(24)} ${'created'}\\n`)\n stdout(`${'-'.repeat(70)}\\n`)\n for (const cp of checkpoints) {\n const label = (cp.label ?? '').padEnd(24)\n const created = new Date(cp.createdAt).toISOString()\n const traceSeq = cp.traceSeq !== undefined ? String(cp.traceSeq).padStart(9) : ' -'\n stdout(`${String(cp.seq).padStart(4)} ${traceSeq} ${label} ${created}\\n`)\n }\n return 0\n}\n\nasync function createCheckpoint(\n client: ServiceClient,\n sessionId: string,\n rest: string[],\n stdout: (s: string) => void,\n): Promise<number> {\n const labelIdx = rest.indexOf('--label')\n const label = labelIdx >= 0 ? rest[labelIdx + 1] : undefined\n\n const { checkpoint } = await client.post<{ checkpoint: CheckpointInfo }>(\n `/api/sessions/${sessionId}/checkpoints`,\n { label },\n )\n stdout(\n `Checkpoint created: seq=${checkpoint.seq}${checkpoint.label ? ` label=\"${checkpoint.label}\"` : ''} traceSeq=${checkpoint.traceSeq}\\n`,\n )\n return 0\n}\n\nasync function showScrubber(\n client: ServiceClient,\n sessionId: string,\n seqRaw: string | undefined,\n stdout: (s: string) => void,\n stderr: (s: string) => void,\n): Promise<number> {\n const seq = Number(seqRaw)\n if (!Number.isInteger(seq)) {\n stderr('otto time-travel scrubber: seq (integer) is required.\\n')\n return 1\n }\n\n const view = await client.get<TracebackView>(`/api/sessions/${sessionId}/traceback?to=${seq}`)\n\n stdout(`Traceback — ${sessionId} @ seq=${view.toSeq}\\n`)\n if (view.baselineCheckpointSeq !== null) {\n stdout(` Baseline: checkpoint seq=${view.baselineCheckpointSeq} (fromTraceSeq=${view.fromTraceSeq})\\n`)\n } else {\n stdout(` Baseline: none (folded from seq 0)\\n`)\n }\n stdout(` Events folded: ${view.events.length}\\n`)\n stdout(` Messages reconstructed: ${view.messages.length}\\n\\n`)\n\n for (const msg of view.messages) {\n const role = msg.role.padEnd(12)\n const content =\n typeof msg.content === 'string'\n ? msg.content.slice(0, 120)\n : JSON.stringify(msg.content).slice(0, 120)\n stdout(` ${role} ${content}\\n`)\n }\n return 0\n}\n\nasync function restoreCheckpoint(\n client: ServiceClient,\n sessionId: string,\n rest: string[],\n stdout: (s: string) => void,\n stderr: (s: string) => void,\n): Promise<number> {\n const seq = Number(rest[0])\n if (!Number.isInteger(seq)) {\n stderr('otto time-travel restore: seq (integer) is required.\\n')\n return 1\n }\n\n const modeIdx = rest.indexOf('--mode')\n const modeRaw = modeIdx >= 0 ? rest[modeIdx + 1] : 'both'\n if (modeRaw !== 'conversation' && modeRaw !== 'code' && modeRaw !== 'both') {\n stderr(`otto time-travel restore: invalid mode '${modeRaw}'. Use conversation | code | both.\\n`)\n return 1\n }\n const rollback = rest.includes('--rollback')\n\n await client.post(`/api/sessions/${sessionId}/checkpoints/${seq}/restore`, {\n mode: modeRaw,\n truncate: rollback,\n })\n stdout(`Restored to checkpoint seq=${seq} mode=${modeRaw} rollback=${rollback}\\n`)\n return 0\n}\n\nasync function forkCheckpoint(\n client: ServiceClient,\n sessionId: string,\n seqRaw: string | undefined,\n stdout: (s: string) => void,\n stderr: (s: string) => void,\n): Promise<number> {\n const seq = Number(seqRaw)\n if (!Number.isInteger(seq)) {\n stderr('otto time-travel fork: seq (integer) is required.\\n')\n return 1\n }\n\n const result = await client.post<{ forkedSessionId: string }>(\n `/api/sessions/${sessionId}/checkpoints/${seq}/fork`,\n {},\n )\n stdout(`Forked session: ${result.forkedSessionId} (from ${sessionId} @ seq=${seq})\\n`)\n return 0\n}\n\nasync function startReplay(\n client: ServiceClient,\n sessionId: string,\n stdout: (s: string) => void,\n): Promise<number> {\n const result = await client.post<{ replaySessionId: string }>(\n `/api/sessions/${sessionId}/replay`,\n {},\n )\n stdout(`Replay session created: ${result.replaySessionId} (source: ${sessionId})\\n`)\n return 0\n}\n","/**\n * `otto observe` 子命令 —— 观测面板:trace 时间线 / 摘要 / span 树。\n *\n * 设计依据:design/observability-platform.md §4/§5 + design/m17-platform-activation.md §3。\n * 数据源 = service REST。\n * 默认连 `http://127.0.0.1:${SERVICE_PORT}`(`otto serve` 启动),`--remote <url>` 覆盖;\n * service 不可达 → 明确报错而非静默空数据。聚合算法在 @x-otto/agent,两端同源。\n */\nimport { SERVICE_URL } from '@x-otto/env'\n\nimport {\n createServiceClient,\n reportServiceError,\n resolveRemote,\n type ServiceClient,\n} from './service-client'\nimport { runDebugCommand } from './debug'\nimport { runTimeTravelCommand } from './time-travel'\nimport type { TraceSpan, TraceSummary } from '@x-otto/agent'\nimport type { TraceTimelineView } from '@x-otto/trace-view'\n\nexport interface ObserveDeps {\n /** 输出(便于测试注入)。 */\n stdout?: (s: string) => void\n stderr?: (s: string) => void\n /** fetch 注入(测试用)。 */\n fetch?: typeof globalThis.fetch\n}\n\nexport async function runObserveCommand(\n args: string[],\n deps: ObserveDeps = {},\n): Promise<number> {\n const stdout = deps.stdout ?? ((s) => process.stdout.write(s))\n const stderr = deps.stderr ?? ((s) => process.stderr.write(s))\n\n // RFC-369 M4:observe 吸收 debug/time-travel 为子命令。\n // `otto observe debug ...` → runDebugCommand\n // `otto observe replay ...` → runTimeTravelCommand\n const [subCommand, ...subRest] = args\n if (subCommand === 'debug') {\n return runDebugCommand(subRest, deps)\n }\n if (subCommand === 'replay') {\n return runTimeTravelCommand(subRest, deps)\n }\n\n // 提取 --json 标志(在 resolveRemote 之前,避免被当作 remote URL)\n const jsonIdx = args.indexOf('--json')\n const json = jsonIdx >= 0\n const cleanArgs = json ? args.filter((_, i) => i !== jsonIdx) : args\n\n let baseUrl: string\n let rest: string[]\n try {\n ;({ baseUrl, rest } = resolveRemote(cleanArgs))\n } catch (error) {\n return reportServiceError('observe', error, stderr)\n }\n\n const [action, sessionId] = rest\n\n if (!action || action === 'help') {\n stdout(`otto observe — 观测面板(trace 时间线 / 摘要 / span 树)\n\n用法:\n otto observe summary <session-id> trace 聚合统计\n otto observe timeline <session-id> 文本时间线\n otto observe spans <session-id> span 树视图\n otto observe debug <action> [...] 调试面板(breakpoints / points / state)\n otto observe replay <action> <id> 时间旅行(checkpoints / scrubber / restore / fork / replay)\n\n选项:\n --remote <url> service 地址(默认 OTTO_SERVICE_URL 或 ${SERVICE_URL})\n --json 输出结构化 JSON(需 service 运行)\n`)\n return 0\n }\n\n if (!sessionId) {\n stderr('otto observe: session-id is required.\\n')\n return 1\n }\n\n const client = createServiceClient(baseUrl, deps.fetch)\n\n try {\n // --json 模式:调用 /trace/timeline 输出结构化 JSON\n if (json) {\n return await observeJson(client, sessionId, stdout, stderr)\n }\n switch (action) {\n case 'summary':\n return await observeSummary(client, sessionId, stdout)\n case 'timeline':\n return await observeTimeline(client, sessionId, stdout)\n case 'spans':\n return await observeSpans(client, sessionId, stdout)\n default:\n stderr(`otto observe: unknown action '${action}'. Use summary | timeline | spans.\\n`)\n return 1\n }\n } catch (error) {\n return reportServiceError('observe', error, stderr)\n }\n}\n\nfunction fetchSpans(client: ServiceClient, sessionId: string): Promise<{ spans: TraceSpan[] }> {\n return client.get<{ spans: TraceSpan[] }>(`/api/sessions/${sessionId}/trace/spans`)\n}\n\nasync function observeSummary(\n client: ServiceClient,\n sessionId: string,\n stdout: (s: string) => void,\n): Promise<number> {\n const summary = await client.get<TraceSummary>(`/api/sessions/${sessionId}/trace/summary`)\n\n if (summary.totalEvents === 0) {\n stdout(`Session ${sessionId}: no trace events found.\\n`)\n return 0\n }\n\n const { totalEvents, kinds, turns, toolCalls, firstTs, lastTs } = summary\n const duration = firstTs !== undefined && lastTs !== undefined ? `${lastTs - firstTs}ms` : 'N/A'\n\n stdout(`Trace Summary — ${sessionId}\n──────────────────────────────────────────\n Total events: ${totalEvents}\n Turns: ${turns}\n Tool calls: ${toolCalls}\n Time span: ${duration}\n First event: ${firstTs ? new Date(firstTs).toISOString() : 'N/A'}\n Last event: ${lastTs ? new Date(lastTs).toISOString() : 'N/A'}\n\n By kind:\n${Object.entries(kinds).map(([k, v]) => ` ${k.padEnd(14)} ${v}`).join('\\n')}\n`)\n return 0\n}\n\nasync function observeTimeline(\n client: ServiceClient,\n sessionId: string,\n stdout: (s: string) => void,\n): Promise<number> {\n const { spans } = await fetchSpans(client, sessionId)\n if (spans.length === 0) {\n stdout(`Session ${sessionId}: no trace events found.\\n`)\n return 0\n }\n\n const lines = spans.map((span) => {\n const ts = new Date(span.ts).toISOString().slice(11, 23)\n const turn = span.turn !== undefined ? `T${String(span.turn).padStart(2)}` : ' -'\n const node = (span.node ?? '').padEnd(26)\n return `${String(span.seq).padStart(5)} ${ts} ${turn} ${span.kind.padEnd(12)} ${node}${span.digest ?? ''}`\n })\n\n stdout(`Trace Timeline — ${sessionId} (${lines.length} events)\n${'seq'.padStart(5)} ${'time'} ${'turn'} ${'kind'.padEnd(12)} ${'node'.padEnd(26)} digest\n${'-'.repeat(100)}\n${lines.join('\\n')}\n`)\n return 0\n}\n\nasync function observeSpans(\n client: ServiceClient,\n sessionId: string,\n stdout: (s: string) => void,\n): Promise<number> {\n const { spans } = await fetchSpans(client, sessionId)\n if (spans.length === 0) {\n stdout(`Session ${sessionId}: no trace events found.\\n`)\n return 0\n }\n\n const turnSpans = new Map<number, TraceSpan[]>()\n let maxTurn = -1\n for (const span of spans) {\n const turn = span.turn ?? 0\n if (turn > maxTurn) maxTurn = turn\n const group = turnSpans.get(turn) ?? []\n group.push(span)\n turnSpans.set(turn, group)\n }\n\n stdout(`Trace Spans — ${sessionId} (${maxTurn + 1} turns)\n`)\n for (let t = 0; t <= maxTurn; t++) {\n const group = turnSpans.get(t)\n if (!group) continue\n stdout(`▼ Turn ${t} (${group.length} events)\\n`)\n for (const span of group) {\n const node = span.node ? `[${span.node}] ` : ''\n stdout(` ├─ ${span.kind.padEnd(12)} ${node}${span.digest ?? ''}\\n`)\n }\n }\n return 0\n}\n\nasync function observeJson(\n client: ServiceClient,\n sessionId: string,\n stdout: (s: string) => void,\n stderr: (s: string) => void,\n): Promise<number> {\n try {\n const timeline = await client.get<TraceTimelineView>(\n `/api/sessions/${sessionId}/trace/timeline`,\n )\n stdout(`${JSON.stringify(timeline, null, 2)}\\n`)\n return 0\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error)\n stderr(`observe --json: ${msg}\\n`)\n return 1\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import{ot as e}from"./command-dispatcher-DaehlD7G.js";import{t}from"./version-DM0x7ndr.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-Cbc7Df1n.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-cli-Cbc7Df1n.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,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{t as e}from"./daemon-base-Bc9p51Um.js";import{PERSISTENCED_DEFAULT_PORT as t,startPersistenced as n}from"@x-otto/service";import{REMOTE_PERSISTENCE_DEFAULT_PORT as r}from"@x-otto/remote-persistence-server";const i=t,a=`127.0.0.1`;async function o(t,r={}){let o=r.stdout??(e=>process.stdout.write(e)),c=r.stderr??(e=>process.stderr.write(e));if(t[0]===`help`||t[0]===`--help`)return s(o),0;let l,u=i,d=a,f;for(let e=0;e<t.length;e++){let n=t[e];if(n===`--dir`||n===`-d`)l=t[++e];else if(n===`--port`||n===`-p`){let n=t[++e],r=Number(n);if(!Number.isInteger(r)||r<1||r>65535)return c(`otto persistenced: invalid --port value: ${n??`(missing)`}\n`),1;u=r}else n===`--host`||n===`-h`?d=t[++e]??a:(n===`--token`||n===`-t`)&&(f=t[++e])}if(!l)return c(`otto persistenced: --dir is required
2
+ import{t as e}from"./daemon-base-Y1XI61JY.js";import{PERSISTENCED_DEFAULT_PORT as t,startPersistenced as n}from"@x-otto/service";import{REMOTE_PERSISTENCE_DEFAULT_PORT as r}from"@x-otto/remote-persistence-server";const i=t,a=`127.0.0.1`;async function o(t,r={}){let o=r.stdout??(e=>process.stdout.write(e)),c=r.stderr??(e=>process.stderr.write(e));if(t[0]===`help`||t[0]===`--help`)return s(o),0;let l,u=i,d=a,f;for(let e=0;e<t.length;e++){let n=t[e];if(n===`--dir`||n===`-d`)l=t[++e];else if(n===`--port`||n===`-p`){let n=t[++e],r=Number(n);if(!Number.isInteger(r)||r<1||r>65535)return c(`otto persistenced: invalid --port value: ${n??`(missing)`}\n`),1;u=r}else n===`--host`||n===`-h`?d=t[++e]??a:(n===`--token`||n===`-t`)&&(f=t[++e])}if(!l)return c(`otto persistenced: --dir is required
3
3
  `),1;if(!f)return c(`otto persistenced: --token is required
4
4
  `),1;try{let{url:t}=await n({dir:l,port:u,host:d,token:f});o(`persistenced listening on ${t}
5
5
  storage: ${t}/api/storage/:namespace
@@ -82,4 +82,4 @@ import{t as e}from"./daemon-base-Bc9p51Um.js";import{PERSISTENCED_DEFAULT_PORT a
82
82
 
83
83
  注:旧命令 otto persistenced / otto remote-persistence-server 仍可用(别名转发)。
84
84
  `),0}}export{f as runPersistenceCommand};
85
- //# sourceMappingURL=persistence-DOaizHMA.js.map
85
+ //# sourceMappingURL=persistence-CRFhOYx6.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"persistence-DOaizHMA.js","names":["DEFAULT_PORT","DEFAULT_HOST","printHelp"],"sources":["../src/commands/persistenced.ts","../src/commands/remote-persistence-server.ts","../src/commands/persistence.ts"],"sourcesContent":["import { startPersistenced, PERSISTENCED_DEFAULT_PORT } from '@x-otto/service'\n\nimport { waitForShutdown } from './daemon-base'\n\nexport interface PersistencedDeps {\n stdout?: (s: string) => void\n stderr?: (s: string) => void\n signal?: AbortSignal\n}\n\n/** 复用 @x-otto/service 的族内单源默认端口,消除双源漂移。 */\nconst DEFAULT_PORT = PERSISTENCED_DEFAULT_PORT\nconst DEFAULT_HOST = '127.0.0.1'\n\nexport async function runPersistencedCommand(\n args: string[],\n deps: PersistencedDeps = {},\n): 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 dir: string | undefined\n let port = DEFAULT_PORT\n let host = DEFAULT_HOST\n let token: string | undefined\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]\n if (arg === '--dir' || arg === '-d') {\n dir = args[++i]\n } else if (arg === '--port' || arg === '-p') {\n const raw = args[++i]\n const n = Number(raw)\n if (!Number.isInteger(n) || n < 1 || n > 65535) {\n stderr(`otto persistenced: invalid --port value: ${raw ?? '(missing)'}\\n`)\n return 1\n }\n port = n\n } else if (arg === '--host' || arg === '-h') {\n host = args[++i] ?? DEFAULT_HOST\n } else if (arg === '--token' || arg === '-t') {\n token = args[++i]\n }\n }\n\n if (!dir) {\n stderr('otto persistenced: --dir is required\\n')\n return 1\n }\n\n if (!token) {\n stderr('otto persistenced: --token is required\\n')\n return 1\n }\n\n try {\n const { url } = await startPersistenced({ dir, port, host, token })\n\n stdout(`persistenced listening on ${url}\n storage: ${url}/api/storage/:namespace\n events: ${url}/api/events/:namespace (SSE)\n sync: ${url}/api/sync/:namespace\n health: ${url}/health\n stop: Ctrl-C\n`)\n\n await waitForShutdown(deps.signal)\n stdout('persistenced shutting down\\n')\n // 硬退出:同 serve.ts,避免孤儿 handle 吊住事件循环。\n process.exit(0)\n } catch (error) {\n stderr(\n `otto persistenced: ${error instanceof Error ? error.message : String(error)}\\n`,\n )\n return 1\n }\n}\n\nfunction printHelp(stdout: (s: string) => void): void {\n stdout(`otto persistenced — 独立持久化存储服务(SQLite + HTTP API)\n\n用法:\n otto persistenced --dir <path> --token <token> [--port <n>] [--host <ip>]\n\n选项:\n --dir, -d <path> 数据目录(SQLite db 文件存放位置,必填)\n --token, -t <token> Bearer token(所有 /api/* 请求必须携带,必填)\n --port, -p <n> 监听端口(默认 ${DEFAULT_PORT})\n --host, -h <ip> 监听地址(默认 ${DEFAULT_HOST},0.0.0.0 = 公网暴露)\n\n示例:\n otto persistenced --dir /var/lib/otto/storage --token \"\\$TOKEN\"\n otto persistenced --dir ./data --port 3002 --token dev-secret\n\n端点:\n GET /health 健康检查\n GET /api/storage/:namespace 列出文档 ID / 分页列表\n GET /api/storage/:namespace/:id 读取文档\n PUT /api/storage/:namespace/:id 写入文档\n DELETE /api/storage/:namespace/:id 删除文档\n GET /api/events/:namespace SSE 变更推送\n POST /api/sync/:namespace/push 批量写入(离线同步)\n GET /api/sync/:namespace/pull 增量拉取\n`)\n}\n\n","import { REMOTE_PERSISTENCE_DEFAULT_PORT, type ServerConfig } from '@x-otto/remote-persistence-server'\n\nimport { waitForShutdown } from './daemon-base'\n\nexport interface RemotePersistenceServerDeps {\n stdout?: (s: string) => void\n stderr?: (s: string) => void\n signal?: AbortSignal\n}\n\n/** 复用 @x-otto/remote-persistence-server 的族内单源默认端口,消除双源漂移。 */\nconst DEFAULT_PORT = REMOTE_PERSISTENCE_DEFAULT_PORT\nconst DEFAULT_HOST = '127.0.0.1'\n\nexport async function runRemotePersistenceServerCommand(\n args: string[],\n deps: RemotePersistenceServerDeps = {},\n): 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 dir: string | undefined\n let port = DEFAULT_PORT\n let host = DEFAULT_HOST\n let token: string | undefined\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]\n if (arg === '--dir' || arg === '-d') {\n dir = args[++i]\n } else if (arg === '--port' || arg === '-p') {\n const raw = args[++i]\n const n = Number(raw)\n if (!Number.isInteger(n) || n < 1 || n > 65535) {\n stderr(\n `otto remote-persistence-server: invalid --port value: ${raw ?? '(missing)'}\\n`,\n )\n return 1\n }\n port = n\n } else if (arg === '--host' || arg === '-h') {\n host = args[++i] ?? DEFAULT_HOST\n } else if (arg === '--token' || arg === '-t') {\n token = args[++i]\n }\n }\n\n if (!dir) {\n stderr('otto remote-persistence-server: --dir is required\\n')\n return 1\n }\n\n if (token === undefined) {\n stderr(\n 'otto remote-persistence-server: --token is required (use --token \"\" to disable auth for dev only)\\n',\n )\n return 1\n }\n\n const { startServer } = await import('@x-otto/remote-persistence-server')\n\n try {\n const config: ServerConfig = { dataDir: dir, port, authToken: token }\n const { url, server, store } = await startServer(config, { host })\n\n stdout(`remote-persistence-server listening on ${url}\n sessions: ${url}/sessions\n health: ${url}/health\n stop: Ctrl-C\n`)\n\n await waitForShutdown(deps.signal)\n stdout('remote-persistence-server shutting down\\n')\n await new Promise<void>((resolve) => server.close(() => resolve()))\n store.close()\n // 硬退出:同 serve.ts,避免孤儿 handle 吊住事件循环。\n process.exit(0)\n } catch (error) {\n stderr(\n `otto remote-persistence-server: ${error instanceof Error ? error.message : String(error)}\\n`,\n )\n return 1\n }\n}\n\nfunction printHelp(stdout: (s: string) => void): void {\n stdout(`otto remote-persistence-server — 最小化远程持久化服务(Hono + SQLite,5 REST 端点)\n\n用法:\n otto remote-persistence-server --dir <path> --token <token> [--port <n>] [--host <ip>]\n\n选项:\n --dir, -d <path> 数据目录(SQLite db 文件存放位置,必填)\n --token, -t <token> Bearer token(所有 /sessions/* 请求必须携带,必填)\n --port, -p <n> 监听端口(默认 ${DEFAULT_PORT})\n --host, -h <ip> 监听地址(默认 ${DEFAULT_HOST},0.0.0.0 = 公网暴露)\n\n示例:\n otto remote-persistence-server --dir ./data --token dev-secret\n otto remote-persistence-server --dir /var/lib/otto/storage --port 3002 --token \"\\$TOKEN\"\n\n端点(:wsKey 为客户端工作区分区段,本服务 accept-and-ignore,按 token 派生 userId 分区):\n GET /health 健康检查(无需认证)\n GET /sessions/:wsKey 列出会话 ID(需要 Bearer token)\n GET /sessions/:wsKey/:id 读取会话快照\n PUT /sessions/:wsKey/:id 写入/更新会话快照\n DELETE /sessions/:wsKey/:id 删除会话快照\n\n对端客户端:\n 另一台机器上的 Otto CLI:\n otto --session-url http://<host>:<port>/sessions --session-token <token>\n`)\n}\n","/**\n * `otto persistence` 子命令 —— 持久化服务族(local / remote)的薄路由。\n *\n * RFC-369 M4:persistence 收编 persistenced(local)与 remote-persistence-server(remote)\n * 为子命令。旧入口 `otto persistenced` / `otto remote-persistence-server` 作为别名保留,\n * 内部分别转发到本命令的 `local` / `remote` 子路径。\n */\nimport { runPersistencedCommand, type PersistencedDeps } from './persistenced'\nimport {\n runRemotePersistenceServerCommand,\n type RemotePersistenceServerDeps,\n} from './remote-persistence-server'\n\n/**\n * persistence 路由的 deps —— 是 PersistencedDeps 与 RemotePersistenceServerDeps 的并集\n * (两者结构相同:stdout? / stderr? / signal?,并集保持兼容)。\n */\nexport type PersistenceDeps = PersistencedDeps & RemotePersistenceServerDeps\n\nexport async function runPersistenceCommand(\n args: string[],\n deps: PersistenceDeps = {},\n): Promise<number> {\n const stdout = deps.stdout ?? ((s) => process.stdout.write(s))\n\n const [subCommand, ...rest] = args\n\n switch (subCommand) {\n case 'local':\n return runPersistencedCommand(rest, deps)\n case 'remote':\n return runRemotePersistenceServerCommand(rest, deps)\n default:\n stdout(`otto persistence — 持久化服务族(local / remote)\n\n用法:\n otto persistence local [...flags] 启动独立持久化服务(storage + events + sync)\n otto persistence remote [...flags] 启动最小化远程持久化服务(5 REST 端点)\n\n子命令说明:\n local 等价于旧 otto persistenced(SQLite + HTTP API,storage/events/sync)\n remote 等价于旧 otto remote-persistence-server(Hono + SQLite,5 REST 端点)\n\n通用选项(详见各子命令 --help):\n --dir, -d <path> 数据目录(必填)\n --token, -t <token> Bearer token(必填)\n --port, -p <n> 监听端口(各子命令有各自默认端口)\n --host, -h <ip> 监听地址(默认 127.0.0.1,0.0.0.0 = 公网暴露)\n\n注:旧命令 otto persistenced / otto remote-persistence-server 仍可用(别名转发)。\n`)\n return 0\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"persistence-CRFhOYx6.js","names":["DEFAULT_PORT","DEFAULT_HOST","printHelp"],"sources":["../src/commands/persistenced.ts","../src/commands/remote-persistence-server.ts","../src/commands/persistence.ts"],"sourcesContent":["import { startPersistenced, PERSISTENCED_DEFAULT_PORT } from '@x-otto/service'\n\nimport { waitForShutdown } from './daemon-base'\n\nexport interface PersistencedDeps {\n stdout?: (s: string) => void\n stderr?: (s: string) => void\n signal?: AbortSignal\n}\n\n/** 复用 @x-otto/service 的族内单源默认端口,消除双源漂移。 */\nconst DEFAULT_PORT = PERSISTENCED_DEFAULT_PORT\nconst DEFAULT_HOST = '127.0.0.1'\n\nexport async function runPersistencedCommand(\n args: string[],\n deps: PersistencedDeps = {},\n): 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 dir: string | undefined\n let port = DEFAULT_PORT\n let host = DEFAULT_HOST\n let token: string | undefined\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]\n if (arg === '--dir' || arg === '-d') {\n dir = args[++i]\n } else if (arg === '--port' || arg === '-p') {\n const raw = args[++i]\n const n = Number(raw)\n if (!Number.isInteger(n) || n < 1 || n > 65535) {\n stderr(`otto persistenced: invalid --port value: ${raw ?? '(missing)'}\\n`)\n return 1\n }\n port = n\n } else if (arg === '--host' || arg === '-h') {\n host = args[++i] ?? DEFAULT_HOST\n } else if (arg === '--token' || arg === '-t') {\n token = args[++i]\n }\n }\n\n if (!dir) {\n stderr('otto persistenced: --dir is required\\n')\n return 1\n }\n\n if (!token) {\n stderr('otto persistenced: --token is required\\n')\n return 1\n }\n\n try {\n const { url } = await startPersistenced({ dir, port, host, token })\n\n stdout(`persistenced listening on ${url}\n storage: ${url}/api/storage/:namespace\n events: ${url}/api/events/:namespace (SSE)\n sync: ${url}/api/sync/:namespace\n health: ${url}/health\n stop: Ctrl-C\n`)\n\n await waitForShutdown(deps.signal)\n stdout('persistenced shutting down\\n')\n // 硬退出:同 serve.ts,避免孤儿 handle 吊住事件循环。\n process.exit(0)\n } catch (error) {\n stderr(\n `otto persistenced: ${error instanceof Error ? error.message : String(error)}\\n`,\n )\n return 1\n }\n}\n\nfunction printHelp(stdout: (s: string) => void): void {\n stdout(`otto persistenced — 独立持久化存储服务(SQLite + HTTP API)\n\n用法:\n otto persistenced --dir <path> --token <token> [--port <n>] [--host <ip>]\n\n选项:\n --dir, -d <path> 数据目录(SQLite db 文件存放位置,必填)\n --token, -t <token> Bearer token(所有 /api/* 请求必须携带,必填)\n --port, -p <n> 监听端口(默认 ${DEFAULT_PORT})\n --host, -h <ip> 监听地址(默认 ${DEFAULT_HOST},0.0.0.0 = 公网暴露)\n\n示例:\n otto persistenced --dir /var/lib/otto/storage --token \"\\$TOKEN\"\n otto persistenced --dir ./data --port 3002 --token dev-secret\n\n端点:\n GET /health 健康检查\n GET /api/storage/:namespace 列出文档 ID / 分页列表\n GET /api/storage/:namespace/:id 读取文档\n PUT /api/storage/:namespace/:id 写入文档\n DELETE /api/storage/:namespace/:id 删除文档\n GET /api/events/:namespace SSE 变更推送\n POST /api/sync/:namespace/push 批量写入(离线同步)\n GET /api/sync/:namespace/pull 增量拉取\n`)\n}\n\n","import { REMOTE_PERSISTENCE_DEFAULT_PORT, type ServerConfig } from '@x-otto/remote-persistence-server'\n\nimport { waitForShutdown } from './daemon-base'\n\nexport interface RemotePersistenceServerDeps {\n stdout?: (s: string) => void\n stderr?: (s: string) => void\n signal?: AbortSignal\n}\n\n/** 复用 @x-otto/remote-persistence-server 的族内单源默认端口,消除双源漂移。 */\nconst DEFAULT_PORT = REMOTE_PERSISTENCE_DEFAULT_PORT\nconst DEFAULT_HOST = '127.0.0.1'\n\nexport async function runRemotePersistenceServerCommand(\n args: string[],\n deps: RemotePersistenceServerDeps = {},\n): 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 dir: string | undefined\n let port = DEFAULT_PORT\n let host = DEFAULT_HOST\n let token: string | undefined\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]\n if (arg === '--dir' || arg === '-d') {\n dir = args[++i]\n } else if (arg === '--port' || arg === '-p') {\n const raw = args[++i]\n const n = Number(raw)\n if (!Number.isInteger(n) || n < 1 || n > 65535) {\n stderr(\n `otto remote-persistence-server: invalid --port value: ${raw ?? '(missing)'}\\n`,\n )\n return 1\n }\n port = n\n } else if (arg === '--host' || arg === '-h') {\n host = args[++i] ?? DEFAULT_HOST\n } else if (arg === '--token' || arg === '-t') {\n token = args[++i]\n }\n }\n\n if (!dir) {\n stderr('otto remote-persistence-server: --dir is required\\n')\n return 1\n }\n\n if (token === undefined) {\n stderr(\n 'otto remote-persistence-server: --token is required (use --token \"\" to disable auth for dev only)\\n',\n )\n return 1\n }\n\n const { startServer } = await import('@x-otto/remote-persistence-server')\n\n try {\n const config: ServerConfig = { dataDir: dir, port, authToken: token }\n const { url, server, store } = await startServer(config, { host })\n\n stdout(`remote-persistence-server listening on ${url}\n sessions: ${url}/sessions\n health: ${url}/health\n stop: Ctrl-C\n`)\n\n await waitForShutdown(deps.signal)\n stdout('remote-persistence-server shutting down\\n')\n await new Promise<void>((resolve) => server.close(() => resolve()))\n store.close()\n // 硬退出:同 serve.ts,避免孤儿 handle 吊住事件循环。\n process.exit(0)\n } catch (error) {\n stderr(\n `otto remote-persistence-server: ${error instanceof Error ? error.message : String(error)}\\n`,\n )\n return 1\n }\n}\n\nfunction printHelp(stdout: (s: string) => void): void {\n stdout(`otto remote-persistence-server — 最小化远程持久化服务(Hono + SQLite,5 REST 端点)\n\n用法:\n otto remote-persistence-server --dir <path> --token <token> [--port <n>] [--host <ip>]\n\n选项:\n --dir, -d <path> 数据目录(SQLite db 文件存放位置,必填)\n --token, -t <token> Bearer token(所有 /sessions/* 请求必须携带,必填)\n --port, -p <n> 监听端口(默认 ${DEFAULT_PORT})\n --host, -h <ip> 监听地址(默认 ${DEFAULT_HOST},0.0.0.0 = 公网暴露)\n\n示例:\n otto remote-persistence-server --dir ./data --token dev-secret\n otto remote-persistence-server --dir /var/lib/otto/storage --port 3002 --token \"\\$TOKEN\"\n\n端点(:wsKey 为客户端工作区分区段,本服务 accept-and-ignore,按 token 派生 userId 分区):\n GET /health 健康检查(无需认证)\n GET /sessions/:wsKey 列出会话 ID(需要 Bearer token)\n GET /sessions/:wsKey/:id 读取会话快照\n PUT /sessions/:wsKey/:id 写入/更新会话快照\n DELETE /sessions/:wsKey/:id 删除会话快照\n\n对端客户端:\n 另一台机器上的 Otto CLI:\n otto --session-url http://<host>:<port>/sessions --session-token <token>\n`)\n}\n","/**\n * `otto persistence` 子命令 —— 持久化服务族(local / remote)的薄路由。\n *\n * RFC-369 M4:persistence 收编 persistenced(local)与 remote-persistence-server(remote)\n * 为子命令。旧入口 `otto persistenced` / `otto remote-persistence-server` 作为别名保留,\n * 内部分别转发到本命令的 `local` / `remote` 子路径。\n */\nimport { runPersistencedCommand, type PersistencedDeps } from './persistenced'\nimport {\n runRemotePersistenceServerCommand,\n type RemotePersistenceServerDeps,\n} from './remote-persistence-server'\n\n/**\n * persistence 路由的 deps —— 是 PersistencedDeps 与 RemotePersistenceServerDeps 的并集\n * (两者结构相同:stdout? / stderr? / signal?,并集保持兼容)。\n */\nexport type PersistenceDeps = PersistencedDeps & RemotePersistenceServerDeps\n\nexport async function runPersistenceCommand(\n args: string[],\n deps: PersistenceDeps = {},\n): Promise<number> {\n const stdout = deps.stdout ?? ((s) => process.stdout.write(s))\n\n const [subCommand, ...rest] = args\n\n switch (subCommand) {\n case 'local':\n return runPersistencedCommand(rest, deps)\n case 'remote':\n return runRemotePersistenceServerCommand(rest, deps)\n default:\n stdout(`otto persistence — 持久化服务族(local / remote)\n\n用法:\n otto persistence local [...flags] 启动独立持久化服务(storage + events + sync)\n otto persistence remote [...flags] 启动最小化远程持久化服务(5 REST 端点)\n\n子命令说明:\n local 等价于旧 otto persistenced(SQLite + HTTP API,storage/events/sync)\n remote 等价于旧 otto remote-persistence-server(Hono + SQLite,5 REST 端点)\n\n通用选项(详见各子命令 --help):\n --dir, -d <path> 数据目录(必填)\n --token, -t <token> Bearer token(必填)\n --port, -p <n> 监听端口(各子命令有各自默认端口)\n --host, -h <ip> 监听地址(默认 127.0.0.1,0.0.0.0 = 公网暴露)\n\n注:旧命令 otto persistenced / otto remote-persistence-server 仍可用(别名转发)。\n`)\n return 0\n }\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
  function e(e){let t=new Set(e.disabled??[]),n=new Set(e.active.map(e=>e.id)),r=e.discovered.filter(e=>!n.has(e.id)&&!t.has(e.id));return{missingFromRuntime:r,missingBundled:r.filter(e=>e.scope===`bundled`),hasGap:r.length>0}}function t(e){if(!e.hasGap)return[];let t=[];e.missingBundled.length>0&&t.push(`内置扩展未装载:${e.missingBundled.map(e=>e.id).join(`, `)}`,` → 宿主装配参数未传达(RFC-364)。若为嵌入式调用,检查 createApp 的 pluginBundledDirs。`);let n=e.missingFromRuntime.filter(e=>e.scope!==`bundled`);return n.length>0&&t.push(`插件已发现但未激活:${n.map(e=>e.id).join(`, `)}`,` → 可能是依赖缺失/版本不兼容/未信任。查看启动日志的 fail-closed 告警定位具体原因。`),t}export{e as checkPluginAssembly,t as formatAssemblyGap};
3
- //# sourceMappingURL=plugin-assembly-check-Ov17ra7G.js.map
3
+ //# sourceMappingURL=plugin-assembly-check-DEozmcJ7.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-assembly-check-Ov17ra7G.js","names":[],"sources":["../src/plugin-assembly-check.ts"],"sourcesContent":["/**\n * plugin-assembly-check.ts —— RFC-364 M2:装配一致性自检。\n *\n * ## 要解决的问题\n * M1 收敛了装配点,但那只修好了**当前**六处入口。真正的风险是**下一次**——任何新的\n * 装配路径若漏配宿主自描述参数,表现仍然是\"插件静默消失\",用户侧症状是\n * 「Setup Wizard 走完了但模型列表空白」,与真实根因(宿主没告诉 App 自己带了什么)\n * 相距甚远,极难诊断。\n *\n * RFC-364 §4 规则 2:装配缺失必须 fail-loud,不得只写 warn 日志了事。\n *\n * ## 判据\n * 比对两个视图:\n * - **应该可见**:`discoverPluginsWithBundled()`(cli 侧完整发现,含 bundled 层)\n * - **实际装载**:`app.activePlugins()`(App 运行时真正激活的集合)\n *\n * 二者的差集分两类,语义不同、处置不同:\n * - `missingFromRuntime`:发现得到却没装载 → **装配缺口或依赖 fail-closed**,需报告;\n * - 反向差集不检查:App 侧可能因 disabled 配置少装,那是用户意图,不是缺陷。\n *\n * ## 为什么放 cli 而不是 coding\n * 判据依赖 `discoverPluginsWithBundled()`(知道 bundled 目录 = cli 独有知识,见\n * `create-host-app.ts` 头注释论证)。放 coding 会重蹈\"下游包推导宿主布局\"的覆辙。\n *\n * 纯函数 + 端口注入:便于穷举单测,不触真实 App。\n */\n\n/** 一个插件的最小身份(两个视图的公共子集)。 */\nexport interface AssemblyPluginRef {\n id: string\n scope?: string\n}\n\nexport interface AssemblyCheckInput {\n /** cli 侧完整发现的插件(含 bundled 层)。 */\n discovered: readonly AssemblyPluginRef[]\n /** App 运行时实际激活的插件。 */\n active: readonly AssemblyPluginRef[]\n /** 用户显式禁用的插件 id(`disabled_plugins` 配置)——不计入缺口。 */\n disabled?: readonly string[]\n}\n\nexport interface AssemblyCheckResult {\n /** 发现得到但未装载(排除用户显式禁用者)。非空 = 装配不完整。 */\n missingFromRuntime: AssemblyPluginRef[]\n /** 其中属于 bundled 作用域的——这类最可能是\"漏传宿主自描述参数\"的信号。 */\n missingBundled: AssemblyPluginRef[]\n /** 是否存在缺口(供调用方决定是否 fail-loud)。 */\n hasGap: boolean\n}\n\n/**\n * 比对「应该可见」与「实际装载」,给出装配缺口。\n *\n * 用户显式 `disabled` 的插件被排除——那是用户意图,不是装配缺陷。这条很重要:\n * 否则任何禁用过插件的用户都会看到假告警,告警一旦有噪音就会被忽略,\n * 这道门也就失去意义了。\n */\nexport function checkPluginAssembly(input: AssemblyCheckInput): AssemblyCheckResult {\n const disabled = new Set(input.disabled ?? [])\n const activeIds = new Set(input.active.map((p) => p.id))\n\n const missingFromRuntime = input.discovered.filter(\n (p) => !activeIds.has(p.id) && !disabled.has(p.id),\n )\n const missingBundled = missingFromRuntime.filter((p) => p.scope === 'bundled')\n\n return {\n missingFromRuntime,\n missingBundled,\n hasGap: missingFromRuntime.length > 0,\n }\n}\n\n/**\n * 把缺口渲染成面向用户的可执行诊断文本(`otto doctor` 消费)。\n *\n * 措辞指向**根因方向**而非症状:bundled 缺失几乎必然是装配漏配(宿主自描述参数没传到),\n * 其余缺失更可能是依赖 fail-closed(missingDeps/版本门/信任门)。两者给不同的下一步,\n * 避免用户拿着\"插件不见了\"去猜。\n */\nexport function formatAssemblyGap(result: AssemblyCheckResult): string[] {\n if (!result.hasGap) return []\n\n const lines: string[] = []\n if (result.missingBundled.length > 0) {\n lines.push(\n `内置扩展未装载:${result.missingBundled.map((p) => p.id).join(', ')}`,\n ' → 宿主装配参数未传达(RFC-364)。若为嵌入式调用,检查 createApp 的 pluginBundledDirs。',\n )\n }\n const others = result.missingFromRuntime.filter((p) => p.scope !== 'bundled')\n if (others.length > 0) {\n lines.push(\n `插件已发现但未激活:${others.map((p) => p.id).join(', ')}`,\n ' → 可能是依赖缺失/版本不兼容/未信任。查看启动日志的 fail-closed 告警定位具体原因。',\n )\n }\n return lines\n}\n"],"mappings":";AA0DA,SAAgB,EAAoB,EAAgD,CAClF,IAAM,EAAW,IAAI,IAAI,EAAM,UAAY,EAAE,CAAC,CACxC,EAAY,IAAI,IAAI,EAAM,OAAO,IAAK,GAAM,EAAE,GAAG,CAAC,CAElD,EAAqB,EAAM,WAAW,OACzC,GAAM,CAAC,EAAU,IAAI,EAAE,GAAG,EAAI,CAAC,EAAS,IAAI,EAAE,GAAG,CACnD,CAGD,MAAO,CACL,qBACA,eAJqB,EAAmB,OAAQ,GAAM,EAAE,QAAU,UAAU,CAK5E,OAAQ,EAAmB,OAAS,EACrC,CAUH,SAAgB,EAAkB,EAAuC,CACvE,GAAI,CAAC,EAAO,OAAQ,MAAO,EAAE,CAE7B,IAAM,EAAkB,EAAE,CACtB,EAAO,eAAe,OAAS,GACjC,EAAM,KACJ,WAAW,EAAO,eAAe,IAAK,GAAM,EAAE,GAAG,CAAC,KAAK,KAAK,GAC5D,mEACD,CAEH,IAAM,EAAS,EAAO,mBAAmB,OAAQ,GAAM,EAAE,QAAU,UAAU,CAO7E,OANI,EAAO,OAAS,GAClB,EAAM,KACJ,aAAa,EAAO,IAAK,GAAM,EAAE,GAAG,CAAC,KAAK,KAAK,GAC/C,sDACD,CAEI"}
1
+ {"version":3,"file":"plugin-assembly-check-DEozmcJ7.js","names":[],"sources":["../src/plugin-assembly-check.ts"],"sourcesContent":["/**\n * plugin-assembly-check.ts —— RFC-364 M2:装配一致性自检。\n *\n * ## 要解决的问题\n * M1 收敛了装配点,但那只修好了**当前**六处入口。真正的风险是**下一次**——任何新的\n * 装配路径若漏配宿主自描述参数,表现仍然是\"插件静默消失\",用户侧症状是\n * 「Setup Wizard 走完了但模型列表空白」,与真实根因(宿主没告诉 App 自己带了什么)\n * 相距甚远,极难诊断。\n *\n * RFC-364 §4 规则 2:装配缺失必须 fail-loud,不得只写 warn 日志了事。\n *\n * ## 判据\n * 比对两个视图:\n * - **应该可见**:`discoverPluginsWithBundled()`(cli 侧完整发现,含 bundled 层)\n * - **实际装载**:`app.activePlugins()`(App 运行时真正激活的集合)\n *\n * 二者的差集分两类,语义不同、处置不同:\n * - `missingFromRuntime`:发现得到却没装载 → **装配缺口或依赖 fail-closed**,需报告;\n * - 反向差集不检查:App 侧可能因 disabled 配置少装,那是用户意图,不是缺陷。\n *\n * ## 为什么放 cli 而不是 coding\n * 判据依赖 `discoverPluginsWithBundled()`(知道 bundled 目录 = cli 独有知识,见\n * `create-host-app.ts` 头注释论证)。放 coding 会重蹈\"下游包推导宿主布局\"的覆辙。\n *\n * 纯函数 + 端口注入:便于穷举单测,不触真实 App。\n */\n\n/** 一个插件的最小身份(两个视图的公共子集)。 */\nexport interface AssemblyPluginRef {\n id: string\n scope?: string\n}\n\nexport interface AssemblyCheckInput {\n /** cli 侧完整发现的插件(含 bundled 层)。 */\n discovered: readonly AssemblyPluginRef[]\n /** App 运行时实际激活的插件。 */\n active: readonly AssemblyPluginRef[]\n /** 用户显式禁用的插件 id(`disabled_plugins` 配置)——不计入缺口。 */\n disabled?: readonly string[]\n}\n\nexport interface AssemblyCheckResult {\n /** 发现得到但未装载(排除用户显式禁用者)。非空 = 装配不完整。 */\n missingFromRuntime: AssemblyPluginRef[]\n /** 其中属于 bundled 作用域的——这类最可能是\"漏传宿主自描述参数\"的信号。 */\n missingBundled: AssemblyPluginRef[]\n /** 是否存在缺口(供调用方决定是否 fail-loud)。 */\n hasGap: boolean\n}\n\n/**\n * 比对「应该可见」与「实际装载」,给出装配缺口。\n *\n * 用户显式 `disabled` 的插件被排除——那是用户意图,不是装配缺陷。这条很重要:\n * 否则任何禁用过插件的用户都会看到假告警,告警一旦有噪音就会被忽略,\n * 这道门也就失去意义了。\n */\nexport function checkPluginAssembly(input: AssemblyCheckInput): AssemblyCheckResult {\n const disabled = new Set(input.disabled ?? [])\n const activeIds = new Set(input.active.map((p) => p.id))\n\n const missingFromRuntime = input.discovered.filter(\n (p) => !activeIds.has(p.id) && !disabled.has(p.id),\n )\n const missingBundled = missingFromRuntime.filter((p) => p.scope === 'bundled')\n\n return {\n missingFromRuntime,\n missingBundled,\n hasGap: missingFromRuntime.length > 0,\n }\n}\n\n/**\n * 把缺口渲染成面向用户的可执行诊断文本(`otto doctor` 消费)。\n *\n * 措辞指向**根因方向**而非症状:bundled 缺失几乎必然是装配漏配(宿主自描述参数没传到),\n * 其余缺失更可能是依赖 fail-closed(missingDeps/版本门/信任门)。两者给不同的下一步,\n * 避免用户拿着\"插件不见了\"去猜。\n */\nexport function formatAssemblyGap(result: AssemblyCheckResult): string[] {\n if (!result.hasGap) return []\n\n const lines: string[] = []\n if (result.missingBundled.length > 0) {\n lines.push(\n `内置扩展未装载:${result.missingBundled.map((p) => p.id).join(', ')}`,\n ' → 宿主装配参数未传达(RFC-364)。若为嵌入式调用,检查 createApp 的 pluginBundledDirs。',\n )\n }\n const others = result.missingFromRuntime.filter((p) => p.scope !== 'bundled')\n if (others.length > 0) {\n lines.push(\n `插件已发现但未激活:${others.map((p) => p.id).join(', ')}`,\n ' → 可能是依赖缺失/版本不兼容/未信任。查看启动日志的 fail-closed 告警定位具体原因。',\n )\n }\n return lines\n}\n"],"mappings":";AA0DA,SAAgB,EAAoB,EAAgD,CAClF,IAAM,EAAW,IAAI,IAAI,EAAM,UAAY,EAAE,CAAC,CACxC,EAAY,IAAI,IAAI,EAAM,OAAO,IAAK,GAAM,EAAE,GAAG,CAAC,CAElD,EAAqB,EAAM,WAAW,OACzC,GAAM,CAAC,EAAU,IAAI,EAAE,GAAG,EAAI,CAAC,EAAS,IAAI,EAAE,GAAG,CACnD,CAGD,MAAO,CACL,qBACA,eAJqB,EAAmB,OAAQ,GAAM,EAAE,QAAU,UAAU,CAK5E,OAAQ,EAAmB,OAAS,EACrC,CAUH,SAAgB,EAAkB,EAAuC,CACvE,GAAI,CAAC,EAAO,OAAQ,MAAO,EAAE,CAE7B,IAAM,EAAkB,EAAE,CACtB,EAAO,eAAe,OAAS,GACjC,EAAM,KACJ,WAAW,EAAO,eAAe,IAAK,GAAM,EAAE,GAAG,CAAC,KAAK,KAAK,GAC5D,mEACD,CAEH,IAAM,EAAS,EAAO,mBAAmB,OAAQ,GAAM,EAAE,QAAU,UAAU,CAO7E,OANI,EAAO,OAAS,GAClB,EAAM,KACJ,aAAa,EAAO,IAAK,GAAM,EAAE,GAAG,CAAC,KAAK,KAAK,GAC/C,sDACD,CAEI"}
@@ -6,4 +6,4 @@ import{OTTO_SESSION_LOCKS_DIR as e,isContainerEnvironment as t,isProcessAlive as
6
6
  `)}if(t.stdout(`\n${s.length} processes, total RSS ${l(p)}; ${m.length} strong orphan(s), ${h.length} weak orphan(s)\n`),!r)return(m.length>0||h.length>0)&&t.stdout("run `otto ps --sweep` to SIGTERM strong orphans"+(h.length>0?` (--include-weak for weak ones)`:``)+`
7
7
  `),0;let _=[...m,...i?h:[]].filter(e=>e.pid!==f&&e.tty===`??`);if(_.length===0)return t.stdout(`nothing to sweep
8
8
  `),0;let v=0,y=[];for(let e of _)try{o(e.pid),v++,y.push(e),t.stdout(`SIGTERM ${e.pid} (${d(e)}, ${l(e.rssKb)})\n`)}catch(n){t.stderr(`failed to signal ${e.pid}: ${n instanceof Error?n.message:String(n)}\n`)}let b=t.probeAlive??n;await(t.sleep??(e=>new Promise(t=>setTimeout(t,e))))(t.verifyDelayMs??3e3);let x=y.filter(e=>b(e.pid)),S=v-x.length;return t.stdout(`swept ${S}/${_.length}\n`),x.length>0?(t.stdout(`${x.length} process(es) ignored SIGTERM (likely chaperone signal guard): `+x.map(e=>e.pid).join(`, `)+`\nescalate manually if needed: kill -9 ${x.map(e=>e.pid).join(` `)}\n`),1):v===_.length?0:1}export{f as runPsCommand};
9
- //# sourceMappingURL=ps-BovE9XMQ.js.map
9
+ //# sourceMappingURL=ps-DiKCLKbs.js.map