@adecore/agents-react 0.23.1 → 0.24.1
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/README.md +14 -11
- package/dist/agents/AccountDot.js +3 -1
- package/dist/agents/accounts.d.ts +3 -2
- package/dist/agents/accounts.js +5 -6
- package/dist/chat/actions.d.ts +2 -2
- package/dist/chat/actions.js +8 -5
- package/dist/chat/bookmarks.d.ts +5 -3
- package/dist/chat/bookmarks.js +13 -14
- package/dist/chat/chat-client.d.ts +1 -0
- package/dist/chat/chat-client.js +7 -0
- package/dist/chat/composer-send.d.ts +3 -0
- package/dist/chat/composer-send.js +22 -0
- package/dist/chat/forks.js +22 -5
- package/dist/chat/index.d.ts +31 -0
- package/dist/chat/index.js +28 -0
- package/dist/chat/logic/embedded-code.d.ts +8 -0
- package/dist/chat/logic/embedded-code.js +371 -0
- package/dist/chat/logic/fork.js +1 -0
- package/dist/chat/logic/goal.d.ts +6 -0
- package/dist/chat/logic/goal.js +26 -0
- package/dist/chat/logic/scrubber.d.ts +1 -1
- package/dist/chat/logic/scrubber.js +6 -5
- package/dist/chat/logic/thought.d.ts +3 -0
- package/dist/chat/logic/thought.js +35 -0
- package/dist/chat/logic/thread-cards.js +2 -0
- package/dist/chat/logic/timeline.d.ts +5 -0
- package/dist/chat/logic/timeline.js +22 -1
- package/dist/chat/logic/tool-catalog.d.ts +1 -0
- package/dist/chat/logic/tool-catalog.js +3 -1
- package/dist/chat/logic/tools.d.ts +14 -0
- package/dist/chat/logic/tools.js +57 -0
- package/dist/chat/preferences.d.ts +6 -0
- package/dist/chat/preferences.js +19 -3
- package/dist/chat/reading-width.d.ts +9 -0
- package/dist/chat/reading-width.js +27 -0
- package/dist/chat/send-key.d.ts +10 -0
- package/dist/chat/send-key.js +24 -0
- package/dist/chat/ui/BookmarkMarker.js +4 -2
- package/dist/chat/ui/BookmarkSubmenu.js +4 -3
- package/dist/chat/ui/ChatActivity.js +14 -10
- package/dist/chat/ui/ChatReferenceChip.js +3 -4
- package/dist/chat/ui/CodeBlock.d.ts +2 -1
- package/dist/chat/ui/CodeBlock.js +14 -96
- package/dist/chat/ui/Composer.d.ts +1 -1
- package/dist/chat/ui/Composer.js +130 -907
- package/dist/chat/ui/ComposerInput.js +5 -4
- package/dist/chat/ui/EditDiff.js +3 -2
- package/dist/chat/ui/GoalDock.d.ts +9 -0
- package/dist/chat/ui/GoalDock.js +63 -0
- package/dist/chat/ui/LimitState.js +5 -3
- package/dist/chat/ui/Markdown.d.ts +2 -1
- package/dist/chat/ui/Markdown.js +70 -31
- package/dist/chat/ui/MermaidBlock.d.ts +3 -0
- package/dist/chat/ui/MermaidBlock.js +73 -0
- package/dist/chat/ui/MessageActions.js +12 -7
- package/dist/chat/ui/Pickers.js +6 -5
- package/dist/chat/ui/PromptComposer.js +4 -3
- package/dist/chat/ui/RowTime.d.ts +3 -0
- package/dist/chat/ui/RowTime.js +12 -0
- package/dist/chat/ui/RunSettings.js +5 -4
- package/dist/chat/ui/ShellCommand.d.ts +3 -0
- package/dist/chat/ui/ShellCommand.js +110 -0
- package/dist/chat/ui/SubagentInfo.js +4 -3
- package/dist/chat/ui/SubagentStopButton.js +4 -3
- package/dist/chat/ui/SubagentTimeline.js +4 -1
- package/dist/chat/ui/Timeline.js +70 -40
- package/dist/chat/ui/TimelineMenu.d.ts +2 -1
- package/dist/chat/ui/TimelineMenu.js +18 -8
- package/dist/chat/ui/UnifiedDiff.js +3 -2
- package/dist/chat/ui/VisualFrame.js +6 -7
- package/dist/chat/ui/code-theme.d.ts +2 -1
- package/dist/chat/ui/code-theme.js +4 -4
- package/dist/chat/ui/composer/ComposerControls.d.ts +27 -0
- package/dist/chat/ui/composer/ComposerControls.js +34 -0
- package/dist/chat/ui/composer/ComposerMenu.d.ts +4 -0
- package/dist/chat/ui/composer/ComposerMenu.js +46 -0
- package/dist/chat/ui/composer/DraftParts.d.ts +18 -0
- package/dist/chat/ui/composer/DraftParts.js +25 -0
- package/dist/chat/ui/composer/QueuedMessages.d.ts +10 -0
- package/dist/chat/ui/composer/QueuedMessages.js +20 -0
- package/dist/chat/ui/composer/key-handler.d.ts +20 -0
- package/dist/chat/ui/composer/key-handler.js +150 -0
- package/dist/chat/ui/composer/keys.d.ts +7 -2
- package/dist/chat/ui/composer/keys.js +19 -9
- package/dist/chat/ui/composer/placeholder.d.ts +9 -0
- package/dist/chat/ui/composer/placeholder.js +38 -0
- package/dist/chat/ui/composer/surface.d.ts +1 -0
- package/dist/chat/ui/composer/surface.js +44 -0
- package/dist/chat/ui/composer/use-chat-controls.d.ts +28 -0
- package/dist/chat/ui/composer/use-chat-controls.js +112 -0
- package/dist/chat/ui/composer/use-compaction-offer.d.ts +2 -0
- package/dist/chat/ui/composer/use-compaction-offer.js +23 -0
- package/dist/chat/ui/composer/use-composer-draft.d.ts +14 -0
- package/dist/chat/ui/composer/use-composer-draft.js +39 -0
- package/dist/chat/ui/composer/use-composer-menus.d.ts +52 -0
- package/dist/chat/ui/composer/use-composer-menus.js +232 -0
- package/dist/chat/ui/composer/use-composer-submit.d.ts +32 -0
- package/dist/chat/ui/composer/use-composer-submit.js +113 -0
- package/dist/chat/ui/composer/use-draft-files.d.ts +27 -0
- package/dist/chat/ui/composer/use-draft-files.js +111 -0
- package/dist/chat/ui/composer/use-notice.d.ts +6 -0
- package/dist/chat/ui/composer/use-notice.js +18 -0
- package/dist/chat/ui/composer/use-pending-requests.d.ts +2 -0
- package/dist/chat/ui/composer/use-pending-requests.js +22 -0
- package/dist/chat/ui/composer/use-prompt-history.d.ts +8 -0
- package/dist/chat/ui/composer/use-prompt-history.js +57 -0
- package/dist/chat/ui/composer/use-prompt-hold.d.ts +6 -0
- package/dist/chat/ui/composer/use-prompt-hold.js +48 -0
- package/dist/chat/ui/composer/use-take-back.d.ts +13 -0
- package/dist/chat/ui/composer/use-take-back.js +82 -0
- package/dist/chat/ui/diff/EditorEditDiff.js +4 -3
- package/dist/chat/ui/diff/LegacyDiffPool.js +3 -2
- package/dist/chat/ui/diff/LegacyEditDiff.js +3 -2
- package/dist/chat/ui/diff/LegacyUnifiedDiff.js +3 -2
- package/dist/chat/ui/diff/syntax-runtime.d.ts +2 -1
- package/dist/chat/ui/diff/syntax-runtime.js +9 -5
- package/dist/chat/ui/diff-syntax.d.ts +2 -2
- package/dist/chat/ui/diff-syntax.js +1 -2
- package/dist/chat/ui/diff-theme.js +10 -9
- package/dist/chat/ui/file-links.d.ts +1 -1
- package/dist/chat/ui/file-links.js +7 -5
- package/dist/chat/ui/highlighter.d.ts +27 -0
- package/dist/chat/ui/highlighter.js +151 -0
- package/dist/chat/ui/index.d.ts +67 -0
- package/dist/chat/ui/index.js +64 -0
- package/dist/chat/ui/intelligent-ui/UiReply.js +3 -2
- package/dist/chat/ui/intelligent-ui/renderers/content.js +3 -2
- package/dist/chat/ui/intelligent-ui/use-ui-links.js +2 -2
- package/dist/chat/ui/intelligent-ui/use-ui-queries.js +4 -3
- package/dist/chat/ui/markdown-blocks.d.ts +8 -0
- package/dist/chat/ui/markdown-blocks.js +260 -12
- package/dist/chat/ui/mermaid.d.ts +23 -0
- package/dist/chat/ui/mermaid.js +93 -0
- package/dist/chat/ui/rows/ForksRow.js +5 -3
- package/dist/chat/ui/rows/GeneratedImageRow.js +6 -4
- package/dist/chat/ui/rows/MessageRows.d.ts +4 -0
- package/dist/chat/ui/rows/MessageRows.js +36 -13
- package/dist/chat/ui/rows/Rows.js +3 -1
- package/dist/chat/ui/rows/SubagentRow.js +3 -2
- package/dist/chat/ui/rows/VisualRow.js +3 -2
- package/dist/chat/ui/rows/WorkRows.d.ts +6 -3
- package/dist/chat/ui/rows/WorkRows.js +57 -20
- package/dist/chat/ui/rows/WorkflowRow.js +3 -2
- package/dist/chat/ui/timestamp-format.d.ts +1 -0
- package/dist/chat/ui/timestamp-format.js +2 -0
- package/dist/chat/ui/use-diff-source-tokens.js +6 -3
- package/dist/chat/ui/visual-theme.js +3 -2
- package/dist/host.d.ts +9 -6
- package/dist/host.js +19 -8
- package/dist/index.d.ts +8 -0
- package/dist/index.js +9 -0
- package/dist/locales/en/agent-chat.d.ts +658 -0
- package/dist/locales/en/agent-chat.js +657 -0
- package/dist/locales/en/agent-prompts.d.ts +44 -0
- package/dist/locales/en/agent-prompts.js +43 -0
- package/dist/locales/en/agent-providers.d.ts +132 -0
- package/dist/locales/en/agent-providers.js +131 -0
- package/dist/locales/en/agent-usage.d.ts +132 -0
- package/dist/locales/en/agent-usage.js +131 -0
- package/dist/locales/nl/agent-chat.d.ts +658 -0
- package/dist/locales/nl/agent-chat.js +657 -0
- package/dist/locales/nl/agent-prompts.d.ts +44 -0
- package/dist/locales/nl/agent-prompts.js +43 -0
- package/dist/locales/nl/agent-providers.d.ts +132 -0
- package/dist/locales/nl/agent-providers.js +131 -0
- package/dist/locales/nl/agent-usage.d.ts +132 -0
- package/dist/locales/nl/agent-usage.js +131 -0
- package/dist/locales.js +9 -8
- package/dist/prompts/index.d.ts +11 -0
- package/dist/prompts/index.js +12 -0
- package/dist/prompts/logic/subjects.d.ts +2 -1
- package/dist/prompts/logic/subjects.js +6 -4
- package/dist/prompts/ui/ApprovalBody.js +6 -4
- package/dist/prompts/ui/PromptCard.js +5 -4
- package/dist/prompts/ui/PromptView.js +3 -2
- package/dist/prompts/ui/QuestionBody.js +3 -2
- package/dist/providers/AccountColors.js +2 -2
- package/dist/providers/AccountDetail.js +7 -6
- package/dist/providers/AccountVariables.js +3 -1
- package/dist/providers/AddAccountForm.js +3 -2
- package/dist/providers/ProvidersPane.js +4 -3
- package/dist/providers/account-actions.d.ts +4 -3
- package/dist/providers/account-actions.js +6 -7
- package/dist/providers/index.d.ts +16 -0
- package/dist/providers/index.js +17 -0
- package/dist/providers/provider-abilities.d.ts +2 -1
- package/dist/providers/provider-abilities.js +4 -0
- package/dist/state/chats.js +4 -2
- package/dist/state/index.d.ts +4 -0
- package/dist/state/index.js +5 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +2 -0
- package/dist/{prompts/logic/prompts.fixtures.d.ts → testing/prompts.d.ts} +1 -1
- package/dist/theme.css +13 -3
- package/dist/usage/UsageBreakdown.js +3 -2
- package/dist/usage/UsageLimits.js +4 -3
- package/dist/usage/UsagePage.js +3 -2
- package/dist/usage/index.d.ts +15 -0
- package/dist/usage/index.js +15 -0
- package/package.json +53 -1031
- package/src/agents/AccountDot.tsx +3 -1
- package/src/agents/accounts.ts +6 -6
- package/src/chat/actions.ts +8 -5
- package/src/chat/bookmarks.ts +23 -14
- package/src/chat/chat-client.ts +8 -0
- package/src/chat/composer-send.ts +27 -0
- package/src/chat/forks.ts +27 -6
- package/src/chat/index.ts +98 -0
- package/src/chat/logic/embedded-code.ts +409 -0
- package/src/chat/logic/fork.ts +1 -0
- package/src/chat/logic/goal.ts +33 -0
- package/src/chat/logic/scrubber.ts +6 -5
- package/src/chat/logic/thought.ts +40 -0
- package/src/chat/logic/thread-cards.ts +2 -0
- package/src/chat/logic/timeline.ts +25 -1
- package/src/chat/logic/tool-catalog.ts +5 -1
- package/src/chat/logic/tools.ts +76 -0
- package/src/chat/preferences.ts +26 -3
- package/src/chat/reading-width.ts +39 -0
- package/src/chat/send-key.ts +38 -0
- package/src/chat/ui/BookmarkMarker.tsx +4 -2
- package/src/chat/ui/BookmarkSubmenu.tsx +4 -3
- package/src/chat/ui/ChatActivity.tsx +14 -10
- package/src/chat/ui/ChatReferenceChip.tsx +3 -4
- package/src/chat/ui/CodeBlock.tsx +33 -124
- package/src/chat/ui/Composer.tsx +212 -1358
- package/src/chat/ui/ComposerInput.tsx +5 -4
- package/src/chat/ui/EditDiff.tsx +3 -2
- package/src/chat/ui/GoalDock.tsx +104 -0
- package/src/chat/ui/LimitState.tsx +5 -3
- package/src/chat/ui/Markdown.tsx +113 -39
- package/src/chat/ui/MermaidBlock.tsx +119 -0
- package/src/chat/ui/MessageActions.tsx +13 -8
- package/src/chat/ui/Pickers.tsx +8 -7
- package/src/chat/ui/PromptComposer.tsx +4 -3
- package/src/chat/ui/RowTime.tsx +21 -0
- package/src/chat/ui/RunSettings.tsx +9 -4
- package/src/chat/ui/ShellCommand.tsx +134 -0
- package/src/chat/ui/SubagentInfo.tsx +4 -3
- package/src/chat/ui/SubagentStopButton.tsx +4 -3
- package/src/chat/ui/SubagentTimeline.tsx +5 -1
- package/src/chat/ui/Timeline.tsx +167 -105
- package/src/chat/ui/TimelineMenu.tsx +42 -7
- package/src/chat/ui/UnifiedDiff.tsx +3 -2
- package/src/chat/ui/VisualFrame.tsx +6 -7
- package/src/chat/ui/code-theme.ts +4 -4
- package/src/chat/ui/composer/ComposerControls.tsx +147 -0
- package/src/chat/ui/composer/ComposerMenu.tsx +138 -0
- package/src/chat/ui/composer/DraftParts.tsx +86 -0
- package/src/chat/ui/composer/QueuedMessages.tsx +64 -0
- package/src/chat/ui/composer/key-handler.ts +174 -0
- package/src/chat/ui/composer/keys.ts +32 -10
- package/src/chat/ui/composer/placeholder.ts +48 -0
- package/src/chat/ui/composer/surface.ts +45 -0
- package/src/chat/ui/composer/use-chat-controls.ts +134 -0
- package/src/chat/ui/composer/use-compaction-offer.ts +25 -0
- package/src/chat/ui/composer/use-composer-draft.ts +71 -0
- package/src/chat/ui/composer/use-composer-menus.ts +289 -0
- package/src/chat/ui/composer/use-composer-submit.ts +163 -0
- package/src/chat/ui/composer/use-draft-files.ts +135 -0
- package/src/chat/ui/composer/use-notice.ts +28 -0
- package/src/chat/ui/composer/use-pending-requests.ts +23 -0
- package/src/chat/ui/composer/use-prompt-history.ts +76 -0
- package/src/chat/ui/composer/use-prompt-hold.ts +58 -0
- package/src/chat/ui/composer/use-take-back.ts +91 -0
- package/src/chat/ui/diff/DiffFallbackBoundary.tsx +2 -2
- package/src/chat/ui/diff/EditorEditDiff.tsx +4 -3
- package/src/chat/ui/diff/LegacyDiffPool.tsx +3 -2
- package/src/chat/ui/diff/LegacyEditDiff.tsx +3 -2
- package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +3 -2
- package/src/chat/ui/diff/syntax-runtime.ts +10 -5
- package/src/chat/ui/diff-syntax.ts +2 -2
- package/src/chat/ui/diff-theme.ts +10 -9
- package/src/chat/ui/file-links.ts +7 -5
- package/src/chat/ui/highlighter.ts +185 -0
- package/src/chat/ui/index.ts +109 -0
- package/src/chat/ui/intelligent-ui/UiReply.tsx +5 -4
- package/src/chat/ui/intelligent-ui/renderers/content.tsx +3 -2
- package/src/chat/ui/intelligent-ui/use-ui-links.ts +2 -2
- package/src/chat/ui/intelligent-ui/use-ui-queries.ts +4 -3
- package/src/chat/ui/markdown-blocks.ts +300 -12
- package/src/chat/ui/mermaid.ts +118 -0
- package/src/chat/ui/rows/ForksRow.tsx +5 -3
- package/src/chat/ui/rows/GeneratedImageRow.tsx +6 -4
- package/src/chat/ui/rows/MessageRows.tsx +73 -18
- package/src/chat/ui/rows/Rows.tsx +14 -1
- package/src/chat/ui/rows/SubagentRow.tsx +3 -2
- package/src/chat/ui/rows/VisualRow.tsx +3 -2
- package/src/chat/ui/rows/WorkRows.tsx +148 -47
- package/src/chat/ui/rows/WorkflowRow.tsx +3 -2
- package/src/chat/ui/timestamp-format.ts +2 -0
- package/src/chat/ui/use-diff-source-tokens.ts +10 -4
- package/src/chat/ui/visual-theme.ts +3 -2
- package/src/host.ts +31 -15
- package/src/index.ts +49 -0
- package/src/locales/en/agent-chat.ts +657 -0
- package/src/locales/en/agent-prompts.ts +43 -0
- package/src/locales/en/agent-providers.ts +131 -0
- package/src/locales/en/agent-usage.ts +131 -0
- package/src/locales/nl/agent-chat.ts +657 -0
- package/src/locales/nl/agent-prompts.ts +43 -0
- package/src/locales/nl/agent-providers.ts +131 -0
- package/src/locales/nl/agent-usage.ts +132 -0
- package/src/locales.ts +9 -8
- package/src/prompts/index.ts +35 -0
- package/src/prompts/logic/subjects.ts +12 -4
- package/src/prompts/ui/ApprovalBody.tsx +6 -4
- package/src/prompts/ui/PromptCard.tsx +11 -4
- package/src/prompts/ui/PromptView.tsx +3 -2
- package/src/prompts/ui/QuestionBody.tsx +3 -2
- package/src/providers/AccountColors.tsx +2 -2
- package/src/providers/AccountDetail.tsx +13 -7
- package/src/providers/AccountVariables.tsx +3 -1
- package/src/providers/AddAccountForm.tsx +3 -2
- package/src/providers/ProvidersPane.tsx +4 -3
- package/src/providers/account-actions.ts +7 -7
- package/src/providers/index.ts +17 -0
- package/src/providers/provider-abilities.ts +6 -1
- package/src/state/chats.ts +4 -2
- package/src/state/index.ts +39 -0
- package/src/testing/index.ts +2 -0
- package/src/{prompts/logic/prompts.fixtures.ts → testing/prompts.ts} +1 -1
- package/src/theme.css +13 -3
- package/src/usage/UsageBreakdown.tsx +3 -2
- package/src/usage/UsageLimits.tsx +4 -3
- package/src/usage/UsagePage.tsx +3 -2
- package/src/usage/index.ts +16 -0
- package/dist/locales/en/agent-chat.json +0 -597
- package/dist/locales/en/agent-prompts.json +0 -43
- package/dist/locales/en/agent-providers.json +0 -131
- package/dist/locales/en/agent-usage.json +0 -131
- package/dist/locales/nl/agent-chat.json +0 -597
- package/dist/locales/nl/agent-prompts.json +0 -43
- package/dist/locales/nl/agent-providers.json +0 -131
- package/dist/locales/nl/agent-usage.json +0 -131
- package/src/locales/en/agent-chat.json +0 -597
- package/src/locales/en/agent-prompts.json +0 -43
- package/src/locales/en/agent-providers.json +0 -131
- package/src/locales/en/agent-usage.json +0 -131
- package/src/locales/nl/agent-chat.json +0 -597
- package/src/locales/nl/agent-prompts.json +0 -43
- package/src/locales/nl/agent-providers.json +0 -131
- package/src/locales/nl/agent-usage.json +0 -131
- /package/dist/{prompts/logic/prompts.fixtures.js → testing/prompts.js} +0 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/* A stretch of a shell command written in another language, as offsets into the command. */
|
|
2
|
+
export interface EmbeddedCode {
|
|
3
|
+
readonly start: number;
|
|
4
|
+
readonly end: number;
|
|
5
|
+
/* A Shiki language id. */
|
|
6
|
+
readonly language: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const SHELL_LANGUAGE = 'shellscript';
|
|
10
|
+
|
|
11
|
+
// Past this a command is drawn without looking for code inside it; the scan is linear, the highlighting is not.
|
|
12
|
+
export const EMBEDDED_CODE_LIMIT = 20_000;
|
|
13
|
+
const MAX_DEPTH = 3;
|
|
14
|
+
|
|
15
|
+
interface Span {
|
|
16
|
+
readonly start: number;
|
|
17
|
+
readonly end: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface Word extends Span {
|
|
21
|
+
/* The word as the shell hands it to the program, quotes and escapes taken out. */
|
|
22
|
+
readonly text: string;
|
|
23
|
+
/* The source of what the program gets, for a word that is bare or one quoted string; null for a mix. */
|
|
24
|
+
readonly body: Span | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface SimpleCommand {
|
|
28
|
+
readonly words: Word[];
|
|
29
|
+
readonly redirects: { readonly op: string; readonly target: Word }[];
|
|
30
|
+
readonly heredocs: Span[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface Interpreter {
|
|
34
|
+
readonly language: string;
|
|
35
|
+
/* Whether a flag says the next argument is the script. */
|
|
36
|
+
script(flag: string): boolean;
|
|
37
|
+
/* Options that take the next argument as their value. */
|
|
38
|
+
readonly takesValue?: ReadonlySet<string>;
|
|
39
|
+
/* A database client: its arguments name a database, and it reads statements from stdin whatever they are. */
|
|
40
|
+
readonly client?: boolean;
|
|
41
|
+
/* The positional argument that holds statements, as in `sqlite3 app.db 'select 1'`. */
|
|
42
|
+
readonly positional?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const SHELL: Interpreter = {
|
|
46
|
+
language: SHELL_LANGUAGE,
|
|
47
|
+
script: (flag) => /^-[a-zA-Z]*c[a-zA-Z]*$/.test(flag),
|
|
48
|
+
takesValue: new Set(['-o', '-O', '--rcfile', '--init-file'])
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const PYTHON: Interpreter = { language: 'python', script: (flag) => flag === '-c', takesValue: new Set(['-W', '-X']) };
|
|
52
|
+
|
|
53
|
+
const NODE: Interpreter = {
|
|
54
|
+
language: 'javascript',
|
|
55
|
+
script: (flag) => ['-e', '--eval', '-p', '--print'].includes(flag),
|
|
56
|
+
takesValue: new Set(['-r', '--require', '--import', '--input-type', '--conditions', '-C'])
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const BUN: Interpreter = { language: 'typescript', script: (flag) => ['-e', '--eval', '-p', '--print'].includes(flag), takesValue: NODE.takesValue };
|
|
60
|
+
|
|
61
|
+
const MYSQL: Interpreter = {
|
|
62
|
+
language: 'sql',
|
|
63
|
+
client: true,
|
|
64
|
+
script: (flag) => flag === '-e' || flag === '--execute',
|
|
65
|
+
takesValue: new Set(['-u', '-h', '-P', '-D', '-S', '--user', '--host', '--port', '--database'])
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const INTERPRETERS: Record<string, Interpreter> = {
|
|
69
|
+
bash: SHELL,
|
|
70
|
+
sh: SHELL,
|
|
71
|
+
zsh: SHELL,
|
|
72
|
+
dash: SHELL,
|
|
73
|
+
ksh: SHELL,
|
|
74
|
+
python: PYTHON,
|
|
75
|
+
node: NODE,
|
|
76
|
+
bun: BUN,
|
|
77
|
+
psql: {
|
|
78
|
+
language: 'sql',
|
|
79
|
+
client: true,
|
|
80
|
+
script: (flag) => flag === '-c' || flag === '--command',
|
|
81
|
+
takesValue: new Set(['-d', '-h', '-p', '-U', '-f', '-v', '-o', '--dbname', '--host', '--port', '--username', '--file', '--set'])
|
|
82
|
+
},
|
|
83
|
+
mysql: MYSQL,
|
|
84
|
+
mariadb: MYSQL,
|
|
85
|
+
sqlite3: { language: 'sql', client: true, script: () => false, takesValue: new Set(['-cmd', '-separator', '-newline', '-nullvalue']), positional: 1 }
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Programs that run the next word as the command; their own options are skipped along with them.
|
|
89
|
+
const WRAPPERS = new Set(['sudo', 'env', 'exec', 'nohup', 'time', 'command', 'nice']);
|
|
90
|
+
// Tools that run a program of a project's environment as `<tool> run <program>`.
|
|
91
|
+
const RUNNERS = new Set(['uv', 'poetry', 'pipenv']);
|
|
92
|
+
// Where a heredoc written to a file goes, by the file's extension.
|
|
93
|
+
const EXTENSIONS: Record<string, string> = {
|
|
94
|
+
py: 'python',
|
|
95
|
+
js: 'javascript',
|
|
96
|
+
mjs: 'javascript',
|
|
97
|
+
cjs: 'javascript',
|
|
98
|
+
jsx: 'jsx',
|
|
99
|
+
ts: 'typescript',
|
|
100
|
+
mts: 'typescript',
|
|
101
|
+
tsx: 'tsx',
|
|
102
|
+
json: 'json',
|
|
103
|
+
sql: 'sql',
|
|
104
|
+
sh: SHELL_LANGUAGE,
|
|
105
|
+
bash: SHELL_LANGUAGE,
|
|
106
|
+
zsh: SHELL_LANGUAGE,
|
|
107
|
+
yml: 'yaml',
|
|
108
|
+
yaml: 'yaml',
|
|
109
|
+
md: 'markdown',
|
|
110
|
+
css: 'css',
|
|
111
|
+
html: 'html',
|
|
112
|
+
php: 'php',
|
|
113
|
+
xml: 'xml'
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const SEPARATORS = ';&|()';
|
|
117
|
+
const BLANKS = ' \t\r';
|
|
118
|
+
|
|
119
|
+
/* The program a command name runs, as the table above knows it: `/usr/bin/python3.12` is `python`. */
|
|
120
|
+
function programOf(word: string): string {
|
|
121
|
+
const name = word.slice(word.lastIndexOf('/') + 1);
|
|
122
|
+
return /^python\d*(\.\d+)?$/.test(name) ? 'python' : name;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Splits `source[from, to)` into simple commands the way a shell would, enough to find the code in them. */
|
|
126
|
+
function parseCommands(source: string, from: number, to: number): SimpleCommand[] {
|
|
127
|
+
const commands: SimpleCommand[] = [];
|
|
128
|
+
let current: SimpleCommand = { words: [], redirects: [], heredocs: [] };
|
|
129
|
+
const pending: { command: SimpleCommand; delimiter: string; strip: boolean }[] = [];
|
|
130
|
+
const finish = (): void => {
|
|
131
|
+
if (current.words.length > 0 || current.redirects.length > 0) {
|
|
132
|
+
commands.push(current);
|
|
133
|
+
}
|
|
134
|
+
current = { words: [], redirects: [], heredocs: [] };
|
|
135
|
+
};
|
|
136
|
+
const skipBlanks = (index: number): number => {
|
|
137
|
+
while (index < to && BLANKS.includes(source[index]!)) {
|
|
138
|
+
index++;
|
|
139
|
+
}
|
|
140
|
+
return index;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const readWord = (start: number): Word => {
|
|
144
|
+
let index = start;
|
|
145
|
+
let text = '';
|
|
146
|
+
let quoted: Span | null = null;
|
|
147
|
+
let parts = 0;
|
|
148
|
+
let bare = true;
|
|
149
|
+
while (index < to) {
|
|
150
|
+
const char = source[index]!;
|
|
151
|
+
if (BLANKS.includes(char) || char === '\n' || SEPARATORS.includes(char) || char === '<' || char === '>') {
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
parts++;
|
|
155
|
+
if (char === "'") {
|
|
156
|
+
const close = source.indexOf("'", index + 1);
|
|
157
|
+
const end = close === -1 || close >= to ? to : close;
|
|
158
|
+
text += source.slice(index + 1, end);
|
|
159
|
+
quoted = { start: index + 1, end };
|
|
160
|
+
bare = false;
|
|
161
|
+
index = Math.min(end + 1, to);
|
|
162
|
+
} else if (char === '"') {
|
|
163
|
+
let end = index + 1;
|
|
164
|
+
while (end < to && source[end] !== '"') {
|
|
165
|
+
if (source[end] === '\\' && end + 1 < to && '$`"\\\n'.includes(source[end + 1]!)) {
|
|
166
|
+
text += source[end + 1];
|
|
167
|
+
end += 2;
|
|
168
|
+
} else {
|
|
169
|
+
text += source[end];
|
|
170
|
+
end++;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
quoted = { start: index + 1, end };
|
|
174
|
+
bare = false;
|
|
175
|
+
index = Math.min(end + 1, to);
|
|
176
|
+
} else if (char === '\\') {
|
|
177
|
+
text += source[index + 1] ?? '';
|
|
178
|
+
bare = false;
|
|
179
|
+
index += 2;
|
|
180
|
+
} else if (char === '$' && source[index + 1] === '(') {
|
|
181
|
+
const end = closingParen(source, index + 1, to);
|
|
182
|
+
text += source.slice(index, end);
|
|
183
|
+
index = end;
|
|
184
|
+
} else {
|
|
185
|
+
text += char;
|
|
186
|
+
index++;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const end = Math.min(index, to);
|
|
190
|
+
const body = bare ? { start, end } : parts === 1 && quoted !== null ? quoted : null;
|
|
191
|
+
return { start, end, text, body };
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const readHeredoc = (start: number, delimiter: string, strip: boolean): { body: Span; next: number } => {
|
|
195
|
+
let index = start;
|
|
196
|
+
while (index < to) {
|
|
197
|
+
const newline = source.indexOf('\n', index);
|
|
198
|
+
const lineEnd = newline === -1 || newline >= to ? to : newline;
|
|
199
|
+
let line = source.slice(index, lineEnd).replace(/\r$/, '');
|
|
200
|
+
if (strip) {
|
|
201
|
+
line = line.replace(/^\t+/, '');
|
|
202
|
+
}
|
|
203
|
+
if (line === delimiter) {
|
|
204
|
+
return { body: { start, end: index }, next: Math.min(lineEnd + 1, to) };
|
|
205
|
+
}
|
|
206
|
+
index = lineEnd + 1;
|
|
207
|
+
}
|
|
208
|
+
return { body: { start, end: to }, next: to };
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
let index = from;
|
|
212
|
+
while (index < to) {
|
|
213
|
+
const char = source[index]!;
|
|
214
|
+
if (char === '\n') {
|
|
215
|
+
index++;
|
|
216
|
+
for (const heredoc of pending) {
|
|
217
|
+
const { body, next } = readHeredoc(index, heredoc.delimiter, heredoc.strip);
|
|
218
|
+
heredoc.command.heredocs.push(body);
|
|
219
|
+
index = next;
|
|
220
|
+
}
|
|
221
|
+
pending.length = 0;
|
|
222
|
+
finish();
|
|
223
|
+
} else if (BLANKS.includes(char)) {
|
|
224
|
+
index++;
|
|
225
|
+
} else if (char === '\\' && source[index + 1] === '\n') {
|
|
226
|
+
index += 2;
|
|
227
|
+
} else if (char === '#') {
|
|
228
|
+
const newline = source.indexOf('\n', index);
|
|
229
|
+
index = newline === -1 || newline >= to ? to : newline;
|
|
230
|
+
} else if (source.startsWith('<<<', index)) {
|
|
231
|
+
index = readWord(skipBlanks(index + 3)).end;
|
|
232
|
+
} else if (source.startsWith('<<', index)) {
|
|
233
|
+
index += 2;
|
|
234
|
+
const strip = source[index] === '-';
|
|
235
|
+
const word = readWord(skipBlanks(strip ? index + 1 : index));
|
|
236
|
+
if (word.text !== '') {
|
|
237
|
+
pending.push({ command: current, delimiter: word.text, strip });
|
|
238
|
+
}
|
|
239
|
+
index = word.end;
|
|
240
|
+
} else if (char === '<' || char === '>' || (char === '&' && source[index + 1] === '>')) {
|
|
241
|
+
let end = index + 1;
|
|
242
|
+
while (end < to && '<>&|'.includes(source[end]!)) {
|
|
243
|
+
end++;
|
|
244
|
+
}
|
|
245
|
+
const op = source.slice(index, end);
|
|
246
|
+
const target = readWord(skipBlanks(end));
|
|
247
|
+
current.redirects.push({ op, target });
|
|
248
|
+
index = Math.max(target.end, end);
|
|
249
|
+
} else if (SEPARATORS.includes(char)) {
|
|
250
|
+
index++;
|
|
251
|
+
finish();
|
|
252
|
+
} else {
|
|
253
|
+
const word = readWord(index);
|
|
254
|
+
// The `2` of `2>&1` is a file descriptor, not an argument.
|
|
255
|
+
const descriptor = /^\d+$/.test(word.text) && (source[word.end] === '>' || source[word.end] === '<');
|
|
256
|
+
if (!descriptor) {
|
|
257
|
+
current.words.push(word);
|
|
258
|
+
}
|
|
259
|
+
index = Math.max(word.end, index + 1);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
finish();
|
|
263
|
+
return commands;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/* Where the `)` that closes the `(` at `open` is, past it; quotes inside do not count. */
|
|
267
|
+
function closingParen(source: string, open: number, to: number): number {
|
|
268
|
+
let depth = 0;
|
|
269
|
+
let quote: string | null = null;
|
|
270
|
+
for (let index = open; index < to; index++) {
|
|
271
|
+
const char = source[index]!;
|
|
272
|
+
if (quote !== null) {
|
|
273
|
+
if (char === quote) {
|
|
274
|
+
quote = null;
|
|
275
|
+
} else if (char === '\\' && quote === '"') {
|
|
276
|
+
index++;
|
|
277
|
+
}
|
|
278
|
+
} else if (char === "'" || char === '"') {
|
|
279
|
+
quote = char;
|
|
280
|
+
} else if (char === '(') {
|
|
281
|
+
depth++;
|
|
282
|
+
} else if (char === ')') {
|
|
283
|
+
depth--;
|
|
284
|
+
if (depth === 0) {
|
|
285
|
+
return index + 1;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return to;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* The index of the word a command runs, past variable assignments, wrappers and runners; -1 for none. */
|
|
293
|
+
function programIndex(words: readonly Word[]): number {
|
|
294
|
+
let index = 0;
|
|
295
|
+
while (index < words.length) {
|
|
296
|
+
const text = words[index]!.text;
|
|
297
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(text)) {
|
|
298
|
+
index++;
|
|
299
|
+
} else if (WRAPPERS.has(programOf(text))) {
|
|
300
|
+
index++;
|
|
301
|
+
while (index < words.length && words[index]!.text.startsWith('-')) {
|
|
302
|
+
index++;
|
|
303
|
+
}
|
|
304
|
+
} else if (RUNNERS.has(programOf(text)) && words[index + 1]?.text === 'run') {
|
|
305
|
+
index += 2;
|
|
306
|
+
} else {
|
|
307
|
+
return index;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return -1;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/* The code of one simple command: an inline script, or a heredoc fed to an interpreter or written to a file. */
|
|
314
|
+
function codeOf(command: SimpleCommand): EmbeddedCode[] {
|
|
315
|
+
const found: EmbeddedCode[] = [];
|
|
316
|
+
const at = programIndex(command.words);
|
|
317
|
+
const program = at === -1 ? '' : programOf(command.words[at]!.text);
|
|
318
|
+
const interpreter = INTERPRETERS[program];
|
|
319
|
+
let readsFile = false;
|
|
320
|
+
if (interpreter !== undefined) {
|
|
321
|
+
let positional = 0;
|
|
322
|
+
for (let index = at + 1; index < command.words.length; index++) {
|
|
323
|
+
const word = command.words[index]!;
|
|
324
|
+
if (word.text.startsWith('-') && word.text !== '-') {
|
|
325
|
+
if (interpreter.script(word.text)) {
|
|
326
|
+
const script = command.words[index + 1];
|
|
327
|
+
if (script?.body) {
|
|
328
|
+
found.push({ ...script.body, language: interpreter.language });
|
|
329
|
+
}
|
|
330
|
+
readsFile = true;
|
|
331
|
+
index++;
|
|
332
|
+
} else if (interpreter.takesValue?.has(word.text)) {
|
|
333
|
+
index++;
|
|
334
|
+
} else if (interpreter === PYTHON && word.text === '-m') {
|
|
335
|
+
readsFile = true;
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (interpreter.client !== true) {
|
|
341
|
+
// A script file; whatever follows is its own arguments.
|
|
342
|
+
readsFile = word.text !== '-';
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
if (positional === interpreter.positional && word.body !== null) {
|
|
346
|
+
found.push({ ...word.body, language: interpreter.language });
|
|
347
|
+
}
|
|
348
|
+
positional++;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const heredocLanguage = interpreter !== undefined && (!readsFile || interpreter.client === true) ? interpreter.language : writtenLanguage(command, program);
|
|
352
|
+
if (heredocLanguage !== null) {
|
|
353
|
+
for (const body of command.heredocs) {
|
|
354
|
+
found.push({ ...body, language: heredocLanguage });
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return found;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/* The language of the file a `cat > file` or `tee file` writes, by its extension; null when nothing says. */
|
|
361
|
+
function writtenLanguage(command: SimpleCommand, program: string): string | null {
|
|
362
|
+
const targets = command.redirects.filter(({ op }) => op === '>' || op === '>>' || op === '>|').map(({ target }) => target.text);
|
|
363
|
+
if (program === 'tee') {
|
|
364
|
+
targets.push(
|
|
365
|
+
...command.words
|
|
366
|
+
.slice(programIndex(command.words) + 1)
|
|
367
|
+
.map(({ text }) => text)
|
|
368
|
+
.filter((text) => !text.startsWith('-'))
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
for (const target of targets) {
|
|
372
|
+
const dot = target.lastIndexOf('.');
|
|
373
|
+
const language = dot === -1 ? undefined : EXTENSIONS[target.slice(dot + 1).toLowerCase()];
|
|
374
|
+
if (language !== undefined) {
|
|
375
|
+
return language;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function collect(source: string, from: number, to: number, depth: number, into: EmbeddedCode[]): void {
|
|
382
|
+
for (const command of parseCommands(source, from, to)) {
|
|
383
|
+
for (const code of codeOf(command)) {
|
|
384
|
+
if (code.end <= code.start) {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
into.push(code);
|
|
388
|
+
if (code.language === SHELL_LANGUAGE && depth < MAX_DEPTH) {
|
|
389
|
+
collect(source, code.start, code.end, depth + 1, into);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/*
|
|
396
|
+
* The code inside a shell command that is in another language, or a shell script of its own: the
|
|
397
|
+
* script of `bash -lc '…'`, `python3 -c '…'`, `node -e '…'`, `psql -c '…'`, and a heredoc fed to an
|
|
398
|
+
* interpreter or written to a file of a known kind. Sorted by start, a stretch before the ones inside
|
|
399
|
+
* it, so drawing them in order leaves the innermost language on top. A heuristic over a small shell
|
|
400
|
+
* grammar, not a parser: what it does not recognize stays plain shell.
|
|
401
|
+
*/
|
|
402
|
+
export function embeddedCode(command: string): EmbeddedCode[] {
|
|
403
|
+
if (command.length > EMBEDDED_CODE_LIMIT) {
|
|
404
|
+
return [];
|
|
405
|
+
}
|
|
406
|
+
const found: EmbeddedCode[] = [];
|
|
407
|
+
collect(command, 0, command.length, 0, found);
|
|
408
|
+
return found.sort((left, right) => left.start - right.start || right.end - left.end);
|
|
409
|
+
}
|
package/src/chat/logic/fork.ts
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ChatGoal, ChatGoalAction, ChatInfo, ProviderCapabilities } from '@adecore/agent-contracts';
|
|
2
|
+
|
|
3
|
+
export interface GoalView {
|
|
4
|
+
goal: ChatGoal;
|
|
5
|
+
// What a person may do to it from the row; empty where the CLI or the host offers nothing.
|
|
6
|
+
actions: ChatGoalAction[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* The row above the composer while the chat has a goal. A CLI with calls for goals pauses, resumes and
|
|
11
|
+
* clears it at any time; one that only reads `/goal` clears it with a turn of its own, so only between
|
|
12
|
+
* turns, and a goal it already met clears itself.
|
|
13
|
+
*/
|
|
14
|
+
export function goalView(info: Pick<ChatInfo, 'goal' | 'activeTurnId'>, goals: ProviderCapabilities['goals'], canAct: boolean): GoalView | null {
|
|
15
|
+
const goal = info.goal;
|
|
16
|
+
if (goal === undefined) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
if (!canAct || goals === undefined) {
|
|
20
|
+
return { goal, actions: [] };
|
|
21
|
+
}
|
|
22
|
+
if (goals === 'command') {
|
|
23
|
+
return { goal, actions: goal.status === 'complete' || info.activeTurnId !== null ? [] : ['clear'] };
|
|
24
|
+
}
|
|
25
|
+
const actions: ChatGoalAction[] = [];
|
|
26
|
+
if (goal.status === 'active') {
|
|
27
|
+
actions.push('pause');
|
|
28
|
+
} else if (goal.status !== 'complete') {
|
|
29
|
+
actions.push('resume');
|
|
30
|
+
}
|
|
31
|
+
actions.push('clear');
|
|
32
|
+
return { goal, actions };
|
|
33
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ChatBookmark } from '@adecore/agent-contracts';
|
|
2
2
|
import type { TimelineRow } from './timeline';
|
|
3
|
+
import { DEFAULT_READING_WIDTH, readingMeasure } from '../reading-width';
|
|
3
4
|
|
|
4
5
|
/*
|
|
5
6
|
* The strip at the left edge of a chat view with a tick per message a person sent, as numbers: which
|
|
@@ -30,8 +31,7 @@ export const TICK_HEIGHT_PX = 2;
|
|
|
30
31
|
const PITCH_PX = 8;
|
|
31
32
|
const MIN_PITCH_PX = 4;
|
|
32
33
|
|
|
33
|
-
/* The
|
|
34
|
-
const CONTENT_MAX_WIDTH_PX = 768;
|
|
34
|
+
/* The scroller's padding around the thread's content column (`px-4`). */
|
|
35
35
|
const THREAD_PADDING_PX = 16;
|
|
36
36
|
export const STRIP_WIDTH_PX = 24;
|
|
37
37
|
/* The strip's distance from the view's left edge, and the air it keeps between itself and the thread's text. */
|
|
@@ -273,13 +273,14 @@ export function stepMessage(starts: readonly number[], eligible: (index: number)
|
|
|
273
273
|
/*
|
|
274
274
|
* The left padding of the thread in a view that draws the strip at its left edge. A wide view keeps
|
|
275
275
|
* the usual padding, since its centered column already clears the strip; a narrower one pads until
|
|
276
|
-
* the column's text starts clear of it, so nothing is ever drawn under the strip.
|
|
276
|
+
* the column's text starts clear of it, so nothing is ever drawn under the strip. `measure` is the
|
|
277
|
+
* widest the content column runs (`readingMeasure`).
|
|
277
278
|
*/
|
|
278
|
-
export function threadPaddingLeft(width: number, strip: boolean): number {
|
|
279
|
+
export function threadPaddingLeft(width: number, strip: boolean, measure = readingMeasure(DEFAULT_READING_WIDTH)): number {
|
|
279
280
|
if (!strip) {
|
|
280
281
|
return THREAD_PADDING_PX;
|
|
281
282
|
}
|
|
282
283
|
// The column's margin is half of what the padding leaves, so the padding counts the clearance twice.
|
|
283
|
-
const padding = 2 * STRIP_CLEARANCE_PX +
|
|
284
|
+
const padding = 2 * STRIP_CLEARANCE_PX + measure + THREAD_PADDING_PX - Math.floor(width);
|
|
284
285
|
return Math.min(STRIP_CLEARANCE_PX, Math.max(THREAD_PADDING_PX, padding));
|
|
285
286
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { stripMarkdown } from './timeline-copy';
|
|
2
|
+
|
|
3
|
+
// A line that is nothing but bold, the way a model titles a stretch of its reasoning.
|
|
4
|
+
const HEADING_LINE = /^[ \t]*\*\*([^*\n]+?)\*\*[ \t]*$/;
|
|
5
|
+
const LIST_MARKER = /^[ \t]*(?:[-*+]|\d+[.)])[ \t]+/gm;
|
|
6
|
+
// A sentence ends on a stop with white space after it, so `a.ts` and `1.5` stay whole.
|
|
7
|
+
const SENTENCE_END = /[.!?]["'”’)\]]*(?=\s)/;
|
|
8
|
+
|
|
9
|
+
/* The thought as one line of plain text: the marks gone and every run of white space one space. */
|
|
10
|
+
export function flattenThought(text: string): string {
|
|
11
|
+
return stripMarkdown(text.replace(LIST_MARKER, '')).replace(/\s+/g, ' ').trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function headingOf(line: string): string | null {
|
|
15
|
+
return HEADING_LINE.exec(line)?.[1] ?? null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* What a folded thought shows on its one line: the heading it opens with, or the whole thought flattened for the row to cut off. */
|
|
19
|
+
export function thoughtPreview(text: string): string {
|
|
20
|
+
const heading = headingOf(text.trimStart().split('\n', 1)[0] ?? '');
|
|
21
|
+
return flattenThought(heading ?? text);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* What the model is thinking about now, in a line: the heading of the last stretch it titled, which
|
|
26
|
+
* is the one it is on, or the first sentence of a thought that titles nothing.
|
|
27
|
+
*/
|
|
28
|
+
export function thoughtLine(text: string): string {
|
|
29
|
+
const headings = text
|
|
30
|
+
.split('\n')
|
|
31
|
+
.map(headingOf)
|
|
32
|
+
.filter((heading): heading is string => heading !== null);
|
|
33
|
+
const last = headings.at(-1);
|
|
34
|
+
if (last !== undefined) {
|
|
35
|
+
return flattenThought(last);
|
|
36
|
+
}
|
|
37
|
+
const flat = flattenThought(text);
|
|
38
|
+
const end = SENTENCE_END.exec(flat);
|
|
39
|
+
return end === null ? flat : flat.slice(0, end.index + end[0].length);
|
|
40
|
+
}
|
|
@@ -45,6 +45,8 @@ export type TimelineRow =
|
|
|
45
45
|
| { kind: 'work'; id: string; tool: ChatToolItem }
|
|
46
46
|
| { kind: 'work-group'; id: string; tools: ChatToolItem[]; summary: string; expanded: boolean }
|
|
47
47
|
| { kind: 'work-live'; id: string; tool: ChatToolItem }
|
|
48
|
+
// The latest thought of a running turn, repeated over the call it led to; `at` is when that call began.
|
|
49
|
+
| { kind: 'live-thought'; id: string; item: ChatThinkingItem; at: number }
|
|
48
50
|
// A Workflow call the machine reports phases and agents for, drawn with them under it.
|
|
49
51
|
| { kind: 'workflow'; id: string; tool: ChatToolItem; workflow: ChatWorkflow }
|
|
50
52
|
| ({ kind: 'subagent' } & SubagentBranch)
|
|
@@ -363,6 +365,27 @@ function lastAssistantRow(rows: TimelineRow[]): TimelineRow | null {
|
|
|
363
365
|
return null;
|
|
364
366
|
}
|
|
365
367
|
|
|
368
|
+
/*
|
|
369
|
+
* The rows of a running turn with its latest thought over the call it led to, so what the model
|
|
370
|
+
* found does not scroll away behind the next tool line. Nothing when that call follows the thought
|
|
371
|
+
* directly, where the thought's own row already shows it, or when a newer thought came after the call.
|
|
372
|
+
*/
|
|
373
|
+
function withLiveThought(rows: TimelineRow[], items: readonly ChatItem[]): TimelineRow[] {
|
|
374
|
+
const thoughtAt = items.findLastIndex((item) => item.kind === 'thinking' && parentOf(item) === null);
|
|
375
|
+
const thought = items[thoughtAt];
|
|
376
|
+
if (thought?.kind !== 'thinking' || thought.text === '') {
|
|
377
|
+
return rows;
|
|
378
|
+
}
|
|
379
|
+
const after = new Set(items.slice(thoughtAt + 1).map((item) => item.id));
|
|
380
|
+
const index = rows.findIndex((row) => row.kind === 'work-live' && after.has(row.tool.id));
|
|
381
|
+
const call = rows[index];
|
|
382
|
+
const previous = rows[index - 1];
|
|
383
|
+
if (call?.kind !== 'work-live' || (previous?.kind === 'thinking' && previous.id === thought.id)) {
|
|
384
|
+
return rows;
|
|
385
|
+
}
|
|
386
|
+
return [...rows.slice(0, index), { kind: 'live-thought', id: `live-thought-${thought.id}`, item: thought, at: call.tool.createdAt }, ...rows.slice(index)];
|
|
387
|
+
}
|
|
388
|
+
|
|
366
389
|
interface TurnChunk {
|
|
367
390
|
turnId: string | null;
|
|
368
391
|
items: ChatItem[];
|
|
@@ -411,7 +434,8 @@ export function deriveTimelineRows(items: ChatItem[], options: TimelineOptions):
|
|
|
411
434
|
}
|
|
412
435
|
const active = turnId === options.activeTurnId || turn?.state === 'running';
|
|
413
436
|
if (!turn || active) {
|
|
414
|
-
|
|
437
|
+
const live = rowsForItems(conversation, options, children);
|
|
438
|
+
rows.push(...(active ? withLiveThought(live, conversation) : live));
|
|
415
439
|
if (active) {
|
|
416
440
|
rows.push({ kind: 'working', id: `working-${turnId}`, startedAt: turn?.createdAt ?? Date.now() });
|
|
417
441
|
}
|
|
@@ -15,6 +15,8 @@ export interface ToolEntry {
|
|
|
15
15
|
changesFiles: boolean;
|
|
16
16
|
/* Whether the call reads a path the thread may draw as an image. */
|
|
17
17
|
readsImage: boolean;
|
|
18
|
+
/* Whether `command` in its input is a shell command, drawn as one. */
|
|
19
|
+
shell: boolean;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
function entry(icon: LucideIcon, summary: readonly string[], rest: Partial<ToolEntry> = {}): ToolEntry {
|
|
@@ -24,6 +26,7 @@ function entry(icon: LucideIcon, summary: readonly string[], rest: Partial<ToolE
|
|
|
24
26
|
grouped: true,
|
|
25
27
|
changesFiles: false,
|
|
26
28
|
readsImage: false,
|
|
29
|
+
shell: false,
|
|
27
30
|
...rest
|
|
28
31
|
};
|
|
29
32
|
}
|
|
@@ -44,7 +47,8 @@ export const TOOL_CATALOG: Record<string, ToolEntry> = {
|
|
|
44
47
|
NotebookEdit: entry(SquarePen, ['file_path', 'notebook_path']),
|
|
45
48
|
// Codex reports a patch as the paths it touches; its own input carries no path of its own.
|
|
46
49
|
ApplyPatch: entry(SquarePen, ['summary'], { grouped: false, changesFiles: true }),
|
|
47
|
-
|
|
50
|
+
// Codex's command executions arrive under this name too.
|
|
51
|
+
Bash: entry(Terminal, ['description', 'command'], { shell: true }),
|
|
48
52
|
Grep: entry(Search, ['pattern']),
|
|
49
53
|
Glob: entry(FolderSearch, ['pattern']),
|
|
50
54
|
WebFetch: entry(Globe, ['url', 'query']),
|