@cjhyy/code-shell 0.1.7 → 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 (144) 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-2OD6VR4N.js +2 -0
  7. package/dist/{chunk-RP26WO4R.js → chunk-5QIVXFCT.js} +1 -1
  8. package/dist/{chunk-NFH3UQ3E.js → chunk-AFMP73YP.js} +1 -1
  9. package/dist/{chunk-XDOWC7EG.js → chunk-D3TVASD2.js} +2 -2
  10. package/dist/chunk-DI26OUIU.js +2 -0
  11. package/dist/chunk-FZXY554D.js +2 -0
  12. package/dist/chunk-GGVAOPI7.js +1 -0
  13. package/dist/chunk-GKAQH2DG.js +2 -0
  14. package/dist/chunk-GYNO4R7R.js +2 -0
  15. package/dist/{chunk-Q4O2HGWS.js → chunk-K4HILJ6G.js} +3 -3
  16. package/dist/chunk-M7VU4YVY.js +49 -0
  17. package/dist/chunk-MLCTC53I.js +1 -0
  18. package/dist/{chunk-HIJ2XXGX.js → chunk-OWBYLMDD.js} +1 -1
  19. package/dist/chunk-SKIX2M5Z.js +4 -0
  20. package/dist/chunk-SV7H4FKT.js +3 -0
  21. package/dist/{chunk-Y2NS6HPO.js → chunk-WONQHE4P.js} +41 -41
  22. package/dist/{chunk-WOP6MJKW.js → chunk-X2S7ASQ2.js} +1 -1
  23. package/dist/chunk-Z5HKHAO7.js +336 -0
  24. package/dist/cli/commands/builtin/plugin-commands-registration.d.ts +12 -0
  25. package/dist/cli/commands/builtin/plugin-handler.d.ts +6 -0
  26. package/dist/cli/commands/registry.d.ts +9 -2
  27. package/dist/cli/main.js +140 -124
  28. package/dist/cli/onboarding.d.ts +7 -2
  29. package/dist/client-base-GSFXPP2Y.js +1 -0
  30. package/dist/compaction-L3Y6NG4N.js +1 -0
  31. package/dist/context/compaction.d.ts +2 -1
  32. package/dist/data/static-catalogs.d.ts +25 -0
  33. package/dist/devtools-FBX4HVVY.js +1 -0
  34. package/dist/engine/engine.d.ts +45 -0
  35. package/dist/engine/turn-loop.d.ts +45 -0
  36. package/dist/hooks/events.d.ts +84 -5
  37. package/dist/hooks/inject.d.ts +19 -0
  38. package/dist/hooks/shell-runner.d.ts +45 -0
  39. package/dist/index.d.ts +4 -3
  40. package/dist/index.js +1 -1
  41. package/dist/llm/api-key-sanitize.d.ts +22 -0
  42. package/dist/llm/capabilities/index.d.ts +20 -0
  43. package/dist/llm/capabilities/rules.d.ts +26 -0
  44. package/dist/llm/capabilities/types.d.ts +108 -0
  45. package/dist/llm/model-pool.d.ts +7 -0
  46. package/dist/llm/provider-catalog.d.ts +2 -0
  47. package/dist/llm/provider-kinds.d.ts +1 -1
  48. package/dist/llm/providers/openai.d.ts +45 -0
  49. package/dist/llm/retry.d.ts +12 -0
  50. package/dist/llm/stream-watchdog.d.ts +42 -0
  51. package/dist/llm/types.d.ts +8 -0
  52. package/dist/logging/logger.d.ts +24 -2
  53. package/dist/logging/session-recorder.d.ts +16 -3
  54. package/dist/manager-4XKLZHKE.js +1 -0
  55. package/dist/mcp-manager-3JGHEJO3.js +1 -0
  56. package/dist/model-fetcher-X5W5EASG.js +1 -0
  57. package/dist/openai-OQ6LLN5J.js +1 -0
  58. package/dist/{openrouter-models-AM6M52D6.js → openrouter-models-ZT4R2AE3.js} +1 -1
  59. package/dist/openrouter-sync-ARMVWY7S.js +1 -0
  60. package/dist/plugin-handler-GJISMH5Q.js +11 -0
  61. package/dist/plugins/gitOps.d.ts +23 -0
  62. package/dist/plugins/installedPlugins.d.ts +15 -0
  63. package/dist/plugins/knownMarketplaces.d.ts +10 -0
  64. package/dist/plugins/loadPluginHooks.d.ts +52 -0
  65. package/dist/plugins/marketplaceManager.d.ts +49 -0
  66. package/dist/plugins/parseMarketplaceInput.d.ts +16 -0
  67. package/dist/plugins/pluginCommandHook.d.ts +54 -0
  68. package/dist/plugins/pluginCommandsLoader.d.ts +25 -0
  69. package/dist/plugins/pluginInstaller.d.ts +36 -0
  70. package/dist/plugins/schemas.d.ts +9 -0
  71. package/dist/plugins/types.d.ts +73 -2
  72. package/dist/plugins/varRewrite.d.ts +42 -0
  73. package/dist/product/index.js +1 -1
  74. package/dist/protocol/client.d.ts +1 -1
  75. package/dist/protocol/helpers.d.ts +42 -0
  76. package/dist/render/clearTerminal.d.ts +9 -3
  77. package/dist/render/components/App.d.ts +1 -0
  78. package/dist/render/components/Static.d.ts +23 -0
  79. package/dist/render/devtools.d.ts +8 -4
  80. package/dist/render/dom.d.ts +2 -1
  81. package/dist/render/frame.d.ts +1 -0
  82. package/dist/render/index.d.ts +10 -0
  83. package/dist/render/ink.d.ts +24 -2
  84. package/dist/render/log-update.d.ts +12 -1
  85. package/dist/render/selection.d.ts +1 -1
  86. package/dist/run/index.js +1 -1
  87. package/dist/{seatbelt-WBYROQT6.js → seatbelt-L54CNI2Y.js} +5 -5
  88. package/dist/services/index.d.ts +1 -0
  89. package/dist/services/memory-orchestrator.d.ts +39 -0
  90. package/dist/settings/schema.d.ts +168 -21
  91. package/dist/skills/frontmatter.d.ts +13 -0
  92. package/dist/skills/index.d.ts +4 -4
  93. package/dist/skills/scanner.d.ts +11 -21
  94. package/dist/tool-summary-HJX6RHG4.js +2 -0
  95. package/dist/tool-system/builtin/agent-notifications.d.ts +55 -0
  96. package/dist/tool-system/builtin/agent-registry.d.ts +35 -4
  97. package/dist/tool-system/builtin/agent-transcript-translator.d.ts +22 -0
  98. package/dist/tool-system/builtin/skill-prompt.d.ts +7 -0
  99. package/dist/tool-system/builtin/skill.d.ts +3 -1
  100. package/dist/tool-system/context.d.ts +23 -0
  101. package/dist/tool-system/executor.d.ts +5 -1
  102. package/dist/tool-system/permission.d.ts +1 -1
  103. package/dist/tool-system/sandbox/index.d.ts +7 -0
  104. package/dist/tool-system/task-guard.d.ts +30 -0
  105. package/dist/types.d.ts +48 -0
  106. package/dist/ui/components/AgentBlock.d.ts +5 -2
  107. package/dist/ui/components/AgentDock.d.ts +36 -0
  108. package/dist/ui/components/CommandInput.d.ts +12 -1
  109. package/dist/ui/components/MessageRow.d.ts +54 -0
  110. package/dist/ui/components/SpinnerWithVerb.d.ts +7 -1
  111. package/dist/ui/components/StatusLine.d.ts +5 -1
  112. package/dist/ui/components/TextInput.d.ts +9 -1
  113. package/dist/ui/components/VirtualMessageList.d.ts +30 -11
  114. package/dist/ui/dev-seed.d.ts +1 -0
  115. package/dist/ui/fullscreen-mode.d.ts +9 -0
  116. package/dist/ui/hooks/useVirtualScroll.d.ts +101 -0
  117. package/dist/ui/perf-probes.d.ts +22 -0
  118. package/dist/ui/query-guard.d.ts +35 -0
  119. package/dist/ui/slice-anchor.d.ts +44 -0
  120. package/dist/ui/store.d.ts +26 -1
  121. package/package.json +5 -3
  122. package/dist/anthropic-UBSB5AQH.js +0 -1
  123. package/dist/arena-OW55ZCHT.js +0 -3
  124. package/dist/chunk-2NS3JMNK.js +0 -1
  125. package/dist/chunk-B7K2OWKC.js +0 -2
  126. package/dist/chunk-EWWK56K6.js +0 -2
  127. package/dist/chunk-KCXSARAA.js +0 -1
  128. package/dist/chunk-NMBYBGC7.js +0 -250
  129. package/dist/chunk-NVAZJ5XS.js +0 -2
  130. package/dist/chunk-PGANL6F2.js +0 -3
  131. package/dist/chunk-UDPUTFQT.js +0 -2
  132. package/dist/chunk-W47Z7MGS.js +0 -33
  133. package/dist/client-base-6ZZJJYDY.js +0 -1
  134. package/dist/compaction-ECVQVOCT.js +0 -1
  135. package/dist/devtools-SGDUOOM4.js +0 -1
  136. package/dist/manager-C3TDN6MS.js +0 -1
  137. package/dist/mcp-manager-OP4XYV53.js +0 -1
  138. package/dist/model-fetcher-BJ64META.js +0 -1
  139. package/dist/openai-KEWQ54TM.js +0 -1
  140. package/dist/openrouter-sync-5JNBL3Z6.js +0 -1
  141. package/dist/plugins/loader.d.ts +0 -72
  142. package/dist/skills/matcher.d.ts +0 -21
  143. package/dist/tool-summary-5UE62VU4.js +0 -2
  144. package/skills-builtin/codeshell-help.md +0 -135
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Translate child Engine StreamEvents into ChatEntry-shaped objects so the
3
+ * AgentDock detail view (which reuses App.renderEntry) can show a sub-agent's
4
+ * conversation in the same visual language as the main feed.
5
+ *
6
+ * The transcript is the canonical record for one background sub-agent. State
7
+ * (current thinking entry, currently-streaming assistant_text, in-flight
8
+ * tool_start map) lives in a per-agent translator. Each translator instance
9
+ * appends ChatEntry-shaped objects via `append` and mutates them via `update`,
10
+ * mirroring how App.tsx's handleStreamEvent talks to chatStore but isolated to
11
+ * a single agent's transcript.
12
+ *
13
+ * The output shapes intentionally match `src/ui/store.ts#ChatEntryData` so the
14
+ * existing renderEntry switch picks them up without code changes.
15
+ */
16
+ import type { StreamEvent } from "../../types.js";
17
+ /**
18
+ * Create a transcript translator scoped to one background sub-agent. Returned
19
+ * function is a StreamCallback drop-in: feed it the child Engine's events and
20
+ * it maintains the agent's transcript in `asyncAgentRegistry`.
21
+ */
22
+ export declare function createTranscriptTranslator(agentId: string): (event: StreamEvent) => void;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Skill listing renderer for the system prompt. Mirrors Claude Code's split
3
+ * between scan/data layer (skills/scanner.ts) and render layer (this file),
4
+ * matching tools/SkillTool/prompt.ts in CC.
5
+ */
6
+ import type { SkillDefinition } from "../../skills/scanner.js";
7
+ export declare function buildSkillListing(skills: SkillDefinition[]): string;
@@ -1,5 +1,7 @@
1
1
  /**
2
- * SkillTool — invoke custom skills/plugins by name.
2
+ * SkillTool — load a skill's SKILL.md body and return it as the tool result.
3
+ * The scanner is the single source of truth; this tool never reads the disk
4
+ * directly. Matches Claude Code's tools/SkillTool/SkillTool.ts pattern.
3
5
  */
4
6
  import type { ToolDefinition } from "../../types.js";
5
7
  export declare const skillToolDef: ToolDefinition;
@@ -17,6 +17,7 @@ import type { ModelPool } from "../llm/model-pool.js";
17
17
  import type { ToolRegistry } from "./registry.js";
18
18
  import type { AgentPresetName } from "../preset/index.js";
19
19
  import type { SandboxBackend } from "./sandbox/index.js";
20
+ import type { HookRegistry } from "../hooks/registry.js";
20
21
  /** One choice in a multiple-choice AskUserQuestion. */
21
22
  export interface AskUserChoice {
22
23
  label: string;
@@ -55,6 +56,14 @@ export interface SubAgentSpawnRequest {
55
56
  prompt: string;
56
57
  maxTurns: number;
57
58
  signal: AbortSignal;
59
+ /**
60
+ * Per-call override for where the spawned child Engine's stream events go.
61
+ * Defaults to `spawner.parentStream` (the parent UI). Background sub-agents
62
+ * pass a transcriptSink here so per-event detail is captured in the agent's
63
+ * transcript instead of leaking into the main feed; the main feed still
64
+ * gets `agent_start` / `agent_end` markers via `spawner.parentStream`.
65
+ */
66
+ streamOverride?: StreamCallback;
58
67
  }
59
68
  export interface SubAgentSpawner {
60
69
  /** Run a sub-agent synchronously and return its text output. */
@@ -87,6 +96,13 @@ export interface ToolContext {
87
96
  askUser?: AskUserFn;
88
97
  /** Sub-agent spawner (Agent tool). Undefined → Agent tool unavailable. */
89
98
  subAgentSpawner?: SubAgentSpawner;
99
+ /**
100
+ * True when this Engine is itself a sub-agent. Set from EngineConfig.
101
+ * The Agent tool refuses to spawn when this is true — runtime check
102
+ * layered on top of the tool-list strip in Engine.spawn so a registry
103
+ * regression can't leak nested spawns.
104
+ */
105
+ isSubAgent?: boolean;
90
106
  /**
91
107
  * Active sandbox backend for the Bash tool. Undefined falls back to "off"
92
108
  * (plain spawn). Headless runs default to an OS-level sandbox; the REPL
@@ -95,6 +111,13 @@ export interface ToolContext {
95
111
  sandbox?: SandboxBackend;
96
112
  /** Optional cancellation signal for the whole turn. */
97
113
  signal?: AbortSignal;
114
+ /**
115
+ * Engine-owned HookRegistry. Tools that emit lifecycle hooks
116
+ * (notification, file_changed for non-Write/Edit paths, custom
117
+ * tool-defined events) use this. Undefined for legacy callers /
118
+ * standalone tests; tools must tolerate absence.
119
+ */
120
+ hooks?: HookRegistry;
98
121
  }
99
122
  /**
100
123
  * Per-Engine container that produces a fresh ToolContext on demand.
@@ -8,6 +8,7 @@ import { PermissionClassifier } from "./permission.js";
8
8
  import { logger as rootLogger } from "../logging/logger.js";
9
9
  import type { ToolContext } from "./context.js";
10
10
  import type { InvestigationGuard } from "./investigation-guard.js";
11
+ import type { TaskGuard } from "./task-guard.js";
11
12
  type Logger = typeof rootLogger;
12
13
  export declare class ToolExecutor {
13
14
  private readonly registry;
@@ -23,9 +24,12 @@ export declare class ToolExecutor {
23
24
  */
24
25
  private log;
25
26
  private guard?;
27
+ private taskGuard?;
26
28
  constructor(registry: ToolRegistry, permission: PermissionClassifier, hooks: HookRegistry);
27
29
  setInvestigationGuard(guard: InvestigationGuard | undefined): void;
28
30
  getInvestigationGuard(): InvestigationGuard | undefined;
31
+ setTaskGuard(guard: TaskGuard | undefined): void;
32
+ getTaskGuard(): TaskGuard | undefined;
29
33
  /** Set the abort signal for cascading cancellation. */
30
34
  setSignal(signal?: AbortSignal): void;
31
35
  /** Set the per-Engine ToolContext (askUser, llmConfig, modelPool, etc.). */
@@ -34,7 +38,7 @@ export declare class ToolExecutor {
34
38
  setLogger(log: Logger): void;
35
39
  /** Check if a tool is safe for concurrent execution (read-only). */
36
40
  isConcurrencySafe(toolName: string): boolean;
37
- executeSingle(call: ToolCall): Promise<ToolResult>;
41
+ executeSingle(callIn: ToolCall): Promise<ToolResult>;
38
42
  private isReadOnlyBashCommand;
39
43
  executeAll(calls: ToolCall[]): Promise<ToolResult[]>;
40
44
  /**
@@ -88,7 +88,7 @@ export declare class PermissionClassifier {
88
88
  reconfigure(mode: PermissionMode, approvalBackend: ApprovalBackend, rules?: PermissionRule[]): void;
89
89
  getMode(): PermissionMode;
90
90
  classify(toolName: string, args: Record<string, unknown>): PermissionDecision;
91
- handleAsk(toolName: string, args: Record<string, unknown>): Promise<boolean>;
91
+ handleAsk(toolName: string, args: Record<string, unknown>, reason?: string): Promise<boolean>;
92
92
  /** Get denial warning message if the model keeps getting denied. */
93
93
  getDenialWarning(toolName: string): string | undefined;
94
94
  private matchesRule;
@@ -38,6 +38,13 @@ export interface SandboxBackend {
38
38
  }): {
39
39
  file: string;
40
40
  args: string[];
41
+ /**
42
+ * Optional callback the Bash tool calls after the child process exits.
43
+ * Backends that allocate per-command resources (e.g. seatbelt writes a
44
+ * profile file to a fresh temp dir) hang their cleanup here so a long
45
+ * session doesn't leak hundreds of temp dirs in /tmp.
46
+ */
47
+ cleanup?: () => void;
41
48
  };
42
49
  /**
43
50
  * If `stderr` looks like a sandbox denial, return a short hint string that
@@ -0,0 +1,30 @@
1
+ /**
2
+ * TaskGuard — nudges the model to update stale in_progress tasks.
3
+ *
4
+ * Background: TaskCreate is sticky in the model's working memory only for the
5
+ * first few turns. After enough intervening tool_results (Read/Edit/Bash) the
6
+ * "I have an open task" signal gets diluted and the model finishes its work
7
+ * without ever calling TaskUpdate(status="completed") — so the UI spinner runs
8
+ * forever on a task that the model considers done.
9
+ *
10
+ * Same shape as InvestigationGuard.turnEnded: turn-loop polls at the end of
11
+ * each turn; if anything is in_progress and hasn't been touched in N turns,
12
+ * we emit a <system-reminder> that lands at the top of the next turn.
13
+ *
14
+ * Re-nag policy: don't fire more than once per (task, threshold) pair. A task
15
+ * stale at turn 5 fires once; if the model still ignores it, we wait for it
16
+ * to cross the next threshold (8, 11, …) before firing again. This keeps the
17
+ * reminder from drowning out everything else when the model is genuinely
18
+ * stuck on something unrelated.
19
+ */
20
+ import type { Task } from "./builtin/task.js";
21
+ export declare class TaskGuard {
22
+ private taskListSource;
23
+ /** Last turn we nagged about a given task id. */
24
+ private lastNagTurn;
25
+ /** Turn the task was first observed in_progress (model-time, not wall-clock). */
26
+ private inProgressSince;
27
+ constructor(taskListSource: () => Task[]);
28
+ turnEnded(turnNumber: number): string | undefined;
29
+ reset(): void;
30
+ }
package/dist/types.d.ts CHANGED
@@ -217,11 +217,14 @@ export type StreamEvent = {
217
217
  } | {
218
218
  type: "agent_start";
219
219
  agentId: string;
220
+ name?: string;
220
221
  description: string;
221
222
  } | {
222
223
  type: "agent_end";
223
224
  agentId: string;
225
+ name?: string;
224
226
  description: string;
227
+ text?: string;
225
228
  error?: string;
226
229
  } | {
227
230
  type: "tool_summary";
@@ -249,6 +252,19 @@ export interface LLMConfig {
249
252
  timeout?: number;
250
253
  retryMaxAttempts?: number;
251
254
  enableStreaming?: boolean;
255
+ /**
256
+ * Default thinking-mode for DeepSeek V4. Per-call `options.thinking`
257
+ * (e.g. summarize sub-calls) overrides this. Ignored on other providers.
258
+ */
259
+ thinking?: "enabled" | "disabled";
260
+ /**
261
+ * The provider's `kind` (deepseek/openai/zai/openrouter/...). Used by
262
+ * the capability layer to look up per-(kind, model) request-shape rules
263
+ * (token-limit field, rejected sampling params, thinking shape, etc.).
264
+ * Distinct from `provider` above, which is the protocol family
265
+ * ("openai" or "anthropic") that picks which client class to use.
266
+ */
267
+ providerKind?: string;
252
268
  }
253
269
  export interface LLMResponse {
254
270
  text: string;
@@ -275,6 +291,32 @@ export interface MCPServerConfig {
275
291
  transport?: MCPTransport;
276
292
  headers?: Record<string, string>;
277
293
  }
294
+ /**
295
+ * Shell-hook configuration entry from settings.json. Each entry binds
296
+ * a hook event to a shell command. The command runs as a child process
297
+ * on every emit (filtered by `matcher` when present), receives ctx
298
+ * JSON on stdin, and returns a HookResult JSON on stdout. See
299
+ * src/hooks/shell-runner.ts for the protocol spec.
300
+ */
301
+ export interface SettingsHookConfig {
302
+ event: string;
303
+ command: string;
304
+ /**
305
+ * Optional regex (matched with new RegExp(matcher).test(toolName)) to
306
+ * filter which tools fire this hook. Currently honored for
307
+ * pre_tool_use / post_tool_use / on_tool_start / on_tool_end /
308
+ * on_permission_check / file_changed. Ignored for events without a
309
+ * meaningful toolName (session/turn/prompt/notification/compact).
310
+ */
311
+ matcher?: string;
312
+ /** Per-hook timeout in milliseconds. Defaults to 60_000. */
313
+ timeout_ms?: number;
314
+ /**
315
+ * Optional working directory for the spawned command. Defaults to
316
+ * Engine.cwd. Useful for hooks that need to run in a sibling repo.
317
+ */
318
+ cwd?: string;
319
+ }
278
320
  export interface Settings {
279
321
  model: {
280
322
  provider: string;
@@ -305,4 +347,10 @@ export interface Settings {
305
347
  output: {
306
348
  format: "text" | "json" | "jsonl" | "stream-json";
307
349
  };
350
+ /**
351
+ * Optional shell-hook entries. Engine reads on construction and
352
+ * registers a wrapper handler per entry that spawns the command and
353
+ * parses the HookResult. Empty / missing = no shell hooks.
354
+ */
355
+ hooks?: SettingsHookConfig[];
308
356
  }
@@ -11,17 +11,20 @@
11
11
  */
12
12
  import React from "react";
13
13
  interface AgentBlockStartProps {
14
+ name?: string;
14
15
  description: string;
15
16
  running?: boolean;
16
17
  isLast?: boolean;
17
18
  }
18
- export declare function AgentBlockStart({ description, running, isLast }: AgentBlockStartProps): import("react/jsx-runtime").JSX.Element;
19
+ export declare function AgentBlockStart({ name, description, running, isLast }: AgentBlockStartProps): import("react/jsx-runtime").JSX.Element;
19
20
  interface AgentBlockEndProps {
21
+ name?: string;
20
22
  description: string;
23
+ text?: string;
21
24
  error?: string;
22
25
  isLast?: boolean;
23
26
  }
24
- export declare function AgentBlockEnd({ description, error, isLast }: AgentBlockEndProps): import("react/jsx-runtime").JSX.Element;
27
+ export declare function AgentBlockEnd({ name, description, text, error, isLast }: AgentBlockEndProps): import("react/jsx-runtime").JSX.Element;
25
28
  /**
26
29
  * Tree line prefix for nested content under an agent.
27
30
  */
@@ -0,0 +1,36 @@
1
+ import React from "react";
2
+ import { type AsyncAgentEntry } from "../../tool-system/builtin/agent-registry.js";
3
+ export declare const MAX_VISIBLE = 5;
4
+ export type DockViewMode = {
5
+ kind: "main";
6
+ } | {
7
+ kind: "agent";
8
+ agentId: string;
9
+ };
10
+ export interface AgentDockProps {
11
+ viewMode: DockViewMode;
12
+ /** null = dock is not the keyboard target; integer = focused row index. */
13
+ focusedIndex: number | null;
14
+ }
15
+ /**
16
+ * AgentDock — vertical list of running and recently-finished sub-agents,
17
+ * pinned at the very bottom of the UI. Updates elapsed text once per
18
+ * second; redraws are local to this subtree (no app-wide re-render).
19
+ *
20
+ * See docs/superpowers/specs/2026-05-18-subagent-dock-revisions-design.md.
21
+ */
22
+ export declare function AgentDock({ viewMode, focusedIndex, }: AgentDockProps): React.ReactElement | null;
23
+ /**
24
+ * Filter the registry snapshot down to rows the dock should show:
25
+ * running agents + recently-finished agents still inside the fade window.
26
+ * Successful completions are dropped immediately — their final text already
27
+ * surfaced via agent_end in the main feed, so the dock row is redundant.
28
+ * Failures and cancellations linger so the user can investigate.
29
+ *
30
+ * Exported so App.tsx's keyboard handler shares the same predicate.
31
+ */
32
+ export declare function getVisibleAgents(all: AsyncAgentEntry[], now: number): AsyncAgentEntry[];
33
+ /**
34
+ * Format an elapsed-millisecond duration as "23s" / "4m 23s" / "1h 4m 23s".
35
+ */
36
+ export declare function formatElapsed(ms: number): string;
@@ -1,6 +1,10 @@
1
1
  interface CommandDef {
2
2
  name: string;
3
3
  description: string;
4
+ /** Optional usage string like "/fullscreen [on|off|toggle]" — the
5
+ * bracketed portion is rendered dim after the command name once the
6
+ * user has finished typing it. */
7
+ usage?: string;
4
8
  }
5
9
  interface CommandInputProps {
6
10
  value: string;
@@ -8,6 +12,13 @@ interface CommandInputProps {
8
12
  onSubmit: (value: string) => void;
9
13
  commands: CommandDef[];
10
14
  placeholder?: string;
15
+ /** Fired when ↓ is pressed and input is empty and history is exhausted.
16
+ * Used by App.tsx to move keyboard focus into the AgentDock. */
17
+ onArrowOut?: (direction: "down") => void;
18
+ /** When true, skip ALL key handling. Used while another part of the UI
19
+ * (e.g. the AgentDock) owns the keyboard. The text input still renders;
20
+ * only the useInput body short-circuits. */
21
+ disabled?: boolean;
11
22
  }
12
- export declare function CommandInput({ value, onChange, onSubmit, commands, placeholder, }: CommandInputProps): import("react/jsx-runtime").JSX.Element;
23
+ export declare function CommandInput({ value, onChange, onSubmit, commands, placeholder, onArrowOut, disabled, }: CommandInputProps): import("react/jsx-runtime").JSX.Element;
13
24
  export {};
@@ -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 {};
@@ -5,9 +5,15 @@ import { type MutableRefObject } from "react";
5
5
  export type SpinnerMode = "responding" | "tool-use" | "thinking";
6
6
  interface SpinnerWithVerbProps {
7
7
  mode: SpinnerMode;
8
+ /**
9
+ * Per-turn streamed token count — used purely as a "still alive" signal so
10
+ * the user sees activity. NOT a measure of context size; that's in the
11
+ * StatusLine ctx bar.
12
+ */
8
13
  streamingTokensRef?: MutableRefObject<number>;
9
14
  runStartRef?: MutableRefObject<number>;
10
15
  thinkingContent?: string;
11
16
  }
12
- 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>;
13
19
  export {};
@@ -12,7 +12,11 @@ interface StatusLineProps {
12
12
  tokens: number;
13
13
  cost: number;
14
14
  sessionId?: string;
15
- /** Last known prompt tokens from completed turn. Live streaming is added on top. */
15
+ /**
16
+ * Authoritative live context size. The engine emits `usage_update` at every
17
+ * message-array mutation (post-tool-result, post-compact, post-LLM), so this
18
+ * tracks the real prompt token count without UI-side accumulation.
19
+ */
16
20
  baseContextTokens?: number;
17
21
  maxContextTokens?: number;
18
22
  gitBranch?: string;
@@ -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;