@ruimte/agents-react 0.0.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +110 -0
- package/README.md +142 -1
- package/dist/agents/AccountDot.d.ts +4 -0
- package/dist/agents/AccountDot.js +8 -0
- package/dist/agents/AgentIcon.d.ts +6 -0
- package/dist/agents/AgentIcon.js +20 -0
- package/dist/agents/ProviderLogo.d.ts +8 -0
- package/dist/agents/ProviderLogo.js +8 -0
- package/dist/agents/account-limits.d.ts +7 -0
- package/dist/agents/account-limits.js +26 -0
- package/dist/agents/accounts.d.ts +22 -0
- package/dist/agents/accounts.js +103 -0
- package/dist/agents/model-name.d.ts +6 -0
- package/dist/agents/model-name.js +44 -0
- package/dist/agents/provider-paths.d.ts +2 -0
- package/dist/agents/provider-paths.js +8 -0
- package/dist/agents/status-look.d.ts +13 -0
- package/dist/agents/status-look.js +22 -0
- package/dist/chat/account-choice.d.ts +11 -0
- package/dist/chat/account-choice.js +30 -0
- package/dist/chat/actions.d.ts +6 -0
- package/dist/chat/actions.js +64 -0
- package/dist/chat/activity.d.ts +5 -0
- package/dist/chat/activity.js +4 -0
- package/dist/chat/attachments.d.ts +23 -0
- package/dist/chat/attachments.js +70 -0
- package/dist/chat/bookmarks.d.ts +14 -0
- package/dist/chat/bookmarks.js +73 -0
- package/dist/chat/chat-client.d.ts +53 -0
- package/dist/chat/chat-client.js +232 -0
- package/dist/chat/chat-references.d.ts +5 -0
- package/dist/chat/chat-references.js +5 -0
- package/dist/chat/drafts.d.ts +29 -0
- package/dist/chat/drafts.js +104 -0
- package/dist/chat/forks.d.ts +2 -0
- package/dist/chat/forks.js +26 -0
- package/dist/chat/guards.d.ts +12 -0
- package/dist/chat/guards.js +63 -0
- package/dist/chat/logic/answers.d.ts +3 -0
- package/dist/chat/logic/answers.js +14 -0
- package/dist/chat/logic/bookmarks.d.ts +5 -0
- package/dist/chat/logic/bookmarks.js +31 -0
- package/dist/chat/logic/context-usage.d.ts +12 -0
- package/dist/chat/logic/context-usage.js +20 -0
- package/dist/chat/logic/fork.d.ts +44 -0
- package/dist/chat/logic/fork.js +138 -0
- package/dist/chat/logic/handback.d.ts +2 -0
- package/dist/chat/logic/handback.js +8 -0
- package/dist/chat/logic/json.d.ts +1 -0
- package/dist/chat/logic/json.js +2 -0
- package/dist/chat/logic/limit.d.ts +7 -0
- package/dist/chat/logic/limit.js +34 -0
- package/dist/chat/logic/resume-compaction.d.ts +14 -0
- package/dist/chat/logic/resume-compaction.js +47 -0
- package/dist/chat/logic/scrubber.d.ts +50 -0
- package/dist/chat/logic/scrubber.js +216 -0
- package/dist/chat/logic/timeline-copy.d.ts +4 -0
- package/dist/chat/logic/timeline-copy.js +51 -0
- package/dist/chat/logic/timeline-target.d.ts +12 -0
- package/dist/chat/logic/timeline-target.js +34 -0
- package/dist/chat/logic/timeline.d.ts +110 -0
- package/dist/chat/logic/timeline.js +339 -0
- package/dist/chat/logic/tool-catalog.d.ts +10 -0
- package/dist/chat/logic/tool-catalog.js +40 -0
- package/dist/chat/logic/tools.d.ts +15 -0
- package/dist/chat/logic/tools.js +79 -0
- package/dist/chat/logic/workflow.d.ts +14 -0
- package/dist/chat/logic/workflow.js +47 -0
- package/dist/chat/mentions.d.ts +46 -0
- package/dist/chat/mentions.js +128 -0
- package/dist/chat/persisted-json.d.ts +5 -0
- package/dist/chat/persisted-json.js +24 -0
- package/dist/chat/preferences.d.ts +23 -0
- package/dist/chat/preferences.js +110 -0
- package/dist/chat/quote.d.ts +2 -0
- package/dist/chat/quote.js +25 -0
- package/dist/chat/recent-messages.d.ts +17 -0
- package/dist/chat/recent-messages.js +28 -0
- package/dist/chat/resume-compaction-dismissals.d.ts +3 -0
- package/dist/chat/resume-compaction-dismissals.js +14 -0
- package/dist/chat/runtime-modes.d.ts +4 -0
- package/dist/chat/runtime-modes.js +11 -0
- package/dist/chat/stash.d.ts +24 -0
- package/dist/chat/stash.js +88 -0
- package/dist/chat/subagent-conversation.d.ts +38 -0
- package/dist/chat/subagent-conversation.js +150 -0
- package/dist/chat/subagent-list.d.ts +16 -0
- package/dist/chat/subagent-list.js +76 -0
- package/dist/chat/subagent-support.d.ts +6 -0
- package/dist/chat/subagent-support.js +14 -0
- package/dist/chat/subagent-view.d.ts +33 -0
- package/dist/chat/subagent-view.js +65 -0
- package/dist/chat/thumbnails.d.ts +10 -0
- package/dist/chat/thumbnails.js +87 -0
- package/dist/chat/timeline-scroll.d.ts +16 -0
- package/dist/chat/timeline-scroll.js +100 -0
- package/dist/chat/ui/AccountPill.d.ts +3 -0
- package/dist/chat/ui/AccountPill.js +24 -0
- package/dist/chat/ui/BookmarkMarker.d.ts +8 -0
- package/dist/chat/ui/BookmarkMarker.js +59 -0
- package/dist/chat/ui/BookmarkSubmenu.d.ts +3 -0
- package/dist/chat/ui/BookmarkSubmenu.js +44 -0
- package/dist/chat/ui/ChatActivity.d.ts +3 -0
- package/dist/chat/ui/ChatActivity.js +87 -0
- package/dist/chat/ui/ChatReferenceChip.d.ts +4 -0
- package/dist/chat/ui/ChatReferenceChip.js +17 -0
- package/dist/chat/ui/CodeBlock.d.ts +4 -0
- package/dist/chat/ui/CodeBlock.js +130 -0
- package/dist/chat/ui/Composer.d.ts +14 -0
- package/dist/chat/ui/Composer.js +821 -0
- package/dist/chat/ui/ComposerInput.d.ts +28 -0
- package/dist/chat/ui/ComposerInput.js +118 -0
- package/dist/chat/ui/DiffPool.d.ts +5 -0
- package/dist/chat/ui/DiffPool.js +26 -0
- package/dist/chat/ui/EditDiff.d.ts +4 -0
- package/dist/chat/ui/EditDiff.js +21 -0
- package/dist/chat/ui/FadingWords.d.ts +3 -0
- package/dist/chat/ui/FadingWords.js +5 -0
- package/dist/chat/ui/ImageView.d.ts +6 -0
- package/dist/chat/ui/ImageView.js +85 -0
- package/dist/chat/ui/LimitState.d.ts +8 -0
- package/dist/chat/ui/LimitState.js +117 -0
- package/dist/chat/ui/Markdown.d.ts +18 -0
- package/dist/chat/ui/Markdown.js +158 -0
- package/dist/chat/ui/MessageActions.d.ts +9 -0
- package/dist/chat/ui/MessageActions.js +56 -0
- package/dist/chat/ui/Pickers.d.ts +15 -0
- package/dist/chat/ui/Pickers.js +110 -0
- package/dist/chat/ui/PromptComposer.d.ts +13 -0
- package/dist/chat/ui/PromptComposer.js +48 -0
- package/dist/chat/ui/QuoteButton.d.ts +7 -0
- package/dist/chat/ui/QuoteButton.js +76 -0
- package/dist/chat/ui/ResumeCompactionDock.d.ts +5 -0
- package/dist/chat/ui/ResumeCompactionDock.js +15 -0
- package/dist/chat/ui/RunSettings.d.ts +25 -0
- package/dist/chat/ui/RunSettings.js +148 -0
- package/dist/chat/ui/Scrubber.d.ts +17 -0
- package/dist/chat/ui/Scrubber.js +121 -0
- package/dist/chat/ui/SubagentControls.d.ts +11 -0
- package/dist/chat/ui/SubagentControls.js +37 -0
- package/dist/chat/ui/SubagentStopButton.d.ts +7 -0
- package/dist/chat/ui/SubagentStopButton.js +36 -0
- package/dist/chat/ui/SubagentTimeline.d.ts +4 -0
- package/dist/chat/ui/SubagentTimeline.js +92 -0
- package/dist/chat/ui/Timeline.d.ts +5 -0
- package/dist/chat/ui/Timeline.js +318 -0
- package/dist/chat/ui/TimelineMenu.d.ts +12 -0
- package/dist/chat/ui/TimelineMenu.js +61 -0
- package/dist/chat/ui/UnifiedDiff.d.ts +8 -0
- package/dist/chat/ui/UnifiedDiff.js +51 -0
- package/dist/chat/ui/UploadThumb.d.ts +4 -0
- package/dist/chat/ui/UploadThumb.js +11 -0
- package/dist/chat/ui/chips.d.ts +4 -0
- package/dist/chat/ui/chips.js +11 -0
- package/dist/chat/ui/code-lines.d.ts +18 -0
- package/dist/chat/ui/code-lines.js +46 -0
- package/dist/chat/ui/code-streaming.d.ts +1 -0
- package/dist/chat/ui/code-streaming.js +3 -0
- package/dist/chat/ui/code-theme.d.ts +3 -0
- package/dist/chat/ui/code-theme.js +10 -0
- package/dist/chat/ui/composer/chips.d.ts +6 -0
- package/dist/chat/ui/composer/chips.js +29 -0
- package/dist/chat/ui/composer/editor.d.ts +11 -0
- package/dist/chat/ui/composer/editor.js +94 -0
- package/dist/chat/ui/composer/keys.d.ts +30 -0
- package/dist/chat/ui/composer/keys.js +98 -0
- package/dist/chat/ui/diff-theme.d.ts +2 -0
- package/dist/chat/ui/diff-theme.js +20 -0
- package/dist/chat/ui/file-links.d.ts +5 -0
- package/dist/chat/ui/file-links.js +18 -0
- package/dist/chat/ui/find-reveal.d.ts +7 -0
- package/dist/chat/ui/find-reveal.js +13 -0
- package/dist/chat/ui/icons.d.ts +4 -0
- package/dist/chat/ui/icons.js +20 -0
- package/dist/chat/ui/markdown-blocks.d.ts +6 -0
- package/dist/chat/ui/markdown-blocks.js +101 -0
- package/dist/chat/ui/quote-selection.d.ts +3 -0
- package/dist/chat/ui/quote-selection.js +44 -0
- package/dist/chat/ui/rehype-chips.d.ts +23 -0
- package/dist/chat/ui/rehype-chips.js +28 -0
- package/dist/chat/ui/rehype-fade.d.ts +23 -0
- package/dist/chat/ui/rehype-fade.js +30 -0
- package/dist/chat/ui/remark-html-as-text.d.ts +8 -0
- package/dist/chat/ui/remark-html-as-text.js +24 -0
- package/dist/chat/ui/reveal.d.ts +13 -0
- package/dist/chat/ui/reveal.js +112 -0
- package/dist/chat/ui/rows/ForksRow.d.ts +4 -0
- package/dist/chat/ui/rows/ForksRow.js +40 -0
- package/dist/chat/ui/rows/MessageRows.d.ts +36 -0
- package/dist/chat/ui/rows/MessageRows.js +190 -0
- package/dist/chat/ui/rows/Rows.d.ts +12 -0
- package/dist/chat/ui/rows/Rows.js +49 -0
- package/dist/chat/ui/rows/SubagentRow.d.ts +15 -0
- package/dist/chat/ui/rows/SubagentRow.js +98 -0
- package/dist/chat/ui/rows/WorkRows.d.ts +46 -0
- package/dist/chat/ui/rows/WorkRows.js +143 -0
- package/dist/chat/ui/rows/WorkflowRow.d.ts +7 -0
- package/dist/chat/ui/rows/WorkflowRow.js +37 -0
- package/dist/chat/ui/rows/row-rhythm.d.ts +3 -0
- package/dist/chat/ui/rows/row-rhythm.js +14 -0
- package/dist/chat/ui/useToggleSet.d.ts +6 -0
- package/dist/chat/ui/useToggleSet.js +25 -0
- package/dist/host.d.ts +167 -0
- package/dist/host.js +41 -0
- package/dist/lazy.d.ts +5 -0
- package/dist/lazy.js +35 -0
- package/dist/locales/en/agent-chat.json +417 -0
- package/dist/locales/en/agent-prompts.json +42 -0
- package/dist/locales/en/agent-providers.json +131 -0
- package/dist/locales/en/agent-usage.json +131 -0
- package/dist/locales/nl/agent-chat.json +417 -0
- package/dist/locales/nl/agent-prompts.json +42 -0
- package/dist/locales/nl/agent-providers.json +131 -0
- package/dist/locales/nl/agent-usage.json +131 -0
- package/dist/locales.d.ts +5 -0
- package/dist/locales.js +18 -0
- package/dist/mounted-registry.d.ts +7 -0
- package/dist/mounted-registry.js +39 -0
- package/dist/port-transport.d.ts +6 -0
- package/dist/port-transport.js +120 -0
- package/dist/prompts/logic/focus.d.ts +1 -0
- package/dist/prompts/logic/focus.js +17 -0
- package/dist/prompts/logic/keys.d.ts +42 -0
- package/dist/prompts/logic/keys.js +84 -0
- package/dist/prompts/logic/prompts.d.ts +34 -0
- package/dist/prompts/logic/prompts.fixtures.d.ts +5 -0
- package/dist/prompts/logic/prompts.fixtures.js +73 -0
- package/dist/prompts/logic/prompts.js +22 -0
- package/dist/prompts/logic/subjects.d.ts +49 -0
- package/dist/prompts/logic/subjects.js +56 -0
- package/dist/prompts/logic/useFocusAfterAnswer.d.ts +6 -0
- package/dist/prompts/logic/useFocusAfterAnswer.js +28 -0
- package/dist/prompts/logic/usePromptSession.d.ts +24 -0
- package/dist/prompts/logic/usePromptSession.js +87 -0
- package/dist/prompts/ui/ApprovalBody.d.ts +13 -0
- package/dist/prompts/ui/ApprovalBody.js +35 -0
- package/dist/prompts/ui/PromptActions.d.ts +26 -0
- package/dist/prompts/ui/PromptActions.js +19 -0
- package/dist/prompts/ui/PromptCard.d.ts +16 -0
- package/dist/prompts/ui/PromptCard.js +50 -0
- package/dist/prompts/ui/PromptView.d.ts +2 -0
- package/dist/prompts/ui/PromptView.js +47 -0
- package/dist/prompts/ui/QuestionBody.d.ts +9 -0
- package/dist/prompts/ui/QuestionBody.js +87 -0
- package/dist/providers/AccountColors.d.ts +5 -0
- package/dist/providers/AccountColors.js +8 -0
- package/dist/providers/AccountDetail.d.ts +12 -0
- package/dist/providers/AccountDetail.js +76 -0
- package/dist/providers/AccountVariables.d.ts +8 -0
- package/dist/providers/AccountVariables.js +48 -0
- package/dist/providers/AddAccountForm.d.ts +10 -0
- package/dist/providers/AddAccountForm.js +58 -0
- package/dist/providers/CliDetail.d.ts +11 -0
- package/dist/providers/CliDetail.js +62 -0
- package/dist/providers/ProvidersPane.d.ts +8 -0
- package/dist/providers/ProvidersPane.js +90 -0
- package/dist/providers/account-actions.d.ts +7 -0
- package/dist/providers/account-actions.js +53 -0
- package/dist/providers/parts.d.ts +9 -0
- package/dist/providers/parts.js +18 -0
- package/dist/providers/provider-abilities.d.ts +2 -0
- package/dist/providers/provider-abilities.js +7 -0
- package/dist/providers/variables.d.ts +13 -0
- package/dist/providers/variables.js +53 -0
- package/dist/scope.d.ts +11 -0
- package/dist/scope.js +10 -0
- package/dist/settings/sections.d.ts +13 -0
- package/dist/settings/sections.js +30 -0
- package/dist/state/chats.d.ts +32 -0
- package/dist/state/chats.js +124 -0
- package/dist/state/provider-accounts.d.ts +17 -0
- package/dist/state/provider-accounts.js +63 -0
- package/dist/state/providers.d.ts +17 -0
- package/dist/state/providers.js +29 -0
- package/dist/state/usage.d.ts +41 -0
- package/dist/state/usage.js +100 -0
- package/dist/theme.css +186 -0
- package/dist/transport.d.ts +24 -0
- package/dist/transport.js +16 -0
- package/dist/ui/PlainTextarea.d.ts +2 -0
- package/dist/ui/PlainTextarea.js +5 -0
- package/dist/usage/LimitsList.d.ts +18 -0
- package/dist/usage/LimitsList.js +69 -0
- package/dist/usage/UsageBreakdown.d.ts +9 -0
- package/dist/usage/UsageBreakdown.js +69 -0
- package/dist/usage/UsageChart.d.ts +10 -0
- package/dist/usage/UsageChart.js +49 -0
- package/dist/usage/UsageDialog.d.ts +6 -0
- package/dist/usage/UsageDialog.js +15 -0
- package/dist/usage/UsageLimits.d.ts +1 -0
- package/dist/usage/UsageLimits.js +62 -0
- package/dist/usage/UsageLimitsCard.d.ts +4 -0
- package/dist/usage/UsageLimitsCard.js +27 -0
- package/dist/usage/UsagePage.d.ts +7 -0
- package/dist/usage/UsagePage.js +161 -0
- package/dist/usage/UsagePane.d.ts +3 -0
- package/dist/usage/UsagePane.js +24 -0
- package/dist/usage/UsageSummary.d.ts +12 -0
- package/dist/usage/UsageSummary.js +15 -0
- package/dist/usage/UsageTiles.d.ts +7 -0
- package/dist/usage/UsageTiles.js +19 -0
- package/dist/usage/format.d.ts +14 -0
- package/dist/usage/format.js +50 -0
- package/dist/usage/limit-groups.d.ts +22 -0
- package/dist/usage/limit-groups.js +78 -0
- package/dist/usage/limits.d.ts +4 -0
- package/dist/usage/limits.js +58 -0
- package/dist/usage/money.d.ts +1 -0
- package/dist/usage/money.js +13 -0
- package/dist/usage/summary.d.ts +34 -0
- package/dist/usage/summary.js +90 -0
- package/package.json +639 -2
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const endpoints = {
|
|
2
|
+
id: 'endpoints',
|
|
3
|
+
header: 'Shared backoff',
|
|
4
|
+
question: 'Which endpoints should share the backoff?',
|
|
5
|
+
multiSelect: false,
|
|
6
|
+
choices: [
|
|
7
|
+
{ label: 'All three', description: 'Chat, files and terminal' },
|
|
8
|
+
{ label: 'Only chat and files', description: 'Keep terminal retries separate' }
|
|
9
|
+
]
|
|
10
|
+
};
|
|
11
|
+
const checks = {
|
|
12
|
+
id: 'checks',
|
|
13
|
+
header: 'Validation',
|
|
14
|
+
question: 'Which checks should run before the change is ready?',
|
|
15
|
+
multiSelect: true,
|
|
16
|
+
choices: [
|
|
17
|
+
{ label: 'Type check', description: 'Check all workspaces' },
|
|
18
|
+
{ label: 'Unit tests', description: 'Run the targeted tests' },
|
|
19
|
+
{ label: 'Build', description: 'Verify the production bundle' }
|
|
20
|
+
]
|
|
21
|
+
};
|
|
22
|
+
const notes = { id: 'notes', header: 'Instructions', question: 'What else should the agent keep in mind?', multiSelect: false, choices: [] };
|
|
23
|
+
const base = (id) => ({ id: `preview-${id}`, requestId: `preview-${id}`, createdAt: 0, turnId: null });
|
|
24
|
+
const question = (id, questions, async = false) => ({
|
|
25
|
+
...base(id),
|
|
26
|
+
kind: 'question',
|
|
27
|
+
questions,
|
|
28
|
+
answers: null,
|
|
29
|
+
state: 'pending',
|
|
30
|
+
async
|
|
31
|
+
});
|
|
32
|
+
const approval = (long = false) => ({
|
|
33
|
+
...base(long ? 'long' : 'edit'),
|
|
34
|
+
kind: 'approval',
|
|
35
|
+
toolUseId: null,
|
|
36
|
+
toolName: 'Edit',
|
|
37
|
+
description: 'Replace the reconnect loop with the shared backoff and export it for the three endpoints.',
|
|
38
|
+
input: {
|
|
39
|
+
file_path: 'src/transport/pool.ts',
|
|
40
|
+
old_string: 'const retries = 10;',
|
|
41
|
+
new_string: 'const retries = 5;\nexport const backoff = createBackoff({ max: retries });' +
|
|
42
|
+
(long ? '\n' + Array.from({ length: 90 }, (_, i) => `// Context line ${i + 1}`).join('\n') : '')
|
|
43
|
+
},
|
|
44
|
+
canAllowAlways: false,
|
|
45
|
+
decision: 'pending'
|
|
46
|
+
});
|
|
47
|
+
// These never enter the chat store or the transport; the preview owns their lifetime.
|
|
48
|
+
export const PROMPT_SAMPLES = [
|
|
49
|
+
{ label: 'Permission · File edit', items: [approval()] },
|
|
50
|
+
{
|
|
51
|
+
label: 'Permission · Command',
|
|
52
|
+
items: [
|
|
53
|
+
{
|
|
54
|
+
...base('command'),
|
|
55
|
+
kind: 'approval',
|
|
56
|
+
toolName: 'Bash',
|
|
57
|
+
toolUseId: null,
|
|
58
|
+
input: { command: 'bun run check && bun test', cwd: '/projects/ruimte' },
|
|
59
|
+
description: "Run the project's validation checks before continuing.",
|
|
60
|
+
canAllowAlways: true,
|
|
61
|
+
allowAlways: { label: 'Allow for this session', description: 'Allow commands in this session until it ends.' },
|
|
62
|
+
decision: 'pending'
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
{ label: 'Permission · Long diff', items: [approval(true)] },
|
|
67
|
+
{ label: 'Question · Single choice', items: [question('single', [endpoints])] },
|
|
68
|
+
{ label: 'Question · Multiple choices', items: [question('multiple', [checks])] },
|
|
69
|
+
{ label: 'Question · Written answer', items: [question('written', [notes])] },
|
|
70
|
+
{ label: 'Question · Three questions', items: [question('questions', [endpoints, checks, notes])] },
|
|
71
|
+
{ label: 'Question · Optional', items: [question('optional', [endpoints], true)] },
|
|
72
|
+
{ label: 'Several requests', items: [question('optional', [endpoints], true), approval(), question('single', [endpoints])] }
|
|
73
|
+
];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { toggleChoice } from '../../chat/logic/answers';
|
|
2
|
+
export const emptyPromptDraft = () => ({ index: 0, answers: {}, reason: '', showReason: false });
|
|
3
|
+
export const answerValue = (answer) => (answer.custom ? answer.text.trim() : answer.choices.join(', '));
|
|
4
|
+
export const questionAnswer = (draft, question) => draft.answers[question.id] ?? { choices: [], text: '', custom: question.choices.length === 0 };
|
|
5
|
+
export function pickPromptChoice(answer, question, label) {
|
|
6
|
+
return { ...answer, custom: false, choices: question.multiSelect ? toggleChoice(answer.choices, label) : [label] };
|
|
7
|
+
}
|
|
8
|
+
/* An optional question never stands in front of a request that holds the agent up. */
|
|
9
|
+
export const isBlockingPrompt = (item) => item.kind === 'approval' || !item.async;
|
|
10
|
+
/* Blocking prompts first and optional ones after, each oldest first. The chat and a canvas's stack both follow it. */
|
|
11
|
+
export const orderPrompts = (prompts, blocking, createdAt) => [...prompts].sort((a, b) => Number(blocking(b)) - Number(blocking(a)) || createdAt(a) - createdAt(b));
|
|
12
|
+
export function nextPrompt(items, activeId) {
|
|
13
|
+
const active = items.find((item) => item.requestId === activeId);
|
|
14
|
+
if (active) {
|
|
15
|
+
return active;
|
|
16
|
+
}
|
|
17
|
+
return orderPrompts(items, isBlockingPrompt, (item) => item.createdAt)[0] ?? null;
|
|
18
|
+
}
|
|
19
|
+
export function promptAnswers(item, draft) {
|
|
20
|
+
const answers = Object.fromEntries(item.questions.map((question) => [question.id, answerValue(questionAnswer(draft, question))]));
|
|
21
|
+
return Object.values(answers).every((answer) => answer !== '') ? answers : null;
|
|
22
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { type PendingPrompt, type PromptAction, type PromptDraft } from './prompts';
|
|
3
|
+
export interface HostPrompt {
|
|
4
|
+
id: string;
|
|
5
|
+
createdAt: number;
|
|
6
|
+
blocking: boolean;
|
|
7
|
+
asks: 'approval' | 'question';
|
|
8
|
+
data: unknown;
|
|
9
|
+
}
|
|
10
|
+
export type PromptSubject = {
|
|
11
|
+
kind: 'chat';
|
|
12
|
+
nodeId: string;
|
|
13
|
+
item: PendingPrompt;
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'host';
|
|
16
|
+
nodeId: string;
|
|
17
|
+
prompt: HostPrompt;
|
|
18
|
+
};
|
|
19
|
+
export interface ChatPromptClients {
|
|
20
|
+
approve(chatId: string, requestId: string, decision: 'allow' | 'allow-always' | 'deny', message?: string): Promise<void>;
|
|
21
|
+
answer(chatId: string, requestId: string, answers: Record<string, string>): Promise<void>;
|
|
22
|
+
dismiss(chatId: string, itemId: string): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
export interface PromptViewProps {
|
|
25
|
+
subject: PromptSubject;
|
|
26
|
+
draft: PromptDraft;
|
|
27
|
+
onDraft(draft: PromptDraft): void;
|
|
28
|
+
onAction(action: PromptAction): void;
|
|
29
|
+
more: number;
|
|
30
|
+
hasDraft: boolean;
|
|
31
|
+
denyReason: boolean;
|
|
32
|
+
disabled: boolean;
|
|
33
|
+
sending: boolean;
|
|
34
|
+
error: string | null;
|
|
35
|
+
top?: ReactNode;
|
|
36
|
+
onReveal?: () => void;
|
|
37
|
+
}
|
|
38
|
+
export interface ApprovalButtonSpec {
|
|
39
|
+
id: string;
|
|
40
|
+
label: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
primary: boolean;
|
|
43
|
+
action: PromptAction;
|
|
44
|
+
}
|
|
45
|
+
export declare const promptIdOf: (subject: PromptSubject) => string;
|
|
46
|
+
export declare const promptCreatedAt: (subject: PromptSubject) => number;
|
|
47
|
+
export declare const isBlockingSubject: (subject: PromptSubject) => boolean;
|
|
48
|
+
export declare const approvalButtons: (subject: PromptSubject, reason: string) => ApprovalButtonSpec[];
|
|
49
|
+
export declare const answerPrompt: (subject: PromptSubject, action: PromptAction, chat: ChatPromptClients | null) => Promise<void>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import i18next from 'i18next';
|
|
2
|
+
import { chatHost } from '../../host';
|
|
3
|
+
import { isBlockingPrompt } from './prompts';
|
|
4
|
+
/* Unique across the chats of a surface and the app's own prompts, and stable while the prompt waits. */
|
|
5
|
+
export const promptIdOf = (subject) => subject.kind === 'chat' ? `agent-chat:${subject.nodeId}:${subject.item.requestId}` : subject.prompt.id;
|
|
6
|
+
export const promptCreatedAt = (subject) => (subject.kind === 'chat' ? subject.item.createdAt : subject.prompt.createdAt);
|
|
7
|
+
/* Only a chat's optional question does not hold anything up; the app says so of its own. */
|
|
8
|
+
export const isBlockingSubject = (subject) => (subject.kind === 'chat' ? isBlockingPrompt(subject.item) : subject.prompt.blocking);
|
|
9
|
+
/* The buttons of a chat's permission request in the order it draws them: a remembered rule, Deny, then Allow as the primary. */
|
|
10
|
+
export const approvalButtons = (subject, reason) => {
|
|
11
|
+
if (subject.kind !== 'chat' || subject.item.kind !== 'approval') {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
const { item } = subject;
|
|
15
|
+
const message = reason.trim();
|
|
16
|
+
return [
|
|
17
|
+
...(item.canAllowAlways && item.allowAlways
|
|
18
|
+
? [
|
|
19
|
+
{
|
|
20
|
+
id: 'allow-always',
|
|
21
|
+
label: item.allowAlways.label,
|
|
22
|
+
description: item.allowAlways.description,
|
|
23
|
+
primary: false,
|
|
24
|
+
action: { kind: 'approve', decision: 'allow-always' }
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
: []),
|
|
28
|
+
{
|
|
29
|
+
id: 'deny',
|
|
30
|
+
label: i18next.t('agent-prompts:approval.deny'),
|
|
31
|
+
primary: false,
|
|
32
|
+
action: { kind: 'approve', decision: 'deny', ...(message ? { message } : {}) }
|
|
33
|
+
},
|
|
34
|
+
{ id: 'allow', label: i18next.t('agent-prompts:approval.allow'), primary: true, action: { kind: 'approve', decision: 'allow' } }
|
|
35
|
+
];
|
|
36
|
+
};
|
|
37
|
+
/* Sends a card's action as the request its subject is answered with. A refusal comes back as an error for the card to show. */
|
|
38
|
+
export const answerPrompt = async (subject, action, chat) => {
|
|
39
|
+
if (subject.kind === 'host') {
|
|
40
|
+
return chatHost().prompts.answer(subject.prompt, action);
|
|
41
|
+
}
|
|
42
|
+
if (chat === null) {
|
|
43
|
+
throw new Error(i18next.t('agent-prompts:error.notConnected'));
|
|
44
|
+
}
|
|
45
|
+
const { item, nodeId } = subject;
|
|
46
|
+
if (action.kind === 'approve') {
|
|
47
|
+
return chat.approve(nodeId, item.requestId, action.decision, action.message);
|
|
48
|
+
}
|
|
49
|
+
if (action.kind === 'answer') {
|
|
50
|
+
return chat.answer(nodeId, item.requestId, action.answers);
|
|
51
|
+
}
|
|
52
|
+
if (action.kind === 'dismiss') {
|
|
53
|
+
return chat.dismiss(nodeId, item.id);
|
|
54
|
+
}
|
|
55
|
+
throw new Error(i18next.t('agent-prompts:error.noChoices'));
|
|
56
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { focusPromptStart } from './focus';
|
|
3
|
+
/*
|
|
4
|
+
* The keyboard stays with the prompts it was answering: on where the card that comes next is answered, or
|
|
5
|
+
* wherever `onEmpty` puts it once none is left. A card answered with the keyboard elsewhere takes nothing.
|
|
6
|
+
*/
|
|
7
|
+
export function useFocusAfterAnswer(activeId, container, onEmpty) {
|
|
8
|
+
const pending = useRef(false);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!pending.current) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
pending.current = false;
|
|
14
|
+
if (!focusPromptStart(container.current)) {
|
|
15
|
+
onEmpty();
|
|
16
|
+
}
|
|
17
|
+
// Only a change of the front card is news; a new `onEmpty` each render is not.
|
|
18
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
19
|
+
}, [activeId]);
|
|
20
|
+
return {
|
|
21
|
+
hold: () => {
|
|
22
|
+
pending.current = container.current?.contains(document.activeElement) === true;
|
|
23
|
+
},
|
|
24
|
+
release: () => {
|
|
25
|
+
pending.current = false;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type MouseEvent } from 'react';
|
|
2
|
+
import { type PromptDraft } from './prompts';
|
|
3
|
+
interface PromptSessionOptions<T> {
|
|
4
|
+
prompts: readonly T[];
|
|
5
|
+
idOf(prompt: T): string;
|
|
6
|
+
pick(waiting: readonly T[], activeId: string | null, activeIndex: number): T | null;
|
|
7
|
+
disabled: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface PromptSession<T> {
|
|
10
|
+
active: T | null;
|
|
11
|
+
activeId: string | null;
|
|
12
|
+
index: number;
|
|
13
|
+
waiting: readonly T[];
|
|
14
|
+
setActive(id: string): void;
|
|
15
|
+
draftOf(id: string): PromptDraft;
|
|
16
|
+
setDraft(id: string, draft: PromptDraft): void;
|
|
17
|
+
sending: boolean;
|
|
18
|
+
errorOf(id: string): string | null;
|
|
19
|
+
act(prompt: T, send: () => Promise<void>): Promise<'sent' | 'failed' | 'busy'>;
|
|
20
|
+
onPointerDownCapture(): void;
|
|
21
|
+
onClickCapture(event: MouseEvent): void;
|
|
22
|
+
}
|
|
23
|
+
export declare function usePromptSession<T>({ prompts, idOf, pick, disabled }: PromptSessionOptions<T>): PromptSession<T>;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useRef, useState } from 'react';
|
|
2
|
+
import { emptyPromptDraft } from './prompts';
|
|
3
|
+
/*
|
|
4
|
+
* What answering a row of prompts needs besides the prompts: a draft per request that survives
|
|
5
|
+
* paging, one send at a time, the error of the request that failed, and the requests answered here
|
|
6
|
+
* that the machine has not taken off yet.
|
|
7
|
+
*/
|
|
8
|
+
export function usePromptSession({ prompts, idOf, pick, disabled }) {
|
|
9
|
+
const [activeId, setActiveId] = useState(null);
|
|
10
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
11
|
+
const [completed, setCompleted] = useState([]);
|
|
12
|
+
const [drafts, setDrafts] = useState({});
|
|
13
|
+
const [sending, setSending] = useState(false);
|
|
14
|
+
const [error, setError] = useState(null);
|
|
15
|
+
const busy = useRef(false);
|
|
16
|
+
const pressedRequest = useRef(null);
|
|
17
|
+
const stillCompleted = completed.filter((id) => prompts.some((prompt) => idOf(prompt) === id));
|
|
18
|
+
if (stillCompleted.length !== completed.length) {
|
|
19
|
+
setCompleted(stillCompleted);
|
|
20
|
+
}
|
|
21
|
+
const waiting = prompts.filter((prompt) => !completed.includes(idOf(prompt)));
|
|
22
|
+
const active = pick(waiting, activeId, activeIndex);
|
|
23
|
+
const activeKey = active === null ? null : idOf(active);
|
|
24
|
+
const index = active === null ? 0 : waiting.indexOf(active);
|
|
25
|
+
if (index !== activeIndex) {
|
|
26
|
+
setActiveIndex(index);
|
|
27
|
+
}
|
|
28
|
+
if (activeKey !== activeId) {
|
|
29
|
+
setActiveId(activeKey);
|
|
30
|
+
setError(null);
|
|
31
|
+
}
|
|
32
|
+
const finish = (id) => {
|
|
33
|
+
setCompleted((current) => [...current, id]);
|
|
34
|
+
setDrafts((current) => {
|
|
35
|
+
const next = { ...current };
|
|
36
|
+
delete next[id];
|
|
37
|
+
return next;
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
const act = async (prompt, send) => {
|
|
41
|
+
if (busy.current || disabled) {
|
|
42
|
+
return 'busy';
|
|
43
|
+
}
|
|
44
|
+
const id = idOf(prompt);
|
|
45
|
+
busy.current = true;
|
|
46
|
+
setSending(true);
|
|
47
|
+
setError(null);
|
|
48
|
+
try {
|
|
49
|
+
await send();
|
|
50
|
+
finish(id);
|
|
51
|
+
return 'sent';
|
|
52
|
+
}
|
|
53
|
+
catch (cause) {
|
|
54
|
+
setError({ id, message: cause instanceof Error ? cause.message : String(cause) });
|
|
55
|
+
return 'failed';
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
busy.current = false;
|
|
59
|
+
setSending(false);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
active,
|
|
64
|
+
activeId: activeKey,
|
|
65
|
+
index,
|
|
66
|
+
waiting,
|
|
67
|
+
setActive: (id) => {
|
|
68
|
+
setActiveId(id);
|
|
69
|
+
setError(null);
|
|
70
|
+
},
|
|
71
|
+
draftOf: (id) => drafts[id] ?? emptyPromptDraft(),
|
|
72
|
+
setDraft: (id, draft) => setDrafts((current) => ({ ...current, [id]: draft })),
|
|
73
|
+
sending,
|
|
74
|
+
errorOf: (id) => (error?.id === id ? error.message : null),
|
|
75
|
+
act,
|
|
76
|
+
onPointerDownCapture: () => {
|
|
77
|
+
pressedRequest.current = activeKey;
|
|
78
|
+
},
|
|
79
|
+
onClickCapture: (event) => {
|
|
80
|
+
// A press that began on Send cannot become Allow when a request arrives underneath it.
|
|
81
|
+
if (event.detail > 0 && event.target.closest('.prompt-card') && pressedRequest.current !== activeKey) {
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
event.stopPropagation();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ChatApprovalItem } from '@ruimte/agent-contracts';
|
|
2
|
+
import type { PromptDraft } from '../logic/prompts';
|
|
3
|
+
export declare function CommandBox({ command, cwd }: {
|
|
4
|
+
command: string;
|
|
5
|
+
cwd?: string;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
|
+
export declare function ApprovalBody({ item, draft, onDraft, denyReason, locked }: {
|
|
8
|
+
item: ChatApprovalItem;
|
|
9
|
+
draft: PromptDraft;
|
|
10
|
+
onDraft(draft: PromptDraft): void;
|
|
11
|
+
denyReason: boolean;
|
|
12
|
+
locked: boolean;
|
|
13
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Suspense, useState } from 'react';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { approvalChanges, fileChanges } from '../../chat/logic/tools';
|
|
6
|
+
import { chatHost } from '../../host';
|
|
7
|
+
import { lazyNamed } from '../../lazy';
|
|
8
|
+
import { isPrimaryKey } from '../logic/keys';
|
|
9
|
+
import { Button } from '@ruimte/ui/Button';
|
|
10
|
+
import { TextMenu } from '@ruimte/ui/TextMenu';
|
|
11
|
+
const EditDiff = lazyNamed(() => import('../../chat/ui/EditDiff'), 'default');
|
|
12
|
+
const UnifiedDiff = lazyNamed(() => import('../../chat/ui/UnifiedDiff'), 'default');
|
|
13
|
+
/* A command in the sunken box a person reads before allowing it, with the folder it runs in when that is known. */
|
|
14
|
+
export function CommandBox({ command, cwd }) {
|
|
15
|
+
return (_jsxs(TextMenu, { className: "rounded-xl bg-surface-sunken p-3", children: [cwd !== undefined && _jsx("p", { className: "mb-2 break-all font-mono text-xs text-text-muted select-text", children: cwd }), _jsx("pre", { className: "whitespace-pre-wrap break-words font-mono text-code text-text select-text", children: command })] }));
|
|
16
|
+
}
|
|
17
|
+
function ApprovalDetails({ item }) {
|
|
18
|
+
const { t } = useTranslation('agent-prompts');
|
|
19
|
+
const [expanded, setExpanded] = useState(false);
|
|
20
|
+
const patches = approvalChanges(item.input);
|
|
21
|
+
const edits = patches.length ? [] : fileChanges(item.toolName, item.input);
|
|
22
|
+
const input = typeof item.input === 'object' && item.input !== null ? item.input : {};
|
|
23
|
+
return (_jsxs(_Fragment, { children: [item.description && _jsx("p", { className: "text-sm text-text-muted", children: item.description }), patches.length + edits.length > 1 && (_jsx("p", { className: "text-xs text-text-muted", children: t('approval.allChanges', { count: patches.length + edits.length }) })), patches.length + edits.length > 0 ? (_jsxs("div", { className: "overflow-hidden rounded-xl bg-surface-sunken", children: [_jsx("div", { className: clsx('overflow-auto', !expanded && 'max-h-44'), children: _jsxs(Suspense, { fallback: _jsx("p", { className: "p-3 text-xs text-text-muted", children: t('approval.loadingDiff') }), children: [patches.map((change, i) => (_jsx(UnifiedDiff, { change: change }, i))), edits.map((change, i) => (_jsx(EditDiff, { change: change }, i)))] }) }), _jsx("div", { className: "flex items-center border-t border-border p-1", children: _jsx(Button, { size: "sm", "aria-expanded": expanded, onClick: () => setExpanded(!expanded), children: expanded ? t('approval.collapseDiff') : t('approval.viewDiff') }) })] })) : typeof input.command === 'string' ? (_jsx(CommandBox, { command: input.command, cwd: typeof input.cwd === 'string' ? input.cwd : undefined })) : (_jsxs("details", { children: [_jsx("summary", { className: "flex h-7 cursor-pointer items-center text-xs text-text-muted", children: t('approval.details') }), _jsx(TextMenu, { children: _jsx("pre", { className: "max-h-64 overflow-auto whitespace-pre-wrap break-words rounded-xl bg-surface-sunken p-3 font-mono text-code text-text select-text", children: JSON.stringify(item.input, null, 2) }) })] }))] }));
|
|
24
|
+
}
|
|
25
|
+
/* A chat's permission request: the diff, the command or the raw input, and the reason a Deny may carry. */
|
|
26
|
+
export function ApprovalBody({ item, draft, onDraft, denyReason, locked }) {
|
|
27
|
+
const { t } = useTranslation('agent-prompts');
|
|
28
|
+
const AnswerField = chatHost().dictation.Textarea;
|
|
29
|
+
return (_jsxs(_Fragment, { children: [_jsx(ApprovalDetails, { item: item }), denyReason && draft.showReason && (_jsx(AnswerField, { className: "field min-h-16 text-sm", autoFocus: true, "aria-label": t('approval.reasonLabel'), placeholder: t('approval.reasonPlaceholder'), disabled: locked, value: draft.reason, onChange: (e) => onDraft({ ...draft, reason: e.target.value }), onKeyDown: (e) => {
|
|
30
|
+
// Mod+Enter presses Allow everywhere else in the card, which is the opposite of what a reason for declining is typed for.
|
|
31
|
+
if (isPrimaryKey(e.nativeEvent, chatHost().isApplePlatform())) {
|
|
32
|
+
e.preventDefault();
|
|
33
|
+
}
|
|
34
|
+
} }))] }));
|
|
35
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ButtonHTMLAttributes } from 'react';
|
|
2
|
+
export declare function PromptPrimary(props: ButtonHTMLAttributes<HTMLButtonElement>): import("react").JSX.Element;
|
|
3
|
+
export interface ApprovalButton {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
primary: boolean;
|
|
8
|
+
onPress(): void;
|
|
9
|
+
}
|
|
10
|
+
export declare function ApprovalActions({ buttons, onAddReason, locked, sending }: {
|
|
11
|
+
buttons: readonly ApprovalButton[];
|
|
12
|
+
onAddReason?: () => void;
|
|
13
|
+
locked: boolean;
|
|
14
|
+
sending: boolean;
|
|
15
|
+
}): import("react").JSX.Element;
|
|
16
|
+
export declare function QuestionActions({ index, dismissable, last, ready, locked, sending, onPrevious, onDismiss, onCommit }: {
|
|
17
|
+
index: number;
|
|
18
|
+
dismissable: boolean;
|
|
19
|
+
last: boolean;
|
|
20
|
+
ready: boolean;
|
|
21
|
+
locked: boolean;
|
|
22
|
+
sending: boolean;
|
|
23
|
+
onPrevious(): void;
|
|
24
|
+
onDismiss(): void;
|
|
25
|
+
onCommit(): void;
|
|
26
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { ArrowUp, CircleCheck } from 'lucide-react';
|
|
4
|
+
import { Button } from '@ruimte/ui/Button';
|
|
5
|
+
import { Icon } from '@ruimte/ui/Icon';
|
|
6
|
+
/* The one button Mod+Enter presses and ArrowDown from the heading lands on, when the body has nothing to choose. */
|
|
7
|
+
export function PromptPrimary(props) {
|
|
8
|
+
return _jsx(Button, { variant: "inverse", size: "sm", "data-prompt-primary": true, ...props });
|
|
9
|
+
}
|
|
10
|
+
/* The buttons of a permission request, in the order a chat draws them: the quieter ones first, Allow last. */
|
|
11
|
+
export function ApprovalActions({ buttons, onAddReason, locked, sending }) {
|
|
12
|
+
const { t } = useTranslation('agent-prompts');
|
|
13
|
+
return (_jsxs(_Fragment, { children: [onAddReason && (_jsx(Button, { size: "sm", disabled: locked, onClick: onAddReason, children: t('approval.addReason') })), _jsx("div", { className: "ml-auto flex flex-wrap items-center justify-end gap-1.5", children: buttons.map((button) => button.primary ? (_jsxs(PromptPrimary, { disabled: locked, onClick: button.onPress, children: [_jsx(Icon, { icon: CircleCheck, size: 16 }), sending ? t('sending') : button.label] }, button.id)) : (_jsx(Button, { size: "sm", disabled: locked, "aria-description": button.description, onClick: button.onPress, children: button.label }, button.id))) })] }));
|
|
14
|
+
}
|
|
15
|
+
/* The buttons of a question: back through its questions, dismissing an optional one, and Next until the last becomes Answer. */
|
|
16
|
+
export function QuestionActions({ index, dismissable, last, ready, locked, sending, onPrevious, onDismiss, onCommit }) {
|
|
17
|
+
const { t } = useTranslation(['agent-prompts', 'agent-chat']);
|
|
18
|
+
return (_jsxs(_Fragment, { children: [index > 0 && (_jsx(Button, { size: "sm", disabled: sending, onClick: onPrevious, children: t('question.previous') })), dismissable && index === 0 && (_jsx(Button, { size: "sm", disabled: locked, onClick: onDismiss, children: t('agent-chat:common.action.dismiss') })), _jsx("span", { className: "grow" }), _jsxs(PromptPrimary, { disabled: locked || !ready, onClick: onCommit, children: [sending ? t('sending') : last ? t('question.answer') : t('question.next'), _jsx(Icon, { icon: ArrowUp, size: 16 })] })] }));
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export declare const PROMPT_SURFACE = "flex flex-col overflow-hidden rounded-2xl border border-border bg-[color-mix(in_srgb,var(--surface-raised)_92%,transparent)] shadow-float backdrop-blur-[14px]";
|
|
3
|
+
export type PromptCardKind = 'approval' | 'question' | 'waiting';
|
|
4
|
+
interface PromptCardProps {
|
|
5
|
+
kind: PromptCardKind;
|
|
6
|
+
heading: ReactNode;
|
|
7
|
+
meta?: ReactNode;
|
|
8
|
+
top?: ReactNode;
|
|
9
|
+
actions: ReactNode;
|
|
10
|
+
busy: boolean;
|
|
11
|
+
notice: string | null;
|
|
12
|
+
error: string | null;
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
export declare function PromptCard({ kind, heading, meta, top, actions, busy, notice, error, children }: PromptCardProps): import("react").JSX.Element;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Hand, MessageCircleQuestionMark } from 'lucide-react';
|
|
4
|
+
import { chatHost } from '../../host';
|
|
5
|
+
import { headingKey, isPrimaryKey, staysInCard, stepIndex, toolbarKey } from '../logic/keys';
|
|
6
|
+
import { Icon } from '@ruimte/ui/Icon';
|
|
7
|
+
/* The raised glass a chat's composer is made of, for a card that stands on its own over a canvas. */
|
|
8
|
+
export const PROMPT_SURFACE = 'flex flex-col overflow-hidden rounded-2xl border border-border bg-[color-mix(in_srgb,var(--surface-raised)_92%,transparent)] shadow-float backdrop-blur-[14px]';
|
|
9
|
+
const ICONS = { approval: Hand, question: MessageCircleQuestionMark, waiting: MessageCircleQuestionMark };
|
|
10
|
+
/*
|
|
11
|
+
* The keys every card shares: Mod+Enter, entering from the heading and the action row. The body handles
|
|
12
|
+
* its own keys first and says so with `preventDefault`.
|
|
13
|
+
*/
|
|
14
|
+
function onCardKeyDown(event) {
|
|
15
|
+
const card = event.currentTarget;
|
|
16
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
17
|
+
if (!event.defaultPrevented) {
|
|
18
|
+
if (isPrimaryKey(event.nativeEvent, chatHost().isApplePlatform())) {
|
|
19
|
+
event.preventDefault();
|
|
20
|
+
// A click on the button itself, so a disabled Next or Answer stays as disabled as it looks.
|
|
21
|
+
card.querySelector('[data-prompt-primary]:not(:disabled)')?.click();
|
|
22
|
+
}
|
|
23
|
+
else if (target?.classList.contains('prompt-heading') && headingKey(event.nativeEvent)) {
|
|
24
|
+
event.preventDefault();
|
|
25
|
+
card.querySelector('[data-prompt-entry]:not(:disabled), [data-prompt-primary]:not(:disabled)')?.focus();
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
const toolbar = target?.closest('[role="toolbar"]');
|
|
29
|
+
const step = toolbar ? toolbarKey(event.nativeEvent) : null;
|
|
30
|
+
if (toolbar && step !== null) {
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
const buttons = [...toolbar.querySelectorAll('button:not(:disabled)')];
|
|
33
|
+
const current = buttons.findIndex((button) => button.contains(target));
|
|
34
|
+
buttons[stepIndex(current, buttons.length, step)]?.focus();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (event.defaultPrevented || staysInCard(event.nativeEvent)) {
|
|
39
|
+
event.stopPropagation();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/* What every prompt looks like, whatever asked it: in a chat's composer and in a canvas's stack. */
|
|
43
|
+
export function PromptCard({ kind, heading, meta, top, actions, busy, notice, error, children }) {
|
|
44
|
+
const { t } = useTranslation('agent-prompts');
|
|
45
|
+
const card = (_jsxs("div", { className: "prompt-card flex min-h-0 flex-col gap-2 p-3", role: "group", "aria-label": t(`card.${kind}`), "aria-busy": busy, onKeyDown: onCardKeyDown, children: [_jsx("div", { className: "-m-1.5 max-h-[min(50dvh,480px)] overflow-auto overscroll-contain p-1.5", children: _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx(Icon, { icon: ICONS[kind], size: 16, className: "mt-[3px] shrink-0 text-status-needs-you" }), _jsx("h3", { tabIndex: -1, className: "prompt-heading min-w-0 break-words text-sm font-semibold text-text outline-offset-4", children: heading })] }), meta && _jsx("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-text-muted", children: meta }), children, notice && _jsx("p", { className: "text-xs text-text-muted", children: notice }), error && (_jsx("p", { role: "alert", className: "text-xs text-status-error", children: error }))] }) }), _jsx("div", { role: "toolbar", "aria-label": t('toolbar'), className: "flex flex-wrap items-center gap-1.5", children: actions })] }));
|
|
46
|
+
if (top === undefined) {
|
|
47
|
+
return card;
|
|
48
|
+
}
|
|
49
|
+
return (_jsxs("div", { className: "flex min-h-0 flex-col", children: [top, card] }));
|
|
50
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { type PromptViewProps } from '../logic/subjects';
|
|
2
|
+
export declare function PromptView(props: PromptViewProps): string | number | bigint | boolean | import("react").JSX.Element | Iterable<import("react").ReactNode> | Promise<string | number | bigint | boolean | Iterable<import("react").ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null | undefined;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { toolSummary } from '../../chat/logic/tools';
|
|
4
|
+
import { chatHost } from '../../host';
|
|
5
|
+
import { answerValue, promptAnswers, questionAnswer } from '../logic/prompts';
|
|
6
|
+
import { approvalButtons } from '../logic/subjects';
|
|
7
|
+
import { ApprovalBody } from './ApprovalBody';
|
|
8
|
+
import { ApprovalActions, QuestionActions } from './PromptActions';
|
|
9
|
+
import { PromptCard } from './PromptCard';
|
|
10
|
+
import { QuestionBody } from './QuestionBody';
|
|
11
|
+
/* A permission request or question, answered in place: a chat's, or one of the app's own that it draws itself. */
|
|
12
|
+
export function PromptView(props) {
|
|
13
|
+
const { subject, draft, onDraft, onAction, more, hasDraft, denyReason, disabled, sending, error, top } = props;
|
|
14
|
+
const { t } = useTranslation('agent-prompts');
|
|
15
|
+
const locked = sending || disabled;
|
|
16
|
+
const notice = disabled ? t('error.notConnected') : null;
|
|
17
|
+
const buttons = approvalButtons(subject, draft.reason).map(({ action, ...button }) => ({ ...button, onPress: () => onAction(action) }));
|
|
18
|
+
if (subject.kind === 'host') {
|
|
19
|
+
return chatHost().prompts.render(subject.prompt, props);
|
|
20
|
+
}
|
|
21
|
+
const { item } = subject;
|
|
22
|
+
const question = item.kind === 'question' ? item.questions[Math.min(draft.index, item.questions.length - 1)] : null;
|
|
23
|
+
const answer = question ? questionAnswer(draft, question) : null;
|
|
24
|
+
const last = item.kind === 'question' && draft.index === item.questions.length - 1;
|
|
25
|
+
// Whether Next or Answer may go with this draft. The keys ask it as well as the button.
|
|
26
|
+
const readyWith = (next) => item.kind === 'question' && !!question && !!answerValue(questionAnswer(next, question)) && !(last && !promptAnswers(item, next));
|
|
27
|
+
const commit = (next) => {
|
|
28
|
+
if (item.kind !== 'question' || locked || !readyWith(next)) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (!last) {
|
|
32
|
+
onDraft({ ...next, index: next.index + 1 });
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const answers = promptAnswers(item, next);
|
|
36
|
+
if (answers) {
|
|
37
|
+
onAction({ kind: 'answer', answers });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const multiple = item.kind === 'question' && item.questions.length > 1;
|
|
42
|
+
const meta = more > 0 || hasDraft || multiple ? (_jsxs(_Fragment, { children: [more > 0 && _jsx("span", { className: "tabular-nums", children: t('meta.waiting', { count: more + 1 }) }), multiple && _jsx("span", { className: "tabular-nums", children: t('meta.question', { index: draft.index + 1, total: item.questions.length }) }), hasDraft && _jsx("span", { children: t('meta.draftSaved') })] })) : null;
|
|
43
|
+
if (item.kind === 'approval') {
|
|
44
|
+
return (_jsx(PromptCard, { kind: "approval", heading: `${item.toolName === 'Bash' ? t('approval.runCommand') : item.toolName} ${item.toolName === 'Bash' ? '' : toolSummary(item.toolName, item.input)}`, meta: meta, top: top, busy: sending, notice: notice, error: error, actions: _jsx(ApprovalActions, { buttons: buttons, onAddReason: denyReason && !draft.showReason ? () => onDraft({ ...draft, showReason: true }) : undefined, locked: locked, sending: sending }), children: _jsx(ApprovalBody, { item: item, draft: draft, onDraft: onDraft, denyReason: denyReason, locked: locked }) }));
|
|
45
|
+
}
|
|
46
|
+
return (_jsx(PromptCard, { kind: "question", heading: question?.question, meta: meta, top: top, busy: sending, notice: notice, error: error, actions: _jsx(QuestionActions, { index: draft.index, dismissable: item.async === true, last: last, ready: readyWith(draft), locked: locked, sending: sending, onPrevious: () => onDraft({ ...draft, index: draft.index - 1 }), onDismiss: () => onAction({ kind: 'dismiss' }), onCommit: () => commit(draft) }), children: question && answer && (_jsx(QuestionBody, { question: question, answer: answer, onAnswer: (next) => onDraft({ ...draft, answers: { ...draft.answers, [question.id]: next } }), onCommit: (next) => commit({ ...draft, answers: { ...draft.answers, [question.id]: next } }), locked: locked })) }));
|
|
47
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ChatQuestion } from '@ruimte/agent-contracts';
|
|
2
|
+
import { type PromptAnswer } from '../logic/prompts';
|
|
3
|
+
export declare function QuestionBody({ question, answer, onAnswer, onCommit, locked }: {
|
|
4
|
+
question: ChatQuestion;
|
|
5
|
+
answer: PromptAnswer;
|
|
6
|
+
onAnswer(answer: PromptAnswer): void;
|
|
7
|
+
onCommit(answer: PromptAnswer): void;
|
|
8
|
+
locked: boolean;
|
|
9
|
+
}): import("react").JSX.Element;
|