@x-otto/cli 0.0.1-alpha.20 → 0.0.1-alpha.21
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.
- package/bin/otto.js +1 -1
- package/dist/{auth-command-kkJh-O5y.js → auth-command-D1T16ed-.js} +1 -1
- package/dist/{auth-command-kkJh-O5y.js.map → auth-command-D1T16ed-.js.map} +1 -1
- package/dist/bundled-extensions/plugin-otto-wire-protocols/plugin-dist/plugin.cjs +14 -12
- package/dist/command-dispatcher-ZyrfHgIk.js +21 -0
- package/dist/command-dispatcher-ZyrfHgIk.js.map +1 -0
- package/dist/{config-migration-0zdXsEDB.js → config-migration-DatflIxs.js} +1 -1
- package/dist/{config-migration-0zdXsEDB.js.map → config-migration-DatflIxs.js.map} +1 -1
- package/dist/{daemon-BtiaXSxn.js → daemon-COESl-zU.js} +1 -1
- package/dist/{daemon-BtiaXSxn.js.map → daemon-COESl-zU.js.map} +1 -1
- package/dist/{daemon-base-BzhYY4FS.js → daemon-base-YOWgYYN7.js} +1 -1
- package/dist/{daemon-base-BzhYY4FS.js.map → daemon-base-YOWgYYN7.js.map} +1 -1
- package/dist/{doctor-DbQC8Z7p.js → doctor-CuNvDmyA.js} +3 -3
- package/dist/{doctor-DbQC8Z7p.js.map → doctor-CuNvDmyA.js.map} +1 -1
- package/dist/{doctor-attach-OJHn8L52.js → doctor-attach-_zeIJewx.js} +2 -2
- package/dist/{doctor-attach-OJHn8L52.js.map → doctor-attach-_zeIJewx.js.map} +1 -1
- package/dist/{doctor-cdp-D66ChCCN.js → doctor-cdp-K_mIFRfA.js} +1 -1
- package/dist/{doctor-cdp-D66ChCCN.js.map → doctor-cdp-K_mIFRfA.js.map} +1 -1
- package/dist/{doctor-heap-Brsro5QJ.js → doctor-heap-Rr2nNvcw.js} +2 -2
- package/dist/{doctor-heap-Brsro5QJ.js.map → doctor-heap-Rr2nNvcw.js.map} +1 -1
- package/dist/{doctor-ps-B4Syy9qa.js → doctor-ps-OL3FGQYu.js} +2 -2
- package/dist/{doctor-ps-B4Syy9qa.js.map → doctor-ps-OL3FGQYu.js.map} +1 -1
- package/dist/feedback-B-d-GV2J.js +2 -0
- package/dist/{feedback-DrUjMuKw.js → feedback-DuTIx3-K.js} +1 -1
- package/dist/{feedback-DrUjMuKw.js.map → feedback-DuTIx3-K.js.map} +1 -1
- package/dist/{feishu-setup-C2UDdpIm.js → feishu-setup-B6ETf-8Y.js} +1 -1
- package/dist/{feishu-setup-C2UDdpIm.js.map → feishu-setup-B6ETf-8Y.js.map} +1 -1
- package/dist/{ftue-doctor-recovery-DhiseXnP.js → ftue-doctor-recovery-DeAf8jVo.js} +1 -1
- package/dist/{ftue-doctor-recovery-DhiseXnP.js.map → ftue-doctor-recovery-DeAf8jVo.js.map} +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -8
- package/dist/index.js.map +1 -1
- package/dist/{install-hTDjeo61.js → install-DlVJRHRT.js} +1 -1
- package/dist/{install-hTDjeo61.js.map → install-DlVJRHRT.js.map} +1 -1
- package/dist/install-WCcQ9k9M.js +2 -0
- package/dist/{mcp-BDfBf6gx.js → mcp-DbTYT7hB.js} +1 -1
- package/dist/{mcp-BDfBf6gx.js.map → mcp-DbTYT7hB.js.map} +1 -1
- package/dist/{migrate-4QDwYLf6.js → migrate-boDXhEyV.js} +2 -2
- package/dist/{migrate-4QDwYLf6.js.map → migrate-boDXhEyV.js.map} +1 -1
- package/dist/observe-CZ96Vre9.js +83 -0
- package/dist/observe-CZ96Vre9.js.map +1 -0
- package/dist/{parse-cli-klcuBpG2.js → parse-cli-CqocMnXY.js} +2 -2
- package/dist/{parse-cli-klcuBpG2.js.map → parse-cli-CqocMnXY.js.map} +1 -1
- package/dist/persistence-CTXa-OyC.js +85 -0
- package/dist/persistence-CTXa-OyC.js.map +1 -0
- package/dist/{plugin-assembly-check-CvuJ4dHf.js → plugin-assembly-check-C2TephMb.js} +1 -1
- package/dist/{plugin-assembly-check-CvuJ4dHf.js.map → plugin-assembly-check-C2TephMb.js.map} +1 -1
- package/dist/proxy-BvdBsB1O.js +2 -0
- package/dist/{proxy-CMQBvdZC.js → proxy-evg7ZvoD.js} +1 -1
- package/dist/{proxy-CMQBvdZC.js.map → proxy-evg7ZvoD.js.map} +1 -1
- package/dist/{ps-Kb6ZsEpC.js → ps-DKrluEuy.js} +1 -1
- package/dist/{ps-Kb6ZsEpC.js.map → ps-DKrluEuy.js.map} +1 -1
- package/dist/registry-BOX7DaxS.js +2 -0
- package/dist/{registry-Cr5To_5r.js → registry-BdCcwyGy.js} +2 -2
- package/dist/{registry-Cr5To_5r.js.map → registry-BdCcwyGy.js.map} +1 -1
- package/dist/{run-attach-CgYMem8s.js → run-attach-CvGCWF_i.js} +1 -1
- package/dist/{run-attach-CgYMem8s.js.map → run-attach-CvGCWF_i.js.map} +1 -1
- package/dist/{serve-Bq21zw1O.js → serve-Dbn3THez.js} +2 -2
- package/dist/{serve-Bq21zw1O.js.map → serve-Dbn3THez.js.map} +1 -1
- package/package.json +2 -2
- package/dist/command-dispatcher-CahuAJnO.js +0 -17
- package/dist/command-dispatcher-CahuAJnO.js.map +0 -1
- package/dist/debug-DfgcrVCI.js +0 -34
- package/dist/debug-DfgcrVCI.js.map +0 -1
- package/dist/extension-plugin-BEQlmUET.js +0 -2
- package/dist/extension-plugin-BYR-MJ1U.js +0 -7
- package/dist/extension-plugin-BYR-MJ1U.js.map +0 -1
- package/dist/feedback-DjNFsjeN.js +0 -2
- package/dist/install-DkJgbg2p.js +0 -2
- package/dist/model-auth-BMY86Bk4.js +0 -3
- package/dist/model-auth-BMY86Bk4.js.map +0 -1
- package/dist/model-auth-CSLiEFJ-.js +0 -2
- package/dist/observe-DSrUS2YI.js +0 -34
- package/dist/observe-DSrUS2YI.js.map +0 -1
- package/dist/persistenced-CW8sdhd0.js +0 -36
- package/dist/persistenced-CW8sdhd0.js.map +0 -1
- package/dist/proxy-TzOLqtkc.js +0 -2
- package/dist/registry-DAJ7oZTe.js +0 -2
- package/dist/remote-persistence-server-BLBIop0u.js +0 -35
- package/dist/remote-persistence-server-BLBIop0u.js.map +0 -1
- package/dist/time-travel-8X-uAgHw.js +0 -21
- package/dist/time-travel-8X-uAgHw.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-attach-CgYMem8s.js","names":[],"sources":["../src/commands/remote-transport.ts","../src/commands/run-attach.ts"],"sourcesContent":["/**\n * remote-transport.ts — RFC-335 T335-07 S2:`otto serve` 的 WS 传输端口实现。\n *\n * web-ui `otto-client` 的 cli 对应物:走同一 `ws://<host>/ws`、同一 JSON-RPC 出站\n * 信封 `{method, params}`、同一 `Session.subscribe{sessionId, since}` 接续协议——\n * Node 22 全局 `WebSocket`(不新增依赖、**不**引 `ws` 包)。\n *\n * 职责与边界(严格对齐 RFC-335 §6 + grill 决议 B):\n * - **重连**归本模块(指数退避,上限 10s);重连 = 新连接,按 subscribe 时记住的\n * since 自动 `Session.subscribe{sessionId, since}` 补发断线期间事件。\n * - **本地合成 seq**:线上 `Session.event` envelope 不含 seq(`coding-service.attachSession`\n * 核实——`append()` 只入服务端 log、扇出 envelope 原样)。本模块对到达事件按序自增\n * 编号、写入 `RemoteSessionEvent.seq`;**不去重不重排**(那是 dataSource 的职责——\n * 它持有 snapshot.cursor 起步的 lastSeq,调 `event.seq <= lastSeq → drop`)。\n * 只一处该表:本模块保证**到达递交顺序 = 服务端发送顺序**(单 ws 连接事件按序抵达)。\n * - **gap(EVENT_LOG_GAP)→ onGap 显式上报 + 断开**:重连续接由 dataSource 的 onGap\n * 回调决策(重水合/重订阅新 cursor,RFC-335 §6:绝不静默补发缺失段)。\n *\n * 不做的事:不 fetch(HTTP 归 service-client)、不做 store 写入(归 dataSource+reduce\n * bridge)、不解释事件 kind、不消费 seq(只是分配透明编号)。\n */\n\nimport { ServiceUnreachableError } from './service-client'\n\n/**\n * 与 tui 契约(`packages/tui/src/bootstrap/session-data-source.ts`)对应的结构镜像。\n * 直接深导 `@x-otto/tui/session-data-source` 会破 package.json exports 边界(只暴露\n * `./`、`./compat`、`./standalone`);故本文件自声明最小类型面,消费方(adapter 装配\n * 代码)将本类型 cast 进 tui 的对应类型——字段名一致即成立。\n *\n * **同步纪律**:tui `session-data-source.ts` 增删 SessionTransport/RemoteSessionEvent\n * 字段时本文件必须同步。\n */\nexport interface RemoteSessionEvent {\n /** 本地合成单调序号(快照后首个到达事件 = snapshot.cursor + 1)。 */\n seq: number\n /** 服务端 Session.event.envelope.data 原样(含 sessionId/kind/... 字段)。 */\n payload: unknown\n}\n\nexport interface RemoteSessionSnapshot {\n session: { id: string; status: string; messages: ReadonlyArray<unknown> }\n cursor: number\n}\n\nexport interface SessionTransport {\n fetchSnapshot(sessionId: string): Promise<RemoteSessionSnapshot>\n subscribe(\n sessionId: string,\n since: number,\n onEvent: (event: RemoteSessionEvent) => void,\n onGap: (info: { reason: 'gap'; oldestAvailable: number }) => void,\n ): () => void\n /**\n * RFC-335 S4 协议面:发任意 WS verb(Session.writeLease.acquire/release 等)。\n * 仅 service 加入 WS 面向服务端的「直接命令语」时调(不遇 Session.event)。\n * 连上时不送(重连中 silently queue——最 recent subscribe 联速 prefer not\n * user-initiated verb race with disconnect)。\n */\n sendVerb(verb: string, data: Record<string, unknown>): void\n}\n\nconst INITIAL_BACKOFF_MS = 500\nconst MAX_BACKOFF_MS = 10_000\nconst WS_PATH = '/ws'\n\ninterface SessionEventEnvelope {\n type: 'Session.event'\n data: Record<string, unknown> & { sessionId?: string }\n}\ninterface RuntimeErrorEnvelope {\n type: 'Runtime.error'\n data: { code?: string; sessionId?: string; message?: string }\n}\ntype WireEnvelope = SessionEventEnvelope | RuntimeErrorEnvelope | { type: string; data?: unknown }\n\n/** 连接生命周期状态(S4 用户可感知——不区分 reconnecting-first/second,统一对两(代)发)。 */\nexport type AttachConnectionState = 'connecting' | 'connected' | 'reconnecting' | 'closed'\n\nexport interface ServiceWsTransportDeps {\n /** snapshot HTTP 读取(复用 service-client,不另开 HTTP client)。 */\n fetchSnapshot: (sessionId: string) => Promise<RemoteSessionSnapshot>\n /** WebSocket 构造器注入(单测注入 fake 避免真网络;生产全局)。 */\n WebSocketImpl?: new (url: string) => WebSocket\n /** 非 gap 的 Runtime.error(PROMPT_FAILED 等)/连接异常通知(上层 toast)。 */\n onNotify?: (msg: string) => void\n /** 连接生命周期通知——connect/disconnect/reconnect/gap-oneway。重复事件不发送(去抖)。 */\n onStateChange?: (state: AttachConnectionState) => void\n /** RFC-335 S4:Runtime.writeLease envelope 到达——acquired/denied/released/not-held。 */\n onWriteLease?: (ev: {\n sessionId: string\n state: 'acquired' | 'denied' | 'released' | 'not-held'\n reason?: string\n leaseOwner?: string\n }) => void\n setTimeoutImpl?: typeof setTimeout\n clearTimeoutImpl?: typeof clearTimeout\n /** 退避常数注入(单测加速)。 */\n initialBackoffMs?: number\n maxBackoffMs?: number\n}\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v)\n\n/** `http(s)://host[:port]` → `ws(s)://host[:port]`(与 web-ui otto-client 一致)。 */\nexport function httpToWsUrl(baseUrl: string, token?: string): string {\n let wsBase: string\n if (baseUrl.startsWith('https://')) wsBase = `wss://${baseUrl.slice('https://'.length)}`\n else if (baseUrl.startsWith('http://')) wsBase = `ws://${baseUrl.slice('http://'.length)}`\n else if (baseUrl.startsWith('localhost') || baseUrl.startsWith('127.')) wsBase = `ws://${baseUrl}`\n else wsBase = `wss://${baseUrl}`\n const url = `${wsBase.replace(/\\/$/, '')}${WS_PATH}`\n return token ? `${url}?token=${encodeURIComponent(token)}` : url\n}\n\nexport function createServiceWsTransport(\n baseUrl: string,\n sessionId: string,\n deps: ServiceWsTransportDeps,\n): SessionTransport {\n const WebSocketImpl =\n deps.WebSocketImpl ??\n (globalThis as unknown as { WebSocket: new (u: string) => WebSocket }).WebSocket\n const setTimeoutImpl = deps.setTimeoutImpl ?? setTimeout\n const clearTimeoutImpl = deps.clearTimeoutImpl ?? clearTimeout\n const initialBackoff = deps.initialBackoffMs ?? INITIAL_BACKOFF_MS\n const maxBackoff = deps.maxBackoffMs ?? MAX_BACKOFF_MS\n\n let ws: WebSocket | null = null\n let backoff = initialBackoff\n let reconnectTimer: ReturnType<typeof setTimeout> | null = null\n let disposed = false\n\n let subscribedSince = 0\n let onEventCb: ((event: RemoteSessionEvent) => void) | null = null\n let onGapCb: ((info: { reason: 'gap'; oldestAvailable: number }) => void) | null = null\n // The last contiguous authority sequence delivered to the data source. A replay\n // and a live event may arrive in either order, so do not advance this over a gap.\n let nextSeq = 1\n const pendingEvents = new Map<number, RemoteSessionEvent>()\n /** 当前会话连接生命周期状态(onStateChange 的发火边——防止重复) */\n let lastState: AttachConnectionState | null = null\n\n const emitState = (next: AttachConnectionState) => {\n if (next === lastState) return\n lastState = next\n deps.onStateChange?.(next)\n }\n\n const sendSubscribe = (socket: WebSocket | null = ws) => {\n if (!socket || socket.readyState !== socket.OPEN) return\n socket.send(\n JSON.stringify({\n method: 'Session.subscribe',\n params: { sessionId, since: subscribedSince },\n }),\n )\n }\n\n const scheduleReconnect = () => {\n if (disposed || reconnectTimer !== null) return\n reconnectTimer = setTimeoutImpl(() => {\n reconnectTimer = null\n emitState('connecting')\n connect()\n }, backoff)\n backoff = Math.min(backoff * 2, maxBackoff)\n }\n\n const closeSocket = (socket: WebSocket) => {\n socket.onopen = null\n socket.onmessage = null\n socket.onclose = null\n socket.onerror = null\n try { socket.close() } catch { /* best-effort */ }\n if (ws === socket) ws = null\n }\n\n const connect = () => {\n if (disposed) return\n emitState('connecting')\n let socket: WebSocket\n try {\n socket = new WebSocketImpl(httpToWsUrl(baseUrl))\n } catch {\n scheduleReconnect()\n return\n }\n ws = socket\n\n socket.onopen = () => {\n backoff = initialBackoff\n emitState('connected')\n sendSubscribe(socket)\n }\n socket.onmessage = (ev: MessageEvent) => {\n let envelope: WireEnvelope\n try {\n envelope = JSON.parse(String(ev.data)) as WireEnvelope\n } catch {\n return // 非 JSON:忽略(同 web-ui)\n }\n\n if (envelope.type === 'Session.event' && isRecord(envelope.data)) {\n const data = envelope.data as Record<string, unknown> & { sessionId?: string; seq?: unknown }\n if (typeof data.sessionId !== 'string' || data.sessionId !== sessionId) return\n // RFC-352 D1:replay and live delivery can interleave. Only the service's\n // event-log sequence preserves causal order; client arrival order does not.\n if (!Number.isSafeInteger(data.seq) || (data.seq as number) < 0) return\n const seq = data.seq as number\n if (seq < nextSeq || pendingEvents.has(seq)) return\n const { seq: _seq, ...payload } = data\n pendingEvents.set(seq, { seq, payload })\n while (pendingEvents.has(nextSeq)) {\n const next = pendingEvents.get(nextSeq)!\n pendingEvents.delete(nextSeq)\n onEventCb?.(next)\n subscribedSince = nextSeq\n nextSeq += 1\n }\n return\n }\n\n if (envelope.type === 'Runtime.writeLease' && isRecord(envelope.data)) {\n // RFC-335 S4:write-lease 通知——run-attach 应能感知 acquired/denied 状态,\n // 以便在 denied 时拒绝本地提交、在 acquired 时允许提交。\n const d = envelope.data as {\n sessionId?: string\n state?: string\n reason?: string\n leaseOwner?: string\n }\n deps.onWriteLease?.({\n sessionId: typeof d.sessionId === 'string' ? d.sessionId : sessionId,\n state: (d.state as 'acquired' | 'denied' | 'released' | 'not-held') ?? 'not-held',\n reason: typeof d.reason === 'string' ? d.reason : undefined,\n leaseOwner: typeof d.leaseOwner === 'string' ? d.leaseOwner : undefined,\n })\n return\n }\n\n if (envelope.type === 'Runtime.error' && isRecord(envelope.data)) {\n const d = envelope.data as { code?: string; message?: string; sessionId?: string }\n const message = typeof d.message === 'string' ? d.message : ''\n if (d.code === 'EVENT_LOG_GAP') {\n // onGap 回调先跑(dataSource 里处理重水合),然后断开——避免继续消费断序流。\n // gap 算 'reconnecting' 中加入(正次 connect.onopen 得为 'connected')。\n onGapCb?.({ reason: 'gap', oldestAvailable: nextSeq })\n closeSocket(socket)\n scheduleReconnect()\n return\n }\n deps.onNotify?.(`[service] ${d.code ?? 'runtime'}: ${message}`)\n }\n }\n socket.onclose = () => {\n if (ws === socket) ws = null\n if (!disposed) {\n // 从 'connected'/'connecting' 均退到 'reconnecting',下轮 socket.onopen 才回 'connected'。\n emitState('reconnecting')\n scheduleReconnect()\n } else {\n emitState('closed')\n }\n }\n socket.onerror = () => {\n // onclose 紧随其后统一走重连路径(对齐 web-ui)。\n }\n }\n\n return {\n fetchSnapshot: (sid) => deps.fetchSnapshot(sid),\n subscribe(_sid, since, onEvent, onGap) {\n subscribedSince = since\n onEventCb = onEvent\n onGapCb = onGap\n // Begin only at the snapshot cursor. Reconnect keeps contiguous progress and\n // any buffered out-of-order replay/live events, so subscribe resumes exactly\n // after the last applied authority sequence.\n subscribedSince = Math.max(subscribedSince, since)\n nextSeq = subscribedSince + 1\n for (const seq of pendingEvents.keys()) {\n if (seq <= subscribedSince) pendingEvents.delete(seq)\n }\n if (!ws) {\n connect()\n } else if (ws.readyState === ws.OPEN) {\n sendSubscribe()\n }\n return () => {\n disposed = true\n if (reconnectTimer !== null) { clearTimeoutImpl(reconnectTimer); reconnectTimer = null }\n // 手动发 'closed'——closeSocket 已清 onclose handler,socket.close() 不再代发状态。\n emitState('closed')\n if (ws) closeSocket(ws)\n onEventCb = null\n onGapCb = null\n }\n },\n\n sendVerb(verb: string, data: Record<string, unknown>): void {\n // RFC-335 S4:直接 verb(Session.writeLease.acquire/release)供应商。\n // 未连接 silently drop(重连 deliver-first-subscribe 不 stick 之前的下达)。\n if (disposed || !ws || ws.readyState !== ws.OPEN) return\n ws.send(JSON.stringify({ method: verb, params: data }))\n },\n }\n}\n\n/** attach 入口快捷形式:复用 service-client 的 fetchSnapshot。 */\nexport function createAttachTransport(args: {\n baseUrl: string\n sessionId: string\n fetchSnapshot: (sessionId: string) => Promise<RemoteSessionSnapshot>\n token?: string\n onNotify?: (msg: string) => void\n}): SessionTransport {\n return createServiceWsTransport(args.baseUrl, args.sessionId, {\n fetchSnapshot: args.fetchSnapshot,\n onNotify: args.onNotify,\n })\n}\n\n// 供 attach 入口报错区分。\nexport { ServiceUnreachableError }\n","/**\n * run-attach.ts — RFC-335 T335-07 S2 装配:`otto --attach <sessionId>` attach 模式。\n *\n * 装配链:\n * service snapshot → ServiceWsTransport(WebSocket) → createAttachSessionDataSource\n * → handle.lifecycle.attachSessionSource → 本地 messageStore(TUI 唯一 writer)\n *\n * 关键取舍(RFC-335 §5/§6 + grill 决议 A):\n * - **writer 唯一**:dataSource 的 hydrateMessages/append 都经 handle 出口 → 本地 zustand\n * messageStore 由 TUI 本体写,cli 这层从不手拿 `messageStore` 句柄。\n * - **事件流不再经 streamController**:attach 模式没 live-stream 流水线(streamText\n * Coalescer/agent sync 管线那都是本地 agent 跑起来的产物),事件逐条经 dataSource\n * 按 seq 应用进 store。\n * - **gap 行为可感知**:EVENT_LOG_GAP → dataSource 重水合(拉新 snapshot + messages\n * 整灌 + 重订阅新 cursor)——用户看到「消息重新出现」,绝不静默断一段。\n * - **写**:纯文本经 Chat.query 下发;任何斜杠命令 fail-fast 提示「attach 暂未支持」。\n */\nimport { startTui, type TuiHandle } from '@x-otto/tui'\nimport type { ChatMessage } from '@x-otto/tui'\nimport {\n createAttachSessionDataSource,\n createSessionEventReduceBridge,\n createStoreTuiSink,\n} from '@x-otto/tui'\n\nimport { createServiceClient } from './service-client'\nimport {\n createServiceWsTransport,\n type RemoteSessionSnapshot,\n type SessionTransport,\n} from './remote-transport'\n\nexport interface RunAttachOptions {\n sessionId: string\n baseUrl: string\n token?: string\n stdin?: NodeJS.ReadStream\n stdout?: NodeJS.WriteStream\n /** 单测注入;生产缺省全局 fetch。 */\n fetchImpl?: typeof globalThis.fetch\n /** RFC-335 S4 补丁:强抢写权——kill -9 造成 stuck lease 的唯一正式解锁口。\n * `otto --attach <id> --force-write-lease`。服务端 audit logger.warn 记录,\n * 原 owner 若仍订阅将收「写权被剥夺」通知。 */\n forceWriteLease?: boolean\n}\n\nexport async function runAttach(opts: RunAttachOptions): Promise<void> {\n const stdout = opts.stdout ?? process.stdout\n const stdin = opts.stdin ?? process.stdin\n const fetchImpl = opts.fetchImpl ?? globalThis.fetch\n\n // RFC-335 S4 写租约状态:false 时 onSubmit 最纯文本被拒(只读观察者态)。初始为 true;\n // transport.onWriteLease 会按 Runtime.writeLease envelope 更新。\n let writeLeaseGranted = true\n let leaseOwnerId: string | undefined\n\n const client = createServiceClient(opts.baseUrl, fetchImpl, { token: opts.token })\n\n // 1) snapshot 先行:确认 session 存在 + 拿 cursor。失败 = 显式报错退出(RFC-335 §6:无 local fallback)。\n let snapshot\n try {\n snapshot = await client.fetchSnapshot(opts.sessionId)\n } catch (err) {\n stdout.write(`otto --attach: 无法连接 serve 上的会话 ${opts.sessionId}:${err instanceof Error ? err.message : String(err)}\\n`)\n throw err\n }\n const sessionId = snapshot.session.id\n\n // 2) 起 TUI(不开本地 session、不起 agent workflow——attach 模式的写全走 Chat.query/slash 灰门)\n let tuiRef: TuiHandle | undefined\n const pushText = (text: string): void => {\n if (!tuiRef) return\n const msg: ChatMessage = { role: 'assistant', content: [{ type: 'text', text }] }\n tuiRef.messages.pushMessage(msg)\n }\n\n tuiRef = await startTui({\n stdin,\n stdout,\n columns: stdout.columns ?? 100,\n needsSetup: false,\n welcome: { cwd: undefined },\n onSubmit: (text: string) => {\n // 灰门:任何斜杠命令 attach 期不消费(steer/pause/resume/setTodoList 归下期;RFC-335 §5)。\n const trimmed = text.trimStart()\n if (trimmed.startsWith('/')) {\n pushText(\n `[attach] \\`${trimmed.split(/\\s+/)[0]}\\` 为 attach 暂未支持命令。\\n` +\n `仅支持纯文本提交(经 Chat.query 下发到 serve);如需本地命令请退出 attach 后运行。`,\n )\n return\n }\n if (!tuiRef) return\n // RFC-335 S4 协议面:写租约被另一 client 占有时,本地预拦截(不发 Chat.query\n // 给服务端制造 PROMPT_FAILED spam)——TUI 提示只读观察者语义。\n if (!writeLeaseGranted) {\n pushText(\n `[attach] 会话的写权已被另一 client 持有(${leaseOwnerId ?? '?'})——本端变只读观察者。` +\n `请另选 session 或让对方退出 attach。`,\n )\n return\n }\n void (async () => {\n try {\n await client.sendPrompt(sessionId, text)\n // 响应经 WS 事件流实时回流到 dataSource → messageStore(无需手动同步)。\n } catch (err) {\n pushText(`[attach] 提交失败:${err instanceof Error ? err.message : String(err)}`)\n }\n })()\n },\n })\n\n // 3) transport + dataSource:service socket → 本地 messageStore\n const transport: SessionTransport = createServiceWsTransport(opts.baseUrl, sessionId, {\n fetchSnapshot: async (sid) => {\n const snap = await client.fetchSnapshot(sid)\n return snap as unknown as RemoteSessionSnapshot\n },\n onNotify: (msg) => pushText(`[attach] ${msg}`),\n onStateChange: (state) => {\n // S4 连接生命周期可感知(去抖已由 transport 管的)。\n if (state === 'connected') {\n pushText('[attach] 已连接 serve。')\n // RFC-335 S4:每次连接立刻尝试 acquire 写租约——酸同 clientId;\n // 服务端返回 acquired/denied 由 onWriteLease 通知。\n // RFC-335 S4 补丁:forceWriteLease 时改走 force_acquire(显式人工强抢)。\n if (opts.forceWriteLease) {\n pushText('[attach] --force-write-lease:向服务端发起 force_acquire(kill -9 解锁通道)……')\n transport.sendVerb('Session.writeLease.force_acquire', { sessionId })\n } else {\n transport.sendVerb('Session.writeLease.acquire', { sessionId })\n }\n } else if (state === 'reconnecting') {\n // RFC-335 S4:重连期间打 write-leases 不确定——服务端在断开时释放;保险状\n // 本端 reactive 防出被 rub:下状态 acquired 前一正在拒绝(更安全默认)。\n writeLeaseGranted = false\n pushText('[attach] 连接断开,正在重连……')\n } else if (state === 'closed') {\n writeLeaseGranted = false\n pushText('[attach] 连接已关闭。')\n }\n },\n onWriteLease: (ev) => {\n if (ev.state === 'acquired') {\n writeLeaseGranted = true\n leaseOwnerId = ev.leaseOwner\n pushText('[attach] 获会话写权——纯文本会走 Chat.query 到 service。')\n } else if (ev.state === 'denied') {\n writeLeaseGranted = false\n leaseOwnerId = ev.leaseOwner\n pushText(\n `[attach] 会话写权被另一 client 持有(${ev.leaseOwner ?? '?'})——` +\n `本端变只读观察者态;提交纯文本会被本端予拦截。\\n` +\n `若对方是 kill -9 残留,可重启带 \\`--force-write-lease\\` 强抢写权。`,\n )\n } else if (ev.state === 'released') {\n writeLeaseGranted = false\n leaseOwnerId = ev.leaseOwner\n if (ev.reason === 'forced_by_other_client') {\n pushText(\n `[attach] 写权被另一 client 强行剥夺(force_acquire by ${ev.leaseOwner ?? '?'})——` +\n `本端变只读观察者态。`,\n )\n } else {\n pushText('[attach] 写权已释放——重新 conn 上可申请。')\n }\n }\n },\n })\n\n // `reduceToTui` is the only event→view projection. It shares the same TuiHandle\n // store actions as local mode; snapshot hydration resets its derived state first.\n const eventBridge = createSessionEventReduceBridge(\n createStoreTuiSink({\n pushMessage: tuiRef.messages.pushMessage,\n echoUserInput: tuiRef.messages.echoUserInput,\n pushEngineEvent: tuiRef.engine.pushEngineEvent,\n getNotifyStats: tuiRef.info.getNotifyStats,\n setTasks: tuiRef.stores.tasks.setTasks,\n addTurnBoundary: tuiRef.stores.tasks.addTurnBoundary,\n }),\n )\n\n const dataSource = createAttachSessionDataSource({\n transport: transport as never,\n sessionId,\n onSessionEvent: eventBridge.handle,\n hydrateMessages: (target, messages) => {\n // Snapshot is authoritative after initial connect and EVENT_LOG_GAP. Reset the\n // reducer before accepting subsequent deltas so turn-boundary indices start at\n // the hydrated message count instead of an obsolete stream.\n target.messageStore.setState(() => ({ messages: [] }))\n if (messages.length > 0) {\n target.messageStore.setState(() => ({\n messages: [...(messages as readonly ChatMessage[]) as never],\n }))\n }\n eventBridge.reset(messages.length)\n },\n onGap: () => {\n pushText(\n `[attach] 会话事件日志 gap(cursor 被 service 逐出)——已重新水合 messages 快照。`,\n )\n },\n })\n\n // 4) 挂载:TuiHandle.lifecycle.attachSessionSource(RFC-347 收敛面内)——TUI 本体握 store;\n // 重复挂载会 throw(attach 一个数据源是硬约束)。stop 由调用方跟 TUI lifecycles 结。\n await tuiRef.lifecycle.attachSessionSource(dataSource)\n\n // 5) attach 概述 = 这一条句话(恰好是唯一由 cli 经 messages.pushMessage 写的位置)\n pushText(\n `[attach] 已连接会话 ${sessionId}(host=${snapshot.authority.hostname} pid=${snapshot.authority.pid})。\\n` +\n 'snapshot messages 已灌入 store;后续 Session.event 经 WS 实时应用;' +\n '纯文本走 Chat.query;/xxx 斜杠命令暂未支持。',\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"run-attach-CvGCWF_i.js","names":[],"sources":["../src/commands/remote-transport.ts","../src/commands/run-attach.ts"],"sourcesContent":["/**\n * remote-transport.ts — RFC-335 T335-07 S2:`otto serve` 的 WS 传输端口实现。\n *\n * web-ui `otto-client` 的 cli 对应物:走同一 `ws://<host>/ws`、同一 JSON-RPC 出站\n * 信封 `{method, params}`、同一 `Session.subscribe{sessionId, since}` 接续协议——\n * Node 22 全局 `WebSocket`(不新增依赖、**不**引 `ws` 包)。\n *\n * 职责与边界(严格对齐 RFC-335 §6 + grill 决议 B):\n * - **重连**归本模块(指数退避,上限 10s);重连 = 新连接,按 subscribe 时记住的\n * since 自动 `Session.subscribe{sessionId, since}` 补发断线期间事件。\n * - **本地合成 seq**:线上 `Session.event` envelope 不含 seq(`coding-service.attachSession`\n * 核实——`append()` 只入服务端 log、扇出 envelope 原样)。本模块对到达事件按序自增\n * 编号、写入 `RemoteSessionEvent.seq`;**不去重不重排**(那是 dataSource 的职责——\n * 它持有 snapshot.cursor 起步的 lastSeq,调 `event.seq <= lastSeq → drop`)。\n * 只一处该表:本模块保证**到达递交顺序 = 服务端发送顺序**(单 ws 连接事件按序抵达)。\n * - **gap(EVENT_LOG_GAP)→ onGap 显式上报 + 断开**:重连续接由 dataSource 的 onGap\n * 回调决策(重水合/重订阅新 cursor,RFC-335 §6:绝不静默补发缺失段)。\n *\n * 不做的事:不 fetch(HTTP 归 service-client)、不做 store 写入(归 dataSource+reduce\n * bridge)、不解释事件 kind、不消费 seq(只是分配透明编号)。\n */\n\nimport { ServiceUnreachableError } from './service-client'\n\n/**\n * 与 tui 契约(`packages/tui/src/bootstrap/session-data-source.ts`)对应的结构镜像。\n * 直接深导 `@x-otto/tui/session-data-source` 会破 package.json exports 边界(只暴露\n * `./`、`./compat`、`./standalone`);故本文件自声明最小类型面,消费方(adapter 装配\n * 代码)将本类型 cast 进 tui 的对应类型——字段名一致即成立。\n *\n * **同步纪律**:tui `session-data-source.ts` 增删 SessionTransport/RemoteSessionEvent\n * 字段时本文件必须同步。\n */\nexport interface RemoteSessionEvent {\n /** 本地合成单调序号(快照后首个到达事件 = snapshot.cursor + 1)。 */\n seq: number\n /** 服务端 Session.event.envelope.data 原样(含 sessionId/kind/... 字段)。 */\n payload: unknown\n}\n\nexport interface RemoteSessionSnapshot {\n session: { id: string; status: string; messages: ReadonlyArray<unknown> }\n cursor: number\n}\n\nexport interface SessionTransport {\n fetchSnapshot(sessionId: string): Promise<RemoteSessionSnapshot>\n subscribe(\n sessionId: string,\n since: number,\n onEvent: (event: RemoteSessionEvent) => void,\n onGap: (info: { reason: 'gap'; oldestAvailable: number }) => void,\n ): () => void\n /**\n * RFC-335 S4 协议面:发任意 WS verb(Session.writeLease.acquire/release 等)。\n * 仅 service 加入 WS 面向服务端的「直接命令语」时调(不遇 Session.event)。\n * 连上时不送(重连中 silently queue——最 recent subscribe 联速 prefer not\n * user-initiated verb race with disconnect)。\n */\n sendVerb(verb: string, data: Record<string, unknown>): void\n}\n\nconst INITIAL_BACKOFF_MS = 500\nconst MAX_BACKOFF_MS = 10_000\nconst WS_PATH = '/ws'\n\ninterface SessionEventEnvelope {\n type: 'Session.event'\n data: Record<string, unknown> & { sessionId?: string }\n}\ninterface RuntimeErrorEnvelope {\n type: 'Runtime.error'\n data: { code?: string; sessionId?: string; message?: string }\n}\ntype WireEnvelope = SessionEventEnvelope | RuntimeErrorEnvelope | { type: string; data?: unknown }\n\n/** 连接生命周期状态(S4 用户可感知——不区分 reconnecting-first/second,统一对两(代)发)。 */\nexport type AttachConnectionState = 'connecting' | 'connected' | 'reconnecting' | 'closed'\n\nexport interface ServiceWsTransportDeps {\n /** snapshot HTTP 读取(复用 service-client,不另开 HTTP client)。 */\n fetchSnapshot: (sessionId: string) => Promise<RemoteSessionSnapshot>\n /** WebSocket 构造器注入(单测注入 fake 避免真网络;生产全局)。 */\n WebSocketImpl?: new (url: string) => WebSocket\n /** 非 gap 的 Runtime.error(PROMPT_FAILED 等)/连接异常通知(上层 toast)。 */\n onNotify?: (msg: string) => void\n /** 连接生命周期通知——connect/disconnect/reconnect/gap-oneway。重复事件不发送(去抖)。 */\n onStateChange?: (state: AttachConnectionState) => void\n /** RFC-335 S4:Runtime.writeLease envelope 到达——acquired/denied/released/not-held。 */\n onWriteLease?: (ev: {\n sessionId: string\n state: 'acquired' | 'denied' | 'released' | 'not-held'\n reason?: string\n leaseOwner?: string\n }) => void\n setTimeoutImpl?: typeof setTimeout\n clearTimeoutImpl?: typeof clearTimeout\n /** 退避常数注入(单测加速)。 */\n initialBackoffMs?: number\n maxBackoffMs?: number\n}\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v)\n\n/** `http(s)://host[:port]` → `ws(s)://host[:port]`(与 web-ui otto-client 一致)。 */\nexport function httpToWsUrl(baseUrl: string, token?: string): string {\n let wsBase: string\n if (baseUrl.startsWith('https://')) wsBase = `wss://${baseUrl.slice('https://'.length)}`\n else if (baseUrl.startsWith('http://')) wsBase = `ws://${baseUrl.slice('http://'.length)}`\n else if (baseUrl.startsWith('localhost') || baseUrl.startsWith('127.')) wsBase = `ws://${baseUrl}`\n else wsBase = `wss://${baseUrl}`\n const url = `${wsBase.replace(/\\/$/, '')}${WS_PATH}`\n return token ? `${url}?token=${encodeURIComponent(token)}` : url\n}\n\nexport function createServiceWsTransport(\n baseUrl: string,\n sessionId: string,\n deps: ServiceWsTransportDeps,\n): SessionTransport {\n const WebSocketImpl =\n deps.WebSocketImpl ??\n (globalThis as unknown as { WebSocket: new (u: string) => WebSocket }).WebSocket\n const setTimeoutImpl = deps.setTimeoutImpl ?? setTimeout\n const clearTimeoutImpl = deps.clearTimeoutImpl ?? clearTimeout\n const initialBackoff = deps.initialBackoffMs ?? INITIAL_BACKOFF_MS\n const maxBackoff = deps.maxBackoffMs ?? MAX_BACKOFF_MS\n\n let ws: WebSocket | null = null\n let backoff = initialBackoff\n let reconnectTimer: ReturnType<typeof setTimeout> | null = null\n let disposed = false\n\n let subscribedSince = 0\n let onEventCb: ((event: RemoteSessionEvent) => void) | null = null\n let onGapCb: ((info: { reason: 'gap'; oldestAvailable: number }) => void) | null = null\n // The last contiguous authority sequence delivered to the data source. A replay\n // and a live event may arrive in either order, so do not advance this over a gap.\n let nextSeq = 1\n const pendingEvents = new Map<number, RemoteSessionEvent>()\n /** 当前会话连接生命周期状态(onStateChange 的发火边——防止重复) */\n let lastState: AttachConnectionState | null = null\n\n const emitState = (next: AttachConnectionState) => {\n if (next === lastState) return\n lastState = next\n deps.onStateChange?.(next)\n }\n\n const sendSubscribe = (socket: WebSocket | null = ws) => {\n if (!socket || socket.readyState !== socket.OPEN) return\n socket.send(\n JSON.stringify({\n method: 'Session.subscribe',\n params: { sessionId, since: subscribedSince },\n }),\n )\n }\n\n const scheduleReconnect = () => {\n if (disposed || reconnectTimer !== null) return\n reconnectTimer = setTimeoutImpl(() => {\n reconnectTimer = null\n emitState('connecting')\n connect()\n }, backoff)\n backoff = Math.min(backoff * 2, maxBackoff)\n }\n\n const closeSocket = (socket: WebSocket) => {\n socket.onopen = null\n socket.onmessage = null\n socket.onclose = null\n socket.onerror = null\n try { socket.close() } catch { /* best-effort */ }\n if (ws === socket) ws = null\n }\n\n const connect = () => {\n if (disposed) return\n emitState('connecting')\n let socket: WebSocket\n try {\n socket = new WebSocketImpl(httpToWsUrl(baseUrl))\n } catch {\n scheduleReconnect()\n return\n }\n ws = socket\n\n socket.onopen = () => {\n backoff = initialBackoff\n emitState('connected')\n sendSubscribe(socket)\n }\n socket.onmessage = (ev: MessageEvent) => {\n let envelope: WireEnvelope\n try {\n envelope = JSON.parse(String(ev.data)) as WireEnvelope\n } catch {\n return // 非 JSON:忽略(同 web-ui)\n }\n\n if (envelope.type === 'Session.event' && isRecord(envelope.data)) {\n const data = envelope.data as Record<string, unknown> & { sessionId?: string; seq?: unknown }\n if (typeof data.sessionId !== 'string' || data.sessionId !== sessionId) return\n // RFC-352 D1:replay and live delivery can interleave. Only the service's\n // event-log sequence preserves causal order; client arrival order does not.\n if (!Number.isSafeInteger(data.seq) || (data.seq as number) < 0) return\n const seq = data.seq as number\n if (seq < nextSeq || pendingEvents.has(seq)) return\n const { seq: _seq, ...payload } = data\n pendingEvents.set(seq, { seq, payload })\n while (pendingEvents.has(nextSeq)) {\n const next = pendingEvents.get(nextSeq)!\n pendingEvents.delete(nextSeq)\n onEventCb?.(next)\n subscribedSince = nextSeq\n nextSeq += 1\n }\n return\n }\n\n if (envelope.type === 'Runtime.writeLease' && isRecord(envelope.data)) {\n // RFC-335 S4:write-lease 通知——run-attach 应能感知 acquired/denied 状态,\n // 以便在 denied 时拒绝本地提交、在 acquired 时允许提交。\n const d = envelope.data as {\n sessionId?: string\n state?: string\n reason?: string\n leaseOwner?: string\n }\n deps.onWriteLease?.({\n sessionId: typeof d.sessionId === 'string' ? d.sessionId : sessionId,\n state: (d.state as 'acquired' | 'denied' | 'released' | 'not-held') ?? 'not-held',\n reason: typeof d.reason === 'string' ? d.reason : undefined,\n leaseOwner: typeof d.leaseOwner === 'string' ? d.leaseOwner : undefined,\n })\n return\n }\n\n if (envelope.type === 'Runtime.error' && isRecord(envelope.data)) {\n const d = envelope.data as { code?: string; message?: string; sessionId?: string }\n const message = typeof d.message === 'string' ? d.message : ''\n if (d.code === 'EVENT_LOG_GAP') {\n // onGap 回调先跑(dataSource 里处理重水合),然后断开——避免继续消费断序流。\n // gap 算 'reconnecting' 中加入(正次 connect.onopen 得为 'connected')。\n onGapCb?.({ reason: 'gap', oldestAvailable: nextSeq })\n closeSocket(socket)\n scheduleReconnect()\n return\n }\n deps.onNotify?.(`[service] ${d.code ?? 'runtime'}: ${message}`)\n }\n }\n socket.onclose = () => {\n if (ws === socket) ws = null\n if (!disposed) {\n // 从 'connected'/'connecting' 均退到 'reconnecting',下轮 socket.onopen 才回 'connected'。\n emitState('reconnecting')\n scheduleReconnect()\n } else {\n emitState('closed')\n }\n }\n socket.onerror = () => {\n // onclose 紧随其后统一走重连路径(对齐 web-ui)。\n }\n }\n\n return {\n fetchSnapshot: (sid) => deps.fetchSnapshot(sid),\n subscribe(_sid, since, onEvent, onGap) {\n subscribedSince = since\n onEventCb = onEvent\n onGapCb = onGap\n // Begin only at the snapshot cursor. Reconnect keeps contiguous progress and\n // any buffered out-of-order replay/live events, so subscribe resumes exactly\n // after the last applied authority sequence.\n subscribedSince = Math.max(subscribedSince, since)\n nextSeq = subscribedSince + 1\n for (const seq of pendingEvents.keys()) {\n if (seq <= subscribedSince) pendingEvents.delete(seq)\n }\n if (!ws) {\n connect()\n } else if (ws.readyState === ws.OPEN) {\n sendSubscribe()\n }\n return () => {\n disposed = true\n if (reconnectTimer !== null) { clearTimeoutImpl(reconnectTimer); reconnectTimer = null }\n // 手动发 'closed'——closeSocket 已清 onclose handler,socket.close() 不再代发状态。\n emitState('closed')\n if (ws) closeSocket(ws)\n onEventCb = null\n onGapCb = null\n }\n },\n\n sendVerb(verb: string, data: Record<string, unknown>): void {\n // RFC-335 S4:直接 verb(Session.writeLease.acquire/release)供应商。\n // 未连接 silently drop(重连 deliver-first-subscribe 不 stick 之前的下达)。\n if (disposed || !ws || ws.readyState !== ws.OPEN) return\n ws.send(JSON.stringify({ method: verb, params: data }))\n },\n }\n}\n\n/** attach 入口快捷形式:复用 service-client 的 fetchSnapshot。 */\nexport function createAttachTransport(args: {\n baseUrl: string\n sessionId: string\n fetchSnapshot: (sessionId: string) => Promise<RemoteSessionSnapshot>\n token?: string\n onNotify?: (msg: string) => void\n}): SessionTransport {\n return createServiceWsTransport(args.baseUrl, args.sessionId, {\n fetchSnapshot: args.fetchSnapshot,\n onNotify: args.onNotify,\n })\n}\n\n// 供 attach 入口报错区分。\nexport { ServiceUnreachableError }\n","/**\n * run-attach.ts — RFC-335 T335-07 S2 装配:`otto --attach <sessionId>` attach 模式。\n *\n * 装配链:\n * service snapshot → ServiceWsTransport(WebSocket) → createAttachSessionDataSource\n * → handle.lifecycle.attachSessionSource → 本地 messageStore(TUI 唯一 writer)\n *\n * 关键取舍(RFC-335 §5/§6 + grill 决议 A):\n * - **writer 唯一**:dataSource 的 hydrateMessages/append 都经 handle 出口 → 本地 zustand\n * messageStore 由 TUI 本体写,cli 这层从不手拿 `messageStore` 句柄。\n * - **事件流不再经 streamController**:attach 模式没 live-stream 流水线(streamText\n * Coalescer/agent sync 管线那都是本地 agent 跑起来的产物),事件逐条经 dataSource\n * 按 seq 应用进 store。\n * - **gap 行为可感知**:EVENT_LOG_GAP → dataSource 重水合(拉新 snapshot + messages\n * 整灌 + 重订阅新 cursor)——用户看到「消息重新出现」,绝不静默断一段。\n * - **写**:纯文本经 Chat.query 下发;任何斜杠命令 fail-fast 提示「attach 暂未支持」。\n */\nimport { startTui, type TuiHandle } from '@x-otto/tui'\nimport type { ChatMessage } from '@x-otto/tui'\nimport {\n createAttachSessionDataSource,\n createSessionEventReduceBridge,\n createStoreTuiSink,\n} from '@x-otto/tui'\n\nimport { createServiceClient } from './service-client'\nimport {\n createServiceWsTransport,\n type RemoteSessionSnapshot,\n type SessionTransport,\n} from './remote-transport'\n\nexport interface RunAttachOptions {\n sessionId: string\n baseUrl: string\n token?: string\n stdin?: NodeJS.ReadStream\n stdout?: NodeJS.WriteStream\n /** 单测注入;生产缺省全局 fetch。 */\n fetchImpl?: typeof globalThis.fetch\n /** RFC-335 S4 补丁:强抢写权——kill -9 造成 stuck lease 的唯一正式解锁口。\n * `otto --attach <id> --force-write-lease`。服务端 audit logger.warn 记录,\n * 原 owner 若仍订阅将收「写权被剥夺」通知。 */\n forceWriteLease?: boolean\n}\n\nexport async function runAttach(opts: RunAttachOptions): Promise<void> {\n const stdout = opts.stdout ?? process.stdout\n const stdin = opts.stdin ?? process.stdin\n const fetchImpl = opts.fetchImpl ?? globalThis.fetch\n\n // RFC-335 S4 写租约状态:false 时 onSubmit 最纯文本被拒(只读观察者态)。初始为 true;\n // transport.onWriteLease 会按 Runtime.writeLease envelope 更新。\n let writeLeaseGranted = true\n let leaseOwnerId: string | undefined\n\n const client = createServiceClient(opts.baseUrl, fetchImpl, { token: opts.token })\n\n // 1) snapshot 先行:确认 session 存在 + 拿 cursor。失败 = 显式报错退出(RFC-335 §6:无 local fallback)。\n let snapshot\n try {\n snapshot = await client.fetchSnapshot(opts.sessionId)\n } catch (err) {\n stdout.write(`otto --attach: 无法连接 serve 上的会话 ${opts.sessionId}:${err instanceof Error ? err.message : String(err)}\\n`)\n throw err\n }\n const sessionId = snapshot.session.id\n\n // 2) 起 TUI(不开本地 session、不起 agent workflow——attach 模式的写全走 Chat.query/slash 灰门)\n let tuiRef: TuiHandle | undefined\n const pushText = (text: string): void => {\n if (!tuiRef) return\n const msg: ChatMessage = { role: 'assistant', content: [{ type: 'text', text }] }\n tuiRef.messages.pushMessage(msg)\n }\n\n tuiRef = await startTui({\n stdin,\n stdout,\n columns: stdout.columns ?? 100,\n needsSetup: false,\n welcome: { cwd: undefined },\n onSubmit: (text: string) => {\n // 灰门:任何斜杠命令 attach 期不消费(steer/pause/resume/setTodoList 归下期;RFC-335 §5)。\n const trimmed = text.trimStart()\n if (trimmed.startsWith('/')) {\n pushText(\n `[attach] \\`${trimmed.split(/\\s+/)[0]}\\` 为 attach 暂未支持命令。\\n` +\n `仅支持纯文本提交(经 Chat.query 下发到 serve);如需本地命令请退出 attach 后运行。`,\n )\n return\n }\n if (!tuiRef) return\n // RFC-335 S4 协议面:写租约被另一 client 占有时,本地预拦截(不发 Chat.query\n // 给服务端制造 PROMPT_FAILED spam)——TUI 提示只读观察者语义。\n if (!writeLeaseGranted) {\n pushText(\n `[attach] 会话的写权已被另一 client 持有(${leaseOwnerId ?? '?'})——本端变只读观察者。` +\n `请另选 session 或让对方退出 attach。`,\n )\n return\n }\n void (async () => {\n try {\n await client.sendPrompt(sessionId, text)\n // 响应经 WS 事件流实时回流到 dataSource → messageStore(无需手动同步)。\n } catch (err) {\n pushText(`[attach] 提交失败:${err instanceof Error ? err.message : String(err)}`)\n }\n })()\n },\n })\n\n // 3) transport + dataSource:service socket → 本地 messageStore\n const transport: SessionTransport = createServiceWsTransport(opts.baseUrl, sessionId, {\n fetchSnapshot: async (sid) => {\n const snap = await client.fetchSnapshot(sid)\n return snap as unknown as RemoteSessionSnapshot\n },\n onNotify: (msg) => pushText(`[attach] ${msg}`),\n onStateChange: (state) => {\n // S4 连接生命周期可感知(去抖已由 transport 管的)。\n if (state === 'connected') {\n pushText('[attach] 已连接 serve。')\n // RFC-335 S4:每次连接立刻尝试 acquire 写租约——酸同 clientId;\n // 服务端返回 acquired/denied 由 onWriteLease 通知。\n // RFC-335 S4 补丁:forceWriteLease 时改走 force_acquire(显式人工强抢)。\n if (opts.forceWriteLease) {\n pushText('[attach] --force-write-lease:向服务端发起 force_acquire(kill -9 解锁通道)……')\n transport.sendVerb('Session.writeLease.force_acquire', { sessionId })\n } else {\n transport.sendVerb('Session.writeLease.acquire', { sessionId })\n }\n } else if (state === 'reconnecting') {\n // RFC-335 S4:重连期间打 write-leases 不确定——服务端在断开时释放;保险状\n // 本端 reactive 防出被 rub:下状态 acquired 前一正在拒绝(更安全默认)。\n writeLeaseGranted = false\n pushText('[attach] 连接断开,正在重连……')\n } else if (state === 'closed') {\n writeLeaseGranted = false\n pushText('[attach] 连接已关闭。')\n }\n },\n onWriteLease: (ev) => {\n if (ev.state === 'acquired') {\n writeLeaseGranted = true\n leaseOwnerId = ev.leaseOwner\n pushText('[attach] 获会话写权——纯文本会走 Chat.query 到 service。')\n } else if (ev.state === 'denied') {\n writeLeaseGranted = false\n leaseOwnerId = ev.leaseOwner\n pushText(\n `[attach] 会话写权被另一 client 持有(${ev.leaseOwner ?? '?'})——` +\n `本端变只读观察者态;提交纯文本会被本端予拦截。\\n` +\n `若对方是 kill -9 残留,可重启带 \\`--force-write-lease\\` 强抢写权。`,\n )\n } else if (ev.state === 'released') {\n writeLeaseGranted = false\n leaseOwnerId = ev.leaseOwner\n if (ev.reason === 'forced_by_other_client') {\n pushText(\n `[attach] 写权被另一 client 强行剥夺(force_acquire by ${ev.leaseOwner ?? '?'})——` +\n `本端变只读观察者态。`,\n )\n } else {\n pushText('[attach] 写权已释放——重新 conn 上可申请。')\n }\n }\n },\n })\n\n // `reduceToTui` is the only event→view projection. It shares the same TuiHandle\n // store actions as local mode; snapshot hydration resets its derived state first.\n const eventBridge = createSessionEventReduceBridge(\n createStoreTuiSink({\n pushMessage: tuiRef.messages.pushMessage,\n echoUserInput: tuiRef.messages.echoUserInput,\n pushEngineEvent: tuiRef.engine.pushEngineEvent,\n getNotifyStats: tuiRef.info.getNotifyStats,\n setTasks: tuiRef.stores.tasks.setTasks,\n addTurnBoundary: tuiRef.stores.tasks.addTurnBoundary,\n }),\n )\n\n const dataSource = createAttachSessionDataSource({\n transport: transport as never,\n sessionId,\n onSessionEvent: eventBridge.handle,\n hydrateMessages: (target, messages) => {\n // Snapshot is authoritative after initial connect and EVENT_LOG_GAP. Reset the\n // reducer before accepting subsequent deltas so turn-boundary indices start at\n // the hydrated message count instead of an obsolete stream.\n target.messageStore.setState(() => ({ messages: [] }))\n if (messages.length > 0) {\n target.messageStore.setState(() => ({\n messages: [...(messages as readonly ChatMessage[]) as never],\n }))\n }\n eventBridge.reset(messages.length)\n },\n onGap: () => {\n pushText(\n `[attach] 会话事件日志 gap(cursor 被 service 逐出)——已重新水合 messages 快照。`,\n )\n },\n })\n\n // 4) 挂载:TuiHandle.lifecycle.attachSessionSource(RFC-347 收敛面内)——TUI 本体握 store;\n // 重复挂载会 throw(attach 一个数据源是硬约束)。stop 由调用方跟 TUI lifecycles 结。\n await tuiRef.lifecycle.attachSessionSource(dataSource)\n\n // 5) attach 概述 = 这一条句话(恰好是唯一由 cli 经 messages.pushMessage 写的位置)\n pushText(\n `[attach] 已连接会话 ${sessionId}(host=${snapshot.authority.hostname} pid=${snapshot.authority.pid})。\\n` +\n 'snapshot messages 已灌入 store;后续 Session.event 经 WS 实时应用;' +\n '纯文本走 Chat.query;/xxx 斜杠命令暂未支持。',\n )\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-BXQ8bU-B.js";import{t}from"./daemon-base-
|
|
2
|
+
import{t as e}from"./create-host-app-BXQ8bU-B.js";import{t}from"./daemon-base-YOWgYYN7.js";import{SERVICE_PORT as n}from"@x-otto/env";import{resolve as r}from"node:path";import{createCodingService as i}from"@x-otto/service";async function a(n,r={}){let a=r.stdout??(e=>process.stdout.write(e)),c=r.stderr??(e=>process.stderr.write(e));if(n[0]===`help`||n[0]===`--help`)return s(a),0;let l,u,d,f;try{({port:l,inspect:u,staticDir:d,token:f}=o(n))}catch(e){return c(`otto serve: ${e instanceof Error?e.message:String(e)}\n`),1}let p=r.app??e({logger:{name:`otto-serve`,level:r.verbose?`debug`:`info`,destination:`stderr`},inspect:u,workspaceDir:r.projectDir,projectConfigPath:r.configPath,modelId:r.modelId});try{await p.start()}catch(e){return c(`otto serve: failed to start app: ${e instanceof Error?e.message:String(e)}\n`),1}let m=i(p,{port:l,host:`127.0.0.1`,staticDir:d,apiToken:f,wsAuthToken:f,authorityId:process.env.OTTO_AUTHORITY_ID||void 0});try{await m.start()}catch(e){return c(`otto serve: failed to listen on port ${l}: ${e instanceof Error?e.message:String(e)}\n`),await p.stop(),1}let h=m.httpServer.address().port;return a(`otto service listening on http://127.0.0.1:${h}${f?` (auth: token required)`:``}
|
|
3
3
|
${d?` web: http://127.0.0.1:${h}/${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:${h}/${f?` (打开时附加 ?token=<你的 to
|
|
|
18
18
|
|
|
19
19
|
面板命令默认连 http://127.0.0.1:${n}(可 --remote 覆盖)
|
|
20
20
|
`)}export{a as runServeCommand};
|
|
21
|
-
//# sourceMappingURL=serve-
|
|
21
|
+
//# sourceMappingURL=serve-Dbn3THez.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve-
|
|
1
|
+
{"version":3,"file":"serve-Dbn3THez.js","names":[],"sources":["../src/commands/serve.ts"],"sourcesContent":["import type { AddressInfo } from 'node:net'\nimport { resolve } from 'node:path'\nimport { SERVICE_PORT } from '@x-otto/env'\nimport { createHostApp } from '../create-host-app'\nimport { createCodingService } from '@x-otto/service'\nimport type { AppContext } from '@x-otto/coding'\n\nimport { waitForShutdown } from './daemon-base'\n\nexport interface ServeDeps {\n stdout?: (s: string) => void\n stderr?: (s: string) => void\n /** 全局 CLI 选项透传(与 `otto run` 同源装配)。 */\n projectDir?: string\n configPath?: string\n modelId?: string\n verbose?: boolean\n /** 测试注入:替代生产装配的 app(结构 = AppContext + start/stop)。 */\n app?: AppContext & { start(): Promise<void>; stop(): Promise<void> }\n /** 测试注入:优雅退出信号(生产 = SIGINT/SIGTERM)。 */\n signal?: AbortSignal\n}\n\nexport async function runServeCommand(args: string[], deps: ServeDeps = {}): Promise<number> {\n const stdout = deps.stdout ?? ((s) => process.stdout.write(s))\n const stderr = deps.stderr ?? ((s) => process.stderr.write(s))\n\n if (args[0] === 'help' || args[0] === '--help') {\n printHelp(stdout)\n return 0\n }\n\n let port: number\n let inspect: boolean\n let staticDir: string | undefined\n let token: string | undefined\n try {\n ;({ port, inspect, staticDir, token } = parseServeArgs(args))\n } catch (error) {\n stderr(`otto serve: ${error instanceof Error ? error.message : String(error)}\\n`)\n return 1\n }\n\n const app =\n deps.app ??\n createHostApp({\n logger: {\n name: 'otto-serve',\n level: deps.verbose ? 'debug' : 'info',\n destination: 'stderr',\n },\n // `--inspect` 必须下传:`AppOptions.inspect` 是 `buildInspectDevtools` 的唯一开关\n // (devtools-setup.ts:19 `if (!inspect) return { devtools: null }`)。此前 serve 解析了\n // 该 flag、也据它打印\"调试器已启用\"提示,却从未传给 createApp —— 于是 `app.devtools`\n // 恒为 null,`/api/debugger/*` 全部 503、DebugBridge 也不会被构造。即 `otto serve\n // --inspect` 实际从不启用调试器(提示与事实相反)。\n inspect,\n workspaceDir: deps.projectDir,\n projectConfigPath: deps.configPath,\n modelId: deps.modelId,\n })\n\n try {\n await app.start()\n } catch (error) {\n stderr(`otto serve: failed to start app: ${error instanceof Error ? error.message : String(error)}\\n`)\n return 1\n }\n\n // --token 同时启用 HTTP Bearer(apiToken)与 WS ?token=(wsAuthToken);页面用 URL `?token=` 对上。\n const service = createCodingService(app, {\n port,\n host: '127.0.0.1',\n staticDir,\n apiToken: token,\n wsAuthToken: token,\n // RFC-335 M2:由 `otto daemon start` 下传,使 AuthorityDescriptor.instanceId 与\n // daemon state 记录一致(客户端据此确认\"连上的 authority 就是 state 里那个 daemon\")。\n // 前台直接 `otto serve` 时缺省,由 service 自行生成。\n authorityId: process.env['OTTO_AUTHORITY_ID'] || undefined,\n })\n\n try {\n await service.start()\n } catch (error) {\n stderr(`otto serve: failed to listen on port ${port}: ${error instanceof Error ? error.message : String(error)}\\n`)\n await app.stop()\n return 1\n }\n\n const actualPort = (service.httpServer.address() as AddressInfo).port\n // 不把 token 明文打进 stdout(会落 scrollback/CI 日志)——只提示需附加 ?token=。\n const webHint = token ? ' (打开时附加 ?token=<你的 token>)' : ''\n stdout(`otto service listening on http://127.0.0.1:${actualPort}${token ? ' (auth: token required)' : ''}\n${staticDir ? ` web: http://127.0.0.1:${actualPort}/${webHint} (静态页面同源托管 ${staticDir})\\n` : ''} panels: otto observe summary <session-id>\n otto time-travel checkpoints <session-id>\n otto debug breakpoints list${inspect ? '' : ' (debugger disabled — restart with `otto serve --inspect`)'}\n stop: Ctrl-C\n`)\n\n await waitForShutdown(deps.signal)\n\n stdout('otto serve: shutting down...\\n')\n await service.stop()\n await app.stop()\n return 0\n}\n\nfunction parseServeArgs(args: string[]): {\n port: number\n inspect: boolean\n staticDir?: string\n token?: string\n} {\n let port = SERVICE_PORT\n let inspect = false\n let staticDir: string | undefined\n let token: string | undefined\n\n // 取下一个值;缺失或形如 --flag(误把下个选项当值)即报错,避免静默吞掉选项。\n const takeValue = (i: number, name: string): string => {\n const raw = args[i + 1]\n if (raw === undefined || raw.startsWith('--')) {\n throw new Error(`${name} requires a value`)\n }\n return raw\n }\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]\n if (arg === '--port' || arg === '-p') {\n const raw = takeValue(i, '--port')\n i++\n const n = Number(raw)\n if (!Number.isInteger(n) || n < 0 || n > 65535) {\n throw new Error(`invalid --port value: ${raw}`)\n }\n port = n\n } else if (arg === '--inspect') {\n inspect = true\n } else if (arg === '--static') {\n staticDir = resolve(takeValue(i, '--static'))\n i++\n } else if (arg === '--token') {\n token = takeValue(i, '--token')\n i++\n }\n // 其它(含全局 flag --verbose/--model 等,经 ctx.args 透传进来)忽略——\n // 不能 throw,否则 `otto serve --verbose` 会误伤(全局 flag 由 parseCLI 已解析)。\n }\n\n return { port, inspect, staticDir, token }\n}\n\nfunction printHelp(stdout: (s: string) => void): void {\n stdout(`otto serve — 启动 HTTP/WS service(观测/调试/时间旅行面板的对端)\n\n用法:\n otto serve [--port <n>] [--inspect] [--static <dir>] [--token <t>]\n\n选项:\n --port, -p <n> 监听端口(默认 ${SERVICE_PORT},可经 OTTO_SERVICE_PORT 覆盖;0 = 随机)\n --inspect 启用调试器(断点/暂停面板需要)\n --static <dir> 同源托管一个已构建的前端目录(SPA),页面与 /api、/ws 同端口免 CORS\n --token <t> 启用鉴权:HTTP /api/* 需 Bearer,WS 需 ?token=;页面用 URL ?token=<t> 对上\n\n面板命令默认连 http://127.0.0.1:${SERVICE_PORT}(可 --remote 覆盖)\n`)\n}\n"],"mappings":";gOAuBA,eAAsB,EAAgB,EAAgB,EAAkB,EAAE,CAAmB,CAC3F,IAAM,EAAS,EAAK,SAAY,GAAM,QAAQ,OAAO,MAAM,EAAE,EACvD,EAAS,EAAK,SAAY,GAAM,QAAQ,OAAO,MAAM,EAAE,EAE7D,GAAI,EAAK,KAAO,QAAU,EAAK,KAAO,SAEpC,OADA,EAAU,EAAO,CACV,EAGT,IAAI,EACA,EACA,EACA,EACJ,GAAI,EACA,uCAAsC,EAAe,EAAK,QACrD,EAAO,CAEd,OADA,EAAO,eAAe,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CAAC,IAAI,CAC1E,EAGT,IAAM,EACJ,EAAK,KACL,EAAc,CACZ,OAAQ,CACN,KAAM,aACN,MAAO,EAAK,QAAU,QAAU,OAChC,YAAa,SACd,CAMD,UACA,aAAc,EAAK,WACnB,kBAAmB,EAAK,WACxB,QAAS,EAAK,QACf,CAAC,CAEJ,GAAI,CACF,MAAM,EAAI,OAAO,OACV,EAAO,CAEd,OADA,EAAO,oCAAoC,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CAAC,IAAI,CAC/F,EAIT,IAAM,EAAU,EAAoB,EAAK,CACvC,OACA,KAAM,YACN,YACA,SAAU,EACV,YAAa,EAIb,YAAa,QAAQ,IAAI,mBAAwB,IAAA,GAClD,CAAC,CAEF,GAAI,CACF,MAAM,EAAQ,OAAO,OACd,EAAO,CAGd,OAFA,EAAO,wCAAwC,EAAK,IAAI,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CAAC,IAAI,CACnH,MAAM,EAAI,MAAM,CACT,EAGT,IAAM,EAAc,EAAQ,WAAW,SAAS,CAAiB,KAejE,OAZA,EAAO,8CAA8C,IAAa,EAAQ,4BAA8B,GAAG;EAC3G,EAAY,+BAA+B,EAAW,GAFtC,EAAQ,6BAA+B,GAEU,eAAe,EAAU,KAAO,GAAG;;wCAE9D,EAAU,GAAK,+DAA+D;;EAEpH,CAEA,MAAM,EAAgB,EAAK,OAAO,CAElC,EAAO;EAAiC,CACxC,MAAM,EAAQ,MAAM,CACpB,MAAM,EAAI,MAAM,CACT,EAGT,SAAS,EAAe,EAKtB,CACA,IAAI,EAAO,EACP,EAAU,GACV,EACA,EAGE,GAAa,EAAW,IAAyB,CACrD,IAAM,EAAM,EAAK,EAAI,GACrB,GAAI,IAAQ,IAAA,IAAa,EAAI,WAAW,KAAK,CAC3C,MAAU,MAAM,GAAG,EAAK,mBAAmB,CAE7C,OAAO,GAGT,IAAK,IAAI,EAAI,EAAG,EAAI,EAAK,OAAQ,IAAK,CACpC,IAAM,EAAM,EAAK,GACjB,GAAI,IAAQ,UAAY,IAAQ,KAAM,CACpC,IAAM,EAAM,EAAU,EAAG,SAAS,CAClC,IACA,IAAM,EAAI,OAAO,EAAI,CACrB,GAAI,CAAC,OAAO,UAAU,EAAE,EAAI,EAAI,GAAK,EAAI,MACvC,MAAU,MAAM,yBAAyB,IAAM,CAEjD,EAAO,OACE,IAAQ,YACjB,EAAU,GACD,IAAQ,YACjB,EAAY,EAAQ,EAAU,EAAG,WAAW,CAAC,CAC7C,KACS,IAAQ,YACjB,EAAQ,EAAU,EAAG,UAAU,CAC/B,KAMJ,MAAO,CAAE,OAAM,UAAS,YAAW,QAAO,CAG5C,SAAS,EAAU,EAAmC,CACpD,EAAO;;;;;;6BAMoB,EAAa;;;;;2BAKf,EAAa;EACtC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x-otto/cli",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.21",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@x-otto/agent": "0.0.1-alpha.0",
|
|
21
21
|
"@x-otto/ai": "0.0.1-alpha.0",
|
|
22
|
-
"@x-otto/coding": "0.0.1-alpha.
|
|
22
|
+
"@x-otto/coding": "0.0.1-alpha.9",
|
|
23
23
|
"@x-otto/schedule": "0.0.1-alpha.0",
|
|
24
24
|
"@x-otto/env": "0.1.0-alpha.4",
|
|
25
25
|
"@x-otto/hooks": "0.0.1-alpha.0",
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import{t as e}from"./build-id-D7Wf4tQw.js";import{t}from"./version-Cq2kcrXL.js";import{t as n}from"./discover-with-bundled-CF8qFP41.js";import{n as r,r as i,t as a}from"./service-client-Cr4sxDVM.js";import{n as o}from"./extension-DLFCUBJr.js";import{a as s,i as c,r as l,s as u}from"./extension-plugin-BYR-MJ1U.js";import{n as d,r as f,t as p}from"./model-auth-BMY86Bk4.js";import{t as m}from"./proxy-CMQBvdZC.js";import{i as h}from"./install-hTDjeo61.js";import{n as g,t as _}from"./registry-Cr5To_5r.js";import{n as ee,t as v}from"./feedback-DrUjMuKw.js";import{createRequire as te}from"node:module";import{arch as ne,cpus as re,freemem as y,homedir as b,platform as ie,release as ae,totalmem as oe}from"node:os";import{execFile as se,execSync as ce,spawn as le,spawnSync as ue}from"node:child_process";import{DEFAULT_WORKSPACE_KEY as de,INDUCTOR_PLUGIN_ID as fe,PLUGIN_MANIFEST_FILENAME as pe,checkUpgrades as me,collectPluginPerfMetrics as he,detectProjectConfigGating as ge,discoverFileCommands as _e,discoverPlugins as ve,evaluateClaudeTrust as ye,evaluatePluginTrust as be,findWorkspaceRoot as xe,getLastAssistantText as x,parsePluginManifest as Se,readSkillLoopStatus as Ce,resolvePackageDir as we,resolveSessionTitle as Te,scanInstalledVersions as Ee,trustProject as De,untrustPlugin as Oe,untrustProject as ke}from"@x-otto/coding";import{checkShellCommand as Ae,openBrowser as je,readFileCapped as Me}from"@x-otto/shared";import{BUILD_ID as Ne,OTTO_HOME as Pe,SDK_VERSION as Fe}from"@x-otto/env";import{dirname as S,join as C,relative as Ie,resolve as Le}from"node:path";import{closeSync as Re,existsSync as w,openSync as ze,readFileSync as Be,readSync as Ve,readdirSync as He,statSync as T}from"node:fs";import{FIGURES as E,buildTurnSummary as Ue,createBuiltinThemeRegistry as We,createReducerState as Ge,deriveTurnBoundaries as Ke,detectLanguage as qe,formatProviderUsageLimitTypeFull as Je,i18next as D,initI18n as Ye,insertTurnSummaries as Xe,reduceToTui as Ze}from"@x-otto/tui";import{readFile as Qe,readdir as $e,stat as et,unlink as tt}from"node:fs/promises";import{createBuiltinCommands as nt}from"@x-otto/tui/compat";import{MaxToolTurnsError as rt,PromptBudgetExceededError as it}from"@x-otto/agent";import{DEFAULT_COMPACTION_CONFIG as at,DEFAULT_PRUNE_CONFIG as ot,estimateTokensHeuristic as st,resolveContextSize as ct}from"@x-otto/memory";import{classifyAuthErrorType as lt,computeCacheHitRatio as ut,createModelUsabilityGate as O,getToolCallsByAssistantMessage as dt,isAuthError as ft}from"@x-otto/ai";import{promisify as pt}from"node:util";var mt=Object.defineProperty,ht=(e,t)=>{let n={};for(var r in e)mt(n,r,{get:e[r],enumerable:!0});return t||mt(n,Symbol.toStringTag,{value:`Module`}),n},gt=te(import.meta.url);function _t(e){return`⚠ 已达工具回合上限(${e.extensions>0?`约 ${e.effectiveMaxTurns} 次(含 ${e.extensions} 次自动扩展,每次 ${e.maxTurns})`:`${e.maxTurns} 次`}),提前停止——任务可能未完成。\n 重试可加 --max-tool-turns <n>(每轮预算,实际上限 ≈ n×${1+e.extensions};如 --max-tool-turns ${e.maxTurns*2})。\n 注:配置了 --prompt-token-budget 时轮次窗口自动 ×4(此处报的是放大后的有效值)。`}function vt(e,t){let n=x(e.session.messages())||t.partialText,r=_t(t);return n?`${n}\n${r}`:r}function yt(e){let t=e.dimension===`tokens`?`输出 token 预算 ${e.used}/${e.budget}`:`时间预算 ${Math.round(e.used/1e3)}s/${Math.round(e.budget/1e3)}s`;return`⚠ 已达本回合${e.dimension===`tokens`?`输出 token`:`墙钟时间`}预算(${t}),提前停止——任务可能未完成。\n 重试可加 ${e.dimension===`tokens`?`--prompt-token-budget <n>`:`--prompt-time-budget <分钟>`} 提升预算。`}function bt(e,t){let n=x(e.session.messages())||t.partialText,r=yt(t);return n?`${n}\n${r}`:r}async function xt(e,t,n={}){let{writer:r,tui:i=!1,tuiOptions:a,onTurnEnd:o}=typeof n==`function`?{writer:n}:n,s=r??(e=>process.stdout.write(`${e}\n`)),c=Date.now();try{await e.prompt(t)}catch(t){if(t instanceof rt||t instanceof it){let n=t instanceof it,r=n?yt(t):_t(t);o?.(n?`budget-exceeded`:`max-tool-turns`,r);let l=o?x(e.session.messages())||t.partialText:n?bt(e,t):vt(e,t);if(i){let{printMessages:t}=await import(`@x-otto/tui`);t(e.session.messages(),{...a,startTime:c,contextWindow:0}),s(l)}else s(l);return l}throw t}if(o&&e.budgetSteeredThisPrompt&&o(`max-tool-turns`,`⚠ 已达工具回合上限——已自动收尾本回合(任务可能未全部完成,可继续指示我,或加 --max-tool-turns 提升预算)。`),i){let{printMessages:t}=await import(`@x-otto/tui`),n=e.session.messages();return t(n,{...a,startTime:c,contextWindow:e.model.contextWindow??0}),x(n)}let l=x(e.session.messages());return s(l),l}async function St(e,t){try{await e.prompt(t)}catch(t){if(t instanceof rt)return{sessionId:e.id,status:`max_turns`,messageCount:e.session.messages().length,response:x(e.session.messages())||t.partialText};if(t instanceof it)return{sessionId:e.id,status:`budget_exceeded`,messageCount:e.session.messages().length,response:x(e.session.messages())||t.partialText};throw t}return{sessionId:e.id,status:e.status,messageCount:e.session.messages().length,response:x(e.session.messages())}}var k=class{commandRegistry;modelList=[];currentModelId;onModelChange;constructor(e,t={}){this.session=e,this.modelList=t.models??[],this.currentModelId=t.currentModel,this.onModelChange=t.onModelChange,this.commandRegistry=nt()}getRegistry(){return this.commandRegistry}abort(){this.session.abort()}async handleInput(e){if(this.session.status===`archived`)return{type:`system`,text:`此会话已归档(archived),无法发送新消息。使用 /resume 切换到其他会话。`};let t=e.trim();if(t.length===0)return{type:`noop`};if(t.startsWith(`!`)){let e=t.slice(1).trim();if(!e)return{type:`system`,text:`Usage: !<shell command>`};let n=Ae(e);if(!n.safe)return{type:`system`,text:`Refusing to execute dangerous command (${n.reason}): ${e}\nUse the bash tool via the agent for privileged operations.`};let r,i=!1;try{r=ce(e,{encoding:`utf-8`,timeout:3e4,cwd:this.session.workspaceDir,maxBuffer:1024*1024})}catch(e){let t=e;r=t.stderr||t.stdout||t.message||String(e),i=!0}return r=r.trimEnd(),this.session.session.append({role:`system`,subtype:`local_command`,level:i?`error`:`info`,content:r||`(no output)`,timestamp:Date.now()}),{type:`response`,text:r||`(no output)`}}if(t.startsWith(`@`)){let e=t.slice(1).trim();return e?{type:`response`,text:await xt(this.session,`Read and analyze the file: ${e}`,{writer:()=>{}})}:{type:`system`,text:`Usage: @<file-path>`}}if(!t.startsWith(`/`)){let e,n;return{type:`response`,text:await xt(this.session,t,{writer:()=>{},onTurnEnd:(t,r)=>{e=t,n=r}}),endReason:e,notice:n}}let[n,...r]=t.slice(1).split(/\s+/);if(!n){let{showCommandPalette:e}=await import(`@x-otto/tui`),t=await e(this.commandRegistry.list(),process.stdin,process.stdout);if(!t.command)return{type:`noop`};let n=this.commandRegistry.get(t.command);if(!n)return{type:`noop`};let r=await n.execute(t.args,{signal:new AbortController().signal,width:process.stdout.columns??80,models:this.modelList,currentModel:this.currentModelId,onModelChange:this.onModelChange});return this._handleCommandResult(r)}let i=r.join(` `),a=this.commandRegistry.find(n);if(!a)return{type:`system`,text:`Unknown command: /${n}. Try /help.`};let o=await a.execute(i,{signal:new AbortController().signal,width:process.stdout.columns??80,models:this.modelList,currentModel:this.currentModelId,onModelChange:this.onModelChange});return this._handleCommandResult(o)}_handleCommandResult(e){if(typeof e==`string`)return{type:`system`,text:e};switch(e.kind){case`exit`:return{type:`exit`,restart:e.restart};case`help`:return{type:`help`};case`clear`:return this.session.clearTurnPanels(),this.session.clearContext(),process.stdout.write(`\x1B[2J\x1B[H`),{type:`noop`};case`abort`:return this.session.abort(),{type:`system`,text:`Aborted current run.`};case`status`:return{type:`system`,text:`Session: ${this.session.id} | Status: ${this.session.status}`};case`copy`:return{type:`system`,text:`Copy not yet available in CLI mode.`};case`model`:return this.onModelChange?(this.onModelChange(e.modelId).then(e=>{process.stdout.write(e+`
|
|
3
|
-
`)}).catch(e=>{process.stdout.write(`Model switch failed: ${e instanceof Error?e.message:String(e)}\n`)}),{type:`system`,text:`Switching to ${e.modelId}...`}):{type:`system`,text:`Model switching not available.`}}}};function Ct(){return qe()}function wt(e=`auto`){let t=e===`auto`?qe():e;return Ye(t),t}async function Tt(e){try{let t=new AbortController,n=setTimeout(()=>t.abort(),5e3),r=await fetch(`https://registry.npmjs.org/%40x-otto%2Fcli/latest`,{signal:t.signal});if(clearTimeout(n),!r.ok)return null;let i=(await r.json())?.version??``;return i?{latest:i,current:e,newer:Et(i,e)>0}:null}catch{return null}}function Et(e,t){let n=Dt(e),r=Dt(t);if(!n||!r)return 0;for(let e=0;e<3;e++)if(n[e]!==r[e])return n[e]-r[e];return 0}function Dt(e){let t=/(\d+)\.(\d+)\.(\d+)/.exec(e);return t?[Number(t[1]),Number(t[2]),Number(t[3])]:null}async function Ot(){return new Promise(e=>{try{let t=le(process.platform===`win32`?`npm.cmd`:`npm`,[`install`,`-g`,`@x-otto/cli@latest`],{stdio:`inherit`,shell:!0});t.on(`error`,t=>{e({ok:!1,error:t.message})}),t.on(`exit`,t=>{if(t!==0){e({ok:!1,error:`npm install exited with code ${t}`});return}e({ok:!0})})}catch(t){e({ok:!1,error:t.message})}})}function kt(e){return e?Date.now()-e>216e5:!0}async function At(e){let t=process.env.OTTO_UPGRADE_MOCK;if(t){let e=t===`force`;return{latest:e?`2.0.0`:`0.2.0`,current:`0.0.1`,newer:!0,forced:e}}return!e.autoCheck||!kt(e.lastCheck)?null:Tt(e.currentVersion).then(async t=>t&&(e.persistLastCheck().catch(()=>{}),t.newer)?{...t,forced:!1}:null).catch(()=>null)}const jt=st;let A,Mt;function Nt(e){let t=e.map(e=>e.name).sort().join(`,`);if(A!==void 0&&Mt===t)return A;let n=e=>typeof e==`function`?`[fn]`:typeof e==`string`?e:``;return A=jt(JSON.stringify(e.map(e=>({name:e.name,description:n(e.description),parameters:e.parameters})))),Mt=t,A}let j,Pt;function Ft(e){return j!==void 0&&Pt===e?j:(j=jt(e),Pt=e,j)}function It(e){let{systemPrompt:t,tools:n,lastIn:r,lastOut:i,lastCacheRead:a,lastCacheWrite:o,contextWindow:s,modelName:c=``,totIn:l=r,totOut:u=i,cost:d,messageCount:f=0,streamingOutputTokens:p=0}=e,m=Ft(t)+Nt(n),h=a+o+r+i+p,g=Math.max(0,h-m),_=Math.max(0,s-m-g),ee=ct(ot.trigger,s),v=ct(at.trigger,s);return{modelName:c,contextWindow:s,contextPercent:s>0?h/s*100:0,overheadTokens:m,messageTokens:g,messageCount:f,totalUsed:h,free:_,pruneLine:ee,compactLine:v,lastInput:r,lastOutput:i,totalInput:l,totalOutput:u,cost:d,live:p>0}}const Lt={openai:`OpenAI`};function Rt(e){let t=[`confirm`,`auto`,`bypass`];return t[(t.indexOf(e)+1)%t.length]??`confirm`}function zt(e){return e===`error`?`error`:e===`approval_required`||e===`input_required`?`warning`:`info`}function Bt(e,t,n){let r=t===void 0?void 0:{code:t},i=r!==void 0&&ft(r),a=i&<(r)===`refresh-failed`;return{authError:i||/\b401\b|authentication|unauthorized|api[\s_-]?key|invalid.*key/i.test(e),isRefreshFailed:a,unsupported:/model_not_supported|not\s+supported|invalid.*model|model.*invalid/i.test(e),rateLimit:/\b429\b|rate.limit|rate_limit|exceed.*rate/i.test(e),pluginSource:n}}function M(e){return e?e.toLowerCase()===`xhigh`?`XHigh`:e.charAt(0).toUpperCase()+e.slice(1):``}function Vt(e,t,n){return e?`${t.on} ${n.on}`:`${t.off} ${n.off}`}function N(e,t,n){if(!t)return`(no model)`;let r=M(n),i=r?`${t}·${r}`:t,a=e.modelRegistry.find(t)?.contextWindow;return a?`${i} (${a>=1e6?`${(a/1e6).toFixed(1)}M`:`${Math.round(a/1e3)}K`})`:i}function Ht(e,t){let n=[..._e(t.workspaceDir,t.activePlugins()),...t.computedPluginCommands()];for(let t of n)e.has(t.name)||e.register({name:t.name,description:t.description??`Project command (.claude/.otto)`,aliases:void 0,type:`action`,source:`builtin`,execute:()=>``});return n}function Ut(e){return{id:e.id,type:`function`,function:{name:e.name,arguments:JSON.stringify(e.arguments)}}}function Wt(e){return{type:`thinking`,thinking:e.text}}function Gt(e){return{type:`text`,text:e.text}}function Kt(e){return{type:`a2ui`,version:e.version,components:e.components}}function qt(e){let t=[],n=[];for(let r of e)switch(r.type){case`text`:r.text.trim()&&t.push(Gt(r));break;case`thinking`:r.text.trim()&&t.push(Wt(r));break;case`tool_call`:n.push(Ut(r));break;case`a2ui`:t.push(Kt(r));break}return{content:t,toolCalls:n}}function Jt(e){return typeof e==`string`?e.trim():e.filter(e=>e.type===`text`).map(e=>e.text).join(``).trim()}function Yt(e){if(Object.keys(e).length===0)return;let t=e.command??e.path??e.file_path??e.pattern??e.query;return typeof t==`string`?t:JSON.stringify(e)}function P(e,t){let n=new Map,r=new Set;if(t?.toolCalls)for(let[e,r]of t.toolCalls)n.set(e,r);if(t?.resolvedIds)for(let e of t.resolvedIds)r.add(e);for(let t of e){let e=t;if(e?.role===`assistant`&&Array.isArray(e.content))for(let t of e.content)t.type===`tool_call`&&n.set(t.id,t);else e?.role===`tool_result`&&r.add(e.toolCallId)}let i=[];for(let t of e){let e=t;if(!(!e||typeof e.role!=`string`)&&!e.internal){if(e.role===`user`){let t=Jt(e.content);t&&i.push({role:`user`,content:t});continue}if(e.role===`assistant`){let{content:t}=qt(e.content??[]);t.length>0&&i.push({role:`assistant`,content:t});for(let t of e.content??[])t.type===`tool_call`&&!r.has(t.id)&&i.push({role:`tool`,name:t.name,args:Yt(t.arguments),argsObj:t.arguments,result:void 0,isError:!1});continue}if(e.role===`tool_result`){let t=n.get(e.toolCallId),r=t?.arguments??{};i.push({role:`tool`,name:e.toolName||t?.name||`tool`,args:Yt(r),argsObj:r,result:Jt(e.content),isError:e.isError});continue}if(e.role===`system_notification`){let t=e;i.push({role:`tool_completion`,content:t.content,isError:t.isError??!1});continue}if(e.role===`system`){let t=e,n=typeof t.content==`string`?t.content:``;if(t.subtype===`model_switch`){i.push({role:`model_switch`,label:n});continue}if(t.subtype===`effort_switch`){i.push({role:`effort_switch`,label:n});continue}if(t.subtype===`run_paused`||t.subtype===`run_resumed`||t.subtype===`run_pause_cancelled`){i.push({role:t.subtype,label:n});continue}continue}}}return i}const Xt=pt(se);function Zt(e,t,n){for(let r of e){if(r.role!==`assistant`)continue;let e=dt(r);for(let r of e){if(r.id!==t)continue;let e=r.arguments;if(n===`edit`)return{oldContent:typeof e.oldContent==`string`?e.oldContent:void 0,newContent:typeof e.newContent==`string`?e.newContent:void 0};if(n===`write`)return{newContent:typeof e.content==`string`?e.content:void 0}}}return{}}function Qt(e,t){let n=S(e),r=e=>{let t=ue(`git`,[...e],{cwd:n,timeout:5e3,encoding:`utf-8`,maxBuffer:10*1024*1024});return t.status===0?t.stdout:null},i;if(t!==`delete`)try{i=Be(e,`utf-8`)}catch{}let a,o=r([`ls-files`,`--full-name`,`--`,e]);if(o&&o.trim()){let e=r([`show`,`HEAD:${o.trim()}`]);e!==null&&(a=e)}return{oldContent:a,newContent:i}}async function $t(e,t){let n=S(e),r=async e=>{try{let{stdout:t}=await Xt(`git`,[...e],{cwd:n,encoding:`utf-8`,maxBuffer:10485760,signal:AbortSignal.timeout(5e3)});return{ok:!0,stdout:t}}catch{return{ok:!1,stdout:null}}},i;if(t!==`delete`)try{i=await Qe(e,`utf-8`)}catch{}let a,o=await r([`ls-files`,`--full-name`,`--`,e]);if(!o.ok)return t===`edit`?{error:`not a git repository (or git unavailable)`,newContent:i}:{oldContent:a,newContent:i};if(o.stdout&&o.stdout.trim()){let e=await r([`show`,`HEAD:${o.stdout.trim()}`]);e.ok&&e.stdout!==null&&(a=e.stdout)}return{oldContent:a,newContent:i}}function en(e){return[...e].sort((e,t)=>t.savedAt-e.savedAt)}function tn(e,t){if(e!=null&&e.trim().length>0)return e.trim();let n=new Set(t.map(e=>e.name)),r=t.length+1,i=`draft-${r}`;for(;n.has(i);)r++,i=`draft-${r}`;return i}function nn(e,t,n){let r=Date.now();return{id:`${r.toString(36)}-${Math.random().toString(36).slice(2,6)}`,name:tn(e,n),text:t,savedAt:r}}function rn(e){return e.length<=50?[...e]:e.slice(-50)}function F(e){let t=P(e.session.visibleMessages()),n=e.session.getTurnSummaries();return!n||n.length===0?t:Xe(t,n,e.session.getTurnCount()??t.filter(e=>e.role===`user`).length)}function an(e){let{resumedNote:t,isFound:n,session:r,projState:i,tui:a}=e,o=F(r);a.messages.pushMessages(o),n&&o.length>0&&a.info.setTopView({session:r.id}),a.info.showToast(n?D.t(`cmd.titleSessionResumed`):D.t(`cmd.titleSession`),t.replace(/^↻ /,``)),on({session:r,projState:i,tui:a,chatMsgs:o})}function on(e){let{session:t,projState:n,tui:r}=e,i=e.chatMsgs??F(t);try{let e=t.session.getTodoList();e&&e.length>0&&!e.every(e=>e.status===`done`||e.status===`skipped`||e.status===`failed`)?(n.todoList=e.map(e=>({id:e.id,title:e.title,status:e.status,turn:e.turn})),r.stores.tasks.setTasks(e.map(e=>({id:e.id??``,title:e.title??``,status:e.status||`pending`,turn:e.turn})),void 0)):e&&e.length>0&&(t.session.setTodoList([]),r.info.showToast(D.t(`cmd.titleSession`),D.t(`cmd.resumeTodosAllDone`)));{let e=t.session.getTurnCount(),a=i.filter(e=>e.role===`user`).length,o=e??a;o>0&&(r.stores.tasks.setTurnBoundaries(Ke(i,o)),n.currentTurn=o)}let a=t.session.getEditedFiles();if(a&&a.length>0){let e=t.session.messages();r.stores.tasks.setEditedFiles(a.map(t=>{let n=t.operation,r=t.toolCallId?Zt(e,t.toolCallId,n):{};return r.oldContent===void 0&&r.newContent===void 0&&(r=Qt(t.path,n)),{path:t.path,operation:n,addedLines:t.addedLines,removedLines:t.removedLines,timestamp:t.timestamp,turnId:t.turnId,toolCallId:t.toolCallId,oldContent:r.oldContent,newContent:r.newContent}}))}let o=t.session.getSubagents();if(o&&o.length>0)for(let e of o)r.plugins.addSubagent({id:e.id,name:e.name,status:e.status,startedAt:e.startedAt});r.stores.tasks.setDrafts(en(t.session.getDrafts()??[]))}catch(e){console.error(`\x1b[90m\x1b[2m[resume] panel state restore skipped (non-fatal): ${e instanceof Error?e.message:String(e)}\x1b[0m`)}}const I=new Map;function sn(e){return I.get(e)}function L(e){I.delete(e);let t=B.get(e);t&&(clearTimeout(t),B.delete(e))}function cn(){return[...I.values()]}function R(e,t,n){let r={id:e,transcript:P(t),streaming:``,status:n?`busy`:`idle`,lastActivityAt:Date.now()};return I.set(e,r),r}function ln(e,t){let n=I.get(e);n&&(n.transcript=P(t),n.lastActivityAt=Date.now())}const z=new Set;function un(e){return z.add(e),()=>{z.delete(e)}}const B=new Map;function dn(e){let t=B.get(e.id);t&&clearTimeout(t),B.set(e.id,setTimeout(()=>{B.delete(e.id);for(let t of z)t(e)},300))}function fn(e,t){let n=``,r=t=>{e.status!==t&&(e.status=t,dn(e))};return[i=>{if(i.type===`stream.event`&&i.event?.type===`text_delta`){n+=i.event.delta,e.streaming=n,e.lastActivityAt=Date.now();return}if(i.type===`prompt.end`){e.streaming=``,n=``,r(`idle`),ln(e.id,t());return}if(i.type===`error`){e.streaming=``,n=``,r(`idle`),e.lastActivityAt=Date.now();return}if(i.type===`approval.required`||i.type===`ask.user.required`){e.status=`needs-input`,e.pendingGate={type:i.type,id:String(i.id??``),data:i.data??{}},e.lastActivityAt=Date.now(),dn(e);return}},()=>e]}function pn(e){let t=[`session ${e.session.id}`,`status ${e.session.status} · ${e.session.messageCount} messages · cursor ${e.cursor}`,`authority host=${e.authority.hostname} pid=${e.authority.pid}`];if(e.session.messages.length>0){t.push(`recent:`);for(let n of e.session.messages.slice(-3))t.push(` · ${typeof n==`object`&&n&&`role`in n?String(n.role):`message`} … ${JSON.stringify(n).slice(0,120)}`)}else t.push(`(无历史消息)`);return t.join(`
|
|
4
|
-
`)}async function mn(e,t){let n=e.join(` `).trim();if(!n){t.tui.info.showToast(`/session inspect`,"Usage: /session inspect <sessionId> —— peek a session running on `otto serve` (read-only snapshot)");return}let o=i(process.env.OTTO_SERVICE_URL??`http://127.0.0.1:8417`,globalThis.fetch,{token:process.env.OTTO_SERVICE_TOKEN}),s;try{s=await o.fetchSnapshot(n)}catch(e){if(e instanceof r){t.tui.info.showToast(`/session inspect`,`无法连接 otto serve:${e.message} —— 先 \`otto serve\`(或指定 --remote/OTTO_SERVICE_URL)`);return}if(e instanceof a){t.tui.info.showToast(`/session inspect`,`拉取 snapshot 失败:${e.message}`);return}t.tui.info.showToast(`/session inspect`,`查看失败:${e instanceof Error?e.message:String(e)}`);return}t.tui.messages.pushMessage({role:`assistant`,content:[{type:`text`,text:`[session inspect]\n${pn(s)}`}]}),t.tui.info.showToast(`/session inspect`,`已查看会话 ${s.session.id}(只读快照;如需实时参与该会话请用 otto --attach ${s.session.id})`)}async function hn(e,t){t.state.session.isBusy&&t.tui.info.showToast(`/clear`,`Aborting current task before clearing…`),t.state.todoList=[],t.callbacks.resetCurrentTurn(),t.callbacks.onContextCompacted(),t.state.session.clearTurnPanels(),await t.state.session.clearContext(),t.tui.lifecycle.clearSession();let n=t.state.session.model;n&&t.tui.stores.tasks.setModelName(N(t.app,n.id,t.state.session.thinkingLevel));let r=n?.contextWindow??2e5,i=It({systemPrompt:t.state.session.systemPrompt,tools:t.state.session.tools,lastIn:0,lastOut:0,lastCacheRead:0,lastCacheWrite:0,contextWindow:r,modelName:n?.name??``,totIn:0,totOut:0});t.tui.stores.budget.setBudget(i),t.tui.stores.context.setBudget(i),t.tui.info.showToast(D.t(`cmd.titleCleared`),D.t(`cmd.clearedDone`))}async function gn(e,t){let n=e.join(` `).replace(/\s+/g,` `).trim();if(!n){t.tui.info.showToast(`/rename`,`Usage: /rename <new title>`);return}t.state.session.renameSession(n),t.tui.lifecycle.setTerminalTitle(n),t.tui.stores.tasks.setSessionTitle(n),t.tui.info.showToast(`✎`,n)}async function _n(e,t){let n,r,i=!1;for(let t=0;t<e.length;t++)e[t]===`--workspace`&&t+1<e.length?r=e[++t]:e[t]===`--archived`?i=!0:e[t].startsWith(`--`)||(n=e[t]);let a=r?{workspaceId:r}:void 0,o=await t.app.listAllSessions(a),s=o.filter(e=>e.status===`archived`).length,c=n?o:o.filter(e=>i?e.status===`archived`:e.status!==`archived`).filter(e=>(e.entryCount??e.messageCount)>0),l=(e,t,n)=>e.tui.info.showToast(t,n),u=async e=>{if(e===t.state.session.id){l(t,D.t(`cmd.titleResume`),D.t(`cmd.resumeAlreadyOn`));return}let n=t.app.getSession(e)??await t.app.sessionManager.restore(e);if(!n){l(t,D.t(`cmd.titleResume`),D.t(`cmd.resumeSessionNotFound`,{idShort:e.slice(0,8)}));return}let r=t.state.session;if(t.state.session.isBusy){let[e]=fn(R(r.id,r.session.messages(),!0),()=>r.session.messages()),n=t.state.session.subscribe(t=>{e(t),(t.type===`prompt.end`||t.type===`error`)&&n()})}else R(r.id,r.session.messages(),!1);await t.callbacks.pasteBinder?.switchTo(r.id,n.id),t.callbacks.sessionUnsub?.(),t.state.session=n,t.state.interactive=new k(n,{models:t.state.models,currentModel:n.model?.id,onModelChange:t.callbacks.applyModel}),t.state.reg=t.state.interactive.getRegistry(),t.callbacks.registerAllTuiCommands(),t.state.slashCommands=t.callbacks.refreshSlashCommands(),t.tui.plugins.setCommands(t.state.slashCommands),t.callbacks.attachSessionHandler();let i=n.model?.id;i?(t.state.currentModelId=i,t.state.modelReady=!0,t.tui.stores.tasks.setModelName(N(t.app,i,t.state.session.thinkingLevel))):(t.state.currentModelId=void 0,t.state.modelReady=!1,t.tui.stores.tasks.setModelName(`(no model)`)),t.tui.lifecycle.clearSession();let a=sn(n.id);if(a)t.tui.messages.pushMessages(a.transcript),L(n.id);else{let e=F(n);t.tui.messages.pushMessages(e)}t.callbacks.restoreSwitchedSession(),t.tui.info.setTopView({session:n.id}),l(t,D.t(`cmd.titleResume`),D.t(`cmd.resumeSwitched`,{idShort:e.slice(0,8),count:n.session.messages().length}))};if(n){let e=c.find(e=>e.id===n);if(e){await u(e.id);return}let r=c.find(e=>e.id.startsWith(n));if(r){await u(r.id);return}let i=c.find(e=>e.id.includes(n));if(i){await u(i.id);return}l(t,D.t(`cmd.titleResume`),D.t(`cmd.sessionNoMatch`,{arg:n}));return}if(c.length===0){l(t,D.t(`cmd.titleResume`),D.t(`cmd.resumeNoSaved`));return}let d=t.state.session.id,f=c.some(e=>e.pinned);function p(e){if(!e.pinned)return``;let t=e.pinGroup?.trim();return t?`\u0001${t.toLowerCase()}`:`\0`}let m=[...c].sort((e,t)=>{let n=p(e).localeCompare(p(t));if(n!==0)return n;let r=e.updatedAt?.getTime()??e.createdAt.getTime();return(t.updatedAt?.getTime()??t.createdAt.getTime())-r});function h(e){if(f)return e.pinned?e.pinGroup?.trim()||D.t(`cmd.resumeDefaultPinGroup`):D.t(`cmd.resumeRecentGroup`)}function g(e){if(!e)return``;let t=Math.max(0,Math.floor((Date.now()-e.getTime())/1e3));return t<10?D.t(`cmd.agoJustNow`):t<60?D.t(`cmd.agoSeconds`,{n:t}):t<3600?D.t(`cmd.agoMinutes`,{n:Math.floor(t/60)}):t<86400?D.t(`cmd.agoHours`,{n:Math.floor(t/3600)}):D.t(`cmd.agoDays`,{n:Math.floor(t/86400)})}let _=c[0]?.updatedAt?g(c[0].updatedAt):`—`,ee=c.reduce((e,t)=>e+(t.messageCount??0),0),v=r?` [workspace: ${r}]`:``,te=[!i&&s>0?D.t(`cmd.resumeArchivedHiddenHint`,{count:s}):``].filter(Boolean),ne=te.length>0?` ${te.join(` `)}`:``,re=new Map(cn().map(e=>[e.id,e])),y=0,b=0,ie=e=>{if(e.id===d)return``;let n=re.get(e.id);return n?n.status===`busy`?(y++,`● ${D.t(`cmd.resumeStatusBusy`)} · `):n.status===`needs-input`?(b++,`● ${D.t(`cmd.resumeStatusNeedsInput`)} · `):`· ${D.t(`cmd.resumeStatusIdle`)} · `:t.app.getSession(e.id)?.isBusy?(y++,`● ${D.t(`cmd.resumeStatusBusy`)} · `):``},ae=[y>0?`${y} ${D.t(`cmd.resumeStatusBusy`)}`:``,b>0?`${b} ${D.t(`cmd.resumeStatusNeedsInput`)}`:``].filter(Boolean).join(`, `),oe=ae?` · ${ae}`:``,se=D.t(`cmd.resumeSubtitle`,{sessions:c.length,msgs:ee,recent:_})+oe+ne,ce=i?D.t(`cmd.resumeArchivedOnlyTitle`,{n:c.length}):D.t(`cmd.resumeSelectTitle`,{n:c.length});t.tui.modal.openModal({kind:`select`,title:ce+v,subtitle:se,searchable:!0,maxVisible:13,options:m.map(e=>{let t=e.id===d?`●`:` `,n=e.pinned?D.t(`cmd.resumePinnedPrefix`):``,r=e.title||e.id.slice(0,8),i=e.status===`archived`?D.t(`cmd.resumeArchivedSuffix`):``,a=e.writeLocked?D.t(`cmd.resumeReadOnlySuffix`):``,o=`${n}${r}${i}`,s=g(e.updatedAt),c=[ie(e).trim(),s,D.t(`welcome.msgCountLabel`,{count:e.messageCount??0}),a.trim()].filter(Boolean);return{label:`${t} ${o}`,trailingLabel:c.join(` · `),value:e.id,searchText:o,group:h(e)}}),detailPlacement:`inline`,onSelect:e=>{u(e).catch(e=>l(t,D.t(`cmd.titleResume`),D.t(`cmd.resumeFailed`,{msg:e instanceof Error?e.message:String(e)})))},onCancel:()=>{}})}async function vn(e,t){t.app.reloadCapabilities().then(e=>{t.tui.plugins.setPluginRegistrations(t.app.getPluginRegistrations()),t.tui.info.showToast(D.t(`cmd.titleReload`),D.t(`cmd.reloadDone`,{mcpTools:e.mcpTools,models:e.models,agents:e.agents,teams:e.teams}))},e=>t.tui.messages.pushMessage({role:`command_echo`,command:`/reload`,status:`error`,summary:D.t(`cmd.reloadFailed`,{msg:e instanceof Error?e.message:String(e)})}))}async function yn(e,t){let n=t.app.diagnoser.propose();if(n.length===0){t.tui.info.showToast(D.t(`cmd.titleDiagnose`),D.t(`cmd.diagnoseNone`));return}let r=n.map((e,t)=>D.t(`cmd.diagnoseLine`,{n:t+1,signalKind:e.evidence.signalKind,count:e.evidence.count,directive:e.directive}));t.tui.messages.pushMessage({role:`command_echo`,command:`/diagnose`,status:`warn`,summary:D.t(`cmd.diagnoseSummary`,{count:n.length}),detail:r.join(`
|
|
5
|
-
`)})}async function bn(e,t){let n=e[0];if(n){let e=t.app.listSessions(),r=e.find(e=>e.id===n)??e.find(e=>e.id.startsWith(n))??e.find(e=>e.id.includes(n));if(!r){t.tui.info.showToast(D.t(`cmd.titleArchive`),D.t(`cmd.sessionNoMatch`,{arg:n}));return}if(r.status===`archived`){let e=t.app.getSession(r.id);if(!e){t.tui.info.showToast(D.t(`cmd.titleUnarchive`),D.t(`cmd.unarchiveNotLoaded`,{idShort:r.id.slice(0,8)}));return}e.unarchive(),t.tui.info.showToast(D.t(`cmd.titleUnarchive`),D.t(`cmd.unarchiveDone`,{idShort:r.id.slice(0,8),title:r.title??``}));return}if(!await t.app.archiveSession(r.id)){t.tui.info.showToast(D.t(`cmd.titleArchive`),D.t(`cmd.archiveCannot`,{idShort:r.id.slice(0,8)}));return}t.tui.info.showToast(D.t(`cmd.titleArchive`),D.t(`cmd.archiveDone`,{idShort:r.id.slice(0,8),title:r.title??``}));return}let r=t.state.session;if(r.status===`archived`){r.unarchive(),t.tui.info.showToast(D.t(`cmd.titleUnarchive`),D.t(`cmd.unarchiveDone`,{idShort:r.id.slice(0,8),title:r.title??``}));return}r.archive(),t.tui.info.showToast(D.t(`cmd.titleArchive`),D.t(`cmd.archiveDone`,{idShort:r.id.slice(0,8),title:r.title??``}))}async function xn(e,t){let n,r=[];for(let i=0;i<e.length;i++)if(e[i]===`--group`){let r=e[i+1];if(r===void 0||r.startsWith(`--`)){t.tui.info.showToast(D.t(`cmd.titlePin`),D.t(`cmd.pinGroupMissingValue`));return}n=r,i++}else r.push(e[i]);if(r.length===0&&n===void 0){let e=t.state.session;e.pinned?(e.unpin(),t.tui.info.showToast(D.t(`cmd.titleUnpin`),D.t(`cmd.unpinDone`,{idShort:e.id.slice(0,8),title:e.title??``}))):(e.pin(),t.tui.info.showToast(D.t(`cmd.titlePin`),D.t(`cmd.pinDone`,{idShort:e.id.slice(0,8),title:e.title??``}))),V(t);return}if(r.length===0){Sn(t.state.session.id,n,t),V(t);return}if(r.length===1){Sn(r[0],n,t),V(t);return}let i=t.app.listSessions(),a=0,o=[];for(let e of r){let r=i.find(t=>t.id===e)??i.find(t=>t.id.startsWith(e))??i.find(t=>t.id.includes(e));if(!r){o.push(e.slice(0,8));continue}let s=t.app.getSession(r.id);if(!s){o.push(r.id.slice(0,8));continue}s.pin(n),a++}t.tui.info.showToast(D.t(`cmd.titlePin`),D.t(`cmd.pinBatchSummary`,{seg:o.length>0?`${a} pinned (${o.length} not found: ${o.join(`, `)})`:`${a} pinned`})),V(t)}function V(e){let t=e.callbacks.refreshSlashCommands();e.tui.plugins.setCommands(t)}function Sn(e,t,n){if(e===n.state.session.id){Cn(n.state.session,t,n);return}let r=n.app.listSessions(),i=r.find(t=>t.id===e)??r.find(t=>t.id.startsWith(e))??r.find(t=>t.id.includes(e));if(!i){n.tui.info.showToast(D.t(`cmd.titlePin`),D.t(`cmd.sessionNoMatch`,{arg:e}));return}let a=n.app.getSession(i.id);if(!a){n.tui.info.showToast(D.t(`cmd.titlePin`),D.t(`cmd.pinNotLoaded`,{idShort:i.id.slice(0,8)}));return}Cn(a,t,n)}function Cn(e,t,n){let r=e.id.slice(0,8),i=e.title??``;if(e.pinned){if(t===void 0||(e.pinGroup??``).toLowerCase()===t.toLowerCase()){e.unpin(),n.tui.info.showToast(D.t(`cmd.titleUnpin`),D.t(`cmd.unpinDone`,{idShort:r,title:i}));return}e.pin(t),n.tui.info.showToast(D.t(`cmd.titlePin`),D.t(`cmd.pinGroupDone`,{idShort:r,title:i,group:t}));return}e.pin(t),t===void 0?n.tui.info.showToast(D.t(`cmd.titlePin`),D.t(`cmd.pinDone`,{idShort:r,title:i})):n.tui.info.showToast(D.t(`cmd.titlePin`),D.t(`cmd.pinGroupDone`,{idShort:r,title:i,group:t}))}async function wn(e,t){let n=e[0];if(n===`fork`){await Tn(e.slice(1),t);return}if(n===`kill`){await An(e.slice(1),t);return}if(n===`inspect`){await mn(e.slice(1),t);return}if(n!==`unlock`){t.tui.info.showToast(D.t(`cmd.titleSessionCmd`),`Usage: /session fork | /session unlock <id> [--force] | /session kill <id> | /session inspect <id>`);return}let r=e.slice(1),i=r.includes(`--force`),a=r.find(e=>e!==`--force`);if(!a){t.tui.info.showToast(D.t(`cmd.titleSessionUnlock`),D.t(`cmd.unlockUsage`));return}let o=t.app.sessionManager.inspectSessionWriteLease(a),s=a.slice(0,8);if(!o.locked){t.tui.info.showToast(D.t(`cmd.titleSessionUnlock`),D.t(`cmd.unlockNotLocked`,{idShort:s}));return}let c=o.alive===!0?D.t(`cmd.unlockAliveConfirmed`):o.alive===!1?D.t(`cmd.unlockDeadConfirmed`):D.t(`cmd.unlockUnknownAlive`),l=Math.round((o.mtimeAgoMs??0)/1e3);if(!i){t.tui.messages.pushMessage({role:`command_echo`,command:`/session unlock`,summary:D.t(`cmd.unlockInspect`,{idShort:s,pid:o.pid,hostname:o.hostname,aliveLabel:c,mtimeAgoSec:l})});return}if(o.alive===!0){o.mtimeAgoMs!==void 0&&o.mtimeAgoMs>6e4&&t.tui.messages.pushMessage({role:`command_echo`,command:`/session unlock --force`,status:`warn`,summary:D.t(`cmd.unlockHungWarning`,{idShort:s})}),t.tui.modal.openModal({kind:`confirm`,title:D.t(`cmd.unlockConfirmTitle`),message:D.t(`cmd.unlockConfirmMessage`,{idShort:s,pid:o.pid,hostname:o.hostname}),confirmLabel:D.t(`cmd.unlockConfirmLabel`),cancelLabel:D.t(`cmd.unlockCancelLabel`),dangerous:!0,onConfirm:()=>{t.app.sessionManager.forceReleaseSessionWriteLease(a),t.tui.info.showToast(D.t(`cmd.titleSessionUnlock`),D.t(`cmd.unlockForcedDone`,{idShort:s}))},onCancel:()=>{t.tui.info.showToast(D.t(`cmd.titleSessionUnlock`),D.t(`cmd.unlockCancelled`))}});return}(o.mtimeAgoMs??0)<5e3&&t.tui.messages.pushMessage({role:`command_echo`,command:`/session unlock --force`,status:`warn`,summary:D.t(`cmd.unlockRecentReleaseHint`,{idShort:s})}),t.app.sessionManager.forceReleaseSessionWriteLease(a),t.tui.info.showToast(D.t(`cmd.titleSessionUnlock`),D.t(`cmd.unlockForcedDone`,{idShort:s}))}async function Tn(e,t){let n=t.state.session.id,r=await t.app.sessionManager.forkSession(n);if(!r){t.tui.info.showToast(D.t(`cmd.titleSessionFork`),D.t(`cmd.forkFailed`));return}let i=t.state.session;if(t.state.session.isBusy){let[e]=fn(R(i.id,i.session.messages(),!0),()=>i.session.messages()),n=t.state.session.subscribe(t=>{e(t),(t.type===`prompt.end`||t.type===`error`)&&n()})}else R(i.id,i.session.messages(),!1);t.callbacks.sessionUnsub?.(),t.state.session=r,t.state.interactive=new k(r,{models:t.state.models,currentModel:r.model?.id,onModelChange:t.callbacks.applyModel}),t.state.reg=t.state.interactive.getRegistry(),t.callbacks.registerAllTuiCommands(),t.state.slashCommands=t.callbacks.refreshSlashCommands(),t.tui.plugins.setCommands(t.state.slashCommands),t.callbacks.attachSessionHandler();let a=r.model?.id;a?(t.state.currentModelId=a,t.state.modelReady=!0,t.tui.stores.tasks.setModelName(N(t.app,a,t.state.session.thinkingLevel))):(t.state.currentModelId=void 0,t.state.modelReady=!1,t.tui.stores.tasks.setModelName(`(no model)`)),t.tui.lifecycle.clearSession();let o=F(r);t.tui.messages.pushMessages(o),t.callbacks.restoreSwitchedSession(),t.tui.info.setTopView({session:r.id}),t.tui.info.showToast(D.t(`cmd.titleSessionFork`),D.t(`cmd.forkSuccess`,{idShort:r.id.slice(0,8)}))}async function En(e,t){let n=e[0],r=e[1];if(!n||!r){t.tui.info.showToast(`a2ui`,`Usage: /a2ui-action <componentId> <action> [payload-json]`);return}let i;if(e[2])try{i=JSON.parse(e.slice(2).join(` `))}catch{t.tui.info.showToast(`a2ui`,`Invalid JSON payload`);return}let a=t.state.session;a.session.emit({type:`a2ui.action`,sessionId:a.id,componentId:n,action:r,payload:i});let o=`[a2ui action] component="${n}" action="${r}"${i?` payload=${JSON.stringify(i)}`:``}`;a.session.prompt(o).catch(e=>{t.tui.info.showToast(`a2ui`,`Action failed: ${e instanceof Error?e.message:String(e)}`)})}async function Dn(e,t){let n=90,r=e.indexOf(`--ttl-days`);if(r>=0&&r+1<e.length){let t=parseInt(e[r+1],10);!isNaN(t)&&t>0&&(n=t)}let i=e.find(e=>e.startsWith(`--ttl-days=`));if(i){let e=parseInt(i.split(`=`)[1],10);!isNaN(e)&&e>0&&(n=e)}let a=e.includes(`--apply`),o=t.app.sessionStorageDir;if(!o){t.tui.info.showToast(`trace-gc`,`当前后端不支持 trace 文件管理(非本地磁盘存储)`);return}let s=await t.app.listAllSessions(),c=Date.now()-n*24*60*60*1e3,l=s.filter(e=>!((e.updatedAt?e.updatedAt.getTime():0)>=c||e.status===`archived`||e.pinned)),u=C(o,`trace`),d=[];for(let e of l){let t=C(u,`${encodeURIComponent(e.id)}.jsonl`);try{let n=await et(t);d.push({session:e,path:t,size:n.size})}catch{}}if(!a){if(d.length===0){t.tui.info.showToast(`trace-gc`,`TTL ${n} 天:没有符合条件的 trace 文件`);return}let e=d.reduce((e,t)=>e+t.size,0),r=`TTL: ${n} 天 | 候选会话: ${d.length} | 预估回收: ${On(e)}`,i=[`会话 ID 大小 最后活跃 标题`,...d.map(e=>` ${e.session.id.slice(0,8)} ${On(e.size).padStart(9)} ${e.session.updatedAt?e.session.updatedAt.toISOString().slice(0,10):`未知`} ${(e.session.title??``).slice(0,60)}`),``,`TTL 基于会话最后一次持久化活动时间(agent 引擎产生新内容的时间),`,`可能与你最后一次查看/交互该会话的时间不同。`,``,`添加 --apply 参数以实际删除这些 trace 文件。`];t.tui.messages.pushMessage({role:`command_echo`,command:`/trace-gc --dry-run`,summary:r,detail:i.join(`
|
|
6
|
-
`)});return}if(d.length===0){t.tui.info.showToast(`trace-gc`,`TTL ${n} 天:没有需要清理的 trace 文件`);return}let f=0,p=0,m=[];for(let e of d)try{await tt(e.path),f++,p+=e.size}catch(t){m.push(`${e.session.id.slice(0,8)}: ${t instanceof Error?t.message:String(t)}`)}let h=`已清理 ${f}/${d.length} 个会话的 trace 文件,回收 ${On(p)}`;f>0?t.tui.info.showToast(`trace-gc`,h+(m.length>0?`\n失败:${m.join(`; `)}`:``)):m.length>0&&t.tui.messages.pushMessage({role:`command_echo`,command:`/trace-gc --apply`,status:`error`,summary:h,detail:`失败:${m.join(`; `)}`})}function On(e){return e>=1073741824?`${(e/1073741824).toFixed(1)} GB`:e>=1048576?`${(e/1048576).toFixed(1)} MB`:e>=1024?`${(e/1024).toFixed(1)} KB`:`${e} B`}async function kn(e,t){let n=e[0];if(!n||n.startsWith(`--`)){t.tui.info.showToast(`archive-export`,`用法: /session-archive-export <sessionId> [--out <path>] [--and-delete]`);return}let r=e.includes(`--and-delete`),i,a=e.indexOf(`--out`);if(a>=0&&a+1<e.length){let t=e[a+1];t.startsWith(`--`)||(i=t)}let o=(await t.app.listAllSessions()).find(e=>e.id.startsWith(n)&&e.status!==`archived`);if(!o){t.tui.info.showToast(`archive-export`,`找不到 ID 前缀为 "${n}" 的活跃会话`);return}let s=o.id,c=s.slice(0,8),l=o.title??c,u=t.app.sessionStorageDir;if(!i&&!u){t.tui.info.showToast(`archive-export`,`当前后端不支持自动选择导出路径,请提供 --out <path>`);return}let d=i??C(u,`archives`,`${s}.json`),f;try{f=await t.app.exportSessionArchive(s,d)}catch(e){let n=e instanceof Error?e.message:String(e);t.tui.messages.pushMessage({role:`command_echo`,command:`/session-archive-export`,status:`error`,summary:`导出失败:${n}`});return}if(!r){t.tui.info.showToast(`archive-export`,`已导出 ${l}(${f.entryCount} entries, sha256=${f.checksum.slice(0,12)})→ ${f.path}`);return}let p=t.app.sessionManager.inspectSessionWriteLease(s);if(p.locked&&p.alive!==!1){let e=p.alive===!0?`(确认存活)`:`(无法判断存活)`,n=p.pid==null?``:`(pid=${p.pid},hostname=${p.hostname??`?`}${e})`;t.tui.messages.pushMessage({role:`command_echo`,command:`/session-archive-export --and-delete`,status:`error`,summary:`会话 ${c} 仍被其他 otto 进程占用${n},无法归档删除`,detail:`仅导出请去掉 --and-delete。`});return}t.tui.modal.openModal({kind:`confirm`,title:`确认删除会话`,message:`确认删除会话「${l}」(${c})?此操作不可逆,且会同步清理 trace/checkpoint。`,confirmLabel:`确认删除`,cancelLabel:`取消`,dangerous:!0,onConfirm:async()=>{try{await t.app.removeSession(s),t.tui.info.showToast(`archive-export`,`已归档并删除 ${l}(${f.entryCount} entries)→ ${f.path}`)}catch(e){let n=e instanceof Error?e.message:String(e);t.tui.messages.pushMessage({role:`command_echo`,command:`/session-archive-export --and-delete`,status:`error`,summary:`删除失败:${n}`,detail:`导出文件仍保存于 ${f.path}`})}},onCancel:()=>{t.tui.info.showToast(`archive-export`,`已取消删除。导出文件仍保存于 ${f.path}`)}})}async function An(e,t){let n=e[0];if(!n){t.tui.info.showToast(`session kill`,`Usage: /session kill <session-id>`);return}let r=t.app.listSessions(),i=r.find(e=>e.id===n)??r.find(e=>e.id.startsWith(n))??r.find(e=>e.id.includes(n));if(!i){t.tui.info.showToast(`session kill`,D.t(`cmd.sessionNoMatch`,{arg:n}));return}if(i.id===t.state.session.id){t.tui.info.showToast(`session kill`,`Cannot kill the current foreground session. /resume to another session first.`);return}let a=t.app.getSession(i.id);if(!a){L(i.id),t.tui.info.showToast(`session kill`,`Removed stale session ${i.id.slice(0,8)} from background.`);return}a.abort(),L(i.id),await t.app.removeSession(i.id),t.tui.info.showToast(`session kill`,`Killed session ${i.id.slice(0,8)}${i.title?` (${i.title})`:``}.`)}async function jn(e,t){let n=t.state.session.id,r=t.state.session.session.metadata().title?.trim()||n.slice(0,8),i=await t.app.sessionManager.forkSession(n);if(!i){t.tui.info.showToast(D.t(`cmd.titleSessionFork`),D.t(`cmd.forkFailed`));return}let a=`${r} (fork)`,o=(await t.tui.modal.openModal({kind:`input`,title:D.t(`cmd.forkNameTitle`),placeholder:a,defaultValue:a})??``).trim()||a;i.renameSession(o);let s=t.state.session;if(t.state.session.isBusy){let[e]=fn(R(s.id,s.session.messages(),!0),()=>s.session.messages()),n=t.state.session.subscribe(t=>{e(t),(t.type===`prompt.end`||t.type===`error`)&&n()})}else R(s.id,s.session.messages(),!1);await t.callbacks.pasteBinder?.fork(s.id,i.id),t.callbacks.sessionUnsub?.(),t.state.session=i,t.state.interactive=new k(i,{models:t.state.models,currentModel:i.model?.id,onModelChange:t.callbacks.applyModel}),t.state.reg=t.state.interactive.getRegistry(),t.callbacks.registerAllTuiCommands(),t.state.slashCommands=t.callbacks.refreshSlashCommands(),t.tui.plugins.setCommands(t.state.slashCommands),t.callbacks.attachSessionHandler();let c=i.model?.id;c?(t.state.currentModelId=c,t.state.modelReady=!0,t.tui.stores.tasks.setModelName(N(t.app,c,t.state.session.thinkingLevel))):(t.state.currentModelId=void 0,t.state.modelReady=!1,t.tui.stores.tasks.setModelName(`(no model)`)),t.tui.lifecycle.clearSession();let l=F(i);t.tui.messages.pushMessages(l),t.callbacks.restoreSwitchedSession(),t.tui.info.setTopView({session:i.id}),t.tui.stores.tasks.setSessionTitle(o),t.tui.lifecycle.setTerminalTitle(o),t.tui.info.showToast(D.t(`cmd.titleSessionFork`),D.t(`cmd.forkSuccessNamed`,{title:o}))}async function Mn(e,t){let n=t.app.settings.get(`keybinding_overrides`),r=()=>{t.tui.info.showToast(D.t(`cmd.keybindTitle`),D.t(`cmd.keybindSavedRestartHint`))};t.tui.modal.openModal({kind:`keybindingPanel`,overrides:n,onSave:(e,n,i)=>{let a=t.app.settings.get(`keybinding_overrides`)??{};t.app.settings.persist({keybinding_overrides:{...a,[e]:n}},i),r()},onSaveReplace:(e,n)=>{let i={...t.app.settings.get(`keybinding_overrides`)??{}};for(let t of e)i[t.commandId]=t.predicate;t.app.settings.persist({keybinding_overrides:i},n),r()},onReset:e=>{let n=t.app.settings.get(`keybinding_overrides`);if(!n||!(e in n))return;let{[e]:i,...a}=n;t.app.settings.persist({keybinding_overrides:a}),r()},onResetAll:()=>{t.app.settings.persist({keybinding_overrides:{}}),r()}})}function H(e,t,n){let r={ts:Date.now(),sessionId:e.sessionId,...e.turn===void 0?{}:{turn:e.turn},kind:`lifecycle`,node:t,payload:n};e.traceStore.append(e.sessionId,r).catch(()=>{})}function Nn(e,t,n){let{tui:r,session:i,traceStore:a}=e,o=t===`model`?`model_switch`:`effort_switch`,s=t===`model`?`model.switch`:`effort.change`;r.messages.pushMessage({role:o,label:n.label}),i.session.append({role:`system`,subtype:o,content:n.label,level:`info`,timestamp:Date.now()});let c=i.session.getTurnCount(),l={ts:Date.now(),sessionId:i.id,...c===void 0?{}:{turn:c},kind:`lifecycle`,node:s,payload:{from:n.from??null,to:n.to,label:n.label}};a.append(i.id,l).catch(()=>{})}function Pn(e,t){Nn(e,`model`,t)}function Fn(e,t){Nn(e,`effort`,t)}function In(e,t){H(e,`retry.queued`,{attempt:t.attempt,max:t.max,...t.mode===void 0?{}:{mode:t.mode},...t.reason===void 0?{}:{reason:t.reason}})}function Ln(e,t){H(e,`retry.cancelled`,{attempt:t.attempt})}function Rn(e){H(e,`retry.reset`,{})}function zn(e,t,n){H(e,`pause.${t}`,{...n===void 0?{}:{source:n}})}const U=(e,t,n)=>{let r=M(n),i=r?`${e}·${r}`:e;return t?`${i} (${t>=1e6?`${(t/1e6).toFixed(1)}M`:`${Math.round(t/1e3)}K`})`:i},W=[`low`,`medium`,`high`,`xhigh`,`max`];function Bn(e,t){return[t,...e.filter(e=>e!==t)].slice(0,5)}function Vn(e){let t=/\[@model:((?:\\\]|[^\]])+)\]/g,n=[];return{text:e.replace(t,(e,t)=>(n.push(t.replace(/\\\]/g,`]`)),``)).replace(/\n{3,}/g,`
|
|
7
|
-
|
|
8
|
-
`).trim(),modelIds:n}}async function Hn(e,t){let n=Vn(e),r=n.modelIds.at(-1);if(r&&r!==t.currentModelId){let e=await t.applyModel(r);e.includes(`not in registry`)&&t.onSwitchFailed?.(e)}return n.text}function Un(e){let t=/\[@effort:((?:\\\]|[^\]])+)\]/g,n=[];return{text:e.replace(t,(e,t)=>(n.push(t.replace(/\\\]/g,`]`)),``)).replace(/\n{3,}/g,`
|
|
9
|
-
|
|
10
|
-
`).trim(),levels:n}}function Wn(e,t){let n=Un(e),r=n.levels.at(-1);return r&&(W.includes(r)?r!==t.currentEffort&&t.applyEffort(r):t.onInvalidLevel?.(r)),n.text}function Gn(e){let{modelReady:t,currentModelId:n,contextWindow:r,tuiRef:i,pushBudgetNow:a,app:o,getSession:s,options:c,fmtDisplayModel:l,onEngineModelAction:u}=e;return async(e,d)=>{let f=s(),p=f.model?.id;c.value&&(c.value.model=e),n.value=e;let m=o.modelRegistry.find(e);if(m&&m.id===`__otto_setup__`)return`Model "${e}" is a setup placeholder — pick a real model with /model first.`;if(d?.persist!==!1)try{let t={model:e},n=o.settings.get(`model_slots`);n&&(t.model_slots={...n,default:e}),m&&(t.recent_models=Bn(o.settings.get(`recent_models`)??[],e)),await o.settings.persist(t)}catch{}if(m){if(f.model=m,t.value=!0,e!==p&&(o.syncSessionToolsFor(f.id),Pn({tui:i.value??{messages:{pushMessage:()=>{}}},session:f,traceStore:o.storage.traceStore},{from:p,to:m.id,label:U(m.id,m.contextWindow,f.thinkingLevel)})),m.api.includes(`:`)&&o.firePluginActivationEvent?.(`onProvider:${m.api}`),u?.({kind:`setModel`,modelId:e}),i.value?.stores.tasks.setModelName(U(m.id,m.contextWindow,f.thinkingLevel)),i.value?.engine.pushEngineEvent({kind:`maxOutputTokens`,n:m.maxOutputTokens}),o.authStore?.getAuthSource?o.authStore.getAuthSource(m.provider).then(e=>{e!==`oauth`&&i.value?.stores.notification.setProviderUsage(null)}).catch(()=>{i.value?.stores.notification.setProviderUsage(null)}):i.value?.stores.notification.setProviderUsage(null),e!==p&&o.storage.memory?.reconfigure){o.storage.memory.reconfigure({contextWindow:m.contextWindow,maxOutput:m.maxOutputTokens});try{let e=f.session.buildContext(),t=await o.storage.memory.ensureFitsWindow?.(e.messages,f.id);if(t?.compacted&&t.summary&&t.replacement){f.session.recordCompaction(t.summary,t.replacement),u?.({kind:`recordCompaction`,summary:t.summary,replacement:t.replacement});let e=m.contextWindow>=1e6?`${(m.contextWindow/1e6).toFixed(1)}M`:`${Math.round(m.contextWindow/1e3)}k`;i.value?.info.showToast(D.t(`cmd.titleModel`),D.t(`cmd.modelCompactedFor`,{window:e}))}}catch(e){console.error(`\x1b[3m\x1b[2m[model-switch] compaction skipped: ${e instanceof Error?e.message:String(e)}\x1b[0m`)}}return r.value=m.contextWindow,a.value?.(),e!==p&&!d?.silent&&i.value?.info.showToast(D.t(`cmd.titleModel`),D.t(`model.switchedTo`,{label:l(e)})),`Model → ${e} (active next turn).`}return f.model&&(i.value?.stores.tasks.setModelName(U(f.model.id,f.model.contextWindow,f.thinkingLevel)),i.value?.engine.pushEngineEvent({kind:`maxOutputTokens`,n:f.model.maxOutputTokens})),`Model "${e}" not in registry — switch NOT applied (still ${f.model?.id??`unset`}). Check the id or /auth the provider.`}}function Kn(e){let{getSession:t,app:n,tuiRef:r,options:i,fmtDisplayModel:a,applyModel:o}=e,s=!1,c=O(n.authStore,`delegation`),l=O(n.authStore,`main-session`);return async()=>{if(s)return;s=!0;let u=t().model;if(!u)return;let d=async e=>{try{return(await c(e)).usable}catch{return!1}},f=async e=>{try{return(await l(e)).usable}catch{return!1}},p=i.value?.model??n.settings.get(`model`);if(typeof p!=`string`||!p)return;let m=n.modelRegistry.find(p);if(m&&await f(m)){m.id!==u.id&&await o(m.id);return}let h=m?D.t(`model.reasonUnauthenticated`,{provider:m.provider}):D.t(`model.reasonNotFound`),g=u;if(!await f(u)){let e=n.modelRegistry.getAll().filter(e=>e.id!==`__otto_setup__`),t;for(let n of e)if(await d(n)){t=n;break}t??=e[0],t&&(g=t)}let _=g.id;r.value?.modal.openModal({kind:`confirm`,title:D.t(`model.title`),message:[`${a(p)} → ${a(g.id)}`,``,h,``,D.t(`model.hint`)],confirmLabel:D.t(`model.permanent`),extraLabel:D.t(`model.login`),cancelLabel:D.t(`model.dismiss`),onConfirm:()=>{o(_,{persist:!0})},onExtra:()=>{e.onLogin&&m?.provider?e.onLogin(m.provider):r.value?.info.showToast(D.t(`model.login`),D.t(`model.fallbackLoginToast`,{provider:m?.provider??``}))},onCancel:()=>{e.onExitSession?.()}})}}function qn(e){let t=e=>e.replace(/]/g,`\\]`),n=()=>{e.pluginInput.unregisterBySource(`extensions`);let n=e.extensions.list().filter(e=>e.category===`agent`||e.category===`skill`||e.category===`mcp`).map(e=>({label:`@${e.name}`,value:`[@${e.category}:${t(e.name)}]`,description:e.description||e.category,kind:e.category,source:`extensions`}));n.length&&e.pluginInput.register(n)};return n(),{refreshSigilExtensions:n}}function Jn(e){let t=e.trim().split(`
|
|
11
|
-
`)[0].replace(/\s+/g,` `).trim();return t.length>40?`${t.slice(0,39)}…`:t}function Yn(e){e.pluginInput.unregisterBySource(`shortcut`);let t=(e.settings.get(`shortcuts`)??[]).map(e=>({label:`@${e.name}`,value:`[@shortcut:${e.name.replace(/]/g,`\\]`)}]`,description:Jn(e.text),kind:`shortcut`,source:`shortcut`}));t.length&&e.pluginInput.register(t)}function Xn(e,t){let n=e.modelRegistry.getAll();if(n.length===0)return;let r=e=>e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${Math.round(e/1e3)}k`:``,i=n.filter(e=>t.has(e.id)).sort((e,t)=>e.name.localeCompare(t.name)).map(e=>{let t=r(e.contextWindow),n=[];e.strengths&&e.strengths.length>0&&n.push(...e.strengths),t&&n.push(t);let i=n.length>0?`${e.provider} · ${n.join(` · `)}`:e.provider;return{label:e.name,value:`[@model:${e.id.replace(/]/g,`\\]`)}]`,description:i,kind:`model`,source:`builtin`}});e.pluginInput.unregisterBySource(`builtin-model`),e.pluginInput.register(i)}const Zn={low:`cmd.effortDescLow`,medium:`cmd.effortDescMedium`,high:`cmd.effortDescHigh`,xhigh:`cmd.effortDescXhigh`,max:`cmd.effortDescMax`};function Qn(e){let t=W.map(e=>({label:M(e),value:`[@effort:${e}]`,description:D.t(Zn[e]??`cmd.effortDescLow`),kind:`effort`,source:`builtin-effort`}));e.pluginInput.unregisterBySource(`builtin-effort`),e.pluginInput.register(t)}async function $n(e,t){t.tui.modal.openModal({kind:`shortcutsPanel`,shortcuts:t.app.getShortcuts(),onSave:(e,n,r)=>{t.app.saveShortcut(e,n,r).then(()=>Yn(t.app)).catch(e=>{t.tui.info.showToast(`Shortcuts`,`Save failed: ${e instanceof Error?e.message:String(e)}`)})},onDelete:e=>{t.app.deleteShortcut(e).then(()=>Yn(t.app)).catch(e=>{t.tui.info.showToast(`Shortcuts`,`Delete failed: ${e instanceof Error?e.message:String(e)}`)})}})}const er={low:{label:`Low`,desc:`节约 Token,快速响应`,intensity:.2},medium:{label:`Medium`,desc:`平衡速度与推理质量`,intensity:.4},high:{label:`High`,desc:`深度推理,延迟较高`,intensity:.6},xhigh:{label:`XHigh`,desc:`更长推理链,更全面思考`,intensity:.8},max:{label:`Max`,desc:`全力推理,最慢但最深入`,intensity:1}};function tr(e){let t=Math.round(e*8);return`━`.repeat(Math.max(0,t))+`╌`.repeat(Math.max(0,8-t))}async function nr(e,t){let n=e[0]?.toLowerCase();if(n&&W.includes(n)){rr(t,n);return}if(n){t.tui.info.showToast(D.t(`cmd.titleEffort`),D.t(`cmd.effortUnknown`,{arg:n,levels:W.join(` / `)}));return}let r=t.state.session.thinkingLevel;t.tui.modal.openModal({kind:`select`,title:D.t(`cmd.selectEffortTitle`),options:W.map(e=>{let t=er[e],n=e===r,i=tr(t.intensity);return{label:t.label,value:e,detail:`${i} ${t.desc}${n?` · 当前`:``}`}}),onSelect:e=>rr(t,e),onCancel:()=>{}})}function rr(e,t){let n=e.state.session,r=n.thinkingLevel;n.thinkingLevel=t,e.app.settings.persist({thinking_level:t}),r!==t&&Fn({tui:e.tui,session:n,traceStore:e.app.storage.traceStore},{from:r,to:t,label:M(t)}),n.model?.id&&e.tui.stores.tasks.setModelName(N(e.app,n.model.id,t)),e.tui.info.showToast(D.t(`cmd.titleEffort`),t)}async function G(e,t){let n=e[0]?.toLowerCase();if(n===`general`||n===`gen`){q(t);return}if(n===`lang`||n===`language`){mr(e.slice(1),t);return}if(n===`theme`){hr(e.slice(1),t);return}if(n===`keybindings`||n===`kb`){Mn(e.slice(1),t);return}if(n===`shortcuts`){$n(e.slice(1),t);return}if(n===`effort`){nr(e.slice(1),t);return}if(n===`proxy`){m(e.slice(1),t);return}if(n){t.tui.info.showToast(D.t(`cmd.titlePreference`),D.t(`cmd.preferenceUsage`));return}K(t)}function K(e,t){let n=e.app.settings,r=n.get(`language`),i=r===`zh`?`中文 (zh)`:r===`en`?`English (en)`:`Auto`,a=e.state.themeRegistry??We(),o=n.get(`theme_preset`)??{},s=a.get(o.dark??`dark`)?.label??o.dark??`dark`;e.tui.modal.openModal({kind:`select`,title:D.t(`cmd.titlePreference`),detailPlacement:`inline`,options:[{label:D.t(`cmd.prefOptionGeneral`),value:`general`,detail:D.t(`cmd.prefDescGeneral`)},{label:D.t(`cmd.prefOptionLang`),trailingLabel:i,value:`lang`,detail:D.t(`cmd.prefDescLang`)},{label:D.t(`cmd.prefOptionTheme`),trailingLabel:s,value:`theme`,detail:D.t(`cmd.prefDescTheme`)},{label:`Keybindings`,value:`keybindings`,detail:`Browse, search, remap, reset shortcuts`},{label:`Shortcuts`,value:`shortcuts`,detail:`Manage custom @ text shortcuts`},{label:`Effort`,value:`effort`,detail:`Switch reasoning effort (low|medium|high|xhigh|max)`},{label:`Proxy`,value:`proxy`,detail:`Configure HTTP proxy`}],defaultSelectedValue:t,onSelect:t=>{t===`general`?q(e):t===`lang`?mr([],e):t===`theme`?hr([],e):t===`keybindings`?Mn([],e):t===`shortcuts`?$n([],e):t===`effort`?nr([],e):t===`proxy`&&m([],e)},onCancel:()=>{}})}let ir;function q(e,t,n,r){let i=e.app.settings,a=i.get(`notification`),o=i.get(`nickname`)??``,s=a?.on_completion!==!1,c=a?.sound===!0,l=i.get(`auto_update_check`)!==!1,u=i.get(`skill_loop`)?.enabled===!0,d=e.app.activePlugins().some(e=>e.id===fe),f={on:E.toggleOn,off:E.toggleOff},p={on:D.t(`cmd.toggleOn`),off:D.t(`cmd.toggleOff`)},m=e=>Vt(e,f,p);e.tui.modal.openModal({kind:`select`,title:D.t(`cmd.selectGeneralTitle`),detailPlacement:`inline`,subtitle:t,replace:r,options:[{label:D.t(`cmd.genNickname`),trailingLabel:o||`—`,value:`nickname`,detail:D.t(`cmd.genNicknameDesc`)},{label:D.t(`cmd.genCompletionNotice`),trailingLabel:m(s),value:`completion`,detail:D.t(`cmd.genCompletionNoticeDesc`)},{label:D.t(`cmd.genSound`),trailingLabel:m(c),value:`sound`,detail:D.t(`cmd.genSoundDesc`)},{label:D.t(`cmd.genUpdateCheck`),trailingLabel:m(l),value:`updateCheck`,detail:D.t(`cmd.genUpdateCheckDesc`)},{label:D.t(`cmd.genSkillLoop`),trailingLabel:d?m(u):D.t(`cmd.genSkillLoopMissing`),value:`skillLoopPanel`,detail:D.t(`cmd.genSkillLoopEntryDesc`)}],defaultSelectedValue:n??ir,onHighlight:e=>{ir=e},onSelect:t=>{if(t===`nickname`){ar(e);return}if(t===`skillLoopPanel`){J(e);return}pr(e,t)},onCancel:()=>{K(e,`general`)}})}async function ar(e){let t=await e.tui.modal.openModal({kind:`input`,title:D.t(`cmd.genNicknameInputTitle`),placeholder:D.t(`cmd.genNicknamePlaceholder`)});if(t==null){q(e);return}let n=t.trim();await e.app.settings.persist({nickname:n}),q(e,D.t(`cmd.genSaved`))}const or=[4,6,12,20];function sr(e){let t=typeof e==`number`?e:6,n=or.includes(t)?`genSkillLoopThr${t}`:``;return n?D.t(`cmd.${n}`):String(t)}function cr(e){let t=e.app.settings,n=t.get(`skill_loop`)?.min_messages??6;e.tui.modal.openModal({kind:`select`,title:D.t(`cmd.genSkillLoopThresholdTitle`),detailPlacement:`inline`,options:or.map(e=>({label:D.t(`cmd.genSkillLoopThr${e}`),value:String(e),detail:D.t(`cmd.genSkillLoopThr${e}Desc`),trailingLabel:e===n?E.toggleOn:void 0})),onSelect:n=>{(async()=>{let r={...t.get(`skill_loop`)??{}};r.min_messages=Number(n),await t.persist({skill_loop:r}),J(e,D.t(`cmd.genSaved`),`threshold`)})()},onCancel:()=>{J(e,void 0,`threshold`)}})}const lr=[3,14,30,365];function ur(e){let t=typeof e==`number`?e:14;return lr.includes(t)?D.t(`cmd.genSkillLoopCd${t}`):D.t(`cmd.genSkillLoopCdDays`,{n:t})}function dr(e){let t=e.app.settings,n=t.get(`skill_loop`)?.dismiss_cooldown_days??14;e.tui.modal.openModal({kind:`select`,title:D.t(`cmd.genSkillLoopCooldownTitle`),detailPlacement:`inline`,options:lr.map(e=>({label:D.t(`cmd.genSkillLoopCd${e}`),value:String(e),detail:D.t(`cmd.genSkillLoopCd${e}Desc`),trailingLabel:e===n?E.toggleOn:void 0})),onSelect:n=>{(async()=>{let r={...t.get(`skill_loop`)??{}};r.dismiss_cooldown_days=Number(n),await t.persist({skill_loop:r}),J(e,D.t(`cmd.genSaved`),`cooldown`)})()},onCancel:()=>{J(e,void 0,`cooldown`)}})}function J(e,t,n){let r=e.app.settings,i=r.get(`skill_loop`)?.enabled===!0,a=e.app.activePlugins().some(e=>e.id===fe),o={on:E.toggleOn,off:E.toggleOff},s={on:D.t(`cmd.toggleOn`),off:D.t(`cmd.toggleOff`)};if(!a){e.tui.modal.openModal({kind:`select`,title:D.t(`cmd.skillLoopPanelTitle`),detailPlacement:`inline`,subtitle:D.t(`cmd.genSkillLoopMissingHint`),options:[{label:D.t(`cmd.genSkillLoopMissing`),value:`noop`,detail:``}],onSelect:()=>{q(e,void 0,`skillLoopPanel`)},onCancel:()=>{q(e,void 0,`skillLoopPanel`)}});return}e.tui.modal.openModal({kind:`select`,title:D.t(`cmd.skillLoopPanelTitle`),detailPlacement:`inline`,subtitle:t,defaultSelectedValue:n,options:[{label:D.t(`cmd.skillLoopOnOff`),trailingLabel:Vt(i,o,s),value:`toggle`,detail:D.t(`cmd.genSkillLoopEntryDesc`)},...i?[{label:D.t(`cmd.genSkillLoopThreshold`),trailingLabel:sr(r.get(`skill_loop`)?.min_messages),value:`threshold`,detail:D.t(`cmd.genSkillLoopThresholdDesc`)},{label:D.t(`cmd.genSkillLoopCooldown`),trailingLabel:ur(r.get(`skill_loop`)?.dismiss_cooldown_days),value:`cooldown`,detail:D.t(`cmd.genSkillLoopCooldownDesc`)},{label:D.t(`cmd.genSkillLoopStatus`),value:`status`,detail:D.t(`cmd.genSkillLoopStatusDesc`)}]:[]],onSelect:t=>{if(t===`threshold`){cr(e);return}if(t===`cooldown`){dr(e);return}if(t===`status`){fr(e);return}(async()=>{let t={...r.get(`skill_loop`)??{}},n=t.enabled!==!0;t.enabled=n,await r.persist({skill_loop:t}),J(e,n?D.t(`cmd.genSkillLoopOn`):D.t(`cmd.genSkillLoopOff`),`toggle`)})()},onCancel:()=>{q(e,void 0,`skillLoopPanel`)}})}function fr(e){let t=e.app.settings,n=e.app.activePlugins().some(e=>e.id===fe),r=Ce({workspaceKey:e.app.workspaceRef?.key??de,now:Date.now(),cooldownDays:t.get(`skill_loop`)?.dismiss_cooldown_days,pluginInstalled:n}),i=e=>D.t(`cmd.skillLoopStatusCount`,{n:e}),a=[];if(!n)a.push({label:D.t(`cmd.skillLoopStatusNotInstalled`),value:`noop`,detail:D.t(`cmd.genSkillLoopMissingHint`)});else{a.push({label:D.t(`cmd.skillLoopStatusEpisodes`),trailingLabel:i(r.episodeCount),value:`noop`,detail:D.t(`cmd.skillLoopStatusSuccess`)+` `+i(r.successCount)},{label:D.t(`cmd.skillLoopStatusPending`),trailingLabel:i(r.pendingCount),value:`noop`,detail:i(r.candidateCount)+` `+D.t(`cmd.skillLoopStatusCandidates`)+` · `+i(r.coolingCount)+` `+D.t(`cmd.skillLoopStatusCooling`)});for(let e of r.pending)a.push({label:e.taskSignature,trailingLabel:D.t(`cmd.skillLoopStatusTimes`,{n:e.size}),value:`noop`,detail:D.t(`cmd.skillLoopStatusGroupPending`)});for(let e of r.recentSignatures)a.push({label:e,value:`noop`,detail:D.t(`cmd.skillLoopStatusGroupRecent`)})}let o=r.episodeCount===0?`cmd.skillLoopStatusEmpty`:{"no-episodes":`cmd.skillLoopStatusHintNoEpisodes`,"not-enough-successes":`cmd.skillLoopStatusHintNotEnough`,"no-cluster-yet":`cmd.skillLoopStatusHintNoCluster`}[r.nextProposalHint??``]??``;e.tui.modal.openModal({kind:`select`,title:D.t(`cmd.skillLoopStatusTitle`),detailPlacement:`inline`,subtitle:o?D.t(o):void 0,options:a,onSelect:()=>{J(e,void 0,`status`)},onCancel:()=>{J(e,void 0,`status`)}})}async function pr(e,t){let n=e.app.settings;if(t===`updateCheck`){let r=n.get(`auto_update_check`)!==!1;await n.persist({auto_update_check:!r}),q(e,D.t(`cmd.genSaved`),t,!0);return}let r={...n.get(`notification`)??{}};t===`completion`?r.on_completion=r.on_completion===!1:t===`sound`&&(r.sound=r.sound!==!0),await n.persist({notification:r}),q(e,D.t(`cmd.genSaved`),t,!0)}function mr(e,t){let n=e[0]?.toLowerCase();if(n===`en`||n===`zh`||n===`auto`){vr(t,n);return}if(n){t.tui.info.showToast(D.t(`cmd.titlePreference`),D.t(`pref.languageUsage`));return}t.tui.modal.openModal({kind:`select`,title:D.t(`cmd.selectLanguageTitle`),detailPlacement:`inline`,options:[{label:`中文 (zh)`,value:`zh`,detail:D.t(`cmd.langDescZh`)},{label:`English (en)`,value:`en`,detail:D.t(`cmd.langDescEn`)},{label:`Auto`,value:`auto`,detail:D.t(`cmd.langDescAuto`)}],onSelect:e=>vr(t,e),onCancel:()=>{K(t,`lang`)}})}function hr(e,t){gr(t)}function gr(e){let t=e.state.themeRegistry??We(),n=e.app.settings.get(`theme_preset`)??{},r=t.get(n.dark??`dark`)??t.get(`dark`),i=t.get(n.light??`light`)??t.get(`light`);e.tui.modal.openModal({kind:`select`,title:D.t(`cmd.prefOptionTheme`),detailPlacement:`inline`,options:[{label:D.t(`cmd.themeDarkPresetOption`),value:`preset-dark`,detail:D.t(`cmd.themeDarkPresetDesc`),trailingLabel:r?.label},{label:D.t(`cmd.themeLightPresetOption`),value:`preset-light`,detail:D.t(`cmd.themeLightPresetDesc`),trailingLabel:i?.label}],onSelect:t=>{if(t===`preset-dark`){_r(e,`dark`);return}if(t===`preset-light`){_r(e,`light`);return}},onCancel:()=>{K(e,`theme`)}})}function _r(e,t){let n=e.state.themeRegistry??We(),r=e.app.settings.get(`theme_preset`)??{},i=r[t]??t,a=[...n.list().filter(e=>e.appearance===t).map(e=>({label:e.label,value:e.id,detail:e.source.kind===`builtin`?D.t(`cmd.themePresetBuiltin`):D.t(`cmd.themePresetFromExtension`,{id:e.source.extensionId}),trailingLabel:e.id===i?`✓`:void 0})),{label:D.t(`cmd.themePresetReset`),value:`__reset__`,detail:D.t(`cmd.themePresetResetDesc`)}],o=n=>{let i={...r};n===void 0?delete i[t]:i[t]=n,e.tui.lifecycle.setPresetSelection?.(i)},s=()=>{e.tui.lifecycle.setPresetSelection?.(r)};e.tui.modal.openModal({kind:`select`,title:t===`dark`?D.t(`cmd.themePresetTitleDark`):D.t(`cmd.themePresetTitleLight`),options:a,onHighlight:e=>{o(e===`__reset__`?void 0:e)},onSelect:i=>{let a=i===`__reset__`?void 0:i,o={...r};a===void 0?delete o[t]:o[t]=a,e.tui.lifecycle.setPresetSelection?.(o),e.app.settings.persist({theme_preset:o},`global`);let s=a===void 0?t===`dark`?n.get(`dark`)?.label:n.get(`light`)?.label:n.get(a)?.label;e.tui.info.showToast(D.t(`cmd.titlePreference`),D.t(`cmd.themePresetSaved`,{label:s??a??t}))},onCancel:()=>{s(),e.tui.info.showToast(D.t(`cmd.titlePreference`),D.t(`cmd.selectCancelled`))}})}function vr(e,t){e.state.currentLang=e.tui.lifecycle.setLanguage(t),wt(t),e.tui.plugins.setCommands(e.callbacks.refreshSlashCommands()),e.app.settings.persist({language:t}),e.tui.info.showToast(D.t(`cmd.titleLanguage`),D.t(`pref.languageSwitched`,{l:e.state.currentLang,name:e.state.currentLang===`zh`?`中文`:`English`}))}async function yr(e,t){let r=t.app.workspaceDir??process.cwd(),i=e[0]?.toLowerCase()??`status`;if(i===`plugin`){let i=e[1]?.toLowerCase()===`untrust`,a=i?e[2]:e[1];if(!a){t.tui.info.showToast(D.t(`cmd.titlePlugin`),D.t(`cmd.pluginUsage`,{seg:(i?`untrust`:`trust`)==`untrust`?`untrust `:``}));return}let o=n({cwd:r,homedir:b()}).find(e=>e.id===a);if(!o){t.tui.info.showToast(D.t(`cmd.titlePlugin`),D.t(`cmd.pluginNotFound`,{id:a}));return}if(i){try{Oe(o.dir)}catch(e){t.tui.info.showToast(D.t(`cmd.titlePlugin`),`撤销信任失败,权限未收回:${e instanceof Error?e.message:String(e)}`);return}t.tui.info.showToast(D.t(`cmd.titlePlugin`),D.t(`cmd.pluginUntrusted`,{id:a}))}else u(t,o);return}if(i===`trust`){De(r),t.tui.info.showToast(D.t(`cmd.titleCompat`),D.t(`cmd.compatTrusting`)),t.app.reloadCapabilities().then(e=>{t.tui.plugins.setPluginRegistrations(t.app.getPluginRegistrations()),t.tui.info.showToast(D.t(`cmd.titleCompat`),D.t(`cmd.compatTrusted`,{count:e.mcpTools}))},e=>t.tui.info.showToast(D.t(`cmd.titleCompat`),D.t(`cmd.compatReloadFailed`,{err:e instanceof Error?e.message:String(e)})));return}if(i===`untrust`){ke(r),t.tui.info.showToast(D.t(`cmd.titleCompat`),D.t(`cmd.compatUntrusted`));return}let a=ye(r),o=ge(r,{interactive:!0}),s=[];if(a.surface.any){let e=[a.surface.mcpJson?`.mcp.json`:null,a.surface.commands?`.claude/commands`:null].filter(Boolean),t=a.trusted?D.t(`cmd.compatTrustedLabel`):D.t(`cmd.compatUntrustedLabel`);s.push(`${e.join(D.t(`cmd.listSep`))} ${t}`)}if(o.highRiskKeys.length>0){let e=o.highRiskKeys.join(D.t(`cmd.listSep`));s.push(o.gated?D.t(`cmd.compatConfigGated`,{keys:e}):D.t(`cmd.compatConfigActive`,{keys:e}))}let c=n({cwd:r,homedir:b()}).filter(e=>e.scope===`project`||e.scope===`repository`);for(let e of c){let t=be(e);if(!t.surface.any)continue;let n=t.gated?D.t(`cmd.compatUntrustedLabel`):D.t(`cmd.compatTrustedLabel`);s.push(`plugin:${e.id} ${n}${t.gated?` → /trust plugin ${e.id}`:``}`)}if(s.length===0){t.tui.info.showToast(D.t(`cmd.titleCompat`),D.t(`cmd.compatNone`));return}o.gated?t.tui.modal.openModal({kind:`info`,title:D.t(`cmd.titleCompat`),lines:s}):t.tui.info.showToast(D.t(`cmd.titleCompat`),s.join(`
|
|
12
|
-
`))}async function br(e,t){let n=e[0];if(n===`clear`){t.app.clearSessionAlwaysAllow(t.state.session.id),t.tui.info.showToast(D.t(`cmd.titlePermissions`),D.t(`cmd.permsCleared`));return}if(n===`add`||n===`remove`){let r=e.slice(1).join(` `).trim();if(!r){t.tui.info.showToast(D.t(`cmd.titlePermissions`),D.t(`cmd.permsManageHint`));return}if(n===`add`){if(!t.app.isWellFormedAllowEntry(r)){t.tui.info.showToast(D.t(`cmd.titlePermissions`),D.t(`cmd.permsInvalidEntry`,{entry:r}));return}await t.app.allowToolAlways(r),t.tui.info.showToast(D.t(`cmd.titlePermissions`),D.t(`cmd.permsAdded`,{entry:r}))}else{let e=await t.app.disallowToolAlways(r);t.tui.info.showToast(D.t(`cmd.titlePermissions`),e?D.t(`cmd.permsRemoved`,{entry:r}):D.t(`cmd.permsNotFound`,{entry:r}))}return}let r=t.app.getSessionAlwaysAllow(t.state.session.id),i=t.app.settings.get(`permission_always_allow`)??[],a=(e,t)=>`${e} ${t.length?t.join(`, `):D.t(`cmd.permsEmpty`)}`;t.tui.modal.openModal({kind:`permissions`,lines:[a(D.t(`cmd.permsSessionLabel`),r),a(D.t(`cmd.permsProjectLabel`),i),``,D.t(`cmd.permsClearHint`),D.t(`cmd.permsManageHint`)]})}async function xr(e,n){let r=t();n.tui.info.showToast(D.t(`cmd.titleUpgrade`),D.t(`cmd.upgradeChecking`,{current:r}));let i=await Tt(r);if(!i){n.tui.modal.openModal({kind:`info`,title:D.t(`cmd.titleUpgrade`),lines:[D.t(`cmd.upgradeNoNetwork`)]});return}if(!i.newer){n.tui.info.showToast(D.t(`cmd.titleUpgrade`),D.t(`cmd.upgradeLatest`,{current:r}));return}n.tui.modal.openModal({kind:`info`,title:D.t(`cmd.titleUpgrade`),lines:D.t(`cmd.upgradeAvailableLines`,{returnObjects:!0,current:r,latest:i.latest})})}async function Sr(e,t){if(!e.includes(`--force`))return!1;t.tui.info.showToast(D.t(`cmd.titleUpgrade`),D.t(`cmd.upgrading`));let{ok:n,error:r}=await Ot();return n?(t.state.restartRequested=!0,t.tui.lifecycle.stop(),!0):(t.tui.info.showToast(D.t(`cmd.titleUpgrade`),D.t(`cmd.upgradeFailed`,{err:r??D.t(`cmd.unknownError`)})),!0)}async function Cr(e,t,n){let r=D.t(`cmdDesc`,{returnObjects:!0}),i={session:D.t(`cmd.helpGroupSession`),model:D.t(`cmd.helpGroupModel`),agent:D.t(`cmd.helpGroupAgent`),debug:D.t(`cmd.helpGroupDebug`),ui:D.t(`cmd.helpGroupUi`),other:D.t(`cmd.helpGroupOther`)},a=[];for(let e of ra){let t=n.filter(t=>t.group===e&&!t.tuiHidden);if(t.length!==0)for(let n of t){let t=n.aliases?.length?[n.name,...n.aliases].map(e=>`/${e}`).join(`, `):`/${n.name}`;a.push({label:t,value:n.name,detail:r[n.name]??n.description??void 0,group:i[e],searchText:[n.name,...n.aliases??[]].join(` `)})}}let o=`${D.t(`cmd.helpPrefixes`)} !<cmd> ${D.t(`cmd.helpPrefixShell`)} · @<file> ${D.t(`cmd.helpPrefixFile`)}`;t.tui.modal.openModal({kind:`select`,title:D.t(`cmd.titleCommands`),subtitle:o,searchable:!0,options:a,onSelect:e=>{sa(e,[],t)},onCancel:()=>{}})}async function wr(e,t){t.tui.panes.togglePane(`history`)}async function Tr(e,t){t.tui.panes.togglePane(`notifications`)}async function Er(e,t){t.tui.panes.togglePane(`prompts`)}async function Dr(e,t){t.tui.panes.setServices(!0)}async function Y(e,t){let n=e[0]?.toLowerCase();if(n===`perf`)return jr(e.slice(1),t);if(n===`context`)return Or(e.slice(1),t);if(n===`cache`||n===`cache-stats`)return Mr(e.slice(1),t);if(n===`diagnose`)return yn(e.slice(1),t);if(n===`services`)return Dr(e.slice(1),t);let r=t.state.session,i=r.session,a=i.messages(),o=0;for(let e=a.length-1;e>=0;e--){let t=a[e]?.usage;if(a[e]?.role===`assistant`&&t){o=(t.cacheReadTokens??0)+(t.cacheWriteTokens??0)+(t.inputTokens??0)+(t.outputTokens??0);break}}let s=r.model?.contextWindow,c=s&&s>0?Math.round(o/s*100):void 0,l=a.reduce((e,t)=>e+(t.usage?.outputTokens??0),0),u=Te(i.metadata().title,i.messages(),r.id),d=t.callbacks.isWriteLeaseReadOnly(),f=[{kind:`row`,label:D.t(`cmd.statusRowTitle`),value:u},{kind:`row`,label:D.t(`cmd.statusRowSessionId`),value:r.id.slice(0,8)},{kind:`row`,label:D.t(`cmd.statusRowModel`),value:r.model?.id??D.t(`cmd.statusModelUnset`)},{kind:`row`,label:D.t(`cmd.statusRowMsgs`),value:String(a.length)},{kind:`row`,label:D.t(`cmd.statusRowOccupancy`),value:D.t(`cmd.statusValueOccupancy`,{occ:o.toLocaleString(),window:s?.toLocaleString()??`N/A`,seg:c===void 0?``:` (${c}%)`})},{kind:`row`,label:D.t(`cmd.statusRowCumOutput`),value:l.toLocaleString()},{kind:`row`,label:D.t(`cmd.statusRowWriteAccess`),value:d?D.t(`cmd.statusValueWriteLeaseReadOnly`):D.t(`cmd.statusValueWriteLeaseOk`),tone:d?`warning`:void 0}];t.callbacks.isRunPaused()&&f.push({kind:`blank`},{kind:`text`,value:D.t(`cmd.statusLineRunPaused`),tone:`warning`}),t.tui.modal.openModal({kind:`info`,title:D.t(`cmd.titleStatus`),lines:[],dataListEntries:f})}async function Or(e,t){let n=t.state.session,r=n.model,i=r?.contextWindow??2e5,a=n.session.messages(),o=0,s=0,c=0,l=0,u=0,d=0,f=!1;for(let e=a.length-1;e>=0;e--){if(a[e]?.role!==`assistant`||!a[e]?.usage)continue;let t=a[e].usage;u+=t.inputTokens??0,d+=t.outputTokens??0,f||=(o=t.inputTokens??0,s=t.outputTokens??0,c=t.cacheReadTokens??0,l=t.cacheWriteTokens??0,!0)}let p=It({systemPrompt:n.systemPrompt,tools:n.tools,lastIn:o,lastOut:s,lastCacheRead:c,lastCacheWrite:l,contextWindow:i,modelName:r?.name??r?.id??``,totIn:u,totOut:d,messageCount:a.length});t.tui.stores.budget.setBudget(p),t.tui.stores.context.setBudget(p),t.tui.modal.openModal({kind:`context`})}function kr(){let e=process.env.OTTO_NOTIFY_COALESCE_WARN_RATIO,t=e?Number(e):NaN;return Number.isFinite(t)&&t>=1?t:1.5}function Ar(e,t){return t===0?`healthy`:e<kr()?`warning`:`healthy`}async function jr(e,t){t.tui.engine.pushEngineEvent({kind:`perf`,memory:t.app.memoryGovernor.sample()});let n=t.tui.info.getNotifyStats(),r=n.coalescedCalls/Math.max(1,n.coalescedFires);t.tui.engine.pushEngineEvent({kind:`perf`,notify:{coalescedCalls:n.coalescedCalls,coalescedFires:n.coalescedFires,immediateCalls:n.immediateCalls,flushFires:n.flushFires,coalesceRatio:r,alert:Ar(r,n.coalescedCalls)}});let i=t.app.activePlugins();try{let e=await he(i,void 0,e=>t.app.pluginHealthTracker.recordToolTimeout(e));t.tui.engine.pushEngineEvent({kind:`perf`,pluginMetrics:e})}catch(e){t.app.logger.warn({err:e instanceof Error?e.message:String(e)},`[perf] collectPluginPerfMetrics failed`),t.tui.engine.pushEngineEvent({kind:`perf`,pluginMetrics:[]})}t.tui.panes.togglePane(`perf`)}async function Mr(e,t){let n=t.state.session.getCostSummary(),r=n.perModel.map(e=>{let t={inputTokens:e.inputTokens,outputTokens:0,cacheReadTokens:e.cacheReadTokens,cacheWriteTokens:e.cacheWriteTokens};return{modelId:e.modelId,modelName:e.modelName,cacheReadTokens:e.cacheReadTokens,cacheWriteTokens:e.cacheWriteTokens,inputTokens:e.inputTokens,hitRatio:ut(t,e.api)}}),i=r.some(e=>e.hitRatio!==null),a={inputTokens:n.totalInputTokens,outputTokens:0,cacheReadTokens:n.totalCacheReadTokens,cacheWriteTokens:n.totalCacheWriteTokens},o=i?ut(a):null;t.tui.stores.cache.setView({lastTurnHitRatio:t.state.session.getLastCacheHitRatio(),sessionHitRatio:o,totalCacheReadTokens:n.totalCacheReadTokens,totalCacheWriteTokens:n.totalCacheWriteTokens,totalInputTokens:n.totalInputTokens,totalCostUSD:n.totalCostUSD>0?n.totalCostUSD:void 0,perModel:r}),t.tui.panes.togglePane(`cacheStats`)}async function Nr(e,t){if(process.env.OTTO_DEV!==`1`){t.tui.info.showToast(`Gallery`,`Set OTTO_DEV=1 to enable the component gallery.`);return}t.tui.panes.togglePane(`gallery`)}function Pr(e){return new Date(e*1e3).toLocaleString()}async function Fr(e,t){let n=t.state.session.model?.provider;if((n?await t.app.authStore.getAuthSource(n).catch(()=>null):null)!==`oauth`){t.tui.modal.openModal({kind:`info`,title:D.t(`cmd.titleUsage`),lines:[D.t(`cmd.usageNotSubscription`)]});return}let r=t.callbacks.getLastProviderUsage();if(!r){t.tui.modal.openModal({kind:`info`,title:D.t(`cmd.titleUsage`),lines:[D.t(`cmd.usageNoSnapshotYet`)]});return}let i=[],a=n===`anthropic`;if(r.limitType&&i.push(D.t(`cmd.usageLineType`,{type:Je(r.limitType)})),(a||r.usedPercent!==void 0)&&i.push(D.t(`cmd.usageLineStatus`,{status:r.status})),r.usedPercent!==void 0&&i.push(D.t(`cmd.usageLinePercent`,{pct:Math.round(r.usedPercent)})),r.resetsAt!==void 0&&i.push(D.t(`cmd.usageLineResetsAt`,{when:Pr(r.resetsAt)})),r.standardLimits){let e=r.standardLimits;i.push(D.t(`cmd.usageLineStandardLimits`,{reqR:e.remainingRequests??0,reqL:e.limitRequests??0,tokRk:(e.remainingTokens??0)/1e3|0,tokLk:(e.limitTokens??0)/1e3|0,reset:Math.round(Math.max(e.resetRequestsMs??0,e.resetTokensMs??0)/1e3)}))}!a&&r.limitType===void 0&&(i.push(``),i.push(D.t(`cmd.usageSubPoolNotInHeaders`))),t.tui.modal.openModal({kind:`info`,title:D.t(`cmd.titleUsage`),lines:i})}function Ir(e){let t=e.trim(),n=(t.split(`
|
|
13
|
-
`,1)[0]??``).replace(/\s+/g,` `).trim(),r=n.split(` `).slice(0,6).join(` `).slice(0,48).trim()||`note`;return{name:r,description:n.slice(0,60).trim()||r,content:t}}const Lr=e=>`把下面的笔记归纳成一个简短标题(≤6 词)和一句话描述。严格只按此格式输出:\nTITLE: <标题>\nDESC: <一句话描述>\n\n笔记:\n${e}`;function Rr(e){let t=/TITLE:\s*(.+)/i.exec(e)?.[1]?.trim();if(!t)return null;let n=/DESC:\s*(.+)/i.exec(e)?.[1]?.trim();return{name:t.slice(0,60),description:(n||t).slice(0,80)}}async function zr(e,t,n=8e3){let r=Ir(e);if(!t)return r;let i=new AbortController,a=setTimeout(()=>i.abort(),n);a.unref?.();try{let n=Rr(await t(Lr(e),{maxTokens:64,signal:i.signal}));if(n)return{name:n.name,description:n.description,content:r.content}}catch{}finally{clearTimeout(a)}return r}function Br(e,t){return{onViewEntry:async n=>{let r=n.source===`project`?e:t;return r?r.read(n.name):null},onForget:async n=>{let r=n.source===`project`?e:t;r&&await r.forget(n.name)}}}async function Vr(e,t){let n=t.app.storage.autoMemory,r=t.app.userMemory,i=(e[0]??``).toLowerCase(),a=e=>t.tui.messages.pushMessage({role:`assistant`,content:[{type:`text`,text:e}]}),o=(e,n,r)=>t.tui.messages.pushMessage({role:`command_echo`,command:e,status:n,summary:r});if(i===`user`){let i=e.slice(1).join(` `).trim();if(i){let e=Ir(i);await r.record(e),o(`/memory user`,`ok`,D.t(`cmd.memoryUserSaved`,{name:e.name}));return}let a=r.getInjection().trim();t.tui.modal.openModal({kind:`memory`,projectIndex:``,userIndex:a,...Br(n,r)});return}if(i===`project`){let e=n?n.getInjection().trim():``;t.tui.modal.openModal({kind:`memory`,projectIndex:e,userIndex:``,...Br(n,r)});return}if(i===`forget`){let t=e.slice(1).join(` `).trim();if(!t){o(`/memory forget`,`warn`,D.t(`cmd.memoryForgetUsage`));return}let i=!1;n&&await n.read(t)!==null&&(await n.forget(t),i=!0),await r.read(t)!==null&&(await r.forget(t),i=!0),o(`/memory forget`,i?`ok`:`error`,i?D.t(`cmd.memoryDeleted`,{name:t}):D.t(`cmd.memoryNotFound`,{name:t}));return}if(e.length>0){let t=e.join(` `).trim(),i=(n?await n.read(t):null)??await r.read(t);i==null?o(`/memory ${t}`,`error`,D.t(`cmd.memoryNotFound`,{name:t})):a(i);return}let s=n?n.getInjection().trim():``,c=r.getInjection().trim();t.tui.modal.openModal({kind:`memory`,projectIndex:s,userIndex:c,...Br(n,r)})}async function Hr(e,t){let n=D.t(`cmd.titleCompact`);if((e[0]??``).toLowerCase()===`log`){t.tui.panes.togglePane(`compactionLog`);return}if(!t.app.storage.memory){t.tui.info.showToast(n,D.t(`compact.unavailable`));return}t.tui.info.showToast(n,D.t(`compact.ing`)),t.tui.engine.pushEngineEvent({kind:`agentProcessing`,value:!0});try{let e=await t.state.session.compactNow();e.compacted?(t.tui.info.showToast(n,D.t(`compact.edResult`,{before:e.before,after:e.after})),t.callbacks.onContextCompacted()):t.tui.info.showToast(n,D.t(`compact.noop`))}catch(e){t.tui.info.showToast(n,D.t(`cmd.compactFailed`,{msg:e instanceof Error?e.message:String(e)}))}finally{t.tui.engine.pushEngineEvent({kind:`agentProcessing`,value:!1})}}const Ur=async(e,t)=>{let n=e[0];switch(n){case`memory-sync`:return Wr(e.slice(1),t);default:t.tui.info.showToast(`Workspace`,`/workspace ${n??``} — unknown subcommand. Use: memory-sync [status|enable|disable]`)}};async function Wr(e,t){let n=e[0]??`status`,r=t.app.workspaceRef;if(!r?.root){t.tui.info.showToast(`Workspace memory-sync`,`No local workspace identity yet — cannot configure memory sync.`);return}switch(n){case`status`:{let e=r.memorySyncEnabled===!0;t.tui.info.showToast(`Workspace memory-sync`,e?`Enabled — workspace memory (auto-memory/lessons) syncs to the remote store.`:`Disabled — workspace memory stays local (default).`);return}case`enable`:{if(!t.app.storage.remoteMemoryConfigured){t.tui.info.showToast(`Workspace memory-sync`,`No remote memory service configured (needs --session-url + remote.getAuth) — enabling now would have no effect.`);return}let e=await t.app.setWorkspaceMemorySyncEnabled(!0);t.tui.info.showToast(`Workspace memory-sync`,e?`Enabled. Next memory_record/learn calls will sync to the remote store.`:`Failed to enable — no workspace marker to update.`);return}case`disable`:{let e=await t.app.setWorkspaceMemorySyncEnabled(!1);t.tui.info.showToast(`Workspace memory-sync`,e?`Disabled. Workspace memory now stays local.`:`Failed to disable — no workspace marker to update.`);return}default:t.tui.info.showToast(`Workspace memory-sync`,`/workspace memory-sync ${n} — unknown subcommand. Use: status|enable|disable`)}}async function Gr(e,t){t.tui.info.showToast(D.t(`cmd.titleUpgrade`),D.t(`cmd.upgradeCheckScanning`));try{let e=Ee({workspaceDir:t.app.workspaceDir,workspaceRoot:xe(t.app.workspaceDir),scope:`all`});if(e.length===0){t.tui.info.showToast(D.t(`cmd.titleUpgrade`),D.t(`cmd.upgradeCheckNoVersioned`));return}let n=await me(e),r=[],i=0;for(let e of n){let t=`[${e.type}]`;e.error?r.push(`${t} ${e.name} v${e.installed} — ${e.error}`):e.newer?(i++,r.push(D.t(`cmd.upgradeCheckLineUpdate`,{tag:t,name:e.name,installed:e.installed,latest:e.latest??``}))):r.push(D.t(`cmd.upgradeCheckLineLatest`,{tag:t,name:e.name,installed:e.installed}))}i>0&&(r.push(``),r.push(D.t(`cmd.upgradeCheckSummary`,{count:i}))),t.tui.modal.openModal({kind:`info`,title:D.t(`cmd.titleUpgradeCheck`),lines:r})}catch(e){t.tui.info.showToast(D.t(`cmd.titleUpgrade`),D.t(`cmd.upgradeCheckFailed`,{err:e instanceof Error?e.message:String(e)}))}}async function Kr(t,n){let r=n.state.session,i=r.model?.id??D.t(`cmd.aboutModelNotSet`),a=re().length,o=Math.round(oe()/(1024*1024*1024)),s=Math.round(y()/(1024*1024)),c=[{kind:`group`,title:D.t(`cmd.aboutGroupVersion`)},{kind:`row`,label:D.t(`cmd.aboutVersion`),value:`${Fe} (build ${Ne})`},{kind:`row`,label:D.t(`cmd.aboutBranch`),value:`dev`},{kind:`row`,label:D.t(`cmd.aboutCommit`),value:e},{kind:`group`,title:D.t(`cmd.aboutGroupRuntime`)},{kind:`row`,label:D.t(`cmd.aboutRuntime`),value:`Node.js ${process.version} · ${ie()} ${ne()} · ${ae()}`,tone:`muted`},{kind:`row`,label:D.t(`cmd.aboutCPU`),value:`${a} cores`,tone:`muted`},{kind:`row`,label:D.t(`cmd.aboutMemory`),value:`${o} GB total / ${Math.round(s)} MB free`,tone:`muted`},{kind:`row`,label:D.t(`cmd.aboutShell`),value:process.env.SHELL??`(unknown)`,tone:`muted`},{kind:`row`,label:D.t(`cmd.aboutTerminal`),value:process.env.TERM??`(unknown)`,tone:`muted`},{kind:`group`,title:D.t(`cmd.aboutGroupSession`)},{kind:`row`,label:D.t(`cmd.aboutModel`),value:i,tone:`secondary`}];r.id&&c.push({kind:`row`,label:D.t(`cmd.aboutSession`),value:r.id.slice(0,8)}),c.push({kind:`blank`},{kind:`text`,value:D.t(`cmd.aboutUpdateHint`),tone:`muted`}),n.tui.modal.openModal({kind:`info`,title:D.t(`cmd.titleAbout`),lines:[],dataListEntries:c})}async function X(e,t){return e.tui.modal.openModal({kind:`input`,...t})}function qr(e,t,n){return new Promise(r=>{e.tui.modal.openModal({kind:`confirm`,title:t,message:n,onConfirm:()=>r(!0),onCancel:()=>r(!1)})})}async function Jr(e,t){let n=t.app.feedbackRegistry.get();if(!n){t.tui.info.showToast(D.t(`cmd.titleFeedback`),D.t(`cmd.feedbackUnavailable`));return}let r=e.indexOf(`--repo`),i=r>=0?e[r+1]:void 0,a=await ee({configuredRepo:t.app.settings.get(`feedback_repo`),cliRepoArg:i,cwd:t.app.workspaceDir});if(!a){t.tui.info.showToast(D.t(`cmd.titleFeedback`),D.t(`cmd.feedbackRepoUnresolved`));return}let o=await X(t,{title:D.t(`cmd.feedbackStepTitle`)});if(!o||o.trim().length===0)return;let s=await X(t,{title:D.t(`cmd.feedbackStepRepro`)})??void 0,c=await X(t,{title:D.t(`cmd.feedbackStepExpected`)})??void 0,l=await X(t,{title:D.t(`cmd.feedbackStepActual`)})??void 0,u=v(t.state.session.model?.id),d={title:o.trim(),reproduce:s,expected:c,actual:l,envInfo:u,repo:a},f=[s,c,l].filter(Boolean).join(`
|
|
14
|
-
`).slice(0,300);if(!await qr(t,D.t(`cmd.feedbackConfirmTitle`),D.t(`cmd.feedbackConfirmMessage`,{title:d.title,bodyPreview:f})))return;if(t.tui.info.showToast(D.t(`cmd.titleFeedback`),D.t(`cmd.feedbackChannelSubmitting`)),n.submitIssueViaGh){let e=await n.submitIssueViaGh(d);if(e.ok){t.tui.info.showToast(D.t(`cmd.titleFeedback`),D.t(`cmd.feedbackCreated`,{url:e.url}));return}}let p=n.buildIssueUrl?.(d);if(!p){t.tui.info.showToast(D.t(`cmd.titleFeedback`),D.t(`cmd.feedbackUnavailable`));return}je(p),t.tui.info.showToast(D.t(`cmd.titleFeedback`),D.t(`cmd.feedbackCreated`,{url:p}))}function Yr(e,t,n){let r=we(e,t);if(r)return r;let i=e.split(`/`).pop();if(i)return ve({cwd:t??process.cwd(),homedir:n??b()}).find(e=>e.id===i)?.dir}function Xr(e,t,n){try{let r=Yr(e,t,n);if(!r)return;let i=C(r,pe),a=Se(Be(i,`utf-8`),i);return a?a.codeProviderIds?.[0]??a.contributes?.providers?.[0]?.id??a.contributes?.oauth?.[0]?.id:void 0}catch{return}}function Zr(e,t,n){return!e||t.get(e)?[{label:n.oauth.label,value:`oauth`,detail:n.oauth.detail},{label:n.apiKey.label,value:`key`,detail:n.apiKey.detail}]:[{label:n.apiKey.label,value:`key`,detail:n.apiKey.detail}]}const Qr={provider:`install-provider`,authentication:`connect-provider`,model:`choose-model`,ready:`send-first-prompt`,"first-prompt":`none`};function $r(e){if(!e.providerInstalled)return`provider`;if((e.connectedProviderIds??[]).length===0)return`authentication`;let t=e.usableModelIds??[];if(t.length===0)return`model`;let n=e.selectedModelId;return!n||!(e.selectedModelUsableInMainSession??t.includes(n))?`model`:e.firstPromptSubmitted?`first-prompt`:`ready`}function ei(e){let t=$r(e),n=e.providerCandidates??[],r=e.connectedProviderIds??[],i=e.usableModelIds??[],a=e.firstPromptSubmitted??!1,o=(t===`provider`||t===`authentication`||t===`model`)&&!(e.dismissed??!1);return{stage:t,provider:{installed:e.providerInstalled,candidates:n},authentication:{connectedProviderIds:r},model:{...e.selectedModelId===void 0?{}:{selectedId:e.selectedModelId},usableIds:i},firstPrompt:{submitted:a},recommendation:{action:Qr[t],stage:t,autoOpen:o}}}function ti(e){return e.stage===`ready`||e.stage===`first-prompt`}function ni(e,t){return e.get(t)===!0}function ri(e){return{personalizationSeen:ni(e,`has_completed_onboarding`),dismissed:ni(e,`has_completed_wizard`)||ni(e,`wizard_dont_show_again`),firstPromptSubmitted:typeof e.get(`first_prompt_submitted_at`)==`number`}}const ii=`__otto_setup__`;async function ai(e){let t=ri(e.settings),n=e.listProviders(),r=(await Promise.all(n.map(async t=>({provider:t,source:await e.getAuthSource(t).catch(()=>null)})))).filter(e=>e.source!=null).map(e=>e.provider),i=e.listModels().filter(e=>e.id!==ii),a=(await Promise.all(i.map(async t=>{try{let{usable:n}=await e.isModelUsable({provider:t.provider,api:t.api});return n?t.id:null}catch{return null}}))).filter(e=>e!==null),o=e.getSelectedModelId(),s=o!==void 0&&o!==ii?i.find(e=>e.id===o):void 0,c=s&&e.isModelUsableInMainSession?await e.isModelUsableInMainSession({provider:s.provider,api:s.api}).then(({usable:e})=>e).catch(()=>!1):void 0;return ei({providerInstalled:e.hasProviderPlugin(),...e.providerCandidates?{providerCandidates:e.providerCandidates}:{},connectedProviderIds:r,usableModelIds:a,...o!==void 0&&o!==ii?{selectedModelId:o}:{},...c===void 0?{}:{selectedModelUsableInMainSession:c},firstPromptSubmitted:t.firstPromptSubmitted,dismissed:t.dismissed})}function oi(e){return{hasProviderPlugin:()=>e.modelRegistry.hasAnyPluginSourcedModel(),listProviders:()=>e.authStore.listProviders(),getAuthSource:t=>e.authStore.getAuthSource(t),listModels:()=>e.modelRegistry.getAll().map(e=>({id:e.id,provider:e.provider,api:e.api})),isModelUsable:e.isModelUsable,...e.isModelUsableInMainSession?{isModelUsableInMainSession:e.isModelUsableInMainSession}:{},getSelectedModelId:e.selectedModelId,settings:{get:t=>e.settings.get(t)},...e.providerCandidates?{providerCandidates:e.providerCandidates}:{}}}var si=class{snapshot=null;listeners=new Set;constructor(e){this.deps=e}current(){return this.snapshot}async refresh(){let e=await ai(this.deps);this.snapshot=e;for(let t of this.listeners)try{t(e)}catch{}return e}subscribe(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}};function ci(e,t){return(e.envKeys??[]).some(e=>{let n=t[e];return typeof n==`string`&&n.trim().length>0})?0:e.supportsOAuth?1:2}function li(e,t){return e.map((e,n)=>({provider:e,index:n,rank:ci(e,t)})).sort((e,t)=>e.rank-t.rank||e.index-t.index).map(e=>e.provider)}function ui(e){try{let{createRequire:t}=gt(`node:module`),{readFileSync:n}=gt(`node:fs`),{dirname:r,join:i}=gt(`node:path`),a=i(r(t(import.meta.url).resolve(`${e}/package.json`)),`otto-plugin.json`),o=JSON.parse(n(a,`utf8`)).contributes??{},s=(o.providers??[]).map(e=>e.envKey).filter(e=>typeof e==`string`&&e.length>0);return{supportsOAuth:!!o.oauth,envKeys:s}}catch{return{supportsOAuth:!1,envKeys:[]}}}function di(e){let t=e.map((e,t)=>({model:e,index:t})).sort((e,t)=>Number(t.model.usable)-Number(e.model.usable)||e.index-t.index).map(e=>e.model),n=fi(t);return t.map(e=>({...e,recommended:e.usable&&e.id===n}))}function fi(e){return e.find(e=>e.usable)?.id}const pi={hasReadme:!1,hasPackageManifest:!1,hasTestConfig:!1,isEmpty:!1},mi=new Set([`node_modules`,`.git`,`.hg`,`.svn`,`.venv`,`venv`,`__pycache__`,`.pytest_cache`,`.mypy_cache`,`.tox`,`dist`,`build`,`.next`,`.nuxt`,`.cache`,`.DS_Store`]),hi=/^readme(\.(md|markdown|txt|rst))?$/i,gi=new Set([`package.json`,`pyproject.toml`,`setup.py`,`requirements.txt`,`Cargo.toml`,`go.mod`,`Gemfile`,`composer.json`,`pom.xml`,`build.gradle`,`build.gradle.kts`,`Package.swift`]),_i=/^(jest\.config|vitest\.config|karma\.conf|\.mocharc)(\.(js|ts|mjs|cjs|json))?$/i,vi=new Set([`pytest.ini`,`tox.ini`,`phpunit.xml`,`phpunit.xml.dist`,`setup.cfg`]);async function yi(e,t){let n;try{n=await t.readdir(e)}catch{return pi}let r=n.filter(e=>!mi.has(e.name)),i=r.length===0,a=!1,o=!1,s=!1,c=0;for(let e of r)if(c>=2e3||(c++,e.isFile()&&(!a&&hi.test(e.name)&&(a=!0),!o&&gi.has(e.name)&&(o=!0),!s&&(vi.has(e.name)||_i.test(e.name))&&(s=!0),a&&o&&s)))break;return{hasReadme:a,hasPackageManifest:o,hasTestConfig:s,isEmpty:i}}function bi(e){if(e.isEmpty)return[`suggestion.emptyProject`];let t=[];return e.hasReadme&&t.push(`suggestion.hasReadme`),e.hasPackageManifest&&t.push(`suggestion.hasPackageManifest`),e.hasTestConfig&&t.push(`suggestion.hasTestConfig`),t.length>0?t:[`suggestion.unrecognized`]}const xi=`oxygen-provider`,Si=new Set([`plugin-oxygen`,xi]);async function Ci(e){try{let{makeRegistryClient:t}=await import(`./registry-DAJ7oZTe.js`),{registries:n}=await t(e.app).loadAll();return n.some(e=>e.entries.some(e=>Si.has(e.id)||e.source===`@x-otto/plugin-oxygen`||e.source.includes(`/plugin-oxygen`)||e.subPath?.endsWith(`plugin-oxygen`)))}catch{return!1}}async function wi(e){let t=e.tui,n=e.app,r=await ai(oi({modelRegistry:n.modelRegistry,authStore:n.authStore,settings:n.settings,selectedModelId:()=>e.state.session?.model?.id,isModelUsable:O(n.authStore,`delegation`),isModelUsableInMainSession:O(n.authStore,`main-session`)})),i=n.authStore.listProviders(),a=[];r.provider.installed&&a.push(0),r.authentication.connectedProviderIds.length>0&&a.push(1),r.model.usableIds.length>0&&a.push(2);let s=(()=>{for(let e=0;e<3;e++)if(!a.includes(e))return e;return 3})(),c=await Ci(e),l=Object.entries(o).filter(([e,t])=>t.capabilities.includes(`provider`)&&(e!==xi||c)),u={"anthropic-provider":`Claude 全系列 · OAuth 一键登录`,"deepseek-provider":`高性价比 · API-key 接入 · 中文友好`,"openai-codex-provider":`GPT / Codex 系列 · 需要 API key 或 OAuth`,"github-copilot-provider":`订阅即用 · GitHub 账号授权`,"cursor-provider":`Cursor IDE 内置 · 需 Cursor 订阅`,"oxygen-provider":`企业级 · 按组织授权`},d=li(l.map(([e,t])=>({id:e,...ui(t.pkg)})),process.env).map(({id:e})=>({label:e,value:e,detail:u[e]??o[e]?.pkg??e})),f=e=>Zr(e,n.authStore.oauth,{oauth:{label:D.t(`setup.loginOAuth`),detail:`Open browser, complete authorization — no copy-paste needed.`},apiKey:{label:D.t(`setup.loginApiKey`),detail:`Generate a key on your provider dashboard and paste it here.`}}),p=async()=>{let t=await ai(oi({modelRegistry:n.modelRegistry,authStore:n.authStore,settings:n.settings,selectedModelId:()=>e.state.session?.model?.id,isModelUsable:O(n.authStore,`delegation`),isModelUsableInMainSession:O(n.authStore,`main-session`)})),r=O(n.authStore,`delegation`),i=new Set(t.model.usableIds),a=n.modelRegistry.getAll().filter(e=>e.id!==`__otto_setup__`);return di((await Promise.all(a.map(async e=>{let t=i.has(e.id);return t?{model:e,usable:t,reason:void 0}:{model:e,usable:!1,reason:(await r({provider:e.provider,api:e.api}).catch(()=>({usable:!1,reason:`unauthenticated`}))).reason??`unauthenticated`}}))).map(({model:e,usable:t,reason:n})=>({id:e.id,label:e.name,detail:`${e.provider} · ${e.strengths?.join(`, `)??``}`,usable:t,...n?{reason:n}:{}}))).map(e=>({label:e.label,value:e.id,detail:e.detail,recommended:e.recommended,...e.reason?{unusableReason:e.reason}:{}}))},m=await p(),h=i.length===1?i[0]:void 0,g=bi(await yi(n.workspaceDir,{readdir:e=>$e(e,{withFileTypes:!0})})).map(e=>D.t(e));t.modal.openModal({kind:`wizard`,title:D.t(`setup.title`),welcomeTitle:D.t(`setup.welcomeTitle`),welcomeGreeting:``,welcomeDescription:D.t(`setup.welcomeDescription`),welcomeDontShowAgainLabel:D.t(`setup.welcomeDontShowAgain`),welcomeStartLabel:D.t(`setup.welcomeStart`),welcomeCancelLabel:D.t(`setup.welcomeCancel`),stepNames:[D.t(`setup.stepNameProvider`),D.t(`setup.stepNameLogin`),D.t(`setup.stepNameModel`),D.t(`setup.stepNameStart`)],stepSubtitles:[D.t(`setup.step1Subtitle`),D.t(`setup.step3Subtitle`),D.t(`setup.step4Subtitle`),D.t(`setup.step5Done`)],initialCompleted:a,initialCurrent:s,providerOptions:d,providerAuthOptions:f,authProviderName:h,modelOptions:m,onRefreshModelOptions:p,examplePrompts:g,workspacePath:n.workspaceDir,async onInstallProvider(t,n){n(`Resolving package…`);try{let{runPluginInstall:r}=await import(`./extension-plugin-BEQlmUET.js`),i=!1,a=await r(t,e,{autoTrust:!0,onProgress:e=>n(`${e}…`),onPendingCapabilities:()=>{i=!0}}),s=o[t]?.pkg??t;if(!a)return{ok:!1,error:D.t(`setup.installFailed`,{reason:t})};let c=[];try{let{readHighRiskCapabilities:e}=await import(`@x-otto/coding`),t=Yr(s);t&&(c=e(t))}catch{}return{ok:!0,providerId:Xr(s),capabilities:c,pendingCapabilities:i}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}},async onProviderOAuthStart(t){let r=t||i[0]||n.authStore.listProviders()[0];if(!r)return null;let a=new AbortController;try{let{openBrowser:t}=await import(`@x-otto/shared`),i,o,s,c=n.authStore.login(r,{signal:a.signal,onAuth:e=>{o=e.code,s=e.instructions,e.url&&(i=e.url,e.code||t(e.url))},onProgress:()=>{},onPrompt:async()=>``});return c.catch(()=>{}),await new Promise(e=>setTimeout(e,200)),{authorizationUrl:i??``,code:o,instructions:s,openAgain:()=>{i&&t(i)},poll:async()=>{try{return await Promise.race([c.then(()=>`done`),new Promise(e=>setTimeout(()=>e(`pending`),500))])===`done`?(await e.callbacks.refreshModelsNow(),`done`):`pending`}catch(e){let t=e instanceof Error?e:void 0;if(t?.code===`OAUTH_CANCELLED`)return{status:`error`,reason:`cancelled`};let n=new Set([`ENOTFOUND`,`ECONNREFUSED`,`ECONNRESET`,`ETIMEDOUT`,`EAI_AGAIN`,`EHOSTUNREACH`,`ENETUNREACH`,`UND_ERR_CONNECT_TIMEOUT`]),r=t?.code,i=t?.cause?.code;return r&&n.has(r)||i&&n.has(i)?{status:`error`,reason:`network`}:{status:`error`,reason:`rejected`}}},cancel:()=>{a.abort()}}}catch{return null}},async onSubmitProviderApiKey(t,r){let a=t||i[0]||n.authStore.listProviders()[0];if(!a)return{ok:!1,error:`No provider selected.`};try{return await n.authStore.setCredentialKey(a,r),await n.authStore.save(),await e.callbacks.refreshModelsNow(),{ok:!0}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}},onOpenProxySettings(){import(`./proxy-TzOLqtkc.js`).then(({handleProxy:t})=>t([],e)).catch(()=>{})},async onSelectModel(t){if(await e.callbacks.applyModel(t),!e.state.modelReady)throw Error(`Model selected but not ready. Check your credentials and try /model refresh.`)},onComplete(){e.callbacks.refreshModelsNow().catch(()=>{}),n.settings.persist({has_completed_wizard:!0},`global`).catch(()=>{})},onDismiss(){},onDontShowAgain(e){n.settings.persist({wizard_dont_show_again:e},`global`).catch(()=>{})}})}async function Ti(e,t){await wi(t)}let Ei;function Di(e){Ei=e}const Oi=async(e,t)=>{let n=Ei;if(!n){t.tui.info.showToast(`Schedule`,`Schedule system not available`);return}let r=e[0];switch(r){case void 0:return ki(t);case`list`:return Ai(n,t);case`add`:return ji(e.slice(1),n,t);case`remove`:return Mi(e[1],n,t);case`run`:return Ni(e[1],n,t);case`enable`:return Fi(e[1],!0,n,t);case`disable`:return Fi(e[1],!1,n,t);case`logs`:return Ii(e[1],t);case`cancel`:return Pi(e[1],t);default:t.tui.info.showToast(`Schedule`,`/schedule ${r} — unknown subcommand. Use: add|list|remove|run|enable|disable|cancel|logs`)}};async function ki(e){let t=(e.app.getPluginPanelRegistrations?.()??[]).find(e=>e.pluginId===`plugin-schedule`);if(!t){e.tui.info.showToast(`Schedule`,`Schedule panel not installed — run "otto extension install plugin-schedule"`);return}e.tui.modal.openModal({kind:`pluginPanel`,panelInfo:{title:t.title,modulePath:t.modulePath,exportName:t.exportName,pluginId:t.pluginId},onClose:()=>{}})}async function Ai(e,t){let n=e.list(),r=e.isFireOwner()?[]:e.fireOwnerFailureReason()===`network-error`?[`[read-only — could not reach the remote schedule service; check network/persistenced connectivity]`]:[`[read-only — another terminal holds the fire-ownership lease for this workspace]`];if(n.length===0){t.tui.info.showToast(`Schedule`,r.length>0?`No scheduled tasks. ${r[0]}`:`No scheduled tasks`);return}let i=[];for(let e of n){let t=e.lastFireFailed?`✕`:e.enabled?`◉`:`○`,n=e.recurring?e.cronExpression:`one-shot`,r=new Date(e.nextFireAt).toLocaleString(),a=e.lastFireFailed&&e.lastFireError?` (failed: ${e.lastFireError})`:``;i.push(`${t} ${e.id} ${e.name}${a}`),i.push(` ${n} next: ${r}`)}t.tui.messages.pushMessage({role:`command_echo`,command:`/schedule`,status:r.length>0?`warn`:`ok`,summary:r.length>0?r[0]:`${n.length} 个已排定任务`,detail:i.join(`
|
|
15
|
-
`)}),t.tui.panes.togglePane?.(`schedulePane`)}async function ji(e,t,n){if(e.length<2){n.tui.info.showToast(`Schedule`,`/schedule add [--once] [--session] <cron> <prompt>`);return}let r=!1,i,a=[...e],o=a.indexOf(`--once`);o!==-1&&(r=!0,a.splice(o,1));let s=a.indexOf(`--session`);if(s!==-1&&(i=`session`,a.splice(s,1)),a.length<2){n.tui.info.showToast(`Schedule`,`Opening schedule panel in create mode`),ki(n);return}let c=a[0],l=a.slice(1).join(` `);try{let e=t.add({name:l.slice(0,40),prompt:l,cronExpression:c,recurring:!r,origin:`user`,target:i}),a=r?`one-shot`:`recurring`,o=new Date(e.nextFireAt).toLocaleString(),s=e.target===`session`?` [fires into this conversation]`:``;n.tui.info.showToast(`Schedule`,`${e.id}: ${e.name} (${a})${s} next: ${o}`)}catch(e){n.tui.info.showToast(`Schedule`,`Failed: ${e instanceof Error?e.message:String(e)}`)}}async function Mi(e,t,n){if(!e){n.tui.info.showToast(`Schedule`,`/schedule remove <id>`);return}let r=t.remove(e);n.tui.info.showToast(`Schedule`,r?`Removed ${e}`:`Schedule "${e}" not found`)}async function Ni(e,t,n){if(!e){n.tui.info.showToast(`Schedule`,`/schedule run <id>`);return}try{t.runNow(e),n.tui.info.showToast(`Schedule`,`Triggering ${e}...`)}catch(e){n.tui.info.showToast(`Schedule`,`Failed: ${e instanceof Error?e.message:String(e)}`)}}async function Pi(e,t){let n=Ei;if(!n){t.tui.info.showToast(`Schedule`,`Schedule system not available`);return}if(!e){t.tui.info.showToast(`Schedule`,`/schedule cancel <id>`);return}let r=n.cancel(e);r.ok?t.tui.info.showToast(`Schedule`,`Canceled "${e}"`):t.tui.info.showToast(`Schedule`,`Cannot cancel "${e}": ${r.reason??`unknown`}`)}async function Fi(e,t,n,r){if(!e){r.tui.info.showToast(`Schedule`,`/schedule ${t?`enable`:`disable`} <id>`);return}let i=n.update(e,{enabled:t});r.tui.info.showToast(`Schedule`,i?`Schedule ${e} ${t?`enabled`:`disabled`}`:`Schedule "${e}" not found`)}async function Ii(e,t){let n=Le(Pe,`schedule-logs`),r;try{r=He(n)}catch{t.tui.info.showToast(`Schedule`,`No schedule logs found`);return}if(e&&(r=r.filter(t=>t.startsWith(e+`-`))),r.length===0){t.tui.info.showToast(`Schedule`,e?`No logs for schedule "${e}"`:`No schedule logs found`);return}r.sort().reverse();let i=r.slice(0,5),a=[];for(let e of i)try{let t=Be(Le(n,e),`utf-8`).trim(),r=e.indexOf(`-`),i=r>0?e.slice(0,r):e;a.push(`${i} ${t}`)}catch{a.push(`${e} (unreadable)`)}let o=r.length-i.length;o>0&&a.push(`... and ${o} more entries`),t.tui.messages.pushMessage({role:`command_echo`,command:`/schedule logs`,summary:`${r.length} 条日志文件`,detail:a.join(`
|
|
16
|
-
`)})}function Z(e){return en(e.state.session.session.getDrafts()??[])}function Li(e,t){e.state.session.session.setDrafts(t),e.app.sessionManager.save(e.state.session.id).catch(()=>{}),e.tui.stores.tasks.setDrafts(Z(e))}const Ri=async(e,t)=>{let n=e[0];switch(n){case void 0:case`list`:return zi(e.slice(1),t);case`save`:return Bi(e.slice(1),t);case`load`:return Vi(e.slice(1),t);case`delete`:case`remove`:return Hi(e.slice(1),t);default:t.tui.info.showToast(`Draft`,`/draft ${n} — unknown subcommand. Use: save|list|load|delete`)}};async function zi(e,t){let n=Z(t);t.tui.stores.tasks.setDrafts(n),t.tui.panes.togglePane(`draftPane`,!0),n.length===0&&t.tui.info.showToast(`Draft`,`No drafts. Save one with Ctrl+S`)}async function Bi(e,t){if(e.length===0){t.tui.info.showToast(`Draft`,`/draft save <name?> <text>`);return}let[n,...r]=e,i=r.length>0,a=i?n:void 0,o=i?r.join(` `):e.join(` `),s=Z(t),c=nn(a,o,s),l=rn([...s,c]);try{Li(t,l)}catch(e){t.tui.info.showToast(`Draft`,`Save failed: ${e instanceof Error?e.message:String(e)}`);return}t.tui.info.showToast(`Draft`,`Saved "${c.name}"`)}async function Vi(e,t){let n=e.join(` `);if(!n){t.tui.info.showToast(`Draft`,`/draft load <name>`);return}let r=Z(t).find(e=>e.name===n);if(!r){t.tui.info.showToast(`Draft`,`Draft "${n}" not found`);return}t.tui.messages.pushMessage({role:`assistant`,content:[{type:`text`,text:r.text}]})}async function Hi(e,t){let n=e.join(` `);if(!n){t.tui.info.showToast(`Draft`,`/draft delete <name>`);return}let r=Z(t),i=r.find(e=>e.name===n);if(!i){t.tui.info.showToast(`Draft`,`Draft "${n}" not found`);return}let a=r.filter(e=>e.id!==i.id);try{Li(t,a)}catch(e){t.tui.info.showToast(`Draft`,`Delete failed: ${e instanceof Error?e.message:String(e)}`);return}t.tui.info.showToast(`Draft`,`Removed "${i.name}"`)}const Ui=async(e,t)=>{let n=e[0];switch(n){case void 0:case`list`:return Wi(t);case`remove`:return Gi(e[1],t);case`move`:return Ki(e[1],e[2],t);case`clear`:return qi(t);default:t.tui.info.showToast(`Queue`,`/queue ${n} — unknown subcommand. Use: list|remove|move|clear`)}};async function Wi(e){e.tui.panes.togglePane(`queuePane`,!0)}async function Gi(e,t){let n=e?Number.parseInt(e,10):NaN;if(!Number.isInteger(n)||n<1){t.tui.info.showToast(`Queue`,`/queue remove <index> (1-based)`);return}t.tui.panes.removeQueuedAt(n-1),t.tui.info.showToast(`Queue`,`Removed item ${n}`)}async function Ki(e,t,n){let r=e?Number.parseInt(e,10):NaN;if(!Number.isInteger(r)||r<1||t!==`up`&&t!==`down`){n.tui.info.showToast(`Queue`,`/queue move <index> <up|down> (1-based)`);return}let i=t===`down`?1:-1;n.tui.panes.moveQueuedItem(r-1,i),n.tui.info.showToast(`Queue`,`Moved item ${r} ${t}`)}async function qi(e){e.tui.panes.clearQueue(),e.tui.info.showToast(`Queue`,`Queue cleared`)}const Ji=async(e,t)=>{let{session:n,agentProcessing:r}=t.state;if(n.isPaused&&!n.isBusy){n.resumeRun(),t.tui.info.showToast(D.t(`cmd.titleInfo`),D.t(`cmd.runResumedByCommand`)),Yi(t);return}if(n.isPaused&&n.isBusy){n.cancelPauseRun()?t.tui.info.showToast(D.t(`cmd.titleInfo`),D.t(`cmd.pauseCancelled`)):(n.resumeRun(),t.tui.info.showToast(D.t(`cmd.titleInfo`),D.t(`cmd.runResumedByCommand`))),Yi(t);return}if(r){n.pauseRun()?t.tui.info.showToast(D.t(`cmd.titlePauseRequested`),D.t(`cmd.runWillPauseAfterTurn`)):t.tui.info.showToast(D.t(`cmd.titleInfo`),D.t(`cmd.noRunningTaskToPause`)),Yi(t);return}t.tui.info.showToast(D.t(`cmd.titleInfo`),D.t(`cmd.nothingToPause`))};function Yi(e){let t=e.callbacks.refreshSlashCommands?.();t&&e.tui.plugins.setCommands(t)}function Xi(e){let t;try{let n=ze(e,`r`);try{t=Buffer.alloc(512);let e=Ve(n,t,0,512,0);t=t.subarray(0,e)}finally{Re(n)}}catch{return!1}if(t.length===0)return!1;let n=0;for(let e of t){if(e===0)return!0;!(e>=32&&e<127)&&!(e===9||e===10||e===13)&&!(e>=192&&e<=253)&&!(e>=128&&e<=191)&&n++}return n/t.length>.3}function Zi(e){return e>=1048576?`${(e/1048576).toFixed(1)} MB`:e>=1024?`${(e/1024).toFixed(1)} KB`:`${e} B`}function Qi(e){let t;try{t=He(e)}catch{return[]}let n=[];for(let r of t)try{let t=T(C(e,r));n.push({name:r,isDir:t.isDirectory(),size:t.isDirectory()?void 0:t.size})}catch{}return n}function $i(e){return[...e].sort((e,t)=>e.isDir===t.isDir?e.name.localeCompare(t.name):e.isDir?-1:1)}function Q(e,t,n,r){let i=$i(Qi(n)),a=Ie(t,n),o=a===``?D.t(`cmd.openBrowserSubtitleRoot`):D.t(`cmd.openBrowserSubtitleDir`,{relDir:a}),s=n!==t,c=i.map(e=>({label:e.isDir?`${e.name}/`:e.name,value:e.name,detail:e.isDir?void 0:Zi(e.size??0)}));e.tui.modal.openModal({kind:`select`,title:D.t(`cmd.titleOpen`),subtitle:o,searchable:!0,options:c,initialFilter:r.get(n)??``,onFilterChange:e=>{r.set(n,e)},onSelect:i=>{let a=C(n,i),o;try{o=T(a).isDirectory()}catch{e.tui.info.showToast(D.t(`cmd.titleOpen`),D.t(`cmd.openNotFound`,{path:i}));return}if(o){Q(e,t,a,r);return}ea(e,a,i,()=>{Q(e,t,n,r)}).catch(()=>{e.tui.info.showToast(D.t(`cmd.titleOpen`),D.t(`cmd.openNotFound`,{path:i}))})},onCancel:s?()=>Q(e,t,S(n),r):()=>{}})}async function ea(e,t,n,r){if(Xi(t)){e.tui.info.showToast(D.t(`cmd.titleOpen`),D.t(`cmd.openBinaryRejected`,{path:n}));return}let{content:i,truncated:a}=await Me(t);e.tui.modal.openModal({kind:`fileViewer`,path:n,content:i,preTruncated:a,onDismiss:r??(()=>{})})}async function ta(e,t){let n=t.app.workspaceDir??process.cwd(),r=e.join(` `).trim(),i=new Map;if(!r){Q(t,n,n,i);return}let a=Le(n,r),o=Ie(n,a).startsWith(`..`);if(!w(a)){if(o){t.tui.info.showToast(D.t(`cmd.titleOpen`),D.t(`cmd.openNotFound`,{path:r}));return}let e=S(a);for(;e!==n&&e!==S(e)&&!w(e);)e=S(e);if(w(e)&&T(e).isDirectory()){Q(t,n,e,i);return}t.tui.info.showToast(D.t(`cmd.titleOpen`),D.t(`cmd.openNotFound`,{path:r}));return}if(T(a).isDirectory()){if(o){t.tui.info.showToast(D.t(`cmd.titleOpen`),D.t(`cmd.openNotFound`,{path:r}));return}Q(t,n,a,i);return}await ea(t,a,r)}var na=ht({DISPATCHABLE_COMMAND_NAMES:()=>oa,ENGINE_BUILTIN_COMMANDS:()=>aa,HELP_GROUP_ORDER:()=>ra,SAFE_COMMAND_NAMES:()=>ia,SLASH_COMMANDS:()=>$,dispatchSlashCommand:()=>sa,handleCompact:()=>Hr,handleMemory:()=>Vr});const ra=[`session`,`model`,`agent`,`debug`,`ui`,`other`],$=[{name:`clear`,handler:hn,engineBuiltin:!0,group:`session`,description:`Clear screen and reset interface state`},{name:`compact`,handler:Hr,group:`session`,description:`Compact context now, or /compact log for recent compaction/prune event details`},{name:`memory`,aliases:[`mem`],handler:Vr,group:`session`,description:`Project + user memory: /memory [user|project|<name>|forget <name>]`},{name:`workspace`,handler:Ur,safe:!0,group:`session`,description:`Manage workspace settings: /workspace memory-sync [status|enable|disable] (RFC-164 R-form remote memory sync opt-in)`},{name:`status`,handler:Y,safe:!0,engineBuiltin:!0,group:`session`,description:`Session status & diagnostics: /status [perf|context|cache|diagnose|services] (RFC-367)`},{name:`open`,handler:ta,safe:!0,group:`session`,description:`Open a file, or browse the current directory to find one: /open [path] (RFC-189)`},{name:`model`,aliases:[`m`],handler:d,group:`model`,description:`Switch model; /model [<id>|refresh]`},{name:`auth`,aliases:[`login`],handler:f,group:`model`,description:`Connect a model provider (OAuth or API key); /auth [<provider>]`},{name:`usage`,handler:Fr,safe:!0,engineBuiltin:!0,group:`model`,description:`Show provider usage quota snapshot (Anthropic OAuth subscription plans only)`},{name:`logout`,handler:p,group:`model`,description:`Remove stored credentials for a provider; /logout [<provider>]`},{name:`job`,handler:s,safe:!0,group:`agent`,description:`Start a background agent job: /job <directive> (worktree-isolated, full tools incl. MCP)`},{name:`schedule`,handler:Oi,safe:!0,group:`agent`,description:`Manage scheduled tasks: /schedule add|list|remove|run|enable|disable <args>`},{name:`delegates`,handler:l,safe:!0,group:`agent`,description:`Toggle the Delegates panel (↑↓ select · k kill · o output · a apply)`},{name:`diagnose`,handler:(e,t)=>Y([`diagnose`,...e],t),safe:!0,group:`debug`,deprecated:!0,description:`Deprecated: use /status diagnose`},{name:`context`,handler:(e,t)=>Y([`context`,...e],t),safe:!0,group:`debug`,deprecated:!0,description:`Deprecated: use /status context`},{name:`perf`,handler:(e,t)=>Y([`perf`,...e],t),safe:!0,group:`debug`,deprecated:!0,description:`Deprecated: use /status perf`},{name:`cache-stats`,handler:(e,t)=>Y([`cache-stats`,...e],t),safe:!0,group:`debug`,deprecated:!0,description:`Deprecated: use /status cache`},{name:`gallery`,handler:Nr,safe:!0,group:`debug`,envGate:`OTTO_DEV`,description:`Open the TUI component gallery (all component examples; dev only, OTTO_DEV=1)`},{name:`trace-gc`,handler:Dn,group:`debug`,description:`Trace TTL lifecycle GC: /trace-gc [--ttl-days N] [--apply]. Cleans stale session trace files (not session data). TTL is based on the session's last agent engine activity time, not your last view/interaction time.`},{name:`session-archive-export`,handler:kn,group:`debug`,description:`Archive-export a cold session: /session-archive-export <sessionId> [--out <path>] [--and-delete]. Exports full session entries as JSON; optional --and-delete removes from DB after checksum verification + confirmation.`},{name:`effort`,handler:(e,t)=>G([`effort`,...e],t),group:`ui`,deprecated:!0,description:`Deprecated: use /preference effort`},{name:`preference`,aliases:[`pref`],handler:G,safe:!0,group:`ui`,description:`Preferences: /preference [general|lang|theme|keybindings|shortcuts|effort|proxy] (RFC-367)`},{name:`trust`,handler:yr,safe:!0,group:`ui`,description:`Trust this project — apply its .otto/config.json high-risk keys + .claude executable config (/trust [status|trust|untrust])`},{name:`notification`,aliases:[`notif`],handler:Tr,group:`ui`,description:`Toggle the notification center panel (≡ ctrl+n)`},{name:`draft`,handler:Ri,safe:!0,group:`ui`,description:`Manage saved drafts (≡ Ctrl+S save, Ctrl+O open panel): /draft [list|save <name?> <text>|load <name>|delete <name>]`},{name:`queue`,handler:Ui,safe:!0,group:`ui`,description:`Open the queued-commands panel (≡ ctrl+q): /queue [list|remove <index>|move <index> <up|down>|clear]`},{name:`keybindings`,aliases:[`kb`],handler:(e,t)=>G([`keybindings`,...e],t),safe:!0,group:`ui`,deprecated:!0,description:`Deprecated: use /preference keybindings`},{name:`shortcuts`,handler:(e,t)=>G([`shortcuts`,...e],t),safe:!0,group:`ui`,deprecated:!0,description:`Deprecated: use /preference shortcuts`},{name:`proxy`,handler:(e,t)=>G([`proxy`,...e],t),group:`ui`,deprecated:!0,description:`Deprecated: use /preference proxy`},{name:`help`,aliases:[`h`,`?`],safe:!0,engineBuiltin:!0,group:`other`,description:`Show available commands`,handler:async(e,t)=>Cr(e,t,$)},{name:`exit`,aliases:[`quit`,`q`],safe:!0,engineBuiltin:!0,group:`other`,tuiHidden:!0,description:`Exit the TUI; /exit --restart to restart into the same session`},{name:`abort`,engineBuiltin:!0,group:`other`,tuiHidden:!0,description:`Abort the current agent run`},{name:`pause`,aliases:[`continue`],handler:Ji,safe:!0,group:`other`,description:`Pause after current turn finishes (toggle with /continue)`},{name:`copy`,engineBuiltin:!0,group:`other`,tuiHidden:!0,description:`Copy last assistant response`},{name:`resume`,handler:_n,group:`other`,description:`List and switch to an existing session (--workspace <id>, --archived, --all)`},{name:`rename`,handler:gn,safe:!0,group:`session`,description:`Set a custom title for the current session (/rename <new title>)`},{name:`a2ui-action`,handler:En,safe:!1,group:`session`,description:`Trigger A2UI interactive component action (/a2ui-action btn1 click)`},{name:`reload`,handler:vn,group:`other`,description:`Hot-reload capabilities (MCP / providers / declarative agents) without restart`},{name:`upgrade`,group:`other`,description:`Check for updates (/upgrade --check for extensions, /upgrade for otto CLI)`,handler:async(e,t)=>{if(e.includes(`--check`)){await Gr(e,t);return}await Sr(e,t)||await xr(e,t)}},{name:`about`,handler:Kr,safe:!0,group:`other`,description:`Show version, build, and environment info (/about)`},{name:`setup`,handler:Ti,safe:!0,group:`other`,description:`Show onboarding progress (provider → login → model) with a jump-to-next-step button (/setup)`},{name:`extension`,aliases:[`extensions`,`ext`],handler:c,group:`other`,description:`Manage pluggable extensions (MCP / skills / agents / plugins / panels): ↑↓ select · Enter toggle/open · Esc close`},{name:`install`,handler:h,safe:!0,group:`other`,description:`Install a skill/plugin/MCP from a local archive/dir/URL (/install [--global] [--force] [--dry-run] [--sub-path <p>] [--ref <tag|branch>] --as <type> <source>)`},{name:`registry`,handler:_,safe:!0,group:`other`,description:`Manage plugin discovery registries (/registry [list|add <name> <url>|remove <name>]) — RFC-201`},{name:`search`,handler:g,safe:!0,group:`other`,description:`Search plugins/skills/MCP across registered registries (/search <keyword>) — RFC-201`},{name:`permissions`,aliases:[`perms`],handler:br,safe:!0,group:`other`,description:`View / clear the always-allow list (/permissions [clear])`},{name:`feedback`,handler:Jr,group:`other`,description:`Report an issue or send feedback via GitHub (/feedback [--repo <owner/repo>])`},{name:`archive`,handler:bn,safe:!0,description:`Toggle archive state (archived disables further input); /archive [<id>]`},{name:`session`,handler:wn,description:`Session management: /session fork | /session unlock <id> [--force] | /session kill <id> | /session inspect <id>`},{name:`fork`,handler:jn,safe:!0,description:`Fork current session into a named branch`},{name:`pin`,aliases:[`unpin`],handler:xn,safe:!0,description:`Toggle pin state (pinned sessions sort to top of /resume); /pin [<id>...] [--group <name>]`},{name:`history`,aliases:[`hist`],handler:wr,safe:!0,description:`Open history search panel (same panel as Ctrl+R)`},{name:`prompts`,handler:Er,safe:!0,description:`View prompt log (list of submitted prompts this session)`}],ia=new Set($.filter(e=>e.safe).flatMap(e=>[e.name,...e.aliases??[]])),aa=new Set($.filter(e=>e.engineBuiltin).flatMap(e=>[e.name,...e.aliases??[]])),oa=new Set($.filter(e=>e.handler).flatMap(e=>[e.name,...e.aliases??[]]));async function sa(e,t,n){let r=$.find(t=>t.name===e);return r||=$.find(t=>t.aliases?.includes(e)),r?.handler?(await r.handler(t,n),!0):!1}export{k as $,an as A,Yt as B,Kn as C,Rn as D,In as E,$t as F,Rt as G,Lt as H,Ue as I,It as J,zt as K,Ge as L,rn as M,nn as N,un as O,en as P,wt as Q,Ze as R,Gn as S,Ln as T,Bt as U,Jt as V,N as W,At as X,jt as Y,Ct as Z,Xn as _,na as a,Wn as b,yi as c,oi as d,St as et,ti as f,Qn as g,Yn as h,$ as i,on as j,L as k,bi as l,rr as m,aa as n,ht as nt,sa as o,zr as p,Ht as q,ia as r,Di as s,oa as t,xt as tt,si as u,qn as v,zn as w,Hn as x,W as y,P as z};
|
|
17
|
-
//# sourceMappingURL=command-dispatcher-CahuAJnO.js.map
|