@adecore/agents-react 0.24.1 → 0.25.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.
Files changed (113) hide show
  1. package/dist/chat/index.d.ts +4 -7
  2. package/dist/chat/index.js +4 -7
  3. package/dist/chat/logic/continue-after-stop.d.ts +3 -0
  4. package/dist/chat/logic/continue-after-stop.js +28 -0
  5. package/dist/chat/logic/thread-cards.d.ts +0 -1
  6. package/dist/chat/logic/thread-cards.js +0 -4
  7. package/dist/chat/preferences.d.ts +0 -1
  8. package/dist/chat/preferences.js +0 -3
  9. package/dist/chat/stash.d.ts +6 -0
  10. package/dist/chat/stash.js +13 -0
  11. package/dist/chat/ui/Composer.js +26 -5
  12. package/dist/chat/ui/EditDiff.js +4 -3
  13. package/dist/chat/ui/Pickers.js +1 -1
  14. package/dist/chat/ui/UnifiedDiff.js +4 -3
  15. package/dist/chat/ui/composer/ComposerControls.d.ts +1 -0
  16. package/dist/chat/ui/composer/ComposerControls.js +3 -3
  17. package/dist/chat/ui/composer/use-composer-submit.d.ts +3 -1
  18. package/dist/chat/ui/composer/use-composer-submit.js +22 -4
  19. package/dist/chat/ui/composer/use-continuable-turn.d.ts +2 -0
  20. package/dist/chat/ui/composer/use-continuable-turn.js +12 -0
  21. package/dist/chat/ui/composer/use-notice.d.ts +3 -1
  22. package/dist/chat/ui/composer/use-notice.js +8 -6
  23. package/dist/chat/ui/diff/ReviewDiff.js +2 -2
  24. package/dist/chat/ui/index.d.ts +9 -28
  25. package/dist/chat/ui/index.js +8 -27
  26. package/dist/chat/ui/intelligent-ui/UiReply.js +20 -2
  27. package/dist/chat/ui/intelligent-ui/node-text.d.ts +3 -1
  28. package/dist/chat/ui/intelligent-ui/node-text.js +32 -0
  29. package/dist/chat/ui/intelligent-ui/registry.js +15 -5
  30. package/dist/chat/ui/intelligent-ui/render-context.d.ts +6 -0
  31. package/dist/chat/ui/intelligent-ui/renderers/base-land.d.ts +1 -0
  32. package/dist/chat/ui/intelligent-ui/renderers/base-land.js +6 -0
  33. package/dist/chat/ui/intelligent-ui/renderers/binding.d.ts +6 -0
  34. package/dist/chat/ui/intelligent-ui/renderers/binding.js +10 -0
  35. package/dist/chat/ui/intelligent-ui/renderers/content.d.ts +2 -0
  36. package/dist/chat/ui/intelligent-ui/renderers/content.js +68 -6
  37. package/dist/chat/ui/intelligent-ui/renderers/inputs.d.ts +2 -0
  38. package/dist/chat/ui/intelligent-ui/renderers/inputs.js +40 -13
  39. package/dist/chat/ui/intelligent-ui/renderers/map-slots.d.ts +20 -0
  40. package/dist/chat/ui/intelligent-ui/renderers/map-slots.js +87 -0
  41. package/dist/chat/ui/intelligent-ui/renderers/map-style.d.ts +18 -0
  42. package/dist/chat/ui/intelligent-ui/renderers/map-style.js +72 -0
  43. package/dist/chat/ui/intelligent-ui/renderers/map-view.d.ts +15 -0
  44. package/dist/chat/ui/intelligent-ui/renderers/map-view.js +213 -0
  45. package/dist/chat/ui/intelligent-ui/renderers/map.d.ts +3 -0
  46. package/dist/chat/ui/intelligent-ui/renderers/map.js +113 -0
  47. package/dist/chat/ui/intelligent-ui/renderers/place-point.d.ts +3 -0
  48. package/dist/chat/ui/intelligent-ui/renderers/place-point.js +13 -0
  49. package/dist/chat/ui/intelligent-ui/renderers/text.d.ts +2 -0
  50. package/dist/chat/ui/intelligent-ui/renderers/text.js +14 -1
  51. package/dist/chat/ui/intelligent-ui/use-in-view.d.ts +1 -0
  52. package/dist/chat/ui/intelligent-ui/use-in-view.js +17 -0
  53. package/dist/chat/ui/rows/WorkRows.js +3 -3
  54. package/dist/host.d.ts +16 -0
  55. package/dist/index.d.ts +2 -2
  56. package/dist/index.js +1 -1
  57. package/dist/lazy.js +5 -7
  58. package/dist/locales/en/agent-chat.d.ts +47 -0
  59. package/dist/locales/en/agent-chat.js +48 -1
  60. package/dist/locales/nl/agent-chat.d.ts +47 -0
  61. package/dist/locales/nl/agent-chat.js +48 -1
  62. package/dist/prompts/index.d.ts +3 -5
  63. package/dist/prompts/index.js +3 -5
  64. package/dist/providers/index.d.ts +3 -9
  65. package/dist/providers/index.js +3 -9
  66. package/dist/state/index.d.ts +2 -2
  67. package/dist/state/index.js +2 -2
  68. package/dist/state/usage.d.ts +0 -1
  69. package/dist/state/usage.js +0 -3
  70. package/dist/theme.css +10 -0
  71. package/package.json +9 -8
  72. package/src/chat/index.ts +2 -23
  73. package/src/chat/logic/continue-after-stop.ts +32 -0
  74. package/src/chat/logic/thread-cards.ts +0 -5
  75. package/src/chat/preferences.ts +0 -4
  76. package/src/chat/stash.ts +17 -0
  77. package/src/chat/ui/Composer.tsx +33 -5
  78. package/src/chat/ui/EditDiff.tsx +6 -5
  79. package/src/chat/ui/Pickers.tsx +1 -0
  80. package/src/chat/ui/UnifiedDiff.tsx +6 -5
  81. package/src/chat/ui/composer/ComposerControls.tsx +13 -3
  82. package/src/chat/ui/composer/use-composer-submit.ts +25 -5
  83. package/src/chat/ui/composer/use-continuable-turn.ts +14 -0
  84. package/src/chat/ui/composer/use-notice.ts +14 -7
  85. package/src/chat/ui/diff/ReviewDiff.tsx +3 -3
  86. package/src/chat/ui/index.ts +9 -49
  87. package/src/chat/ui/intelligent-ui/UiReply.tsx +24 -1
  88. package/src/chat/ui/intelligent-ui/node-text.ts +34 -1
  89. package/src/chat/ui/intelligent-ui/registry.ts +33 -5
  90. package/src/chat/ui/intelligent-ui/render-context.ts +14 -0
  91. package/src/chat/ui/intelligent-ui/renderers/base-land.ts +7 -0
  92. package/src/chat/ui/intelligent-ui/renderers/binding.ts +14 -0
  93. package/src/chat/ui/intelligent-ui/renderers/content.tsx +144 -9
  94. package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +79 -17
  95. package/src/chat/ui/intelligent-ui/renderers/map-slots.ts +105 -0
  96. package/src/chat/ui/intelligent-ui/renderers/map-style.ts +96 -0
  97. package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +251 -0
  98. package/src/chat/ui/intelligent-ui/renderers/map.tsx +226 -0
  99. package/src/chat/ui/intelligent-ui/renderers/place-point.ts +16 -0
  100. package/src/chat/ui/intelligent-ui/renderers/text.tsx +28 -1
  101. package/src/chat/ui/intelligent-ui/use-in-view.ts +18 -0
  102. package/src/chat/ui/rows/WorkRows.tsx +3 -3
  103. package/src/host.ts +28 -0
  104. package/src/index.ts +2 -12
  105. package/src/lazy.tsx +5 -7
  106. package/src/locales/en/agent-chat.ts +48 -1
  107. package/src/locales/nl/agent-chat.ts +48 -1
  108. package/src/prompts/index.ts +3 -18
  109. package/src/providers/index.ts +3 -9
  110. package/src/state/index.ts +1 -16
  111. package/src/state/usage.ts +0 -4
  112. package/src/stylesheets.d.ts +2 -0
  113. package/src/theme.css +10 -0
@@ -1,11 +1,9 @@
1
- export { focusPromptStart, focusPromptHeading } from './logic/focus.js';
1
+ export { focusPromptStart } from './logic/focus.js';
2
2
  export { PROMPT_SHORTCUTS, pageKey, type PromptKeyEvent } from './logic/keys.js';
3
- export { type PendingPrompt, type PromptAction, emptyPromptDraft, answerValue, questionAnswer, pickPromptChoice, isBlockingPrompt, orderPrompts, nextPrompt, promptAnswers, type PromptAnswer, type PromptDraft } from './logic/prompts.js';
3
+ export { type PendingPrompt, type PromptAction, emptyPromptDraft, orderPrompts, type PromptAnswer, type PromptDraft } from './logic/prompts.js';
4
4
  export { type HostPrompt, type PromptSubject, type ChatPromptClients, type PromptViewProps, type ApprovalButtonSpec, promptIdOf, promptCreatedAt, isBlockingSubject, answerPrompt } from './logic/subjects.js';
5
5
  export { useFocusAfterAnswer, type FocusAfterAnswer } from './logic/useFocusAfterAnswer.js';
6
6
  export { usePromptSession, type PromptSession } from './logic/usePromptSession.js';
7
- export { CommandBox, ApprovalBody } from './ui/ApprovalBody.js';
8
- export { PromptPrimary, ApprovalActions, QuestionActions, type ApprovalButton } from './ui/PromptActions.js';
7
+ export { PromptPrimary, ApprovalActions, type ApprovalButton } from './ui/PromptActions.js';
9
8
  export { PROMPT_SURFACE, PromptCard, type PromptCardKind } from './ui/PromptCard.js';
10
9
  export { PromptView } from './ui/PromptView.js';
11
- export { QuestionBody } from './ui/QuestionBody.js';
@@ -1,12 +1,10 @@
1
1
  // Public API: exports.test.ts snapshots this list, so a name added or dropped here is an API change.
2
- export { focusPromptStart, focusPromptHeading } from './logic/focus.js';
2
+ export { focusPromptStart } from './logic/focus.js';
3
3
  export { PROMPT_SHORTCUTS, pageKey } from './logic/keys.js';
4
- export { emptyPromptDraft, answerValue, questionAnswer, pickPromptChoice, isBlockingPrompt, orderPrompts, nextPrompt, promptAnswers } from './logic/prompts.js';
4
+ export { emptyPromptDraft, orderPrompts } from './logic/prompts.js';
5
5
  export { promptIdOf, promptCreatedAt, isBlockingSubject, answerPrompt } from './logic/subjects.js';
6
6
  export { useFocusAfterAnswer } from './logic/useFocusAfterAnswer.js';
7
7
  export { usePromptSession } from './logic/usePromptSession.js';
8
- export { CommandBox, ApprovalBody } from './ui/ApprovalBody.js';
9
- export { PromptPrimary, ApprovalActions, QuestionActions } from './ui/PromptActions.js';
8
+ export { PromptPrimary, ApprovalActions } from './ui/PromptActions.js';
10
9
  export { PROMPT_SURFACE, PromptCard } from './ui/PromptCard.js';
11
10
  export { PromptView } from './ui/PromptView.js';
12
- export { QuestionBody } from './ui/QuestionBody.js';
@@ -2,15 +2,9 @@ export { AccountDot } from '../agents/AccountDot.js';
2
2
  export { AgentIcon } from '../agents/AgentIcon.js';
3
3
  export { ModelOptionControl } from '../agents/ModelOptionControl.js';
4
4
  export { ProviderLogo } from '../agents/ProviderLogo.js';
5
- export { type AccountEntry, accountsOfKind, accountName, ACCOUNT_TONE_CLASSES, accountStatusLine, canContinueOn, type AccountTone } from '../agents/accounts.js';
5
+ export { type AccountEntry, accountsOfKind, ACCOUNT_TONE_CLASSES, accountStatusLine, canContinueOn, type AccountTone } from '../agents/accounts.js';
6
6
  export { modelName, useModelName, shortModelName } from '../agents/model-name.js';
7
- export { optionValue, carryOptions } from '../agents/model-options.js';
8
- export { modelRequirementOf, type ModelRequirement } from '../agents/model-requirement.js';
9
- export { AccountColors } from './AccountColors.js';
10
- export { AccountDetail } from './AccountDetail.js';
11
- export { AccountVariables } from './AccountVariables.js';
12
- export { AddAccountForm } from './AddAccountForm.js';
13
- export { CliDetail } from './CliDetail.js';
7
+ export { carryOptions } from '../agents/model-options.js';
8
+ export { type ModelRequirement } from '../agents/model-requirement.js';
14
9
  export { ProvidersPane } from './ProvidersPane.js';
15
- export { saveAccounts, saveAccount, removeAccount, createAccount, linkAccount } from './account-actions.js';
16
10
  export { CliMark, CliTile } from './parts.js';
@@ -3,15 +3,9 @@ export { AccountDot } from '../agents/AccountDot.js';
3
3
  export { AgentIcon } from '../agents/AgentIcon.js';
4
4
  export { ModelOptionControl } from '../agents/ModelOptionControl.js';
5
5
  export { ProviderLogo } from '../agents/ProviderLogo.js';
6
- export { accountsOfKind, accountName, ACCOUNT_TONE_CLASSES, accountStatusLine, canContinueOn } from '../agents/accounts.js';
6
+ export { accountsOfKind, ACCOUNT_TONE_CLASSES, accountStatusLine, canContinueOn } from '../agents/accounts.js';
7
7
  export { modelName, useModelName, shortModelName } from '../agents/model-name.js';
8
- export { optionValue, carryOptions } from '../agents/model-options.js';
9
- export { modelRequirementOf } from '../agents/model-requirement.js';
10
- export { AccountColors } from './AccountColors.js';
11
- export { AccountDetail } from './AccountDetail.js';
12
- export { AccountVariables } from './AccountVariables.js';
13
- export { AddAccountForm } from './AddAccountForm.js';
14
- export { CliDetail } from './CliDetail.js';
8
+ export { carryOptions } from '../agents/model-options.js';
9
+ export {} from '../agents/model-requirement.js';
15
10
  export { ProvidersPane } from './ProvidersPane.js';
16
- export { saveAccounts, saveAccount, removeAccount, createAccount, linkAccount } from './account-actions.js';
17
11
  export { CliMark, CliTile } from './parts.js';
@@ -1,4 +1,4 @@
1
- export { type ChatState, type ChatsById, type ChatStatuses, type ChatSink, prependPage, waitingRequestsOf, applyEvent, useChats, chatSink, useChatRow, useReportedModels, useCurrentItem, type ChatHistoryPage, type ChatPage } from './chats.js';
1
+ export { type ChatState, type ChatsById, type ChatStatuses, type ChatSink, waitingRequestsOf, applyEvent, useChats, chatSink, useChatRow, type ChatHistoryPage, type ChatPage } from './chats.js';
2
2
  export { useProviderAccountsStore, useProviderAccounts, providerAccountsOf, knownAccounts, watchProviderAccounts, type ProviderAccountsRow } from './provider-accounts.js';
3
3
  export { useProvidersStore, useProviders, providersOf, providerSinkFor, type ProvidersRow } from './providers.js';
4
- export { type UsageMetric, USAGE_PREFERENCES_KEY, usagePreferencesKey, dayOf, windowFor, summaryPayload, reloadUsagePreferences, useUsageStore, useUsage, type UsagePeriod, type UsageView } from './usage.js';
4
+ export { type UsageMetric, USAGE_PREFERENCES_KEY, dayOf, windowFor, reloadUsagePreferences, useUsageStore, type UsagePeriod, type UsageView } from './usage.js';
@@ -1,5 +1,5 @@
1
1
  // Public API: exports.test.ts snapshots this list, so a name added or dropped here is an API change.
2
- export { prependPage, waitingRequestsOf, applyEvent, useChats, chatSink, useChatRow, useReportedModels, useCurrentItem } from './chats.js';
2
+ export { waitingRequestsOf, applyEvent, useChats, chatSink, useChatRow } from './chats.js';
3
3
  export { useProviderAccountsStore, useProviderAccounts, providerAccountsOf, knownAccounts, watchProviderAccounts } from './provider-accounts.js';
4
4
  export { useProvidersStore, useProviders, providersOf, providerSinkFor } from './providers.js';
5
- export { USAGE_PREFERENCES_KEY, usagePreferencesKey, dayOf, windowFor, summaryPayload, reloadUsagePreferences, useUsageStore, useUsage } from './usage.js';
5
+ export { USAGE_PREFERENCES_KEY, dayOf, windowFor, reloadUsagePreferences, useUsageStore } from './usage.js';
@@ -7,7 +7,6 @@ export declare const USAGE_PERIODS: readonly {
7
7
  days: number;
8
8
  }[];
9
9
  export declare let USAGE_PREFERENCES_KEY: string;
10
- export declare function usagePreferencesKey(): string;
11
10
  interface Preferences {
12
11
  period: UsagePeriod;
13
12
  metric: UsageMetric;
@@ -14,9 +14,6 @@ export let USAGE_PREFERENCES_KEY = storageKey();
14
14
  onChatStorageConfiguration(() => {
15
15
  USAGE_PREFERENCES_KEY = storageKey();
16
16
  });
17
- export function usagePreferencesKey() {
18
- return storageKey();
19
- }
20
17
  const DEFAULTS = { period: '7d', metric: 'cost', currency: 'USD' };
21
18
  function parsePreferences(raw) {
22
19
  try {
package/dist/theme.css CHANGED
@@ -27,6 +27,9 @@
27
27
  --chart-4: #eda100;
28
28
  --chart-5: #e87ba4;
29
29
  --chart-6: #008300;
30
+ /* The two grounds of the map a page draws without tiles. */
31
+ --map-land: #f2f1ed;
32
+ --map-water: #d3dde5;
30
33
  }
31
34
 
32
35
  [data-theme="dark"] {
@@ -46,6 +49,8 @@
46
49
  --chart-4: #c98500;
47
50
  --chart-5: #d55181;
48
51
  --chart-6: #008300;
52
+ --map-land: #2c3135;
53
+ --map-water: #1a2026;
49
54
  }
50
55
 
51
56
  /* Output and diffs take the terminal's colors when the app loads `@adecore/terminal/terminal.css`,
@@ -56,6 +61,9 @@
56
61
  --chat-added: var(--term-ansi-green, var(--positive));
57
62
  --chat-removed: var(--term-ansi-red, var(--status-error));
58
63
  --chart-1: var(--accent);
64
+ /* The number of a place on a map and in its list: the ink on the ground, so it reads on either. */
65
+ --map-marker: var(--text);
66
+ --map-marker-text: var(--surface);
59
67
  }
60
68
 
61
69
  @theme inline {
@@ -73,6 +81,8 @@
73
81
  --color-chat-output: var(--chat-output);
74
82
  --color-chat-added: var(--chat-added);
75
83
  --color-chat-removed: var(--chat-removed);
84
+ --color-map-marker: var(--map-marker);
85
+ --color-map-marker-text: var(--map-marker-text);
76
86
  }
77
87
 
78
88
  @layer components {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adecore/agents-react",
3
- "version": "0.24.1",
3
+ "version": "0.25.0",
4
4
  "description": "React agent chat threads, composers, approvals, provider accounts and usage views.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -67,11 +67,11 @@
67
67
  "test:visuals": "bun --config=integration.bunfig.toml --conditions=source test ./src/chat/visual-viewport.integration.test.ts"
68
68
  },
69
69
  "dependencies": {
70
- "@adecore/agent-contracts": "0.24.1",
71
- "@adecore/editor": "0.24.1",
72
- "@adecore/editor-react": "0.24.1",
73
- "@adecore/intelligent-ui": "0.24.1",
74
- "@adecore/merge": "0.24.1",
70
+ "@adecore/agent-contracts": "0.25.0",
71
+ "@adecore/editor": "0.25.0",
72
+ "@adecore/editor-react": "0.25.0",
73
+ "@adecore/intelligent-ui": "0.25.0",
74
+ "@adecore/merge": "0.25.0",
75
75
  "@codemirror/commands": "^6.11.1",
76
76
  "@codemirror/language": "^6.12.4",
77
77
  "@codemirror/state": "^6.7.6",
@@ -83,6 +83,7 @@
83
83
  "@tanstack/react-virtual": "^3.14.13",
84
84
  "clsx": "^2.1.1",
85
85
  "lucide-react": "^1.47.0",
86
+ "maplibre-gl": "^6.14.0",
86
87
  "mermaid": "^12.2.0",
87
88
  "react-markdown": "^10.1.0",
88
89
  "remark-breaks": "^4.0.0",
@@ -93,14 +94,14 @@
93
94
  "zustand": "^5.0.15"
94
95
  },
95
96
  "peerDependencies": {
96
- "@adecore/ui": "^0.24.1",
97
+ "@adecore/ui": "^0.25.0",
97
98
  "i18next": "^26.4.2",
98
99
  "react": "^19.3.0",
99
100
  "react-dom": "^19.3.0",
100
101
  "react-i18next": "^17.0.15"
101
102
  },
102
103
  "devDependencies": {
103
- "@adecore/ui": "0.24.1",
104
+ "@adecore/ui": "0.25.0",
104
105
  "@types/bun": "^1.4.2",
105
106
  "@types/react": "^19.3.0",
106
107
  "@types/react-dom": "^19.3.0",
package/src/chat/index.ts CHANGED
@@ -1,18 +1,6 @@
1
1
  // Public API: exports.test.ts snapshots this list, so a name added or dropped here is an API change.
2
2
  export { type AccountChoice, useAccountChoice } from './account-choice';
3
3
  export { transportActions, actionsOf, useChatActions } from './actions';
4
- export { backgroundCounts } from './activity';
5
- export {
6
- isImageAttachment,
7
- fileBadge,
8
- formatBytes,
9
- checkAttachmentLimits,
10
- filesOf,
11
- readAttachments,
12
- readStoredAttachments,
13
- uploadPreviewUrl,
14
- uploadBytes
15
- } from './attachments';
16
4
  export { type ChatSendExtras, ChatClient } from './chat-client';
17
5
  export type { ProjectChat } from './chat-references';
18
6
  export { sendFromComposer, type ComposerSend } from './composer-send';
@@ -36,24 +24,17 @@ export {
36
24
  export { handbackReportOf } from './logic/handback';
37
25
  export { limitView, type LimitView } from './logic/limit';
38
26
  export type { TickKind, ScrubberTick } from './logic/scrubber';
39
- export { type TimedRow, cardRows, visualRows, withTimedRows, timedRowsOf, withThreadCards } from './logic/thread-cards';
27
+ export { type TimedRow, withTimedRows, timedRowsOf } from './logic/thread-cards';
40
28
  export { EMPTY_TARGET, readTimelineTarget, type TimelineTarget } from './logic/timeline-target';
41
29
  export { type TimelineRow, summarizeGroup, deriveTimelineRows, type SubagentBranch, type BaseMoved } from './logic/timeline';
42
- export { toolSummary, fileChanges, unifiedChanges, approvalChanges, hasFileChanges, type FileChange } from './logic/tools';
30
+ export { toolSummary, type FileChange } from './logic/tools';
43
31
  export {
44
32
  MENTION_DRAG_TYPE,
45
33
  LEGACY_MENTION_DRAG_TYPE,
46
- MENTION_DRAG_TYPES,
47
34
  carriesMentions,
48
35
  droppedMentions,
49
36
  writeMentionDrag,
50
- findMentionQuery,
51
- findSkillQuery,
52
- insertMention,
53
- insertSkill,
54
- presentMentions,
55
37
  chipText,
56
- tokenizeChips,
57
38
  type MentionQuery,
58
39
  type ChipSegment
59
40
  } from './mentions';
@@ -66,7 +47,6 @@ export {
66
47
  useChatPreferences,
67
48
  readChatPreferences,
68
49
  CHAT_PREFERENCES_KEY,
69
- chatPreferencesKey,
70
50
  reloadChatPreferences,
71
51
  rememberChatPreferences,
72
52
  rememberReadingWidth,
@@ -93,6 +73,5 @@ export {
93
73
  type SubagentStep,
94
74
  type SubagentTrail
95
75
  } from './subagent-view';
96
- export { THUMBNAIL_PX, useUploadThumbnail } from './thumbnails';
97
76
  export { stepTimelineMessage, jumpToTimelineItem, type TurnTarget, registerTurnJumper, jumpToTimelineTurn, type ItemJumpOptions } from './timeline-scroll';
98
77
  export { type StatusWord, statusLookOf, taskStatusWord, type StatusLook, type TaskState } from '../agents/status-look';
@@ -0,0 +1,32 @@
1
+ import { abortedByEnd, abortedByMachine, type ChatInfo, type ChatItem } from '@adecore/agent-contracts';
2
+
3
+ /* English like the other prompts the CLI gets, and it reaches the thread as the person's own message, which it is. */
4
+ export const CONTINUE_PROMPT = 'Continue.';
5
+
6
+ /*
7
+ * The turn a person stopped last, when nothing has happened since and Continue can pick it up; null otherwise.
8
+ * A paused queue, a limit and a paused goal have their own way out, a turn the machine ended has its own resume,
9
+ * and a request that waits on a person comes first.
10
+ */
11
+ export function continuableTurn(info: ChatInfo, items: readonly ChatItem[]): string | null {
12
+ if (
13
+ info.activeTurnId !== null ||
14
+ info.status === 'needs-you' ||
15
+ (info.queue ?? []).length > 0 ||
16
+ info.limit !== undefined ||
17
+ info.goal?.status === 'paused'
18
+ ) {
19
+ return null;
20
+ }
21
+ const index = items.findLastIndex((item) => item.kind === 'turn');
22
+ const turn = items[index];
23
+ if (turn?.kind !== 'turn' || turn.state !== 'aborted' || turn.limit !== undefined || abortedByMachine(turn, items) || abortedByEnd(turn, items)) {
24
+ return null;
25
+ }
26
+ // A message of this turn sits after the turn item too, so only one sent after the turn ended counts.
27
+ const endedAt = turn.endedAt ?? turn.createdAt;
28
+ if (items.slice(index + 1).some((item) => item.kind === 'user' && item.createdAt > endedAt)) {
29
+ return null;
30
+ }
31
+ return turn.id;
32
+ }
@@ -92,8 +92,3 @@ export function withTimedRows(rows: TimelineRow[], timed: readonly TimedRow[]):
92
92
  export function timedRowsOf(cards: readonly ThreadCard[], visuals: readonly ChatVisual[] | null, heldFrom: number | null = null): TimedRow[] {
93
93
  return [...(visuals === null ? [] : visualRows(visuals, heldFrom)), ...cardRows(cards)];
94
94
  }
95
-
96
- /* The thread's rows with an app's cards among them, the way `withTimedRows` places them. */
97
- export function withThreadCards(rows: TimelineRow[], cards: readonly ThreadCard[]): TimelineRow[] {
98
- return withTimedRows(rows, cardRows(cards));
99
- }
@@ -143,10 +143,6 @@ onChatStorageConfiguration(() => {
143
143
  CHAT_PREFERENCES_KEY = storageKey();
144
144
  });
145
145
 
146
- export function chatPreferencesKey(): string {
147
- return storageKey();
148
- }
149
-
150
146
  /* Reads the remembered default again, once another window of the same origin wrote it. */
151
147
  export function reloadChatPreferences(): void {
152
148
  useChatPreferences.setState(storage.read(), true);
package/src/chat/stash.ts CHANGED
@@ -26,6 +26,8 @@ export interface StashedPrompt {
26
26
  text: string;
27
27
  mentions: string[];
28
28
  skills: string[];
29
+ /* Chats of the project picked with `@`; an entry from before this field has none. */
30
+ chats: string[];
29
31
  attachments: StashedAttachment[];
30
32
  /* A quote that knew its reply, kept apart from the text so it can lead back to it in the chat it came from. */
31
33
  quote?: StashedQuote;
@@ -83,6 +85,7 @@ export function parseStash(raw: string | null): StashedPrompt[] {
83
85
  text: entry.text as string,
84
86
  mentions: strings(entry.mentions),
85
87
  skills: strings(entry.skills),
88
+ chats: strings(entry.chats),
86
89
  attachments: attachments(entry.attachments),
87
90
  ...stashedQuoteField(entry.quote),
88
91
  createdAt: typeof entry.createdAt === 'number' ? entry.createdAt : 0
@@ -113,11 +116,25 @@ export function stashedFrom(draft: ChatDraft, id: string, now: number, chatId: s
113
116
  ...(kept ? { quote: { text: draft.quote, chatId, itemId: draft.quoteSource! } } : {}),
114
117
  mentions: [...draft.mentions],
115
118
  skills: [...draft.skills],
119
+ chats: [...draft.chats],
116
120
  attachments: draft.attachments.map((attachment) => ({ name: attachment.name, mime: attachment.mime, size: uploadBytes(attachment) })),
117
121
  createdAt: now
118
122
  };
119
123
  }
120
124
 
125
+ /* The limit of `chats` in a send. */
126
+ export const STASH_CHATS_LIMIT = 16;
127
+
128
+ /*
129
+ * The attached chats a composer may take back: the ones it can still reference, never its own chat, in the
130
+ * order they were picked. `dropped` counts the chats left out because the project no longer has them.
131
+ */
132
+ export function restorableChats(stashed: readonly string[], referable: ReadonlySet<string>, chatId: string): { chats: string[]; dropped: number } {
133
+ const others = stashed.filter((id) => id !== chatId);
134
+ const chats = others.filter((id) => referable.has(id));
135
+ return { chats: chats.slice(0, STASH_CHATS_LIMIT), dropped: others.length - chats.length };
136
+ }
137
+
121
138
  const storage = persistedJson<StashedPrompt[]>(storageKey, parseStash, [], () => chatStorageLegacyKeys('stash'));
122
139
 
123
140
  /* Prompts put aside for later can move to another chat, so the shelf is shared across chats. */
@@ -36,6 +36,7 @@ import { useComposerDraft } from './composer/use-composer-draft';
36
36
  import { useComposerMenus } from './composer/use-composer-menus';
37
37
  import { useComposerSubmit } from './composer/use-composer-submit';
38
38
  import { LEAVE_DROPS_TO_THE_CARD, useDraftFiles } from './composer/use-draft-files';
39
+ import { useContinuableTurn } from './composer/use-continuable-turn';
39
40
  import { useNotice } from './composer/use-notice';
40
41
  import { usePendingRequests } from './composer/use-pending-requests';
41
42
  import { usePromptHistory } from './composer/use-prompt-history';
@@ -96,7 +97,11 @@ export function Composer({ chatId, info, focused, answerPromptsElsewhere, disabl
96
97
  const files = useDraftFiles({ capabilities, providerName: provider?.name, writable, draft: box, inputRef, setNotice: notice.setNotice });
97
98
  const controls = useChatControls({ chatId, info, notice, onRetarget });
98
99
  const guard = promptGuard(withQuote(draft.quote, draft.text));
99
- const { submit, restoreStashed, toggleStash } = useComposerSubmit({
100
+ const continuable = useContinuableTurn(chatId, info);
101
+ // The stopped turn something went out for, since the chat takes a moment to answer and Continue must not stand there meanwhile.
102
+ const [sentFor, setSentFor] = useState<string | null>(null);
103
+ const extraPrompts = host.prompts.useExtra(scope.id, chatId);
104
+ const { submit, continueTurn, restoreStashed, toggleStash } = useComposerSubmit({
100
105
  chatId,
101
106
  writable,
102
107
  guard,
@@ -109,7 +114,23 @@ export function Composer({ chatId, info, focused, answerPromptsElsewhere, disabl
109
114
  hold,
110
115
  inputRef,
111
116
  setNotice: notice.setNotice,
112
- onSend
117
+ onSend: (text, extras) => {
118
+ setSentFor(continuable);
119
+ const sent = onSend(text, extras);
120
+ // A send the host did not take leaves Continue where it was; the original promise still reaches its caller.
121
+ void Promise.resolve(sent).then(
122
+ (taken) => {
123
+ if (taken === false) {
124
+ setSentFor(null);
125
+ }
126
+ },
127
+ (error: unknown) => {
128
+ setSentFor(null);
129
+ throw error;
130
+ }
131
+ );
132
+ return sent;
133
+ }
113
134
  });
114
135
  const { takingBack, takeBack } = useTakeBack({
115
136
  chatId,
@@ -231,7 +252,7 @@ export function Composer({ chatId, info, focused, answerPromptsElsewhere, disabl
231
252
  quote={draft.quote}
232
253
  source={draft.quoteSource}
233
254
  onRemove={box.removeQuote}
234
- onSourceGone={() => notice.setNotice(t('rows.user.quoteGone'))}
255
+ onSourceGone={() => notice.setNotice(t('rows.user.quoteGone'), 'info')}
235
256
  />
236
257
  )}
237
258
  {draft.chats.length > 0 && <DraftChats chats={draft.chats} onRemove={box.removeChat} />}
@@ -263,7 +284,10 @@ export function Composer({ chatId, info, focused, answerPromptsElsewhere, disabl
263
284
  onPaste={files.onPaste}
264
285
  />
265
286
  {notice.notice && (
266
- <div className="px-5 pb-1 text-xs text-status-error @max-md/composer:px-3.5" role="alert">
287
+ <div
288
+ className={clsx('px-5 pb-1 text-xs @max-md/composer:px-3.5', notice.tone === 'error' ? 'text-status-error' : 'text-text-muted')}
289
+ role={notice.tone === 'error' ? 'alert' : 'status'}
290
+ >
267
291
  {notice.notice}
268
292
  </div>
269
293
  )}
@@ -290,7 +314,11 @@ export function Composer({ chatId, info, focused, answerPromptsElsewhere, disabl
290
314
  disabled: empty || !writable || guard.tooLong,
291
315
  label: t(sendLabelKey(busy, steerable, sendDelivery)),
292
316
  kbd: sendShortcut(sendKey, draft.text.includes('\n')),
293
- onSend: (alternate) => submit(alternate ? 'alternate' : 'default')
317
+ onSend: (alternate) => submit(alternate ? 'alternate' : 'default'),
318
+ onContinue:
319
+ continuable !== null && continuable !== sentFor && empty && writable && pending.length === 0 && extraPrompts.length === 0
320
+ ? continueTurn
321
+ : null
294
322
  }}
295
323
  dictationToolbar={setDictationToolbar}
296
324
  onFiles={(picked) => {
@@ -1,4 +1,5 @@
1
- import { Suspense, useMemo } from 'react';
1
+ import { useMemo } from 'react';
2
+ import { LazyBoundary } from '@adecore/ui';
2
3
  import { useTranslation } from 'react-i18next';
3
4
  import { lazyNamed } from '../../lazy';
4
5
  import { diffSourceBudget, legacyDiffBudget } from './diff/diff-source-budget';
@@ -22,9 +23,9 @@ export default function EditDiff({ change, allowEditor = true, maxHeight }: { ch
22
23
  const omitted = <DiffOmitted path={change.path} />;
23
24
  const fallback = legacyAllowed ? (
24
25
  <DiffFallbackBoundary fallback={omitted} resetKeys={[change.path, change.before, change.after]}>
25
- <Suspense fallback={<div className="px-3 py-2 text-xs text-text-faint">{t('work.loadingDiff')}</div>}>
26
+ <LazyBoundary fallback={<div className="px-3 py-2 text-xs text-text-faint">{t('work.loadingDiff')}</div>}>
26
27
  <LegacyEditDiff change={change} maxHeight={maxHeight} />
27
- </Suspense>
28
+ </LazyBoundary>
28
29
  </DiffFallbackBoundary>
29
30
  ) : (
30
31
  omitted
@@ -38,9 +39,9 @@ export default function EditDiff({ change, allowEditor = true, maxHeight }: { ch
38
39
  const unavailable = <DiffUnavailable path={change.path} />;
39
40
  return (
40
41
  <DiffFallbackBoundary fallback={unavailable} resetKeys={[change.path, change.before, change.after, loadEngine]}>
41
- <Suspense fallback={<div className="px-3 py-2 text-xs text-text-faint">{t('work.loadingDiff')}</div>}>
42
+ <LazyBoundary fallback={<div className="px-3 py-2 text-xs text-text-faint">{t('work.loadingDiff')}</div>}>
42
43
  <EditorEditDiff change={change} loadEngine={loadEngine!} fallback={unavailable} maxHeight={maxHeight} sourceChecked />
43
- </Suspense>
44
+ </LazyBoundary>
44
45
  </DiffFallbackBoundary>
45
46
  );
46
47
  }
@@ -273,6 +273,7 @@ export function StashPicker({ onRestore }: { onRestore(prompt: StashedPrompt): v
273
273
  {prompt.attachments.length > 0 && (
274
274
  <span className="text-text-faint">{t('pickers.stash.files', { count: prompt.attachments.length })}</span>
275
275
  )}
276
+ {prompt.chats.length > 0 && <span className="text-text-faint">{t('pickers.stash.chats', { count: prompt.chats.length })}</span>}
276
277
  </Popover.Close>
277
278
  <IconButton
278
279
  icon={Trash2}
@@ -1,4 +1,5 @@
1
- import { Suspense, useMemo } from 'react';
1
+ import { useMemo } from 'react';
2
+ import { LazyBoundary } from '@adecore/ui';
2
3
  import { useTranslation } from 'react-i18next';
3
4
  import { lazyNamed } from '../../lazy';
4
5
  import { diffSourceBudget, legacyDiffBudget, patchSourceBudget } from './diff/diff-source-budget';
@@ -27,9 +28,9 @@ export default function UnifiedDiff(props: UnifiedDiffProps) {
27
28
  const omitted = <DiffOmitted path={change.path} />;
28
29
  const fallback = legacyAllowed ? (
29
30
  <DiffFallbackBoundary fallback={omitted} resetKeys={[change.path, change.diff, oldText, newText, diffStyle]}>
30
- <Suspense fallback={<div className="px-3 py-2 text-xs text-text-faint">{t('work.loadingDiff')}</div>}>
31
+ <LazyBoundary fallback={<div className="px-3 py-2 text-xs text-text-faint">{t('work.loadingDiff')}</div>}>
31
32
  <LegacyUnifiedDiff {...props} />
32
- </Suspense>
33
+ </LazyBoundary>
33
34
  </DiffFallbackBoundary>
34
35
  ) : (
35
36
  omitted
@@ -46,7 +47,7 @@ export default function UnifiedDiff(props: UnifiedDiffProps) {
46
47
  const unavailable = <DiffUnavailable path={change.path} />;
47
48
  return (
48
49
  <DiffFallbackBoundary fallback={unavailable} resetKeys={[change.path, change.diff, oldText, newText, ignoreWhitespace, diffStyle, loadEngine]}>
49
- <Suspense fallback={<div className="px-3 py-2 text-xs text-text-faint">{t('work.loadingDiff')}</div>}>
50
+ <LazyBoundary fallback={<div className="px-3 py-2 text-xs text-text-faint">{t('work.loadingDiff')}</div>}>
50
51
  <EditorPatchDiff
51
52
  change={change}
52
53
  sourceChecked
@@ -59,7 +60,7 @@ export default function UnifiedDiff(props: UnifiedDiffProps) {
59
60
  loadEngine={loadEngine!}
60
61
  fallback={unavailable}
61
62
  />
62
- </Suspense>
63
+ </LazyBoundary>
63
64
  </DiffFallbackBoundary>
64
65
  );
65
66
  }
@@ -1,8 +1,8 @@
1
1
  import { useRef, type Ref } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
- import { ArrowUp, Plus, Square } from 'lucide-react';
3
+ import { ArrowUp, Play, Plus, Square } from 'lucide-react';
4
4
  import type { ChatInfo } from '@adecore/agent-contracts';
5
- import { ErrorBoundary, Icon, Tooltip, type Shortcut } from '@adecore/ui';
5
+ import { Button, ErrorBoundary, Icon, Tooltip, type Shortcut } from '@adecore/ui';
6
6
  import { useChatHost } from '../../../host';
7
7
  import { useChatScope } from '../../../scope';
8
8
  import { useProviders } from '../../../state/providers';
@@ -20,6 +20,8 @@ interface SendProps {
20
20
  label: string;
21
21
  kbd: Shortcut;
22
22
  onSend(alternate: boolean): void;
23
+ /* Set when a person stopped the last turn and the box is empty: Continue stands where the send button would. */
24
+ onContinue: (() => void) | null;
23
25
  }
24
26
 
25
27
  interface ComposerControlsProps {
@@ -130,7 +132,15 @@ export function ComposerControls({
130
132
  </button>
131
133
  </Tooltip>
132
134
  )}
133
- {send.visible && (
135
+ {send.onContinue !== null && (
136
+ <Tooltip label={t('composer.continueHint')}>
137
+ <Button variant="primary" shape="pill" disabled={controls.configurationPending} onClick={send.onContinue}>
138
+ <Icon icon={Play} size={14} />
139
+ {t('composer.continue')}
140
+ </Button>
141
+ </Tooltip>
142
+ )}
143
+ {send.onContinue === null && send.visible && (
134
144
  <Tooltip label={send.label} kbd={send.kbd} name>
135
145
  <button
136
146
  className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-accent text-accent-text hover:brightness-90 disabled:opacity-50 disabled:hover:brightness-100"