@cjhyy/code-shell 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/README.md +35 -2
  3. package/dist/anthropic-DJXVKFMJ.js +1 -0
  4. package/dist/arena/index.js +1 -1
  5. package/dist/arena-MRBATZFD.js +3 -0
  6. package/dist/{chunk-EU3J7TBP.js → chunk-5QIVXFCT.js} +1 -1
  7. package/dist/{chunk-XXCZE2U2.js → chunk-AFMP73YP.js} +1 -1
  8. package/dist/{chunk-L7B2ORID.js → chunk-D3TVASD2.js} +2 -2
  9. package/dist/chunk-DI26OUIU.js +2 -0
  10. package/dist/chunk-FZXY554D.js +2 -0
  11. package/dist/chunk-GGVAOPI7.js +1 -0
  12. package/dist/chunk-GKAQH2DG.js +2 -0
  13. package/dist/chunk-GYNO4R7R.js +2 -0
  14. package/dist/{chunk-RERBX75D.js → chunk-K4HILJ6G.js} +3 -3
  15. package/dist/{chunk-2EUIMKJA.js → chunk-OWBYLMDD.js} +1 -1
  16. package/dist/chunk-SKIX2M5Z.js +4 -0
  17. package/dist/chunk-SV7H4FKT.js +3 -0
  18. package/dist/{chunk-2GJ7H5NC.js → chunk-WONQHE4P.js} +40 -40
  19. package/dist/{chunk-DEYFK6FP.js → chunk-X2S7ASQ2.js} +1 -1
  20. package/dist/chunk-Z5HKHAO7.js +336 -0
  21. package/dist/cli/commands/builtin/plugin-commands-registration.d.ts +12 -0
  22. package/dist/cli/commands/builtin/plugin-handler.d.ts +6 -0
  23. package/dist/cli/commands/registry.d.ts +9 -2
  24. package/dist/cli/main.js +138 -122
  25. package/dist/client-base-GSFXPP2Y.js +1 -0
  26. package/dist/devtools-FBX4HVVY.js +1 -0
  27. package/dist/engine/engine.d.ts +29 -0
  28. package/dist/engine/turn-loop.d.ts +25 -0
  29. package/dist/hooks/events.d.ts +84 -5
  30. package/dist/hooks/inject.d.ts +19 -0
  31. package/dist/hooks/shell-runner.d.ts +45 -0
  32. package/dist/index.d.ts +3 -2
  33. package/dist/index.js +1 -1
  34. package/dist/llm/api-key-sanitize.d.ts +22 -0
  35. package/dist/llm/providers/openai.d.ts +23 -0
  36. package/dist/llm/retry.d.ts +12 -0
  37. package/dist/llm/stream-watchdog.d.ts +42 -0
  38. package/dist/logging/logger.d.ts +14 -0
  39. package/dist/manager-4XKLZHKE.js +1 -0
  40. package/dist/mcp-manager-3JGHEJO3.js +1 -0
  41. package/dist/openai-OQ6LLN5J.js +1 -0
  42. package/dist/{openrouter-models-GZVCRWD2.js → openrouter-models-ZT4R2AE3.js} +1 -1
  43. package/dist/openrouter-sync-ARMVWY7S.js +1 -0
  44. package/dist/plugin-handler-GJISMH5Q.js +11 -0
  45. package/dist/plugins/gitOps.d.ts +23 -0
  46. package/dist/plugins/installedPlugins.d.ts +15 -0
  47. package/dist/plugins/knownMarketplaces.d.ts +10 -0
  48. package/dist/plugins/loadPluginHooks.d.ts +52 -0
  49. package/dist/plugins/marketplaceManager.d.ts +49 -0
  50. package/dist/plugins/parseMarketplaceInput.d.ts +16 -0
  51. package/dist/plugins/pluginCommandHook.d.ts +54 -0
  52. package/dist/plugins/pluginCommandsLoader.d.ts +25 -0
  53. package/dist/plugins/pluginInstaller.d.ts +36 -0
  54. package/dist/plugins/schemas.d.ts +9 -0
  55. package/dist/plugins/types.d.ts +73 -2
  56. package/dist/plugins/varRewrite.d.ts +42 -0
  57. package/dist/product/index.js +1 -1
  58. package/dist/protocol/helpers.d.ts +42 -0
  59. package/dist/render/clearTerminal.d.ts +9 -3
  60. package/dist/render/components/App.d.ts +1 -0
  61. package/dist/render/components/Static.d.ts +23 -0
  62. package/dist/render/devtools.d.ts +8 -4
  63. package/dist/render/dom.d.ts +2 -1
  64. package/dist/render/frame.d.ts +1 -0
  65. package/dist/render/index.d.ts +10 -0
  66. package/dist/render/ink.d.ts +24 -2
  67. package/dist/render/log-update.d.ts +12 -1
  68. package/dist/render/selection.d.ts +1 -1
  69. package/dist/run/index.js +1 -1
  70. package/dist/services/index.d.ts +1 -0
  71. package/dist/services/memory-orchestrator.d.ts +39 -0
  72. package/dist/settings/schema.d.ts +93 -6
  73. package/dist/skills/frontmatter.d.ts +13 -0
  74. package/dist/skills/index.d.ts +4 -4
  75. package/dist/skills/scanner.d.ts +11 -21
  76. package/dist/tool-summary-HJX6RHG4.js +2 -0
  77. package/dist/tool-system/builtin/agent-notifications.d.ts +55 -0
  78. package/dist/tool-system/builtin/agent-registry.d.ts +35 -4
  79. package/dist/tool-system/builtin/agent-transcript-translator.d.ts +22 -0
  80. package/dist/tool-system/builtin/skill-prompt.d.ts +7 -0
  81. package/dist/tool-system/builtin/skill.d.ts +3 -1
  82. package/dist/tool-system/context.d.ts +23 -0
  83. package/dist/tool-system/executor.d.ts +1 -1
  84. package/dist/tool-system/permission.d.ts +1 -1
  85. package/dist/types.d.ts +35 -0
  86. package/dist/ui/components/AgentBlock.d.ts +5 -2
  87. package/dist/ui/components/AgentDock.d.ts +36 -0
  88. package/dist/ui/components/CommandInput.d.ts +12 -1
  89. package/dist/ui/components/MessageRow.d.ts +54 -0
  90. package/dist/ui/components/SpinnerWithVerb.d.ts +2 -1
  91. package/dist/ui/components/TextInput.d.ts +9 -1
  92. package/dist/ui/components/VirtualMessageList.d.ts +30 -11
  93. package/dist/ui/dev-seed.d.ts +1 -0
  94. package/dist/ui/fullscreen-mode.d.ts +9 -0
  95. package/dist/ui/hooks/useVirtualScroll.d.ts +101 -0
  96. package/dist/ui/perf-probes.d.ts +22 -0
  97. package/dist/ui/query-guard.d.ts +35 -0
  98. package/dist/ui/slice-anchor.d.ts +44 -0
  99. package/dist/ui/store.d.ts +26 -1
  100. package/package.json +5 -3
  101. package/dist/anthropic-OVRWAZRD.js +0 -1
  102. package/dist/arena-64T2EQD4.js +0 -3
  103. package/dist/chunk-A4M2Y42L.js +0 -2
  104. package/dist/chunk-DTPABBSV.js +0 -2
  105. package/dist/chunk-MGP35GKD.js +0 -254
  106. package/dist/chunk-Q3RSX66I.js +0 -1
  107. package/dist/chunk-VKKU4LOM.js +0 -3
  108. package/dist/chunk-ZSAONDT6.js +0 -2
  109. package/dist/client-base-F5FCRSB3.js +0 -1
  110. package/dist/devtools-SGDUOOM4.js +0 -1
  111. package/dist/manager-J6FXEUEK.js +0 -1
  112. package/dist/mcp-manager-27EVBKRT.js +0 -1
  113. package/dist/openai-3QEADSYL.js +0 -1
  114. package/dist/openrouter-sync-WG4AG5NC.js +0 -1
  115. package/dist/plugins/loader.d.ts +0 -72
  116. package/dist/skills/matcher.d.ts +0 -21
  117. package/dist/tool-summary-V2B3MD5N.js +0 -2
  118. package/skills-builtin/codeshell-help.md +0 -135
@@ -0,0 +1,54 @@
1
+ /**
2
+ * MessageRow — memo boundary around a single chat entry's rendered output.
3
+ *
4
+ * Why this exists: stream events fire dozens of times per second. Without
5
+ * a memo boundary, every entry's renderer (markdown lexing, Yoga layout,
6
+ * syntax-highlight token cache) re-runs on every frame, defeating ink's
7
+ * blit fast-path. The hand-written comparator below mirrors Claude Code
8
+ * 2.1.88's areMessageRowPropsEqual: bail only when we are CERTAIN the
9
+ * entry didn't change.
10
+ *
11
+ * Why `render` is excluded from the comparator (intentional, see CC's
12
+ * VirtualItem note): when entry/columns/isStreaming all match, React.memo
13
+ * returns the PREVIOUS rendered ReactNode — it does NOT invoke render
14
+ * again with the new closure, so a stale-closure-on-bail scenario is
15
+ * impossible. Including `render` in the comparator would defeat memo
16
+ * entirely (render is fresh on every parent render).
17
+ */
18
+ import React, { type ReactNode } from "react";
19
+ import type { ChatEntry } from "../store.js";
20
+ export interface MessageRowProps {
21
+ entry: ChatEntry;
22
+ /** Terminal columns — affects text wrap inside the renderer. */
23
+ columns: number;
24
+ /**
25
+ * True iff this entry is the one currently receiving stream deltas.
26
+ * Streaming rows never memo-bail (their text grows mid-frame).
27
+ */
28
+ isStreaming: boolean;
29
+ /**
30
+ * True iff this entry is the transcript-mode cursor target. State
31
+ * external to the entry that affects rendering (cursor outline) must
32
+ * be a prop so the comparator can invalidate when it flips. CC handles
33
+ * cursor/hover/expanded the same way on its VirtualItem.
34
+ */
35
+ isSelected: boolean;
36
+ /**
37
+ * True when long entries should render expanded (transcript mode).
38
+ * Flipping this re-renders every row — low-frequency event, acceptable.
39
+ */
40
+ expanded: boolean;
41
+ /**
42
+ * Called with the entry when we actually need to render. Excluded
43
+ * from the memo comparator on purpose (see file header).
44
+ */
45
+ render: (entry: ChatEntry) => ReactNode;
46
+ }
47
+ declare function MessageRowImpl({ entry, render }: MessageRowProps): import("react/jsx-runtime").JSX.Element;
48
+ /**
49
+ * Returns true when the row can safely skip re-render. False on any
50
+ * uncertainty — we fail safe by re-rendering.
51
+ */
52
+ export declare function areMessageRowPropsEqual(prev: MessageRowProps, next: MessageRowProps): boolean;
53
+ export declare const MessageRow: React.MemoExoticComponent<typeof MessageRowImpl>;
54
+ export {};
@@ -14,5 +14,6 @@ interface SpinnerWithVerbProps {
14
14
  runStartRef?: MutableRefObject<number>;
15
15
  thinkingContent?: string;
16
16
  }
17
- export declare function SpinnerWithVerb({ mode, streamingTokensRef, runStartRef, thinkingContent, }: SpinnerWithVerbProps): import("react/jsx-runtime").JSX.Element;
17
+ declare function SpinnerWithVerbImpl({ mode, streamingTokensRef, runStartRef, thinkingContent, }: SpinnerWithVerbProps): import("react/jsx-runtime").JSX.Element;
18
+ export declare const SpinnerWithVerb: import("react").MemoExoticComponent<typeof SpinnerWithVerbImpl>;
18
19
  export {};
@@ -4,6 +4,14 @@ interface TextInputProps {
4
4
  onSubmit?: (value: string) => void;
5
5
  placeholder?: string;
6
6
  focus?: boolean;
7
+ /** Suffix rendered dim after the cursor — e.g. "on|off" arg hint for
8
+ * a slash command. Not part of `value`; not editable. */
9
+ hint?: string;
10
+ /** Bumping this number snaps the cursor to value.length on the next
11
+ * render. Use when the parent rewrites `value` externally (e.g. tab
12
+ * completion) — the in-component cursor would otherwise stay at its
13
+ * pre-rewrite offset and land in the middle of the new text. */
14
+ cursorResetCounter?: number;
7
15
  }
8
- export default function TextInput({ value, onChange, onSubmit, placeholder, focus, }: TextInputProps): import("react/jsx-runtime").JSX.Element;
16
+ export default function TextInput({ value, onChange, onSubmit, placeholder, focus, hint, cursorResetCounter, }: TextInputProps): import("react/jsx-runtime").JSX.Element;
9
17
  export {};
@@ -1,24 +1,43 @@
1
1
  /**
2
- * VirtualMessageList — renders only visible messages for performance.
2
+ * VirtualMessageList — ScrollBox + viewport-windowed chat rendering.
3
3
  *
4
- * When message count exceeds VIRTUALIZE_THRESHOLD, only renders messages
5
- * within the visible viewport (plus overscan buffer). Uses height estimation
6
- * for off-screen items via spacer elements.
4
+ * Owns its own ScrollBox + useVirtualScroll. Only items in the viewport
5
+ * (+ overscan) are mounted as React fibers / Yoga nodes; the rest live
6
+ * as height-equivalent topSpacer / bottomSpacer boxes. ScrollBox handles
7
+ * sticky-bottom, wheel/key scroll, render-time clamping.
7
8
  *
8
- * For smaller conversations, renders all messages directly (no virtualization).
9
+ * Each rendered item is wrapped in <MessageRow> (React.memo) so historical
10
+ * entries skip re-render entirely on stream ticks. Cursor-outline decoration
11
+ * is applied OUTSIDE the memo boundary so the render closure stays cursor-
12
+ * agnostic.
9
13
  *
10
- * Also provides an unseen message divider ("── N new ──") when the user
11
- * scrolls away and new messages arrive.
14
+ * Auto-stick: when entries grow while ScrollBox is "sticky" (default + after
15
+ * scrollToBottom), the view follows the tail. User scrolling up breaks
16
+ * stickiness; clicking the "N new messages" pill should call onJumpToNew
17
+ * which ultimately invokes scrollToBottom (wired via the imperative handle).
12
18
  */
13
- import { type ReactNode } from "react";
14
- import type { ChatEntry } from "../store.js";
19
+ import React, { type ReactNode } from "react";
20
+ import { type ChatEntry } from "../store.js";
15
21
  interface VirtualMessageListProps {
16
22
  entries: ChatEntry[];
17
- renderEntry: (entry: ChatEntry, key: string) => ReactNode;
23
+ renderEntry: (entry: ChatEntry, key: string, expanded: boolean) => ReactNode;
24
+ /** Terminal width — threaded into MessageRow so width changes invalidate memo. */
25
+ columns: number;
26
+ /** Id of the entry currently receiving stream deltas (null = no stream). */
27
+ streamingEntryId?: string | null;
28
+ /** Id of the entry under the transcript-mode cursor (null = no cursor). */
29
+ selectedEntryId?: string | null;
30
+ /** True when long entries should render expanded (transcript mode). */
31
+ expanded?: boolean;
18
32
  /** Index where "N new" divider should render (null = no divider). */
19
33
  dividerIndex?: number | null;
20
34
  /** Count of unseen messages to show in divider. */
21
35
  unseenCount?: number;
22
36
  }
23
- export declare function VirtualMessageList({ entries, renderEntry, dividerIndex, unseenCount, }: VirtualMessageListProps): import("react/jsx-runtime").JSX.Element;
37
+ export interface VirtualMessageListHandle {
38
+ scrollToBottom: () => void;
39
+ scrollBy: (dy: number) => void;
40
+ getViewportHeight: () => number;
41
+ }
42
+ export declare const VirtualMessageList: React.ForwardRefExoticComponent<VirtualMessageListProps & React.RefAttributes<VirtualMessageListHandle>>;
24
43
  export {};
@@ -0,0 +1 @@
1
+ export declare function applyDevSeed(): void;
@@ -0,0 +1,9 @@
1
+ export declare const TAIL_ENTRY_LIMIT = 500;
2
+ export declare const INITIAL_FULLSCREEN_MODE: boolean;
3
+ export interface FullscreenModeValue {
4
+ fullscreen: boolean;
5
+ setFullscreen: (next: boolean) => void;
6
+ toggleFullscreen: () => void;
7
+ }
8
+ export declare const FullscreenModeContext: import("react").Context<FullscreenModeValue>;
9
+ export declare function useFullscreenMode(): FullscreenModeValue;
@@ -0,0 +1,101 @@
1
+ import type { RefObject } from 'react';
2
+ import type { ScrollBoxHandle } from '../../render/components/ScrollBox.js';
3
+ import type { DOMElement } from '../../render/dom.js';
4
+ export type VirtualScrollResult = {
5
+ /** [startIndex, endIndex) half-open slice of items to render. */
6
+ range: readonly [number, number];
7
+ /** Height (rows) of spacer before the first rendered item. */
8
+ topSpacer: number;
9
+ /** Height (rows) of spacer after the last rendered item. */
10
+ bottomSpacer: number;
11
+ /**
12
+ * Callback ref factory. Attach `measureRef(itemKey)` to each rendered
13
+ * item's root Box; after Yoga layout, the computed height is cached.
14
+ */
15
+ measureRef: (key: string) => (el: DOMElement | null) => void;
16
+ /**
17
+ * Attach to the topSpacer Box. Its Yoga computedTop IS listOrigin
18
+ * (first child of the virtualized region, so its top = cumulative
19
+ * height of everything rendered before the list in the ScrollBox).
20
+ * Drift-free: no subtraction of offsets, no dependence on item
21
+ * heights that change between renders (tmux resize).
22
+ */
23
+ spacerRef: RefObject<DOMElement | null>;
24
+ /**
25
+ * Cumulative y-offset of each item in list-wrapper coords (NOT scrollbox
26
+ * coords — logo/siblings before this list shift the origin).
27
+ * offsets[i] = rows above item i; offsets[n] = totalHeight.
28
+ * Recomputed every render — don't memo on identity.
29
+ */
30
+ offsets: ArrayLike<number>;
31
+ /**
32
+ * Read Yoga computedTop for item at index. Returns -1 if the item isn't
33
+ * mounted or hasn't been laid out. Item Boxes are direct Yoga children
34
+ * of the ScrollBox content wrapper (fragments collapse in the Ink DOM),
35
+ * so this is content-wrapper-relative — same coordinate space as
36
+ * scrollTop. Yoga layout is scroll-independent (translation happens
37
+ * later in renderNodeToOutput), so positions stay valid across scrolls
38
+ * without waiting for Ink to re-render. StickyTracker walks the mount
39
+ * range with this to find the viewport boundary at per-scroll-tick
40
+ * granularity (finer than the 40-row quantum this hook re-renders at).
41
+ */
42
+ getItemTop: (index: number) => number;
43
+ /**
44
+ * Get the mounted DOMElement for item at index, or null. For
45
+ * ScrollBox.scrollToElement — anchoring by element ref defers the
46
+ * Yoga-position read to render time (deterministic; no throttle race).
47
+ */
48
+ getItemElement: (index: number) => DOMElement | null;
49
+ /** Measured Yoga height. undefined = not yet measured; 0 = rendered nothing. */
50
+ getItemHeight: (index: number) => number | undefined;
51
+ /**
52
+ * Scroll so item `i` is in the mounted range. Sets scrollTop =
53
+ * offsets[i] + listOrigin. The range logic finds start from
54
+ * scrollTop vs offsets[] — BOTH use the same offsets value, so they
55
+ * agree by construction regardless of whether offsets[i] is the
56
+ * "true" position. Item i mounts; its screen position may be off by
57
+ * a few-dozen rows (overscan-worth of estimate drift), but it's in
58
+ * the DOM. Follow with getItemTop(i) for the precise position.
59
+ */
60
+ scrollToIndex: (i: number) => void;
61
+ };
62
+ /**
63
+ * React-level virtualization for items inside a ScrollBox.
64
+ *
65
+ * The ScrollBox already does Ink-output-level viewport culling
66
+ * (render-node-to-output.ts:617 skips children outside the visible window),
67
+ * but all React fibers + Yoga nodes are still allocated. At ~250 KB RSS per
68
+ * MessageRow, a 1000-message session costs ~250 MB of grow-only memory
69
+ * (Ink screen buffer, WASM linear memory, JSC page retention all grow-only).
70
+ *
71
+ * This hook mounts only items in viewport + overscan. Spacer boxes hold the
72
+ * scroll height constant for the rest at O(1) fiber cost each.
73
+ *
74
+ * Height estimation: fixed DEFAULT_ESTIMATE for unmeasured items, replaced
75
+ * by real Yoga heights after first layout. No scroll anchoring — overscan
76
+ * absorbs estimate errors. If drift is noticeable in practice, anchoring
77
+ * (scrollBy(delta) when topSpacer changes) is a straightforward followup.
78
+ *
79
+ * stickyScroll caveat: render-node-to-output.ts:450 sets scrollTop=maxScroll
80
+ * during Ink's render phase, which does NOT fire ScrollBox.subscribe. The
81
+ * at-bottom check below handles this — when pinned to the bottom, we render
82
+ * the last N items regardless of what scrollTop claims.
83
+ */
84
+ export declare function useVirtualScroll(scrollRef: RefObject<ScrollBoxHandle | null>, itemKeys: readonly string[],
85
+ /**
86
+ * Terminal column count. On change, cached heights are stale (text
87
+ * rewraps) — SCALED by oldCols/newCols rather than cleared. Clearing
88
+ * made the pessimistic coverage back-walk mount ~190 items (every
89
+ * uncached item → PESSIMISTIC_HEIGHT=1 → walk 190 to reach
90
+ * viewport+2×overscan). Each fresh mount runs marked.lexer + syntax
91
+ * highlighting ≈ 3ms; ~600ms React reconcile on first resize with a
92
+ * long conversation. Scaling keeps heightCache populated → back-walk
93
+ * uses real-ish heights → mount range stays tight. Scaled estimates
94
+ * are overwritten by real Yoga heights on next useLayoutEffect.
95
+ *
96
+ * Scaled heights are close enough that the black-screen-on-widen bug
97
+ * (inflated pre-resize offsets overshoot post-resize scrollTop → end
98
+ * loop stops short of tail) doesn't trigger: ratio<1 on widen scales
99
+ * heights DOWN, keeping offsets roughly aligned with post-resize Yoga.
100
+ */
101
+ columns: number): VirtualScrollResult;
@@ -0,0 +1,22 @@
1
+ export declare const PERF_ENABLED: boolean;
2
+ export declare function startEventLoopMonitor(intervalMs?: number, warnThresholdMs?: number): void;
3
+ export declare function stopEventLoopMonitor(): void;
4
+ export declare function recordStreamEvent(type: string, agentId: string | undefined): void;
5
+ export declare function startStreamRateReporter(windowMs?: number): void;
6
+ export declare function stopStreamRateReporter(): void;
7
+ export declare function recordAppRender(): void;
8
+ export declare function startAppRenderReporter(windowMs?: number): void;
9
+ export declare function stopAppRenderReporter(): void;
10
+ export declare function recordInkFrame(event: {
11
+ durationMs: number;
12
+ phases: {
13
+ diff: number;
14
+ write: number;
15
+ yoga: number;
16
+ patches: number;
17
+ };
18
+ }): void;
19
+ export declare function startInkFrameReporter(windowMs?: number): void;
20
+ export declare function stopInkFrameReporter(): void;
21
+ export declare function startAllPerfProbes(): void;
22
+ export declare function stopAllPerfProbes(): void;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * QueryGuard — synchronous state machine for "is a query in flight".
3
+ * Replaces React useState for isRunning. Subscribers (via useSyncExternalStore)
4
+ * see state changes immediately, bypassing React 18 batching that otherwise
5
+ * opens a 1–10 ms window for double-submit / dead-click bugs.
6
+ *
7
+ * States:
8
+ * idle — no query active
9
+ * reserved — processUserInput started its sync prep but hasn't created the AbortController yet
10
+ * running — AbortController attached; query is in flight
11
+ *
12
+ * The only writers are reserve/tryStart/cancelReservation/end/forceEnd.
13
+ * No setter is exposed.
14
+ */
15
+ export type QueryState = "idle" | "reserved" | "running";
16
+ export declare class QueryGuard {
17
+ private state;
18
+ private controller;
19
+ private listeners;
20
+ subscribe: (cb: () => void) => (() => void);
21
+ getSnapshot: () => boolean;
22
+ private notify;
23
+ /** Reserve a slot synchronously before the AbortController exists. */
24
+ reserve(): boolean;
25
+ /** Attach the AbortController. Must follow reserve(). */
26
+ tryStart(controller: AbortController): boolean;
27
+ /** Roll back reserve() when processUserInput threw before tryStart. */
28
+ cancelReservation(): void;
29
+ /** Normal completion — clean up without aborting (already finished). */
30
+ end(): void;
31
+ /** Hard abort: abort the controller AND clean up. */
32
+ forceEnd(reason?: string): void;
33
+ /** Read the current controller's signal; null when idle/reserved. */
34
+ getSignal(): AbortSignal | null;
35
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * SliceAnchor — UUID-anchored cap for the flow-mode chat slice.
3
+ *
4
+ * Flow mode renders all entries through Ink's normal diff path (no <Static>),
5
+ * relying on the terminal's native scrollback for history. To keep yoga
6
+ * layout and the screen buffer bounded, we slice the rendered list to a
7
+ * fixed cap. The naive `entries.slice(-CAP)` shifts the head by one on every
8
+ * append — that means row 0 of prev and row 0 of next describe different
9
+ * content, and log-update's diff sees changes at scrollback rows it can't
10
+ * reach, forcing a fullResetSequence_CAUSES_FLICKER per turn.
11
+ *
12
+ * Fix: anchor the slice start to a specific entry id and only advance when
13
+ * (entries.length - start) exceeds CAP + STEP. Quantizing in STEP-sized
14
+ * chunks turns "flicker every append" into "flicker every STEP appends".
15
+ *
16
+ * The anchor stores BOTH id and idx — if the anchored id disappears (rare
17
+ * for chat entries since ids are stable, but defensive), we fall back to
18
+ * the stored idx clamped into range, instead of resetting to 0 which would
19
+ * try to render the full history at once.
20
+ *
21
+ * Mirrors Claude Code's computeSliceStart in src/components/Messages.tsx.
22
+ */
23
+ export declare const DEFAULT_CAP = 200;
24
+ export declare const DEFAULT_STEP = 50;
25
+ export type SliceAnchor = {
26
+ id: string;
27
+ idx: number;
28
+ } | null;
29
+ export interface AnchorRef {
30
+ current: SliceAnchor;
31
+ }
32
+ /**
33
+ * Returns the index at which the rendered slice should start. May mutate
34
+ * `anchorRef.current` to refresh the stored anchor when the window advances
35
+ * or the anchored id has shifted in the entries array.
36
+ *
37
+ * @param entries - The full, post-filter chat entries (must expose `.id`).
38
+ * @param anchorRef - Mutable container for the persistent anchor.
39
+ * @param cap - Soft maximum entries to render. Window advances past this.
40
+ * @param step - Hysteresis: only advance when length exceeds cap + step.
41
+ */
42
+ export declare function computeSliceStart(entries: ReadonlyArray<{
43
+ id: string;
44
+ }>, anchorRef: AnchorRef, cap?: number, step?: number): number;
@@ -48,21 +48,36 @@ type ChatEntryData = {
48
48
  } | {
49
49
  type: "agent_start";
50
50
  agentId: string;
51
+ name?: string;
51
52
  description: string;
52
53
  } | {
53
54
  type: "agent_end";
54
55
  agentId: string;
56
+ name?: string;
55
57
  description: string;
58
+ text?: string;
56
59
  error?: string;
57
60
  };
58
61
  /** Specialized error kinds for smart rendering. */
59
62
  export type ErrorKind = "rate_limit" | "context_limit" | "invalid_api_key" | "api_timeout" | "credit_balance" | "generic";
60
63
  /** System message subtypes. */
61
- export type SystemSubtype = "compact_boundary" | "memory_saved" | "turn_duration" | "info";
64
+ export type SystemSubtype = "compact_boundary" | "memory_saved" | "turn_duration" | "info" | "bg_agent_notification";
62
65
  export type ChatEntry = ChatEntryData & {
63
66
  id: string;
64
67
  };
65
68
  export declare function createEntry(data: ChatEntryData): ChatEntry;
69
+ /**
70
+ * "Finalized" = entry's content is stable; further stream events will not
71
+ * mutate it in place. Used by VirtualMessageList flow mode to route entries
72
+ * into <Static> (append-only, never re-rendered) vs. the active region (kept
73
+ * in React tree and re-rendered on each delta).
74
+ *
75
+ * assistant_text → final once streaming flips to false
76
+ * tool_running → transient, always replaced by tool_result; not final
77
+ * thinking → transient, filtered out on next tool_use_start
78
+ * everything else → final on append
79
+ */
80
+ export declare function isEntryFinalized(e: ChatEntry): boolean;
66
81
  type Listener = () => void;
67
82
  declare class ChatStore {
68
83
  private entries;
@@ -74,6 +89,16 @@ declare class ChatStore {
74
89
  append(data: ChatEntryData): void;
75
90
  /** Functional update — same API as setState(fn). */
76
91
  update(fn: (prev: ChatEntry[]) => ChatEntry[]): void;
92
+ /**
93
+ * Commit the in-flight streaming assistant entry as a final (non-streaming)
94
+ * message, optionally appending a suffix (e.g., "[Request interrupted by user]").
95
+ * Drops any thinking entries — model scratch, no user value after interruption.
96
+ * No-op if there is no streaming entry.
97
+ *
98
+ * Empty-text streaming entry is finalized in place (streaming=false) without
99
+ * suffix — keeps the store transition consistent without persisting noise.
100
+ */
101
+ commitInterruptedStreaming(suffix: string): void;
77
102
  clear(): void;
78
103
  subscribe(listener: Listener): () => void;
79
104
  private notify;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cjhyy/code-shell",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Code Shell — general-purpose agent orchestration framework with a terminal coding preset",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -44,10 +44,13 @@
44
44
  "build": "bun run sync-models && bun run tsup && bun run build:dts",
45
45
  "build:dts": "tsc --emitDeclarationOnly --declaration --outDir dist || true",
46
46
  "sync-models": "bun run scripts/sync-models.ts",
47
- "dev": "CODE_SHELL_DEV=1 bun run src/cli/main.ts",
47
+ "dev": "CODE_SHELL_DEV=1 CODESHELL_UI_PERF=1 bun run src/cli/main.ts",
48
+ "dev:bigtranscript": "bun run scripts/render-bigtranscript-dev.ts",
48
49
  "test": "bun test",
50
+ "bench:render": "bun run bench/render-tail.bench.ts && bun run bench/render-streaming.bench.ts && bun run bench/render-spinner.bench.ts && bun run bench/render-wheel.bench.ts",
49
51
  "test:watch": "bun test --watch",
50
52
  "lint": "eslint src/",
53
+ "lint:engine-bypass": "bash scripts/check-no-engine-bypass.sh",
51
54
  "format": "prettier --write 'src/**/*.ts'",
52
55
  "typecheck": "bun run tsc --noEmit",
53
56
  "prepublishOnly": "bun run build"
@@ -58,7 +61,6 @@
58
61
  "files": [
59
62
  "dist",
60
63
  "scripts/check-node.cjs",
61
- "skills-builtin",
62
64
  "README.md",
63
65
  "LICENSE",
64
66
  "CHANGELOG.md"
@@ -1 +0,0 @@
1
- export{a as AnthropicClient}from'./chunk-DEYFK6FP.js';import'./chunk-O5HFCH2U.js';import'./chunk-2EUIMKJA.js';import'./chunk-FU7AHZNN.js';import'./chunk-ZSAONDT6.js';import'./chunk-3QP5BQ3L.js';
@@ -1,3 +0,0 @@
1
- import {a as a$1}from'./chunk-MLCTC53I.js';import'./chunk-P6JSTIFM.js';import {c as c$1}from'./chunk-DTPABBSV.js';import {U,V,J,W,T as T$1,R as R$1,Q as Q$1}from'./chunk-2GJ7H5NC.js';export{W as formatArenaResultForSession}from'./chunk-2GJ7H5NC.js';import'./chunk-A4M2Y42L.js';import'./chunk-FU7AHZNN.js';import'./chunk-ZSAONDT6.js';import {h,a}from'./chunk-3QP5BQ3L.js';import l from'chalk';h();async function Q(e,s,a){let{models:n,mode:i,base:m,head:r,topic:t}=E(e),o=a?.output??(d=>console.log(d));if(!t){o(R());return}let f=j(s,n);if(f.length<2){o(L());return}let P=i?i.charAt(0).toUpperCase()+i.slice(1):"Auto";o(l.bold(`\u2550\u2550\u2550 Arena ${P} \u2550\u2550\u2550`)),o(l.dim(`Topic: ${t}`)),o(l.dim(`Models: ${f.map(d=>d.name).join(" vs ")}`));let x=a?.output?U(a.output):V,O=new J({participants:f,mode:i,enableContextTools:true,onProgress:x});try{let d=await O.run(t,{mode:i,base:m,head:r}),y=a?.outputMessage??a?.output;return y?y(W(d)):T$1(d),d}catch(d){o(l.red(`Arena failed: ${d.message}`));return}}a(Q,"runArenaReview");function T(e){let s=new a$1;try{let a=new c$1(process.cwd()).get();if(a.models?.length)for(let n of a.models)s.register({key:n.key,label:n.label,provider:n.provider??"",model:n.model,baseUrl:n.baseUrl??e.llm.baseUrl,apiKey:n.apiKey??e.llm.apiKey,maxOutputTokens:n.maxOutputTokens});}catch{}return s}a(T,"buildModelPool");function K(e,s,a){if(typeof e=="string"){let n=s.get(e);return n?{name:n.label??c(n.model),llm:{provider:n.provider,model:n.model,apiKey:n.apiKey??a.llm.apiKey,baseUrl:n.baseUrl??a.llm.baseUrl,temperature:.3,maxTokens:n.maxOutputTokens??R$1(n.model),enableStreaming:false}}:void 0}return {name:e.name,llm:{provider:e.provider??a.llm.provider,model:e.model,apiKey:e.apiKey??a.llm.apiKey,baseUrl:e.baseUrl??a.llm.baseUrl,temperature:.3,maxTokens:R$1(e.model),enableStreaming:false}}}a(K,"resolveOneParticipant");function j(e,s){let a=T(e);if(s)return s.split(",").map(r=>r.trim().toLowerCase()).map(r=>{let t=a.get(r);if(t)return {name:t.label??c(t.model),llm:{provider:t.provider,model:t.model,apiKey:t.apiKey??e.llm.apiKey,baseUrl:t.baseUrl??e.llm.baseUrl,temperature:.3,maxTokens:t.maxOutputTokens??R$1(t.model),enableStreaming:false}};let o=Q$1[r];return o?{name:c(o.model),llm:{provider:o.provider,model:o.model,apiKey:e.llm.apiKey,baseUrl:e.llm.baseUrl,temperature:.3,maxTokens:o.maxOutputTokens,enableStreaming:false}}:{name:c(r),llm:{...e.llm,model:r,enableStreaming:false}}});let n=new c$1(process.cwd()).get();if(n.arena?.participants?.length>=2){let m=n.arena.participants.map(r=>K(r,a,e)).filter(r=>r!==void 0);if(m.length>=2)return m}let i=[{name:c(e.llm.model),llm:{...e.llm,maxTokens:R$1(e.llm.model),enableStreaming:false}}];if(e.llm.baseUrl?.includes("openrouter")){let r=e.llm.model.includes("claude")?"openai/gpt-5.4":"anthropic/claude-opus-4.6";i.push({name:c(r),llm:{...e.llm,model:r,maxTokens:R$1(r),enableStreaming:false}});}return i}a(j,"resolveParticipants");var S=["review","discussion","planning"];function E(e){let s,a,n,i,m=[],r=e.split(/\s+/);for(let t=0;t<r.length;t++)if(r[t]==="--models"&&r[t+1])s=r[++t];else if(r[t]==="--mode"&&r[t+1]){let o=r[++t].toLowerCase();S.includes(o)?a=o:console.log(l.yellow(` Unknown mode "${o}", valid: ${S.join(", ")}`));}else r[t]==="--base"&&r[t+1]?n=r[++t]:r[t]==="--head"&&r[t+1]?i=r[++t]:m.push(r[t]);return {models:s,mode:a,base:n,head:i,topic:m.join(" ").trim()}}a(E,"parseFlags");function R(){return [l.bold("Arena \u2014 Multi-model collaborative analysis"),"",l.dim("Usage:")," /arena review the authentication module"," /arena --models claude,gpt4o is the error handling correct?"," /arena --mode planning design the new auth system"," /arena --mode discussion should we use REST or GraphQL"," /arena --base main review changes since main",' code-shell arena "review my latest changes"',"",`${l.dim("Modes:")} review (default), discussion, planning`,l.dim("Mode is auto-detected from the topic if --mode is not specified."),"",l.dim("Flags:")," --base <ref> Base branch/ref for comparison"," --head <ref> Head branch/ref (default: HEAD)","",`${l.dim("Available model presets:")} ${Object.keys(Q$1).join(", ")}`].join(`
2
- `)}a(R,"formatUsage");function L(e){return [l.red("Arena needs at least 2 models."),"","Use --models to specify:"," /arena --models claude,gpt4o review the code","","Or configure in ~/.code-shell/settings.json:",' "arena": { "participants": [',' { "name": "Claude", "model": "anthropic/claude-sonnet-4-20250514" },',' { "name": "GPT-4o", "model": "openai/gpt-4o" }'," ]}"].join(`
3
- `)}a(L,"formatModelHelp");function c(e){return (e.split("/").pop()??e).replace(/-\d.*$/,"").split("-").map(a=>a.charAt(0).toUpperCase()+a.slice(1)).join(" ")}a(c,"modelDisplayName");export{Q as runArenaReview};