@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.
- package/CHANGELOG.md +108 -0
- package/README.md +35 -2
- package/dist/anthropic-DJXVKFMJ.js +1 -0
- package/dist/arena/index.js +1 -1
- package/dist/arena-MRBATZFD.js +3 -0
- package/dist/{chunk-EU3J7TBP.js → chunk-5QIVXFCT.js} +1 -1
- package/dist/{chunk-XXCZE2U2.js → chunk-AFMP73YP.js} +1 -1
- package/dist/{chunk-L7B2ORID.js → chunk-D3TVASD2.js} +2 -2
- package/dist/chunk-DI26OUIU.js +2 -0
- package/dist/chunk-FZXY554D.js +2 -0
- package/dist/chunk-GGVAOPI7.js +1 -0
- package/dist/chunk-GKAQH2DG.js +2 -0
- package/dist/chunk-GYNO4R7R.js +2 -0
- package/dist/{chunk-RERBX75D.js → chunk-K4HILJ6G.js} +3 -3
- package/dist/{chunk-2EUIMKJA.js → chunk-OWBYLMDD.js} +1 -1
- package/dist/chunk-SKIX2M5Z.js +4 -0
- package/dist/chunk-SV7H4FKT.js +3 -0
- package/dist/{chunk-2GJ7H5NC.js → chunk-WONQHE4P.js} +40 -40
- package/dist/{chunk-DEYFK6FP.js → chunk-X2S7ASQ2.js} +1 -1
- package/dist/chunk-Z5HKHAO7.js +336 -0
- package/dist/cli/commands/builtin/plugin-commands-registration.d.ts +12 -0
- package/dist/cli/commands/builtin/plugin-handler.d.ts +6 -0
- package/dist/cli/commands/registry.d.ts +9 -2
- package/dist/cli/main.js +138 -122
- package/dist/client-base-GSFXPP2Y.js +1 -0
- package/dist/devtools-FBX4HVVY.js +1 -0
- package/dist/engine/engine.d.ts +29 -0
- package/dist/engine/turn-loop.d.ts +25 -0
- package/dist/hooks/events.d.ts +84 -5
- package/dist/hooks/inject.d.ts +19 -0
- package/dist/hooks/shell-runner.d.ts +45 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -1
- package/dist/llm/api-key-sanitize.d.ts +22 -0
- package/dist/llm/providers/openai.d.ts +23 -0
- package/dist/llm/retry.d.ts +12 -0
- package/dist/llm/stream-watchdog.d.ts +42 -0
- package/dist/logging/logger.d.ts +14 -0
- package/dist/manager-4XKLZHKE.js +1 -0
- package/dist/mcp-manager-3JGHEJO3.js +1 -0
- package/dist/openai-OQ6LLN5J.js +1 -0
- package/dist/{openrouter-models-GZVCRWD2.js → openrouter-models-ZT4R2AE3.js} +1 -1
- package/dist/openrouter-sync-ARMVWY7S.js +1 -0
- package/dist/plugin-handler-GJISMH5Q.js +11 -0
- package/dist/plugins/gitOps.d.ts +23 -0
- package/dist/plugins/installedPlugins.d.ts +15 -0
- package/dist/plugins/knownMarketplaces.d.ts +10 -0
- package/dist/plugins/loadPluginHooks.d.ts +52 -0
- package/dist/plugins/marketplaceManager.d.ts +49 -0
- package/dist/plugins/parseMarketplaceInput.d.ts +16 -0
- package/dist/plugins/pluginCommandHook.d.ts +54 -0
- package/dist/plugins/pluginCommandsLoader.d.ts +25 -0
- package/dist/plugins/pluginInstaller.d.ts +36 -0
- package/dist/plugins/schemas.d.ts +9 -0
- package/dist/plugins/types.d.ts +73 -2
- package/dist/plugins/varRewrite.d.ts +42 -0
- package/dist/product/index.js +1 -1
- package/dist/protocol/helpers.d.ts +42 -0
- package/dist/render/clearTerminal.d.ts +9 -3
- package/dist/render/components/App.d.ts +1 -0
- package/dist/render/components/Static.d.ts +23 -0
- package/dist/render/devtools.d.ts +8 -4
- package/dist/render/dom.d.ts +2 -1
- package/dist/render/frame.d.ts +1 -0
- package/dist/render/index.d.ts +10 -0
- package/dist/render/ink.d.ts +24 -2
- package/dist/render/log-update.d.ts +12 -1
- package/dist/render/selection.d.ts +1 -1
- package/dist/run/index.js +1 -1
- package/dist/services/index.d.ts +1 -0
- package/dist/services/memory-orchestrator.d.ts +39 -0
- package/dist/settings/schema.d.ts +93 -6
- package/dist/skills/frontmatter.d.ts +13 -0
- package/dist/skills/index.d.ts +4 -4
- package/dist/skills/scanner.d.ts +11 -21
- package/dist/tool-summary-HJX6RHG4.js +2 -0
- package/dist/tool-system/builtin/agent-notifications.d.ts +55 -0
- package/dist/tool-system/builtin/agent-registry.d.ts +35 -4
- package/dist/tool-system/builtin/agent-transcript-translator.d.ts +22 -0
- package/dist/tool-system/builtin/skill-prompt.d.ts +7 -0
- package/dist/tool-system/builtin/skill.d.ts +3 -1
- package/dist/tool-system/context.d.ts +23 -0
- package/dist/tool-system/executor.d.ts +1 -1
- package/dist/tool-system/permission.d.ts +1 -1
- package/dist/types.d.ts +35 -0
- package/dist/ui/components/AgentBlock.d.ts +5 -2
- package/dist/ui/components/AgentDock.d.ts +36 -0
- package/dist/ui/components/CommandInput.d.ts +12 -1
- package/dist/ui/components/MessageRow.d.ts +54 -0
- package/dist/ui/components/SpinnerWithVerb.d.ts +2 -1
- package/dist/ui/components/TextInput.d.ts +9 -1
- package/dist/ui/components/VirtualMessageList.d.ts +30 -11
- package/dist/ui/dev-seed.d.ts +1 -0
- package/dist/ui/fullscreen-mode.d.ts +9 -0
- package/dist/ui/hooks/useVirtualScroll.d.ts +101 -0
- package/dist/ui/perf-probes.d.ts +22 -0
- package/dist/ui/query-guard.d.ts +35 -0
- package/dist/ui/slice-anchor.d.ts +44 -0
- package/dist/ui/store.d.ts +26 -1
- package/package.json +5 -3
- package/dist/anthropic-OVRWAZRD.js +0 -1
- package/dist/arena-64T2EQD4.js +0 -3
- package/dist/chunk-A4M2Y42L.js +0 -2
- package/dist/chunk-DTPABBSV.js +0 -2
- package/dist/chunk-MGP35GKD.js +0 -254
- package/dist/chunk-Q3RSX66I.js +0 -1
- package/dist/chunk-VKKU4LOM.js +0 -3
- package/dist/chunk-ZSAONDT6.js +0 -2
- package/dist/client-base-F5FCRSB3.js +0 -1
- package/dist/devtools-SGDUOOM4.js +0 -1
- package/dist/manager-J6FXEUEK.js +0 -1
- package/dist/mcp-manager-27EVBKRT.js +0 -1
- package/dist/openai-3QEADSYL.js +0 -1
- package/dist/openrouter-sync-WG4AG5NC.js +0 -1
- package/dist/plugins/loader.d.ts +0 -72
- package/dist/skills/matcher.d.ts +0 -21
- package/dist/tool-summary-V2B3MD5N.js +0 -2
- 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
|
-
|
|
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 —
|
|
2
|
+
* VirtualMessageList — ScrollBox + viewport-windowed chat rendering.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
11
|
-
*
|
|
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
|
|
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
|
|
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;
|
package/dist/ui/store.d.ts
CHANGED
|
@@ -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.
|
|
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';
|
package/dist/arena-64T2EQD4.js
DELETED
|
@@ -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};
|