@sayknow-cli/coding-agent 0.6.4 → 0.6.5

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 (81) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/README.md +2 -2
  3. package/dist/types/config/keybindings.d.ts +1 -1
  4. package/dist/types/config/settings-schema.d.ts +7 -3
  5. package/dist/types/i18n/messages/en.d.ts +16 -0
  6. package/dist/types/modes/components/session-selector.d.ts +7 -3
  7. package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
  8. package/dist/types/modes/components/user-message-selector.d.ts +5 -2
  9. package/dist/types/modes/components/user-message.d.ts +3 -1
  10. package/dist/types/modes/components/welcome.d.ts +49 -6
  11. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  12. package/dist/types/modes/controllers/selector-controller.d.ts +5 -0
  13. package/dist/types/modes/interactive-mode.d.ts +7 -1
  14. package/dist/types/modes/theme/defaults/index.d.ts +342 -0
  15. package/dist/types/modes/theme/theme.d.ts +5 -1
  16. package/dist/types/modes/types.d.ts +1 -1
  17. package/dist/types/session/session-manager.d.ts +9 -0
  18. package/dist/types/session/session-stars.d.ts +15 -0
  19. package/dist/types/tui/output-block.d.ts +12 -1
  20. package/dist/types/tui/utils.d.ts +1 -3
  21. package/dist/types/utils/session-title-generation.d.ts +6 -0
  22. package/dist/types/utils/title-generator.d.ts +11 -2
  23. package/package.json +7 -7
  24. package/scripts/generate-sdk-operation-inventory.ts +8 -0
  25. package/src/cli/session-picker.ts +4 -0
  26. package/src/config/keybindings.ts +1 -1
  27. package/src/config/settings-schema.ts +4 -3
  28. package/src/i18n/messages/de.messages.ts +4 -1
  29. package/src/i18n/messages/de.settings.ts +2 -0
  30. package/src/i18n/messages/de.ts +16 -0
  31. package/src/i18n/messages/en.ts +16 -0
  32. package/src/i18n/messages/es.messages.ts +4 -1
  33. package/src/i18n/messages/es.settings.ts +2 -0
  34. package/src/i18n/messages/es.ts +16 -0
  35. package/src/i18n/messages/fr.messages.ts +4 -1
  36. package/src/i18n/messages/fr.settings.ts +2 -0
  37. package/src/i18n/messages/fr.ts +16 -0
  38. package/src/i18n/messages/ja.messages.ts +4 -1
  39. package/src/i18n/messages/ja.settings.ts +2 -0
  40. package/src/i18n/messages/ja.ts +16 -0
  41. package/src/i18n/messages/ko.messages.ts +4 -1
  42. package/src/i18n/messages/ko.settings.ts +2 -0
  43. package/src/i18n/messages/ko.ts +16 -0
  44. package/src/i18n/messages/zh.messages.ts +2 -1
  45. package/src/i18n/messages/zh.settings.ts +2 -0
  46. package/src/i18n/messages/zh.ts +16 -0
  47. package/src/internal-urls/docs-index.generated.ts +10 -10
  48. package/src/lsp/render.ts +0 -1
  49. package/src/modes/components/session-selector.ts +71 -21
  50. package/src/modes/components/sessions-dashboard.ts +8 -5
  51. package/src/modes/components/settings-selector.ts +1 -0
  52. package/src/modes/components/status-line/presets.ts +5 -5
  53. package/src/modes/components/status-line/separators.ts +4 -0
  54. package/src/modes/components/tool-execution.ts +6 -19
  55. package/src/modes/components/tool-status-header.ts +103 -12
  56. package/src/modes/components/user-message-selector.ts +57 -15
  57. package/src/modes/components/user-message.ts +25 -15
  58. package/src/modes/components/welcome.ts +374 -478
  59. package/src/modes/controllers/command-controller.ts +56 -7
  60. package/src/modes/controllers/input-controller.ts +4 -0
  61. package/src/modes/controllers/selector-controller.ts +91 -10
  62. package/src/modes/interactive-mode.ts +101 -12
  63. package/src/modes/theme/defaults/glow-octopus.json +114 -0
  64. package/src/modes/theme/defaults/index.ts +6 -0
  65. package/src/modes/theme/defaults/ink-octopus.json +114 -0
  66. package/src/modes/theme/defaults/violet-octopus.json +114 -0
  67. package/src/modes/theme/theme.ts +18 -2
  68. package/src/modes/types.ts +1 -1
  69. package/src/prompts/system/title-system.md +1 -1
  70. package/src/sdk/protocol/operation-inventory.generated.json +40 -0
  71. package/src/session/agent-session.ts +12 -0
  72. package/src/session/internal/managed-session-scope.ts +10 -0
  73. package/src/session/session-manager.ts +26 -1
  74. package/src/session/session-stars.ts +104 -0
  75. package/src/slash-commands/builtin-registry.ts +104 -4
  76. package/src/tools/debug.ts +0 -1
  77. package/src/tools/fetch.ts +0 -1
  78. package/src/tui/output-block.ts +29 -61
  79. package/src/tui/utils.ts +1 -8
  80. package/src/utils/session-title-generation.ts +29 -0
  81. package/src/utils/title-generator.ts +46 -2
@@ -14,9 +14,10 @@ interface UserMessageItem {
14
14
  */
15
15
  class UserMessageList implements Component {
16
16
  #selectedIndex: number = 0;
17
+ #selectionInFlight = false;
17
18
  onSelect?: (entryId: string) => void;
18
19
  onCancel?: () => void;
19
- #maxVisible: number = 10; // Max messages visible
20
+ #availableRows = Number.POSITIVE_INFINITY;
20
21
 
21
22
  constructor(private readonly messages: UserMessageItem[]) {
22
23
  // Store messages in chronological order (oldest to newest)
@@ -24,6 +25,10 @@ class UserMessageList implements Component {
24
25
  this.#selectedIndex = Math.max(0, this.messages.length - 1);
25
26
  }
26
27
 
28
+ setAvailableRows(rows: number): void {
29
+ this.#availableRows = Math.max(3, rows);
30
+ }
31
+
27
32
  invalidate(): void {
28
33
  // No cached state to invalidate currently
29
34
  }
@@ -36,12 +41,15 @@ class UserMessageList implements Component {
36
41
  return lines;
37
42
  }
38
43
 
39
- // Calculate visible range with scrolling
44
+ // Each entry uses two content rows plus a separator. Leave one row for
45
+ // the scroll position whenever the full history does not fit.
46
+ const needsScrollIndicator = this.messages.length * 3 > this.#availableRows;
47
+ const maxVisible = Math.max(1, Math.floor((this.#availableRows - (needsScrollIndicator ? 1 : 0)) / 3));
40
48
  const startIndex = Math.max(
41
49
  0,
42
- Math.min(this.#selectedIndex - Math.floor(this.#maxVisible / 2), this.messages.length - this.#maxVisible),
50
+ Math.min(this.#selectedIndex - Math.floor(maxVisible / 2), this.messages.length - maxVisible),
43
51
  );
44
- const endIndex = Math.min(startIndex + this.#maxVisible, this.messages.length);
52
+ const endIndex = Math.min(startIndex + maxVisible, this.messages.length);
45
53
 
46
54
  // Render visible messages (2 lines per message + blank line)
47
55
  for (let i = startIndex; i < endIndex; i++) {
@@ -77,6 +85,7 @@ class UserMessageList implements Component {
77
85
  }
78
86
 
79
87
  handleInput(keyData: string): void {
88
+ if (this.#selectionInFlight) return;
80
89
  // Up arrow - go to previous (older) message, wrap to bottom when at top
81
90
  if (matchesKey(keyData, "up")) {
82
91
  this.#selectedIndex = this.#selectedIndex === 0 ? this.messages.length - 1 : this.#selectedIndex - 1;
@@ -89,6 +98,7 @@ class UserMessageList implements Component {
89
98
  else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
90
99
  const selected = this.messages[this.#selectedIndex];
91
100
  if (selected && this.onSelect) {
101
+ this.#selectionInFlight = true;
92
102
  this.onSelect(selected.id);
93
103
  }
94
104
  }
@@ -102,21 +112,43 @@ class UserMessageList implements Component {
102
112
  }
103
113
 
104
114
  /**
105
- * Component that renders a user message selector for branching
115
+ * Component that renders a prompt selector for creating a new session
106
116
  */
107
117
  export class UserMessageSelectorComponent extends Container {
108
118
  #messageList: UserMessageList;
109
-
110
- constructor(messages: UserMessageItem[], onSelect: (entryId: string) => void, onCancel: () => void) {
119
+ #header: Container;
120
+ #footer: Container;
121
+
122
+ constructor(
123
+ messages: UserMessageItem[],
124
+ onSelect: (entryId: string) => void,
125
+ onCancel: () => void,
126
+ private readonly getViewportRows: () => number,
127
+ ) {
111
128
  super();
112
129
 
113
130
  // Add header
114
- this.addChild(new Spacer(1));
115
- this.addChild(new Text(theme.bold("Branch from Message"), 1, 0));
116
- this.addChild(new Text(theme.fg("muted", "Select a message to create a new branch from that point"), 1, 0));
117
- this.addChild(new Spacer(1));
118
- this.addChild(new DynamicBorder());
119
- this.addChild(new Spacer(1));
131
+ this.#header = new Container();
132
+ this.#header.addChild(new Spacer(1));
133
+ this.#header.addChild(new Text(theme.bold("Fork from Prompt"), 1, 0));
134
+ this.#header.addChild(
135
+ new Text(
136
+ theme.fg(
137
+ "muted",
138
+ "Creates a new session with history before the prompt; shared files stay in the same cwd.",
139
+ ),
140
+ 1,
141
+ 0,
142
+ ),
143
+ );
144
+ this.#header.addChild(
145
+ new Text(theme.fg("muted", "The selected prompt is restored for editing, not submitted."), 1, 0),
146
+ );
147
+ this.#header.addChild(new Text(theme.fg("muted", "↑/↓ move · Enter select · Esc cancel"), 1, 0));
148
+ this.#header.addChild(new Spacer(1));
149
+ this.#header.addChild(new DynamicBorder());
150
+ this.#header.addChild(new Spacer(1));
151
+ this.addChild(this.#header);
120
152
 
121
153
  // Create message list
122
154
  this.#messageList = new UserMessageList(messages);
@@ -126,8 +158,10 @@ export class UserMessageSelectorComponent extends Container {
126
158
  this.addChild(this.#messageList);
127
159
 
128
160
  // Add bottom border
129
- this.addChild(new Spacer(1));
130
- this.addChild(new DynamicBorder());
161
+ this.#footer = new Container();
162
+ this.#footer.addChild(new Spacer(1));
163
+ this.#footer.addChild(new DynamicBorder());
164
+ this.addChild(this.#footer);
131
165
 
132
166
  // Auto-cancel if no messages
133
167
  if (messages.length === 0) {
@@ -135,6 +169,14 @@ export class UserMessageSelectorComponent extends Container {
135
169
  }
136
170
  }
137
171
 
172
+ render(width: number): string[] {
173
+ const chromeRows = this.#header.render(width).length + this.#footer.render(width).length;
174
+ // Reserve the pinned status/composer rows outside the overlay. Recompute
175
+ // against current terminal rows and wrapped header height on every render.
176
+ this.#messageList.setAvailableRows(this.getViewportRows() - chromeRows - 2);
177
+ return super.render(width);
178
+ }
179
+
138
180
  getMessageList(): UserMessageList {
139
181
  return this.#messageList;
140
182
  }
@@ -14,8 +14,13 @@ const OSC133_ZONE_START = "\x1b]133;A\x07";
14
14
  const OSC133_ZONE_END = "\x1b]133;B\x07";
15
15
  const OSC133_ZONE_FINAL = "\x1b]133;C\x07";
16
16
 
17
+ /** Columns taken by the rail glyph plus the gap before the prompt text. */
18
+ const RAIL_WIDTH = 2;
19
+
17
20
  /**
18
- * Component that renders a user message
21
+ * Component that renders a user message: the prompt sits on the transcript
22
+ * background behind an accent rail instead of inside a filled bubble, so the
23
+ * user's turns read as margin notes rather than cards.
19
24
  */
20
25
  export class UserMessageComponent extends Container {
21
26
  #viewportAnchorSource?: ViewportAnchorSource;
@@ -23,14 +28,15 @@ export class UserMessageComponent extends Container {
23
28
  constructor(text: string, synthetic = false, viewportAnchorId?: string) {
24
29
  super();
25
30
  if (viewportAnchorId) this.#viewportAnchorSource = { id: viewportAnchorId };
26
- const bgColor = (value: string) => theme.bg("userMessageBg", value);
27
31
  const color = synthetic
28
32
  ? (value: string) => theme.fg("dim", value)
29
33
  : (value: string) => theme.fg("userMessageText", value);
34
+ // Resolved per render so a /theme switch recolors prompts already in the transcript.
35
+ const rail = () => theme.fg(synthetic ? "dim" : "accent", theme.rail.user);
30
36
  this.addChild(new Spacer(1));
31
- const label = synthetic ? "replay" : "user";
32
- this.addChild(new Text(theme.bold(theme.fg("accent", label)), 1, 0));
33
- const prompt = new PromptZoneMarkdown(text, bgColor, color);
37
+ // A replayed prompt is not something the user just typed; say so once, quietly.
38
+ if (synthetic) this.addChild(new Text(`${rail()} ${theme.fg("dim", "replay")}`, 0, 0));
39
+ const prompt = new PromptZoneMarkdown(text, color, rail);
34
40
  this.addChild(prompt);
35
41
  if (this.#viewportAnchorSource) this.setViewportAnchorSource(prompt, this.#viewportAnchorSource);
36
42
  }
@@ -38,31 +44,35 @@ export class UserMessageComponent extends Container {
38
44
 
39
45
  class PromptZoneMarkdown implements Component {
40
46
  #markdown: Markdown;
47
+ #rail: () => string;
41
48
 
42
- constructor(text: string, bgColor: (value: string) => string, color: (value: string) => string) {
43
- this.#markdown = new Markdown(text, 1, 1, getMarkdownTheme(), {
44
- bgColor,
45
- color,
46
- });
49
+ constructor(text: string, color: (value: string) => string, rail: () => string) {
50
+ this.#markdown = new Markdown(text, 0, 0, getMarkdownTheme(), { color });
51
+ this.#rail = rail;
47
52
  }
48
53
 
49
54
  invalidate(): void {
50
55
  this.#markdown.invalidate();
51
56
  }
52
57
 
53
- #withPromptZone(lines: string[]): string[] {
58
+ #withRailAndPromptZone(lines: string[]): string[] {
54
59
  if (lines.length === 0) return lines;
55
- const zoned = [...lines];
60
+ const rail = this.#rail();
61
+ const zoned = lines.map(line => `${rail} ${line}`);
56
62
  zoned[0] = OSC133_ZONE_START + zoned[0];
57
63
  zoned[zoned.length - 1] = `${zoned[zoned.length - 1]}${OSC133_ZONE_END}${OSC133_ZONE_FINAL}`;
58
64
  return zoned;
59
65
  }
60
66
 
67
+ #innerWidth(width: number): number {
68
+ return Math.max(1, width - RAIL_WIDTH);
69
+ }
70
+
61
71
  renderWithViewportAnchorSource(width: number, source: ViewportAnchorSource): ViewportAnchorRender {
62
- const rendered = this.#markdown.renderWithViewportAnchorSource(width, source);
63
- return { lines: this.#withPromptZone(rendered.lines), anchors: rendered.anchors };
72
+ const rendered = this.#markdown.renderWithViewportAnchorSource(this.#innerWidth(width), source);
73
+ return { lines: this.#withRailAndPromptZone(rendered.lines), anchors: rendered.anchors };
64
74
  }
65
75
  render(width: number): string[] {
66
- return this.#withPromptZone(this.#markdown.render(width));
76
+ return this.#withRailAndPromptZone(this.#markdown.render(this.#innerWidth(width)));
67
77
  }
68
78
  }