wave-code 1.1.0 → 1.1.2

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.
@@ -5,6 +5,7 @@ import React, {
5
5
  useRef,
6
6
  useEffect,
7
7
  useState,
8
+ useMemo,
8
9
  } from "react";
9
10
  import { useInput, useStdout } from "ink";
10
11
  import { useAppConfig } from "./useAppConfig.js";
@@ -15,6 +16,7 @@ import type {
15
16
  Task,
16
17
  SlashCommand,
17
18
  SubagentConfiguration,
19
+ SkillMetadata,
18
20
  PermissionDecision,
19
21
  PermissionMode,
20
22
  QueuedMessage,
@@ -100,6 +102,8 @@ export interface ChatContextType {
100
102
  hasSlashCommand: (commandId: string) => boolean;
101
103
  // Agent definitions (for /agents overlay)
102
104
  agentDefinitions: SubagentConfiguration[];
105
+ // Skill metadata (for /skills overlay)
106
+ skills: SkillMetadata[];
103
107
  // Permission functionality
104
108
  permissionMode: PermissionMode;
105
109
  setPermissionMode: (mode: PermissionMode) => void;
@@ -193,6 +197,200 @@ const snapshotMessage = (message: Message): Message => ({
193
197
  blocks: message.blocks.map((block) => ({ ...block })),
194
198
  });
195
199
 
200
+ /**
201
+ * Pure updater: appends a text-content delta to the target message's text
202
+ * block (creating it on first delta), applying the stage signal.
203
+ */
204
+ const applyContentDelta = (
205
+ prev: Message[],
206
+ params: StreamingUpdateParams,
207
+ ): Message[] => {
208
+ const { messageId, chunk, stage } = params;
209
+ return prev.map((m) => {
210
+ if (m.id !== messageId) return m;
211
+ const textBlockIndex = m.blocks.findIndex((b) => b.type === "text");
212
+ if (textBlockIndex === -1) {
213
+ return {
214
+ ...m,
215
+ blocks: [...m.blocks, { type: "text", content: chunk, stage }],
216
+ };
217
+ }
218
+ return {
219
+ ...m,
220
+ blocks: m.blocks.map((b, idx) =>
221
+ idx === textBlockIndex && b.type === "text"
222
+ ? {
223
+ ...b,
224
+ content: (b.content || "") + chunk,
225
+ stage,
226
+ }
227
+ : b,
228
+ ),
229
+ };
230
+ });
231
+ };
232
+
233
+ /**
234
+ * Pure updater: appends a reasoning delta to the target message's reasoning
235
+ * block (creating it on first delta), applying the stage signal.
236
+ */
237
+ const applyReasoningDelta = (
238
+ prev: Message[],
239
+ params: StreamingUpdateParams,
240
+ ): Message[] => {
241
+ const { messageId, chunk, stage } = params;
242
+ return prev.map((m) => {
243
+ if (m.id !== messageId) return m;
244
+ const reasoningBlockIndex = m.blocks.findIndex(
245
+ (b) => b.type === "reasoning",
246
+ );
247
+ if (reasoningBlockIndex === -1) {
248
+ return {
249
+ ...m,
250
+ blocks: [...m.blocks, { type: "reasoning", content: chunk, stage }],
251
+ };
252
+ }
253
+ return {
254
+ ...m,
255
+ blocks: m.blocks.map((b, idx) =>
256
+ idx === reasoningBlockIndex && b.type === "reasoning"
257
+ ? {
258
+ ...b,
259
+ content: (b.content || "") + chunk,
260
+ stage,
261
+ }
262
+ : b,
263
+ ),
264
+ };
265
+ });
266
+ };
267
+
268
+ /**
269
+ * Pure updater: applies a tool block update. Streaming carries only the
270
+ * `parametersChunk` delta, appended to the accumulated parameters;
271
+ * start/running/end carry the authoritative value and replace wholesale.
272
+ */
273
+ const applyToolBlockUpdate = (
274
+ prev: Message[],
275
+ params: ToolBlockUpdateCallbackParams,
276
+ ): Message[] => {
277
+ const { messageId, id: toolBlockId, parametersChunk, ...updates } = params;
278
+ return prev.map((m) => {
279
+ if (m.id !== messageId) return m;
280
+ const toolBlockIndex = m.blocks.findIndex(
281
+ (b) => b.type === "tool" && b.id === toolBlockId,
282
+ );
283
+ if (toolBlockIndex === -1) {
284
+ return {
285
+ ...m,
286
+ blocks: [
287
+ ...m.blocks,
288
+ {
289
+ type: "tool",
290
+ id: toolBlockId,
291
+ name: updates.name || "",
292
+ stage: updates.stage || "start",
293
+ parameters: (updates.parameters || "") + (parametersChunk || ""),
294
+ result: updates.result || "",
295
+ ...updates,
296
+ },
297
+ ],
298
+ };
299
+ }
300
+ return {
301
+ ...m,
302
+ blocks: m.blocks.map((b, idx) =>
303
+ idx === toolBlockIndex && b.type === "tool"
304
+ ? {
305
+ ...b,
306
+ ...updates,
307
+ parameters: parametersChunk
308
+ ? (b.parameters || "") + parametersChunk
309
+ : updates.parameters !== undefined
310
+ ? updates.parameters
311
+ : b.parameters,
312
+ }
313
+ : b,
314
+ ),
315
+ };
316
+ });
317
+ };
318
+
319
+ /**
320
+ * Single throttled updater entry for ALL message-state updates. The one
321
+ * 500ms window-concat window replaces the previous three-channel throttles
322
+ * (content/reasoning/tool), so every message-state update — including the
323
+ * tool `running` stage's high-frequency `shortResult`/`result` updates from
324
+ * bash — coalesces into the same window.
325
+ *
326
+ * Semantics: leading edge applies immediately and opens a window; updates
327
+ * arriving within the window are queued in arrival order (FIFO) and applied
328
+ * at the trailing edge as ONE composed updater, so no update is lost and no
329
+ * update is reordered. `flush` applies queued updates immediately (used by
330
+ * end signals and one-shot structural updates); `cancel` drops queued
331
+ * updates (used by refreshMessages after pulling the authoritative snapshot —
332
+ * the queued updates are already contained in it).
333
+ *
334
+ * The FIFO queue structurally replaces the old tool throttle's "drop a
335
+ * tool's buffered streaming deltas when running arrives" logic: a running
336
+ * updater is queued BEHIND the streaming chunks that preceded it and applies
337
+ * after them, so a stale streaming event can never flush after running and
338
+ * regress the stage (yellow dot -> gray). See
339
+ * docs/specs/core/stream-content-updates.md.
340
+ */
341
+ export function createThrottledUpdater<T>(
342
+ apply: (updater: (prev: T) => T) => void,
343
+ wait: number,
344
+ ): {
345
+ (updater: (prev: T) => T): void;
346
+ cancel: () => void;
347
+ flush: () => void;
348
+ } {
349
+ let timer: NodeJS.Timeout | null = null;
350
+ let queued: Array<(prev: T) => T> = [];
351
+
352
+ const fire = () => {
353
+ if (queued.length === 0) return;
354
+ const batch = queued;
355
+ queued = [];
356
+ // Compose the whole batch into a single updater applied in FIFO order —
357
+ // one state commit per trailing edge, no update dropped, no reordering.
358
+ apply((prev) => batch.reduce((acc, upd) => upd(acc), prev));
359
+ };
360
+
361
+ const throttled = (updater: (prev: T) => T) => {
362
+ if (timer) {
363
+ queued.push(updater);
364
+ return;
365
+ }
366
+ // Leading edge: apply immediately, then open a window whose trailing
367
+ // edge only carries updates arriving within the window.
368
+ apply(updater);
369
+ timer = setTimeout(() => {
370
+ timer = null;
371
+ fire();
372
+ }, wait);
373
+ };
374
+
375
+ throttled.cancel = () => {
376
+ if (timer) {
377
+ clearTimeout(timer);
378
+ timer = null;
379
+ }
380
+ queued = [];
381
+ };
382
+
383
+ throttled.flush = () => {
384
+ if (timer) {
385
+ clearTimeout(timer);
386
+ timer = null;
387
+ }
388
+ fire();
389
+ };
390
+
391
+ return throttled;
392
+ }
393
+
196
394
  export const ChatProvider: React.FC<ChatProviderProps> = ({
197
395
  children,
198
396
  bypassPermissions,
@@ -223,132 +421,26 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
223
421
  const [latestTotalTokens, setLatestTotalTokens] = useState(0);
224
422
  const [maxInputTokens, setMaxInputTokens] = useState(200000);
225
423
 
226
- // Direct incremental streaming updaters every delta is applied to the
227
- // message state immediately, without a cooldown window. The former 500ms
228
- // window-concat throttle is gone: its trailing-edge flush could interleave
229
- // with a full-list `refreshMessages` snapshot replacement mid-stream and
230
- // re-append old deltas on top of the authoritative content (first-word
231
- // duplication). Terminal write frequency is bounded downstream by Ink's
232
- // built-in ~30fps output throttle. See
233
- // docs/specs/core/stream-content-updates.md.
234
- const applyContentUpdate = useCallback((params: StreamingUpdateParams) => {
235
- const { messageId, chunk, stage } = params;
236
- setMessages((prev) =>
237
- prev.map((m) => {
238
- if (m.id !== messageId) return m;
239
- const textBlockIndex = m.blocks.findIndex((b) => b.type === "text");
240
- if (textBlockIndex === -1) {
241
- return {
242
- ...m,
243
- blocks: [...m.blocks, { type: "text", content: chunk, stage }],
244
- };
245
- }
246
- return {
247
- ...m,
248
- blocks: m.blocks.map((b, idx) =>
249
- idx === textBlockIndex && b.type === "text"
250
- ? {
251
- ...b,
252
- content: (b.content || "") + chunk,
253
- stage,
254
- }
255
- : b,
256
- ),
257
- };
258
- }),
259
- );
260
- }, []);
261
-
262
- const applyReasoningUpdate = useCallback((params: StreamingUpdateParams) => {
263
- const { messageId, chunk, stage } = params;
264
- setMessages((prev) =>
265
- prev.map((m) => {
266
- if (m.id !== messageId) return m;
267
- const reasoningBlockIndex = m.blocks.findIndex(
268
- (b) => b.type === "reasoning",
269
- );
270
- if (reasoningBlockIndex === -1) {
271
- return {
272
- ...m,
273
- blocks: [...m.blocks, { type: "reasoning", content: chunk, stage }],
274
- };
275
- }
276
- return {
277
- ...m,
278
- blocks: m.blocks.map((b, idx) =>
279
- idx === reasoningBlockIndex && b.type === "reasoning"
280
- ? {
281
- ...b,
282
- content: (b.content || "") + chunk,
283
- stage,
284
- }
285
- : b,
286
- ),
287
- };
288
- }),
289
- );
290
- }, []);
291
-
292
- const applyToolBlockUpdate = useCallback(
293
- (params: ToolBlockUpdateCallbackParams) => {
294
- const {
295
- messageId,
296
- id: toolBlockId,
297
- parametersChunk,
298
- ...updates
299
- } = params;
300
- setMessages((prev) =>
301
- prev.map((m) => {
302
- if (m.id !== messageId) return m;
303
- const toolBlockIndex = m.blocks.findIndex(
304
- (b) => b.type === "tool" && b.id === toolBlockId,
305
- );
306
- if (toolBlockIndex === -1) {
307
- return {
308
- ...m,
309
- blocks: [
310
- ...m.blocks,
311
- {
312
- type: "tool",
313
- id: toolBlockId,
314
- name: updates.name || "",
315
- stage: updates.stage || "start",
316
- parameters:
317
- (updates.parameters || "") + (parametersChunk || ""),
318
- result: updates.result || "",
319
- ...updates,
320
- },
321
- ],
322
- };
323
- }
324
- return {
325
- ...m,
326
- blocks: m.blocks.map((b, idx) =>
327
- idx === toolBlockIndex && b.type === "tool"
328
- ? {
329
- ...b,
330
- ...updates,
331
- // Streaming carries only the delta; append it to the
332
- // accumulated parameters. start/running/end carry the
333
- // authoritative value and replace wholesale.
334
- parameters: parametersChunk
335
- ? (b.parameters || "") + parametersChunk
336
- : updates.parameters !== undefined
337
- ? updates.parameters
338
- : b.parameters,
339
- }
340
- : b,
341
- ),
342
- };
343
- }),
344
- );
345
- },
424
+ // Single throttled entry for ALL message-state updates one 500ms
425
+ // window-concat window shared by every callback (content/reasoning deltas,
426
+ // tool parametersChunk, tool `running` stage shortResult/result, one-shot
427
+ // structural updates). The tool `running` stage used to bypass throttling
428
+ // (bash shortResult height changes per chunk layout flicker); routing it
429
+ // through the same window as streaming deltas coalesces it to ≤1 render per
430
+ // window. See createThrottledUpdater + docs/specs/core/stream-content-updates.md.
431
+ const updateMessages = useMemo(
432
+ () =>
433
+ createThrottledUpdater<Message[]>((updater) => setMessages(updater), 500),
346
434
  [],
347
435
  );
348
436
 
349
437
  useEffect(() => {
350
438
  isExpandedRef.current = isExpanded;
351
- }, [isExpanded]);
439
+ if (isExpanded) {
440
+ // Cancel pending throttled updates so the frozen expanded view isn't overwritten
441
+ updateMessages.cancel();
442
+ }
443
+ }, [isExpanded, updateMessages]);
352
444
 
353
445
  const [isLoading, setIsLoading] = useState(false);
354
446
  const [sessionId, setSessionId] = useState("");
@@ -378,6 +470,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
378
470
  const [agentDefinitions, setAgentDefinitions] = useState<
379
471
  SubagentConfiguration[]
380
472
  >([]);
473
+ // Skill metadata (for /skills overlay)
474
+ const [skills, setSkills] = useState<SkillMetadata[]>([]);
381
475
 
382
476
  // Permission state
383
477
  const [permissionMode, setPermissionModeState] = useState<PermissionMode>(
@@ -447,10 +541,19 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
447
541
  const refreshMessages = useCallback(() => {
448
542
  if (!isExpandedRef.current && agentRef.current) {
449
543
  const msgs = agentRef.current.messages.map(snapshotMessage);
544
+ // Snapshot-safe: the full-list replacement makes the SDK state
545
+ // authoritative. Any update still queued inside the 500ms throttle
546
+ // window was applied to the SDK before this pull, so it is already
547
+ // contained in `msgs` — dropping it prevents the trailing-edge flush
548
+ // from re-appending the pre-refresh chunk on top of the snapshot
549
+ // (first-word duplication). Updates arriving after the pull start fresh
550
+ // windows and append on top of the snapshot. See
551
+ // docs/specs/core/stream-content-updates.md.
552
+ updateMessages.cancel();
450
553
  setMessages(msgs);
451
554
  setLatestTotalTokens(extractLatestTotalTokens(msgs));
452
555
  }
453
- }, []);
556
+ }, [updateMessages]);
454
557
 
455
558
  // Permission confirmation methods with queue support
456
559
  const showConfirmation = useCallback(
@@ -491,18 +594,23 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
491
594
 
492
595
  const callbacks: AgentCallbacks = {
493
596
  // ── Incremental message updates (no full-list pushes) ──────
597
+ // All of these funnel through the single throttled `updateMessages`
598
+ // entry, so every message-state update shares one 500ms window.
494
599
  onUserMessageAdded: () => {
495
600
  if (isExpandedRef.current || !agentRef.current) return;
496
601
  const msgs = agentRef.current.messages;
497
602
  const last = msgs[msgs.length - 1];
498
603
  if (!last || last.role !== "user") return;
499
604
  // Eager snapshot at callback time — React defers updater execution to
500
- // the batch flush, so evaluating snapshotMessage inside setMessages
605
+ // the batch flush, so evaluating snapshotMessage inside the updater
501
606
  // would read the SDK message after its in-place mutations.
502
607
  const snapshot = snapshotMessage(last);
503
- setMessages((prev) =>
608
+ updateMessages((prev) =>
504
609
  prev.some((m) => m.id === last.id) ? prev : [...prev, snapshot],
505
610
  );
611
+ // One-shot structural update — flush immediately so the message
612
+ // card appears at once (queuing it gains no coalescing).
613
+ updateMessages.flush();
506
614
  },
507
615
  onAssistantMessageAdded: (messageId: string) => {
508
616
  if (isExpandedRef.current || !agentRef.current) return;
@@ -512,25 +620,29 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
512
620
  // fires this callback BEFORE the first delta writes into the shared
513
621
  // message, so capturing here copies the pre-mutation (empty) blocks.
514
622
  const snapshot = snapshotMessage(msg);
515
- setMessages((prev) =>
623
+ updateMessages((prev) =>
516
624
  prev.some((m) => m.id === messageId) ? prev : [...prev, snapshot],
517
625
  );
626
+ updateMessages.flush();
518
627
  },
519
628
  onAssistantContentUpdated: (params) => {
520
629
  if (isExpandedRef.current) return;
521
- applyContentUpdate(params);
630
+ updateMessages((prev) => applyContentDelta(prev, params));
631
+ if (params.stage === "end") updateMessages.flush();
522
632
  },
523
633
  onAssistantReasoningUpdated: (params) => {
524
634
  if (isExpandedRef.current) return;
525
- applyReasoningUpdate(params);
635
+ updateMessages((prev) => applyReasoningDelta(prev, params));
636
+ if (params.stage === "end") updateMessages.flush();
526
637
  },
527
638
  onToolBlockUpdated: (params) => {
528
639
  if (isExpandedRef.current) return;
529
- applyToolBlockUpdate(params);
640
+ updateMessages((prev) => applyToolBlockUpdate(prev, params));
641
+ if (params.stage === "end") updateMessages.flush();
530
642
  },
531
643
  onErrorBlockAdded: (error: string) => {
532
644
  if (isExpandedRef.current) return;
533
- setMessages((prev) => {
645
+ updateMessages((prev) => {
534
646
  // Append to the LAST message only if it is an assistant message
535
647
  // (the current turn's in-flight reply). If the last message is a
536
648
  // user message, the error belongs BELOW it — create a new
@@ -558,10 +670,13 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
558
670
  },
559
671
  ];
560
672
  });
673
+ // One-shot structural update — flush immediately (errors must not be
674
+ // delayed by a streaming window).
675
+ updateMessages.flush();
561
676
  },
562
677
  onAddBangMessage: (command, messageId) => {
563
678
  if (isExpandedRef.current) return;
564
- setMessages((prev) =>
679
+ updateMessages((prev) =>
565
680
  prev.some((m) => m.id === messageId)
566
681
  ? prev
567
682
  : [
@@ -582,10 +697,11 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
582
697
  },
583
698
  ],
584
699
  );
700
+ updateMessages.flush();
585
701
  },
586
702
  onUpdateBangMessage: (command, output, messageId) => {
587
703
  if (isExpandedRef.current) return;
588
- setMessages((prev) =>
704
+ updateMessages((prev) =>
589
705
  prev.map((m) =>
590
706
  m.id === messageId
591
707
  ? {
@@ -602,7 +718,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
602
718
  },
603
719
  onCompleteBangMessage: (command, exitCode, messageId, output) => {
604
720
  if (isExpandedRef.current) return;
605
- setMessages((prev) =>
721
+ updateMessages((prev) =>
606
722
  prev.map((m) =>
607
723
  m.id === messageId
608
724
  ? {
@@ -622,6 +738,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
622
738
  : m,
623
739
  ),
624
740
  );
741
+ // Completion signal — flush so the final state applies immediately.
742
+ updateMessages.flush();
625
743
  },
626
744
  onLatestTotalTokensChange: (tokens) => {
627
745
  setLatestTotalTokens(tokens);
@@ -772,6 +890,10 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
772
890
  const initialAgentDefinitions =
773
891
  agent.getSubagentConfigurations?.() || [];
774
892
  setAgentDefinitions(initialAgentDefinitions);
893
+
894
+ // Get initial skill metadata
895
+ const initialSkills = agent.getSkillMetadata?.() || [];
896
+ setSkills(initialSkills);
775
897
  } catch (error) {
776
898
  console.error("Failed to initialize AI manager:", error);
777
899
  }
@@ -791,9 +913,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
791
913
  originalCwd,
792
914
  model,
793
915
  initialPermissionMode,
794
- applyContentUpdate,
795
- applyReasoningUpdate,
796
- applyToolBlockUpdate,
916
+ updateMessages,
797
917
  mcpServers,
798
918
  ],
799
919
  );
@@ -813,6 +933,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
813
933
  setMcpServerStatuses([]);
814
934
  setSlashCommands([]);
815
935
  setAgentDefinitions([]);
936
+ setSkills([]);
816
937
  setSessionId("");
817
938
  setIsLoading(false);
818
939
  setLatestTotalTokens(0);
@@ -832,6 +953,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
832
953
  // Cleanup on unmount
833
954
  useEffect(() => {
834
955
  return () => {
956
+ updateMessages.cancel();
835
957
  if (agentRef.current) {
836
958
  try {
837
959
  // Display usage summary before cleanup
@@ -845,7 +967,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
845
967
  agentRef.current.destroy();
846
968
  }
847
969
  };
848
- }, []);
970
+ }, [updateMessages]);
849
971
 
850
972
  // Send message function (including judgment logic)
851
973
  const sendMessage = useCallback(
@@ -1188,6 +1310,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
1188
1310
  slashCommands,
1189
1311
  hasSlashCommand,
1190
1312
  agentDefinitions,
1313
+ skills,
1191
1314
  permissionMode,
1192
1315
  setPermissionMode,
1193
1316
  isConfirmationVisible,
@@ -527,6 +527,10 @@ export const useInputManager = (
527
527
  dispatch({ type: "SET_SHOW_WORKFLOW_MANAGER", payload: show });
528
528
  }, []);
529
529
 
530
+ const setShowSkillsManager = useCallback((show: boolean) => {
531
+ dispatch({ type: "SET_SHOW_SKILLS_MANAGER", payload: show });
532
+ }, []);
533
+
530
534
  const setPermissionMode = useCallback(
531
535
  (mode: PermissionMode) => {
532
536
  dispatch({ type: "SET_PERMISSION_MODE", payload: mode });
@@ -671,6 +675,7 @@ export const useInputManager = (
671
675
  showPluginManager: state.showPluginManager,
672
676
  showModelSelector: state.showModelSelector,
673
677
  showWorkflowManager: state.showWorkflowManager,
678
+ showSkillsManager: state.showSkillsManager,
674
679
  permissionMode: state.permissionMode,
675
680
  attachedImages: state.attachedImages,
676
681
  btwState: state.btwState,
@@ -717,6 +722,7 @@ export const useInputManager = (
717
722
  setShowPluginManager,
718
723
  setShowModelSelector,
719
724
  setShowWorkflowManager,
725
+ setShowSkillsManager,
720
726
  setPermissionMode,
721
727
  setBtwState,
722
728
 
@@ -369,6 +369,8 @@ export const handleCommandSelect = (
369
369
  dispatch({ type: "SET_SHOW_MCP_MANAGER", payload: true });
370
370
  } else if (command === "agents") {
371
371
  dispatch({ type: "SET_SHOW_AGENTS_MANAGER", payload: true });
372
+ } else if (command === "skills") {
373
+ dispatch({ type: "SET_SHOW_SKILLS_MANAGER", payload: true });
372
374
  } else if (command === "rewind") {
373
375
  dispatch({ type: "SET_SHOW_REWIND_MANAGER", payload: true });
374
376
  } else if (command === "help") {
@@ -138,6 +138,7 @@ export interface InputState {
138
138
  showPluginManager: boolean;
139
139
  showModelSelector: boolean;
140
140
  showWorkflowManager: boolean;
141
+ showSkillsManager: boolean;
141
142
  permissionMode: PermissionMode;
142
143
  selectorJustUsed: boolean;
143
144
  history: PromptEntry[];
@@ -176,6 +177,7 @@ export const initialState: InputState = {
176
177
  showPluginManager: false,
177
178
  showModelSelector: false,
178
179
  showWorkflowManager: false,
180
+ showSkillsManager: false,
179
181
  permissionMode: "default",
180
182
  selectorJustUsed: false,
181
183
  history: [],
@@ -457,6 +459,7 @@ export type InputAction =
457
459
  | { type: "SET_SHOW_PLUGIN_MANAGER"; payload: boolean }
458
460
  | { type: "SET_SHOW_MODEL_SELECTOR"; payload: boolean }
459
461
  | { type: "SET_SHOW_WORKFLOW_MANAGER"; payload: boolean }
462
+ | { type: "SET_SHOW_SKILLS_MANAGER"; payload: boolean }
460
463
  | { type: "SET_PERMISSION_MODE"; payload: PermissionMode }
461
464
  | { type: "SET_SELECTOR_JUST_USED"; payload: boolean }
462
465
  | { type: "INSERT_TEXT_WITH_PLACEHOLDER"; payload: string }
@@ -687,6 +690,12 @@ export function inputReducer(
687
690
  showWorkflowManager: action.payload,
688
691
  selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
689
692
  };
693
+ case "SET_SHOW_SKILLS_MANAGER":
694
+ return {
695
+ ...state,
696
+ showSkillsManager: action.payload,
697
+ selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
698
+ };
690
699
  case "SET_PERMISSION_MODE":
691
700
  return { ...state, permissionMode: action.payload };
692
701
  case "SET_SELECTOR_JUST_USED":
package/src/print-cli.ts CHANGED
@@ -217,7 +217,9 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
217
217
  worktreeSession.repoRoot,
218
218
  );
219
219
  }
220
+ process.stdout.write("\nDeleting worktree ...\n");
220
221
  await removeWorktree(worktreeSession);
222
+ process.stdout.write("Done.\n");
221
223
  } catch (error) {
222
224
  // Never block print-mode exit on worktree cleanup failures
223
225
  process.stdout.write(
@@ -273,7 +275,9 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
273
275
  worktreeSession.repoRoot,
274
276
  );
275
277
  }
278
+ process.stdout.write("\nDeleting worktree ...\n");
276
279
  await removeWorktree(worktreeSession);
280
+ process.stdout.write("Done.\n");
277
281
  } catch (error) {
278
282
  process.stdout.write(
279
283
  `\n⚠️ Skipping worktree removal: ${(error as Error).message}\n`,