@design.estate/dees-catalog 3.92.0 → 3.94.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_bundle/bundle.js +2125 -1392
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.d.ts +15 -3
- package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.js +159 -18
- package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.js +126 -49
- package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.d.ts +13 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.demo.js +5 -2
- package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.js +118 -3
- package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.d.ts +25 -3
- package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.js +156 -48
- package/dist_ts_web/elements/00group-harness/dees-harness-permission-card/dees-harness-permission-card.d.ts +2 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-permission-card/dees-harness-permission-card.js +9 -5
- package/dist_ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.d.ts +31 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.demo.d.ts +1 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.demo.js +69 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.js +382 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-question-card/index.d.ts +1 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-question-card/index.js +2 -0
- package/dist_ts_web/elements/00group-harness/index.d.ts +1 -0
- package/dist_ts_web/elements/00group-harness/index.js +2 -1
- package/dist_ts_web/elements/00group-harness/interfaces.d.ts +33 -0
- package/dist_ts_web/elements/00group-harness/toolregistry.js +9 -1
- package/dist_ts_web/elements/00theme.js +6 -1
- package/dist_watch/bundle.js +2126 -1393
- package/dist_watch/bundle.js.map +4 -4
- package/package.json +1 -1
- package/readme.md +14 -4
- package/readme.playbook.md +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.ts +168 -21
- package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.ts +98 -49
- package/ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.demo.ts +4 -1
- package/ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.ts +99 -0
- package/ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts +148 -46
- package/ts_web/elements/00group-harness/dees-harness-permission-card/dees-harness-permission-card.ts +9 -4
- package/ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.demo.ts +70 -0
- package/ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.ts +319 -0
- package/ts_web/elements/00group-harness/dees-harness-question-card/index.ts +1 -0
- package/ts_web/elements/00group-harness/index.ts +1 -0
- package/ts_web/elements/00group-harness/interfaces.ts +36 -0
- package/ts_web/elements/00group-harness/toolregistry.ts +8 -0
- package/ts_web/elements/00theme.ts +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@design.estate/dees-catalog",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.94.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.",
|
|
6
6
|
"main": "dist_ts_web/index.js",
|
package/readme.md
CHANGED
|
@@ -64,7 +64,7 @@ For developers working on this library, please refer to the [UI Components Playb
|
|
|
64
64
|
| **Dialogs & Overlays** | [`DeesModal`](#deesmodal), [`DeesContextmenu`](#deescontextmenu), [`DeesSpeechbubble`](#deesspeechbubble), [`DeesWindowlayer`](#deeswindowlayer) |
|
|
65
65
|
| **Navigation** | [`DeesStepper`](#deesstepper), [`DeesProgressbar`](#deesprogressbar) |
|
|
66
66
|
| **Workspace / IDE** | [`DeesWorkspace`](#deesworkspace), [`DeesWorkspaceMonaco`](#deesworkspacemonaco), [`DeesWorkspaceDiffEditor`](#deesworkspacediffeditor), [`DeesWorkspaceFiletree`](#deesworkspacefiletree), [`DeesWorkspaceTerminal`](#deesworkspaceterminal), [`DeesWorkspaceTerminalPreview`](#deesworkspaceterminalpreview), [`DeesWorkspaceMarkdown`](#deesworkspacemarkdown), [`DeesWorkspaceMarkdownoutlet`](#deesworkspacemarkdownoutlet), [`DeesWorkspaceBottombar`](#deesworkspacebottombar) |
|
|
67
|
-
| **Agentic Chat** | [`DeesHarnessChat`](#deesharnesschat), [`DeesHarnessMessageList`](#deesharnessmessagelist), [`DeesHarnessMessage`](#deesharnessmessage), [`DeesHarnessReasoning`](#deesharnessreasoning), [`DeesHarnessToolCard`](#deesharnesstoolcard), [`DeesHarnessContentBlocks`](#deesharnesscontentblocks), [`DeesHarnessPermissionCard`](#deesharnesspermissioncard), [`DeesHarnessComposer`](#deesharnesscomposer), [`DeesHarnessStatus`](#deesharnessstatus), [`DeesHarnessTodos`](#deesharnesstodos), [`DeesHarnessUsage`](#deesharnessusage), [`DeesHarnessSessionList`](#deesharnesssessionlist) |
|
|
67
|
+
| **Agentic Chat** | [`DeesHarnessChat`](#deesharnesschat), [`DeesHarnessMessageList`](#deesharnessmessagelist), [`DeesHarnessMessage`](#deesharnessmessage), [`DeesHarnessReasoning`](#deesharnessreasoning), [`DeesHarnessToolCard`](#deesharnesstoolcard), [`DeesHarnessContentBlocks`](#deesharnesscontentblocks), [`DeesHarnessPermissionCard`](#deesharnesspermissioncard), [`DeesHarnessQuestionCard`](#deesharnessquestioncard), [`DeesHarnessComposer`](#deesharnesscomposer), [`DeesHarnessStatus`](#deesharnessstatus), [`DeesHarnessTodos`](#deesharnesstodos), [`DeesHarnessUsage`](#deesharnessusage), [`DeesHarnessSessionList`](#deesharnesssessionlist) |
|
|
68
68
|
| **Theming** | [`DeesTheme`](#deestheme), [`DeesUpdater`](#deesupdater) |
|
|
69
69
|
| **Pre-built Templates** | [`DeesSimpleAppdash`](#deessimpleappdash), [`DeesSimpleLogin`](#deessimplelogin) |
|
|
70
70
|
| **Shopping** | [`DeesShoppingProductcard`](#deesshoppingproductcard) |
|
|
@@ -1699,7 +1699,7 @@ The `00group-harness` family renders what an agent harness produces — streamin
|
|
|
1699
1699
|
|
|
1700
1700
|
#### `DeesHarnessChat`
|
|
1701
1701
|
|
|
1702
|
-
The assembled chat: optional toolbar (heading, usage chip, history/new-session buttons), streaming message list
|
|
1702
|
+
The assembled chat: optional toolbar (heading, usage chip, history/new-session buttons), streaming message list with inline permission cards, transcript status line, and docked composer.
|
|
1703
1703
|
|
|
1704
1704
|
```typescript
|
|
1705
1705
|
const chat = document.querySelector('dees-harness-chat');
|
|
@@ -1721,11 +1721,13 @@ chat.applyDelta({ type: 'text', messageId: 'm1', delta: 'chunk' });
|
|
|
1721
1721
|
chat.applyDelta({ type: 'message-end', messageId: 'm1', usage: { totalTokens: 1200 } });
|
|
1722
1722
|
```
|
|
1723
1723
|
|
|
1724
|
-
Key props: `messages`, `permissions`, `status`, `usage`, `heading`, `subheading`, `showToolbar`, `busy`, `queuedCount`, `disabled`, `model`/`modelOptions`, `reasoningEffort`/`effortOptions`, `toolRegistry`.
|
|
1724
|
+
Key props: `messages`, `permissions`, `questions`, `status`, `usage`, `todos`/`showTodosPanel`, `heading`, `subheading`, `showToolbar`, `busy`, `queuedCount`, `disabled`, `model`/`modelOptions`, `reasoningEffort`/`effortOptions`, `mode`/`modeOptions`, `toolRegistry`. The status line renders inside the transcript column (it never pushes between the Tasks panel and the composer), and both the toolbar and the docked composer cast a subtle shadow over the scrolling messages.
|
|
1725
|
+
|
|
1726
|
+
A **Tasks side panel** (OpenCode-style) shows the current todo list next to the transcript: set `.todos` explicitly, or leave it empty and the chat derives it from the newest todo tool call in `messages`. The panel hides itself in narrow containers (< 520px) where the transcript's todo cards carry the same information; `showTodosPanel={false}` disables it entirely. Methods: `applyDelta()`, `focusComposer()`, `clearComposer()`, `scrollToBottom()`. Events (also from children, all composed): `harness-send`, `harness-steer`, `harness-abort`, `harness-input`, `harness-attachments-change`, `harness-attachment-error`, `harness-model-change`, `harness-mode-change`, `harness-permission-response`, `harness-question-response`, `harness-new-session`, `harness-open-sessions`.
|
|
1725
1727
|
|
|
1726
1728
|
#### `DeesHarnessMessageList`
|
|
1727
1729
|
|
|
1728
|
-
Scrollable streaming-safe log. Keyed repeat
|
|
1730
|
+
Scrollable streaming-safe log. Keyed repeat keeps DOM identity stable; `applyDelta()` routes updates to the affected message element only. Auto-follows the bottom while pinned; otherwise raises a "Jump to latest" chip instead of yanking the scroll. Permission requests and agent questions render inline in the flow, and while pinned the list follows content growth frame-by-frame (ResizeObserver), so a new box growing into place and the scroll move as one motion. Props: `messages`, `permissions`/`permissionsBusy`, `questions`, `status`, `autoFollow`, `showRoles`, `showTimestamps`, `emptyTitle`/`emptyText` (+ `slot="empty"`).
|
|
1729
1731
|
|
|
1730
1732
|
#### `DeesHarnessMessage`
|
|
1731
1733
|
|
|
@@ -1762,12 +1764,20 @@ Renders MCP `CallToolResult` content structurally: text, base64 images, audio wi
|
|
|
1762
1764
|
|
|
1763
1765
|
Permission prompt: title, type badge (shell/delete → error tone, write/browser → warning), metadata key/values, Allow Once / Always Allow / Reject, and a "remember" checkbox (`always` implies remember). Emits `harness-permission-response`; disables itself once answered or while `busy`.
|
|
1764
1766
|
|
|
1767
|
+
Inside the chat, permission cards render **inline with the messages**: pending requests pin to the end of the transcript (action required), and answered ones freeze at their response time in the flow. Set `response`/`respondedAt` on the request after handling it and keep it in the array — the card stays in the transcript showing what was granted or rejected.
|
|
1768
|
+
|
|
1769
|
+
#### `DeesHarnessQuestionCard`
|
|
1770
|
+
|
|
1771
|
+
A question the agent asks mid-run: option buttons with descriptions (single-select answers on click; `multiSelect` collects then confirms) plus an optional free-text answer (`allowCustom`, default on — Enter or "Send Answer" submits, combining selections with the text). Emits `harness-question-response` with `{ requestId, answers, request }`. Questions flow through `dees-harness-chat`'s `questions` property and render inline like permissions: pending ones pin to the end of the transcript, and answered ones persist showing the chosen answers once the host sets `response`/`respondedAt`.
|
|
1772
|
+
|
|
1765
1773
|
#### `DeesHarnessComposer`
|
|
1766
1774
|
|
|
1767
1775
|
Prompt input: autogrow textarea, attachment picker/paste/drag-drop with per-file (10 MB) and total (30 MB) limits and MIME allowlist, optional model + reasoning-effort dropdowns, Cmd/Ctrl+Enter to send. Controlled component — the host owns `value` and `attachments`.
|
|
1768
1776
|
|
|
1769
1777
|
While `busy`, Stop renders as an additional button and Send stays available so hosts can queue messages. With an empty prompt and `queuedCount > 0`, the primary action becomes **Steer now** (`harness-steer` event) to force the agent to continue with the queued messages immediately.
|
|
1770
1778
|
|
|
1779
|
+
Set `modeOptions` (e.g. `['Planning', 'Building']`) to render a segmented **mode switcher**; the active `mode` changes on click — or **Shift+Tab** inside the prompt cycles it — and emits `harness-mode-change`. Inside `dees-harness-chat` the composer renders `docked` — flush edge-to-edge with a top border and an upward shadow (`--dees-shadow-up-sm`) so it meets the transcript and the Tasks panel like the toolbar does; standalone composers keep the floating-card chrome.
|
|
1780
|
+
|
|
1771
1781
|
#### `DeesHarnessTodos`
|
|
1772
1782
|
|
|
1773
1783
|
Agent task list for `IHarnessTodoItem[]` — completed items dim with a check and strikethrough, the in-progress item leads with a pulsing accent marker, and an optional heading shows `completed/total` progress. Used automatically as the todo tool-card body (`harnessParseTodos` normalizes tool input shapes) and standalone for a "current tasks" sidebar panel fed from the latest `todowrite` call.
|
package/readme.playbook.md
CHANGED
|
@@ -439,7 +439,7 @@ Components that render continuously arriving data (agent chat, logs) follow the
|
|
|
439
439
|
- **Props are the source of truth; deltas are the fast path.** The host owns the array (e.g. `.messages`). Incremental updates go through `applyDelta()`, which mutates the same object instance held in the array and calls `requestUpdate()` on the one affected child element. A later wholesale re-assignment of the array is therefore idempotent — no divergence between streamed and re-set state.
|
|
440
440
|
- **Keyed `repeat` for identity.** List children render via `directives.repeat` keyed by a stable id so streaming never recreates sibling DOM (which would drop scroll position, expansion state, and selection).
|
|
441
441
|
- **Cheap while streaming, rich when done.** While `streaming` is set, text renders as pre-wrap with a cursor; expensive work (markdown parse) is throttled (≥150 ms, token-guarded like the codebox highlight pattern) and syntax highlighting waits for the final parse.
|
|
442
|
-
- **Scroll follow with user override.** Auto-scroll only when the user is already pinned to the bottom (`scrollHeight - scrollTop - clientHeight < threshold`); otherwise show a "jump to latest" chip.
|
|
442
|
+
- **Scroll follow with user override.** Auto-scroll only when the user is already pinned to the bottom (`scrollHeight - scrollTop - clientHeight < threshold`); otherwise show a "jump to latest" chip. While pinned, a content change opens a short follow window and a `ResizeObserver` on the scroll content re-pins the bottom on every layout change (post-layout, pre-paint), so a growing box and the scroll move as one motion; an external upward scroll ends the window before the next pin so following never fights the user. Disconnect the observer in `disconnectedCallback`.
|
|
443
443
|
- **Interaction state lives on the child.** Expand/collapse (reasoning, tool cards) is element-local state seeded from data (`expanded ?? isStreaming`), so parent re-renders can't reset what the user opened.
|
|
444
444
|
|
|
445
445
|
## Performance Optimization
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@design.estate/dees-catalog',
|
|
6
|
-
version: '3.
|
|
6
|
+
version: '3.94.0',
|
|
7
7
|
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
|
8
8
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { html } from '@design.estate/dees-element';
|
|
2
2
|
import type { DeesHarnessChat } from './dees-harness-chat.js';
|
|
3
3
|
import type { IHarnessMessage, IHarnessPermissionRequest } from '../interfaces.js';
|
|
4
|
-
import { harnessDemoConversation
|
|
4
|
+
import { harnessDemoConversation } from '../harness.demodata.js';
|
|
5
5
|
|
|
6
6
|
const toolShowcase = (): IHarnessMessage[] => {
|
|
7
7
|
const now = Date.now();
|
|
@@ -84,30 +84,160 @@ let stopStream: (() => void) | undefined;
|
|
|
84
84
|
const chatOf = (event: MouseEvent): DeesHarnessChat | null =>
|
|
85
85
|
(event.target as HTMLElement).closest('.chatDemo')?.querySelector('dees-harness-chat') ?? null;
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Streams a realistic turn: reasoning, a todo write, a test run, a file
|
|
89
|
+
* edit rendered as a diff, then the streamed summary text.
|
|
90
|
+
*/
|
|
87
91
|
const simulate = (event: MouseEvent) => {
|
|
88
92
|
const chat = chatOf(event);
|
|
89
93
|
if (!chat) return;
|
|
90
94
|
stopStream?.();
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
streaming: true,
|
|
95
|
+
const runId = Date.now();
|
|
96
|
+
const cancels: Array<() => void> = [];
|
|
97
|
+
const at = (ms: number, action: () => void) => {
|
|
98
|
+
const timeout = setTimeout(action, ms);
|
|
99
|
+
cancels.push(() => clearTimeout(timeout));
|
|
97
100
|
};
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
stopStream = () => {
|
|
102
|
+
for (const cancel of cancels) cancel();
|
|
103
|
+
chat.status = { type: 'idle' };
|
|
104
|
+
};
|
|
105
|
+
const pushMessage = (message: IHarnessMessage) => {
|
|
106
|
+
chat.messages = [...chat.messages, message];
|
|
107
|
+
};
|
|
108
|
+
const updateTool = (messageId: string, tool: Partial<IHarnessMessage['toolCall']> & { id: string }) => {
|
|
109
|
+
chat.applyDelta({ type: 'tool-update', messageId, tool: tool as never });
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
chat.status = { type: 'busy', message: 'Planning…' };
|
|
113
|
+
|
|
114
|
+
// 1) reasoning streams first
|
|
115
|
+
const reasoningMessage: IHarnessMessage = {
|
|
116
|
+
id: `sim-${runId}-reasoning`, role: 'assistant', text: '', createdAt: Date.now(), streaming: true,
|
|
117
|
+
};
|
|
118
|
+
pushMessage(reasoningMessage);
|
|
119
|
+
chat.applyDelta({ type: 'reasoning-start', messageId: reasoningMessage.id, partId: 'r1', startedAt: Date.now() });
|
|
120
|
+
const reasoningText = 'The cache key includes the locale list, so every rollout re-crawls. Plan: track the fix as todos, run the tests, then pin the key to the route manifest.';
|
|
121
|
+
reasoningText.split(' ').forEach((word, index) => {
|
|
122
|
+
at(80 + index * 45, () => chat.applyDelta({ type: 'reasoning', messageId: reasoningMessage.id, partId: 'r1', delta: word + ' ' }));
|
|
123
|
+
});
|
|
124
|
+
const reasoningDoneAt = 80 + reasoningText.split(' ').length * 45 + 150;
|
|
125
|
+
at(reasoningDoneAt, () => {
|
|
126
|
+
chat.applyDelta({ type: 'reasoning-end', messageId: reasoningMessage.id, partId: 'r1', endedAt: Date.now() });
|
|
127
|
+
chat.applyDelta({ type: 'message-end', messageId: reasoningMessage.id });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// 2) todo write
|
|
131
|
+
const todoId = `sim-${runId}-todos`;
|
|
132
|
+
at(reasoningDoneAt + 300, () => {
|
|
133
|
+
chat.status = { type: 'busy', message: 'Updating task list…' };
|
|
134
|
+
pushMessage({
|
|
135
|
+
id: todoId, role: 'tool', text: '', createdAt: Date.now(),
|
|
136
|
+
toolCall: {
|
|
137
|
+
id: `${todoId}-call`, name: 'todowrite', status: 'running',
|
|
138
|
+
input: { todos: [
|
|
139
|
+
{ content: 'Pin sitemap cache key to route manifest hash', status: 'in_progress' },
|
|
140
|
+
{ content: 'Run the sitemap test suite', status: 'pending' },
|
|
141
|
+
{ content: 'Apply the cache-key patch', status: 'pending' },
|
|
142
|
+
{ content: 'Verify staging timings', status: 'pending' },
|
|
143
|
+
] },
|
|
144
|
+
startedAt: Date.now(),
|
|
107
145
|
},
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
at(reasoningDoneAt + 1000, () => updateTool(todoId, { id: `${todoId}-call`, status: 'completed', finishedAt: Date.now() }));
|
|
149
|
+
|
|
150
|
+
// 3) test run
|
|
151
|
+
const testId = `sim-${runId}-test`;
|
|
152
|
+
at(reasoningDoneAt + 1400, () => {
|
|
153
|
+
chat.status = { type: 'busy', message: 'Running tests…' };
|
|
154
|
+
pushMessage({
|
|
155
|
+
id: testId, role: 'tool', text: '', createdAt: Date.now(),
|
|
156
|
+
toolCall: {
|
|
157
|
+
id: `${testId}-call`, name: 'run_command', status: 'running',
|
|
158
|
+
input: { command: 'pnpm test --filter sitemap', cwd: '/srv/app' },
|
|
159
|
+
startedAt: Date.now(),
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
at(reasoningDoneAt + 3200, () => updateTool(testId, {
|
|
164
|
+
id: `${testId}-call`, status: 'completed',
|
|
165
|
+
output: { exitCode: 0, stdout: '14 tests passed\n0 failed' },
|
|
166
|
+
finishedAt: Date.now(),
|
|
167
|
+
}));
|
|
168
|
+
|
|
169
|
+
// 4) file edit rendered as a diff
|
|
170
|
+
const editId = `sim-${runId}-edit`;
|
|
171
|
+
at(reasoningDoneAt + 3600, () => {
|
|
172
|
+
chat.status = { type: 'busy', message: 'Applying the patch…' };
|
|
173
|
+
pushMessage({
|
|
174
|
+
id: editId, role: 'tool', text: '', createdAt: Date.now(),
|
|
175
|
+
toolCall: {
|
|
176
|
+
id: `${editId}-call`, name: 'write_file', status: 'running',
|
|
177
|
+
input: {
|
|
178
|
+
path: '/srv/app/src/jobs/sitemap.job.ts',
|
|
179
|
+
oldContent: 'const cacheKey = hash(locales);\nconst ttl = 3600;',
|
|
180
|
+
content: 'const cacheKey = hash(routeManifest);\nconst ttl = 3600;',
|
|
181
|
+
},
|
|
182
|
+
startedAt: Date.now(),
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
at(reasoningDoneAt + 4600, () => updateTool(editId, { id: `${editId}-call`, status: 'completed', finishedAt: Date.now() }));
|
|
187
|
+
|
|
188
|
+
// 5) updated todos, then the streamed summary
|
|
189
|
+
const todoDoneId = `sim-${runId}-todos-2`;
|
|
190
|
+
at(reasoningDoneAt + 5000, () => {
|
|
191
|
+
pushMessage({
|
|
192
|
+
id: todoDoneId, role: 'tool', text: '', createdAt: Date.now(),
|
|
193
|
+
toolCall: {
|
|
194
|
+
id: `${todoDoneId}-call`, name: 'todowrite', status: 'completed',
|
|
195
|
+
input: { todos: [
|
|
196
|
+
{ content: 'Pin sitemap cache key to route manifest hash', status: 'completed' },
|
|
197
|
+
{ content: 'Run the sitemap test suite', status: 'completed' },
|
|
198
|
+
{ content: 'Apply the cache-key patch', status: 'completed' },
|
|
199
|
+
{ content: 'Verify staging timings', status: 'in_progress' },
|
|
200
|
+
] },
|
|
201
|
+
startedAt: Date.now(), finishedAt: Date.now(),
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
const summaryId = `sim-${runId}-summary`;
|
|
206
|
+
at(reasoningDoneAt + 5400, () => {
|
|
207
|
+
chat.status = { type: 'busy', message: 'Responding…' };
|
|
208
|
+
pushMessage({ id: summaryId, role: 'assistant', text: '', createdAt: Date.now(), streaming: true });
|
|
209
|
+
const summary = 'Patched `sitemap.job.ts` to key the cache on the **route manifest hash** — tests pass (14/14). Staging verification is the last open task.';
|
|
210
|
+
let offset = 0;
|
|
211
|
+
const interval = setInterval(() => {
|
|
212
|
+
const chunk = summary.slice(offset, offset + 6);
|
|
213
|
+
offset += 6;
|
|
214
|
+
if (!chunk) {
|
|
215
|
+
clearInterval(interval);
|
|
216
|
+
chat.applyDelta({ type: 'message-end', messageId: summaryId, usage: { inputTokens: 2100, outputTokens: 260, totalTokens: 2360 } });
|
|
217
|
+
chat.status = { type: 'idle' };
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
chat.applyDelta({ type: 'text', messageId: summaryId, delta: chunk });
|
|
221
|
+
}, 40);
|
|
222
|
+
cancels.push(() => clearInterval(interval));
|
|
223
|
+
});
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const askQuestion = (event: MouseEvent) => {
|
|
227
|
+
const chat = chatOf(event);
|
|
228
|
+
if (!chat) return;
|
|
229
|
+
chat.questions = [
|
|
230
|
+
...chat.questions,
|
|
231
|
+
{
|
|
232
|
+
id: `question-${Date.now()}`,
|
|
233
|
+
question: 'Which cache-key strategy should I roll out?',
|
|
234
|
+
createdAt: Date.now(),
|
|
235
|
+
options: [
|
|
236
|
+
{ label: 'Route manifest hash', description: 'Deterministic, invalidates on route changes' },
|
|
237
|
+
{ label: 'Content hash', description: 'Fine-grained, more churn' },
|
|
238
|
+
],
|
|
108
239
|
},
|
|
109
|
-
|
|
110
|
-
);
|
|
240
|
+
];
|
|
111
241
|
};
|
|
112
242
|
|
|
113
243
|
const askPermission = (event: MouseEvent) => {
|
|
@@ -161,9 +291,23 @@ export const demoFunc = () => html`
|
|
|
161
291
|
console.log('harness-permission-response', event.detail);
|
|
162
292
|
const chat = (event.target as HTMLElement).closest('.chatDemo')?.querySelector('dees-harness-chat');
|
|
163
293
|
if (chat) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
294
|
+
// answered cards stay in the transcript showing the outcome
|
|
295
|
+
chat.permissions = chat.permissions.map((request) =>
|
|
296
|
+
request.id === event.detail.requestId
|
|
297
|
+
? { ...request, response: event.detail.response, respondedAt: Date.now() }
|
|
298
|
+
: request,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}}
|
|
302
|
+
@harness-question-response=${(event: CustomEvent) => {
|
|
303
|
+
console.log('harness-question-response', event.detail);
|
|
304
|
+
const chat = (event.target as HTMLElement).closest('.chatDemo')?.querySelector('dees-harness-chat');
|
|
305
|
+
if (chat) {
|
|
306
|
+
chat.questions = chat.questions.map((request) =>
|
|
307
|
+
request.id === event.detail.requestId
|
|
308
|
+
? { ...request, response: event.detail.answers, respondedAt: Date.now() }
|
|
309
|
+
: request,
|
|
310
|
+
);
|
|
167
311
|
}
|
|
168
312
|
}}
|
|
169
313
|
@harness-new-session=${() => console.log('harness-new-session')}
|
|
@@ -176,6 +320,7 @@ export const demoFunc = () => html`
|
|
|
176
320
|
<div class="controls">
|
|
177
321
|
<dees-button type="accent" @clicked=${simulate}>Simulate streaming</dees-button>
|
|
178
322
|
<dees-button @clicked=${askPermission}>Ask permission</dees-button>
|
|
323
|
+
<dees-button @clicked=${askQuestion}>Ask a question</dees-button>
|
|
179
324
|
<dees-button @clicked=${toggleError}>Toggle error status</dees-button>
|
|
180
325
|
<dees-button type="ghost" @clicked=${resetChat}>Reset</dees-button>
|
|
181
326
|
</div>
|
|
@@ -184,6 +329,8 @@ export const demoFunc = () => html`
|
|
|
184
329
|
subheading="acme-app · main · connected"
|
|
185
330
|
model="gpt-5.5"
|
|
186
331
|
reasoningEffort="high"
|
|
332
|
+
mode="Building"
|
|
333
|
+
.modeOptions=${['Planning', 'Building']}
|
|
187
334
|
.modelOptions=${['gpt-5.5', 'gpt-5', 'o4-mini']}
|
|
188
335
|
.effortOptions=${['xhigh', 'high', 'medium', 'low']}
|
|
189
336
|
.usage=${{ inputTokens: 18_400, outputTokens: 3_100, totalTokens: 21_500 }}
|
|
@@ -13,15 +13,17 @@ import type {
|
|
|
13
13
|
IHarnessAttachment,
|
|
14
14
|
IHarnessMessage,
|
|
15
15
|
IHarnessPermissionRequest,
|
|
16
|
+
IHarnessQuestionRequest,
|
|
16
17
|
IHarnessStatus,
|
|
18
|
+
IHarnessTodoItem,
|
|
17
19
|
IHarnessUsage,
|
|
18
20
|
THarnessStreamDelta,
|
|
19
21
|
} from '../interfaces.js';
|
|
22
|
+
import { harnessParseTodos } from '../harness.helpers.js';
|
|
20
23
|
import { DeesHarnessToolRegistry } from '../toolregistry.js';
|
|
21
24
|
import { DeesHarnessMessageList } from '../dees-harness-message-list/dees-harness-message-list.js';
|
|
22
25
|
import { DeesHarnessComposer } from '../dees-harness-composer/dees-harness-composer.js';
|
|
23
|
-
import '../dees-harness-
|
|
24
|
-
import '../dees-harness-status/dees-harness-status.js';
|
|
26
|
+
import '../dees-harness-todos/dees-harness-todos.js';
|
|
25
27
|
import '../dees-harness-usage/dees-harness-usage.js';
|
|
26
28
|
import '../../00group-button/dees-button/dees-button.js';
|
|
27
29
|
import '../../00group-utility/dees-icon/dees-icon.js';
|
|
@@ -32,9 +34,6 @@ declare global {
|
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
// stable idle binding for the embedded list — a fresh object literal per
|
|
36
|
-
// render would re-trigger its follow logic on every chat render
|
|
37
|
-
const idleStatus: IHarnessStatus = { type: 'idle' };
|
|
38
37
|
|
|
39
38
|
/**
|
|
40
39
|
* The assembled agent chat: optional toolbar, streaming message list,
|
|
@@ -55,12 +54,27 @@ export class DeesHarnessChat extends DeesElement {
|
|
|
55
54
|
@property({ attribute: false })
|
|
56
55
|
accessor permissions: IHarnessPermissionRequest[] = [];
|
|
57
56
|
|
|
57
|
+
/** agent questions render inline like permissions; answered ones persist */
|
|
58
|
+
@property({ attribute: false })
|
|
59
|
+
accessor questions: IHarnessQuestionRequest[] = [];
|
|
60
|
+
|
|
58
61
|
@property({ attribute: false })
|
|
59
62
|
accessor status: IHarnessStatus = { type: 'idle' };
|
|
60
63
|
|
|
61
64
|
@property({ attribute: false })
|
|
62
65
|
accessor usage: IHarnessUsage = {};
|
|
63
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Current task list for the side panel. When empty, the panel derives it
|
|
69
|
+
* from the newest todo tool call in `messages` (registry kind 'todo'), so
|
|
70
|
+
* hosts that stream todowrite calls get the sidebar for free.
|
|
71
|
+
*/
|
|
72
|
+
@property({ attribute: false })
|
|
73
|
+
accessor todos: IHarnessTodoItem[] = [];
|
|
74
|
+
|
|
75
|
+
@property({ type: Boolean })
|
|
76
|
+
accessor showTodosPanel: boolean = true;
|
|
77
|
+
|
|
64
78
|
@property()
|
|
65
79
|
accessor heading: string = '';
|
|
66
80
|
|
|
@@ -101,6 +115,13 @@ export class DeesHarnessChat extends DeesElement {
|
|
|
101
115
|
@property({ attribute: false })
|
|
102
116
|
accessor effortOptions: string[] = [];
|
|
103
117
|
|
|
118
|
+
/** agent mode switcher (e.g. Planning / Building); empty hides it */
|
|
119
|
+
@property()
|
|
120
|
+
accessor mode: string = '';
|
|
121
|
+
|
|
122
|
+
@property({ attribute: false })
|
|
123
|
+
accessor modeOptions: string[] = [];
|
|
124
|
+
|
|
104
125
|
@property({ attribute: false })
|
|
105
126
|
accessor toolRegistry: DeesHarnessToolRegistry = DeesHarnessToolRegistry.default;
|
|
106
127
|
|
|
@@ -116,6 +137,32 @@ export class DeesHarnessChat extends DeesElement {
|
|
|
116
137
|
font-family: var(--dees-font-family);
|
|
117
138
|
color: var(--dees-color-text-primary);
|
|
118
139
|
background: var(--dees-color-bg-primary);
|
|
140
|
+
container-type: inline-size;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.chatBody {
|
|
144
|
+
flex: 1;
|
|
145
|
+
min-height: 0;
|
|
146
|
+
display: flex;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.todosPanel {
|
|
150
|
+
flex: 0 0 232px;
|
|
151
|
+
min-width: 0;
|
|
152
|
+
overflow-y: auto;
|
|
153
|
+
scrollbar-width: thin;
|
|
154
|
+
scrollbar-color: var(--dees-color-scrollbar-thumb) transparent;
|
|
155
|
+
padding: var(--dees-spacing-sm) var(--dees-spacing-md);
|
|
156
|
+
border-left: 1px solid var(--dees-color-border-subtle);
|
|
157
|
+
background: var(--dees-color-bg-secondary);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* narrow hosts (e.g. a sidebar chat) drop the panel; todo tool cards
|
|
161
|
+
in the transcript still carry the same information */
|
|
162
|
+
@container (max-width: 520px) {
|
|
163
|
+
.todosPanel {
|
|
164
|
+
display: none;
|
|
165
|
+
}
|
|
119
166
|
}
|
|
120
167
|
|
|
121
168
|
.toolbar {
|
|
@@ -126,6 +173,10 @@ export class DeesHarnessChat extends DeesElement {
|
|
|
126
173
|
padding: var(--dees-spacing-sm) var(--dees-spacing-md);
|
|
127
174
|
border-bottom: 1px solid var(--dees-color-border-subtle);
|
|
128
175
|
background: var(--dees-color-bg-secondary);
|
|
176
|
+
/* chrome floats above the scrolling transcript */
|
|
177
|
+
position: relative;
|
|
178
|
+
z-index: 1;
|
|
179
|
+
box-shadow: var(--dees-shadow-sm);
|
|
129
180
|
}
|
|
130
181
|
|
|
131
182
|
.headingBlock {
|
|
@@ -158,37 +209,18 @@ export class DeesHarnessChat extends DeesElement {
|
|
|
158
209
|
flex: 0 0 auto;
|
|
159
210
|
}
|
|
160
211
|
|
|
161
|
-
dees-harness-message-list {
|
|
212
|
+
.chatBody dees-harness-message-list {
|
|
162
213
|
flex: 1;
|
|
163
214
|
min-height: 0;
|
|
164
215
|
}
|
|
165
216
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
padding: 0 var(--dees-spacing-md);
|
|
170
|
-
max-height: 40%;
|
|
171
|
-
overflow-y: auto;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
.permissionQueue > * + * {
|
|
175
|
-
margin-top: var(--dees-spacing-xs);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/* always mounted so the status leave transition can play; the status
|
|
179
|
-
element renders empty when idle, collapsing the row to zero height */
|
|
180
|
-
.statusRow {
|
|
181
|
-
flex: 0 0 auto;
|
|
182
|
-
padding: 0 var(--dees-spacing-md);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
.statusRow dees-harness-status {
|
|
186
|
-
margin: 0;
|
|
187
|
-
}
|
|
188
|
-
|
|
217
|
+
/* the composer docks edge-to-edge like the toolbar, so the tasks
|
|
218
|
+
panel's border meets its top border cleanly; z-order keeps its
|
|
219
|
+
upward shadow above the transcript */
|
|
189
220
|
.composerRow {
|
|
190
221
|
flex: 0 0 auto;
|
|
191
|
-
|
|
222
|
+
position: relative;
|
|
223
|
+
z-index: 1;
|
|
192
224
|
}
|
|
193
225
|
`,
|
|
194
226
|
];
|
|
@@ -214,6 +246,19 @@ export class DeesHarnessChat extends DeesElement {
|
|
|
214
246
|
this.listElement?.scrollToBottom(force);
|
|
215
247
|
}
|
|
216
248
|
|
|
249
|
+
public get effectiveTodos(): IHarnessTodoItem[] {
|
|
250
|
+
if (this.todos.length) return this.todos;
|
|
251
|
+
// the newest todo call is the current state — an empty write clears
|
|
252
|
+
// the panel instead of falling back to an older list
|
|
253
|
+
for (let index = this.messages.length - 1; index >= 0; index--) {
|
|
254
|
+
const call = this.messages[index]?.toolCall;
|
|
255
|
+
if (!call) continue;
|
|
256
|
+
if (this.toolRegistry.resolve(call).kind !== 'todo') continue;
|
|
257
|
+
return harnessParseTodos(call.input);
|
|
258
|
+
}
|
|
259
|
+
return [];
|
|
260
|
+
}
|
|
261
|
+
|
|
217
262
|
private get listElement(): DeesHarnessMessageList | undefined {
|
|
218
263
|
const element = this.shadowRoot?.querySelector('dees-harness-message-list');
|
|
219
264
|
return element instanceof DeesHarnessMessageList ? element : undefined;
|
|
@@ -229,28 +274,27 @@ export class DeesHarnessChat extends DeesElement {
|
|
|
229
274
|
public render(): TemplateResult {
|
|
230
275
|
return html`
|
|
231
276
|
${this.showToolbar ? this.renderToolbar() : ''}
|
|
232
|
-
<
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
<div class="statusRow">
|
|
250
|
-
<dees-harness-status .status=${this.status}></dees-harness-status>
|
|
277
|
+
<div class="chatBody">
|
|
278
|
+
<dees-harness-message-list
|
|
279
|
+
.messages=${this.messages}
|
|
280
|
+
.permissions=${this.permissions}
|
|
281
|
+
.permissionsBusy=${this.disabled}
|
|
282
|
+
.questions=${this.questions}
|
|
283
|
+
.status=${this.status}
|
|
284
|
+
.showRoles=${this.showRoles}
|
|
285
|
+
.toolRegistry=${this.toolRegistry}
|
|
286
|
+
></dees-harness-message-list>
|
|
287
|
+
${this.showTodosPanel && this.effectiveTodos.length
|
|
288
|
+
? html`
|
|
289
|
+
<aside class="todosPanel">
|
|
290
|
+
<dees-harness-todos heading="Tasks" .todos=${this.effectiveTodos}></dees-harness-todos>
|
|
291
|
+
</aside>
|
|
292
|
+
`
|
|
293
|
+
: ''}
|
|
251
294
|
</div>
|
|
252
295
|
<div class="composerRow">
|
|
253
296
|
<dees-harness-composer
|
|
297
|
+
docked
|
|
254
298
|
.value=${this.composerValue}
|
|
255
299
|
.attachments=${this.attachments}
|
|
256
300
|
.disabled=${this.disabled}
|
|
@@ -260,6 +304,8 @@ export class DeesHarnessChat extends DeesElement {
|
|
|
260
304
|
.modelOptions=${this.modelOptions}
|
|
261
305
|
.reasoningEffort=${this.reasoningEffort}
|
|
262
306
|
.effortOptions=${this.effortOptions}
|
|
307
|
+
.mode=${this.mode}
|
|
308
|
+
.modeOptions=${this.modeOptions}
|
|
263
309
|
@harness-input=${(event: CustomEvent<{ value: string }>) => {
|
|
264
310
|
this.composerValue = event.detail?.value ?? '';
|
|
265
311
|
}}
|
|
@@ -270,6 +316,9 @@ export class DeesHarnessChat extends DeesElement {
|
|
|
270
316
|
if (event.detail?.model) this.model = event.detail.model;
|
|
271
317
|
if (event.detail?.reasoningEffort) this.reasoningEffort = event.detail.reasoningEffort;
|
|
272
318
|
}}
|
|
319
|
+
@harness-mode-change=${(event: CustomEvent<{ mode?: string }>) => {
|
|
320
|
+
if (event.detail?.mode) this.mode = event.detail.mode;
|
|
321
|
+
}}
|
|
273
322
|
></dees-harness-composer>
|
|
274
323
|
</div>
|
|
275
324
|
`;
|
|
@@ -30,11 +30,14 @@ export const demoFunc = () => html`
|
|
|
30
30
|
@harness-attachments-change=${logEvent}
|
|
31
31
|
@harness-attachment-error=${logEvent}
|
|
32
32
|
@harness-model-change=${logEvent}
|
|
33
|
+
@harness-mode-change=${logEvent}
|
|
33
34
|
>
|
|
34
|
-
<div class="label">default with model controls (events log to console)</div>
|
|
35
|
+
<div class="label">default with mode switcher + model controls (events log to console)</div>
|
|
35
36
|
<dees-harness-composer
|
|
36
37
|
model="gpt-5.5"
|
|
37
38
|
reasoningEffort="high"
|
|
39
|
+
mode="Planning"
|
|
40
|
+
.modeOptions=${['Planning', 'Building']}
|
|
38
41
|
.modelOptions=${['gpt-5.5', 'gpt-5', 'o4-mini']}
|
|
39
42
|
.effortOptions=${['xhigh', 'high', 'medium', 'low']}
|
|
40
43
|
></dees-harness-composer>
|