@adecore/agents-react 0.21.1 → 0.22.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/dist/agents/model-requirement.d.ts +6 -0
- package/dist/agents/model-requirement.js +26 -0
- package/dist/agents/status-look.js +2 -3
- package/dist/chat/chat-client.d.ts +3 -2
- package/dist/chat/chat-client.js +4 -2
- package/dist/chat/drafts.d.ts +2 -1
- package/dist/chat/drafts.js +21 -17
- package/dist/chat/logic/ansi.js +5 -1
- package/dist/chat/logic/fork.js +8 -9
- package/dist/chat/logic/limit.js +17 -14
- package/dist/chat/logic/picker-entries.d.ts +10 -0
- package/dist/chat/logic/picker-entries.js +19 -0
- package/dist/chat/logic/resume-compaction.js +2 -11
- package/dist/chat/logic/scrubber.js +9 -18
- package/dist/chat/logic/timeline.d.ts +5 -0
- package/dist/chat/logic/timeline.js +25 -11
- package/dist/chat/logic/tool-catalog.js +3 -4
- package/dist/chat/mentions.js +5 -6
- package/dist/chat/prompt-hold.d.ts +8 -0
- package/dist/chat/prompt-hold.js +16 -0
- package/dist/chat/quote.d.ts +4 -0
- package/dist/chat/quote.js +19 -0
- package/dist/chat/runtime-modes.js +2 -4
- package/dist/chat/stash.d.ts +8 -2
- package/dist/chat/stash.js +22 -8
- package/dist/chat/subagent-list.d.ts +7 -1
- package/dist/chat/subagent-list.js +13 -1
- package/dist/chat/thumbnails.js +3 -4
- package/dist/chat/timeline-scroll.d.ts +6 -2
- package/dist/chat/timeline-scroll.js +45 -46
- package/dist/chat/ui/BookmarkSubmenu.js +1 -5
- package/dist/chat/ui/ChatActivity.js +22 -8
- package/dist/chat/ui/CodeBlock.js +2 -3
- package/dist/chat/ui/Composer.js +160 -88
- package/dist/chat/ui/ComposerInput.d.ts +3 -1
- package/dist/chat/ui/ComposerInput.js +13 -4
- package/dist/chat/ui/Markdown.js +11 -11
- package/dist/chat/ui/Pickers.js +31 -27
- package/dist/chat/ui/PromptComposer.d.ts +8 -1
- package/dist/chat/ui/PromptComposer.js +18 -6
- package/dist/chat/ui/QuoteButton.js +4 -5
- package/dist/chat/ui/RunSettings.js +8 -2
- package/dist/chat/ui/Scrubber.js +2 -3
- package/dist/chat/ui/SubagentControls.d.ts +4 -2
- package/dist/chat/ui/SubagentControls.js +13 -11
- package/dist/chat/ui/SubagentInfo.d.ts +1 -1
- package/dist/chat/ui/SubagentInfo.js +9 -15
- package/dist/chat/ui/SubagentStopButton.js +2 -1
- package/dist/chat/ui/Timeline.js +39 -21
- package/dist/chat/ui/TimelineMenu.js +3 -9
- package/dist/chat/ui/UnifiedDiff.js +3 -5
- package/dist/chat/ui/VisualFrame.js +3 -4
- package/dist/chat/ui/composer/keys.js +19 -11
- package/dist/chat/ui/composer/menu-aria.d.ts +10 -0
- package/dist/chat/ui/composer/menu-aria.js +18 -0
- package/dist/chat/ui/intelligent-ui/render-context.d.ts +1 -1
- package/dist/chat/ui/quote-selection.d.ts +5 -2
- package/dist/chat/ui/quote-selection.js +3 -2
- package/dist/chat/ui/rows/MessageRows.js +26 -8
- package/dist/chat/ui/rows/Rows.js +1 -1
- package/dist/chat/ui/rows/WorkRows.d.ts +7 -4
- package/dist/chat/ui/rows/WorkRows.js +22 -17
- package/dist/chat/visual-viewport.d.ts +1 -1
- package/dist/chat/visual-viewport.js +9 -8
- package/dist/host.d.ts +1 -0
- package/dist/host.js +1 -0
- package/dist/locales/en/agent-chat.json +30 -7
- package/dist/locales/nl/agent-chat.json +30 -7
- package/dist/port-transport.js +4 -8
- package/dist/state/chats.d.ts +2 -1
- package/dist/state/chats.js +29 -15
- package/dist/state/usage.js +44 -49
- package/dist/storage.js +7 -7
- package/dist/theme.css +6 -14
- package/dist/usage/LimitsList.js +1 -2
- package/dist/usage/UsageBreakdown.js +1 -3
- package/dist/usage/UsageDialog.js +2 -4
- package/dist/usage/format.js +6 -6
- package/dist/usage/money.js +1 -5
- package/dist/usage/summary.js +2 -4
- package/package.json +24 -4
- package/src/agents/model-requirement.ts +36 -0
- package/src/agents/status-look.ts +2 -3
- package/src/chat/chat-client.ts +7 -3
- package/src/chat/drafts.ts +33 -21
- package/src/chat/logic/ansi.ts +5 -1
- package/src/chat/logic/fork.ts +8 -9
- package/src/chat/logic/limit.ts +17 -16
- package/src/chat/logic/picker-entries.ts +25 -0
- package/src/chat/logic/resume-compaction.ts +2 -11
- package/src/chat/logic/scrubber.ts +13 -17
- package/src/chat/logic/timeline.ts +39 -13
- package/src/chat/logic/tool-catalog.ts +7 -6
- package/src/chat/mentions.ts +5 -6
- package/src/chat/prompt-hold.ts +27 -0
- package/src/chat/quote.ts +20 -0
- package/src/chat/runtime-modes.ts +2 -4
- package/src/chat/stash.ts +32 -8
- package/src/chat/subagent-list.ts +19 -5
- package/src/chat/thumbnails.ts +3 -4
- package/src/chat/timeline-scroll.ts +58 -53
- package/src/chat/ui/BookmarkSubmenu.tsx +1 -5
- package/src/chat/ui/ChatActivity.tsx +35 -9
- package/src/chat/ui/CodeBlock.tsx +2 -3
- package/src/chat/ui/Composer.tsx +263 -138
- package/src/chat/ui/ComposerInput.tsx +18 -3
- package/src/chat/ui/Markdown.tsx +13 -17
- package/src/chat/ui/Pickers.tsx +56 -39
- package/src/chat/ui/PromptComposer.tsx +37 -8
- package/src/chat/ui/QuoteButton.tsx +5 -5
- package/src/chat/ui/RunSettings.tsx +9 -2
- package/src/chat/ui/Scrubber.tsx +2 -3
- package/src/chat/ui/SubagentControls.tsx +23 -22
- package/src/chat/ui/SubagentInfo.tsx +24 -42
- package/src/chat/ui/SubagentStopButton.tsx +2 -1
- package/src/chat/ui/SubagentTimeline.tsx +1 -2
- package/src/chat/ui/Timeline.tsx +44 -24
- package/src/chat/ui/TimelineMenu.tsx +3 -9
- package/src/chat/ui/UnifiedDiff.tsx +3 -5
- package/src/chat/ui/VisualFrame.tsx +3 -4
- package/src/chat/ui/composer/keys.ts +21 -11
- package/src/chat/ui/composer/menu-aria.ts +29 -0
- package/src/chat/ui/intelligent-ui/render-context.ts +2 -2
- package/src/chat/ui/quote-selection.ts +6 -4
- package/src/chat/ui/rehype-chips.ts +1 -1
- package/src/chat/ui/rehype-fade.ts +1 -1
- package/src/chat/ui/remark-html-as-text.ts +1 -1
- package/src/chat/ui/rows/MessageRows.tsx +47 -9
- package/src/chat/ui/rows/Rows.tsx +3 -1
- package/src/chat/ui/rows/WorkRows.tsx +101 -70
- package/src/chat/visual-viewport.ts +10 -8
- package/src/host.ts +4 -3
- package/src/locales/en/agent-chat.json +30 -7
- package/src/locales/nl/agent-chat.json +30 -7
- package/src/port-transport.ts +4 -8
- package/src/state/chats.ts +42 -16
- package/src/state/usage.ts +49 -60
- package/src/storage.ts +7 -7
- package/src/theme.css +6 -14
- package/src/usage/LimitsList.tsx +2 -4
- package/src/usage/UsageBreakdown.tsx +2 -5
- package/src/usage/UsageDialog.tsx +2 -4
- package/src/usage/format.ts +6 -6
- package/src/usage/money.ts +1 -5
- package/src/usage/summary.ts +2 -4
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ModelInfo, ProviderInfo } from '@adecore/agent-contracts';
|
|
2
|
+
export interface ModelRequirement {
|
|
3
|
+
cli: string;
|
|
4
|
+
minVersion: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function modelRequirementOf(model: ModelInfo, provider: ProviderInfo, reported: ReadonlySet<string>): ModelRequirement | null;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
function partsOf(version, strict) {
|
|
2
|
+
const found = (strict ? /^(\d+)\.(\d+)(?:\.(\d+))?$/ : /(\d+)\.(\d+)\.(\d+)/).exec(version);
|
|
3
|
+
return found === null ? null : [Number(found[1]), Number(found[2]), Number(found[3] ?? 0)];
|
|
4
|
+
}
|
|
5
|
+
/* Whether the model's reported name is this model: its slug, the slug with a suffix such as `[1m]`. */
|
|
6
|
+
function isReported(slug, reported) {
|
|
7
|
+
return [...reported].some((name) => name === slug || name.startsWith(`${slug}[`));
|
|
8
|
+
}
|
|
9
|
+
/*
|
|
10
|
+
* What a model asks of the CLI that is too old for it, or null when nothing blocks it: no minimum is
|
|
11
|
+
* known, the CLI's version is not a readable x.y.z (so an unknown version invents no requirement), the
|
|
12
|
+
* CLI is new enough, or a chat of this provider already reported running the model.
|
|
13
|
+
*/
|
|
14
|
+
export function modelRequirementOf(model, provider, reported) {
|
|
15
|
+
const minimum = model.minCliVersion === undefined ? null : partsOf(model.minCliVersion, true);
|
|
16
|
+
const installed = provider.version === null ? null : partsOf(provider.version, false);
|
|
17
|
+
if (minimum === null || installed === null || isReported(model.slug, reported)) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
for (let i = 0; i < 3; i++) {
|
|
21
|
+
if (installed[i] !== minimum[i]) {
|
|
22
|
+
return (installed[i] ?? 0) > (minimum[i] ?? 0) ? null : { cli: provider.name, minVersion: model.minCliVersion ?? '' };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { CircleCheck, CircleSlash, CircleX, Hourglass, LoaderCircle } from 'lucide-react';
|
|
2
2
|
/*
|
|
3
|
-
* One state, one mark
|
|
4
|
-
*
|
|
5
|
-
* motion with every other animation (`styles.css`).
|
|
3
|
+
* One state, one mark, wherever work is drawn: a sub-agent entry, a task, a summary or a toast. A
|
|
4
|
+
* spinner stops under reduced motion with every other animation.
|
|
6
5
|
*/
|
|
7
6
|
const LOOKS = {
|
|
8
7
|
running: { icon: LoaderCircle, tone: 'text-status-running', spins: true },
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentKind, ChatAttachmentUpload, ChatAttachResult, ChatBookmark, ChatPreferencesPayload, ChatSkill, ChatVisual, ModelSelection, ProviderInfo, RuntimeMode } from '@adecore/agent-contracts';
|
|
1
|
+
import type { AgentKind, ChatAttachmentUpload, ChatAttachResult, ChatBookmark, ChatPreferencesPayload, ChatQuoteSource, ChatSkill, ChatVisual, ModelSelection, ProviderInfo, RuntimeMode } from '@adecore/agent-contracts';
|
|
2
2
|
import type { ChatSink } from '../state/chats.js';
|
|
3
3
|
import { type ChatTransport } from '../transport.js';
|
|
4
4
|
interface ChatOpenOptions {
|
|
@@ -14,6 +14,7 @@ export interface ChatSendExtras {
|
|
|
14
14
|
mentions?: string[];
|
|
15
15
|
skills?: string[];
|
|
16
16
|
chats?: string[];
|
|
17
|
+
quoteSource?: ChatQuoteSource;
|
|
17
18
|
attachments?: ChatAttachmentUpload[];
|
|
18
19
|
}
|
|
19
20
|
interface ProviderSink {
|
|
@@ -26,9 +27,9 @@ export declare class ChatClient {
|
|
|
26
27
|
private readonly mounted;
|
|
27
28
|
private readonly unsubscribe;
|
|
28
29
|
private readonly earlier;
|
|
30
|
+
private readonly inspections;
|
|
29
31
|
private preferences;
|
|
30
32
|
constructor(transport: ChatTransport, sink: ChatSink, providers?: ProviderSink | null);
|
|
31
|
-
private readonly inspections;
|
|
32
33
|
inspect(chatId: string): Promise<ChatAttachResult>;
|
|
33
34
|
open(chatId: string, options: ChatOpenOptions): Promise<boolean>;
|
|
34
35
|
retarget(chatId: string, provider: AgentKind, selection: ModelSelection): Promise<void>;
|
package/dist/chat/chat-client.js
CHANGED
|
@@ -15,6 +15,7 @@ export class ChatClient {
|
|
|
15
15
|
mounted = new MountedRegistry();
|
|
16
16
|
unsubscribe = [];
|
|
17
17
|
earlier = new Map();
|
|
18
|
+
inspections = new Map();
|
|
18
19
|
preferences = null;
|
|
19
20
|
constructor(transport, sink, providers = null) {
|
|
20
21
|
this.transport = transport;
|
|
@@ -34,7 +35,6 @@ export class ChatClient {
|
|
|
34
35
|
void this.loadStatuses();
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
|
-
inspections = new Map();
|
|
38
38
|
inspect(chatId) {
|
|
39
39
|
const pending = this.inspections.get(chatId);
|
|
40
40
|
if (pending) {
|
|
@@ -113,9 +113,11 @@ export class ChatClient {
|
|
|
113
113
|
return this.transport.request('chat.send', {
|
|
114
114
|
chatId,
|
|
115
115
|
text,
|
|
116
|
+
delivery: extras.delivery,
|
|
116
117
|
mentions: extras.mentions,
|
|
117
118
|
skills: extras.skills,
|
|
118
119
|
chats: extras.chats,
|
|
120
|
+
quoteSource: extras.quoteSource,
|
|
119
121
|
attachments: extras.attachments
|
|
120
122
|
});
|
|
121
123
|
}
|
|
@@ -277,7 +279,7 @@ export class ChatClient {
|
|
|
277
279
|
this.sendPreferences();
|
|
278
280
|
void this.loadProviders();
|
|
279
281
|
void this.loadStatuses();
|
|
280
|
-
void this.mounted.reattachAll((chatId) => this.attach(chatId)
|
|
282
|
+
void this.mounted.reattachAll((chatId) => this.attach(chatId));
|
|
281
283
|
return;
|
|
282
284
|
}
|
|
283
285
|
this.mounted.detachAll();
|
package/dist/chat/drafts.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface ChatDraft {
|
|
|
6
6
|
chats: string[];
|
|
7
7
|
attachments: ChatAttachmentUpload[];
|
|
8
8
|
quote: string;
|
|
9
|
+
quoteSource: string | null;
|
|
9
10
|
}
|
|
10
11
|
export declare const EMPTY_DRAFT: ChatDraft;
|
|
11
12
|
export declare function isEmptyDraft(draft: ChatDraft): boolean;
|
|
@@ -16,7 +17,7 @@ export declare function useHasDraft(chatId: string): boolean;
|
|
|
16
17
|
export declare function readDraft(chatId: string): ChatDraft;
|
|
17
18
|
export declare function writeDraft(chatId: string, draft: ChatDraft): void;
|
|
18
19
|
export declare function joinDraftText(current: string, added: string): string;
|
|
19
|
-
export declare function takeBackIntoDraft(current: ChatDraft, taken: Omit<ChatDraft, 'quote'>): {
|
|
20
|
+
export declare function takeBackIntoDraft(current: ChatDraft, taken: Omit<ChatDraft, 'quote' | 'quoteSource'>): {
|
|
20
21
|
draft: ChatDraft;
|
|
21
22
|
rejected: Array<{
|
|
22
23
|
name: string;
|
package/dist/chat/drafts.js
CHANGED
|
@@ -2,14 +2,8 @@ import { chatStorageKey, chatStorageLegacyKeys, createChatStore } from '../stora
|
|
|
2
2
|
import { checkAttachmentLimits, uploadBytes } from './attachments.js';
|
|
3
3
|
import { persistedJson } from './persisted-json.js';
|
|
4
4
|
const storageKey = () => chatStorageKey('drafts');
|
|
5
|
-
export const EMPTY_DRAFT = { text: '', mentions: [], skills: [], chats: [], attachments: [], quote: '' };
|
|
5
|
+
export const EMPTY_DRAFT = { text: '', mentions: [], skills: [], chats: [], attachments: [], quote: '', quoteSource: null };
|
|
6
6
|
const storage = persistedJson(storageKey, (raw) => (raw ? JSON.parse(raw) : {}), {}, () => chatStorageLegacyKeys('drafts'));
|
|
7
|
-
function readAll() {
|
|
8
|
-
return storage.read();
|
|
9
|
-
}
|
|
10
|
-
function store(drafts) {
|
|
11
|
-
return storage.write(drafts);
|
|
12
|
-
}
|
|
13
7
|
export function isEmptyDraft(draft) {
|
|
14
8
|
return draft.text.trim() === '' && draft.attachments.length === 0 && draft.quote === '';
|
|
15
9
|
}
|
|
@@ -17,7 +11,7 @@ export function isEmptyDraft(draft) {
|
|
|
17
11
|
* Which chats hold an unsent prompt, for a dot beside them in a list of chats. The drafts themselves
|
|
18
12
|
* stay in storage: a node that is not on screen has no composer, and this is all anyone else needs.
|
|
19
13
|
*/
|
|
20
|
-
export const useDrafts = createChatStore(() => ({ ids: [] }), (store) => store.setState({ ids: Object.keys(
|
|
14
|
+
export const useDrafts = createChatStore(() => ({ ids: [] }), (store) => store.setState({ ids: Object.keys(storage.read()) }));
|
|
21
15
|
export function useHasDraft(chatId) {
|
|
22
16
|
return useDrafts((s) => s.ids.includes(chatId));
|
|
23
17
|
}
|
|
@@ -30,7 +24,7 @@ function trackDraft(chatId, held) {
|
|
|
30
24
|
}
|
|
31
25
|
/* An unsent prompt per chat node, kept across reloads so a half-written message is never lost. */
|
|
32
26
|
export function readDraft(chatId) {
|
|
33
|
-
const record =
|
|
27
|
+
const record = storage.read()[chatId];
|
|
34
28
|
return record
|
|
35
29
|
? {
|
|
36
30
|
text: record.text,
|
|
@@ -38,16 +32,17 @@ export function readDraft(chatId) {
|
|
|
38
32
|
skills: record.skills ?? [],
|
|
39
33
|
chats: record.chats ?? [],
|
|
40
34
|
attachments: record.attachments ?? [],
|
|
41
|
-
quote: record.quote ?? ''
|
|
35
|
+
quote: record.quote ?? '',
|
|
36
|
+
quoteSource: record.quoteSource ?? null
|
|
42
37
|
}
|
|
43
38
|
: EMPTY_DRAFT;
|
|
44
39
|
}
|
|
45
40
|
export function writeDraft(chatId, draft) {
|
|
46
|
-
const drafts =
|
|
41
|
+
const drafts = storage.read();
|
|
47
42
|
trackDraft(chatId, !isEmptyDraft(draft));
|
|
48
43
|
if (isEmptyDraft(draft)) {
|
|
49
44
|
delete drafts[chatId];
|
|
50
|
-
|
|
45
|
+
storage.write(drafts);
|
|
51
46
|
return;
|
|
52
47
|
}
|
|
53
48
|
drafts[chatId] = {
|
|
@@ -56,14 +51,22 @@ export function writeDraft(chatId, draft) {
|
|
|
56
51
|
skills: draft.skills,
|
|
57
52
|
chats: draft.chats,
|
|
58
53
|
attachments: draft.attachments,
|
|
59
|
-
quote: draft.quote
|
|
54
|
+
quote: draft.quote,
|
|
55
|
+
...(draft.quoteSource === null ? {} : { quoteSource: draft.quoteSource })
|
|
60
56
|
};
|
|
61
|
-
if (
|
|
57
|
+
if (storage.write(drafts)) {
|
|
62
58
|
return;
|
|
63
59
|
}
|
|
64
60
|
// A file can outgrow the storage quota; the text is the part worth keeping then.
|
|
65
|
-
drafts[chatId] = {
|
|
66
|
-
|
|
61
|
+
drafts[chatId] = {
|
|
62
|
+
text: draft.text,
|
|
63
|
+
mentions: draft.mentions,
|
|
64
|
+
skills: draft.skills,
|
|
65
|
+
chats: draft.chats,
|
|
66
|
+
quote: draft.quote,
|
|
67
|
+
...(draft.quoteSource === null ? {} : { quoteSource: draft.quoteSource })
|
|
68
|
+
};
|
|
69
|
+
storage.write(drafts);
|
|
67
70
|
}
|
|
68
71
|
/* Text handed to a draft from outside the composer goes under what was already typed, never over it. */
|
|
69
72
|
export function joinDraftText(current, added) {
|
|
@@ -86,7 +89,8 @@ export function takeBackIntoDraft(current, taken) {
|
|
|
86
89
|
skills: union(taken.skills, current.skills),
|
|
87
90
|
chats: union(taken.chats, current.chats),
|
|
88
91
|
attachments: [...checked.accepted.map((entry) => entry.upload), ...current.attachments],
|
|
89
|
-
quote: current.quote
|
|
92
|
+
quote: current.quote,
|
|
93
|
+
quoteSource: current.quoteSource
|
|
90
94
|
},
|
|
91
95
|
rejected: checked.rejected
|
|
92
96
|
};
|
package/dist/chat/logic/ansi.js
CHANGED
|
@@ -144,7 +144,7 @@ function styleOf(state) {
|
|
|
144
144
|
}
|
|
145
145
|
return style;
|
|
146
146
|
}
|
|
147
|
-
|
|
147
|
+
function styledTokens(text) {
|
|
148
148
|
const tokens = [];
|
|
149
149
|
let state = {};
|
|
150
150
|
let position = 0;
|
|
@@ -160,6 +160,10 @@ export function parseAnsi(text, options = {}) {
|
|
|
160
160
|
if (position < text.length) {
|
|
161
161
|
tokens.push({ content: text.slice(position), style: styleOf(state) });
|
|
162
162
|
}
|
|
163
|
+
return tokens;
|
|
164
|
+
}
|
|
165
|
+
export function parseAnsi(text, options = {}) {
|
|
166
|
+
const tokens = styledTokens(text);
|
|
163
167
|
const plain = tokens.map((token) => token.content).join('');
|
|
164
168
|
let start = 0;
|
|
165
169
|
let end = plain.length;
|
package/dist/chat/logic/fork.js
CHANGED
|
@@ -121,20 +121,19 @@ export function forkShapes(origin) {
|
|
|
121
121
|
return origin === 'view' ? ['view'] : ['node', 'view'];
|
|
122
122
|
}
|
|
123
123
|
export function forkPayload(input) {
|
|
124
|
+
const { cli, worktree } = input;
|
|
125
|
+
const provider = cli !== undefined && cli.chosen.provider !== cli.original.provider ? cli.chosen.provider : null;
|
|
126
|
+
const selection = cli !== undefined && (provider !== null || cli.chosen.selection.model !== cli.original.selection.model) ? cli.chosen.selection : null;
|
|
127
|
+
const account = cli?.chosen.account !== undefined && (provider !== null || cli.chosen.account !== cli.original.account) ? cli.chosen.account : null;
|
|
124
128
|
return {
|
|
125
129
|
chatId: input.chatId,
|
|
126
130
|
turnId: input.turnId,
|
|
127
131
|
title: input.title,
|
|
128
132
|
...(input.shape === 'view' ? { asView: true } : {}),
|
|
129
|
-
...(
|
|
130
|
-
...(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
...(input.cli?.chosen.account !== undefined &&
|
|
134
|
-
(input.cli.chosen.provider !== input.cli.original.provider || input.cli.chosen.account !== input.cli.original.account)
|
|
135
|
-
? { account: input.cli.chosen.account }
|
|
136
|
-
: {}),
|
|
137
|
-
...(input.worktree ? { worktree: { branch: input.worktree.branch }, ...(input.worktree.filesAfterTurn ? { filesAfterTurn: true } : {}) } : {})
|
|
133
|
+
...(provider === null ? {} : { provider }),
|
|
134
|
+
...(selection === null ? {} : { selection }),
|
|
135
|
+
...(account === null ? {} : { account }),
|
|
136
|
+
...(worktree ? { worktree: { branch: worktree.branch }, ...(worktree.filesAfterTurn ? { filesAfterTurn: true } : {}) } : {})
|
|
138
137
|
};
|
|
139
138
|
}
|
|
140
139
|
/* Why a branch cannot be the fork's, or null when it can. */
|
package/dist/chat/logic/limit.js
CHANGED
|
@@ -17,18 +17,21 @@ export function limitView(info, now) {
|
|
|
17
17
|
detail: resumes === null ? i18next.t('agent-chat:limit.overload.sendAgain') : i18next.t('agent-chat:limit.overload.retries', { time: resumes })
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
const title = i18next.t('agent-chat:limit.usage.title');
|
|
21
|
+
if (resumes !== null) {
|
|
22
|
+
return {
|
|
23
|
+
pill: i18next.t('agent-chat:limit.usage.pillResumes', { time: resumes }),
|
|
24
|
+
title,
|
|
25
|
+
detail: i18next.t('agent-chat:limit.usage.resumes', { time: resumes })
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (limit.resetsAt !== undefined) {
|
|
29
|
+
const resets = formatMoment(limit.resetsAt, now);
|
|
30
|
+
return {
|
|
31
|
+
pill: i18next.t('agent-chat:limit.usage.pillUntil', { time: resets }),
|
|
32
|
+
title,
|
|
33
|
+
detail: i18next.t('agent-chat:limit.usage.resets', { time: resets })
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return { pill: i18next.t('agent-chat:limit.usage.pill'), title, detail: null };
|
|
34
37
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ModelInfo, ProviderInfo } from '@adecore/agent-contracts';
|
|
2
|
+
export type PickerEntry = {
|
|
3
|
+
kind: 'model';
|
|
4
|
+
provider: ProviderInfo;
|
|
5
|
+
model: ModelInfo;
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'legacy';
|
|
8
|
+
count: number;
|
|
9
|
+
};
|
|
10
|
+
export declare function pickerEntriesOf(providers: readonly ProviderInfo[], query: string, legacyOpen: boolean): PickerEntry[];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
function matches(model, query) {
|
|
2
|
+
return model.name.toLowerCase().includes(query) || model.slug.toLowerCase().includes(query);
|
|
3
|
+
}
|
|
4
|
+
/*
|
|
5
|
+
* The rows of the model list. A model an old CLI cannot run is a row like any other, so a search for
|
|
6
|
+
* only that model still finds the row that explains why it cannot be picked.
|
|
7
|
+
*/
|
|
8
|
+
export function pickerEntriesOf(providers, query, legacyOpen) {
|
|
9
|
+
const rows = providers.flatMap((entry) => entry.models.map((model) => ({ kind: 'model', provider: entry, model })));
|
|
10
|
+
if (query !== '') {
|
|
11
|
+
return rows.filter((row) => matches(row.model, query));
|
|
12
|
+
}
|
|
13
|
+
const live = rows.filter((row) => !row.model.legacy);
|
|
14
|
+
const legacy = rows.filter((row) => row.model.legacy);
|
|
15
|
+
if (legacy.length === 0) {
|
|
16
|
+
return live;
|
|
17
|
+
}
|
|
18
|
+
return legacyOpen ? [...live, { kind: 'legacy', count: legacy.length }, ...legacy] : [...live, { kind: 'legacy', count: legacy.length }];
|
|
19
|
+
}
|
|
@@ -6,18 +6,9 @@ export const RESUME_COMPACTION_TOKENS = 100_000;
|
|
|
6
6
|
/* The longest prompt cache Claude keeps is an hour, so past this the whole thread is read again anyway. */
|
|
7
7
|
export const RESUME_COMPACTION_IDLE_MS = 70 * 60_000;
|
|
8
8
|
function lastTurn(items) {
|
|
9
|
-
|
|
10
|
-
const item = items[i];
|
|
11
|
-
if (item.kind === 'turn') {
|
|
12
|
-
return item;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
return null;
|
|
9
|
+
return items.findLast((item) => item.kind === 'turn') ?? null;
|
|
16
10
|
}
|
|
17
|
-
/*
|
|
18
|
-
* Whether to offer compacting before the next message. The caller passes `now`, so this stays pure
|
|
19
|
-
* and the test needs no clock.
|
|
20
|
-
*/
|
|
11
|
+
/* Whether to offer compacting before the next message. */
|
|
21
12
|
export function resumeCompactionOffer({ info, compaction, items, dismissedTurnId, now }) {
|
|
22
13
|
if (compaction === undefined || compaction === 'none') {
|
|
23
14
|
return null;
|
|
@@ -76,20 +76,7 @@ export function tickOfRow(ticks, rowIndex) {
|
|
|
76
76
|
if (ticks.length === 0) {
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
79
|
-
|
|
80
|
-
let high = ticks.length - 1;
|
|
81
|
-
let found = 0;
|
|
82
|
-
while (low <= high) {
|
|
83
|
-
const middle = (low + high) >> 1;
|
|
84
|
-
if (ticks[middle].rowIndex <= rowIndex) {
|
|
85
|
-
found = middle;
|
|
86
|
-
low = middle + 1;
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
high = middle - 1;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return found;
|
|
79
|
+
return Math.max(0, lastIndexAtOrBelow(ticks.length, (index) => ticks[index].rowIndex, rowIndex));
|
|
93
80
|
}
|
|
94
81
|
/* Per tick, whether any of the rows has a find hit in its stretch. */
|
|
95
82
|
export function ticksWithHits(ticks, hitRows) {
|
|
@@ -146,14 +133,14 @@ export function tickWidth(kind, distanceFromPointer) {
|
|
|
146
133
|
}
|
|
147
134
|
return kind === 'wake' ? WIDTH_WAKE_PX : WIDTH_PERSON_PX;
|
|
148
135
|
}
|
|
149
|
-
/* The last index whose
|
|
150
|
-
function
|
|
136
|
+
/* The last index whose value is at most `limit`, or -1. The values only grow. */
|
|
137
|
+
function lastIndexAtOrBelow(length, valueAt, limit) {
|
|
151
138
|
let low = 0;
|
|
152
|
-
let high =
|
|
139
|
+
let high = length - 1;
|
|
153
140
|
let found = -1;
|
|
154
141
|
while (low <= high) {
|
|
155
142
|
const middle = (low + high) >> 1;
|
|
156
|
-
if (
|
|
143
|
+
if (valueAt(middle) <= limit) {
|
|
157
144
|
found = middle;
|
|
158
145
|
low = middle + 1;
|
|
159
146
|
}
|
|
@@ -163,6 +150,10 @@ function lastAtOrAbove(starts, line) {
|
|
|
163
150
|
}
|
|
164
151
|
return found;
|
|
165
152
|
}
|
|
153
|
+
/* The last index whose start lies at or above a line, or -1. */
|
|
154
|
+
function lastAtOrAbove(starts, line) {
|
|
155
|
+
return lastIndexAtOrBelow(starts.length, (index) => starts[index], line);
|
|
156
|
+
}
|
|
166
157
|
/*
|
|
167
158
|
* The messages on screen. A message counts from its own start to the start of the next one, so the
|
|
168
159
|
* question whose answer fills the screen is in view even after it scrolled out at the top.
|
|
@@ -74,6 +74,7 @@ export type TimelineRow = {
|
|
|
74
74
|
tools: ChatToolItem[];
|
|
75
75
|
diff: ChatCheckpointDiff | null;
|
|
76
76
|
checkpoint: boolean;
|
|
77
|
+
baseMoved?: BaseMoved;
|
|
77
78
|
} | {
|
|
78
79
|
kind: 'turn-fold';
|
|
79
80
|
id: string;
|
|
@@ -115,5 +116,9 @@ export declare function findSubagentBranch(rows: readonly TimelineRow[], toolUse
|
|
|
115
116
|
index: number;
|
|
116
117
|
branch: SubagentBranch;
|
|
117
118
|
} | null;
|
|
119
|
+
export interface BaseMoved {
|
|
120
|
+
from: string;
|
|
121
|
+
to: string;
|
|
122
|
+
}
|
|
118
123
|
export declare function deriveTimelineRows(items: ChatItem[], options: TimelineOptions): TimelineRow[];
|
|
119
124
|
export {};
|
|
@@ -21,10 +21,9 @@ export function summarizeGroup(tools) {
|
|
|
21
21
|
if (only !== null && toolEntry(only)?.grouped === true) {
|
|
22
22
|
return i18next.t(`agent-chat:group.tools.${only}`, { count: tools.length });
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
files rather than the calls: three edits to one file are one file edited. */
|
|
24
|
+
// Counts the files rather than the calls: three edits to one file are one file edited.
|
|
26
25
|
if (tools.every((tool) => isFileChange(tool.name))) {
|
|
27
|
-
const paths = new Set(tools.map(
|
|
26
|
+
const paths = new Set(tools.map(editedPath));
|
|
28
27
|
return i18next.t('agent-chat:group.edited', { count: paths.size });
|
|
29
28
|
}
|
|
30
29
|
if (only !== null) {
|
|
@@ -32,6 +31,9 @@ export function summarizeGroup(tools) {
|
|
|
32
31
|
}
|
|
33
32
|
return i18next.t('agent-chat:group.toolCalls', { count: tools.length });
|
|
34
33
|
}
|
|
34
|
+
function editedPath(tool) {
|
|
35
|
+
return tool.input?.file_path ?? tool.id;
|
|
36
|
+
}
|
|
35
37
|
/*
|
|
36
38
|
* What a folded turn did, a sentence per kind of call in the order it first came: "Read 11 files",
|
|
37
39
|
* "Searched 2 patterns". Every file change reads as one sentence, and calls without a sentence of
|
|
@@ -44,7 +46,7 @@ export function summarizeTurn(tools) {
|
|
|
44
46
|
let other = 0;
|
|
45
47
|
for (const tool of tools) {
|
|
46
48
|
if (isFileChange(tool.name)) {
|
|
47
|
-
edited.add(tool
|
|
49
|
+
edited.add(editedPath(tool));
|
|
48
50
|
counts.set('agent-chat:group.edited', edited.size);
|
|
49
51
|
}
|
|
50
52
|
else if (toolEntry(tool.name)?.grouped === true) {
|
|
@@ -251,7 +253,16 @@ function changedFilesRow(turn, items) {
|
|
|
251
253
|
if (empty) {
|
|
252
254
|
return null;
|
|
253
255
|
}
|
|
254
|
-
|
|
256
|
+
const moved = turn.checkpointHead !== undefined && turn.checkpointHeadAfter !== undefined && turn.checkpointHead !== turn.checkpointHeadAfter;
|
|
257
|
+
return {
|
|
258
|
+
kind: 'changed-files',
|
|
259
|
+
id: `files-${turn.id}`,
|
|
260
|
+
turnId: turn.id,
|
|
261
|
+
tools: edits,
|
|
262
|
+
diff,
|
|
263
|
+
checkpoint: turn.checkpoint !== undefined,
|
|
264
|
+
...(moved ? { baseMoved: { from: turn.checkpointHead, to: turn.checkpointHeadAfter } } : {})
|
|
265
|
+
};
|
|
255
266
|
}
|
|
256
267
|
function lastAssistantRow(rows) {
|
|
257
268
|
for (let i = rows.length - 1; i >= 0; i--) {
|
|
@@ -265,11 +276,8 @@ function lastAssistantRow(rows) {
|
|
|
265
276
|
}
|
|
266
277
|
return null;
|
|
267
278
|
}
|
|
268
|
-
/*
|
|
269
|
-
|
|
270
|
-
const rows = [];
|
|
271
|
-
const children = groupChildren(items);
|
|
272
|
-
// Items are grouped by turn, in order of first appearance; runs of turnless items keep their place.
|
|
279
|
+
/* Items grouped by turn, in order of first appearance; runs of turnless items keep their place. */
|
|
280
|
+
function chunksByTurn(items) {
|
|
273
281
|
const chunks = [];
|
|
274
282
|
const byTurn = new Map();
|
|
275
283
|
for (const item of items) {
|
|
@@ -291,7 +299,13 @@ export function deriveTimelineRows(items, options) {
|
|
|
291
299
|
}
|
|
292
300
|
bucket.push(item);
|
|
293
301
|
}
|
|
294
|
-
|
|
302
|
+
return chunks;
|
|
303
|
+
}
|
|
304
|
+
/* The whole thread as rows; items without a turn (older records) are shown as they are. */
|
|
305
|
+
export function deriveTimelineRows(items, options) {
|
|
306
|
+
const rows = [];
|
|
307
|
+
const children = groupChildren(items);
|
|
308
|
+
for (const chunk of chunksByTurn(items)) {
|
|
295
309
|
if (chunk.turnId === null) {
|
|
296
310
|
rows.push(...rowsForItems(chunk.items, options, children));
|
|
297
311
|
continue;
|
|
@@ -10,10 +10,9 @@ function entry(icon, summary, rest = {}) {
|
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
12
|
/*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* wrench and to the first string of its input.
|
|
13
|
+
* Every tool the thread has a word for, in one table so a new name is never missing from a lookup. A
|
|
14
|
+
* name that is not here still draws, reads and groups; it falls back to the wrench and to the first
|
|
15
|
+
* string of its input.
|
|
17
16
|
*/
|
|
18
17
|
export const TOOL_CATALOG = {
|
|
19
18
|
Read: entry(Eye, ['file_path'], { readsImage: true }),
|
package/dist/chat/mentions.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
/*
|
|
2
|
-
|
|
1
|
+
/*
|
|
2
|
+
* What a file dragged out of an app's own list of files carries: the paths, space separated, relative
|
|
3
|
+
* to the folder. The composer takes it next to the image files it already accepts.
|
|
4
|
+
*/
|
|
3
5
|
export const MENTION_DRAG_TYPE = 'application/x-adecore-mention';
|
|
4
6
|
export const LEGACY_MENTION_DRAG_TYPE = 'application/x-ruimte-mention';
|
|
5
7
|
export const MENTION_DRAG_TYPES = [MENTION_DRAG_TYPE, LEGACY_MENTION_DRAG_TYPE];
|
|
@@ -110,10 +112,7 @@ export function presentMentions(text, chosen) {
|
|
|
110
112
|
export function presentSkills(text, chosen) {
|
|
111
113
|
return present(text, '$', chosen);
|
|
112
114
|
}
|
|
113
|
-
/*
|
|
114
|
-
* The characters a segment stands for, sigil included. Joined, the segments spell the text they
|
|
115
|
-
* were cut from.
|
|
116
|
-
*/
|
|
115
|
+
/* The characters a segment stands for, sigil included; joined, the segments spell the text they were cut from. */
|
|
117
116
|
export function chipText(segment) {
|
|
118
117
|
if (segment.kind === 'mention') {
|
|
119
118
|
return `@${segment.path}`;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const PROMPT_HOLD_PAUSE_MS = 2000;
|
|
2
|
+
export interface HoldInput {
|
|
3
|
+
typedAt: number | null;
|
|
4
|
+
composing: boolean;
|
|
5
|
+
}
|
|
6
|
+
export type HoldRelease = 'pause' | 'send' | 'blur' | 'show';
|
|
7
|
+
export declare function holdsPrompt(input: HoldInput, now: number): boolean;
|
|
8
|
+
export declare function holdLiftsIn(input: HoldInput, now: number): number | null;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/* How long a person must stop typing before a waiting prompt card takes the composer's place. */
|
|
2
|
+
export const PROMPT_HOLD_PAUSE_MS = 2000;
|
|
3
|
+
/* A card waits while the person is composing, or typed less than the pause ago. */
|
|
4
|
+
export function holdsPrompt(input, now) {
|
|
5
|
+
if (input.composing) {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
return input.typedAt !== null && now - input.typedAt < PROMPT_HOLD_PAUSE_MS;
|
|
9
|
+
}
|
|
10
|
+
/* The milliseconds until the hold lifts by itself; null when it holds until something else lifts it, or does not hold. */
|
|
11
|
+
export function holdLiftsIn(input, now) {
|
|
12
|
+
if (input.composing || input.typedAt === null) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
return Math.max(0, input.typedAt + PROMPT_HOLD_PAUSE_MS - now);
|
|
16
|
+
}
|
package/dist/chat/quote.d.ts
CHANGED
package/dist/chat/quote.js
CHANGED
|
@@ -23,3 +23,22 @@ export function withQuote(quote, message) {
|
|
|
23
23
|
.join('\n');
|
|
24
24
|
return message === '' ? block : `${block}\n\n${message}`;
|
|
25
25
|
}
|
|
26
|
+
/*
|
|
27
|
+
* The inverse of `withQuote`: the quote a message opens with and what follows it. Null when the text
|
|
28
|
+
* does not open with a blockquote that ends on a blank line or the end of the text.
|
|
29
|
+
*/
|
|
30
|
+
export function splitQuote(text) {
|
|
31
|
+
const lines = text.split('\n');
|
|
32
|
+
let end = 0;
|
|
33
|
+
while (end < lines.length && (lines[end] === '>' || lines[end].startsWith('> '))) {
|
|
34
|
+
end += 1;
|
|
35
|
+
}
|
|
36
|
+
if (end === 0 || (end < lines.length && lines[end] !== '')) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const quote = lines
|
|
40
|
+
.slice(0, end)
|
|
41
|
+
.map((line) => line.slice(2))
|
|
42
|
+
.join('\n');
|
|
43
|
+
return { quote, rest: lines.slice(end + 1).join('\n') };
|
|
44
|
+
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import i18next from 'i18next';
|
|
2
2
|
import { RuntimeModeSchema } from '@adecore/agent-contracts';
|
|
3
3
|
/*
|
|
4
|
-
* The runtime modes in the order they are offered
|
|
5
|
-
*
|
|
6
|
-
* rank from narrowest to widest. Only the ids live here: the words are in `modes.<id>` of the `chat`
|
|
7
|
-
* namespace, read when a surface draws one rather than when this module loads.
|
|
4
|
+
* The runtime modes in the order they are offered: the schema's, which the host reads as the rank
|
|
5
|
+
* from narrowest to widest. Their words are read when a surface draws one, not when this module loads.
|
|
8
6
|
*/
|
|
9
7
|
export const RUNTIME_MODES = RuntimeModeSchema.options;
|
|
10
8
|
export function runtimeModeLabel(mode) {
|
package/dist/chat/stash.d.ts
CHANGED
|
@@ -12,13 +12,19 @@ export interface StashedPrompt {
|
|
|
12
12
|
mentions: string[];
|
|
13
13
|
skills: string[];
|
|
14
14
|
attachments: StashedAttachment[];
|
|
15
|
+
quote?: StashedQuote;
|
|
15
16
|
createdAt: number;
|
|
16
17
|
}
|
|
18
|
+
export interface StashedQuote {
|
|
19
|
+
text: string;
|
|
20
|
+
chatId: string;
|
|
21
|
+
itemId: string;
|
|
22
|
+
}
|
|
17
23
|
export declare function parseStash(raw: string | null): StashedPrompt[];
|
|
18
24
|
export declare function withStashed(prompts: StashedPrompt[], entry: StashedPrompt): StashedPrompt[];
|
|
19
|
-
export declare function stashedFrom(draft: ChatDraft, id: string, now: number): StashedPrompt | null;
|
|
25
|
+
export declare function stashedFrom(draft: ChatDraft, id: string, now: number, chatId: string): StashedPrompt | null;
|
|
20
26
|
export declare const useStash: import("zustand").UseBoundStore<import("zustand").StoreApi<{
|
|
21
27
|
prompts: StashedPrompt[];
|
|
22
28
|
}>>;
|
|
23
|
-
export declare function stashDraft(draft: ChatDraft): boolean;
|
|
29
|
+
export declare function stashDraft(draft: ChatDraft, chatId: string): boolean;
|
|
24
30
|
export declare function forgetStashed(id: string): void;
|