mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-09 17:29:12 +00:00
feat(web): introduce Kimi web app and daemon gateway (#625)
* docs(reports): collapse P3 plan into a single final-solution doc Drop the per-step TDD/commit scaffolding; keep the substance as one final approach per area (what it does, files to touch, key types/events/projection, component responsibilities, verification, risks, sequencing). * fix(kimi-web): normalize chat block spacing Group consecutive tool cards structurally so chat block spacing is applied consistently without leaking card borders or shadows. * feat(web): land P3 — goal / swarm / subagent + terminal + view split Implements the locked P3 design end-to-end: - subagent lifecycle projection (spawned→started→suspended→completed/failed) + inline Agent / AgentGroup cards; swarm progress card (multi-column) derived from swarmIndex; goal dock strip (expandable) from goal.updated; plan/goal/ swarm activation badges in the composer status line. - terminal as a view (xterm + WS terminal_* frames with since_seq replay) and a tab/view-dimension split (usePaneLayout tree + ViewGroup + SplitLayout, VSCode editor-group style), persisted to localStorage. Adds swarm-groups / subagent-goal / agent-group-turns unit tests and stub-daemon seeds. 98 tests pass; vue-tsc + oxlint clean; production build OK. Accepted by review (see reports/web-p3-acceptance.md); no blocking issues. * docs(reports): P3 landing acceptance review Comprehensive acceptance of the P3 landing (f5a7f21c): per-area verdicts, the terminal 'map' crash explained as a stale-stub test artifact, non-blocking recommendations, and verification record (98 tests, vue-tsc/oxlint clean, prod build, in-browser smoke). No serious issues found; no code changed per the 'only fix serious issues' instruction. * fix(terminal): make node-pty load and spawn in packaged + pnpm-dev builds Two distinct PTY failures: - 'Failed to load native module: pty.node' (npx/published daemon): node-pty was transitively bundled via @moonshot-ai/services (alwaysBundle), inlining its JS while its native binary can't be bundled and wasn't shipped. Mark node-pty external in tsdown (neverBundle) and declare it as a runtime dependency of @moonshot-ai/kimi-code so npm/npx installs it with its prebuilt pty.node. - 'posix_spawnp failed' (local pnpm dev): node-pty's prebuilds/*/spawn-helper loses its +x bit through pnpm's store extraction. Add a root postinstall (scripts/fix-node-pty-perms.mjs) that restores the executable bit; verified it fixes a reproducible spawn failure. Also harden defaultShell() to fall back on an empty (not just unset) $SHELL. Note: the SEA standalone binary still needs node-pty's pty.node + spawn-helper wired into scripts/native/native-deps.mjs (not addressed here; npx path covers the reported case). * fix(web): use a real monospace font + tighter line height in the terminal xterm's fontFamily takes a literal font string, so 'var(--mono)' never resolved and the terminal fell back to courier with loose metrics — the wrong-looking font and spacing. Pass the actual JetBrains Mono stack, await document.fonts before xterm measures the cell (so the variable font isn't mismeasured), tighten lineHeight 1.25 → 1.1, and pin letterSpacing 0. * style(web): drop the staggered line-in animation on expanded tool-call output Remove the per-line kimi-line-in stagger on `.box.open .bb > div` (modern/kimi themes) and its keyframes — expanding a tool card no longer animates each output line in. * feat(web): move the tool-call summary into the card when expanded Previously the command/summary always sat on the header. Now it shows on the header only while collapsed; expanding hides it from the header and renders it at the top of the card body (above the output) — so it appears exactly once and the expanded header stays clean. Re-adds the .bb-summary style and a mount test. * feat(web): show the full, un-truncated summary in the expanded tool card The expanded body has room to wrap, so it shouldn't keep the header's '…' clip. Add a `full` flag to toolSummary that skips the length clip and use it for the .bb-summary; the collapsed header keeps the clipped form (CSS ellipsis still guards overflow). Extends the mount test to cover full-vs-clipped. * revert(web): keep the sending moon until the turn ends Reverts980ff9d4: dropping the moon the instant the first token streamed wasn't wanted. Remove the assistantDelta/messageUpdated clear so sendingBySession is again cleared only on turn end (onSessionIdle), restoring the prior behavior, and delete the now-moot sending-moon test. * style(web): bump composer textarea font-size to 14px The composer input (.ph) under the modern/kimi themes was 13px while the terminal-theme baseline is 14px. Unify on 14px so the textarea text matches the rest of the composer. * fix(web): dedupe the daemon echo of an image steer (no double user bubble) Steering an image while a turn was running rendered TWO user bubbles and the steer text looked like it never landed. Two causes: 1. The reducer matched the daemon's user-message echo to our optimistic copy by exact content equality. Image content serializes differently on each side (our {source:{kind:'file',fileId}} vs the daemon's resolved URL/base64), so the echo never matched and appended a duplicate. Match by prompt_id first (stamped on the optimistic message at submit), falling back to content. 2. Optimistic message ids were msg_opt_<Date.now()>. A queued send + a steer in the same millisecond collided on one id, so the prompt_id stamp landed on the wrong message. Use a monotonic counter for a unique id per optimistic message. steerPrompt now also stamps the real prompt_id onto its optimistic echo, like submitPromptInternal already did. * fix(web): don't flash the chat pane when opening an empty session Selecting a never-opened session set sessionLoading=true until its snapshot arrived, so the chat pane (loading spinner) rendered for a beat before the empty-composer. A session the daemon reports as empty (messageCount 0) has nothing to load — keep sessionLoading false for it so the empty-composer shows immediately. Non-empty sessions still show the loading state. * fix(web): auto-scroll to the latest content after a mid-stream refresh Refreshing while a turn was streaming left two things parked above the live output: - The thinking block's inner 5-line window stayed at its TOP. Its scroll watcher only re-pins when already at the bottom, but a refresh delivers the whole thinking text at once with scrollTop 0. Pin a streaming block to its latest line on mount. - The transcript could stop short of the bottom: the first scroll runs before markdown highlighting/images lay out and grow the content. Re-pin on the next couple of frames (only while still following) so a refresh ends at the latest content. * fix(web): stop subagent turns from fragmenting the parent transcript A subagent runs under the parent session id and streams its own turn / step / delta / tool frames over the SAME session channel, each tagged with the subagent's agentId. The web projector folded them into the parent transcript, which produced the reported bug: empty 'skeleton' assistant bubbles (a subagent turn.step.started opened a parent assistant message the main agent never filled) and fragmented snippets (subagent deltas appended to the parent). Skip transcript-building frames whose agentId is a non-main subagent, mirroring the server's InFlightTurnTracker (which already tracks only main-agent activity). Subagent progress is unaffected — it flows through the subagent.* -> task -> AgentCard path, which is intentionally not gated. * feat(web): remove the floating todo/background-task overlay The wide-screen float-stack pinned a todo card + running-tasks card to the top-right of the chat. Drop the overlay entirely (and the now-unused TasksCard.vue) — todos and background tasks live in their own ~/todo and ~/tasks tabs, so the overlay was a redundant, transcript-covering duplicate. * feat(web): show all background tasks in the tasks tab, scroll on overflow The tasks tab capped the list at 5 rows and showed '… +N more', hiding the rest even with plenty of room. Render every task and let the list scroll internally once it overflows the pane, so nothing is silently dropped. * feat(web): running spinner + unread blue dot left of the session title The gutter slot left of each session title (which kept the title aligned under the workspace name) now carries a status indicator instead of being an empty spacer: - a small SVG spinner (Kimi-blue arc) while the session is running, replacing the old absolutely-positioned pulse dot; - an unread blue dot when a BACKGROUND session finished a turn the user hasn't opened yet. Tracked via unreadBySession (set on idle for a non-active session, cleared when the session is selected). * feat(web): unify archive/remove wording + keep the confirm within the title - Clarify the two list-removal actions: a session is 'Archive' (归档), a workspace is 'Remove workspace' (移除工作区) — the workspace menu used the bare 'Delete', which read as the same action as the session archive. - Keep the session row's archive-confirm strip aligned under the title: the leading gutter slot now persists in the confirm state, so the confirm row starts at the title's left boundary instead of spilling to the row edge. * feat(web): new-conversation button + workspace picker on the empty composer - Add a compose button in the sidebar header (top-left) that starts a new conversation in the active workspace. It wires up the previously-dead 'create' emit (handleCreateSession → openWorkspaceDraft). - On the empty composer, add a workspace picker below the hint so a new conversation can be started in any workspace without leaving the screen (switching enters that workspace's draft via openWorkspaceDraft). * feat(web): add a Fork entry to the session row menu Forking already worked via the /fork command and the daemon's :fork route, but had no discoverable affordance. Add a 'Fork session' item to each session row's kebab menu; forkSession() now takes an optional session id so any row (not just the active one) can be forked. * feat(web): recall sent messages with ArrowUp/ArrowDown in the composer Shell-style history: ArrowUp on the first line of the composer walks back through previously sent messages; ArrowDown on the last line walks forward and finally restores the live draft. Editing the text leaves history-browsing, and the edge-line guards keep multi-line cursor movement intact. Submitting (or steering) a message appends it to the history (consecutive duplicates skipped). * feat(web): capture console.log/info/debug + reusable log export The client trace only captured console.error/warn. Capture every console level (log/info/debug too) when tracing is enabled, so the exported troubleshooting log reflects the full front-end console. Extract the JSONL download into a reusable downloadTraceLog() (the debug panel now calls it; a settings 'Export log' action can reuse it). * feat(web): extract settings into a dedicated Settings page Settings used to live in the sidebar account popover (a cramped fixed dropdown that mixed appearance, language, account and the daemon endpoint). Move them into a dedicated SettingsDialog modal opened from the header gear: - Appearance (theme / colour scheme / accent), Language - Account (provider, add workspace, reopen onboarding, sign in/out) - Advanced (daemon endpoint, Export log — reuses downloadTraceLog) The sidebar popover and its anchoring/positioning code are removed; the gear now just emits openSettings. A Notifications section is added next (T14). * feat(web): browser notification when a turn completes (with a settings toggle) When a session finishes a turn and the user isn't already watching it (page hidden, or a different session is active), fire a browser system notification titled with the session, clicking it focuses the window and opens the session. Opt-in via a new Notifications toggle in the Settings page; enabling it requests OS permission and the preference is persisted (stays off if the user blocks it). * feat(web): modes selector (plan/goal/swarm) + fix swarm double-render - The plan pill at the composer's bottom-left becomes a 'Modes' popover that groups Plan (a working client toggle) with Goal and Swarm. Each shows its activated state (plan on / goal active / swarm n/m), and goal/swarm focus their card in the chat when active. The menu is position:fixed so the composer input row can't paint over it. - Fix the swarm 'two blocks' bug: a multi-member swarm rendered BOTH inline as an AgentGroup AND as its SwarmCard. messagesToTurns now skips the inline block for swarm members (same membership test as buildSwarmGroups), so the swarm shows once — its special card in the chat flow. Note: starting a goal/swarm from the web needs a daemon REST endpoint (the goal RPC isn't exposed over REST and the daemon doesn't interpret slash commands in prompts); display + activation state are wired here. * feat(web): add a chat context header (workspace/session, git, open, copy, PR) A thin bar above the chat shows the workspace / session breadcrumb, the git branch with ahead/behind + changed-file count, an 'open in editor' action (daemon fs:open on the workspace root), and a 'copy all conversation' action (reuses ChatPane.copyConversation). It also has a GitHub PR slot that renders when PR data is available — the daemon doesn't expose PR status yet, so it's wired but currently passed null. Hidden on mobile and for the empty composer. * feat(web): default path + fuzzy recursive search in the add-workspace browser - Open the folder browser at the path kimi-web is working in (the active workspace root, falling back to $HOME) instead of always at $HOME. - The filter becomes an fzf-style search: typing runs a bounded, debounced RECURSIVE subsequence-fuzzy walk under the current folder (capped depth/dirs/ results, cancellable) and lists matching directories by relative path. The result list keeps a fixed height, so the dialog never resizes while searching. - Collapse the paste-an-absolute-path field behind a secondary 'enter a path' toggle (auto-expanded when the daemon can't browse). * chore(web): remove the non-functional /undo slash command /undo had no daemon endpoint — it only pushed an 'undo not implemented' warning, so it was a dead menu entry. Remove it from the slash list, the command router, and the client. The full slash-command review with deletion suggestions for the remaining commands is in reports/web-goal2-fixes.md (T17). * docs(reports): results report for the second web TODO sweep (19 items) * test(web): provide browser storage in vitest under node 24 * docs(reports): add web goal2 acceptance notes * feat: show shortPath over branch in sidebar workspace header * feat(kimi-code-web): use rounded chat bubble icon for new session button * feat(kimi-code-web): add workspace creation in empty composer and tidy settings dialog * feat: add manual swarm and goal activation to web ui - Extend protocol schemas with swarm_mode, goal_objective, goal_control - Add stub diff-dispatch in PromptService for new runtime controls - Wire swarm/goal state through useKimiWebClient and daemon events - Add Swarm toggle and Goal create/pause/resume/cancel in Composer modes menu - Update StatusPanel and MobileSettingsSheet with swarm indicator/toggle - Add bilingual i18n strings and update fixtures/tests * feat: remove copy-conversation button from view-tabs - Drop showCopyConversation / copyConversationCopied props from TabBar and ViewGroup - Remove the share-conversation button markup and styles from TabBar - Clean up related i18n strings in en/zh sidebar locales - Keep the existing ChatHeader copy-all button and internal copy state unchanged * feat: reorder chat-header layout and simplify git status styling - Move Copy all button next to the workspace/session title on the left - Move git branch/status and Open-in-editor to the right - Shorten editor button label via new openInEditorShort i18n key - Render ahead/behind/changes as plain colored text without pills - Update en/zh header locale files * style: make chat-header action buttons borderless icon + text - Remove border, background, border-radius, and padding from .ch-act - Keep label collapse on narrow widths, drop obsolete padding override * feat: move copy-all to kebab menu and add session actions in chat-header * feat: redesign chat-header open button with open-in menu * feat: align chat-header diff stats with git ++/-- red/green style * style(web): thinner, fainter scrollbars across all components * fix(web): re-pin chat to bottom when a turn finishes streaming * fix(web): keep the working moon spinning after a refresh mid-stream * fix(web): subagent card margins in bubble layout + expandable task/result detail * feat(web): rebuild subagent cards from the transcript so they survive a refresh * fix(web): stop code blocks getting stuck on the loading skeleton markstream's CodeBlock shows a skeleton while !stream && loading, and its loading prop defaults to true. We never set it, so every settled code block waited on shiki to highlight before showing anything; a screenful of code (long session / fast burst) overwhelms shiki and the skeletons get stuck, leaving the whole page blank. Pin loading:false so blocks render their plain-text fallback immediately and upgrade to highlighted when ready. * fix(web): tick running task timers + make task rows expandable to view output * fix(web): dedupe image-steer echo via a loose (text+image-count) match The daemon's messageCreated echo can land before submitPrompt stamps the prompt_id onto the optimistic copy, and an image serializes differently (file ref vs resolved URL), so neither the prompt_id nor exact-content match fired and the echo rendered as a SECOND user bubble. Add a loose fallback matching on text + image-count so the echo reconciles regardless of order. * fix(web): let ↑/↓ walk all the way through input history once browsing Recalling a multi-line entry left the caret on its last line, and the 'ArrowUp only on the first line' gate then refused to recall further, so history only ever went one step back. Once browsing (historyIndex set), walk history directly regardless of caret line; typing still exits browsing. * feat(web): minimize button on question/approval cards + stack option label/desc - Add a minimize toggle so a blocking question/approval can collapse to a thin header bar instead of covering the chat; number-key shortcuts are gated while collapsed so an unseen option can't be picked. - Stack each option's label above its description (was squeezed side-by-side into many thin lines when the description was long). Note: there is no question/approval timeout in the codebase (ask-user waits indefinitely), so the '10 minute' request is a no-op. * feat(web): archive-confirm text matches title size; workspace remove always hides - Bump the 'archive session?' confirm label to the session-title size (14px) so it lines up with the title instead of reading as a smaller note. - 'Remove workspace' now always hides the sidebar entry, even when it still has sessions: record the root in a persisted hidden set so mergedWorkspaces stops re-deriving it from session cwds. History/sessions are untouched; re-adding the same path un-hides it. * feat(web): persist unsent composer drafts per session in localStorage The composer text is saved under a per-session key as you type and restored when you switch back to that session or reload the page; sending/steering clears it. New-session drafts use a '__new__' key. * feat(web): implement undo + edit-and-resend the last user message - Wire the daemon POST /sessions/{id}:undo endpoint: client.undo(count) reverts the last turn(s) and re-syncs the snapshot. Restore the /undo slash command. - Add an 'edit & resend' button on the latest user message: it undoes the last exchange and refills the composer with that message's text for editing. * fix(web): reflect the agent's plan mode in the composer toggle The agent reports plan mode via agent.status.updated (e.g. it auto-entered plan mode for a 'make a plan' prompt), but the projector only forwarded swarmMode, so the composer's plan toggle never lit up. Carry planMode on sessionUsageUpdated, sync it into state, and also read it from GET /status — mirroring swarmMode. * fix(web): hide an empty {} argument from the tool-call title (kept in details) An empty tool argument was rendered as a noisy '{}' in the collapsed tool-card header. toolSummary now returns '' for empty args in header (non-full) mode while the expanded body still shows it. * feat(web): show file/media preview as a split pane (peer of chat/files) On desktop, opening a preview from a chat link/media now splits the layout and shows it as a 'preview' view at the chat/files level (a transient tab in that group, closeable via the group's close button) instead of a separate right-side panel — matching the split buttons. Mobile keeps the full-screen side panel; the preview view isn't persisted across reloads. * docs(reports): results report for the third web TODO sweep (16 items) * chore(kimi-web): temporarily hide open-in-app header menu * docs: design doc for temporarily disabling swarm and goal modes in web composer * feat(web): gray out swarm and goal modes with not-supported label * docs: design doc for composer queue bubble + expanded panel * feat(kimi-web): move undo button out of bubble with new icon and confirm step * fix: inherit split layout attributes * fix(kimi-web): smooth moon spinner speed * feat(web): wire swarm and goal controls to agent-core - enable swarm toggle and goal input/pause/resume/cancel controls in Composer\n- add goal error codes and agent-core-to-protocol route mappings\n- wire enterSwarm/exitSwarm and create/pause/resume/cancelGoal RPCs in PromptService\n- bootstrap swarmMode from agent-core state and track it in the shadow\n- update tests for swarm/goal dispatch and session status serialization * fix(tui): only show provider refresh status for added models Skip removed / metadata-only provider updates when reporting model list changes.\n\n add: test to enforce the behavior. * feat(tasks): add background task command and output polling to web - include command field on protocol/server tasks\n- support withOutput/outputBytes on task get endpoint\n- poll running task output and fetch final output in web client\n- show bash command and terminal output in TasksPane with copy buttons * feat(web,server): wire open-in app menu to daemon endpoint - add /fs:open-in endpoint and command builders for vscode, cursor, finder, iterm, terminal\n- expose installed open_in_apps via meta response\n- filter OpenInMenu by available apps and remove antigravity target\n- support optional line number when opening files in apps\n- add unit tests for open-in launch commands * feat(kimi-web): expose git diff line stats in chat header - add additions/deletions to fs:git_status protocol and daemon response\n- compute aggregate diff stats with git diff --numstat HEAD\n- render +N/-N counter and detached HEAD label in chat header\n- update tests and stub daemon fixtures * fix(kimi-web): hide terminal tab temporarily * feat(kimi-web): add UI font size setting * fix(kimi-web): repin chat after tail layout settles * feat(kimi-web): show live subagent progress * fix(kimi-web): align plaintext colors in dark mode * feat(kimi-web): stream running bash output * fix(kimi-web): avoid shiki overload on large messages * feat(kimi-web): preselect recommended questions * feat(kimi-web): add conversation outline nav * fix(kimi-web): tighten mobile layouts * feat(kimi-web): animate undo removal * feat(kimi-web): reorganize bottom dock * fix(web): lengthen session row running spinner arc * feat(kimi-web): turn goal mode into a toolbar toggle Turn the Modes menu's 'Goal' row from a dedicated input form into a switch that arms the main composer. When goal mode is on, the composer placeholder prompts for an objective and the next submitted prompt is sent as a goal via updateSession({ goalObjective }). The armed state is surfaced in the composer toolbar the same way as Plan and Swarm: the Modes pill shows a 'Goal' tag and the menu switch is highlighted. An active (agent-driven) goal continues to expose Pause / Resume controls in the menu. Also includes minor bottom-dock spacing alignment changes to keep goal chips, workbar, and composer visually consistent. * feat(config): add config API endpoint with redaction support add GET/POST /api/v1/config routes\nadd ConfigService and config protocol schemas\nredact api_key in config response\nadd web client bindings and config event handling\nadd e2e tests for config routes and ws-broadcast * fix: keep web tool calls collapsed by default * feat(kimi-web): split dock work panel into bash, subagent, and todos tabs - Replace the single Background tasks tab with separate Bash and Subagent tabs in the bottom dock work panel. - Add i18n labels for the new dock tabs in both en and zh. - Filter task lists by kind and reuse TasksPane for each tab. - Align left edges of Bash/Subagent/Todos tab bodies and match the dock panel width/background to the Goal card style. - Hide TasksPane header title when rendered inside the dock to avoid duplicate headings. - Make the dock tab header show only the current tab name instead of clickable buttons. - Hide Goal card title summary on expand while keeping layout alignment for status/progress/chevron. - Update unit tests for the three-chip dock behavior. - Add changesets for the dock split and alignment fixes. * feat(kimi-web): beta proportional conversation outline with viewport indicator and hover tooltip * fix(kimi-web): avoid streaming markdown placeholders * fix(kimi-web): hide legacy conversation outline when beta TOC is off * chore(kimi-web): rename beta settings section to Experimental / 实验性 * fix(web): scale UI font size consistently Apply the web font size preference across readable text using the shared UI font scale, keep fixed icon glyph sizes pinned, and raise the default font size to 15px. * fix(web): remove task tabs from tab bar * feat(web): refresh OAuth model metadata for always-thinking models * fix(kimi-web): use 'Sub Agent' and 'Mode' in English labels * fix(kimi-web): keep swarm subagents across background-task refreshes REST /tasks lists only the main agent's background-task store and never returns foreground swarm subagents (kind 'subagent'), which arrive purely through the WS event stream. Both the 1s output poll and the session-load task fetch rebuilt tasksBySession from that REST list, so a plain replace dropped the subagents on every refresh and the next event re-added them — flickering the swarm/subagent cards, their live "currently doing" line, and the dock "running" count about once per second. Add keepLiveSubagents() to carry WS-owned subagent tasks across the REST refresh (REST stays authoritative for the background tasks it does return) and use it at both rebuild sites. * fix(kimi-web): hide completed swarm cards from conversation bottom stack - Filter out swarm groups whose members are all completed/failed. - Preserve markstream-vue .table-node styles without overriding its layout. - Add unit tests for swarm stack visibility. * feat(session): add session-level abort and expose current_prompt_id in snapshot - add POST /sessions/{sid}:abort to cancel running turns without prompt_id\n- expose current_prompt_id in in-flight turn snapshot\n- wire session-level abort fallback in kimi-web stop button\n- add IPromptService.abortBySession and getCurrentPromptId\n- update protocol, server, services, and web tests * fix(server): allow aborting queued prompts and add server-e2e send/cancel coverage add server-e2e scenario (12-send-and-cancel) and vitest cases for send prompt / cancel prompt flows, including repeated ESC idempotency\nsupport aborting queued prompts in PromptService.abort and add abortSession helper to DaemonClient/HttpClient\nhandle SSE transport case in MCP server mapping and fix related typecheck issues\nadd changeset for @moonshot-ai/services and @moonshot-ai/kimi-code * fix(kimi-web): prevent file preview scroll jump when opening a file at a line * fix(kimi-web): show just now for sessions created less than a minute ago * fix(kimi-web): keep sidebar logo intact and hide product name on narrow sidebars * fix(kimi-web): preserve markdown code gutter * feat(web): carry message createdAt into ChatTurn * feat(web): add formatMessageTime utility * feat(i18n): localize yesterday label for message timestamps * feat(web): render timestamp below user query bubble * fix(web): move user query timestamp outside the bubble * fix(web): place timestamp on same line as undo action * fix(web): swap timestamp and undo positions, reveal undo text on hover * feat(web): make timestamp a button that toggles full date time * feat(web): update sidebar branding and enlarge session tags; clean up changesets - Replace "Kimi Code Web" + "BETA" with "Kimi Code" + version pill - Enlarge session pending tags to match the title font size - Ignore internal private packages in changeset config - Remove stale changesets that only affected ignored packages - Document web release flow in README * style(web): equalize timestamp and undo button heights and alignment * feat(web): make workspace names bolder to distinguish from session titles * feat(kimi-web): rename themes to Explore/Native and remove accent selector * style(web): nudge undo icon up by 1px * style(web): align both meta actions to the right * style(web): remove gap between undo and timestamp buttons * style(web): nudge undo icon up by another 0.5px * feat(web): tune workspace name font-weight to 500 * fix(kimi-web): center tag/question text and limit shell-cmd height in approval card * style(kimi-web): remove icons from session pending tags * feat(kimi-web): limit recent workspaces in empty-composer picker * feat(kimi-web): rename sidebar new-workspace button to new-chat and adjust empty conversation title * style(kimi-web): refine sidebar typography, spacing and font settings * style(kimi-web): unify Composer typography with sidebar - Use --ui-font-size for queue text and --ui-font-size-xs for queue labels/bubbles. - Remove mono font-family from composer queue/bubble elements. - Normalize perm/mode/model pill text color to --text. - Set placeholder color to --muted. * style(kimi-web): keep model pill color dimmed Revert the model selector pill text color from --text back to --dim so it stays visually secondary, matching the original design intent. * style(kimi-web): adjust ChatHeader git status spacing and badge layout * style(kimi-web): polish composer dock chip styles * feat(kimi-web): refresh session list relative times on a 30s clock * fix(kimi-web): decode base64 file content in the preview pane * feat(kimi-web): show per-session answer/approve tags in the sidebar * feat(kimi-web): pop the KAP debug panel out into a separate window * feat(kimi-web): open subagent detail in the side panel; inline agent-live * style(kimi-web): align DiffView focus outline with KMBlue * feat(kimi-web): surface goal protocol errors; ignore global config-changed events * feat(kimi-web): support video attachments in user messages end-to-end * feat(kimi-web): add /swarm and /goal slash commands * feat(kimi-web): add /btw side chat backed by child sessions * style(kimi-web): pin user message bubble font size to 15px * style(kimi-web): use Lucide PR icon and text-only git status in header * fix(kimi-web): update undo tooltip copy and reduce hover delay * fix(kimi-web): auto-scroll to bottom on send, session switch, and tab switch - Scroll side-chat panel to bottom after sending and while streaming - Reset scroll baseline on session switch to avoid stale lastScrollTop - Reset scroll baseline when returning from files tab to chat - Reset scroll baseline after user sends a message - Include @moonshot-ai/kimi-code so CLI rebuilds bundle the updated web app * fix(kimi-web): drop duplicate config-changed case shadowing the real handler A stopgap no-op `case 'event.config.changed'` (added before the config feature landed) ended up earlier in the switch than the real configChanged mapper after merging origin/feat/web, silently swallowing config events. Remove the no-op so the proper handler runs. * style(kimi-web): unify PR badge with git status pills and drop changes count * style(kimi-web): remove unused changes computed in ChatHeader * fix: keep packaged web build in sync Build kimi-web before copying packaged web assets and surface the build version plus short commit in the settings dialog. * fix(web): support slash command input tails * fix(web): scope composer dock to chat tab * fix(web): route btw through side-channel agents * feat(web): open changed files from git status * feat(web): copy final assistant summary * feat(web): add tabbed model picker * fix(web): keep composer input height fixed * fix(web): preview composer attachments * feat(web): open workspace links in files tab * fix(web): stabilize subagent progress * docs(web): design tab split workflow * feat(web): connect daemon config settings * fix: resolve CI failures on feat/web - add missing 'event.config.changed' case in exhaustive switch test\n- fix oxlint errors (unused import, string spread, unsafe stringification)\n- update protocol test fixtures for additions/deletions, open_in_apps, swarm_mode\n- fix services mcp transport switch exhaustiveness for sse\n- update nix pnpm deps hash * fix(server): suppress debug logs by default - route BridgeClientAPI and PromptService debug logs through ILogService instead of console.error\n- lower SessionClientsService debug logs from info to debug\n- add changeset for @moonshot-ai/services, @moonshot-ai/server and @moonshot-ai/kimi-code * fix(kimi-web): remove model picker top blue bar and widen dialog - Remove the inset blue box-shadow from the model picker header. - Increase the default dialog width from 620px to 760px. * fix(kimi-web): replace model picker checkmark with icon - Swap the textual checkmark for a proper SVG check icon in ModelPicker, matching the icon used in the composer model dropdown. * fix(kimi-web): hide the Open in app menu - Remove OpenInMenu usage from ChatHeader and the prop/event plumbing through ConversationPane and App. - Remove the now-obsolete test case in files-tab-no-git.test.ts. * feat(kimi-web): scope composer dock to chat tab and polish BTW side chat - Move the composer dock into the chat tab only so it no longer appears in split file, task, preview, or BTW panes. - Render the BTW side chat as a split side pane scoped to the active session, and keep its messages out of the main conversation transcript. - Remove the side-chat panel header, relabel the tab to Side chat / 侧边聊天, and use the shared moon spinner while waiting for the first token. - Suppress the generic Started a step progress text for side-channel agents. * feat(server): expose live session status via HTTP and WebSocket - add status field to session status response schema and event.session.status_changed\n- compute session lifecycle status in SessionService from approvals, questions, prompts, and turns\n- broadcast event.session.status_changed globally to all WebSocket connections\n- re-export new event types from agent-core\n- add e2e and unit tests for status computation and broadcasting * fix(kimi-web): label sidebar session removal as Archive * feat(kimi-web): make tab/split chrome accessible TabBar is now a real ARIA tablist: role=tab buttons with aria-selected, roving tabindex, Left/Right/Home/End keyboard nav, focus-visible styling, and aria-controls wired to each ViewGroup's tabpanel (role=tabpanel + aria-labelledby). ViewGroup split-right/split-down/close buttons get localized aria-labels (no longer English title-only) and a 28x28 hit area. * fix(kimi-web): move auth banner into layout flow The onboarding/auth banner was position:fixed over the top of the conversation column, covering the desktop ChatHeader and the mobile top bar. Wrap the app grid in a flex-column shell and render the banner as the shell's first in-flow child so it reserves its own height above both the sidebar/header and the mobile top bar instead of overlapping navigation. * fix(kimi-web): enlarge and label header/sidebar icon buttons Unify icon-button hit areas and keyboard affordances: - ChatHeader kebab: 28x28 target, aria-label + aria-expanded/haspopup, focus-visible ring. - Sidebar workspace kebab (.gh-more): 24x24 target, aria-label, stays visible on keyboard focus (it was hover-only), focus ring. - Sidebar per-workspace add (.gh-add): aria-label + larger tap target. - Focus rings on the settings button and new-chat/new-workspace buttons. * feat(kimi-web): give overlay dialogs modal focus management Add useDialogFocus(): records the opener, moves focus into the dialog on open, and restores focus to the opener on close. Wire it plus aria-modal="true" / tabindex="-1" into ModelPicker, LoginDialog and ProviderManager (Escape-to-close was already present). ModelPicker keeps focusing its search box on open. Covered by a model-picker focus test. SettingsDialog is intentionally left for a follow-up to avoid colliding with in-flight settings work. * feat(kimi-web): consistent rules for the right-side detail layer The transient detail panels (thinking, compaction summary, subagent detail, mobile file/media preview) now share one set of rules: - the aside is a labelled role=complementary region (aria-hidden when collapsed), - Escape closes whichever panel is open — handled in App on the capture phase so it takes precedence over the conversation's "Esc interrupts a run" handler instead of firing both, - every close button has an aria-label (not just a title) and a focus-visible ring; thinking/subagent close targets bumped to 28x28, - FilePreview toolbar buttons get focus-visible rings too. * fix(kimi-web): calm the diff line colors Added/removed diff lines washed the entire row in green/red (12% tint + fully coloured text), which competed with reading the code. Drop the background to a faint 7% tint plus a left accent bar, color only the +/- sign, and let the code text keep the normal ink color so the content — not the color wash — is what stands out. * docs(web): record tab/split convergence + UI audit follow-through Implementation note for the tab/split work: the final three-layer view model (persistent chat/files tabs, transient preview/btw tabs, right-side detail layer), the transient-view routing rules, a per-task status table, which audit suggestions were absorbed vs intentionally skipped, and why the SettingsDialog focus item is deferred (concurrent in-flight rewrite). * feat(kimi-web): add side-tab navigation to SettingsDialog * feat(session): persist session archive state and add include_archive list filter - Replace deleteSession with archiveSession RPC and REST endpoint\n- Persist archived flag in session state and filter archived sessions by default\n- Add optional include_archive query parameter to list archived sessions\n- Expose archived flag on session responses through protocol and web types\n- Rename web session delete events/handlers to archive * feat(kimi-web): give SettingsDialog modal focus management Completes the dialog-focus baseline (task 8): wire useDialogFocus + aria-modal/tabindex into SettingsDialog now that the side-tab rewrite has landed. Focus moves into the dialog on open and returns to the opener on close; covered by a settings-dialog focus test. * refactor(workspace): centralize registry into a single workspaces.json - replace per-bucket workspace.json files with one workspaces.json registry\n- serialize registry reads/writes through an opQueue to avoid races\n- delete now removes only the registry entry, leaving the session bucket intact\n- update workspace e2e tests and scenario for the new storage model * feat(workspace): add workspace lifecycle WS events - publish event.workspace.created/updated/deleted from the registry service\n- broadcast them to every connection via the __global__ watermark\n- add protocol types/schemas and frontend mapping for real-time workspace sync\n- cover with protocol, broadcast, and exhaustive-switch tests * feat(fs): add fs:mkdir action for creating directories - add fsMkdir request/response schemas and FS_ALREADY_EXISTS (40919) error code - implement IFsService.mkdir guarded by resolveSafePath, returning the created directory entry - register the mkdir action in the fs route dispatcher with EEXIST/ENOENT mapping - add protocol schema tests and server e2e coverage * style(kimi-web): set fixed heights for all modal dialogs * feat(kimi-web): add collapsible sidebar * refactor(web): temporarily hide new workspace button in sidebar * feat(kimi-web): add starred models support to model picker and composer dropdown - Persist starred model ids in localStorage via useKimiWebClient. - Pin starred models to the top of the All tab in ModelPicker. - Show a Starred section in the Composer quick-switch dropdown, including models from other providers. - Render a star glyph on each starred model row in both pickers. - Add --star CSS variable with a brighter yellow across themes. - Add tests for starred model ordering and Composer dropdown rendering. * test(kimi-web): cover chat dock composer alignment * feat(server): expose GitHub pull request in git status and web header Add a pullRequest field to the session fs:git_status response, looked up via gh pr view with a 5s timeout, GH_NO_UPDATE_NOTIFIER/GH_PROMPT_DISABLED, and a 60s per-cwd cache.\nNormalize gh state to open/merged/closed and fail soft to null so git status never breaks.\nWire the web chat header PR badge to the active session. * feat(kimi-web): surface 5-state session status with a separate busy flag The session view-model collapsed every lifecycle state to running|idle, so awaiting-input and aborted sessions were indistinguishable and the spinner span while a session was actually waiting on the user. Session now carries the real `status` (idle/running/awaitingApproval/ awaitingQuestion/aborted) plus a separate `busy` flag (running + a real task in flight). SessionRow spins only when busy, shows awaiting tags from status as a fallback for background sessions, and a distinct aborted tag; SessionsDialog and MobileSwitcherSheet distinguish the states too. (The producers in useKimiWebClient already landed via an earlier commit; this adds the Session type, the UI, and labels so the tree type-checks.) * fix(kimi-web): persist unread dots across a page reload unreadBySession was pure in-memory state seeded empty on every load, with no localStorage persistence and no server-side read cursor — so a browser refresh dropped every sidebar unread dot. Persist the `true` entries to localStorage (compact: only unread sessions are stored) and seed the map from storage on init; opening a session clears the flag and the stored entry. Covered by a reload test in the session-cache suite. Note: also carries pre-existing empty-session-flash test edits that were already part of this file's working state. * style(kimi-web): redraw collapse/expand sidebar icons Use an indent-style glyph: three lines with a directional chevron, mirrored between the collapse (left) and expand (right) states. * feat: add server-hosted web UI and document its packages - wire kimi-web into root and CI typecheck (vue-tsc) and refresh the Nix pnpm hash - consolidate per-feature changesets into a single server-hosted-web-ui entry - make agentEventProjector.shortJson resilient to stringify failures and adjust tests - add AGENTS.md for kimi-web and server; add READMEs for server and services - expand root AGENTS.md project map and clarify the flake.nix workspace-sync rule * test(kimi-web): cover empty-session flash + draft-send paths Tests and changeset for the empty-session-flash fix (the sessionsKnownEmpty / sessionLoading logic itself already landed in an earlier commit): selecting a locally-created session shows the empty composer with no loading flash, an existing session reported as empty still loads its snapshot (messageCount is not trusted), and sending straight from the draft composer does not flash the empty state. * chore: ignore generated docs and reports * style(apps/kimi-web): remove app shell top border * fix(build): build kimi-web assets before native SEA build The native SEA build embeds the Kimi web SPA from apps/kimi-code/dist-web (see scripts/native/02-sea-blob.mjs) and fails when that directory is missing. Build kimi-web and stage its assets via copy-web-assets.mjs before running build:native:sea. - flake.nix: add the web build + asset copy to buildPhase so `nix build` works. - _native-build.yml: add the same prep step before the SEA build so CI release / manual native bundles keep working. Fixes "Kimi web build output was not found at .../dist-web" in the nix build and the CI native build stage. * feat(web): hide context indicator on empty session composer * feat(kimi-web): unify detail panel layout * fix(web): improve dark toc tooltip contrast * fix(web): keep markdown code blocks mounted * fix(web): tighten dark color contrast * fix(web): close dock cards on outside click * feat(web): show session content summaries * fix(web): report web client telemetry * refactor(services): merge @moonshot-ai/services into agent-core - move services/src/** into agent-core/src/services/** and delete the standalone @moonshot-ai/services package - re-export service contracts/implementations from agent-core src/index.ts - update all server, test, and kimi-web imports to @moonshot-ai/agent-core - enable experimentalDecorators in tsconfig and adjust dev/build configs - sync workspace registry (changeset config, flake.nix, pnpm-lock) * refactor(server): remove Swagger UI and --swagger flag - drop @fastify/swagger-ui and the dev-only --swagger option - keep /openapi.json via @fastify/swagger (bundled in the SEA) - simplify the native SEA build (no swagger-ui external or asset copy) - update tests, docs, and lockfile * feat(server): add on-demand daemon for kimi web with idle shutdown - make kimi web non-blocking by spawning or reusing a single detached daemon per device (via ~/.kimi-code/server/lock), auto-picking a free port on conflict - daemon self-exits after a 1-minute grace once the last web WebSocket client disconnects (new onConnectionCountChange hook + createIdleShutdownHandler) - add getLiveLock helper for daemon discovery - hide kimi server install/uninstall/start/stop/restart/status (service-ization) for now; implementation preserved for later re-exposure * fix(web): improve mobile dialog layouts * fix(ci): resolve lint, typecheck, and nix build failures - add startBtw to IPromptService test mocks (agent-core, server) - remove useless spread in PromptService session cleanup - add assertion to concurrent-connection WS handshake test - bind idle onConnectionCountChange callback in server run - type getSessionSnapshot mock via vi.mocked in kimi-web test - update pnpmDeps hash in flake.nix Co-Authored-By: Claude <noreply@anthropic.com> * fix(web): distinguish native markdown links * fix(vis-web): enable experimentalDecorators for typecheck vis-web type-checks agent-core source (via source exports), whose services use legacy parameter decorators for DI. Without experimentalDecorators, tsc reports TS1206 "Decorators are not valid here" and the CI typecheck job fails. * Revert "feat(web): show session content summaries" This reverts commit8de58eacbc. * fix(web): align active toc-bubble highlight with bubble edges * fix(web): hide conversation toc when chat pane is too narrow * feat(server): add `kimi server ps` to list active clients - add GET /api/v1/connections endpoint backed by IConnectionRegistry - record connection metadata (connectedAt, remoteAddress, userAgent) on WsConnection - add connection wire schema in @moonshot-ai/protocol - add kimi server ps CLI command with table and --json output * feat(kimi-web): add queue chip to chat dock workbar * fix(agent-core): hide console window when spawning git/gh on Windows On Windows, spawning a console-subsystem executable (gh.exe / git.exe) from the background Kimi server creates a visible console window that flashes on screen. Set windowsHide: true (a no-op on POSIX) to suppress it. * feat(server): add startup and ws connection telemetry for kimi web - bootstrap telemetry in `kimi web`/`kimi server run` via initializeServerTelemetry (ui_mode=web, honors telemetry=false) - wire the real client into KimiCore so agent-core events carry the enriched context - emit server_started after the server listens; flush telemetry on shutdown - emit ws_connected/ws_disconnected from WSGateway via WSGatewayOptions.telemetry - re-export loadRuntimeConfigSafe/resolveConfigPath from the SDK for host config reads * feat(web): dynamic page title based on session or workspace * fix(web): show recently active sessions at the top of the web session list * feat(web): show running indicator in dynamic page title * feat(kimi-web): show elapsed time for completed assistant turns * fix(kimi-web): resolve TDZ error on App mount * refactor(kimi-web): align turn duration and support multi-tab timing * feat: display per-turn wall-clock duration in web chat * feat(web): use animated spinner in page title while running * feat(server): add kill command and background server run - add `kimi server kill` to stop the running daemon (graceful API + forced PID kill) - add `POST /api/v1/shutdown` so the server can terminate itself - make `kimi server run` start in the background and print the ready banner - route `kimi web` through the same path as `server run` so it prints the banner too * fix(server): remove duplicate startBtw key in prompt e2e test Resolves eslint no-dupe-keys and TS1117 errors that broke the lint and typecheck CI jobs. * chore: bundle Inter font locally * fix(nix): update pnpmDeps hash for new font dependency The local Inter font dependency changed pnpm-lock.yaml, so refresh the fixed-output derivation hash to match what the nix builder computes. Resolves the nix build .#kimi-code CI failure. * fix(server): restore web client telemetry and stabilize skills cleanup Restore forwarding of x-kimi-client-* headers into session creation telemetry, which was dropped during the services-to-agent-core merge and left the new-session telemetry test with empty records.\n\nRetry the skills e2e sandbox cleanup to ride out ENOTEMPTY races when the core process flushes files into the sandboxed home after close(). * chore: remove trailing blank lines * chore(changeset): remove consumed changeset files These 20 changeset files were applied during the version bump and are no longer needed. * chore: clean up web release changesets * docs: clean up kimi web readme * chore: scope package lint to cli release * chore: remove temporary design docs and preview files from PR Remove files that were not intended for submission: - docs HTML research/design archives - docs/superpowers specs added during design phase - apps/kimi-web icon preview pages and one-off test script * chore(kimi-web): remove dev stub daemon The real server package is now available; the throwaway stub daemon is no longer needed for development. * test(server-e2e): accept aborted image prompt scenario * chore: update flake.nix workspace paths and add new changesets - Added new packages: daemon, server-e2e, and kimi-migration-legacy to the workspacePaths in flake.nix. - Introduced new changeset for "@moonshot-ai/kimi-code-sdk" to add host-side config helpers. - Removed outdated changesets related to server-hosted web UI and server web APIs. * feat(server): daemonize by default and fall back to port +1 - kimi server run now spawns a background daemon by default; --foreground keeps the terminal attached - default server port moves from 7878 to 58627 across CLI, web, e2e, and docs - listenWithPortRetry retries on port + 1 when a third party holds the port (capped at 100) - lock gains updatePort so status/kill/ps find the daemon on its real bound port * fix(cli): resolve oxlint unbound-method errors in server run --------- Signed-off-by: qer <wbxl2000@outlook.com> Co-authored-by: qer <wbxl2000@outlook.com> Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
254f946a50
commit
9a8fea5c85
563 changed files with 111354 additions and 132 deletions
82
scripts/check-service-naming.mjs
Normal file
82
scripts/check-service-naming.mjs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Enforce the VS Code-style service naming convention finalized in
|
||||
* Phase 5 of the 2026.06.07 services-alignment plan:
|
||||
*
|
||||
* - packages/services/src/<domain>/<domain>.ts (+ <domain>Service.ts)
|
||||
* - packages/server/src/services/<domain>.ts (+ <domain>Service.ts)
|
||||
*
|
||||
* Domain dirs and service-related .ts files must be camelCase — never
|
||||
* kebab-case (no `-` in the name). Anything outside these two roots is
|
||||
* ignored (test fixtures, agent-core, etc.).
|
||||
*
|
||||
* Exit code 0 if clean, 1 with an actionable report otherwise.
|
||||
*/
|
||||
|
||||
import { readdirSync, statSync, existsSync } from "node:fs";
|
||||
import { resolve, join, relative } from "node:path";
|
||||
|
||||
const ROOT = resolve(import.meta.dirname, "..");
|
||||
const SERVICES_SRC = join(ROOT, "packages/services/src");
|
||||
const SERVER_SERVICES_SRC = join(ROOT, "packages/server/src/services");
|
||||
|
||||
/** @type {Array<{ kind: string, path: string }>} */
|
||||
const violations = [];
|
||||
|
||||
function isKebab(name) {
|
||||
return name.includes("-");
|
||||
}
|
||||
|
||||
function report(kind, absPath) {
|
||||
violations.push({ kind, path: relative(ROOT, absPath) });
|
||||
}
|
||||
|
||||
/**
|
||||
* packages/services/src is organised as <domain>/<files>.ts plus a few
|
||||
* top-level files (index.ts, module.ts). Flag kebab in dir names and in
|
||||
* any .ts file directly under a domain dir.
|
||||
*/
|
||||
function scanServicesSrc() {
|
||||
if (!existsSync(SERVICES_SRC)) return;
|
||||
for (const entry of readdirSync(SERVICES_SRC)) {
|
||||
const abs = join(SERVICES_SRC, entry);
|
||||
const st = statSync(abs);
|
||||
if (st.isDirectory()) {
|
||||
if (isKebab(entry)) report("kebab-dir", abs);
|
||||
for (const f of readdirSync(abs)) {
|
||||
if (!f.endsWith(".ts")) continue;
|
||||
if (isKebab(f)) report("kebab-file", join(abs, f));
|
||||
}
|
||||
} else if (entry.endsWith(".ts") && isKebab(entry)) {
|
||||
report("kebab-file", abs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* packages/server/src/services is a flat directory of <domain>.ts and
|
||||
* <domain>Service.ts modules (plus serviceCollection.ts wiring).
|
||||
*/
|
||||
function scanServerServicesSrc() {
|
||||
if (!existsSync(SERVER_SERVICES_SRC)) return;
|
||||
for (const f of readdirSync(SERVER_SERVICES_SRC)) {
|
||||
if (!f.endsWith(".ts")) continue;
|
||||
if (isKebab(f)) report("kebab-file", join(SERVER_SERVICES_SRC, f));
|
||||
}
|
||||
}
|
||||
|
||||
scanServicesSrc();
|
||||
scanServerServicesSrc();
|
||||
|
||||
if (violations.length > 0) {
|
||||
console.error(
|
||||
"Service naming violations (no kebab-case allowed for service files/dirs):"
|
||||
);
|
||||
for (const v of violations) console.error(` [${v.kind}] ${v.path}`);
|
||||
console.error(
|
||||
"\nRename to camelCase per VS Code convention: <domain>.ts + <domain>Service.ts."
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("Service naming check passed.");
|
||||
46
scripts/fix-node-pty-perms.mjs
Normal file
46
scripts/fix-node-pty-perms.mjs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Restore the executable bit on node-pty's `spawn-helper` prebuilt binaries.
|
||||
*
|
||||
* Why: on macOS/Linux node-pty launches the shell through a tiny `spawn-helper`
|
||||
* executable shipped under `prebuilds/<platform-arch>/`. pnpm's content-
|
||||
* addressable store does not preserve the +x mode on these non-bin prebuild
|
||||
* assets, so after `pnpm install` the helper lands as 0644 and any PTY spawn
|
||||
* fails with "posix_spawnp failed". npm/yarn (and the published tarball) keep
|
||||
* the bit, so this is a pnpm-dev-only fixup.
|
||||
*
|
||||
* Idempotent and never fails the install: any error is logged and ignored.
|
||||
*/
|
||||
import { chmodSync, existsSync, readdirSync, statSync } from 'node:fs';
|
||||
import { createRequire } from 'node:module';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
function nodePtyRoot() {
|
||||
const require = createRequire(import.meta.url);
|
||||
// Resolve from packages/services (where node-pty is declared) so we find the
|
||||
// workspace's hoisted copy regardless of where this script runs.
|
||||
const entry = require.resolve('node-pty', {
|
||||
paths: [join(process.cwd(), 'packages/services'), process.cwd()],
|
||||
});
|
||||
// .../node-pty/lib/index.js -> .../node-pty
|
||||
return dirname(dirname(entry));
|
||||
}
|
||||
|
||||
try {
|
||||
const root = nodePtyRoot();
|
||||
const prebuilds = join(root, 'prebuilds');
|
||||
if (!existsSync(prebuilds)) process.exit(0);
|
||||
let fixed = 0;
|
||||
for (const arch of readdirSync(prebuilds)) {
|
||||
const helper = join(prebuilds, arch, 'spawn-helper');
|
||||
if (!existsSync(helper)) continue;
|
||||
const mode = statSync(helper).mode;
|
||||
if ((mode & 0o111) === 0o111) continue; // already executable
|
||||
chmodSync(helper, 0o755);
|
||||
fixed++;
|
||||
}
|
||||
if (fixed > 0) console.log(`[fix-node-pty-perms] made ${fixed} spawn-helper binary(ies) executable`);
|
||||
} catch (err) {
|
||||
console.warn('[fix-node-pty-perms] skipped:', err instanceof Error ? err.message : String(err));
|
||||
}
|
||||
process.exit(0);
|
||||
Loading…
Add table
Add a link
Reference in a new issue