@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,339 @@
|
|
|
1
|
+
import i18next from 'i18next';
|
|
2
|
+
import { abortedByMachine } from '@ruimte/agent-contracts';
|
|
3
|
+
import { formatElapsedShort } from '@ruimte/ui/format/duration';
|
|
4
|
+
import { handbackReportOf } from './handback';
|
|
5
|
+
import { toolEntry } from './tool-catalog';
|
|
6
|
+
import { hasFileChanges, isFileChange } from './tools';
|
|
7
|
+
/*
|
|
8
|
+
* A turn runs in two rhythms. The tool lines are a list and read as one when they sit tight
|
|
9
|
+
* against each other; prose and cards are blocks and need room around them. Where the two meet,
|
|
10
|
+
* the block gap marks the seam, so an answer never looks glued to the call above it.
|
|
11
|
+
*/
|
|
12
|
+
const BLOCK_KINDS = new Set(['assistant', 'report', 'thinking', 'changed-files', 'compaction']);
|
|
13
|
+
export const isBlock = (row) => BLOCK_KINDS.has(row.kind);
|
|
14
|
+
/* "Read 4 files", "Ran 2 commands", or "12 tool calls" when the run mixes kinds. */
|
|
15
|
+
export const summarizeGroup = (tools) => {
|
|
16
|
+
const names = new Set(tools.map((tool) => tool.name));
|
|
17
|
+
const only = names.size === 1 ? tools[0].name : null;
|
|
18
|
+
if (only !== null && toolEntry(only)?.grouped === true) {
|
|
19
|
+
return i18next.t(`agent-chat:group.tools.${only}`, { count: tools.length });
|
|
20
|
+
}
|
|
21
|
+
/* A run that only touches files says so once, whether it took one tool or three, and counts the
|
|
22
|
+
files rather than the calls: three edits to one file are one file edited. */
|
|
23
|
+
if (tools.every((tool) => isFileChange(tool.name))) {
|
|
24
|
+
const paths = new Set(tools.map((tool) => tool.input?.file_path ?? tool.id));
|
|
25
|
+
return i18next.t('agent-chat:group.edited', { count: paths.size });
|
|
26
|
+
}
|
|
27
|
+
if (only !== null) {
|
|
28
|
+
return i18next.t('agent-chat:group.calls', { name: only, count: tools.length });
|
|
29
|
+
}
|
|
30
|
+
return i18next.t('agent-chat:group.toolCalls', { count: tools.length });
|
|
31
|
+
};
|
|
32
|
+
/*
|
|
33
|
+
* What a folded turn did, a sentence per kind of call in the order it first came: "Read 11 files",
|
|
34
|
+
* "Searched 2 patterns". Every file change reads as one sentence, and calls without a sentence of
|
|
35
|
+
* their own are counted together at the end.
|
|
36
|
+
*/
|
|
37
|
+
export const summarizeTurn = (tools) => {
|
|
38
|
+
// Keyed by the sentence, so its place is where the first call of that kind came.
|
|
39
|
+
const counts = new Map();
|
|
40
|
+
const edited = new Set();
|
|
41
|
+
let other = 0;
|
|
42
|
+
for (const tool of tools) {
|
|
43
|
+
if (isFileChange(tool.name)) {
|
|
44
|
+
edited.add(tool.input?.file_path ?? tool.id);
|
|
45
|
+
counts.set('agent-chat:group.edited', edited.size);
|
|
46
|
+
}
|
|
47
|
+
else if (toolEntry(tool.name)?.grouped === true) {
|
|
48
|
+
const key = `agent-chat:group.tools.${tool.name}`;
|
|
49
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
other += 1;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const parts = [...counts].map(([key, count]) => i18next.t(key, { count }));
|
|
56
|
+
if (other > 0) {
|
|
57
|
+
parts.push(i18next.t(parts.length === 0 ? 'agent-chat:group.toolCalls' : 'agent-chat:group.otherCalls', { count: other }));
|
|
58
|
+
}
|
|
59
|
+
return parts;
|
|
60
|
+
};
|
|
61
|
+
/*
|
|
62
|
+
* What a turn nobody asked for is about. The CLI wakes the agent when a background subagent settles
|
|
63
|
+
* and hands over its summary; without one all that is known is that the agent went on by itself.
|
|
64
|
+
*/
|
|
65
|
+
export const agentTurnLabel = (turn) => {
|
|
66
|
+
const about = (what) => (turn.label ? i18next.t('agent-chat:turn.about', { what, label: turn.label }) : what);
|
|
67
|
+
// A turn the machine opened with the results of tasks this chat gave; the label is their titles.
|
|
68
|
+
if (turn.taskIds !== undefined && turn.taskIds.length > 0) {
|
|
69
|
+
return about(i18next.t('agent-chat:turn.wokenByTask', { count: turn.taskIds.length }));
|
|
70
|
+
}
|
|
71
|
+
// A turn the machine opened over a message another node sent; the label names who sent it.
|
|
72
|
+
if (turn.messageFrom !== undefined && turn.messageFrom.length > 0) {
|
|
73
|
+
return about(i18next.t('agent-chat:turn.wokenByMessage', { count: turn.messageFrom.length }));
|
|
74
|
+
}
|
|
75
|
+
return turn.label ? i18next.t('agent-chat:turn.subagentFinished', { label: turn.label }) : i18next.t('agent-chat:turn.continued');
|
|
76
|
+
};
|
|
77
|
+
/* The items of the turn tell a turn a person stopped from one the machine ended after a restart. */
|
|
78
|
+
export const turnLabel = (turn, items = []) => {
|
|
79
|
+
const duration = formatElapsedShort((turn.endedAt ?? turn.createdAt) - turn.createdAt);
|
|
80
|
+
switch (turn.state) {
|
|
81
|
+
case 'aborted':
|
|
82
|
+
return abortedByMachine(turn, items) ? i18next.t('agent-chat:turn.stopped', { duration }) : i18next.t('agent-chat:turn.youStopped', { duration });
|
|
83
|
+
case 'error':
|
|
84
|
+
return i18next.t('agent-chat:turn.failed', { duration });
|
|
85
|
+
default:
|
|
86
|
+
return i18next.t('agent-chat:turn.worked', { duration });
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
// A subagent's own work, and an agent it opened, belong to its row, not to the thread; an old record has no row for it.
|
|
90
|
+
const parentOf = (item) => item.kind === 'tool' || item.kind === 'assistant' || item.kind === 'subagent' ? (item.parentToolUseId ?? null) : null;
|
|
91
|
+
// Deeper than the CLI forwards any call; it also ends a chain of rows that would name each other.
|
|
92
|
+
const MAX_NESTING = 8;
|
|
93
|
+
const branchOf = (item, options, children, depth = 0) => {
|
|
94
|
+
const own = children.get(item.toolUseId) ?? [];
|
|
95
|
+
return {
|
|
96
|
+
id: item.id,
|
|
97
|
+
item,
|
|
98
|
+
children: own.filter((child) => child.kind !== 'subagent'),
|
|
99
|
+
nested: depth >= MAX_NESTING
|
|
100
|
+
? []
|
|
101
|
+
: own.filter((child) => child.kind === 'subagent').map((child) => branchOf(child, options, children, depth + 1)),
|
|
102
|
+
expanded: options.expandedSubagents.has(item.id)
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
/* The branch a subagent's row sits in, its own or one it hangs under, anywhere in the thread's rows. */
|
|
106
|
+
export const findSubagentBranch = (rows, toolUseId) => {
|
|
107
|
+
const search = (branch) => {
|
|
108
|
+
if (branch.item.toolUseId === toolUseId) {
|
|
109
|
+
return branch;
|
|
110
|
+
}
|
|
111
|
+
for (const child of branch.nested) {
|
|
112
|
+
const found = search(child);
|
|
113
|
+
if (found) {
|
|
114
|
+
return found;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
};
|
|
119
|
+
for (let index = 0; index < rows.length; index++) {
|
|
120
|
+
const row = rows[index];
|
|
121
|
+
const branch = row.kind === 'subagent' ? search(row) : null;
|
|
122
|
+
if (branch) {
|
|
123
|
+
return { index, branch };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
};
|
|
128
|
+
/* What each subagent did, keyed by the call that spawned it, in the order it happened. */
|
|
129
|
+
const groupChildren = (items) => {
|
|
130
|
+
const children = new Map();
|
|
131
|
+
for (const item of items) {
|
|
132
|
+
const parent = parentOf(item);
|
|
133
|
+
if (parent === null) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const bucket = children.get(parent);
|
|
137
|
+
if (bucket) {
|
|
138
|
+
bucket.push(item);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
children.set(parent, [item]);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return children;
|
|
145
|
+
};
|
|
146
|
+
const flushTools = (buffer, rows, options) => {
|
|
147
|
+
if (buffer.length === 0) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const tools = buffer.splice(0);
|
|
151
|
+
const live = tools.filter((tool) => tool.state === 'running');
|
|
152
|
+
const settled = tools.filter((tool) => tool.state !== 'running');
|
|
153
|
+
if (settled.length === 1) {
|
|
154
|
+
rows.push({ kind: 'work', id: settled[0].id, tool: settled[0] });
|
|
155
|
+
}
|
|
156
|
+
else if (settled.length > 1) {
|
|
157
|
+
const id = `group-${settled[0].id}`;
|
|
158
|
+
const expanded = options.expandedGroups.has(id);
|
|
159
|
+
rows.push({ kind: 'work-group', id, tools: settled, summary: summarizeGroup(settled), expanded });
|
|
160
|
+
if (expanded) {
|
|
161
|
+
for (const tool of settled) {
|
|
162
|
+
rows.push({ kind: 'work', id: tool.id, tool });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
for (const tool of live) {
|
|
167
|
+
rows.push({ kind: 'work-live', id: tool.id, tool });
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
/* Rows for a run of items, in order, with tool runs folded and a subagent's work under its own row. */
|
|
171
|
+
const rowsForItems = (items, options, children) => {
|
|
172
|
+
const rows = [];
|
|
173
|
+
const tools = [];
|
|
174
|
+
for (const item of items) {
|
|
175
|
+
if (parentOf(item) !== null) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const report = handbackReportOf(item);
|
|
179
|
+
if (item.kind === 'tool' && item.workflow !== undefined) {
|
|
180
|
+
flushTools(tools, rows, options);
|
|
181
|
+
rows.push({ kind: 'workflow', id: item.id, tool: item, workflow: item.workflow });
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (item.kind === 'tool' && report === null) {
|
|
185
|
+
tools.push(item);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (report !== null) {
|
|
189
|
+
flushTools(tools, rows, options);
|
|
190
|
+
rows.push({ kind: 'report', id: item.id, text: report });
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
flushTools(tools, rows, options);
|
|
194
|
+
switch (item.kind) {
|
|
195
|
+
case 'subagent':
|
|
196
|
+
rows.push({ kind: 'subagent', ...branchOf(item, options, children) });
|
|
197
|
+
break;
|
|
198
|
+
case 'user':
|
|
199
|
+
rows.push({ kind: 'user', id: item.id, item });
|
|
200
|
+
break;
|
|
201
|
+
case 'assistant':
|
|
202
|
+
if (item.text !== '' || item.streaming) {
|
|
203
|
+
rows.push({ kind: 'assistant', id: item.id, item });
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
case 'thinking':
|
|
207
|
+
rows.push({ kind: 'thinking', id: item.id, item });
|
|
208
|
+
break;
|
|
209
|
+
case 'approval':
|
|
210
|
+
// A pending one sits on the composer; only its outcome belongs in the transcript.
|
|
211
|
+
if (item.decision !== 'pending') {
|
|
212
|
+
rows.push({ kind: 'approval', id: item.id, item });
|
|
213
|
+
}
|
|
214
|
+
break;
|
|
215
|
+
case 'question':
|
|
216
|
+
if (item.state !== 'pending') {
|
|
217
|
+
rows.push({ kind: 'question', id: item.id, item });
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
case 'note':
|
|
221
|
+
rows.push({ kind: 'note', id: item.id, level: item.level, text: item.text, ...(item.from === undefined ? {} : { from: item.from }) });
|
|
222
|
+
break;
|
|
223
|
+
case 'compaction':
|
|
224
|
+
rows.push({ kind: 'compaction', id: item.id, preTokens: item.preTokens });
|
|
225
|
+
break;
|
|
226
|
+
case 'turn':
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
flushTools(tools, rows, options);
|
|
231
|
+
return rows;
|
|
232
|
+
};
|
|
233
|
+
/* The card of a settled turn: the checkpoint diff when the host took one, else what the tool calls carry. */
|
|
234
|
+
const changedFilesRow = (turn, items) => {
|
|
235
|
+
const edits = items.filter((item) => item.kind === 'tool' && item.state === 'done' && isFileChange(item.name) && hasFileChanges(item));
|
|
236
|
+
const diff = turn.checkpointDiff ?? null;
|
|
237
|
+
const empty = diff === null ? edits.length === 0 : diff.files.length === 0;
|
|
238
|
+
if (empty) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
return { kind: 'changed-files', id: `files-${turn.id}`, turnId: turn.id, tools: edits, diff, checkpoint: turn.checkpoint !== undefined };
|
|
242
|
+
};
|
|
243
|
+
const lastAssistantRow = (rows) => {
|
|
244
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
245
|
+
const row = rows[i];
|
|
246
|
+
if (row.kind === 'assistant') {
|
|
247
|
+
return row;
|
|
248
|
+
}
|
|
249
|
+
if (row.kind !== 'note') {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return null;
|
|
254
|
+
};
|
|
255
|
+
/* The whole thread as rows; items without a turn (older records) are shown as they are. */
|
|
256
|
+
export const deriveTimelineRows = (items, options) => {
|
|
257
|
+
const rows = [];
|
|
258
|
+
const children = groupChildren(items);
|
|
259
|
+
// Items are grouped by turn, in order of first appearance; runs of turnless items keep their place.
|
|
260
|
+
const chunks = [];
|
|
261
|
+
const byTurn = new Map();
|
|
262
|
+
for (const item of items) {
|
|
263
|
+
if (item.turnId === null) {
|
|
264
|
+
const last = chunks[chunks.length - 1];
|
|
265
|
+
if (last && last.turnId === null) {
|
|
266
|
+
last.items.push(item);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
chunks.push({ turnId: null, items: [item] });
|
|
270
|
+
}
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
let bucket = byTurn.get(item.turnId);
|
|
274
|
+
if (!bucket) {
|
|
275
|
+
bucket = [];
|
|
276
|
+
byTurn.set(item.turnId, bucket);
|
|
277
|
+
chunks.push({ turnId: item.turnId, items: bucket });
|
|
278
|
+
}
|
|
279
|
+
bucket.push(item);
|
|
280
|
+
}
|
|
281
|
+
for (const chunk of chunks) {
|
|
282
|
+
if (chunk.turnId === null) {
|
|
283
|
+
rows.push(...rowsForItems(chunk.items, options, children));
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
const turnId = chunk.turnId;
|
|
287
|
+
const turn = chunk.items.find((item) => item.kind === 'turn');
|
|
288
|
+
const user = chunk.items.filter((item) => item.kind === 'user');
|
|
289
|
+
const rest = chunk.items.filter((item) => item.kind !== 'user' && item.kind !== 'turn');
|
|
290
|
+
if (turn?.origin === 'agent') {
|
|
291
|
+
// The agent started this one itself, so there is no message of the person to show above it.
|
|
292
|
+
rows.push({ kind: 'turn-start', id: `start-${turnId}`, turn, label: agentTurnLabel(turn) });
|
|
293
|
+
}
|
|
294
|
+
rows.push(...rowsForItems(user, options, children));
|
|
295
|
+
const active = turnId === options.activeTurnId || turn?.state === 'running';
|
|
296
|
+
const work = rowsForItems(rest, options, children);
|
|
297
|
+
if (!turn || active) {
|
|
298
|
+
rows.push(...work);
|
|
299
|
+
if (active) {
|
|
300
|
+
rows.push({ kind: 'working', id: `working-${turnId}`, startedAt: turn?.createdAt ?? Date.now() });
|
|
301
|
+
}
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
// The closing answer stays visible; everything before it folds behind the label.
|
|
305
|
+
const finalAssistant = lastAssistantRow(work);
|
|
306
|
+
const before = finalAssistant ? work.slice(0, work.indexOf(finalAssistant)) : work;
|
|
307
|
+
// A workflow runs on past the turn that launched it, so its row stays in sight until it ends.
|
|
308
|
+
const standing = before.filter((row) => row.kind === 'workflow' && row.tool.state === 'running');
|
|
309
|
+
const folded = before.filter((row) => !standing.includes(row));
|
|
310
|
+
const expanded = options.expandedTurns.has(turnId);
|
|
311
|
+
if (folded.length > 0) {
|
|
312
|
+
const tools = rest.filter((item) => item.kind === 'tool' && parentOf(item) === null && handbackReportOf(item) === null);
|
|
313
|
+
rows.push({
|
|
314
|
+
kind: 'turn-fold',
|
|
315
|
+
id: `fold-${turnId}`,
|
|
316
|
+
turn,
|
|
317
|
+
label: turnLabel(turn, chunk.items),
|
|
318
|
+
work: summarizeTurn(tools),
|
|
319
|
+
hiddenCount: folded.length,
|
|
320
|
+
expanded
|
|
321
|
+
});
|
|
322
|
+
if (expanded) {
|
|
323
|
+
rows.push(...folded);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
rows.push(...standing);
|
|
327
|
+
const changed = changedFilesRow(turn, rest);
|
|
328
|
+
if (changed) {
|
|
329
|
+
rows.push(changed);
|
|
330
|
+
}
|
|
331
|
+
if (finalAssistant) {
|
|
332
|
+
rows.push(finalAssistant);
|
|
333
|
+
}
|
|
334
|
+
if (options.forkedTurns?.has(turnId) === true) {
|
|
335
|
+
rows.push({ kind: 'forks', id: `forks-${turnId}`, turnId });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return rows;
|
|
339
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type LucideIcon } from 'lucide-react';
|
|
2
|
+
export interface ToolEntry {
|
|
3
|
+
icon: LucideIcon;
|
|
4
|
+
summary: readonly string[];
|
|
5
|
+
grouped: boolean;
|
|
6
|
+
changesFiles: boolean;
|
|
7
|
+
readsImage: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare const TOOL_CATALOG: Record<string, ToolEntry>;
|
|
10
|
+
export declare const toolEntry: (name: string) => ToolEntry | undefined;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Bot, Eye, FolderSearch, Globe, ListTodo, Search, SquarePen, Terminal, Workflow, Zap } from 'lucide-react';
|
|
2
|
+
const entry = (icon, summary, rest = {}) => ({
|
|
3
|
+
icon,
|
|
4
|
+
summary,
|
|
5
|
+
grouped: true,
|
|
6
|
+
changesFiles: false,
|
|
7
|
+
readsImage: false,
|
|
8
|
+
...rest
|
|
9
|
+
});
|
|
10
|
+
/*
|
|
11
|
+
* The icon, the summary line, the group sentence and the file-changing flag of every tool the thread
|
|
12
|
+
* has a word for, in one table, because they used to sit in four and a name added to one of them was
|
|
13
|
+
* missing from the rest. A name that is not here still draws, reads and groups; it falls back to the
|
|
14
|
+
* wrench and to the first string of its input.
|
|
15
|
+
*/
|
|
16
|
+
export const TOOL_CATALOG = {
|
|
17
|
+
Read: entry(Eye, ['file_path'], { readsImage: true }),
|
|
18
|
+
// Only `readImagePath` ever had this one, which is why it has no sentence of its own yet.
|
|
19
|
+
NotebookRead: entry(Eye, ['file_path', 'notebook_path'], { grouped: false, readsImage: true }),
|
|
20
|
+
// These three read as "Edited 3 files" through `changesFiles`, so they carry no copy of that sentence.
|
|
21
|
+
Edit: entry(SquarePen, ['file_path'], { grouped: false, changesFiles: true }),
|
|
22
|
+
Write: entry(SquarePen, ['file_path'], { changesFiles: true }),
|
|
23
|
+
MultiEdit: entry(SquarePen, ['file_path'], { grouped: false, changesFiles: true }),
|
|
24
|
+
NotebookEdit: entry(SquarePen, ['file_path', 'notebook_path']),
|
|
25
|
+
// Codex reports a patch as the paths it touches; its own input carries no path of its own.
|
|
26
|
+
ApplyPatch: entry(SquarePen, ['summary'], { grouped: false, changesFiles: true }),
|
|
27
|
+
Bash: entry(Terminal, ['description', 'command']),
|
|
28
|
+
Grep: entry(Search, ['pattern']),
|
|
29
|
+
Glob: entry(FolderSearch, ['pattern']),
|
|
30
|
+
WebFetch: entry(Globe, ['url', 'query']),
|
|
31
|
+
WebSearch: entry(Globe, ['url', 'query']),
|
|
32
|
+
Task: entry(Bot, ['description']),
|
|
33
|
+
Agent: entry(Bot, ['description']),
|
|
34
|
+
Skill: entry(Zap, ['skill']),
|
|
35
|
+
// A plan is a list of objects, so there is no line to lift out of it.
|
|
36
|
+
TodoWrite: entry(ListTodo, []),
|
|
37
|
+
// Its input is the whole script; the line takes what the CLI says the workflow is about instead.
|
|
38
|
+
Workflow: entry(Workflow, [], { grouped: false })
|
|
39
|
+
};
|
|
40
|
+
export const toolEntry = (name) => TOOL_CATALOG[name];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ChatFileChange, ChatToolItem } from '@ruimte/agent-contracts';
|
|
2
|
+
export interface FileChange {
|
|
3
|
+
path: string;
|
|
4
|
+
before: string;
|
|
5
|
+
after: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const toolSummary: (name: string, input: unknown) => string;
|
|
8
|
+
export declare const readImagePath: (name: string, input: unknown) => string | null;
|
|
9
|
+
export declare const fileChanges: (name: string, input: unknown) => FileChange[];
|
|
10
|
+
export declare const isFileChange: (name: string) => boolean;
|
|
11
|
+
export declare const unifiedChanges: (tool: ChatToolItem) => ChatFileChange[];
|
|
12
|
+
export declare const approvalChanges: (input: unknown) => ChatFileChange[];
|
|
13
|
+
export declare const hasFileChanges: (tool: ChatToolItem) => boolean;
|
|
14
|
+
export declare const toolStartedAt: (tool: ChatToolItem) => number;
|
|
15
|
+
export declare const liveOutput: (tool: ChatToolItem) => string | null;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { isRecord } from './json';
|
|
2
|
+
import { toolEntry } from './tool-catalog';
|
|
3
|
+
const str = (value) => (typeof value === 'string' ? value : null);
|
|
4
|
+
/* The one line that says what a tool call is about: the command, the file, the pattern. */
|
|
5
|
+
export const toolSummary = (name, input) => {
|
|
6
|
+
if (!isRecord(input)) {
|
|
7
|
+
return '';
|
|
8
|
+
}
|
|
9
|
+
const known = toolEntry(name);
|
|
10
|
+
if (known === undefined) {
|
|
11
|
+
// A tool nobody wrote down, an MCP one above all, gets its best guess from the first string it was given.
|
|
12
|
+
const first = Object.values(input).find((value) => typeof value === 'string');
|
|
13
|
+
return typeof first === 'string' ? first : '';
|
|
14
|
+
}
|
|
15
|
+
for (const key of known.summary) {
|
|
16
|
+
const value = str(input[key]);
|
|
17
|
+
if (value !== null) {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return '';
|
|
22
|
+
};
|
|
23
|
+
// What `GET /fs/file` will serve; a path with another suffix is not worth asking the host about.
|
|
24
|
+
const IMAGE_SUFFIXES = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg'];
|
|
25
|
+
/* The image file a tool call looked at, so the row can draw it; null for every other call. */
|
|
26
|
+
export const readImagePath = (name, input) => {
|
|
27
|
+
if (toolEntry(name)?.readsImage !== true) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const path = isRecord(input) ? str(input.file_path) : null;
|
|
31
|
+
if (path === null) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const lowered = path.toLowerCase();
|
|
35
|
+
return IMAGE_SUFFIXES.some((suffix) => lowered.endsWith(suffix)) ? path : null;
|
|
36
|
+
};
|
|
37
|
+
/* The before and after text of a file-changing tool call, so the thread can show it as a diff. */
|
|
38
|
+
export const fileChanges = (name, input) => {
|
|
39
|
+
if (!isRecord(input)) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
const path = str(input.file_path) ?? '';
|
|
43
|
+
if (name === 'Edit') {
|
|
44
|
+
return [{ path, before: str(input.old_string) ?? '', after: str(input.new_string) ?? '' }];
|
|
45
|
+
}
|
|
46
|
+
if (name === 'Write') {
|
|
47
|
+
return [{ path, before: '', after: str(input.content) ?? '' }];
|
|
48
|
+
}
|
|
49
|
+
if (name === 'MultiEdit' && Array.isArray(input.edits)) {
|
|
50
|
+
return input.edits.filter(isRecord).map((edit) => ({ path, before: str(edit.old_string) ?? '', after: str(edit.new_string) ?? '' }));
|
|
51
|
+
}
|
|
52
|
+
return [];
|
|
53
|
+
};
|
|
54
|
+
export const isFileChange = (name) => toolEntry(name)?.changesFiles === true;
|
|
55
|
+
/* The unified diffs a provider put next to a tool call; empty for one that reports before and after. */
|
|
56
|
+
export const unifiedChanges = (tool) => (tool.changes ?? []).filter((change) => change.diff !== '');
|
|
57
|
+
/* The same, for the copy an approval carries so the person can read what they are approving. */
|
|
58
|
+
export const approvalChanges = (input) => {
|
|
59
|
+
const changes = isRecord(input) ? input.changes : null;
|
|
60
|
+
if (!Array.isArray(changes)) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
return changes.filter((change) => isRecord(change) && typeof change.path === 'string' && typeof change.diff === 'string' && change.diff !== '');
|
|
64
|
+
};
|
|
65
|
+
/* Whether a settled call is worth a row in the turn's changed files card. */
|
|
66
|
+
export const hasFileChanges = (tool) => unifiedChanges(tool).length > 0 || fileChanges(tool.name, tool.input).length > 0;
|
|
67
|
+
/* When a running call started: what the CLI reported, or the moment the call appeared. */
|
|
68
|
+
export const toolStartedAt = (tool) => tool.progress?.startedAt ?? tool.createdAt;
|
|
69
|
+
// Partial output is a tail: what the command says now matters more than what it said first.
|
|
70
|
+
const LIVE_OUTPUT_LINES = 12;
|
|
71
|
+
/* The last lines of a running call's output, or null when the provider streams none. */
|
|
72
|
+
export const liveOutput = (tool) => {
|
|
73
|
+
const output = tool.progress?.output;
|
|
74
|
+
if (!output) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const lines = output.replace(/\n$/, '').split('\n');
|
|
78
|
+
return lines.slice(-LIVE_OUTPUT_LINES).join('\n');
|
|
79
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ChatSubagentStatus, ChatWorkflow, ChatWorkflowAgent } from '@ruimte/agent-contracts';
|
|
2
|
+
export type WorkflowAgentState = ChatSubagentStatus | 'waiting' | 'stopped';
|
|
3
|
+
export interface WorkflowAgentView {
|
|
4
|
+
agent: ChatWorkflowAgent;
|
|
5
|
+
state: WorkflowAgentState;
|
|
6
|
+
}
|
|
7
|
+
export interface WorkflowPhaseView {
|
|
8
|
+
key: string;
|
|
9
|
+
title: string | null;
|
|
10
|
+
agents: WorkflowAgentView[];
|
|
11
|
+
}
|
|
12
|
+
export declare const workflowAgentState: (agent: ChatWorkflowAgent, workflowRunning: boolean) => WorkflowAgentState;
|
|
13
|
+
export declare const workflowPhases: (workflow: ChatWorkflow, workflowRunning: boolean) => WorkflowPhaseView[];
|
|
14
|
+
export declare const workflowAgentTime: (view: WorkflowAgentView) => string | null;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import i18next from 'i18next';
|
|
2
|
+
import { formatElapsedShort } from '@ruimte/ui/format/duration';
|
|
3
|
+
export const workflowAgentState = (agent, workflowRunning) => {
|
|
4
|
+
if (agent.status !== 'running') {
|
|
5
|
+
return agent.status;
|
|
6
|
+
}
|
|
7
|
+
if (!workflowRunning) {
|
|
8
|
+
return 'stopped';
|
|
9
|
+
}
|
|
10
|
+
return agent.startedAt === null ? 'waiting' : 'running';
|
|
11
|
+
};
|
|
12
|
+
/*
|
|
13
|
+
* The phases in the order the script announced them, each with the agents it started in it. A phase
|
|
14
|
+
* announced ahead of its agents is there without any, and agents of no known phase come first, untitled.
|
|
15
|
+
*/
|
|
16
|
+
export const workflowPhases = (workflow, workflowRunning) => {
|
|
17
|
+
const known = new Map(workflow.phases.map((phase) => [phase.index, phase]));
|
|
18
|
+
const loose = [];
|
|
19
|
+
const byPhase = new Map();
|
|
20
|
+
for (const agent of workflow.agents) {
|
|
21
|
+
const view = { agent, state: workflowAgentState(agent, workflowRunning) };
|
|
22
|
+
if (agent.phaseIndex === null || !known.has(agent.phaseIndex)) {
|
|
23
|
+
loose.push(view);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
byPhase.set(agent.phaseIndex, [...(byPhase.get(agent.phaseIndex) ?? []), view]);
|
|
27
|
+
}
|
|
28
|
+
const phases = workflow.phases.map((phase) => ({ key: `phase-${phase.index}`, title: phase.title, agents: byPhase.get(phase.index) ?? [] }));
|
|
29
|
+
return loose.length > 0 ? [{ key: 'loose', title: null, agents: loose }, ...phases] : phases;
|
|
30
|
+
};
|
|
31
|
+
/* What the line of an agent that is not at work says about its time; one at work counts on by itself. */
|
|
32
|
+
export const workflowAgentTime = (view) => {
|
|
33
|
+
const duration = view.agent.durationMs === null ? null : formatElapsedShort(view.agent.durationMs);
|
|
34
|
+
switch (view.state) {
|
|
35
|
+
case 'running':
|
|
36
|
+
return null;
|
|
37
|
+
case 'waiting':
|
|
38
|
+
return i18next.t('agent-chat:rows.workflow.waiting');
|
|
39
|
+
case 'stopped':
|
|
40
|
+
return i18next.t('agent-chat:rows.workflow.stopped');
|
|
41
|
+
case 'done':
|
|
42
|
+
case 'failed':
|
|
43
|
+
return duration === null
|
|
44
|
+
? i18next.t(`agent-chat:common.status.${view.state}`)
|
|
45
|
+
: i18next.t(`agent-chat:rows.subagent.${view.state}In`, { duration });
|
|
46
|
+
}
|
|
47
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export declare const MENTION_DRAG_TYPE = "application/x-ruimte-mention";
|
|
2
|
+
export interface MentionQuery {
|
|
3
|
+
start: number;
|
|
4
|
+
query: string;
|
|
5
|
+
}
|
|
6
|
+
export type ChipSegment = {
|
|
7
|
+
kind: 'text';
|
|
8
|
+
text: string;
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'mention';
|
|
11
|
+
path: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'skill';
|
|
14
|
+
name: string;
|
|
15
|
+
};
|
|
16
|
+
export declare const findMentionQuery: (text: string, caret: number) => MentionQuery | null;
|
|
17
|
+
export declare const findSkillQuery: (text: string, caret: number) => MentionQuery | null;
|
|
18
|
+
export declare const insertToken: (text: string, query: MentionQuery, sigil: string, value: string) => {
|
|
19
|
+
text: string;
|
|
20
|
+
caret: number;
|
|
21
|
+
};
|
|
22
|
+
export declare const dropQuery: (text: string, query: MentionQuery) => {
|
|
23
|
+
text: string;
|
|
24
|
+
caret: number;
|
|
25
|
+
};
|
|
26
|
+
export declare const insertMention: (text: string, query: MentionQuery, path: string) => {
|
|
27
|
+
text: string;
|
|
28
|
+
caret: number;
|
|
29
|
+
};
|
|
30
|
+
export declare const insertSkill: (text: string, query: MentionQuery, name: string) => {
|
|
31
|
+
text: string;
|
|
32
|
+
caret: number;
|
|
33
|
+
};
|
|
34
|
+
export declare const presentMentions: (text: string, chosen: string[]) => string[];
|
|
35
|
+
export declare const presentSkills: (text: string, chosen: string[]) => string[];
|
|
36
|
+
export declare const chipText: (segment: ChipSegment) => string;
|
|
37
|
+
export interface TextRange {
|
|
38
|
+
from: number;
|
|
39
|
+
to: number;
|
|
40
|
+
}
|
|
41
|
+
export interface ChipRange extends TextRange {
|
|
42
|
+
kind: 'mention' | 'skill';
|
|
43
|
+
value: string;
|
|
44
|
+
}
|
|
45
|
+
export declare const chipRanges: (text: string, mentions: string[], skills?: string[], excluded?: readonly TextRange[]) => ChipRange[];
|
|
46
|
+
export declare const tokenizeChips: (text: string, mentions: string[], skills?: string[]) => ChipSegment[];
|