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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/{auth-command-DF_POx4L.js → auth-command--ky3JspQ.js} +2 -2
  2. package/dist/{auth-command-DF_POx4L.js.map → auth-command--ky3JspQ.js.map} +1 -1
  3. package/dist/build-id-C94zHRXj.js +3 -0
  4. package/dist/build-id-C94zHRXj.js.map +1 -0
  5. package/dist/{command-dispatcher-FRiCoN16.js → command-dispatcher-CKj6qR-G.js} +3 -3
  6. package/dist/{command-dispatcher-FRiCoN16.js.map → command-dispatcher-CKj6qR-G.js.map} +1 -1
  7. package/dist/{create-host-app-BgjEc81C.js → create-host-app-BtifD7KK.js} +2 -2
  8. package/dist/{create-host-app-BgjEc81C.js.map → create-host-app-BtifD7KK.js.map} +1 -1
  9. package/dist/{doctor-2AA2A9G7.js → doctor-BAxvhLws.js} +2 -2
  10. package/dist/{doctor-2AA2A9G7.js.map → doctor-BAxvhLws.js.map} +1 -1
  11. package/dist/{feedback-BsU8b1k9.js → feedback-CJVKfI0S.js} +2 -2
  12. package/dist/{feedback-BsU8b1k9.js.map → feedback-CJVKfI0S.js.map} +1 -1
  13. package/dist/{feedback-backend-methods-C2SzcIzm.js → feedback-backend-methods-C23Z2suw.js} +2 -2
  14. package/dist/{feedback-backend-methods-C2SzcIzm.js.map → feedback-backend-methods-C23Z2suw.js.map} +1 -1
  15. package/dist/index.js +11 -11
  16. package/dist/index.js.map +1 -1
  17. package/dist/{interactive-schedule-aQEHfUKL.js → interactive-schedule-BKiI3Qrw.js} +2 -2
  18. package/dist/{interactive-schedule-aQEHfUKL.js.map → interactive-schedule-BKiI3Qrw.js.map} +1 -1
  19. package/dist/{mcp-server-C2bw9J2i.js → mcp-server-CfJK4pav.js} +2 -2
  20. package/dist/{mcp-server-C2bw9J2i.js.map → mcp-server-CfJK4pav.js.map} +1 -1
  21. package/dist/{parse-cli-C1r2cQ0K.js → parse-cli-B9Go-f6A.js} +2 -2
  22. package/dist/{parse-cli-C1r2cQ0K.js.map → parse-cli-B9Go-f6A.js.map} +1 -1
  23. package/dist/{serve-DHqx-Mab.js → serve-ChSSkjLs.js} +2 -2
  24. package/dist/{serve-DHqx-Mab.js.map → serve-ChSSkjLs.js.map} +1 -1
  25. package/dist/{version-CdzXY8m2.js → version-BL66TSQP.js} +2 -2
  26. package/dist/{version-CdzXY8m2.js.map → version-BL66TSQP.js.map} +1 -1
  27. package/package.json +16 -16
  28. package/dist/build-id-BmQPXMGN.js +0 -3
  29. package/dist/build-id-BmQPXMGN.js.map +0 -1
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{at as e}from"./command-dispatcher-FRiCoN16.js";import{n as t}from"./schedule-Ke0jwatM.js";import{t as n}from"./backend-handler-DUafuNWw.js";import{globalAgentJobRegistry as r}from"@x-otto/runtime";import{DEFAULT_SCHEDULE_CONFIG as i,FireLogger as a,InProcessEventTrigger as o,JobFireTarget as s,ScheduleRegistry as c,SchedulerService as l,SessionFireTarget as u,SubscriberFireTarget as d,createLocalFireOwnership as f,createRemoteFireOwnership as p,createRemoteScheduleStore as m,createScheduleStore as h,parseCron as g}from"@x-otto/schedule";import{z as _}from"zod";function v(e){let t=()=>({tasks:e.list(),runningTaskIds:[...e.getRunningTaskIds()],isFireOwner:e.isFireOwner(),fireOwnerFailureReason:e.fireOwnerFailureReason()});return{...n({"schedule.list":{params:_.object({}),run:async()=>t()},"schedule.add":{params:_.object({name:_.string(),prompt:_.string(),cronExpression:_.string(),recurring:_.boolean(),target:_.enum([`job`,`session`]).optional()}),run:async t=>{try{let n=e.add({name:t.name,prompt:t.prompt,cronExpression:t.cronExpression,recurring:t.recurring,origin:`user`,target:t.target});return{ok:!0,taskId:n.id,name:n.name,target:n.target}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}},"schedule.update":{params:_.object({taskId:_.string(),name:_.string().optional(),prompt:_.string().optional(),cronExpression:_.string().optional(),enabled:_.boolean().optional()}),run:async t=>{let n={};return t.name!==void 0&&(n.name=t.name),t.prompt!==void 0&&(n.prompt=t.prompt),t.cronExpression!==void 0&&(n.cronExpression=t.cronExpression),t.enabled!==void 0&&(n.enabled=t.enabled),{ok:e.update(t.taskId,n)}}},"schedule.runNow":{params:_.object({taskId:_.string()}),run:async t=>(e.runNow(t.taskId),{ok:!0})},"schedule.toggle":{params:_.object({taskId:_.string(),enabled:_.boolean()}),run:async t=>({ok:e.update(t.taskId,{enabled:t.enabled})})},"schedule.cancel":{params:_.object({taskId:_.string()}),run:async t=>e.cancel(t.taskId)},"schedule.remove":{params:_.object({taskId:_.string()}),run:async t=>({ok:e.remove(t.taskId)})},"schedule.readLogs":{params:_.object({taskId:_.string(),limit:_.number().optional()}),run:async t=>({entries:e.readFireLogs(t.taskId,t.limit??20)})},"schedule.parseCron":{params:_.object({expr:_.string()}),run:async e=>{try{return{ok:!0,next:g(e.expr).toISOString()}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}}},`schedule`),getSnapshot:t}}var y=e({createFollowUpCurrentSession:()=>b,setupScheduleService:()=>x});function b(e){return t=>{let n=e.value;if(n.id!==t.boundSessionId||n.isPaused||n.status===`archived`)return!1;try{return n.prompt(t.prompt,{streamingBehavior:`followUp`}).catch(()=>{}),!0}catch{return!1}}}function x(e,n){let{sessionRef:g,tui:_,remoteConfig:y}=n,x=e.workspaceRef?.key??`default`,S=new c,C,w;if(e.storage.remoteSessionConfigured&&y){let e=async()=>({token:y.sessionToken});C=m({sessionUrl:y.sessionUrl,getAuth:e,wsKey:x}),w=p({sessionUrl:y.sessionUrl,wsKey:x,getAuth:e})}else C=h(x),w=f(x);let T=new a(i),E=[];g&&E.push(new d({notify:(t,n)=>e.notifyPluginServiceSchedule({pluginId:t.pluginId,serviceId:t.serviceId},n),fireLogger:T}),new u({followUp:b(g),getSessionId:()=>g.value.id,fireLogger:T})),E.push(new s({jobRegistry:r,startJob:t=>({id:e.agentJobs.start({title:t.title,prompt:t.prompt,sessionId:t.sessionId,origin:t.origin,...t.model?{model:t.model}:{}}).id}),...g?{getSessionId:()=>g.value.id}:{},retryBackoffMs:e.getResilienceConfig().schedule.retryBackoffMs,fireLogger:T,needsInputMaxExtensionFactor:i.needsInputMaxExtensionFactor}));let D=new l({registry:S,store:C,ownership:w,fireTargets:E,triggers:[new o({jobRegistry:r})]});D.start().then(()=>{if(!g){let t=D.list().filter(e=>e.target===`session`||!!e.subscriber).length;if(t>0){let n=`${t} 个任务(session/subscriber 类)在 daemon 无人值守形态不会投递,仅 job 类任务执行`;e.logger?.warn?.({count:t},`[schedule] headless mode: non-job tasks skipped`),new a(i).write(`daemon-scheduler`,`headless aggregate warning: ${n}`)}}}).catch(t=>{e.logger?.warn?.({err:t instanceof Error?t.message:String(t)},`[schedule] scheduler failed to start`)}),t(D),e.setScheduleService(D);let O=v(D);if(e.panelBackendController.registerInProcessBackend(`plugin-schedule`,O.call),!_)return{pollInterval:void 0,scheduler:D};let k=setInterval(()=>{if(!_)return;let e=D.list().map(e=>({id:e.id,name:e.name,prompt:e.prompt,cronExpression:e.cronExpression,enabled:e.enabled,recurring:e.recurring,nextFireAt:e.nextFireAt,lastFiredAt:e.lastFiredAt,lastFireFailed:e.lastFireFailed,lastFireError:e.lastFireError,lastFireCancelled:e.lastFireCancelled}));_.stores.tasks.setScheduleTasks(e)},5e3);return k.unref(),{pollInterval:k,scheduler:D}}export{x as n,y as t};
3
- //# sourceMappingURL=interactive-schedule-aQEHfUKL.js.map
2
+ import{ot as e}from"./command-dispatcher-CKj6qR-G.js";import{n as t}from"./schedule-Ke0jwatM.js";import{t as n}from"./backend-handler-DUafuNWw.js";import{globalAgentJobRegistry as r}from"@x-otto/runtime";import{DEFAULT_SCHEDULE_CONFIG as i,FireLogger as a,InProcessEventTrigger as o,JobFireTarget as s,ScheduleRegistry as c,SchedulerService as l,SessionFireTarget as u,SubscriberFireTarget as d,createLocalFireOwnership as f,createRemoteFireOwnership as p,createRemoteScheduleStore as m,createScheduleStore as h,parseCron as g}from"@x-otto/schedule";import{z as _}from"zod";function v(e){let t=()=>({tasks:e.list(),runningTaskIds:[...e.getRunningTaskIds()],isFireOwner:e.isFireOwner(),fireOwnerFailureReason:e.fireOwnerFailureReason()});return{...n({"schedule.list":{params:_.object({}),run:async()=>t()},"schedule.add":{params:_.object({name:_.string(),prompt:_.string(),cronExpression:_.string(),recurring:_.boolean(),target:_.enum([`job`,`session`]).optional()}),run:async t=>{try{let n=e.add({name:t.name,prompt:t.prompt,cronExpression:t.cronExpression,recurring:t.recurring,origin:`user`,target:t.target});return{ok:!0,taskId:n.id,name:n.name,target:n.target}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}},"schedule.update":{params:_.object({taskId:_.string(),name:_.string().optional(),prompt:_.string().optional(),cronExpression:_.string().optional(),enabled:_.boolean().optional()}),run:async t=>{let n={};return t.name!==void 0&&(n.name=t.name),t.prompt!==void 0&&(n.prompt=t.prompt),t.cronExpression!==void 0&&(n.cronExpression=t.cronExpression),t.enabled!==void 0&&(n.enabled=t.enabled),{ok:e.update(t.taskId,n)}}},"schedule.runNow":{params:_.object({taskId:_.string()}),run:async t=>(e.runNow(t.taskId),{ok:!0})},"schedule.toggle":{params:_.object({taskId:_.string(),enabled:_.boolean()}),run:async t=>({ok:e.update(t.taskId,{enabled:t.enabled})})},"schedule.cancel":{params:_.object({taskId:_.string()}),run:async t=>e.cancel(t.taskId)},"schedule.remove":{params:_.object({taskId:_.string()}),run:async t=>({ok:e.remove(t.taskId)})},"schedule.readLogs":{params:_.object({taskId:_.string(),limit:_.number().optional()}),run:async t=>({entries:e.readFireLogs(t.taskId,t.limit??20)})},"schedule.parseCron":{params:_.object({expr:_.string()}),run:async e=>{try{return{ok:!0,next:g(e.expr).toISOString()}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}}},`schedule`),getSnapshot:t}}var y=e({createFollowUpCurrentSession:()=>b,setupScheduleService:()=>x});function b(e){return t=>{let n=e.value;if(n.id!==t.boundSessionId||n.isPaused||n.status===`archived`)return!1;try{return n.prompt(t.prompt,{streamingBehavior:`followUp`}).catch(()=>{}),!0}catch{return!1}}}function x(e,n){let{sessionRef:g,tui:_,remoteConfig:y}=n,x=e.workspaceRef?.key??`default`,S=new c,C,w;if(e.storage.remoteSessionConfigured&&y){let e=async()=>({token:y.sessionToken});C=m({sessionUrl:y.sessionUrl,getAuth:e,wsKey:x}),w=p({sessionUrl:y.sessionUrl,wsKey:x,getAuth:e})}else C=h(x),w=f(x);let T=new a(i),E=[];g&&E.push(new d({notify:(t,n)=>e.notifyPluginServiceSchedule({pluginId:t.pluginId,serviceId:t.serviceId},n),fireLogger:T}),new u({followUp:b(g),getSessionId:()=>g.value.id,fireLogger:T})),E.push(new s({jobRegistry:r,startJob:t=>({id:e.agentJobs.start({title:t.title,prompt:t.prompt,sessionId:t.sessionId,origin:t.origin,...t.model?{model:t.model}:{}}).id}),...g?{getSessionId:()=>g.value.id}:{},retryBackoffMs:e.getResilienceConfig().schedule.retryBackoffMs,fireLogger:T,needsInputMaxExtensionFactor:i.needsInputMaxExtensionFactor}));let D=new l({registry:S,store:C,ownership:w,fireTargets:E,triggers:[new o({jobRegistry:r})]});D.start().then(()=>{if(!g){let t=D.list().filter(e=>e.target===`session`||!!e.subscriber).length;if(t>0){let n=`${t} 个任务(session/subscriber 类)在 daemon 无人值守形态不会投递,仅 job 类任务执行`;e.logger?.warn?.({count:t},`[schedule] headless mode: non-job tasks skipped`),new a(i).write(`daemon-scheduler`,`headless aggregate warning: ${n}`)}}}).catch(t=>{e.logger?.warn?.({err:t instanceof Error?t.message:String(t)},`[schedule] scheduler failed to start`)}),t(D),e.setScheduleService(D);let O=v(D);if(e.panelBackendController.registerInProcessBackend(`plugin-schedule`,O.call),!_)return{pollInterval:void 0,scheduler:D};let k=setInterval(()=>{if(!_)return;let e=D.list().map(e=>({id:e.id,name:e.name,prompt:e.prompt,cronExpression:e.cronExpression,enabled:e.enabled,recurring:e.recurring,nextFireAt:e.nextFireAt,lastFiredAt:e.lastFiredAt,lastFireFailed:e.lastFireFailed,lastFireError:e.lastFireError,lastFireCancelled:e.lastFireCancelled}));_.stores.tasks.setScheduleTasks(e)},5e3);return k.unref(),{pollInterval:k,scheduler:D}}export{x as n,y as t};
3
+ //# sourceMappingURL=interactive-schedule-BKiI3Qrw.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"interactive-schedule-aQEHfUKL.js","names":[],"sources":["../src/commands/schedule-backend-methods.ts","../src/commands/interactive-schedule.ts"],"sourcesContent":["/**\n * backend-methods.ts —— RFC-349:schedule 面板 in-process backend methods。\n *\n * 与 browser-use/mobile-use 的区别:那些插件的 backend methods 跑在 MCP server 子进程里。\n * schedule 的 SchedulerService 依赖 `AgentJobService`/`sessionRef`/`notifySubscriber`——\n * 这些只能存活在 App 主进程,无法搬到子进程。故本文件注册为 **in-process backend handler**\n * (经 `PanelBackendController.registerInProcessBackend`),面板 `usePanelBackend().call()`\n * 走同一条路径,但执行发生在 App 主进程内。\n *\n * RFC-439:分发收敛为类型化注册表(backend-handler.ts)——方法 = zod schema + run,\n * 参数校验/前缀剥离/未知方法 fail-closed 由注册表统一承担,不再手写 switch/双重断言。\n *\n * 面板侧调用经 `toBackendWireMethod` 统一加 `backend/` 前缀,注册表接收时剥离前缀。\n */\nimport { z } from 'zod'\nimport type { SchedulerService } from '@x-otto/schedule'\nimport { parseCron } from '@x-otto/schedule'\nimport type { ScheduledTask } from '@x-otto/schedule'\nimport { createBackendHandler, type BackendMethod } from './backend-handler'\n\n/** 面板状态快照(经 pushState 推送或 call 返回)。 */\nexport interface SchedulePanelState {\n tasks: ScheduledTask[]\n runningTaskIds: string[]\n isFireOwner: boolean\n fireOwnerFailureReason: string | undefined\n}\n\nexport function createScheduleBackendHandler(scheduler: SchedulerService): {\n call: (method: string, params: Record<string, unknown>) => Promise<unknown>\n getSnapshot: () => SchedulePanelState\n} {\n const getSnapshot = (): SchedulePanelState => ({\n tasks: scheduler.list(),\n runningTaskIds: [...scheduler.getRunningTaskIds()],\n isFireOwner: scheduler.isFireOwner(),\n fireOwnerFailureReason: scheduler.fireOwnerFailureReason(),\n })\n\n // RFC-439:satisfies 保留各方法的 zod infer(不用默认泛型宽化致 run 参数塌成 unknown)。\n const methods = {\n 'schedule.list': {\n params: z.object({}),\n run: async () => getSnapshot(),\n },\n 'schedule.add': {\n params: z.object({\n name: z.string(),\n prompt: z.string(),\n cronExpression: z.string(),\n recurring: z.boolean(),\n // RFC-357 D3 (M2): optional session-local target, requires fire ownership.\n target: z.enum(['job', 'session']).optional(),\n }),\n run: async (params: {\n name: string\n prompt: string\n cronExpression: string\n recurring: boolean\n target?: 'job' | 'session'\n }) => {\n // RFC-357 D2: scheduler.add() throws when target:'session' is requested without\n // fire ownership — surface that as a structured error, not an unhandled rejection.\n try {\n const task = scheduler.add({\n name: params.name,\n prompt: params.prompt,\n cronExpression: params.cronExpression,\n recurring: params.recurring,\n origin: 'user' as const,\n target: params.target,\n })\n return { ok: true, taskId: task.id, name: task.name, target: task.target }\n } catch (err) {\n return { ok: false, error: err instanceof Error ? err.message : String(err) }\n }\n },\n },\n\n 'schedule.update': {\n params: z.object({\n taskId: z.string(),\n name: z.string().optional(),\n prompt: z.string().optional(),\n cronExpression: z.string().optional(),\n enabled: z.boolean().optional(),\n }),\n run: async (params: {\n taskId: string\n name?: string\n prompt?: string\n cronExpression?: string\n enabled?: boolean\n }) => {\n const patch: { name?: string; prompt?: string; cronExpression?: string; enabled?: boolean } = {}\n if (params.name !== undefined) patch.name = params.name\n if (params.prompt !== undefined) patch.prompt = params.prompt\n if (params.cronExpression !== undefined) patch.cronExpression = params.cronExpression\n if (params.enabled !== undefined) patch.enabled = params.enabled\n const ok = scheduler.update(params.taskId, patch)\n return { ok }\n },\n },\n\n 'schedule.runNow': {\n params: z.object({ taskId: z.string() }),\n run: async (params: { taskId: string }) => {\n scheduler.runNow(params.taskId)\n return { ok: true }\n },\n },\n\n 'schedule.toggle': {\n params: z.object({ taskId: z.string(), enabled: z.boolean() }),\n run: async (params: { taskId: string; enabled: boolean }) => {\n const ok = scheduler.update(params.taskId, { enabled: params.enabled })\n return { ok }\n },\n },\n\n 'schedule.cancel': {\n params: z.object({ taskId: z.string() }),\n run: async (params: { taskId: string }) => scheduler.cancel(params.taskId),\n },\n\n 'schedule.remove': {\n params: z.object({ taskId: z.string() }),\n run: async (params: { taskId: string }) => {\n const removed = scheduler.remove(params.taskId)\n return { ok: removed }\n },\n },\n\n 'schedule.readLogs': {\n params: z.object({ taskId: z.string(), limit: z.number().optional() }),\n run: async (params: { taskId: string; limit?: number }) => {\n const entries = scheduler.readFireLogs(params.taskId, params.limit ?? 20)\n return { entries }\n },\n },\n\n 'schedule.parseCron': {\n params: z.object({ expr: z.string() }),\n run: async (params: { expr: string }) => {\n try {\n const next = parseCron(params.expr)\n return { ok: true, next: next.toISOString() }\n } catch (err) {\n return { ok: false, error: err instanceof Error ? err.message : String(err) }\n }\n },\n },\n } satisfies Record<string, BackendMethod>\n\n return { ...createBackendHandler(methods, 'schedule'), getSnapshot }\n}\n","/**\n * 定时任务 TUI 交互管线(状态栏/事件/toast 接线)。\n */\n// RFC-161 M2:SchedulerService 等实现已迁至 @x-otto/schedule,直连新真源\n// (coding 的 re-export shim 仅供尚未迁移的其余消费方使用)。\nimport { SchedulerService, ScheduleRegistry, createScheduleStore, createLocalFireOwnership, createRemoteScheduleStore, createRemoteFireOwnership, FireLogger, DEFAULT_SCHEDULE_CONFIG, JobFireTarget, SessionFireTarget, SubscriberFireTarget, InProcessEventTrigger } from '@x-otto/schedule'\nimport { globalAgentJobRegistry } from '@x-otto/runtime'\nimport { setScheduleService } from './handlers/schedule'\nimport { createScheduleBackendHandler } from './schedule-backend-methods'\n\nimport type { App, AgentSession } from '@x-otto/coding'\nimport type { ScheduleStore, FireOwnership, FireTarget } from '@x-otto/schedule'\nimport type { TuiHandle } from '@x-otto/tui'\n\n/** Remote connection details, threaded from CLIOptions.sessionUrl/sessionToken. */\nexport interface ScheduleRemoteConfig {\n sessionUrl: string\n sessionToken: string\n}\n\n/**\n * RFC-357 D1/D2(M2):`SchedulerDeps.followUpCurrentSession` 的真实实现——把 session-local\n * 任务的 prompt 注入 `boundSessionId` 对应的当前会话。\n *\n * 三重失效校验(均转 `false`,不抛错——调度器侧 `fireSession()` 把异常/false 统一折叠为\n * fire 失败记日志,不重试,同 `notifySubscriber` 缺省行为的旁路信号纪律):\n *\n * 1. **会话身份不匹配**(独立评审 R1):`sessionRef` 是活引用,RFC-234 attach/detach 会\n * 在同一进程内重新赋值它——若 `boundSessionId` 与当前 `sessionRef.value.id` 不等,说明\n * 用户已切到别的会话,绝不能把内容误投进一个不相干的对话。\n * 2. **paused 态**(D1):暂停会话收到注入会被 `prompt()` 判定为 idle 直接起新回合,\n * 这实质上绕过了用户暂停会话的意图——先查 `isPaused` 拒绝。\n * 3. **archived 态 / 会话已销毁**(D1):`AgentSession.status` 是同步 getter,先在此\n * 同步判断 `'archived'` 拒绝(M3 e2e 测试发现的修订——`prompt()` 是 `async` 函数,\n * 其内部对 archived 态的检查会抛出**被拒绝的 Promise** 而非同步异常;`void\n * session.prompt(...)` 外面包一层 `try/catch` 完全捕获不到它,会变成未处理的\n * promise rejection,在真实调度场景下会让 Node 进程崩溃)。为兜底 `ensureNotDisposed()`\n * 等其他潜在的异步失效路径,仍链一个 `.catch()`(fire-and-forget,不 await、不重试,\n * 只是防止任何未预见的异步异常变成未处理 rejection)。\n */\nexport function createFollowUpCurrentSession(\n sessionRef: { readonly value: AgentSession },\n): (task: { prompt: string; boundSessionId: string; name: string; origin: 'user' | 'model' }) => boolean {\n return (task) => {\n const session = sessionRef.value\n\n if (session.id !== task.boundSessionId) return false\n if (session.isPaused) return false\n if (session.status === 'archived') return false\n\n try {\n // 不 await:fire() 是 fire-and-forget 的旁路信号语义(同 fireSubscriber),\n // prompt() 的 busy/idle 路由与错误处理已在 AgentSession 内部完成;这里只需\n // 同步判断\"是否成功发起了注入\"(streamingBehavior:'followUp' 使 prompt()\n // 同步完成入队/发起,不阻塞到回合真正跑完)。`.catch()` 兜底任何异步异常\n // (见上方文档注释——不能只靠 try/catch,那只捕获同步抛出)。\n void session.prompt(task.prompt, { streamingBehavior: 'followUp' }).catch(() => {})\n return true\n } catch {\n // 理论上不会走到这——保留作为同步异常的最后防线。\n return false\n }\n }\n}\n\nexport interface ScheduleServiceSetupOptions {\n /** 交互会话(TUI 装配传;serve/daemon 无人值守装配不传——job-only 模式)。 */\n sessionRef?: { readonly value: AgentSession }\n /** TUI 句柄(仅交互模式;缺失时跳过 5s taskStore 轮询)。 */\n tui?: TuiHandle\n remoteConfig?: ScheduleRemoteConfig\n}\n\nexport function setupScheduleService(\n app: App,\n opts: ScheduleServiceSetupOptions,\n): { pollInterval: ReturnType<typeof setInterval> | undefined; scheduler: SchedulerService } {\n const { sessionRef, tui, remoteConfig } = opts\n // Wire schedule service for /schedule command + cron tools\n const workspaceKey = app.workspaceRef?.key ?? 'default'\n const scheduleRegistry = new ScheduleRegistry()\n\n let scheduleStore: ScheduleStore\n let scheduleOwnership: FireOwnership\n if (app.storage.remoteSessionConfigured && remoteConfig) {\n const getAuth = async (): Promise<{ token: string }> => ({ token: remoteConfig.sessionToken })\n scheduleStore = createRemoteScheduleStore({\n sessionUrl: remoteConfig.sessionUrl,\n getAuth,\n wsKey: workspaceKey,\n })\n scheduleOwnership = createRemoteFireOwnership({\n sessionUrl: remoteConfig.sessionUrl,\n wsKey: workspaceKey,\n getAuth,\n })\n } else {\n scheduleStore = createScheduleStore(workspaceKey)\n scheduleOwnership = createLocalFireOwnership(workspaceKey)\n }\n\n // RFC-406 D7:投递目标从 SchedulerDeps 内联字段拆为 FireTarget handler 列表。\n // 注册顺序即优先级:subscriber > session > job(保持旧 fire() 分支优先级)。\n //\n // RFC-435 R1(独立核对修复②):SubscriberFireTarget **仅在有 sessionRef 的交互形态注册**。\n // 此前无条件注册使 headless(serve/daemon)实际装配 = Subscriber+Job 两件,违背 R1\n // 「daemon 形态只装配 JobFireTarget」与用户 grill 确认的范围收敛(方案 A:只放 job)。\n // 插件 schedules 声明的 subscriber 任务在 headless 形态走 fire log 降级(D3),\n // 不投递——与「daemon 不启用 subscriber 投递」的决策一致(RFC §2 非目标)。\n const fireLogger = new FireLogger(DEFAULT_SCHEDULE_CONFIG)\n const fireTargets: FireTarget[] = []\n\n if (sessionRef) {\n fireTargets.push(\n // RFC-327 D2:订阅者到点通知的窄端口——scheduler 只交出纯数据标识,\n // 由 App 决定投给哪个运行中的 service(并强制\"插件仍激活\"这一 revoke 语义)。\n // 该路径**不开会话授权 epoch**(schedule.tick 无 sessionId),见 App 侧注释。\n new SubscriberFireTarget({\n notify: (subscriber, tick) =>\n app.notifyPluginServiceSchedule(\n { pluginId: subscriber.pluginId, serviceId: subscriber.serviceId },\n tick,\n ),\n fireLogger,\n }),\n // RFC-357 D1/D2(M2):session-local 任务到点后的投递端口 + 创建时绑定的 sessionId。\n new SessionFireTarget({\n followUp: createFollowUpCurrentSession(sessionRef),\n getSessionId: () => sessionRef.value.id,\n fireLogger,\n }),\n )\n }\n\n // RFC-435 D1:job 类任务不依赖交互会话(detached run + worktree 隔离),\n // 交互与无人值守形态都注册。getSessionId 仅在有 sessionRef 时提供——\n // 无人值守(serve/daemon)缺省,job 任务创建时不绑定 session(target 非 session)。\n fireTargets.push(\n new JobFireTarget({\n jobRegistry: globalAgentJobRegistry,\n startJob: (input) => ({\n id: app.agentJobs.start({\n title: input.title,\n prompt: input.prompt,\n sessionId: input.sessionId,\n origin: input.origin,\n // RFC-438:per-job 模型指定透传(task.modelId/thinkingLevel)\n ...(input.model ? { model: input.model } : {}),\n }).id,\n }),\n ...(sessionRef ? { getSessionId: () => sessionRef.value.id } : {}),\n // RFC-230:本函数在 TUI 启动流程内被调用,晚于 app.start()(settings 已 load),\n // getResilienceConfig() 调用安全。真实构造点(非 packages/coding App 内部),\n // 见 RFC-230 §3 D6 Scheduler 小节二轮评审 P2 修正。\n retryBackoffMs: app.getResilienceConfig().schedule.retryBackoffMs,\n fireLogger,\n needsInputMaxExtensionFactor: DEFAULT_SCHEDULE_CONFIG.needsInputMaxExtensionFactor,\n }),\n )\n\n const scheduler = new SchedulerService({\n registry: scheduleRegistry,\n store: scheduleStore,\n ownership: scheduleOwnership,\n fireTargets,\n // RFC-423:进程内事件触发器——监听 AgentJobRegistry 终态(如 job diff applied),\n // 事件满足时把 prompt 注入当前对话(配 target:'session')。三重死循环闸门内建\n // (自产 job 排除 + add() 拒绝 job 投递 + 频率 debounce)。无人值守(无 sessionRef)\n // 时事件触发投递目标无会话——fire 时跳过并记日志(fireLogger),见 RFC-435 D2。\n triggers: [new InProcessEventTrigger({ jobRegistry: globalAgentJobRegistry })],\n })\n // Non-critical background feature — a bootstrap failure here must not take down\n // the whole interactive session (fire-and-forget start() would otherwise surface\n // as an unhandled rejection and crash the process).\n void scheduler.start().then(() => {\n // RFC-435 D3 聚合告警(独立核对修复①):必须在 start() **完成**后检查——start()\n // 是 async(首个 await tryAcquire()),此前同步 list() 拿到的是空 registry,\n // nonJobCount 恒为 0,告警是生产死代码(评审 A.3 坐实)。移到 then() 内,\n // registry 已加载,计数真实。\n if (!sessionRef) {\n const nonJobCount = scheduler.list().filter((t) => t.target === 'session' || !!t.subscriber).length\n if (nonJobCount > 0) {\n const msg = `${nonJobCount} 个任务(session/subscriber 类)在 daemon 无人值守形态不会投递,仅 job 类任务执行`\n app.logger?.warn?.({ count: nonJobCount }, '[schedule] headless mode: non-job tasks skipped')\n new FireLogger(DEFAULT_SCHEDULE_CONFIG).write('daemon-scheduler', `headless aggregate warning: ${msg}`)\n }\n }\n }).catch((err) => {\n // RFC-435 R9:失败日志走 logger 而非 console(serve 无人值守场景 console 无观察者)。\n app.logger?.warn?.({ err: err instanceof Error ? err.message : String(err) }, '[schedule] scheduler failed to start')\n })\n setScheduleService(scheduler)\n app.setScheduleService(scheduler) // A1: let cron_* model tools reach this scheduler\n\n // RFC-349 M1:注册 in-process backend handler——面板经 usePanelBackend().call() 访问 scheduler\n const scheduleBackend = createScheduleBackendHandler(scheduler)\n app.panelBackendController.registerInProcessBackend('plugin-schedule', scheduleBackend.call)\n\n // Push schedule tasks to TUI store every 5 seconds (status bar / task list).\n // RFC-349 M2: panel data is now fetched via backend.call('schedule.list') —\n // this interval only updates the TUI taskStore for status bar display.\n // RFC-435 D1:serve/daemon 无 tui → 不建轮询(无人值守无状态栏消费方)。\n if (!tui) return { pollInterval: undefined, scheduler }\n\n const pollInterval = setInterval(() => {\n if (!tui) return\n const tasks = scheduler.list()\n const taskSummaries = tasks.map((t) => ({\n id: t.id,\n name: t.name,\n prompt: t.prompt,\n cronExpression: t.cronExpression,\n enabled: t.enabled,\n recurring: t.recurring,\n nextFireAt: t.nextFireAt,\n lastFiredAt: t.lastFiredAt,\n lastFireFailed: t.lastFireFailed,\n lastFireError: t.lastFireError,\n lastFireCancelled: t.lastFireCancelled,\n }))\n tui.stores.tasks.setScheduleTasks(taskSummaries)\n }, 5000)\n pollInterval.unref()\n\n return { pollInterval, scheduler }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"interactive-schedule-BKiI3Qrw.js","names":[],"sources":["../src/commands/schedule-backend-methods.ts","../src/commands/interactive-schedule.ts"],"sourcesContent":["/**\n * backend-methods.ts —— RFC-349:schedule 面板 in-process backend methods。\n *\n * 与 browser-use/mobile-use 的区别:那些插件的 backend methods 跑在 MCP server 子进程里。\n * schedule 的 SchedulerService 依赖 `AgentJobService`/`sessionRef`/`notifySubscriber`——\n * 这些只能存活在 App 主进程,无法搬到子进程。故本文件注册为 **in-process backend handler**\n * (经 `PanelBackendController.registerInProcessBackend`),面板 `usePanelBackend().call()`\n * 走同一条路径,但执行发生在 App 主进程内。\n *\n * RFC-439:分发收敛为类型化注册表(backend-handler.ts)——方法 = zod schema + run,\n * 参数校验/前缀剥离/未知方法 fail-closed 由注册表统一承担,不再手写 switch/双重断言。\n *\n * 面板侧调用经 `toBackendWireMethod` 统一加 `backend/` 前缀,注册表接收时剥离前缀。\n */\nimport { z } from 'zod'\nimport type { SchedulerService } from '@x-otto/schedule'\nimport { parseCron } from '@x-otto/schedule'\nimport type { ScheduledTask } from '@x-otto/schedule'\nimport { createBackendHandler, type BackendMethod } from './backend-handler'\n\n/** 面板状态快照(经 pushState 推送或 call 返回)。 */\nexport interface SchedulePanelState {\n tasks: ScheduledTask[]\n runningTaskIds: string[]\n isFireOwner: boolean\n fireOwnerFailureReason: string | undefined\n}\n\nexport function createScheduleBackendHandler(scheduler: SchedulerService): {\n call: (method: string, params: Record<string, unknown>) => Promise<unknown>\n getSnapshot: () => SchedulePanelState\n} {\n const getSnapshot = (): SchedulePanelState => ({\n tasks: scheduler.list(),\n runningTaskIds: [...scheduler.getRunningTaskIds()],\n isFireOwner: scheduler.isFireOwner(),\n fireOwnerFailureReason: scheduler.fireOwnerFailureReason(),\n })\n\n // RFC-439:satisfies 保留各方法的 zod infer(不用默认泛型宽化致 run 参数塌成 unknown)。\n const methods = {\n 'schedule.list': {\n params: z.object({}),\n run: async () => getSnapshot(),\n },\n 'schedule.add': {\n params: z.object({\n name: z.string(),\n prompt: z.string(),\n cronExpression: z.string(),\n recurring: z.boolean(),\n // RFC-357 D3 (M2): optional session-local target, requires fire ownership.\n target: z.enum(['job', 'session']).optional(),\n }),\n run: async (params: {\n name: string\n prompt: string\n cronExpression: string\n recurring: boolean\n target?: 'job' | 'session'\n }) => {\n // RFC-357 D2: scheduler.add() throws when target:'session' is requested without\n // fire ownership — surface that as a structured error, not an unhandled rejection.\n try {\n const task = scheduler.add({\n name: params.name,\n prompt: params.prompt,\n cronExpression: params.cronExpression,\n recurring: params.recurring,\n origin: 'user' as const,\n target: params.target,\n })\n return { ok: true, taskId: task.id, name: task.name, target: task.target }\n } catch (err) {\n return { ok: false, error: err instanceof Error ? err.message : String(err) }\n }\n },\n },\n\n 'schedule.update': {\n params: z.object({\n taskId: z.string(),\n name: z.string().optional(),\n prompt: z.string().optional(),\n cronExpression: z.string().optional(),\n enabled: z.boolean().optional(),\n }),\n run: async (params: {\n taskId: string\n name?: string\n prompt?: string\n cronExpression?: string\n enabled?: boolean\n }) => {\n const patch: { name?: string; prompt?: string; cronExpression?: string; enabled?: boolean } = {}\n if (params.name !== undefined) patch.name = params.name\n if (params.prompt !== undefined) patch.prompt = params.prompt\n if (params.cronExpression !== undefined) patch.cronExpression = params.cronExpression\n if (params.enabled !== undefined) patch.enabled = params.enabled\n const ok = scheduler.update(params.taskId, patch)\n return { ok }\n },\n },\n\n 'schedule.runNow': {\n params: z.object({ taskId: z.string() }),\n run: async (params: { taskId: string }) => {\n scheduler.runNow(params.taskId)\n return { ok: true }\n },\n },\n\n 'schedule.toggle': {\n params: z.object({ taskId: z.string(), enabled: z.boolean() }),\n run: async (params: { taskId: string; enabled: boolean }) => {\n const ok = scheduler.update(params.taskId, { enabled: params.enabled })\n return { ok }\n },\n },\n\n 'schedule.cancel': {\n params: z.object({ taskId: z.string() }),\n run: async (params: { taskId: string }) => scheduler.cancel(params.taskId),\n },\n\n 'schedule.remove': {\n params: z.object({ taskId: z.string() }),\n run: async (params: { taskId: string }) => {\n const removed = scheduler.remove(params.taskId)\n return { ok: removed }\n },\n },\n\n 'schedule.readLogs': {\n params: z.object({ taskId: z.string(), limit: z.number().optional() }),\n run: async (params: { taskId: string; limit?: number }) => {\n const entries = scheduler.readFireLogs(params.taskId, params.limit ?? 20)\n return { entries }\n },\n },\n\n 'schedule.parseCron': {\n params: z.object({ expr: z.string() }),\n run: async (params: { expr: string }) => {\n try {\n const next = parseCron(params.expr)\n return { ok: true, next: next.toISOString() }\n } catch (err) {\n return { ok: false, error: err instanceof Error ? err.message : String(err) }\n }\n },\n },\n } satisfies Record<string, BackendMethod>\n\n return { ...createBackendHandler(methods, 'schedule'), getSnapshot }\n}\n","/**\n * 定时任务 TUI 交互管线(状态栏/事件/toast 接线)。\n */\n// RFC-161 M2:SchedulerService 等实现已迁至 @x-otto/schedule,直连新真源\n// (coding 的 re-export shim 仅供尚未迁移的其余消费方使用)。\nimport { SchedulerService, ScheduleRegistry, createScheduleStore, createLocalFireOwnership, createRemoteScheduleStore, createRemoteFireOwnership, FireLogger, DEFAULT_SCHEDULE_CONFIG, JobFireTarget, SessionFireTarget, SubscriberFireTarget, InProcessEventTrigger } from '@x-otto/schedule'\nimport { globalAgentJobRegistry } from '@x-otto/runtime'\nimport { setScheduleService } from './handlers/schedule'\nimport { createScheduleBackendHandler } from './schedule-backend-methods'\n\nimport type { App, AgentSession } from '@x-otto/coding'\nimport type { ScheduleStore, FireOwnership, FireTarget } from '@x-otto/schedule'\nimport type { TuiHandle } from '@x-otto/tui'\n\n/** Remote connection details, threaded from CLIOptions.sessionUrl/sessionToken. */\nexport interface ScheduleRemoteConfig {\n sessionUrl: string\n sessionToken: string\n}\n\n/**\n * RFC-357 D1/D2(M2):`SchedulerDeps.followUpCurrentSession` 的真实实现——把 session-local\n * 任务的 prompt 注入 `boundSessionId` 对应的当前会话。\n *\n * 三重失效校验(均转 `false`,不抛错——调度器侧 `fireSession()` 把异常/false 统一折叠为\n * fire 失败记日志,不重试,同 `notifySubscriber` 缺省行为的旁路信号纪律):\n *\n * 1. **会话身份不匹配**(独立评审 R1):`sessionRef` 是活引用,RFC-234 attach/detach 会\n * 在同一进程内重新赋值它——若 `boundSessionId` 与当前 `sessionRef.value.id` 不等,说明\n * 用户已切到别的会话,绝不能把内容误投进一个不相干的对话。\n * 2. **paused 态**(D1):暂停会话收到注入会被 `prompt()` 判定为 idle 直接起新回合,\n * 这实质上绕过了用户暂停会话的意图——先查 `isPaused` 拒绝。\n * 3. **archived 态 / 会话已销毁**(D1):`AgentSession.status` 是同步 getter,先在此\n * 同步判断 `'archived'` 拒绝(M3 e2e 测试发现的修订——`prompt()` 是 `async` 函数,\n * 其内部对 archived 态的检查会抛出**被拒绝的 Promise** 而非同步异常;`void\n * session.prompt(...)` 外面包一层 `try/catch` 完全捕获不到它,会变成未处理的\n * promise rejection,在真实调度场景下会让 Node 进程崩溃)。为兜底 `ensureNotDisposed()`\n * 等其他潜在的异步失效路径,仍链一个 `.catch()`(fire-and-forget,不 await、不重试,\n * 只是防止任何未预见的异步异常变成未处理 rejection)。\n */\nexport function createFollowUpCurrentSession(\n sessionRef: { readonly value: AgentSession },\n): (task: { prompt: string; boundSessionId: string; name: string; origin: 'user' | 'model' }) => boolean {\n return (task) => {\n const session = sessionRef.value\n\n if (session.id !== task.boundSessionId) return false\n if (session.isPaused) return false\n if (session.status === 'archived') return false\n\n try {\n // 不 await:fire() 是 fire-and-forget 的旁路信号语义(同 fireSubscriber),\n // prompt() 的 busy/idle 路由与错误处理已在 AgentSession 内部完成;这里只需\n // 同步判断\"是否成功发起了注入\"(streamingBehavior:'followUp' 使 prompt()\n // 同步完成入队/发起,不阻塞到回合真正跑完)。`.catch()` 兜底任何异步异常\n // (见上方文档注释——不能只靠 try/catch,那只捕获同步抛出)。\n void session.prompt(task.prompt, { streamingBehavior: 'followUp' }).catch(() => {})\n return true\n } catch {\n // 理论上不会走到这——保留作为同步异常的最后防线。\n return false\n }\n }\n}\n\nexport interface ScheduleServiceSetupOptions {\n /** 交互会话(TUI 装配传;serve/daemon 无人值守装配不传——job-only 模式)。 */\n sessionRef?: { readonly value: AgentSession }\n /** TUI 句柄(仅交互模式;缺失时跳过 5s taskStore 轮询)。 */\n tui?: TuiHandle\n remoteConfig?: ScheduleRemoteConfig\n}\n\nexport function setupScheduleService(\n app: App,\n opts: ScheduleServiceSetupOptions,\n): { pollInterval: ReturnType<typeof setInterval> | undefined; scheduler: SchedulerService } {\n const { sessionRef, tui, remoteConfig } = opts\n // Wire schedule service for /schedule command + cron tools\n const workspaceKey = app.workspaceRef?.key ?? 'default'\n const scheduleRegistry = new ScheduleRegistry()\n\n let scheduleStore: ScheduleStore\n let scheduleOwnership: FireOwnership\n if (app.storage.remoteSessionConfigured && remoteConfig) {\n const getAuth = async (): Promise<{ token: string }> => ({ token: remoteConfig.sessionToken })\n scheduleStore = createRemoteScheduleStore({\n sessionUrl: remoteConfig.sessionUrl,\n getAuth,\n wsKey: workspaceKey,\n })\n scheduleOwnership = createRemoteFireOwnership({\n sessionUrl: remoteConfig.sessionUrl,\n wsKey: workspaceKey,\n getAuth,\n })\n } else {\n scheduleStore = createScheduleStore(workspaceKey)\n scheduleOwnership = createLocalFireOwnership(workspaceKey)\n }\n\n // RFC-406 D7:投递目标从 SchedulerDeps 内联字段拆为 FireTarget handler 列表。\n // 注册顺序即优先级:subscriber > session > job(保持旧 fire() 分支优先级)。\n //\n // RFC-435 R1(独立核对修复②):SubscriberFireTarget **仅在有 sessionRef 的交互形态注册**。\n // 此前无条件注册使 headless(serve/daemon)实际装配 = Subscriber+Job 两件,违背 R1\n // 「daemon 形态只装配 JobFireTarget」与用户 grill 确认的范围收敛(方案 A:只放 job)。\n // 插件 schedules 声明的 subscriber 任务在 headless 形态走 fire log 降级(D3),\n // 不投递——与「daemon 不启用 subscriber 投递」的决策一致(RFC §2 非目标)。\n const fireLogger = new FireLogger(DEFAULT_SCHEDULE_CONFIG)\n const fireTargets: FireTarget[] = []\n\n if (sessionRef) {\n fireTargets.push(\n // RFC-327 D2:订阅者到点通知的窄端口——scheduler 只交出纯数据标识,\n // 由 App 决定投给哪个运行中的 service(并强制\"插件仍激活\"这一 revoke 语义)。\n // 该路径**不开会话授权 epoch**(schedule.tick 无 sessionId),见 App 侧注释。\n new SubscriberFireTarget({\n notify: (subscriber, tick) =>\n app.notifyPluginServiceSchedule(\n { pluginId: subscriber.pluginId, serviceId: subscriber.serviceId },\n tick,\n ),\n fireLogger,\n }),\n // RFC-357 D1/D2(M2):session-local 任务到点后的投递端口 + 创建时绑定的 sessionId。\n new SessionFireTarget({\n followUp: createFollowUpCurrentSession(sessionRef),\n getSessionId: () => sessionRef.value.id,\n fireLogger,\n }),\n )\n }\n\n // RFC-435 D1:job 类任务不依赖交互会话(detached run + worktree 隔离),\n // 交互与无人值守形态都注册。getSessionId 仅在有 sessionRef 时提供——\n // 无人值守(serve/daemon)缺省,job 任务创建时不绑定 session(target 非 session)。\n fireTargets.push(\n new JobFireTarget({\n jobRegistry: globalAgentJobRegistry,\n startJob: (input) => ({\n id: app.agentJobs.start({\n title: input.title,\n prompt: input.prompt,\n sessionId: input.sessionId,\n origin: input.origin,\n // RFC-438:per-job 模型指定透传(task.modelId/thinkingLevel)\n ...(input.model ? { model: input.model } : {}),\n }).id,\n }),\n ...(sessionRef ? { getSessionId: () => sessionRef.value.id } : {}),\n // RFC-230:本函数在 TUI 启动流程内被调用,晚于 app.start()(settings 已 load),\n // getResilienceConfig() 调用安全。真实构造点(非 packages/coding App 内部),\n // 见 RFC-230 §3 D6 Scheduler 小节二轮评审 P2 修正。\n retryBackoffMs: app.getResilienceConfig().schedule.retryBackoffMs,\n fireLogger,\n needsInputMaxExtensionFactor: DEFAULT_SCHEDULE_CONFIG.needsInputMaxExtensionFactor,\n }),\n )\n\n const scheduler = new SchedulerService({\n registry: scheduleRegistry,\n store: scheduleStore,\n ownership: scheduleOwnership,\n fireTargets,\n // RFC-423:进程内事件触发器——监听 AgentJobRegistry 终态(如 job diff applied),\n // 事件满足时把 prompt 注入当前对话(配 target:'session')。三重死循环闸门内建\n // (自产 job 排除 + add() 拒绝 job 投递 + 频率 debounce)。无人值守(无 sessionRef)\n // 时事件触发投递目标无会话——fire 时跳过并记日志(fireLogger),见 RFC-435 D2。\n triggers: [new InProcessEventTrigger({ jobRegistry: globalAgentJobRegistry })],\n })\n // Non-critical background feature — a bootstrap failure here must not take down\n // the whole interactive session (fire-and-forget start() would otherwise surface\n // as an unhandled rejection and crash the process).\n void scheduler.start().then(() => {\n // RFC-435 D3 聚合告警(独立核对修复①):必须在 start() **完成**后检查——start()\n // 是 async(首个 await tryAcquire()),此前同步 list() 拿到的是空 registry,\n // nonJobCount 恒为 0,告警是生产死代码(评审 A.3 坐实)。移到 then() 内,\n // registry 已加载,计数真实。\n if (!sessionRef) {\n const nonJobCount = scheduler.list().filter((t) => t.target === 'session' || !!t.subscriber).length\n if (nonJobCount > 0) {\n const msg = `${nonJobCount} 个任务(session/subscriber 类)在 daemon 无人值守形态不会投递,仅 job 类任务执行`\n app.logger?.warn?.({ count: nonJobCount }, '[schedule] headless mode: non-job tasks skipped')\n new FireLogger(DEFAULT_SCHEDULE_CONFIG).write('daemon-scheduler', `headless aggregate warning: ${msg}`)\n }\n }\n }).catch((err) => {\n // RFC-435 R9:失败日志走 logger 而非 console(serve 无人值守场景 console 无观察者)。\n app.logger?.warn?.({ err: err instanceof Error ? err.message : String(err) }, '[schedule] scheduler failed to start')\n })\n setScheduleService(scheduler)\n app.setScheduleService(scheduler) // A1: let cron_* model tools reach this scheduler\n\n // RFC-349 M1:注册 in-process backend handler——面板经 usePanelBackend().call() 访问 scheduler\n const scheduleBackend = createScheduleBackendHandler(scheduler)\n app.panelBackendController.registerInProcessBackend('plugin-schedule', scheduleBackend.call)\n\n // Push schedule tasks to TUI store every 5 seconds (status bar / task list).\n // RFC-349 M2: panel data is now fetched via backend.call('schedule.list') —\n // this interval only updates the TUI taskStore for status bar display.\n // RFC-435 D1:serve/daemon 无 tui → 不建轮询(无人值守无状态栏消费方)。\n if (!tui) return { pollInterval: undefined, scheduler }\n\n const pollInterval = setInterval(() => {\n if (!tui) return\n const tasks = scheduler.list()\n const taskSummaries = tasks.map((t) => ({\n id: t.id,\n name: t.name,\n prompt: t.prompt,\n cronExpression: t.cronExpression,\n enabled: t.enabled,\n recurring: t.recurring,\n nextFireAt: t.nextFireAt,\n lastFiredAt: t.lastFiredAt,\n lastFireFailed: t.lastFireFailed,\n lastFireError: t.lastFireError,\n lastFireCancelled: t.lastFireCancelled,\n }))\n tui.stores.tasks.setScheduleTasks(taskSummaries)\n }, 5000)\n pollInterval.unref()\n\n return { pollInterval, scheduler }\n}\n"],"mappings":";8jBA4BA,SAAgB,EAA6B,EAG3C,CACA,IAAM,OAAyC,CAC7C,MAAO,EAAU,MAAM,CACvB,eAAgB,CAAC,GAAG,EAAU,mBAAmB,CAAC,CAClD,YAAa,EAAU,aAAa,CACpC,uBAAwB,EAAU,wBAAwB,CAC3D,EAqHD,MAAO,CAAE,GAAG,EAlHI,CACd,gBAAiB,CACf,OAAQ,EAAE,OAAO,EAAE,CAAC,CACpB,IAAK,SAAY,GAAa,CAC/B,CACD,eAAgB,CACd,OAAQ,EAAE,OAAO,CACf,KAAM,EAAE,QAAQ,CAChB,OAAQ,EAAE,QAAQ,CAClB,eAAgB,EAAE,QAAQ,CAC1B,UAAW,EAAE,SAAS,CAEtB,OAAQ,EAAE,KAAK,CAAC,MAAO,UAAU,CAAC,CAAC,UAAU,CAC9C,CAAC,CACF,IAAK,KAAO,IAMN,CAGJ,GAAI,CACF,IAAM,EAAO,EAAU,IAAI,CACzB,KAAM,EAAO,KACb,OAAQ,EAAO,OACf,eAAgB,EAAO,eACvB,UAAW,EAAO,UAClB,OAAQ,OACR,OAAQ,EAAO,OAChB,CAAC,CACF,MAAO,CAAE,GAAI,GAAM,OAAQ,EAAK,GAAI,KAAM,EAAK,KAAM,OAAQ,EAAK,OAAQ,OACnE,EAAK,CACZ,MAAO,CAAE,GAAI,GAAO,MAAO,aAAe,MAAQ,EAAI,QAAU,OAAO,EAAI,CAAE,GAGlF,CAED,kBAAmB,CACjB,OAAQ,EAAE,OAAO,CACf,OAAQ,EAAE,QAAQ,CAClB,KAAM,EAAE,QAAQ,CAAC,UAAU,CAC3B,OAAQ,EAAE,QAAQ,CAAC,UAAU,CAC7B,eAAgB,EAAE,QAAQ,CAAC,UAAU,CACrC,QAAS,EAAE,SAAS,CAAC,UAAU,CAChC,CAAC,CACF,IAAK,KAAO,IAMN,CACJ,IAAM,EAAwF,EAAE,CAMhG,OALI,EAAO,OAAS,IAAA,KAAW,EAAM,KAAO,EAAO,MAC/C,EAAO,SAAW,IAAA,KAAW,EAAM,OAAS,EAAO,QACnD,EAAO,iBAAmB,IAAA,KAAW,EAAM,eAAiB,EAAO,gBACnE,EAAO,UAAY,IAAA,KAAW,EAAM,QAAU,EAAO,SAElD,CAAE,GADE,EAAU,OAAO,EAAO,OAAQ,EAAM,CACpC,EAEhB,CAED,kBAAmB,CACjB,OAAQ,EAAE,OAAO,CAAE,OAAQ,EAAE,QAAQ,CAAE,CAAC,CACxC,IAAK,KAAO,KACV,EAAU,OAAO,EAAO,OAAO,CACxB,CAAE,GAAI,GAAM,EAEtB,CAED,kBAAmB,CACjB,OAAQ,EAAE,OAAO,CAAE,OAAQ,EAAE,QAAQ,CAAE,QAAS,EAAE,SAAS,CAAE,CAAC,CAC9D,IAAK,KAAO,KAEH,CAAE,GADE,EAAU,OAAO,EAAO,OAAQ,CAAE,QAAS,EAAO,QAAS,CAAC,CAC1D,EAEhB,CAED,kBAAmB,CACjB,OAAQ,EAAE,OAAO,CAAE,OAAQ,EAAE,QAAQ,CAAE,CAAC,CACxC,IAAK,KAAO,IAA+B,EAAU,OAAO,EAAO,OAAO,CAC3E,CAED,kBAAmB,CACjB,OAAQ,EAAE,OAAO,CAAE,OAAQ,EAAE,QAAQ,CAAE,CAAC,CACxC,IAAK,KAAO,KAEH,CAAE,GADO,EAAU,OAAO,EAAO,OAAO,CACzB,EAEzB,CAED,oBAAqB,CACnB,OAAQ,EAAE,OAAO,CAAE,OAAQ,EAAE,QAAQ,CAAE,MAAO,EAAE,QAAQ,CAAC,UAAU,CAAE,CAAC,CACtE,IAAK,KAAO,KAEH,CAAE,QADO,EAAU,aAAa,EAAO,OAAQ,EAAO,OAAS,GAAG,CACvD,EAErB,CAED,qBAAsB,CACpB,OAAQ,EAAE,OAAO,CAAE,KAAM,EAAE,QAAQ,CAAE,CAAC,CACtC,IAAK,KAAO,IAA6B,CACvC,GAAI,CAEF,MAAO,CAAE,GAAI,GAAM,KADN,EAAU,EAAO,KAAK,CACL,aAAa,CAAE,OACtC,EAAK,CACZ,MAAO,CAAE,GAAI,GAAO,MAAO,aAAe,MAAQ,EAAI,QAAU,OAAO,EAAI,CAAE,GAGlF,CACF,CAEyC,WAAW,CAAE,cAAa,0EClHtE,SAAgB,EACd,EACuG,CACvG,MAAQ,IAAS,CACf,IAAM,EAAU,EAAW,MAI3B,GAFI,EAAQ,KAAO,EAAK,gBACpB,EAAQ,UACR,EAAQ,SAAW,WAAY,MAAO,GAE1C,GAAI,CAOF,OADK,EAAQ,OAAO,EAAK,OAAQ,CAAE,kBAAmB,WAAY,CAAC,CAAC,UAAY,GAAG,CAC5E,QACD,CAEN,MAAO,KAab,SAAgB,EACd,EACA,EAC2F,CAC3F,GAAM,CAAE,aAAY,MAAK,gBAAiB,EAEpC,EAAe,EAAI,cAAc,KAAO,UACxC,EAAmB,IAAI,EAEzB,EACA,EACJ,GAAI,EAAI,QAAQ,yBAA2B,EAAc,CACvD,IAAM,EAAU,UAAyC,CAAE,MAAO,EAAa,aAAc,EAC7F,EAAgB,EAA0B,CACxC,WAAY,EAAa,WACzB,UACA,MAAO,EACR,CAAC,CACF,EAAoB,EAA0B,CAC5C,WAAY,EAAa,WACzB,MAAO,EACP,UACD,CAAC,MAEF,EAAgB,EAAoB,EAAa,CACjD,EAAoB,EAAyB,EAAa,CAW5D,IAAM,EAAa,IAAI,EAAW,EAAwB,CACpD,EAA4B,EAAE,CAEhC,GACF,EAAY,KAIV,IAAI,EAAqB,CACvB,QAAS,EAAY,IACnB,EAAI,4BACF,CAAE,SAAU,EAAW,SAAU,UAAW,EAAW,UAAW,CAClE,EACD,CACH,aACD,CAAC,CAEF,IAAI,EAAkB,CACpB,SAAU,EAA6B,EAAW,CAClD,iBAAoB,EAAW,MAAM,GACrC,aACD,CAAC,CACH,CAMH,EAAY,KACV,IAAI,EAAc,CAChB,YAAa,EACb,SAAW,IAAW,CACpB,GAAI,EAAI,UAAU,MAAM,CACtB,MAAO,EAAM,MACb,OAAQ,EAAM,OACd,UAAW,EAAM,UACjB,OAAQ,EAAM,OAEd,GAAI,EAAM,MAAQ,CAAE,MAAO,EAAM,MAAO,CAAG,EAAE,CAC9C,CAAC,CAAC,GACJ,EACD,GAAI,EAAa,CAAE,iBAAoB,EAAW,MAAM,GAAI,CAAG,EAAE,CAIjE,eAAgB,EAAI,qBAAqB,CAAC,SAAS,eACnD,aACA,6BAA8B,EAAwB,6BACvD,CAAC,CACH,CAED,IAAM,EAAY,IAAI,EAAiB,CACrC,SAAU,EACV,MAAO,EACP,UAAW,EACX,cAKA,SAAU,CAAC,IAAI,EAAsB,CAAE,YAAa,EAAwB,CAAC,CAAC,CAC/E,CAAC,CAIG,EAAU,OAAO,CAAC,SAAW,CAKhC,GAAI,CAAC,EAAY,CACf,IAAM,EAAc,EAAU,MAAM,CAAC,OAAQ,GAAM,EAAE,SAAW,WAAa,CAAC,CAAC,EAAE,WAAW,CAAC,OAC7F,GAAI,EAAc,EAAG,CACnB,IAAM,EAAM,GAAG,EAAY,2DAC3B,EAAI,QAAQ,OAAO,CAAE,MAAO,EAAa,CAAE,kDAAkD,CAC7F,IAAI,EAAW,EAAwB,CAAC,MAAM,mBAAoB,+BAA+B,IAAM,IAG3G,CAAC,MAAO,GAAQ,CAEhB,EAAI,QAAQ,OAAO,CAAE,IAAK,aAAe,MAAQ,EAAI,QAAU,OAAO,EAAI,CAAE,CAAE,uCAAuC,EACrH,CACF,EAAmB,EAAU,CAC7B,EAAI,mBAAmB,EAAU,CAGjC,IAAM,EAAkB,EAA6B,EAAU,CAO/D,GANA,EAAI,uBAAuB,yBAAyB,kBAAmB,EAAgB,KAAK,CAMxF,CAAC,EAAK,MAAO,CAAE,aAAc,IAAA,GAAW,YAAW,CAEvD,IAAM,EAAe,gBAAkB,CACrC,GAAI,CAAC,EAAK,OAEV,IAAM,EADQ,EAAU,MAAM,CACF,IAAK,IAAO,CACtC,GAAI,EAAE,GACN,KAAM,EAAE,KACR,OAAQ,EAAE,OACV,eAAgB,EAAE,eAClB,QAAS,EAAE,QACX,UAAW,EAAE,UACb,WAAY,EAAE,WACd,YAAa,EAAE,YACf,eAAgB,EAAE,eAClB,cAAe,EAAE,cACjB,kBAAmB,EAAE,kBACtB,EAAE,CACH,EAAI,OAAO,MAAM,iBAAiB,EAAc,EAC/C,IAAK,CAGR,OAFA,EAAa,OAAO,CAEb,CAAE,eAAc,YAAW"}
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
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
- `);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}async function c(e,t={}){let{createHostApp:n}=await import(`./create-host-app-BgjEc81C.js`).then(e=>e.n),r=n({logger:{name:`otto-mcp-server`,level:`error`,destination:`stderr`},...t.projectDir?{workspaceDir:t.projectDir}:{},...t.modelId?{modelId:t.modelId}:{}});await r.start();let i=l(r.tools,r.permissionRegistry),a=new AbortController;return process.on(`SIGTERM`,()=>a.abort()),process.on(`SIGINT`,()=>a.abort()),s(e,{...i,signal:a.signal,onShutdown:()=>r.stop()})}function l(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(`
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}async function c(e,t={}){let{createHostApp:n}=await import(`./create-host-app-BtifD7KK.js`).then(e=>e.n),r=n({logger:{name:`otto-mcp-server`,level:`error`,destination:`stderr`},...t.projectDir?{workspaceDir:t.projectDir}:{},...t.modelId?{modelId:t.modelId}:{}});await r.start();let i=l(r.tools,r.permissionRegistry),a=new AbortController;return process.on(`SIGTERM`,()=>a.abort()),process.on(`SIGINT`,()=>a.abort()),s(e,{...i,signal:a.signal,onShutdown:()=>r.stop()})}function l(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 runMcpServerCommandFromCli};
5
- //# sourceMappingURL=mcp-server-C2bw9J2i.js.map
5
+ //# sourceMappingURL=mcp-server-CfJK4pav.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-server-C2bw9J2i.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-410 M1 T4:CLI 生产装配入口——从顶层命令目录调用,编排下沉至此(原内联在\n * `cli.ts` 的 SUB_COMMANDS Map 条目里,注册表条目应只做路由)。\n *\n * headless App 起工具注册表(settings/插件/权限装载),readonly 工具投影进 MCP server 面;\n * SIGTERM/SIGINT → 优雅退出信号;退出时 app.stop() 收尾。\n */\nexport async function runMcpServerCommandFromCli(\n args: string[],\n opts: { projectDir?: string; modelId?: string } = {},\n): Promise<number> {\n const { createHostApp } = await import('../create-host-app')\n const app = createHostApp({\n logger: { name: 'otto-mcp-server', level: 'error', destination: 'stderr' },\n ...(opts.projectDir ? { workspaceDir: opts.projectDir } : {}),\n ...(opts.modelId ? { modelId: opts.modelId } : {}),\n })\n await app.start()\n const deps = buildMcpServerDepsFromApp(app.tools, app.permissionRegistry)\n const shutdown = new AbortController()\n process.on('SIGTERM', () => shutdown.abort())\n process.on('SIGINT', () => shutdown.abort())\n return runMcpServerCommand(args, {\n ...deps,\n signal: shutdown.signal,\n onShutdown: () => app.stop(),\n })\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').AgentToolDefinition[],\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-CfJK4pav.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-410 M1 T4:CLI 生产装配入口——从顶层命令目录调用,编排下沉至此(原内联在\n * `cli.ts` 的 SUB_COMMANDS Map 条目里,注册表条目应只做路由)。\n *\n * headless App 起工具注册表(settings/插件/权限装载),readonly 工具投影进 MCP server 面;\n * SIGTERM/SIGINT → 优雅退出信号;退出时 app.stop() 收尾。\n */\nexport async function runMcpServerCommandFromCli(\n args: string[],\n opts: { projectDir?: string; modelId?: string } = {},\n): Promise<number> {\n const { createHostApp } = await import('../create-host-app')\n const app = createHostApp({\n logger: { name: 'otto-mcp-server', level: 'error', destination: 'stderr' },\n ...(opts.projectDir ? { workspaceDir: opts.projectDir } : {}),\n ...(opts.modelId ? { modelId: opts.modelId } : {}),\n })\n await app.start()\n const deps = buildMcpServerDepsFromApp(app.tools, app.permissionRegistry)\n const shutdown = new AbortController()\n process.on('SIGTERM', () => shutdown.abort())\n process.on('SIGINT', () => shutdown.abort())\n return runMcpServerCommand(args, {\n ...deps,\n signal: shutdown.signal,\n onShutdown: () => app.stop(),\n })\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').AgentToolDefinition[],\n permission?: {\n evaluate(context: {\n timing: string\n toolName: string\n args: Record<string, unknown>\n agentName: string\n sessionId: string\n metadata: Record<string, unknown>\n }): { effect: 'allow' | 'ask' | 'deny'; reason?: string }\n },\n): Required<Pick<McpServerDeps, 'tools' | 'callTool' | 'allToolNames'>> {\n const readonlyTools = appTools.filter(\n (t) => t.readonly === true && t.exposure !== 'hidden' && t.exposure !== 'deferred',\n )\n\n const tools: McpToolDefinition[] = readonlyTools.map((t) => ({\n name: t.name,\n description: typeof t.description === 'function' ? t.name : t.description,\n // RFC-384 M3:zod 4 原生 toJSONSchema(含 description/enum/required/optional 语义)——\n // 协议垫片(interchange ZodType.toJSONSchema)可选调用,缺失时回退最小骨架\n // (执行层 zod safeParse 仍兜底校验)。\n inputSchema:\n (typeof t.parameters?.toJSONSchema === 'function'\n ? (t.parameters.toJSONSchema() as Record<string, unknown>)\n : undefined) ?? { type: 'object' },\n }))\n\n const allToolNames = appTools.map((t) => t.name)\n\n return {\n tools,\n allToolNames,\n callTool: async (name, args, sessionId) => {\n const tool = appTools.find((t) => t.name === name)\n const { createToolExecutor } = await import('@x-otto/agent')\n\n // RFC-384 M4:write 工具执行前权限裁决(fail-closed)。\n if (tool && tool.readonly !== true) {\n if (!permission) {\n return {\n text: `Tool \"${name}\" is a write tool but no permission registry is wired; ` +\n `denied (fail-closed).`,\n isError: true,\n }\n }\n const decision = permission.evaluate({\n timing: 'tool.execute.before',\n toolName: name,\n args,\n agentName: 'mcp-server',\n sessionId: sessionId ?? 'mcp-server',\n metadata: {},\n })\n if (decision.effect === 'ask') {\n return {\n text: `Tool \"${name}\" requires interactive approval, which is not available ` +\n `over MCP (fail-closed). Run it in an interactive otto session instead.`,\n isError: true,\n }\n }\n if (decision.effect === 'deny') {\n return {\n text: `Tool \"${name}\" denied by permission policy${decision.reason ? `: ${decision.reason}` : ''}.`,\n isError: true,\n }\n }\n // allow → 继续执行\n }\n\n const executor = createToolExecutor(appTools, {\n sessionId: sessionId ?? 'mcp-server',\n agentName: 'mcp-server',\n })\n const result = await executor.execute(\n { type: 'tool_call', id: randomUUID(), name, arguments: args },\n new AbortController().signal,\n )\n const text = result.content\n .map((c) => (c.type === 'text' ? c.text : `[${c.type}]`))\n .join('\\n')\n return { text: text || '(no output)', isError: result.isError === true }\n },\n }\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{at as e}from"./command-dispatcher-FRiCoN16.js";import{t}from"./version-CdzXY8m2.js";import{SERVICE_PORT as n}from"@x-otto/env";import{resolve as r}from"node:path";const i=[{name:`run`,helpLines:[` otto run <prompt> 执行一次性任务`]},{name:`serve`,helpLines:[` otto serve [--port N] [--inspect] [--static <dir>] [--token <t>] 启动 HTTP/WS service(--static 同源托管前端 SPA,--token 启用鉴权)`],run:async e=>{let{runServeCommand:t}=await import(`./serve-DHqx-Mab.js`);return t(e.args,{projectDir:e.options.projectDir,configPath:e.options.configPath,modelId:e.options.model,verbose:e.options.verbose})}},{name:`doctor`,helpLines:[` otto doctor [--summary|--json] 环境健康检查(环境/配置/存储状态/连通性)`],run:async e=>{let{runDoctorCommand:t}=await import(`./doctor-2AA2A9G7.js`);return t(e.args,{stdout:e.stdout,stderr:e.stderr})}},{name:`auth`,helpLines:[` otto auth login <provider> 登录(cursor / anthropic / openai-codex / github-copilot)`,` otto auth status 查看各 provider 凭据来源`,` otto auth logout <provider> 注销并清除凭据`],run:async e=>{let{runAuthCommand:t}=await import(`./auth-command-DF_POx4L.js`);return t(e.args,{bootstrap:{workspaceDir:e.options.projectDir,projectConfigPath:e.options.configPath,verbose:e.options.verbose},stdout:e.stdout,stderr:e.stderr})}},{name:`config`,helpLines:[` 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] 撤销最近一次导入`],run:async e=>{let{runConfigMigrationCommand:t}=await import(`./config-migration-DatflIxs.js`);return t(e.args,{stdout:e.stdout,stderr:e.stderr})}},{name:`migrate`,helpLines:[` otto migrate export [--no-sessions] [-o <path>] 导出配置+插件清单+会话数据为迁移包(换电脑用)`,` otto migrate import <archive> [--no-sessions] 导入迁移包(目标已有会话数据时跳过会话部分)`],run:async e=>{let{runMigrateCommand:t}=await import(`./migrate-BBi3Txu2.js`);return t(e.args,{stdout:e.stdout,stderr:e.stderr})}},{name:`observe`,helpLines:[` otto observe <action> <id> 观测面板(summary / timeline / spans)`,` otto observe debug <action> [...] 调试面板(breakpoints / points / state)`,` otto observe replay <action> <id> 时间旅行(checkpoints / scrubber / restore / fork / replay)`],run:async e=>{let{runObserveCommand:t}=await import(`./observe-BqLCpmFD.js`);return t(e.args)}},{name:`debug`,helpLines:[` otto debug <action> [...] (alias: otto observe debug)`],run:async e=>{let{runObserveCommand:t}=await import(`./observe-BqLCpmFD.js`);return t([`debug`,...e.args])}},{name:`time-travel`,helpLines:[` otto time-travel <action> <id> (alias: otto observe replay)`],run:async e=>{let{runObserveCommand:t}=await import(`./observe-BqLCpmFD.js`);return t([`replay`,...e.args])}},{name:`mcp`,helpLines:[` otto mcp login <name> MCP server OAuth 登录`,` otto mcp logout <name> 登出 MCP server`,` otto mcp list 列出已认证的 MCP server`],run:async e=>{let{runMcpCommand:t}=await import(`./mcp-DjReRjuu.js`);return t(e.args)}},{name:`extension`,helpLines:[` otto extension create <name> 生成插件骨架(命令 + 面板 + manifest,开箱即用)`,` otto extension install <name> [--yes] 安装首方/自定义插件(anthropic-provider / deepseek-provider / browser-use / …)`,` otto extension update [name] 检查并重装已过期的已安装 skill/plugin`],run:async e=>{let{runExtensionCommand:t}=await import(`./extension-CoJqqktf.js`);return t(e.args,{stdout:e.stdout,stderr:e.stderr})}},{name:`persistence`,helpLines:[` otto persistence local [...flags] 启动独立持久化服务(storage + events + sync)`,` otto persistence remote [...flags] 启动最小化远程持久化服务(5 REST 端点)`],run:async e=>{let{runPersistenceCommand:t}=await import(`./persistence-BvLRnpfi.js`);return t(e.args)}},{name:`persistenced`,helpLines:[` otto persistenced [...flags] (alias: otto persistence local)`],run:async e=>{let{runPersistenceCommand:t}=await import(`./persistence-BvLRnpfi.js`);return t([`local`,...e.args])}},{name:`remote-persistence-server`,helpLines:[` otto remote-persistence-server [...flags] (alias: otto persistence remote)`],run:async e=>{let{runPersistenceCommand:t}=await import(`./persistence-BvLRnpfi.js`);return t([`remote`,...e.args])}},{name:`ps`,helpLines:[` otto ps [--sweep] [--include-weak] [--json] 进程群列表与孤儿清扫(SIGTERM only,RFC-196)`],run:async e=>{let{runPsCommand:t}=await import(`./ps-BVKv6rAo.js`);return t(e.args,{stdout:e.stdout,stderr:e.stderr})}},{name:`export-trajectory`,helpLines:[` otto export-trajectory --session <id> --out <path> [--since-seq <n>] 导出会话轨迹 JSONL(RFC-385;默认关)`],run:async e=>{let{runExportTrajectoryCommand:t}=await import(`./export-trajectory-D-BcH0Wt.js`);return t(e.args,{stdout:e.stdout,stderr:e.stderr})}},{name:`daemon`,run:async e=>{let{runDaemonCommand:t}=await import(`./daemon-COESl-zU.js`);return t(e.args,{projectDir:e.options.projectDir,stdout:e.stdout,stderr:e.stderr})}},{name:`mcp-server`,run:async e=>{let{runMcpServerCommandFromCli:t}=await import(`./mcp-server-C2bw9J2i.js`);return t(e.args,{projectDir:e.options.projectDir,modelId:e.options.model})}},{name:`feedback`,run:async e=>{let{runFeedbackCommand:t}=await import(`./feedback-BsU8b1k9.js`);return t(e.args,{stdout:e.stdout,stderr:e.stderr,projectDir:e.options.projectDir,configPath:e.options.configPath})}}],a=new Set(i.map(e=>e.name)),o=new Map(i.filter(e=>e.run).map(e=>[e.name,e.run]));function s(e,t){let n=o.get(e);return n?n(t):void 0}function c(){return i.flatMap(e=>e.helpLines??[])}var l=e({SUB_COMMAND_NAMES:()=>a,getHelpLines:()=>m,parseCLI:()=>p});function u(e){return e.startsWith(`--`)||/^-[^\s-]/.test(e)}const d=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:h}].flatMap(e=>e.flags.map(t=>[t,e])));var f=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 p(e){let t=e.slice(2),n=new f(t),i={mode:`interactive`,projectDir:process.cwd(),verbose:!1,quiet:!1},o=n.current(),s,c=[],l,p;o&&a.has(o)?(s=o,c=t.slice(1),n.skip(1),s===`run`&&(i.prompt=t.slice(1).filter(e=>!u(e)).join(` `),i.mode=`print`)):o&&!u(o)&&(l=o,p=t.slice(1));let m=i.prompt?[i.prompt]:[];for(;n.current()!==void 0;){let e=n.current();if(e===`--inspect`){i.inspect=!0,n.advance();continue}if(e?.startsWith(`--inspect=`)){let t=Number(e.slice(10));i.inspect=Number.isInteger(t)&&t>0?t:!0,n.advance();continue}if(e?.startsWith(`--restart-generation=`)){let t=Number(e.slice(21));Number.isInteger(t)&&t>0&&(i.restartGeneration=t),n.advance();continue}let t=d.get(e);if(t){switch(n.advance(),t.type){case`boolean`:i[t.key]=!0;break;case`mode`:i.mode=t.value;break;case`string`:i[t.key]=n.current(),n.advance();break;case`number`:{let e=n.current();if(e===void 0||e.startsWith(`-`))break;let r=Number(e),a=t.key===`maxToolTurnExtensions`;Number.isFinite(r)&&(a?r>=0:r>0)&&(i[t.key]=r),n.advance();continue}case`path`:i[t.key]=r(n.current()??process.cwd()),n.advance();break;case`action`:t.handler();break}continue}e&&!u(e)&&!s&&(i.prompt===void 0&&i.mode===`interactive`&&(i.mode=`print`),m.push(e)),n.advance()}return m.length>0&&(i.prompt=m.join(` `)),{options:i,subCommand:s,subCommandArgs:c,unresolvedCommandName:l,unresolvedCommandArgs:p}}function m(){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> 继续已有会话`," --attach <id> attach 到 `otto serve` 上运行的会话(只读预览 + Chat.query 写)",` --force-write-lease attach 时强抢写权(kill -9 造成 stuck lease 的正式解锁口;服务端 audit 记录)`,` --id <workspace> 显式 workspace id(一目录多 workspace DevPod 模式)`,` --session-url <url> 远端会话持久化地址(如 http://host:3001/sessions)`,` --session-token <token> 远端会话持久化 Bearer token`,` --inspect[=port] 启用 Node.js inspector(默认: 9229)`,` -h, --help 显示帮助`,` --version, -v 显示版本`,``,`命令:`,...c(),``,`面板命令默认连 \`otto serve\` 实例(http://127.0.0.1:${n},OTTO_SERVICE_URL/--remote 可覆盖)。`]}function h(){process.stdout.write(`otto ${t()}\n`),process.exit(0)}export{s as i,l as n,a as r,p as t};
3
- //# sourceMappingURL=parse-cli-C1r2cQ0K.js.map
2
+ import{ot as e}from"./command-dispatcher-CKj6qR-G.js";import{t}from"./version-BL66TSQP.js";import{SERVICE_PORT as n}from"@x-otto/env";import{resolve as r}from"node:path";const i=[{name:`run`,helpLines:[` otto run <prompt> 执行一次性任务`]},{name:`serve`,helpLines:[` otto serve [--port N] [--inspect] [--static <dir>] [--token <t>] 启动 HTTP/WS service(--static 同源托管前端 SPA,--token 启用鉴权)`],run:async e=>{let{runServeCommand:t}=await import(`./serve-ChSSkjLs.js`);return t(e.args,{projectDir:e.options.projectDir,configPath:e.options.configPath,modelId:e.options.model,verbose:e.options.verbose})}},{name:`doctor`,helpLines:[` otto doctor [--summary|--json] 环境健康检查(环境/配置/存储状态/连通性)`],run:async e=>{let{runDoctorCommand:t}=await import(`./doctor-BAxvhLws.js`);return t(e.args,{stdout:e.stdout,stderr:e.stderr})}},{name:`auth`,helpLines:[` otto auth login <provider> 登录(cursor / anthropic / openai-codex / github-copilot)`,` otto auth status 查看各 provider 凭据来源`,` otto auth logout <provider> 注销并清除凭据`],run:async e=>{let{runAuthCommand:t}=await import(`./auth-command--ky3JspQ.js`);return t(e.args,{bootstrap:{workspaceDir:e.options.projectDir,projectConfigPath:e.options.configPath,verbose:e.options.verbose},stdout:e.stdout,stderr:e.stderr})}},{name:`config`,helpLines:[` 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] 撤销最近一次导入`],run:async e=>{let{runConfigMigrationCommand:t}=await import(`./config-migration-DatflIxs.js`);return t(e.args,{stdout:e.stdout,stderr:e.stderr})}},{name:`migrate`,helpLines:[` otto migrate export [--no-sessions] [-o <path>] 导出配置+插件清单+会话数据为迁移包(换电脑用)`,` otto migrate import <archive> [--no-sessions] 导入迁移包(目标已有会话数据时跳过会话部分)`],run:async e=>{let{runMigrateCommand:t}=await import(`./migrate-BBi3Txu2.js`);return t(e.args,{stdout:e.stdout,stderr:e.stderr})}},{name:`observe`,helpLines:[` otto observe <action> <id> 观测面板(summary / timeline / spans)`,` otto observe debug <action> [...] 调试面板(breakpoints / points / state)`,` otto observe replay <action> <id> 时间旅行(checkpoints / scrubber / restore / fork / replay)`],run:async e=>{let{runObserveCommand:t}=await import(`./observe-BqLCpmFD.js`);return t(e.args)}},{name:`debug`,helpLines:[` otto debug <action> [...] (alias: otto observe debug)`],run:async e=>{let{runObserveCommand:t}=await import(`./observe-BqLCpmFD.js`);return t([`debug`,...e.args])}},{name:`time-travel`,helpLines:[` otto time-travel <action> <id> (alias: otto observe replay)`],run:async e=>{let{runObserveCommand:t}=await import(`./observe-BqLCpmFD.js`);return t([`replay`,...e.args])}},{name:`mcp`,helpLines:[` otto mcp login <name> MCP server OAuth 登录`,` otto mcp logout <name> 登出 MCP server`,` otto mcp list 列出已认证的 MCP server`],run:async e=>{let{runMcpCommand:t}=await import(`./mcp-DjReRjuu.js`);return t(e.args)}},{name:`extension`,helpLines:[` otto extension create <name> 生成插件骨架(命令 + 面板 + manifest,开箱即用)`,` otto extension install <name> [--yes] 安装首方/自定义插件(anthropic-provider / deepseek-provider / browser-use / …)`,` otto extension update [name] 检查并重装已过期的已安装 skill/plugin`],run:async e=>{let{runExtensionCommand:t}=await import(`./extension-CoJqqktf.js`);return t(e.args,{stdout:e.stdout,stderr:e.stderr})}},{name:`persistence`,helpLines:[` otto persistence local [...flags] 启动独立持久化服务(storage + events + sync)`,` otto persistence remote [...flags] 启动最小化远程持久化服务(5 REST 端点)`],run:async e=>{let{runPersistenceCommand:t}=await import(`./persistence-BvLRnpfi.js`);return t(e.args)}},{name:`persistenced`,helpLines:[` otto persistenced [...flags] (alias: otto persistence local)`],run:async e=>{let{runPersistenceCommand:t}=await import(`./persistence-BvLRnpfi.js`);return t([`local`,...e.args])}},{name:`remote-persistence-server`,helpLines:[` otto remote-persistence-server [...flags] (alias: otto persistence remote)`],run:async e=>{let{runPersistenceCommand:t}=await import(`./persistence-BvLRnpfi.js`);return t([`remote`,...e.args])}},{name:`ps`,helpLines:[` otto ps [--sweep] [--include-weak] [--json] 进程群列表与孤儿清扫(SIGTERM only,RFC-196)`],run:async e=>{let{runPsCommand:t}=await import(`./ps-BVKv6rAo.js`);return t(e.args,{stdout:e.stdout,stderr:e.stderr})}},{name:`export-trajectory`,helpLines:[` otto export-trajectory --session <id> --out <path> [--since-seq <n>] 导出会话轨迹 JSONL(RFC-385;默认关)`],run:async e=>{let{runExportTrajectoryCommand:t}=await import(`./export-trajectory-D-BcH0Wt.js`);return t(e.args,{stdout:e.stdout,stderr:e.stderr})}},{name:`daemon`,run:async e=>{let{runDaemonCommand:t}=await import(`./daemon-COESl-zU.js`);return t(e.args,{projectDir:e.options.projectDir,stdout:e.stdout,stderr:e.stderr})}},{name:`mcp-server`,run:async e=>{let{runMcpServerCommandFromCli:t}=await import(`./mcp-server-CfJK4pav.js`);return t(e.args,{projectDir:e.options.projectDir,modelId:e.options.model})}},{name:`feedback`,run:async e=>{let{runFeedbackCommand:t}=await import(`./feedback-CJVKfI0S.js`);return t(e.args,{stdout:e.stdout,stderr:e.stderr,projectDir:e.options.projectDir,configPath:e.options.configPath})}}],a=new Set(i.map(e=>e.name)),o=new Map(i.filter(e=>e.run).map(e=>[e.name,e.run]));function s(e,t){let n=o.get(e);return n?n(t):void 0}function c(){return i.flatMap(e=>e.helpLines??[])}var l=e({SUB_COMMAND_NAMES:()=>a,getHelpLines:()=>m,parseCLI:()=>p});function u(e){return e.startsWith(`--`)||/^-[^\s-]/.test(e)}const d=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:h}].flatMap(e=>e.flags.map(t=>[t,e])));var f=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 p(e){let t=e.slice(2),n=new f(t),i={mode:`interactive`,projectDir:process.cwd(),verbose:!1,quiet:!1},o=n.current(),s,c=[],l,p;o&&a.has(o)?(s=o,c=t.slice(1),n.skip(1),s===`run`&&(i.prompt=t.slice(1).filter(e=>!u(e)).join(` `),i.mode=`print`)):o&&!u(o)&&(l=o,p=t.slice(1));let m=i.prompt?[i.prompt]:[];for(;n.current()!==void 0;){let e=n.current();if(e===`--inspect`){i.inspect=!0,n.advance();continue}if(e?.startsWith(`--inspect=`)){let t=Number(e.slice(10));i.inspect=Number.isInteger(t)&&t>0?t:!0,n.advance();continue}if(e?.startsWith(`--restart-generation=`)){let t=Number(e.slice(21));Number.isInteger(t)&&t>0&&(i.restartGeneration=t),n.advance();continue}let t=d.get(e);if(t){switch(n.advance(),t.type){case`boolean`:i[t.key]=!0;break;case`mode`:i.mode=t.value;break;case`string`:i[t.key]=n.current(),n.advance();break;case`number`:{let e=n.current();if(e===void 0||e.startsWith(`-`))break;let r=Number(e),a=t.key===`maxToolTurnExtensions`;Number.isFinite(r)&&(a?r>=0:r>0)&&(i[t.key]=r),n.advance();continue}case`path`:i[t.key]=r(n.current()??process.cwd()),n.advance();break;case`action`:t.handler();break}continue}e&&!u(e)&&!s&&(i.prompt===void 0&&i.mode===`interactive`&&(i.mode=`print`),m.push(e)),n.advance()}return m.length>0&&(i.prompt=m.join(` `)),{options:i,subCommand:s,subCommandArgs:c,unresolvedCommandName:l,unresolvedCommandArgs:p}}function m(){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> 继续已有会话`," --attach <id> attach 到 `otto serve` 上运行的会话(只读预览 + Chat.query 写)",` --force-write-lease attach 时强抢写权(kill -9 造成 stuck lease 的正式解锁口;服务端 audit 记录)`,` --id <workspace> 显式 workspace id(一目录多 workspace DevPod 模式)`,` --session-url <url> 远端会话持久化地址(如 http://host:3001/sessions)`,` --session-token <token> 远端会话持久化 Bearer token`,` --inspect[=port] 启用 Node.js inspector(默认: 9229)`,` -h, --help 显示帮助`,` --version, -v 显示版本`,``,`命令:`,...c(),``,`面板命令默认连 \`otto serve\` 实例(http://127.0.0.1:${n},OTTO_SERVICE_URL/--remote 可覆盖)。`]}function h(){process.stdout.write(`otto ${t()}\n`),process.exit(0)}export{s as i,l as n,a as r,p as t};
3
+ //# sourceMappingURL=parse-cli-B9Go-f6A.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"parse-cli-C1r2cQ0K.js","names":[],"sources":["../src/top-command-catalog.ts","../src/parse-cli.ts"],"sourcesContent":["/**\n * top-command-catalog.ts —— 顶层子命令单一真相源(RFC-410 M1 T1)。\n *\n * ## 要解决的问题\n * 此前顶层命令面有**四处真源**,加一个子命令要同步改 4 处,漏一处即静默漂移:\n * - `parse-cli.ts` 的 `SubCommand` 类型联合\n * - `parse-cli.ts` 的 `SUB_COMMAND_NAMES` 集合\n * - `cli.ts` 的 `SUB_COMMANDS` Map(dispatch)\n * - `parse-cli.ts` 的 `getHelpLines()` 手写帮助\n * 实测漂移:`--attach`/`--id`/`--force-write-lease` 在 OPTIONS 表有、`--help` 无。\n *\n * ## 单源方案(复刻 RFC-074 D2 斜杠命令目录已验证的模式)\n * 本文件的 `TOP_LEVEL_COMMANDS` 是顶层命令名的**唯一真相源**:\n * - `SUB_COMMAND_NAMES`(保留字集合)从目录 `flatMap` 派生\n * - dispatch 经 `runTopCommand(name, ctx)` 查目录 `run`\n * - 命令段帮助文本从目录条目的 `helpLines` 按**数组顺序**拼接(数组顺序 = 帮助显示顺序)\n * `SubCommand` 类型联合就此退役为 `string`——类型联合与目录双真源正是漂移根因,砍掉一边。\n *\n * ## 惰性加载保持不变(RFC-150 D3)\n * 每个 `run` 内部动态 `import()` 对应模块——只有命中该子命令才加载其代码与传递依赖,\n * `otto doctor`/`otto extension` 等轻命令不承担全部子命令处理器的 import 开销。\n *\n * ## `run` 是特例\n * `otto run <prompt>` 不走 dispatch——`parseCLI` 特判它(设 prompt + print 模式后落回\n * prompt 主路径)。故它在目录里**无 `run` 处理器**(仅占名 + 帮助行),既进保留字集合\n * (不被当插件候选名),又不进 dispatch。\n */\nimport type { SubCommandContext } from './types'\n\n/** 顶层子命令目录条目。数组顺序即 `--help` 命令段显示顺序。 */\nexport interface TopCommandEntry {\n /** 命令名(`otto <name> ...`)。 */\n name: string\n /**\n * dispatch 处理器(动态 import 对应模块)。缺省 = 保留名但不 dispatch:\n * 目前仅 `run`(由 `parseCLI` 特判为 prompt + print 模式)。\n */\n run?: (ctx: SubCommandContext) => Promise<number>\n /**\n * 命令段帮助文本行(精确到既有 `getHelpLines()` 的逐行文案,保持字节一致)。\n * 缺省 = 不在 `--help` 命令段列出(内部/未文档化命令:daemon / mcp-server / feedback)。\n */\n helpLines?: string[]\n}\n\n/**\n * 顶层命令目录(单一真相源)。**数组顺序 = 帮助显示顺序**,与注册无关的保留字/dispatch\n * 都从本数组派生,故顺序只影响帮助呈现。可见命令在前(按帮助分组排列),内部命令在后。\n */\nexport const TOP_LEVEL_COMMANDS: TopCommandEntry[] = [\n // ── run(保留名,无 dispatch:parseCLI 特判为 prompt + print) ──\n {\n name: 'run',\n helpLines: [' otto run <prompt> 执行一次性任务'],\n },\n {\n name: 'serve',\n helpLines: [' otto serve [--port N] [--inspect] [--static <dir>] [--token <t>] 启动 HTTP/WS service(--static 同源托管前端 SPA,--token 启用鉴权)'],\n run: async (ctx) => {\n const { runServeCommand } = await import('./commands/serve')\n return runServeCommand(ctx.args, {\n projectDir: ctx.options.projectDir,\n configPath: ctx.options.configPath,\n modelId: ctx.options.model,\n verbose: ctx.options.verbose,\n })\n },\n },\n {\n name: 'doctor',\n helpLines: [' otto doctor [--summary|--json] 环境健康检查(环境/配置/存储状态/连通性)'],\n run: async (ctx) => {\n const { runDoctorCommand } = await import('./commands/doctor')\n return runDoctorCommand(ctx.args, { stdout: ctx.stdout, stderr: ctx.stderr })\n },\n },\n {\n name: 'auth',\n helpLines: [\n ' otto auth login <provider> 登录(cursor / anthropic / openai-codex / github-copilot)',\n ' otto auth status 查看各 provider 凭据来源',\n ' otto auth logout <provider> 注销并清除凭据',\n ],\n run: async (ctx) => {\n const { runAuthCommand } = await import('./auth-command')\n return runAuthCommand(ctx.args, {\n bootstrap: {\n workspaceDir: ctx.options.projectDir,\n projectConfigPath: ctx.options.configPath,\n verbose: ctx.options.verbose,\n },\n stdout: ctx.stdout,\n stderr: ctx.stderr,\n })\n },\n },\n {\n name: 'config',\n helpLines: [\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 ],\n run: async (ctx) => {\n const { runConfigMigrationCommand } = await import('./commands/config-migration')\n return runConfigMigrationCommand(ctx.args, { stdout: ctx.stdout, stderr: ctx.stderr })\n },\n },\n {\n name: 'migrate',\n helpLines: [\n ' otto migrate export [--no-sessions] [-o <path>] 导出配置+插件清单+会话数据为迁移包(换电脑用)',\n ' otto migrate import <archive> [--no-sessions] 导入迁移包(目标已有会话数据时跳过会话部分)',\n ],\n run: async (ctx) => {\n const { runMigrateCommand } = await import('./commands/migrate')\n return runMigrateCommand(ctx.args, { stdout: ctx.stdout, stderr: ctx.stderr })\n },\n },\n {\n name: 'observe',\n helpLines: [\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 ],\n run: async (ctx) => {\n const { runObserveCommand } = await import('./commands/observe')\n return runObserveCommand(ctx.args)\n },\n },\n {\n name: 'debug',\n helpLines: [' otto debug <action> [...] (alias: otto observe debug)'],\n run: async (ctx) => {\n const { runObserveCommand } = await import('./commands/observe')\n return runObserveCommand(['debug', ...ctx.args])\n },\n },\n {\n name: 'time-travel',\n helpLines: [' otto time-travel <action> <id> (alias: otto observe replay)'],\n run: async (ctx) => {\n const { runObserveCommand } = await import('./commands/observe')\n return runObserveCommand(['replay', ...ctx.args])\n },\n },\n {\n name: 'mcp',\n helpLines: [\n ' otto mcp login <name> MCP server OAuth 登录',\n ' otto mcp logout <name> 登出 MCP server',\n ' otto mcp list 列出已认证的 MCP server',\n ],\n run: async (ctx) => {\n const { runMcpCommand } = await import('./commands/mcp')\n return runMcpCommand(ctx.args)\n },\n },\n {\n name: 'extension',\n helpLines: [\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 ],\n run: async (ctx) => {\n const { runExtensionCommand } = await import('./commands/extension')\n return runExtensionCommand(ctx.args, { stdout: ctx.stdout, stderr: ctx.stderr })\n },\n },\n {\n name: 'persistence',\n helpLines: [\n ' otto persistence local [...flags] 启动独立持久化服务(storage + events + sync)',\n ' otto persistence remote [...flags] 启动最小化远程持久化服务(5 REST 端点)',\n ],\n run: async (ctx) => {\n const { runPersistenceCommand } = await import('./commands/persistence')\n return runPersistenceCommand(ctx.args)\n },\n },\n {\n name: 'persistenced',\n helpLines: [' otto persistenced [...flags] (alias: otto persistence local)'],\n run: async (ctx) => {\n const { runPersistenceCommand } = await import('./commands/persistence')\n return runPersistenceCommand(['local', ...ctx.args])\n },\n },\n {\n name: 'remote-persistence-server',\n helpLines: [' otto remote-persistence-server [...flags] (alias: otto persistence remote)'],\n run: async (ctx) => {\n const { runPersistenceCommand } = await import('./commands/persistence')\n return runPersistenceCommand(['remote', ...ctx.args])\n },\n },\n {\n name: 'ps',\n helpLines: [' otto ps [--sweep] [--include-weak] [--json] 进程群列表与孤儿清扫(SIGTERM only,RFC-196)'],\n run: async (ctx) => {\n const { runPsCommand } = await import('./commands/ps')\n return runPsCommand(ctx.args, { stdout: ctx.stdout, stderr: ctx.stderr })\n },\n },\n {\n name: 'export-trajectory',\n helpLines: [' otto export-trajectory --session <id> --out <path> [--since-seq <n>] 导出会话轨迹 JSONL(RFC-385;默认关)'],\n run: async (ctx) => {\n const { runExportTrajectoryCommand } = await import('./commands/export-trajectory')\n return runExportTrajectoryCommand(ctx.args, { stdout: ctx.stdout, stderr: ctx.stderr })\n },\n },\n\n // ── 内部/未文档化命令(不进 --help 命令段:无 helpLines) ──\n {\n name: 'daemon',\n run: async (ctx) => {\n const { runDaemonCommand } = await import('./commands/daemon')\n return runDaemonCommand(ctx.args, {\n projectDir: ctx.options.projectDir,\n stdout: ctx.stdout,\n stderr: ctx.stderr,\n })\n },\n },\n {\n name: 'mcp-server',\n run: async (ctx) => {\n // RFC-384 M2 生产装配下沉至 commands/mcp-server.ts(RFC-410 M1 T4)——\n // 注册表条目只做路由,headless App 起停/信号编排归命令模块自身。\n const { runMcpServerCommandFromCli } = await import('./commands/mcp-server')\n return runMcpServerCommandFromCli(ctx.args, {\n projectDir: ctx.options.projectDir,\n modelId: ctx.options.model,\n })\n },\n },\n {\n name: 'feedback',\n run: async (ctx) => {\n const { runFeedbackCommand } = await import('./commands/feedback')\n return runFeedbackCommand(ctx.args, {\n stdout: ctx.stdout,\n stderr: ctx.stderr,\n projectDir: ctx.options.projectDir,\n configPath: ctx.options.configPath,\n })\n },\n },\n]\n\n/**\n * 保留字集合(插件不得覆盖,`parseCLI` 据此判定首参是内建命令还是插件候选名)。\n * 从目录派生——加命令改目录一处即自动跟随。\n */\nexport const SUB_COMMAND_NAMES: ReadonlySet<string> = new Set(\n TOP_LEVEL_COMMANDS.map((c) => c.name),\n)\n\n/** dispatch 查表:name → run。缺省 run 的条目(run)不入表——由 parseCLI 特判。 */\nconst DISPATCH: ReadonlyMap<string, (ctx: SubCommandContext) => Promise<number>> = new Map(\n TOP_LEVEL_COMMANDS.filter((c) => c.run).map((c) => [c.name, c.run!]),\n)\n\n/**\n * 分发顶层子命令。返回 `undefined` 表示该名无 dispatch 处理器(如 `run`,或未来的纯保留名)\n * ——调用方应回退到既有路径(prompt 处理)。\n */\nexport function runTopCommand(name: string, ctx: SubCommandContext): Promise<number> | undefined {\n const run = DISPATCH.get(name)\n return run ? run(ctx) : undefined\n}\n\n/** `--help` 命令段文本行(从目录 `helpLines` 按数组顺序拼接,单源不漂移)。 */\nexport function getCommandHelpLines(): string[] {\n return TOP_LEVEL_COMMANDS.flatMap((c) => c.helpLines ?? [])\n}\n","import { resolve } from 'node:path'\n\nimport { SERVICE_PORT } from '@x-otto/env'\n\nimport type { CLIOptions, RunMode } from './types'\nimport { getVersion } from './version'\nimport { getCommandHelpLines, SUB_COMMAND_NAMES } from './top-command-catalog'\n\nexport type SubCommand = string\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 /**\n * RFC-410 M1 T2:子命令位置参数**数组**(原为 join(' ') 字符串中间态,消费方 split(' ')\n * 还原——带空格引号参数 `otto feedback --title \"a b\"` 会被切碎)。直接存数组,无损。\n */\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/**\n * RFC-410 M1 T1:保留字集合从顶层命令目录派生(单一真相源)。此处再导出,供 cli.ts 与\n * 既有消费点从 `./parse-cli` 引入(原地定义已迁至 top-command-catalog.ts)。\n */\nexport { SUB_COMMAND_NAMES }\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: string[] = []\n let unresolvedCommandName: string | undefined\n let unresolvedCommandArgs: string[] | undefined\n if (first && SUB_COMMAND_NAMES.has(first)) {\n subCommand = first\n subCommandArgs = args.slice(1)\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 ' --attach <id> attach 到 `otto serve` 上运行的会话(只读预览 + Chat.query 写)',\n ' --force-write-lease attach 时强抢写权(kill -9 造成 stuck lease 的正式解锁口;服务端 audit 记录)',\n ' --id <workspace> 显式 workspace id(一目录多 workspace DevPod 模式)',\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 // RFC-410 M1 T1:命令段从顶层命令目录单源派生(原为手写,与注册表漂移——\n // 实测 daemon/mcp-server/feedback 有意不列,--attach/--id 曾漏进帮助)。\n ...getCommandHelpLines(),\n '',\n `面板命令默认连 \\`otto serve\\` 实例(http://127.0.0.1:${SERVICE_PORT},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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"}
1
+ {"version":3,"file":"parse-cli-B9Go-f6A.js","names":[],"sources":["../src/top-command-catalog.ts","../src/parse-cli.ts"],"sourcesContent":["/**\n * top-command-catalog.ts —— 顶层子命令单一真相源(RFC-410 M1 T1)。\n *\n * ## 要解决的问题\n * 此前顶层命令面有**四处真源**,加一个子命令要同步改 4 处,漏一处即静默漂移:\n * - `parse-cli.ts` 的 `SubCommand` 类型联合\n * - `parse-cli.ts` 的 `SUB_COMMAND_NAMES` 集合\n * - `cli.ts` 的 `SUB_COMMANDS` Map(dispatch)\n * - `parse-cli.ts` 的 `getHelpLines()` 手写帮助\n * 实测漂移:`--attach`/`--id`/`--force-write-lease` 在 OPTIONS 表有、`--help` 无。\n *\n * ## 单源方案(复刻 RFC-074 D2 斜杠命令目录已验证的模式)\n * 本文件的 `TOP_LEVEL_COMMANDS` 是顶层命令名的**唯一真相源**:\n * - `SUB_COMMAND_NAMES`(保留字集合)从目录 `flatMap` 派生\n * - dispatch 经 `runTopCommand(name, ctx)` 查目录 `run`\n * - 命令段帮助文本从目录条目的 `helpLines` 按**数组顺序**拼接(数组顺序 = 帮助显示顺序)\n * `SubCommand` 类型联合就此退役为 `string`——类型联合与目录双真源正是漂移根因,砍掉一边。\n *\n * ## 惰性加载保持不变(RFC-150 D3)\n * 每个 `run` 内部动态 `import()` 对应模块——只有命中该子命令才加载其代码与传递依赖,\n * `otto doctor`/`otto extension` 等轻命令不承担全部子命令处理器的 import 开销。\n *\n * ## `run` 是特例\n * `otto run <prompt>` 不走 dispatch——`parseCLI` 特判它(设 prompt + print 模式后落回\n * prompt 主路径)。故它在目录里**无 `run` 处理器**(仅占名 + 帮助行),既进保留字集合\n * (不被当插件候选名),又不进 dispatch。\n */\nimport type { SubCommandContext } from './types'\n\n/** 顶层子命令目录条目。数组顺序即 `--help` 命令段显示顺序。 */\nexport interface TopCommandEntry {\n /** 命令名(`otto <name> ...`)。 */\n name: string\n /**\n * dispatch 处理器(动态 import 对应模块)。缺省 = 保留名但不 dispatch:\n * 目前仅 `run`(由 `parseCLI` 特判为 prompt + print 模式)。\n */\n run?: (ctx: SubCommandContext) => Promise<number>\n /**\n * 命令段帮助文本行(精确到既有 `getHelpLines()` 的逐行文案,保持字节一致)。\n * 缺省 = 不在 `--help` 命令段列出(内部/未文档化命令:daemon / mcp-server / feedback)。\n */\n helpLines?: string[]\n}\n\n/**\n * 顶层命令目录(单一真相源)。**数组顺序 = 帮助显示顺序**,与注册无关的保留字/dispatch\n * 都从本数组派生,故顺序只影响帮助呈现。可见命令在前(按帮助分组排列),内部命令在后。\n */\nexport const TOP_LEVEL_COMMANDS: TopCommandEntry[] = [\n // ── run(保留名,无 dispatch:parseCLI 特判为 prompt + print) ──\n {\n name: 'run',\n helpLines: [' otto run <prompt> 执行一次性任务'],\n },\n {\n name: 'serve',\n helpLines: [' otto serve [--port N] [--inspect] [--static <dir>] [--token <t>] 启动 HTTP/WS service(--static 同源托管前端 SPA,--token 启用鉴权)'],\n run: async (ctx) => {\n const { runServeCommand } = await import('./commands/serve')\n return runServeCommand(ctx.args, {\n projectDir: ctx.options.projectDir,\n configPath: ctx.options.configPath,\n modelId: ctx.options.model,\n verbose: ctx.options.verbose,\n })\n },\n },\n {\n name: 'doctor',\n helpLines: [' otto doctor [--summary|--json] 环境健康检查(环境/配置/存储状态/连通性)'],\n run: async (ctx) => {\n const { runDoctorCommand } = await import('./commands/doctor')\n return runDoctorCommand(ctx.args, { stdout: ctx.stdout, stderr: ctx.stderr })\n },\n },\n {\n name: 'auth',\n helpLines: [\n ' otto auth login <provider> 登录(cursor / anthropic / openai-codex / github-copilot)',\n ' otto auth status 查看各 provider 凭据来源',\n ' otto auth logout <provider> 注销并清除凭据',\n ],\n run: async (ctx) => {\n const { runAuthCommand } = await import('./auth-command')\n return runAuthCommand(ctx.args, {\n bootstrap: {\n workspaceDir: ctx.options.projectDir,\n projectConfigPath: ctx.options.configPath,\n verbose: ctx.options.verbose,\n },\n stdout: ctx.stdout,\n stderr: ctx.stderr,\n })\n },\n },\n {\n name: 'config',\n helpLines: [\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 ],\n run: async (ctx) => {\n const { runConfigMigrationCommand } = await import('./commands/config-migration')\n return runConfigMigrationCommand(ctx.args, { stdout: ctx.stdout, stderr: ctx.stderr })\n },\n },\n {\n name: 'migrate',\n helpLines: [\n ' otto migrate export [--no-sessions] [-o <path>] 导出配置+插件清单+会话数据为迁移包(换电脑用)',\n ' otto migrate import <archive> [--no-sessions] 导入迁移包(目标已有会话数据时跳过会话部分)',\n ],\n run: async (ctx) => {\n const { runMigrateCommand } = await import('./commands/migrate')\n return runMigrateCommand(ctx.args, { stdout: ctx.stdout, stderr: ctx.stderr })\n },\n },\n {\n name: 'observe',\n helpLines: [\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 ],\n run: async (ctx) => {\n const { runObserveCommand } = await import('./commands/observe')\n return runObserveCommand(ctx.args)\n },\n },\n {\n name: 'debug',\n helpLines: [' otto debug <action> [...] (alias: otto observe debug)'],\n run: async (ctx) => {\n const { runObserveCommand } = await import('./commands/observe')\n return runObserveCommand(['debug', ...ctx.args])\n },\n },\n {\n name: 'time-travel',\n helpLines: [' otto time-travel <action> <id> (alias: otto observe replay)'],\n run: async (ctx) => {\n const { runObserveCommand } = await import('./commands/observe')\n return runObserveCommand(['replay', ...ctx.args])\n },\n },\n {\n name: 'mcp',\n helpLines: [\n ' otto mcp login <name> MCP server OAuth 登录',\n ' otto mcp logout <name> 登出 MCP server',\n ' otto mcp list 列出已认证的 MCP server',\n ],\n run: async (ctx) => {\n const { runMcpCommand } = await import('./commands/mcp')\n return runMcpCommand(ctx.args)\n },\n },\n {\n name: 'extension',\n helpLines: [\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 ],\n run: async (ctx) => {\n const { runExtensionCommand } = await import('./commands/extension')\n return runExtensionCommand(ctx.args, { stdout: ctx.stdout, stderr: ctx.stderr })\n },\n },\n {\n name: 'persistence',\n helpLines: [\n ' otto persistence local [...flags] 启动独立持久化服务(storage + events + sync)',\n ' otto persistence remote [...flags] 启动最小化远程持久化服务(5 REST 端点)',\n ],\n run: async (ctx) => {\n const { runPersistenceCommand } = await import('./commands/persistence')\n return runPersistenceCommand(ctx.args)\n },\n },\n {\n name: 'persistenced',\n helpLines: [' otto persistenced [...flags] (alias: otto persistence local)'],\n run: async (ctx) => {\n const { runPersistenceCommand } = await import('./commands/persistence')\n return runPersistenceCommand(['local', ...ctx.args])\n },\n },\n {\n name: 'remote-persistence-server',\n helpLines: [' otto remote-persistence-server [...flags] (alias: otto persistence remote)'],\n run: async (ctx) => {\n const { runPersistenceCommand } = await import('./commands/persistence')\n return runPersistenceCommand(['remote', ...ctx.args])\n },\n },\n {\n name: 'ps',\n helpLines: [' otto ps [--sweep] [--include-weak] [--json] 进程群列表与孤儿清扫(SIGTERM only,RFC-196)'],\n run: async (ctx) => {\n const { runPsCommand } = await import('./commands/ps')\n return runPsCommand(ctx.args, { stdout: ctx.stdout, stderr: ctx.stderr })\n },\n },\n {\n name: 'export-trajectory',\n helpLines: [' otto export-trajectory --session <id> --out <path> [--since-seq <n>] 导出会话轨迹 JSONL(RFC-385;默认关)'],\n run: async (ctx) => {\n const { runExportTrajectoryCommand } = await import('./commands/export-trajectory')\n return runExportTrajectoryCommand(ctx.args, { stdout: ctx.stdout, stderr: ctx.stderr })\n },\n },\n\n // ── 内部/未文档化命令(不进 --help 命令段:无 helpLines) ──\n {\n name: 'daemon',\n run: async (ctx) => {\n const { runDaemonCommand } = await import('./commands/daemon')\n return runDaemonCommand(ctx.args, {\n projectDir: ctx.options.projectDir,\n stdout: ctx.stdout,\n stderr: ctx.stderr,\n })\n },\n },\n {\n name: 'mcp-server',\n run: async (ctx) => {\n // RFC-384 M2 生产装配下沉至 commands/mcp-server.ts(RFC-410 M1 T4)——\n // 注册表条目只做路由,headless App 起停/信号编排归命令模块自身。\n const { runMcpServerCommandFromCli } = await import('./commands/mcp-server')\n return runMcpServerCommandFromCli(ctx.args, {\n projectDir: ctx.options.projectDir,\n modelId: ctx.options.model,\n })\n },\n },\n {\n name: 'feedback',\n run: async (ctx) => {\n const { runFeedbackCommand } = await import('./commands/feedback')\n return runFeedbackCommand(ctx.args, {\n stdout: ctx.stdout,\n stderr: ctx.stderr,\n projectDir: ctx.options.projectDir,\n configPath: ctx.options.configPath,\n })\n },\n },\n]\n\n/**\n * 保留字集合(插件不得覆盖,`parseCLI` 据此判定首参是内建命令还是插件候选名)。\n * 从目录派生——加命令改目录一处即自动跟随。\n */\nexport const SUB_COMMAND_NAMES: ReadonlySet<string> = new Set(\n TOP_LEVEL_COMMANDS.map((c) => c.name),\n)\n\n/** dispatch 查表:name → run。缺省 run 的条目(run)不入表——由 parseCLI 特判。 */\nconst DISPATCH: ReadonlyMap<string, (ctx: SubCommandContext) => Promise<number>> = new Map(\n TOP_LEVEL_COMMANDS.filter((c) => c.run).map((c) => [c.name, c.run!]),\n)\n\n/**\n * 分发顶层子命令。返回 `undefined` 表示该名无 dispatch 处理器(如 `run`,或未来的纯保留名)\n * ——调用方应回退到既有路径(prompt 处理)。\n */\nexport function runTopCommand(name: string, ctx: SubCommandContext): Promise<number> | undefined {\n const run = DISPATCH.get(name)\n return run ? run(ctx) : undefined\n}\n\n/** `--help` 命令段文本行(从目录 `helpLines` 按数组顺序拼接,单源不漂移)。 */\nexport function getCommandHelpLines(): string[] {\n return TOP_LEVEL_COMMANDS.flatMap((c) => c.helpLines ?? [])\n}\n","import { resolve } from 'node:path'\n\nimport { SERVICE_PORT } from '@x-otto/env'\n\nimport type { CLIOptions, RunMode } from './types'\nimport { getVersion } from './version'\nimport { getCommandHelpLines, SUB_COMMAND_NAMES } from './top-command-catalog'\n\nexport type SubCommand = string\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 /**\n * RFC-410 M1 T2:子命令位置参数**数组**(原为 join(' ') 字符串中间态,消费方 split(' ')\n * 还原——带空格引号参数 `otto feedback --title \"a b\"` 会被切碎)。直接存数组,无损。\n */\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/**\n * RFC-410 M1 T1:保留字集合从顶层命令目录派生(单一真相源)。此处再导出,供 cli.ts 与\n * 既有消费点从 `./parse-cli` 引入(原地定义已迁至 top-command-catalog.ts)。\n */\nexport { SUB_COMMAND_NAMES }\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: string[] = []\n let unresolvedCommandName: string | undefined\n let unresolvedCommandArgs: string[] | undefined\n if (first && SUB_COMMAND_NAMES.has(first)) {\n subCommand = first\n subCommandArgs = args.slice(1)\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 ' --attach <id> attach 到 `otto serve` 上运行的会话(只读预览 + Chat.query 写)',\n ' --force-write-lease attach 时强抢写权(kill -9 造成 stuck lease 的正式解锁口;服务端 audit 记录)',\n ' --id <workspace> 显式 workspace id(一目录多 workspace DevPod 模式)',\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 // RFC-410 M1 T1:命令段从顶层命令目录单源派生(原为手写,与注册表漂移——\n // 实测 daemon/mcp-server/feedback 有意不列,--attach/--id 曾漏进帮助)。\n ...getCommandHelpLines(),\n '',\n `面板命令默认连 \\`otto serve\\` 实例(http://127.0.0.1:${SERVICE_PORT},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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"}
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{t as e}from"./create-host-app-BgjEc81C.js";import{t}from"./daemon-base-D015ew5y.js";import{SERVICE_PORT as n}from"@x-otto/env";import{resolve as r}from"node:path";import{createCodingService as i}from"@x-otto/service";async function a(n,r={}){let a=r.stdout??(e=>process.stdout.write(e)),c=r.stderr??(e=>process.stderr.write(e));if(n[0]===`help`||n[0]===`--help`)return s(a),0;let l,u,d,f,p;try{({port:l,inspect:u,staticDir:d,token:f}=o(n))}catch(e){return c(`otto serve: ${e instanceof Error?e.message:String(e)}\n`),1}let m=r.app??e({logger:{name:`otto-serve`,level:r.verbose?`debug`:`info`,destination:`stderr`},inspect:u,workspaceDir:r.projectDir,projectConfigPath:r.configPath,modelId:r.modelId});try{await m.start()}catch(e){return c(`otto serve: failed to start app: ${e instanceof Error?e.message:String(e)}\n`),1}if(`agentJobs`in m&&typeof m.start==`function`){let{setupScheduleService:e}=await import(`./interactive-schedule-aQEHfUKL.js`).then(e=>e.t),{FireLogger:t,DEFAULT_SCHEDULE_CONFIG:n}=await import(`@x-otto/schedule`),r=new t(n);try{let{scheduler:t}=e(m,{});p=t,r.write(`daemon-scheduler`,`headless scheduler assembled (job-only)`)}catch(e){let t=e instanceof Error?e.message:String(e);m.logger?.warn?.({err:t},`[schedule] serve headless scheduler setup failed (non-fatal)`),r.write(`daemon-scheduler`,`headless scheduler setup FAILED: ${t}`)}}let h=i(m,{port:l,host:`127.0.0.1`,staticDir:d,apiToken:f,wsAuthToken:f,authorityId:process.env.OTTO_AUTHORITY_ID||void 0});try{await h.start()}catch(e){return c(`otto serve: failed to listen on port ${l}: ${e instanceof Error?e.message:String(e)}\n`),await m.stop(),1}let g=h.httpServer.address().port;if(a(`otto service listening on http://127.0.0.1:${g}${f?` (auth: token required)`:``}
2
+ import{t as e}from"./create-host-app-BtifD7KK.js";import{t}from"./daemon-base-D015ew5y.js";import{SERVICE_PORT as n}from"@x-otto/env";import{resolve as r}from"node:path";import{createCodingService as i}from"@x-otto/service";async function a(n,r={}){let a=r.stdout??(e=>process.stdout.write(e)),c=r.stderr??(e=>process.stderr.write(e));if(n[0]===`help`||n[0]===`--help`)return s(a),0;let l,u,d,f,p;try{({port:l,inspect:u,staticDir:d,token:f}=o(n))}catch(e){return c(`otto serve: ${e instanceof Error?e.message:String(e)}\n`),1}let m=r.app??e({logger:{name:`otto-serve`,level:r.verbose?`debug`:`info`,destination:`stderr`},inspect:u,workspaceDir:r.projectDir,projectConfigPath:r.configPath,modelId:r.modelId});try{await m.start()}catch(e){return c(`otto serve: failed to start app: ${e instanceof Error?e.message:String(e)}\n`),1}if(`agentJobs`in m&&typeof m.start==`function`){let{setupScheduleService:e}=await import(`./interactive-schedule-BKiI3Qrw.js`).then(e=>e.t),{FireLogger:t,DEFAULT_SCHEDULE_CONFIG:n}=await import(`@x-otto/schedule`),r=new t(n);try{let{scheduler:t}=e(m,{});p=t,r.write(`daemon-scheduler`,`headless scheduler assembled (job-only)`)}catch(e){let t=e instanceof Error?e.message:String(e);m.logger?.warn?.({err:t},`[schedule] serve headless scheduler setup failed (non-fatal)`),r.write(`daemon-scheduler`,`headless scheduler setup FAILED: ${t}`)}}let h=i(m,{port:l,host:`127.0.0.1`,staticDir:d,apiToken:f,wsAuthToken:f,authorityId:process.env.OTTO_AUTHORITY_ID||void 0});try{await h.start()}catch(e){return c(`otto serve: failed to listen on port ${l}: ${e instanceof Error?e.message:String(e)}\n`),await m.stop(),1}let g=h.httpServer.address().port;if(a(`otto service listening on http://127.0.0.1:${g}${f?` (auth: token required)`:``}
3
3
  ${d?` web: http://127.0.0.1:${g}/${f?` (打开时附加 ?token=<你的 token>)`:``} (静态页面同源托管 ${d})\n`:``} panels: otto observe summary <session-id>
4
4
  otto time-travel checkpoints <session-id>
5
5
  otto debug breakpoints list${u?``:" (debugger disabled — restart with `otto serve --inspect`)"}
@@ -18,4 +18,4 @@ ${d?` web: http://127.0.0.1:${g}/${f?` (打开时附加 ?token=<你的 to
18
18
 
19
19
  面板命令默认连 http://127.0.0.1:${n}(可 --remote 覆盖)
20
20
  `)}export{a as runServeCommand};
21
- //# sourceMappingURL=serve-DHqx-Mab.js.map
21
+ //# sourceMappingURL=serve-ChSSkjLs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"serve-DHqx-Mab.js","names":[],"sources":["../src/commands/serve.ts"],"sourcesContent":["import type { AddressInfo } from 'node:net'\nimport { resolve } from 'node:path'\nimport { SERVICE_PORT } from '@x-otto/env'\nimport { createHostApp } from '../create-host-app'\nimport { createCodingService } from '@x-otto/service'\nimport type { AppContext } from '@x-otto/coding'\n\nimport { waitForShutdown } from './daemon-base'\n\nexport interface ServeDeps {\n stdout?: (s: string) => void\n stderr?: (s: string) => void\n /** 全局 CLI 选项透传(与 `otto run` 同源装配)。 */\n projectDir?: string\n configPath?: string\n modelId?: string\n verbose?: boolean\n /** 测试注入:替代生产装配的 app(结构 = AppContext + start/stop)。 */\n app?: AppContext & { start(): Promise<void>; stop(): Promise<void> }\n /** 测试注入:优雅退出信号(生产 = SIGINT/SIGTERM)。 */\n signal?: AbortSignal\n}\n\nexport async function runServeCommand(args: string[], deps: ServeDeps = {}): Promise<number> {\n const stdout = deps.stdout ?? ((s) => process.stdout.write(s))\n const stderr = deps.stderr ?? ((s) => process.stderr.write(s))\n\n if (args[0] === 'help' || args[0] === '--help') {\n printHelp(stdout)\n return 0\n }\n\n let port: number\n let inspect: boolean\n let staticDir: string | undefined\n let token: string | undefined\n /** RFC-435 D2:serve headless scheduler(无人值守定时任务)。退出时 stop 释放 ownership。 */\n let appScheduler: { stop(): Promise<void> } | undefined\n try {\n ;({ port, inspect, staticDir, token } = parseServeArgs(args))\n } catch (error) {\n stderr(`otto serve: ${error instanceof Error ? error.message : String(error)}\\n`)\n return 1\n }\n\n const app =\n deps.app ??\n createHostApp({\n logger: {\n name: 'otto-serve',\n level: deps.verbose ? 'debug' : 'info',\n destination: 'stderr',\n },\n // `--inspect` 必须下传:`AppOptions.inspect` 是 `buildInspectDevtools` 的唯一开关\n // (devtools-setup.ts:19 `if (!inspect) return { devtools: null }`)。此前 serve 解析了\n // 该 flag、也据它打印\"调试器已启用\"提示,却从未传给 createApp —— 于是 `app.devtools`\n // 恒为 null,`/api/debugger/*` 全部 503、DebugBridge 也不会被构造。即 `otto serve\n // --inspect` 实际从不启用调试器(提示与事实相反)。\n inspect,\n workspaceDir: deps.projectDir,\n projectConfigPath: deps.configPath,\n modelId: deps.modelId,\n })\n\n try {\n await app.start()\n } catch (error) {\n stderr(`otto serve: failed to start app: ${error instanceof Error ? error.message : String(error)}\\n`)\n return 1\n }\n\n // RFC-435 D2:serve/daemon 无人值守装配 headless SchedulerService——job/subscriber 类\n // cron 任务在交互会话关闭后仍到点执行(用户原始诉求\"每天凌晨自动维护文档\")。\n // 无 sessionRef/tui → job-only 形态(session 类任务跳过 + fire log 记录,RFC-435 D2)。\n // fail-soft:装配失败不阻断 serve 主功能(调度是附加能力,R3)。\n // 仅完整 App(createHostApp 生产路径)装配——测试注入的 AppContext 桩无 agentJobs/\n // panelBackendController 等调度依赖,跳过(T435-5 用 fake app 直接验证装配调用)。\n if ('agentJobs' in app && typeof app.start === 'function') {\n const { setupScheduleService } = await import('./interactive-schedule')\n const { FireLogger, DEFAULT_SCHEDULE_CONFIG } = await import('@x-otto/schedule')\n const serveFireLogger = new FireLogger(DEFAULT_SCHEDULE_CONFIG)\n try {\n const { scheduler } = setupScheduleService(app, {})\n // serve 生命周期:优雅退出时释放 ownership(stop),避免锁残留 5min stale 窗口。\n appScheduler = scheduler\n // D5:无人值守装配成功落盘可查(daemon 壳 stdio:'ignore',stderr 日志不可见)。\n serveFireLogger.write('daemon-scheduler', 'headless scheduler assembled (job-only)')\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error)\n app.logger?.warn?.({ err: msg }, '[schedule] serve headless scheduler setup failed (non-fatal)')\n // D5:装配失败落盘——无人值守时用户第二天必须能查到调度未起。\n serveFireLogger.write('daemon-scheduler', `headless scheduler setup FAILED: ${msg}`)\n }\n }\n\n // --token 同时启用 HTTP Bearer(apiToken)与 WS ?token=(wsAuthToken);页面用 URL `?token=` 对上。\n const service = createCodingService(app, {\n port,\n host: '127.0.0.1',\n staticDir,\n apiToken: token,\n wsAuthToken: token,\n // RFC-335 M2:由 `otto daemon start` 下传,使 AuthorityDescriptor.instanceId 与\n // daemon state 记录一致(客户端据此确认\"连上的 authority 就是 state 里那个 daemon\")。\n // 前台直接 `otto serve` 时缺省,由 service 自行生成。\n authorityId: process.env['OTTO_AUTHORITY_ID'] || undefined,\n })\n\n try {\n await service.start()\n } catch (error) {\n stderr(`otto serve: failed to listen on port ${port}: ${error instanceof Error ? error.message : String(error)}\\n`)\n await app.stop()\n return 1\n }\n\n const actualPort = (service.httpServer.address() as AddressInfo).port\n // 不把 token 明文打进 stdout(会落 scrollback/CI 日志)——只提示需附加 ?token=。\n const webHint = token ? ' (打开时附加 ?token=<你的 token>)' : ''\n stdout(`otto service listening on http://127.0.0.1:${actualPort}${token ? ' (auth: token required)' : ''}\n${staticDir ? ` web: http://127.0.0.1:${actualPort}/${webHint} (静态页面同源托管 ${staticDir})\\n` : ''} panels: otto observe summary <session-id>\n otto time-travel checkpoints <session-id>\n otto debug breakpoints list${inspect ? '' : ' (debugger disabled — restart with `otto serve --inspect`)'}\n stop: Ctrl-C\n`)\n\n await waitForShutdown(deps.signal)\n\n stdout('otto serve: shutting down...\\n')\n // RFC-435 D4:先 stop scheduler(release fire ownership,避免锁残留 stale 窗口,\n // 交互会话/其他 daemon 立即可接管),再停 service/app。\n if (appScheduler) {\n try {\n await appScheduler.stop()\n } catch (error) {\n app.logger?.warn?.(\n { err: error instanceof Error ? error.message : String(error) },\n '[schedule] serve scheduler stop failed',\n )\n }\n }\n await service.stop()\n await app.stop()\n // 硬退出:service.stop()/app.stop() 可能留下孤儿 handle(worker_threads port、\n // DB pool、trace tail),吊住事件循环导致 Ctrl+C 后进程不退。与 interactive 路径\n // (cli.ts 末尾 process.exit)对齐。stdio 已同步写,无截断风险。\n process.exit(0)\n}\n\nfunction parseServeArgs(args: string[]): {\n port: number\n inspect: boolean\n staticDir?: string\n token?: string\n} {\n let port = SERVICE_PORT\n let inspect = false\n let staticDir: string | undefined\n let token: string | undefined\n\n // 取下一个值;缺失或形如 --flag(误把下个选项当值)即报错,避免静默吞掉选项。\n const takeValue = (i: number, name: string): string => {\n const raw = args[i + 1]\n if (raw === undefined || raw.startsWith('--')) {\n throw new Error(`${name} requires a value`)\n }\n return raw\n }\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]\n if (arg === '--port' || arg === '-p') {\n const raw = takeValue(i, '--port')\n i++\n const n = Number(raw)\n if (!Number.isInteger(n) || n < 0 || n > 65535) {\n throw new Error(`invalid --port value: ${raw}`)\n }\n port = n\n } else if (arg === '--inspect') {\n inspect = true\n } else if (arg === '--static') {\n staticDir = resolve(takeValue(i, '--static'))\n i++\n } else if (arg === '--token') {\n token = takeValue(i, '--token')\n i++\n }\n // 其它(含全局 flag --verbose/--model 等,经 ctx.args 透传进来)忽略——\n // 不能 throw,否则 `otto serve --verbose` 会误伤(全局 flag 由 parseCLI 已解析)。\n }\n\n return { port, inspect, staticDir, token }\n}\n\nfunction printHelp(stdout: (s: string) => void): void {\n stdout(`otto serve — 启动 HTTP/WS service(观测/调试/时间旅行面板的对端)\n\n用法:\n otto serve [--port <n>] [--inspect] [--static <dir>] [--token <t>]\n\n选项:\n --port, -p <n> 监听端口(默认 ${SERVICE_PORT},可经 OTTO_SERVICE_PORT 覆盖;0 = 随机)\n --inspect 启用调试器(断点/暂停面板需要)\n --static <dir> 同源托管一个已构建的前端目录(SPA),页面与 /api、/ws 同端口免 CORS\n --token <t> 启用鉴权:HTTP /api/* 需 Bearer,WS 需 ?token=;页面用 URL ?token=<t> 对上\n\n面板命令默认连 http://127.0.0.1:${SERVICE_PORT}(可 --remote 覆盖)\n`)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"serve-ChSSkjLs.js","names":[],"sources":["../src/commands/serve.ts"],"sourcesContent":["import type { AddressInfo } from 'node:net'\nimport { resolve } from 'node:path'\nimport { SERVICE_PORT } from '@x-otto/env'\nimport { createHostApp } from '../create-host-app'\nimport { createCodingService } from '@x-otto/service'\nimport type { AppContext } from '@x-otto/coding'\n\nimport { waitForShutdown } from './daemon-base'\n\nexport interface ServeDeps {\n stdout?: (s: string) => void\n stderr?: (s: string) => void\n /** 全局 CLI 选项透传(与 `otto run` 同源装配)。 */\n projectDir?: string\n configPath?: string\n modelId?: string\n verbose?: boolean\n /** 测试注入:替代生产装配的 app(结构 = AppContext + start/stop)。 */\n app?: AppContext & { start(): Promise<void>; stop(): Promise<void> }\n /** 测试注入:优雅退出信号(生产 = SIGINT/SIGTERM)。 */\n signal?: AbortSignal\n}\n\nexport async function runServeCommand(args: string[], deps: ServeDeps = {}): Promise<number> {\n const stdout = deps.stdout ?? ((s) => process.stdout.write(s))\n const stderr = deps.stderr ?? ((s) => process.stderr.write(s))\n\n if (args[0] === 'help' || args[0] === '--help') {\n printHelp(stdout)\n return 0\n }\n\n let port: number\n let inspect: boolean\n let staticDir: string | undefined\n let token: string | undefined\n /** RFC-435 D2:serve headless scheduler(无人值守定时任务)。退出时 stop 释放 ownership。 */\n let appScheduler: { stop(): Promise<void> } | undefined\n try {\n ;({ port, inspect, staticDir, token } = parseServeArgs(args))\n } catch (error) {\n stderr(`otto serve: ${error instanceof Error ? error.message : String(error)}\\n`)\n return 1\n }\n\n const app =\n deps.app ??\n createHostApp({\n logger: {\n name: 'otto-serve',\n level: deps.verbose ? 'debug' : 'info',\n destination: 'stderr',\n },\n // `--inspect` 必须下传:`AppOptions.inspect` 是 `buildInspectDevtools` 的唯一开关\n // (devtools-setup.ts:19 `if (!inspect) return { devtools: null }`)。此前 serve 解析了\n // 该 flag、也据它打印\"调试器已启用\"提示,却从未传给 createApp —— 于是 `app.devtools`\n // 恒为 null,`/api/debugger/*` 全部 503、DebugBridge 也不会被构造。即 `otto serve\n // --inspect` 实际从不启用调试器(提示与事实相反)。\n inspect,\n workspaceDir: deps.projectDir,\n projectConfigPath: deps.configPath,\n modelId: deps.modelId,\n })\n\n try {\n await app.start()\n } catch (error) {\n stderr(`otto serve: failed to start app: ${error instanceof Error ? error.message : String(error)}\\n`)\n return 1\n }\n\n // RFC-435 D2:serve/daemon 无人值守装配 headless SchedulerService——job/subscriber 类\n // cron 任务在交互会话关闭后仍到点执行(用户原始诉求\"每天凌晨自动维护文档\")。\n // 无 sessionRef/tui → job-only 形态(session 类任务跳过 + fire log 记录,RFC-435 D2)。\n // fail-soft:装配失败不阻断 serve 主功能(调度是附加能力,R3)。\n // 仅完整 App(createHostApp 生产路径)装配——测试注入的 AppContext 桩无 agentJobs/\n // panelBackendController 等调度依赖,跳过(T435-5 用 fake app 直接验证装配调用)。\n if ('agentJobs' in app && typeof app.start === 'function') {\n const { setupScheduleService } = await import('./interactive-schedule')\n const { FireLogger, DEFAULT_SCHEDULE_CONFIG } = await import('@x-otto/schedule')\n const serveFireLogger = new FireLogger(DEFAULT_SCHEDULE_CONFIG)\n try {\n const { scheduler } = setupScheduleService(app, {})\n // serve 生命周期:优雅退出时释放 ownership(stop),避免锁残留 5min stale 窗口。\n appScheduler = scheduler\n // D5:无人值守装配成功落盘可查(daemon 壳 stdio:'ignore',stderr 日志不可见)。\n serveFireLogger.write('daemon-scheduler', 'headless scheduler assembled (job-only)')\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error)\n app.logger?.warn?.({ err: msg }, '[schedule] serve headless scheduler setup failed (non-fatal)')\n // D5:装配失败落盘——无人值守时用户第二天必须能查到调度未起。\n serveFireLogger.write('daemon-scheduler', `headless scheduler setup FAILED: ${msg}`)\n }\n }\n\n // --token 同时启用 HTTP Bearer(apiToken)与 WS ?token=(wsAuthToken);页面用 URL `?token=` 对上。\n const service = createCodingService(app, {\n port,\n host: '127.0.0.1',\n staticDir,\n apiToken: token,\n wsAuthToken: token,\n // RFC-335 M2:由 `otto daemon start` 下传,使 AuthorityDescriptor.instanceId 与\n // daemon state 记录一致(客户端据此确认\"连上的 authority 就是 state 里那个 daemon\")。\n // 前台直接 `otto serve` 时缺省,由 service 自行生成。\n authorityId: process.env['OTTO_AUTHORITY_ID'] || undefined,\n })\n\n try {\n await service.start()\n } catch (error) {\n stderr(`otto serve: failed to listen on port ${port}: ${error instanceof Error ? error.message : String(error)}\\n`)\n await app.stop()\n return 1\n }\n\n const actualPort = (service.httpServer.address() as AddressInfo).port\n // 不把 token 明文打进 stdout(会落 scrollback/CI 日志)——只提示需附加 ?token=。\n const webHint = token ? ' (打开时附加 ?token=<你的 token>)' : ''\n stdout(`otto service listening on http://127.0.0.1:${actualPort}${token ? ' (auth: token required)' : ''}\n${staticDir ? ` web: http://127.0.0.1:${actualPort}/${webHint} (静态页面同源托管 ${staticDir})\\n` : ''} panels: otto observe summary <session-id>\n otto time-travel checkpoints <session-id>\n otto debug breakpoints list${inspect ? '' : ' (debugger disabled — restart with `otto serve --inspect`)'}\n stop: Ctrl-C\n`)\n\n await waitForShutdown(deps.signal)\n\n stdout('otto serve: shutting down...\\n')\n // RFC-435 D4:先 stop scheduler(release fire ownership,避免锁残留 stale 窗口,\n // 交互会话/其他 daemon 立即可接管),再停 service/app。\n if (appScheduler) {\n try {\n await appScheduler.stop()\n } catch (error) {\n app.logger?.warn?.(\n { err: error instanceof Error ? error.message : String(error) },\n '[schedule] serve scheduler stop failed',\n )\n }\n }\n await service.stop()\n await app.stop()\n // 硬退出:service.stop()/app.stop() 可能留下孤儿 handle(worker_threads port、\n // DB pool、trace tail),吊住事件循环导致 Ctrl+C 后进程不退。与 interactive 路径\n // (cli.ts 末尾 process.exit)对齐。stdio 已同步写,无截断风险。\n process.exit(0)\n}\n\nfunction parseServeArgs(args: string[]): {\n port: number\n inspect: boolean\n staticDir?: string\n token?: string\n} {\n let port = SERVICE_PORT\n let inspect = false\n let staticDir: string | undefined\n let token: string | undefined\n\n // 取下一个值;缺失或形如 --flag(误把下个选项当值)即报错,避免静默吞掉选项。\n const takeValue = (i: number, name: string): string => {\n const raw = args[i + 1]\n if (raw === undefined || raw.startsWith('--')) {\n throw new Error(`${name} requires a value`)\n }\n return raw\n }\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]\n if (arg === '--port' || arg === '-p') {\n const raw = takeValue(i, '--port')\n i++\n const n = Number(raw)\n if (!Number.isInteger(n) || n < 0 || n > 65535) {\n throw new Error(`invalid --port value: ${raw}`)\n }\n port = n\n } else if (arg === '--inspect') {\n inspect = true\n } else if (arg === '--static') {\n staticDir = resolve(takeValue(i, '--static'))\n i++\n } else if (arg === '--token') {\n token = takeValue(i, '--token')\n i++\n }\n // 其它(含全局 flag --verbose/--model 等,经 ctx.args 透传进来)忽略——\n // 不能 throw,否则 `otto serve --verbose` 会误伤(全局 flag 由 parseCLI 已解析)。\n }\n\n return { port, inspect, staticDir, token }\n}\n\nfunction printHelp(stdout: (s: string) => void): void {\n stdout(`otto serve — 启动 HTTP/WS service(观测/调试/时间旅行面板的对端)\n\n用法:\n otto serve [--port <n>] [--inspect] [--static <dir>] [--token <t>]\n\n选项:\n --port, -p <n> 监听端口(默认 ${SERVICE_PORT},可经 OTTO_SERVICE_PORT 覆盖;0 = 随机)\n --inspect 启用调试器(断点/暂停面板需要)\n --static <dir> 同源托管一个已构建的前端目录(SPA),页面与 /api、/ws 同端口免 CORS\n --token <t> 启用鉴权:HTTP /api/* 需 Bearer,WS 需 ?token=;页面用 URL ?token=<t> 对上\n\n面板命令默认连 http://127.0.0.1:${SERVICE_PORT}(可 --remote 覆盖)\n`)\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{n as e}from"./build-id-BmQPXMGN.js";import{createRequire as t}from"node:module";const n=t(import.meta.url);function r(){return`${n(`../package.json`).version??`0.0.0`} (build ${e})`}export{r as t};
3
- //# sourceMappingURL=version-CdzXY8m2.js.map
2
+ import{n as e}from"./build-id-C94zHRXj.js";import{createRequire as t}from"node:module";const n=t(import.meta.url);function r(){return`${n(`../package.json`).version??`0.0.0`} (build ${e})`}export{r as t};
3
+ //# sourceMappingURL=version-BL66TSQP.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version-CdzXY8m2.js","names":[],"sources":["../src/version.ts"],"sourcesContent":["import { createRequire } from 'node:module'\n\nimport { BUILD_ID } from './build-id'\n\nconst require = createRequire(import.meta.url)\n\n/**\n * 版本号 + build ID。\n * 格式:`0.1.0 (build 2072)`。build ID = git rev-list --count HEAD,随每次构建自增。\n */\nexport function getVersion(): string {\n const pkg = require('../package.json') as { version?: string }\n const ver = pkg.version ?? '0.0.0'\n return `${ver} (build ${BUILD_ID})`\n}\n"],"mappings":";uFAIA,MAAM,EAAU,EAAc,OAAO,KAAK,IAAI,CAM9C,SAAgB,GAAqB,CAGnC,MAAO,GAFK,EAAQ,kBAAkB,CACtB,SAAW,QACb,UAAU,EAAS"}
1
+ {"version":3,"file":"version-BL66TSQP.js","names":[],"sources":["../src/version.ts"],"sourcesContent":["import { createRequire } from 'node:module'\n\nimport { BUILD_ID } from './build-id'\n\nconst require = createRequire(import.meta.url)\n\n/**\n * 版本号 + build ID。\n * 格式:`0.1.0 (build 2072)`。build ID = git rev-list --count HEAD,随每次构建自增。\n */\nexport function getVersion(): string {\n const pkg = require('../package.json') as { version?: string }\n const ver = pkg.version ?? '0.0.0'\n return `${ver} (build ${BUILD_ID})`\n}\n"],"mappings":";uFAIA,MAAM,EAAU,EAAc,OAAO,KAAK,IAAI,CAM9C,SAAgB,GAAqB,CAGnC,MAAO,GAFK,EAAQ,kBAAkB,CACtB,SAAW,QACb,UAAU,EAAS"}